Monthly Archives: 6月 2014

推荐两款值得买的 Android 应用

BetterBatteryStats 和 XPrivacy,一个对电池,一个对隐私,都是瞄着别的应用的应用,总体感觉比较值得买。其实我买过很多移动应用 iOS 和 Android 平台都有,算起来可能有两千块钱了,总的讲,Android 平台真正值得买的好用的应用并不是很多,而且有不少买了之后就不再装了…… 不过这两款我还是要推荐一下。

BetterBatteryStats

Play Store 链接:http://ift.tt/YSpqgR ,这个应用可以统计手机睡眠相关的情况,帮你发现省点的方法。

首先说我的一个观点——手机是用来拿在手里玩的,不是用来省电的,如果你什么应用都不用,什么功能都不开,屏幕亮度还调到最低,那么,为啥花四五千块钱买个手机呢,去买个我老婆他们公司(最近改叫微软移动的那家)生产的两百块钱的手机好了。不过,手机虽然是用来玩的,可也只是有电的时候才能玩,所以省电也是需要的,BBS这款应用,可以帮你确定什么应用阻碍了手机进入睡眠,确定是ROM的问题还是应用的问题,进一步解决。

比如说这个——

可以看到,一半的 PartialWakeLock 都来自淘宝无线,相比之下,微信就很少影响到手机的睡眠了,而且我得说,我在这段时间里,动不动就看看微信,可是从来没打开过淘宝,所以,这个结果就是——我卸掉了淘宝,之前还卸掉了奇艺。

当然,这些问题只是当时有,不一定现在的版本还有,本着授之以渔的精神,我把应用介绍给大家,大家自己看吧。

XPrivacy Pro

XPrivacy 是 Xposed 框架下的应用,不在 play store 里买的,网站在这里 http://www.xprivacy.eu/ ,这个程序是开源的,可以在 GitHub 上得到源代码,付费也就是捐款,捐款得到 license 放到主存里就可以了。与之类似,CM 的 PrivacyGuard 也是用来限制应用权限的,不过 XPrivacy Pro 感觉功能更丰富一些,包括可以导出导入、可以看详细的权限使用记录,有模板,支持各个API的管理,非常丰富。

隐私这东西,看不见摸不着,而且保护隐私和便利性常常是冲突的,比如,有好几个应用可以拦截短信读取验证码,省去了看-背-输 这个过程,这本来是好好的,但我偏偏不喜欢。因为我不想授权给应用来读我的短信(虽然我的短信差不多全是机器自动发来的)。当然,这个见仁见智了,说得通,只是看你信不信、乐意不乐意了,但我们希望把决定权握在自己手里,而不只是装或不装。

Android 相比于 iOS 系统对每个应用的每个权限的授权功能是非常不足的,XPrivacy 就在这方面可以为用户提供更好的保护。我的用法是——对于要用的应用,开放必须开放的和我认为可以开放的权限,对于可用可不用的应用,如果有莫名其妙的权限要求,那就卸掉。这里也有个例子,就是今天在微博上吵的——

支付宝(和其他阿里的无线应用,诸如淘宝、虾米)就这样,即使你没打开它,它也在后台读你的剪贴板。其实读设备ID的请求,差不多所有应用都有,这个用于应用统计也不违反政策,如果咱不喜欢,关掉就是了,可是读剪贴板是为什么呢?Pocket 会在打开的时候读,刚好可以帮你收藏拷进来的链接,这个可以理解,但在后台运行的支付宝和虾米服务就比较难理解了,完全不知所云。

额外的废话

作为混迹技术圈这么多年的人,我和阿里很多人都是关系远近不同的朋友,其中很多都是比我强很多的大牛,我和阿里,特别是淘宝、支付宝没有任何恩怨,我贴他们是因为我用着他们,没装的应用谁会注意到呢。

有人说他们可能是在做好事,在帮助我或其他小白,嗯,可能是,我没看代码不知道你们在做什么,但一者,建议你们公开解释下这是在干什么,最好在 play store 的应用介绍里直接说出来为什么用这个权限,很多应用都这么做了;二者,希望你们给用户选择的权利,不是所有用户都喜欢你们这样的行为的,毕竟我们知道国内偷取用户隐私的下三滥应用很多,希望你们作为大公司出来的应用,能多点节操。

有人说爱用不用,XXX还也读剪贴板呢,对,就是这个道理,在便利和其他方面权衡是我自己做的,但我觉得你们这方面做得不好,拿出来吐槽有什么不行呢,反正在我用的应用里,在后台乱读剪贴板的就你阿里一家。

最后,我想很贱地问一句,你们阿里无线996工作了这么长时间,Android应用就做成这样(包括电池那段和剪贴板这段),你们觉得你们对得起你们加班的时间么?

from 我有分寸: http://ift.tt/1j1bcDM

Spray 中协议处理 Pipelines 的实现

最近在玩 scala,用到了 spray 来处理 Http,看了一下代码觉得很神奇,这里抄一段 spray 1.3 中的协议处理 pipeline 的实现,原始文件在 spray 中的 spray-io/src/main/scala/spray/io/Pipelines.scala

在 spray-io 中,网络协议可以拆分成多级流水线来处理,从网络到应用逐级升高,在前一级中处理低级事务,屏蔽掉一些底层机制,把高层决断交给后一级处理;而从应用到网络逐级降低,在前一级接受处理高层命令,分解成低级命令,交给后一级执行。spray-io框架规定了每层的接口,并帮助实现了拼接,而不同协议就负责实现自己的每级流水线。

比如 Spray-can 的 Http 协议栈处理中,就通过很多级的拼接来实现了一个协议栈:

    ServerFrontend(settings) >>
      RequestChunkAggregation(requestChunkAggregationLimit) ? (requestChunkAggregationLimit > 0) >>
      PipeliningLimiter(pipeliningLimit) ? (pipeliningLimit > 0) >>
      StatsSupport(statsHolder.get) ? statsSupport >>
      RemoteAddressHeaderSupport ? remoteAddressHeader >>
      SSLSessionInfoSupport ? parserSettings.sslSessionInfoHeader >>
      RequestParsing(settings) >>
      ResponseRendering(settings) >>
      ConnectionTimeouts(idleTimeout) ? (reapingCycle.isFinite && idleTimeout.isFinite) >>
      PreventHalfClosedConnections(sslEncryption) >>
      SslTlsSupport(maxEncryptionChunkSize, parserSettings.sslSessionInfoHeader) ? sslEncryption >>
      TickGenerator(reapingCycle) ? (reapingCycle.isFinite && (idleTimeout.isFinite || requestTimeout.isFinite)) >>
      BackPressureHandling(backpressureSettings.get.noAckRate, backpressureSettings.get.readingLowWatermark) ? autoBackPressureEnabled

这其中每一行都是一级流水线,>> 就是流水线拼接的方法,它们实现了这样一对流水线:

  /**
   * The HttpServerConnection pipeline setup:
   *
   * |------------------------------------------------------------------------------------------
   * | ServerFrontend: converts HttpMessagePart, Closed and SendCompleted events to
   * |                 MessageHandlerDispatch.DispatchCommand,
   * |                 generates HttpResponsePartRenderingContext
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | HttpMessagePart                     | HttpResponsePartRenderingContext
   *    | IOServer.Closed                     | IOServer.Tell
   *    | IOServer.SentOK                     |
   *    | TickGenerator.Tick                  |
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | RequestChunkAggregation: listens to HttpMessagePart events, generates HttpRequest events
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | HttpMessagePart                     | HttpResponsePartRenderingContext
   *    | IOServer.Closed                     | IOServer.Tell
   *    | IOServer.SentOK                     |
   *    | TickGenerator.Tick                  |
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | PipeliningLimiter: throttles incoming requests according to the PipeliningLimit, listens
   * |                    to HttpResponsePartRenderingContext commands and HttpRequestPart events,
   * |                    generates StopReading and ResumeReading commands
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | HttpMessagePart                     | HttpResponsePartRenderingContext
   *    | IOServer.Closed                     | IOServer.Tell
   *    | IOServer.SentOK                     | IOServer.StopReading
   *    | TickGenerator.Tick                  | IOServer.ResumeReading
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | StatsSupport: listens to most commands and events to collect statistics
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | HttpMessagePart                     | HttpResponsePartRenderingContext
   *    | IOServer.Closed                     | IOServer.Tell
   *    | IOServer.SentOK                     | IOServer.StopReading
   *    | TickGenerator.Tick                  | IOServer.ResumeReading
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | RemoteAddressHeaderSupport: add `Remote-Address` headers to incoming requests
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | HttpMessagePart                     | HttpResponsePartRenderingContext
   *    | IOServer.Closed                     | IOServer.Tell
   *    | IOServer.SentOK                     | IOServer.StopReading
   *    | TickGenerator.Tick                  | IOServer.ResumeReading
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | SSLSessionInfoSupport: add `SSL-Session-Info` header to incoming requests
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | HttpMessagePart                     | HttpResponsePartRenderingContext
   *    | IOServer.Closed                     | IOServer.Tell
   *    | IOServer.SentOK                     | IOServer.StopReading
   *    | TickGenerator.Tick                  | IOServer.ResumeReading
   *    | SslTlsSupport.SSLSessionEstablished |
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | RequestParsing: converts Received events to HttpMessagePart,
   * |                 generates HttpResponsePartRenderingContext (in case of errors)
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | IOServer.Closed                     | HttpResponsePartRenderingContext
   *    | IOServer.SentOK                     | IOServer.Tell
   *    | IOServer.Received                   | IOServer.StopReading
   *    | TickGenerator.Tick                  | IOServer.ResumeReading
   *    | SslTlsSupport.SSLSessionEstablished |
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | ResponseRendering: converts HttpResponsePartRenderingContext
   * |                    to Send and Close commands
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | IOServer.Closed                     | IOServer.Send
   *    | IOServer.SentOK                     | IOServer.Close
   *    | IOServer.Received                   | IOServer.Tell
   *    | TickGenerator.Tick                  | IOServer.StopReading
   *    | SslTlsSupport.SSLSessionEstablished | IOServer.ResumeReading
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | ConnectionTimeouts: listens to Received events and Send commands and
   * |                     TickGenerator.Tick, generates Close commands
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | IOServer.Closed                     | IOServer.Send
   *    | IOServer.SentOK                     | IOServer.Close
   *    | IOServer.Received                   | IOServer.Tell
   *    | TickGenerator.Tick                  | IOServer.StopReading
   *    | SslTlsSupport.SSLSessionEstablished | IOServer.ResumeReading
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | SslTlsSupport: listens to event Send and Close commands and Received events,
   * |                provides transparent encryption/decryption in both directions
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | IOServer.Closed                     | IOServer.Send
   *    | IOServer.SentOK                     | IOServer.Close
   *    | IOServer.Received                   | IOServer.Tell
   *    | TickGenerator.Tick                  | IOServer.StopReading
   *    |                                     | IOServer.ResumeReading
   *    |                                    \/
   * |------------------------------------------------------------------------------------------
   * | TickGenerator: listens to Closed events,
   * |                dispatches TickGenerator.Tick events to the head of the event PL
   * |------------------------------------------------------------------------------------------
   *    /\                                    |
   *    | IOServer.Closed                     | IOServer.Send
   *    | IOServer.SentOK                     | IOServer.Close
   *    | IOServer.Received                   | IOServer.Tell
   *    | TickGenerator.Tick                  | IOServer.StopReading
   *    |                                     | IOServer.ResumeReading
   *    |                                    \/
   */

流水线本身的实现非常简单,>> 操作符只有17行代码,整个 trait 也只有 26 行

trait RawPipelineStage[-C <: PipelineContext] { left => 
  type CPL = Pipeline[Command] // alias for brevity 
  type EPL = Pipeline[Event] // alias for brevity 
 
  def apply(context: C, commandPL: CPL, eventPL: EPL): Pipelines 
 
  def >>[R <: C](right: RawPipelineStage[R]): RawPipelineStage[R] = 
    if (right eq EmptyPipelineStage) this 
    else new RawPipelineStage[R] { 
      def apply(ctx: R, cpl: CPL, epl: EPL) = { 
        var cplProxy: CPL = Pipeline.Uninitialized 
        var eplProxy: EPL = Pipeline.Uninitialized 
        val cplProxyPoint: CPL = cplProxy(_) 
        val eplProxyPoint: EPL = eplProxy(_) 
        val leftPL = left(ctx, cplProxyPoint, epl) 
        val rightPL = right(ctx, cpl, eplProxyPoint) 
        cplProxy = rightPL.commandPipeline 
        eplProxy = leftPL.eventPipeline 
        Pipelines( 
          commandPL = (if (leftPL.commandPipeline eq cplProxyPoint) rightPL else leftPL).commandPipeline, 
          eventPL = (if (rightPL.eventPipeline eq eplProxyPoint) leftPL else rightPL).eventPipeline) 
      } 
    } 
 
  def ?(condition: Boolean): RawPipelineStage[C] = macro RawPipelineStage.enabled[C] 
}

这里解释一下这个拼接的实现:

  • RawPipelineStage自己是 left(左级),被拼接的是right(右级),拼接的结果仍然是 RawPipeline,可以拼接下一级
  • RawPipelineStage 有 apply<span lang=”zh-CN” style=”font-family:

    SimSun”>方法,这个方法应该带有参数(Context,CPL, EPL),返回 Pipelines,Pipelines 就是两条流水线,Command Pipeline (CPL) 是下行的命令处理,Event Pipeline (EPL) 是上行的事件处理,每个实际都是一个 Command 或 Event 处理的方法(Command => Unit 或 Event => Unit)

  • 在拼接时,
    • 如果右侧是空的,那么拼接的结果就是自己
    • 如果右侧不是空的,那么拼接的结果是一个RawPipelineStage,大意是:
      • 构造一个新的RawPipelineapply方法,参数为 ctx, cpl, epl
      • 如果左级不改变 cmd pipeline 的话,就调用右级的 cpl,否则用左级的cpl,但把右级的 cpl 当参数传给左级;对 cmd 来说,右级是左级的下一级,左级处理过交给右级
      • 如果右级不改变 event pipeline 的话,就用左级的 epl,否则用右级的 epl,但把左级的 epl当作参数传给右级;对event来说,左级是右级的下一级,右级处理过交给左级
    • 以一个方向为例:
      • 首先初始化 cplProxy 是一个处理 Command 的 Pipeline
      • val cplProxyPoint 是调用 cplProxy 的调用方法
      • leftPL 调用了左级的 apply,生成一个 Pipelines,而在生成的过程中,CPL 参数传入的就是 cplProxyPoint,也就是会调用 cplProxy
      • 定 cpl 为 rightPL 的 command pipeline,也就是说,leftPL 的 cpl 可以使用 rightPL 的 cpl
      • 最后,如果 leftPL 的 cpl 就是 rightPL 的 cpl 的话,也就是说,左级对 command 处理没有任何附加操作,那么就直接使用右级的 command 处理,反之,就使用左级的处理,但是左级可以利用作为参数传进来的右级的command 处理
      • event 处理与此对称

最终调用 RawPipelineState apply 的方法是 spray-io ConnectionHandler.running 方法,送进来的是 baseCommandPipeline baseEventPipeline,分别作为两者的最后一级

  • baseCommandPipeline 送给最右级作为下一级
    • TcpWriteCommandTcpCloseCommand 送给connection
    • (Tcp.SuspendReading | Tcp.ResumeReading | Tcp.ResumeWriting)backpressure相关的送给connection
    • Pipeline.Tell(receiver, msg, sender)消息,为sendermsg送给receiver
  • baseEventPipeline送给最左级作为下一级
    • 处理 ConnectionClosed 消息,等待tcpConnection Actor结束来自杀
    • 丢弃消息,对于 Droppable 的不warning,否则warning

就是这样,完成,非常简洁,有些晦涩。

from 我有分寸: http://ift.tt/1rI9hx3

为什么说航空博物馆不适合给孩子做科普

自从几个月前带儿子去了一次航空博物馆之后,一直想写blog吐槽一下,不过一直没有档期,终于,今天我抽空写两句吧,有些东西或图以后慢慢补。

之前带儿子去过环铁的铁道博物馆,展区虽然不是很大,展品也算不上特别多,而且以国产为主,但是孩子可以全方位接触机车,还是挺好玩的。可是,相比之下航空博物馆虽然展区很大,但是既没有什么知识介绍,又没有历史渊源,没有什么太多体验机会,甚至安全方面做得也很不好,一言以蔽之——别带孩子去。

介绍缺失 + 谬误

航空博物馆有露天展区和洞库展区,有庞大的歼-6机群,也有各种发动机等展品,可是几乎没有任何知识介绍,本来这都是很好的科普材料,帮孩子们认识活塞发动机和涡轮喷气发动机的构造,了解飞机的各种功能。可是,因为连个标牌都没有,就有很多家长在给孩子瞎介绍,比如——

  • 有的把副油箱当炸弹
  • 有的把空速管当大炮
  • 有的把进气道当大炮
  • ……

这些孩子们可能要带着这些错误的认识好多年了,所以,如果各位不知道我上面说的都是什么东西,那最好也别带你的孩子去航空博物馆了。

这些只是不作为,还有些更离谱的,洞库里有一段科幻小视频,说的是未来某年的先进制造和设计方法,嗯,幻想这东西可以跑点火车哈,可是,你设计个扑翼机还要飞入外太空是闹哪样啊,在真空里扇翅膀真的能飞动么?!

来历不明的展品

馆藏的丰富程度确实是一个博物馆的硬实力,但是,即使是不很丰富的博物馆,用心做还是可以让大家有很多收获的。可航空博物馆里,确实藏了一些外国飞机,但是,很多飞机都是无型号、无来源、无介绍的展品,不知道摆放在这里干什么

  • 有一架飞机没有标型号,据我观察可能是米格-23,我国空军似乎从来没有这个机型,不知来源
  • 有一家飞机没有标型号,据我观察是达索幻影的某型号,我国空军似乎从来没有这个机型,不知来源
  • 有一架飞机没有标型号,据我观察是F-104,飞机上写着意大利空军赠送
  • 有一架飞机没有标型号,据我观察是F-5,应该是从湾湾飞来的
  • 有一架直升机,应该是米-24,不知来源
  • ……

这么多飞机都摆进来了,写个牌子,说说从哪来的,是啥很难么,如果不能说,还摆出来干啥呢?

缺乏安全保护

某客机的舷梯可以走上去,我就让我儿子上去拍照了,他上去我才发现,舷梯靠飞机那边,有一侧是没有保护,如果孩子玩得太 high,是可能掉下来的,心有余悸。在此提醒各位,如果你看了我的介绍,还想带着孩子去,那一定要看好了,如果想上啥,看清楚结实不结实,有没有栏杆啥的。

小结

嗯,图先没上,有空发一些,基本上展品乏善可陈,介绍基本没有,很多展品来历不明,维护状况也不怎么好,想想那么大片展区,转一圈累得够呛,可是收获却没啥,真的不怎么值。唯一的优点是,露天展区是免费的,只有进洞库或者进馆才要钱。总评必然是负分。

顺便说,参观航空博物馆那天是3月8日,看着很多客机,然后忽然听说马航客机失联,心里真不是滋味,加重了我的负面情绪……唉,飞机残骸还没找到,太可怜了……

from 我有分寸: http://ift.tt/1mW6xqE