[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs     diff=csharp\n\n# Standard to msysgit\n*.doc\t diff=astextplain\n*.DOC\t diff=astextplain\n*.docx diff=astextplain\n*.DOCX diff=astextplain\n*.dot  diff=astextplain\n*.DOT  diff=astextplain\n*.pdf  diff=astextplain\n*.PDF\t diff=astextplain\n*.rtf\t diff=astextplain\n*.RTF\t diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "*.project\n*.classpath\nbin/\ngen/\n*.DS_Store\n*/bin/\n*/gen/\n.settings/\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Android开发技术前线 ( android-tech-frontier )\n\n> 一个定期翻译、发布国内外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目,让我们的技术跟上国际步伐。        \n> 我们翻译的文章在能够联系到作者的情况下都会在获得作者授权后进行翻译，并且公开发布。发布的文章中都会保留原文链接、作者名，如有相关的版权协议我们也会一并附上。目前已经联系到的作者列表请参考[授权文档](authorization.md);\n\n---------\n\n[Android App内存泄漏自动分析工具 - MMAT发布](https://github.com/hehonghui/mmat)\n\n\n## 可阅读文章列表\n\n* [请移步到wiki](https://github.com/bboyfeiyu/android-tech-frontier/wiki)\n\n\n## 其他学习资源\n* [Android 源码设计模式分析 地址](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis)\n* [Android优秀学习资料整理](the-bad-guys/)\n* [软件架构模式](https://raw.githubusercontent.com/bboyfeiyu/android-tech-frontier/master/software-architecture-patterns/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F.pdf)\n\n\n## 版权信息\n<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"><img alt=\"知识共享许可协议\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png\" /></a><br />该项目下的所有作品由<a xmlns:cc=\"http://creativecommons.org/ns#\" href=\"https://github.com/bboyfeiyu/android-tech-frontier\" property=\"cc:attributionName\" rel=\"cc:attributionURL\">Android开发技术前线</a>团队翻译，采用<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可。\n\n"
  },
  {
    "path": "android-blog/Android性能优化系列/readme.md",
    "content": "## Android 应用性能优化系列\n\n>原文链接分别为 :   \n>\n>* [https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE)\n* [https://www.udacity.com/course/ud825](https://www.udacity.com/course/ud825)\n* 译者 : [胡凯](http://hukai.me)\n\n\n1. [Android性能优化典范](http://hukai.me/android-performance-patterns/)\n2. [Android性能优化之渲染篇](http://hukai.me/android-performance-render/)\n3. [Android性能优化之运算篇](http://hukai.me/android-performance-compute/)\n4. [Android性能优化之内存篇](http://hukai.me/android-performance-memory/)\n5. [Android性能优化之电量篇](http://hukai.me/android-performance-battery/) "
  },
  {
    "path": "android-blog/Google+ 团队的 Android UI 测试/readme.md",
    "content": "Google+ 团队的 Android UI 测试\n---\n\n>\n* 原文链接：[How the Google+ Team Tests Mobile Apps](http://googletesting.blogspot.sg/2013/08/how-google-team-tests-mobile-apps.html)\n* 译者：[allenlsy](http://allelsy.com)\n* 译者博文地址：[http://allenlsy.com/android-ui-tests-in-google-plus-team/]()\n* 校对者:\n\nAndroid 测试主要分为3个类型：\n\n#### 单元测试（Unit Test)\n\n区分UI代码和功能代码在Android开发中尤其困难。因为有时Activity既有Controller的功能，又有View的功能。[Robolectric](http://pivotal.github.io/robolectric/)是一个很优秀的Android测试框架，它提供了一个Android框架的stub，这样测试运行时实际上是在JVM上运行，而不是在Android平台（比如Robotium和Instrumentation都是在Android平台运行测试），从而提高了速度。另外请参考[Gradle 对 Unit tests的支持](http://tools.android.com/tech-docs/unit-testing-support)。\n\n#### 封闭UI测试 （Hermetic UI Test)\n\n这个测试方法使得测试不需要外部依赖和网络请求。这样做的主要目的是提高测试速度，减少测试时的外部影响，毕竟网络调用是相对很慢的。[Espresso](http://www.youtube.com/watch?v=T7ugmCuNxDU)可以用来模拟用户的UI操作。\n\n#### Monkey Test\n\nMonkey Test 就好像一只猴子在测试app一样，没有任何规律的在你的app上胡按。计算机运行monkey test的时候，每秒钟能做出几千个UI动作（可以配置这个频率），比如点击和拖拽。所以这个测试可以算是一个压力测试，用来检测[ANR](http://developer.android.com/training/articles/perf-anr.html)。\n\n---\n\nGoogle+ 团队总结了一些 UI 测试时的经验和策略。\n\n#### 策略1： 不要使用 End-to-end 测试作为UI测试\n\n先看一些定义：__UI 测试__ 是为了确保对于用户的UI动作，app能返回正确的UI输出。__End-to-end测试（E2E test)__ 是通过客户端和后台服务器的交互测试整个系统。下面这个图在展示了测试步骤：\n\n![](http://img.my.csdn.net/uploads/201503/28/1427507159_1836.png)\n\n通常做UI测试，你需要后台服务器，所以可能产生网络调用。所以UI测试和E2E测试很像。但是在E2E测试中会遇到很多困难：\n\n* 测试速度缓慢\n* 网络请求会失败\n* 难以Debug\n\n下面看看如何解决这些问题。\n\n#### 策略2：使用伪服务器做封闭UI测试\n\n这个策略中，你可以通过假的后台服务器来避免网络请求，以及其他外部依赖。技术上，你就需要在app本地提供返回数据了。有很多办法可以做到，比如手动做一次网络请求，把response保存下来，在测试的时候重复这个response。这样你就做了一个封闭在本地的伪服务器\n\n当你有了这个伪服务器，你还需要给这个伪服务器写测试。于是这是，你的E2E测试就分为了服务器测试，客户端测试和集成测试。\n\n![](http://img.my.csdn.net/uploads/201503/28/1427507159_8354.png)\n\n现在这样的解决方案，你需要自己维护伪服务器，本地数据库和tests了。\n\n下面这是E2E 测试的示例图：\n\n![](http://img.my.csdn.net/uploads/201503/28/1427507160_4776.jpg)\n\n这是使用了伪服务器的封闭UI测试\n\n![](http://img.my.csdn.net/uploads/201503/28/1427507167_8779.jpg)\n\n其区别在于：Frontend Server的几个数据源变了。由原来的真实后端，变成了封闭服务器，或者是mock服务器。这个在测试调用网络API的时候非常有用。\n\n#### 策略3：使用Dependency Injection\n\nDependency Injection（依赖注入）可以帮助生成测试数据。我推荐选择使用[dagger](http://square.github.io/dagger/)作为依赖注入框架。\n\n依赖注入在UI test和unit test都中都可以用于生成假数据。在instrumentation test框架中，测试用的apk文件和测试时运行的app，是在同一个进程下面，所以测试代码可以调用app代码。你还可以覆盖app的classpath，通过这种方式注入假数据。比如你可以用依赖注入来伪造一个网络连接的实现，调用这个网络连接的时候就可以提供假数据。\n\n![](http://img.my.csdn.net/uploads/201503/28/1427507159_6700.png)\n\n\n### 策略4：把app分为小的libraries\n\n这个方法可以更好地模块化你的app。你的app被分为更小的类库之后，你可以为这些类库添加他们自己的UI依赖或gradle库依赖。\n\n当你有了自己的库，并提供依赖注入的支持，那么你可以为各个库写测试app。最后，可以写__集成测试__来确保类库直接的合作正确。\n\n比如我们有一个登陆功能的库，那么我可以写一个测试app只为这个登陆功能库：\n\n![](http://img.my.csdn.net/uploads/201503/28/1427507160_4803.png)\n\n#### 总结：\n\n1. 不要用E2E测试来代替UI测试。更好的做法是用单元测试 + 集成测试 + UI测试。\n2. 使用封闭测试策略\n3. 使用依赖注入\n4. 把app分为不同的小组件小类库，并分别写测试，然后再写集成测试来确保各组件之间的交互正确。\n5. 模块化 UI 测试已经被证明了比E2E测试快，并且十分稳定。这样的测试又能极大的提高开发效率。\n"
  },
  {
    "path": "android-blog/readme.md",
    "content": "## Android官方博客"
  },
  {
    "path": "androidweekly/Android中的人脸检测入门.md",
    "content": "Android中的人脸检测入门\n---\n\n> * 原文链接 : [An Introduction to Face Detection on Android](http://code.tutsplus.com/tutorials/an-introduction-to-face-detection-on-android--cms-25212?utm_source=Android+Weekly&utm_campaign=15ee59bb7a-Android_Weekly_181&utm_medium=email&utm_term=0_4eb677ad19-15ee59bb7a-337955857)\n* 原文作者 : [Paul Trebilcox-Ruiz](http://tutsplus.com/authors/paul-trebilcox-ruiz)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [LangleyChang](https://github.com/LangleyChang) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  已完成\n\n\n\n随着Play服务8.1中引入了视觉库，作为一个开发者，Face Detection让你可以更容易的通过分析视频或图像来定位人脸(face)。一旦有了一个图像中人脸的列表，你就能获取到每个人脸的相关信息，比如方向，笑脸的概率，某人是睁眼还是闭眼，还有他们脸上特定的关键点(landmark)。\n\n\n这些信息在很多应用中都有用，比如一个相机应用，它可以在每个人都在睁着眼笑的时刻拍一张照片，或者给图片加一些搞笑的效果，比如牛角什么的。有一点很重要，注意人脸检测(Face Detection)并不是人脸识别(facial recognition)。尽管都是在获取一张脸的信息，(Play服务中的)视觉库并不会用这些信息来分辨两张脸是不是来自同一个人。\n\n\n这个教程会使用一张静止的图片来跑Face Detection API，然后获取有关照片中人物的信息。同时，还会在其上覆盖图形来展示获取到的信息。这个教程的所有代码都可以在[GitHub](https://github.com/tutsplus/Android-PlayServices-FaceDetection)上找到。\n\n\n![](https://cms-assets.tutsplus.com/uploads/users/798/posts/25212/image/unicorn.png)\n\n搞笑效果的例子，在人脸上添加牛角\n\n## 1. 建立项目\n\n\n为了把视觉库添加到你的项目中，你需要把Play服务8.1或更高版本导入到你的项目。这个教程仅仅导入了Play服务视觉库(Play Servcies Vision library)。打开你项目的**build.gradle**文件，把如下的编译行添加到`dependencies`结点中\n```groovy\ncompile 'com.google.android.gms:play-services-vision:8.1.0'\n```\n\n一旦你在你的项目中包含了Play服务，你就可以关掉**build.gradle**文件，然后打开**AndroidManifest.xml**。你需要在`manifest`的`application`结点添加一个`meta-data`项来定义人脸检测的依赖。\n```xml\n<meta-data android:name=\"com.google.android.gms.vision.DEPENDENCIES\" android:value=\"face\"/>\n```\n\n完成了`AndroidManifest.xml`的建立，你就可以直接关掉它了。接着，你需要创建一个叫`FaceOverlayView.java`的新类。这个类继承了`View`，并且包含着项目中人脸检测的逻辑，其中包括显示分析的位图并且在图像上绘制用以示意的点。\n\n\n现在，在类的开头添加一个成员变量并且定义构造函数。`Bitmap`对象用来存储被分析的位图，`Face`对象中的`SparseArray`用来存储位图中发现的每张人脸。\n\n```Java\npublic class FaceOverlayView extends View {\n \n    private Bitmap mBitmap;\n    private SparseArray<Face> mFaces;\n \n    public FaceOverlayView(Context context) {\n        this(context, null);\n    }\n \n    public FaceOverlayView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n \n    public FaceOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n}\n```\n\t\n\n\n接着，在`FaceOverlayView`中添加一个新的方法叫`setBitmap(Bitmap bitmap)`。这个方法当前只是简单把传给他的位图存起来，一会儿你要用这个方法来分析图像。\n\n```Java\npublic void setBitmap( Bitmap bitmap ) {\n    mBitmap = bitmap;\n}\n```\n\n下一步，你需要一个位图。我已经在[GitHub](https://github.com/tutsplus/Android-PlayServices-FaceDetection)上的示例项目中包含了一张，但是你也可以拿你喜欢任何图片和Face Detection玩玩，看看哪些好使，哪些不好使。这个教程假设你的图片名为`face.jpg`。\n\n\n把你的图片放在`res/raw`目录下之后，打开`res/layout/activity_main.xml`。这个布局包含了一个`FaceOverlayView`引用，这样就可以把它显示在`MainActivity`中。\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.tutsplus.facedetection.FaceOverlayView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:id=\"@+id/face_overlay\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\" />\n```\n\n布局定义好了，现在打开`MainActivity`，在`onCreate()`中建立`FaceOverlayView`。怎么建立呢？你先获取一个这个view的引用，接着从raw目录的输入流中读取`face.jpg`文件，然后把它转换成一个位图。一旦你拿到了这个位图，你就可以调用`FaceOverlayView`的`setBitmap()`方法把这幅图像传给你自定义控件了。\n\n```Java\npublic class MainActivity extends AppCompatActivity {\n \n    private FaceOverlayView mFaceOverlayView;\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        mFaceOverlayView = (FaceOverlayView) findViewById( R.id.face_overlay );\n \n        InputStream stream = getResources().openRawResource( R.raw.face );\n        Bitmap bitmap = BitmapFactory.decodeStream(stream);\n \n        mFaceOverlayView.setBitmap(bitmap);\n \n    }\n}\n```\n\n## 2.检测人脸\n\n既然你的项目已经建立完成了，现在就该开始检测人脸了。在`setBitmap(Bitmap bitmap)`中你需要创建一个`FaceDetector`。可以通过用一个`FaceDetector.Builder`来创建。它允许你定义多个参数，这些参数影响着你多快能检测出人脸和`FaceDetector`还能生成其他什么数据。\n\n你选择什么设置取决于你想在应用中做什么。如果你开启了关键点(landmark)搜索，那么检测人脸的速度就会慢一些。这和程序设计的大多数时候一样，鱼和熊掌不可兼得。想了解更多关于`FaceDetector.Builder`中的可用选项，你可以查看Android开发网站的官方文档。\n\n```Java\nFaceDetector detector = new FaceDetector.Builder( getContext() )\n        .setTrackingEnabled(false)\n        .setLandmarkType(FaceDetector.ALL_LANDMARKS)\n        .setMode(FaceDetector.FAST_MODE)\n        .build();\n```\n\n同时，你也需要看看`FaceDetector`是否可用。当一个用户第一次在他的设备上使用人脸检测的时候，Play服务需要跳出来加载一系列小的原生库来处理你的请求。虽然大多数情况下这都能在你的应用结束前完成，你还是需要处理加载失败的意外情况。\n\n如果`FaceDetector`是可用的，那你就可以把你的位图转成一个`Frame`对象，然后把它传给检测器来获取图像中关于人脸的数据。完成了以后，你需要释放检测器，防止内存泄漏。当人脸检测完成了以后，你就可以调用`invalidate()`来触发这个控件的重绘了。\n\n```Java\nif (!detector.isOperational()) {\n    //Handle contingency\n} else {\n    Frame frame = new Frame.Builder().setBitmap(bitmap).build();\n    mFaces = detector.detect(frame);\n    detector.release();\n}\ninvalidate();\n```\n\n既然你已经从图像中检测到了人脸，现在就该用它们了。在这个例子中，你简单的在每个脸周围画一个绿框。因为`invalidate`已经在检测完人脸后调用了，所以你可以在`onDraw(Canvas canvas)`中添加必要的逻辑。这个方法得确保位图和人脸都已经设置好了，然后就开始在画布上绘制位图，再在每个脸的周围绘制一个框。\n\n因为不同的设备有不同的显示尺寸，你需要跟踪位图的缩放大小，让整个图片在不同的设备上都时刻可见，上面的覆盖物也要合适的绘制。\n\n```Java\n@Override\nprotected void onDraw(Canvas canvas) {\n    super.onDraw(canvas);\n \n    if ((mBitmap != null) && (mFaces != null)) {\n        double scale = drawBitmap(canvas);\n        drawFaceBox(canvas, scale);\n    }\n}\n```\n\n`drawBitmap(Canvas canvas)`将你的位图绘制在画布上，并且把它的大小调整合适。同时，它还会返回一个系数以正确调整其他尺寸的比例。\n\t\n```Java\nprivate double drawBitmap( Canvas canvas ) {\n    double viewWidth = canvas.getWidth();\n    double viewHeight = canvas.getHeight();\n    double imageWidth = mBitmap.getWidth();\n    double imageHeight = mBitmap.getHeight();\n    double scale = Math.min( viewWidth / imageWidth, viewHeight / imageHeight );\n \n    Rect destBounds = new Rect( 0, 0, (int) ( imageWidth * scale ), (int) ( imageHeight * scale ) );\n    canvas.drawBitmap( mBitmap, null, destBounds, null );\n    return scale;\n}\n\n```\n\n每一个检测出的人脸都有一个其左上角的位置信息。`drawFaceBox(Canvas canvas, double scale)`这个方法比较有趣，它会用利用这个位置按照每张脸的宽度和高度画一个绿色的矩形把它们都圈出来。\n\n你需要定义你的`Paint`对象，然后循环遍历`SparseArray`的每一个`Face`找到它的位置，宽度和高度，然后用这些信息在画布上画出矩形。\n\n\n```Java\nprivate void drawFaceBox(Canvas canvas, double scale) {\n    //paint should be defined as a member variable rather than \n    //being created on each onDraw request, but left here for \n    //emphasis.\n    Paint paint = new Paint();\n    paint.setColor(Color.GREEN);\n    paint.setStyle(Paint.Style.STROKE);\n    paint.setStrokeWidth(5);\n \n    float left = 0;\n    float top = 0;\n    float right = 0;\n    float bottom = 0;\n \n    for( int i = 0; i < mFaces.size(); i++ ) {\n        Face face = mFaces.valueAt(i);\n \n        left = (float) ( face.getPosition().x * scale );\n        top = (float) ( face.getPosition().y * scale );\n        right = (float) scale * ( face.getPosition().x + face.getWidth() );\n        bottom = (float) scale * ( face.getPosition().y + face.getHeight() );\n \n        canvas.drawRect( left, top, right, bottom, paint );\n    }\n}\n```\n\n现在，你的应用应该能跑了，并且你能看到图像中每一个检测出来的脸上都用矩形框了起来。注意，现在Face Detection API仍然相当稚嫩，所以它可能不会检测出每一张脸。你可以折腾折腾`FaceDetector`中的其他设置，看看能不能获得一些数据，尽管我也不能保证。\n\n![](https://cms-assets.tutsplus.com/uploads/users/798/posts/25212/image/facebox.png)\n\n人脸被检测了出来并用矩形框出\n## 3.理解关键点\n\n关键点是一个人脸中一些感兴趣的点。Face Detection API不用关键点来检测人脸，但是可以检测到脸的整体轮廓之后再寻找关键点。这就是为什么发现关键点在是一个可以用`FaceDetector.Builder`开启的可选选项。\n\n你可以把这些关键点作为一个额外的信息源，比如对象的眼睛在哪里，这样你就可以在你的应用中恰当的交互。可被找到的关键点共有十二个：\n\n* 左右眼\n* 左右耳\n* 左右耳廓尖\n* 鼻子基部\n* 左右脸颊\n* 嘴的左右角\n* 嘴基部\n\n哪些关键点是可用的取决于检测到的脸的角度。举个例子，某人的侧脸中只能检测到一个可见的眼睛，这意味着另一个眼睛是检测不到的。下面的表格概括了基于人脸的欧拉Y角度(左右方向)哪些关键点是可以被检测到的\n \n| 欧拉 Y 角度      | 可见的关键点          | \n| ------------- |:-------------:| \n| < -36°      | 左眼，嘴的左半边，左耳，鼻子基部，左脸颊 | \n| -36° to -12°      | 嘴的左半边，鼻子基部，嘴的底部，右眼，左眼，左脸颊，左耳尖    |  \n| -12° to 12° | 右眼，左眼，鼻子基部，左脸颊，右脸颊，嘴的左半边，嘴的右半边，嘴的底部 |  \n| 12° to 36° | 嘴的右半边，鼻子基部，嘴的底部，左眼，右眼，右脸颊，右耳尖|\n| > 36° | 右眼，嘴的右半边，右耳，鼻子基部，右脸颊 |\n\n\n不管你信不信，在你的应用中使用关键点也是非常的容易，因为你已经在人脸检测的时候把它们包含到了你的项目中了。你只需要调用一个`Face`对象的`getLandMarks()`就能获取到一个`LandMark`对象的`List`，这样你就能使用它了。\n\n\n你这个教程中，你将会在每个检测出的关键点上绘制一个小圆圈，只需调用一个新的方法`drawFaceLandmarks(Canvas canvas, double scale)`。这个方法是在`onDraw(Canvas canvas)`调用，而不是`drawFaceBox(Canvas canvas)`。它会获取每个关键点的位置，根据位图的比例调整它的大小，然后绘制出示意关键点的圆圈。\n\n```Java\nprivate void drawFaceLandmarks( Canvas canvas, double scale ) {\n    Paint paint = new Paint();\n    paint.setColor( Color.GREEN );\n    paint.setStyle( Paint.Style.STROKE );\n    paint.setStrokeWidth( 5 );\n \n    for( int i = 0; i < mFaces.size(); i++ ) {\n        Face face = mFaces.valueAt(i);\n \n        for ( Landmark landmark : face.getLandmarks() ) {\n            int cx = (int) ( landmark.getPosition().x * scale );\n            int cy = (int) ( landmark.getPosition().y * scale );\n            canvas.drawCircle( cx, cy, 10, paint );\n        }\n \n    }\n}\n```\n\n在调用了这个方法之后，你应该能看到许多绿色的小圆圈覆盖在检测到的人脸上，如下面的例子所示。\n![](https://cms-assets.tutsplus.com/uploads/users/798/posts/25212/image/landmarks.png)\n\n在检测到的关键点之后放置圆圈\n\n## 4.额外的人脸数据\n\n\n人脸的位置和关键点是很有用，但是你还可以通过一些`Face`对象的内置方法在检测到的人脸中发现更多的信息。`getIsSmilingProbability()`,`getIsLeftEyeOpenProbability()`和`getIsRightEyeOpenProbability()`方法会返回一个0.0到1.0的浮点数，你可以用其确定眼睛是否睁开或者检测到的这个人是否在笑。返回值越接近1.0，这个人就越有可能在小或者他的左右眼越有可能是睁开的。\n\n你也可以通过检查图像的欧拉值找出一张图像中人脸在Y轴和Z轴上的角度。Z欧拉值的一直都能收到。但是，要想接受到X值你必须使用精确模式。在下面的代码片段中你会看到如何获取这些值。\n\n```Java\nprivate void logFaceData() {\n    float smilingProbability;\n    float leftEyeOpenProbability;\n    float rightEyeOpenProbability;\n    float eulerY;\n    float eulerZ;\n    for( int i = 0; i < mFaces.size(); i++ ) {\n        Face face = mFaces.valueAt(i);\n \n        smilingProbability = face.getIsSmilingProbability();\n        leftEyeOpenProbability = face.getIsLeftEyeOpenProbability();\n        rightEyeOpenProbability = face.getIsRightEyeOpenProbability();\n        eulerY = face.getEulerY();\n        eulerZ = face.getEulerZ();\n \n        Log.e( \"Tuts+ Face Detection\", \"Smiling: \" + smilingProbability );\n        Log.e( \"Tuts+ Face Detection\", \"Left eye open: \" + leftEyeOpenProbability );\n        Log.e( \"Tuts+ Face Detection\", \"Right eye open: \" + rightEyeOpenProbability );\n        Log.e( \"Tuts+ Face Detection\", \"Euler Y: \" + eulerY );\n        Log.e( \"Tuts+ Face Detection\", \"Euler Z: \" + eulerZ );\n    }\n}\n```\n## 结论\n\n在这个教程中，你已经学到了Play服务视觉库的一个主要组件，**Face Detection**。你现在知道如何在一张静止的图像中检测人脸，如何获取其中的信息，然后从每张人脸中找到重要的关键点。\n\n用你学到的东西，你应该可以在你自己的应用中添加一些很棒的特性，比如添加静态图像、在视频中跟踪人脸或者你能想到的任何创意。\n"
  },
  {
    "path": "androidweekly/Android性能案例研究续集/readme.md",
    "content": "# Android性能案例研究续集\n---\n> * 原文链接 : [Android Performance Case Study Follow-up](http://www.curious-creature.com/2015/03/25/android-performance-case-study-follow-up/?utm_source=Android+Weekly&utm_campaign=0692ef161b-Android_Weekly_146&utm_medium=email&utm_term=0_4eb677ad19-0692ef161b-337914749)\n> * 译者 : [shenyansycn](https://github.com/shenyansycn)\n> * 校对 : [Mr.Simple](https://github.com/bboyfeiyu)\r\r两年前，我发表了名为Android Performance Case Study的文章来帮助Android开发者了解什么工具和技术能被应用到识别、追踪和解决性能问题上。\r\r这篇文章的示例程序叫Falcon Pro，是由Joaquim Vergès设计和开发的一个Twitter客户端。感谢Joaquim让我以Flacon Pro为例来处理我做演示。一切都很顺利，直到Joaquim开始开发Falcon Pro 3，在发布他新的应用之前不久，Joaquim因为需要解决一个影响滚动的性能问题他联系到了我（这一次我依然没有他的源码可以参考）。\r\rJoaquim使用了所有工具来检测问题所在，但是都发现与之前猜测的原因无关。比如，他发现并不是overdraw引发问题。于是他将范围缩小到ViewPager类，他发给我如图1-1：\r \r![image](http://androidperformance.com/images/android-performance-case-study-follow-up/falconpro3.png)     \n图1-1 \n\rJoaquim使用系统的GPU图形分析工具发现了帧速的下降。左边的截图展示了没有Viewpage的滚动性能时间轴，右边的展示了有Viewpager的时间轴（他使用2014年的Moto X获得这些数据）。这个问题根源看起来非常明显。\n\r我的第一反应是ViewPager是不是误用了硬件加速。我们观察到的这个性能问题可能在List滚动时每一帧硬件层都被更新了。系统的 hardware layers updates debugging tool 没有提供有价值的信息。我检查了两次HierarchyViewer， 但ViewPager表现令我感到满意。（相反，我认定它不太可能出现问题）\n\r我转而使用了另一个强大的、不常用的工具: Tracer for OpenGL。我前一篇文章解释了这个工具的更多细节。这个工具收集了所有你想知道的UI工具发送给GPU的绘画命令。\n\rAndroid4.3及以前：从Android4.3我们引进了reordering and merging of drawing commands后，Tracer变得难以使用。reordering and merging of drawing commands是一个令人惊讶的有用优化，但是他阻止了来自view的组绘画命令。使用如下命令（在启动你的应用前）你可以通过关闭显示list优化来恢复旧的行为。\n\r分析OpenGl Traces：蓝色的命令显示屏幕上绘制像素的GL操作。其他所有被用于传输数据或者设置状态命令能很容易的被忽略掉。每次你点击蓝色的命令，Tracer会更新Detail选项卡，在你点击的被执行后立即显示当前渲染对象的内容。通过一个接一个的点击，你可以重建每一帧。这几乎就是我用Trace分析性能问题的流程。了解被渲染的一帧都提供了什么。\r\r当仔细看收集到的traces时，我惊奇的发现了一些SaveLayer/ComposeLayer命令块，r如图1-2。\r \r![](http://androidperformance.com/images/android-performance-case-study-follow-up/glTrace.png)      \n图 1-2 \r\r\r这些块表明创建和合成了临时性的硬件层。这些临时硬件层被不同的Canvas.saveLayer()创建。当下面的特殊条件被满足时，UI 系统调用 Canvas.saveLayer()函数来绘制视图，并且alpha值小于1：\n\r* getAlpha() 返回一个小于1的值\r* onSetAlpha() 返回 false\r* getLayerType() 返回LAYER_TYPE_NONE\r* hasOverlappingRendering() 返回 true\n\r在一些演讲中，Chet和我解释了为什么要谨慎使用alpha的原因 ( 译者注 : 关于alpha问题可以参考这篇文章 [Android Tips: Best Practices for Using Alpha](http://imid.me/blog/2014/01/17/best-practices-for-using-alpha/))。那就是每一次UI系统必须使用临时硬件层时，绘画命令发送给不同的渲染对象，对于GPU来说切换不同的渲染对象是非常耗时的操作。对于用tiling/deferred 架构的GPU是一个硬伤(例如ImaginationTech’s SGX, Qualcomm’s Adreno等等)。直接渲染架构更好在这种情况下的表现会稍微好一些，例如：Nvidia。但是Joaquim和我使用的手机都是使用了Qualcomm Adreno GPU 的Moto X 2014版，因此多个临时硬件层的使用可能是引起这个性能问题的根源。\n\r更重要的问题是：是什么创建了这些临时层？Tracer给了我们答案。如果你看到了Tracer的截屏，你能看到仅是一组OpenGL的SaveLayer命令在一个小的渲染对象循环调用了。现在让我们看下应用截图， 如图1-3：\r\r![](http://androidperformance.com/images/android-performance-case-study-follow-up/before.png)     \n图1-3\n \r你看到顶部的几个小圆点了么？这是ViewPager指示器，用于显示使用者的页面位置。Joaquim使用了一个第三方库来画这些指示器。当前页面的指示器是一个白色的圆点，其他页面的指示器是一个灰色的圆。我说“什么导致了它显示为灰色”，因为这些圆实际上是半透明的白色圆点。这个库中对于每一个圆都用了一个View（这本身就是浪费）并调用setAlpha()改变他们的颜色。\r\r这里有一些修复这个问题的几个解决方案：\r\r* 使用一个可定制的颜色来代替在View上设置不透明度;\r* 使hasOverlappingRendering()返回false，然后系统会为你设置一个适当的alpha值到画笔上；（译者注 : 对于有重叠内容的View，系统简单粗暴的使用 offscreen buffer来协助处理。当告知系统该View无重叠内容时，系统会分别使用合适的alpha值绘制每一层。）\r* 使onSetAlpha()返回true，并设置Paint的alpha来绘画灰色的圆。\n\r最合适的是第二种方法，但最低支持API level 16.如果你必须支持老版本，可以使用另外两个方法中的一个。我相信Joaquim会抛弃第三方库并使用他自己的指示器。\r\r我希望这篇文章中市我们能够意识到性能问题很可能出现在看似无害的操作上。请记住：不要假设，实践出真理！\r \r"
  },
  {
    "path": "androidweekly/Kotlin for Android (II)创建一个工程/readme.md",
    "content": "Kotlin for Android (II)创建一个工程\n---\n\n>\n* 原文链接 : [Kotlin for Android (II): Create a new project](http://antonioleiva.com/kotlin-android-create-project/)\n* 译者 : [Lollypo](https://github.com/Lollypo) \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  校对中\n\n\n当我从[what Kotlin is and what it can do for us](http://antonioleiva.com/kotlin-for-android-introduction/)获得一些启发之后,觉得是时候配置下 Android Studio来帮助我们使用Kotlin开发Android应用程序了. 其中有些步骤只需要在初次使用时完成一次, 但是其他一些Gradle配置需要为每一个新项目做一遍. ( 译者注 : 如果你对Kotlin还不了解，可以先看看[kotlin-for-android简介](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/androidweekly/kotlin-for-android%E7%AE%80%E4%BB%8B)这篇文章 )\n\n对于本系列文章, 我将创建一个我早些时候创建的[Bandhook](https://play.google.com/store/apps/details?id=com.limecreativelabs.bandhook)的简化版本, 它基本上就是连接到一个基于RESTful的音乐API然后接收一些乐队的信息. 链接到 [Bandhook Kotlin on Github](https://github.com/antoniolg/Bandhook-Kotlin) 查看源代码.\n\n\n###创建一个新项目然后下载Kotlin插件###\n\n就像你平常做的那样，我们只需要用Android Studio创建一个带Activity的基本Android项目。\n\n一旦完成,我们需要做的第一件事就是去下载Kotlin插件. 去到Android Studio的系统设置中然后查找plugins.之后，再次使用搜索找到Kotlin插件，安装并重启IDE。\n\n![kotlin-plugin](http://7xi8kj.com1.z0.glb.clouddn.com/kotlin-plugin-e1424632570741.png)\n\n###添加Kotlin插件的依赖到的应用程序的build.gradle中###\n\n该项目的build.gradle需要添加一个新的依赖，这个依赖将会被Kotlin插件要求以在主Module中使用:\n```gradle\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.1.3'\n        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.0'\n    }\n}\n```\n\n\n\n###配置Module的build.grade###\n\n首先, 应用Kotlin插件:\n```gradle\napply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\n```\n接着, 添加Kotlin库到你的依赖:\n```gradle\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.0'\n}\n```\n最后, 你需要添加我们在下一个步骤创建的Kotlin文件夹:\n```gradle\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.0\"\n \n    ...\n \n    sourceSets {\n        main.java.srcDirs += 'src/main/kotlin'\n    }\n}\n```\n或者,你可以跳过这一步,当做完下一个步骤时，使用这个Android Studio的操作:\n\n![configure-kotlin-project](http://7xi8kj.com1.z0.glb.clouddn.com/configure-kotlin-project.png)\n\n我更倾向于手动去做以保持我的Gradle文件有整洁有序, 但第二个选项可能较为容易些。\n\n\n\n###创建Kotlin文件夹###\n\n如果你将项目的视图从‘Android’转到‘Project’，那将会非常容易。依次选择‘app->src->main’ 然后创建一个名为 ‘kotlin'的文件夹:\n\n![kotlin-folder](http://7xi8kj.com1.z0.glb.clouddn.com/kotlin-folder.png)\n\n\n\n###将Java activity转换成Kotlin文件###\n\nKotlin插件能将Java转换为Kotlin类. 我们可以轻松的通过‘Code’菜单中的‘Convert Java File to Kotlin File'选项转换当前的Activity到Kotlin类 :\n\n![convert-java-to-kotlin](http://7xi8kj.com1.z0.glb.clouddn.com/convert-java-to-kotlin-e1424633562637.png)\n\nIDE将建议你移动新文件到Kotlin文件夹，点击‘Move File’(或者手动完成，假如你没看到这个选项).\n```java\npublic class MainActivity : ActionBarActivity() {\n \n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n    }\n \n \n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_main, menu)\n        return true\n    }\n \n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        val id = item.getItemId()\n \n        //noinspection SimplifiableIfStatement\n        if (id == R.id.action_settings) {\n            return true\n        }\n \n        return super.onOptionsItemSelected(item)\n    }\n}\n```\n\n\n\n###主要区别###\n\n看一看之前的代码, 我们可以看到一些明显的差异。 其中很大一部分我们将会在下一篇文章讲解到:\n\n- 使用冒号，而不是'extends'。\n- 显式使用‘override': 在Java中, 我们可以使用一个注释使我们的代码更清晰,但它不是必要条件. Kotlin将迫使我们使用它.\n- 函数则使用‘fun’关键字: Kotlin是一个面向对象的函数式语言, 因此可能会与其他语言类似，例如Scala. Java方法被函数的形式表示。\n- 函数参数命名规则不同: 类型和名称都写在相反的位置,并用冒号隔开。\n- 分号可选: 我们不需要在行的结尾处加上分号。如果我们想要也可以加上, 但如果我们不这样做，它就可以节省大量的时间,并使我们的代码整洁。\n- 其他小细节: 在简介一文中, 我已经说到了 ‘?’ 的意义. 这表明参数可以为空。NULL的处理方式不同于Java。 \n\n\n\n###总结###\n\n也许我们会认为使用一门新语言将会非常困难, Kotlin被JetBrains团队开发出来的，要成为最容易和可交互的语言用来覆盖那些Java的不足之处。由于Android Studio也是基于JetBrains的产品，这将让集成到这个IDE中并且开始工作非常简单。\n\n下一篇文章将介绍一些让我们在使用Kotlin开发Android应用程序时，能让开发过程更简单的奇巧淫技。\n"
  },
  {
    "path": "androidweekly/Kotlin for Android (III)-扩展函数与默认值/readme.md",
    "content": "Kotlin for Android (III)/ 扩展函数与默认值\n---\n\n>\n* 原文链接 : [Kotlin for Android (III): Extension functions and default values](http://antonioleiva.com/kotlin-android-extension-functions/)\n* 译者 : [Lollypo](https://github.com/Lollypo) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)\n* 状态 :  校对完成\n\n现在你已经了解[Kotlin基础](http://antonioleiva.com/kotlin-for-android-introduction/)与[如何配置你的项目](http://antonioleiva.com/kotlin-android-create-project/),是时候谈论Kotlin能为我们做哪些Java做不到的有趣的事情了.请记住,如果你对Kotlin语言有任何疑问,可以参考[官方文档](http://kotlinlang.org/docs/reference/).这份文档条理分明、简单易懂, 而且我这篇文章不会涉及到语言的基础部分.\n\n\n\n### 扩展函数\n\nKotlin的扩展函数功能可以让我们添加新的函数到现有的类上而不必去修改它本身.例如,我们可以轻松的的通过扩展函数语法将一个显示Toast的函数添加到Activity类中:\n\n```java\nfun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){ \n    Toast.makeText(this, message, duration) \n}\n```\n\n我们可以在任意的地方声明这个函数(例如一个工具类文件), 并在我们的Activities中当作一个普通方法使用:\n\n```java\noverride fun onCreate(savedInstanceState: Bundle?) { \n    super<BaseActivity>.onCreate(savedInstanceState)     \n    toast(\"This is onCreate!!\") \n}\n```\n\n声明一个扩展函数跟添加类名到函数名上一样简单.这个函数将被作为导入的类使用. \n\n这可以帮助我们简化代码而且让封闭的类打破局限.但是我们必须小心和适度的使用.最后,这些函数通常会替代工具类.工具方法通常是静态的且不能被修改. 因此,过度使用通常是表示我们懒得创建一个委托类.\n\n这里是另一个有趣的例子,让我解释另一个有趣的概念:具体类型(reified types).\n\n```java\ninline public fun <reified T : Activity> Activity.navigate(id: String) { \n    val intent = Intent(this, javaClass<T>())\n     intent.putExtra(\"id\", id)\n     startActivity(intent) \n}\n```\n\n内联函数可以使用具体类型,这意味着我们可以从内部函数取得类而不用通过将类类型作为参数.\n\n> 内联函数的有点不同于一般的函数.内联函数将会在编译过程中替换代码而不是真的调用一个函数. 这会简化某些场景.例如,如果我们将一个函数作为参数,常规函数将在内部创建一个包含该函数de对象,.另一方面,内联函数会在函数被调用的地方替换掉,因此它真的不需要一个内部对象.\n\n  > ```java\n navigate<DetailActivity>(\"2\")\n ```\n\n使用具体类型,我们可以在函数内部创建一个意图, 并使用扩展函数,我们可以直接调用startActivity().\n\n\n\n### 可选参数与默认值\n\n\n由于有参数默认值和构造函数,你永远不需要重载函数了.一个声明可以满足你所有的需求.回到Toast示例:\n\n```java\nfun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){ \n    Toast.makeText(this, message, duration) \n}\n```\n\n第二个参数指的是Toast的持续时长.它是可选参数,在没有指定的情况下,将使用Toast.LENGTH_SHORT作为默认值.现在你有两种方法调用这个函数:\n\n```java\n    toast(\"Short Toast!!\")\n    toast(\"Long Toast!!\", Toast.LENGTH_LONG)\n```\n\n对于第二个示例,我们也许想添加一些转换棒棒糖的参数:\n\n```java\ninline public fun <reified T : Activity> Activity.navigate(\n         id: String, \n        sharedView: View? = null,\n         transitionName: String? = null) {          \n    ...\n }\n```\n\n我们现在有两种不同的方式调用相同的函数:\n\n```java\nnavigate<DetailActivity>(\"2\")\nnavigate<DetailActivity>(\"2\", sharedView, TRANSITION_NAME)\n```\n\n甚至是第三种,虽然在大多数情况没什么意义,但是可以帮助我们理解另一种概念:我们可以使用参数名来决定我们想调用哪些参数:\n\n```java\nnavigate<DetailActivity>(id = \"2\", transitionName = TRANSITION_NAME)\n```\n\n可选参数可以被用于默认构造函数,因此你可以通过一个声明实现许多的重载方法.自定义视图是一个特例, 因为在Java中它们需要多个构造函数才能够正常工作.我将会在下一篇文章讲解到.\n\n\n\n## 总结\n\n通过这两个优势,我们可以简化大量的代码甚至可以做那些Java中不可能的事情.Kotlin真的是简洁明了.下一篇文章将会讲解Kotlin的Android拓展,这可以让我们在Activities中自动注入视图,与如何在Kotlin中自定义View.\n\n记得浏览一下[示例库](https://github.com/antoniolg/Bandhook-Kotlin)来看一下它的实际应用."
  },
  {
    "path": "androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-1/readme.md",
    "content": "ListView或者RecycleView滚动时隐藏Toolbar (1)\n---\n\n>\n* 原文链接 : [How to hide/show Toolbar when list is scroling (part 1)](http://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling%28part1%29/)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  校对中\n\n\n今天我打算写一篇博文给大家介绍Google+ App的一个酷炫效果——向上/向下滚动ListView/RecyclerView时，Toolbar和FAB（屏幕右下方的小按钮）会隐藏/出现。这个效果也被Google视为符合 Material Design 规范的效果哦，详情参见： [Material Design Checklist](http://android-developers.blogspot.com/2014/10/material-design-on-android-checklist.html) 。\n\n\n> 如果可以的话，我们希望当屏幕向下滚动时，App的Toolbar和FAB将离开屏幕，从而在垂直方向上为可显示区域腾出更大的空间来显示我们的内容。而当我们的屏幕向上滚动时，App的Toolbar和FAB会再次出现。\n\n我们做出来的最终效果应该是下面这样的：\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447324_1070.gif)\n\n在这篇博文的讲解中，我们将会用RecyclerView作为我们的list，但这不代表其他可滚动的容器就不能实现这样的效果了，只是其他的可滚动容器（如：ListView）需要要多花一些功夫才能实现这个效果。那么我们要怎么去实现呢？我想到了两个办法：\n\n- 对list容器加一个Padding\n- 对list容器加一个Header\n\n就我个人而言，我更想用第二种方法去实现，因为在设计代码的过程中我发现：为RecyclerView添加Header会产生几个问题，这给了我很好的机会去思考如何解决它，与此同时，在这个思考和解决问题的过程中我还能学习到更多的知识，何乐而不为呢？不过我还是会给大家简要地介绍如何使用第一种方法实现的啦！\n\n## 那就让我们开始今天的讲解吧！ \n\n首先，我们需要创建一个工程和添加必要的工具库：\n\n接着我们需要定义 styles.xml ，以确保我们的App没有添加Actionbar（因为我们将会使用Toolbar），同时我们App的设计风格要符合Google的 Material Design 规范。\n\n最后我们就要创建我们activity中显示的布局：\n\n事实上，我们只需要一个简单的布局，其中包含了：RecyclerView，Toolbar和ImageButton。但需要注意的是：我们需要把它们放在一个FrameLayout里，否则当我们隐藏Toolbar时list的上方将会出现一个空白区域，这显然不会是我们想要的效果。我们理想的效果应该是：当Toolbar被隐藏，list能在屏幕的可见区域中显示出一整个列表，而这就需要通过使Toolbar覆盖在RecycleView上面来实现。\n\n接着来看看我们MainActivity代码吧：\n\n就像你看到的那样，这是一个只重写了OnCreate()方法，非常非常非常简单的Activity。而且OnCreate()方法也只做了下面三件事情：\n\n1. 初始化Toolbar\n\n1. 获取mFabButton的引用（mFabButton是FAB的对象哦，也就是屏幕右下方的小按钮）\n\n1. 初始化RecyclerView\n\n为了在list中显示出内容，我们现在就要为RecyclerView创建一个Adapter啦。但在此之前，我们应该为我们list中的item添加相应的子布局以及对应的ViewHolder：\n\nlist的每一个item只需要一个text用来显示文字，非常简单！\n\n那RecyclerAdapter该怎么实现呢：\n\n就像你看到的这样，这是一个非常普通的RecycleView.Adapter，没有任何特别的地方，是不是感觉被骗了呐  23333～（如果你想要了解更多有关RecyclerView知识，我强烈建议你去看大牛 Mark Allison's 巨巨写的这些优秀文章 [series of posts](https://blog.stylingandroid.com/material-part-4/)）\n\n经过上面的努力，我们已经把车子的小零件组装的七七八八啦，是时候让小车子上路跑一跑，展现真正的技术了！\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447324_5421.png)\n\nWTF，谁能告诉我这是什么鬼……？\n\n我相信只要不是瞎子都会发现，App的Toolbar竟然把我们的item挡住了！！！或许部分机智的小伙伴已经发现了问题所在：出现这样的问题是因为我们在activity_main.xml里使用了FrameLayout，正是FrameLayout导致了这样的问题，而这就是我开头所提到的问题之一了。\n\n第一个解决办法是为我们的RecyclerView添加一个PaddingTop，并将PaddingTop的值设置为Toolbar的高度。但有一个细节我们不能忽略，那就是RecyclerView会默认裁剪到子View的Padding区域，所以为了我们伟大的事业，我们必须把这个特性关掉。\n\n经过这些修改之后，我们就能实现我们想要的效果，这就是我所说的第一种方法。但如我所说，我写这篇博文的目的，不仅仅只是教你实现这个效果，然后就完了。我想教给你实现同一个效果各种各样的方法，并且为你介绍其中的思想，让你接触到平常很难接触到的问题并教你如何解决它。有些方法固然会更加复杂（在本文中是为list添加一个Header），但你在实现过程中也能学到更多的知识，毕竟授人以鱼不如授人以渔嘛。\n\n## 为RecycleView添加一个Header\n\n\n要用第二种方法去实现这个效果，首先我们要做的就是稍微修改一下我们的Adapter：\n\n下面是其实现原理：\n\n我们需要定义一个常量来区分Recycler展现的item的类型。这里我需要为你介绍的是:RecyclerView是一个非常灵活的组件,RecyclerView 完全能实现你想要让list的item具有各种各样不同的布局的愿望，而此时，我们定义来区分item类型的常量就会被利用到。而这样的特性正是我们想要的——让Header成为RecyclerView的第一个item，显然这会与其余的item不一样。（第3-4行）\n\n因而我们需要让Recycler知道它需要展示的子布局是什么类型的，在本文中我们用作类型区分的常量则是TYPE_ITEM和TYPE_HEADER。（第49-54行）\n\n接着，我们需要修改onCreateViewHolder()和onBindViewHolder()方法，如果item的类型是TYPE_ITEM的话，使它们返回和绑定一个普通的item；如果item的类型是TYPE_HEADER的话，则返回Header。（第14-34行）\n\n此外，由于我们的list并不仅仅只有普通的item，我们还在list中添加了Header，因此我们需要修改getItemCount()方法的返回值，让我们的返回值是普通item的总数量 + 1（第43-45行）\n\n最后让我们来为Header布局创建一个layout和一个ViewHolder，但出乎意料的是，我们需要为Header创建的layout和ViewHolder都非常简单，唯一需要注意的是：Header的高度必须和Toolbar的高度一致。\n\n那么这样我们就把布局弄好啦～不信你看图！\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447325_8379.png)\n\n所以总的来说，我们为RecyclerView添加了一个和Toolbar有相同高度的Header，而现在我们的Toolbar把header隐藏起来了（因为header现在是一个空的view)，同时，我们所有的普通item都是可见的。那么现在就让我们来实现滚动时改变Toolbar和FAB的出现和隐藏吧！\n\n## 滚动时控制Toolbar和FAB的出现和隐藏 \n\n\n为了实现这个效果，我们为RecyclerView再创建一个——OnScrollListener类就够了你敢信？\n\n我现在还要告诉你，在OnScrollListener类里，我们只需要重载onScrolled()方法就能让这个酷炫的效果成为App中秒杀用户的黑魔法！其中，onScrolled()方法的参数——dx,dy分别是水平和垂直方向上的滚动距离。但大家需要注意的是：这里dx，dy并不是代表屏幕上的物理距离，而是两个事件的相对距离。\n\n具体的实现算法大体如下：\n\n只有当list向上滚动且Toolbar和FAB被隐藏，抑或是当list向下滚动且Toolbar和FAB出现，我们才会计算总的滚动距离，因为这两种情况下的滚动距离才是我们实现这个效果所需要关心的。\n\n总的滚动距离需要超过我们展现/隐藏Toolbar和FAB所在的方向的极限值才能将其展现/隐藏（你把极限值调整的越大，通过滚动展示/隐藏Toolbar和FAB需要的距离就越大）（dy>0意味着我们在向下滚动，dy<0意味着我们在向上滑动）\n\n实际上我们并没有在我们的滚动监听类里面展现/隐藏Toolbar和FAB，我们是通过调用show()/hide()方法来展现/隐藏Toolbar和FAB的，所以调用者可以通过接口实现它。\n\n现在我们需要为RecyclerView添加它的监听：\n\n下面是我们通过动画改变我们的视图的方法：\n\n当我们隐藏Toolbar和FAB的时候，我们必须把Padding也考虑进去，不然的话视图并不能够完全被隐藏。\n\n是骡子是马，让我们拉出来溜一溜！\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447325_8449.gif)\n\n虽然现在看起来已经很nice了，但其实这里有一个小小的bug——如果你在list的顶部，此时临界值非常小，因而你能隐藏Toolbar，但你在list的顶部会看到有一个空白的区域。不过幸好这里有一个很简单的方法可以解决这个bug：我们可以通过检测当前list的第一个item是否为可见的，只有当它不可见，才使用我们设计的展示/隐藏逻辑。\n\n在这样的修改后，即使第一个item是可见的并且有item被它挡住了，我们也在展示它们，除此以外的情况我们都像之前说的那样实现我们的效果。\n\n各位观众，接下来，就是见证奇迹的时刻：\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447326_5458.gif)\n\n太棒了思密达！感觉之前的失败都如雨后甘霖温润我脆弱的心灵呐～\n\n其实羞羞地说一句……这篇文章是我人生中的第一篇博文呐，如果你觉得很无聊或者我有哪里讲解错误的话千万不要喷我哦。我会在未来变得更棒哒，然后尽我所能为大家贡献更多的文章！\n\n如果你看到这里还是通过添加Header来实现这个效果很恼火的话，用第一种方法结合HidingScrolllistener 也是可以实现这个效果的～\n\n如果你有什么疑问的话，可以在评论区问我哦，我都会尽我所能为你解答的！\n\n## 源码 \n\n整个项目的源码在GitHub上面都有，大家可以在这看 [repo](https://github.com/mzgreen/HideOnScrollExample)\n\n感谢Mirek Stanek帮我校对文章，么么哒！爱你的好基友Michal Z～\n\n如果你喜欢这篇博文的话，你可以[在Twitter上分享给你的小伙伴](https://twitter.com/intent/tweet?url=http://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling(part1)/&text=How%20to%20hide/show%20Toolbar%20when%20list%20is%20scroling%20(part%201)&via=mzmzgreen)或者[在Twitter上关注我哦](https://twitter.com/mzmzgreen)!\n"
  },
  {
    "path": "androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-2/readme.md",
    "content": "ListView或者RecycleView滚动时隐藏Toolbar( Part 2 )\n---\n\n>\n* 原文链接 : [How to hide/show Toolbar when list is scrolling (part 2)](http://mzgreen.github.io/2015/02/28/How-to-hideshow-Toolbar-when-list-is-scrolling(part2)/)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  校对中\n\n\n\n\n\nHello，各位小伙伴，俺胡汉三又来了！！！今天我打算接着上一篇博文继续给大家讲解展现/隐藏Toolbar的效果。我建议没有读过 [ListView或者RecycleView滚动时隐藏Toolbar](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/androidweekly/ListView%E6%88%96%E8%80%85RecycleView%E6%BB%9A%E5%8A%A8%E6%97%B6%E9%9A%90%E8%97%8FToolbar/readme.md) 这篇文章的小伙伴先去看看那篇博文再来看这篇博文，不然会跟不上我的讲解节奏的哦。在上一篇博文里，我们学习了如何去实现Google+那个酷炫的展现/隐藏Toolbar的效果，今天，我将会给大家讲解怎么让上一篇博文的效果进化成Google Play Store Toolbar那样，废话不多说，我们现在进入正题吧：\n\n在我们开始之前，我想先告诉大家的是我已经对这个项目进行了一些重构——我继承项目的 MainActivity 实现了两个新的子类：PartOneActivity 和 PartTwoActivity。源码在包 partone 和 parttwo里，你可以在这两个包里挑选你喜欢的那一个使用\n\n下面是我们的最终效果图，我把它和 Google Play Store Toolbar 放在一起比较，大家可以感受一下：\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447727_3335.gif)\n![](images/playstore.gif)\n\n## 准备工作 ##\n\n在这里我不会再给大家展示 build.gradle 文件，因为这和第一部分的 build.gradle 文件是一样的，所以我们将会从这个步骤开始——为我们的Activity创建一个layout：\n\n同样的，layout里面只有一个 RecyclerView 和一个 Toolbar （稍后再加上 Tabs）。值得注意的是，我这里的实现是使用之前说的第二种方法（为RecyclerView添加Padding）\n\n同样的道理，由于我们的布局文件、list、RecyclerAdapter 都和之前是一样的，我在这里都不会再给大家讲解了。\n\n那现在我们来看看 PartTwoAcitivty 的代码吧：\n\n在 PartTwoActivty 里面仍然是简单地对 RecyclerView 和 Toolbar 进行初始化，但大家一定要记得设置 OnScrollListener 哦（第27行）\n\n感觉大家看到这里也感觉昏昏欲睡了，因为前面提到的内容大体上都和上一篇相似。但是莫慌！俺接下来就要讲这篇博文中最有趣的部分 —— HidingScrollListener 了，请大家紧紧抱住我，跟上节奏！\n\n如果你看过第一篇博文可能会觉得此情此景很熟悉（可能还会感觉简单一些）。我们在 HidingScrollListener 里耍了什么 tricks 呢？那就是存了与 Toolbar 的高度相关联的屏幕滚动偏移量 —— mToolbarOffset。为了简化其中的逻辑，只有当 mToolbarOffset 的取值在[0 , Toolbar的高度]之间时，我们才会实现我们的逻辑：当我们向上滚动时，mToolbarOffset的值会增加（但不会超过Toolbar的高度）；当我们向下滚动时，mToolbarOffset 的值会减少（但不会低于0）。大家现在可能会有许多疑问：为什么要引用 mToolbarOffset 呀？为什么要让 mToolbarOffset 的取值范围介于那两者之间呐？别怕！你马上就会理解为什么我们要这样限制mToolbarOffset的取值了。我们必须知道的是，尽管我们极力去避免意外的发生，但现实总会出人意料，在这里也不例外，有时候 mToolbarOffset 的值就是会不可避免地在我们的取值范围之外，但由于我们的逻辑设计的限制，最终的显示效果会是闪烁一下。（例如：在屏幕上快速的挥动、滑动）这样的结果显然不是我们这些有格调的 Android 工程师想要的，因而我们需要对 mToolbarOffset 进行一定程度的裁剪，以规避这样的风险。基于这样的考虑，我们重载了 onMoved()方法 —— onMoved() 方法是一个当我们滚动视图时被调用的抽象方法。可能会吓到你，但是莫慌，继续抱住我！\n\n接下来，我们就要回到我们 PartTwoActivity 设计之中，并且在我们的滚动监听器中实现 onMoved()方法。\n\n是的，这就是所有内容啦。我们运行 App 后可以看到我们的最终效果：\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447725_3945.gif)\n\n是不是感觉自己棒棒哒～就像我们想象的那样，Toolbar 完美的随着list的滚动实现了展现/隐藏的效果。其中的功劳都得归功于我们对 mToolbarOffset 取值范围的限制。如果我们省略掉检查 mToolbarOffset 是否在[0 , Toolbar的高度]范围中取值的过程，带着完成控件的喜悦向上滚动我们的list，Toolbar 确实会如你所期望的那样离开屏幕，但与此同时，Toolbar 还会远远地，远远地，离开视图，再也不回来。然后当你满是期许地向下滚动时，你就会发现刚刚那一声再见，竟是永远。如果你想再次遇见它，你就必须想下滚动，直到 mToolbarOffset = 0。\n\n再脑补第二种情况吧，现在你正好把 Toolbar 滚动到 mToolbarHeight = mToolbarOffset 的位置，不偏不倚。那么现在Toolbar就刚好“坐”在了list顶部，如果你向上滚动的话，无论你怎么滚，它都不会动，只会静静地坐在那儿笑看人世沧桑。而如果你向下滚动，它又成为许多年前那个明亮、可爱的小女孩了。\n\n虽然最终的实现效果看起来非常赞，但这并不是我想要的。因为我们能在滚动过程中停止整个效果，使得 Toolbar 有一部分是可见的，另一部分又是不可见的。但悲伤的是，Google Play Games 就是这么干的，而我一直认为这是一个Bug……\n\n## 在某一点停下 Toolbar ##\n\n就我的认知来说，我认为滚动的Views是能够如丝般顺滑地对齐相应的位置的，就像 Chrome 应用里的 Logo/SearchBar 又或者是 Google Play Store应用里那样。我很确定我在 Material Design 的guidelines/checklist 或者是 以前听过的Google I/O 大会上听过类似的规范。\n\n那我们现在再来看看 HidingScrollListener 的代码吧：\n\n虽然为了实现上面提到的效果我们会让 HidingScrollListener 的代码变得更复杂一些，但是我再说一次，莫慌，抱紧我！我们现在只需要重载 RecyclerView.onScrollListener 类的 onScrollStateChanged()方法，然后按照下面那样干就行了：\n\n首先，我们需要检查list是否处于 RecyclerView.SCROLL_STATE_IDLE 状态，以确保list没有在滚动或者挥动（因为如果list如果正在滚动或者挥动的时候，我们就需要像第一篇博文那样去考虑 Toolbar 的Y方向位置哦）\n\n如果我们抬起了手指，而且list已经停止移动了（）我们就要去检查Toolbar是不是可见的，如果是可见的，我们就需要考虑 mToolbarOffset 的值了。如果此时 mToolbarOffset 的值大于 HIDE_THRESHOLD 我们就必须把 Toolbar 隐藏起来；mToolbarOffset 的值小于 HIDE_THRESHOLD，我们则需要让 Toolbar 显示出来。\n\n如果 Toolbar 不是可见的，我们就需要做相反的事情 —— 如果 此时mToolbarOffset（此时计算 mToolbarOffset要从顶部位置来考虑了，也就是 mToolbarHeight - mToolbarOffset） 大于 SHOW_THRESHOLD 我们就让 Toolbar显示；相反，如果 mToolbarOfffset 小于 SHOW_THRESHOLD ，我们就要再次将 Toolbar隐藏起来。\n\nonScrolled()方法和第一篇博文的一样，我们并不需要作什么改变。我们现在最后需要做的就是在 PartTwoActivity 类里实现我们的两个抽象方法：\n\n那么，你准备好看魔术了吗？\n\n![](http://img.my.csdn.net/uploads/201503/27/1427448161_2643.gif)\n\nhey，派大星你看，是不是很酷！\n\n现在你可能在脑补添加 Tabs 会有多麻烦了，可是兄弟啊，生活很多时候都是出人意料的呐，不信你继续往下看呀\n\n## 添加 Tabs ##\n\n为了添加 Tabs，首先要做的当然是为我们的 Activity 布局添加一个 tabs.xml啦\n\n你可以从源码那发现，我并没有添加真正的 Tabs，只是在布局里面模拟了 Tabs。而以上的一切不会改变任何之前的实现，你能在里面放任何你想要放的View。下面是一些 GitHub 上符合 Material Design规范的 Tabs 实现，当然你也可以自己实现啦。\n\n添加 Tabs 意味这他们会稍微覆盖我们的list，所以我们需要增加Padding。为了减少代码的操作复杂度，我们不会在 xml 里进行这个操作（注意把 RecyclerView 在 part_tuo_activity里的padding删掉哦），因为 Toolbar 可能会在不同的设备中切换方向时拥有不同的高度（例如在平板中），这样的话我们需要创建一大堆的 xml 去解决这些乱七八糟的烦人问题。所以我决定在代码中解决这个问题：这是非常简单的，我们只需要和 Tabs高度的总和。如果我们把 Padding设置为 Toolbar 的高度现在运行起来的话，就会发现这样的东西：\n\n![](http://img.my.csdn.net/uploads/201503/27/1427448162_3352.png)\n\n看起来很正常的样子……我们第一个item刚刚好是可见的，我们也能移动跟随着它。实际上我们在 HidingScrollListener 类里什么也没干，唯一需要的改变都是在 PartTwoActivity 里做的：\n\n你能发现什么发生了改变吗？我们现在不妨创建一个 mToolbarContainer 的引用，但是大家要注意哦， mToolbarContainer 是 LinearLayout 对象而不是 Toolbar对象，而且在 onMove()，onHide()，和 onShow()方法中，我们都把 Toolbar 改成了 mToolbarContainer。这会使得包含了 Toolbar 和 Tabs 的Container被移动，这恰恰就是我们想要做的。\n\n如果我们把修改后的代码运行起来会发现，实际的运行效果正好就是我们所期望的，但如果你看的认真一些你会发现，里面其实有一个小Bug。在 Tabs 和 Toolbar 之间有时候会有一条白线，虽然时间非常短，但还是很惹人讨厌呐。我个人觉得这大概是因为当Toolbar 和 Tabs被显示的时候，他们并没有像我们期望的那样同步在一起。不过万幸这不是什么无法解决的Bug～\n \n解决办法非常简单，就是让 Toolbar 和 Tabs 的背景和父布局保持一致：\n\n现在即使 mToolbarContainer 在显示过程中没有很好的同步在一起，白线也不会出现了。正当我打算吃根辣条庆祝我们伟大战役的胜利的时候，又出现了一个Bug………………这个Bug和我们在第一篇博文里遇到的Bug是一样的，如果我们在list的顶部，我们可以向上滚动一丢丢，如果此时 HIDE_THRESHOLD 足够小，Toolbar 就会藏起来，导致那里有一块空白区域（其实就是我们设置的Padding）在list的顶部，但是我相信你到了现在应该不会慌了，因为你已经知道所有Bug在我眼里都是非常容易解决的：\n\n我们只需要再增加一个变量用于存储list的总滚动偏移量，当我们准备去检查我们是否应该展现/隐藏 Toolbar的时候，我们首先应该检查我们的滚动距离是否比 Toolbar 的高度要大（如果不是的话，我们再让 Toolbar 出现）\n\n这就是今天博文要讲的一切了，让我们来看一看实际效果！\n\n![](http://img.my.csdn.net/uploads/201503/27/1427447727_3335.gif)\n\n现在运行的效果简直完美啊大兄弟～即使用其他的 LayoutManagers 也不需要改变任何东西的哦：\n\n![](http://img.my.csdn.net/uploads/201503/27/1427448162_7185.png)\n\n评论区有好学的同学问了个有关存储滚动状态的问题，这确实是个小麻烦。如果我们list的item中的文字在垂直方向达到2行，在水平方向达到1行的话，我们的item高度就会变得很奇怪了……举个例子吧，如果我们滚动到垂直方向100的位置，然后旋转我们的设备，同时存储 mTotalScrolledDistance的值，在旋转之后，我们会滚动到list的顶部，然后我们会发现 mTotalscrolleddistance 的值不等于0。这个时候即使全能如我也想不到简单的办法来解决这个问题了，但是在我们的使用场景中，这样的小问题并不会有什么影响。如果你真的想要解决这个问题的话，我个人的做法是：在旋转之后把 mTotalScrolledDistance 的值重设为0 并且显示 Toolbar。\n\n感觉今天写了好多内容啊，大家看到这里应该感觉很累了吧？不过今天这篇博文就是这个系列的最后一篇文章啦，大家能在第一篇博文中学习到知识我真的很高兴呢。大家鼓励和夸奖的话也让我很感动，我会继续写我的博客，为大家传授更多的知识，不过我也不知道下一篇博文会在什么时候写 2333。\n\n除此以外我还想说的是，在这两篇博文中我提到的方法可能看起来运行的很好，但其实我并没有进行非常严谨的测试，所以我也不确定它们能不能被用于企业级应用中（你看我们不就遇到了好几个Bug了嘛）。这个系列的博文的初衷只是想告诉你，即使只使用标准API里面的一两个简单方法，也能够实现酷炫的效果。同时，我在写博文的过程中也发现了这些方法还有其他有趣的用法（例如：利用视差背景制作有粘性的 Tabs，就像在 Google+ 个人页面那样）。不管怎样，祝大家在写代码的过程中找到更多的快乐！\n\n## 源码 ##\n\n整个项目的源码都已经被上传到 [GitHub](https://github.com/mzgreen/HideOnScrollExample) ，大家可以去下载和使用哦，爱你们的 Michal Z。\n\n如果你喜欢这篇博文的话，你可以 [在Twitter上分享给你的小伙伴](https://twitter.com/intent/tweet?url=http://mzgreen.github.io/2015/02/28/How-to-hideshow-Toolbar-when-list-is-scrolling(part2)/&text=How%20to%20hide/show%20Toolbar%20when%20list%20is%20scrolling%20(part%202)&via=mzmzgreen) 或者 [在Twitter上关注我哦](https://twitter.com/mzmzgreen) 。"
  },
  {
    "path": "androidweekly/Square 开源库Flow和Mortar的介绍/readme.md",
    "content": "Flow和Mortar的调查\n---\n>\n* 原文链接 : [Architecting An Investigation into Flow and Mortar](http://www.bignerdranch.com/blog/an-investigation-into-flow-and-mortar/)\n* 译者 : [sundroid](https://github.com/sundroid)( [chaossss](https://github.com/chaossss) 协同翻译)\n* 校对者: [chaossss](https://github.com/chaossss)、[Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n“在 App 开发过程中尽可能使用 Fragment 替代 Activity”，Google 官方的这个建议无疑让万千 Android 开发者开始关注、使用 Fragment。但随着使用 Fragment 的人数增多，Fragment 存在的各种问题也开始暴露，在各种 Android 社区中，已经开始有人质疑用 Fragment 替代 Activity 在应用开发中是否真的像 Google 说的那样有益。质疑 Fragment 的理由大体如下：\n\n- 在使用 Fragment 时，我们只能选择使用默认的构造方法，而不能自由地构造我们想要的构造方法。\n\n- 嵌套使用 Fragment 很容易出现各种奇奇怪怪的 Bug，抑或是受到种种让人郁闷的限制。\n\n- Fragment 自身的生命周期非常复杂。\n\n更让人哭笑不得的是，让这部分开发者坚定地站在“反 Fragment”队伍中的原因竟然是：在开发过程中使用 Fragment 完全不能让这部分 Android 开发者感受到使用 Fragment 能给他们带来的便利和愉悦；相反，使用 Fragment 给他们带来的是无尽的困然和烦恼。真不知道 Google 看到这些批评 Fragment 的帖子会想什么…………\n\n但在我们的 Android 学习社区 [Big Nerd Ranch](http://www.bignerdranch.com/) 中，我们制作的 [Android bootcamp](https://training.bignerdranch.com/classes/android-bootcamp) 课程一直坚持使用 Fragment ，并且为大家介绍 Fragment 给我们带来的种种便利和好处（特别是 Android 开发的新手），此外，我们还在我们做的 [资讯项目](http://www.bignerdranch.com/we-develop) 中广泛地使用了 Fragment。\n\n然而，虽然我们是 Fragment 的忠实粉丝，但本着不断学习和探索新知识的心态，我们还是对现有的 Android 库进行了相当多的研究和探索，以求能够找到 Fragment 的最佳替代物，帮助这些备受煎熬的 Android 开发者早日脱离苦海，走向 Android 开发的美丽新世界。\n\n## 进入Flow和Mortar \n奉行着想毁灭世界上所有 Fragment 的信条，Square 大概在一年前介绍了两个全新的库： Flow 和 Mortar。作为反 Fragment 教主，Square 还创造了许多很好的库：\n\n*\t[Dagger](http://square.github.io/dagger/)\n*\t[Retrofit](http://square.github.io/retrofit/)\n*\t[Picasso](http://square.github.io/picasso/)\n*\t[Otto](http://square.github.io/otto/)\n*\tAnd so many [more](https://github.com/square)\n\n我只想说，我相信他们，我认为任何来自Square的资源可能是有用的或者至少是有趣的，所以他们的项目都值得一看。\n\n在我们深入了解这些库之前我想提醒大家的是，Square只在他们内部的一小部分项目中使用这些库，并且我在写本文章时这些库还在预发布阶段。也就是说，这两个库在最近几个月取得了积极的进展，这预示着一个值得尊敬的未来，虽然库就像流沙，随时可能改变，崩溃甚至停止发布，但库所依赖的核心架构原则是一成不变的。\n\n##体系架构\n 首先，我们先来看下 Android 应用的体系架构，在 Android  Honeycomb 被使用之前，甚至在Fragment 出现之前，开发 Android 应用的标准模式是创建许多 Activity。在那个时候最常见的现象是：大多数开发者都没有规范地遵循 MVC模式进行开发，不过这也很正常。因为模型(Model)依赖于数据，传统的一些数据库或者是以 JSON 的形式存储的 Web 请求，抑或是各种各样的java对象枚举。开发者们很高兴地通过 XML 布局去为 Activity 设置 View ，而且 View 的控制器就是每一个屏幕显示的 Actvitiy 自身。\n\n虽然这只是一个简要的概述，但是你能从中了解到 Android 与 MVC 模式是如何自然契合的。\n![mvc-pre](http://www.bignerdranch.com/img/blog/2015/02/mvc-pre.png)\n\n随着 Fragment 在 Honeycomb 中的出现，Android 团队也让处理不同形式的事件变得更简单。到了今天，标准的 Android 应用架构已经转变为由一小部分的 Activity 和 许多 Fragment 构成，使得我们的 App 能够在 手机、平板、智能手表甚至是太空船上跨平台使用。\n![mvc-post](http://www.bignerdranch.com/img/blog/2015/02/mvc-post.png)\n\n在这样的愿景下，有关 Fragment 的一切都是美好的，Fragment 变得流行起来，将一个 Activity 分解为几个 Fragment 是被提倡的。除此以外，即使 Activity 常常被简化为 Fragment 的持有者，或者是 Fragment 和 系统之间的接口，Android 应用的架构仍然遵循着 MVC 模式。\n\n但到底是 Activity 不能实现我们 App 跨平台使用的愿望，还是我们没有用正确的方式使用 Activity呢？也许，如果将 Activity 与 自定义 View结合在一起使用，说不定不需要 Fragment 就能让 Activity 实现跨平台使用的目标呢。使用 Flow 和 Mortar库背后的关键目的就是探索这个问题，并得到确切的答案。Flow 和 Mortar 的工作通过用自定义 View 代替 Fragment，并使用注入自定义 View 中的特定 Controller 对象，控制视图，以此允许我们通过 View 来代替 Fragment 完成工作。\n![mvc-no-fragments](http://www.bignerdranch.com/img/blog/2015/02/mvc-no-fragments.png)\n\n我们将在我们的讨论中构建这个图的中间部分，弄清楚如何在不使用 Fragment 的情况下把不同的视图碎片拼凑到一个 Activity 里。我们会看着标准MVC架构演变成完全不同的东西，这将大量涉及到咱们的Flow和Mortar。\n\n那么，Flow和Mortar到底是什么？它们又是如何起作用的呢？\n\n##Flow\nFlow 将一个应用分成一个逻辑上的 Screen组合，Screen不是任何形式的特殊的库对象，而是一个被创造来代表我们应用视图的普通java对象（POJO）。每一个Screen是这个app里面自包含的段，他们有自己的功能和意图。一个Screen的用处和传统Activity的用处没有什么不同，应用程序中的每一个Screen都对应于一个特定的位置，有点像一个Android中的URL网页或者是特定的隐式Intent。所以，Screen类可以被看作是应用中某个部分自带的可读定义。\n\n我们应用中的每一个Activity将会成为一个 Flow 对象，Flow对象在返回栈中保存了 Screen 的记录，和 Activity 或者 FragmentManager 的返回栈有些类似，通过这样的设计允许我们在 Screen 之间通过简单地实例化就可以轻松的切换，而不需要在应用中包含很多Activity。这里有一小部分 Activity（最好是一个）来持有这些 Screen。他们之间的关系下图类似：\n![screen](http://www.bignerdranch.com/img/blog/2015/02/screen.png)\n\n如果我们想切换到一个新的 Screen，我们只需简单地实例化这个 Screen，并且告诉我们 Flow 对象帮助我们切换为这个 Screen。除此以外，正如我们所期待的，Flow 被实例化后也会实现 goBack() 和 goUp() 方法。然而，许多开发者都把 Java 中的 goto 语句看作洪水猛兽，但事实上 Java 中的 goto 语句并没有它听起来那么恐怖。\n![flow](http://www.bignerdranch.com/img/blog/2015/02/flow.png)\n\n从本质上看，Flow 的作用仅仅是在 App 中告诉我们将要切换到哪一个 Screen。而这样设计的好处在于，Flow 通过这样的设计让我们能够方便地在我们定义的各种不同的自定义 View 中切换，并使我们免受在 Activity 或 Fragment 需要考虑的种种麻烦，让我们把注意力都集中在处理 View上。Flow 为我们创造了一个简单，方便，以 View 为中心的应用架构。\n\n##Mortar\nMortar是一个专注拖拽和依赖注入的库，Mortar 用以下几个不同的部分将一个应用分为可组合的模块：Blueprints, Presenters and a boatload of custom Views。\n\nMortar App里的每一个部分（在这里指的是每一个 Screen，因为我们在使用 Flow）都由 Blueprint 定义，并赋予他们一个私有的 Dagger 模块。它看起来有点像是下面这样的\n\n![blueprint](http://www.bignerdranch.com/img/blog/2015/02/blueprint.png)\n\nFlow 和 Mortar 结合在一起使用的效果很好，我们只需要调节我们的 Screen 类实现去 Mortar 提供的 Blueprint 接口，然后它就会给我们一个可以自由使用的 Dagger 作用域。\n\n![presenters](http://www.bignerdranch.com/img/blog/2015/02/presenters.png)\n\nPresenter 是一个拥有简单生命周期和伴随其生命周期的 Bundle 的 View 私有对象，主要被用作该 View 的控制器。每一个 View 都有存在于对应的 Screen （还有 Blueprint）中，与 View 自身相关联的 Presenter。因为 Presenter 只能作用于他所在的 Screen，所以当我们使用 Flow 进入一个新的 Screen，Presenter（在我们这个架构中非常重要的一环） 很可能会被 Java 的垃圾回收机制自动回收掉。此外，在 Mortar 作用域中的 Dagger 将与自动垃圾回收机制结合在一起，使得我们 App 能更好的管理、使用其内存——其中原因当然是：当前没有被使用的控制器对象都被我们回收掉了。而在传统的 Activity 开发中，Fragment 和 Activity 的切换过程中，不经意的垃圾回收并不能很好的被注意和提防。\n\n由于自定义 View 在我们的架构中被频繁地使用，以至于我们只需要通过 Dagger 简单地注入所有重要的模型数据，然后使用与 View 关联的 Presenter 去控制 View 本身。即使配置被改变，Presenters 也不会消失，而且我们还非常了解与 Activity 生命周期相关的知识，使得 Presenters 在进程被杀死之后还能被恢复。事实上，Presenter 与 Activity onSavedInstanceState() 方法的 bundle 钩连在一起，使得它能够用与 Activity 相同的机制储存和读取配置改变后产生的数据。而 Presenter 的生命周期非常简单，只有四个回调方法：\n\n- onEnterScope(MortarScope scope)\n- onLoad(Bundle savedInstanceState)\n- onSave(Bundle outState)\n- onExitScope()\n\n完全没有 Fragment 那样复杂的生命周期，这可不是我吹的！\n\n文章写到这里，你会发现在 Flow 和 Mortar 中有许多发生改变的部分，新的术语和类，还有新的使用规范，这难免会让人一头雾水。所以为了方便大家的理解，总的来说，我们需要重视的是下面几个部分：\n\n- Screen: 在应用导航层次结构中的一个特殊存在，用来代表我们视图的对象\n- Blueprint: 应用中具有私有的 Dagger 模块的部分\n- Presenter: 一个 View 控制器对象\n- Custom Views: 通过 Java 代码定义的 View，当然，用 XML 定义也是很常见的\n\nHere’s what our final Mortar and Flow architecture looks like:\n\n我们 Mortar 和 Flow 整个体系架构将会如下所示：\n\n![](https://www.bignerdranch.com/img/blog/2015/02/mortar-and-flow.png)\n\n抛弃了对 MVC 模式的执念，这个架构在完成之后变得更像 MVP 模式。这样巨大的转变使得新的架构需要关注如何处理应用在运行时配置信息改变的问题，例如：旋转。在 MVC 模式中，我们的控制器（Activity 和 Fragment）会随着我们的 View 一起被杀死。然而，在 MVP 模式中，我们只有 View \n被杀死，又在需要它的时候重现它。挺有趣的对吧？\n\n![](https://www.bignerdranch.com/img/blog/2015/02/mvp.png)\n\n## 积极的反馈 \n\n为了摆脱 Fragment，Square 付出了无数的汗水去进行重新架构和设计，并完成了 Mortar 和 Flow库，他们当然会获得相应的回报，接下来我就给大家介绍这两个库给我们带来的好处吧。\n\n使用 Mortar 和 Flow 库强迫我们创建了一个符合 MVP 模式设计的模块化 App 结构，通过这样做能有效地帮助我们保持代码的整洁。\n\n通过对我们自定义 View 和 Presenters 的依赖注入，测试变得更简单了\n\n动画能够在 View 层被处理，而不用像从前在 Activity 和 Fragment 中使用时那样担心动画会出现Bug\n\nMortar 在 View 和 Presenter 层中自动进行垃圾回收以处理其作用域，意味着应用能更有效地利用内存\n\n## 可优化的空间 \n尽管 Flow 和 Mortar 给我们带来了许多好处，但是它们也还存在一些问题：\n\n**想要熟练使用 Flow 和 Mortar，需要面对一条陡峭的学习曲线。**在你真正理解这两个库的设计思想和原理之前，它们的使用模式看起来非常复杂，如果你想要将他们用的得心应手，无疑需要大量的探索和实验，此外，这些库并不是为初学者提供的，我们更建议初学者先学习如何正确和有效地使用 Activity 和 Fragment，我可不是吓唬你们，这样跟你们说吧，就算是 Android 开发大神，在面对这些库时仍需要花费大量的精力和时间去学习有关设计模式的知识，才能真正理解这个库。\n\n**如果你正准备使用 Mortar 和 Flow 库，你真的要全面了解它们的用法。**因为让它和标准的“少使用 Fragment”开发模式相互作用是很困难的。如果你想修改一个已经写好的项目，让它使用 Mortar 和 Flow，虽然不是不可能的，但是完成这个目标的过程会是非常漫长和艰难的。\n\n**这里还存在无数的模板和配置信息需要被处理。**而这正是我最大的担忧，在使用这些新的类和接口时，我常常觉得被淹没在无趣的代码海洋里，因为这些代码都被设计成和其中的各个类、接口钩连在一起，而这也的设计让我觉得这两个库并没有像我期待的那样有趣。\n\n## 接下来呢 \n不过现在 Mortar 和 Flow 库都处于预发布阶段，现在也没有官方发布的版本。这意味着 Square 还在处理这两个库存在的问题，改动和更新，但这同样也意味着它们还需要许多时间作改进，才能真正投入到使用中。\n\n使用 Mortar 和 Flow 库是个有趣的体验，我非常享受使用各种新的库和寻找官方以 Fragment 为导向的应用结构的替代品，但我并不认为 Mortar 和 Flow 是 Android 寻找的替代 Fragment 的办法，毕竟 Fragment 可能在接下来的几个月或者几年中被修改。但我仍然希望这些项目能够引起更多人关注，并且继续优化，我肯定会继续关注他们的最新进展的，希望大家继续关注我的博客哦。\n"
  },
  {
    "path": "androidweekly/kotlin-for-android简介/readme.md",
    "content": "kotlin-for-android简介(1)\n---\n\n>\n* 原文链接 : [Kotlin for Android (I): Introduction\n](http://antonioleiva.com/kotlin-for-android-introduction/)\n* 译者 : [canglangwenyue](https://github.com/canglangwenyue) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\nKotlin是众多基于JVM的语言之一，它一开始是作为android 开发中java语言的可能的代替者出现的。java是世界上使用最多的语言之一，而伴随着其他语言的发展使得程序员编程越来越容易，但是java并没有尽快地向预期目标发展。\n\n###Kotlin简介\nKotlin是JetBrains创造的基于JVM的语言，JetBrains是IntelliJ的缔造团队。\t\nKotlin是一个拥有很多函数编程特点的面向对象的编程语言。\n\n###为什么要用Kotlin\n我首先声明我并没有使用Kotlin很长时间，我几乎是在学习的同时写了这些文章的。我并没有尝试任何其它的替\t代语言，例如Go和Scala，所以如果你是真的考虑换一种开发语言的话，我建议你去搜索一下其他人对这些\t语言的评价。一个使用Scala开发android的例子可以在[47deg Github site](http:/\t\n47deg.github.io/translate-bubble-android/)找到。\n\n\n以下是我选择学习Kotlin的原因：   \n* **学习曲线相对较快**：以Scala作为例子进行比较，我们是向着更简单的方向前进。Kotlin有更多的限制，但是如果你没有学习过一门现代编程语言的话，Kotlin更容易学习。\n* **轻量级**：与其他语言相比Kotlin的核心库更小。这很重要，因为android函数数量限制(函数数量不能大于64k)是一个问题，虽\n然有一些选择来解决这个问题，例如proguard 或 multidexing，但是这些解决方案会加复杂度，并导\n致调试时花费更多的时间。引入Kotlin核心库添加了不到7000个方法，大致和support－v4一样。\n* **高交互性**：Kotlin和其它java库协调使用的特别好，并且交互操作很简单。这是Kotlin团队\n在开发新语言是的主要理念之一。他们想在使用Kotlin开发时并不用重写之前所有用java写的代码，所以，Kotlin和java交互的能力必须非常高。\n* **与AS和Gradle完美结合**：我们有一个IDE的插件和另一个属于Grade的插件，因此，用Kotlin进行\n\tandroid编程并不困难。\n\n在开始任何争论之前我建议你看一下Jake Wharton写的一个有趣的文档[the use of Kotlin for Android development](https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/edit?hl=es&forcehl=1&pli=1)。\n\n###Kotlin的优点\n\n####1. 可读性更高，更简洁\n使用Kotlin，可以更容易的避免创建模版型代码，因为大多数经典的情景都默认包含在Kotlin中。       \n例如，在java中，我们想要创建一个典型的data class时需要这样做：    \n\n```java\t\n\tpublic class Artist {\n    private long id;\n    private String name;\n    private String url;\n    private String mbid;\n \n    public long getId() {\n        return id;\n    }\n \n    public void setId(long id) {\n        this.id = id;\n    }\n \n    public String getName() {\n        return name;\n    }\n \n    public void setName(String name) {\n        this.name = name;\n    }\n \n    public String getUrl() {\n        return url;\n    }\n \n    public void setUrl(String url) {\n        this.url = url;\n    }\n \n    public String getMbid() {\n        return mbid;\n    }\n \n    public void setMbid(String mbid) {\n        this.mbid = mbid;\n    }\n \n    @Override public String toString() {\n        return \"Artist{\" +\n                \"id=\" + id +\n                \", name='\" + name + '\\'' +\n                \", url='\" + url + '\\'' +\n                \", mbid='\" + mbid + '\\'' +\n                '}';\n    }\n}\n```\n\n那么在Kotlin需要多少代码呢？仅仅是下面这个简单的数据类：     \n\n```java\n\n\tdata class Artist(\n    var id: Long, \n    var name: String, \n    var url: String, \n    var mbid: String)\n```\n\n####2. 空指针安全\n当我们用java开发时，我们的大多数代码是要进行类型检查的，如果我们不想出现**unexpected\nNullPointerException**的话,我们就要在运行代码之前持续的检查是否有对象为null。Kotlin，和其它语\n言一样，是空指针安全的，因为我们可以通过安全的调用操作来准确的声明一个object可以为null。\n\n我们可以这样做：\n\n```java\n\t\n\t//This won´t compile. Artist can´t be null\n\tvar notNullArtist: Artist = null\n \n\t//Artist can be null\n\tvar artist: Artist? = null\n \n\t// Won´t compile, artist could be null and we need to deal with that\n\tartist.print()\n \n\t// Will print only if artist != null\n\tartist?.print()\n \n\t// Smart cast. We don´t need to use safe call operator if we previously checked \tnullity\n\tif (artist != null) {\n\t    artist.print()\n\t}\n\t \n\t// Only use it when we are sure it´s not null. Will throw an exception otherwise.\n\tartist!!.print()\n \n\t// Use Elvis operator to give an alternative in case the object is null\n\tval name = artist?.name ?: \"empty\"\n```\n\n####3. 扩展方法\n我们可以给任何类添加新方法。这比我们在project中使用的工具类可读性更高。例如：我们可以给Fragment添加一个新方法来显示Toast。\n\n```java\nfun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {\n    Toast.makeText(getActivity(), message, duration).show()\n}\n```\n我们可以这样使用：\n\n```java\n\tfragment.toast(\"Hello world!\")\n```\n\n####4. 支持函数式编程\n如果我们可以不用在我们需要的时候每一次都创建一个listener，就像创建一个click listener那样的操作，\n而是仅仅定义我们想要做什么？这种想法的确可以实现，它的实现得益于**lambda**d的使用：  \n\n```java\n\tview.setOnClickListener({ toast(\"Hello world!\") })\n```\t\n\t\n###Kotlin目前存在的限制\nKotlin 依旧在发展，虽然它相对稳定，并且final release版本就很快发布，但是Kotlin在进行android相关开发的时候还是有些限制的。   \n\n* **交互性与自动代码生成**：一些有名的android Libraries，例如Dagger 或 Butterknife，他们依赖于自动\n代码生成，这种情况下如果有名字冲突的话将无法进行代码生成。Kotlin正在解决这个问题，所以这个问题也许\n会很快解决。无论如何，我将在接下来的文章里阐明，Kotlin语言的表达能力会让我们觉得不再需要那么多的\nLibraries。\n\n* **声明自定义View比较困难**：Kotlin类只能声明一个构造函数，然而自定义View通常需要三个。如果我\n们使用代码来创建View的话可以避免这个问题，但对于使用XML文件来声明View的话就不能满足需求了。最容易的变通方式是用java来声明这些\n自定义View的实现类，然后通过Kotlin来使用它们。Kotlin团队许诺将在M11 release解决这个问题。\n**Update: Kotlin M11 is out and now includes [secondary constructors](http://kotlinlang.org/docs/reference/classes.html#constructors)**\n\n* **android 下Junit测试**：AS 1.1中介绍的新特性并不适用与Kotlin。顺便说说，系统测试和Junit 测试对于纯Kotlin项目是完全可用。\n\n###结论\n对于android apps 开发，Kotlin是一个非常有趣的java替代者。下一篇文章将会描述如何用Kotlin新建一\n个project，和如何更好的适用Kotlin来使得android开发更加简单。敬请关注！\n\n"
  },
  {
    "path": "androidweekly/readme.md",
    "content": "# 不要将文章放到这个文件夹下啦!请放到最新的issue文件夹中,谢谢大家。\n"
  },
  {
    "path": "androidweekly/一个支持多设备的Android参考应用/readme.md",
    "content": "一个支持多设备的Android参考应用\n---\n>\n* 原文链接 : [a-new-reference-app-for-multi-device](http://android-developers.blogspot.com/2015/03/a-new-reference-app-for-multi-device.html)\n* 译者 : [Mr.Simple & Sophie.Ping](https://www.github.com/bboyfeiyu)\n\n现在你可以把你的app的好用之处分享给你的用户，不管他们身处何地，手上拿着何种设备。今儿我们会发布一个参考示例，展示一下如何把这种服务运用到一个在多个Android form-Factor运作的app上。这个示例叫做Universal Music Player，它是一个准系统但是参考功能齐全，在单个代码库里支持多种设备和形式因素。它能与Android Auto、Android Wear和Google Cast设备兼容。你可以试试把你的app适配到你的用户，无论他在哪里，无论他手上拿的是手机，手表，电视，汽车还是其他的设备。\n![android](http://img.blog.csdn.net/20150322112056022)  \n\n锁屏时的播放控制和专辑封面\n应用toolbar上的Google Cast 图标\n\n![auto](http://img.blog.csdn.net/20150322111453169)     \n使用Android Auto控制播放\n\n![watch](http://img.blog.csdn.net/20150322111518047)      \n通过Android Wear Watch控制播放\n\n\n本示例运用了Android 5.0 Lollipop的几个新功能，比如MediaStyle通知，MediaSession和MediaBrowserService.这几个新功能使得在多个设备上使用单一app版本操作媒体浏览和回放变得更容易。\n一起来看看源代码吧，让你的用户以他们的方式爱上你的app。\n作者：Renato Mangini，高级开发工程师，Google开发者平台团队成员"
  },
  {
    "path": "androidweekly/一种在android中实现MVP模式的新思路/readme.md",
    "content": "一种在android中实现MVP模式的新思路\n---\n\n>\n* 原文链接 : [android-mvp-an-alternate-approach](http://blog.cainwong.com/android-mvp-an-alternate-approach/)\n* 译者 : [FTExplore](https://github.com/FTExplore)\n* 校对 : [sundroid](https://github.com/sundroid)\n\n今天我想分享我自己在Android上实现MVP模式的方法。如果你对MVP模式还不熟悉或者你不清楚为什么要在Android应用中使用MVP模式，我建议你先阅读以下[这篇维基百科的文章](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)和[这篇博客](http://antonioleiva.com/mvp-android/)。\n\n## 使用Activity和Fragment作为视图层(View)真的合适么?\n\n目前很多使用了MVP模式的android 项目,基本上都是将activity和fragment作为视图层来进行处理的.而presenters通常是通过继承自被视图层实例化或者注入的对象来得到的. 诚然,我同意说,这种方式可以节省掉很多让人厌烦的\"import android.*.*\"语句, 并且将presenters从activity的生命周期中分割出来以后, 项目后续的维护会变得简便很多.这种思路是正确的， 但是,从另一个角度来说, activity 有一个很复杂的生命周期(fragment的生命周期可能会更复杂), 而这些生命周期很有可能对你项目的业务逻辑有非常重大的影响. Activity 可以获取上下文环境和多种android系统服务. Activity中发送Intent，启动Service和执行FragmentTransisitons等。而这些特性在我看来绝不应该是视图层应该涉及的领域(视图的功能就是现实数据和从用户那里获取输入数据，在理想的情况下，视图应该避免业务逻辑). \n\n基于上述的原因，我对目前的主流做法并不赞同，所以我在尝试使用Activity和Fragment作为Presenters。\n\n## 使用Activity和Fragment作为presenters的步骤\n### 1. 去除所有的view    \n\n将Activity和Fragment作为presenter最大的困难就是如何将关于UI的逻辑抽取出来.我的解决方案是: 让需要作为presenter的activity 或者 fragment来继承一个抽象的类(或者叫\"基类\"), 这样关于View 各种组件的初始化以及逻辑,都可以在继承了抽象类的方法中进行操作，而当继承了该抽象类的class需要对某些组件进行操作的时候，只需要调用继承自抽象类的方法，就可以了。      \n\n那么抽象类怎么获取到的view组件呢？在抽象类里面会有一个实例化的接口，这个接口里面的init()方法就会对view进行实例化，这个接口如下：\n\n```java\npublic interface Vu {\n\n\tvoid init(LayoutInflater inflater, ViewGroup container);\n\tView getView();\n}\n```\n\n\n正如你所见，Vu定义了一个通用的初始化例程，我可以通过它来实现一个容器视图，它也有一个方法来获得一个View的实例，每一个presenter将会和它自己的Vu关联，这个presenter将会继承这个接口（直接或者间接的去继承一个来自Vu的接口）\n\n### 2. 创建一个presenter基类 (Activity)  \n\n有了Vu接口，我们可以通过构建一系列的class来操纵很多不同的view组件，这些class 使用Vu接口来初始化View组件，并通过继承的方式给子类以操纵view组件的方法，以此来达到将ui 逻辑剥离出activity的目的。在下面的代码中，你可以看到，我覆写了activity的onCreate 、 onCreateView、onDestroy 、 onDestroyView，通过对这些方法的覆写，就可以对Vu的实例化和销毁进行精确的控制（vu.init()就是实例化一个view组件）。onBindVu() 和onDestoryVu()是控制view生命周期的两个方法。通过对actiivty中相关方法的覆写达到控制组件的生命周期的目的（具体看下面的代码，你就明白了）， 这样做的好处就是无论是activity 还是 fragment， 其用与控制view组件创建和销毁的语句是一样的（尽量避免定义多余的函数）。这样的话，二者之间的切换也会减少一定的阻力（也许你今天的需求是用fragment实现的，但是第二天发现使用fragment会有一个惊天bug，译者本人就遇到过）。     \n\n```java\npublic abstract class BasePresenterActivity<V extends Vu> extends Activity {\n\n    protected V vu;\n\n    @Override\n    protected final void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        try {\n            vu = getVuClass().newInstance();\n            vu.init(getLayoutInflater(), null);\n            setContentView(vu.getView());\n            onBindVu();\n        } catch (InstantiationException e) {\n            e.printStackTrace();\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    protected final void onDestroy() {\n        onDestroyVu();\n        vu = null;\n        super.onDestroy();\n    }\n\n    protected abstract Class<V> getVuClass();\n\n    protected void onBindVu(){};\n\n    protected void onDestroyVu() {};\n\n}\n```\n\n### 3. 创建一个基本的presenter(Fragment)\n\n```java\npublic abstract class BasePresenterFragment<V extends Vu> extends Fragment {\n\n    protected V vu;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    @Override\n    public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View view = null;\n        try {\n            vu = getVuClass().newInstance();\n            vu.init(inflater, container);\n            onBindVu();\n            view = vu.getView();\n        } catch (java.lang.InstantiationException e) {\n            e.printStackTrace();\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        }\n        return view;\n    }\n\n    @Override\n    public final void onDestroyView() {\n        onDestroyVu();\n        vu = null;\n        super.onDestroyView();\n    }\n\n    protected void onDestroyVu() {};\n\n    protected void onBindVu(){};\n\n    protected abstract Class<V> getVuClass();\n\n}\n\n```\n\n\n### 4. 一个简单的例子\n如前文所述，我们已经确定了一个框架，现在就来写一个简单的例子来进一步的说明. 为了避免篇幅过长，我就写一个“hello world”的例子。首先要有一个实现Vu接口的类：    \n\n```java\npublic class HelloVu implements Vu {\n\nView view;\nTextView helloView;\n\n@Override\npublic void init(LayoutInflater inflater, ViewGroup container) {\n    view = inflater.inflate(R.layout.hello, container, false);\n    helloView = (TextView) view.findViewById(R.id.hello);\n}\n\n@Override\npublic View getView() {\n    return view;\n}\n\npublic void setHelloMessage(String msg){\n    helloView.setText(msg);\n}\n\n}\n```\n下一步，创建一个presenter来操作这个TextView\n```\npublic class HelloActivity extends BasePresenterActivity {\n@Override\nprotected void onBindVu() {\n    vu.setHelloMessage(\"Hello World!\");\n}\n\n@Override\nprotected Class<HelloVu> getVuClass() {\n    return HelloVu.class;\n}\n\n}\n```\n\nOK,这样就大功告成了！！是不是很简便！\n\n### 等等...耦合警告!\n\n你可能注意到我的HelloVu类直接实现了Vu接口，我的Presenter的getVuClass方法直接引用了实现类。传统的MVP模式中，Presenter是要通过接口与他们的View解耦合的。因此，你也可以这么做。避免直接实现Vu接口，我们可以创建一个扩展了Vu的IHelloView接口，然后使用这个接口作为Presenter的泛型类型。这样Presenter看起来应该是如下这样的 : \n\n```java\npublic class HelloActivity extends BasePresenterActivity<IHelloVu> {\n\n    @Override\n    protected void onBindVu() {\n        vu.setHelloMessage(\"Hello World!\");\n    }\n\n    @Override\n    protected Class<HelloVuImpl> getVuClass() {\n        return HelloVuImpl.class;\n    }\n}\n```\n\n在我使用强大的模拟工具过程中，我个人并没有看到在一个接口下面实现Vu所带来的好处。但是对于我来说一个好的方面是，有没有Vu接口它都能够工作，唯一的需求就是最终你会实现Vu。\n\n## 5. 如何进行测试\n通过以上几步，我们可以发现，去除了UI逻辑之后，Activity变得非常简洁。并且，相关的测试\n也变的非常异常的简单。请看如下的代码：\n\n```java\npublic class HelloActivityTest {\n    HelloActivity activity;\n    HelloVu vu;\n\n    @Before\n    public void setup() throws Exception {\n        activity = new HelloActivity();\n        vu = Mockito.mock(HelloVu.class);\n        activity.vu = vu;\n    }\n\n    @Test\n    public void testOnBindVu(){\n        activity.onBindVu();\n        verify(vu).setHelloMessage(\"Hello World!\");\n    }\n    }\n```\n\n以上代码是一段标准的jnuit单元测试的代码，不需要在android设备中部署运行，只需要在编译环境中即可测试。大幅度的提高了测试效率。但是，在测试某些方法的时候，你必须要使用android设备，例如当你想测试activity生命周期中的resume()方法。在缺乏设备环境的时候，super.resume()会报错。为了解决这个问题，可以借鉴一些工具，例如Robolectric、还有android gradle 1.1 插件中内置的testOptions { unitTests.returnDefaultValues = true }。此外，你仍然可以将这些生命周期也抽离出来。例如如下:\n\n```java\n@Override\nprotected final void onResume() {\n    super.onResume();\n    afterResume();\n}\n\nprotected void afterResume(){}\n```\n\n\n现在，你可以在没有android设备的情况下，快速的测试了！   \n\n## 意外收获：使用adapter作为presenter\n将Activity作为presente已经足够狡猾了吧？使用adapter作为presenter，你想过没有？\n好吧，请看如下的代码：\n\n```java\npublic abstract class BasePresenterAdapter extends BaseAdapter {\nprotected V vu;\n\n@Override\npublic final View getView(int position, View convertView, ViewGroup parent) {\n    if(convertView == null) {\n        LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n        try {\n            vu = (V) getVuClass().newInstance();\n            vu.init(inflater, parent);\n            convertView = vu.getView();\n            convertView.setTag(vu);\n        } catch (InstantiationException e) {\n            e.printStackTrace();\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        }\n    } else {\n        vu = (V) convertView.getTag();\n    }\n    if(convertView!=null) {\n        onBindListItemVu(position);\n    }\n    return convertView;\n}\n\nprotected abstract void onBindListItemVu(int position);\n\nprotected abstract Class<V> getVuClass();\n}\n```\n\n如上代码，使用adapter作为presenter其实和activity或者fragement几乎是一样的，只有一点明显的区别就是，我把onBingVu替换成了onBindListItemVu（接受int参数）,其实我是借鉴了[ViewHolder模式](http://developer.android.com/training/improving-layouts/smooth-scrolling.html)。\n\n## 总结和一个demo\n我在这篇文章里介绍了我自己的一个实现MVP的方法。我非常期待其他android开发者对我的这套方法的反馈。我切实的想知道我的方法是否可行？我已经把这套思路用在一个框架的开发上，并且即将公布，与此同时，我在github上面有一个demo项目，感兴趣的人可以去看一下https://github.com/wongcain/MVP-Simple-Demo\n\n"
  },
  {
    "path": "androidweekly/一种更清晰的Android架构/readme.md",
    "content": "一种更清晰的Android架构\n---\n\n>\n* 原文链接 : [Architecting Android…The clean way?](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)\n* 译者 : [Mr.Simple & Sophie.Ping](https://www.github.com/bboyfeiyu)\n\r过去几个月以来，通过在Tuenti网站上与@pedro_g_s和@flipper83（安卓开发两位大牛）进行友好讨论之后，我决定写这篇关于架构安卓应用的文章。     \n\r我写这篇文章的目的是想把我在过去几个月体悟到的小方法以及在调查和应用中学到的有用的东西分享给大家。\r\r## 入门指南\r大家都知道要写一款精品软件是有难度且很复杂的：不仅要满足特定要求，而且软件还必须具有稳健性，可维护、可测试性强，并且能够灵活适应各种发展与变化。这时候，“清晰架构”就应运而生了，这一架构在开发任何软件应用的时候用起来非常顺手。\r\r这个思路很简单：简洁架构 意味着产品系统中遵循一系列的习惯原则：\n\r\n* 框架独立性\n* 可测试\n* UI独立性\n* 数据库独立性\n* 任何外部代理模块的独立性  \n\r![arch](https://camo.githubusercontent.com/dd69e725f30c30031dea279adc5a9d09ea3432f2/687474703a2f2f6665726e616e646f63656a61732e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30392f636c65616e5f617263686974656374757265312e706e67)\r\r我们并不要求一定要用四环结构（如图所示），这只是一个示例图解，但是要考虑的是依赖项规则：源码依赖项只能向内指向，内环里的所有项不能了解外环所发生的东西。  \n\r以下是更好地理解和熟悉本方法的一些相关词汇：     \r\r* Entities：是指一款应用的业务对象\r* Use cases：是指结合数据流和实体中的用例，也称为Interactor\r* Interface Adapters： 这一组适配器，是负责以最合理的格式转换用例（use cases）和实体（entities）之间的数据，表现层（Presenters ）和控制层（Controllers ），就属于这一块的。\n* Frameworks and Drivers: 这里是所有具体的实现了：比如：UI，工具类，基础框架，等等。\r\r想要更具体，更生动丰富的解释，可以参考[这篇文章](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)或者[这个视频](https://vimeo.com/43612849)。\n\r\r## 场景\r我会设置一个简单的场景来开始：创建一个简单的小app，app中显示从云端获取的一个朋友或用户列表。当点击其中任何一个时，会打开一个新的窗口，显示该用户的详细信息。这里我放了一段视频，大家看看[这个视频 (需翻墙)](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)大概就可以对我所描述的东西了解个大概了。 \n\r\r## Android应用架构\r这一对象遵循关注分离原则，也就是通过业务规则让内环操作对外环事物一无所知，这样一来，在测试时它们就不会依赖任何的外部元素了。    \r要达到这个目的，我的建议就是把一个项目分成三个层次，每个层次拥有自己的目的并且各自独立于堆放运作。\r值得一提的是，每一层次使用其自有的数据模型以达到独立性的目的（大家可以看到，在代码中需要一个数据映射器来完成数据转换。如果你不想把你的模型和整个应用交叉使用，这是你要付出的代价）。    \n\r\r以下是图解，大家感受下：   \n\r![schema](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_android.png)\n\r> \r注：我并没有使用任何的外部库（除了用于json数据句法分析的gson和用于测试的junit, mockito, robolectric和espresso以外）。原因是它可以使这个示例更清晰。总之，在存储磁盘数据时，记得加上ORM、依赖注入框架或者你熟悉的任何工具或库，这些都会带来很大帮助。（记住：重复制造轮子可不是明智的选择）\n\r## 表现层 (Presentation Layer)\n\r表现层在此，表现的是与视图和动画相关的逻辑。这里仅用了一个Model View Presenter（下文简称MVP），但是大家也可以用MVC或MVVM等模式。这里我不再赘述细节，但是需要强调的是，这里的fragment和activity都是View,其内部除了UI逻辑以外没有其他逻辑，这也是所有渲染的东西发生的地方。\r本层次的Presenter由多个interactor（用例）组成，Presenter在 android UI 线程以外的新线程里工作，并通过回调将要渲染到View上的数据传递回来。\r![mvp](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_mvp.png) \n \r如果你需要一个使用MVP和MVVM的[Effective Android UI](https://github.com/pedrovgs/EffectiveAndroidUI/)典型案例，可以参考我朋友Pedro Gómez的文章。\r\r## 领域层 (Domain Layer)\n\r这里的业务规则是指所有在本层发生的逻辑。对于Android项目来说，大家还可以看到所有的interactor（用例）实施。这一层是纯粹的java模块，没有任何的Android依赖性。当涉及到业务对象时，所有的外部组件都使用接口。    \n\r![domain](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_domain.png) \n \r## 数据层 (Data Layer)\r应用所需的所有数据都来自这一层中的UserRepository实现（接口在领域层）。这一实现采用了[Repository Pattern](http://martinfowler.com/eaaCatalog/repository.html)，主要策略是通过一个工厂根据一定的条件选取不同的数据来源。\r比如，通过ID获取一个用户时，如果这个用户在缓存中已经存在，则硬盘缓存数据源会被选中，否则会通过向云端发起请求获取数据，然后存储到硬盘缓存。\r这一切背后的原理是由于原始数据对于客户端是透明的，客户端并不关心数据是来源于内存、硬盘还是云端，它需要关心的是数据可以正确地获取到。\r\r![data](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_data.png)   \n\r>\r注：在代码方面，出于学习目的，我通过文件系统和Android preference实现了一个简单、原始的硬盘缓存。请记住，如果已经存在了能够完成这些工作的库，就不要重复制造轮子。\r\r## 错误处理\r这是一个长期待解决的讨论话题，如果大家能够分享各自的解决方案，那真真是极好的。\r我的策略是使用回调，这样的话，如果数据仓库发生了变化，回调有两个方法：onResponse()和onError(). onError方法将异常信息封装到一个ErrorBundle对象中: 这种方法的难点在于这其中会存在一环扣一环的回调链，错误会沿着这条回调链到达展示层。因此会牺牲一点代码的可读性。另外，如果出现错误，我本来可以通过事件总线系统抛出事件，但是这种实现方式类似于使用C语言的goto语法。在我看来，当你订阅多个事件时，如果不能很好的控制，你可能会被弄得晕头转向。\r\r\r## 测试\r关于测试方面，我根据不同的层来选择不同的方法:    \n\r*\t展示层 ( Presentation Layer) : 使用android instrumentation和 espresso进行集成和功能测试\r*\t领域层 ( Domain Layer) : 使用JUnit和Mockito进行单元测试；\r*\t数据层 ( Data Layer) : 使用Robolectric （ 因为依赖于Android SDK中的类 ）进行集成测试和单元测试。\r\r## 代码展示\r我猜你现在在想，扯了那么久的淡，代码究竟在哪里呢？ 好吧，这就是你可以找到上述解决方案的[github链接](https://github.com/android10/Android-CleanArchitecture)。还要提一点，在文件夹结构方面，不同的层是通过以下不同的模块反应的:     \n\r*\tpresentation: 展示层的Android模块\r*\tdomain: 一个没有android依赖的java模块\r*\tdata: 一个数据获取来源的android模块。\r*\tdata-test: 数据层测试，由于使用Robolectric 存在一些限制，所以我得再独立的java模块中使用。\r\r## 结论\r正如 Bob大叔 所说：“Architecture is About Intent, not Frameworks” ，我非常同意这个说法，当然了，有很多不同的方法做不同的事情（不同的实现方法），我很确定，你每天（像我一样）会面临很多挑战，但是遵循这些方法，可以确保你的应用会：   \n\r*\t易维护 Easy to maintain\r*\t易测试 Easy to tes.\r*   高内聚 Very cohesive.\r*\t低耦合 Decoupled. \t\r\r最后，我强烈推荐你去实践一下，并且分享你的经验。也许你会找到更好的解决方案：我们都知道，不断提升自己是一件件非常好的事。我希望这篇文章对你有所帮助，欢迎拍砖。\r\r## 参考资料\r<ol>\n<li>Source code: <a href=\"https://github.com/android10/Android-CleanArchitecture\">https://github.com/android10/Android-CleanArchitecture</a></li>\n<li><a href=\"http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html\">The clean architecture by Uncle Bob</a></li>\n<li><a href=\"http://www.infoq.com/news/2013/07/architecture_intent_frameworks\">Architecture is about Intent, not Frameworks</a></li>\n<li><a href=\"http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter\">Model View Presenter</a></li>\n<li><a href=\"http://martinfowler.com/eaaCatalog/repository.html\">Repository Pattern by Martin Fowler</a></li>\n<li><a href=\"http://www.slideshare.net/PedroVicenteGmezSnch/\">Android Design Patterns Presentation</a></li>\n</ol>\r\r"
  },
  {
    "path": "androidweekly/使用Robolectric的参数化测试/readme.md",
    "content": "使用Robolectric的参数化测试\n---\n\n>\n* 原文标题 : Parameterized testing with Robolectric\n* 原文链接 : [Parameterized testing with Robolectric](http://www.jayway.com/2015/03/19/parameterized-testing-with-robolectric/)\n* 译者 : [Lollypo](https://github.com/Lollypo) \n* 校对者: [Chaos](https://github.com/chaossss)   \n* 状态 :  校对完成\n\n在目前的项目中我们使用Robolectric为Android应用程序编写单元测试,它一直都干的不错。最近我需要编写一个测试用例,通过每次使用不同的测试数据，将同一个操作执行若干次，并由此断言：正确的动作能否发生是由数据决定的。\n\nJUnit对于这个情况提供了一个易于使用的选项，它叫做参数化测试-先定义测试数据，,然后使用参数化测试运行器来执行测试。这将创建一个该测试类的实例把测试数据中的每个元素传递到构造函数的参数中。\n\n事实证明，Robolectric有一个完全相同的`ParameterizedRobolectricTestRunner`(稍微调整以适应Robolectric)，而且它对于我的测试：验证应用程序从外部服务提供者接收到不同的错误代码的行为，做的非常好\n\n```java\n@RunWith(ParameterizedRobolectricTestRunner.class)\npublic class ContactServiceTest {\n \n    @ParameterizedRobolectricTestRunner.Parameters(name = \"ErrorCode = {0}\")\n    public static Collection<Object[]> data() {\n        return Arrays.asList(new Object[][]{\n                {105, 105_ERROR_MSG},\n                {113, 113_ERROR_MSG},\n                {114, 114_ERROR_MSG},\n                {134, 134_ERROR_MSG},\n                {137, 137_ERROR_MSG},\n                {999, DEFAULT_ERROR_MSG} // Bogus错误代码\n        });\n    }\n \n    private int errorCode;\n    private String expectedErrorMsg;\n \n    public ContactServiceTest(int errorCode, String errorMsg) {\n        this.errorCode = errorCode;\n        this.expectedErrorMsg = errorMsg;\n    }\n \n    @Test\n    public void when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user() {\n        // HTTP响应从服务包含定义的错误代码\n        Robolectric.addPendingHttpResponse(HttpStatus.SC_OK, buildFakeServiceResponse(errorCode)); \n        // 联系服务\n        mService.contactService();\n        // 使用awaitility等到错误消息显示给用户\n\t\t// 然后断言该错误代码与期望一致\n        await().until(getDisplayedErrorMsg(), is(expectedErrorMsg));\n    }\n```\n\n该测试用例将被执行6次，一旦遍历测试数据的每个元素，就会将打印的错误消息与特定错误代码定义的错误消息相比较。当创建测试报告时，每一个测试运行都将视为其自身的测试用例。\n\n添加了`name`参数到Parameters注解上将会整理测试结果。作为测试运行结果显示,测试用例的名称将会像下面这种情况下\n\n```java\nwhen_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 105]\nwhen_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 113]\nwhen_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 114]\n...\n```\n\n如需深入理解请查阅[JUnit Theories](https://github.com/junit-team/junit/wiki/Theories)以及[junit-quickcheck](https://github.com/pholser/junit-quickcheck)，一个好的生成测试数据的方法是在JUnit中自动生成（Robolectric也差不多），而不是由你自己定义。"
  },
  {
    "path": "androidweekly/使用RxJava.Observable取代AsyncTask和AsyncTaskLoader/readme.md",
    "content": "使用RxJava.Observable取代AsyncTask和AsyncTaskLoader\n---\n\n>\n* 原文链接 : [Replace AsyncTask and AsyncTaskLoader with rx.Observable – RxJava Android Patterns](http://stablekernel.com/blog/replace-asynctask-asynctaskloader-rx-observable-rxjava-android-patterns/)\n* 译者 : [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  校对完成\n\n\n在网上有很多关于RxJava入门指南的帖子，其中一些是基于Android环境的。但是，我想到目前为止，很多人只是喜欢他们所看到的这些，当要解决在他们的Android项目中出现的具体问题时，他们并不知道如何或者是为什么要使用RxJava。在这一系列的文章中，我想要探索在我工作过的一些依赖于RxJava架构的Android项目中的模式。\n\n在文章的开始，我想要处理一些Android开发者在使用RxJava的时候，很容易遇到的状况。从这个角度，我将提供更高级和更合适的解决方案。在这一系列的文章中，我希望可以听到其他开发者在使用RxJava的过程中解决类似的问题，或许他们和我发现的一样呢。\n\n#问题一：后台任务\nAndroid开发者首先遇到的挑战就是如何有效的在后台线程中工作，然后在UI线程中更新UI。这经常是因为需要从web service中获取数据。对于已经有相关经验的你可能会说：“这有什么挑战性？你只需要启动一个AsyncTask，然后所有的工作它就都给你做了。”如果你是这样想的，那么你有一个机会去改善这种状况。这是因为你已经习惯了这种复杂的方式并且忘记这本应该是很简洁的，或者是说你没有处理所有应该处理的边界情况。让我们来谈谈这个。\n\n##默认的解决方案：AsyncTask\nAsyncTask是在Android里面默认的处理工具，开发者可以做里面一些长时间的处理工作，而不会阻塞用户界面。(注意：最近，AsyncTaskLoader用来处理一些更加具体的数据加载任务，我们以后会再谈谈这个)\n\n表面上，这似乎很简单，你定义一些代码在后台线程中运行，然后定义一些代码运行在UI线程中，在后台任务处理完之后，它在UI线程会处理从后台任务传递过来的执行结果。\n\n```java\n\tprivate class CallWebServiceTask extends AsyncTask<String, Result, Void> {\n    \n\t    protected Result doInBackground(String... someData) {\n\t        Result result = webService.doSomething(someData);\n\t        return result;\n\t    }\n    \n\t    protected void onPostExecute(Result result) {\n\t        if (result.isSuccess() {\n\t            resultText.setText(\"It worked!\");\n\t        }\n\t    }\n\t}\n```  \n\n使用AsyncTask的最大的问题是在细节的处理上，让我们谈谈这个问题。\n\n###错误处理\n这种简单用法的第一个问题就是：“如果出现了错误怎么办？”不幸的是，暂时没有非常好的解决方案。所以很多的开发者最终要继承AsyncTask，然后在doInBackground()中包裹一个try/catch，返回一个<TResult, Exception>，然后根据发生的情况，分发到新定义的例如onSuccess()或者是onError()中。(我也曾经见过仅捕获异常的引用，然后在 onPostExcecute()中进行检查的写法) \n\n这最终是有点帮助的，但是你必须为你的每个项目写上额外的代码，随着时间的推移，这些自定义的代码在开发者之间和项目之间，可能不会保持很好的一致性和可预见性。\n\n###Activity和Fragment的生命周期\n另外一个你必须面对的问题是：“当AsyncTask正在运行的时候，如果我退出Activity或者是旋转设备的话会发生什么？”嗯，如果你只是发送一些数据，之后就不再关心发送结果，那可能是没有问题的，但是如果你需要根据Task的返回结果更新UI呢？如果你不做一些事情阻止的话，那么当你试图去调用Activity或者是view的话，你将得到一个空指针异常导致程序崩溃，因为他们现在是不可见或者是null的。\n\n同样，在这个问题上AsyncTask没有做很多工作去帮助你。作为一个开发者，你需要确保保持一个Task的引用，并且要么当Activity正在被销毁的时候取消Task，要么当你试图在onPostExecute()里面更新UI的时候，确保Activity是在一个可达状态。当你只想明确的做一些工作，并且让项目容易维护的时候，这将会继续提高维护项目的难度。\n\n###旋转时的缓存(或是其他情况)\n当你的用户还是待在当前Activity，仅仅是旋转屏幕会发生什么？在这种情况下，取消Task没有任何意义，因为在旋转之后，你最终还是需要重新启动Task。或者是你不想重启Task，因为状况在一些地方以非幕等的方式发生了突变(because it mutates some state somewhere in a non-idempotent way),但是你确实想要得到结果，因为这样你就可以更新UI来反映这种情况。\n\n如果你专门的做一个只读的加载操作，你可以使用AsyncTaskLoader去解决这个问题。但是对于标准的Android方式来说，它还是很沉重，因为缺少错误处理，在Activity中没有缓存，还有很多独有的其他怪癖。\n\n###组合的多个Web Server调用\n现在，假如说我们已经想办法把上面的问题都解决了，但是我们现在需要做一些连续的网络请求，每一个请求都需要基于前一个请求的结果。或者是，我们想做一些并发的网络请求，然后把结果合并在一起发送到UI线程，但是，再次抱歉，AsyncTask在这里帮不到你。\n\n一旦你开始做这样的事情，随着更多的复杂线程模型的增长，之前的问题会导致处理这样的事情非常的痛苦和苍白无力。如果想要这些线程一起运行，要么你就让它们单独运行，然后回调，要么让它们在一个后台线程中同步运行，最后复制组成不同。(To chain calls together, you either keep them separate and end up in callback hell, or run them synchronously together in one background task end up duplicating work to compose them differently in other situations.)\n\n如果要并行运行，你必须创建一个自定义的executor然后传递给AsyncTaskTask，因为默认的AsyncTask不支持并行。并且为了协调并行线程，你需要使用像是CountDownLatchs, Threads, Executors 和 Futures去降低更复杂的同步模式。\n\n###可测试性\n抛开这些不说，如果你喜欢测试你的代码，AsyncTask并不能给你带来什么帮助。如果我们不做额外的工作，测试AsyncTask非常困难，它很脆弱并且难以维持。这是一篇有关如何成功测试AsyncTask的[帖子](http://www.making-software.com/2012/10/31/testable-android-asynctask/)。\n\n##更好的解决方案：RxJava’s Observable\n\n幸运的是，一旦我们决定使用RxJava依赖库的时候，我们讨论的这些问题就都迎刃而解了。下面我们看看它能为我们做什么。\n\n下面我们将会使用Observables写一个请求代码来替代上面的AsyncTask方式。(如果你使用Retrofit，那么你应该很容易使用，其次它还支持Observable 返回值，并且它工作在一个后台的线程池，无需你额外的工作)\n\n```java\n\twebService.doSomething(someData)\n    .observeOn(AndroidSchedulers.mainThread())\n    .subscribe(\n        result -> resultText.setText(\"It worked!\")),\n        e -> handleError(e)\n    );\n```\n\n###错误处理\n你可能会注意到，没有做额外的工作，我们已经处理了AsyncTask不会处理的成功和错误的情况，并且我们写了很少的代码。你看到的额外的组件是我们想要Observer 在UI主线程中处理的结果。这样可以让我们前进一点点。并且如果你的webService对象不想在后台线程中运行，你也可以在这里通过使用.subscribeOn(...) 声明。(注意：这些例子是使用Java 8的lambda语法，使用[Retrolambda](https://github.com/orfjackal/retrolambda)就可以在Android项目中进行使用了，但在我看来，这样做的回报是高于风险的，和写这篇文章相比，我们更喜欢在我们的项目中使用。)\n\n###Activity和Fragment的生命周期\n现在，我们想在这里利用RxAndroid解决上面提到的生命周期的问题，我们不需要指定mainThread() scheduler(顺便说一句，你只需要导入RxAndroid)。就像下面这样\n\n```java\n\tAppObservable.bindFragment(this, webService.doSomething(someData))\n    .subscribe(\n        result -> resultText.setText(\"It worked!\")),\n        e -> handleError(e)\n    );\n```\n\n我通常的做法是在应用的Base Fragment里面创建一个帮助方法来简化这一点，你可以参考我维护的一个[Base RxFragment](https://github.com/rosshambrick/standardlib/blob/master/src/main/java/com/rosshambrick/standardlib/RxFragment.java) 获得一些指导。\n\n```java\n\tbind(webService.doSomething(someData))\n    .subscribe(\n        result -> resultText.setText(\"It worked!\")),\n        e -> handleError(e)\n    );\n```\n\n如果我们的Activity或者是Fragment不再是可见状态，那么AppObservable.bindFragment()可以在调用链中插入一个垫片，来阻止onNext()运行。如果当Task试图运行的时候，Activity、Fragment是不可达状态，subscription 将会取消订阅并且停止运行，所以不会存在空指针的风险，程序也不会崩溃。一个需要注意的是，当我们离开Activity和Fragment时，我们会暂时或者是永久的泄露，这取决于问题中的Observable 的行为。所以在bind()方法中，我也会调用LifeCycleObservable机制，当Fragment销毁的时候自动取消。这样做的好处是一劳永逸。\n\n所以，这解决了首要的两个问题，但是下面这一个才是RxJava大发神威的地方。\n\n###组合的多个Web Server调用\n在这里我不会详细的说明，因为这是一个[复杂的问题](http://reactivex.io/documentation/observable.html)，但是通过使用Observables，你可以用非常简单和易于理解的方式完成复杂的事情。这里是一个链式Web Service调用的例子，这些请求互相依赖，在线程池中运行第二批并行调用，然后在将结果返回给Observer之前，对数据进行合并和排序。为了更好的测量，我甚至在里面放置了一个过滤器。所有的业务逻辑都在下面这短短五行代码里面...\n\n```java\n\tpublic Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {\n    return cityDirectory.getUsCapitals() \n        .flatMap(cityList -> Observable.from(cityList))\n        .filter(city -> city.getPopulation() > 500,000)\n        .flatMap(city -> weatherService.getCurrentWeather(city)) //each runs in parallel\n        .toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()));\n\t}\n```\n\n###旋转时的缓存(或是其他情况)\n既然这是一个加载的数据，那么我们可能需要将数据进行缓存，这样当我们旋转设备的时候，就不会触发再次调用全部web service的事件。一种实现的方式是保留Fragment，并且保存一个对Observable 的缓存的引用，就像下面这样：\n\n```java\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setRetainInstance(true);\n        weatherObservable = weatherManager.getWeatherForLargeUsCapitals().cache();\n    }\n\n    public void onViewCreated(...) {\n        super.onViewCreated(...)\n        bind(weatherObservable).subscribe(this);\n    }\n```\n\n在旋转之时,正在运行当中的Subscription(代表了事件源和订阅者之间的关系)会被缓存到一个实例,在旋转之后，这个实例就会立即发送一些和旋转之前已经发送过的事件相同的事件，从而避免了再去重复请求网络服务。\n\n如果你想要避免缓存的Fragment(或者是由于它是一个子Fragment,你不能缓存它)，此时我们可以通过putting the same cache instance one layer down inside a service singleton,或者使用一个无论何时被订阅,都会重新发出最后的事件AsyncSubject实现缓存,或者使用可以在整个应用中获得最后的值以及由改变引起的新值的BehaviorSubject这些来完成缓存(以上这些我将会在我不久后的一篇文章里面更详细的讲明,那篇文章我将使用一种更接近事件总线的observables)。\n\n```java\n WeatherListFragment.java \n\t\n\tpublic void onViewCreated() {\n    super.onViewCreated()\n    bind(weatherManager.getWeatherForLargeUsCapitals()).subscribe(this);\n\t}\n\n WeatherManager.java\n\n\tpublic Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {\n    if (weatherSubject == null) {\n        weatherSubject = AsyncSubject.create();\n\n        cityDirectory.getUsCapitals() \n            .flatMap(cityList -> Observable.from(cityList))\n            .filter(city -> city.getPopulation() > 500,000)\n            .flatMap(city -> weatherService.getCurrentWeather(city))\n            .toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()))\n            .subscribe(weatherSubject);\n    }\n    return weatherSubject;\n\t}\n```\n\n因为“缓存”是由Manager单独管理的，它不会与Fragment/Activity的周期绑定，并且会保持与Activity/Fragment的解耦。如果你想以一种和处理保存的fragment的生命周期事件相同的手段来强制刷新这个缓存实例的话，你可以这样做：\n\n```java\n\tpublic void onCreate() {\n    super.onCreate()\n    if (savedInstanceState == null) {\n        weatherManager.invalidate(); //invalidate cache on fresh start\n    }\n\t}\n```\n\n这件事情的伟大之处在于，它不像是Loaders，我们可以很灵活的缓存这些结果,他们来自我们选择的Activity和Services中。只需要去掉oncreate()中的invalidate()调用，并让你的Manager对象决定何时发出新的气象数据就可以了。可能是一个Timer，或者是用户定位改变，或者是其他任何时刻，这真的没关系。你现在可以控制什么时候如何去更新缓存和重新加载。并且当你的缓存策略发生改变的时候，Fragment和你的Manager对象之间的接口不需要进行改变。它只不过是一个 List<WeatherData>的Observer。\n\n###可测试性\n测试是我们想要实现干净、简单的最后一个挑战。(我们可以不用模拟真实的网络服务来进行测试。做法很简单，下面通过一个接口注入这些依赖,这个接口你可能已经在用了。)\n\n幸运的是，Observables给我们一个简单的方式来将一个异步方法变成同步，你要做的就是使用toblocking()方法。我们看一个测试例子。\n\n```java\n\tList results = getWeatherForLargeUsCapitals().toBlocking().first();\n\tassertEquals(12, results.size());\n```\n\n就像这样，我们没有必要去使用Futures或者是CountDownLatchs让做一些脆弱的操作，比如线程睡眠或者是让我们的测试变得很复杂，我们的测试现在是简单、干净、可维护的。\n\n##结论\n更新：我已经创建了一对简单的项目来演示[AsyncTask风格](https://github.com/rosshambrick/AsyncExamples)和[AsyncTaskLoader](https://github.com/rosshambrick/rain-or-shine)风格。\n\nRxJava，你值得拥有。我们使用rx.Observable来替换AsyncTask和AsyncTaskLoader可实现更加强大和清晰的代码。使用RxJava Observables很快乐，而且我期待能够呈现更多的Android问题的解决方案。\n"
  },
  {
    "path": "androidweekly/使用buildSrc Gradle项目和Codemodel生成java代码/readme.md",
    "content": "#How to generate Java sources using buildSrc Gradle project and Codemodel\n#使用buildSrc Gradle项目和Codemodel生成java代码\n\r\n[Android](http://www.thedroidsonroids.com/category/blog/android/) • 1 September 2015\n\n> * 原文链接 : [How to generate Java sources using buildSrc Gradle project and Codemodel](http://www.thedroidsonroids.com/blog/android/how-to-generate-java-sources-using-buildsrc-gradle-project/)\n> * 原文作者 : [Karol](http://www.thedroidsonroids.com/blog/)\n> * 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn/)\r> * 译者 : [shenyansycn](https://github.com/shenyansycn)\r> * 校对者 : \n> * 状态 : 校对中\r\n\r\n##Introduction\r\n\n假设现在你现在需要在Android应用中嵌入和解析外部数据，你会怎么做？在本文中我们将在应用中获取[互联网顶级域名(TLD)的列表](https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains)。你可以在[ICANN TLD报告](http://stats.research.icann.org/dns/tld_report/)报告中看到一共有一千多种顶级域名，时不时有新域名的加入，也有旧的域名被废弃。\n\n由于Android中管理TLDs的[API](https://developer.android.com/reference/android/util/Patterns.html#TOP_LEVEL_DOMAIN_STR)很快就过时了，不赞成使用。哪里能获得最新的TLD的列表呢？幸好这里有一个INAN维护的机器识别源:[IANA - Root Zone DataBase](https://data.iana.org/TLD/tlds-alpha-by-domain.txt).好了，我们已经获得了一个源，那如何在应用中嵌入呢？这里有一些方法，例如，我们可以下载文本文件到**assets**或者**res/raw**目录，运行时再解析。但这里有一个更有效的方法－应用编译前解析数据和运行时使用已经处理好的数据。我们可以使用所提供的方法叫**getTldList()**,它返回最新的TLDS。就像Android的编译工具在每次编译时自动刷新**R**类一样。\n\n##生成的代码长什么样？\r\n\n例子中的数据是一个字符串的列表，所以它可以被表示为**List****<****String****>**。域是独一无二的，它永远不能被编辑并且[List](http://developer.android.com/reference/java/util/List.html)接口提供稍微多一点的内容，比如索引。我们用一个方法创建了一个实用性的类，就像下边这样：\n\n```\r\npublic final class TldList {\r\n   \tprivate static final List TLD_LIST = Collections.unmodifiableList(Arrays.asList(<TLDs here>));\r\n    /**\r\n     * javadoc here\r\n     */\r\n    public static List<String> getTldList() { \r\n        return TLD_LIST; \r\n    }\r\n    private TldList() {}\r\n}\n```\r\n \r\n##How to generate Java code automatically?\n##如何自动生成Java代码\n\n生成Java代码需要下载源文件并重写到Java源文件中。后者可以打印java语法元素到文件中。但更好的方法是使用专用库。在此情况下你不必担心大括号，换行符和其他语法元素只需要关注逻辑。在这个例子中使用的生成java代码的库是[Codemodel](https://codemodel.java.net/)。使用Codemodel生成代码是简单的。我们用Groovy编写Gradle本身和大部分的插件。\n\n这里有生成好的代码：\r\n\r\n```\r\npublic class TldListGenerator {\r\n\r\n    private static final URL IANA_TLDS_URL = \r\n            new URL('https://data.iana.org/TLD/tlds-alpha-by-domain.txt')\r\n\r\n    /**\r\n     * Generates <code>pl.droidsonroids.domainnameutils.TldList</code> \r\n     * and places it into <code>outputDir</code>.\r\n     * @param outputDir directory where generated sources will be written to\r\n     * @param useSavedVersion if true then saved TLD data will be used instead of downloading it \r\n     * from IANA's website\r\n     */\r\n    public static void generateTldListClass(final File outputDir, final boolean useSavedVersion) {\r\n        def javadocConfig = new ConfigSlurper()\r\n                .parse(TldListGenerator.class.getResource('javadoc.properties'))\r\n        def sourceUrl = useSavedVersion ?\r\n                TldListGenerator.class.getResource('tlds-alpha-by-domain.txt')\r\n                : IANA_TLDS_URL\r\n\r\n        def codeModel = new JCodeModel()\r\n\r\n        def fqcn = TldListGenerator.class.getPackage().getName() + '.TldList'\r\n        def tldListClass = codeModel._class(PUBLIC | FINAL, fqcn, ClassType.CLASS)\r\n        def classJavadoc = tldListClass.javadoc()\r\n        classJavadoc.append(javadocConfig.getProperty('classJavadoc'))\r\n\r\n        def listStringType = codeModel.ref(List.class).narrow(codeModel.ref(String.class))\r\n        def asListInvocation = codeModel.directClass(Arrays.class.getName()).staticInvoke('asList')\r\n\r\n        sourceUrl.eachLine {\r\n            if (!it.startsWith('#')) {\r\n                asListInvocation.arg(it.toLowerCase(Locale.ENGLISH))\r\n            } else {\r\n                classJavadoc.append('\\n<br/>').append(it.replaceFirst('#\\\\s+', ''))\r\n            }\r\n            return\r\n        }\r\n\r\n        def constant = codeModel\r\n                .directClass(Collections.class.getName())\r\n                .staticInvoke('unmodifiableList')\r\n                .arg(asListInvocation)\r\n        def field = tldListClass.field(PRIVATE | STATIC | FINAL, listStringType, 'TLD_LIST', constant)\r\n        def method = tldListClass.method(PUBLIC | STATIC, listStringType, 'getTldList')\r\n\r\n        method.javadoc()\r\n                .append(javadocConfig.getProperty('methodJavadoc'))\r\n                .addReturn()\r\n                .append(javadocConfig.getProperty('methodReturnJavadoc'))\r\n        method.body()._return(field)\r\n        tldListClass.constructor(PRIVATE)\r\n\r\n        if (!outputDir.isDirectory() && !outputDir.mkdirs()) {\r\n            throw new IOException('Could not create directory: ' + outputDir)\r\n        }\r\n        codeModel.build(outputDir)\r\n    }\r\n}\n```\n\n让我们解释下关键代码部分。在开始的部分我们使用[ConfigSlurper](http://docs.groovy-lang.org/latest/html/gapi/groovy/util/ConfigSlurper.html)从属性中加载java文档。属性是一对key-value，和代码位于不同文件中，并没有被混淆在一起，所有的java文档都在一个地方就像好Android的String资源。Codemodel API就像我们平时写代码一样调用方法即可。\n\n读取输入使用[ResourceGroovyMethods#eachLine()](http://docs.groovy-lang.org/latest/html/api/org/codehaus/groovy/runtime/ResourceGroovyMethods.html#eachLine)方法，从URL中读取每一行内容就好像闭包（代码片段用大括号包围）。特别的变量**it**是一个String内容，是每一行的内容。刚开始的行是注释，所以我们可以写入到java文档中。其他的行包含TLDs，所以我们转换为小写字母写入代码中。所有的行被处理过后，源会被自动关闭。像java中的try-catch-finally或try-with-resources声明。\n\n小写TLDs的操作设置为英语环境也是很重要的。如果没有则使用host提供的默认设置。例如：如果设置为土耳其或者阿塞拜疆，非ASII字符小写i会被当作一个小写的ASCII字符I，我们生成的一些TLDs就会无效。更多信息可以查看[Internationalizing Turkish](http://www.i18nguy.com/unicode/turkish-i18n.html).最终，我们创建了输出的目录结构并保存了生成的代码。\n\n怎么处理错误，如果没有网络连接或者数据没有被下载会发生什么？如，你看到的没有**cactch**声明也无**throws**处理。在Groovy里我们是不需要的，所有的异常都是未被检查的被处理过。如果我们的代码抛出了异常，它仅仅引起Gradle编译失败，会在Android Studio的消息窗口和控制台中显示。\r\n\n##哪里存放生成的代码\n\nGradle给我们提供了几个方式保存生成的代码，例如：\n\n1.直接嵌入到我们App项目的**build.gradle**文件中\n\n2.分开的文件。例如：**generator.gradle**和在**build.gradle**文件中使用**apply from: 'generator.gradle'**\n\n3.[buildSrc project](https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources)\n\n4.[standalone project](https://docs.gradle.org/current/userguide/custom_plugins.html#N16FA7)\n\n头两个选项没有一点灵活性。例如：我们生成的代码不能很简单的测试，（特别是第一个）生成的代码与编译配置选项容易混淆让人难以读懂。剩余两项的关键不同是在应用中如何配置。独立的项目在当很多项目被配置使用、需要一个仓库或至少拷贝一个JAR文件时是有用处的。在这个例子中我们将在一个单独的项目中使用我们生成好的代码并且我们将它放在**buildSrc**项目中\r\n\n##**buildSrc** project是什么？\n\nGradle特别处理过的名字为**buildSrc**的目录在项目的根目录时。子项目**buildSrc**（和Android Studio或IntelliJ的子module）被自动创建（不需要在**settings.gradle**中声明）。甚至项目的**build.gradle**都不是必须的因为默认的已经被隐式实现。在Gradle文档中查看[Organizing Build Logic](https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources)更多信息。这个项目被加入到了编译脚本的classpath中，所以它的内容可以在build.gradle中用同样的方式使用作为classpath的依赖。例如：**classpath com.android.tools.build:gradle:1.2.3'**。**buildSrc**项目一样也有单元测试和资源。可以被任一项目的Gradle执行。\r\n\n##如何使用生成的代码？\n\n我们需要调用我们的**generateTldListClass()**方法。我们能创建一个完整的Gradle插件，但对于这个简单的目的，我们在App项目中的**build.gradle**文件中添加一个自定义的任务。例子实现如下：\r\n\r\n```\r\nimport pl.droidsonroids.domainnameutils.TldListGenerator\r\n\r\napply plugin: 'com.android.application'\r\n\r\ndef generatedSrcDir = new File(buildDir, \"generated/tld/src/main/java/\")\r\n\r\ntask generateTldList << {\r\n    TldListGenerator.generateTldListClass(generatedSrcDir, true)\r\n}\r\n\r\npreBuild.dependsOn generateTldList\r\n\r\nandroid {\r\n    sourceSets {\r\n        main {\r\n            java {\r\n                srcDirs += generatedSrcDir\r\n            }\r\n        }\r\n    }\r\n<rest of the android closure>\r\n}\r\n```\n\n让我们分析这个编译脚本。Gradle编译时产生的文件默认会被放在项目根目录下的**build**目录。在**build.gradle**中会被作为**buildDir**被检索到。如此，我们构建了输出目录。\n\n我们也可以创建自定义任务叫**generateTldList**.注意**<<**是一个定义为行为的快捷方式。关于任务的更多信息看[Gradle tasks documentation](https://docs.gradle.org/current/userguide/more_about_tasks.html)。在Android Gradle插件里我们继续添加我们的任务作为**preBuild**任务的一个依赖，当每一个项目编译刚开始时被执行。最终我们把输出目录加入到了main source中，在app源码中可以被引入。\r\n\n##如何使用生成的代码\r\n\n我们可以像使用其他类一样使用我们生成的类。下边的例子展示如何创建一个包含TLDS的简单Spinner：\n\r\n```\r\nfinal Spinner spinner = (Spinner) findViewById(R.id.spinner_tld);\r\nspinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, TldList.getTldList()));\n```\r\n \r\n##Sample project\n##例子\n\n生成代码的例子和简单的Android项目可以在Github上看到：[koral–/buildsrc-sample](https://github.com/koral--/buildsrc-sample)。Android Studio 1.3中会提示**Class 'TldListGenerator' already exists in 'pl.droidsonroids.domainnameutils'**是但是它并不会影响项目的构建。运行App看起来像下边这样\n\n![Screenshot](https://cloud.githubusercontent.com/assets/3340954/9006569/24f8743a-3789-11e5-9ccf-b36bce782894.png)\r\n\r\n##References\n##引用\r\n\r\n* [IANA — Root Zone Database](https://data.iana.org/TLD/tlds-alpha-by-domain.txt)\r\n* [Codemodel](https://codemodel.java.net/)\n* [Internationalizing Turkish](http://www.i18nguy.com/unicode/turkish-i18n.html)\n* [Organizing Gradle Build Logic](https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources)\n* [Writing Custom Gradle Task Classes](https://docs.gradle.org/current/userguide/custom_tasks.html)\n* [More about Gradle tasks](https://docs.gradle.org/current/userguide/more_about_tasks.html)\n* [Sample project](https://github.com/koral--/buildsrc-sample)"
  },
  {
    "path": "androidweekly/功能测试框架 espresso/readme.md",
    "content": "功能测试框架 espresso\n---\n\n>\n* 原文链接 : [the-hitchhikers-guide-to-android-testing-part-2-espresso](http://wiebe-elsinga.com/blog/the-hitchhikers-guide-to-android-testing-part-2-espresso/)\n* 译者 : [Lollypo](https://github.com/Lollypo) \n* 校对者: [kang](https://github.com/tiiime)\n* 状态 :  校对完成\n\n![VtFd68Pr19fYk.](http://7xi8kj.com1.z0.glb.clouddn.com/VtFd68Pr19fYk.gif)\n\n正如[Ali Derbane](https://plus.google.com/+AliDerbane)和我写的第一篇关于Android的功能测试的文章中提到的，有许多的框架供你使用.\n在这个旅程的第二部分,我将讲解[Espresso](https://code.google.com/p/android-test-kit/)这个功能测试框架.\n\n\n### 简介\n\nEspresso 是在2013年的 GTAC 上首次提出，目的是让开发人员能够快速地写出简洁，美观，可靠的 Android UI 测试。\n\nEspresso有以下几个通用组件:\n\n- “Espresso”类提供的“onView”和“onData”方法,仅可用于特定接口上测试最优数.\n- `ViewMatchers` 包含一个实现了`Matcher <? super View>`接口的对象集合. 使用该类你可以收集或是检查View元素.例如,通过文本 “7” 获取一个View元素(Button).\n- `ViewActions` 包含了一组`viewAction`对象，储存了将要在View上执行的动作. 这些动作被传递给`ViewInteraction.perform`方法,也许包含更多的动作. For 例如, 点击一下View元素(Button).\n- `ViewAssertions` 包含`ViewAssertion`集合，用于对Views进行检查.\n\n举个例子说明一下，这些测试组件看起来就像下面这样:\n\n```java\nEspresso.onView(ViewMatchers.withText(\"7\")).perform(ViewActions.click());\nEspresso.onView(withId(R.id.result)).check(ViewAssertions.matches(ViewMatchers.withText(\"42\")));\n ```\n\n好消息，去年谷歌推出了集成Espresso的[Testing Support Library](https://developer.android.com/tools/support-library/index.html).因此，让我们通过实现Espresso开始吧.\n\n>  为了方便解释, 我们要编写一些测试用例来测试[Android calculator application](https://github.com/welsinga/sample_espresso/app)这个App. 先来实现一个测试“6”x“7”等于“42”是否正确的普通测试场景。\n\n\n\n### 定义test runner\n\n 使用Espresso我们首先需要定义这些测试用例。Espresso使用新的名为AndroidJUnitRunner的测试用例。该测试用例基于“InstrumentationTestRunner”和“GoogleInstrumentationTestRunner”,运行JUnit3和JUnit4来测试你的Android应用程序。\n\n首先将依赖项添加到你的`build.gradle`文件中, 这里假设你已经安装好了[Testing Support Library](https://developer.android.com/tools/support-library/index.html).\n\n ```gradle\n dependencies {\n  androidTestCompile 'com.android.support.test:testing-support-lib:0.1'\n}\n```\n\n然后添加测试用例到你的`build.gradleandroid.defaultConfig`配置中 \n\n```gradle\ndefaultConfig {\n  ...\n  testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n}\n```\n\n\n\n### 编写测试\n\n你可能已经想到了，测试类必须在`src\\androidTest\\com.example.package.tests`中.包com.example.package是在AndroidManifest文件中指定的属性.\n\n每一个测试类还必须继承抽象类`ActivityInstrumentationTestCase2`并且使用默认测试的 Activity 作为泛型.\n\n它还需要通过`super()`方法传递给父类.要使被测试的Activity被测试框架调用，只需要在setup方法中同步调用`getActivity()`方法.\n\n```java\npublic class FunctionalInstrumentationTest extends ActivityInstrumentationTestCase2<ActivityToTest> {\n\n    public FunctionalInstrumentationTest() {\n        super(ActivityToTest.class);\n    }\n\n    @Override\n    protected void setUp() throws Exception {\n        super.setUp();\n        getActivity();\n    }\n}\n```\n\n正如前面提到的,我们想要检查“6”x“7”是否等于“42”.\n\n```java\npublic void testAnswer_to_the_Ultimate_Question_of_Life_the_Universe_and_Everything() {\n        onView(withText(\"7\")).perform(click());\n        onView(withText(\"×\")).perform(click());\n        onView(withText(\"6\")).perform(click());\n        onView(withText(\"=\")).perform(click());\n\n        onView(withId(R.id.resText)).check(matches(withText(\"42\")));\n    }\n```\n\n你可能已经注意到,这个示例是使用静态导入.这样做完全是为了使代码更易于阅读.\n\n其他你可能会用到的操作:\n\n- `pressBack()`; to simulate the use of the “back” button,\n- `isDisplayed()`; to check if an element is being shown and\n- `scrollTo()`; to scroll to an element.\n- `pressBack()`; 模拟后退按钮\n- `isDisplayed()`; jian检查某个元素是否显示\n- `scrollTo()`; 滚动到另外一个元素\n\n\n### 运行测试\n\n现在我们做做有趣的,运行测试.这可以通过`gradle clean assembleDebug connectedAndroidTest`从命令行运行,或者使用Android Studio:\n\n1. 打开Run菜单 | Edit Configurations\n2. 添加一个新的Android Tests configuration\n3. 选择你需要测试的Module\n4. 定义我们的测试用例: `android.support.test.runner.AndroidJUnitRunner`\n\n![10OjZwNlstPj9e](http://7xi8kj.com1.z0.glb.clouddn.com/10OjZwNlstPj9e.gif)\n\n现在你对于Espresso有一些了解了。如果需要深入，可以浏览以下链接:\n\n- [Espresso website](https://code.google.com/p/android-test-kit/)\n- [Github repo corresponding to this article](https://github.com/welsinga/sample_espresso)\n- [General Espresso Github samples by Google](https://github.com/googlesamples/android-testing)\n"
  },
  {
    "path": "androidweekly/在Android 5.0中使用JobScheduler/readme.md",
    "content": "在Android 5.0中使用JobScheduler\n---\n\n> * 原文链接 : [using-the-jobscheduler-api-on-android-lollipop](http://code.tutsplus.com/tutorials/using-the-jobscheduler-api-on-android-lollipop--cms-23562)\n> * 译者 : [Mr.Simple](https://github.com/bboyfeiyu)\n> * 校对者 : [Mr.Simple](https://github.com/bboyfeiyu)\n\n在这篇文章中，你会学习到在Android 5.0中如何使用JobScheduler API。JobScheduler API允许开发者在符合某些条件时创建执行在后台的任务。\n\n## 介绍\n在Android开发中，会存在这么些场景 : 你需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务，例如当设备接通电源适配器或者连接到WIFI。幸运的是在API 21 ( Android 5.0，即Lollipop )中，google提供了一个叫做JobScheduler API的新组件来处理这样的场景。\n\n当一系列预置的条件被满足时，JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外，JobScheduler API允许同时执行多个任务。这允许你的应用执行某些指定的任务时不需要考虑时机控制引起的电池消耗。\n\n这篇文章中，你会学到关于JobScheduler API更多的东西以及在你的应用中用于运行一个简单的后台任务的JobService，这篇文章中所展示的代码你都可以在[github](https://github.com/tutsplus/Android-JobSchedulerAPI)中找到。\n\n## 1. 创建Job Service\n\n首先，你需要创建一个API最低为21的Android项目，因为JobScheduler是最近的版本才加入Android的，在写这篇文章的时候，它还没有兼容库支持。（译者注：截止目前已知一个兼容库 [JobSchedulerCompat](https://github.com/evant/JobSchedulerCompat)）\n\n假定你使用的是Android Studio,当你点击了创建项目的完成按钮之后，你会得到一个\"hello world\"的应用骨架。你要做的第一步就是创建一个新的java类。为了简单起见，让我们创建一个继承自JobService且名字为JobSchedulerService的类，这个类必须实现两个方法，分别是`onStartJob(JobParameters params) `和 `onStopJob(JobParameters params)`；\n\n```java\npublic class JobSchedulerService extends JobService {\n \n    @Override\n    public boolean onStartJob(JobParameters params) {\n \n        return false;\n    }\n \n    @Override\n    public boolean onStopJob(JobParameters params) {\n         \n        return false;\n    }\n}\n```\n\n当任务开始时会执行`onStartJob(JobParameters params)`方法，因为这是系统用来触发已经被执行的任务。正如你所看到的，这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。如果返回值是true,那么系统假定这个任务正要被执行，执行任务的重担就落在了你的肩上。当任务执行完毕时你需要调用`jobFinished(JobParameters params, boolean needsRescheduled)`来通知系统。\n\n当系统接收到一个取消请求时，系统会调用`onStopJob(JobParameters params)`方法取消正在等待执行的任务。很重要的一点是如果`onStartJob(JobParameters params)`返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。换句话说，`onStopJob(JobParameters params)`在这种情况下不会被调用。\n\n需要注意的是这个job service运行在你的主线程，这意味着你需要使用子线程，handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。因为多线程技术已经超出了我们这篇文章的范围，让我们简单实现一个Handler来执行我们在JobSchedulerService定义的任务吧。\n\n```java\nprivate Handler mJobHandler = new Handler( new Handler.Callback() {\n     \n    @Override\n    public boolean handleMessage( Message msg ) {\n        Toast.makeText( getApplicationContext(), \n            \"JobService task running\", Toast.LENGTH_SHORT )\n            .show();\n        jobFinished( (JobParameters) msg.obj, false );\n        return true;\n    }\n     \n} );\n```\n\n在Handler中，你需要实现`handleMessage(Message msg)`方法来处理你的任务逻辑。在这个例子中，我们尽量保证例子简单，因此我们只在`handleMessage(Message msg)`中显示了一个Toast，这里就是你要写你的任务逻辑( 耗时操作 )的地方，比如同步数据等。\n\n当任务执行完毕之后，你需要调用`jobFinished(JobParameters params, boolean needsRescheduled)`来让系统知道这个任务已经结束，系统可以将下一个任务添加到队列中。如果你没有调用`jobFinished(JobParameters params, boolean needsRescheduled)`，你的任务只会执行一次，而应用中的其他任务就不会被执行。\n\n`jobFinished(JobParameters params, boolean needsRescheduled) `的两个参数中的params参数是从JobService的`onStartJob(JobParameters params)`的params传递过来的，needsRescheduled参数是让系统知道这个任务是否应该在最初的条件下被重复执行。这个boolean值很有用，因为它指明了你如何处理由于其他原因导致任务执行失败的情况，例如一个失败的网络请求调用。\n\n创建了Handler实例之后，你就可以实现`onStartJob(JobParameters params)` 和`onStopJob(JobParameters params)`方法来控制你的任务了。你可能已经注意到在下面的代码片段中`onStartJob(JobParameters params)`返回了true。这是因为你要通过Handler实例来控制你的操作，这意味着Handler的handleMessage方法的执行时间可能比`onStartJob(JobParameters params)`更长。返回true,你会让系统知道你会手动地调用`jobFinished(JobParameters params, boolean needsRescheduled)`方法。\n\n```java\n@Override\npublic boolean onStartJob(JobParameters params) {\n    mJobHandler.sendMessage( Message.obtain( mJobHandler, 1, params ) );\n    return true;\n}\n \n@Override\npublic boolean onStopJob(JobParameters params) {\n    mJobHandler.removeMessages( 1 );\n    return false;\n}\n```\n一旦你在Java部分做了上述工作之后，你需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。\n\n```\n<service android:name=\".JobSchedulerService\"\n    android:permission=\"android.permission.BIND_JOB_SERVICE\" />\n```\n    \n## 2. 创建一个JobScheduler对象\n随着JobSchedulerService构建完毕，我们可以开始研究你的应用如何与JobScheduler API进行交互了。第一件要做的事就是你需要创建一个JobScheduler对象，在实例代码的MainActivity中我们通过`getSystemService( Context.JOB_SCHEDULER_SERVICE )`初始化了一个叫做mJobScheduler的JobScheduler对象。\n\n```java\nmJobScheduler = (JobScheduler) \n    getSystemService( Context.JOB_SCHEDULER_SERVICE );\n```    \n当你想创建定时任务时，你可以使用`JobInfo.Builder`来构建一个JobInfo对象，然后传递给你的Service。JobInfo.Builder接收两个参数，第一个参数是你要运行的任务的标识符，第二个是这个Service组件的类名。\n\n```java\nJobInfo.Builder builder = new JobInfo.Builder( 1,\n        new ComponentName( getPackageName(), \n            JobSchedulerService.class.getName() ) );\n```\n            \n\n这个builder允许你设置很多不同的选项来控制任务的执行。下面的代码片段就是展示了如何设置以使得你的任务可以每隔三秒运行一次。\n\n```java\nbuilder.setPeriodic( 3000 );\n```\n    \n其他设置方法 : \n\n* setMinimumLatency(long minLatencyMillis): 这个函数能让你设置任务的延迟执行时间(单位是毫秒),这个函数与`setPeriodic(long time)`方法不兼容，如果这两个方法同时调用了就会引起异常；\n* setOverrideDeadline(long maxExecutionDelayMillis):             \n这个方法让你可以设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足，你的任务也会被启动。与`setMinimumLatency(long time)`一样，这个方法也会与`setPeriodic(long time)`不兼容，同时调用这两个方法会引发异常。\n* setPersisted(boolean isPersisted):       \n这个方法告诉系统当你的设备重启之后你的任务是否还要继续执行。\n* setRequiredNetworkType(int networkType):      \n这个方法让你这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE，这意味着不管是否有网络这个任务都会被执行。另外两个可选类型，一种是JobInfo.NETWORK_TYPE_ANY，它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED，它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行。\n* setRequiresCharging(boolean requiresCharging):    \n这个方法告诉你的应用，只有当设备在充电时这个任务才会被执行。\n* setRequiresDeviceIdle(boolean requiresDeviceIdle):       \n这个方法告诉你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务。\n\n需要注意的是`setRequiredNetworkType(int networkType)`, `setRequiresCharging(boolean requireCharging)` 以及 `setRequiresDeviceIdle(boolean requireIdle)`这几个方法可能会使得你的任务无法执行，除非调用`setOverrideDeadline(long time)`设置了最大延迟时间，使得你的任务在未满足条件的情况下也会被执行。一旦你预置的条件被设置，你就可以构建一个JobInfo对象，然后通过如下所示的代码将它发送到你的JobScheduler中。\n\n```java\nif( mJobScheduler.schedule( builder.build() ) <= 0 ) {\n    //If something goes wrong\n}\n```\n\n你可能注意到了，这个schedule方法会返回一个整型。如果schedule方法失败了，它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。\n\n如果你的应用想停止某个任务，你可以调用JobScheduler对象的`cancel(int jobId)`来实现；如果你想取消所有的任务，你可以调用JobScheduler对象的`cancelAll()`来实现。\n\n```java\nmJobScheduler.cancelAll();\n```\n到了这里，你现在应该已经知道如何在你的应用中使用JobScheduler API来执行批量任务和后台操作了。\n\n## 结论\n这篇文章中，你学会了怎么实现一个使用Handler对象来运行后台任务的JobService子类，你也学会了如何使用JobInfo.Builder来设置JobService。掌握了这些之后，你可以在减少资源消耗的同时提升应用的效率。\n"
  },
  {
    "path": "androidweekly/在Android调试模式中使用Stetho/README.md",
    "content": "#Stetho for Android debug builds only\n# 在Android调试模式中使用Stetho\n------\n\n - 原文链接:[在Android调试模式中使用Stetho][11]\n - 译者:[BillionWang](https://github.com/BillionWang)\n - 校对者:[chaossss](https://github.com/chaossss)\n - 状态:完成\n \n最近FaceBook发布了一个叫做[Stetho][2]的工具.这个工具是一个谷歌浏览器的开发者工具扩展 ，它可以用来检测你的应用。我发现这东西挺好用的，因为它还提供了访问应用中SQLite数据库的接口。很明显，这种类型的工具只应该在应用的调试模式中使用。接下来我们来看看怎么用这个工具。\n\n\n#添加依赖\n为了保证只在调试模式中使用Stetho，你可以添加一个调试编译依赖，而不是平时常用的普通依赖类型。\n\n    depencencies {\n    // your other dependencies here...\n    debugCompile 'com.facebook.stetho:stetho:1.0.0'\n    }\n\n#在调试模式中初始化Stetho\n\n现在我们在调试中使用Stetho。你会怎么做?当然使用牛逼闪闪的Android Gradle构建系统啦。创建一个源文件夹，目录结构为 <font color=\"red\">src/debug/java</font>。这个目录中的代码仅仅是用于调试模式。这个目录结构和<font color=\"red\">src/main/java</font>很像，因为构建模式就是用于应用程序的调试的。(这句话再想想)。然后添加一个[Stetho][5]主页上描述的 [Application][6]。\n\n    import com.facebook.stetho.Stetho;\n    \n    public class MyDebugApplication extends MyApplication {\n        @Override\n        public void onCreate() {\n            super.onCreate();\n            Stetho.initialize(\n                    Stetho.newInitializerBuilder(this)\n                            .enableDumpapp(Stetho.defaultDumperPluginsProvider(this))\n                            .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))\n                            .build());\n        }\n    }\n看清楚这个类是怎样继承一个已经有的<font color=\"red\">MyApplication.</font>类的。这样写的确很方便，因为你的应用里可能已经有一个application来进行其他的初始化了。如果你还没有一个application。你从<font color=\"red\">android.app.Application.</font>继承一个就行了。\n\n#激活我的调试应用\n\n\n最后一步，我们要做的工具是确保当前的应用的调试版本使用的是MyDebugApplication类。在这里我们用Gradle来验证。在<font color=\"red\">src/debug</font>文件夹中添加一个<font color=\"red\">AndroidManifest.xml</font>\n\n\n    <manifest\n        package=\"com.mycompany\"\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\">\n    \n        <application\n            tools:replace=\"android:name\"\n            android:name=\".MyDebugApplication\"/>\n    \n    </manifest>\n\n这个<font color=\"red\">AndroidManifest.xml</font>会合并到<font color=\"red\">src/main</font>中的主AndroidManifest.xml文件里，并且会替换标签中的<font color=\"red\">android:name</font>属性。这是因为我们特别添加上了tools:replace=\"android:name\"属性。真屌。\n\n\n现在当你启动程序的调试模式，[Stetho][8]就会被激活。如果你切换到发布版本，Stetho绝对不会被激活，也看不到它的任何痕迹。如果想要不丢人，程序员要保证软件没有BUG。\n\n\n#总结\n用Android Gradle构建系统，可以很容易的给你的应用添加更多的调试功能。这门手艺不仅可以用在[Stetho][10]上，还可以用于那些你希望仅仅在调试模式中添加的类库或者工具。\n\n\n  [1]: https://github.com/facebook/stetho\n  [2]: https://github.com/facebook/stetho\n  [3]: http://developer.android.com/reference/android/app/Application.html\n  [4]: https://github.com/facebook/stetho\n  [5]: https://github.com/facebook/stetho\n  [6]: http://developer.android.com/reference/android/app/Application.html\n  [7]: https://github.com/facebook/stetho\n  [8]: https://github.com/facebook/stetho\n  [9]: https://github.com/facebook/stetho\n  [10]: https://github.com/facebook/stetho\n  [11]:http://littlerobots.nl/blog/stetho-for-android-debug-builds-only/\n"
  },
  {
    "path": "androidweekly/安卓字体渲染器/readme.md",
    "content": "安卓字体渲染器\n---\n\n* 原文：[android font renderer](https://medium.com/@romainguy/androids-font-renderer-c368bbde87d9)\n* 译者：[7heaven](http://github.com/7heaven)\n* 校对者：\n* 状态：翻译完成\n\n\n\n任何一个有几年的客户端应用开发经验的开发者都会知道文本渲染有多复杂。至少我在2010年开始写libhwui(基于OpenGL的安卓2D绘制API)之前是这么认为的。在开始写libhwui后，我意识到如果试图用GPU来渲染文本会使文本渲染变得更复杂。\n\n##Text and Android\n\n##文本与安卓\n\n安卓的硬件加速字体渲染最开始是由Renderscript团队的一位同事编写的，后来经过了多位工程师的修改和优化，其中就包括我以及我的朋友Chet Haase。你可以很容易的找到很多关于如何用OpenGL渲染文本的教程。但是，大部分的这些文章都把重点放在游戏开发以及如何绕过一些复杂的问题上。\n\n下面的内容并非如小说般的通俗易懂,但我认为它能很容易地给开发者一个如何实现完整的基于GPU的文字渲染系统的总览。文章中同时也描述了几个容易实现的文本渲染的优化。\n\n通常用OpenGL渲染文本的方法是计算一张包含所有需要的字形的纹理集合。这通常是在离线状态下用一个非常复杂的打包算法来最大化的减小纹理集合的资源浪费。创建这样一个纹理集合需要预先知道哪些文本--包括字体、字号以及其他属性等--然后应用就可以在运行时使用这些字形。\n\n在安卓上用预先渲染的纹理显然不是一个可行的解决方案。UI组件无法预先得知哪些文本需要被渲染；部分应用甚至会在运行时加载自定义字体。这是个主要的约束，但是这仅仅是其中一个：\n\n* 必须在运行时建立字体缓存\n\n* 必须能处理数量巨大的字体\n\n* 必须能处理数量巨大的字形\n\n* 必须最大化地减小纹理浪费\n\n* 渲染速度必须够快\n\n* 在高端和低端机型上必须效果一致\n\n* 在任何驱动/GPU组合上都必须完美运行\n\n##实现字体渲染\n\n在我们研究底层OpenGL文字渲染是如何实现之前，我们先来看看应用中直接调用的上层接口。这些接口对理解libhwui如何工作是非常重要的。\n\n###文本接口\n\n应用中用来排版和绘制文本的主要API有4个：\n\n* android.widget.TextView，一个处理排版和渲染的控件\n\n* android.text.*，创建风格化文本和文本布局的类集合\n\n* android.graphics.Paint，文本测量\n\n* android.graphics.Canvas，文本渲染\n\nTextView以及android.text都是以Paint和Canvas为基础的顶层实现。在安卓3.0之前Paint和Canvas都是直接由[Skia](https://code.google.com/p/skia/)(软件渲染库)实现的顶层API，Skia提供一个抽象库叫[Freetype](http://www.freetype.org/),一个流行的开源字体光栅化器。\n\n>安卓软件文本渲染\n>![text rendering](http://img.my.csdn.net/uploads/201503/31/1427770402_6442.png)\n\n安卓4.4以后整个过程变得有点复杂。Paint和Canvas用一个叫TextLayoutCache的内部JNI接口来实现复杂的文本排版布局(CTL)。这个接口依赖于[Harfbuzz](http://www.freedesktop.org/wiki/Software/HarfBuzz/),这是一个开源的文字shaping引擎。TextLayoutCache接受字体和UTF-16编码的字符串输入，并输出一个包含了x，y坐标的字形标示列表。\n\nTextLayoutCache是处理非拉丁文字，包括阿拉伯文、希伯来文、泰文等的关键。这边我不详细解释关于TextLayoutCache和Harfbuzz是如何工作的。但是如果你想在你的应用中更好的支持非拉丁文字，我强烈建议你学习CTL(复杂文本排版布局)的相关知识。这个问题极少在讨论用OpenGL渲染文本的教程中提到。绘制文本会比单纯地从左到右一个接一个地摆放字形更复杂。部分语言，例如阿拉伯语，是从右到左排列的。泰文甚至需要把字形从上到下 或者从下到上排列。\n\n> Android hardware accelerated text rendering\n\n>安卓硬件加速文字渲染\n>![harware text rendering](http://img.my.csdn.net/uploads/201503/31/1427770402_8107.png)\n\n所有这些意味着当你调用Canvas.drawText()，不管是直接还是间接调用。OpenGL渲染器都不会接收到你发送的参数，而只是接收到一个字形标示以及x/y坐标的数组。\n\n###光栅化和缓存\n\n所有字体渲染的调用都要有字体的配合。字体用来缓存多个独立的字形。字形储存在一个缓存纹理上(一个缓存纹理可以包含不同字体的字形)。缓存纹理是用来存放多个缓存的重要对象：一个空的块列表、一个像素缓存、OpenGL纹理和顶点缓存(the mesh)。\n\n>缓存结构\n>![cache arch](http://img.my.csdn.net/uploads/201503/31/1427770403_5202.png)\n\n用来储存所有这些对象的数据结构很简单：\n\n* 字体储存在字体渲染器的一个LRU缓存中\n\n* 字形存放在每一个字体的映射集中(the key is the glyph identifier)\n\n* 缓存纹理用一个块链表来追踪剩余空间\n\n* 像素缓存为uint8 或者 uint32_t的数组(alpha以及RGB缓存)\n\n* mesh是一个包含x/y坐标和u/v坐标的顶点缓存\n\n* 纹理是一个GLunit句柄\n\n字体渲染器初始化的时候会创建两种类型的缓存纹理：alpha和 RGBA。Alpha纹理用来储存普通的字形；字体本身不包含颜色信息，所以我们只需要储存抗锯齿相关的信息。RGBA缓存用来储存emoji表情。\n\n字体渲染器会为每种类型的纹理创建多个针对不同尺寸的CacheTexture实例。缓存的尺寸在不同设备上不一样，下面是几个默认的尺寸(缓存的数量是硬编码的)：\n\n* 1024x512 alpha缓存\n* 2048x256 alpha缓存\n* 2048x256 alpha缓存\n* 2048x512 alpha缓存\n* 1024x512 RGBA缓存\n* 2048x256 RGBA缓存\n\nCacheTexture实例创建后，它下面的缓存并不会自动分配。字体渲染器会根据需要来分配，1024x512alpha缓存作为一个例外每次都会分配。\n\n字形会在纹理中被打包成多个列。当渲染器遇到一个没有缓存的字形时，它会要求上面列表中对应类型的CacheTexture缓存该字形。\n\n这时候上面提到的块列表就登场了。这个列表包含了给定缓存纹理的已分配空间加上可用空间。如果一个已存在的列可以容纳下某个字形，那么这个字形就会被添加到这个列的已占用空间的底部。\n\n如果所有的列都被占用，它便会在左边的剩余空间中创建一个新列。由于部分字体是等宽字体，渲染器会把每一个字形的宽度四舍五入到4的倍数(默认情况下)。打包并不是最优解，但是它提供了一个快速实现方法。\n\n所有储存在纹理中的字形都由一个空的一像素的边包围。这是为了避免在双线性过滤时需要对字体纹理进行人工干预处理。\n\n这边需要了解的一个重点是当文本在渲染的时候做了缩放变换，这些变换会被交给Skia/Freetype。这表示这些字形是以变换后的形态储存在缓存纹理中。这在提高渲染质量的同时造成性能损耗。幸运的是，文本很少做动画缩放，即使做了动画缩放也只影响到少部分的字形。我做了大量的测试也没有出现性能造成比较大影响的情况。\n\n粗体、斜体、文本x轴缩放(这边不是用canvas的变换矩阵来处理)、样式和线宽等属性也会影响字形的光栅化和储存。\n\n###光栅化代替方案\n\n有另外一种用GPU处理文本的方法。字形可以直接用顶点向量的方式渲染，但是这样开销非常大。我也稍微研究了一下有向距离场，但是简单的实现方式会导致精确度的问题(curves tend to become \"wobbly\").\n\n建议看一下[Glyphy](https://code.google.com/p/glyphy/)，这是一个由Harfbuzz的作者写的开源库，扩展了有向距离场技术并解决了精度的问题。我有一段时间没关注这个项目了，上次看的时候着色器开销在安卓上还是禁止的。\n\n###预缓存\n\n缓存字形是理所当然的，但是预缓存会更好一点。由于libhwui是一个延迟渲染器(和Skia的即时模式相反)，所有即将被绘制到屏幕上的字形在帧开始时都是预知的。在显示列表操作的排序过程中(批处理和合并)，字体渲染器会被要求尽可能多的预先缓存字形。\n\n这样做的主要优势是完全或者 最大化的避免纹理在两帧之前的上传数量。纹理上传是一个开销极大的操作，会导致CPU或者GPU的延迟。更严重的是，在部分GPU架构上帧间修改纹理会导致内存紧张。\n\nImaginationTech公司的PowerVR SGX 系列GPU用了一个很有意思的延迟tiling架构，但是会强制驱动保留一份帧间修改的纹理的备份。字体纹理是非常大的，如果不注意纹理上传问题很容易导致内存溢出。\n\nGoogle Play上的一款应用就出现了这个问题。这款应用是一个简单的计算器，包含多个有数学符号和数字的按钮。字体渲染器在第一帧渲染的时候内存溢出了。因为按钮是按顺序绘制的。每一个按钮的绘制都会触发纹理上传。以及对整个字体缓存的拷贝。系统没有足够的内存来维持这么多的缓存拷贝。\n\n###清理缓存\n\n缓存字形的纹理非常大，它们在部分情况下会被系统回收来把空间让给其他应用。\n\n当用户让应用进入后台的时候，系统会发送一条要求释放尽可能多内存的信息给应用。最显而易见的方式就是销毁最大得缓存纹理。在安卓系统上，所有除了第一个创建的缓存纹理（默认是1024x512）都被视为大型纹理。\n\n当所有缓存都没有任何剩余空间得时候，纹理也会被清理掉。字体渲染器用LRU来追踪字体，但不对它做任何操作。如果需要的话，可以选择清理相对使用较少的纹理，这样更加智能化。现在还没有证据证明这是必须的，但是这是一个潜在的优化策略。\n\n###批处理和合并\n\n安卓4.3引入了[批处理和合并](https://www.youtube.com/watch?v=vQZFaec9NpA)绘制操作，彻底降低了OpenGL驱动的指令问题数量，是一个很重要的优化策略。\n\n为了实现合并，字体渲染器在多个绘制请求上对文本几何结构进行缓存。每一个缓存纹理用于一个2048 quad的客户端数组(1 quad = 一个字形),他们共享一个索引缓存（在GPU中储存为一个VBO）。当libhwui内部发起一个绘制请求时，字体渲染器会为每一个字形获取一个mesh并把x/y坐标和u/v坐标写进去。mesh在批处理的最后或者在quad缓存满得时候被发送给GPU(由延迟显示列表系统中所描述)。有可能在渲染一个字符串的时候会有多个mesh，每个缓存纹理一个。\n\n这个优化策略容易实现，并且对性能提升有很大帮助。由于字体渲染器使用多个缓存纹理，导致字符串中的大部分字形一部分在一个纹理中，一部分在另一个纹理中。如果没有批处理/合并优化策略，每次字体渲染器需要切换不同缓存纹理的时候都会发起一个绘制请求给GPU。\n\n我用来测试字体渲染器的一个应用上就出现了这个问题。这个应用用不同的样式和尺寸渲染一个\"Hello world\"字符串。\"o\"字被储存在和其他字符不同的纹理中。这会导致字体渲染器先绘制\"hell\", 然后是\"o\",\"w\",\"o\", 最后是\"rld\"。一共五次绘制请求以及5次纹理绑定，但实际上只需要两个纹理。使用优化后，渲染器会先绘制\"hell w rld\"然后再同时绘制两个\"o\"。\n\n###优化纹理上传\n\n我之前提到字体渲染器在上传缓存纹理的时候会追踪每个纹理的dirty rectangle来尽可能地上传最少量的数据。但是这种方式有两个限制。\n\n首先，OpenGL ES 2.0不允许上传长方形的任意一个部分。glTexSubImage2D允许你指定纹理内部的长方形的x/y和宽高but it assumes that the stride of the data in main memory is the width of that rectangle.可以通过创建一个新的合适大小的CPU缓存来绕过这个问题，但是这就需要预先知道长方形的大小。\n\n一个妥协的办法是上传包含这个长方形的最小带宽的像素(smallest band of pixels)。由于带宽总是和纹理本身一样宽所以我们还是会浪费掉部分带宽，但是这总好过上传整个纹理。\n\n第二个问题是纹理上传是同步的。这会导致CPU长时间的停顿（多至一毫秒，取决于纹理大小、驱动和CPU）。这在预缓存正常工作的情况下并不是大问题，但是在使用大量文本的应用或者使用大量字形的语言(例如中文)的时候用户会感受到停顿。\n\nOpenGL ES 3.0提供了这两个问题的解决方案。用一个叫GL_UNPACK_ROW_LENGTH的像素储存新属性可以上传一个长方形的一部分。这个属性指定了幅度或者主内存中的原始数据。但请注意：这个属性会对当前的OpenGL上下文的全局状态造成影响。\n\n通过使用像素缓存对象或者PBO可以避免CPU停顿。类似于OpenGL中的其他缓存对象，PBO reside in the GPU but can be mapped in main memory.PBO有很多有趣的属性，但是其中最让我们关注的事它允许异步上传纹理。整个操作过程变成：\n\n```\nglMapBufferRange->把字形写入缓存->glUnmapBuffer->glPixlStorei(GL_UNPACK_ROW_LENGTH)->glTexSubImage2D\n```\n\n对glTexSubImage2D的调用现在会立刻返回而不会阻断渲染器。字体渲染器会同时把整个缓存映射到主内存中。虽然这不太可能导致性能问题，但是最好处理方式还是只映射更新缓存纹理所必须的那一部分。\n\n这两个OpenGL ES 3.0的优化策略[已经在安卓4.4中实现](https://plus.google.com/+RomainGuy/posts/9QSTyVCSoz3)\n\n###投影\n\n文本通常在渲染的时候会带上阴影。这是一个开销较大的操作。由于相邻的字形的阴影模糊会互相影响，字体渲染器无法预先对字形进行模糊化。实现模糊的方法很多，但为了减小每帧间的混合操作和纹理取样，投影会以纹理的形式储存并延续到多个帧。\n\n由于应用很容易让GPU超负荷，我们决定把模糊化交给CPU处理。最简单和高效的处理方法是使用RenderScript的C++ 接口。只需要几行代码and takes advantage of all the available cores.唯一需要注意的是初始化Renderscript的时候要指定RS_INIT_LOW_LATENCY标示来把操作交给CPU执行。\n\n###未来的优化策略？\n\n在我离开安卓团队前有一个优化策略我希望能够实现。文本预缓存，异步以及部分纹理更新都是相当重要的优化方式，但是字形的光栅化依然是一个开销极大的操作。在systrace里很容易看出来。(勾选gfx标签并找到precacheText事件)。\n\n一个简单的优化方法是在后台使用worker线程来执行字形光栅化。这种技巧在不渲染成OpenGL几何体的复杂路径光栅化上已经得到应用。\n\n文本渲染的批处理和合并也有潜在的提升空间。用来绘制文本的部分的颜色是以整体的形式发送给碎片着色器的。这降低了发送给GPU的顶点数据但是同时也导致了副作用，会产生不必要得批处理指令：一个批处理只能包含单色的文本。如果以顶点的属性方式储存会减少发送给GPU的批处理指令。\n\n###源码\n\n如果你需要深入研究字体渲染器的实现可以访问libhwui的[github地址](https://github.com/android/platform_frameworks_base/tree/master/libs/hwui)。大部分操作都在[FontRenderer.cpp](https://github.com/android/platform_frameworks_base/blob/master/libs/hwui/FontRenderer.cpp)中，所以你可以选择从这个类开始看。和它相关的类在[font/](https://github.com/android/platform_frameworks_base/tree/master/libs/hwui/font)的子文件夹中。[PixelBuffer.cpp](https://github.com/android/platform_frameworks_base/blob/master/libs/hwui/PixelBuffer.cpp)也非常有帮助。这是一个由CPU缓存(uint8_t数组)或者GPU缓存(PBO)支持的像素缓存的抽象类。\n\n你会发现源代码中有一些配置属性。这些属性在安卓的[性能调节](http://source.android.com/devices/tuning.html)文档中有描述。\n\n###题外话\n\n这篇文章仅仅是对安卓字体渲染器的简单介绍。还有很多实现的细节被我略过或者会出现在我其他的文章中。有问题请尽管提出。\n"
  },
  {
    "path": "androidweekly/安卓的模糊视图/readme.md",
    "content": "安卓的模糊视图\n---\n\n>\n* 原文链接 : [A Blurring View for Android](http://developers.500px.com/2015/03/17/a-blurring-view-for-android.html)\n* 作者 : [Jun Luo](https://500px.com/junluo)\n* 译者 : [lvtea0105](https://github.com/lvtea0105) \n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)  \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :   校对完成\n\n\n\n\n模糊效果可以生动地表现出内容的层次感，当使用者关注重点内容，即便在模糊表面之下发生视差效果或者动态改变，也能够保持当前背景。   \n\n在IOS设备中，我们首先构造一个UIVisualEffectView,之后添加 visualEffectView 到view层，在view中可以进行动态的模糊。\n\n```java\nUIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];\n\nUIVisualEffectView *visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];\n```\n\n## 安卓中的表现形式\n\n我们在雅虎天气APP中确实看到了很好的模糊效果实例，但是根据 [Nicholas Pomepuy 的博客帖子](http://nicolaspomepuy.fr/blur-effect-for-android-design/)，然而，这个 App 是通过缓存一张预渲染的背景图片来实现图片虚化的。\n\n虽然这种方法非常有效，但是确实不符合我们的需求，在 [500px](https://500px.com/) 的APP中，图像通常是获得焦点的内容，而不仅仅是提供背景，这说明图像的变化很大且迅速，即便他们是在模糊层之下。在我们的[安卓APP](https://play.google.com/store/apps/details?id=com.fivehundredpx.viewer) 中即是一个恰当的例子：当用户滑动至下一页时，整排图片会以相反方向淡出，为了组成所需的模糊效果，适当地管理多个预渲染图是困难的。\n\n![](http://developers.500px.com/images/2015-03-17-500px-android-tour-blurring.png)\n\n## 一种绘制模糊视图的方法 ##\n\n我们需要的效果是，实时地模糊其下的视图，最终需要给界面的是模糊视图的一个 blurred view 引用。\n\n```java\nblurringView.setBlurredView(blurredView);\n```\n\n之后当blurred view 改变时，不管是因为内容改变（比如呈现新的图片）、view的变换还是处在动画过程，我们都要刷新 blurring view。\n\n```java\nblurringView.invalidate();\n```\n\n为了实现 blurring view, 我们需要继承 view类并重写 onDraw()方法来渲染模糊效果。\n\n```java\nprotected void onDraw(Canvas canvas) {\n    super.onDraw(canvas);\n\n    // Use the blurred view’s draw() method to draw on a private canvas.\n    mBlurredView.draw(mBlurringCanvas);\n\n    // Blur the bitmap backing the private canvas into mBlurredBitmap\n    blur();\n\n    // Draw mBlurredBitmap with transformations on the blurring view’s main canvas.\n    canvas.save();\n    canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY());\n    canvas.scale(DOWNSAMPLE_FACTOR, DOWNSAMPLE_FACTOR);\n    canvas.drawBitmap(mBlurredBitmap, 0, 0, null);\n    canvas.restore();\n}\n```\n\n这里的关键点在于，当模糊视图重绘时，它使用blurred view 的draw()方法，模糊视图保持blurred view的引用，它绘制一个私有的，以bitmap作为背景的画布。\n\n```java\nmBlurredView.draw(mBlurringCanvas);\n```\n\n这种使用另一个视图的 draw（）方法，也适用于建立放大或者个性的UI界面，在其中，放大区域的内容是扩大的，而不是模糊的。\n\n根据 [Nicholas Pomepuy 讨论](http://nicolaspomepuy.fr/blur-effect-for-android-design/) 的想法，我们使用子采样和 [渲染脚本](http://developer.android.com/guide/topics/renderscript/compute.html) 进行快速绘制，当初始化blurring view的私有画布mBlurringCanvas 后，子采样就已经完成了。\n\n```java\nint scaledWidth = mBlurredView.getWidth() / DOWNSAMPLE_FACTOR;\nint scaledHeight = mBlurredView.getHeight() / DOWNSAMPLE_FACTOR;\n\nmBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);\nmBlurringCanvas = new Canvas(mBitmapToBlur);\n```\n\n通过mBlurringCanvas的 建立与恰当的渲染脚本初始化，重绘时的blur()方法如下：\n\n```java\nmBlurInput.copyFrom(mBitmapToBlur);\nmBlurScript.setInput(mBlurInput);\nmBlurScript.forEach(mBlurOutput);\nmBlurOutput.copyTo(mBlurredBitmap);\n```\n\n此时 mBlurredBitmap 已准备好，剩下的就是使用适当的变换和缩放，在blurring view自己的画布上重绘视图。\n\n## 实现细节\n\n完整实现blurring view 时，我们需要注意几个技术点：\n\n一：我们发现缩放因子8，模糊半径15 很好地满足我们的目标，但满足你需求的参数可能是不同的。\n\n二：在模糊的bitmap边缘会遇到一些渲染脚本效果，我们将缩放的宽度和高度进行了圆角化，直到最近的4的倍数。\n\n```java\n// The rounding-off here is for suppressing RenderScript artifacts at the edge.\nscaledWidth = scaledWidth - (scaledWidth % 4) + 4;\nscaledHeight = scaledHeight - (scaledHeight % 4) + 4;\n```\n\n三：为了保证更好的表现效果，我们新建两个bitmap对象——mBitmapToBlur 和 mBlurredBitmap ，\nmBitmapToBlur 位于私有画布mBlurringCanvas 之下， mBlurredBitmap 仅当blurred view的大小变化时才重新建立他们；\n同样地当blurred view的大小改变时，我们才创建渲染脚本对象mBlurInput 和 mBlurOutput。\n\n四：我们以with PorterDuff.Mode.OVERLAY 模式绘制一个白色半透明的层，它处在被模糊的的图片上层。用来处理设计需求中的淡化。\n\n最后，因为RenderScript（渲染脚本）至少在API 17 上才可用，我们需要降低旧版本的安卓，糟糕的是，[Nicholas Pomepuy帖子](http://nicolaspomepuy.fr/blur-effect-for-android-design/) 中提到的Java bitmap的模糊方法，当恰当地预渲染缓存副本时，对于实时渲染不够迅速，我们所做的是使用一个半透明的view作为回调。(更新于2015年3月23日：通过使用 [RenderScript 库](http://android-developers.blogspot.ca/2013/09/renderscript-in-android-support-library.html)，我的解决方法能在更低版本的 API 中运行。下面提及的库和 Demo 都更新了，非常感谢 GitHub 上的小伙伴 [panzerdev](https://github.com/panzerdev) 告诉我这一点）\n\n## 优点和缺点\n\n我们喜欢这个view的绘制方法，因为它可以实时模糊并且容易使用，使得blurred view 的内容，也在blurring view 和blurred view中间保证了灵活性，最重要的是，它满足了我们的需求\n\n这个方法确实使得blurring view 与适当协同变换的 blurred view 保持了私有联系，相关地，模糊视图必须不能是blurred view的子类，否则会因为互相调用造成堆栈溢出。简单有效处理此限制的方法是要保证模糊视图与blurred view 在同级，并且Z轴次序上 blurred view 在blurring view 之前。\n\n另一点需要注意的限制是，由于与 矢量图形和文本 有关，我们默认的bitmap削减采样表现效果不是很好。\n\n## 库文件和示例\n\n你可以在我们的安卓 [APP](https://play.google.com/store/apps/details?id=com.fivehundredpx.viewer) 上看到解决方法，我们也把开源的库文件连同一个示例分享到了 [github](https://github.com/500px/500px-android-blur)，它能够展示内容变换、动画和视图变换。\n\n![](https://github.com/500px/500px-android-blur/raw/master/blurdemo.gif)\n\n有关这个效果在 [HackNews](https://news.ycombinator.com/item?id=9219097) 中的讨论"
  },
  {
    "path": "androidweekly/欢迎来到Android多进程时代/readme.md",
    "content": "欢迎来到Android多进程时代\n---\n\n>\n* 原文标题 : Going multiprocess on Android\n* 原文链接 : [Going multiprocess on Android](https://medium.com/@rotxed/going-multiprocess-on-android-52975ed8863c)\n* 译者 : [Lollypo](https://github.com/Lollypo) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)   \n* 状态 :  完成\n\n###That moment when one Dalvik alone is no longer enough.\n\n\n生活在内存限制中\n---\n\n有很多方面使得Android成为一个独特的移动平台操作系统，但有时候却让人觉得难以融入，特别是从开发人员的角度看。\n\n例如,把内存限制。iOS应用程序提供几乎没有限制的内存预算(200 MB不是什么大不了的事),Android有严重的局限性,从最近设备的24/32/48 MB以及旧设备极小的16 MB便可以看出。\n\nRAM预算就是一切你的应用运行时所能获得的全部了，这意味着，它必须满足加载类、线程、服务、资源和你的应用程序想要显示的内容。想象一个通过网格视图展示优美图片的照片浏览应用,或一个需要在后台播放的音乐播放器:这太恐怖了\n\n> 那时候你的体会应该是这样的\n\n![Life’s a bitch, sometimes.](http://7xi8kj.com1.z0.glb.clouddn.com/img01.gif)\n\n要理解为什么Android提出了这些限制以及提供了什么解决方案来应对他们,我们需要知道一点点在这背后之后发生了些什么。\n\n理解Android进程\n---\n\n你应该已经知道了,安卓系统是基于Linux的。因此,每个应用程序都运行在其本身的进程(拥有一个独一无二的PID)中:这允许应用运行在一个相互隔离的环境中,不能被其他应用程序/进程干扰。通常来说,当你想启动一个应用程序,Android创建一个进程(从Zygote中fork出来的),并创建一个主线程，然后开始运行Main Activity。\n\n你可能不知道的是,你可以指定应用程序的一些组件运行在不同的进程中，而不是那个被用于启动应用程序的。先来看一下这个Nice的属性:\n<center>\n```xml\nandroid:process\n```\n</center>\n\n该进程属性可用于activities、services、content providers和broadcast receivers 和指定的进程中应该执行的特定组件。\n\n在这个例子中,我指定MusicService必须执行在一个单独的“music”的进程:\n```xml\n<manifest ...>\n  <application\n    android:icon=\"@drawable/ic_launcher\"\n    android:label=\"@string/app_name\"\n    android:theme=\"@style/Theme.Main\" >\n    \n    <activity\n      android:name=\".MusicActivity\"\n      />\n    <service\n      android:name=\".MusicService\"\n      android:process=\":music\"\n    />\n  </application>\n</manifest>\n```\n\n它有什么意义呢?\n\n在这个简短的介绍中，我提到了每一个Android应用程序在运行的时候都有一个不能超出的内存预算值。更精确的说，这限制了它只能在单个基础的进程上运行。换句话说，应用程序的每一个进程都将会有一个专门的内存预算(更不用说其中止时也有更酷的不同的规则)\n\n让我们看看这种方法将是一件好事还是坏事。(剧透:两者都是)\n\n使用多进程有啥好处\n---\n\n正如我刚才提到的,一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。\n\n此外，操作系统对待运行在不同组件中的进程是不一样的。这意味着，当系统运行在低可用内存的条件时，并不是所有的进程都会被杀死。想象一下：你的音乐播放器正在后台运行，音乐突然播放，系统需要释放一些内存(因为facebook,这就是原因)。由于播放音乐的服务跑在另一个进程中，一种极为可能的情况就是操作系统将会先杀死你的主进程(那个运行着你的UI的)，而留下那个在另一个进程播放音乐的。\n\n最后一点对于用户来说看起来似乎很不错！因为你的程序的每一个进程都有自身的在应用程序管理器上的屏幕显示RAM用度。其中有一个或多个将出现在“缓存”部分(这意味着它们是不活跃的)。\n\n> 正如你所看到的,Spotify在后台播放一些音乐。有一个活跃的带有服务的进程 [上图]，而另一个进程(持有UI的)是缓存状态的，因为不再可见/不活动的[下图]。\n\n![](http://7xi8kj.com1.z0.glb.clouddn.com/img02.png)\n![](http://7xi8kj.com1.z0.glb.clouddn.com/img03.png)\n\n\n使用多进程时的那些坑  \n---\n\n不幸的是,坑有很多。事实上,你要学习拥有多个进程不是一下子就能完成的事\n\n首先,进程是被设计成独立的(如安全特性),这意味着每一个进程将有自己的Dalvik VM实例。反过来,这意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。并且这延伸到应用程序每一个的状态。\n\n这是否意味着两个独立的进程之间互相交流是不可能的吗?不,实际上是可能的,有几种方法可以做到。最值得注意的是,Intent可以跨进程“旅行”,Handlers和Messengers也可以。。你也可以依靠AIDL(Android接口定义语言)和Binder,和你通常声明一个bound service茶不错(但你可以做更多的事!)。\n\n我需要使用多进程吗\n---\n\n当然,这取决于你需要查看到的迹象。如果你的用户正在经历越来越频繁OutOfMemory错误或者他们抱怨你的应用程序是极其消耗RAM,你可能需要考虑使用一个或多个独立的进程。\n\n音乐播放器的例子是第二个进程能使你的App做的更好的其中最常见的一个场景，当然，还有更多。\n例如,你的应用程序是一个客户端云存储服务：委托同步服务组件专用的进程似乎是完全合理的，所以即使UI会被系统杀死，服务仍然可以运行并且保持文件更新。\n\n> 类似的情况会发生在你第一次真正意识到进程隔离的意思时\n\n![This happens when you first realize what “isolation between processes” really means.](http://7xi8kj.com1.z0.glb.clouddn.com/img04.gif)\n\n如果你认为你需要它,那么我建议你先玩一个小试验台应用:只有通过实际体验过使用多个进程的优势和其内在的复杂性，你才能够决定你是否真的需要它,如果是这样,什么是最好的处理它的方式而不至于把我们逼疯。\n\n结语\n---\n\n我知道我仅仅触及到这个问题的表面，我只是想给你一些实用的建议，而不是告诉你在操作系统层调控进程的全部理论与工作机制。\n\n还是那句话，如果你对此感兴趣并愿意深入其中，那就留言让我知道！同时，不要忘记文档是你最好的朋友[[1]](http://developer.android.com/guide/components/processes-and-threads.html#Processes) [[2]](https://developer.android.com/training/articles/memory.html) [[3]](https://developer.android.com/tools/debugging/debugging-memory.html)"
  },
  {
    "path": "androidweekly/深入了解Android Graphics Pipeline-part-1/readme.md",
    "content": "深入了解Android Graphics Pipeline-part-1\n---\n\n* 原文链接 : [Android Graphics Pipeline: From Button to Framebuffer (Part 1)](https://blog.inovex.de/android-graphics-pipeline-from-button-to-framebuffer-part-1/)\n* 作者 : [mgarbe](https://blog.inovex.de/author/mgarbe/)\n* 译者 : [dupengwei](https://github.com/dupengwei) \n* 校对者: [chaossss](https://github.com/chaossss)   \n* 状态 :  完成 \n\n\n\n在这个小型博文系列中我们想给有兴趣研究 Android Graphics Pipeline 内部结构的开发者带来一些启发。在此主题上，Google自己也发布了一些见解和文档，如由Chet Haase 和 Romain Guy主持的Google I/O 2012演讲[For Butter or Worse](https://www.youtube.com/watch?v=Q8m9sHdyXnE) (如果没看过就去看看吧!) 和文章 [Graphics architecture](http://source.android.com/devices/graphics/architecture.html) 。虽然这些资料肯定能帮助我们从宏观上理解一个简单的view是如何显示在屏幕上，但是当我们尝试去理解背后的源代码时，这些对我们的帮助并不大。本系列博文将带你走进Android Graphics Pipeline这个有趣的世界。\n\n请注意，本小型系列博文中会涉及大量的源代码和序列图！它值得一读，就算你对Android Graphics Pipeline一点兴趣也没有，你也可以学到很多（或者你至少看看这些漂亮的图片）。所以给自己一杯咖啡，读吧！\n\n## 引言\n\n为了充分理解view显示到屏幕的过程，我们采用一个小Demo来描述Android Graphics Pipeline的每个主要阶段，由Android Java API (SDK)开始，然后是本地C++代码，最后看原始的OpenGL绘图操作。\n\n\n![one-button-layout-cropped](http://img.my.csdn.net/uploads/201504/09/1428538541_6558.png)\n\n这个 Demo 非常值得我们研究，因为这个不起眼的 App 的代码就能充分覆盖整个 Android Graphics 的内部结构，所以，它实际上是一个相当好的例子。\n\n这个activity由一个简单 RelativeLayout，一个带应用图标和标题 ActionBar 和一个读作“Hello world!”的简单 Button 组成。\n\n![one-button-viewhierarchy](http://img.my.csdn.net/uploads/201504/09/1428538541_8737.png)\n\n但从上图你也可以看到，我们这个简单的 Demo 的视图层实际上是相当复杂。\n\n在Android 视图层内部，相对布局（RelativeLayout）由一个简单的颜色渐变背景组成。更复杂的， Actionbar 由一个渐变的背景结合一个bitmap，**One Button** 文本元素和应用图标（也是一个bitmap）组成。9-Patch用作按钮的背景，文本 **Hello World!** 画在它的最上层。屏幕顶部的导航栏和底部的状态栏不属于 App 的 Activity 部分，它们由系统服务`SystemUI`进行渲染。\n\n## Pipeline概述\n\n如果有看过Google I/O演讲**For Butter or Worse**，你肯定认识下面的幻灯片，因为它它显示了完整的 Android Graphics Pipeline 结构。\n\n![pipeline](http://img.my.csdn.net/uploads/201504/09/1428538607_4861.png)\n\n上图就是在 Google 在 Google I/O 2012 大会中提供的完整的 Android Graphics Pipeline 结构。\n\nSurface Flinger负责创建图形缓冲区并将其合成到主显示器，虽然这对应安卓系统非常重要，但是在此不作赘述。\n\n相反，我们将注意力放在其他组件上，因为这些组件的主要任务就是将view显示到屏幕上。\n\n![interesting_bits](http://img.my.csdn.net/uploads/201504/09/1428538541_4202.png)\n\n## Display Lists\n\n可能你已经知道，Android 使用一个叫做 DisplayLists 的概念去渲染所有的view。对于不知道的人来说，display list是一个绘图命令序列集合，需要执行这些命令去渲染指定的view。display lists是Android Graphics Pipeline达到高性能的重要元素。\n\n\n![display-lists](http://img.my.csdn.net/uploads/201504/09/1428538540_9146.png)\n\n每个视图层的 view 都有其对应的 display list，每个开发者都知道 `onDraw()` 方法，这个 display list 就是由 view 的 `onDraw()` 方法生成的。为了将视图层画到屏幕上，只有 display lists 需要被评估并执行。假如某个单一 view 失效（因用户输入、动画或转换），受影响的 display lists 将会重建和重绘。这就避免Android在每个frame层调用相当耗费资源的 `onDraw()`。\n\nDisplay lists can also be nested, meaning that a display list can issue a command to draw a childrens display list. This is important in order to be able to reproduce view hierarchies with display lists. After all, even our simple app has multiple nested views.\n\nDisplay lists 也可以被嵌套，这意味着一个Display list也可以发出一个命令绘制一个子display list。这对复制视图层的display lists来说非常重要。毕竟，即使是最简单的 App 也会具有多个嵌套视图。\n\n这些命令的语句的可以直接映射到OpenGL命令，如翻译和设置剪辑矩阵，或更复杂的命令如`DrawText` 和 `DrawPatch`，但这些复杂命令需要相应的OpenGL命令集，不然我们也无法使用。\n\n`An example of a display list for a button.`\n\n`一个按钮的display list示例`\n\n```java\nSave 3\nDrawPatch\nSave 3\nClipRect 20.00, 4.00, 99.00, 44.00, 1\nTranslate 20.00, 12.00\nDrawText 9, 18, 9, 0.00, 19.00, 0x17e898\nRestore\nRestoreToCount 0\n```\n\n在上面的示例中，你可以清楚地看到 display list 为绘制一个简单的按钮进行了什么操作。第一个操作是保存当前翻译矩阵到栈中，使得它随后可以被恢复。然后又画了9-Patch按钮，接下来是另外一个保存命令，这是必要的，因为对于要绘制的文本来说，只有绘制文本的区域才会创建裁剪矩阵。手机绘图处理器将此矩形区域当做一个线索以便在后续阶段对绘图调用进一步优化。然后将绘画圆点转换到文本位置，并绘制文本。最后，最初的转换矩阵和状态从堆中还原，裁剪矩阵也被重置。\n\n在这篇文章的底部可以看到示例用的的display lists的完整日志。\n\n## 深入代码\n\n带着这些新获取的知识，我们准备深入研究代码。\n\n### 根视图（Root View）\n\n每个Android activity在视图层的最顶层都有一个隐式根视图（Root View），包含一个子view。这个子view是程序员在应用中定义的第一个真正的view。根视图负责调度和执行多种操作，如绘图，使view无效等等。\n\n类似的，每个view都有一个parent的引用。视图层内部的第一个view将根视图作为parent。虽然View类作为每个可视化的元素或组件的基类，但是它并不支持任何子类。然而，其派生类ViewGroup支持多子类，并作为一个容器基类，它已经被标准布局（RelativeLayout 等等)所采用。\n\n如果一个view局部重绘，那么该view会调用根视图的invalidateChildInParent()方法。根视图跟踪所有重绘的区域，并在choreographer调度一个新的遍历，choreographer会在下一个VSync事件执行。\n\n![view-invalidate](http://img.my.csdn.net/uploads/201504/09/1428538608_9350.png)\n\nViewRoot: InvalidateChildInParent(…)\n\n```java\npublic ViewParent invalidateChildInParent(int[] location, Rect dirty) {\n    // Add the new dirty rect to the current one\n    mDirty.union(dirty.left, dirty.top, \n                 dirty.right, dirty.bottom);\n    // Already scheduled?\n    if (!mWillDrawSoon) {\n        scheduleTraversals();\n    }\n    return null;\n}\n```\n\n## 创建Display Lists\n\nAs previously mentioned, each view is responsible to generate its own display list. When a VSync event is fired and the choreographer called performTraversals on the root view, the  HardwareRenderer is asked to draw the view, which in turn will ask the view to generate its display list.\n\n正如刚刚提到的，每个view负责生产自己的display list。当一个VSync事件被触发，choreographer调用根视图的`performTraversals`方法，根视图要求`HardwareRenderer`绘制view，`HardwareRenderer`反过来要求view生成自己的display list。\n\n![perform-traversals1](http://img.my.csdn.net/uploads/201504/09/1428538542_9748.png)\n\n在Android framework层中，View是其中一个比较大的类，当前代码量将近20000行。这并不令人惊奇，因为它是每个小组件和应用的构建块。它处理键盘、轨迹球、触摸事件,以及滚动,滚动条,布局和测量,还有很多很多。\n\n![view-getdisplaylist](http://img.my.csdn.net/uploads/201504/09/1428538608_4788.png)\n\n`View.getDisplayList(…)`方法被Hardware Renderer调用时将会创建一个内部的display list，这个内部display list将会在view生命周期的剩余部分被使用。然后这个内部display list被要求提供一个足够大的画布来容纳这个view。并提供一个GLES20RecordingCanvas，所有view和它的children将会在上面绘图，然后传递给`draw(…)`方法。这个画布有点特殊，因为它不执行绘图命令，而是保存命令到display list。这意味着小组件和每个view能使用正常的绘图API，甚至无须关注display list内部的命令\n\n`View: getDisplayList(…)`\n```java\nprivate DisplayList getDisplayList(DisplayList displayList, \n                                   boolean isLayer) {\n    HardwareCanvas canvas = displayList.start(width, height);\n    if (!isLayer && layerType != LAYER_TYPE_NONE) {\n        // Layers don't get drawn via a display list\n    } else {\n        draw(canvas);\n    }\n    return displayList;\n}\n```\n\n在`draw(…)`方法中，view将执行`onDraw()`方法的代码，渲染自己到画布上。如果这个view有任何children，children各自调用`draw()`方法。这些children可以是任何东西，可以是一个正常的按钮，也可以是一个布局或view group，这些children包含另外的children，都将被绘制。\n\n![view-draw](http://img.my.csdn.net/uploads/201504/09/1428538608_8013.png)\n\n## 寄语\n\n伴随着display list的产生，part 1结束了。如果你对这系列博文有兴趣的话，请关注 part 2,届时我们会知道 display lists 将如何被呈现在屏幕上!\n\n## 下载\n\nThe full Bachelor’s Thesis on which this article is based is available for download.\n\n本文参考的所有学士论文可供[下载](http://mathias-garbe.de/files/introduction-android-graphics.pdf)\n\n\n\n\n\n"
  },
  {
    "path": "androidweekly/深入了解Android Graphics Pipeline-part-2/readme.md",
    "content": "深入了解Android Graphics Pipeline-part-2\n---\n\n>\n* 原文链接 : [Android Graphics Pipeline: From Button to Framebuffer (Part 2)](https://blog.inovex.de/android-graphics-pipeline-from-button-to-framebuffer-part-2/)\n* 作者 : [Mathias Garbe](https://blog.inovex.de/author/mgarbe/)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成 \n\n\n\n\n在上一篇博文中，我们图文结合介绍了 Android 怎么把 onDraw() 方法的 Java 代码转换为 Native 层的 C/C++ 代码。而今天，我们会承接上一篇博文讲的内容，继续探索 Android 在 Native 层是如何通过 C/C++ 代码绘制屏幕上的各种元素（控件、动画等）。在开始讲解之前，我需要提前提醒大家：要深入到 Native 层中研究 C/C++ 代码对 Java 代码的实现，意味着我们从此刻开始要忘掉 Java 安逸的垃圾回收机制，并忍受 C/C++ 代码带来的各种令人蛋疼的内存管理问题。不过大家不必惶恐，我会尽可能简单地讲解今天的内容，而且只会展示与我们今天的内容有关的有趣代码。\n\n## 绘制元素 ##\n\n在 Android 4.3 之前，UI 的绘制流程和将 UI 元素添加到相应 View 层级的顺序相同，并将它们添加到 display list 中。但这里存在一个很严重的问题，这样的渲染流程会不断进入 GPU 最糟糕的使用场景，原因在于：这样的绘制流程会导致在绘制不同的控件时，需要不断地切换状态。举例来说吧，我们现在要绘制两个 Button，那么 GPU 就需要先绘制第一个 Button 的背景和文字，绘制完成后再对第二个 Button 做同样的操作，渲染流程结束后，至少进行了三次的状态改变。\n\n## 重新设计绘制流程 ##\n\n所以为了最小化状态切换带来的 GPU 开销，Android 基于 UI 元素的类型和状态重新设计了相应的绘制流程。不过在接着讲解之前我们要暂时放下“界面中只有一个 Button ”的例子，并随便找一个 Activity 先进行相关知识的介绍。\n\n![](https://blog.inovex.de/wp-content/uploads/2015/03/merging-layout-anot-300x215.png)\n\n> 示例 Activity 包含了许多重叠的元素，我们这样做的目的是：通过使用极端的例子模拟各种可能出现的情况，解释为什么绘制时会产生这些问题，以及对应的解决策略。\n\n如你所见，简单地根据 UI 的元素类型重新设计绘制流程在大多数情况下都不能满足我们的需求，原因在于：无论是先绘制所有文字，再绘制图片；还是先绘制图片，再绘制文字，都不能获得我们我们真正想要的 UI，因为总有一些该显示的 UI 元素会因为绘制顺序被挡住，我相信任何一个有品位的 UI 设计师都不会接受这样的客户端。\n\n为了能正确地渲染示例 Activity 的 UI，UI 中的文字元素 A 和 B 必须先被绘制，然后绘制图片元素 C，最后是文字元素 D。由于 A 和 B 是同类型的元素，所以我们可以同时进行 A 和 B的绘制，但 C 和 D 只能按照顺序绘制，不然又会产生 UI 元素的覆盖问题。\n\n为了更好地优化 UI 每一个 View 层级的绘制流程，在重新设计了绘制顺序后，就要将类似的绘制操作合并。合并在 DeferredDisplayList 中执行，给这个函数取这个名字是因为绘制操作没有按序执行，而是在所有操作的分析，重排序，合并完成前，不断地延迟其绘制动作，直到分析，重排序，合并完成才按序绘制。\n\n由于每一个 display list 的绘制操作只能用于绘制它自身，如果一个操作支持把相同类型的元素的多个操作合并，那这个操作必须能被用于绘制多个具有相同类型的不同页面。但你需要注意的是，这不意味每一个操作都能进行合并，在新的设计里还留有许多只能重排序的操作。\n\n![](https://blog.inovex.de/wp-content/uploads/2015/03/canvas-drawdisplaylist-1024x594.png)\n\nOpenGLRenderer 是 Skia 2D 图像绘制 API 的一个接口，与常见的接口不同，它不需要利用 CPU 进行硬件加速，而是用 OpenGL 完成了所有硬件加速的工作。虽然有很多办法能完成这样的工作，但是 OpenGLRenderer 是第一个用 C++ 实现的本地类。OpenGLRenderer 在 Android 3.0 中被提出，设计它主要是让它和 GLES20Canvas 协作，绘制我们想要的界面元素。有趣的是，在界面绘制的操作中，只有它们是以协作的形式进行的。\n\n为了把多个操作合并到一个绘制操作中，每一个操作都通过调用 addDrawOp() 方法被添加到 deferred display list 中。同时，绘制操作还需要提供 batchId，因为绘制操作必须知道这个类型的操作能否被合并，此外，绘制操作还需要通过调用 DrawOp.onDefer(...) 方法提供 mergeId,以指明哪些操作已经被合并.\n\n一般 batchId 包含了一个简单的枚举，主要是为 9-Patch 图片元素提供 OpBatch_Patch，并为普通的文字元素提供 OpBatch_Text。mergeId 的值由 DrawOpitself 决定，用于判断两个具有相同的 DrawOp 类型的操作能否被合并。对 9-Patch 图片元素来说，mergeId 用于指向图片资源文件，对文字元素来说，则是对应的文字颜色。来自同一个资源文件夹的资源文件可能会被合并到同一个 batch 中，帮助我们大量地节约绘制流程带来时间开销。\n\n有关一个操作的所有信息都被归并到一个简单的结构中，代码如下：\n\n```java\n\tstruct DeferInfo {\n\t    // Type of operation (TextView, Button, etc.)\n\t\t// batchId 注明被操作的 UI 元素的类型（如 TextView，Button等……）\n\t    int batchId;\n\t \n\t    // State of operation (Text size, font, color, etc.)\n\t    // mergeId 注明被操作的 UI 元素的状态（如 文字大小，字体，文字颜色等……）\n\t    mergeid_t mergeId;\n\t \n\t    // Indicates if operation is mergable\n\t    // 标记操作是否可被合并\n\t    bool mergeable;\n\t};\n```\n\n当一个绘制操作的 batchId 和 mergeId 被确定，如果它还没有被合并，就会被添加到 batch 队列的尾部。如果没有可用的 batch，我们就会创建一个新的 batch。不过一般情况下，这些绘制操作都是可以合并的。为了知道每一个最近合并的 batch 的去向，我们会通过一个简化的算法调用 MergeBatches 的实例 hashmap，用 batchId 构建键值对保存相应的 batch。对每一个 batch 使用 hashmap 能避免使用 mergeId 导致的冲突。\n\n```java\n\tvector<DrawBatch> batches;\n\tHashmap<MergeId, DrawBatch*> mergingBatches[BatchTypeCount];\n\t \n\tvoid DeferredDisplayList::addDrawOp(DrawOp op):\n\t    DeferInfo info;\n\t    /* DrawOp fills DeferInfo with its mergeId and batchId */\n\t    /* DrawOp 方法用 mergeId 和 batchId 填充 DeferInfo */\n\t    op.onDefer(info);\n\t \n\t    if(/* op is not mergeable */):\n\t        /* Add Op to last added Batch with same batchId, if first\n\t           op then create a new Batch */\n\t        /* 将 Op 添加到最后被添加入元素的 Batch 中，但这个 Batch 必须与 Op 具有相同 batchId，此外，如果 op 是 Batch 中的第一个元素，那么需要新建一个 Batch */\n\n\t        return; \n\t \n\t    DrawBatch batch = NULL;\n\t    if(batches.isEmpty() == false):\n\t        batch = mergingBatches[info.batchId].get(info.mergeId);\n\t        if(batch != NULL && /* Op can merge with batch */):\n\t            batch.add(op);\n\t            mergingBatches[info.batchId].put(info.mergeId, batch);\n\t            return;\n\t \n\t        /* Op can not merge with batch due to different states,\n\t           flags or bounds */\n\t        /* 如果 Op 与 Batch 具有不同的状态，标记，和边界，那么 Op 将无法被合并到 Batch 中 */\n\n\t        int newBatchIndex = batches.size();\n\t        for(overBatch in batches.reverse()):\n\t            if (overBatch == batch):\n\t                /* No intersection as we found our own batch */\n\t                /* Batch 之间应该没有交集 */\n\n\t                break;\n\t \n\t            if(overBatch.batchId  == info.batchId):\n\t                /* Save position of similar batches to insert \n\t                   after (reordering) */\n\t                /* 在重排序后保存 batchId 相同的 batch 中对应的位置，便于后面插入元素 */\n\n\t                newBatchIndex == iterationIndex;\n\t \n\t            if(overBatch.intersects(localBounds)):\n\t                /* We can not merge due to intersection */\n\t                /* 如果 Batch 间产生了交集，我们不能进行合并 */\n\n\t                batch = NULL\n\t                break;\n\t \n\t    if(batch == NULL):\n\t        /* Create new Batch and add to mergingBatches */\n\t        /* 如果 batch 为空，则创建一个新的 batch，并将它添加到 mergingBatches 中 */\n\n\t        batch = new DrawBatch(...);\n\t        mergingBatches[deferInfo.batchId].put(info.mergeId, batch);\n\t        batches.insertAt(newBatchIndex, batch);\n\t    batch.add(op);\n```\n\n如果当前操作能够与其他具有相同 mergeId 和 batchId 的操作合并，那么这个操作和下一个可以合并的操作都会被添加到现有的 batch 中。但如果它因为状态不一致、绘制标记或边界限制无法被合并，算法就需要将它插入到一个新的 batch 中。为了实现这样的需求，我们则需要获得 batch 队列中所有 batch 对应的 postion。理想情况下，它能在当前绘制操作中找到一个和它状态相同的 batch。不过需要注意的是，在这个绘制操作中为它找到合适的位置的过程中，也必须保证它和其他 batch 没有交集。因此，batch 列表都以逆序寻找一个合适的位置，并确认对应的位置与其他元素没有交集。如果出现了交集，那么对应操作则不能被合并，并需要在这个位置创建一个新的 DrawBatch，并将其插入 Mergebatchedhaspmap。新的 batch 会被添加到 batch 队列的相应位置中。无论发生什么，改操作都会被加入到当前的 batch 中，区别在于：是在新的 batch 还是已存在的 batch 中。\n\n具体的实现会比我们的简化讲解更复杂（虽然我们这里讲的也很难懂……）。但其中优化的方法值得我们学习：算法通过移除堵塞的绘制操作尽可能地避免重绘，同时通过对为合并的操作进行重排序，从而避免 GPU 状态改变带来的开销。\n\n## 绘制界面 ##\n\n在重排序和合并后，新的 deferred display list 终于可以被绘制到屏幕上了。\n\n![](https://blog.inovex.de/wp-content/uploads/2015/03/ddl-flush.png)\n\n在 OpenGLRenderers::drawDisplayList(…) 方法里，deferred display list 其实就是一个填满了操作的新建普通显示列表，填充完成后延迟显示页面将绘制它自身。\n\n**OpenGLRenderer: drawDisplayList(…)**\n\n```java\n\tstatus_t OpenGLRenderer::drawDisplayList(\n\t               DisplayList* displayList, Rect& dirty,\n\t               int32_t replayFlags) {\n\t    // All the usual checks and setup operations \n\t    // (quickReject, setupDraw, etc.)\n\t    // will be performed by the display list itself\n\t    // 所有的常规检查与创建操作（例如 quickReject, setupDraw 等等）都会由 display list 完成\n\n\t    if (displayList && displayList->isRenderable()) {\n\t        DeferredDisplayList deferredList(*(mSnapshot->clipRect));\n\t        DeferStateStruct deferStruct(\n\t            deferredList, *this, replayFlags);\n\t        displayList->defer(deferStruct, 0);\n\t        return deferredList.flush(*this, dirty);\n\t    }\n\t    return DrawGlInfo::kStatusDone;\n\t}\n```\n\nmultiDraw(…) 方法会在列表中的第一个操作中被调用，而其他的操作都被视作参数。被调用的操作负责立刻绘制所有被提供的操作，并调用 OpenGLRenderer 执行绘制其自身的操作。\n\n## 显示列表中的操作 ##\n\n每一个绘制操作都会在拥有对应显示操作列表的 Canvas 里被执行，所有显示操作列表必须实现重载了绘制操作的 replay() 方法。这些绘制操作调用 OpenGLRenderer 去绘制他们，当我们创建一个操作时需要提供一个 renderer 的引用。除此以外，我们还需要实现 onDefer() 方法，并返回操作的 drawId 和 mergeId。为合并的 batch 会设置相应的绘制 id 为 kOpBatch_None。可合并的操作必须实现用于立刻绘制所有已合并的操作的 multiDraw() 方法。\n\n例如，绘制 9-Patch 的操作包含了下列 multiDraw(…) 实现：\n\n**DrawPatchOp::multiDraw(…)**\n\n```java\n\tvirtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,\n\t        const Vector<OpStatePair>& ops, const Rect& bounds) {\n\t \n\t    // Merge all 9-Patche vertices and texture coordinates \n\t    // into one big vector\n\t    // 将所有 9-Patche 图片的顶点坐标和纹理坐标合并到一个矢量中\n\n\t    Vector<TextureVertex> vertices;\n\t    for (unsigned int i = 0; i < ops.size(); i++) {\n\t        DrawPatchOp* patchOp = (DrawPatchOp*) ops[i].op;\n\t        const Patch* opMesh = patchOp->getMesh(renderer);\n\t        TextureVertex* opVertices = opMesh->vertices;\n\t        for (uint32_t j = 0; j < opMesh->verticesCount; \n\t             j++, opVertices++) {\n\t            vertices.add(TextureVertex(opVertices->position[0], \n\t                                       opVertices->position[1],\n\t                                       opVertices->texture[0], \n\t                                       opVertices->texture[1]));\n\t        }\n\t    }\n\t \n\t    // Tell the renderer to draw multipe textured polygons\n\t    // 让渲染器绘制具有多种纹理的多边形\n\t    return renderer.drawPatches(mBitmap, getAtlasEntry(),\n\t                        &vertices[0], getPaint(renderer));\n\t}\n```\n\n9-Patch 图片的 batchId 总是常量 kOpBatch_Patch，而 mergeId 则是指向图片的指针，因此，所有使用了相同图片的 9-Patch 对象都能够被合并为一个。此外，这种特性对我们使用资源文件里的图片非常有帮助，因为现在 Android 框架层所有经常被使用的 9-Patch 图片都可以根据其相同的纹理合并到同一个地方存储、使用。\n\n## 纹理贴图集 ##\n\nAndroid 的起始进程 Zygote 总会预加载一些与所有进程共享的资源文件，这些资源文件夹包含了频繁被使用的那些 9-Batch 图片和 Android 控件使用的图片。但在 Android 4.4 之前，每一个进程在 GPU 内存中都拥有这些资源文件的独立拷贝。从 Android 4.4 开始，这些频繁被使用的资源文件则被打包到一个纹理贴图集，随后传输到 GPU 内存中，并共享于所有进程之中。在这些操作完成之后才有可能对标准 Android 框架中的 9-Patch 和 Drawable资源文件进行合并。\n\n![](https://blog.inovex.de/wp-content/uploads/2015/03/atlas.png)\n\n> 系统产生的纹理贴图集是为了减少切换纹理带来的 GPU 负荷。\n\n刚刚那张图展示了 Nexus 7 在 Android 4.4 系统下生成的纹理贴图集，图集中包含了所有频繁被使用的 Android 框架层图像资源，如果你看得仔细，你会发现 9-Patch 文件没有突出布局和间距区域的边界，而原始的资源文件在系统启动之初则进行了解析，不过它们之后也不会被用于绘制，所以我们也不用在意。\n\n在 Android 进行系统更新后，系统第一次进行引导时（或者每一次进行引导时）， AssetAtlasService 都会重新生成纹理贴图集，并在之后的每一次重新启动过程中再次使用它，直到 Android 更新的内容被应用于系统中。\n\n为了生成纹理贴图集，service 组件会强行搜查各种图集配置，想尽千方百计找到那个能穿上水晶鞋的贴图集配置，那么什么样的贴图集配置是最好的呢？答案是：纹理资源最丰富，且纹理尺寸最小。原因在于：我们所获得的配置信息，会被写入到 /data/system/framework_atlas.config 中，此外，无论元素是否允许旋转，是否添加了间距，配置信息中都包含了我们选择的算法和尺寸大小。完成上述操作后，配置信息就会在之后的每一次重新启动过程中被应用，生成纹理贴图集。之后，系统会分配一个 RGBA8888 的图形缓存区，通过使用 Skiabitmap 将 资源纹理贴图集和所有资源文件绘制到这个缓存区中。经过上述繁复的操作后，资源纹理贴图集将在 AssetAtlasService 组件整个生命周期中有效，只有在系统自身被关闭时才会被释放。\n\n为了真正把所有资源文件打包到一个图集中，AssetAtlasService 会从打包空白纹理开始。在将第一个资源文件放入之后，剩下的空间将会被切分成两个矩形区域。然后利用我们在配置信息中选择的算法，可以将这两个区域分别作为水平方向和垂直方向的视图处理区域。而空白纹理之后的纹理资源文件将会被添加到足以存放它的第一个区域中。而这个区域会再次被切分成两个区域，把下一个资源文件添加到相应区域后再次切分，循环往复。可能有人会问了，这样难道不会因为迭代操作使得时间开销很大吗？不用担心，AssetAtlasService 同时使用多个线程并行操作，使得时间开销被大大减少。\n\n当一个新的 App 被创建，它的硬件渲染器需要 AssetAtlasService 为它提供相应的纹理贴图，并且当渲染器每一次需要绘制 bitmap 或者 9-Patch 图片，它都会先检查它的贴图集。\n\n## 字体的缓存与绘制 ##\n\n为了合并包含文字的 View 的绘制操作，一个简单的方法就是缓存字体。但与纹理贴图集的操作方法相反，字体集是每一个 App 或字体独有的。但由于字体的颜色会被应用到 shader 中，因此 Android 不会将文字颜色添加到字体集中。\n\n![](https://blog.inovex.de/wp-content/uploads/2015/03/font-300x167.png)\n\n> 左边：字体集由字体渲染器生成；右边：用 CPU 生成的几何体渲染字符\n\n就算你只是瞥一眼字体集，你都会注意到只有一部分字符被绘制，如果你看得认真一些，你会注意到只有被使用的字符才会被绘制出来（没有重复的字符）！如果你看到这里在想 Android 支持多少种语言，抑或是支持多少种字符，那只缓存被使用的字符确实是最优解。又因为 Actionbar 和 Button 使用着相同的字体，那么它们俩使用的所有字符都能被合并到一种纹理中。\n\n为了将相应的文字绘制到视图中，渲染器需要在一块拥有边界的纹理区域中生成一个几何体。几何体由 CPU 生成后通过 OpenGL 的 glDrawElements() 命令绘制，如果设备支持 OpenGL ES 3.0,字体渲染器会异步更新字体的纹理缓存，并将其上传到框架的入口，即 GPU 仍接近处于闲置状态时，进行这样的操作能够为每一个框架的加载节省下宝贵的时间。为了实现异步上传，纹理缓存会被实现为 OpenGL 的像素缓存对象。\n\n## OpenGL ##\n\n在博文的前言阶段我曾许下承诺：我会结合部分原生的 OpenGL 绘制命令讲解博文中的知识。那么现在，就是我兑现诺言的时刻了。从此刻开始，我会用 OpenGL 的绘制 log 讲解之前的简单示例 Activity（只有一个 Button 的那个例子！）\n\n```shell\n\tglUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)\n\t\n\tglGenBuffers(n = 1, buffers = [3])\n\t\n\tglBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 3)\n\t\n\tglBufferData(target = GL_ELEMENT_ARRAY_BUFFER, size = 24576, data = [ 24576 bytes ], usage = GL_STATIC_DRAW)\n\t\n\tglVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf18)\n\t\n\tglVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf20)\n\t\n\tglVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)\n\t\n\tglVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)\n\t\n\tglDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 72, type = GL_UNSIGNED_SHORT, indices = 0x0)\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)\n\t\n\tglBufferSubData(target = GL_ARRAY_BUFFER, offset = 768, size = 576, data = [ 576 bytes ])\n\t\n\tglDisable(cap = GL_BLEND)\n\t\n\tglUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 33.0, 0.0, 1.0])\n\t\n\tglVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x300)\n\t\n\tglVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x308)\n\t\n\tglDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 54, type = GL_UNSIGNED_SHORT, indices = 0x0)\n\t\n\teglSwapBuffers()\n```\n\n你可以在博文的结尾看到完整的绘制 log\n\n## 结论 ##\n\n两篇超简单的博文看下来，Android 如何在 display list 中通过一系列的命令对 View 层的操作进行重排序和合并，如何在底层中执行这些命令，我相信没人会不懂了吧！（有人懂才怪呢 XD～）\n\n结合两篇博文的知识储备，我们重新回去分析之前的示例 App：App 中 View 的整个渲染流程其实只有5步：\n\n![](https://blog.inovex.de/wp-content/uploads/2015/03/one-button-all-1024x180.png)\n\n1. 布局绘制其背景图片，也就是我们的线性布局。\n\n2. ActionBar 和 Button 的 9-Patch 图片文件均被绘制，这两个操作被合并到一个 batch 中，因为它俩的 9-Patch 被用在相同的纹理区域中。\n\n3. 为 Actionbar 绘制一个线性布局。\n\n4. 同时绘制 Button 和 ActionBar 中的文字，因为这两个 View 使用了相同的字体，使得字体渲染器能够使用相同的字体纹理，因此能合并两个文字绘制操作。\n\n5. 应用图标绘制完成。\n\n就像你看到的，我们深入解析了绘制 View 层的操作是如何在底层与 OpenGL 命令一一对应的，这个系列的博文也算是得到了一个完美的收尾了。\n\n## Download ##\n\n文章中引用的论文可以下载，[链接在此](http://mathias-garbe.de/files/introduction-android-graphics.pdf)\n\n## 完整 Log ##\n\n**Display List**\n\nShell\n\n```shell\n\tStart display list (0x5ea4f008, PhoneWindow.DecorView, render=1)\n\t  Save 3\n\t  ClipRect 0.00, 0.00, 720.00, 1184.00\n\t  SetupShader, shader 0x5ea5af08\n\t  Draw Rect    0.00    0.00  720.00 1184.00\n\t  ResetShader\n\t  Draw Display List 0x5ea64d30, flags 0x244053\n\t  Start display list (0x5ea64d30, ActionBarOverlayLayout, render=1)\n\t    Save 3\n\t    ClipRect 0.00, 0.00, 720.00, 1184.00\n\t    Draw Display List 0x5ea5ad78, flags 0x24053\n\t    Start display list (0x5ea5ad78, FrameLayout, render=1)\n\t      Save 3\n\t      Translate (left, top) 0, 146\n\t      ClipRect 0.00, 0.00, 720.00, 1038.00\n\t      Draw Display List 0x5ea59bf8, flags 0x224053\n\t      Start display list (0x5ea59bf8, RelativeLayout, render=1)\n\t        Save 3\n\t        ClipRect 0.00, 0.00, 720.00, 1038.00\n\t        Save flags 3\n\t        ClipRect   32.00   32.00  688.00 1006.00\n\t        Draw Display List 0x5cfee368, flags 0x224073\n\t        Start display list (0x5cfee368, Button, render=1)\n\t          Save 3\n\t          Translate (left, top) 32, 32\n\t          ClipRect 0.00, 0.00, 243.00, 96.00\n\t          Draw patch    0.00    0.00  243.00   96.00\n\t          Save flags 3\n\t          ClipRect   24.00    0.00  219.00   80.00\n\t          Translate by 24.000000 23.000000\n\t          Draw Text of count 12, bytes 24\n\t          Restore to count 1\n\t        Done (0x5cfee368, Button)\n\t        Restore to count 1\n\t      Done (0x5ea59bf8, RelativeLayout)\n\t    Done (0x5ea5ad78, FrameLayout)\n\t    Draw Display List 0x5ea64ac8, flags 0x24053\n\t    Start display list (0x5ea64ac8, ActionBarContainer, render=1)\n\t      Save 3\n\t      Translate (left, top) 0, 50\n\t      ClipRect 0.00, 0.00, 720.00, 96.00\n\t      Draw patch    0.00    0.00  720.00   96.00\n\t      Draw Display List 0x5ea64910, flags 0x224053\n\t      Start display list (0x5ea64910, ActionBarView, render=1)\n\t        Save 3\n\t        ClipRect 0.00, 0.00, 720.00, 96.00\n\t        Draw Display List 0x5ea63790, flags 0x224053\n\t        Start display list (0x5ea63790, LinearLayout, render=1)\n\t          Save 3\n\t          Translate (left, top) 17, 0\n\t          ClipRect 0.00, 0.00, 265.00, 96.00\n\t          Draw Display List 0x5ea5fe80, flags 0x224053\n\t          Start display list (0x5ea5fe80, \n\t                              ActionBarView.HomeView, render=1)\n\t            Save 3\n\t            ClipRect 0.00, 0.00, 80.00, 96.00\n\t            Draw Display List 0x5ea5ed00, flags 0x224053\n\t            Start display list (0x5ea5ed00, ImageView, render=1)\n\t              Save 3\n\t              Translate (left, top) 8, 16\n\t              ClipRect 0.00, 0.00, 64.00, 64.00\n\t              Save flags 3\n\t              ConcatMatrix \n\t                [0.67 0.00 0.00] [0.00 0.67 0.00] [0.00 0.00 1.00]\n\t              Draw bitmap 0x5d33ae70 at 0.000000 0.000000\n\t              Restore to count 1\n\t            Done (0x5ea5ed00, ImageView)\n\t          Done (0x5ea5fe80, ActionBarView.HomeView)\n\t          Draw Display List 0x5ea63618, flags 0x224053\n\t          Start display list (0x5ea63618, LinearLayout, render=1)\n\t            Save 3\n\t            Translate (left, top) 80, 23\n\t            ClipRect 0.00, 0.00, 185.00, 49.00\n\t            Save flags 3\n\t            ClipRect    0.00    0.00  169.00   49.00\n\t            Draw Display List 0x5ea634a0, flags 0x224073\n\t            Start display list (0x5ea634a0, TextView, render=1)\n\t              Save 3\n\t              ClipRect 0.00, 0.00, 169.00, 49.00\n\t              Save flags 3\n\t              ClipRect    0.00    0.00  169.00   49.00\n\t              Draw Text of count 9, bytes 18\n\t              Restore to count 1\n\t            Done (0x5ea634a0, TextView)\n\t            Restore to count 1\n\t          Done (0x5ea63618, LinearLayout)\n\t        Done (0x5ea63790, LinearLayout)\n\t      Done (0x5ea64910, ActionBarView)\n\t    Done (0x5ea64ac8, ActionBarContainer)\n\t    Draw patch    0.00  146.00  720.00  178.00\n\t  Done (0x5ea64d30, ActionBarOverlayLayout)\n\tDone (0x5ea4f008, PhoneWindow.DecorView)\n```\n\n**OpenGL**\n\n```shell\n\teglCreateContext(version = 1, context = 0)\n\t\n\teglMakeCurrent(context = 0)\n\t\n\tglGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])\n\t\n\tglGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])\n\t\n\tglGetString(name = GL_VERSION) = OpenGL ES 2.0 14.01003\n\t\n\tglGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])\n\t\n\tglGenBuffers(n = 1, buffers = [1])\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 1)\n\t\n\tglBufferData(target = GL_ARRAY_BUFFER, size = 64, data = [64 bytes], \n\t             usage = GL_STATIC_DRAW)\n\t\n\tglDisable(cap = GL_SCISSOR_TEST)\n\t\n\tglActiveTexture(texture = GL_TEXTURE0)\n\t\n\tglGenBuffers(n = 1, buffers = [2])\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)\n\t\n\tglBufferData(target = GL_ARRAY_BUFFER, size = 131072, data = 0x0, \n\t             usage = GL_DYNAMIC_DRAW)\n\t\n\tglGetIntegerv(pname = GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,\n\t              params = [16])\n\t\n\tglGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])\n\t\n\tglGenTextures(n = 1, textures = [1])\n\t\n\tglBindTexture(target = GL_TEXTURE_2D, texture = 1)\n\t\n\tglEGLImageTargetTexture2DOES(target = GL_TEXTURE_2D, \n\t                             image = 2138532008)\n\t\n\tglGetError(void) = (GLenum) GL_NO_ERROR\n\t\n\tglDisable(cap = GL_DITHER)\n\t\n\tglClearColor(red = 0,000000, green = 0,000000, blue = 0,000000, \n\t             alpha = 0,000000)\n\t\n\tglEnableVertexAttribArray(index = 0)\n\t\n\tglDisable(cap = GL_BLEND)\n\t\n\tglGenTextures(n = 1, textures = [2])\n\t\n\tglBindTexture(target = GL_TEXTURE_2D, texture = 2)\n\t\n\tglPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 1)\n\t\n\tglTexImage2D(target = GL_TEXTURE_2D, level = 0, \n\t             internalformat = GL_ALPHA, width = 1024, height = 512, \n\t             border = 0, format = GL_ALPHA, type = GL_UNSIGNED_BYTE, \n\t             pixels = [])\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, \n\t                param = 9728)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9728)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)\n\tglViewport(x = 0, y = 0, width = 800, height = 1205)\n\t\n\tglPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 1)\n\t\n\tglTexSubImage2D(target = GL_TEXTURE_2D, level = 0, xoffset = 0, yoffset = 0, width = 1024, height = 80, format = GL_ALPHA, type = GL_UNSIGNED_BYTE, pixels = 0x697b7008)\n\t\n\tglInsertEventMarkerEXT(length = 0, marker = Flush)\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)\n\t\n\tglBindTexture(target = GL_TEXTURE_2D, texture = 1)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, param = 9729)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9729)\n\t\n\tglCreateShader(type = GL_VERTEX_SHADER) = (GLuint) 1\n\t\n\tglShaderSource(shader = 1, count = 1, string = attribute vec4 position;\n\t\n\tattribute vec2 texCoords;\n\t\n\tuniform mat4 projection;\n\t\n\tuniform mat4 transform;\n\t\n\tvarying vec2 outTexCoords;\n\t \n\tvoid main(void) {\n\t    outTexCoords = texCoords;\n\t    gl_Position = projection * transform * position;\n\t}\n\t \n\t, length = [0])\n\tglCompileShader(shader = 1)\n\t\n\tglGetShaderiv(shader = 1, pname = GL_COMPILE_STATUS, params = [1])\n\t\n\tglCreateShader(type = GL_FRAGMENT_SHADER) = (GLuint) 2\n\t\n\tglShaderSource(shader = 2, count = 1, string = precision mediump float;\n\t \n\tvarying vec2 outTexCoords;\n\tuniform sampler2D baseSampler;\n\t \n\tvoid main(void) {\n\t    gl_FragColor = texture2D(baseSampler, outTexCoords);\n\t}\n\t \n\t, length = [0])\n\tglCompileShader(shader = 2)\n\t\n\tglGetShaderiv(shader = 2, pname = GL_COMPILE_STATUS, params = [1])\n\t\n\tglCreateProgram(void) = (GLuint) 3\n\t\n\tglAttachShader(program = 3, shader = 1)\n\t\n\tglAttachShader(program = 3, shader = 2)\n\t\n\tglBindAttribLocation(program = 3, index = 0, name = position)\n\t\n\tglBindAttribLocation(program = 3, index = 1, name = texCoords)\n\t\n\tglGetProgramiv(program = 3, pname = GL_ACTIVE_ATTRIBUTES, params = [2])\n\t\n\tglGetProgramiv(program = 3, pname = GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, params = [10])\n\t\n\tglGetActiveAttrib(program = 3, index = 0, bufsize = 10, length = [0], size = [1], type = [GL_FLOAT_VEC4], name = position)\n\t\n\tglGetActiveAttrib(program = 3, index = 1, bufsize = 10, length = [0], size = [1], type = [GL_FLOAT_VEC2], name = texCoords)\n\t\n\tglGetProgramiv(program = 3, pname = GL_ACTIVE_UNIFORMS, params = [3])\n\t\n\tglGetProgramiv(program = 3, pname = GL_ACTIVE_UNIFORM_MAX_LENGTH, params = [12])\n\t\n\tglGetActiveUniform(program = 3, index = 0, bufsize = 12, length = [0], size = [1], type = [GL_FLOAT_MAT4], name = projection)\n\t\n\tglGetActiveUniform(program = 3, index = 1, bufsize = 12, length = [0], size = [1], type = [GL_FLOAT_MAT4], name = transform)\n\t\n\tglGetActiveUniform(program = 3, index = 2, bufsize = 12, length = [0], size = [1], type = [GL_SAMPLER_2D], name = baseSampler)\n\t\n\tglLinkProgram(program = 3)\n\t\n\tglGetProgramiv(program = 3, pname = GL_LINK_STATUS, params = [1])\n\t\n\tglGetUniformLocation(program = 3, name = transform) = (GLint) 2\n\t\n\tglGetUniformLocation(program = 3, name = projection) = (GLint) 1\n\t\n\tglUseProgram(program = 3)\n\t\n\tglGetUniformLocation(program = 3, name = baseSampler) = (GLint) 0\n\t\n\tglUniform1i(location = 0, x = 0)\n\t\n\tglUniformMatrix4fv(location = 1, count = 1, transpose = false, value = [0.0025, 0.0, 0.0, 0.0, 0.0, -0.001659751, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -1.0, 1.0, -0.0, 1.0])\n\t\n\tglUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [800.0, 0.0, 0.0, 0.0, 0.0, 1205.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])\n\tglEnableVertexAttribArray(index = 1)\n\t\n\tglVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x681e7af4)\n\t\n\tglVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x681e7afc)\n\t\n\tglVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 4)\n\t\n\tglVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 4)\n\t\n\tglDrawArrays(mode = GL_TRIANGLE_STRIP, first = 0, count = 4)\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)\n\t\n\tglBufferSubData(target = GL_ARRAY_BUFFER, offset = 0, size = 576, data = [ 576 bytes ])\n\t\n\tglBufferSubData(target = GL_ARRAY_BUFFER, offset = 576, size = 192, data = [ 192 bytes ])\n\t\n\tglEnable(cap = GL_BLEND)\n\t\n\tglBlendFunc(sfactor = GL_SYNC_FLUSH_COMMANDS_BIT, dfactor = GL_ONE_MINUS_SRC_ALPHA)\n\t\n\tglUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)\n\t\n\tglGenBuffers(n = 1, buffers = [3])\n\t\n\tglBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 3)\n\t\n\tglBufferData(target = GL_ELEMENT_ARRAY_BUFFER, size = 24576, data = [ 24576 bytes ], usage = GL_STATIC_DRAW)\n\t\n\tglVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf18)\n\t\n\tglVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf20)\n\t\n\tglVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)\n\t\n\tglVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)\n\t\n\tglDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 72, type = GL_UNSIGNED_SHORT, indices = 0x0)\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)\n\t\n\tglBufferSubData(target = GL_ARRAY_BUFFER, offset = 768, size = 576, data = [ 576 bytes ])\n\t\n\tglDisable(cap = GL_BLEND)\n\t\n\tglUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 33.0, 0.0, 1.0])\n\t\n\tglVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x300)\n\t\n\tglVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x308)\n\t\n\tglDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 54, type = GL_UNSIGNED_SHORT, indices = 0x0)\n\t\n\tglEnable(cap = GL_BLEND)\n\t\n\tglUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])\n\t\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)\n\t\n\tglBindTexture(target = GL_TEXTURE_2D, texture = 2)\n\t\n\tglVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x696bd008)\n\t\n\tglVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x696bd010)\n\t\n\tglVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 80)\n\t\n\tglVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 80)\n\t\n\tglDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 120, type = GL_UNSIGNED_SHORT, indices = 0x0)\n\t\n\tglGenTextures(n = 1, textures = [3])\n\t\n\tglBindTexture(target = GL_TEXTURE_2D, texture = 3)\n\t\n\tglPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 4)\n\t\n\tglTexImage2D(target = GL_TEXTURE_2D, level = 0, internalformat = GL_RGBA, width = 64, height = 64, border = 0, format = GL_RGBA, type = GL_UNSIGNED_BYTE, pixels = 0x420cd930)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, param = 9728)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9728)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)\n\t\n\tglTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)\n\t\n\tglUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [64.0, 0.0, 0.0, 0.0, 0.0, 64.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 16.0, 38.0, 0.0, 1.0])\n\tglBindBuffer(target = GL_ARRAY_BUFFER, buffer = 1)\n\t\n\tglVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x0)\n\t\n\tglVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x8)\n\t\n\tglBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 0)\n\t\n\tglDrawArrays(mode = GL_TRIANGLE_STRIP, first = 0, count = 4)\n\t\n\tglGetError(void) = (GLenum) GL_NO_ERROR\n\t\n\teglSwapBuffers()\n```\n\n\n\n"
  },
  {
    "path": "androidweekly/深入了解Bundle和Map/readme.md",
    "content": "深入了解Bundle和Map\n---\n\n>\n* 原文链接 : [The mysterious case of the Bundle and the Map](https://medium.com/the-wtf-files/the-mysterious-case-of-the-bundle-and-the-map-7b15279a794e)\n* 译者 : [yinna317](https://github.com/yinna317) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  翻译完成\n\n##前言\n\n因为往Bundle对象中放入Map实际上没有表面上看起来那么容易。\n\n这篇博客是在Eugenio @workingkills Marletti的帮助下完成的。\n\n**警告**：这是一篇篇幅较长的博客\n\n\n##案例：往Bundle对象放入特殊的Map\n\n假设有这样一个案例：你需要将一个要传递的map附加到Intent对象。这个案例虽然不常见，但是，这种情况也是很有可能发生。\n   \n      \n如果你在Intent对象中附加的是一个Map最常见的接口实现类HashMap，而不是包含附加信息的自定义类，你是幸运的，你可以用以下方法将map附加到Intent对象:\n\n```java\n intent.putExtra(\"map\",myHashMap);\n```\n\n在你接收的Activity里，你可以用以下方法毫无问题地取出之前在Intent中附加的Map:\n\n```java\n HashMap map = (HashMap) getIntent().getSerializableExtra(\"map\");\n```\n    \n\n但是，如果你在Intent对象附加另一种类型的Map，比如：一个TreeMap（或者其他的自定义Map接口实现类），你在Intent中取出之前附加的TreeMap时，你用如下方法：\n\n```java\n TreeMap map = (TreeMap) getIntent().getSerializableExtra(\"map\");`\n```\n    \n然后就会出现一个类转换异常:\n```java\n java.lang.ClassCastException: java.util.HashMap cannot be cast to java.util.TreeMap`\n```\n\n因为编译器认为你的Map(TreeMap)正试图转换成一个HashMap\n\n\n稍后我会详细地为大家讲解我为什么用 getSerializableExtra() 这个方法来取出附加到Intent中的Map。现在我可以先给大家一个通俗易懂的解释：因为所有默认的 Map 接口实现类都是Serializable,并且 putExtra()/getExtra() 方法接受的参数几乎都是“键-值”对，而其中值的类型非常广泛，Serializable就是其中之一，因此我们能够使用 getSerializableExtra() 来取得我们传递的Map。\n\n\n在我们进行下一步之前，让我们去了解调用putExtra()/get*Extra()时涉及到的一些类或方法。\n\n**提示**：文章很长，如果你只是想要一个解决方案的话，请跳到文章的最后面解决方案那里。\n\n\n##Parcels:\n\n大家都知道（也许少部分人不知道），在Android 系统中所有进程间通信是基于Binder机制。但是，希望大家明白的是允许数据在进程间传递是基于Parcel。\n\nParcel是Android进程间通信中， 高效的专用序列化机制。\n\n与 Serializable 相反，Parcels 决不应该被用于储存任何类型的持久性数据，因为 Parcels 并不是为“操作可更新数据”（可更新数据指的是，具有持久性的数据会由于它的长留存时间会不断更新它的值）提供的，Parcels 更多的是传递 “短暂的一次性数据”，所以，不管什么时候使用Bundle，你在底层处理的都是Parcel。比如附加数据到Intent对象，在Fragment中设参数，等等。\n\n\nParcels 能处理很多类型，包括：本地类型，字符串类型，数组类型，Map类型，sparse arrays类型，以及parcelables和serializables对象。\n除非你必须使用Serializable,一般情况下推荐使用Parcelables读写数据到Parcel.\n\n\n相较于Serializable，Parcelable的优势更多地体现在性能上，因为Parcelable在内存开销方面更小，而这个理由足以让我们在大多数情况下毫不犹豫地选择Parcelable而不是Serializable。\n\n\n##深入底层分析\n\n让我们来了解下是什么原因使我们得到了ClassCastException异常。\n从我们的代码中可以看到，我们对Intent中putExtras()的调用实际上是传入了一个String值和一个Serializable的对象，而不是传入一个Map值。因为Map接口实现类都是Serializable的，而不是Parcelable的。\n\n\n###第一步：找到第一个突破口\n\n让我们来看看在Intent.putExtra(String, Serializable)方法中做了什么。\n\n>Intent.java\n\n```java\npublic Intent putExtra(String name, Serializable value) {\n      // ...\n      mExtras.putSerializable(name, value);\n      return this;\n    }\n}\n```  \n\n\n在这里，mExtras是个Bundle，Intent指令所有附加信息到bundle，从而调用了Bundle的putSerializable()方法，让我们来看看在Bundle中的putSerializable()方法中做了什么：\n\n>Bundle.java\n\n```java\n@Override\n    public void putSerializable(String key, Serializable value) {\n      super.putSerializable(key, value);\n    }\n```   \n\n从上面代码我们可以看出，Bundle中的putSerializable()方法中只是对父类的实现，调用了父类BaseBundle中的putSerializable（）方法。\n\n>BaseBundle.java\n\n```java\n void putSerializable(String key, Serializable value) {\n      unparcel();\n      mMap.put(key, value);\n    }\n```  \n\n首先，让我们忽略其中的unparcel()这个方法。我们注意到mMap是一个ArrayMap<String, Object>类型的。这告诉我们，到了这步，我们往mMap中设入的是一个Object类型的。也就是说，不管我们之前是什么类型，在父类BaseBundle这里都转成了Obeject类型。\n\n####这里开始出现问题了\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*GokSVk3wQIhGoCQtVIwNTA.gif)\n\n\n###第二步：分析写入 map\n\n有趣的是当把Bundle中的值写入到一个Parcel中时，如果此时我们去检查我们附加值的类型，我们发现仍然能得到正确的类型。\n\n```java\n \tIntent intent = new Intent(this, ReceiverActivity.class);\n    intent.putExtra(\"map\", treeMap);\n    Serializable map = intent.getSerializableExtra(\"map\");\n    Log.i(\"MAP TYPE\", map.getClass().getSimpleName());\n```  \n    \n\n\n如我们所料，这里打印出来的是TreeMap类型的。因此，在Bundle中写成一个Parcel，与再次读这期间一定发生了类型转换。\n\n\n如果我们观察下是怎样写入Parcel的，我们看到，实际上是调BaseBundle中的writeToParcelInner()方法。\n\n>BaseBundle.java\n\n```java\n \tvoid writeToParcelInner(Parcel parcel, int flags) {\n  if (mParcelledData != null) {\n    // ...\n  } else {\n    // ...\n    int startPos = parcel.dataPosition();\n    parcel.writeArrayMapInternal(mMap);\n    int endPos = parcel.dataPosition();\n    // ...\n  }\n}\n```  \n\n\n跳过所有不相干的代码，我们看到在Parcel的writeArrayMapInternal()方法中做了大量的事（mMap 是一个 ArrayMap类型）\n\n>Parcel.java\n\n```java\n  /* package */ void writeArrayMapInternal(\n    \tArrayMap<String, Object> val) {\n      \t// ...\n      \tint startPos;\n      \tfor (int i=0; i<N; i++) {\n    \t// ...\n    \twriteString(val.keyAt(i));\n    \twriteValue(val.valueAt(i));\n    \t// ...\n      }\n    }\n```  \n\n####下一步让我们更深入地分析\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*TOBlt2WOVLElnQTyG-R7YQ.gif)\n\n\n###第三步：分析写入Map值\n\n那么，在Parcel中writeValue() 方法又是怎样呢？主要是一些if...else语句。\n\n>Parcel.java\n\n```java\n   public final void writeValue(Object v) {\n\t      if (v == null) {\n\t    \twriteInt(VAL_NULL);\n\t      } else if (v instanceof String) {\n\t\t    writeInt(VAL_STRING);\n\t\t    writeString((String) v);\n\t      } else if (v instanceof Integer) {\n\t\t    writeInt(VAL_INTEGER);\n\t\t    writeInt((Integer) v);\n\t      } else if (v instanceof Map) {\n\t\t    writeInt(VAL_MAP);\n\t\t    writeMap((Map) v);\n\t      } else if (/* you get the idea, this goes on and on */) {\n\t    \t// ...\n\t      } else {\n\t    \tClass<?> clazz = v.getClass();\n\t    \tif (clazz.isArray() &&\n\t    \tclazz.getComponentType() == Object.class) {\n\t      // Only pure Object[] are written here, Other arrays of non-primitive types are\n\t      // handled by serialization as this does not record the component type.\n\t      \twriteInt(VAL_OBJECTARRAY);\n\t     \t writeArray((Object[]) v);\n\t    } else if (v instanceof Serializable) {\n\t      // Must be last\n\t      writeInt(VAL_SERIALIZABLE);\n\t      writeSerializable((Serializable) v);\n\t    } else {\n\t      throw new RuntimeException(\"Parcel: unable to marshal value \"+ v);\n\t    }\n\t      }\n\t    }\n```  \n\n虽然TreeMap是以Serializable的类型传入到 bundle，但是在Parcel中writeValue(）方法执行的是map这个分支的代码---“v instanceof Map”，（“v instanceof Map”在“v instanceOf Serializable”之前）\n\n####到了这里，问题更明显了。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*zA893bdM9QmLYPlRe7estg.gif)\n\n\n现在我想，他们是不是对 Map 进行了一些非常规的处理，使得 Map 将无可避免地被转换为 HashMap 类型。\n\n###第四步：分析将Map写入到Parcel中\n\nParcel中的writeMap()方法并没有做什么事，只是将我们传入的Map值强转成Map<String, Object>类型，调用writeMapInternal（）方法。\n\n>Parcel.java\n\n```java\n   public final void writeMap(Map val) {\n      \twriteMapInternal((Map<String, Object>) val);\n    }\n```  \n  \nJavaDoc文档对这个方法的解释非常清楚：即Map必须是String类型的。\n\n尽管我们可能传入一个key值不为String的Map,类型擦除也使我们不会获得运行时错误。(这是完全非法的)\n\n事实上，看一下Parcel中的writeMapInternal()方法，这更打击我们。\n\n>Parcel.java\n\n```java\n  /* package */ void writeMapInternal(Map<String,Object> val) {\n      // ...\n      Set<Map.Entry<String,Object>> entries = val.entrySet();\n      writeInt(entries.size());\n      for (Map.Entry<String,Object> e : entries) {\n    \twriteValue(e.getKey());\n    \twriteValue(e.getValue());\n      }\n    }\n```   \n\n类型擦除使所有的这些代码都不会出现运行时错误。\n\n实际上，在我们遍历Map调用writeValue()方法时，依赖的是原先的类型检查。从我们之前分析writeValue() 这个方法能看出，writeValue(）能处理非String类型的key值。\n\n\n也许这里的文档和代码在某些地方有些不一致（还没有同步）。\n但是，如果你在一个Bundle里对TreeMap<Integer, Object>进行设值和取值，将不会出现问题。\n当然也还是会出现TreeMap转换成HashMap的异常。\n\n\n####黑洞启示录：\n\n![](https://d262ilb51hltx0.cloudfront.net/max/600/1*sOK3EennxwiAPNzz5s0C4Q.gif)\n\n\n在这里已经非常清楚了，当Map写入到一个Parcel时，Map丢失了它们的类型，所以当我们再次读时是没办法来复原原来的信息。\n\n\n\n###第五步：分析读Map\n\n让我们来看看Parcel中readValue()这个方法，这个方法和writeValue()相对应。\n\n>Parcel.java\n\n```java\n public final Object readValue(ClassLoader loader) {\n      int type = readInt();\n    \n      switch (type) {\n    \tcase VAL_NULL:\n      \treturn null;\n    \n    \tcase VAL_STRING:\n      \treturn readString();\n    \n    \tcase VAL_INTEGER:\n      \treturn readInt();\n    \n    \tcase VAL_MAP:\n     \treturn readHashMap(loader);\n    \n    // ...\n      }\n    }\n```  \n\n>parcel处理写入数据的方式是：\n\n1. 写入一个int来定义数据类型（一个VAL_*的常量）。\n\n2. 存储数据本身（包括其他一些元数据，比如String这种没有固定大小的类型的数据长度）。\n\n3. 递归调用非原始数据类型。\n\n\n这里我们可以看到，readValue()方法中，首先读取一个int的数据，这个int数据是在writeValue（）中将TreeMap设成的VAL_MAP的常量，然后去匹配后面的分支,调用readHashMap()方法来取回数据。\n\n>Parcel.java\n\n```java\n  public final HashMap readHashMap(ClassLoader loader)\n    {\n      int N = readInt();\n      if (N < 0) {\n     return null;\n      }\n      HashMap m = new HashMap(N);\n      readMapInternal(m, N, loader);\n      return m;\n    }\n```  \n\nreadMapInternal()这个方法只是将我们从Parcel中读取的map重新进行打包。\n\n这就是为什么我们总是从Bundle中获得一个HashMap，同样的，如果你创建了一个实现了Parcelable自定义类型Map,得到的也是一个HashMap。\n\n很难说本身设计如此，还是是一个疏忽。\n这确实是一个极端例子，因为在一个Intent中传一个Map是比较少见的，你也只有很小的理由来传Serializable而不是Parcelable的。\n但是文档上没有写，这让我觉得应该是个疏忽，而不是本身设计如此。\n\n\n##解决方案：\n\n好了，分析底层代码我们已经弄明白了我们的问题，现在我们定位到问题的关键位置。\n我们需要明白的是在writeValue()方法中，我们的TreeMap不应该进入“v instanceOf Map”这个分支。\n\n\n当我和 Eugenio谈话时，我想到的第一个想法是将map包裹成一个Serializable的容器，这个想法是丑陋的但是有效的。\nEugenio迅速写了个通用的wrapper类解决了这个问题。\n\n>MapWrapper.java\n\n```java\n  public class MapWrapper<T extends Map & Serializable> implements Serializable {\n \n  private final T map;\n\n  public MapWrapper(T map) {\n    this.map = map;\n  }\n\n  public T getMap() {\n    return map;  public static <T extends Map & Serializable> Intent\n         putMapExtra(Intent intent, String name, T map) {\n\n    return intent.putExtra(name, new MapWrapper<>(map));\n  }\n\n  public static <T extends Map & Serializable> T\n         getMapExtra(Intent intent, String name)\n         throws ClassCastException {\n\n    Serializable s = intent.getSerializableExtra(name);\n    return s == null ? null : ((MapWrapper<T>)s).getMap();\n  }\n}\n  }\n```  \n\n\n##另一个可行的解决方案：\n\n另一个解决方法是，在你将Map附加到Intent前，将Map转成byte array的。然后调用getByteArrayExtra()方法。但是这种方法你必须处理序列化与反序列化的问题。\n\n如果你想要其他的解决方案，你可以依据Eugenio提供的代码要点来写一个。\n\n##当你不能掌控Intent上面的代码时：\n\n也许，有这样或那样的原因，对于Bundle中的代码你无法掌控，比如可能在第三方的library中。\n\n这种情况，要想到，\nMap接口实现类有一个构造器方法，可以将map作为参数传入，比如 new TreeMap(Map)，你可以把从Bundle中取回的HashMap，用构造器的方式转成你想要的类型。\n\n不过，要记得的是，这种用构造器的方式，map中的附加属性将会丢失，只有键值对被保存了下来。\n\n##总结：\n\n在Android开发中，你可能会被一些表面的事所欺骗，特别是一些小的，似乎是无关紧要的事。\n\n当事情没有像我们期盼中那样发生时，不要死盯着JavaDoc文档，因为JavaDoc可能过时了，JavaDoc的作者也不知道你的特殊需求。这个时候去看看源码，答案可能在AOSP代码里。\n\nAOSP代码是我们的巨大财富，这在移动开发领域几乎是独一无二的，因为我们能够准确的知道在底层都做了些什么。\n\n\n当你知道了底层代码执行了什么，你也就能够成为一个更好的开发人员。\n\n记住：凡事要嘛就是成功，要嘛就是失败\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/0*40IPQ7jhknFGYKNR.)\n\n\n##最后：\n\n感谢[chaossss](https://github.com/chaossss)  帮我校对文章\n\n初次翻译，Android也是刚接触，翻译不好的地方，请大家多多指教。\n\n\n\n\n\n"
  },
  {
    "path": "androidweekly/符合Material Design的抽屉导航效果/readme.md",
    "content": "###符合Material Design的抽屉导航效果：\n---\n>\n* 原文链接 : [Navigation Drawer styling according to Material Design](https://medium.com/@sotti/navigation-drawer-styling-according-material-design-5306190da08f)\n* 译者 : [wly2014](https://github.com/wly2014)\n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  校对完成\n\n![](https://d262ilb51hltx0.cloudfront.net/max/720/1*hLe32r_m-fWUQrnJCalKkQ.png)\n\n####前言：\n现在看来，抽屉式导航[已经成为主流导航模式之一](http://goo.gl/w4FVWS)。尽管广受[批评](https://medium.com/@villainschorus/your-problem-goes-beyond-hamburgers-e0aae6a1576)，但我还是很喜欢该样式，因此我决定在我写的几个app上添加这个控件。这篇文章想通过介绍我觉得抽屉式导航有趣的地方，帮助阅读本文的 Android 开发者们学习到一些知识，同时从其他人的评论中学习到更多的东西。\n\n这是三篇文章中的第二篇。欢迎查看第一篇和第三篇：\n\n    * [Material Design下的抽屉导航的大小](https://medium.com/@sotti/navigation-drawer-styling-under-material-design-f0767882e692)\n\t* Material Design下的抽屉效果的行为（敬请期待）\n\n你可以从下面查看Material Design指南上关于抽屉导航的部分：\n\n\t* [Navigation Drawer pattern](http://www.google.com/design/spec/patterns/navigation-drawer.html)\n\t* [Material Design metrics and keylines](http://www.google.com/design/spec/layout/metrics-keylines.html#)\n\t* [Toolbar metrics](http://www.google.com/design/spec/layout/structure.html#structure-toolbars)\n\n### 开始：\n\n抽屉式导航一直以来都是被争论的热点话题。当 Material Design 规范刚发布的时候，抽屉式导航就在规范中处在一个尴尬的位置，使得开发者们很[困惑](http://goo.gl/q3dnCI)到底要不要使用抽屉式导航，甚至在 Material Design 规范下的[开发指南出来以后](https://plus.google.com/+SebastianoPoggi/posts/6MFgMeRLrrg)，有关如何在 Android 开发中对待抽屉式导航都没有一个清晰的答案。\n\n现在，虽然这儿已有一些漂亮的[库](https://plus.google.com/+MikePenz/posts/Erwn9mDZszr)，甚至有一些Google的[源码](https://github.com/google/iosched)能拿来看看... 但是，你之所以还来到这里，可能是因为你[热衷于编程](http://i3.kym-cdn.com/photos/images/facebook/000/234/765/b7e.jpg)。\n\n在这篇文章里，我将会谈论如何使用抽屉的样式，但是并不会完全涉及 Material Design 指南上的所有样式，只是捡一些我认为需要强调的东西。\n\n准备好了吗？\n\n### 位置\n在过去，抽屉式导航栏 和 ActionBar 处于同一个 View 层级\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*Vh9mKyCUMDCFMFjM8ZEG9Q.png)\n\n随着这种设计模式的发展，其中相互矛盾的地方也开始浮出水面……在 Material Design 中很清晰地指出：处于不同 View 层级的两个页面，是不能共存于同一个父布局中的同一个 View 层级的。有关这个问题引发了[许多讨论](https://plus.google.com/+RomanNurik/posts/3G8zYvN5oRC)，但[更重要的是](http://goo.gl/SHrQmd)， [Google 并没有给出好的解释](http://goo.gl/FghQhb)，不过最后抽屉式导航还是得到了一个被认可的定义：\n\n左抽屉式导航打开后，导航栏的高度应该和屏幕一致，但要低于状态栏。此外，其他任何在抽屉层以下的内容都应该被阴影覆盖，但这些内容又是可见的。\n\n> 左侧的导航抽屉横跨屏幕的高度并覆盖下方的状态栏。抽屉下的东西将会变暗。但仍是可见的。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/1267/1*Gg34WwPMo1ylOX7KLPaY0w.png)\n\n注意图中右边的抽屉略有不同。\n\n注：我假设你使用的是[AppCompat ](https://chris.banes.me/2014/10/17/appcompat-v21/) [toolbar](https://developer.android.com/reference/android/widget/Toolbar.html)\n\n既然toolbar是View 层级的另一个view，不妨就就将toolbar置于抽屉的layout之下，跟其他的view一样。\n\n如果你在Google应用上看到如下的东西也不用担心：\n\n![](https://d262ilb51hltx0.cloudfront.net/max/700/1*ncHm2VDOAU4GRN5Act_P5A.png)\n\nGoogle+ Photos可能是最后一个使用抽屉，却没有覆盖ActionBar/Toolbar的Google应用，但是我想他应该很快就会被改过来。\n\n### 旋转标题图标\n\n你还记得当抽屉打开时那个ActionBar/Toolbar中的漂亮的图标动画吗？这个动画在Holo主题下并不是很好看，在 Matarial Design 下却很漂亮。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/768/1*QfXDV7tpaGwEqil_l6Pe8Q.gif)\n\n我觉得当它第一次出现在Google Play Store时，很多的开发者和设计师都会很喜欢它。\n\n仅在那数周之后，抽屉式导航上就[开始出现该动画](https://lh6.googleusercontent.com/-9oPeSA7FUkI/VOs530mLbLI/AAAAAAABapc/ekQNTZPXyoE/w499-h281-no/tumblr_inline_nk2y80nOF91rllljr.gif)。那时它显得很特别，因为在Google Material Design videos 和 promo features上都出现了它的身影。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*XI72aAgZON-g0F_Teo6kiA.jpeg)\n\n我记得很多人在第一次遵循 Matarial Design 规范开发时，就是使用这个汉堡/箭头图标动画。Google第一次实现这个抽屉式导航栏的时候，并不是把它放在 ToolBar 的上面，因此你可以看到这个精美的动画。但是 Material Design 指南发布后，出现了一个很奇怪的现象，很多的Google应用都在做与 Material Design 指南背道而驰的事情。即使是现在我写这篇博文的时候，大部分的应用还是在遵循着 Material Desgin 指南，但我还是希望抽屉式导航栏都能在 Toolbar 上面。\n\n在我看来，Material Design发布的已经有点晚了。因为图标动画已出现在发布了的SDK中并且在默认情况下被使用了。\n\n由于某些原因，即使要求把抽屉导航栏布局到其他的view之上，但大多数的google应用还是会有这样的动画(注：在写下这篇文章的时候，Gmail和Inbox已经停用了)，即使你很难发现它（但如果你仔细看的话，在缓慢的移动抽屉式导航时，还是可以看到动画的）。让我很不爽的是：一旦你看到了，又会每次都忍不住去看，得不偿失。因此，我也决定关掉这种动画效果。\n\n第一眼看来，DrawerArrowStyle的参数很容易懂：\n\n[< item name=\"spineBars\"> true < /item>](https://developer.android.com/reference/android/support/v7/mediarouter/R.attr.html#spinBars)\n\n[Android Developers中定义如下：](https://developer.android.com/reference/android/support/v7/appcompat/R.attr.html#spinBars)\n\n> 在移动抽屉导航栏的过程中，无论图标是否应该旋转，都要设定一个布尔值：要么是 true，要么是false.\n\n但问题是，这并不能起到作用。如果你设置为false，bars就会以一种奇怪的方式旋转。\n\n我发现的解决的方式是：覆写onDrawerSlide方法。见下面链接的Gist。\n\n> 既然这个图标动画的可视性较差，那就没有必要再保留它了。如果你不注意看，你就看不到它，但当你注意看并看到的时候，又不知道是怎么回事。\n\n### 资料图片\n\n这张个人头像是圆形的，我们有很多方法能让图片变成圆形，但我每次需要实现这个需求想起的都是 [Romain Guy](https://plus.google.com/+RomainGuy/about) 的[方法](http://www.curious-creature.com/2012/12/11/android-recipe-1-image-with-rounded-corners/comment-page-1/)。所以这次我还是使用了 Romain Guy 的 CircleImageView，毕竟“信RM，无BUG”。有人可能会提到 Google IO 大会上被使用的那个[ App](https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/BezelImageView.java)，我还没去了解过它的具体实现，可能值得我们看一看吧。\n\n在Google Paly Movies与Google Paly Books上，这个图片有一个白色的边框。而其他的Google app上却没有。Google+和Hangouts的资料图片在toolbar上，不过却有白边框。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*58WoVNOl-JIRyp1o3_f9JQ.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*v5KQiG7MNlpFdCjSk0WHqQ.jpeg)\n\n注意：[查看资料图片大小](https://medium.com/@sotti/navigation-drawer-styling-under-material-design-f0767882e692)\n\n> 资料图片是圆形的，通常没有边框。建议你通过Romain Guy推出的库来获得圆形。\n\n### 封面图片\n\n封面图片（不同于资料图片），是账号/头像部分的背景（就是抽屉式导航的上部，通常你可以在此切换账号，查看昵称，email和你的资料图片）。\n\n这块的文字是白色的，并且要确保能看的见，你可以应用一个前景或者半透明的黑色来覆盖封面图片。我试了一下，发现40-50% 的黑色是最好的。要注意的是，不要既弄得图片不可见，又弄得文字没法读。\n\n我是在FrameLayout中加一个前景。但我不知道这是不是最好的方法，欢迎大家交流。我并没有实现在账号切换的功能，而且这整个layout/section都是可点击的，有touch反馈，或者是Lollipop中的ripple，或者两者。当然你也可以使用[centerCrop ](https://developer.android.com/reference/android/widget/ImageView.ScaleType.html)scaleType 让它更漂亮。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*JnT0ei6pp-M7Fn0ZZxl-yg.jpeg)\n\n仔细看一下这个图片，你会发现其实它在状态栏下也是可见的。当我写下这行字的时候，Google apps正在应用这种效果。Gmail，Inbox，Keep，Playe Story和Hangouts已经实现了，而其他的也准备实现它。当然，这只是Lollipop及以上的版本中才会有的效果。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*0K95PBc-Ym1quF4S4-svlw.png)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*eYUlXFl-nKp54CbhSqHy5Q.png)\n\n即使是现在，在Play Store 上的Google IO 的有些应用的抽屉式导航也是完全错的，但他们在改良代码，并且准备下一个版本了（不过好像有段时间了... 可能会在今年几个月后的Google IO 大会之前更新）\n\n比较神奇的是[Google ScrimInsets layout](http://goo.gl/07TJnm)。拷贝，粘贴，然后自己试着修修改改就OK了。我觉得Google的人员应该比我做的更好。深入的阅读一下gist上的代码，了解一下关于 themes/styles 的更多的详细内容，能让你有更好地表现。\n\n让我有点疑惑就是ScrimInsets layout能不能应用到Lollipop以下的版本中。我知道在Kit Kat中是可行的，但是Google并没这样做。可以肯定的是“挤满”状态栏和/或导航栏在Lollipop下的版本中并不存在，这可能是背后的一个原因。\n\n注意：[查看封面照片的大小](https://medium.com/@sotti/navigation-drawer-styling-under-material-design-f0767882e692)\n\n> 只有在Lollipop中，抽屉导航会出现在状态栏之下。在Lollipop之下的版本中，挤满状态或导航栏并不是什么事情，这可能是其中的原因。\n\n### 可选中行的背景，图标和文字\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*l38m8vKyELP23Fgo0VIYLg.png)\n\n当要更改抽屉中的主要行的样式的时候，对每行的每个元素，我们都要处理其中的三个子元素（背景，图标，文本）和3个不同的状态（默认，选中，点击）。但每一个开发者都需要明白：开发 App 不需要完全遵循[规范](http://www.google.com/design/spec/patterns/navigation-drawer.html) ，但是了解规范又是必不可少的，大家可以看看 Google 的 App 和其他的一些好看的apps是怎样体现 Material Desgin 所包含的思想的，把你从这些 App 里总结出来的东西应用到你的 App 中。\n\nOkay，现在来看一下Google apps 都有哪些特征。在下面的图片中，第一行是默认的状态，第二行是选中的状态，第三行是点击的状态。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*cbbbhJcPwsrL6XuF_7B-BA.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*S7YeU9ekl1_nC03isgeVBw.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*guYsqqXCcQikqMNMVTL1_g.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*fTlbwdydeGKNznbliR2J-g.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*rzdSKrX4NgQqwPOeNReuTw.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*ubYKjE5BgQUnuf8vXBjucA.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*W0l0rAj_f08DcYiBKhnpYQ.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*WMsmw3UZ6kzBGaMCHPWgHA.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*ZsjyMCtqBLG4iq7_ypaLUA.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*Mn1OyDGujUgv3hXi-bBggw.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*BV3ByPvh4Elbq2MKPrE-Bg.jpeg)\n\n虽然上面的图片看起来很相似，但其实它们是不一样的。总结一下就是：\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*BXfi8inb3rWh0gRCve5uCg.jpeg)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*0_cj6ynaM0rDvBmQG0WJFw.png)\n\n> Google apps看起来十分的连贯，但是当你注意下细节时就会发现，此时抽屉式导航的选中行有超过10多种的样式。\n\n* 拇指规则：\n\n在自己不断地尝试并参考了设计指南与Google apps后，这是我提出的一些想法：\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*zU5_BF4hNM3ESUqqOyn7Dw.png)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*X2iLimaiz8NgrdDtbcinFg.jpeg)\n\n### 如何实现\n\n尽管我想知道，但是没人告诉我他是怎么来实现的，所以我自己就这么做了。\n\n首先，使用两个drawable作为背景。一个放在res/drawable-v21里，为Lollipop及以上的版本使用；另一个在res/drawable中，目标是更低的版本。因为ripple在5.0以下的版本中并不存在。\n\n> 仅在Lollipop及以上的版本中使用ripple。Ripples并不支持5.0以下的版本。\n\n每当你点击一行的时候，ripple就会出现，不管它选中与否。因此，你在res/drawable-v21中的是有一组包含ripple的items的selector。这是因为我们希望对于选中和未选中的行，在被点击时都能显示相同的ripple，但是未选中的背景是白色，而选中的item的背景是grey_200。\n\n另外在res/drawable中，你需要的是一个不包含ripple的selector。\n\n### 图标\n\n在最近的几个月里，我尝试让同一图标呈现出不同的颜色来。 因此，我就把所有的图标都先弄成白色，然后在用想要的颜色进行[着色](https://developer.android.com/reference/android/widget/ImageView.html#attr_android:tint)。这样做的优点是，你不必每次创建一个新的图标。[你可以先从Google上得到不同大小和颜色的这些图标](https://github.com/google/material-design-icons)，再用不同的颜色进行二次加工，看看那种效果最好。另外你要是使用不同颜色的同一图标的话，要记得保存成相同的大小。如果你需要根据状态（点击，选中...）来改变它的颜色的话，可以设置一个[color state list resource](https://developer.android.com/guide/topics/resources/color-list-resource.html)。\n\n我实现的方式是在一个[自定义的ImageView](https://github.com/Sottti/MaterialDesignNavDrawer/blob/master/app/src/main/java/com/demo/materialdesignnavdrawer/customViews/TintOnStateImageView.java)中编写的，因为color state list在Kit Kat及其一下的版本中并不可用（android:tint中可以使用color，但是不能用color state）。\n\n看一下下面链接中的gist中我是如何做的。如果你发现了更好的方式，或错误，请反馈给我。\n\n### Header和footer的表现\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*COQpEPm-a2DLqdifPANmzw.png)\n\n头部（aka account section）中有些是不可滑动的，而有些是可滑动的。以我看来，如果可以的话，最好设计成不可滑动的。这样的话，抽屉式导航看起来更美观，连贯，易用。\n\n底部（aka setting and support）可以是可滑动的，也可以不是。如果你看一下Google Apps，就会发现有些是不可滑动的，而有些是在可滑动的scroll的底部。如果你有需求是不能放在抽屉的底部的话，那就放在能滑动的列表的后面。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*8HEdHs9YQq35OKtsCmAwLQ.png)\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*JW5RFt4JfEahP9tqH3-bPg.png)\n\n再次强调一下，在我看来，不可滑动是最好的方式，但是也可以有例外。比如，当头部不可滑动时，有些条目就被固定了，因此在抽屉里就有了可滚动的区域了。但是如果可滚动的空间太小，那么看起来就很糟糕了（只有一行或两行），要想得到更多的空间，那么一个好的办法就是将footer 解除固定。\n\n> 头部和底部理应被固定住，除非抽屉需要更多的空间以表现更出色。\n\n由于抽屉选项，路径，结构...的不同，因此这里其实并没有什么拇指法则。\n\n### 资源\n-[Google Official Material Design icons](https://github.com/google/material-design-icons)\n-[Material Design Color Definitions](http://www.google.com/design/spec/style/color.html)\n\n### 代码\n\n-[Github项目地址](https://github.com/Sottti/MaterialDesignNavDrawer)\n\n### 总结\n这是关于如何更改抽屉式导航的样式，你仍需要花费很多时间来想清楚你想做成什么样，而这要比做到花费的时间要长的多。\n\n如果你想要了解更改抽屉式导航的样式，请看看另两篇文章。\n\n欢迎评论，反馈... \n\nHava fun！\n"
  },
  {
    "path": "androidweekly/让你的Android应用能使用多种主题-Part-1/readme.md",
    "content": "在你的Android App中支持多主题\n---\n\n>\n* 原文链接 : [Supporting multiple themes in your Android app (Part 1)](http://www.hidroh.com/2015/02/16/support-multiple-themes-android-app/)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n\n![](images/multiple-theme-dark.png)\n![](images/multiple-theme-light.png)\n\n我最近一直在忙着整我的黑客资讯App——Materialistic，今天难得有空，就让我来给大家分享一下我在Materialistic里使用的一个有趣的功能吧。\n\n纵观现在的主流阅读类App，用户最常见的需求就是能够基于自己的阅读习惯选择明亮/灰暗两种风格的主题。为了用户的使用体验，我当然要为Materialistic添加这样的功能啦，要不然没人用我会很伤心的！而且很幸运的是，在Android里支持多种主题的切换并不麻烦（如果你的代码没有问题的话），实现这个功能蛮顺利的。所以今天我打算通过这篇博客给大家介绍我在Materialistic里面为了支持多种主题切换所使用的方法。\n\n准备工作：\n\n1. 你最少要有两个由Android 基本的light/dark主题衍生而来的主题。如果你使用了最新的appcompat-v7包，所对应的就是Theme.AppCompat.Light 或 Theme.AppCompat.Light.DarkActionBar（明亮风格），和Theme.AppCompat（灰暗风格）主题\n\n1. 你需要为你的主题设置颜色。你可以在 [Google design spec](http://www.google.com/design/spec/style/color.html#color-color-palette \" Google design spec website\") 里面看到有关颜色搭配的指导\n\n1. （可选项）为每一个主题的选项菜单图标加上颜色。取决于你的实现方式，染色过程可以是自动的，也可以是手动的，不过自动化的过程不就意味着你可以把一套图标应用于一种主题嘛，其他的调整只要改改颜色就可以了；但就Materialistic的实际需求来考虑，我还是为一个主题预留了多套不同的图标来避免麻烦……\n\n我今天就以明亮风格的主题来开始讲解吧：\n\n## values/styles.xml ##\n\n```xml\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light\">\n   \t\t <item name=\"colorPrimary\">@color/colorPrimary</item>\n   \t\t <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n   \t\t <item name=\"colorAccent\">@color/colorAccent</item>\n   \t\t <item name=\"android:textColorPrimary\">@color/textColorPrimary</item>\n   \t\t <item name=\"android:textColorSecondary\">@color/textColorSecondary</item>\n    \t <item name=\"android:textColorPrimaryInverse\">@color/textColorPrimaryInverse</item>\n  \t     <item name=\"android:textColorSecondaryInverse\">@color/textColorSecondaryInverse</item>\n   \t\t <!-- some other theme configurations for actionbar, overflow menu etc. -->\n   \t\t ...\n    </style>\n```\n\n## values/colors.xml ##\n\n```xml\n    <!-- brand color: orange -->\n    <color name=\"colorPrimary\">#FF9800</color>\n    <color name=\"colorPrimaryDark\">#F57C00</color>\n    <color name=\"colorPrimaryLight\">#FFE0B2</color>\n    <!-- accent color: red -->\n    <color name=\"colorAccent\">#FF5252</color>\n    <!-- text color: white -->\n    <color name=\"textColorPrimary\">#FFFFFF</color>\n    <color name=\"textColorSecondary\">#9E9E9E</color>\n    <!-- inverse text color: 87% black -->\n    <color name=\"textColorPrimaryInverse\">#DE000000</color>\n    <color name=\"textColorSecondaryInverse\">#9E9E9E</color>\n```\n\n## AndroidManifest.xml ##\n\n```xml\n    <application android:name=\".Application\" android:theme=\"@style/AppTheme\">\n    ...\n    </application>\n```\n\ntheme 中涉及的各种属性的含义可以在[Android Developers blog](http://android-developers.blogspot.sg/2014/10/appcompat-v21-material-design-for-pre.html \"Android Developers blog\") 里面找到解释\n\n## 贴心小提示 ##\n\n> 虽然Android里面style的属性/值非常全面，我们想要实现的效果style基本上都包含了有，但是Android文档有关这些主题属性的解释特别少，尤其是对appcompat的解释。所以我们还是建议你写一个小Demo去测试style里面的属性/值应该怎么使用、能实现什么样的效果，然后再根据我们的需求去考虑使用哪些属性/值来实现我们想要的效果。\n\n根据Android的Material Design规范，选项菜单图标的颜色应该和action bar上面的文字颜色保持一致，在我这是通过 android:textColorPrimary 来实现的，也就是使用#FFFFFF，基于这样的规范，我们需要为action bar提供一套白色的选项菜单图标。\n\n## 贴心小提示 ##\n\n> Google 有在 [material-design-icons - Github](https://github.com/google/material-design-icons \"Github\") 上提供一些开源的Material Design图标哦。\n\n## menu/my_menu.xml ##\n\n```xml\n    <menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    \t<item android:id=\"@id/menu_comment\"\n    android:icon=\"@drawable/ic_mode_comment_white_24dp\" />\n    \t<item android:id=\"@id/menu_story\"\n    android:icon=\"@drawable/ic_subject_white_24dp\" />\n    \t<item android:id=\"@id/menu_share\"\n    app:actionProviderClass=\"android.support.v7.widget.ShareActionProvider\" />\n    </menu>\n```\n\n为了使颜色一致，并且能让我们的Views和Texts能够在多个主题下被使用，最好的解决办法就是把颜色变成资源的引用，例如：android:textColor=\"@color/textColorPrimary；又或者是通过设置style来改变，例如：在textEmptyStyle.xml文件下，我们只使用被选中的颜色\n\n## values/styles.xml ##\n\n```xml\n    <style name=\"textEmptyStyle\">\n   \t\t <item name=\"android:textColor\">@color/textColorSecondary</item>\n   \t\t <item name=\"android:textSize\">@dimen/abc_text_size_headline_material</item>\n    ...\n    </style>\n```\n\n我相信通过今天在上面所介绍的这些内容已经足够让我们实现一个符合Material Design的明亮风格的主题了，下一篇博文我将会给大家介绍如何实现一个符合Material Design的灰暗风格的主题，以及如何在运行App的过程中切换主题。希望大家继续关注我的博客哦。\n"
  },
  {
    "path": "androidweekly/让你的Android应用能使用多种主题-Part-2/readme.md",
    "content": "让你的Android应用能使用多种主题 ( Part 2 )\n---\n\n>\n* 原文链接 : [Supporting multiple themes in your Android app (Part 2)](http://www.hidroh.com/2015/02/25/support-multiple-themes-android-app-part-2/)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 :  完成\n\n在 [上一篇博文](http://www.hidroh.com/2015/02/16/support-multiple-themes-android-app/) 中，我们创建了一个明亮风格的主题，并且为实现使用多种主题作了一些前期的准备，而今天呢，我打算在这篇博文中接着上一篇博文继续为大家讲解，而我今天要讲的内容大概是以下三个部分：使 Android 应用能够使用多种主题，创建一个灰暗风格的主题，以及允许 Android 应用在运行时自由地切换不同的主题。\n\n在理想的情况下，如果我们把主题的设置看作是一项配置，那么我们应该能够在类似 \"theme-qualifier\" 的目录下指定我们想要的特定主题，例如：values-dark 就是我们想要的灰暗风格主题；而values-light 则是明亮风格的主题。但很遗憾，在这篇博文所要讲述的实现方法里，这种方法并没有成为实现方式之一。\n\n那么我们要怎么为不同的主题指定相应的资源文件呢？如果我们有了解过 appcompat 是怎么使用资源文件的话，对 Android 系统是如何管理和使用资源文件会有一个粗略的认识。毫无疑问，[Materialistic](https://play.google.com/store/apps/details?id=io.github.hidroh.materialistic) 中使用的方法就是类似于 Android 系统使用的方法。\n\n\n## 主题设置 ##\n\n**values/styles.xml**\n\n```xml\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light\">\n    <!-- original theme attributes -->\n    ...\n    </style>\n    \n    <style name=\"AppTheme.Dark\" parent=\"Theme.AppCompat\">\n   \t\t <item name=\"colorPrimary\">@color/colorPrimaryInverse</item>\n   \t\t <item name=\"colorPrimaryDark\">@color/colorPrimaryDarkInverse</item>\n   \t\t <item name=\"colorAccent\">@color/colorAccentInverse</item>\n   \t\t <item name=\"android:textColorPrimary\">@color/textColorPrimaryInverse</item>\n   \t\t <item name=\"android:textColorSecondary\">@color/textColorSecondaryInverse</item>\n   \t\t <item name=\"android:textColorPrimaryInverse\">@color/textColorPrimary</item>\n   \t\t <item name=\"android:textColorSecondaryInverse\">@color/textColorSecondary</item>\n    ...\n    </style>\n```    \n\n**values/color.xml**\n\n```xml\n    <!-- original color palette -->\n    ...\n    <!-- alternative color palette -->\n    <color name=\"colorPrimaryInverse\">...</color>\n    <color name=\"colorPrimaryDarkInverse\">...</color>\n    <color name=\"colorAccentInverse\">...</color>\n\n```\n\n\n在上面的操作中我们创建了一个名叫 AppTheme.Dark 的灰暗风格主题，此外，为了保持 style 和 color 的一致性，我们的 AppTheme.Dark 主题衍生于 appcompat 的 Theme.AppCompat 主题（一个 Android 自带的灰暗风格主题）。然而，由于我们的两个主题（明亮风格和灰暗风格）衍生于不同的基础主题，因此这两个主题之间并不能够进行属性的共享（在JAVA中，类只能进行单继承）。\n\n这两个主题理应有一些恰当的属性值，能同时用于设置基本的 Android 和 appcompat的主题属性，例如：在灰暗风格中，android:textColorPrimary 应该被设置为明亮的，而在明亮风格中，android:textColorPrimary则应该是灰暗的。按照常用的命名习惯，我们在这里将用相反的后缀来区分可替代的主题颜色。\n\n## 温馨小提示 \n\n> 在某些情况下，一种颜色能同时在明亮风格和灰暗风格的主题中被使用，这当然是喜闻乐见的情况，但是在大部分主题中这并不能够实现。所以我希望你在设计主题的过程中，通过在 AndroidManifest.xml 中短暂地切换你应用里正在使用的可替代主题，以此确定你的主题是否需要添加其他的 colors/style 文件来满足你的主题设计需求。\n\n\n## 特定的主题资源文件 \n\n到了现在，我相信我们都能很轻松地为我们的 App 设计出美如画的灰暗风格主题，但这里还存在一些小麻烦，例如：用于美化 action bar 菜单选项的 drawables 资源文件。灰暗风格的 action bar 需要用明亮的颜色修饰它的菜单选项，反之亦然。为了让 Android 能够在不同的App主题下区分不同的 drawables 资源文件，我们创建了能够指定正确资源文件的 [自定义属性](http://developer.android.com/training/custom-views/create-view.html#customattr) 引用，并且在不同的主题下提供了不同的 drawable 引用，将其值赋给特定的自定义属性。（温婉如妻，appcompat 库贴心地为我们准备了类似 colorPrimary 的自定义属性值）\n\n**values/attrs.xml** \n\n```xml\n\n    <attr name=\"themedMenuStoryDrawable\" format=\"reference\" />\n    <attr name=\"themedMenuCommentDrawable\" format=\"reference\" />\n    ...\n```\n\n**values/styles.xml** \n\n```xml\n\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light\">\n    <!-- original theme attributes -->\n    ...\n  \t  <item name=\"themedMenuStoryDrawable\">@drawable/ic_subject_white_24dp</item>\n   \t  <item name=\"themedMenuCommentDrawable\">@drawable/ic_mode_comment_white_24dp</item>\n    </style>\n    \n    <style name=\"AppTheme.Dark\" parent=\"Theme.AppCompat\">\n    <!-- alternative theme attributes -->\n    ...\n   \t\t <item name=\"themedMenuStoryDrawable\">@drawable/ic_subject_black_24dp</item>\n   \t\t <item name=\"themedMenuCommentDrawable\">@drawable/ic_mode_comment_black_24dp</item>\n    </style>\n```\n\n**menu/my_menu.xml** \n\n```xml\n    <menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    \t<item android:id=\"@id/menu_comment\"\n    android:icon=\"?attr/themedMenuCommentDrawable\" />\n   \t\t<item android:id=\"@id/menu_story\"\n    android:icon=\"?attr/themedMenuStoryDrawable\" />\n   \t\t<item android:id=\"@id/menu_share\"\n    app:actionProviderClass=\"android.support.v7.widget.ShareActionProvider\" />\n    </menu>\n```\n\n根据你实际的主题设计需要，类似的实现也能被用于为大多数自定义属性指定相应的资源值。但这个方法存在一个问题：根据实际的需要从 drawable 资源文件中解析相应的属性值，并应用于主题的方法在API 21之前的版本似乎都不可行。举例来说明这个问题吧：如果你有一个 layer-list 中包含了各种你所需要的 color 的 drawable 资源文件，在API 21之前的版本中，这些 color 的值都应该是固定的，而不是能够在App运行过程中不断变化的。这个问题在 Google I/O 2014 大会上有被提出，并要求给出相应的解决办法。（详情参见 [Click Me!](https://github.com/google/iosched/commit/dd7ed72a7eb2d223203db079bd99d31c6ef3061e)）。\n\n此外，为了避免在不同的主题中重复使用相同的资源文件，我们可以利用 drawable 的 tint 属性解决这个需求。虽然这个属性可以在API 21之后的版本中使用。但 [Dan Lew](http://blog.danlew.net/2014/08/18/fast-android-asset-theming-with-colorfilter/) 在他的博客中为我们介绍了怎么在所有的 API 版本中使用 tint 属性。但就个人偏好来说，如果可以的话，我会更倾向于选择不受 View 逻辑影响的 Java 实现，所以我选择为每一个主题提供不同的 drawable 资源文件。\n\n## 动态主题切换 \n\n现在我们已经有两个可以使用的主题了，接下来我们需要做的就是让用户能够在使用 App 时能够自如地根据他们的个人偏好切换不同的主题。要实现这个功能，我们可以通过使用 SharedPreferences 来实现，通过改变 pref_dark_theme 的值去存储当前被选择的主题并决定我们的 App 将要被切换成什么主题。但从实际情况来考虑，主题切换后，App 所有 Activity 的 View 在被创建之前都应该被改变，所以我们只需要在 onCreate()方法中实现我们的逻辑。\n\n**BaseActivity.java** \n\n```java\n    public abstract class BaseActivity extends ActionBarActivity {\n   \t\t @Override\n    \t protected void onCreate(Bundle savedInstanceState) {\n   \t\t\t if (PreferenceManager.getDefaultSharedPreferences(this)\n   \t\t\t\t .getBoolean(\"pref_dark_theme\"), false)) {\n    \t\t   setTheme(R.style.AppTheme_Dark);\n    \t\t}\n    \tsuper.onCreate(savedInstanceState);\n    \t}\n    }\n```\n\n在这里，因为我们的 App 已经使用了默认的明亮风格主题，所以我们只需要检查默认的引用是否被重载，是否被用于重载灰暗风格的主题。为了默认的引用能够被所有 Activity共享，其中的逻辑已经在 \"base\" Activity中被写好了。\n\n值得注意的是，这个方法只能被用于改变没有处在 [back stack](http://developer.android.com/guide/components/tasks-and-back-stack.html) 中的 Acitivity 的主题。而那些已经在 back stack 中的 Activity，仍然会显示为之前的主题，因为当我们结束当前 Activity，返回到上一个 Activity，只会触发 onResume() 方法，而不是我们期望的 onCreate()方法。因此，考虑到实际的产品功能设计需求，我们当然要解决这些“过时”的 Activity 了，我在这里为大家提供了两种解决办法，都挺简单的：一方面，我们可以清空我们的 back stack；另一方面，一旦 preference 被改变，我们就在 back stack 中按照顺序让所有 Acitivty 出栈后重新加载，将所有 Activity 的主题改变后再重新入栈。在这里为了简便，我们选择的实现方法是：当主题被改变，我们就简单地清空 back stack，然后重启当前的 Activity。\n\n## SettingsFragment.java ##\n\n```java\n    public class SettingsFragment extends PreferenceFragment {\n   \t\t...\n    \n    \t@Override\n   \t\tpublic void onActivityCreated(Bundle savedInstanceState) {\n    \t\tsuper.onActivityCreated(savedInstanceState);\n\n    \t\tmListener = new SharedPreferences.OnSharedPreferenceChangeListener() {\n    \t\t@Override\n    \t\tpublic void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {\n    \t\t\tif (!key.equals(\"pref_dark_theme\")) {\n    \t\t\t\treturn;\n    \t\t\t}\n    \n    \t\tgetActivity().finish();\n\n    \t\tfinal Intent intent = getActivity().getIntent();\n    \t\tintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);\n    \t\tgetActivity().startActivity(intent);\n    \t\t}\n    \t};\n    }\n    \n    ...\n```\n\n虽然结束得有些突然，但我们今天的讲解就到此结束啦。现在我们的 App 拥有了两个这么优雅的主题，就算是挑剔的文艺小清新也不会嫌弃我们的 App 很 low 了吧！如果你想要了解整个 Materialistic 的具体实现，或者是这个功能的源码，可以来我的 [GitHub](https://github.com/hidroh/materialistic) 上获取哦～"
  },
  {
    "path": "androidweekly/那些年我们错过的响应式编程/readme.md",
    "content": "## 那些年我们错过的响应式编程\n---\n\n>\n* 原文链接 : [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)\n* 作者 : [@andrestaltz](https://twitter.com/andrestaltz)\n* 译者 : [yaoqinwei](https://github.com/yaoqinwei) \n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)、[chaossss](https://github.com/chaossss)\n* 状态 :  完成\n\n\n\n\n相信你们在学习响应式编程这个新技术的时候都会充满了好奇，特别是它的一些变体，例如：Rx系列、Bacon.js、RAC等等……\n\n在缺乏优秀资料的前提下，响应式编程的学习过程将满是荆棘。起初，我试图寻找一些教程，却只找到少量的实践指南，而且它们讲的都非常浅显，从来没人接受围绕响应式编程建立一个完整知识体系的挑战。此外，官方文档通常也不能很好地帮助你理解某些函数，因为它们通常看起来很绕，不信请看这里：\n\n> **Rx.Observable.prototype.flatMapLatest(selector, [thisArg])**\n\n> 根据元素下标，将可观察序列中每个元素一一映射到一个新的可观察序列当中，然后...%…………%&￥#@@……&**(晕了)\n\n天呐，这简直太绕了！\n\n我读过两本相关的书，一本只是在给你描绘响应式编程的伟大景象，而另一本却只是深入到如何使用响应式库而已。我在不断的构建项目过程中把响应式编程了解的透彻了一些，最后以这种艰难的方式学完了响应式编程。在我工作公司的一个实际项目中我会用到它，当我遇到问题时，还可以得到同事的支持。\n\n学习过程中最难的部分是**如何以响应式的方式来思考**，更多的意味着要摒弃那些老旧的命令式和状态式的典型编程习惯，并且强迫自己的大脑以不同的范式来运作。我还没有在网络上找到任何一个教程是从这个层面来剖析的，我觉得这个世界非常值得拥有一个优秀的实践教程来教你**如何以响应式编程的方式来思考**，方便引导你开始学习响应式编程。然后看各种库文档才可以给你更多的指引。希望这篇文章能够帮助你快速地进入**响应式编程**的世界。\n\n## \"什是响应式编程?\"\n\n网络上有一大堆糟糕的解释和定义，如[Wikipedia](https://en.wikipedia.org/wiki/Reactive_programming)上通常都是些非常笼统和理论性的解释，而[Stackoverflow](http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming)上的一些规范的回答显然也不适合新手来参考，[Reactive Manifesto](http://www.reactivemanifesto.org/)看起来也只像是拿给你的PM或者老板看的东西，微软的[Rx术语](https://rx.codeplex.com/)\"Rx = Observables + LINQ + Schedulers\" 也显得太过沉重，而且充满了太多微软式的东西，反而给我们带来更多疑惑。相对于你使用的MV*框架以及你钟爱的编程语言，\"Reactive\"和\"Propagation of change\"这样的术语并没有传达任何有意义的概念。当然，我的view框架能够从model做出反应，我的改变当然也会传播，如果没有这些，我的界面根本就没有东西可渲染。\n\n所以，不要再扯这些废话了。\n\n####  响应式编程就是与异步数据流交互的编程范式\n\n一方面，这已经不是什么新事物了。事件总线(Event Buses)或一些典型的点击事件本质上就是一个异步事件流(asynchronous event stream)，这样你就可以观察它的变化并使其做出一些反应(do some side effects)。响应式是这样的一个思路：除了点击和悬停(hover)的事件，你还可以给其他任何事物创建数据流。数据流无处不在，任何东西都可以成为一个数据流，例如变量、用户输入、属性、缓存、数据结构等等。举个栗子，你可以把你的微博订阅功能想象成跟点击事件一样的数据流，你可以监听这样的数据流，并做出相应的反应。\n\n**最重要的是，你会拥有一些令人惊艳的函数去结合、创建和过滤任何一组数据流。** 这就是\"函数式编程\"的魔力所在。一个**数据流**可以作为另一个**数据流**的输入，甚至多个**数据流**也可以作为另一个**数据流**的输入。你可以_合并_两个**数据流**，也可以_过滤_一个数据流得到另一个只包含你感兴趣的事件的**数据流**，还可以_映射_一个**数据流**的值到一个新的**数据流**里。\n\n**数据流**是整个响应式编程体系中的核心，要想学习响应式编程，当然要先走进数据流一探究竟了。那现在就让我们先从熟悉的\"点击一个按钮\"的**事件流**开始\n\n![Click event stream](http://img.my.csdn.net/uploads/201504/13/1428914359_2150.png)\n\n一个**数据流**是一个**按时间排序的即将发生的事件(Ongoing events ordered in time)**的序列。如上图，它可以发出3种不同的事件(上一句已经把它们叫做事件)：一个某种类型的**值事件**，一个**错误事件**和一个**完成事件**。当一个**完成事件**发生时，在某些情况下，我们可能会做这样的操作：关闭包含那个按钮的窗口或者视图组件。\n\n我们只能**异步**捕捉被发出的事件，使得我们可以在发出一个**值事件**时执行一个函数，发出**错误事件**时执行一个函数，发出**完成事件**时执行另一个函数。有时候你可以忽略后两个事件，只需聚焦于如何定义和设计在发出**值事件**时要执行的函数，监听这个**事件流**的过程叫做**订阅**，我们定义的函数叫做**观察者**，而事件流就可以叫做被观察的**主题**(或者叫被观察者)。你应该察觉到了，对的，它就是[**观察者模式**](https://en.wikipedia.org/wiki/Observer_pattern)。\n\n上面的示意图我们也可以用ASCII码的形式重新画一遍，请注意，下面的部分教程中我们会继续使用这幅图：\n```\n--a---b-c---d---X---|->\n\na, b, c, d 是值事件\nX 是错误事件\n| 是完成事件\n---> 是时间线(轴)\n```\n\n现在你对响应式编程事件流应该非常熟悉了，为了不让你感到无聊，让我们来做一些新的尝试吧：我们将创建一个由原始点击事件流演变而来的一种新的点击事件流。\n\n首先，让我们来创建一个记录按钮点击次数的事件流。在常用的响应式库中，每个事件流都会附有一些函数，例如 `map`, `filter`, `scan`等，当你调用这其中的一个方法时，比如`clickStream.map(f)`，它会返回基于点击事件流的一个**新事件流**。它不会对原来的点击事件流做任何的修改。这种特性叫做**不可变性(immutability)**，而且它可以和响应式事件流搭配在一起使用，就像豆浆和油条一样完美的搭配。这样我们可以用链式函数的方式来调用，例如：`clickStream.map(f).scan(g)`:\n\n```\n  clickStream: ---c----c--c----c------c-->\n               vvvvv map(c becomes 1) vvvv\n               ---1----1--1----1------1-->\n               vvvvvvvvv scan(+) vvvvvvvvv\ncounterStream: ---1----2--3----4------5-->\n```\n\n`map(f)`函数会根据你提供的`f`函数把原事件流中每一个返回值分别映射到新的事件流中。在上图的例子中，我们把每一次点击事件都映射成数字1，`scan(g)`函数则把之前映射的值聚集起来，然后根据`x = g(accumulated, current)`算法来作相应的处理，而本例的`g`函数其实就是简单的加法函数。然后，当一个点击事件发生时，`counterStream`函数则上报当前点击事件总数。\n\n为了展示响应式编程真正的魅力，我们假设你有一个\"双击\"事件流，为了让它更有趣，我们假设这个事件流同时处理\"三次点击\"或者\"多次点击\"事件，然后深吸一口气想想如何用传统的命令式和状态式的方式来处理，我敢打赌，这么做会相当的讨厌，其中还要涉及到一些变量来保存状态，并且还得做一些时间间隔的调整。\n\n而用响应式编程的方式处理会非常的简洁，实际上，逻辑处理部分只需要[四行代码](http://jsfiddle.net/staltz/4gGgs/27/)。但是，当前阶段让我们现忽略代码的部分，无论你是新手还是专家，看着图表思考来理解和建立事件流将是一个非常棒的方法。\n\n![多次点击事件流](http://img.my.csdn.net/uploads/201504/13/1428914360_6429.png)\n\n图中，灰色盒子表示将上面的事件流转换下面的事件流的**函数**过程，首先根据250毫秒的间隔时间(event silence, 译者注：无事件发生的时间段，上一个事件发生到下一个事件发生的间隔时间)把点击事件流一段一隔开，再将每一段的一个或多个点击事件添加到列表中(这就是这个函数：`buffer(stream.throttle(250ms))`所做的事情，当前我们先不要急着去理解细节，我们只需专注响应式的部分先)。现在我们得到的是多个含有事件流的列表，然后我们使用了`map()`中的函数来算出每一个列表长度的整数数值映射到下一个事件流当中。最后我们使用了过滤`filter(x >= 2)` 函数忽略掉了小于`1` 的整数。就这样，我们用了3步操作生成了我们想要的事件流，接下来，我们就可以订阅(\"监听\")这个事件并作出我们想要的操作了。\n\n我希望你能感受到这个示例的优雅之处。当然了，这个示例也只是响应式编程魔力的冰山一角而已，你同样可以将这3步操作应用到不同种类的事件流中去，例如，一串API响应的事件流。另一方面，你还有非常多的函数可以使用。\n\n## \"我为什么要采用响应式编程？\"\n\n响应式编程可以加深你代码抽象的程度，让你可以更专注于定义与事件相互依赖的业务逻辑，而不是把大量精力放在实现细节上，同时，使用响应式编程还能让你的代码变得更加简洁。\n\n特别对于现在流行的webapps和mobile apps，它们的 UI 事件与数据频繁地产生交互，在开发这些应用时使用响应式编程的优点将更加明显。十年前，web页面的交互是通过提交一个很长的表单数据到后端，然后再做一些简单的前端渲染操作。而现在的Apps则演变的更具有实时性：仅仅修改一个单独的表单域就能自动的触发保存到后端的代码，就像某个用户对一些内容点了赞，就能够实时反映到其他已连接的用户一样，等等。\n\n当今的Apps都含有丰富的实时事件来保证一个高效的用户体验，我们就需要采用一个合适的工具来处理，那么响应式编程就正好是我们想要的答案。\n\n## 以响应式编程方式思考的例子\n\n让我们深入到一些真实的例子，一个能够一步一步教你如何以响应式编程的方式思考的例子，没有虚构的示例，没有一知半解的概念。在这个教程的末尾我们将产生一些真实的函数代码，并能够知晓每一步为什么那样做的原因(知其然，知其所以然)。\n\n我选了**JavaScript**和**[RxJS](https://github.com/Reactive-Extensions/RxJS)**来作为本教程的编程语言，原因是：JavaScript是目前最多人熟悉的语言，而[Rx系列的库](http://www.reactivex.io)对于很多语言和平台的运用是非常广泛的，例如([.NET](https://rx.codeplex.com/), [Java](https://github.com/Netflix/RxJava), [Scala](https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-scala), [Clojure](https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-clojure),  [JavaScript](https://github.com/Reactive-Extensions/RxJS), [Ruby](https://github.com/Reactive-Extensions/Rx.rb), [Python](https://github.com/Reactive-Extensions/RxPy), [C++](https://github.com/Reactive-Extensions/RxCpp), [Objective-C/Cocoa](https://github.com/ReactiveCocoa/ReactiveCocoa), [Groovy](https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-groovy)等等。所以，无论你用的是什么语言、库、工具，你都能从下面这个教程中学到东西(从中受益)。\n\n## 实现一个推荐关注(Who to follow)的功能\n\n在Twitter里有一个UI元素向你推荐你可以关注的用户，如下图：\n\n![Twitter Who to follow suggestions box](http://img.my.csdn.net/uploads/201504/13/1428914371_1788.png)\n\n我们将聚焦于模仿它的主要功能，它们是：\n\n* 开始阶段，从API加载推荐关注的用户账户数据，然后显示三个推荐用户\n* 点击刷新，加载另外三个推荐用户到当前的三行中显示\n* 点击每一行的推荐用户上的'x'按钮，清楚当前被点击的用户，并显示新的一个用户到当前行\n* 每一行显示一个用户的头像并且在点击之后可以链接到他们的主页。\n\n我们可以先不管其他的功能和按钮，因为它们是次要的。因为Twitter最近关闭了未经授权的公共API调用，我们将用[Github获取用户的API](https://developer.github.com/v3/users/#get-all-users)代替，并且以此来构建我们的UI。\n\n如果你想先看一下最终效果，这里有完成后的[代码](http://jsfiddle.net/staltz/8jFJH/48/)。\n\n## Request和Response\n\n**在Rx中是怎么处理这个问题呢？**，在开始之前，我们要明白，(几乎)_一切都可以成为一个事件流_，这就是Rx的准则(mantra)。让我们从最简单的功能开始：\"开始阶段，从API加载推荐关注的用户账户数据，然后显示三个推荐用户\"。其实这个功能没什么特殊的，简单的步骤分为： (1)发出一个请求，(2)获取响应数据，(3)渲染响应数据。ok，让我们把请求作为一个事件流，一开始你可能会觉得这样做有些夸张，但别急，我们也得从最基本的开始，不是吗？\n\n开始时我们只需做一次请求，如果我们把它作为一个数据流的话，它只能成为一个仅仅返回一个值的事件流而已。一会儿我们还会有很多请求要做，但当前，只有一个。\n\n```\n--a------|->\n\na就是字符串：'https://api.github.com/users'\n```\n\n这是一个我们要请求的URL事件流。每当发生一个请求时，它将告诉我们两件事：**什么时候**和**做了什么事**(when and what)。**什么时候**请求被执行，什么时候事件就被发出。而**做了什么**就是请求了什么，也就是请求的URL字符串。\n\n在Rx中，创建返回一个值的事件流是非常简单的。其实事件流在Rx里的术语是叫\"被观察者\"，也就是说它是可以被观察的，但是我发现这名字比较傻，所以我更喜欢把它叫做_事件流_。\n\n```javascript\nvar requestStream = Rx.Observable.just('https://api.github.com/users');\n```\n\n但现在，这只是一个字符串的事件流而已，并没有做其他操作，所以我们需要在发出这个值的时候做一些我们要做的操作，可以通过[订阅(subscribing)](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypesubscribeobserver--onnext-onerror-oncompleted)这个事件来实现。\n\n```javascript\nrequestStream.subscribe(function(requestUrl) {\n  // execute the request\n  jQuery.getJSON(requestUrl, function(responseData) {\n    // ...\n  });\n}\n```\n\n注意到我们这里使用的是JQuery的AJAX回调方法(我们假设你[已经很了解JQuery和AJAX了](http://devdocs.io/jquery/jquery.getjson))来的处理这个异步的请求操作。但是，请稍等一下，Rx就是用来处理**异步数据流**的，难道它就不能处理来自请求(request)在未来某个时间响应(response)的数据流吗？好吧，理论上是可以的，让我们尝试一下。\n\n```javascript\nrequestStream.subscribe(function(requestUrl) {\n  // execute the request\n  var responseStream = Rx.Observable.create(function (observer) {\n    jQuery.getJSON(requestUrl)\n    .done(function(response) { observer.onNext(response); })\n    .fail(function(jqXHR, status, error) { observer.onError(error); })\n    .always(function() { observer.onCompleted(); });\n  });\n  \n  responseStream.subscribe(function(response) {\n    // do something with the response\n  });\n}\n```\n\n[`Rx.Observable.create()`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservablecreatesubscribe)操作就是在创建自己定制的事件流，且对于数据事件(`onNext()`)和错误事件(`onError()`)都会显示的通知该事件每一个观察者(或订阅者)。我们做的只是小小的封装一下jQuery Ajax Promise而已。**等等，这是否意味者jQuery Ajax Promise本质上就是一个被观察者呢(Observable)？**\n\n![Amazed](http://img.my.csdn.net/uploads/201504/13/1428914359_8403.gif)\n\n是的。\n\nPromise++就是被观察者(Observable)，在Rx里你可以使用这样的操作：`var stream = Rx.Observable.fromPromise(promise)`，就可以很轻松的将Promise转换成一个被观察者(Observable)，非常简单的操作就能让我们现在就开始使用它。不同的是，这些被观察者都不能兼容[Promises/A+](http://promises-aplus.github.io/promises-spec/)，但理论上并不冲突。一个Promise就是一个只有一个返回值的简单的被观察者，而Rx就远超于Promise，它允许多个值返回。\n\n这样更好，这样更突出被观察者至少比Promise强大，所以如果你相信Promise宣传的东西，那么也请留意一下响应式编程能胜任些什么。\n\n现在回到示例当中，你应该能快速发现，我们在`subscribe()`方法的内部再次调用了`subscribe()`方法，这有点类似于回调地狱(callback hell)，而且`responseStream`的创建也是依赖于`requestStream`的。在之前我们说过，在Rx里，有很多很简单的机制来从其他事件流的转化并创建出一些新的事件流，那么，我们也应该这样做试试。\n\n现在你需要了解的一个最基本的函数是[`map(f)`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypemapselector-thisarg)，它可以从事件流A中取出每一个值，并对每一个值执行`f()`函数，然后将产生的新值填充到事件流B。如果将它应用到我们的请求和响应事件流当中，那我们就可以将请求的URL映射到一个响应Promises上了(伪装成数据流)。\n\n```javascript\nvar responseMetastream = requestStream\n  .map(function(requestUrl) {\n    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));\n  });\n```\n\n然后，我们创造了一个叫做\"_metastream_\"的怪兽：一个装载了事件流的事件流。先别惊慌，metastream就是每一个发出的值都是另一个事件流的事件流，你看把它想象成一个[指针(pointers)]((https://en.wikipedia.org/wiki/Pointer_(computer_programming))数组：每一个单独发出的值就是一个_指针_，它指向另一个事件流。在我们的示例里，每一个请求URL都映射到一个指向包含响应数据的promise数据流。\n\n![Response metastream](http://img.my.csdn.net/uploads/201504/13/1428914360_7454.png)\n\n一个响应的metastream，看起来确实让人容易困惑，看样子对我们一点帮助也没有。我们只想要一个简单的响应数据流，每一个发出的值是一个简单的JSON对象就行，而不是一个'Promise' 的JSON对象。ok，让我们来见识一下另一个函数：[Flatmap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypeflatmapselector-resultselector)，它是`map()`函数的另一个版本，它比metastream更扁平。一切在\"主躯干\"事件流发出的事件都将在\"分支\"事件流中发出。Flatmap并不是metastreams的修复版，metastreams也不是一个bug。它俩在Rx中都是处理异步响应事件的好工具、好帮手。\n\n```javascript\nvar responseStream = requestStream\n  .flatMap(function(requestUrl) {\n    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));\n  });\n```\n\n![Response stream](http://img.my.csdn.net/uploads/201504/13/1428914371_2167.png)\n\n很赞，因为我们的响应事件流是根据请求事件流定义的，**如果**我们以后有更多事件发生在请求事件流的话，我们也将会在相应的响应事件流收到响应事件，就如所期待的那样：\n\n```\nrequestStream:  --a-----b--c------------|->\nresponseStream: -----A--------B-----C---|->\n\n(小写的是请求事件流, 大写的是响应事件流)\n```\n\n现在，我们终于有响应的事件流了，并且可以用我们收到的数据来渲染了：\n\n```javascript\nresponseStream.subscribe(function(response) {\n  // render `response` to the DOM however you wish\n});\n```\n\n让我们把所有代码合起来，看一下：\n\n```javascript\nvar requestStream = Rx.Observable.just('https://api.github.com/users');\n\nvar responseStream = requestStream\n  .flatMap(function(requestUrl) {\n    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));\n  });\n\nresponseStream.subscribe(function(response) {\n  // render `response` to the DOM however you wish\n});\n```\n\n## 刷新按钮\n\n我还没提到本次响应的JSON数据是含有100个用户数据的list，这个API只允许指定页面偏移量(page offset)，而不能指定每页大小(page size)，我们只用到了3个用户数据而浪费了其他97个，现在可以先忽略这个问题，稍后我们将学习如何缓存响应的数据。\n\n每当刷新按钮被点击，请求事件流就会发出一个新的URL值，这样我们就可以获取新的响应数据。这里我们需要两个东西：点击刷新按钮的事件流(准则：一切都能作为事件流)，我们需要将点击刷新按钮的事件流作为请求事件流的依赖(即点击刷新事件流会引起请求事件流)。幸运的是，RxJS已经有了可以从事件监听者转换成被观察者的方法了。\n\n```javascript\nvar refreshButton = document.querySelector('.refresh');\nvar refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');\n```\n\n因为刷新按钮点击事件不会携带将要请求的API的URL，我们需要将每次的点击映射到一个实际的URL上，现在我们将请求事件流转换成了一个点击事件流，并将每次的点击映射成一个随机的页面偏移量(offset)参数来组成API的URL。\n\n```javascript\nvar requestStream = refreshClickStream\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  });\n```\n\n因为我比较笨而且也没有使用自动化测试，所以我刚把之前做好的一个功能搞烂了。这样，请求在一开始的时候就不会执行，而只有在点击事件发生时才会执行。我们需要的是两种情况都要执行：刚开始打开网页和点击刷新按钮都会执行的请求。\n\n我们知道如何为每一种情况做一个单独的事件流：\n\n```javascript\nvar requestOnRefreshStream = refreshClickStream\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  });\n  \nvar startupRequestStream = Rx.Observable.just('https://api.github.com/users');\n```\n\n但是我们是否可以将这两个合并成一个呢？没错，是可以的，我们可以使用[`merge()`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypemergemaxconcurrent--other)方法来实现。下图可以解释`merge()`函数的用处：\n\n```\nstream A: ---a--------e-----o----->\nstream B: -----B---C-----D-------->\n          vvvvvvvvv merge vvvvvvvvv\n          ---a-B---C--e--D--o----->\n```\n\n现在做起来应该很简单：\n\n```javascript\nvar requestOnRefreshStream = refreshClickStream\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  });\n  \nvar startupRequestStream = Rx.Observable.just('https://api.github.com/users');\n\nvar requestStream = Rx.Observable.merge(\n  requestOnRefreshStream, startupRequestStream\n);\n```\n\n还有一个更干净的写法，省去了中间事件流变量：\n\n```javascript\nvar requestStream = refreshClickStream\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  })\n  .merge(Rx.Observable.just('https://api.github.com/users'));\n```\n\n甚至可以更简短，更具有可读性：\n```javascript\nvar requestStream = refreshClickStream\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  })\n  .startWith('https://api.github.com/users');\n```\n\n[`startWith()`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypestartwithscheduler-args)函数做的事和你预期的完全一样。无论你的输入事件流是怎样的，使用`startWith(x)`函数处理过后输出的事件流一定是一个`x` 开头的结果。但是我没有总是[重复代码( DRY)](https://en.wikipedia.org/wiki/Don't_repeat_yourself)，我只是在重复API的URL字符串，改进的方法是将 `startWith()`函数挪到`refreshClickStream`那里，这样就可以在启动时，模拟一个刷新按钮的点击事件了。\n\n```javascript\nvar requestStream = refreshClickStream.startWith('startup click')\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  });\n```\n\n不错，如果你倒回到\"搞烂了的自动测试\"的地方，然后再对比这两个地方，你会发现我仅仅是加了一个`startWith()`函数而已。\n\n## 用事件流将3个推荐的用户数据模型化\n\n直到现在，在响应事件流(responseStream)的订阅(`subscribe()`)函数发生的渲染步骤里，我们只是稍微提及了一下_推荐关注_的UI。现在有了刷新按钮，我们就会出现一个问题：当你点击了刷新按钮，当前的三个推荐关注用户没有被清楚，而只要响应的数据达到后我们就拿到了新的推荐关注的用户数据，为了让UI看起来更漂亮，我们需要在点击刷新按钮的事件发生的时候清楚当前的三个推荐关注的用户。\n\n```javascript\nrefreshClickStream.subscribe(function() {\n  // clear the 3 suggestion DOM elements \n});\n```\n\n不，老兄，还没那么快。我们又出现了新的问题，因为我们现在有两个订阅者在影响着推荐关注的UI DOM元素(另一个是 `responseStream.subscribe()`)，这看起来并不符合[关注分离(Separation of concerns)](https://en.wikipedia.org/wiki/Separation_of_concerns)原则，还记得响应式编程的原则么？\n\n![Mantra](http://i.imgur.com/AIimQ8C.jpg)\n\n现在，让我们把推荐关注的用户数据模型化成事件流形式，每个被发出的值是一个包含了推荐关注用户数据的JSON对象。我们将把这三个用户数据分开处理，下面是推荐关注的1号用户数据的事件流：\n\n```javascript\nvar suggestion1Stream = responseStream\n  .map(function(listUsers) {\n    // get one random user from the list\n    return listUsers[Math.floor(Math.random()*listUsers.length)];\n  });\n```\n\n其他的，如推荐关注的2号用户数据的事件流`suggestion2Stream`和推荐关注的3号用户数据的事件流`suggestion3Stream` 都可以方便的从`suggestion1Stream` 复制粘贴就好。这里并不是**重复代码**，只是为让我们的示例更加简单，而且我认为这是一个思考如何避免**重复代码**的好案例。\n\nInstead of having the rendering happen in responseStream's subscribe(), we do that here:\n\n```javascript\nsuggestion1Stream.subscribe(function(suggestion) {\n  // render the 1st suggestion to the DOM\n});\n```\n\n我们不在responseStream的subscribe()中处理渲染了，我们这样处理：\n\n```javascript\nsuggestion1Stream.subscribe(function(suggestion) {\n  // render the 1st suggestion to the DOM\n});\n```\n\n回到\"当刷新时，清楚掉当前的推荐关注的用户\"，我们可以很简单的把刷新点击映射为没有推荐数据(`null` suggestion data)，并且在`suggestion1Stream`中包含进来，如下：\n\n```javascript\nvar suggestion1Stream = responseStream\n  .map(function(listUsers) {\n    // get one random user from the list\n    return listUsers[Math.floor(Math.random()*listUsers.length)];\n  })\n  .merge(\n    refreshClickStream.map(function(){ return null; })\n  );\n```\n\n当渲染时，我们将 `null`解释为\"没有数据\"，然后把UI元素隐藏起来。\n\n```javascript\nsuggestion1Stream.subscribe(function(suggestion) {\n  if (suggestion === null) {\n    // hide the first suggestion DOM element\n  }\n  else {\n    // show the first suggestion DOM element\n    // and render the data\n  }\n});\n```\n\n现在我们大概的示意图如下：\n\n```\nrefreshClickStream: ----------o--------o---->\n     requestStream: -r--------r--------r---->\n    responseStream: ----R---------R------R-->   \n suggestion1Stream: ----s-----N---s----N-s-->\n suggestion2Stream: ----q-----N---q----N-q-->\n suggestion3Stream: ----t-----N---t----N-t-->\n```\n\n`N`代表`null`\n\n作为一种补充，我们可以在一开始的时候就渲染空的推荐内容。这通过把startWith(null)添加到推荐关注的事件流就可以了：\n\n```javascript\nvar suggestion1Stream = responseStream\n  .map(function(listUsers) {\n    // get one random user from the list\n    return listUsers[Math.floor(Math.random()*listUsers.length)];\n  })\n  .merge(\n    refreshClickStream.map(function(){ return null; })\n  )\n  .startWith(null);\n```\n\n结果是这样的：\n\n```\nrefreshClickStream: ----------o---------o---->\n     requestStream: -r--------r---------r---->\n    responseStream: ----R----------R------R-->   \n suggestion1Stream: -N--s-----N----s----N-s-->\n suggestion2Stream: -N--q-----N----q----N-q-->\n suggestion3Stream: -N--t-----N----t----N-t-->\n```\n\n## 推荐关注的关闭和使用已缓存的响应数据(responses)\n\n只剩这一个功能没有实现了，每个推荐关注的用户UI会有一个'x'按钮来关闭自己，然后在当前的用户数据UI中加载另一个推荐关注的用户。最初的想法是：点击任何关闭按钮时都需要发起一个新的请求：\n\n```javascript\nvar close1Button = document.querySelector('.close1');\nvar close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');\n// and the same for close2Button and close3Button\n\nvar requestStream = refreshClickStream.startWith('startup click')\n  .merge(close1ClickStream) // we added this\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  });\n```\n\n这样没什么效果，这样会关闭和重新加载_全部_的推荐关注用户，而不仅仅是处理我们点击的那一个。这里有几种方式来解决这个问题，并且让它变得有趣，我们将重用之前的请求数据来解决这个问题。这个API响应的每页数据大小是100个用户数据，而我们只使用了其中三个，所以还有一大堆未使用的数据可以拿来用，不用去请求更多数据了。\n\nok，再来，我们继续用事件流的方式来思考。当'close1'点击事件发生时，我们想要使用_最近发出的_响应数据，并执行`responseStream`函数来从响应列表里随机的抽出一个用户数据来，就像下面这样：\n\n```\n    requestStream: --r--------------->\n   responseStream: ------R----------->\nclose1ClickStream: ------------c----->\nsuggestion1Stream: ------s-----s----->\n```\n\n在Rx中一个组合函数叫做[`combineLatest`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypecombinelatestargs-resultselector)，应该是我们需要的。这个函数会把数据流A和数据流B作为输入，并且无论哪一个数据流发出一个值了，`combineLatest` 函数就会将从两个数据流最近发出的值`a`和`b`作为`f`函数的输入，计算后返回一个输出值(`c = f(x,y)`)，下面的图表会让这个函数的过程看起来会更加清晰：\n\n```\nstream A: --a-----------e--------i-------->\nstream B: -----b----c--------d-------q---->\n          vvvvvvvv combineLatest(f) vvvvvvv\n          ----AB---AC--EC---ED--ID--IQ---->\n\nf是转换成大写的函数\n```\n\n这样，我们就可以把`combineLatest()`函数用在`close1ClickStream`和 `responseStream`上了，只要关闭按钮被点击，我们就可以获得最近的响应数据，并在`suggestion1Stream`上产生出一个新值。另一方面，`combineLatest()`函数也是相对的：每当在`responseStream`上发出一个新的响应，它将会结合一次新的`点击关闭按钮事件`来产生一个新的推荐关注的用户数据，这非常有趣，因为它可以给我们的`suggestion1Stream`简化代码：\n\n```javascript\nvar suggestion1Stream = close1ClickStream\n  .combineLatest(responseStream,             \n    function(click, listUsers) {\n      return listUsers[Math.floor(Math.random()*listUsers.length)];\n    }\n  )\n  .merge(\n    refreshClickStream.map(function(){ return null; })\n  )\n  .startWith(null);\n```\n\n现在，我们的拼图还缺一小块地方。`combineLatest()`函数使用了最近的两个数据源，但是如果某一个数据源还没有发出任何东西，`combineLatest()`函数就不能在输出流上产生一个数据事件。如果你看了上面的ASCII图表(文章中第一个图表)，你会明白当第一个数据流发出一个值`a`时并没有任何的输出，只有当第二个数据流发出一个值`b`的时候才会产生一个输出值。\n\n这里有很多种方法来解决这个问题，我们使用最简单的一种，也就是在启动的时候模拟'close 1'的点击事件：\n\n```javascript\nvar suggestion1Stream = close1ClickStream.startWith('startup click') // we added this\n  .combineLatest(responseStream,             \n    function(click, listUsers) {l\n      return listUsers[Math.floor(Math.random()*listUsers.length)];\n    }\n  )\n  .merge(\n    refreshClickStream.map(function(){ return null; })\n  )\n  .startWith(null);\n```\n\n## 封装起来\n\n我们完成了，下面是封装好的完整示例代码：\n\n```javascript\nvar refreshButton = document.querySelector('.refresh');\nvar refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');\n\nvar closeButton1 = document.querySelector('.close1');\nvar close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');\n// and the same logic for close2 and close3\n\nvar requestStream = refreshClickStream.startWith('startup click')\n  .map(function() {\n    var randomOffset = Math.floor(Math.random()*500);\n    return 'https://api.github.com/users?since=' + randomOffset;\n  });\n\nvar responseStream = requestStream\n  .flatMap(function (requestUrl) {\n    return Rx.Observable.fromPromise($.ajax({url: requestUrl}));\n  });\n\nvar suggestion1Stream = close1ClickStream.startWith('startup click')\n  .combineLatest(responseStream,             \n    function(click, listUsers) {\n      return listUsers[Math.floor(Math.random()*listUsers.length)];\n    }\n  )\n  .merge(\n    refreshClickStream.map(function(){ return null; })\n  )\n  .startWith(null);\n// and the same logic for suggestion2Stream and suggestion3Stream\n\nsuggestion1Stream.subscribe(function(suggestion) {\n  if (suggestion === null) {\n    // hide the first suggestion DOM element\n  }\n  else {\n    // show the first suggestion DOM element\n    // and render the data\n  }\n});\n```\n\n**你可以在[这里](http://jsfiddle.net/staltz/8jFJH/48/)看到可演示的示例工程**\n\n以上的代码片段虽小但做到很多事：它适当的使用关注分离(separation of concerns)原则的实现了对多个事件流的管理，甚至做到了响应数据的缓存。这种函数式的风格使得代码看起来更像是声明式编程而非命令式编程：我们并不是在给一组指令去执行，只是定义了事件流之间关系来**告诉它这是什么**。例如，我们用Rx来告诉计算机_`suggestion1Stream`**是**'close 1'事件结合从最新的响应数据中拿到的一个用户数据的数据流，除此之外，当刷新事件发生时和程序启动时，它就是`null`_。\n\n留意一下代码中并未出现例如`if`, `for`, `while`等流程控制语句，或者像JavaScript那样典型的基于回调(callback-based)的流程控制。如果可以的话(稍候会给你留一些实现细节来作为练习)，你甚至可以在`subscribe()`上使用 `filter()`函数来摆脱`if`和`else`。在Rx里，我们有例如： `map`, `filter`, `scan`, `merge`, `combineLatest`, `startWith`等数据流的函数，还有很多函数可以用来控制**事件驱动编程(event-driven program)**的流程。这些函数的集合可以让你使用更少的代码实现更强大的功能。\n\n## 接下来\n\n如果你认为Rx将会成为你首选的响应式编程库，接下来就需要花一些时间来熟悉[一大批的函数](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md)用来变形、联合和创建被观察者。如果你想在事件流的图表当中熟悉这些函数，那就来看一下这个：[RxJava's very useful documentation with marble diagrams](https://github.com/Netflix/RxJava/wiki/Creating-Observables)。请记住，无论何时你遇到问题，可以画一下这些图，思考一下，看一看这一大串函数，然后继续思考。以我个人经验，这样效果很有效。\n\n一旦你开始使用了Rx编程，请记住，理解[Cold vs Hot Observables](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables)的概念是非常必要的，如果你忽视了这一点，它就会反弹回来并残忍的反咬你一口。我这里已经警告你了，学习函数式编程可以提高你的技能，熟悉一些常见问题，例如Rx会带来的副作用\n\n但是响应式编程库并不仅仅是Rx，还有相对容易理解的，没有Rx那些怪癖的[Bacon.js](http://baconjs.github.io/)。[Elm Language](http://elm-lang.org/)则以它自己的方式支持响应式编程：它是一门会编译成Javascript + HTML + CSS的响应式编程语言，并有一个[time travelling debugger](http://debug.elm-lang.org/)功能，很棒吧。\n\n而Rx对于像前端和App这样需要处理大量的编程效果是非常棒的。但是它不只是可以用在客户端，还可以用在后端或者接近数据库的地方。事实上，[RxJava就是Netflix服务端API用来处理并行的组件](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)。Rx并不是局限于某种应用程序或者编程语言的框架，它真的是你编写任何事件驱动程序，可以遵循的一个非常棒的编程范式。\n\n如果这篇教程对你有帮助, [那么就请来转发一下吧(tweet it forward)](https://twitter.com/intent/tweet?original_referer=https%3A%2F%2Fgist.github.com%2Fstaltz%2F868e7e9bc2a7b8c1f754%2F&amp;text=The%20introduction%20to%20Reactive%20Programming%20you%27ve%20been%20missing&amp;tw_p=tweetbutton&amp;url=https%3A%2F%2Fgist.github.com%2Fstaltz%2F868e7e9bc2a7b8c1f754&amp;via=andrestaltz).\n"
  },
  {
    "path": "authorization.md",
    "content": "# 作者授权列表\nAndroid开发技术前线翻译的文章都尽可能的找到原作者的联系方式，然后获得文章作者的翻译授权，原文也将保留原文链接和作者名。下面是目前已获得授权的作者列表。\n\n|  作者  | 博客地址 |  授权状态  | 联系人  |备注 |\n|-------|---------|-----------|-----------|--------|\n| Romain Guy | http://www.curious-creature.com/ | 已授权 | Mr.Simple | email |\n| Mark Allison | https://blog.stylingandroid.com/ | 已授权 | Mr.Simple| 需要遵循Creative Commons [Attribution-NonCommercial-ShareAlike 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/) 协议 |\n| Mark Richards | http://www.wmrichards.com/ | 沟通中 | Mr.Simple|  翻译的是《软件架构模式》免费电子书，需要跟O‘Reilly的编辑沟通 |\n| Hannes Dorfmann | http://hannesdorfmann.com/ | 沟通中 | Mr.Simple|  g+沟通中 |\n| Ravi Tamada | http://www.androidhive.info/ | 沟通中 | Mr.Simple|  g+沟通中 |\n| nuunei | http://inthecheesefactory.com/blog | 沟通中 | Mr.Simple|  博客留言沟通中 |\n| inovex | [http://blog.inovex.de](http://blog.inovex.de) | 沟通中 | Mr.Simple|  facebook留言沟通中 |\n| Alex Lockwood | [http://www.androiddesignpatterns.com/](http://www.androiddesignpatterns.com/) | 已授权 | Mr.Simple|  google+ |\n| Robert Martin (Bob大叔) | [Clean Coder](https://sites.google.com/site/unclebobconsultingllc/) 和 [http://blog.8thlight.com/](http://blog.8thlight.com/) | 沟通中 | Mr.Simple|  沟通中 |\n| Zaitsev Serge | [Zaitsev Serge](http://zserge.com/blog.html)  | 已授权 | Mr.Simple|  沟通中 |\n| flavien Laurent | [flavien Laurent](http://flavienlaurent.com/)  | 已授权 | Mr.Simple|  沟通中 |\n\n"
  },
  {
    "path": "git简单使用教程.md",
    "content": "#git简单教程 (适用于参与开发技术前线)\n\n## 1、git的安装与配置\n[Git详解之一：Git起步](http://blog.jobbole.com/25775/)\n\n## 2、参与开发技术前线的项目\n\n1. 首先到[iOS-tech-frontier](https://github.com/bboyfeiyu/iOS-tech-frontier),将该仓库fork(右上角)到你的个人账户下，因此你首先需要注册一个github账户；\n2. 用命令行进入到你的某个目录下，然后输入如下命令 : `git clone https://github.com/这里替换为你的用户名/iOS-tech-frontier`将你fork的那份克隆下来；\n3. 进入到iOS-tech-frontier目录中，在对应期数的文件夹中(例如,issue-2代表第二期)创建你的文章文件，例如:swift编程指南.md，注意文件名不要有空格；\n4. 按照规范翻译文章，并且保留原文，每段英文下面跟一段译文；\n5. 完成翻译之后，通过如下命令提交你的工作。首先提交到本地,`git add .`,然后`git commit -m \"我翻译了xxx\"`,最好通过`git push origin master`将你的翻译内容提交到github上；\n6. 等待提交结束之后，你的提交也只是提交到了你个人的项目中，此时你需要向主仓库发一个pull request (简称 pr ) 请求，可参考[Fork + Pull模式](http://www.worldhello.net/gotgithub/04-work-with-others/010-fork-and-pull.html)；\n7. 发布pr之后管理员会安排人员进行文章校对，有问题的地方校对人员会在pr下进行评论，翻译人员确认问题之后修改问题即可；\n8. 校对并且修改完之后翻译人员更新pr，管理员确认没有问题之后会合并该pr，整个翻译流程就此结束啦！\n\n如果在这个过程中有冲突，翻译人员需要先解决冲突，可以参考[Git下的冲突解决](http://www.cnblogs.com/sinojelly/archive/2011/08/07/2130172.html)。\n\n## 3、更详细的资料\n\n[git - 简易指南](http://www.bootcss.com/p/git-guide/)\n[pro git中文版](http://pan.baidu.com/s/1o6Hsets)"
  },
  {
    "path": "issue-10/Android如何直播RTMP流.md",
    "content": "Android 如何直播RTMP流\n===\n> * 原文链接 : [How To Stream RTMP live in Android](http://www.truiton.com/2015/03/stream-rtmp-live-android/)\n* 原文作者 : [Mohit Gupt](google.com/+MohitGupt)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [ayyb1988](https://github.com/ayyb1988) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n![rtmp](http://www.truiton.com/wp-content/uploads/2015/03/Android-RTMP-Player.png)\n\n在android上，视频/音频流直播是极少有人关注的一部分。每当我们讨论流媒体，RTMP[(Real Time Messaging Protocol)](http://en.wikipedia.org/wiki/Real_Time_Messaging_Protocol)是不可或缺的。RTMP是一个基本的视频/音频直播流协议，但是不幸的是Android标准的[VideoView](http://developer.android.com/reference/android/widget/VideoView.html)不支持RTMP的播放。因此，如果想在android上播放RTMP直播流，你必须使用支持RTMP协议的库。在本教程中我们将讨论如何通过使用安卓的 [Vitamio]（https://www.vitamio.org/en/） 库播放由 RTMP 协议传输的流媒体。\n\n##Android Vitamio 库\nVitamio是一个android和ios上基于[FFmpeg](https://www.ffmpeg.org/)的开源项目。Vitamio为我们提供了一个清洁、简单、全面、真实的硬件加速解码器和渲染器API，Vitamio是一个支持多种音视频格式 如 FLV, TS/TP, WMV, DivX, Xvid等多种标准格式的非常强大的库。所不同的是，它也支持类似.mkv和.srt嵌入和外挂字幕播放。但是它带有一个许可证，因此在使用它之前请先获得[认证](https://www.vitamio.org/en/License/)。在这个android RTMP例子中，我们不仅讨论RTMP直播流，而且也会讨论m3u8流（HLS），RTSP流和 MMS (Microsoft Media Stream)。首先让在我们的项目中引用Vitamio库。\n\n####在Android Studio中引用Vitamio库的步骤如下：\n1. 下载Vitamio bundle [https://github.com/yixia/VitamioBundle](https://github.com/yixia/VitamioBundle)\n2. 解压并且在Android Studio上File->Import Module\n3. 指定到VitamioBundle路径，选择vitamio文件夹 点击完成\n4. 在build.gradle(Module: app)依赖部分添加依赖项目(‘:vitamio’)\n5. 打开build.gradle (Module: vitamio) - 改变最小sdk版本为7\n6. 不要忘记在manifest.xml中添加internet权限\n7. 完成！\n\n####Android RTMP流\n在讲述如何使用之前，让我们先了解下RTMP。Real Time Messaging Protocol (RTMP)是一个Adobe Systems所拥有的一个协议。该协议是Adobe公司拥有的开发音视频流的flash player。后来该协议的部分被公开，供公众使用。更多请查看[这里](http://en.wikipedia.org/wiki/Real_Time_Messaging_Protocol).这个协议大多用于IPTV和实时视频点播流，但它也用于其他领域。\n\n在android上，标准的VideoView不支持RTMP播放。但WebView可以播放RTMP流。这解决了播放RTMP流的问题，但是我认为web apps 不能提供一个很好的界面和体验。因此这这个android RTMP例子中我们将运用第三方库-Vitamio 直播RTMP流的流媒体。在工程中引用Vitamio之后，请在你的layout文件添加Vitamio的VideoView：\n\nactivity_main.xml\n\n```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t        android:layout_width=\"match_parent\"\n\t        android:layout_height=\"match_parent\"\n\t        android:orientation=\"vertical\" >\n\t\n\t    <io.vov.vitamio.widget.VideoView\n\t        android:id=\"@+id/vitamio_videoView\"\n\t        android:layout_width=\"wrap_content\"\n\t       android:layout_height=\"wrap_content\" />\n\t\n\t</LinearLayout>\n```\n\n另外请编写你的activity如下：\n\nMainActivity.java\n\n```java\n\tpackage com.truiton.rtmpplayer;\n\t \n\timport android.net.Uri;\n\timport android.os.Bundle;\n\timport android.support.v7.app.ActionBarActivity;\n\t \n\timport java.util.HashMap;\n\t \n\timport io.vov.vitamio.LibsChecker;\n\timport io.vov.vitamio.MediaPlayer;\n\timport io.vov.vitamio.widget.MediaController;\n\timport io.vov.vitamio.widget.VideoView;\n\t \n\t \n\tpublic class MainActivity extends ActionBarActivity {\n\t    private static final String TAG = \"MainActivity\";\n\t    private String path;\n\t    //private HashMap<String, String> options;\n\t    private VideoView mVideoView;\n\t \n\t    @Override\n\t    protected void onCreate(Bundle savedInstanceState) {\n\t        super.onCreate(savedInstanceState);\n\t        if (!LibsChecker.checkVitamioLibs(this))\n\t            return;\n\t        setContentView(R.layout.activity_main);\n\t        mVideoView = (VideoView) findViewById(R.id.vitamio_videoView);\n\t        path = \"rtmp://rrbalancer.broadcast.tneg.de:1935/pw/ruk/ruk\";\n\t        /*options = new HashMap<>();\n\t        options.put(\"rtmp_playpath\", \"\");\n\t        options.put(\"rtmp_swfurl\", \"\");\n\t        options.put(\"rtmp_live\", \"1\");\n\t        options.put(\"rtmp_pageurl\", \"\");*/\n\t        mVideoView.setVideoPath(path);\n\t        //mVideoView.setVideoURI(Uri.parse(path), options);\n\t        mVideoView.setMediaController(new MediaController(this));\n\t        mVideoView.requestFocus();\n\t \n\t        mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {\n\t            @Override\n\t            public void onPrepared(MediaPlayer mediaPlayer) {\n\t                mediaPlayer.setPlaybackSpeed(1.0f);\n\t            }\n\t        });\n\t    }\n\t}\n```\n\n虽然上面代码很清晰明了，但需要指出的是请修改你播放RTMP流的路径。在android上，有时可能使用带报头路径来播放RTMP流。幸运的是，Vitamio RTMP播放器也支持这种方式。因此，所有类型的RTMP流可以使用Vitamio库。上面的例子会是这个样子：\n![Rtmpplayer](http://www.truiton.com/wp-content/uploads/2015/03/Android-RTMP-Stream-Live.png)\n\n####Android RTSP流媒体\n实时流协议(RTSP)通过多媒体服务器传输内容，例如YouTube使用RTSP流发布内容。关于RTSP流比较容易的部分是，它可以通过android标准的VideoView来完成，想了解更多，请参考我的[VideoView例子](http://www.truiton.com/2013/08/android-videoview-example-with-youtube-playback/)。\n\n但是如果你使用Vitamio库，可以更好的播放RTSP流。事实上Vitamio也支持RTSP流的回播。和上面过程是一样的，包括Vitamio的VideoView在布局文件，并使用路径变量指定的RTSP url\n\n```java\n\tmVideoView = (VideoView) findViewById(R.id.vitamio_videoView);\n\tpath = \"rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov\";\n\tmVideoView.setVideoPath(path);\n\tmVideoView.setMediaController(new MediaController(this));\n\tmVideoView.requestFocus();\n\t\n\tmVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {\n\t    @Override\n\t    public void onPrepared(MediaPlayer mediaPlayer) {\n\t        mediaPlayer.setPlaybackSpeed(1.0f);\n\t    }\n\t});\n```\n\n####Android m3u8 流媒体\n“如何在android上播放m3u8视频”是android开发者最常见的问题之一。通过Http 协议进行视频流直播最简单的办法就是使用标准的 VideoView. 但只能在android3.0以上的设备上播放m3u8流。因为在Android 3.0引入HTTP/ HTTPS直播和HTTP/ HTTPS渐进式流媒体协议，在android3.1完全支持HTTPS。\n\n如果你希望在早期的版本上实现支持android m3u8流的HTTP实时流媒体 (HLS)。应该考虑使用Vitamio库，这个库支持在android API7以上播放m3u8。使用方式，同样的在布局文件中使用Vitamio的VideoView，并指定的HTTP实时流媒体URL。\n\n```java\n\tmVideoView = (VideoView) findViewById(R.id.vitamio_videoView);\n\tpath = \"http://93.184.221.133/00573D/236/236-0.m3u8\";\n\tmVideoView.setVideoPath(path);\n\tmVideoView.setMediaController(new MediaController(this));\n\tmVideoView.requestFocus();\n\t\n\tmVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {\n\t    @Override\n\t    public void onPrepared(MediaPlayer mediaPlayer) {\n\t        mediaPlayer.setPlaybackSpeed(1.0f);\n\t    }\n\t});\n```\n\nPlaying m3u8 stream on Android with Vitamio would look something like this:\n在androi上使用Vitamio播放m3u8流效果如下：\n![m3u8](http://www.truiton.com/wp-content/uploads/2015/03/Android-m3u8-Streaming.png)\n\n####Android MMS 流\n\nVitamio库是一个强大的库，还支持Microsoft媒体服务器（MMS）流中的播放。 MMS是网络流媒体协议，主要用于网络广播和电台直播。使用Vitamio用于在anroid的MMS流和其他协议没有什么不同。所有你需要做的只是更换路径变量指向一个MMS url：\n\n```java\n\tmVideoView = (VideoView) findViewById(R.id.vitamio_videoView);\n\tpath = \"mms://beotelmedia.beotel.net/studiob\";\n\tmVideoView.setVideoPath(path);\n\tmVideoView.setMediaController(new MediaController(this));\n\tmVideoView.requestFocus();\n\t\n\tmVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {\n\t    @Override\n\t    public void onPrepared(MediaPlayer mediaPlayer) {\n\t        mediaPlayer.setPlaybackSpeed(1.0f);\n\t    }\n\t});\n```\n\n##结论\n通过上面的讨论，可以确定地说，Vitamio是一个强大的多平台库（ios and android）。通过使用Vitamio库 能播放多种类型的视频格式和协议如RTMP, RTSP, HTTP Live, and HTTP渐进式流协议。另外一个很好的功能是，vitamio支持字幕和多音轨的播放。Vitamio的唯一的缺点是，它不是完全的开源。您可能需要购买许可证来使用它。希望这会有所帮助。通过Facebook, Google+ and Twitter来联系我们获取更多更新。\n\n"
  },
  {
    "path": "issue-10/Android进行单元测试难在哪-part2.md",
    "content": "Android 进行单元测试难在哪-part2\n---\n\n> * 原文链接 : [Why Android Unit Testing is so hard](http://philosophicalhacker.com/2015/04/24/why-android-unit-testing-is-so-hard-pt-2/)\n* 原文作者 : [Matthew Dupree](http://philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [tiiime](https://github.com/tiiime)  \n* 状态 :  完成\n\n\n在上一篇博文中，我用干货告诉大家：即使是 Google 大牛写出来的代码也无法进行测试。确切地说，我真正告诉大家的是：根本没办法在 SessionDetailActivity 的 onStop() 方法里进行单元测试，而且详细地解释了个中因果：由于无法改变预测试状态，我们无法在 onStop() 方法里完成断言；在 onStop() 方法中进行测试时，获得测试后状态也是无法完成的。在上篇博文的结尾处，我跟大家说：正是 Android SDK 的某些特性，以及 Google 官方推荐的代码模板使得单元测试处于如此尴尬的境地，而且我承诺会在这篇博文中详尽地解释各种因由，那现在就让我来兑现我的诺言吧。\n\n在我开始论述之前，我再说一次：正是标准的 Android 应用架构使测试 Android 应用变得如此困难，这句话是本系列博文的核心论点。这篇博文的意义在于：我们尝试提出理由证明重构 Android 应用的必要性，使得这些 Android 应用不需要明确地依赖于 Android SDK，与此同时，我们也尝试着提出一种健壮的应用架构，以增强 Android 应用的测试性，你会在这篇博文里了解到相关的概述。因此，我接下来将尝试去证明这篇博文的核心论点。\n\n众所周知，开发 Android 应用有一种标准的架构，在示例代码和开源代码里很常见到应用的业务逻辑被放在 Android 应用的组件类，Activity，Service，Fragment 里执行。而我接下来就要遵循这种架构进行开发。而这篇博文要论述的就是：如果我们遵循这种标准架构进行开发，极有可能写下无法测试的代码，我在上一篇博文里也论证了这样的问题并不是偶然，正是标准的 Android 应用架构让测试变得支离破碎，单元测试几乎不能进行。\n\n## 传统的 Android 应用架构让单元测试变得不可能\n\n为了开始论证为什么标准开发架构让应用组件变得无法测试，大家不妨和我一起简要地复习下上篇博文的一些结论。单元测试包含三个步骤：准备，测试，断言。为了完成准备步骤，需要改变测试代码的预测试状态，此外，为了完成单元测试的断言步骤，我们需要获得程序的测试后状态。\n\n复习了这些知识点后，可以开始进入正题了哈。在某些情况下，依赖注入是实现能够改变预测试状态代码的唯一办法，而且这些代码的测试后状态也是可访问的。我写了一个与 Android 完全无关的示例：\n\n```java\n\tpublic class MathNerd {\n\t    \n\t    private final mCalcCache;\n\t    \n\t    private final mCalculator;\n\t    \n\t    public MathNerd(CalculationCache calcCache, Calculator calculator) {\n\t        mCalcCache = calcCache;\n\t        mCalculator = calculator;\n\t    }\n\t    \n\t    \n\t    public void doIntenseCalculation(Calculation calculation, IntenseCalculationCompletedListener listener) {\n\t        \n\t        if (!mCalcCache.contains(calculation)) {\n\t            \n\t            mCalculator.doIntenseCalculationInBackground(listener);\n\t            \n\t        } else {\n\t            \n\t            Answer answer = mCalcCache.getAnswerFor(calculation);\n\t            listener.onCalculationCompleted(answer);\n\t        }\n\t    }\n\t}\n```\n\n如上所示，依赖注入确实是对 doIntenseCalculation() 进行单元测试的唯一办法，因为 doIntenseCalculation() 方法根本没有返回值。除此以外，MathNerd 类里也没有判断测试后状态有效性的属性。但通过依赖注入，我们可以通过 mCalcCache 获得单元测试中的测试后状态。\n\n```java\n\tpublic void testCacheUpdate() {\n\t    \n\t    //Arrange\n\t    CalculationCache calcCache = new CalculationCache();\n\t    \n\t    Calculator calculator = new Calculator();\n\t    \n\t    MathNerd mathNerd = new MathNerd(calcCache, calculator);\n\t    \n\t    Calculation calcualation = new Calculation(\"e^2000\");\n\t    \n\t    //Act\n\t    mathNerd.doIntenseCalculationInBackground(calculation, null);\n\t    \n\t    //some smelly Thread.sleep() code...\n\t    \n\t    //Assert\n\t    calcCache.contains(calculation);\n\t}\n```\n\n如果我们这样做，很遗憾，恐怕是没办法为 MathNerd 类实现一个测试单元了。我们将会实现一个整合测试，用于检查 MathNerd 实际行为以及类是否根据 doIntenseCalculationInBackground() 方法处理后的值更新 CalcCache。\n\n此外，依赖注入实际上也是验证测试单元测试后状态的唯一办法。我们通过注入验证方法在正确的位置被调用：\n\n```java\n\tpublic void testCacheUpdate() {\n\t    \n\t   //Arrange\n\t    CalculationCache calcCache = mock(CalculationCache.class);\n\t \n\t    when(calcCache.contains()).thenReturn(false);\n\t \n\t    Calculator calculator = mock(Calculator.class);\n\t \n\t    MathNerd mathNerd = new MathNerd(calcCache, calculator);\n\t \n\t    Calculation calculation = new Calculation(\"e^2000\");\n\t \n\t    //Act\n\t    mathNerd.doIntenseCalculationInBackground(calculation, null);\n\t \n\t    //Assert should use calculator to perform calcluation because cache was empty\n\t    verify(calculator).doIntenseCalculationInBackground(any());\n\t}\n```\n\n在 Android 应用的相关类中进行单元测试涉及的许多测试实例都需要一个东西：依赖注入。但问题来了：核心 Android 类持有我们无法注入的依赖。例如我上次提到的通过 SessionDetailActivity 启动的 SessionCalendarService 就是一个很好的例子：\n\n```java\n\t@Override\n\tprotected void onHandleIntent(Intent intent) {\n\t    \n\t    final String action = intent.getAction();\n\t    Log.d(TAG, \"Received intent: \" + action);\n\t \n\t    final ContentResolver resolver = getContentResolver();\n\t \n\t    boolean isAddEvent = false;\n\t \n\t    if (ACTION_ADD_SESSION_CALENDAR.equals(action)) {\n\t        isAddEvent = true;\n\t \n\t    } else if (ACTION_REMOVE_SESSION_CALENDAR.equals(action)) {\n\t        isAddEvent = false;\n\t \n\t    } else if (ACTION_UPDATE_ALL_SESSIONS_CALENDAR.equals(action) &&\n\t            PrefUtils.shouldSyncCalendar(this)) {\n\t        try {\n\t            getContentResolver().applyBatch(CalendarContract.AUTHORITY,\n\t                    processAllSessionsCalendar(resolver, getCalendarId(intent)));\n\t            sendBroadcast(new Intent(\n\t                    SessionCalendarService.ACTION_UPDATE_ALL_SESSIONS_CALENDAR_COMPLETED));\n\t        } catch (RemoteException e) {\n\t            LOGE(TAG, \"Error adding all sessions to Google Calendar\", e);\n\t        } catch (OperationApplicationException e) {\n\t            LOGE(TAG, \"Error adding all sessions to Google Calendar\", e);\n\t        }\n\t \n\t    } else if (ACTION_CLEAR_ALL_SESSIONS_CALENDAR.equals(action)) {\n\t        try {\n\t            getContentResolver().applyBatch(CalendarContract.AUTHORITY,\n\t                    processClearAllSessions(resolver, getCalendarId(intent)));\n\t        } catch (RemoteException e) {\n\t            LOGE(TAG, \"Error clearing all sessions from Google Calendar\", e);\n\t        } catch (OperationApplicationException e) {\n\t            LOGE(TAG, \"Error clearing all sessions from Google Calendar\", e);\n\t        }\n\t \n\t    } else {\n\t        return;\n\t    }\n\t \n\t   //...\n\t}\n```\n\nSessionCalendarService 的依赖是 ContentResolver，而且 ContentResolver 就是一个无法注入的依赖，所以如果我们并没有办法在 onHandleIntent() 方法里进行注入。而 onHandleIntent() 方法没有返回值，SessionCalendarService 类里也没有能让我们检查测试后状态的可访问的属性。为了验证测试后状态，我们可以通过查询 ContentProvider 检查请求数据是否被插入，但我们不会这样的方式为 SessionCalendarService 实现测试单元。相反，我们用的方法是实现一个整合测试，同时测试 SessionCalendarService 以及受 ContentProvider 操控的日历会议数据。\n\n所以如果你把业务逻辑放在 Android 类里，而这个类的依赖又无法被注入，那这部分代码铁定没办法进行单元测试了。类似的无法被注入的依赖还有呢，例如：Activity 和 Fragment 的 FragmentManager。因此，至今为止 Google 官方一直鼓励我们使用的标准 Android 应用架构模式，教导我们在开发应用的时候要把业务逻辑放在应用的组件类里，信誓旦旦地说这是为我们好，而我们今天才知道真相竟然是：正是这样的架构让我们写下无法测试的代码。\n\n## 标准开发模式让单元测试变得困难重重\n\n某些情况下，标准的开发模式使代码的单元测试变得十分困难。如果我们回到上一篇博文提到的 SessionDetailActivity 里的 onStop() 方法，可以看到：\n\n```java\n\t@Override\n\tpublic void onStop() {\n\t    super.onStop();\n\t    if (mInitStarred != mStarred) {\n\t        if (UIUtils.getCurrentTime(this) < mSessionStart) {\n\t            // Update Calendar event through the Calendar API on Android 4.0 or new versions.\n\t            Intent intent = null;\n\t            if (mStarred) {\n\t                // Set up intent to add session to Calendar, if it doesn't exist already.\n\t                intent = new Intent(SessionCalendarService.ACTION_ADD_SESSION_CALENDAR,\n\t                        mSessionUri);\n\t                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n\t                        mSessionStart);\n\t                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n\t                        mSessionEnd);\n\t                intent.putExtra(SessionCalendarService.EXTRA_SESSION_ROOM, mRoomName);\n\t                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n\t            } else {\n\t                // Set up intent to remove session from Calendar, if exists.\n\t                intent = new Intent(SessionCalendarService.ACTION_REMOVE_SESSION_CALENDAR,\n\t                        mSessionUri);\n\t                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n\t                        mSessionStart);\n\t                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n\t                        mSessionEnd);\n\t                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n\t            }\n\t            intent.setClass(this, SessionCalendarService.class);\n\t            startService(intent);\n\t \n\t            if (mStarred) {\n\t                setupNotification();\n\t            }\n\t        }\n\t    }\n\t}\n```\n\n就像你看到的那样，onStop() 方法里压根没有能让我们知道 SessionCalendarService 是否通过正确的参数启动的可访问属性，此外，onStop() 方法是一个受保护的方法，使其返回值是无法修改的。因此，我们访问测试后状态的唯一办法就是检查注入到 onStop() 方法内的注入的状态。\n\n这样一来，我们就会注意到 onStop() 方法中用于启动 SessionCalendarService 的代码并不属于某一个类。换句话说，onStop() 方法中注入的依赖根本不存在用于检查 SessionCalendarService 是否在正确的情况下通过正确的参数启动的测试单元测试后状态的属性。为了提出能让 onStop() 方法变为可测试的的第三种办法，那我们需要一些这样的东西：\n\n```\n\t@Override\n\tpublic void onStop() {\n\t    super.onStop();\n\t    if (mInitStarred != mStarred) {\n\t        if (UIUtils.getCurrentTime(this) < mSessionStart) {\n\t            // Update Calendar event through the Calendar API on Android 4.0 or new versions.\n\t            Intent intent = null;\n\t            if (mStarred) {\n\t                \n\t                // Service launcher sets up intent to add session to Calendar\n\t                mServiceLauncher.launchSessionCalendarService(SessionCalendarService.ACTION_ADD_SESSION_CALENDAR, mSessionUri, \n\t                                                            mSessionStart, mSessionEnd, mRoomName, mTitleString);\n\t                                                            \n\t            } else {\n\t                \n\t                // Set up intent to remove session from Calendar, if exists.\n\t                mServiceLauncher.launchSessionCalendarService(SessionCalendarService.ACTION_REMOVE_SESSION_CALENDAR, mSessionUri,\n\t                                                            mSessionStart, mSessionEnd, mTitleString);\n\t            }\n\t \n\t            if (mStarred) {\n\t                setupNotification();\n\t            }\n\t        }\n\t    }\n\t}\n```\n\n虽然这不是重构 onStop() 方法最简洁的方式，但如果我们按照标准开发方法把业务逻辑写在 Activity 里，并让写下的代码可以进行单元测试，类似的处理就变得必要了。现在不妨想想这种重构方式有多么违反常理：我们没有简单地调用 startService() 方法（startService() 是 Context 的一个方法，我们甚至可以说调用的是 SessionDetailActivity 的方法），而是通过依赖于 Context 的 ServiceLauncher 对象去启动该服务。SesionDetailActivity 作为 Context 的子类也将使用一个持有 Context 的对象去启动 SessionCalendarService。\n\n不幸的是，即使我们像上面说的那样重构了 onStop() 方法，我们仍然不能保证能为 onStop() 方法实现测试单元。问题在于：ServiceLauncher 没有被注入，使得我们不能对 ServiceLauncher 进行注入，使我们能验证在测试过程中调用了正确的方法。\n\n要对 ServiceLauncher进行注入，除了刚刚提到的以外，还会因为 ServiceLauncher 自身依赖于 Context 变得复杂，因为 Context 是一个非打包对象。因此，你并不能简单地通过将其传入用于启动  SessionDetailActivity 的 Intent 注入 ServiceLauncher。所以为了注入 ServiceLauncher，你需要开动你的小脑筋，或者使用类似于 Dagger¹ 的注入库。现在你应该也会发现，为了让我们的代码可以进行单元测试，我们确实需要完成许多复杂、繁琐的工作，而且，正如我即将在下篇博文中的论述，就算我们为了进行依赖注入而使用 Dagger 这样的库，在 Activity 内进行单元测试仍然是令人备受煎熬的。\n\n为了让 onStop() 方法能进行单元测试，标准开发方式强迫我们使用反常理的重构方法，并要求我们在“根据以 Intent 为基础的依赖注入机制想出更好的重构方法”或“使用第三方的依赖注入库”。而标准开发方式为写下可测试代码带来的困难，就像在鼓励我们写下无法进行测试的代码，正是这种困难让我认为：标准开发方式阻碍我们写下可测试代码。\n\n## 结论\n\n在整个系列博文中，我一直在提出这样的观点：通过反思为什么在 Android 中进行单元测试如此困难，将帮助我们发现重构应用架构的各种好处，使我们的应用不必明确地依赖于 Android SDK。这篇博文论述到这里，我相信大家有足够理由相信完全摆脱 Android SDK 或许是个好提议了。\n\n我刚刚把业务逻辑放在应用的组件类中，并向大家证明了对其进行单元测试有多么困难，甚至我们可以说对其进行单元测试这是不可能的。在下一篇博文中，我将建议大家将业务逻辑委托给使用了正确的依赖注入姿势的类。如果我们觉得定义这些类很麻烦的话，退而求其次，也能让这些类的依赖成为与 Android 无关的接口。与增强程序测试性的第一步相比，这一步是至关重要的，而完成第二步使我们无需 Android 特有的测试工具（例如：Roboletric，Instrumented Tests）就能写下更高效的测试单元。\n\n**注**\n\n1. 毫无疑问，你在传入 ServiceLauncher 时应该使他变为一个序列化对象。但这并不是一个特别健壮的解决办法，因为只有在你不在乎序列化带来的性能影响时才能使用这个办法。\n"
  },
  {
    "path": "issue-10/Kotlin-for-Android-(IV)：自定义视图和Android的扩展.md",
    "content": "Kotlin for Android (IV)：自定义视图和Android的扩展\n---\n\n> * 原文链接 : [Kotlin for Android (IV): Custom Views and Android Extensions](http://antonioleiva.com/kotlin-android-custom-views/)\n* 原文作者 : [Antonio Leiva](http://antonioleiva.com)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [sundroid](https://github.com/sundroid) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  已校对\n\n\n在了解[extension functions and default values](http://antonioleiva.com/kotlin-android-extension-functions/)\n可以为我们做什么后，你可能想知道接下来将会发生什么。\n在[first article about Kotlin](http://antonioleiva.com/kotlin-for-android-introduction/)\n我们讨论过，Kotlin使得我们的开发变的更加简单，并且接下来我将更加深入的阐述这一观点。\n\n自定义View\n---\n\nKotlin的以往版本（一直到M10）是不支持去创建自定义view的。原因是我们在使用Kotlin时，每一个类只有一个构造函数。这通常是足够的，因为我们使用可选参数来使构造函数足以满足我们的需求。下面就是这样的一个例子：\n\n``` Java\nclass MyClass(param: Int, optParam1: String = \"\", optParam2: Int = 1) \n{ \n     init {\n         // Initialization code\n     } \n}\n```\n\n有一个独特的构造函数，我们现在有四种方法来创建这个类：\n\n\n``` Java\nval myClass1 = MyClass(1)\nval myClass2 = MyClass(1, \"hello\")\nval myClass3 = MyClass(param = 1, optParam2 = 4)\nval myClass4 = MyClass(1, \"hello\", 4)\n```\n\n\n正如你所见，我们通过可选参数达到了我们想要的多构造函数的目的。但是这导致了一个问题就是如果我们尝试通过继承系统的View来创建一个Android自定义View。自定义View需要重写不止一个父类的构造函数才能够正常运行，并且Kotlin没有提供这样的方法。幸运的是，从Kotlin M11版本发布后就支持声明多个构造函数，就像我们在Java下一样。这是一个ImageView保存平方率的例子：\n\n``` Java\nclass SquareImageView : ImageView {\n \n    public constructor(context: Context) : super(context) {\n    }\n \n    public constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {\n    }\n \n    public constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {\n    }\n \n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        val width = getMeasuredWidth()\n        setMeasuredDimension(width, width)\n    }\n}\n```\n\n非常简单，它可能不太详细，但是至少我们现在有一个方法。\n\nKotlin Android 扩展组件\n---\n\n通过Kotlin M11，我们还提供了一个新的插件，这个插件将会帮助我们这个android开发者们用一种更加简单的方法去访问xml下定义的view，当你们使用这个方法时你们将会记得Butterknife，但是这个将会更加简单的去使用它。\n\nKotlin Android 扩展组件是一个基本的控件绑定，它将会让你在代码中通过id来使用你在xml中定义的view，它将会在不使用任何注解或者findViewById的方法自动创建这些属性。\n\n使用这个扩展组件，你需要安装一个新的插件，这个插件叫做Kotlin Android Extensions 并且添加通过buildscript来添加这个地址（在项目的build.gradle=中）\n\n\n\n``` Java\nbuildscript { \n    …\n     dependencies {\n        … \n        classpath \"org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version\"\n    }\n }\n```\n\n设想你有一个一个布局，叫main.xml：\n\n``` Java\n<FrameLayout \n    xmlns:android=\"...\"\n     android:id=\"@+id/frameLayout\"\n     android:orientation=\"vertical\"\n     android:layout_width=\"match_parent\"\n     android:layout_height=\"match_parent\">\n \n      <TextView\n         android:id=\"@+id/welcomeText\" \n        android:layout_width=\"wrap_content\"\n         android:layout_height=\"wrap_content\"/>  \n \n</FrameLayout>\n```\n\n\n如果你想在你的activity中使用这个view，你唯一需要做得就是导入这个Kotlin Android Extensions提供的属性：\n\n``` Java\nimport kotlinx.android.synthetic.<xml_name>.*\n```\n\n在我们的例子中，它将是主要的：\n\n\n``` Java\nimport kotlinx.android.synthetic.main.*\n```\n\n现在你可以通过id使用你的view了：\n\n``` Java\noverride fun onCreate(savedInstanceState: Bundle?) { \n    super<BaseActivity>.onCreate(savedInstanceState)\n    setContentView(R.id.main) \n    frameLayout.setVisibility(View.VISIBLE)\n     welcomeText.setText(\"I´m a welcome text!!\") \n}\n```\n\n\n总结\n---\n\n通过这两个新功能，我们Kotlin 团队真正感兴趣的是让android开发者们可以生活的更加轻松。他们也发布了一个叫做Anko的库，让我们通过Kotlin创建布局。我们有使用其主要功能，但是你在处理View时可以使用它去简化你的代码，我有一些Kotlin项目的例子已经提交到github，你可以看看这些例子或者其他相关资料。\n\n下一个文章将会介绍使用lambda表达式和他们怎样才能帮助我们简化代码。非常有趣，对我来说，Kotlin和java 1.7比较起来将会变的非常有力。\n"
  },
  {
    "path": "issue-10/readme.md",
    "content": "# 开发技术前线 第10期\n\n| 文章名称 |   译者  | \n|---------|--------|\n| [Android如何直播RTMP流](Android如何直播RTMP流.md)  | [ayyb1988](https://github.com/ayyb1988)      |\n| [Android 进行单元测试难在哪-part2](Android进行单元测试难在哪-part2.md)  | [chaossss](https://github.com/chaossss)|\n| [Kotlin-for-Android-(IV)：自定义视图和Android的扩展](Kotlin-for-Android-(IV)：自定义视图和Android的扩展.md)  | [sundroid](https://github.com/sundroid)      |\n| [使用Facebook-SDK为安卓应用添加Like按钮](使用Facebook-SDK为安卓应用添加Like按钮.md)  | [objectlife](https://github.com/objectlife)|\n| [将基于Dagger-1开发的项目迁移到Dagger-2中](将基于Dagger-1开发的项目迁移到Dagger-2中.md)  | [chaossss](https://github.com/chaossss)     |\n"
  },
  {
    "path": "issue-10/使用Facebook-SDK为安卓应用添加Like按钮.md",
    "content": "使用Facebook SDK为安卓应用添加Like按钮\n\n> * 原文链接 : [How to add a Native Facebook Like Button to your Android app using Facebook SDK for Android v4](http://inthecheesefactory.com/blog/how-to-add-facebook-like-button-in-android-app/en)\n* 原文作者 : [nuuneoi](http://inthecheesefactory.com/blog)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [objectlife](https://github.com/objectlife) \n* 校对者: [tiiime](https://github.com/tiiime)\n* 状态 :  完成 \n\n\nLike按钮是为你的网站增加流量最重要策略之一。不要惊讶为什么Facebook会引入这个Like按钮，`LikeView`,开发者可以随意将Like按钮添加到他们的Android/iOS中。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/likes.png)\n\n虽然它听起非常简单，尤如我们在做网站的其它内容一样，但是答案是否定的。如果我们只是在应用的layout中添LikeView,它也会起作用但是在功能上会有限制，比如Like的数目和Like的状态不会显示。如果不安装Facebook app那是不行的。\n\n在深挖Facebook SDK's的源代码之后，我发现**LikeView被设计成只有安装Facebook app之后才能完全展现所有功能**。。。据我所知(AFAIK)也没有其它的文档提到这一问题。\n\n在试验了几次之后，最后我终于找到可以展现LikeView所有功能的方法，并且实践起来很容易。让我一步一步的介绍。\n\n##创建一个Facebook应用\n\n正如我上面所提到的，如果想让LikeView展现所有的功能是需要连接Facebook app的。所以我们第一步是创建一个Facebook app。\n\n我们打开[https://developers.facebook.com/apps](https://developers.facebook.com/apps)页面之后添加新应用。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/addnewapp.png)\n\n输入你的应用的名字点击Create New Facebook App ID\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/newapp2.jpg)\n\n选择一个类别点击Create App ID\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/categoryselection.jpg)\n\n接下来你会跳转到Facebook App的设置页面，滚动到页面底部并且输入你应用的相关信息，**Package Name**和**Default Activity Class Name**点击**Next**\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/classes.jpg)\n\n接下来有一点小复杂，为了让你的应用无论是在开发环境还是生产环境下都能完美使用Facebook App，需要分别填写Debug Key Hash and Release Key Hash。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/keyhashesbefore.jpg)\n\n有两种方式可以生成key hashes：命令或者代码。\n\n###方法1 - 命令行\n\n假设你使用**Mac**或者**Linux**并且已经安装了`keytool`(jdk自带)和`openssl`.你可以使用下面命令\n\n```\nkeytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64\n```\n\nWindows下:\n\n```\nkeytool -exportcert -alias androiddebugkey -keystore %HOMEPATH%\\.android\\debug.keystore | openssl sha1 -binary | openssl base64\n```\n\n下面的命令是是用生成发布签名的Key Hash，这样上线的应用也可以使用Facebook app。\n\n```\nkeytool --exportcert -alias ENTER_ALIAS_HERE -keystore PATH_TO_KEYSTORE.keystore | openssl sha1 -binary | openssl base64\n```\n\n###方法2 - 通过JAVA代码\n\n假设你还没有并且你也不想安装keytool和openssl，你也可以通过下面JAVA代码来生成。**不要忘记修改成你的包名**.\n\n```\ntry {\n            PackageInfo info = getPackageManager().getPackageInfo(\n                    \"com.inthecheesefactory.lab.facebooklike\",\n                    PackageManager.GET_SIGNATURES);\n            for (Signature signature : info.signatures) {\n                MessageDigest md = MessageDigest.getInstance(\"SHA\");\n                md.update(signature.toByteArray());\n                Log.d(\"KeyHash:\", Base64.encodeToString(md.digest(), Base64.DEFAULT));\n            }\n        } catch (PackageManager.NameNotFoundException e) {\n\n        } catch (NoSuchAlgorithmException e) {\n\n        }              \n```\n\n为了生成开发环境下的Key Hash，你可以在IDE中简单运行一下应用从打印的Logcat中复制出生成的key hash，把生成key hash分别填进**Development Key Hashes** 和 **Release Key Hash**。\n\n为了生成正式环境下的Key Hash，你需要为你的应用打包签名，将打包签名的应用安装到设备或者模拟器上打开，将生成的Key Hash填到Release Key Hash处。\n\n> 注意，用于正式打包的Key Hash值可以稍后补上，最重要的是你现在先把开发环境下的Key Hash值填写到Development Key Hashes and Release Key Hash中\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/keyhashes2.jpg)\n\n点击**Next**并滚到页面到底端，点击**Skip to Developer Dashboard**进入到你刚刚创建的应用面板中。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/skiptodashboard.jpg)\n\n将**App ID**复制下来一会要用，到此为止一个Facebook App已经创建完成。\n\n##项目中设置Facebook SDK\n\n在客户端导入该库\n\n```\ndependencies {\n    compile 'com.facebook.android:facebook-android-sdk:4.0.1'\n}\n```\n\n将Application ID添加到value/strings中：\n\n```\n<string name=\"app_id\">你的Application ID</string>\n```\n\n将下面的代码粘到`AndroidManifest.xml`的`</application>`之前，**一定要修改下面FacebookContentProvider后面的值为你的Application ID**。\n\n```\n        <activity android:name=\"com.facebook.FacebookActivity\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar\"\n            android:configChanges=\"keyboard|keyboardHidden|screenLayout|screenSize|orientation\"\n            android:label=\"@string/app_name\" />\n        <meta-data android:name=\"com.facebook.sdk.ApplicationName\"\n            android:value=\"@string/app_name\" />\n        <meta-data android:name=\"com.facebook.sdk.ApplicationId\" android:value=\"@string/app_id\"/>\n\n        <provider android:authorities=\"com.facebook.app.FacebookContentProvider1459806660978042\"\n            android:name=\"com.facebook.FacebookContentProvider\"\n            android:exported=\"true\"/>\n```        \n\n\nINTERNET permission is needed for LikeView. Don't forget to add this line inside `AndroidManifest.xml` before `<application>`.\n\n添加访问网络权限\n\n```\n<uses-permission android:name=\"android.permission.INTERNET\"/>\n```\n\n在自定义的Application中作Facebook SDK的初使化工作\n\n```\npublic class MainApplication extends Application {\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        FacebookSdk.sdkInitialize(getApplicationContext());\n   }\n}\n```\n\n检查一下自定义Application是否在`AndroidManifest.xml`注册。\n\n```\n<application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\"\n        android:name=\".MainApplication\" >\n```\n\n至此，已经完成了Facebook SDK的设置。\n\n## 使用LikeView\n\n在部局文件中使用\n\n```\n<com.facebook.share.widget.LikeView\n        android:id=\"@+id/likeView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n```\n\n代码初使化\n\n```\nLikeView likeView = (LikeView) findViewById(R.id.likeView);\nlikeView.setLikeViewStyle(LikeView.Style.STANDARD);      \nlikeView.setAuxiliaryViewPosition(LikeView.AuxiliaryViewPosition.INLINE);\n```\n\n通过**setObjectIdAndType**方法设置LikeView的url。\n\n```\nlikeView.setObjectIdAndType(\n        \"http://inthecheesefactory.com/blog/understand-android-activity-launchmode/en\",\n        LikeView.ObjectType.OPEN_GRAPH)\n```\n\n添加成功后如图所示\n![](http://inthecheesefactory.com/uploads/source/facebooklike/exp0.jpg)\n\n虽然效果出来了，但是效果还不完美，目前有两个比较大的问题：\n\n**问题1：如果不点击Like按钮，就不会看到Like的数目和状态。**\n\n**问题2：没有安装Facebook应用的设备是不能使用的。**\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/exp1.jpg)\n\n在上面已经描述过其中的原由了，只有和Facebook连接成功才能完美的使用LikeView。与在网站中使用不同的一点就是不需要登录。(作者:我很好奇Facebook为什么会这样设计)\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/onweb.png)\n\n看来得变通一下才行，要显示LikeView需要登录Facebook，可以用和LikeView相同外观的登录按钮代替。\n\n这并不复杂。在LikeView的父部局RelativeLayout中使用LinearLayout创建一个登录按钮\n\n```\n<RelativeLayout\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <!-- Login Button in the same style as LikeView -->\n    <LinearLayout\n        android:id=\"@+id/btnLoginToLike\"\n        android:background=\"@drawable/com_facebook_button_like_background\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:clickable=\"true\" >\n\n        <ImageView\n            android:src=\"@drawable/com_facebook_button_icon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:layout_marginTop=\"7.5dp\"\n            android:layout_marginBottom=\"7.5dp\"/>\n\n        <TextView\n            android:id=\"@+id/tvLogin\"\n            android:text=\"Login\"\n            android:layout_marginLeft=\"2dp\"\n            android:layout_marginRight=\"8dp\"\n            android:textColor=\"@android:color/white\"\n            android:textStyle=\"bold\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"/>\n\n    </LinearLayout>\n\n    <com.facebook.share.widget.LikeView\n        android:id=\"@+id/likeView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n</RelativeLayout>\n```\n\n然后在代码中使用Facebook SDK提供的**LoginManager, CallbackManager** 和 **AccessToken** 类来完成登录的逻辑。\n\n```\npublic class MainActivity extends Activity {\n    \n    LinearLayout btnLoginToLike;\n    LikeView likeView;\n    CallbackManager callbackManager;\n\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        initInstances();\n        initCallbackManager();\n        refreshButtonsState();\n    }\n\n    private void initInstances() {\n        btnLoginToLike = (LinearLayout) findViewById(R.id.btnLoginToLike);\n        likeView = (LikeView) findViewById(R.id.likeView);\n        likeView.setLikeViewStyle(LikeView.Style.STANDARD);\n        likeView.setAuxiliaryViewPosition(LikeView.AuxiliaryViewPosition.INLINE);\n\n        btnLoginToLike.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                LoginManager.getInstance().logInWithReadPermissions(MainActivity.this, Arrays.asList(\"public_profile\"));\n            }\n        });\n    }\n\n    private void initCallbackManager() {\n        callbackManager = CallbackManager.Factory.create();\n        LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback<LoginResult>() {\n            @Override\n            public void onSuccess(LoginResult loginResult) {\n                refreshButtonsState();\n            }\n\n            @Override\n            public void onCancel() {\n\n            }\n\n            @Override\n            public void onError(FacebookException e) {\n\n            }\n        });\n    }\n\n    private void refreshButtonsState() {\n        if (!isLoggedIn()) {\n            btnLoginToLike.setVisibility(View.VISIBLE);\n            likeView.setVisibility(View.GONE);\n        } else {\n            btnLoginToLike.setVisibility(View.GONE);\n            likeView.setVisibility(View.VISIBLE);\n        }\n    }\n\n    public boolean isLoggedIn() {\n        return AccessToken.getCurrentAccessToken() != null;\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        // Handle Facebook Login Result\n        callbackManager.onActivityResult(requestCode, resultCode, data);\n    }\n}\n```\n\nOK。让我们看一下结果。\n\n###结果\n\n如果程序无法连接到Facebook app,我们的登录按钮将会代替LikeView显示。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/btnLogin2.png)\n\n一旦点击登录按钮，跳转到用户登录\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/loginprocess.png)\n\n用户登录之后，登录按钮将会被隐藏同时显示LikeView，届时你将会看到Like的数目和状态也会像网站中的一样完美的显示出来。如果url改变了，那些数目和状态也会相应的刷新。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/loggedin.png)\n\n如果用户点击Like,也会影响到网站中使用的Like.\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/liked.png)\n\n这种方法可以让没有安装Facebook应用的设备使用Facebook SDK.意味着在Chrome和ARC Welder 也可以使用\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/arcwelder.jpg)\n\n##已知bug\n\n虽然它接近完美但是仍然有一些bug。如果在网站中点击了Like，应用中的Like状态不会改变。除了等Facebook修复这个bug我们无能为力。\n\n##FBLikeAndroid Library\n\n为了让它更加容易使用，我特意制作了一个library。**FBLikeAndroid 就是当成功连接到Facebook应用时，登录按钮自动改变原生Like按钮的library**\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/fblikeandroid.png)\n\n使用它的时候你只需按照上面的步骤创建一个Facebook应用，然后添加依赖到你项目`build.gradle`中。**注意：此依赖已经包括了Facebook SDK，你不需要再添加额外依赖**\n\n```\ndependencies {\n    compile 'com.inthecheesefactory.thecheeselibrary:fb-like:0.9.3'\n}\n```\n\n像如下方式使用该组件\n\n```\n<com.inthecheesefactory.lib.fblike.widget.FBLikeView\n        android:id=\"@+id/fbLikeView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n```\n\nFBLikeView中LikeView使用了标准外观，你不需要再重新设置，除非你想改变它的style.如果想访问LikeView你可以使用`getLikeView()`方法。下面的代码是为LikeView设置url\n\n```\nFBLikeView fbLikeView = (FBLikeView) rootView.findViewById(R.id.fbLikeView);\nfbLikeView.getLikeView().setObjectIdAndType(\n        \"http://inthecheesefactory.com/blog/understand-android-activity-launchmode/en\",\n        LikeView.ObjectType.OPEN_GRAPH);\n```\n\n最后一步，你需要在每一个Activity的`onActivityResult`中调用`FBLikeView.onActivityResult`方法进行Facebook登录\n\n```\n@Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        FBLikeView.onActivityResult(requestCode, resultCode, data);\n    }\n```\n\n就这样。是不是很简单。\n\n如果你想与Facebook取消连接。使用下面代码就可以了。按钮会自动改变其状态。\n\n```\nFBLikeView.logout();\n```\n\nFBLikeAndroid Library的源码[https://github.com/nuuneoi/FBLikeAndroid](https://github.com/nuuneoi/FBLikeAndroid)有空的时候可以看一下。\n\n\n##Submit for public use\n\n现在LikeView只在当前Facebook的账号下可以使用。为了让每一个人可以使用LikeView，你需要向Facebook提交申请。下面是申请步骤：\n\n1）进入你的Facebook应用的详情页。输入描述、隐私政策URL并上传你希望使用的应用图标。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/appdetails.jpg)\n\n2)进入Status & Review页面点击Start a Submission\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/submission1.png)\n\n3)选择Native Like Button项并点击Add 1 Item\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/submission2.png)\n\n4)点击Add Notes在弹窗中输入一些介绍信息，根据我的经验，提供一些应用截图的链接比输入文字的效果更好。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/AddNotes.jpg)\n\n上传APK文件，并上传你应用的截图(最少4张)勾选**I have tested that my application loads on all of the above platforms**,并点击**Submit for Review**。\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/submission3.jpg)\n\n6)在**Settings**页面输入联系Email\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/submission4.jpg)\n\n7)最后一步是为了让每个人都可以使用创建的Facebook应用，在**Status & Review**页面将按钮状态切换为**On**\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/submission5.jpg)\n\n出去旅个游，钓个鱼什么的等个一两天就会有结果了。平均来说，大概需要提交2-3次才能通过，所以在发布你应用的前一个星期就需要开始申请。\n\n当申请通过之后**Status & Review**页面是这样的\n\n![](http://inthecheesefactory.com/uploads/source/facebooklike/submissionpassed.png)\n\n一旦状态变成如图中那样，那么每个人都可以使用你的LikeView了\n\n希望这篇文章对你有用，不要忘了给文章点赞哦。=)\n\n\n"
  },
  {
    "path": "issue-10/将基于Dagger-1开发的项目迁移到Dagger-2中.md",
    "content": "将基于Dagger-1开发的项目迁移到Dagger-2中\n---\n\n> * 原文链接 : [Dagger 1 to 2 migration process](http://frogermcs.github.io/dagger-1-to-2-migration/)\n* 原文作者 : [Miroslaw Stanek](https://about.me/froger_mcs)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [tiiime](https://github.com/tiiime)\n* 状态 :  完成\n\n我相信每一个 Android 开发者都听说过依赖注入框架，事实上几乎所有 Android 官方会议都讨论过软件设计模式。虽然我是依赖注入的脑残粉，但我不得不承认也有人不喜欢依赖注入，主要原因如下：\n\n- **依赖注入框架很慢** - 好吧，在使用 [RoboGuice](https://github.com/roboguice/roboguice) 的时代里，整个依赖图表会在运行时被创建和验证，使依赖注入框架确实会存在这样的问题。但现在，[Dagger](https://github.com/square/dagger) 让这一切发生了改变。在 Dagger 1 框架中，大量的工作（图表验证）在编译时被完成，而且对象创建也不需要通过反射机制完成（值得一提的是：最近发布的 Roboguice 3 也在编译时完成了大量的工作）。虽然 Dagger 1 框架的效率还是不如工程师亲自写的代码，但在大部分 App 里这都是可以接受的。\n\n- **依赖注入框架需要大量的模板** - 可以说它是对的，也可以说不是。确实，我们需要为提供依赖的类和注入添加额外的代码，但正因为我们完成了这样的工作，使我们不必在每次需要使用它们的时候通过构造器处理它们。我不否认依赖注入框架会在影响小型 App 的性能，但随着 App 内部结构变得复杂，依赖注入的好处会越来越明显。\n\n- 其他缺点就是较差的溯源性，已生成代码的可读性差等等……\n\n但我已经说了：我是依赖注入框架的脑残粉，刚刚提到的种种缺点并不会让我放弃在项目中使用它。直到最近我使用 Dagger 1 都没产生什么问题，但当我们决定完全重写 [Azimo](https://azimo.com/) （我的新项目），并将它运行在依赖注入框架时，Dagger 1 中存在的一些问题渐渐显现。这些问题到底是什么呢？且听我娓娓道来。\n\n不过好消息是，Dagger 2 虽然还没有发布正式版，但 Dagger 2 已经有一个稳定，特征完备的版本了。\n\n## Dagger 2\n\n说实话，我并没有太深入地了解 Dagger 2 的实现细节，我只是稍微看了看官网的一些说明 [Dagger 2 website](http://google.github.io/dagger/) 和 Jake Wharton 的随笔-[The Future of Dependency Injection with Dagger 2](https://www.parleys.com/talk/5471cdd1e4b065ebcfa1d557/)。\n\n毕竟对我来说最重要的是，Dagger 2 几乎自动地帮我解决了 Dagger 1 中大量的长耗时任务。\n\n## 对项目使用 Proguard\n\n我不得不承认，Azimo 应用的方法数已经超过了 65536 个了（[Android Dex's 64k 限制](http://ingramchen.io/blog/2014/09/prevention-of-android-dex-64k-method-size-limit.html)）。刚开始开发 Azimo 的时候我们使用的是 [MultiDex solution](https://developer.android.com/tools/building/multidex.html)，但随着应用的迭代我们发现 MultiDex solution 存在一些缺陷，因此我们必须考虑使用 Proguard。为什么我们必须要作出这样的决定呢？\n\n> 译者注：ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具，它可以删除无用的类、字段、方法和属性。可以删除没用的注释，最大限度地优化字节码文件。它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性。常常用于Android开发用于混淆最终的项目，增加项目被反编译的难度。\n\n仅有在 MultiDexApplication 类的 onCreate() 方法中调用的 MultiDex.install(this) 在使用 Android 4.4 系统的 Nexus 7 设备中花费大约 4000ms 的时间（Lollipop 对 MultDex 的支持是基于内核的，所以在相同的设备中只需要1ms）。此外，应用的构建时间戏剧性地不降反增（即使我们只修改了一行代码，每一个 Gradel assembleDebug 仍需要2分钟左右的时间）。为什么耗时这么长？简单来说：每一次我们修改代码决定将哪些代码放到第一个 .dex 文件中以及哪些代码能够移动到其他 .dex 文件时，MultiDex 插件都需要扫描资源文件。\n\n所以我们决定使用 Proguard，然后就爆炸了……因为在 Proguard 规范下没有用来处理 Dagger 自动生成代码的简单办法。所以我们得使用 [Squad leader](https://bitbucket.org/littlerobots/squadleader) 的 @Keep 注解来简化这个问题，但我们还是需要花费一定的时间更新代码，并将这条规范在注释中标注。\n\nDagger 2 回应：Proguard 中不存在 Dagger 2 所需要的单一原则。一切都是自然而然发生的。因为 Dagger 2 产生完全可溯源的代码，而且不需要使用反射 - 这对 Proguard 的友好度简直爆表。\n\n## 其他事项\n\n现在还有一些不是非常重要（但又有用）的事项让我们确信将基于 Dagger 1 框架开发的代码转移到 Dagger 2 中：\n\n- Dagger 1 自动生成的代码非常难度，虽然 Dagger 1 他爹值得信赖，但了解 Dagger 1 在底层到底留下了什么对我们也有好处。由于这些代码并不是完全可溯源的，那就意味着我们不能用 IDE 中类似 “find usages” 的功能。\n\nDagger 2 生成了和程序员亲手实现的依赖注入代码几乎一样的整个栈。使代码具有完全可溯源性，便于我们更好地理解代码的运行机制。\n\n- 或许 Dagger 2 的拓展性变差了些许，但 Dagger 2 的 API 比 Dagger 1 清晰易用得多。我们的团队仍然在发展，在我们重写应用的时候，理解整个应用的架构过程必须尽可能快， Bug 要尽可能地少是很重要的。幸亏 Dagger 2 的学习曲线并不陡峭，这为我们节省了学习成本。\n\n- 依赖图的构建时间。或许现在它对我们来说不是什么大问题 - 因为在 Nexus 7（Android 4.4 系统）构造应用的依赖图大概需要 80ms 的时间。但使用了 Dagger 2，只需要 40ms 依赖图就构建完成了。\n\n# 将基于 Dagger 1 开发的项目迁移到 Dagger 2 中\n\nAntonio Leiva 曾在几个月前写了有关如何在 Android 基于 MVP 模式开发的项目中使用依赖注入的系列博文([post 1](http://antonioleiva.com/dependency-injection-android-dagger-part-1/), [post 2](http://antonioleiva.com/dependency-injection-android-dagger-part-2/), [post 3](http://antonioleiva.com/dependency-injection-android-dagger-part-3/))。我觉得这个项目挺好的，于是将它下了下来，并让它的依赖注入框架更新为 Dagger 2。\n\n## 依赖图\n\n为了更好地理解 Dagger 1 是如何在范例中运行的，我用 DaggerExample 项目创建了依赖图：\n\n![](http://frogermcs.github.io/images/12/dagger1-graph.jpg)\n\n现在我们来看看在相同的项目中使用 Dagger 2 创建的依赖图：\n\n![](http://frogermcs.github.io/images/12/dagger2-graph.jpg)\n\n你能看到其中的相似之处吗？\n\nDagger 1 和 Dagger 2 两者中最值得一提的区别就是 Compontents。简单来说，它们把调用者可能请求的所有类型都枚举出来了。但组件接口只声明它为调用者提供了某些东西，以及这些东西由 Module 提供，所以 Module 仍然负责创建对象。组件只是依赖图的公有 API。\n\n# 迁移过程\n\n## 创建依赖\n\n首先，为了添加新的依赖，我们要更新 build.gradle 文件。当我在写这篇博文的时候，只有一个快照版本是可用的。这也是我们必须添加 Sonatype snapshot 库的原因：\n\n**build.gradle:**\n\n```java\n\tbuildscript {\n\t    repositories {\n\t        mavenCentral()\n\t        maven { url \"https://oss.sonatype.org/content/repositories/snapshots/\" }\n\t    }\n\t    dependencies {\n\t        classpath 'com.android.tools.build:gradle:1.1.3'\n\t        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'\n\t    }\n\t}\n\t \n\tallprojects {\n\t    repositories {\n\t        mavenCentral()\n\t        maven { url \"https://oss.sonatype.org/content/repositories/snapshots/\" }\n\t    }\n\t}\n```\n\nAndroid-ADT 插件与注解处理器协作，并仅在注解处理器为依赖时允许配置编译时间而不把 artifact 添加到最后的 APK 中。当然了，它还为自动生成的代码生成了相应的原路径，在 Android Studio 中它们都是可见的并且可溯源的。\n\nDagger 的 app/build.gradle 文件和 Android-ADT 中的信息应该大致如下：\n\n```java\n\tapply plugin: 'com.android.application'\n\tapply plugin: 'com.neenbedankt.android-apt'\n\t \n\tandroid {\n\t    compileSdkVersion 22\n\t    buildToolsVersion \"22.0.1\"\n\t \n\t    defaultConfig {\n\t        minSdkVersion 14\n\t        targetSdkVersion 22\n\t        versionCode 1\n\t        versionName \"1.0\"\n\t    }\n\t \n\t    compileOptions {\n\t        sourceCompatibility JavaVersion.VERSION_1_7\n\t        targetCompatibility JavaVersion.VERSION_1_7\n\t    }\n\t    buildTypes {\n\t        release {\n\t            minifyEnabled false\n\t            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'\n\t        }\n\t    }\n\t}\n\t \n\tdependencies {\n\t    compile fileTree(dir: 'libs', include: ['*.jar'])\n\t    compile 'com.google.dagger:dagger:2.0-SNAPSHOT'\n\t    apt 'com.google.dagger:dagger-compiler:2.0-SNAPSHOT'\n\t    provided 'org.glassfish:javax.annotation:10.0-b28'\n\t}\n```\n\n## Modules\n\n接下来这一步可以说是迁移过程中最简单的一步了。Module 将尽可能变得简单，它们只需要为将要提供的对象添加 @Module（不需要任何额外的参数） 和 @Provides 注解。\n\n这是 AppModule 类：\n\n**Dagger 1:**\n\n```java\n\t@Module(\n\t        injects = {\n\t                App.class\n\t        },\n\t        includes = {\n\t                DomainModule.class,\n\t                InteractorsModule.class\n\t        }\n\t)\n\tpublic class AppModule {\n\t \n\t    private App app;\n\t \n\t    public AppModule(App app) {\n\t        this.app = app;\n\t    }\n\t \n\t    @Provides public Application provideApplication() {\n\t        return app;\n\t    }\n\t}\n```\n\n**Dagger 2:**\n\n```java\n\t@Module\n\tpublic class AppModule {\n\t \n\t    private App app;\n\t \n\t    public AppModule(App app) {\n\t        this.app = app;\n\t    }\n\t \n\t    @Provides public Application provideApplication() {\n\t        return app;\n\t    }\n\t}\n```\n\n两者的区别在哪里？区别在于：注入参数被移动到组件内，就像使用 include。所有 Module 参数都该尽快过时/移除。\n\n##组件\n\n就像我刚刚说的，Dagger 2 中还添加了一些新的特性。简单来说把，就是依赖图的一些公有 API。大家不妨再回去看看刚刚 Dagger 2 的依赖图，这是每一个 @Componentclass 的实现：\n\n**AppComponent:**\n\n```java\n\t@Singleton\n\t@Component(\n\t        modules = {\n\t                AppModule.class,\n\t                DomainModule.class,\n\t                InteractorsModule.class\n\t        }\n\t)\n\tpublic interface AppComponent {\n\t    void inject(App app);\n\t \n\t    AnalyticsManager getAnalyticsManager();\n\t    LoginInteractor getLoginInteractor();\n\t    FindItemsInteractor getFindItemsInteractor();\n\t}\n```\n\n**LoginComponent:**\n\n```java\n\t@ActivityScope\n\t@Component(\n\t        dependencies = AppComponent.class,\n\t        modules = LoginModule.class\n\t)\n\tpublic interface LoginComponent {\n\t    void inject(LoginActivity activity);\n\t \n\t    LoginPresenter getLoginPresenter();\n\t}\n```\n\n**MainComponent:**\n\n```java\n\t@ActivityScope\n\t@Component(\n\t        dependencies = AppComponent.class,\n\t        modules = MainModule.class\n\t)\n\tpublic interface MainComponent {\n\t    void inject(MainActivity activity);\n\t \n\t    MainPresenter getLoginPresenter();\n\t}\n```\n\n大家可以看到我使用了 @AcivityScope 注解。简单来说，它是本地子图中 @Singleton 注解的代替品。在 Dagger 1 里，假如我们有一个 LoginPresenter 类的单例对象，虽然这个对象确实是一个单例，但某种程度上它总像一个本地单例 - 因为它只存活于图的作用域中（示例代码中，该单例会在 onDestory() 方法中被设置为 null)。\n\n@ActivityScope is used just for semantic clarity and it’s defined in our code:\n\n@ActivtiyScope 只用于语境清晰的环境中，它在代码中的定义如下：\n\n```java\n\t@Scope\n\t@Retention(RetentionPolicy.RUNTIME)\n\tpublic @interface ActivityScope {\n\t}\n```\n\n## 对象图\n\nDagger 2 中不再有对象图，因为对象图被组件取代了。确切来说，Dagger 2 框架将自动生成以 DaggerComponent_ 为前缀的代码。这就意味着我们现在必须处理生成代码（但只在这里处理）。\n\n我们必须记住：DaggerComponent_ 类只有在所有代码有效的情况下被生成，所以在你解决所有 Error 之前你不会看到它。\n\n那它具体长什么样呢？\n\n```java\n\tpublic class App extends Application {\n\t \n\t    private AppComponent component;\n\t \n\t    @Inject\n\t    AnalyticsManager analyticsManager;\n\t \n\t    @Override\n\t    public void onCreate() {\n\t        super.onCreate();\n\t        setupGraph();\n\t        analyticsManager.registerAppEnter();\n\t    }\n\t \n\t    private void setupGraph() {\n\t        component = Dagger_AppComponent.builder()\n\t                .appModule(new AppModule(this))\n\t                .build();\n\t        component.inject(this);\n\t    }\n\t \n\t    public AppComponent component() {\n\t        return component;\n\t    }\n\t \n\t    public static App get(Context context) {\n\t        return (App) context.getApplicationContext();\n\t    }\n\t}\n```\n\n我们的图将通过16-18行代码生成，取代了 Dagger 1 的 ObjectGraph.create(getModules()) 代码。\n\n第19行代码将 App 对象注入到途中（而且此时类中所有 @Inject 注解都是可信的）\n\n这是一个本地图（MainActivity）的范例：\n\n```java\n\tprotected void setupComponent(AppComponent appComponent) {\n\t    Dagger_MainComponent.builder()\n\t            .appComponent(appComponent)\n\t            .mainModule(new MainModule(this))\n\t            .build()\n\t            .inject(this);\n\t}\n```\n\nMainComponent 依赖于 AppComponent，使我们必须准确地提供这个对象。如果 Module 没有默认的构造器，你必须提供（就像 MainModule)。\n\n那么迁移工作到这里就算完成了。提交完整的迁移过程的 [pull request](https://github.com/antoniolg/DaggerExample/pull/5) 能防止我们遗漏某些东西。大家必须记住：这篇文章没有讲解更多复杂的解决办法，以及 Dagger 2 的所有特性和功能。下面是一些能帮你更好地理解 Dagger 2 和依赖注入框架运行机制的链接：\n\n[The Future of Dependency Injection with Dagger 2](https://www.parleys.com/talk/5471cdd1e4b065ebcfa1d557/)\n[Dagger 2 doc by Gregory Kick](https://docs.google.com/document/d/1fwg-NsMKYtYxeEWe82rISIHjNrtdqonfiHgp8-PQ7m8/edit)\n[Dagger 2 official website](http://google.github.io/dagger/)\n\n那这篇博文就到此为止啦，感谢你能耐心看完这篇文章，我希望能更深入地挖掘 Dagger 2 的技术细节，我相信大家很快会看到我的新博文的！😃\n\n## 源码\n\n[Github](https://github.com/frogermcs/DaggerExample) 上面有整个项目的源码。"
  },
  {
    "path": "issue-11/Android-Espresso测试框架介绍.md",
    "content": "Android Espresso 测试框架介绍\n---\n\n> * 原文链接 : [Introduction to Android Espresso](https://androidresearch.wordpress.com/2015/04/04/an-introduction-to-espresso/)\n* 原文作者 : [Veaceslav Grec](https://androidresearch.wordpress.com/author/androidresearch/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [zhengxiaopeng](https://github.com/zhengxiaopeng) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n\n\nEspresso 是一个提供了简单 API 的用于 android app UI 测试的测试框架。最新的 2.0 版本发布后已经可以在 Android Support Repository 中下载了，那么在项目中集成它就方便多了。\n\n但在我们看 Espresso 的 API 之前，让我们来细看下它与其它测试框架的不同：\n\n- 你首先会注意到的是，他写出来的代码很像英文，可想而知它是很容易学习的\n- API 相当的小，当然也会对扩展开放的\n- Espresso 的测试跑起来那是相当的快（没有等待、睡眠）\n- Gradle 和 Android Studio 的支持\n\n\n## 在你的项目中添加 Espresso\n\n1、首先保证你的 Android Support Repository 已经成功安装\n![Android Support Repository](https://androidresearch.files.wordpress.com/2015/03/sdk-manager1.png?w=550&h=303)\n\n2、在你程序的 build.gradle 文件中添加依赖\n\n``` Gradle\ndependencies {\n   androidTestCompile 'com.android.support.test:testing-support-lib:0.1'\n   androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'\n}\n```\n\n3、最后，在默认配置中指定 test instrumentation runner\n\n``` Gradle\nandroid {\n \n    defaultConfig {\n        // ....\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n}\n```\n\n这些基本上就是在你的项目中集成 Espresso 测试框架了（给你的项目一杯浓咖啡提提神~）！\n\n\n## Espresso 的主要组件\n\nEspresso 由 3 个主要的组件构成。\n\n这些组件是：\n\n- ViewMatchers - 在当前的 view 层级中定位一个 view\n- ViewActions - 跟你的 view 交互\n- ViewAssertions - 给你的 view 设置断言\n\n更简单的可以用下面的短语来表述它们：\n\n- ViewMatchers – “ `找` 某些东西“\n- ViewActions – “ `做` 某些事情“\n- ViewAssertions – “ `检查` 某些东西“\n\n举个例子，当你需要 `检查` 某些东西（像在屏幕中显示一些文字），你就会知道你需要一个 ViewAssertions 来做这些工作。\n\nBelow is an example of a test in Espresso, and where the main components find their place.\n\n下面是使用 Espresso 的例子，你会看到那些主要的组件将会在哪里出现使用。\n\n![main components](https://androidresearch.files.wordpress.com/2015/03/espesso_main_components.png)\n\n\n## 一个使用 onView() 的简单测试\n\n假设我们有一个 app，需要用户输入它的名字。\n\n输入名字之后，用户按下了“下一步”按钮然后就会跳转到另一个显示问候信息的 activity。\n\n![scenario](https://androidresearch.files.wordpress.com/2015/03/simple_test_onview.png?w=595)\n\n如果我们按这个方案来写一个测试的话，它看上去将会这样：\n\n``` Java\n// locate the view with id \"user_name\" and type the text \"John\"\nonView(withId(R.id.user_name)).perform(typeText(\"John\"));\n \n// locate the view with id \"next\" and click on it\nonView(withId(R.id.next)).perform(click());\n \n// locate the view with id \"greeting_message\" and check its text is equal with \"Hello John!\"\nonView(withId(R.id.greeting_message)).check(matches(withText(\"Hello John!\")));\n\n```\n\n注意到我们并没有特别指定与其交互的 view 的信息（eg： EditText、Button），我们只是简单的说明了我们要找一个指定 id 的 view。\n\n同样，当点击“下一步”按钮时然后检测文本时，我们也没有写代码来告诉 Espresso 我们有跳转到其它的 activity。\n\n现在，如果要跑起这个测试用例，我们需要把这写代码写到一个类中，然后对于在 Gradle 中这个类应该保存的位置：yourApp`/src/androidTest/java`。\n\n这个就是测试类的样子和它的主要特征：\n\n![SimpleNameTest](https://androidresearch.files.wordpress.com/2015/03/class.png)\n\n## 一个使用 onData 的简单测试\n\n每当你有一个 ListView、GridView、Spinner 或者其它基于 Adapter 的view时，你都必须使用 `onData()` 来把 item 和 list 的数据联系起来。\n\n**onData()** 是给你的 adapter 提供数据的。这是什么意思呢，接下来你就会知道了。\n\n在一个假想的程序中我们需要在一个 **Spinner** 中选择一个国家，一旦选定，这个国家的名字就会在 Spinner 的旁边显示出来。\n\n![Registration](https://androidresearch.files.wordpress.com/2015/03/ondata_simple.png?w=595)\n\n下面的测试就是检查显示出的国家是否与我们选择的相符合，代码长这样：\n\n``` Java\n// locate the view with id \"country_spinner\" and click on it\nonView(withId(R.id.country_spinner)).perform(click());\n \n// match an item that is a String and is equal with whatever value the COUNTRY constant is initialized, then click on it.\nonData(allOf(is(instanceOf(String.class)), is(COUNTRY))).perform(click());\n \n// locate the view with id \"selected_country\" and check its text is equal with COUNTRY\nonView(withId(R.id.selected_country)).check(matches(withText(\"selected: \" + COUNTRY)));\n```\n\n上文中你看到的这个 Spinner 的适配数据是一个字符串的简单数组，所以对于我们要找的 item 来说我们也要指定 String 的数据类型。如果不是一个 String 而是一些自定义的对象呢，我们应该指定这些自定义的对象。\n\n思考下下面这个显示一个 books 的 list 集合数据的例子：\n\n![AdapterView](https://androidresearch.files.wordpress.com/2015/03/books-adapter1.png?w=595&h=388)\n\n把 item 的数据改为 Book 后，来看看查询:\n\n``` Java\nonData(allOf(is(instanceOf(Book.class)), withBookTitle(BOOK_TITLE))).perform(click());\n```\n\n\n## 数据交互\n\nEspresso 有一些很有用的方法可以用来处理数据间的交互。\n\n**atPosition()** - 在下述的这些情况中会很有用，与相应元素交互的对象是不相关的，或 items 的顺序是特定的所以你知道每个 item 在哪个位置。\n\n``` Java\nonData(...).atPosition(2).perform(click());\n```\n\n**inRoot()** - 在没有默认窗口的情况下使用 inRoot()。这个场景可以应用在测试需自动完成时。这个 list 出现在自动输入填写完成的 view 是属于应用窗口之上的窗口视图。\n\n这种情况下你必须指定你要查找的数据，而这数据并不在主程序窗口中。\n\n``` Java\nonView(withText(\"AutoCompleteText\"))\n        .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))\n        .check(matches(isDisplayed()));\n```\n\n**onChildView()**  - 这个数据交互可以进一步地精取出在一个 list 中的指定的（item） view。\n\n你有一个集合列表，每一行的 item 上都有一个删除按钮。你想点击指定 item 上的删除按钮：\n\n``` Java\nonData(withBookTitle(\"My Book\"))\n      .onChildView(withId(R.id.book_delete)).perform(click());\n```\n\n**inAdapterView()** - 可以选择指定一个 adapter view 去操作，默认情况下 Espresso 可以操作任何 adapter view。\n\n你可能会发现这个在处理 `ViewPagers` 和 `Fragments` 时很有用，或者，你想要与当前显示的 AdapterView 交互时，你的 activity 中有多个 adapter view 时。\n\n``` Java\nonData(withBookTitle(\"My Book\"))\n      .inAdapterView(allOf(isAssignableFrom(AdapterView.class), isDisplayed()))\n      .perform(click());\n```\n\n\n## Espresso 和 RecyclerView\n\n[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) 是一个像 ListView、GridVIew 那样呈现数据集合的 UI 组件，实际上它的目的是要替换掉这两个组件。从测试的角度上来看我们感兴趣的有是 RecyclerView 不是一个 AdapterView，这意味着你不能使用 onData() 去跟你的 list items 交互。\n\nFortunately, there is a class called RecyclerViewActions that exposes a small API to operate on a RecyclerView. RecyclerViewActions is part of a separate lib called espresso-contrib, that also should be added to build.gradle:\n\n幸运的是，有一个叫 [RecyclerViewActions](https://developer.android.com/reference/android/support/test/espresso/contrib/RecyclerViewActions.html) 的类提供了简单的 API 给我们操作 RecyclerView。RecyclerViewActions 是 **espresso-contrib**库的一部分，这个库的依赖可以在 build.gradle 中添加：\n\n``` Gradle\ndependencies {\n    // ...\n \n    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.0');\n}\n```\n\n因为你的项目已经包括 recyclerview 依赖, 所以不妨也加上支持库的，一些依赖关系可能出现的冲突。在这种情况下可以在 espresso-contrib 中 exclude 他们：\n\n``` Java\ndependencies {\n    // ...\n \n    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.0') {\n        exclude group: 'com.android.support', module: 'appcompat'\n        exclude group: 'com.android.support', module: 'support-v4'\n        exclude module: 'recyclerview-v7'\n    }\n}\n```\n\n下面就是示例怎么（在RecyclerView）点击指定位置的 item：\n\n``` Java\nonView(withId(R.id.recyclerView))\n      .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));\n```\n\n或者怎么点击指定 item 上的 view：\n\n``` Java\nonView(withId(R.id.recyclerView))\n      .perform(RecyclerViewActions.actionOnItem(\n                hasDescendant(withText(BOOK_TITLE)), click()));\n```\n\n更多 Espresso 的例子请戳：[https://github.com/vgrec/EspressoExamples](https://github.com/vgrec/EspressoExamples)"
  },
  {
    "path": "issue-11/Android进行单元测试难在哪-part3.md",
    "content": "Android 进行单元测试难在哪-part3\n---\n\n> * 原文链接 : [HOW TO MAKE OUR ANDROID APPS UNIT TESTABLE (PT. 1)](http://philosophicalhacker.com/2015/05/01/how-to-make-our-android-apps-unit-testable-pt-1/)\n* 原文作者 : [Matthew Dupree](http://philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [tiiime](https://github.com/tiiime) \n* 状态 :  完成 \n\n\n在 Android 应用中进行单元测试很困难，有时候甚至是不可能的。在[之前的两篇博文](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-9/Android%20%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part1.md)中，我已经向大家解释了在 Android 中进行单元测试如此困难的原因。而[上一篇博文](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-10/Android%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part2.md)我们通过分析得到的结论是：正是 Google 官方所提倡的应用架构方式使得在 Android 中进行单元测试变成一场灾难。因为在官方提倡的架构方式中，Google 似乎希望我们将业务逻辑都放在应用的组件类中（例如：Activity，Fragment，Service，等等……）。而这种开发方式也是我们一直以来使用的开发模板。\n\n在这篇博文中，我列举出几种架构 Android 应用的方法，使用这些方法进行开发能让单元测试变得轻松些。但正如我在[序](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-8/Android%20%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-%E5%BA%8F.md)中所说，我最推崇的办法始终是[ Square 在他们的应用中抛弃 Fragment ](https://corner.squareup.com/2014/10/advocating-against-android-fragments.html)所用的通用方法。因为这个方法是由 Square 中的 Android 开发工程师想出来的，所以我会在接下来的博文中将这个办法叫作“Square 大法”。\n\nSquare 大法的核心思想是：把应用组件类中的业务逻辑全部移除（例如：Activity，Fragment，Service，等等……），并且把业务逻辑转移到业务对象，而这些业务对象都是被依赖注入的纯 Java 对象，以及与 Android 无关的接口在此的 Android 特定实现。如果我们在开发应用的时候使用 Square 大法，那进行单元测试就简单多了。在这篇博文中，我会解释 Square 大法是如何帮助我们重构 UI 无关的应用组件（例如我们在之前的博文中讨论的 SessionCalendarService），并让对它进行单元测试变得容易许多。\n\n## 用 Square 大法重构 UI 无关的应用组件\n\n用 Square 大法重构类似于 Service，ContentProvider，BroadcastReceiver这样的 UI 无关的应用组件相对来说比较容易。我再说一次我们要做的事情吧：把在这些类中的业务逻辑移除，并把它们放到业务对象中。\n\n由于“业务逻辑”是很容易有歧义的词语，我来解释下我使用“业务逻辑”这个词时，它所代表的含义吧。当我提到“业务逻辑”，它的含义和维基百科上的解释是一致的：程序中根据现实世界中的规则用于决定数据将如何被创建，展示，储存和修改的那部分代码。那么现在我们就可以就“业务逻辑”这个词的含义达成共识了，那就来看看 Square 大法到底是啥吧。\n\n我们先来看看怎么用 Square 大法实现我在之前的博文中介绍的 SessionCalendarService 吧，具体代码如下：\n\n```java\n/**\n * Background {@link android.app.Service} that adds or removes session Calendar events through\n * the {@link CalendarContract} API available in Android 4.0 or above.\n */\npublic class SessionCalendarService extends IntentService {\n    private static final String TAG = makeLogTag(SessionCalendarService.class);\n \n    //...\n \n    public SessionCalendarService() {\n        super(TAG);\n    }\n \n    @Override\n    protected void onHandleIntent(Intent intent) {\n        final String action = intent.getAction();\n        Log.d(TAG, \"Received intent: \" + action);\n \n        final ContentResolver resolver = getContentResolver();\n \n        boolean isAddEvent = false;\n \n        if (ACTION_ADD_SESSION_CALENDAR.equals(action)) {\n            isAddEvent = true;\n \n        } else if (ACTION_REMOVE_SESSION_CALENDAR.equals(action)) {\n            isAddEvent = false;\n \n        } else if (ACTION_UPDATE_ALL_SESSIONS_CALENDAR.equals(action) &&\n                PrefUtils.shouldSyncCalendar(this)) {\n            try {\n                getContentResolver().applyBatch(CalendarContract.AUTHORITY,\n                        processAllSessionsCalendar(resolver, getCalendarId(intent)));\n                sendBroadcast(new Intent(\n                        SessionCalendarService.ACTION_UPDATE_ALL_SESSIONS_CALENDAR_COMPLETED));\n            } catch (RemoteException e) {\n                LOGE(TAG, \"Error adding all sessions to Google Calendar\", e);\n            } catch (OperationApplicationException e) {\n                LOGE(TAG, \"Error adding all sessions to Google Calendar\", e);\n            }\n \n        } else if (ACTION_CLEAR_ALL_SESSIONS_CALENDAR.equals(action)) {\n            try {\n                getContentResolver().applyBatch(CalendarContract.AUTHORITY,\n                        processClearAllSessions(resolver, getCalendarId(intent)));\n            } catch (RemoteException e) {\n                LOGE(TAG, \"Error clearing all sessions from Google Calendar\", e);\n            } catch (OperationApplicationException e) {\n                LOGE(TAG, \"Error clearing all sessions from Google Calendar\", e);\n            }\n \n        } else {\n            return;\n        }\n \n        final Uri uri = intent.getData();\n        final Bundle extras = intent.getExtras();\n        if (uri == null || extras == null || !PrefUtils.shouldSyncCalendar(this)) {\n            return;\n        }\n \n        try {\n            resolver.applyBatch(CalendarContract.AUTHORITY,\n                    processSessionCalendar(resolver, getCalendarId(intent), isAddEvent, uri,\n                            extras.getLong(EXTRA_SESSION_START),\n                            extras.getLong(EXTRA_SESSION_END),\n                            extras.getString(EXTRA_SESSION_TITLE),\n                            extras.getString(EXTRA_SESSION_ROOM)));\n        } catch (RemoteException e) {\n            LOGE(TAG, \"Error adding session to Google Calendar\", e);\n        } catch (OperationApplicationException e) {\n            LOGE(TAG, \"Error adding session to Google Calendar\", e);\n        }\n    }\n    \n    //...\n}\n```\n\n如你所见，SessionCalendarService 调用了将要在后面定义的 helper 方法。一旦我们将这些 helper 方法和类的字段声明也考虑进来，Service 类的代码就有400多行。要 hold 住这么庞大的类内发生的业务逻辑可不是什么简单的活，而且就像我们在上一篇博文中看到的那样，要在 SessionCalendarService 中进行单元测试简直是天方夜谭。\n\n那现在来看看用 Square 大法实现它代码会是怎样的。我再强调一次：Square 大法需要我们将 Android 类内的业务逻辑迁移到一个业务对象中。在这里，SessionCalendarService 所对应的业务对象则是 SessionCalendarUpdater，具体代码如下：\n\n```java\npublic class SessionCalendarUpdater {\n \n    //...\n \n    private SessionCalendarDatabase mSessionCalendarDatabase;\n    private SessionCalendarUserPreferences mSessionCalendarUserPreferences;\n \n    public SessionCalendarUpdater(SessionCalendarDatabase sessionCalendarDatabase,\n                                  SessionCalendarUserPreferences sessionCalendarUserPreferences) {\n \n        mSessionCalendarDatabase = sessionCalendarDatabase;\n        mSessionCalendarUserPreferences = sessionCalendarUserPreferences;\n    }\n \n    public void updateCalendar(CalendarUpdateRequest calendarUpdateRequest) {\n \n        boolean isAddEvent = false;\n \n        String action = calendarUpdateRequest.getAction();\n \n        long calendarId = calendarUpdateRequest.getCalendarId();\n \n        if (ACTION_ADD_SESSION_CALENDAR.equals(action)) {\n            isAddEvent = true;\n \n        } else if (ACTION_REMOVE_SESSION_CALENDAR.equals(action)) {\n            isAddEvent = false;\n \n        } else if (ACTION_UPDATE_ALL_SESSIONS_CALENDAR.equals(action)\n                && mSessionCalendarUserPreferences.shouldSyncCalendar()) {\n \n            try {\n \n                mSessionCalendarDatabase.updateAllSessions(calendarId);\n \n            } catch (RemoteException | OperationApplicationException e) {\n \n                LOGE(TAG, \"Error adding all sessions to Google Calendar\", e);\n            }\n \n        } else if (ACTION_CLEAR_ALL_SESSIONS_CALENDAR.equals(action)) {\n \n            try {\n \n                mSessionCalendarDatabase.clearAllSessions(calendarId);\n \n            } catch (RemoteException | OperationApplicationException e) {\n \n                LOGE(TAG, \"Error clearing all sessions from Google Calendar\", e);\n            }\n \n        } else {\n            return;\n        }\n \n \n        if (!shouldUpdateCalendarSession(calendarUpdateRequest, mSessionCalendarUserPreferences)) {\n            return;\n        }\n \n        try {\n \n            CalendarSession calendarSessionToUpdate = calendarUpdateRequest.getCalendarSessionToUpdate();\n \n            if (isAddEvent) {\n \n                mSessionCalendarDatabase.addCalendarSession(calendarId, calendarSessionToUpdate);\n            } else {\n \n                mSessionCalendarDatabase.removeCalendarSession(calendarId, calendarSessionToUpdate);\n            }\n \n        } catch (RemoteException | OperationApplicationException e) {\n            LOGE(TAG, \"Error adding session to Google Calendar\", e);\n        }\n    }\n \n    private boolean shouldUpdateCalendarSession(CalendarUpdateRequest calendarUpdateRequest, \n                                                SessionCalendarUserPreferences sessionCalendarUserPreferences) {\n \n        return calendarUpdateRequest.getCalendarSessionToUpdate() == null || !sessionCalendarUserPreferences.shouldSyncCalendar();\n    }\n}\n```\n\n我想要强调其中的一些要点：首先，需要注意，我们完全不需要用到任何新的关键字，因为业务对象的依赖都被注入了，它根本不会使用新的关键字，而这正是让类可单元测试的关键。其次，你会注意到类没有确切地依赖于 Android SDK，因为业务对象的依赖都是 Android 无关接口的 Android 特定实现，因此它不需要依赖于 Android SDK。\n\n那么这些依赖是怎么添加到　SessionCalendarUpdater　类中的呢？是通过　SessionCalendarService　类注入进去的：\n\n```java\n/**\n * Background {@link android.app.Service} that adds or removes session Calendar events through\n * the {@link CalendarContract} API available in Android 4.0 or above.\n */\npublic class SessionCalendarService extends IntentService {\n    private static final String TAG = makeLogTag(SessionCalendarService.class);\n \n    public SessionCalendarService() {\n        super(TAG);\n    }\n \n    @Override\n    protected void onHandleIntent(Intent intent) {\n        final String action = intent.getAction();\n        Log.d(TAG, \"Received intent: \" + action);\n \n        final ContentResolver resolver = getContentResolver();\n \n        Broadcaster broadcaster = new AndroidBroadcaster(this);\n \n        SessionCalendarDatabase sessionCalendarDatabase = new AndroidSessionCalendarDatabase(resolver,\n                                                                                             broadcaster);\n \n        SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);\n \n        SessionCalendarUserPreferences sessionCalendarUserPreferences = new AndroidSessionCalendarUserPreferences(defaultSharedPreferences);\n \n        SessionCalendarUpdater sessionCalendarUpdater\n                                    = new SessionCalendarUpdater(sessionCalendarDatabase,\n                                                                 sessionCalendarUserPreferences);\n \n        AccountNameRepository accountNameRepository = new AndroidAccountNameRepository(intent, this);\n \n        String accountName = accountNameRepository.getAccountName();\n \n        long calendarId = sessionCalendarDatabase.getCalendarId(accountName);\n        CalendarSession calendarSessionToUpdate = CalendarSession.fromIntent(intent);\n \n        CalendarUpdateRequest calendarUpdateRequest = new CalendarUpdateRequest(action, calendarId, calendarSessionToUpdate);\n \n        sessionCalendarUpdater.updateCalendar(calendarUpdateRequest);\n    }\n}\n```\n\n值得注意的是，修改后的 SessionCalendarService 到处都是新的关键字，但这些关键字在类中并不会引起什么问题。如果我们花几秒时间略读一下要点就会明白这一点：SessionCalendarService 类中已经没有任何业务逻辑，因此 SessionCalendarService 类不再需要进行单元测试。只要我们确定在 SessionCalendarService 调用的是  SessionCalendarUpdater 类中的 updateCalendar() 方法，在 SessionCalendarService 唯一可能出现的就是编译时错误。我们完全不需要为此实现测试单元，因为这是编译器的工作，与我们无关。\n\n由于我在前两篇博文中提到的相关原因，将我们的 Service 类拆分成这样会使对业务逻辑进行单元测试变得非常简单，例如我们对 SessionCalendarUpdater 类进行单元测试的代码可以写成下面的样子：\n\n```java\npublic class SessionCalendarUpdaterTests extends TestCase {\n \n    public void testShouldClearAllSessions() throws RemoteException, OperationApplicationException {\n \n        SessionCalendarDatabase sessionCalendarDatabase = mock(SessionCalendarDatabase.class);\n \n        SessionCalendarUserPreferences sessionCalendarUserPreferences = mock(SessionCalendarUserPreferences.class);\n \n \n        SessionCalendarUpdater sessionCalendarUpdater = new SessionCalendarUpdater(sessionCalendarDatabase,\n                                                                                   sessionCalendarUserPreferences);\n \n        CalendarUpdateRequest calendarUpdateRequest = new CalendarUpdateRequest(SessionCalendarUpdater.ACTION_CLEAR_ALL_SESSIONS_CALENDAR,\n                                                                                0,\n                                                                                null);\n        \n        sessionCalendarUpdater.updateCalendar(calendarUpdateRequest);\n \n        verify(sessionCalendarDatabase).clearAllSessions(0);\n    }\n}\n```\n\n## 结论\n\n为了能够进行单元测试，我认为修改后的代码变得更易读和更易维护了。可以肯定的是，我们还有许多办法能让代码变得更好，但在让代码能够进行单元测试的过程中，我想让修改后的代码尽可能与修改前风格相似，所以我没有进行其他修改。在下一篇博文中，我将会教大家如何使用 Square 大法重构应用的 UI 组件（例如：Fragment 和 Activity）。\n"
  },
  {
    "path": "issue-11/Code Review最佳实践.md",
    "content": "Code Review最佳实践\n====\n> * 原文链接 : [Code Review Best Practices](http://kevinlondon.com/2015/05/05/code-review-best-practices.html)\n* 原文作者 : [Kevin London](http://kevinlondon.com/about/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [ayyb1988](https://github.com/ayyb1988) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n\n在Wiredrive上，我们做了很多的Code Review。在此之前我从来没有做过，这对于我来说是一个全新的体验，下面来总结一下在Code Review中做的事情以及说说谈论Code Review的最好方式。\n\n简单的说，Code Review是开发者之间讨论修改代码来解决问题的过程。很多文章谈论了Code Review的诸多好处，包括知识共享，代码的质量，开发者的成长，却很少讨论审查什么、如何审查。\n\n###审查的内容\n\n####体系结构和代码设计\n\n* [单一职责原则：](http://en.wikipedia.org/wiki/Single_responsibility_principle)一个类有且只能一个职责。我通常使用这个原则去衡量，如果我们必须使用“和”来描述一个方法做的事情，这可能在抽象层上出了问题。\n\n* [开闭原则](http://en.wikipedia.org/wiki/Open/closed_principle)如果是面向对象的语言，对象对可扩展开放、对修改关闭。如果我们需要添加另外的内容会怎样？\n\n* 代码复用：根据[\"三振法\"](http://c2.com/cgi/wiki?ThreeStrikesAndYouRefactor),如果代码被复制一次，虽然如喜欢这种方式，但通常没什么问题。但如果再一次被复制，就应该通过提取公共的部分来重构它。\n\n* [换位考虑](http://robertheaton.com/2014/06/20/code-review-without-your-eyes/)，如果换位考虑，这行代码是否有问题？用这种模式是否可以发现代码中的问题。\n\n* 用更好的代码： 如果在一块混乱的代码做修改，添加几行代码也许更容易，但我建议更进一步，用比原来更好的代码。\n\n* 潜在的bugs：是否会引起的其他错误？循环是否以我们期望的方式终止？\n\n* 错误处理：错误确定被优雅的修改？会导致其他错误？如果这样，修改是否有用？\n\n* 效率： 如果代码中包含算法，这种算法是否是高效？ 例如，在字典中使用迭代，遍历一个期望的值，这是一种低效的方式。\n\n\n####代码风格\n* 方法名： 在计算机科学中，命名是一个难题。一个函数被命名为==get_message_queue_name==，但做的却是完全不同的事情，比如从输入内容中清除html，那么这是一个不准确的命名，并且可能会误导。\n\n* 值名：对于数据结构，==foo== or ==bar== 可能是无用的名字。相比==exception==， ==e==同样是无用的。如果需要(根据语言)尽可能详细，在重新查看代码时，那些见名知意的命名是更容易理解的。\n\n* 函数长度： 对于一个函数的长度，我的经验值是小于20行，如果一个函数在50行以上，最好把它分成更小的函数块。\n\n\n* 类的长度：我认为类的长度应该小于300行，最好在100内。把较长的类分离成独立的类，这样更容易理解类的功能。\n\n* 文件的长度： 对于Python，一个文件最多1000行代码。任何高于此的文件应该把它分离成更小更内聚,看一下是否违背的“单一职责” 原则。\n* 文档：对于复杂的函数来说，参数个数可能较多，在文档中需要指出每个参数的用处，除了那些显而易见的。\n\n* 注释代码： 移除任何注释代码行。\n* 函数参数个数：不要太多， 一般不要超过3个。。\n* 可读性： 代码是否容易理解？在查看代码时要不断的停下来分析它？\n\n####测试\n* 测试的范围：我喜欢测试新功能。测试是否全面？是否涵盖了故障的情况【比如：网络，信号等，译者注】？是否容易使用？是否稳定？大多的测试？性能的快慢？\n* 合乎规范的测试：当复查测试时，确保我们用适当的方式。换句话说，当我们在一个较低水平测试却要求期望的功能？Gary Bernhardt建议95％的单元测试，5％的集成测试。特别是在Django项目中，在较高的测试水平上，很容易发现意外bug，创建一个详细的测试用例，认真仔细也是很重要的。\n\n####审查代码\n在提交代码之前，我经常用git添加改变的文件/文件夹,然后通过git diff 来查看做了哪些修改。通常，我会关注如下几点：\n* 是否有注释？\n* 变量名是否见名知意？\n* ...等上面提到的\n\n和著名的橡皮鸭调试法（Rubber Duck Debugging）一样，每次提交前整体把自己的代码过一遍非常有帮助，尤其是看看有没有犯低级错误。\n\n\n####如何进行Code Review\n当Code Review时，会遇到不少问题，我也学会了如何处理，下面是一些方法：\n\n* 提问： 这个函数是如何生效的？如果需求变更，应该做什么改变？怎么更容易维护？\n* 表扬/奖励良好的做法：Code Review重要的一点是奖励开发者的成长和努力。得到别人的肯定是一件很不错的事情，我尽可能多的给人积极的评论。\n* 当面讨论代替评论。 大部分情况下小组内的同事是坐在一起的，当面的 code review是非常有效的。\n* 说明理由 ：是否还有跟好的方式，证明为什么这样做是好的。\n\n####心态上\n* 作为一个Developer , 不仅要Deliver working code, 还要Deliver maintable code.\n* 必要时进行重构，随着项目的迭代，在计划新增功能的同时，开发要主动计划重构的工作项。\n* 开放的心态，虚心接受大家的Review Comments。\n\n####参考\n一些关于clean code的书籍，如下：\n* [Clean Code](http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)\n* [Refactoring](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672)\n* [All the Small Things by Sandi Metz](https://www.youtube.com/watch?v=8bZh5LMaSmE&index=1&list=LLlt4ZSW8NUcXLWiB3NMnK_w)\n* [How to Design a Good API and Why it Matters](https://www.youtube.com/watch?v=aAb7hSCtvGw&list=LLlt4ZSW8NUcXLWiB3NMnK_w&index=48)\n* [Discussion on Hacker News](https://news.ycombinator.com/item?id=9517892)\n\n##译者注\n#####一. 参考了 http://jimhuang.cn/?p=59\n\n#####二. 国内阿里的[陈皓](http://coolshell.cn/articles/author/haoel)写的关于codereview的文章，也很有见底，推荐大家看看\n######1.[Code Review中的几个提示](http://coolshell.cn/articles/1302.html)\n* 先Review设计实现思路，然后Review设计模式，接着Review成形的骨干代码，最后Review完成的代码，如果程序复杂的话，需要拆成几个单元或模块分别Review\n* Code Review不要太正式，而且要短\n* 学会享受Code Reivew\n\n######2.[从Code Review 谈如何做技术](http://coolshell.cn/articles/11432.html/comment-page-1#comments)\n\n#####三. Code Review 工具\n[Review Board](https://github.com/reviewboard/reviewboard)\n\n#####四.\n在Code Review时，要在 **意识** **方法** **心态** **习惯** 这几个方面上下功夫，坚持code review，相信我们会在各方面有很大的提升。\n"
  },
  {
    "path": "issue-11/readme.md",
    "content": "# 开发技术前线 第10期\n\n| 文章名称 |   译者  | \n|---------|--------|\n| [Android-Espresso测试框架介绍](Android-Espresso测试框架介绍.md)  | [zhengxiaopeng](https://github.com/zhengxiaopeng)      |\n| [Android 进行单元测试难在哪-part3](Android进行单元测试难在哪-part3.md)  | [chaossss](https://github.com/chaossss)|\n| [Code Review最佳实践](Code Review最佳实践.md)  | [ayyb1988](https://github.com/ayyb1988)      |\n| [听FaceBook工程师讲Custom-ViewGroups](听FaceBook工程师讲Custom-ViewGroups.md)  | [objectlife](https://github.com/objectlife)|\n| [详解Dagger2](详解Dagger2.md)  | [xianjiajun](https://github.com/xianjiajun)     |\n"
  },
  {
    "path": "issue-11/听FaceBook工程师讲Custom-ViewGroups.md",
    "content": "#听FackBook工程师讲*Custom ViewGroups*\n\n---\n\n> * 原文链接 : [Custom ViewGroups](https://sriramramani.wordpress.com/2015/05/06/custom-viewgroups/)\n* 原文作者 : [Sriram Ramani](https://sriramramani.wordpress.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [objectlife](https://github.com/objectlife) \n* 校对者: [xianjiajun](https://github.com/xianjiajun)  \n* 状态 : 完成 \n\nAndroid提供了几个ViewGroups如LinearLayout, RelativeLayout, FrameLayout来固定child Views的位置。在这些普通的ViewGroups中有多种使用选择。\n例如：LinearLayout几乎支持HTML Flexbox的所有特性(除了包装)。在view之间你可以选择是否显示分割线(dividers),并且基于最大的child测量所有的children。RelativeLayout是一种限制性的解决方案。这些layouts都已经足够好了，但是当你的UI非常复杂的时候它们还能很好的解决么？\n\n![](https://sriramramani.files.wordpress.com/2015/05/custom-view-group.png?w=1594&h=204)\n\n>ViewGroup with a ProfilePhoto, Title, Subtitle and Menu button.\n\n上面的这种布局在Facebook app中是非常常见的。有头像、其它的view垂直摆在它的右侧、还有一个可选操作的view在最右边。这个布局可以通过使用LinearLayout嵌套或者一个RelativeLayout这样的普通ViewGroup实现。我们看一下当分别使用这两种布局的情况下在measure时会发生什么。\n\n使用LinearLayout完成布局的示例\n\n```java\n<LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n \n    <ProfilePhoto\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"/>\n \n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:orientation=\"vertical\">\n \n        <Title\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"/>\n \n        <Subtitle\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"/>\n \n    </LinearLayout>\n \n    <Menu\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"/>\n \n</LinearLayout>\n```\n在Nexus 5设备上measure发生时的情况如下\n\n```\n> LinearLayout [horizontal]       [w: 1080  exactly,       h: 1557  exactly    ]\n    > ProfilePhoto                [w: 120   exactly,       h: 120   exactly    ]\n    > LinearLayout [vertical]     [w: 0     unspecified,   h: 0     unspecified]\n        > Title                   [w: 0     unspecified,   h: 0     unspecified]\n        > Subtitle                [w: 0     unspecified,   h: 0     unspecified]\n        > Title                   [w: 222   exactly,       h: 57    exactly    ]\n        > Subtitle                [w: 222   exactly,       h: 57    exactly    ]\n    > Menu                        [w: 60    exactly,       h: 60    exactly    ]\n    > LinearLayout [vertical]     [w: 900   exactly,       h: 1557  at_most    ]\n        > Title                   [w: 900   exactly,       h: 1557  at_most    ]\n        > Subtitle                [w: 900   exactly,       h: 1500  at_most    ]       \n```\n\n\nProfilePhoto和Menu只被测量了一次，因为它们有明确的宽高值。垂直的LinearLayout被测量了两次。第一次的时候，父LinearLayout要求以UNSPECIFIED spec的方式来测量。导致了垂直的LinearLayout也以这种方式测量它的子view.此时它在它们返回值的基础上以EXACTLY spec的方式测量它的子view，但是它还没有结束。一旦在测量ProfilePhoto和Menu之后，父布局知道可用于垂直的LinearLayout的尺寸大小。以AT_MOST height对Title 和 Subtitle测量之后导致了第二次传值。显然，每一个TextView (Title and Subtitle)被测量3次。第二次传值创建或者废弃Layouts，这些操作是昂贵的。如果想ViewGroup发挥更好的性能，首要的工作就是免去对TextViews的测量传值工作。\n\n\n使用RelativeLayout效果会不会好一些?\n\n```java\n<RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n \n    <ProfilePhoto\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentLeft=\"true\"/>\n \n    <Menu\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentRight=\"true\"/>\n \n    <Title\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_toRightOf=\"@id/profile_photo\"\n        android:layout_toLeftOf=\"@id/menu\"/>\n \n    <Subtitle\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title\"\n        android:layout_toRightOf=\"@id/profile_photo\"\n        android:layout_toLeftOf=\"@id/menu\"/>\n \n</RelativeLayout>\n```\n\n测量情况如下：\n\n```\n> RelativeLayout                  [w: 1080  exactly,   h: 1557  exactly]\n    > Menu                        [w: 60    exactly,   h: 1557  at_most]\n    > ProfilePhoto                [w: 120   exactly,   h: 1557  at_most]\n    > Title                       [w: 900   exactly,   h: 1557  at_most]\n    > Subtitle                    [w: 900   exactly,   h: 1557  at_most]\n    > Title                       [w: 900   exactly,   h: 1557  at_most]\n    > Subtitle                    [w: 900   exactly,   h: 1500  at_most]\n    > Menu                        [w: 60    exactly,   h: 60    exactly]\n    > ProfilePhoto                [w: 120   exactly,   h: 120   exactly]\n```\n\n\n正如先前提到的，RelativeLayout是通过solving constraints(分解约束。译者认为就是一层一层的测量)进行测量，上面的布局中ProfilePhoto和Menu没有依赖其它的参照物(siblings)，因此它们先被测量(with an AT_MOST height).这时Title(2个约束)和Subtitle(3个约束)才会被测量。此时所有view明确了自己想要的尺寸大小。RelativeLayout使用这些信息第二次传值给Title, Subtitle, Menu和ProfilePhoto。再重复一遍，每个view被测量了两次，因此这种方案稍佳。如果你和上面的LinearLayout例子相比较一下，最后用于测量所有leaf Views所使用的MeasureSpec是相同的-因此最后的输出结果是一样的。\n\n\n怎么样才能免去对子view的测量传值呢？自定义一个ViewGroup是不是会有帮助？让我们分析一下这个布局。Title 和 Subtitle 总是在ProfilePhoto的左侧在Menu按钮的右侧。如果我们手工解决这个问题，需要计算出ProfilePhoto和Menu按钮的尺寸，并且使用剩下的尺寸再来计算Title 和 Subtitle。这时对每个view只进行一次测量传值。我们叫这种布局为ProfilePhotoLayout。\n\n\n```java\npublic class ProfilePhotoLayout extends ViewGroup {\n \n    private ProfilePhoto mProfilePhoto;\n    private Menu mMenu;\n    private Title mTitle;\n    private Subtitle mSubtitle;\n \n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        // 1. Setup initial constraints.\n        int widthConstraints = getPaddingLeft() + getPaddingRight();\n        int heightContraints = getPaddingTop() + getPaddingBottom();\n        int width = 0;\n        int height = 0;\n \n        // 2. Measure the ProfilePhoto\n        measureChildWithMargins(\n            mProfilePhoto,\n            widthMeasureSpec,\n            widthConstraints,\n            heightMeasureSpec,\n            heightConstraints);\n \n        // 3. Update the contraints.\n        widthConstraints += mProfilePhoto.getMeasuredWidth();\n        width += mProfilePhoto.getMeasuredWidth();\n        height = Math.max(mProfilePhoto.getMeasuredHeight(), height);\n \n        // 4. Measure the Menu.\n        measureChildWithMargins(\n            mMenu,\n            widthMeasureSpec,\n            widthConstraints,\n            heightMeasureSpec,\n            heightConstraints);\n \n        // 5. Update the constraints.\n        widthConstraints += mMenu.getMeasuredWidth();\n        width += mMenu.getMeasuredWidth();\n        height = Math.max(mMenu.getMeasuredHeight(), height);\n \n        // 6. Prepare the vertical MeasureSpec.\n        int verticalWidthMeasureSpec = MeasureSpec.makeMeasureSpec(\n            MeasureSpec.getSize(widthMeasureSpec) - widthConstraints,\n            MeasureSpec.getMode(widthMeasureSpec));\n \n        int verticalHeightMeasureSpec = MeasureSpec.makeMeasureSpec(\n            MeasureSpec.getSize(heightMeasureSpec) - heightConstraints,\n            MeasureSpec.getMode(heightMeasureSpec));\n \n        // 7. Measure the Title.\n        measureChildWithMargins(\n            mTitle,\n            verticalWidthMeasureSpec,\n            0,\n            verticalHeightMeasureSpec,\n            0);\n \n        // 8. Measure the Subtitle.\n        measureChildWithMargins(\n            mSubtitle,\n            verticalWidthMeasureSpec,\n            0,\n            verticalHeightMeasureSpec,\n            mTitle.getMeasuredHeight());\n \n        // 9. Update the sizes.\n        width += Math.max(mTitle.getMeasuredWidth(), mSubtitle.getMeasuredWidth());\n        height = Math.max(mTitle.getMeasuredHeight() + mSubtitle.getMeasuredHeight(), height);\n \n        // 10. Set the dimension for this ViewGroup.\n        setMeasuredDimension(\n            resolveSize(width, widthMeasureSpec),\n            resolveSize(height, heightMeasureSpec));\n    }\n \n    @Override\n    protected void measureChildWithMargins(\n        View child,\n        int parentWidthMeasureSpec,\n        int widthUsed,\n        int parentHeightMeasureSpec,\n        int heightUsed) {\n        MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();\n \n        int childWidthMeasureSpec = getChildMeasureSpec(\n            parentWidthMeasureSpec,\n            widthUsed + lp.leftMargin + lp.rightMargin,\n            lp.width);\n \n        int childHeightMeasureSpec = getChildMeasureSpec(\n            parentHeightMeasureSpec,\n            heightUsed + lp.topMargin + lp.bottomMargin,\n            lp.height);\n \n        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);\n    }\n}\n```\n\n我们来分析一下代码。我们从已知的约束条件开始 — 所有边的内边距，另外还需要考虑的约束是使用固定值的控件的高和宽。Android提供了一个帮助方法-measureChildWithMargins()用于测量ViewGroup内的子view.然而它总是添加padding作为约束条件的一部分。因此我们复写这个方法自己来管理这些约束条件。从测量ProfilePhoto开始，测量完成后更新一下constraints。对menu按钮的测量亦是如此。\n现在还剩下Title和Subtitle的宽度没有测量。Android还提供了另外一个帮助方法-makeMeasureSpec()，用于构造MeasureSpec,传入相应的size和mode返回一个MeasureSpec。接下来我们传入Title 和 Subtitle可用的width 和 height及相应的MeasureSpecs来测量Title 和 Subtitle。最后更新一下ViewGroup的尺寸。在这一步可以明确每个view都只被测量一次。\n\n\n```\n> ProfilePhotoLayout              [w: 1080  exactly,   h: 1557  exactly]\n    > ProfilePhoto                [w: 120   exactly,   h: 120   exactly]\n    > Menu                        [w: 60    exactly,   h: 60    exactly]\n    > Title                       [w: 900   exactly,   h: 1557  at_most]\n    > Subtitle                    [w: 900   exactly,   h: 1500  at_most]\n```\n\n\n性能上是不是提升了？Facebook app中你看见的大多数布局都使用了这种布局，并且经证明确实提高了性能。我把没有提到的onLayout()方法留给读者作为练习。\n\n\n你喜欢解决这种Android UI 工程问题么? Facebook在这方面缺少专业的人才。\n\n[点击申请](https://www.facebook.com/careers/department?req=a0I1200000G49VNEAZ&dept=engineering&q=UI%20Engineer)\n\n"
  },
  {
    "path": "issue-11/详解Dagger2.md",
    "content": "Dagger2\n---\n\n> * ԭ : [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/)\n* ԭ : [Fernando Cejas](http://fernandocejas.com/)\n* [ĳ :  ǰ www.devtf.cn](http://www.devtf.cn)\n*  : [xianjiajun](https://github.com/xianjiajun) \n* У: [chaossss](https://github.com/chaossss)  \n* ״̬ :  \n\n##Ϊʲôʹע\nҪ֪ںܳһʱﶼÿƷתԭ涨ӦóȡڳʱͼĽͨĶ󽻻ʵĶ̡̬ʹע뼼߷λʱ󶨡\n\nʹעԴºô\n\n* עö֮⡣\n* ΪһϵĵطʼԵע󷽷ʱֻҪ޸ĶʵַôĴ⡣\n* ע뵽һУǿעЩģʵ֣ʹòԸӼ򵥡\n\nԿܹʵķΧһǳ顣ҵĹ۵㣬appежЭ߶Ӧ֪йʵڵκ飬ЩӦǵעܹġ\n\n![p1](http://fernandocejas.com/wp-content/uploads/2015/04/dependency_inversion1.png)\n\n#ʲôJSR-330\nΪ̶ȵߴĸԡԺάԣjavaעΪעеʹöһע⣨ͽӿڣ׼Dagger1Dagger2Guiceǻױ׼ȶԺͱ׼ע뷽\n\n#Dagger1\n汾ƪµص㣬ֻǼԵ˵һ¡Dagger1˺ܶĹף˵AndroidеעܡSquare˾ܵGuiceġ\n\nص㣺\n\n* ע㣺ͨinjected\n* ְ󶨷ͨprovided\n* modulesʵĳֹܵİ󶨼\n* ͼ ʵһΧmodules\n\nDagger1ڱʱʵа󶨣Ҳõ˷ơ䲻ʵģͼĹɡDaggerеʱȥǷһжʹõʱḶһЩۣżЧ͵ѡ\n\n#Dagger2\nDagger2Dagger1ķ֧ɹȸ蹫˾ֿĿǰİ汾2.0Dagger2ܵ[AutoValueĿ](https://github.com/google/auto)\nտʼDagger2Ļ˼ǣɺдĴϴﵽеĲṩĴ붼дӡ\n\nǽDagger21Ƚϣںܶ෽涼ǳƣҲкҪ£\n\n* Ҳûʹ÷䣺ͼ֤úԤöڱʱִС\n* ׵ԺͿɸ٣ȫصṩʹĶջ\n* õܣȸ13%Ĵ\n* ʹǲͬԼдĴһ\n\nȻЩܰص㶼ҪһۣǾȱԣ磺Dagger2û÷ûж̬ơ\n\n#о\nҪ˽Dagger2ͱҪ֪עĻеÿһ\n\n* @Inject: ͨҪĵطʹע⡣仰˵DaggerֶҪע롣Daggerͻṹһʵǵ\n\n* @Module: ModulesķרṩǶһ࣬@Moduleע⣬Daggerڹʵʱ򣬾֪ȥҵҪmodulesһҪΪһ𣨱˵ǵappпжһmodules\n\n* @Provide: modulesУǶķע⣬ԴDaggerҪṩЩ\n\n* @Component: ComponentsӸ˵һעҲ˵@Inject@ModuleҪþ֡Componentsṩж˵͵ʵ磺Ǳ@ComponentעһӿȻге@Modulesɸȱʧκһ鶼ڱʱ򱨴еͨmodules֪ķΧ\n\n* @Scope: ScopesǷǳãDagger2ͨԶע޶ע򡣺ʾһӣһǳǿص㣬Ϊǰ˵һûҪÿȥ˽ιǵʵscopeУԶ@PerActivityעһ࣬ʱͺactivityһ˵ǿԶзΧ(@PerFragment, @PerUser, ȵ)\n\n* Qualifier: ͲԼһʱǾͿʹעʾ磺AndroidУǻҪͬ͵contextǾͿԶqualifierע⡰@ForApplication͡@ForActivityעһcontextʱǾͿԸDaggerҪ͵context\n\n#ϻϴ\nǰѾ˺ܶˣԽǿʹDagger2ȻҪǵbuild.gradleļã\n```java\napply plugin: 'com.neenbedankt.android-apt'\n \nbuildscript {\n  repositories {\n    jcenter()\n  }\n  dependencies {\n    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'\n  }\n}\n \nandroid {\n  ...\n}\n \ndependencies {\n  apt 'com.google.dagger:dagger-compiler:2.0'\n  compile 'com.google.dagger:dagger:2.0'\n  \n  ...\n}\n```\n\nʾ˱п⣬бزٵaptûdaggerܲرAndroid studioС\n\n#\nǰдһƪAndroidʵbobܹ£ǿҽȥһ£֮㽫иõ⡣ԹǰķУṩʱ򣬻⣬£ע\n```java\n  @Override void initializePresenter() {\n    // All this dependency initialization could have been avoided by using a\n    // dependency injection framework. But in this case this is used this way for\n    // LEARNING EXAMPLE PURPOSE.\n    ThreadExecutor threadExecutor = JobExecutor.getInstance();\n    PostExecutionThread postExecutionThread = UIThread.getInstance();\n\n    JsonSerializer userCacheSerializer = new JsonSerializer();\n    UserCache userCache = UserCacheImpl.getInstance(getActivity(), userCacheSerializer,\n        FileManager.getInstance(), threadExecutor);\n    UserDataStoreFactory userDataStoreFactory =\n        new UserDataStoreFactory(this.getContext(), userCache);\n    UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper();\n    UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory,\n        userEntityDataMapper);\n\n    GetUserDetailsUseCase getUserDetailsUseCase = new GetUserDetailsUseCaseImpl(userRepository,\n        threadExecutor, postExecutionThread);\n    UserModelDataMapper userModelDataMapper = new UserModelDataMapper();\n\n    this.userDetailsPresenter =\n        new UserDetailsPresenter(this, getUserDetailsUseCase, userModelDataMapper);\n  }\n```\n\nԿİ취ʹעܡҪô룺಻漰Ĵṩ\nǸôأȻʹDagger2ȿṹͼ\n![pic2](http://fernandocejas.com/wp-content/uploads/2015/04/composed_dagger_graph1.png)\n\nǻֽͼ͸ֻд롣\n\nApplication Component: ڸApplicationһע뵽AndroidApplicationBaseActivityС\n\n```java\n@Singleton // Constraints this component to one-per-application or unscoped bindings.\n@Component(modules = ApplicationModule.class)\npublic interface ApplicationComponent {\n  void inject(BaseActivity baseActivity);\n\n  //Exposed to sub-graphs.\n  Context context();\n  ThreadExecutor threadExecutor();\n  PostExecutionThread postExecutionThread();\n  UserRepository userRepository();\n}\n```\n\nΪʹ@Singletonע⣬ʹ䱣֤ΨһԡҲΪʲôҪcontextԱ¶ȥDaggercomponentsҪʣ㲻modulesͱ¶ôֻʾʹǡУҰЩԪر¶ͼɾʱͻᱨ\n\nApplication Module: moduleṩӦõдĶҲΪʲô@ProvideעķҪ@Singleton޶\n\n```java\n@Module\npublic class ApplicationModule {\n  private final AndroidApplication application;\n\n  public ApplicationModule(AndroidApplication application) {\n    this.application = application;\n  }\n\n  @Provides @Singleton Context provideApplicationContext() {\n    return this.application;\n  }\n\n  @Provides @Singleton Navigator provideNavigator() {\n    return new Navigator();\n  }\n\n  @Provides @Singleton ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) {\n    return jobExecutor;\n  }\n\n  @Provides @Singleton PostExecutionThread providePostExecutionThread(UIThread uiThread) {\n    return uiThread;\n  }\n\n  @Provides @Singleton UserCache provideUserCache(UserCacheImpl userCache) {\n    return userCache;\n  }\n\n  @Provides @Singleton UserRepository provideUserRepository(UserDataRepository userDataRepository) {\n    return userDataRepository;\n  }\n}\n```\n\nActivity Component: ڸActivityһ\n\n```java\n@PerActivity\n@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)\npublic interface ActivityComponent {\n  //Exposed to sub-graphs.\n  Activity activity();\n}\n```\n\n@PerActivityһԶķΧע⣬󱻼¼ȷУȻЩӦѭactivityڡһܺõϰҽǶһ£ºô\n\n* ע󵽹췽Ҫactivity\n* һper-activityϵĵʹá\n* ֻactivityʹʹȫֵĶͼ\n\n´룺\n```java\n@Scope\n@Retention(RUNTIME)\npublic @interface PerActivity {}\n```\n\nActivity Module: ڶͼУmoduleactivity¶ࡣfragmentʹactivitycontext\n\n```java\n@Module\npublic class ActivityModule {\n  private final Activity activity;\n\n  public ActivityModule(Activity activity) {\n    this.activity = activity;\n  }\n\n  @Provides @PerActivity Activity activity() {\n    return this.activity;\n  }\n}\n```\nUser Component: ̳ActivityComponent@PerActivityע⡣ͨעûصfragmentʹáΪActivityModuleactivity¶ͼˣκҪһactivitycontextʱDaggerṩע룬ûҪmodulesжˡ\n\n```java\n@PerActivity\n@Component(dependencies = ApplicationComponent.class, modules = {ActivityModule.class, UserModule.class})\npublic interface UserComponent extends ActivityComponent {\n  void inject(UserListFragment userListFragment);\n  void inject(UserDetailsFragment userDetailsFragment);\n}\n```\nUser Module: ṩûصʵǵӣṩû\n\n```java\n@Module\npublic class UserModule {\n  @Provides @PerActivity GetUserListUseCase provideGetUserListUseCase(GetUserListUseCaseImpl getUserListUseCase) {\n    return getUserListUseCase;\n  }\n\n  @Provides @PerActivity GetUserDetailsUseCase provideGetUserDetailsUseCase(GetUserDetailsUseCaseImpl getUserDetailsUseCase) {\n    return getUserDetailsUseCase;\n  }\n}\n```\n#ϵһ\nѾʵעͼҸע룿Ҫ֪Daggerһѡע\n\n 1. 췽ע룺Ĺ췽ǰע@Inject\n 2. Աע룺ĳԱ˽Уǰע@Inject\n 3. ע룺ںǰע@Inject\n\n˳DaggerʹõģΪеĹУܻһЩֵǿָ룬Ҳζڶ󴴽ʱܻûгʼɡAndroidactivityfragmentʹóԱעᾭΪûǵĹ췽ʹá\n\nصǵУһBaseActivityעһԱУעһNavigator࣬Ӧиࡣ\n\n```java\npublic abstract class BaseActivity extends Activity {\n\n  @Inject Navigator navigator;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    this.getApplicationComponent().inject(this);\n  }\n\n  protected ApplicationComponent getApplicationComponent() {\n    return ((AndroidApplication)getApplication()).getApplicationComponent();\n  }\n\n  protected ActivityModule getActivityModule() {\n    return new ActivityModule(this);\n  }\n}\n```\n\nNavigatorǳԱעģApplicationModule@ProvideעʾṩġǳʼcomponentȻinject()עԱͨActivityonCreate()еgetApplicationComponent()ЩgetApplicationComponent()Ϊ˸ԣҪΪ˻ȡʵApplicationComponent\n\nFragmentpresenterҲͬ飬Ļȡһ㲻һΪʹõper-activityΧ޶componentע뵽UserDetailsFragmentеUserComponentʵפUserDetailsActivityеġ\n\n```java\nprivate UserComponent userComponent;\n```\nǱactivityonCreate()ķʽʼ\n```java\nprivate void initializeInjector() {\n  this.userComponent = DaggerUserComponent.builder()\n      .applicationComponent(getApplicationComponent())\n      .activityModule(getActivityModule())\n      .build();\n}\n```\nDaggerᴦǵע⣬ΪcomponentsʵֲϡDaggerǰ׺ΪһϵcomponentڹʱǱеĴȥcomponentsmodulesǵcomponentѾ׼ˣΪ˿fragmentдһȡ\n\n```java\n@Override public UserComponent getComponent() {\n  return userComponent;\n}\n```\nڿgetȡcomponentȻinject()FragmentΪȥ˰UserDetailsFragment\n\n```java\n@Override public void onActivityCreated(Bundle savedInstanceState) {\n  super.onActivityCreated(savedInstanceState);\n  this.getComponent.inject(this);\n}\n```\n[Ҫ鿴ӣȥҵgithub](https://github.com/android10/Android-CleanArchitecture).һЩطع˵ģҿԸһҪ˼루Թٷӣǣ\n\n```java\npublic interface HasComponent<C> {\n  C getComponent();\n}\n```\nˣͻˣfragmentԻȡʹcomponentactivity\n```java\n@SuppressWarnings(\"unchecked\")\nprotected <C> C getComponent(Class<C> componentType) {\n  return componentType.cast(((HasComponent<C>)getActivity()).getComponent());\n}\n```\nʹǿתͻ˲ܻȡõcomponentٺܿͻʧܡκ뷨ܹõؽ⣬ҡ\n#Dagger2ɵĴ\n˽DaggerҪ֮ڲ졣Ϊ˾˵ǻNavigator࣬δעġǿһǵDaggerApplicationComponent\n```java\n@Generated(\"dagger.internal.codegen.ComponentProcessor\")\npublic final class DaggerApplicationComponent implements ApplicationComponent {\n  private Provider<Navigator> provideNavigatorProvider;\n  private MembersInjector<BaseActivity> baseActivityMembersInjector;\n\n  private DaggerApplicationComponent(Builder builder) {  \n    assert builder != null;\n    initialize(builder);\n  }\n\n  public static Builder builder() {  \n    return new Builder();\n  }\n\n  private void initialize(final Builder builder) {  \n    this.provideNavigatorProvider = ScopedProvider.create(ApplicationModule_ProvideNavigatorFactory.create(builder.applicationModule));\n    this.baseActivityMembersInjector = BaseActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideNavigatorProvider);\n  }\n\n  @Override\n  public void inject(BaseActivity baseActivity) {  \n    baseActivityMembersInjector.injectMembers(baseActivity);\n  }\n\n  public static final class Builder {\n    private ApplicationModule applicationModule;\n  \n    private Builder() {  \n    }\n  \n    public ApplicationComponent build() {  \n      if (applicationModule == null) {\n        throw new IllegalStateException(\"applicationModule must be set\");\n      }\n      return new DaggerApplicationComponent(this);\n    }\n  \n    public Builder applicationModule(ApplicationModule applicationModule) {  \n      if (applicationModule == null) {\n        throw new NullPointerException(\"applicationModule\");\n      }\n      this.applicationModule = applicationModule;\n      return this;\n    }\n  }\n}\n```\nصҪע⡣һҪע뵽activityУԻõһעȳԱעDaggerɵBaseActivity_MembersInjector\n```java\n@Generated(\"dagger.internal.codegen.ComponentProcessor\")\npublic final class BaseActivity_MembersInjector implements MembersInjector<BaseActivity> {\n  private final MembersInjector<Activity> supertypeInjector;\n  private final Provider<Navigator> navigatorProvider;\n\n  public BaseActivity_MembersInjector(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) {  \n    assert supertypeInjector != null;\n    this.supertypeInjector = supertypeInjector;\n    assert navigatorProvider != null;\n    this.navigatorProvider = navigatorProvider;\n  }\n\n  @Override\n  public void injectMembers(BaseActivity instance) {  \n    if (instance == null) {\n      throw new NullPointerException(\"Cannot inject members into a null reference\");\n    }\n    supertypeInjector.injectMembers(instance);\n    instance.navigator = navigatorProvider.get();\n  }\n\n  public static MembersInjector<BaseActivity> create(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) {  \n      return new BaseActivity_MembersInjector(supertypeInjector, navigatorProvider);\n  }\n}\n```\nעһ㶼ΪactivityעԱṩֻҪһinject()ͿԻȡҪֶκ\n\nڶص㣺ǵDaggerApplicationComponent࣬һProviderһṩʵĽӿڣǱScopedProviderģԼ¼ʵķΧ\n\nDaggerΪǵNavigatorһApplicationModule_ProvideNavigatorFactoryĹԴᵽķΧȻõΧڵʵ\n\n```java\n@Generated(\"dagger.internal.codegen.ComponentProcessor\")\npublic final class ApplicationModule_ProvideNavigatorFactory implements Factory<Navigator> {\n  private final ApplicationModule module;\n\n  public ApplicationModule_ProvideNavigatorFactory(ApplicationModule module) {  \n    assert module != null;\n    this.module = module;\n  }\n\n  @Override\n  public Navigator get() {  \n    Navigator provided = module.provideNavigator();\n    if (provided == null) {\n      throw new NullPointerException(\"Cannot return null from a non-@Nullable @Provides method\");\n    }\n    return provided;\n  }\n\n  public static Factory<Navigator> create(ApplicationModule module) {  \n    return new ApplicationModule_ProvideNavigatorFactory(module);\n  }\n}\n```\n\nǳ򵥣ǵApplicationModule@ProvideNavigatorࡣ\n\n֮Ĵ뿴óģҷǳ⣬ڵԡ໹кܶȥ̽ǿͨȥDagger󶨵ġ\n![pic3](http://fernandocejas.com/wp-content/uploads/2015/04/debugging_dagger.png)\n\n#Դ:\n: https://github.com/android10/Android-CleanArchitecture\n#:\n[Architecting AndroidThe clean way?](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)\n[Dagger 2, A New Type of Dependency Injection.](https://www.youtube.com/watch?v=oK_XtfXPkqw)\n[Dependency Injection with Dagger 2.](https://speakerdeck.com/jakewharton/dependency-injection-with-dagger-2-devoxx-2014)\n[Dagger 2 has Components.](https://publicobject.com/2014/11/15/dagger-2-has-components/)\n[Dagger 2 Official Documentation.](http://google.github.io/dagger/)"
  },
  {
    "path": "issue-12/Android上MVP的介绍.md",
    "content": "MVP在Android平台上的应用\n---\n\n> * 原文链接 : [Introduction to Model-View-Presenter on Android](http://konmik.github.io/introduction-to-model-view-presenter-on-android.html)\n* 原文作者 : [konmik](http://konmik.github.io/)\n* 译文出自 : [  其他  http://konmik.github.io/introduction-to-model-view-presenter-on-android.html](http://konmik.github.io/introduction-to-model-view-presenter-on-android.html)\n* 译者 : [MiJack](https://github.com/MiJack)  \n* 校对者: [MiJack](https://github.com/MiJack)  \n* 状态 : 校对完成\n\n=======\n\n\n\n#Android平台上MVP的介绍\n\n\n\n这篇文章向你介绍Android平台上的MVP模式，从一个简浅的例子开始实践之路。文章也会介绍一个一个库让你在Android平台上轻松的实现MVP\n\n\n\n##简单吗？我怎么才能从中受益？\n\n\n###什么是MVP？\n\n\n\n\n- **View**层主要是用于展示数据并对用户行为做出反馈。在Android平台上，他可以对应为Activity, Fragment,View或者对话框。\n- **Model**是数据访问层，往往是数据库接口或者服务器的API。\n- **Presenter**层可以想View层提供来自数据访问层的数据，除此以外，他也会处理一些后台事务。\n\n\n在Android平台上，MVP可以将后台事务从Activity/View/Fragment中分离出来，让它们独立于大部分生命周期事件。这样，一个应用将会变得简单， 整个应用可靠性可以提高10倍，应用的代码将会变短, 代码的可维护性提高，开发者也为此感到高兴。\n\n\n###Android为什么需要MVP\n\n\n####理由1：尽量简单\n\n如果你还有读过这篇文章，请阅读它：[Kiss原则](https://people.apache.org/~fhanik/kiss.html)（Keep It Stupid Simple）\n\n\n- 大部分的安卓应用只使用View-Model结构\n- 程序员现在更多的是和复杂的View打交道而不是解决业务逻辑。\n\n\n当你在应用中只使用Model-View时，到最后，你会发现“所有的事物都被连接到一起”。\n![](http://konmik.github.io/images/mvp_everything_is_connected_with_everything.png)\n\n\n如果这张图看上去还不是很复杂，那么请你想象一下以下情况：每一个View在任意一个时刻都有可能出现或者消失。不要忘记View的保存和恢复，在临时的view上挂载一个后台任务。\n\n“所有的事物都被连接到一起”的替代品是一个万能对象(god object)。\n\n![](http://konmik.github.io/images/mvp_a_god_object.png)\n\n\ngod object是十分复杂的，他的每一个部分都不能重复利用，无法轻易的测试、或者调试和重构。\n\n###With MVP\n\n###使用MVP\n\n![](http://konmik.github.io/images/mvp_mvp.png)\n\n\n\n复杂的任务被分成细小的任务，并且很容易解决。越小的东西，bug越少，越容易debug，更好测试。在MVP模式下的View层将会变得简单，所以即便是他请求数据的时候也不需要回调函数。View逻辑变成十分直接。\n\n\n理由2：后台任务\n\n\n当你编写一个Actviity、Fragment、自定义View的时候，你会把所有的和后台任务相关的方法写在一个静态类或者外部类中。这样，你的Task不再和Activity联系在一起，这既不会导致内存泄露，也不依赖于Activity的重建。\n\n\n这里有若干种方法处理后台任务，但是它们的可靠性都不及MVP。\n\n\n###为什么它是可行的？\n\n\n这里有一张表格，用于展示在configuration改变、Activity 重启、Out-Of-Memory时，不同的应用部分会发生什么？\n\n \n|    |    情景 1     |     情景 2     |    情景 3|\n|:-------------:|:-------------:|:-------------:|:-------------:|\n||配置改变| Activity 重启|  进程重启|\n |对话框                   |     重置     |    重置     |    重置|\n|Activity, View, Fragment  | 保存/恢复  | 保存/恢复 | 保存/恢复|\n| Fragment with setRetainInstance(true)  |无变化 | 保存/恢复 | 保存/恢复|\n| Static variables and threads  |  无变化 |  无变化  |    重置|\n\n\n\n情景 1: 当用户切换屏幕、更改语言设置或者链接外部的模拟器时，往往意味着设置改变。 相关更多请阅读[这里](http://developer.android.com/reference/android/R.attr.html#configChanges)。\n\n情景 2:Activity的重启发生在当用户在开发者选项中选中了“Don't keep activities”（“中文下为 不保留活动”）的复选框，然后另一个Activity在最顶上的时候。\n\n情景 3: 进程的重启发生在应用运行在后台，但是这个时候内存不够的情况下。\n\n\n\n###总结\n\n\n现在你可以发现，一个setRetainInstance(true)的Fragment也不奏效，我们还是希望这样的Fragment在所有的情景下为保存/恢复的状态模式，所以为简化问题，我们暂不考虑上述情况的Fragment。[Occam's razor](http://en.wikipedia.org/wiki/Occam's_razor)\n\n\n\n|       |配置改变,   Activity重启  |  进程重启|\n|:-------------:|:-------------:|:-------------:|\n |Activity, View, Fragment, DialogFragment | 保存/恢复  |保存/恢复  |\n|Static variables and threads             |   无变化   |   重置|\n\n\n\n现在，看上去更舒服了，我们只需要写两段代码为了恢复应用：\n\n* 保存/恢复 for Activity, View, Fragment, DialogFragment;\n\n* 重启后台请求由于进程重启\n\n\n第一个部分,用Android的API可以实现。第二个部分，就是Presenter的作用了。Presenter只会记住有哪些请求需要执行，当进程在执行过程中重启时，Presenter将会再次执行它们。\n\n\n#####一个简单的例子(no MVP)\n\n这个例子用于从远程服务器加载数据并呈现，当发生异常时，会通过Toast提示。\n\n我推荐使用[RxJava](https://github.com/ReactiveX/RxJava)构建Presenter，因为这个库更容易控制数据流。\n\n我想对创造如此简单的API的伙计说声谢谢，我把它用于[The Internet Chuck Norris Database](http://www.icndb.com/)\n\n无MVP的[例子00](https://github.com/konmik/MVPExamples/tree/master/example00)：\n\n```java\n\npublic class MainActivity extends Activity {\n    public static final String DEFAULT_NAME = \"Chuck Norris\";\n\n    private ArrayAdapter<ServerAPI.Item> adapter;\n    private Subscription subscription;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        ListView listView = (ListView)findViewById(R.id.listView);\n        listView.setAdapter(adapter = new ArrayAdapter<>(this, R.layout.item));\n        requestItems(DEFAULT_NAME);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        unsubscribe();\n    }\n\n    public void requestItems(String name) {\n        unsubscribe();\n        subscription = App.getServerAPI()\n            .getItems(name.split(\"\\\\s+\")[0], name.split(\"\\\\s+\")[1])\n            .delay(1, TimeUnit.SECONDS)\n            .observeOn(AndroidSchedulers.mainThread())\n            .subscribe(new Action1<ServerAPI.Response>() {\n                @Override\n                public void call(ServerAPI.Response response) {\n                    onItemsNext(response.items);\n                }\n            }, new Action1<Throwable>() {\n                @Override\n                public void call(Throwable error) {\n                    onItemsError(error);\n                }\n            });\n    }\n\n    public void onItemsNext(ServerAPI.Item[] items) {\n        adapter.clear();\n        adapter.addAll(items);\n    }\n\n    public void onItemsError(Throwable throwable) {\n        Toast.makeText(this, throwable.getMessage(), Toast.LENGTH_LONG).show();\n    }\n\n    private void unsubscribe() {\n        if (subscription != null) {\n            subscription.unsubscribe();\n            subscription = null;\n        }\n    }\n}\n\n```\n\n有经验的开发者会注意到这个例子有以下不妥：\n\n\n当用户翻转屏幕时候会开始请求，应用发起了过多的请求，将会是屏幕在切换的时候呈现空白的界面。\n\n当用户频繁的切换屏幕，这将会造成内存泄露，请求运行时，每一个回调将会持有MainActivity的引用，让其保存在内存中。因此引起的OOM和应用反应迟缓，会引发应用的Crash。\n\n\nMVP模式下的[例子 01](https://github.com/konmik/MVPExamples/tree/master/example01)\n\n```java\npublic class MainPresenter {\n\n    public static final String DEFAULT_NAME = \"Chuck Norris\";\n\n    private ServerAPI.Item[] items;\n    private Throwable error;\n\n    private MainActivity view;\n\n    public MainPresenter() {\n        App.getServerAPI()\n            .getItems(DEFAULT_NAME.split(\"\\\\s+\")[0], DEFAULT_NAME.split(\"\\\\s+\")[1])\n            .delay(1, TimeUnit.SECONDS)\n            .observeOn(AndroidSchedulers.mainThread())\n            .subscribe(new Action1<ServerAPI.Response>() {\n                @Override\n                public void call(ServerAPI.Response response) {\n                    items = response.items;\n                    publish();\n                }\n            }, new Action1<Throwable>() {\n                @Override\n                public void call(Throwable throwable) {\n                    error = throwable;\n                    publish();\n                }\n            });\n    }\n\n    public void onTakeView(MainActivity view) {\n        this.view = view;\n        publish();\n    }\n\n    private void publish() {\n        if (view != null) {\n            if (items != null)\n                view.onItemsNext(items);\n            else if (error != null)\n                view.onItemsError(error);\n        }\n    }\n}\n\n```\n\n从严格意义上来说，MainPresenter有三个事件处理线程： *onNext*, *onError*, *onTakeView*。他们在`publish()`方法中被回调，*onNext* 或 *onError*的值将会在由onTakeView方法传入的View实例,也就是MainActivity中来发布。 \t \n\n\n```java\npublic class MainActivity extends Activity {\n\n    private ArrayAdapter<ServerAPI.Item> adapter;\n\n    private static MainPresenter presenter;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        ListView listView = (ListView)findViewById(R.id.listView);\n        listView.setAdapter(adapter = new ArrayAdapter<>(this, R.layout.item));\n\n        if (presenter == null)\n            presenter = new MainPresenter();\n        presenter.onTakeView(this);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        presenter.onTakeView(null);\n        if (isFinishing())\n            presenter = null;\n    }\n\n    public void onItemsNext(ServerAPI.Item[] items) {\n        adapter.clear();\n        adapter.addAll(items);\n    }\n\n    public void onItemsError(Throwable throwable) {\n        Toast.makeText(this, throwable.getMessage(), Toast.LENGTH_LONG).show();\n    }\n}\n```\n\nMainActivty构建了MainPresenter，将其维持在onCreate/onDestroy周期外，MainActivity持有MainPresenter的静态引用，所以每一个进程由于OOM重启时，MainActivity可以确认Presenter是否仍然存在，必要时创建。\n\n当然，确认和使用静态变量可能是代码变得臃肿，稍后我们会告诉你如何好看些：。:)\n\n####重要思路：\n\n* 示例程序不会在每次切换屏幕的时候都开始一个新的请求\n* 当进程重启时，示例程序将会重新加载数据。\n* 当MainActivity销毁时，MainPresenter不会持有MainActivity的引用，因此不会在切换屏幕的时候发生内存泄漏，而且没必要去unsubscribe请求。\n\n###[Nucleus](https://github.com/konmik/nucleus)\n\nNucleus是我从[Mortar](https://github.com/square/mortar)和 Keep It Stupid Simple 这篇文章得到的灵感而建立的库。\n\n它有以下特征：\n\n* 它支持在View/Fragment/Activity的Bundle中保存/恢复Presenter的状态，一个Presenter可以保存它的请求参数到bundles中，以便之后重启它们\n \n* 只需要一行代码，它就可以直接将请求结果和错误反馈给View，所以你不需要写`!= null`之类的非空判断语句。\n \n* 它允许一个view实例可以持有多个Presenter。不过你不能在用[Dagger](http://square.github.io/dagger/)实例化的presenter中这样使用(传统方法).\n\n* 它可以用一行代码快速的将View和Presenter绑定。\n\n* 它提供一些现成的基类，例如: `NucleusView`, `NucleusFragment`, `NucleusSupportFragment`, `NucleusActivity`. 你可以将他们的代码拷贝出来改造出一个自己的类以利用Nucleus的presenter。\n\n* 支持在进程重启后，自动重新发起请求，在`onDestroy`方法中，自动的退订RxJava的订阅。\n\n* 最后，它简洁明了，每一个开发者都会理解，以上这些只用了180行代码来驱动Presenter这个类,加上230行RxJava的依赖。\n\n\n使用了[Nucleus](https://github.com/konmik/nucleus) 的[例 02](https://github.com/konmik/MVPExamples/tree/master/example02)\n\n```java\npublic class MainPresenter extends RxPresenter<MainActivity> {\n\n    public static final String DEFAULT_NAME = \"Chuck Norris\";\n\n    @Override\n    protected void onCreate(Bundle savedState) {\n        super.onCreate(savedState);\n\n        App.getServerAPI()\n            .getItems(DEFAULT_NAME.split(\"\\\\s+\")[0], DEFAULT_NAME.split(\"\\\\s+\")[1])\n            .delay(1, TimeUnit.SECONDS)\n            .observeOn(AndroidSchedulers.mainThread())\n            .compose(this.<ServerAPI.Response>deliverLatestCache())\n            .subscribe(new Action1<ServerAPI.Response>() {\n                @Override\n                public void call(ServerAPI.Response response) {\n                    getView().onItemsNext(response.items);\n                }\n            }, new Action1<Throwable>() {\n                @Override\n                public void call(Throwable throwable) {\n                    getView().onItemsError(throwable);\n                }\n            });\n    }\n}\n\n@RequiresPresenter(MainPresenter.class)\npublic class MainActivity extends NucleusActivity<MainPresenter> {\n\n    private ArrayAdapter<ServerAPI.Item> adapter;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        ListView listView = (ListView)findViewById(R.id.listView);\n        listView.setAdapter(adapter = new ArrayAdapter<>(this, R.layout.item));\n    }\n\n    public void onItemsNext(ServerAPI.Item[] items) {\n        adapter.clear();\n        adapter.addAll(items);\n    }\n\n    public void onItemsError(Throwable throwable) {\n        Toast.makeText(this, throwable.getMessage(), Toast.LENGTH_LONG).show();\n    }\n}\n```\n\n正如你看到的，跟上一个代码相比，这个例子十分简洁。Nucleus 可以构造/销毁/保存 Presenter, 绑定/解绑 View ，并且自动向已经绑定的view发送请求的结果。\n\n`MainPresenter`的代码比较短，因为它使用`deliverLatestCache（）`的操作，延迟了由一个数据源发出所有的数据和错误，直到View可用。它还把数据缓存在内存中，以便它可以在Configuration change时可以被重用。\n\n`MainActivity`的代码比较短，因为主Presenter的创作由`NucleusActivity`管理。当你需要绑定一个Presenter的时候，只需要添加注解`@RequiresPresenter（MainPresenter.class）`。\n\n警告！注释！在Android中，如果你使用注解，这是最好检查以下这么做会不会降低性能。以我使用的'Galaxy S`（2010年设备）为例，处理此批注耗时不超过0.3毫秒。只在实例化view的时候才会发生，因此注解在这里对性能的影响可以忽略。\n\n\n####更多例子\n\n\n一个扩展的例子,带有请求参数的Presenter：[Nucleus Example](https://github.com/konmik/nucleus/tree/master/nucleus-example)。\n\n\n带有单元测试的例子： [Nucleus Example With Tests](https://github.com/konmik/nucleus/tree/master/nucleus-example-with-tests)\n\n####`deliverLatestCache()` 方法\n\n这个RxPresenter的工具方法有三个变种：\n\n* `deliver()` will just delay all onNext, onError and onComplete emissions until a View becomes available. Use it for cases when you're doing a one-time request, like logging in to a web service. [Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onCreate(android.os.Bundle))\n\n* `deliver()`只是推迟onNext、onError、onComplete的调用，直到视图有效。使用它，你只需要一次请求，就像发起登陆web服务一样。[Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onCreate(android.os.Bundle))\n\n* `deliverLatest（）`当有新的的onNext值，将会舍弃原有的值，如果你有可更新的数据源，这将让你去除那些不需要的数据。[Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/RxPresenter.html#deliverLatest)\n\n* `deliverLatestCache（）`，和`deliverLatest（）`一样，但除了它会在内存中保存最新的结果外，当View的另一个实例可用（例如：在配置更改的时候）时，还是会触发一次。如果你不想组织请求在你的View中的保存/恢复事务（比方说，结果太大或者不能很容易地保存在Bundle中），这个方法可以让用户体验更好。[Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/RxPresenter.html#deliverLatestCache)\n\n####Presenter的生命周期\n\n相比Android组件，Presenter的生命周期更加简短。\n\n* `void onCreate(Bundle savedState)` - 每一个Presenter构造时 .  [Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onCreate(android.os.Bundle))\n\n* `void onDestroy()` - 用户离开View时调用 . [Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onDestroy())\n\n* `void onSave(Bundle state)` - 在View的`onSaveInstanceState`方法中调用，用于持有Presenter的状态.  [Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onSave(android.os.Bundle))\n\n* `void onTakeView(ViewType view)` - 在Activity或者Fragment的`onResume()`方法中或者`android.view.View#onAttachedToWindow()`调用.  [Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onTakeView(ViewType))\n\n* `void onTakeView(ViewType view)` - 在Activity或者Fragment的`onResume()`方法中或者`android.view.View#onAttachedToWindow()`调用.  [Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onTakeView(ViewType))\n\n* `void onDropView()` -  在Activity或者Fragment的`onPause()`方法中或者`android.view.View#onDetachedFromWindow()`调用. [Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onDropView)\n\n####View的回收与View栈\n\n通常来说,你的view（比如fragment或者自定义的view）在用户的交互过程中挂载与解挂（attached and detached）都是随机发生的。 这倒是不让presenter在view每次解挂（detached）的时候都销毁的一个启发。你可以在任何时候挂载与解挂view，但是presenter可以在这些行为中幸存下来，继续后台的工作。\n\n\n\n这里还存在着一个关于View回收的问题：一个Fragment在Configuration change或者从stack中弹出的情况下，不知道自身有没有解挂（detached）。\n\n默认只在Activity处于finish时，才在调用View的`onDetachedFromWindow()`/`onDestroy()` 销毁Presenter。\n\n所以，当你在常规的Activity生命周期内，销毁View，你需要给给View一个销毁Presenter的信号。在这里，公有方法`NucleusLayout.destroyPresenter()` and `NucleusFragment.destroyPresenter()`就派上用场了。\n\n例如，在我的项目中，下面的是我如何进行FragmentManager的`pop()`操作:\n\n```java\n    fragment = fragmentManager.findFragmentById(R.id.fragmentStackContainer);\n    fragmentManager.popBackStackImmediate();\n    if (fragment instanceof NucleusFragment)\n        ((NucleusFragment)fragment).destroyPresenter();\n```\n\n在进行*replace*Fragment栈和对处于底部的Fragment进行*push*操作时，你可能需要进行相同的操作。\n\n在View从Activity解挂（detached）时，您可能会选择摧毁Presenter来避免问题的发生，但是，这将意味着当View解挂（detached）时，后台任务无法继续进行。\n\n所以这一节的 \"view recycling\"完全留你你自己考虑，也许有一天我会找到更好的解决办法，如果你有一个办法，请告诉我。\n\n####最佳实践\n\n\n在Presenter中保存你的请求参数。\n\n规则很简单：Presenter的主要作用是管理请求。所以，View不应该自己处理或者重启请求。从View中，我们可以看见，后台事务不会消失，总是会返回结果或者错误，*而不是通过回调的方式*。\n\n\n\n```java\n\npublic class MainPresenter extends RxPresenter<MainActivity> {\n\n    private String name = DEFAULT_NAME;\n\n    @Override\n    protected void onCreate(Bundle savedState) {\n        super.onCreate(savedState);\n        if (savedState != null)\n            name = savedState.getString(NAME_KEY);\n        ...\n\n    @Override\n    protected void onSave(@NonNull Bundle state) {\n        super.onSave(state);\n        state.putString(NAME_KEY, name);\n    }\n}\n```\n\n我推荐使用一个很棒的库[Icepick](https://github.com/frankiesardo/icepick)。在不使用运行时注解的前提下，它可以减少代码量，并简化应用程序逻辑 - 所有的事都在编译过程中已经处理好了。这个库和[ButterKnife](http://jakewharton.github.io/butterknife)搭配是个不错的选择。\n\n \n```java\npublic class MainPresenter extends RxPresenter<MainActivity> {\n\n    @Icicle String name = DEFAULT_NAME;\n\n    @Override\n    protected void onCreate(Bundle savedState) {\n        super.onCreate(savedState);\n        Icepick.restoreInstanceState(this, savedState);\n        ...\n    }\n    @Override\n    protected void onSave(@NonNull Bundle state) {\n        super.onSave(state);\n        Icepick.saveInstanceState(this, state);\n    }\n}\n \n```\n\n如果你有不止一对请求参数，这个库在不使用运行时注解的前提下。您可以创建`BasePresenter`并把*Icepick*到该类中，所有的子类将会自动保存标有`@Icicle`这一注解的变量，而你将不再需要去实现`OnSave`。这也适用于保存Activity，Fragment，View的状态。\n\n\n####在主线程中调用`onTakeView`进行即时查询[Javadoc](http://konmik.github.io/nucleus/nucleus/presenter/Presenter.html#onTakeView(ViewType))\n\n\n有时候，你要进行少量的数据查询，如从数据库中读取少量的数据。虽然你可以很容易地用Nucleus创建一个可重启的请求，你不必到处使用这个强大的工具。如果你在fragment创建的时候初始化一个后台请求，即使只有几毫秒，用户也会看到一会儿的空白屏。因此，为了使代码更短，用户体验更好，可以使用主线程。\n\n\n#### 不要让Presenter控制你的View\n\n这不是很好的工作方式 - 由于这种不自然的方式，应用程序逻辑变得太复杂。\n\n自然的方式是操作流由用户发起，通过View，Presenter和Model，最后流向数据。毕竟，用户将使用应用,用户是控制应用程序的源头。因此，控制应该从用户开始而不是一些应用的内部结构。\n\n\n从view，到presenter到model是很直接的形式，很容易书写这样的代码。你将得到以下序列： __user -> view -> presenter -> model -> data__ 。但是，当控制流变成这样时: __user -> view -> presenter -> view -> presenter -> model -> data__，它只是违反KISS原则.\n\n\n\nFragments？不好意思它是违背了这种自然操作流程的。它们太复杂。这里是一个非常好讲诉Fragment的文章：[抨击Android的Fragment](http://corner.squareup.com/2014/10/advocating-against-android-fragments.html)。fragment的替代者[Flow](https://github.com/square/flow) 并没有简化多少东西。\n\n####MVC\n\n如果你对MVC（模型-View-控制器）-不要去使用。模型-View-控制器和MVP完全不同，不能解决接口开发者面对的问题。\n\n####What is MVC?\n\n####什么是MVC?\n\n\n\n\n\n* **Model**代表着应用程序的内部状态。它可以负责存储，当然也可以不考虑。\n\n* **View**是唯一的与MVP相同的部分 - 它用于将模型呈现在屏幕上，应用程序的一部分。\n\n* **Controller**表示输入装置，如键盘，鼠标或操纵杆。\n\n\nMVC在过去以键盘为驱动的应用中（比如游戏），是比较好的模式。没有窗口和图形用户界面的交互——应用接受输入(Controller),维持状态（Model），产生输出（View）。同样，数据和控制的关系是这样的。**controller -> model -> view**。这种模式是在Android绝对无用。\n\n\n\n这里有一些关于MVC的困惑。人们（Web开发人员）觉得他们使用MVC，而实际上，他们使用的MVP。许多Android开发者认为Controller是用于控制View的，所以他们试图在创建View时，从视图（View）中提取视图逻辑，交由专门的控制器控制。我个人是没有看出这种架构的好处。\n\n\n#### 在数据复杂的项目中使用固定的数据结构\n\n\n\n在这方面，[AutoValue](https://github.com/google/auto/tree/master/value)是十分好的库，在它的描述中，你会发现一大堆好处，我建议你阅读它。Android平台上还有一个接口：[AutoParcel](https://github.com/frankiesardo/auto-parcel)。其主要原因是，你可以四处传递，而不用关心是否在程序的某个地方被修改了。而且他们也是线程安全的。\n\n\n\n\n###总结\n\n\n试试MVP吧，然后告诉你的朋友。:)\n"
  },
  {
    "path": "issue-12/Android自动截屏工具.md",
    "content": "Android 自动截屏工具\n---\n\n> * 原文链接 : [Automating Android Screenshots](https://medium.com/@swanhtet1992/automating-android-screenshots-5b7574c0621d)\n* 原文作者 : [Swan Htet Aung](https://medium.com/@swanhtet1992)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [sundroid](https://github.com/sundroid) \n* 校对者: [yinna317](https://github.com/yinna317 )  \n* 状态 :  完成\n\n随着mac版本AndroidTool的发布，获取android应用截屏变得非常简单。与此同时，感谢开发商！这对于我们开发者来说真是太好了！\n\n对于简单应用来说，AndroidTool是足够满足截屏的功能需求了，然而，我需要在在我正在开发的一款app上完成一个完全自动化的截图过程，并且将截图发布到应用市场。我认为这将不简单，所以，我尽量避免复杂的实现过程，而是想办法如何更好的结合AndroidTool来完成这个功能。\n\n然而当我昨天阅读了[Enrique López Mañas yesterday](https://medium.com/@enriquelopezmanas)的[Automating Android development](https://medium.com/google-developer-experts/automating-android-development-6daca3a98396)文章，我意识到，他在博客中讨论的话题我已经完成了4/5。唯一我还没有做的就是测试。我不喜欢测试，然而，那篇文章激励着我去尝试写测试代码。。 所以，我今天早上尝试了一下。经过几个小时编写测试代码，我意外的找到了自动化截图的解决方案。\n\n在这片文章中，我将会谈论关于如何通过ui 测试来完成自动截图和提交这些截图到应用商店。\n\n##UI Automator查看器\n\n‘uiautomatorviewer’是一个非常强大的工具来查看views，当发现极好的布局时，我通常会使用‘uiautomatorviewer’来查看，如果你运行这个工具将会获得下图所示。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/2000/1*2GVDSxydFfqY4WvXBBVQ1Q.png)\n\n通过这个工具你可以看到UI对象，在这里，我可以检测TextView的id，这个技巧在稍后会变得非常有用。。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/2000/1*9yNBO3PwetoOv7EWEChsag.png)\n\n##UI Automator\n\nGoogle在[Android Testing Support Library](https://developer.android.com/tools/testing-support-library/index.html)里面同时也提供了一个叫做‘UI Automator’ 的库，它允许开发者自动化获取用户交互过程。\n\n使用UI Automator时，你需要在你的项目中添加依赖，具体配置信息需要填写在`build.gradle`里面。\n\n正如[文档](https://developer.android.com/training/testing/ui-testing/uiautomator-testing.html#run)中所说。你需要指定[AndroidJUnitRunner](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)作为你默认的测试工具。\n\nUI Automator 里面频繁使用的类有：UiDevice, UiSelector, UiObject, and UiScrollable.\n\n我们将会在androidTestScreenshot文件夹下创建一个简单的测试类，并且这个类继承InstrumentationTestCase。\n\n这个过程很直接：\n\n首先，监听设备点击“Home”键时，执行UiDevice 的方法pressHome()。在每一个测试里面，我们做这样重复性的工作：\n\n从最开始的地方打开app。我个人发现一个简答的方法去获取截屏。你可以使用UiDevice的pressBack()方法为其他测试。\n\n获取想要的UI交互可以使用UiSelector, UiScrollable, and UiObject。\n\n使用SystemClock.sleep方法，为异步任务的执行腾出一些时间（异步任务的执行可能在截屏之后），以此来避免发生截屏获取的为空异常和UiObject not found异常。\n\n最后我们截屏并且将获取的截屏保存在指定的位置。\n\n到目前为止，你可能已经了解了如何使用uiautomatorviewer来帮助我们获取许多我们想要的UI元素，然而，我使用UiSelector().resourceId，因为我们可以通过我们在layout里面使用的id来完成截屏，这样不是更加简单了吗？你也可以有其他选择，比如使用 className, text, etc… 来完成这一过程。\n\n##Product Flavor\n\n我不知道为什么UiAutomator下的minSdkVersion是18，因为我需要minSdk至少是14，我需要使用这个额外的方法。如果这里有任何其他方法可以避免我自己去实现截屏的，请让我知道。\n\nAndroid使用androidTest来实现主要的测试工作，也为了实现不同产品的写测试需要，我们需要写我们的测试在androidTestFlavorName文件夹。这就是为什么我们在androidTestScreenshot路径下创建SimpleUiTest 类的原因。\n\n万事具备，现在，运行`gradle connectedAndroidTestScreenshotDebug`。在这个测试完成后你将会获得屏幕截图。\n\n"
  },
  {
    "path": "issue-12/Android进行单元测试难在哪-part4.md",
    "content": "Android 进行单元测试难在哪-part4\n---\n\n> * 原文链接 : [HOW TO MAKE OUR ANDROID APPS UNIT TESTABLE (PT. 2)](http://philosophicalhacker.com/2015/05/08/how-to-make-our-android-apps-unit-testable-pt-2/)\n* 原文作者 : [Matthew Dupree](http://philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n\n在**[上一篇博文](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-11/Android%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part3.md)**中，我给大家介绍了新的应用架构方式 - Square 大法，就像我之前说的，Square 大法是 Square 用于使 Fragment 内的业务逻辑能够进行单元测试的通用方法，我还给大家展示了如何使用 Square 大法重构 Google 的 IOSched 应用的 SessionCalendarService 类，使得对 SessionCalendarService 类内的业务逻辑进行单元测试由几乎不可能变为可行。而在今天的这篇博文中，我会和大家一起继续探索 Square 大法，让我们对应用的 UI 组件进行单元测试成为可能，也让测试变得容易。\n\n## 这篇博文有“依赖”\n\n将 Sqaure 大法应用到 App 的 UI 组件中（例如 Activity 和 Fragment） 比起将它应用到无 UI 的应用中要复杂一些，造成这种情况的根源正好与我们重构代码的核心方法相关联，解决了这些额外的复杂性问题，我们也就能够改变对 UI 组件进行单元测试时需要使用的预测试状态，以及修改测试后状态。如果你听到“预测试状态”和“测试后状态”后感觉一头雾水，或者觉得有一点点印象却不记得具体是什么意思的话，最好复习复习我之前写的`[Android 进行单元测试难在哪-part1](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-9/Android%20%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part1.md)`。如果你清楚地理解这两个概念的话，确保你知道 SessionDetailActivity 干了啥。为了学习该如何将 Square 大法应用到应用的 UI 组件类中，我们将重构 SessionDetailActivity 类的代码，使我们能够对类中的 onStop() 方法中的业务逻辑进行单元测试。\n\n开始之前我在唠叨几句吧，如果你有了解过 MVP 模式的话对你理解用 Square 大法重构应用 UI 组件大有裨益，不过呢，由于 Square 已经写了一篇非常精彩的[博文](https://corner.squareup.com/2014/10/advocating-against-android-fragments.html)介绍 MVP 模式了，我就不再给大家介绍啦，有兴趣的话看 Square 写的博文就好了。如果你在学习 MVP 模式的时候觉得很难理解与 View 相关的操作，不妨看看我之前写的一篇[博文](http://philosophicalhacker.com/2015/04/05/dont-call-it-mvp/)，这篇博文能帮你区分进行 Android 开发时我们使用的 View 和 MVP 模式中的“View”。除此以外，我在这篇博文中将 Presenter 用于更新应用界面显示的对象称为 “ViewTranslator” 而不是 “View”。\n\n## 用 Square 大法重构应用 UI 组件类\n\n虽然用 Square 大法重构应用的 UI 组件可能会更复杂些，但我们的重构策略始终没有发生变化：抽取出应用组件类中的业务逻辑（例如 Activity，Fragment，Service），并将业务逻辑放到我之前说的被进行了依赖注入的“业务对象”中，也就是与 Android 无关接口的 Android 特定实现。\n\n下面是重构后的 onStop() 方法：\n\n```java\n@Override\npublic void onStop() {\n    super.onStop();\n    if (mInitStarred != mStarred) {\n        if (UIUtils.getCurrentTime(this) < mSessionStart) {\n            // Update Calendar event through the Calendar API on Android 4.0 or new versions.\n            Intent intent = null;\n            if (mStarred) {\n                // Set up intent to add session to Calendar, if it doesn't exist already.\n                intent = new Intent(SessionCalendarService.ACTION_ADD_SESSION_CALENDAR,\n                        mSessionUri);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n                        mSessionStart);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n                        mSessionEnd);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_ROOM, mRoomName);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n            } else {\n                // Set up intent to remove session from Calendar, if exists.\n                intent = new Intent(SessionCalendarService.ACTION_REMOVE_SESSION_CALENDAR,\n                        mSessionUri);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n                        mSessionStart);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n                        mSessionEnd);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n            }\n            intent.setClass(this, SessionCalendarService.class);\n            startService(intent);\n \n            if (mStarred) {\n                setupNotification();\n            }\n        }\n    }\n}\n```\n\n正如我之前提到的，这段代码存在一个问题：代码并没有通过被注入到 SessionDetailActivity 的依赖中的方法启动 SessionCalendarService。那我们现在就用 Square 大法来解决这个问题。首先，我们将业务逻辑抽取出来，并放入业务对象中，Square 的工程师们为这个在 Activity（或 Fragment，或其他……）中使用的业务对象起了一个名字 —— “Presenter”。\n\nPresenter 负责用 Model 中的数据更新 View，为了使单元测试在 Presenter 中是可行的，这就意味着 Model 和 View 都必须是注入到 Presenter 中的依赖。也正是这三个对象的组合使用构成了我们所说的 MVP 架构模式。\n\n下面就是 SessionDetailPresenter 中与 onStop() 方法等价的实现啦：\n\n```java\npublic class SessionDetailViewPresenter implements RepositoryManagerCallbacks {\n \n    public SessionDetailViewPresenter(SessionDetailView sessionDetailView,\n                                      RepositoryManager loaderManager,\n                                      ServiceStarter serviceStarter,\n                                      long calendarId\n                                      ) {\n \n        mSessionDetailView = sessionDetailView;\n        mLoaderManager = loaderManager;\n        mServiceStarter = serviceStarter;\n        mCalendarId = calendarId;\n    }\n    \n    //...\n \n    public void onViewTranslatorStopped() {\n \n        if (mInitStarred != mStarred) {\n \n            if (System.currentTimeMillis() < mSessionStart) {\n \n                CalendarSession calendarSession = new CalendarSession(mSessionUri, mSessionStart, mSessionEnd, mTitleString, mRoomName);\n \n                if (mStarred) {\n \n                    mServiceStarter.startAddCalendarSessionService(mCalendarId, calendarSession);\n \n                } else {\n \n                    mServiceStarter.startRemoveCalendarSessionService(mCalendarId, calendarSession);\n                }\n            }\n \n            if (mStarred) {\n \n                setupSessionNotification();\n            }\n        }\n    }\n    \n    //...\n}\n```\n\n完成这个类的关键在于：SessionDetailPresenter 的依赖通过它的构造器传递，因为这些依赖都被注入了，所以我们现在可以修改 SessionDetailPresenter 类中 onViewTranslatorStopped() 方法的测试单元的测试后状态。\n\n```java\npackage com.google.samples.apps.iosched.test;\n \nimport com.google.samples.apps.iosched.service.CalendarSession;\nimport com.google.samples.apps.iosched.ui.RepositoryManager;\nimport com.google.samples.apps.iosched.ui.ServiceStarter;\nimport com.google.samples.apps.iosched.ui.sessiondetail.SessionDetailViewPresenter;\nimport com.google.samples.apps.iosched.ui.sessiondetail.SessionDetailViewTranslator;\n \nimport junit.framework.TestCase;\n \nimport static org.mockito.Matchers.any;\nimport static org.mockito.Matchers.anyLong;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n \n/**\n * Created by MattDupree on 5/8/15.\n */\npublic class SessionDetailPresnterTests extends TestCase {\n \n \n    public void testShouldLaunchAddSessionService() {\n \n        //Arrange\n        SessionDetailViewTranslator sessionDetailViewTranslator = mock(SessionDetailViewTranslator.class);\n \n        RepositoryManager repositoryManager = mock(RepositoryManager.class);\n \n        ServiceStarter serviceStarter = mock(ServiceStarter.class);\n \n        long calendarId = 0;\n \n        SessionDetailViewPresenter sessionDetailViewPresenter = new SessionDetailViewPresenter(sessionDetailViewTranslator,\n                                                                                               repositoryManager,\n                                                                                               serviceStarter,\n                                                                                               calendarId);\n        sessionDetailViewPresenter.onViewCreated(null);\n \n        //Act\n        sessionDetailViewPresenter.onViewStopped();\n \n        //Assert\n        verify(serviceStarter).startAddCalendarSessionService(anyLong(), any(CalendarSession.class));\n \n    }\n \n}\n```\n\n虽然我们现在可以修改测试单元的测试后状态了，但这还不够，因为这个测试单元无法完成测试。为什么呢？我们不妨一起看看 onViewTranslatorStopped() 方法：\n\n```java\npublic void onViewTranslatorStopped() {\n \n    if (mInitStarred != mStarred) {\n \n        if (System.currentTimeMillis() < mSessionStart) {\n \n            CalendarSession calendarSession = new CalendarSession(mSessionUri, mSessionStart, mSessionEnd, mTitleString, mRoomName);\n \n            if (mStarred) {\n \n                mServiceStarter.startAddCalendarSessionService(mCalendarId, calendarSession);\n \n            } else {\n \n                mServiceStarter.startRemoveCalendarSessionService(mCalendarId, calendarSession);\n            }\n        }\n \n        if (mStarred) {\n \n            setupSessionNotification();\n        }\n    }\n}\n```\n\nonViewTranslatorStopped() 方法的代码被包裹在一个判断模块中，只有在“starred button”的状态与其初始化状态相异时才会执行判断模块中的代码。mInitStarred 将会在 Loader 回调中被初始化，IOSched 应用检索数据库以判断用户选中的 I/O 大会是否已经被添加到日历中，并在用户返回到 SessionDetailActivity 后通过消息更新 UI。但在上面这段业务逻辑的测试单元中，mInitStarred  和 mStarred 都将有一个初始值 `false`，使得判断模块内的代码永远不会被执行。\n\n即使我们能执行判断模块内的代码，我们还是不能获得进行单元测试所需要的一切，因为启动 SessionCalendarService 的代码在另一个用于确保在 System.currentTimeMillis() 的返回值小于 mSessionStart 时才执行相关代码的判断模块中。既然我们不能修改 mSessionStart 的值，也就不能保证启动 SessionCalendarService 的代码会被运行。\n\n这些问题都是我思考如何在 Android 中进行单元测试时想到的普遍问题的极端例子：我们常常缺乏对测试单元预测试状态的控制力。然而，由于我们将 SessionRepositoryManager 注入到 SessionDetailPresenter 中，我们现在可以判断 mSessionStart 和 mInitStarred 的值了，因为 SessionRepositoryManager 是一个 Android 无关的接口¹:\n\n```java\npackage com.google.samples.apps.iosched.ui;\n \nimport android.os.Bundle;\n \nimport com.google.samples.apps.iosched.io.model.Session;\n \n/**\n * \n * Created by MattDupree on 5/6/15.\n */\npublic interface SessionRepositoryManager {\n \n    void initRepository(int id, Bundle bundle, SessionRepositoryManagerCallbacks repositoryManagerCallbacks);\n \n    \n    interface SessionRepositoryManagerCallbacks {\n \n        void onLoadFinished(Session session);\n    }\n}\n```\n\n然而，当我们创建 SessionDetailPresenter，我们注入了包含 LoaderManager 的 Android 无关接口 `SessionRepositoryManager` 的实现。\n\n```java\npublic class SessionDetailActivity extends Activity implements SessionDetailViewTranslator {\n    \n    @Override\n    public void onCreate(Bundle savedInstanceState)\n    \n        //...\n    \n        ServiceStarter serviceStarter = new AndroidServiceStarter(this);\n \n        SessionRepositoryManager repositoryManager = new AndroidSessionRepositoryManager(getLoaderManager());\n \n        mSessionDetailViewPresenter = new SessionDetailViewPresenter(this, repositoryManager, \n                                                                     serviceStarter, calendarId);\n        mSessionDetailViewPresenter.onViewCreated(savedInstanceState);\n    \n        //...\n    }\n    \n}\n```\n\n因为 SessionRepositoryManager 只是一个接口，所以我们能轻松地定义 MockRepositoryManager 帮助我们完成单元测试：\n\n```java\npackage com.google.samples.apps.iosched.ui.sessiondetail;\n \nimport android.os.Bundle;\n \nimport com.google.samples.apps.iosched.io.model.Session;\n \n/**\n * Created by MattDupree on 5/8/15.\n */\npublic class MockSessionRepositoryManager implements SessionRepositoryManager{\n \n \n    private Session mSession;\n \n    public MockSessionRepositoryManager(Session session) {\n \n        mSession = session;\n    }\n \n \n    @Override\n    public void initRepository(int id, Bundle bundle,\n                               SessionRepositoryManagerCallbacks repositoryManagerCallbacks) {\n        \n        repositoryManagerCallbacks.onLoadFinished(mSession);\n    }\n}\n```\n\n不知道大家有没有注意到：当有操作通过向构造器传递一个 Session 对象调用 initRepository() 方法时，我们能够指定 MockSessionRepositoryManager 的返回值。SessionDetailPresenter 中类似 mSessionStart 这样的值能在 Session 的模板对象中通过 startTimeStamp 实例初始化。这样一来，我们就能掌控这些值了，也就是说，我们现在几乎拥有了在对 onViewTranslatorStopped() 方法进行单元测试时，准备阶段所需要的一切。\n\n```java\npublic void testShouldLaunchAddSessionService() {\n \n    //Arrange\n    SessionDetailViewTranslator sessionDetailViewTranslator = mock(SessionDetailViewTranslator.class);\n \n    Session session = new Session();\n    session.startTimestamp = \"1431081943\";\n \n    SessionRepositoryManager repositoryManager = new MockSessionRepositoryManager(session);\n \n    ServiceStarter serviceStarter = mock(ServiceStarter.class);\n \n    long calendarId = 0;\n \n    SessionDetailViewPresenter sessionDetailViewPresenter = new SessionDetailViewPresenter(sessionDetailViewTranslator,\n                                                                                           repositoryManager,\n                                                                                           serviceStarter,\n                                                                                           calendarId);\n    sessionDetailViewPresenter.onViewCreated(null);\n \n    //Act\n    sessionDetailViewPresenter.onViewStopped();\n \n    //Assert\n    verify(serviceStarter).startAddCalendarSessionService(anyLong(), any(CalendarSession.class));\n \n}\n```\n\n我之所以说“几乎”，是因为 onViewTranslatorStopped() 方法中还有一个地方不能通过上面的代码处理。在 onViewTranslatorStopped() 方法的最下面有一个只有在 mStarred 的值为 true 时才会运行的代码块。这段代码会启动一个 Service 提醒用户参与并且/或者排列他们添加到日历中的 IO 大会：\n\n```java\npublic class SessionDetailViewPresenter implements RepositoryManagerCallbacks {\n \n    public SessionDetailViewPresenter(SessionDetailView sessionDetailView,\n                                      RepositoryManager loaderManager,\n                                      ServiceStarter serviceStarter,\n                                      long calendarId\n                                      ) {\n \n        mSessionDetailView = sessionDetailView;\n        mLoaderManager = loaderManager;\n        mServiceStarter = serviceStarter;\n        mCalendarId = calendarId;\n    }\n    \n    //...\n \n    public void onViewTranslatorStopped() {\n \n        if (mInitStarred != mStarred) {\n \n            if (System.currentTimeMillis() < mSessionStart) {\n \n                CalendarSession calendarSession = new CalendarSession(mSessionUri, mSessionStart, mSessionEnd, mTitleString, mRoomName);\n \n                if (mStarred) {\n \n                    mServiceStarter.startAddCalendarSessionService(mCalendarId, calendarSession);\n \n                } else {\n \n                    mServiceStarter.startRemoveCalendarSessionService(mCalendarId, calendarSession);\n                }\n            }\n \n            if (mStarred) {\n \n                setupSessionNotification();\n            }\n        }\n    }\n    \n    //...\n}\n```\n\n为了让这段代码运行，我们需要确保 mStarred 的值为 true。我们能通过调用 SessionDetailPresenter 的 onSessionStarred() 方法完成这项工作，因为 onSessionStarred() 方法就是在用户点击 star button 时 SessionDetailViewTranslator（如果你觉得这些命名让你觉得晕乎乎的，你可以把它当作 SessionDetailView）调用的方法。\n\n```java\npublic void onToggleSessionStarred() {\n \n    mStarred = !mStarred;\n}\n```\n\n```java\npublic void testShouldLaunchAddSessionService() {\n \n    //Arrange\n    SessionDetailViewTranslator sessionDetailViewTranslator = mock(SessionDetailViewTranslator.class);\n \n    Session session = new Session();\n    session.startTimestamp = \"1431081943\";\n \n    SessionRepositoryManager repositoryManager = new MockSessionRepositoryManager(session);\n \n    ServiceStarter serviceStarter = mock(ServiceStarter.class);\n \n    long calendarId = 0;\n \n    SessionDetailViewPresenter sessionDetailViewPresenter = new SessionDetailViewPresenter(sessionDetailViewTranslator,\n                                                                                           repositoryManager,\n                                                                                           serviceStarter,\n                                                                                           calendarId);\n    sessionDetailViewPresenter.onViewCreated(null);\n    //****** We call onToggleSessionStarred() to make sure that mStarrred is true\n    sessionDetailViewPresenter.onToggleSessionStarred();\n    //******\n    \n    //Act\n    sessionDetailViewPresenter.onViewStopped();\n \n    //Assert\n    verify(serviceStarter).startAddCalendarSessionService(anyLong(), any(CalendarSession.class));\n \n}\n```\n\n把上面提到的所有工作万仇后，我们终于能够在 onViewTranslatorStopped() 方法中进行单元测试了。\n\n##结论\n\n你可能觉得我们为单元测试的准备阶段进行了大量的工作，我不得不承认你的感觉是对的。最后，我提一点个人想法：我认为 SessionDetailActivity 类中的代码实在是太多了，都有1000多行……正是这个原因，使得为其实现测试单元变得如此艰难。此外，因为这篇博文的目的只是展示 Square 大法的核心用法，所以我并不打算讨论优化单元测试的方案。²\n\nSquare 大法是告别传统 Android 开发架构的里程碑，因为我们确实发现了遵循传统架构进行开发的种种缺陷。为此，我将在下一篇博文中指出 Square 大法潜在的问题，并提出可能的解决办法。此外，本系列的最后一篇博文也会告诉大家 Square 大法的种种优点，而且我所说的优点可不是只有增强应用的可测试性哦。\n\n##注：\n\n1. 严格来说这个接口并不是 Android 无关的，因为它的核心方法使用 Bundle 作为参数，但我不确定这会不会带来什么问题，毕竟 Bundle 非常普通，并不会是什么我们想要测试的东西。退一万步说，即使要对它进行测试也没啥麻烦。\n\n2. 在 Droidcon Montreal 中，Richa Khandelwal 在 Coursa 上开了一节课建议我们使用[更简洁、更易于测试的架构进行开发](https://speakerdeck.com/richk/clean-android-architecture)，这或许也能让实现单元测试变得简单些。"
  },
  {
    "path": "issue-12/MVP框架Mosby架构详解.md",
    "content": "# Ted Mosby - 软件架构\n\n* 作者：Hannes Dorfmann\n* 原文链接 : [http://hannesdorfmann.com/android/mosby/]\n(http://hannesdorfmann.com/android/mosby/)\n* 文章出自 : [Android开发技术前线](https://github.com/bboyfeiyu/android-tech-frontier)\n* 译者 : [Mr.Simple](https://github.com/bboyfeiyu)\n\n我给这篇关于Android库的博客起的名字灵感来源于《老爸老妈浪漫史》中的建筑设计师Ted Mosby。这个Mosby库可以帮助大家在Android上通过Model-View-Presenter模式做出一个完善稳健、可重复使用的软件，还可以借助ViewState轻松实现屏幕翻转。\n\n## Model-View-Presenter (MVP)\n\n**MVP**模式是一个把view从低层模型分离出来的一种现代模式。**MVP**由model–view–controller (MVC)软件模式衍生而来，常用于构建UI\n\n* **MVP**中的**M**（model）代表的是将会显示在view（UI）中的数据。\n* **MVP**中的**V**（view）是显示数据（model）并且将用户指令（events）传送到presenter以便作用于那些数据的一个接口。View通常含有Presenter的引用。\n* **MVP**中的**P**（presenter）扮演的是“中间人”的作用（就如MVC中的controller），且presenter同时引用view和model。值得注意的是，“Model”这个词并不正确。严格意义上来说，它指的应该是检索或控制一个Model的业务逻辑层。举个例子，比如你的数据库里面包含了User，而你的View想要显示一个User列表，那么Presenter会引用数据库中的业务逻辑层（比如DAO）从而查询到一个User列表。如图1-1.\n ![](http://hannesdorfmann.com/images/mosby/mvp-overview.png)\n\n从数据库中查询或显示User列表的具体流程如图1-2：\n![](http://hannesdorfmann.com/images/mosby/mvp-workflow.png)\n\n以上工作流程图应该能够说明问题了。但是，还有以下几点值得注意的地方：\n\n* **Presenter**不是一个**OnClickListener**。**View**主要是负责处理用户输入并调用presenter相应的方法。那么问题来了，为什么不把**Presenter** 直接做成一个**OnClickListener**，从而把“转发流程”给省略掉呢？大家想想，如果这样做的话，首先，**presenter**需要知道view的内部构件。举个例子，如果一个View有两个按钮，且这个view在这两个按钮上都把**Presenter** 注册成**OnClickListener**的话，那么发生点击事件时Presenter （在不知道view中按钮引用等内部构件的情况下）怎么能够区分出是哪一个按钮被点击了呢？Model，View和Presenter三者应解耦。其次，如果让Presenter 执行OnClickListener，Presenter就被绑定到了Android平台上。理论上来说**presenter**和业务逻辑层都是纯旧式的能够与桌面应用或其他任何java应用共享的java代码。\n\n* 大家在第1步和第2步中可以看到，**View** 只执行**Presenter** 指示的操作：用户点击“load user button”（第1步）后，view并没有直接显示加载动画，而是在第2步presenter明确告诉其显示加载动画后才显示的。这一Model-View-Presenter的变体称之为MVP 被动视图。这个view可以说是要多笨有多笨。这时我们需要让presenter以一种更抽象的方式来控制view。比如，presenter在调用 **view.showLoading()** 时并不控制view的诸如动画等具体事项。所以presenter不应调用**view.startAnimation()** 等方法。\n\n* 通过执行**MVP**被动视图，并发性以及多线程更容易处理。大家可以看到，第3步中数据库查询异步运行，并且**presenter作为Listener/Observer**，在数据准备显示时presenter收到通知。\n\n##**Android**上的**MVP**\n\n目前为止一切顺利。但是大家怎么样把MVP运用到自己的Android 应用上呢？第一个问题在于，我们要把MVP模式运用到什么地方？Activity上、Fragment上、还是像RelativeLayout这类的ViewGroup上？我们来看看Android平板上的Gmail应用，如图1-3：\n\n![](http://hannesdorfmann.com/images/mosby/mvp-gmail.png)\n\n在我看来，上图屏幕中有四个可以使用MVP的地方。我所说的“可以使用MVP的地方”是指屏幕上显示的、在逻辑上属于一个整体的UI元素。因此这些地方也可以称为是可以运用MVP的一个单独的UI单元。如图 1-4.\n\n![](http://hannesdorfmann.com/images/mosby/mvp-gmail-candidates.png)\n\n看起来MVP似乎很适合运用到Activity，特别是Fragment上。通常Fragment只负责显示单一的如ListView之类的内容，就像依靠**MailProvider** 来获取一系列**Mails**的**InboxPresenter** 控制下的 **InboxView**一样。但是，**MVP**不仅仅限于Fragment或Activity，它还可以运用到SearchView中显示的ViewGroup中。在我的大多数app里面我都在Fragment运用MVP模式。但是大家可以自行决定把MVP运用到什么地方，前提是view是独立的，这样这样presenter才能在不与其他Presenter冲突的情况下控制View。\n\n##我们为什么要实现**MVP**？\n\n我们如何在不使用MVP模式时显示Email列表到Fragment? 通常，我们需要获取并且合并本地SQL数据库和从IMAP邮件服务器获取的邮件列表，然后将邮件列表绑定到收件箱view中。那么，此时fragment的代码又会是怎么样的呢？我们需要运行两个**AsyncTasks** 并实现一个“等待机制”（等到两个任务将两者的加载数据合并到一个单独的mail列表）。我们还需要注意的是在加载时要显示加载动画（ProgressBar），之后用ListView替代。我们需要把所有的代码放到Fragment中吗？要是加载过程中出现错误怎么办？屏幕翻转怎么办？谁来负责撤销**AsyncTasks** ？这一系列的问题都可以通过MVP得到解决。让我们跟那些带有上千行大杂烩代码的activity和fragment说拜拜吧\n\n但是，在我们深入研究如何将MVP运用到Android中之前，我们需要弄清楚的一个问题是：Activity或Fragment究竟是一个View还是一个Presenter。Activity或Fragment似乎既是**View**也是**Presenter**，因为它们都有 **onCreate()** 或**onDestroy()**之类的生命周期回调功能，并且它们负责从一个UI控件到另一个UI 控件的转换（比如在加载时显示ProgressBar，然后显示带有数据的ListView）等View操作。大家可能会觉得这里的Activity或Fragment就是一个Controller，我猜可能也是这么一个初衷。但是在经历了几年的Android应用开发之后，我得出这么一个结论：我们应该把Activity或Fragment看作是一个不太智能的View，而不是把它们看作一个Presenter。后文我会给出原因。\n\n综上，我想给大家介绍一个在Android平台上开发基于MVP的应用的一个 **Mosby**库。\n**Mosby**\n\n大家可以在[Github](https://github.com/sockeqwe/mosby)和Maven Central上找到Mosby库。Mosby分为几个子模块，大家可以根据自己的需要选取组件。我们来回顾一下最重要的一个模块。\n\n##核心模块 ( Core Module)\n\n《老爸老妈浪漫史》中的建筑设计师Ted Mosby想建造一栋摩天大楼。而建造这样一栋宏伟的建筑必须打好坚实的地基。这对Android应用的开发来说是也是一样的道理。基本上，**Core Module** 分为两种类型：**MosbyActivity** 和**MosbyFragment**。这两者是所有其他activity或fragment子类的基类（相当于建筑的地基）。两者都使用我们大家所熟知的APT **（Annotation Processing Tool）**来减少一些样板式代码。**MosbyActivity** 和**MosbyFragment** 使用Butterknife进行view的注入，使用Icepick 将实例状态保存和存储到Bundle中，使用FragmentArgs注入Fragment参数。我们不需要再调用Butterknife.inject(this)等插入方法。这类代码已经包含在了MosbyActivity 和 MosbyFragment中。它是即时可用的。我们需要做的就是使用子类中相应的注解。核心模块与MVP没有关联，它只是写一个大型软件的基础。\n\n##MVP模块( MVP Module )\n\nMosby库中的MVP模块使用泛型来确保类型安全。所有view的基类是**MvpView**。从根本上说这只是一个空的interface 。Presenter的基类是**MvpPresenter**：\n\n```\npublic interface MvpView{}\n\npublic interface MvpPresenter<V extends MvpView>{\n\tpublic void attachView(V view);\n\tpublic void detachView(boolean retainInstance);\n}\n```\n上文提到，我们把**Activity**和**Fragment**看做View。因此Mosby库的MVP模块提供了 属于**MvpViews** 的**MvpActivity**和**MvpFragment**作为**Activity**和**Fragment**的基类。\n\n```\npublic abstract class MvpActivity<P extends MvpPresenter> extends MosbyActivity implements MvpView{\n\n\tprotected P presenter;\n\t@Override  protected void onCreate(Bundle savedInstanceState){\n\t\tsuper.onCreate(savedInstanceState);\n\t\tpresenter = createPresenter();\n\t\tpresenter.attachView(this);\n\t\tsuper.onDestroy();\n\t\tpresenter.detachView(false);\n\t}\n\n\tprotected abstract PcreatePresenter();\n}\n\npublic abstract class MvpFragment<P extends MvpPresenter> MosbyFragment implements MvpView{\n\tprotected Ppresenter;\n\n\t@Override public void onViewCreated(View view,@Nullable Bundle savedInstanceState){\n\t\tsuper.onViewCreated(view,savedInstanceState);\n\t\t// Create the presenter if needed\n\t\tif(presenter == null){\n\t\t\tpresenter = createPresenter();\n\t\t}\n\t\tpresenter.attachView(this);\n\t}\n\n\t@Override public void onDestroyView(){\n\t\tsuper.onDestroyView();\n\t\tpresenter.detachView(getRetainInstance());\n\t}\n\t\n\tprotected abstract PcreatePresenter();\n\t}\n}\n\n@Override protected void onDestroy(){\n\n```\n\n这一理念主要是一个**MvpView** (也就是Fragment or Activity)会关联一个MvpPresenter，并且管理**MbpPresenter**的声明周期。大家从上面的代码片段可以看到，Mosby使用Activity和Fragement生命周期来实现这一目的。通常presenter是绑定在该生命周期上的。所以初始化或者清理一些东西等操作（例如撤销异步运行任务）应该在 **presenter.onAttach()**和 presenter.onDetach()上进行。我们稍后会谈到presenter如何使用setRetainInstanceState(true) “避开”Fragment中的生命周期。我相信大家也注意到了， MvpPresenter是一个interface 。MVP模块提供一个 **MvpBasePresenter**，这个**MvpBasePresenter**只持有View（是一个Fragment或Activity）的弱引用，从而避免内存泄露。因此，当**presenter**想要调用view方法时，我们需要查看**isViewAttached()** 并使用**getView()**来获取引用，以检查view是否连接到了presenter。\n\n##Loading-Content-Error (LCE)\n\n通常Fragment会一直重复做某一件事。它在后台加载数据，同时显示加载view（即ProgressBar），并在屏幕上显示加载的数据，或者当加载失败时显示view错误。如今，下拉刷新支持很容易实现，因为SwipeRefreshLayout是Android支持库的组成部分。为了避免重复执行这一工作流，Mosby库的**MVP**模块提供了**MvpLceView**。\n\n```\npublic interface MvpLceView<M> extends MvpView{\n\t/**\n\t   * 显示一个加载中的视图\n\t   * loading view 必须有个id 为 R.id.loadingView的View\n\t   * @param pullToRefresh 如果是true,那么表示下拉刷新被触发了\n\t   */\n\tpublic void showLoading(boolean pullToRefresh);\n\t/**\n\t   * 显示 content view.\n\t   * <content view 的id必须是R.id.contentView\n\t   */\n\tpublic void showContent();\n\n\t/**\n\t   * 显示错误信息\n\t   * @param e The Throwable that has caused this error\n\t   * @param pullToRefresh true, if the exception was thrown during pull-to-refresh, otherwise\n\t   * false.\n\t   */\n\tpublic void showError(Throwable e,boolean pullToRefresh);\n\n\t/**\n\t   * The data that should be displayed with {@link #showContent()}\n\t   */\n\tpublic void setData(M data);\n}\n```\n\n针对那种类型的view我们可以采用 **MvpLceActivity  implements  MvpLceView 和 MvpLceFragment  implements  MvpLceView。**两者均假设解析的xml布局包括了含有**R.id.loadingView,R.id.contentView和R.id.errorView的view**。\n\n示例\n接下来要举的例子[Github](https://github.com/sockeqwe/mosby/tree/master/sample)上也有中，我们使用CountriesAsyncLoader加载一系列的Country，并将其显示在Fragment的RecyclerView中。大家可以从这个链接 [https://db.tt/ycrCwt1L](https://db.tt/ycrCwt1L )下载。\n\n首先我们要定义**CountriesView**这一view interface 。\n\n```\npublic interface CountriesView extends MvpLceView<List<Country>>{\n}\n```\n\n为什么要为View定义接口呢？\n1.因为定义了这个接口之后我们可以更改view的实现。我们可以简单地把代码从一个继承自 Activity的实现转移到继承自 Fragment的实现。\n\n2.模块性：我们可以移动独立的库项目中的整个业务逻辑层、Presenter以及View 接口，然后把这个包含了Presenter的库应用到各类app当中。下图中左侧是使用了嵌入在ViewPager中的Activity的**kicker app**，以及使用嵌入在ViewPager中的Fragment的**meinVerein app**，如图1-5。 两者采用的是同一个定义了View接口和Presenter且测试了单元的库。\n\n![](http://hannesdorfmann.com/images/mosby/mvp-reuse.png)\n\n由于我们可以通过执行view接口来模拟view，所以我们可以很容易地编写单元测试。还有一个更简单的方法就是在presenter中引入java接口并使用模拟presenter对象来编写单元测试。\n还有一个良性副作用就是，定义了view接口之后，我们不用直接从presenter再回调activity/fragment方法。我们这样区分开来是因为在执行presenter时我们在IDE自动完成上看到的方法只是关于view接口的方法。就我个人体会来说，我觉得这个方法非常有用，特别是团队一起工作的时候。需要注意的是，除了定义一个CountriesView接口之外，我们还可以采用**MvpLceView<List<Country>>** 。但是，定义一个专门的接口可以提高代码可读性，并且将来可以灵活地定义更多其他的与View相关的方法。\n\nNext we define our views xml layout file with the required ids:\n\n下一步我们需要按照指定的id来定义view xml 布局文件.\n\n```xml\n<FrameLayoutxmlns:android=\"http://schemas.android.com/apk/res/android\"\nandroid:layout_width=\"match_parent\"\nandroid:layout_height=\"match_parent\"\n>\n\n\t<!-- Loading View -->\n\t<ProgressBar\n\tandroid:id=\"@+id/loadingView\"\n\tandroid:layout_width=\"wrap_content\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_gravity=\"center\"\n\tandroid:indeterminate=\"true\"\n\t/>\n\n\t<!-- Content View -->\n\t<android.support.v4.widget.SwipeRefreshLayout\n\tandroid:id=\"@+id/contentView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\t>\n\n\t<android.support.v7.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t/>\n\n\t</android.support.v4.widget.SwipeRefreshLayout>\n\n\n\t<!-- Error view -->\n\t<TextView\n\t\tandroid:id=\"@+id/errorView\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t/>\n\n</FrameLayout>\n```\n\nCountriesPresenter控制CountriesView并运行CountriesAsyncLoader。\n\n```java\npublic class CountriesPresenter extends MvpBasePresenter<CountriesView>{\n\n\t@Override \n\tpublic void loadCountries(final boolean pullToRefresh){\n\t\tgetView().showLoading(pullToRefresh);\n\t\t\n\t\tCountriesAsyncLoader countriesLoader = new CountriesAsyncLoader(\n\t\tnew CountriesAsyncLoader.CountriesLoaderListener(){\n\n\t\t@Override public void onSuccess(List<Country> countries){\n\n\t\t\tif(isViewAttached()){\n\t\t\t\tgetView().setData(countries);\n\t\t\t\tgetView().showContent();\n\t\t\t}\n\t\t}\n\n\t\t@Override public void onError(Exception e){\n\n\t\t\tif(isViewAttached()){\n\t\t\t\tgetView().showError(e,pullToRefresh);\n\t\t\t}\n\t\t}\n\t});\n\n\t\tcountriesLoader.execute();\n\t}\n}\n```\n\n实现**CountriesView**接口 的**CountriesFragment** 如下所示：\n\n```java\npublic class CountriesFragment\n extends MvpLceFragment<SwipeRefreshLayout,List<Country>,CountriesView,CountriesPresenter>\n implements CountriesView,SwipeRefreshLayout.OnRefreshListener{\n\n\t@InjectView(R.id.recyclerView)RecyclerViewrecyclerView;\n\tCountriesAdapteradapter;\n\n\t@Override public void onViewCreated(View view,@Nullable Bundle savedInstance){\n\tsuper.onViewCreated(view,savedInstance);\n\n\t\t// Setup contentView == SwipeRefreshView\n\t\tcontentView.setOnRefreshListener(this);\n\n\t// Setup recycler view\n\t\tadapter = new CountriesAdapter(getActivity());\n\t\trecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n\t\trecyclerView.setAdapter(adapter);\n\t\tloadData(false);\n\t}\n\n\tpublic void loadData(boolean pullToRefresh){\n\t\tpresenter.loadCountries(pullToRefresh);\n\t}\n\n\t@Override protected CountriesPresenter createPresenter(){\n\t\treturn new SimpleCountriesPresenter();\n\t}\n\n\t// Just a shorthand that will be called in onCreateView()\n\t@Override protected int getLayoutRes(){\n\t\treturn R.layout.countries_list;\n\t}\n\n\t@Override public void setData(List<Country> data){\n\t\tadapter.setCountries(data);\n\t\tadapter.notifyDataSetChanged();\n\t}\n\n\t@Override public void onRefresh(){\n\t\tloadData(true);\n\t}\n}\n```\n\n代码数量也并不是很多嘛，对吧？这是因为基类已经执行了从加载view到content view或error view的转换。我们可能第一眼看到那一列**MvpLceFragment**类属参数会觉得灰心。但是我要解释一下：第一种类属参数代表的是content view的类型；第二种是指以fragment显示的Model；第三种是View接口;最后一种是Presenter的类型。总结起来就是：**MvpLceFragment<AndroidView, Model, View接口, Presenter>**。\n\n大家可能还注意到的一个点就是 **getLayoutRes()**，它是**MosbyFragment**引入的用于解析xml view布局的速记法。\n\n```java\n@Override public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState){\n\tReturn  inflater.inflate(getLayoutRes(),container,false);\n}\n```\n\n因此，我们不用重写**onCreateView()**，只需重写**getLayoutRes()**。一般来说，onCreateView()只能创建view而**onViewCreated()**需要被重写，以便为RecyclerView初始化Adapter等项。因此，千万不要忘记调用super.OnViewCreated()；\n\n##ViewState模块\n\n看到这里大家应该大概了解了如何运用Mosby库。Mosby中的ViewState模块能帮助我们在Android开发中解决一些棘手的难题：处理屏幕旋转。\n\n问：如果把正在运行country这个例子的app并显示了一列country的设备从横屏旋转到竖屏，会出现什么情况？\n\n\n答：大家到这个视频链接[https://youtu.be/9iSBGEIZmUw](https://youtu.be/9iSBGEIZmUw)中看看，结果是一个新的 CountriesFragment会被实例化，app开始显示ProgressBar（并重新加载country列表）而不再在RecyclerView中显示country列表（屏幕旋转前的状态）\n\nMosby引入了**ViewState**来解决这个问题。原理就是，我们跟踪presenter从关联的View中调用的方法。比如，**presenter**调用的是view.showContent()，一旦showContent()被调用，view就会意识到其状态变更为**“showing content”**，从而view把这一信息存储到一个ViewState。如果view在方向改变过程中遭到破坏，那么ViewState 就会被存储到**Activity.onSaveInstanceState(Bundle) 或 Fragment.onSaveInstanceState(Bundle)中，并在Activity.onCreate(Bundle) 或Fragment.onActivityCreated(Bundle)**中修复。\n\n由于不是每种数据都能存储在Bundle中，所以不同的数据类型采用不同的ViewState 实现：数据类型**ArrayList<Parcelable >**采用**ArrayListLceViewState**；数据类型Parcelable 采用**Parcelable DataLceViewState**；数据类型**Serializeable**采用**SerializeableLceViewState**。如果使用的是一个可保持( Retaining )的Fragment，那么 ViewState在屏幕旋转时不会被破坏，所以也就不需要存储到Bundle中。因此，它可以存储任何类型的数据。在这种情况下，我们需要使用**RetainingFragmentLceViewState**。存储一个ViewState比较容易。由于我们的架构比较整洁，我们的View又有接口，ViewState 可以向presenter一样通过调用同样的接口方法来修复相关联的view。举个例子，MvpLceView一般有3种状态，即：显示**showContent()，showLoading()和showError()**，所以ViewState本身会调用相应的方法来修复view的状态。\n\n\n那只是一些内部构件。如果大家想编写自定义的ViewState，了解以上内容就够了。ViewStates的使用非常简单。事实上，要把MvpLceFragment 迁移到MvpLceViewStateFragment ，我们只需要另外执行**createViewState()** 和 **getData()**。下面我们就在CountriesFragment中实践一下吧：\n\n```java\npublic class CountriesFragment\n extends MvpLceViewStateFragment<SwipeRefreshLayout,List<Country>,CountriesView,CountriesPresenter>\n implements CountriesView,SwipeRefreshLayout.OnRefreshListener{\n\n\t@InjectView(R.id.recyclerView)RecyclerView recyclerView;\n\tCountriesAdapter adapter;\n\n\t@Override public LceViewState<List<Country>,CountriesView> createViewState(){\n\t\treturn new RetainingFragmentLceViewState<List<Country>,CountriesView>(this);\n\t}\n\n\t@Override public List<Country> getData(){\n\t\treturn adapter == null? null : adapter.getCountries();\n\t}\n\n\t// The code below is the same as before\n\n\t@Override public void onViewCreated(Viewview,@Nullable Bundle savedInstance){\n\tsuper.onViewCreated(view,savedInstance);\n\n\t// Setup contentView == SwipeRefreshView\n\tcontentView.setOnRefreshListener(this);\n\n\t// Setup recycler view\n\t\tadapter = new CountriesAdapter(getActivity());\n\t\trecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n\t\trecyclerView.setAdapter(adapter);\n\t\tloadData(false);\n\t}\n\n\n\tpublic void loadData(boolean pullToRefresh){\n\t\tpresenter.loadCountries(pullToRefresh);\n\t}\n\n\t@Override protected CountriesPresenter createPresenter(){\n\t\treturn new SimpleCountriesPresenter();\n\t}\n\n\t// Just a shorthand that will be called in onCreateView()\n\t@Override protected int getLayoutRes(){\n\t\treturn R.layout.countries_list;\n\t}\n\n\t@Override public void setData(List<Country> data){\n\t\tadapter.setCountries(data);\n\t\tadapter.notifyDataSetChanged();\n\t}\n\n\t@Override public void onRefresh(){\n\t\tloadData(true);\n\t}\n}\n```\n\n以上就是全部过程啦。我们不必更改presenter或其他代码。[这里](https://youtu.be/9iSBGEIZmUw ) 是一个关于我们的获得ViewState支持的CountriesFragment的视频。在这个视频中我们可以看到，view在方位转变之后仍然处于同样的“状态”，即，view横屏显示country列表，随后横屏显示country列表。View能横屏显示下拉刷新指示，变更为竖屏时也能显示。\n\n##自定义ViewState\n\nViewState确实是一个强大且灵活的概念。看到这里我相信大家都了解了LCE (Loading-Content-Error) ViewState的易用性。下面我们就一起来编写自己的View和ViewState吧。我们的View只显示两类不同的数据对象：A和B。结果应该像这个视频 [https://youtu.be/9iSBGEIZmUw](https://youtu.be/9iSBGEIZmUw ) 中演示的这样：\n\n大家心里肯定觉得，这也不怎么样啊！别介啊，我只是想演示一下创建自己的ViewState是一件多么容易的事。\n\nView 接口和数据对象（model）如下所示：\n\n```java\npublic class A implements Parcelable {\n\tString  name;\n\n\tpublic A(String  name){\n\t\tthis.name=name;\n\t}\n\n\tpublic String  getName(){\n\t\treturn name;\n\t}\n}\n\npublic class B implements Parcelable {\n\tString  foo;\n\n\tpublic B(String  foo){\n\t\tthis.foo=foo;\n\t}\n\n\tpublic String  getFoo(){\n\t\treturn foo;\n\t}\n}\n\npublic interface MyCustomView extends MvpView{\n\n\tpublic void showA(A a);\n\n\tpublic void showB(B b);\n}\n```\n\n在这个简单的例子中我们没有加入业务逻辑层。因为我们假设在实际的app中如果有业务逻辑层的话会使整个生成A或B的操作变得复杂。**Presenter**如下所示：\n\n```java\npublic class MyCustomPresenter extends MvpBasePresenter<MyCustomView>{\n\tRandom random = new Random();\n\n\tpublic void doA(){\n\n\t\tA a = new A(\"My name is A \"+random.nextInt(10));\n\n\t\tif(isViewAttached()){\n\t\t\tgetView().showA(a);\n\t\t}\n\t}\n\n\tpublic void doB(){\n\t\tB b = new B(\"I am B \"+random.nextInt(10));\n\n\t\tif(isViewAttached()){\n\t\t\tgetView().showB(b);\n\t\t}\n\t}\n}\n\n```\n\n\n我们定义了实现了MyCustomView接口的**MyCustomActivity**。\n\n```java\npublic class MyCustomActivity extends MvpViewStateActivity<MyCustomPresenter>\n implements MyCustomView{\n\n\t@InjectView(R.id.textViewA) TextViewaView;\n\t@InjectView(R.id.textViewB) TextViewbView;\n\n\t@Override protected void onCreate(Bundle savedInstanceState){\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.my_custom_view);\n\t}\n\n\t@Override public RestoreableViewState createViewState(){\n\t\treturn new MyCustomViewState();// Our ViewState implementation\n\t}\n\n\t// Will be called when no view state exist yet,\n\t// which is the case the first time MyCustomActivity starts\n\t@Override public void onNew ViewStateInstance(){\n\t\tpresenter.doA();\n\t}\n\n\t@Override protected MyCustomPresenter createPresenter(){\n\t\treturn new MyCustomPresenter();\n\t}\n\n\t@Override public void showA(A a){\n\t\tMyCustomViewState vs = ((MyCustomViewState)viewState);\n\t\tvs.setShowingA(true);\n\t\tvs.setData(a);\n\t\taView.setText(a.getName());\n\t\taView.setVisibility(View.VISIBLE);\n\t\tbView.setVisibility(View.GONE);\n\t}\n\n\t@Override public void showB(B b){\n\t\tMyCustomViewState vs=((MyCustomViewState)viewState);\n\t\tvs.setShowingA(false);\n\t\tvs.setData(b);\n\t\tbView.setText(b.getFoo());\n\t\taView.setVisibility(View.GONE);\n\t\tbView.setVisibility(View.VISIBLE);\n\t}\n\n\t@OnClick(R.id.loadA)public void onLoadAClicked(){\n\t\tpresenter.doA();\n\t}\n\n\t@OnClick(R.id.loadB)public void onLoadBClicked(){\n\t\tpresenter.doB();\n\t}\n}\n```\n\n由于我们没有LCE(Loading-Content-Error)，所以不把 **MvpLceActivity**作为基类。我们采用的是最普遍的支持 ViewState的**MvpViewStateActivity**作为基类。基本上我们的View只显示aView 或 bView。\n\n在**onNew ViewStateInstance()**中，我们需要明确在第一个Activity运行时需要做什么，因为先前并不存在ViewState 例子用于修复。在showA(A a) 和 showB(B b)中，我们需要将显示A 或 B的信息存储到ViewState。到这一步，我们就差不多完成了，现在只差MyCustomViewState执行这一步啦：\n\n```java\nublic class MyCustomViewState implements RestoreableViewState<MyCustomView>{\n\n\tprivate  final String  KEY_STATE=\"MyCustomViewState-flag\";\n\tprivate  final String  KEY_DATA=\"MyCustomViewState-data\";\n\n\tpublic boolean showingA=true;// if false, then show B\n\tpublic Parcelable  data;// Can be A or B\n\n\t@Override public void saveInstanceState(Bundle out){\n\t\tout.putBoolean (KEY_STATE,showingA);\n\t\tout.putParcelable (KEY_DATA,data);\n\t}\n\n\t@Override public boolean restoreInstanceState(Bundle in){\n\t\tif(in==null){\n\t\t return false;\n\t\t}\n\n\t\tshowingA = in.getBoolean (KEY_STATE,true);\n\t\tdata = in.getParcelable (KEY_DATA);\n\t\treturn true;\n\t}\n\n\t@Override public void apply(MyCustomView view,boolean retained){\n\n\t\tif(showingA){\n\t\t\tview.showA((A)data);\n\t\t}else{\n\t\t\tview.showB((B)data);\n\t\t}\n\t}\n\n\t/**\n\t   * @param a true if showing a, false if showing b\n\t   */\n\tpublic void setShowingA(boolean a){\n\t\tthis.showingA=a;\n\t}\n\n\tpublic void setData(Parcelable data){\n\t\tthis.data=data;\n\t}\n}\n```\n\n大家可以看到，我们需要把**ViewState**保存到从**Activity.onSaveInstanceState()**调用的 **saveInstanceState()**中，并且在从Activity.onCreate()调用的**restoreInstanceState()**中修复viewstate的数据。apply()方法将会从Activity中调用以修复view state。我们像presenter一样通过调用同样的View interface  方法showA() 或 showB()来实现这一操作。\n\n大家可以看到，我们需要把ViewState保存到从**Activity.onSaveInstanceState()**调用的 **saveInstanceState()**中，并且在从Activity.onCreate()调用的**restoreInstanceState()**中修复viewstate的数据。apply()方法将会从Activity中调用以修复view state。我们像presenter一样通过调用同样的View interface  方法showA() 或 showB()来实现这一操作。\n\n这个外部的**ViewState**把view state修复的复杂性和职责从Activity代码中剥离，并入到这个单独的类中。而编写**ViewState**类的单元测试要比Activity类的单元测试容易得多。\n\n##怎样处理后台线程？\n通常，**Presenter**会管理后台线程。Presenter如何处理后台线程取决于它所关联的Activity或者Fragment ，具体分为两种情况：\n\n* 可保持的Fragment : 如果你调用了Fragment的setRetainInstanceState(true)那么这个Fragment在屏幕旋转时就不会被销毁。只有该Fragment的GUI会被销毁，并且在屏幕旋转时重新调用onCreateView创建视图。这就是说当屏幕旋转时Fragment所有的成员成员变量和Presenter不会发生变化。在这个示例中，我们将新的视图关联到Presenter中。因此，Presenter不需要去掉任何正在运行中的后台任务，因为Presenter已经关联了新的视图。例如:\n\n1.竖屏情况下启动应用\n2.实例化Fragment时会调用onCreate()、onCreateView()、createPresenter(), 然后通过调用presenter的attachView()函数将View关联到Presenter中。\n3. 下一步我们旋转手机屏幕，从竖屏切换到横屏；\n4. 此时onDestroyView() 会调用，而onDestroyView() 又会调用presenter的detachView(true)函数。我们注意到detachView有个参数为true,这是告诉presenter这个Fragment是可持有的Fragment（否则这个参数应该为false）。通过这个参数，presenter就知道它不需要取消正在运行的后台任务；\n5. 应用现在是横屏状态了，在旋转时onCreateView方法会被调用，但是createPresenter()函数不会被调用，因为我们会对presenter 进行不为空的判断，当presenter为空时才调用createPresenter()函数。而Fragment的setRetainInstanceState(true)会保持这个presenter对象，因此presenter此时不会被重新创建；\n6. 在调用了presenter的attachView()之后新创建的View会被重新关联到presenter中。\n7. ViewState会被恢复，但是没有后台任务会被取消，因此也没有后台任务需要重新启动。\n\n\n* Activity和不保持的Fragment :在这个示例中工作流非常的简单。所有的东西都会被销毁，包括presenter。因此presenter对象应该取消所有正在运行的任务。例如 :\n我们采用非保持fragment在竖屏情况下启动app。\n\n8.我们采用非保持fragment在竖屏情况下启动app。\n9.Fragment被实例化之后，调用onCreate()， onCreateView()，和createPresenter()，然后通过调用**presenter.attachView()**将**view（fragment）**附着到presenter。\n10.下一步我们旋转设备屏幕，从竖屏切换到横屏。\n11.此时onDestroyView() 会调用，而**onDestroyView()** 又会调用**presenter**的**detachView(true)**函数。**Presenter**取消后台任务。\n12. **onSaveInstanceState(Bundle)**被调用， **ViewState**被保存到Bundle中。\n13. **App**现在出于横屏状态。新的Fragment被实例化并调用onCreate()，onCreateView()和 **createPresenter()**来创建一个新的presenter例子，通过调用**presenter.attachView()**将新的view附着到新的presenter\n14. **ViewState**会从**Bundle**中恢复，且view的状态也会被恢复。如果ViewState是showLoading，那么**presenter**会重新启动后台线程来加载数据。\n15. 以下是获得ViewState支持的Activity的生命周期图解，如图1-6：\n![](http://hannesdorfmann.com/images/mosby/mvp-activity-lifecycle.png)\n\n以下是获得ViewState支持的Fragment的生命周期图解， 如图 1-7：\n![](http://hannesdorfmann.com/images/mosby/mvp-fragment-lifecycle.png)\n\n##Retrofit模块\n\nMosby提供了 **LceRetrofitPresenter** 和 **LceCallback**。为获得LCE方法showLoading(), showContent() 和 showError()支持的Retrofit编写presenter ，几行代码就能搞定。\n\n```java\npublic class MembersPresenter extends LceRetrofitPresenter<MembersView,List<User>>{\n\n\tprivate  GithubApigithubApi;\n\n\tpublic MembersPresenter(GithubApi githubApi){\n\t\tthis.githubApi=githubApi;\n\t}\n\n\tpublic void loadSquareMembers(boolean pullToRefresh){\n\t\tgithubApi.getMembers(\"square\",new LceCallback(pullToRefresh));\n\t}\n}\n```\n\n##Dagger模块\n   想在不依靠注入式的情况下写应用？Ted Mosby告诉你，这是行不通滴！Dagger是java依赖注入式框架最常用的方法，也是Android开发者们的心头好。Mosby支持Dagger1。Mosby通过一个叫做getObjectGraph()的方法提供Injector界面。通常，我们的应用模块非常广泛。要想轻松分享这一模块，我们需要把android.app.Application归入子类，使其执行Injector。之后所有的Activity和Fragment都可以通过调用getObjectGraph()来存取ObjectGraph，因为**DaggerActivity and DaggerFragment**也都是Injector。我们也可以通过重写Activity 或 Fragment中的 getObjcetGraph() ，从而调用plus(Module)以增加模块。我个人已经用到Dagger2了，它与Mosby也兼容。大家可以在Github上找到关于Dagger1 和 Dagger2的示例。点此这个链接[https://db.tt/3fVqVdAz](https://db.tt/3fVqVdAz )Dagger1示例 apk；点此这个链接[https://db.tt/z85y4fSY]( https://db.tt/z85y4fSY )Dagger2 示例 apk。\n\n##Rx模块\n   **Observables**赞爆了！现在稍微潮一点的小伙儿们都用RxJava了好吗！你猜结果怎么着？RxJava确实是太酷了！所以，Mosby给大家提供一个本质上是Subscriber的MvpLceRxPresenter，它能帮我们自动处理**onNext()， onCompleted() 和 onError()并回调相应的LCE方法，比如showLoading(), shwoContent() **和 showError()。它还将 RxAndroid 附带到observerOn() Android主要 UI 线程。你可能觉得，要是用了RxJava的话就不再需要Model View Presenter了。呃，那只是你的一家之言。在我看来，把View和Model清晰地区分开来非常重要。而且我也认为其中的某些好用的功能在没有MVP的情况下不容易执行。最后，大家要是还想回到过去那个Activity和Fragment包含了上千条又臭又长的代码行时代，那么我祝你在面条式代码的地狱里过得愉快。好了，废话不多说，我介绍的方法不属于面条式代码是因为Observerables引入了一个结构齐整的工作流，把Activity或Fragment做成一个BLOB的想法已经近在咫尺了。\n   \n##测试模块\n大家可能注意到这里存在着一个测试模块。这个模块用于Mosby库的内部测试。但是，它也可以为我们自己的app所用。它使用Robolectric为我们的LCE Presenter， Activities 和 Fragments提供单元测试模板。它的基本功能是查看测试中的Presenter是否正确工作：通过观察presenter时候调用showLoading()，\n**showContent()** 和 **showError()**。我们还可以验证setData()中的数据。所以我们可以为Presenter和底层编写类似黑匣子的测试。Mosby的测试模块也提供了测试MvpLceFragment 或 **MvpLceActivity**的可能性。它相当于一种“精简版”的UI 测试。这些测试通过查看xml布局是否包含R.id.loadingView， R.id.contentView 和R.id.errorView之类的指定id、loadingView是否可视，在加载view时，是否是错误的view可视、content view能否处理由setData()提交的已加载数据等方面来检验Fragment或Activity是否正常工作，是否遇到crashing。它和Espresso类的UI测试并不相同。我觉得没有必要为LCE View单独写一个UI 测试。\n\n以下是Ted Mosby库的一些测试小建议：\n1. 编写传统的单元测试来测试业务逻辑层和model。\n2. 使用**MvpLcePresenterTest**来测试presenter。\n3.使用**MvpLceFragmentTest** 和 **MvpLceActivityTest**来测试MvpLceFragment 和 Activity。\n4.如果有必要，可以使用Espresso来编写UI测试。\n\n测试模块尚未完成。大家可以看到这个模块是测试版，因为Robolectric 3.0还没完成，而且Android gradle plugin也没用完全支持传统的单元测试。android gradle plugin \n\n1.2应该会好得多。Robolectric 和 androids gradle plugin可以用了之后我会再写一篇关于Mosby，Dagger，Retrofit和RxJava单元测试的博客。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "issue-12/readme.md",
    "content": "# 2015.5.31 ( 第十二期 )\n| 文章名称 |   译者  | \n|---------|--------|\n| [自动化Android开发](自动化Android开发.md)  | [tmc9031](https://github.com/tmc9031)      |\n| [Android进行单元测试难在哪-part4](Android进行单元测试难在哪-part4.md)  | [chaossss](https://github.com/chaossss)|\n| [当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合](当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合.md)  | [Rocko](https://github.com/Rocko)      |\n| [Ted Mosby - 软件架构](MVP框架Mosby架构详解.md)  | [Mr.Simple](https://github.com/bboyfeiyu)|\n| [Android自动截屏工具](Android自动截屏工具.md)  | [sundroid](https://github.com/sundroid)     |\n| [Android上MVP的介绍](Android上MVP的介绍.md)  | [MiJack](https://github.com/MiJack)     |\n"
  },
  {
    "path": "issue-12/当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合.md",
    "content": "当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合\n---\n\n> * 原文链接 : [When the Avengers meet Dagger2, RxJava and Retrofit in a clean way](http://saulmm.github.io/when-Thor-and-Hulk-meet-dagger2-rxjava-1/)\n* 原文作者 : [Saúl M](http://saulmm.github.io/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [zhengxiaopeng](https://github.com/zhengxiaopeng) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n\n\n最近，许多文章、框架和 android 社区中的讨论都出现关于测试和软件架构方面的内容，就像上次 [Droidcon Spain](http://es.droidcon.com/2015/speakers/) 上所说的，我们专注于做出健壮的程序而不是去开发特性功能。这些现象也意味着 Android 框架和当前 Android 社区的日渐成熟。\n\n如果你是一名 Android 开发者，而到现在你还没听过 [Dagger 2](http://google.github.io/dagger/)、[RxJava](https://github.com/ReactiveX/RxJava)、[Retrofit](http://square.github.io/retrofit/) 这些名词的话你就错过了一些东西了，这个（文章）系列将会把一些关注点放在怎么用一种 [清晰架构](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html) 去综合使用这些框架。\n\n我刚开始的想法是仅仅写一篇文章的，但是看到这些框架中有大量的内容所以我最终决定写一个最少 3 篇的系列文章。\n\n一如既往，所有的代码都放在了 [Github](https://github.com/saulmm/Avengers)，所有的建议、错误提交和评论都欢迎，我可能没那么多时间去回答所有问题，先说声抱歉 ：）\n\n![Avengers](http://androcode.es/wp-content/uploads/2015/05/avengers_list-e1431571424213.png)\n\n\n## 依赖注入与 Dagger 2\n\n弄懂这个框架的工作机制花费了一些时间，所以我将会根据我所学习到的内容用更加清晰的方式写出来。\n\n[Dagger 2](http://google.github.io/dagger/)是基于 [依赖注入](http://en.wikipedia.org/wiki/Dependency_injection) 模式的。\n\n看下下面的代码片段：\n\n``` Java\n    // Thor is awesome. He has a hammer!\n    public class Thor extends Avenger {\n        private final AvengerWeapon myAmazingHammer;\n\n        public Thor (AvengerWeapon anAmazingHammer) {\n            myAmazingHammer = anAmazingHammer;\n        }\n\n        public void doAmazingThorWork () {\n            myAmazingHammer.hitSomeone();\n        }\n    }\n```\n\n雷神（Thor）需要一个 `复仇者武器（AvengerWeapon）` 才能正确工作，依赖注入的基本思想是，如果雷神不是通过构造器创建他自己的 `复仇者武器` 而是在内部自己创建了出来那么他就不能得到很多的优势。如果雷神自己创建出雷锤将会增加耦合度。\n\n`复仇者武器（AvengerWeapon）` 可以是一个接口，根据我们的逻辑可以有不同的实现和注入方式。\n\n在 Android 中，因为框架已经设计好了，我们并不总是能访问构造器，`Activity` 和 `Fragment`  就是这样的例子。\n\n这些依赖注入器框架像 [http://google.github.io/dagger/](http://google.github.io/dagger/)、[Dagger](http://square.github.io/dagger/) 、[Guice](https://github.com/google/guice) 可以给我们带来便利。\n\n使用 [Dagger 2](http://google.github.io/dagger/) 我们可以把之前的代码改写成这样：\n\n``` Java\n// Thor is awesome. He has a hammer!\n    public class Thor extends Avenger {\n        @Inject AvengerWeapon myAmazingHammer;\n\n        public void doAmazingThorWork () {\n            myAmazingHammer.hitSomeone();\n        }\n    }\n```\n\n我们没有直接访问雷神的构造方法，注入器使用了几个指令去创建了雷神的雷锤\n\n``` Java\n    public class ThorHammer extends AvengerWeapon () {\n\n        @Inject public AvengerWeapon() {\n\n            initGodHammer();\n        }\n    }\n```\n\n`@Inject` 注解用于告诉 Dagger 2 构造器有用于创建雷神的雷锤。\n\n## Dagger 2\n\n[Dagger 2](http://google.github.io/dagger/) 由 Google 开发和维护，是 [Square](https://corner.squareup.com/) 的 [Dagger](http://square.github.io/dagger/) 项目的分支。\n\n首先必须配置注解处理器，`android-apt`  插件就是负责这个角色，允许使用注解处理器而不将其插入到最后的 .apk 中。处理器还配置由该处理器所产生的源代码。\n\n`build.gradle` （项目的根目录中）\n\n``` Gradle\ndependencies {\n        ...\n        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'\n    }\n```\n\n`build.gradle` （你的 android module 中）\n\n``` Gradle\napply plugin: 'com.neenbedankt.android-apt'\n\n    dependencies {\n        ...\n        apt 'com.google.dagger:dagger-compiler:2.0'\n    }\n\n```\n\n\n## 组件（Components）、模块（modules）和复仇者\n\n模块负责提供依赖，组件负责注入它们（依赖）。\n\n这是一个例子：\n\n``` Java\n@Module\npublic class AppModule {\n    private final AvengersApplication mAvengersApplication;\n\n    public AppModule(AvengersApplication avengersApplication) {\n        this.mAvengersApplication = avengersApplication;\n    }\n\n    @Provides @Singleton \n    AvengersApplication provideAvengersAppContext () { \n        return mAvengersApplication; \n    }\n\n    @Provides @Singleton \n    Repository provideDataRepository (RestRepository restRepository) { \n        return restRepository; \n    }\n}\n```\n\n这个就是主模块，我们感兴趣的是它的依赖存在于程序的生命周期中，一个通用的上下文和一个取回信息的仓库。\n\n很简单，对吧？\n\n我们在 Dagger 2 中所说的 `@Provides`  注解，如果有需要则必须会创建其依赖。因此如果我们没有给一个特别的依赖指定一个提供者（provider），Dagger 2 将会去寻找有 `@Inject` 注解的构造方法。\n\n组件使用模块去完成依赖注入，看看这个模块的组件：\n\n``` Java\n@Singleton @Component(modules = AppModule.class)\npublic interface AppComponent {\n\n    AvengersApplication app();\n    Repository dataRepository();\n}\n```\n\n这个模块并不由任何的 activity 或者 fragment 去调用，而是通过更复杂的模块，以提供这些需要得到的依赖\n\n``` Java\nAvengersApplication app();\nRepository dataRepository();\n```\n\n组件必须暴露它们的依赖给图（该模块提供的依赖关系），也即是这个模块提供的依赖关系必须对其它组件是可见的，其它的组件有把当前这个组件作为依赖，如果这些依赖关系是不可见的，Dagger 2 将不会注入这些要求的依赖。\n\n下面是我们的依赖关系树：\n\n![tree of dependencies](http://androcode.es/wp-content/uploads/2015/05/Dagger-graph.png)\n\n``` Java\n@Module\npublic class AvengersModule {\n\n    @Provides @Activity\n    List<Character> provideAvengers() {\n\n        List<Character> avengers = new ArrayList<>(6);\n\n        avengers.add(new Character(\n            \"Iron Man\", R.drawable.thumb_iron_man, 1009368));\n\n        avengers.add(new Character(\n            \"Thor\", R.drawable.thumb_thor, 1009664));\n\n        avengers.add(new Character(\n            \"Captain America\", R.drawable.thumb_cap,1009220));\n\n        avengers.add(new Character(\n            \"Black Widow\", R.drawable.thumb_nat, 1009189));\n\n        avengers.add(new Character(\n            \"Hawkeye\", R.drawable.thumb_hawkeye, 1009338));\n\n        avengers.add(new Character(\n            \"Hulk\", R.drawable.thumb_hulk, 1009351));\n\n        return avengers;\n    }\n}\n```\n\n这个模块将会用于一个特别的 activity 的依赖注入，实际上就是负责绘画的复仇者名单：\n\n``` Java\n@Activity \n@Component(\n    dependencies = AppComponent.class, \n    modules = {\n        AvengersModule.class, \n        ActivityModule.class\n    }\n)\npublic interface AvengersComponent extends ActivityComponent {\n\n    void inject (AvengersListActivity activity);\n    List<Character> avengers();\n}\n```\n\n再次，我们暴露出我们的依赖：`List<Character>` 给其它的组件，在这种情况下出现了一个新方法：`void inject (AvengersListActivity activity)` 。**在此方法被调用时**，这些依赖关系将可被消耗，将会被注入给 `AvengerListActivity` 。\n\n\n## 结合所有\n\n我们的类 `AvengersApplication`，将负责提供应用到其他组件的组件，注意，仅仅提供组件而不会用于注入依赖。\n\n再次提醒的是 [Dagger 2](http://google.github.io/dagger/) 是在编译时生成必要的元素，如果你没有构建项目你是找不到 `DaggerAppComponent` 类的。\n\nDagger 2 从你的组件中生成的类的格式：`Dagger$${YourComponent}.` 。\n\n`AvengersApplication.java`\n``` Java\npublic class AvengersApplication extends Application {\n\n    private AppComponent mAppComponent;\n\n    @Override\n    public void onCreate() {\n\n        super.onCreate();\n        initializeInjector();\n    }\n\n    private void initializeInjector() {\n\n        mAppComponent = DaggerAppComponent.builder()\n            .appModule(new AppModule(this))\n            .build();\n    }\n\n    public AppComponent getAppComponent() {\n\n        return mAppComponent;\n    }\n}\n```\n\n`AvengersListActivity.java`\n``` Java\npublic class AvengersListActivity extends Activity \n    implements AvengersView {\n\n    @InjectView(R.id.activity_avengers_recycler) \n    RecyclerView mAvengersRecycler;\n\n    @InjectView(R.id.activity_avengers_toolbar) \n    Toolbar mAvengersToolbar;\n\n    @Inject \n    AvengersListPresenter mAvengersListPresenter;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_avengers_list);\n        ButterKnife.inject(this);\n\n        initializeToolbar();\n        initializeRecyclerView();\n        initializeDependencyInjector();\n        initializePresenter();\n    }\n\n    private void initializeDependencyInjector() {\n\n        AvengersApplication avengersApplication = \n            (AvengersApplication) getApplication();\n\n        DaggerAvengersComponent.builder()\n            .avengersModule(new AvengersModule())\n            .activityModule(new ActivityModule(this))\n        .appComponent(avengersApplication.getAppComponent())\n            .build().inject(this);\n    }\n```\n\n当 `initializeDependencyInjector()` 中执行到 `.inject(this)` 中时 [Dagger 2](http://google.github.io/dagger/) 就会开始工作并提供必要的依赖关系，请记住 [Dagger 2](http://google.github.io/dagger/) 在注入时是严格执行的，我要说的意思是组件必须是 **完全相同** 的组件类，此组件类负责调用 `inject()` 方法。\n\n`AvengersComponent.java`\n``` Java\n...\npublic interface AvengersComponent extends ActivityComponent {\n\n    void inject (AvengersListActivity activity);\n    List<Character> avengers();\n}\n```\n\n否则，依赖关系将不会被解决。在如下情况，presenter 与由 Dagger 2 提供的复仇者一起初始化：\n\n``` Java\npublic class AvengersListPresenter implements Presenter, RecyclerClickListener {\n\n    private final List<Character> mAvengersList;\n    private final Context mContext;\n    private AvengersView mAvengersView;\n    private Intent mIntent;\n\n    @Inject public AvengersListPresenter (List<Character> avengers, Context context) {\n\n        mAvengersList = avengers;\n        mContext = context;\n    }\n```\n\n[Dagger 2](http://google.github.io/dagger/) 将会解决这个 presenter，因为其有 `@Inject` 注解。该构造方法的参数由 Dagger 2 解决，因为它知道怎么去构建它，这得益于这个模块中的 `@Provides` 方法。\n\n\n## 总结\n\n像 [Dagger 2](http://google.github.io/dagger/) 这样，使用好了依赖注入器，其力量是无可争辩的，想象下根据该框架提供的 API 级别你可以有不同的策略，其可能性是无止境的。\n\n## 资源\n\n-  **Chiu-Ki Chan** - [Dagger 2 + Espresso + Mockito](http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html)\n\n- **Fernando Cejas** - [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/)\n\n- **Google Developers** - [Dagger 2, A new type of dependency injection](https://www.youtube.com/watch?v=oK_XtfXPkqw)\n\n- **Mike Gouline** - [Dagger 2, Even sharper, less square](http://blog.gouline.net/2015/05/04/dagger-2-even-sharper-less-square/)\n"
  },
  {
    "path": "issue-12/自动化Android开发.md",
    "content": "自动化 Android 开发\n---\n\n> * 原文链接：[Automating Android development](https://medium.com/google-developer-experts/automating-android-development-6daca3a98396)\n> * 原文作者：[Enrique López Mañas](https://medium.com/google-developer-experts/automating-android-development-6daca3a98396)\n> * 译者：[tmc9031](https://github.com/tmc9031)\n> * 校对者：[Mr.Simple](https://github.com/bboyfeiyu)\n> * 状态：完成\n\n\n\n我最近已经在 [DroidCon Spain](http://es.droidcon.com/2015/) 和 [DroidCon Italy](http://it.droidcon.com/2015/) 讨论过关于如何自动化传统的Android工作流。\n令我惊讶的是，仍然还有很多组织缺少执行持续集成（CI）的策略。这是一个巨大的灾难！\n我决定用文字表达我的思想，就是如何高效的实现持续集成（CI）。\n\n作为一个软件攻城狮，你的目标应该是尽可能多的自动化许多工作流程。\n计算机比人更高效，它们不需要吃饭睡觉，它们的任务表现没有错误，并且它们使你的生活更轻松。\n请记住：*做的越多是为了了做的更少*\n\n持续集成（CI）尽管是一个包含许多不同要点的综合领域，并且你需要把它们整合在一起。\n比如，你需要讨论 Jira，你需要关心测试和分支，你需要脚本和构建。\n\n这里有一大块我想在这个帖子中介绍的。他们的每一点都值得展开介绍，但这不是这篇文章的目的。其目的是展示给你每个基础知识，以及如何组合他们。\n\n1. 定义一个分支策略。\n2. 使用敏捷方法\n3. Gradle和构建脚本\n4. 测试\n5. 使用CI服务器。\n\n\n### 分支策略\n\n\n分支是重要的。当你正在构建一个新的产品，你想建立一个协议如何工作。\n人们怎么应该提交自己的特性？我们如何发布？我们如何保证不正在破坏什么东西？\n要回答这些问题，你需要采用分支策略。\n\n我正在使用一个经过轻微修改的由 [Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/) 提出的分支策略。\n让我们考虑我们应用程序的三种状态：**alpha**, **beta** 和 **release**.\n\n* **Alpha** 的状态是你的系统正在开发。\n* **Beta** 当你测试的功能已被批准和合并。\n* **Release** 是当系统可交付的状态。\n\n> (一些人喜欢叫 alpha 为 “develop” ，并且 beta 为 “stage”. 我认为希腊字母表的字母总是更cool的).\n\n下面的图片是一个项目的第一个状态。你有一个初始的提交到了master分支。\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*CWNjrbmm5jQ0uvp-L53EYg.png)\n\n我们的系统才刚刚开始。只有第一个提交在master分支，其他分支还是空的。\n追着工作时间的进行。你需要从初始状态进入到develop过程。这将是你的1.0.1版本。\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*JLekFIGh6ciEvj52w3hddA.png)\n\n> 在这个点上，alpha的确与master相同\n\n现在，你会在功能特性上开始工作。对于每一个特性，你会创建一个分支。\n使用正确的命名是很重要的，有几种方法可以做到这一点。如果您使用的是一个问题跟踪系统像 [Jira](https://www.atlassian.com/software/jira)，你可能会用一个与功能相关联的标签名（比如 FEATURE-123）。\n当我提交特性时，我会包括分支名在提交信息里，并添加一个完整的描述。\n\n    *[FEATURE-123] Created a new screen that performs an action.*\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*wKF9mdhTLC8MFdy02WJ1EQ.png)\n\n注意，在每个分支的项目里都有自己的版本号。您可以使用 [git tags](http://git-scm.com/book/en/v2/Git-Basics-Tagging) 来执行版本号的控制\n\n当一个功能已经完成，一个 pull request 就开放了，所以你的组织的其他成员就可以批准它。这是为了确保你提供高质量软件的一个关键部分。在 [Sixt](http://www.sixt.de/mobileapps/)，另一成员被分配到你的代码审查，这个人会通读你全部的代码。\n我们保证我们的代码是符合我们的[coding conventions](https://speakerdeck.com/kikoso/android-coding-guidelines)和我们对过程中严格的要求，典型的如在XML文件中有一个额外的空格。我们评论的命名（“函数名我不清楚”），检查我们的设计是完美的（“你的文本视图的颜色 \\#DCDCDC 但设计是 \\#DEDEDE”）有一个功能测试去检查该特性是覆盖了在问题跟踪里编写的验收标准的。\n我们甚至进行一些哲学讨论，比如关于null和void或empty变量的意义。这听起来令人讨厌，但它是有趣的。如果是充满热情的做了这一切，到时候你就知道你的代码达到了产品需要的提交质量。\n\n\n### 敏捷和迭代\n\n\n你可能会在执行[SCRUM](http://en.wikipedia.org/wiki/Scrum_%28software_development%29), [Kanban](http://en.wikipedia.org/wiki/Kanban_%28development%29)或另外的敏捷方法。通常你会进行在几个星期的冲刺工作。我们认为是一个好主意，把冲刺分到两周：第一周是用来开发特性，而第二周将稳定在第一阶段创造的特性。在第二个冲刺中，我们将修复发现的漏洞，实现完美的布局或提高重构我们的代码。这项工作是在 **beta/stage** 分支完成的。\n如下图所示\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*7lynNxY8iQpFHCee_Rkjjg.png)\n\n> 这些黄点属于bug修复和稳定性阶段\n\n如果你遵循我们的约定，敏捷结束时你将会有一个可交付的成果。这将是一个准备发表在谷歌商店的可交付文件。此时此刻，我们的应用程序的最后版本已经合并到了master。\n另一个非常重要的课题是如何创建一个热修复补丁。我们的模型试图通过使用代码检查和修复bug和产品稳定的第二周来阻止他们发生，但错误确实已经发生。这是发生在生产时，这种模式要求错误直接被修正在 **master** 分支。\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*sdL8HDfMgoNuhnLKu7Soew.png)\n\n你有没有意识到这个模型的标志？是的，就是那！该热修复补丁是不存在在我们的 **alpha/beta** 的分支。经过修复和稳定期后（第二周），我们的 **alpha** 分支是旧状态，错误仍然是存在的。我们需要立即合并到各分支来保证正确，从而确保每一个修复是存在所有的分支的。\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*Rijzdkk4SA4T9T3koBAXCg.png)\n\n很难理解？可能是读起来比实施来的难。\n如果你没有一个分支策略，只是试图使用这个模型来开发一个特性。你会发现很容易工作，而且你甚至会开始定制！\n\n\n### Gradle 和脚本化\n\n\n现在您已经阅读分支模型，我们准备继续讨论接下来的步骤。Gradle是一个工具,将帮助我们自动完成很多事情。你可能熟悉Gradle(或其它家族成员,Maven和Ant)。Gradle是一个项目的自动化工具，当我们正在建设我们的应用程序时，可以使用执行功能和定义属性。它介绍了一种基于Groovy的领域语言，它能做到的基本上只受限于我们的想象力。\n\n我以前写的一个 [post](http://codetalk.de/?p=112) 和一些技巧来使用工具。他们中的一些将非常有用，包括对于你的应用，但之后也有一些我已经应用的，我想在这里介绍。\n\n#### 构建配置（BuildConfig）的力量\n\n当我们编译Android应用程序时，*BuildConfig* 是一个自动生成的文件。这个文件，默认情况下，看起来如下：\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*wkoXjbSYaYymUhZrO8-jRw.png)\n\nBuildConfig 包含一个字段，叫 **DEBUG**，指示应用程序是否已经编译在调试模式。这个文件是高度可定制的，当我们工作在不同的构造类型时，这是非常便利的。\n\n应用程序通常使用服务工具追踪其行为 [Google Analytics](http://www.google.com/analytics/ce/mws/), [Crashlytics](https://try.crashlytics.com/) 或其他平台。当我们正在开发应用时，我们可能不想影响这些指标（想象一个用户界面的测试，自动发布的每一天，跟踪您的登录屏幕？）。我们也会根据我们的构建有不同的域名（例如development.domain.com, staging.domain.com…），我们要使用自动的。但我们怎么可以做的干净利落？容易！在Gradle的buildTypes域，我们可以添加任何我们希望的新域。这些域将通过*BuildConfig* 在以后可用（这意味着，我们可以使用 BuildType.FIELD 来读取它们）。\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*Z_YYrTPF7FTShHAt5tqW3g.png)\n\n在 [this post](http://codetalk.de/?p=112) 我展示了如何使用不同的图标和如何改变包的名称。利用这个我们可以安装我们应用程序的不同版本。这能够非常方便的同时看到我们的 beta, alpha 和 release 版本。\n\n\n### 保持测试\n\n\n测试本身，和整个过程都有它自己的中间环节。当我们谈论测试就是我们在谈论模拟的组件，关于UI测试和集成测试，关于仪器，和所有对于Android可用的不同框架。\n\n测试是非常重要的，因为它可以防止开发者破坏现有的东西。没有测试，当我们开发一个新的功能B时，我们可以很容易地干扰一个旧的特性A。当一个新的特征被提交，是很难手动测试整个系统的，但自动的做这些就更容易控制一个系统的稳定性。\n\n这里有了许多不同的测试可以在移动设备上实施：只是列举几个，我们可以考虑，集成测试，功能测试，性能测试和用户界面测试。每一种都有不同的功能，它们一般是定期触发以确保新功能没有破坏或干扰系统。\n\n为了展示一个基本的例子，如何将测试在 Jenkins 集成（以及他们如何实现生成出错时停止的功能）\n我们将看到一个做UI测试的小例子 [Espresso](https://code.google.com/p/android-test-kit/wiki/Espresso) 每次都是在 Jenkins 构建测试我们的Android应用程序。\n\n\n### 一个应用程序的示例\n\n\n我创建了一个小示例应用程序并上传到 [GitHub](https://github.com/kikoso/Android-Testing-Espresso)，所以你可以来这里看看。也有一些分支使用命名约定和 pull requests，直到现在你可以看到审查的一切解释。该应用程序是相当基本的：它有一个TextView屏幕。还有三个已在文件执行的UI测试单元\n[MainActivityInstrumentationTest](https://github.com/kikoso/Android-Testing-Espresso/blob/master/src/androidTest/java/com/dropsport/espressoreadyproject/tests/MainActivityInstrumentationTest.java):\n\n1. 检查在屏幕上存在一个TextView。\n2. 检查TextView包含文本“Hello World!”\n3. 检查TextView包含文本“What a label!”\n\n最后的两个试验是互斥的（这意味着，无论是一个或另一个是成功的，但不能两者同时成立）。我们通过以下命令对应用进行测试：\n\n    ./gradlew clean connectedCheck.\n\n如果你检出了代码，你可以自己试试注释功能 *testFalseLabel*。这将使测试失败。\n\n\n### 把一切都集成在 Jenkins\n\n\n现在，我们已经检查了一些事情，让我们看看他们如何适配 Jenkins。如果你没有安装它，你可以从网站下载 [last version](https://jenkins-ci.org/)。\n\n我们没有提到一些东西，但这里也有分支策略。\n有许多不同的方法，它们都具有各自的优点和缺点：\n\n1. 你可以在分支构建前触发测试。\n2. 你可以晚上或每日构建，不要阻止构建，但如果失败仍然要发出通知。\n\n在本教程中我选择了第一种方法，也是为了显示 Jenkins 的一大特征：jobs 之间的依赖关系。让我们创造三个 jobs：**Job Beta**, **Job Alpha** and **Job Tests**。\n\n1. **Job Alpha** 将构建 alpha 分支 (通过 ./gradlew clean assembleAlpha)\n2. **Job Beta** 将做同样的工作在 beta 分支上（通过 ./gradlew clean assemblebeta）。这是每一次有分支合并到 beta 分支上就会执行的\n3. **Job Tests** 每次有分支合并到 alpha 分支时都将触发。如果它成功了，它会引发 **Job Alpha**。\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*OPzV-aZLBGxdPYWPIip8Bg.png)\n\nJenkins 是一个基于大量的插件的平台。许多公司正在为他们的产品不断地发布插件，他们将集成在 Jenkins，我们可以很容易地与其他平台连接。\n让我们看看在 Jenkins 的一些选项\n\n#### 依赖\n\n使用依赖 Jenkins 可以互连项目。也许我们要连接测试 jobs 和基于试验结果来控制启动。或许我们在实际构建应用之前，部分逻辑首先存在需要编译的lib库里。\n\n#### 通知\n\nJenkins 可以通知一个人或一个工作组或构建错误。通知一般是电子邮件，但也有插件可以通过IM系统发送消息，如 [Skype](https://wiki.jenkins-ci.org/display/JENKINS/Skype+Plugin) 或者 [SMS](https://wiki.jenkins-ci.org/display/JENKINS/SMS+Notification)（最新版当你有重要的测试失败时可以很方便的通知）。\n\n#### 交付\n\n你可能知道，在这一点上 [HockeyApp](http://hockeyapp.net/) 或另一个 [delivery platforms](http://alternativeto.net/software/hockeyapp/)。他们基本上可以存储二进制文件，创建组，并当应用程序被上传时通知他们。想象看测试者自动在他/她的设备上接收每次他们被创造的文件，和产品所有者被通知有新的beta版的情景。这里有一个Jenkins 插件 [HockeyApp plugin](https://wiki.jenkins-ci.org/display/JENKINS/HockeyApp+Plugin) 能够上传二进制文件到 Hockey（甚至通知成员，或作为你最近提交的 release notes 使用）。\n\n![image](https://d262ilb51hltx0.cloudfront.net/max/800/1*P6-P4hBkKfAG7Ls0bzXeEQ.png)\n\n我还是喜欢保持手动发布产品的步骤，这可能是由于在出版过程中不经过人工控制的一种担心。但是，确实有一个 [plugin](https://wiki.jenkins-ci.org/display/JENKINS/Google+Play+Android+Publisher+Plugin) 用来直接发步到 Google Play。\n\n\n### 结论\n\n\n在 *building*, *testing*, *delivering* 和 *publishing* 实现自动化，主要是在一个团队工作中选择正确的决策。当这个决定是明确的，我们才可以继续去技术上实现。\n\n有一件事情是肯定的：错误会由于以往人们的行动而大幅度减少，并结合强大的测试覆盖率，我们的软件质量将大大提高。这里我借用同事的座右铭 [Cyril Mottier](https://developers.google.com/experts/people/cyril-mottier)：\n\n> 致精而大\n\n这是你职业生涯中的一个重要时刻！当你想在工作中保证 **质量** 而努力，而不是 **数量** 的多少。我了解这件事，第一步是尽你所能的实现自动化。事实上，我可以用以前的口号改述为另一句话，我将在我的日常生活里：\n\n> 自动化的更多，所以你被动化的更少\n\n祝：快乐编程！\n\n![image](https://d262ilb51hltx0.cloudfront.net/fit/c/63/63/1*AV6Ju95BJPkkIXg1x8Ni1w.jpeg \"Enrique López Mañas\")\n"
  },
  {
    "path": "issue-13/Android进行单元测试难在哪-终.md",
    "content": "Android 进行单元测试难在哪-终\n---\n\n> * 原文链接 : [WHAT I’VE LEARNED FROM TRYING TO MAKE AN ANDROID APP UNIT TESTABLE](http://www.philosophicalhacker.com/2015/05/22/what-ive-learned-from-trying-to-make-an-android-app-unit-testable/)\n* 原文作者 : [Matthew Dupree](http://philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 :  完成\n\n\n\n\n在前面的博文中，我给大家介绍并展示了要怎么使用 Square 大法架构 Android 应用，事实上，Square 开发新的 Android 应用架构本意只是增强应用的可测试性。正如我在[Android 进行单元测试难在哪-序](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-8/Android%20%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-%E5%BA%8F.md)中所说，要在 Android 中进行单元测试在大多数情况下都很费时，尝试一些奇技淫巧或者第三方库情况可能会好一些。为了实现高效且不依赖第三方库的测试单元，Square 大法应运而生。\n\n此外，我们在[ Android 进行单元测试难在哪-part1](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-9/Android%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part1.md)和[ Android 进行单元测试难在哪-part3](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-11/Android%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part3.md)两篇博文就这个问题进行了研究，最后成功地通过 Square 大法为 Android 应用实现了测试单元，这篇文章则是对 Square 大法进行评估，我将从下面三个方面进行：\n\n1. 为 Android 应用实现高效的测试单元并不需要移除所有对 Android SDK 的编译时依赖。（事实上这样做有些不切实际。）\n\n2. 加入我们重新设计 Square 大法，使 Square 大法的使用不需要移除所有对 Android SDK 的编译时依赖，尝试将 Square 大法运营到项目中唯一会发生的问题是：实现一大堆模板代码。幸运的是，许多诸如此类的模板代码都可以通过 Android Studio 自动生成。\n\n3. 依赖注入确实是让 Square 大法成为应用可测试性治病良方的好办法。\n\n##移除所有对 Android SDK 的编译时依赖既不必要也不切合实际\n\n完成这个系列博文的最初愿望就是通过让 Android 应用栈变成下图那样，增强应用的可测试性：\n\n![](http://i2.wp.com/www.philosophicalhacker.com/wp-content/uploads/2015/04/androidstack-02.png?resize=279%2C172)\n\n接下来我将告诉大家，这个想法一直误导着我们。要使应用的可测试性增强，我们还需要利用依赖注入的其它特性，而不仅仅是用它将业务逻辑代码和 Android SDK 解耦。最初的原因是对象的 Android 依赖可以通过类似 Mockito 的东西模拟出来，而且如果我们无法单独通过 Mocktio 完全控制测试单元的预测试状态的话，我们还可以用具有 mock 实现的接口代替这些 Android 依赖。而这也正是我们在[ Android 进行单元测试难在哪-part4](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-12/Android%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part4.md)中重构 SessionRepositoryManager 的办法。\n\n移除依赖不仅是不必要的，将它完全与 Android SDK 解耦也是不切实际的，问题在于：当你尝试完成这个目标时，不妨回顾你已经完成的工作，你会很明显地感觉这是不必要的，所以我只打算在此简要地陈述个中因由。\n\n要移除移除应用中所有对 Android SDK 的编译时依赖可能会导致下面的问题：\n\n1. 应用必须为此定义许多方法和类。\n\n2. 添加的接口和已有的 Android 接口几乎一样。\n\n3. 由于有些情况下，对象需要被注入；有些情况下，对象不需要被注入，带有依赖的类构造器会因此变得很臃肿。\n\n虽然 Square 大法存在一些问题，但这并不影响我在之前的博文中有关 Square 的讲解，Square 的这些特性依旧是实用，值得尝试的。由于 Android SDK 供予我们使用的应用组件类都没有被依赖注入，我们要在 Android 应用中进行单元测试确实很困难，但有了 Square 大法后，只要我们将业务逻辑交给被依赖注入的纯 Java 对象，就能轻易地对 Android 应用进行单元测试。尽管 Square 大法减小了移除 Android SDK 依赖的需求，但 Square 大法仍然是增强应用可测试性的屠龙宝刀。但我个人还是希望对 Square 大法进行优化，使得 Square 大法能无视这个需求，换句话说，我希望能够找到一种不需要我们移除所有对 Android SDK 依赖的架构方法，\n\n##乏味的模板代码是阻碍应用变得可测试的绊脚石\n\n如果我们对 Square 大法进行优化，使其不再要求我们移除对 Android SDK的依赖，Square 大法看起来真的是天下第一的神功了。委以业务逻辑的纯 Java 对象将被应用组件类引用，使它们能够访问所有组件类内的属性和回调。因此，将业务逻辑转移到进行了依赖注入的纯 Java 对象，不应该将那些拥有履行自身职责的数据的对象排除在外。\n\n如果这是正确的，那么唯一阻止我们使用 Square 大法的就是实现乏味的模板代码。幸运的是，Android Studio 为我们提供了过渡到 Square 大法的重构选项——the Extract Delegate option。利用这个选项，可以自动地将类的方法和实例变量转移到一个委托类中，并让原始类通过调用委托类处理逻辑，而不用依赖类自身的方法。\n\n这个[视频](www.youtube.com/embed/N0F7w4wEnQ8)向我们展示了如何利用 `the Extract Delegate option` 完成重构 SessionDetailActivity 的 onStop() 并使之能够进行单元测试必要的操作。我曾在之前的博文中给大家解释过进行这样的重构为什么是必要的，很显然，手动操作无法涵盖所有情况，而且你需要为此不断重复实现代码语句块中分离 Activity View 和数据的方法，这样重复而又没有意义的工作无异于浪费生命，因此这个选项真的非常实用。\n\n##依赖注入是 Square 大法的精髓\n\n[Android 进行单元测试难在哪-part3](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-11/Android%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E9%9A%BE%E5%9C%A8%E5%93%AA-part3.md)的秘密在于：**依赖注入**。\n\n— Chris Arriola (@arriolachris) [May 15, 2015](https://twitter.com/arriolachris/status/599232312492982273)\n\nSquare 大法之所以能解决 Android 的单元测试问题，是因为它允许我们对持有业务逻辑的类进行真正的依赖注入，我之所以强调 Square 允许我们进行的是**真正的**依赖注入，是因为依赖注入这个概念在 Dagger 库中也被提到过，然而，Dagger 并不能真正简化 Android 应用进行单元测试的代码。\n\n这是因为 Dagger 就像它介绍的那样，确实是一个 Service 定位库，正是如此，Dagger 强迫我们实现一个模块，使之为我们想要进行单元测试的对象提供 mock 依赖。为了能利用这些模块，我们还要确保由这些 mock 提供者模块构建的对象图与我们尝试进行单元测试的对象使用的对象图相同。\n\n而正是这样的操作使得 Dagger 的依赖注入不如 Square 大法中真正的依赖注入那样简便，解释为什么 Dagger 不能简化对 Android 应用进行单元测试的过程完全可以写一篇博文，所以现在，我唯一能做的就是先指出这个问题，如果我们理解[ Martin Fowler 对依赖注入的定义](http://martinfowler.com/articles/injection.html#InversionOfControl)（这篇博文确实值得一读，因为他创建了一个新的术语），就会发现 Dagger 确实只是一个 Service 定位库，而且 Google 官方有关测试的博客也有[一篇博文](http://martinfowler.com/articles/injection.html#InversionOfControl)对此作出解释。\n\n##结论\n\n如果想让 Android 应用可以进行单元测试，那就用 Square 大法吧，当然了，如果有其他解决办法的话我也会支持的。在这里友情提示一下哈：我只是列出我了解的几种办法，我可没有说 Square 大法天下第一无人可敌，事实上增强应用可测试性应该还会有其他办法的。\n\n这篇博文的发布也预示着这系列博文走向终结啦，我非常感谢每一个关注本系列博文的 Android 开发者的支持，感谢每一个人对我的鼓励、转发以及大家在社交媒体上对我的褒奖，我感恩这一切。这些积极的反馈使我认识到讨论和思考对 Android 应用进行测试的重要性，正是完成这个系列博文给我带来的启示使我决定：我要在未来花费更多的时间在博客中研究 Android 测试方面的知识。我会在每个周五更新博客，希望能学习更多有关测试的知识，和大家一起进步。"
  },
  {
    "path": "issue-13/Square：从今天开始抛弃Fragment吧！.md",
    "content": "Square：从今天开始抛弃Fragment吧！\n---\n\n> * 原文链接 : [Advocating Against Android Fragments](https://corner.squareup.com/2014/10/advocating-against-android-fragments.html)\n* 原文作者 : [Pierre-Yves Ricau](http://twitter.com/Piwai)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Belial](www.belial.me)\n* 状态 :  完成\n\n最近我在 Droidcon Paris 上进行了[一个技术相关的演讲](http://fr.droidcon.com/2014/agenda/detail?title=D%C3%A9fragmentez+vos+apps+avec+Mortar+%21)，我在这次演讲中给大家展示了 Square 使用 Fragment 进行开发时遇到的种种问题，以及其他 Android 开发者是怎么避免在项目中使用 Fragment 的。\n\n在 2011 年那会，由于下面的原因我们决定使用 Fragment：\n\n- 在那会，虽然我们很想让应用能在平板设备上被使用，但我们确实没能为平板提供平台支持。而 Fragment 能帮助我们完成这项愿望，建立响应式 UI 界面。\n\n- Fragment 是视图控制器，它们能够将一大块耦合严重的业务逻辑模块解耦，并使得解耦后的业务逻辑能够被测试。\n\n- Fragment 的 API 能够进行回退栈管理（例如，它能反射某个 Activity 内 Activity 栈的具体操作）\n\n- 因为 Fragment 处于视图层的顶层，而为 View 设置动画并不麻烦，使得 Fragment 为设置页面切换的过渡效果提供了更好的支持。\n\n- Google 建议我们使用 Fragment，而我们作为开发者都想让自己的代码符合标准。\n\n在 2011年之后，我们在为 Square 进行开发的过程中发现了比使用 Fragment 更好的方法。\n\n#关于 Fragment 你不知道的事\n\n##The lolcycle\n\n在 Android 中，Context 就像一个[上帝对象](http://en.wikipedia.org/wiki/God_object)，因为在 Context 类中涵盖了太多 Android 系统的信息和相关的操作，使得 Context 在 Android 系统中相当于一个全知全能的上帝，而 Activity 就是为 Context 添加了生命周期的子类。不过让上帝具有生命周期还是有些讽刺的。虽然 Fragment 不是上帝对象，但 Fragment 为了能够完成 Activity 中能完成的各种操作，使 Fragment 自身的生命周期变得异常复杂。\n\nSteve Pomeroy 做了一张[ Fragment 的完整生命周期图](https://github.com/xxv/android-lifecycle)，我相信任谁看到这张图都不会好受：\n\n![](https://corner.squareup.com/images/no-fragments/lifecycle.png)\n\n这张图由 Steve Pomeroy 完成，图中移除了 Activity 的生命周期，分享这张图需要获得 [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) 许可。\n\n整个 Fragment 的生命周期让你很头疼要怎样使用这些回调方法，它们是同步调用的呢，还是只是一次性全部调用呢，还是其它情况……？\n\n##难于调试\n\n当你的应用出现 Bug，你得用调试工具一步一步地执行代码才能知道到底发生了什么，虽说一般情况下这样做 Bug 都能解决，但如果你在调试的时候发现 Bug 和 FragmentManagerImpl 类存在某种联系，那么我可要好好恭喜你即将中大奖了！\n\n因为要跟踪 FragmentManagerImpl 类内代码的执行顺序，并进行调试是很困难的，这也使得修复应用中相关的 Bug 也变得异常困难：\n\n```java\nswitch (f.mState) {\n    case Fragment.INITIALIZING:\n        if (f.mSavedFragmentState != null) {\n            f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(\n                    FragmentManagerImpl.VIEW_STATE_TAG);\n            f.mTarget = getFragment(f.mSavedFragmentState,\n                    FragmentManagerImpl.TARGET_STATE_TAG);\n            if (f.mTarget != null) {\n                f.mTargetRequestCode = f.mSavedFragmentState.getInt(\n                        FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);\n            }\n            f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(\n                    FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);\n            if (!f.mUserVisibleHint) {\n                f.mDeferStart = true;\n                if (newState > Fragment.STOPPED) {\n                    newState = Fragment.STOPPED;\n                }\n            }\n        }\n// ...\n}\n```\n\n如果你曾经需要解决应用旋转后产生一个与旋转前 UI 相同（方向发生变化）的独立的 Fragment 的需求，我想你应该懂我在说什么。（别给我提嵌套使用的 Fragment！）\n\n我想下面这张图很好地诠释了这类代码给程序员带来的伤害（由于版权问题我得放出这张图的出处哈：[this cartoon](http://www.osnews.com/story/19266/WTFs_m)）：\n\n![](https://corner.squareup.com/images/no-fragments/code-quality.png)\n\n在多年的深度分析中我得出结论：操蛋程度/调试耗费的时间 = 2^m，m 为 Fragment 的个数。\n\n##Fragment 是视图控制器？想太多\n\n因为 Fragment 需要创建、绑定和配置 View，它们包含了许多与 View 关联的结点，这就意味着 View 类代码中的业务逻辑并没有真正地被解耦，正是这个原因使得我们要为 Fragment 实现测试单元将会变得很困难。\n\n##Fragment transactions\n\nFragment 的 transaction 允许你执行一系列的 Fragment 操作，但不幸的是，提交 transaction 是异步操作，并且在 UI 线程的 Handler 队列的队尾被提交。这会在接收多个点击事件或配置发生改变时让你的 App 处在未知的状态。\n\n```java\nclass BackStackRecord extends FragmentTransaction {\n    int commitInternal(boolean allowStateLoss) {\n        if (mCommitted)\n            throw new IllegalStateException(\"commit already called\");\n        mCommitted = true;\n        if (mAddToBackStack) {\n            mIndex = mManager.allocBackStackIndex(this);\n        } else {\n            mIndex = -1;\n        }\n        mManager.enqueueAction(this, allowStateLoss);\n        return mIndex;\n    }\n}\n```\n\n##创建 Fragment 可能带来的问题\n\nFragment 的实例能够通过 Fragment Manager 创建，例如下面的代码看起来没有什么问题：\n\n```java\nDialogFragment dialogFragment = new DialogFragment() {\n  @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ... }\n};\ndialogFragment.show(fragmentManager, tag);\n```\n\n然而，当我们需要存储 Activity 实例的状态时，Fragment Manager 可能会通过反射机制重新创建该 Fragment 的实例，又因为这是一个匿名内部类，该类有一个隐藏的构造器的参数正是外部类的引用，如果大家有看过[这篇博文](http://blog.csdn.net/u012403246/article/details/45666369)的话就会知道，拥有外部引用可能会带来内存泄漏的问题。\n\n```java\nandroid.support.v4.app.Fragment$InstantiationException:\n    Unable to instantiate fragment com.squareup.MyActivity$1:\n    make sure class name exists, is public, and has an empty\n    constructor that is public\n```\n\n##Fragment 教给我们的思想\n\n尽管 Fragment 有着上面提到的缺点，但也是 Fragment 教给我们许多代码架构的思想：\n\n- 独立的 Activity 接口：实际上我们并不需要为每一个页面创建一个 Activity，我们大可以将应用切分成许多解耦的视图组件，按照我们的实际需求把它们组装成我们想要的界面。这样做也能简化生命周期和动画设置，因为我们还能将视图组件切分为 view 组件和控制器组件。\n\n- 回退栈不是 Activity 的特有概念，也就意味着你能在 Activity 内部实现回退栈。\n\n- 不需要添加新的 API，我们需要的只是 Activity，View 和 LayoutInflater。\n\n#响应式 UI：Fragment VS Custom View\n\n##Fragment\n\n我们不妨先来看看一个 Fragment 的[范例](http://developer.android.com/shareables/training/FragmentBasics.zip)，界面中显示了一个 list。\n\nHeadlinesFragment 就是显示 List 的简单 Fragment：\n\n```java\npublic class HeadlinesFragment extends ListFragment {\n  OnHeadlineSelectedListener mCallback;\n\n  public interface OnHeadlineSelectedListener {\n    void onArticleSelected(int position);\n  }\n\n  @Override\n  public void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setListAdapter(\n        new ArrayAdapter<String>(getActivity(),\n            R.layout.fragment_list,\n            Ipsum.Headlines));\n  }\n\n  @Override\n  public void onAttach(Activity activity) {\n    super.onAttach(activity);\n    mCallback = (OnHeadlineSelectedListener) activity;\n  }\n\n  @Override\n  public void onListItemClick(ListView l, View v, int position, long id) {\n    mCallback.onArticleSelected(position);\n    getListView().setItemChecked(position, true);\n  }\n}\n```\n\n现在有趣的事情来了：ListFragmentActivity 必须控制 list 是否处于同一个页面中。\n\n```java\npublic class ListFragmentActivity extends Activity\n    implements HeadlinesFragment.OnHeadlineSelectedListener {\n  @Override\n  public void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.news_articles);\n    if (findViewById(R.id.fragment_container) != null) {\n      if (savedInstanceState != null) {\n        return;\n      }\n      HeadlinesFragment firstFragment = new HeadlinesFragment();\n      firstFragment.setArguments(getIntent().getExtras());\n      getFragmentManager()\n          .beginTransaction()\n          .add(R.id.fragment_container, firstFragment)\n          .commit();\n    }\n  }\n  public void onArticleSelected(int position) {\n    ArticleFragment articleFrag =\n        (ArticleFragment) getFragmentManager()\n            .findFragmentById(R.id.article_fragment);\n    if (articleFrag != null) {\n      articleFrag.updateArticleView(position);\n    } else {\n      ArticleFragment newFragment = new ArticleFragment();\n      Bundle args = new Bundle();\n      args.putInt(ArticleFragment.ARG_POSITION, position);\n      newFragment.setArguments(args);\n      getFragmentManager()\n          .beginTransaction()\n          .replace(R.id.fragment_container, newFragment)\n          .addToBackStack(null)\n          .commit();\n    }\n  }\n}\n```\n\n##自定义 View\n\n我们不妨重新实现一个简化版的只使用了 View 的代码\n\n首先，我们会引入一个叫作“容器”的概念，“容器”的作用是帮助我们展示一项内容并处理后退操作\n\n```java\npublic interface Container {\n  void showItem(String item);\n\n  boolean onBackPressed();\n}\n```\n\nAcitivity 将假设始终存在容器，并且几乎不会将业务交给容器处理。\n\n```java\npublic class MainActivity extends Activity {\n  private Container container;\n\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.main_activity);\n    container = (Container) findViewById(R.id.container);\n  }\n\n  public Container getContainer() {\n    return container;\n  }\n\n  @Override public void onBackPressed() {\n    boolean handled = container.onBackPressed();\n    if (!handled) {\n      finish();\n    }\n  }\n}\n```\n\n要显示的 List 也只是个平凡的 List。\n\n```java\npublic class ItemListView extends ListView {\n  public ItemListView(Context context, AttributeSet attrs) {\n    super(context, attrs);\n  }\n\n  @Override protected void onFinishInflate() {\n    super.onFinishInflate();\n    final MyListAdapter adapter = new MyListAdapter();\n    setAdapter(adapter);\n    setOnItemClickListener(new OnItemClickListener() {\n      @Override public void onItemClick(AdapterView<?> parent, View view,\n            int position, long id) {\n        String item = adapter.getItem(position);\n        MainActivity activity = (MainActivity) getContext();\n        Container container = activity.getContainer();\n        container.showItem(item);\n      }\n    });\n  }\n}\n```\n\n这样做的好处是：能够基于资源文件夹在不同的 XML 布局文件\n\n`res/layout/main_activity.xml`\n\n```xml\n<com.squareup.view.SinglePaneContainer\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/container\"\n    >\n  <com.squareup.view.ItemListView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n</com.squareup.view.SinglePaneContainer>\n```\n\n`res/layout-land/main_activity.xml`\n\n```xml\n<com.squareup.view.DualPaneContainer\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"horizontal\"\n    android:id=\"@+id/container\"\n    >\n  <com.squareup.view.ItemListView\n      android:layout_width=\"0dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_weight=\"0.2\"\n      />\n  <include layout=\"@layout/detail\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"match_parent\"\n      android:layout_weight=\"0.8\"\n      />\n</com.squareup.view.DualPaneContainer>\n```\n\n下面是这些容器类的简单实现：\n\n```java\npublic class DualPaneContainer extends LinearLayout implements Container {\n  private MyDetailView detailView;\n\n  public DualPaneContainer(Context context, AttributeSet attrs) {\n    super(context, attrs);\n  }\n\n  @Override protected void onFinishInflate() {\n    super.onFinishInflate();\n    detailView = (MyDetailView) getChildAt(1);\n  }\n\n  public boolean onBackPressed() {\n    return false;\n  }\n\n  @Override public void showItem(String item) {\n    detailView.setItem(item);\n  }\n}\n```\n\n```java\npublic class SinglePaneContainer extends FrameLayout implements Container {\n  private ItemListView listView;\n\n  public SinglePaneContainer(Context context, AttributeSet attrs) {\n    super(context, attrs);\n  }\n\n  @Override protected void onFinishInflate() {\n    super.onFinishInflate();\n    listView = (ItemListView) getChildAt(0);\n  }\n\n  public boolean onBackPressed() {\n    if (!listViewAttached()) {\n      removeViewAt(0);\n      addView(listView);\n      return true;\n    }\n    return false;\n  }\n\n  @Override public void showItem(String item) {\n    if (listViewAttached()) {\n      removeViewAt(0);\n      View.inflate(getContext(), R.layout.detail, this);\n    }\n    MyDetailView detailView = (MyDetailView) getChildAt(0);\n    detailView.setItem(item);\n  }\n\n  private boolean listViewAttached() {\n    return listView.getParent() != null;\n  }\n}\n```\n\n不难想象：将容器类抽象，并用这种的方式开发 App，不但不需要 Fragment，还能架构出容易理解的代码。\n\n##View 和 Presenter\n\n自定义 View 在应用中非常有用，但我们希望将业务逻辑从 View 中剥离，转交给特定的控制器处理，也就是接下来我们所说的 Presenter，引入 Presenter 能提高代码的可读性和可测试性。如果你不信的话，不妨看看重构后的 MyDetailView：\n\n```java\npublic class MyDetailView extends LinearLayout {\n  TextView textView;\n  DetailPresenter presenter;\n\n  public MyDetailView(Context context, AttributeSet attrs) {\n    super(context, attrs);\n    presenter = new DetailPresenter();\n  }\n\n  @Override protected void onFinishInflate() {\n    super.onFinishInflate();\n    presenter.setView(this);\n    textView = (TextView) findViewById(R.id.text);\n    findViewById(R.id.button).setOnClickListener(new OnClickListener() {\n      @Override public void onClick(View v) {\n        presenter.buttonClicked();\n      }\n    });\n  }\n\n  public void setItem(String item) {\n    textView.setText(item);\n  }\n}\n```\n\n我们来看看 Square 注册界面中编辑账户的页面吧！\n\n![](https://corner.squareup.com/images/no-fragments/edit-discounts.png)\n\nPresenter 将在更高层级中操控 View：\n\n```java\nclass EditDiscountPresenter {\n  // ...\n  public void saveDiscount() {\n    EditDiscountView view = getView();\n    String name = view.getName();\n    if (isBlank(name)) {\n      view.showNameRequiredWarning();\n      return;\n    }\n    if (isNewDiscount()) {\n      createNewDiscountAsync(name, view.getAmount(), view.isPercentage());\n    } else {\n      updateNewDiscountAsync(discountId, name, view.getAmount(),\n        view.isPercentage());\n    }\n    close();\n  }\n}\n```\n\n大家可以看到，为这个 Presenter 实现测试单元犹如一缕春风拂面来，甚是舒心爽快呐～\n\n```java\n@Test public void cannot_save_discount_with_empty_name() {\n  startEditingLoadedPercentageDiscount();\n  when(view.getName()).thenReturn(\"\");\n  presenter.saveDiscount();\n  verify(view).showNameRequiredWarning();\n  assertThat(isSavingInBackground()).isFalse();\n}\n```\n\n##回退栈管理\n\n通过异步处理来管理回退栈实在是牛刀杀鸡，大材小用了……我们只需要用一个超轻量级库——Flow，就可以达到目的。有关 Flow 的介绍 Ray Ryan 已经写过博客了，我就不在此赘述啦。\n\n##我把 UI 相关的代码全都写在 Fragment 里了咋办呀，在线等，急！！！\n\n别理你的 Fragment，你就一点一点地把 View 相关的代码移到自定义 View 里，然后把涉及到的业务逻辑交给能够与 View 进行交互的 Presenter，然后你就会发现 Fragment 沦为空壳，只有一些初始化自定义 View 和连接 View 和 Presenter 的操作：\n\n```java\npublic class DetailFragment extends Fragment {\n  @Override public View onCreateView(LayoutInflater inflater,\n    ViewGroup container, Bundle savedInstanceState) {\n    return inflater.inflate(R.layout.my_detail_view, container, false);\n  }\n}\n```\n\n事实上到了这一步你已经可以抛弃 Fragment 了。\n\n抛弃 Fragment 确实得花很大的功夫，但我们已经做到了，感谢[ Dimitris Koutsogiorgas ](https://twitter.com/dnkoutso)和[ Ray Ryan ](https://twitter.com/rjrjr)的伟大贡献！\n\n##Dagger 和 Mortar 是什么？\n\nDagger & Mortar 与 Fragment 成正交关系，换句话说，两者间各自的变化不会影响对方，使用 Dagger & Mortar 既可以用 Fragment，也可以不用 Fragment。\n\n[Dagger](http://square.github.io/dagger/) 能帮你将应用模块化为一张由解耦组件构成的图，它考虑了所有类间的连接关系并简化了抽取依赖的操作，并实现一个与此相关的单例对象。\n\n[Mortar](https://github.com/square/mortar) 在 Dagger 的顶层进行操作，主要优势有如下两点：\n\n- Mortar 为被注入组件提供简单的生命周期回调，使你能实现不会因旋转被销毁的单例 Presenter，不过需要注意的是，Mortar 将当前界面元素的状态储存在 Bundle 中，使数据不会随进程的结束而被清除。\n\n- Mortar 为你管理 Dagger 的子图，并帮你将它们与 Activity 的生命周期关联在一起，这种功能让你能有效地实现“域”：当一个 View 被添加进来，它的 Presenter 和依赖都会作为子图被创建；当 View 被移除，你能轻易地销毁“域”，并让垃圾回收机制去完成它的工作。\n\n##结论\n\n我们曾为 Fragment 的诞生满心欢喜，幻想着 Fragment 能为我们带来种种便利，然而这一切不过是场虚空大梦，我们最后发现骑着白马的 Fragment 既不是王子也不是唐僧，只不过是人品爆发捡了只白马的乞丐罢了：\n\n- 我们遇到的大多数难以解决的 Bug 都与 Fragment 的生命周期有关。\n\n- 我们只需要 View 创建响应式 UI，实现回退栈以及屏幕事件的处理，不用 Fragment 也能满足实际开发的需求。"
  },
  {
    "path": "issue-13/readme.md",
    "content": "# 2015.6.5 ( 第十三期 )\n| 文章名称 |   译者  | \n|---------|--------|\n| [Square：从今天开始抛弃Fragment吧！](Square：从今天开始抛弃Fragment吧！.md)  | [chaossss](https://github.com/chaossss)      |\n| [Android进行单元测试难在哪-终](Android进行单元测试难在哪-终.md)  | [chaossss](https://github.com/chaossss)|\n| [优化android-studio编译效率的方法](优化android-studio编译效率的方法.md)  | [FTExplore](https://github.com/FTExplore)      |\n| [创建 RecyclerView LayoutManager – Part 2](创建-RecyclerView-LayoutManager-Part-2.md)  | [tiiime](https://github.com/tiiime)|\n| [创建-RecyclerView-LayoutManager-Part-3](创建-RecyclerView-LayoutManager-Part-3.md)  | [tiiime](https://github.com/tiiime)     |\n| [创建-RecyclerView-LayoutManager-Redux](创建-RecyclerView-LayoutManager-Redux.md)  | [Mr.Simple](https://github.com/bboyfeiyu)     |\n"
  },
  {
    "path": "issue-13/优化android-studio编译效率的方法.md",
    "content": "优化android studio编译效率的方法\n---\n\n> * 原文链接 : [Boosting the performance for Gradle in your Android projects](https://medium.com/@erikhellman/boosting-the-performance-for-gradle-in-your-android-projects-6d5f9e4580b6)\n* 原文作者 : [Erik Hellman](https://medium.com/@erikhellman)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [FTExplore](https://github.com/FTExplore) \n\n## 引言\n\n如果你之前用eclipse开发过Android app的化,转到android studio的第一反应也许就是:\"编译速度有点慢\". 表现的最明显的一点就是,当我使用eclipse开发的时候,选中了auto building.这个时候\n我更改了几个字符,eclipse会速度非常快的编译出一个新的apk. 而android studio使用gradle编译,每次编译,即便是更改的代码量很少,也会按照预先设置的task的顺序,依次走完编译的各项流程.所以\n这点就让人很痛苦. 然而问题总还是要被解决的,作者曾经亲眼看到过使用android studio仅仅用了2.5秒就编译完毕(在代码更改很少的情况下). 现在把如何优化gradle编译速度的方法记录在此,希望可以\n帮助到广大的同行们.\n\n## 准备工作\n\ngradle现在最新的版本是2.4, 相比较之前的版本, 在编译效率上面有了一个非常大的提高,为了确保你的android项目使用的是最新版的gradle版本,有两种方法可以使用,下面依次进行介绍\n\n### 1、在build.gradle中进行设置\n\n在你的项目gradle文件内(不是app里面的gradle文件), 添加一个task, 代码如下:\n\n```java\ntask wrapper(type: Wrapper) {\n    gradleVersion = '2.4'\n}\n```\n\n然后打开terminal, 输入./gradlew wrapper, 然后gradle就会自动去下载2.4版本,这也是官方推荐的手动设置gradle的方法(http://gradle.org/docs/current/userguide/gradle_wrapper.html)\n\n### 2、使用android studio对gradle版本进行设置\n\n这种方法需要你去手动去gradle官网下载一个zip包,解压缩后,打开android studio 设置界面的Project Structure. 然后手动添加你解压缩后的gradle的磁盘路径即可,可以参考如下的图片\n\n![image](http://img.blog.csdn.net/20150604104904903)\n\n有一点需要注意的是,这种设置方法仅适用于在你的项目中使用gradle wrapper进行编译打包的操作(就是android studio默认需要的东东).如果你想使用gradle做其他的事情,请出门左转,去gradle官网(http://gradle.org)\n\n## 守护进程,并行编译\n\n通过以上步骤,我们设置好了android studio使用最新的gradle版本,下一步就是正式开启优化之路了. 我们需要将gradle作为守护进程一直在后台运行,这样当我们需要编译的时候,gradle就会立即跑过来然后\n吭哧吭哧的开始干活.除了设置gradle一直开启之外,当你的工作空间存在多个project的时候,还需要设置gradle对这些projects并行编译,而不是单线的依次进行编译操作.\n\n说了那么多, 那么怎么设置守护进程和并行编译呢?其实非常简单,gradle本身已经有了相关的配置选项,在你电脑的GRADLE_HOME这个环境变量所指的那个文件夹内,有一个`.gradle/gradle.properties`文件.\n在这个文件里,放入下面两句话就OK了:\n\n```\norg.gradle.daemon=true\norg.gradle.parallel=true\n```\n\n有一个地方需要注意的是,android studio 本身在编译的时候,已经是使用守护进程中的gradle了,那么这里加上了org.gradle.daemon=true就是保证了你在使用命令行编译apk的时候也是使用的守护进程.\n\n你也可以将上述的配置文件放到你project中的根目录下,以绝对确保在任何情况下,这个project都会使用守护进程进行编译.不过有些特殊的情况下也许你应该注意守护进程的使用,具体的细节参考http://gradle.org/docs/current/userguide/gradle_daemon.html#when_should_i_not_use_the_gradle_daemon\n\n在使用并行编译的时候必须要注意的就是,你的各个project之间不要有依赖关系,否则的话,很可能因为你的Project A 依赖Project B, 而Project B还没有编译出来的时候,gradle就开始编译Project A 了.最终\n导致编译失败.具体可以参考http://gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects。\n \n还有一些额外的gradle设置也许会引起你的兴趣,例如你想增加堆内存的空间,或者指定使用哪个jvm虚拟机等等(代码如下)  \n\norg.gradle.jvmargs=-Xmx768m     \norg.gradle.java.home=/path/to/jvm\n\n如果你想详细的了解gradle的配置,请猛戳http://gradle.org/docs/current/userguide/userguide_single.html#sec:gradle_configuration_properties\n\n## 一个实验性的功能\n\n最后一个要介绍的是incremental dexing, 这个功能目前还在试验阶段,android studio默认是关闭的, 作者个人是非常推荐的,程序员就是爱折腾啊.\n\n开启incremental dexing也是非常简单的,就是在app级别的buid.gradle文件中加入下面的代码:\n\n```\ndexOptions {\n        incremental true\n}\n```\n\n感性您的阅读,希望这边文章可以对您有所帮助. 如果您有好的建议或者意见请联系我\n\n"
  },
  {
    "path": "issue-13/创建-RecyclerView-LayoutManager-Part-2.md",
    "content": "创建 RecyclerView LayoutManager – Part 2\n---\n\n> * 原文链接 : [Building a RecyclerView LayoutManager – Part 2][part2]\n> * 原文作者 : [Dave Smith][author]\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime) \n* 校对者:[chaossss](https://github.com/chaossss) \n* 状态 :   完成 \n\n\n# 创建 a RecyclerView LayoutManager – Part 2\n\n>本文是这个系列中的 Part 2，这里是 [Part 1][part1] 和 [Part 3][part3] 的链接。\n\n上次我们讲了创建一个 RecyclerView LayoutManager 的核心步骤。接下来，\n我们会介绍如何给普通基于适配器的 View 加入一些附加特性。\n\n>友情提醒：示例中的代码在这里 [Github][sample-github]\n\n---\n\n#Item Decorations 支持\n\nRecyclerView 有一个很好的特性 `RecyclerView.ItemDecoration`，它可以给\n子视图添加自定义样式，还可以在不修改子视图布局参数的情况下插入\n布局属性(margins)。后者就是 LayoutManager 必须提供的约束子视图布局方式。\n\n---\n\n\n>[RecyclerPlayground ][sample-github] 里有几个 decorator 用来介绍它们的实现方式。\n\nLayoutManager 中提供了一些辅助方法操作 decorations ，不需要我们自己实现：\n\n- 用`getDecoratedLeft()`代替`child.getLeft()`获取子视图的 left 边缘。\n- 用`getDecoratedTop()`代替`getTop()`获取子视图的 top 边缘。\n- 用`getDecoratedRight()`代替`getRight()`获取子视图的 right 边缘。\n- 用`getDecoratedBottom()`代替`getBottom()`获取子视图的 bottom 边缘。\n- 使用 `measureChild() ` 或 `measureChildWithMargins()` 代替`child.measure()` \n\t测量来自 Recycler 的新视图。\n- 使用`layoutDecorated() `代替 `child.layout()` 布局来自 Recycler 的新视图。\n- 使用  `getDecoratedMeasuredWidth() `或 `getDecoratedMeasuredHeight()`\n\t代替 `child.getMeasuredWidth()`或` child.getMeasuredHeight()`获取\n\t子视图的测量数据。\n\n只要你使用了正确的方法去获取视图的属性和测量数据，RecyclerView 会自己搞定细节部分的处理。\n\n---\n\n#数据集改变\n\n当使用 `notifyDataSetChanged()`触发 `RecyclerView.Adapter` 的更新操作时，\nLayoutManager 负责更新布局中的视图。这时，`onLayoutChildren()`会被再次调用。\n实现这个功能需要我们调整代码，判断出当前状态是生成一个新的视图 还是 adapter \n更新期间的视图改变。下面是`FixedGridLayoutManager`中的填充方法的完整实现：\n\n```java\n@Override\npublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n    //We have nothing to show for an empty data set but clear any existing views\n    if (getItemCount() == 0) {\n        detachAndScrapAttachedViews(recycler);\n        return;\n    }\n\n    //...on empty layout, update child size measurements\n    if (getChildCount() == 0) {\n        //Scrap measure one child\n        View scrap = recycler.getViewForPosition(0);\n        addView(scrap);\n        measureChildWithMargins(scrap, 0, 0);\n\n        /*\n         * We make some assumptions in this code based on every child\n         * view being the same size (i.e. a uniform grid). This allows\n         * us to compute the following values up front because they\n         * won't change.\n         */\n        mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);\n        mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);\n\n        detachAndScrapView(scrap, recycler);\n    }\n\n    updateWindowSizing();\n\n    int childLeft;\n    int childTop;\n    if (getChildCount() == 0) { //First or empty layout\n        /*\n         * Reset the visible and scroll positions\n         */\n        mFirstVisiblePosition = 0;\n        childLeft = childTop = 0;\n    } else if (getVisibleChildCount() > getItemCount()) {\n        //Data set is too small to scroll fully, just reset position\n        mFirstVisiblePosition = 0;\n        childLeft = childTop = 0;\n    } else { //Adapter data set changes\n        /*\n         * Keep the existing initial position, and save off\n         * the current scrolled offset.\n         */\n        final View topChild = getChildAt(0);\n        if (mForceClearOffsets) {\n            childLeft = childTop = 0;\n            mForceClearOffsets = false;\n        } else {\n            childLeft = getDecoratedLeft(topChild);\n            childTop = getDecoratedTop(topChild);\n        }\n\n        /*\n         * Adjust the visible position if out of bounds in the\n         * new layout. This occurs when the new item count in an adapter\n         * is much smaller than it was before, and you are scrolled to\n         * a location where no items would exist.\n         */\n        int lastVisiblePosition = positionOfIndex(getVisibleChildCount() - 1);\n        if (lastVisiblePosition >= getItemCount()) {\n            lastVisiblePosition = (getItemCount() - 1);\n            int lastColumn = mVisibleColumnCount - 1;\n            int lastRow = mVisibleRowCount - 1;\n\n            //Adjust to align the last position in the bottom-right\n            mFirstVisiblePosition = Math.max(\n                    lastVisiblePosition - lastColumn - (lastRow * getTotalColumnCount()), 0);\n\n            childLeft = getHorizontalSpace() - (mDecoratedChildWidth * mVisibleColumnCount);\n            childTop = getVerticalSpace() - (mDecoratedChildHeight * mVisibleRowCount);\n\n            //Correct cases where shifting to the bottom-right overscrolls the top-left\n            // This happens on data sets too small to scroll in a direction.\n            if (getFirstVisibleRow() == 0) {\n                childTop = Math.min(childTop, 0);\n            }\n            if (getFirstVisibleColumn() == 0) {\n                childLeft = Math.min(childLeft, 0);\n            }\n        }\n    }\n\n    //Clear all attached views into the recycle bin\n    detachAndScrapAttachedViews(recycler);\n\n    //Fill the grid for the initial layout of views\n    fillGrid(DIRECTION_NONE, childLeft, childTop, recycler);\n}\n```\n\n---\n\n我们根据有没有已经被 attach 的子视图来判断当前是一个新的布局还是一个更新操作。\n如果是更新，我们根据第一个可见视图的 position（通过监测视图左上角是哪个子视图）\n和当前 x/y 滑动的位移这些信息去执行新的 `fillGrid()`，同时保证左上角的 item 位置不变。\n\n---\n\n下面是一些需要特殊处理得情况：\n\n- 当新的数据集很小，不足以滑动时，布局会将左上角重置为 position 是 0 的item。\n- 如果新的数据集很小，保持当前位置会使滚动超出边界。\n\t我们就应该调整第一个 item 的位置，以便和右下角对齐。\n\t\n---\n\n###onAdapterChanged()\n\n这个方法提供了另一个重置布局的场所，设置新的 adapter 会触发这个事件\n(在这，`setAdapter`会被再次调用)。\n这个阶段你可以安全的返回一个与之前 adapter 完全不同的视图。所以，\n示例中我们移除了所有当前视图(并没有回收它们)。\n\n```java\n@Override\npublic void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {\n    //Completely scrap the existing layout\n    removeAllViews();\n}\n```\n\n移除视图会触发一个新的布局过程，当  `onLayoutChildren()` 被再次调用时，\n我们的代码会执行创建新视图的布局过程，因为现在没有 attched 的子视图。\n\n---\n\n#Scroll to Position\n另一个重要的特性就是给 LayoutManager 添加滚动到特定位置的功能。\n可以带有有动画效果，也可以没有，下面是对应的两个回调方法。\n\n###scrollToPosition()\n\n当 layout 应该立即将所给位置设为第一个可见 item 时，调用 RecyclerView 的 `scrollToPosition()`。\n在一个 vertical list 里，item 应该放在顶部；horizontal list 中，通常放在左边。在我们的\n网格布局中，被选中的 item 应该放在视图的左上角。\n\n```java\n@Override\npublic void scrollToPosition(int position) {\n    if (position >= getItemCount()) {\n        Log.e(TAG, \"Cannot scroll to \"+position+\", item count is \"+getItemCount());\n        return;\n    }\n\n    //Ignore current scroll offset, snap to top-left\n    mForceClearOffsets = true;\n    //Set requested position as first visible\n    mFirstVisiblePosition = position;\n    //Trigger a new view layout\n    requestLayout();\n}\n```\n\n因为有一个良好的 `onLayoutChildren()` 实现，这里就可以简单的更新目标位置并触发一个\n新的 fill 操作。\n\n---\n\n###smoothScrollToPosition()\n\n在带有动画的情况下，我们需要使用一些稍微不同的方法。\n在这方法里我们需要创建一个 `RecyclerView.SmoothScroller`实例，\n然后在方法返回前请求`startSmoothScroll()`启动动画。\n\n`RecyclerView.SmoothScroller` 是提供 API 的抽象类，含有四个方法：\n\n---\n\n- `onStart()`：当滑动动画开始时被触发。\n- `onStop()`：当滑动动画停止时被触发。\n- `onSeekTargetStep()`：当 scroller 搜索目标 view 时被重复调用，这个方法负责读取提供的 \n\tdx/dy ，然后更新应该在这两个方向移动的距离。\n\t- 这个方法有一个`RecyclerView.SmoothScroller.Action`实例做参数。\n\t\t通过向 action 的 `update()`方法传递新的 dx, dy, duration 和 `Interpolator` ，\n\t\t告诉 view 在下一个阶段应该执行怎样的动画。\n\t- **NOTE：** 如果动画耗时过长，框架会对你发出警告，\n\t\t\t应该调整动画的步骤，尽量和框架标准的动画耗时相同。\n- `onTargetFound()`：只在目标视图被 attach 后调用一次。\t这是将目标视图要通过动画移动到准确位置最后的场所。\n\t- 在内部，当 view 被 attach 时使用  LayoutManager  的 `findViewByPosition()` 方法\n\t\t查找对象。如果你的 LayoutManager 可以有效匹配 view 和 position ，\n\t\t可以覆写这个方法来优化性能。默认提供的实现是通过每次遍历所有子视图查找。\n\n---\n\n你可以自己实现一个 scroller 达到你想要的效果。不过这里我们只使用系统提供的\n`LinearSmoothScroller` 就好了。只需实现一个方法`computeScrollVectorForPosition()`，\n然后告诉 scroller 初始方向还有从当前位置滚动到目标位置的大概距离。\n\n---\n```java\n@Override\npublic void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, final int position) {\n    if (position >= getItemCount()) {\n        Log.e(TAG, \"Cannot scroll to \"+position+\", item count is \"+getItemCount());\n        return;\n    }\n\n    /*\n     * LinearSmoothScroller's default behavior is to scroll the contents until\n     * the child is fully visible. It will snap to the top-left or bottom-right\n     * of the parent depending on whether the direction of travel was positive\n     * or negative.\n     */\n    LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {\n        /*\n         * LinearSmoothScroller, at a minimum, just need to know the vector\n         * (x/y distance) to travel in order to get from the current positioning\n         * to the target.\n         */\n        @Override\n        public PointF computeScrollVectorForPosition(int targetPosition) {\n            final int rowOffset = getGlobalRowOfPosition(targetPosition)\n                    - getGlobalRowOfPosition(mFirstVisiblePosition);\n            final int columnOffset = getGlobalColumnOfPosition(targetPosition)\n                    - getGlobalColumnOfPosition(mFirstVisiblePosition);\n\n            return new PointF(columnOffset * mDecoratedChildWidth, rowOffset * mDecoratedChildHeight);\n        }\n    };\n    scroller.setTargetPosition(position);\n    startSmoothScroll(scroller);\n}\n```\n\n---\n\n在这个实现中，和现有 ListView 的行为相似，无论是 RecyclerView 的哪个方向滚动，\n当视图完全可见时滚动就会停止。\n\n\n\n---\n\n#接下来？\n\n我们现在已经有些起色了！事实上还有很多可以实现的功能。在[下篇文章][part3]中，我们会介绍\n如何给你的 LayoutManager 提供数据集改变时的动画效果。\n\n\n---\n\n[part1]:../issue-9/创建-RecyclerView-LayoutManager-Part-1.md\n[author]:http://wiresareobsolete.com/\n[part2]:http://wiresareobsolete.com/2014/09/recyclerview-layoutmanager-2/\n[part3]:./创建-RecyclerView-LayoutManager-Part-3.md\n[sample-github]:https://github.com/devunwired/recyclerview-playground\n"
  },
  {
    "path": "issue-13/创建-RecyclerView-LayoutManager-Part-3.md",
    "content": "创建 RecyclerView LayoutManager – Part 3\n---\n\n> * 原文链接 : [Building a RecyclerView LayoutManager – Part 3][part3]\n> * 原文作者 : [Dave Smith][author]\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime) \n* 校对者:[chaossss](https://github.com/chaossss) \n* 状态 :   完成 \n\n\n# Building a RecyclerView LayoutManager – Part 3\n\n\n>本文是这个系列中的 Part 3，这里是 [Part 1][part1] 和 [Part 2][part2] 的链接。\n\n在[之前的文章][part2]里，我们讨论了怎样对数据集改变和定量滚动\n提供正确的支持。接下来我们会介绍怎样给 LayoutManager 添加\n合适的动画效果。\n\n>友情提醒：示例中的代码在这里 [Github][sample-github]\n\n---\n\n#The Problem With Free\n\n上次我们说到了 `notifyDataSetChanged()` ，但是使用这个方法\n不会有数据改变的动画效果<a id=\"1\" href=\"#b1\">(1)</a>。\nRecyclerView 提供了新的 API 让我们可以通知 adapter \n做出带有动画效果的改变。它们是：\n\n- `notifyItemInserted()`  和 `notifyItemRangeInserted()`：\n\t在给定位置/范围插入新item(s)。\n- `notifyItemChanged()` 和 `notifyItemRangeChanged()`：\n\t使给定 位置/范围 的 item(s) 无效，数据集并没有结构上的改变。\n- `notifyItemRemoved()` 和 `notifyItemRangeRemoved()`：\n\t移除给定 位置/范围 的 item(s)。\n- `notifyItemMoved()`：\n\t将数据集中的一个 item 重定位到一个新的位置。\n\n\n使用这些方法你的 LayoutManager 会得到一个很简单的默认 item 动画。\n这些动画是根据当前每一个 view 在改变后是否还存在于 layout 之中生成的。\n新的 view 渐入，被移除的 view 淡出，其他 view 移动到新的位置。\n下面是我们 grid layout 示例的动画效果：\n\n![img][animate-01]\n \n不过这里有个问题，一些没有被移除的 item 也淡出了。\n这是因为它们在父控件 RecyclerView 的边界中不再可见。\n我们想要这些 view 朝着用户期望的方向滑去，但是框架只知道\n我们的代码在数据改变后没有显示出这些 item。此外，\n新加入的 view 是淡入进来的，让他们从预定位置滑入界面会更好。\n\n我们要给 LayoutManager 加点东西才能实现这些。\n\n---\n\n#Predictive Item Animations\n\n下面的图片展示了我们期望的移除 item 动画效果：\n![img][animate-02]\n左侧一列 items 滑到右侧填补空白部分的动画很引人注意。\n和这个差不多，你可以脑补出在这个位置添加一个 item 时的动画效果。\n\n在第一篇文章里曾提到的，`onLayoutChildren()` 通常只会\n在父控件 `RecyclerView` 初始化布局 或者 数据集的大小(比如 item 的数量)改变时调用一次。\nPredictive Item Animations \n这个特性允许我们给 view (基于数据改变产生)的过渡动画\n提供更多有用的信息。想要使用这个特性，就要告诉\n框架我们的 `LayoutManager` 提供了这个附加数据：\n\n```java\n@Override\npublic boolean supportsPredictiveItemAnimations() {\n    return true;\n}\n```\n\n有了这个改动，`onLayoutChildren() `会在每次数据集改变后被调用两次，\n一次是\"预布局\"(pre-layout)阶段，一次是真实布局(real layout)。\n\n---\n\n#在  pre-layout 阶段该做些什么\n\n在`onLayoutChildren()`的  pre-layout 阶段，\n你应该运行你的布局逻辑设置动画的**初始状态**。\n这需要你在动画执行前布局所有 当前可见的 view **和** 在动画后会可见的 view\n(被称为 **APPEARING** view)。Appearing views 应该被布局在\n屏幕之外，用户期望它进入的位置。框架会捕获他们的位置，\n籍此创建更合适的动画效果。\n\n>我们可以使用 `RecyclerView.State.isPreLayout()` 来检测当前处于哪一阶段\n\n在 `FixedGridLayoutManager` 示例中，我们根据数据集的改变\n使用  pre-layout 决定哪些 view 被移除。被移除的 view \n在 pre-layout 的 Recycler 中仍然会被返回，所以你不用担心\n会出现空白位置。如果你想要判断视图是否会被移除，\n可以使用`LayoutParams.isViewRemoved()` 这个方法 。\n我们的示例统计了被移除 view 的数量，让我们\n对有多少空间会被 appearing views  填充有一个大概的印象。\n\n---\n\n```java\n@Override\npublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n    …\n\n    SparseIntArray removedCache = null;\n    /*\n     * During pre-layout, we need to take note of any views that are\n     * being removed in order to handle predictive animations\n     */\n    if (state.isPreLayout()) {\n        removedCache = new SparseIntArray(getChildCount());\n        for (int i=0; i < getChildCount(); i++) {\n            final View view = getChildAt(i);\n            LayoutParams lp = (LayoutParams) view.getLayoutParams();\n\n            if (lp.isItemRemoved()) {\n                //Track these view removals as visible\n                removedCache.put(lp.getViewPosition(), REMOVE_VISIBLE);\n            }\n        }\n        …\n    }\n\n    …\n\n    //Fill the grid for the initial layout of views\n    fillGrid(DIRECTION_NONE, childLeft, childTop, recycler, state.isPreLayout(), removedCache);\n\n    …\n}\n```\n\n>Tip：在 pre-layout 期间，RecyclerView 会尝试用 view\n>在 adapter 中的位置匹配它们的\"原位置\"(数据改变前的位置)。\n>如果你想通过 position 请求一个 view，并且希望这个位置\n>是这个视图初始化时的位置。就不要在 pre-layout 和\n>real-layout 期间改变它们。\n\n示例代码中最后的变动是对`fillGrid()`进行修改，在这里给 N \n个 appearing views  布局，N 是被移除的可见视图个数。\n这些 view 永远是从右侧进入的，所以他们被安排在最后一列\n可见 view 的后面。\n\n```java\nprivate void fillGrid(int direction, int emptyLeft, int emptyTop, RecyclerView.Recycler recycler,\n        boolean preLayout, SparseIntArray removedPositions) {\n    …\n\n    for (int i = 0; i < getVisibleChildCount(); i++) {\n        int nextPosition = positionOfIndex(i);\n\n        …\n\n        if (i % mVisibleColumnCount == (mVisibleColumnCount - 1)) {\n            leftOffset = startLeftOffset;\n            topOffset += mDecoratedChildHeight;\n\n            //During pre-layout, on each column end, apply any additional appearing views\n            if (preLayout) {\n                layoutAppearingViews(recycler, view, nextPosition, removedPositions.size(), …);\n            }\n        } else {\n            leftOffset += mDecoratedChildWidth;\n        }\n    }\n\n    …\n}\n\nprivate void layoutAppearingViews(RecyclerView.Recycler recycler, View referenceView,\n        int referencePosition, int extraCount, int offset) {\n    //Nothing to do...\n    if (extraCount < 1) return;\n\n    for (int extra = 1; extra <= extraCount; extra++) {\n        //Grab the next position after the reference\n        final int extraPosition = referencePosition + extra;\n        if (extraPosition < 0 || extraPosition >= getItemCount()) {\n            //Can't do anything with this\n            continue;\n        }\n\n        /*\n         * Obtain additional position views that we expect to appear\n         * as part of the animation.\n         */\n        View appearing = recycler.getViewForPosition(extraPosition);\n        addView(appearing);\n\n        //Find layout delta from reference position\n        final int newRow = getGlobalRowOfPosition(extraPosition + offset);\n        final int rowDelta = newRow - getGlobalRowOfPosition(referencePosition + offset);\n        final int newCol = getGlobalColumnOfPosition(extraPosition + offset);\n        final int colDelta = newCol - getGlobalColumnOfPosition(referencePosition + offset);\n\n        layoutTempChildView(appearing, rowDelta, colDelta, referenceView);\n    }\n}\n```\n\n在 `layoutAppearingViews()`这个方法里，每一个 appearing view\n被布局到它的\"全局\"位置(就是它在这个网格中占据的行/列)。\n虽然位置在屏幕之外，但是为框架创建滑入 view 动画的起始点提供了\n必要的数据。\n\n\n---\n\n#Changes for the “Real” Layout\n\n[part1][part1]中我们已经讨论过布局期间的基本工作，\n然而要想为我们的动画提供支持还要做一些修改。\n其中之一就是判断有没有 disappearing views。在我们的示例中\n是通过运行一个普通的布局过程，然后检查 Recycler\n的 scrap heap 之中有没有剩下的 view。\n\n\n>注意：我们之所以能以这种方式使用 scrap heap 是因为\n>在每一次布局过程开始前，布局逻辑总是调用了\n>`detachAndScrapAttachedViews()`这个方法。\n>前面说过，这是布局中你需要遵循的最佳实践。\n\n---\nViews still in scrap that aren’t considered removed are \ndisappearing views. We need to lay these views out in \ntheir off-screen positions so the animation system \ncan slide them out of view (instead of just fading them out).\n\n仍在 scrap 中没有被移除的视图就是 disappearing views。\n我们需要把它们放置到屏幕之外的位置，以便动画系统\n将它们滑出视图(用来取代淡出动画)。\n\n---\n\n```java\n@Override\npublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n    …\n\n    if (!state.isPreLayout() && !recycler.getScrapList().isEmpty()) {\n        final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();\n        final HashSet<View> disappearingViews = new HashSet<View>(scrapList.size());\n\n        for (RecyclerView.ViewHolder holder : scrapList) {\n            final View child = holder.itemView;\n            final LayoutParams lp = (LayoutParams) child.getLayoutParams();\n            if (!lp.isItemRemoved()) {\n                disappearingViews.add(child);\n            }\n        }\n\n        for (View child : disappearingViews) {\n            layoutDisappearingView(child);\n        }\n    }\n}\n\nprivate void layoutDisappearingView(View disappearingChild) {\n    /*\n     * LayoutManager has a special method for attaching views that\n     * will only be around long enough to animate.\n     */\n    addDisappearingView(disappearingChild);\n\n    //Adjust each disappearing view to its proper place\n    final LayoutParams lp = (LayoutParams) disappearingChild.getLayoutParams();\n\n    final int newRow = getGlobalRowOfPosition(lp.getViewPosition());\n    final int rowDelta = newRow - lp.row;\n    final int newCol = getGlobalColumnOfPosition(lp.getViewPosition());\n    final int colDelta = newCol - lp.column;\n\n    layoutTempChildView(disappearingChild, rowDelta, colDelta, disappearingChild);\n}\n```\n\n---\n\n>小心：布局视图(然后将它们加入container)把它们从 scrap 列表中移除。\n>在开始变化前，小心处理你需要从 scrap 中获取的视图，否则你可能会\n>在这个集合上出现并发修改的问题结束运行。\n\n\n和之前处理 appearing views  的代码差不多，`layoutDisappearingView()`\n将所有剩余 view 放在与之对应的\"全局\"位置作为最终布局位置。\n给框架提供必要信息创建出适当方向的滑出动画。\n\n下面的图片可以帮你理解`FixedGridLayoutManager`之中的过程：\n\n- 黑框是 `RecyclerView` 的可视边界。\n- Red View：数据集中被移除的 item。\n- Green View (Appearing View)：开始时没有，在 pre-layout 过程中被布局到屏幕外的item。\n- Purple Views (Disappearing views)：pre-layout 时期放置在他们的原始位置 ，\n\treal-layout 时期被布局到屏幕之外的位置。\n\n![img][animate-03]\n\n---\n\n\n#响应屏幕外的变动\n你或许注意到在上一节中我们可以判断可视 views 的移除操作。\n如果变化出现在可视边界之外会怎样？这取决于你的布局结构，\n像这样的变化可能需要你调整布局来达到更好的动画效果。\n\nAdapter 会将这个变化 post 给你的 LayoutManager。你可以覆写\n`onItemsRemoved()`, `onItemsMoved()`, `onItemsAdded()` 或者\n`onItemsChanged()` 响应 item 的这些事件，无论 item \n在当前布局中是否可见。\n\n如果被移除的范围在可视边界之外， 调用 pre-layout 之前会调用\n`onItemRemoved()`。我们可以利用它收集和这个变化有关的数据，为\n这个事件可能触发的  appearing view 改变提供更好的支持。\n\n示例中，我们像之前一样收集被移除的 view，但是将它们标记成不同的类型。\n\n---\n\n```java\n@Override\npublic void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {\n    mFirstChangedPosition = positionStart;\n    mChangedPositionCount = itemCount;\n}\n\n@Override\npublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n    …\n\n    SparseIntArray removedCache = null;\n    /*\n     * During pre-layout, we need to take note of any views that are\n     * being removed in order to handle predictive animations\n     */\n    if (state.isPreLayout()) {\n        …\n\n        //Track view removals that happened out of bounds (i.e. off-screen)\n        if (removedCache.size() == 0 && mChangedPositionCount > 0) {\n            for (int i = mFirstChangedPosition; i < (mFirstChangedPosition + mChangedPositionCount); i++) {\n                removedCache.put(i, REMOVE_INVISIBLE);\n            }\n        }\n    }\n\n    …\n\n    //Fill the grid for the initial layout of views\n    fillGrid(DIRECTION_NONE, childLeft, childTop, recycler, state.isPreLayout(), removedCache);\n\n    …\n}\n```\n\n---\n\n>TIP：如果被移除的 item 是可见的，这个方法在 pre-layout \n>之后还会被调用。这也就是为什么\n>当被移除的可见 views 出现时我们仍要从它们获取数据。\n\n所有步骤就位，现在我们可以启动这个应用啦。可以看到左边消失的items\n移到对应行的后面。右边新出现的 items 滑动进入现有的界面。\n现在，新的动画中只有被移除的 item 是淡出的了。\n\n![img][animate-04]\n\n---\n\n#未完待续...\n\n我说过...这应该是这系列中的最后一篇。不过，在编写 `FixedGridLayoutManager`\n动画效果的过程中又出现了些有趣的问题，并不是所有自定义的实现。\n所以在下一篇文章里(这次真的是最后一篇了)，我会解决这些问题。\n\n特别感谢 [Yiğit Boyar][thanks]提供技术支持，帮助完成这篇文章。\n\n---\n\n1. 如果你的 adapter 使用了固定的 IDs ，可以提供足够的数据推测哪些 view 被 移除/添加/等等\n\t框架就会尝试 给它添加动画\t<a id=\"b1\" href=\"#1\">↩</a>\n\n---\n\n\n[part1]:../issue-9/创建-RecyclerView-LayoutManager-Part-1.md\n[author]:http://wiresareobsolete.com/\n[part2]:./创建-RecyclerView-LayoutManager-Part-2.md\n[part3]:http://wiresareobsolete.com/2015/02/recyclerview-layoutmanager-3/\n[sample-github]:https://github.com/devunwired/recyclerview-playground\n[animate-01]:http://i.embed.ly/1/display/resize?url=http%3A%2F%2Fwiresareobsolete.com%2Fwp-content%2Fuploads%2F2015%2F02%2FDefaultRecyclerAnimationsSmall.gif&grow=true&key=92b31102528511e1a2ec4040d3dc5c07\n[animate-02]:http://i.embed.ly/1/display/resize?url=http%3A%2F%2Fwiresareobsolete.com%2Fwp-content%2Fuploads%2F2015%2F02%2FRecycleConceptSmall.gif&grow=true&key=92b31102528511e1a2ec4040d3dc5c07&height=400\n[animate-03]:http://i.embed.ly/1/display/resize?url=http%3A%2F%2Fwiresareobsolete.com%2Fwp-content%2Fuploads%2F2015%2F02%2FRecycleRemove.gif&grow=true&key=92b31102528511e1a2ec4040d3dc5c07&height=400\n[animate-04]:http://i.embed.ly/1/display/resize?url=http%3A%2F%2Fwiresareobsolete.com%2Fwp-content%2Fuploads%2F2015%2F02%2FPredictiveRecyclerAnimationsSmall.gif&grow=true&key=92b31102528511e1a2ec4040d3dc5c07\n[thanks]:https://plus.google.com/111851968937104436377/posts\n"
  },
  {
    "path": "issue-13/创建-RecyclerView-LayoutManager-Redux.md",
    "content": "创建-RecyclerView-LayoutManager-Redux\n---\n\n> * 原文链接 : [Building A RecyclerView LayoutManager – Redux](http://wiresareobsolete.com/2015/02/recyclerview-layoutmanager-redux/)\n* 原文作者 : [Dave Smith](http://wiresareobsolete.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Mr.Simple](https://github.com/bboyfeiyu) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成 \n\n\n\n这篇文章是RecyclerView系列文章的结尾篇。前三部分的链接在这里 :       \n\n* [第一部分](../issue-9/创建-RecyclerView-LayoutManager-Part-1.md)\n* [第二部分](./创建-RecyclerView-LayoutManager-Part-2.md)\n* [第三部分](./创建-RecyclerView-LayoutManager-Part-3.md)\n\n当我在写这个系列的最后一篇文章，也就是关于predictIve animation的讨论时，我突然想到几个很有意思的点，并且很有讨论价值。这个系列文章以调查RecyclerView是否能够以简单的方式满足竖向、横向滚动的布局需求以及开发者要定制一个LayoutManager的难度有多大开始。这篇文章中我选择了一个基本的网格布局作为自定义layoutManager的示例。\n\n下面这幅图展示了运用这个自定义LayoutManager到RecyclerView的滚动效果。       \n\n![](http://i.embed.ly/1/display/resize?url=http%3A%2F%2Fwiresareobsolete.com%2Fwp-content%2Fuploads%2F2015%2F02%2FGridWindow.gif&grow=true&key=92b31102528511e1a2ec4040d3dc5c07&height=400)\n\n## 打破常规设计\n\n无论你打算如何组织Adapter的位置（从左到右，上到下等等）,内容视图的视角都是显示部分不连贯数据集的区域。更确切地说，在第一个和最后一个可视位置之间的Adapter区域的数据项也会在可视区域view的外面。 \n\n这是一个很重要的点，因为它与在单轴上滚动的布局大相径庭（从而导致与当前框架下提供的默认布局背道而驰）。这些标准小工具显示，数据集是在一个连接块范围内--从第一个到最后一个可视位置之间不间断。\n\nRecyclerView LayoutManager API会假定有一个可见的数据集合，RecyclerView LayoutManager只会显示数据集合中某个范围内可见的数据项，然后产生一个像上述所展示的网格布局一样的布局效果，这稍微有那么点挑战。在predictive animation里没有什么比这部分更加明显了。为了便于扩展，我感觉在这里指出这些毛病是非常必要的。\n\n\n## 假设1 ： 从不可见区域移除一个数据项不会影响当前可见的View\n\n当你考虑到一个Adapter移除一个数据项时LinearLayoutManager或者GridLayoutManager会做何反应时，这两个LayoutManager实际上都处理得非常好。如果被移除的数据项是可见的，一个可用的区域将会空置出来，此时就需要一个周边的View进行填充。这意味着周边显示的View必须被布局到这个位置以填补缺口。然而，并没有一个示例表明当一个移除操作执行时会发送一个隐藏视图的操作，唯一被隐藏的视图是那些被显式移除的。如果被移除的View在可见区域之外，那么对于可见区域的布局不会产生影响。在这个情况之下，你不会看到任何动画，它可能做出的修改是数据项被移到其他位置。\n\n从上述的情况看，这与文章开头我们提到的一样，LayoutManager显示的是一个不连续的数据项。然而，这一可视区域的不连续本质使得数据项在屏幕外从可视区域内移除！换种说法，它们的位置处于第一个和最后一个位置之间，但是目前数据项view并不在布局中。这导致的结果就是，屏幕外发生的数据项移除可以并且会影响我们在布局动画处理时view的生成和消失。\n\n在有机会对view的生成进行布局时，预布局是RecyclerView动画的关键阶段。RecyclerView通过其初始位置值将view返回，据此我们可以将内容布局在其初始状态。但是，当view移除与可视区域不相交时。RecyclerView就会通过其最终位置值将view返回。这样一来，在没有附加记账的情况下处理view的生成就变得困难得多。不过话说回来，难是难，也还是可以做到。\n\n在上一篇文章中我们看到的FixedGridLayoutManager，我们不仅要解析可视view，还要听从onItemsRemoved()的回调来找到移除点，并妥当处理所有生成的view案例。RecyclerView确保我们在需要时，该回调会在预布局之前出现（屏幕外案例），但相反情况下会在预布局之后出现。RecyclerView这么做是为了避免这些事件与你的布局产生冲突--其时间点对我们来说只是一个美丽的意外。\n\n我们还需要追踪的一个事实就是，可视移除会以一种我们意料之中的方式偏移view的位置，而屏幕外移除则不会。这也是为什么移除会被冠以不同类型的原因。上一篇文章忽略的一点表明，在屏幕外进行移除时，我们会对view生成逻辑提供一个手动偏移。。。所以当移除可视时，位置之间会相互匹配。\n\n```java\nprivate void fillGrid(int direction, int emptyLeft, int emptyTop, RecyclerView.Recycler recycler,\n        boolean preLayout, SparseIntArray removedPositions) {\n    …\n\n    for (int i = 0; i < getVisibleChildCount(); i++) {\n        int nextPosition = positionOfIndex(i);\n\n        /*\n         * When a removal happens out of bounds, the pre-layout positions of items\n         * after the removal are shifted to their final positions ahead of schedule.\n         * We have to track off-screen removals and shift those positions back\n         * so we can properly lay out all current (and appearing) views in their\n         * initial locations.\n         */\n        int offsetPositionDelta = 0;\n        if (preLayout) {\n            int offsetPosition = nextPosition;\n\n            for (int offset = 0; offset < removedPositions.size(); offset++) {\n                //Look for off-screen removals that are less-than this\n                if (removedPositions.valueAt(offset) == REMOVE_INVISIBLE\n                        && removedPositions.keyAt(offset) < nextPosition) {\n                    //Offset position to match\n                    offsetPosition--;\n                }\n            }\n            offsetPositionDelta = nextPosition - offsetPosition;\n            nextPosition = offsetPosition;\n        }\n\n        if (nextPosition < 0 || nextPosition >= getItemCount()) {\n            //Item space beyond the data set, don't attempt to add a view\n            continue;\n        }\n\n        …\n\n        if (i % mVisibleColumnCount == (mVisibleColumnCount - 1)) {\n            leftOffset = startLeftOffset;\n            topOffset += mDecoratedChildHeight;\n\n            //During pre-layout, on each column end, apply any additional appearing views\n            if (preLayout) {\n                layoutAppearingViews(recycler, view, nextPosition, removedPositions.size(), offsetPositionDelta);\n            }\n        } else {\n            leftOffset += mDecoratedChildWidth;\n        }\n    }\n\n    …\n}\n```\n\n之后offsetPositionDelta值会作为我们在预布局中使用到的行/列位置的全局偏移被传递到layoutAppearingViews()。如果不是出于附加记账要求，这一偏移完全可以不必存在。\n\n## 假设#2：添加新的数据项只会导致同级view的消失，而不是生成。\n\n添加了新的数据项之后，反面为真。如果在添加时数据项为可视，那么标准布局管理器会在屏幕外推开不可视的view以便腾出空间。之前没有出现过关于这种行为会同时触发一个或多个同级view滑向可视子类位置的案例。移除也是同样的道理，在可视区域外添加数据项并不会影响可视view，所以通常来说动画也不会起作用。\n\n对于FixedGridLayoutManager或其他任何非连贯区域的布局，在可视区域内还是可视区域外进行添加并没有多大差别。两种情况中我们都需要管理可能出现的view生成和消失。而我们在移除中使用的方案在这里则不可行，因为onItemsAdded()总是在预布局之后被调用。。。这一次我们就没那么幸运地再一次能够碰到美丽的意外了。\n\n缺少了那次回调，关于添加数据项，我们实际上在预布局时就没有多少可做的了。这样一来就变成了布局额外的view以备不时之需，而不布局一定量的额外view就会损害性能这两种情况之间的妥协。FixedGridLayoutManager在添加数据项时不支持预测view生成。\n\n## 一切才刚刚开始\n\nRecyclerView APIs是一个新生事物，现有案例中还有非常多的地方需要改进的。同时，它也非常复杂，想要做对并不容易。表面上看起来RecyclerView要求大家付出的努力，背后可能大家要花10倍的精力才能实现。而且这些类型越到后面可能月蛋疼。希望正在尝试这种做法的各位同仁可以视本文为一个预警，不要盲目浪费时间，但同时我们也期待这这个框架的日渐成熟。\n\n\n"
  },
  {
    "path": "issue-14/Android-Design-Support-Library.md",
    "content": "Android Design Support Library\n---\n\n> * 原文链接 : [Android Design Support Library](http://android-developers.blogspot.jp/2015/05/android-design-support-library.html)\n* 原文作者 : [Android Developers Blog](http://developer.android.com/index.html)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [MiJack](https://github.com/MiJack) \n* 状态 :  校对完成\n\nAndroid 5.0是有史以来最重要的Android 版本之一，这其中有很大部分要归功于Material design的引入，这种新的设计语言让整个Android的用户体验焕然一新。我们的详细专题是帮助你开始采用Material Design。但是我们也知道，这种设计对于开发者，尤其是那些在意向后兼容的开发者来说是一种挑战。在Android Design Support Library的帮助下，我们为所有的开发者，所有2.1以上的设备，带来了一些重要的Material design控件。你可以在这里面找到Navigation Drawer View，输入控件的悬浮标签，Floating Action Button，Snackbar，Tab以及将这些控件结合在一起的手势滚动框架。\n\n[YouTube的介绍](https://youtu.be/32i7ot0y78U)\n\n###Navigation View\n\n[Navigation drawer](http://www.google.com/design/spec/patterns/navigation-drawer.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)是app识别度与内部导航的关键，保持这里设计上的一致性对app的可用性至关重要，尤其是对于第一次使用的用户。 NavigationView 通过提供抽屉导航所需的框架让实现更简单，同时它还能够直接通过菜单资源文件直接生成导航元素。\n\n![](http://3.bp.blogspot.com/-WmBBQQEJIKM/VWikAyy08sI/AAAAAAAABvc/1R36Txk83UI/s400/drawer.png)\n\n把NavigationView作为DrawerLayout的内容视图来使用，布局如下：\n\n```\n<android.support.v4.widget.DrawerLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n    <!-- your content layout -->\n\n    <android.support.design.widget.NavigationView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"start\"\n            app:headerLayout=\"@layout/drawer_header\"\n            app:menu=\"@menu/drawer\"/>\n</android.support.v4.widget.DrawerLayout>\n```\n\n你会注意到 NavigationView 的两个属性：app:headerLayout  - 控制头部的布局， app:menu - 导航菜单的资源文件（也可以在运行时配置）。NavigationView处理好了和状态栏的关系，可以确保NavigationView在API21+设备上正确的和状态栏交互。\n\n最简单的抽屉菜单往往是几个可点击的菜单的集合：\n\n```\n<group android:checkableBehavior=\"single\">\n    <item\n        android:id=\"@+id/navigation_item_1\"\n        android:checked=\"true\"\n        android:icon=\"@drawable/ic_android\"\n        android:title=\"@string/navigation_item_1\"/>\n    <item\n        android:id=\"@+id/navigation_item_2\"\n        android:icon=\"@drawable/ic_android\"\n        android:title=\"@string/navigation_item_2\"/>\n</group>\n```\n\n被点击过的item会高亮显示在抽屉菜单中，让用户知道当前是哪个菜单被选中。\n你也可以在menu中使用subheader来为菜单分组：\n\n```\n<item\n    android:id=\"@+id/navigation_subheader\"\n    android:title=\"@string/navigation_subheader\">\n    <menu>\n        <item\n            android:id=\"@+id/navigation_sub_item_1\"\n            android:icon=\"@drawable/ic_android\"\n            android:title=\"@string/navigation_sub_item_1\"/>\n        <item\n            android:id=\"@+id/navigation_sub_item_2\"\n            android:icon=\"@drawable/ic_android\"\n            android:title=\"@string/navigation_sub_item_2\"/>\n    </menu>\n</item>\n```\n\n你可以通过设置一个OnNavigationItemSelectedListener，使用其setNavigationItemSelectedListener()来获得元素被选中的回调事件。它为你提供可以点击的[MenuItem](http://developer.android.com/reference/android/view/MenuItem.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)，让你可以处理选择事件，改变复选框状态，加载新内容，关闭导航菜单，以及其他任何你想做的操作。\n\n\n###Floating labels for editing text\n\n即便是十分简单的EditText，在Material Design 中也有提升的空间。在EditText中，当你填入第一个字符后，hint就消失了。现在将它换成[TextInputLayout](http://developer.android.com/reference/android/support/design/widget/TextInputLayout.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)，提示信息会变成一个显示在EditText之上的floating label，这样用户就始终知道他们现在输入的是什么。\n\n![](http://4.bp.blogspot.com/-BUKc5AwzS4A/VWihVlHr9cI/AAAAAAAABvI/rslBAoaHwzA/s1600/textinputlayout.png)\n\n除此以外，还可以通过setError()设置当用户输入不合法时的Error提示；\n\n###Floating Action Button\n\n\n[Floating action button](http://www.google.com/design/spec/components/buttons-floating-action-button.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)是一个负责显示界面基本操作的圆形按钮。Design library中的[FloatingActionButton](http://developer.android.com/reference/android/support/design/widget/FloatingActionButton.html?utm_campaign=io15&utm_source=dac&utm_medium=blog) 实现了一个默认颜色为主题中colorAccent的悬浮操作按钮。\n\n![](http://2.bp.blogspot.com/-tdrgNYnQZyw/VWiOcfSRoYI/AAAAAAAABuU/6LsOxJFE4hE/s1600/image03.png)\n\n\n除了一般大小的悬浮操作按钮，它还支持mini size（fabSize=”mini”）。FloatingActionButton继承自ImageView，你可以使用android:src或者ImageView的任意方法，比如[setImageDrawable()](http://developer.android.com/reference/android/widget/ImageView.html?utm_campaign=io15&utm_source=dac&utm_medium=blog#setImageDrawable(android.graphics.drawable.Drawable)来设置FloatingActionButton里面的图标。\n\n###Snackbar\n\n\n如果你需要一个轻量级、可以快速作出反馈的控件，可以试试[SnackBar](http://www.google.com/design/spec/components/snackbars-toasts.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)。[Snackbar](http://www.google.com/design/spec/components/snackbars-toasts.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)显示在屏幕的底部,包含了文字信息与一个可选的操作按钮。在指定时间结束之后自动消失。另外，用户还可以滑动删除它们。\n\n\n[example video](http://material-design.storage.googleapis.com/publish/material_v_3/material_ext_publish/0B6Okdz75tqQsLVVnZlF4UEtKRU0/components_snackbar_specs_fabtablet_002.webm)\n\n\nSnackbar，可以通过滑动或者点击进行交互，可以看作是比Toast更强大的快速反馈机制，你会发现他们的API非常相似。\n\n```\nSnackbar\n  .make(parentLayout, R.string.snackbar_text, Snackbar.LENGTH_LONG)\n  .setAction(R.string.snackbar_action, myOnClickListener)\n  .show(); // Don’t forget to show!\n```\n\n你应该注意到了make()方法中把一个View作为第一个参数，Snackbar试图找到一个合适的父亲以确保自己是被放置于底部。\n\n###Tabs\n\n通过选项卡的方式切换[View](http://www.google.com/design/spec/components/tabs.html)并不是Material design中才有的新概念，它们和[顶层导航模式](http://www.google.com/design/spec/patterns/app-structure.html?utm_campaign=io15&utm_source=dac&utm_medium=blog#app-structure-top-level-navigation-strategies)或者组织app中不同分组内容（比如，不同风格的音乐）是同一个概念。\n\n![](http://1.bp.blogspot.com/-liraQhLEn60/VWihbiaXaJI/AAAAAAAABvQ/nKi1_xcx6yk/s320/tabs.png)\n\nDesign library的[TabLayout](http://developer.android.com/reference/android/support/design/widget/TabLayout.html?utm_campaign=io15&utm_source=dac&utm_medium=blog) 既实现了固定的选项卡（View的宽度平均分配），也实现了可滚动的选项卡（View宽度不固定同时可以横向滚动）,也可以通过编写代码添加Tab。\n```\n\nTabLayout tabLayout = ...;\ntabLayout.addTab(tabLayout.newTab().setText(\"Tab 1\"));\n```\n\n如果，你使用[ViewPager](http://developer.android.com/reference/android/support/v4/view/ViewPager.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)在tab之间横向切换，你可以直接从[PagerAdapter](http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)的[getPageTitle()](http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html?utm_campaign=io15&utm_source=dac&utm_medium=blog#getPageTitle(int))中创建选项卡，然后使用setupWithViewPager()将两者联系在一起。它可以使tab的选中事件能更新ViewPager,同时ViewPager的页面改变能更新tab的选中状态。\n\n\n\n###CoordinatorLayout, 动作和滚动\n\n\n独特的视觉效果只是Material design小小的一部分：运动也是设计好一款Material designed应用的重要组成部分。而在Material design中，包括[触摸Ripple](http://www.google.com/design/spec/animation/responsive-interaction.html?utm_campaign=io15&utm_source=dac&utm_medium=blog#responsive-interaction-surface-reaction)和[有意义的转场](http://www.google.com/design/spec/animation/meaningful-transitions.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)，Design library引入[CoordinatorLayout](http://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html?utm_campaign=io15&utm_source=dac&utm_medium=blog0)，一个从另一层面去控制子view之间触摸事件的布局，Design library中的很多控件都利用了它。\n\n\n###CoordinatorLayout和Floating Action Buttons\n\n\n一个很好的例子就是当你将FloatingActionButton作为一个子View添加进CoordinatorLayout并且将CoordinatorLayout传递给 Snackbar.make()，在3.0及其以上的设备上，Snackbar不会显示在悬浮按钮的上面，而是FloatingActionButton利用CoordinatorLayout提供的回调方法，在Snackbar以动画效果进入的时候自动向上移动让出位置，并且在Snackbar动画地消失的时候回到原来的位置，不需要额外的代码。\n\n\n[example video](http://material-design.storage.googleapis.com/publish/material_v_3/material_ext_publish/0B6Okdz75tqQsLWFucDNlYWEyeW8/components_snackbar_usage_fabdo_002.webm)\n\n\nCoordinatorLayout还提供了layout_anchor和layout_anchorGravity属性一起配合使用，可以用于放置floating view，比如FloatingActionButton与其他View的相对位置\n\n\n###CoordinatorLayout 和app bar\n\n\n另一个比较重要的场合是CoordinatorLayout结合app bar (或者action bar)和 [滚动处理](http://www.google.com/design/spec/patterns/scrolling-techniques.html?utm_campaign=io15&utm_source=dac&utm_medium=blog). 你可能在你的布局里已经使用了[Toolbar](https://developer.android.com/reference/android/support/v7/widget/Toolbar.html?utm_campaign=io15&utm_source=dac&utm_medium=blog), 能让你自定义外观，将应用中最显眼的部分和其他部分整合到一起. Design library采用了进一步的解决方案:使用[AppBarLayout](http://developer.android.com/reference/android/support/design/widget/AppBarLayout.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)可以让Toolbar和其他View（例如展示Tab的TabLayout）对滚动事件作出反应，前提是他们在一个标有ScrollingViewBehavior的View中.因此，你可以创建如下的布局：\n```\n <android.support.design.widget.CoordinatorLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n     \n     <! -- Your Scrollable View -->\n    <android.support.v7.widget.RecyclerView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    <android.support.design.widget.AppBarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n   <android.support.v7.widget.Toolbar\n                  ...\n                  app:layout_scrollFlags=\"scroll|enterAlways\">\n\n        <android.support.design.widget.TabLayout\n                  ...\n                  app:layout_scrollFlags=\"scroll|enterAlways\"/>\n     </android.support.design.widget.AppBarLayout>\n</android.support.design.widget.CoordinatorLayout>\n```\n\n\n现在，随着用户滚动RecyclerView，AppBarLayout通过子视图上的scroll flag，处理事件作出反应，控制他们如何进入，如何退出。Flag包括：\n\n\n\n> - scroll: 所有想滚动出屏幕的view都需要设置这个flag- 没有设置这个flag的view将被固定在屏幕顶部。\n- enterAlways: 这个flag让任意向下的滚动都会导致该view变为可见，启用\"快速返回\"模式。\n- enterAlwaysCollapsed: 当你的视图已经设置minHeight属性又使用此标志时，你的视图只会在最小高度处进入，只有当滚动视图到达顶部时才扩大到完整高度。\n- exitUntilCollapsed: 在滚动过程中，只有当视图折叠到最小高度的时候，它才退出屏幕。\n\n\n注意：那些使用Scroll flag的视图必须在其他视图之前声明。这样才能确保所有的视图从顶部撤离，剩下的元素固定在前面（译者注：剩下的元素压在其他元素的上面）。\n\n###折叠 Toolbars\n\n直接向AppBarLayout添加ToolBar，你需要添加enteralwayscollapsed和exituntilcollapsed两个滚动Flag，但是不能在细节上不同的元素对此的反应。为此，您可以使用 [CollapsingToolbarLayout](http://developer.android.com/reference/android/support/design/widget/CollapsingToolbarLayout.html?utm_campaign=io15&utm_source=dac&utm_medium=blog):\n\n```\n<android.support.design.widget.AppBarLayout\n        android:layout_height=\"192dp\"\n        android:layout_width=\"match_parent\">\n    <android.support.design.widget.CollapsingToolbarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_scrollFlags=\"scroll|exitUntilCollapsed\">\n        <android.support.v7.widget.Toolbar\n                android:layout_height=\"?attr/actionBarSize\"\n                android:layout_width=\"match_parent\"\n                app:layout_collapseMode=\"pin\"/>\n        </android.support.design.widget.CollapsingToolbarLayout>\n</android.support.design.widget.AppBarLayout>\n```\n\n\n这个设置使用collapsingtoolbarlayout的layout_collapsemode =\"pin\" 确保在View折叠时，Toolbar本身仍然在屏幕顶部。更好的是，当你同时使用collapsingtoolbarlayout和Toolbar，当布局完全可见时，标题看上去明显变大了；当布局折叠完成后，它恢复到其默认大小。请注意，在这些情况下，你应该调用CollapsingToolbarLayout#settitle() ，而不是调用Toolbar。\n\n\n[example video](http://material-design.storage.googleapis.com/publish/material_v_3/material_ext_publish/0B0NGgBg38lWWcFhaV1hiSlB4aFU/patterns-scrollingtech-scrolling-070801_Flexible_Space_xhdpi_003.webm)\n\n\n\n如果你希望添加压住特定的视图效果，您可以使用app：layout_collapsemode =\"parallax\"（和app：layout_collapseparallaxmultiplier =“0.7”（可选,用于设置视差乘数）实现视差滚动（也就是说ImageView，作为Toolbar的兄弟节点，在collapsingtoolbarlayout中）。在这种情况下，建议在CollapsingToolbarLayout中设置\napp:contentScrim=\"?attr/colorPrimary\"这一属性，这样，当视图折叠的时候，就会有蒙上纱布的渐变效果。\n\n[example video](http://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0B6Okdz75tqQscXNQY3dNdVlYeTQ/patterns-scrolling-techniques_flex_space_image_xhdpi_003.webm)\n\n\n###CoordinatorLayout与自定义控件\n\n\n\n还有一件需要注意的事情，CoordinatorLayout跟FloatingActionButton或AppBarLayout需要一定的配置-它在[Coordinator.Behavior](http://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.Behavior.html?utm_campaign=io15&utm_source=dac&utm_medium=blog)提供了一些API,子视图既可以更好地控制触摸事件也可以通过[onDependentViewChanged()](http://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.Behavior.html?utm_campaign=io15&utm_source=dac&utm_medium=blog#onDependentViewChanged(android.support.design.widget.CoordinatorLayout,%20V,%20android.view.View))给别人提供一个回调方法。\n\n\nViews可以用CoordinatorLayout.DefaultBehavior(YourView.Behavior.class)注解（annotation）声明默认的Behavior,或者在你的布局文件中声明app:layout_behavior=\"com.example.app.YourView$Behavior\" 属性. 这样做，就可以将任何一个View和CoordinatorLayout整合在一起.\n\n###马上使用吧！\n\nDesign library现在就可以使用，请确保已经用SDk Manager更新了Android Support Repository. 然后添加一条dependency，你就可以使用Design library了:\n\n```\n compile 'com.android.support:design:22.2.0'\n```\n\n需要注意的是，Design library 依赖于Support v4和AppCompat Support Libraries,在你添加  Design library时，这些库也会自动的添加到依赖中。同时，这些控件在Android Studio的Layout Editor (可以在CustomView中找到)中是可用的，你可以便捷的预览一些新的控件。\n\nDesign library, AppCompat和所有的Android Support Library 都是开发Android的强有力工具，当你打造一个符合当代风格、好看的应用时，可以使用提供现成的模块，无需从0开始。\n"
  },
  {
    "path": "issue-14/Android之WebRTC介绍.md",
    "content": "#Android之WebRTC介绍\n---\n\n> * 原文链接 : [Introduction to WebRTC on Android](https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/)\n* 原文作者 : [Dag-Inge Aas](https://disqus.com/home/forums/appearin/)\n* 译文出自 : [appear.in](https://tech.appear.in/)\n* 译者 : [DorisMinmin](https://github.com/DorisMinmin) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 ：完成\n\nWebRTC被誉为是web长期开源开发的一个新启元，是近年来web开发的最重要创新。WebRTC允许Web开发者在其web应用中添加视频聊天或者点对点数据传输，不需要复杂的代码或者昂贵的配置。目前支持Chrome、Firefox和Opera，后续会支持更多的浏览器，它有能力达到数十亿的设备。\n\n然而，WebRTC一直被误解为仅适合于浏览器。事实上，WebRTC最重要的一个特征是允许本地和web应用间的互操作，很少有人使用到这个特性。\n\n本文将探讨如何在自己的Android应用中植入WebRTC，使用[WebRTC Initiative](http://www.webrtc.org/)中提供的本地库。这边文章不会讲解如何使用信号机制建立通话，而是重点探讨Android与浏览器中实现的差异性和相似性。下文将讲解Android中实现对应功能的一些接口。如果想要了解WebRTC的基础知识，强烈推荐[Sam Dutton’s Getting started with WebRTC](http://www.html5rocks.com/en/tutorials/webrtc/basics/)。\n\n##项目中添加WebRTC\n*下面的讲解基于Android WebRTC库版本9127.*\n\n首先要做的是在应用中添加WebRTC库。 WebRTC Initiative 提供了[一种简洁的方式来编译](http://www.webrtc.org/native-code/android)，但尽量不要采用那种方式。取而代之，建议使用原始的io编译版本，可以从[maven central repository](https://oss.sonatype.org/content/groups/public/io/pristine/)中获取。\n\n添加WebRTC到工程中，需要在你的依赖中添加如下内容：\n```Gradle\n1 compile 'io.pristine:libjingle:9127@aar'\n```\n同步工程后，WebRTC库就准备就绪。\n\n##权限\n同其他Android应用一样，使用某些 API 需要申请相应权限。WebRTC也不例外。制作的应用不同，或者需要的功能不同，例如音频或者视频，所需要的权限集也是不同的。请确保按需申请！一个好的视频聊天应用权限集如下：\n```xml\n1 <uses-feature android:name=\"android.hardware.camera\" />\n2 <uses-feature android:name=\"android.hardware.camera.autofocus\" />\n3 <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />\n4 \n5 <uses-permission android:name=\"android.permission.CAMERA\" />\n6 <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n7 <uses-permission android:name=\"android.permission.INTERNET\" />\n8 <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n9 <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n```\n##灯光，摄影，工厂\n\n在浏览器中使用WebRTC时，有一些功能完善、说明详细的API可供使用。 [navigator.getUserMedia](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getUserMedia) 和 [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) 包含了可能用到的几乎所有功能。结合`<video>`标签使用，可以显示任何想要显示的本地视频流和远程视频流。\n\n所幸的是Android上也有相同的API，虽然它们的名字有所不同。Android相关的API有[VideoCapturerAndroid](https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/#VideoCapturerAndroid), [VideoRenderer](https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/#VideoRenderer), [MediaStream](https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/#MediaStream), [PeerConnection](https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/#PeerConnection), 和 [PeerConnectionFactory](https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/#PeerConnectionFactory)。下面我们将逐一讲解。\n\n在开始之前，需要创建PeerConnectionFactory，这是Android上使用WebRTC最核心的API。\n\n##PeerConnectionFactory\nAndroid WebRTC最核心的类。理解这个类并了解它如何创建其他任何事情是深入了解Android中WebRTC的关键。它和我们期望的方式还是有所不同的，所以我们开始深入挖掘它。\n\n首先需要初始化PeerConnectionFactory，如下：\n```Java\n// First, we initiate the PeerConnectionFactory with\n// our application context and some options.\nPeerConnectionFactory.initializeAndroidGlobals(\n    context,\n    initializeAudio,\n    initializeVideo,\n    videoCodecHwAcceleration,\n    renderEGLContext);\n```\n为了理解这个方法，需要了解每个参数的意义：\n\n**context**\n应用上下文，或者上下文相关的，和其他地方传递的一样。\n\n**initializeAudio**\n是否初始化音频的布尔值。\n\n**initializeVideo**\n是否初始化视频的布尔值。跳过这两个就允许跳过请求API的相关权限，例如数据通道应用。\n\n**videoCodecHwAcceleration**\n是否允许硬件加速的布尔值。\n\n**renderEGLContext**\n用来提供支持硬件视频解码，可以在视频解码线程中创建共享EGL上下文。可以为空——在本文例子中硬件视频解码将产生yuv420帧而非texture帧。\n\ninitializeAndroidGlobals也是返回布尔值，true表示一切OK，false表示有失败。如果返回false是最好的练习。更多信息请参考[源码](https://code.google.com/p/webrtc/source/browse/trunk/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java?r=8344&spec=svn8423#64)。\n\n如果一切ok，可以使用PeerConnectionFactory 的构造函数创建自己的工厂，和其他类一样。\n```Java\n1 PeerConnectionFactory peerConnectionFactory = new PeerConnectionFactory();\n```\n##行动、获取媒体流、渲染\n有了`peerConnectionFactory`实例，就可以从用户设备获取视频和音频，最终将其渲染到屏幕上。web中可以使用`getUserMedia` 和`<video>`。在Android中，没有这么简单，但可以有更多选择！在Android中，我们需要了解VideoCapturerAndroid，VideoSource，VideoTrack和VideoRenderer，先从VideoCapturerAndroid开始。\n\n###VideoCapturerAndroid\n\nVideoCapturerAndroid其实是一系列Camera API的封装，为访问摄像头设备的流信息提供了方便。它允许获取多个摄像头设备信息，包括前置摄像头，或者后置摄像头。\n```Java\n1  // Returns the number of camera devices                       \n2  VideoCapturerAndroid.getDeviceCount();                        \n3                                                              \n4  // Returns the front face device name                         \n5  VideoCapturerAndroid.getNameOfFrontFacingDevice();            \n6  // Returns the back facing device name                        \n7  VideoCapturerAndroid.getNameOfBackFacingDevice();             \n8                                                              \n9  // Creates a VideoCapturerAndroid instance for the device name\n10 VideoCapturerAndroid.create(name);\n```\n有了包含摄像流信息的VideoCapturerAndroid实例，就可以创建从本地设备获取到的包含视频流信息的MediaStream，从而发送给另一端。但做这些之前，我们首先研究下如何将自己的视频显示到应用上面。\n\n###VideoSource/VideoTrack\n\n从VideoCapturer实例中获取一些有用信息，或者要达到最终目标————为连接端获取合适的媒体流，或者仅仅是将它渲染给用户，我们需要了解VideoSource 和 VideoTrack类。\n\n[VideoSource](https://chromium.googlesource.com/external/webrtc/+/master/talk/app/webrtc/java/src/org/webrtc/VideoSource.java)允许方法开启、停止设备捕获视频。这在为了延长电池寿命而禁止视频捕获的情况下比较有用。\n\n[VideoTrack](https://chromium.googlesource.com/external/webrtc/+/master/talk/app/webrtc/java/src/org/webrtc/VideoTrack.java) 是简单的添加VideoSource到MediaStream 对象的一个封装。\n\n我们通过代码看看它们是如何一起工作的。`capturer`是VideoCapturer的实例，`videoConstraints`是MediaConstraints的实例。\n```Java\n1  // First we create a VideoSource                                     \n2  VideoSource videoSource =                                            \n3  \t       peerConnectionFactory.createVideoSource(capturer, videoConstraints);\n4                                                                       \n5  // Once we have that, we can create our VideoTrack                   \n6  // Note that VIDEO_TRACK_ID can be any string that uniquely          \n7  // identifies that video track in your application                   \n8  VideoTrack localVideoTrack =                                         \n9  \t       peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);\n```\n\n###AudioSource/AudioTrack\n\n[AudioSource](https://chromium.googlesource.com/external/webrtc/+/master/talk/app/webrtc/java/src/org/webrtc/AudioSource.java)和[AudioTrack](https://chromium.googlesource.com/external/webrtc/+/master/talk/app/webrtc/java/src/org/webrtc/AudioSource.java)与VideoSource和VideoTrack相似，只是不需要AudioCapturer 来获取麦克风，`audioConstraints`是 MediaConstraints的一个实例。\n```Java\n1  // First we create an AudioSource                                    \n2  AudioSource audioSource =                                            \n3  \t       peerConnectionFactory.createAudioSource(audioConstraints);          \n4                                                                       \n5  // Once we have that, we can create our AudioTrack                   \n6  // Note that AUDIO_TRACK_ID can be any string that uniquely          \n7  // identifies that audio track in your application                   \n8  AudioTrack localAudioTrack =                                         \n9  \t       peerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);\n```\n###VideoRenderer\n\n通过在浏览器中使用WebRTC，你肯定已经熟悉了使用`<Video>`标签来显示出从 getUserMedia 方法得到的 MediaStream。但在本地Android中，没有类似`<Video>`的标签。进入VideoRenderer，WebRTC库允许通过`VideoRenderer.Callbacks`实现自己的渲染。另外，它提供了一种非常好的默认方式VideoRendererGui。简而言之，[VideoRendererGui](https://chromium.googlesource.com/external/webrtc/+/master/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java)是一个[GLSurfaceView](https://developer.android.com/reference/android/opengl/GLSurfaceView.html) ，使用它可以绘制自己的视频流。我们通过代码看一下它是如何工作的，以及如何添加renderer 到 VideoTrack。\n```Java\n1   // To create our VideoRenderer, we can use the                           \n2   // included VideoRendererGui for simplicity                              \n3   // First we need to set the GLSurfaceView that it should render to       \n4   GLSurfaceView videoView = (GLSurfaceView) findViewById(R.id.glview_call);\n5                                                                            \n6   // Then we set that view, and pass a Runnable                            \n7   // to run once the surface is ready                                      \n8   VideoRendererGui.setView(videoView, runnable);                           \n9                                                                            \n10  // Now that VideoRendererGui is ready, we can get our VideoRenderer      \n11  VideoRenderer renderer = VideoRendererGui.createGui(x, y, width, height);\n12                                                                           \n13  // And finally, with our VideoRenderer ready, we                         \n14  // can add our renderer to the VideoTrack.                               \n15  localVideoTrack.addRenderer(renderer);                                   \n```\n这里要说明的一点是createGui 需要四个参数。这样做是使一个单一的GLSurfaceView 渲染所有视频成为可能。但在实际使用中我们使用了多个GLSurfaceViews，这意味为了渲染正常，x、y一直是0。这让我们了解到实现过程中各个参数的意义。\n\n###MediaConstraints\nMediaConstraints是支持不同约束的WebRTC库方式的类，可以加载到MediaStream中的音频和视频轨道。具体参考[规范](https://w3c.github.io/mediacapture-main/#idl-def-MediaTrackSupportedConstraints)查看支持列表。对于大多数需要MediaConstraints的方法，一个简单的MediaConstraints实例就可以做到。\n```Java\n1  MediaConstraints audioConstraints = new MediaConstraints();\n```\n要添加实际约束，可以定义`KeyValuePairs`，并将其推送到约束的`mandatory`或者`optional`list。\n\n###MediaStream\n现在可以在本地看见自己了，接下来就要想办法让对方看见自己。在web开发时，对[MediaStream](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_API)已经很熟悉了。`getUserMedia`直接返回MediaStream ，然后将其添加到RTCPeerConnection 传送给对方。在Android上此方法也是通用的，只是我们需要自己创建MediaStream。 接下来我们就研究如何添加本地的VideoTrack 和AudioTrack来创建一个合适的MediaStream。 \n```Java\n1   // We start out with an empty MediaStream object,                                             \n2   // created with help from our PeerConnectionFactory                                           \n3   // Note that LOCAL_MEDIA_STREAM_ID can be any string                                          \n4   MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream(LOCAL_MEDIA_STREAM_ID);\n5                                                                                                 \n6   // Now we can add our tracks.                                                                 \n7   mediaStream.addTrack(localVideoTrack);                                                        \n8   mediaStream.addTrack(localAudioTrack);                                                        \n```\n\n##Hi，有人在那里吗？\n我们现在有了包含视频流和音频流的MediaStream实例，而且在屏幕上显示了我们漂亮的脸庞。现在就该把这些信息传送给对方了。这篇文章不会介绍如何建立自己的信号流，我们直接介绍对应的API方法，以及它们如何与web关联的。[AppRTC](https://chromium.googlesource.com/external/webrtc/+/master/talk/examples/android/)使用[autobahn](http://autobahn.ws/android/)使得WebSocket连接到信号端。我建议下载下来这个项目来仔细研究下如何在Android中建立自己的信号流。\n\n###PeerConnection\n现在我们有了自己的MediaStream，就可以开始连接远端了。幸运的是这部分和web上的处理很相似，所以如果对浏览器中的WebRTC熟悉的话，这部分就相当简单了。创建PeerConnection很简单，只需要PeerConnectionFactory的协助即可。\n```Java\n1  PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(\n2  \t        iceServers,                                                               \n3  \t        constraints,                                                              \n4  \t        observer);                                                                \n```\n参数的作用如下：\n\n**iceServers**\n连接到外部设备或者网络时需要用到这个参数。在这里添加STUN 和 TURN 服务器就允许进行连接，即使在网络条件很差的条件下。\n\n**constraints**\nMediaConstraints的一个实例，应该包含`offerToRecieveAudio` 和 `offerToRecieveVideo`\n\n**observer**\nPeerConnectionObserver实现的一个实例。\n\nPeerConnection 和web上的对应API很相似，包含了addStream、addIceCandidate、createOffer、createAnswer、getLocalDescription、setRemoteDescription 和其他类似方法。下载[WebRTC入门](http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcpeerconnection) 来学习如何协调所有工作在两点之间建立起通讯通道，或者[AppRTC](https://chromium.googlesource.com/external/webrtc/+/master/talk)如何使得一个实时的功能完整的Android WebRTC应用工作的。我们快速浏览一下这几个重要的方法，看它们是如何工作的。\n\n###addStream\n这个是用来将MediaStream 添加到PeerConnection中的,如同它的命名一样。如果你想要对方看到你的视频、听到你的声音，就需要用到这个方法。\n\n###addIceCandidate\n一旦内部IceFramework发现有candidates允许其他方连接你时，就会创建[IceCandidates](http://stackoverflow.com/questions/21069983/what-are-ice-candidates-and-how-do-the-peer-connection-choose-between-them/21071464#21071464) 。当通过PeerConnectionObserver.onIceCandidate传递数据到对方时，需要通过任何一个你选择的信号通道获取到对方的IceCandidates。使用addIceCandidate 添加它们到PeerConnection，以便PeerConnection可以通过已有信息试图连接对方。\n\n###createOffer/createAnswer\n这两个方法用于原始通话的建立。如你所知，在WebRTC中，已经有了caller和callee的概念，一个是呼叫，一个是应答。createOffer是caller使用的，它需要一个sdpObserver，它允许获取和传输会话描述协议Session Description Protocol (SDP)给对方，还需要一个MediaConstraint。一旦对方得到了这个请求，它将创建一个应答并将其传输给caller。SDP是用来给对方描述期望格式的数据（如video、formats、codecs、encryption、resolution、 size等）。一旦caller收到这个应答信息，双方就相互建立的通信需求达成了一致，如视频、音频、解码器等。\n\n###setLocalDescription/setRemoteDescription\n这个是用来设置createOffer和createAnswer产生的SDP数据的，包含从远端获取到的数据。它允许内部PeerConnection 配置链接以便一旦开始传输音频和视频就可以开始真正工作。\n\n###PeerConnectionObserver\n这个接口提供了一种监测PeerConnection事件的方法，例如收到MediaStream时，或者发现iceCandidates 时，或者需要重新建立通讯时。这些在功能上与web相对应，如果你学习过相关web开发理解这个不会很困难，或者学习[WebRTC入门](http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcpeerconnection)。这个接口必须被实现，以便你可以有效处理收到的事件，例如当对方变为可见时，向他们发送信号iceCandidates。\n\n##结束语\n如上所述，如果你了解了如何与web相对应，Android上面的API是非常简单易懂的。有了以上这些工具，我们就可以开发出一个WebRTC相关产品，立即部署到数十亿设备上。\n\nWebRTC打开了人与人之间的通讯，对开发者免费，对终端用户免费。 它不仅仅提供了视频聊天，还有其他应用，比如健康服务、低延迟文件传输、种子下载、甚至游戏应用。\n\n想要看到一个真正的WebRTC应用实例，请下载[Android](https://play.google.com/store/apps/details?id=appear.in.app&referrer=utm_source%3Dtech.appear.in%26utm_medium%3Dblog%26utm_campaign%3Dandroid-launch-may15)或[ios](https://itunes.apple.com/app/apple-store/id878583078?pt=1259761&ct=tech.appear.in&mt=8)版的appear.in。它在浏览器和本地应用间运行的相当完美，在同一个房间内最多可以8个人免费使用。不需要安装和注册。\n\n现在就发挥你们的潜力，开发出更多新的应用！\n"
  },
  {
    "path": "issue-14/Design-Support-Library(I)-Navigation-View.md",
    "content": "\nDesign Support Library (I): Navigation View\n\n---\n\n> * 原文链接 : [Design Support Library (I): Navigation View][source]\n* 原文作者 : [Antonio](http://robovm.com/author/mario/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime) \n* 校对者:  [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 :  完成 \n\n\n---\n\n[Google I/O 2015][io2015] 给 Android 开发者提供了一整套新工具，让 Android 开发更容易。\n我会完成一个系列文章来介绍超级实用的 [Design Support Library][design-support-library].\n\n[Github][example] 上已经有一个很好的使用示例，不过我会通过完成一个[App][MaterializeYourApp]，\n深入讲解每一个新特性。\n\n##Navigation View\n\n在这次的文章中，我们先介绍 **Navigation View**。Material Design 发布以来，\n我们只是知道 [Navigation Drawer ][navigation-drawer] 应该长什么样。\n\n![img-01]\n\n想要完成自己实现一个又不是很容易。不过现在我们有了 navigation view，\n这个工作就很轻松了。\n\n##Navigation View如何工作\n你应该用它替换你之前 Drawer Layout 中自定义 View 的位置。\nNavigation View 可以接受一些参数，一个可选的 header layout，\n还有一个用来构建导航选项的 menu。然后你只需添加事件监听器就可以了。\n\n##Implementation\n\n首先，创建 menu。很简单，创建一个 group，设置同时只有一个 item 能被选中。\n\n```xml\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n \n    <group\n        android:checkableBehavior=\"single\">\n \n        <item\n            android:id=\"@+id/drawer_home\"\n            android:checked=\"true\"\n            android:icon=\"@drawable/ic_home_black_24dp\"\n            android:title=\"@string/home\"/>\n \n        <item\n            android:id=\"@+id/drawer_favourite\"\n            android:icon=\"@drawable/ic_favorite_black_24dp\"\n            android:title=\"@string/favourite\"/>\n        ...\n \n        <item\n            android:id=\"@+id/drawer_settings\"\n            android:icon=\"@drawable/ic_settings_black_24dp\"\n            android:title=\"@string/settings\"/>\n \n    </group>\n</menu>\n```\n\n理论上，你也可以像下面这样，在每个 item 中添加子 menu 实现带有分类的选项块：\n\n```xml\n<item\n    android:id=\"@+id/section\"\n    android:title=\"@string/section_title\">\n    <menu>\n        <item\n            android:id=\"@+id/drawer_favourite\"\n            android:icon=\"@drawable/ic_favorite_black_24dp\"\n            android:title=\"@string/favourite\"/>\n \n        <item\n            android:id=\"@+id/drawer_downloaded\"\n            android:icon=\"@drawable/ic_file_download_black_24dp\"\n            android:title=\"@string/downloaded\"/>\n    </menu>\n</item>\n```\n\n这样会创建一个 divider 和 header，然后把 items 加到它的正下方。然而这样我们就没办法\n判断哪个 item 被选中了。如果日后我知道怎样做我会更新这部分的。不过，我还是鼓励你去尝试\n看看它的效果。\n\n现在，我们可以把 navigation view 加到我们的 activity layout 里了，设置好 menu 和  header layout。\nHeader 可以是任意 view ，所以这里不做过多介绍，你可以到 [Github][header-axample] 查看 header 部分代码。\n\n```xml\n<android.support.v4.widget.DrawerLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/drawer_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\".MainActivity\">\n \n    <FrameLayout\n        android:id=\"@+id/content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n \n    ...\n \n    </FrameLayout>\n \n    <android.support.design.widget.NavigationView\n        android:id=\"@+id/navigation_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        app:headerLayout=\"@layout/drawer_header\"\n        app:menu=\"@menu/drawer\"/>\n \n</android.support.v4.widget.DrawerLayout>\n```\n\n接下来看看 Java 代码部分。首先你要启用  `home as up`：\n\n```java\nfinal Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\nsetSupportActionBar(toolbar);\nfinal ActionBar actionBar = getSupportActionBar();\n \nif (actionBar != null) {\n    actionBar.setHomeAsUpIndicator(R.drawable.ic_menu_black_24dp);\n    actionBar.setDisplayHomeAsUpEnabled(true);\n}\n```\n然后初始化 navigation drawer。当一个 item 被选中时，显示一个 `Snackbar` (我会在下篇文章中介绍它)，\n选中被点击的 item，关闭 drawer：\n\n```java\ndrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);\n \nNavigationView view = (NavigationView) findViewById(R.id.navigation_view);\nview.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {\n    @Override public boolean onNavigationItemSelected(MenuItem menuItem) {\n        Snackbar.make(content, menuItem.getTitle() + \" pressed\", Snackbar.LENGTH_LONG).show();\n        menuItem.setChecked(true);\n        drawerLayout.closeDrawers();\n        return true;\n    }\n});\n```\n\n最后，在 menu action 按下时打开 drawer ：\n\n```java\n@Override\npublic boolean onOptionsItemSelected(MenuItem item) {\n    switch (item.getItemId()) {\n        case android.R.id.home:\n            drawerLayout.openDrawer(GravityCompat.START);\n            return true;\n    }\n \n    return super.onOptionsItemSelected(item);\n}\n```\n\n##Conclusion\n\n创建一个满足 Material 设计准则的 navigation drawer 是如此简单！感谢 design support library 。\n下篇文章会继续介绍其他好用的新控件。你可以到这([Github][MaterializeYourApp])查看我们所有的代码\n。\n\n\n---\n\n[source]:http://antonioleiva.com/navigation-view/\n[author]:http://antonioleiva.com/category/blog/\n[io2015]:https://events.google.com/io2015/\n[design-support-library]:http://android-developers.blogspot.com.es/2015/05/android-design-support-library.html\n[example]:https://github.com/chrisbanes/cheesesquare/\n[MaterializeYourApp]:https://github.com/antoniolg/MaterializeYourApp\n[navigation-drawer]:http://www.google.com/design/spec/patterns/navigation-drawer.html\n[img-01]:http://antonioleiva.com/wp-content/uploads/2015/05/navigation_drawer1-e1433071058464.png\n[header-axample]:https://github.com/antoniolg/MaterializeYourApp/blob/master/app/src/main/res/layout/drawer_header.xml"
  },
  {
    "path": "issue-14/MVVM 模式简介.md",
    "content": "MVVM 模式简介\n---\n\n> * 原文链接 : [MVVM on Android: What You Need to Know](http://www.willowtreeapps.com/blog/mvvm-on-android-what-you-need-to-know/)\n* 原文作者 : [Frank](http://www.willowtreeapps.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n\nGoogle 在今年的 IO 大会推出了一个超赞的框架，开发者可以通过这个框架为 View 中的任何一个对象的数据域进行绑定，框架会注意到数据是否被更新，如果数据被更新，将自动更新 View 中对应的内容。\n\n说实话，这个框架真的很实用，还能让我们使用 Windows 程序开发时使用的 MVVM 模式。在结合这个新的框架和 MVVM 模式进行开发之前，花时间了解 MVVM 架构的基本概念和它能为应用带来的好处是个性价比极高的选择，MVVM 模式由下面三个部分组成：\n\n- Model – 代表你的业务逻辑 \n- View – 显示界面内容\n- ViewModel – 关联上面两者的对象\n\nViewModel 接口提供 Action 和 Data，Action 改变基础的 Model（例如：点击监听器，文字修改状态监听器，等等……），而 Data 则是 Model 的内容。\n\n举个例子，一个用于拍卖页面的 ViewModel 可能对外公布的数据域有：拍卖品的图片、标题、描述和价钱；此外，还可能会对外提供进行拍卖、购买、与卖方沟通的方法。\n\n在传统的 Android 架构中，Controller 为 View 提供数据，你要做的就是在 Activity 中找到相应的 View，并更新其中的数据。但在 MVVM 中，如果 ViewModel 修改了某些内容，而且数据绑定框架注意到内容发生了改变，框架就会自动更新与该数据相关的所有 View，通过这个提供数据和命令的接口使得这两个组件间呈松耦合状态。\n\n这个框架除了让 View 变得更加智能，使用起来更加便利以外，还大大简化了测试的过程。\n\n由于 ViewModel 再也不需要依赖于 View 了，这使你甚至能对一个不存在的 View 进行测试，通过对其他依赖进行恰当的依赖注入，大大简化了测试的复杂度。\n\n还是举例子来说吧，如果我们不能将 ViewModel 与某个已存在的 View 绑定，开发者可能会在测试用例中新建一个 ViewModel，然后为它提供一些数据，再执行某些操作以确保数据正确地被改变。回到刚刚的拍卖例子中，你可能会创建一个提供 mock API 服务的 ViewModel，用它读取任意一个拍卖品并投标，然后确保显示出来的数据是正确的。不需要与实体 View 产生交互就能完成上面所有操作。\n\n当对某个 View 进行测试时，开发者可以创建许多对外暴露预测试状态（与通过请求网络或访问数据库获得数据相反）的 mock ViewModel，并分析 View 在任何数量发生改变时的反应。\n\n希望这篇博文能帮助你理解 MVVM 模式的概念以及它的好处，接下来我们将发布一些博文为大家讲解具体的实现细节，以及使用数据绑定框架开发时的一些小提示和小技巧。"
  },
  {
    "path": "issue-14/新的测试注解.md",
    "content": "# 新的测试注解\n--------\n\n> * 原文链接 : [New annotation for testing ](https://plus.google.com/+StephanLinzner/posts/GBdq6NsRy6S)\n* 原文作者 : [StephanLinzner](https://plus.google.com/+StephanLinzner)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Mr.Simple](https://github.com/bboyfeiyu) \n\n随着support 库 22.2.0版本的发布，我们添加了一个`@VisibleForTesting`注解到[support-annotations](http://goo.gl/niwFS8﻿)中。这个注解作用于类型、函数或者字段上，在添加了`@VisibleForTesting`注解后这些类型、函数、字段会在测试时具有开放的访问权限使你的代码具有更好的可测试性。\n\n开始之前，请通过SDK Manager更新你的Support仓库。然后添加一个依赖到你的gradle编译配置中: \n\n```\ndependencies {\n    compile 'com.android.support:support-annotations:22.2.0'\n}\n```\n\n最后,在你的代码中添加`@VisibleForTesting`注解来标识目标类型的在测试时的可访问性： \n\n```java\n@VisibleForTesting\npublic void setLogger(ILogger logger) {...}\n```\n"
  },
  {
    "path": "issue-15/2015-Google-IO带来的新Android开发工具 .md",
    "content": "2015 Google IO带来的新 Android 开发工具\n---\n\n> * 原文链接 : [Google I/O Summary: What’s new in Android Development Tools](http://robovm.com/google-io-summary-whats-new-in-android-development-tools/)\n* 原文作者 : [Mario Zechner](http://robovm.com/author/mario/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [ReonDz](http://reondz.github.io/) \n\n每年我们都非常期待 Youtube 中Google IO上那些关于 Android 的精彩讲解。然而，观看这些视频太耗费时间了。因此我们做了撰写了总结性的文字，来介绍这次的 Google IO 带来了哪些新的 Android 开发工具。（[对应的讲演视频](https://www.youtube.com/watch?v=f7ihSQ44WO0)）\n\n##  便于设计\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 10.18.10-OI2RFWPbn6.png)  \n\nAndroid Design Support Library 帮助我们遵循最新的最杰出的 material design 特性。这个库还附带了好几个 material design 组件诸如 navigation drawer, [floating labels for editing text](http://arjunu.com/2015/05/floating-labels-for-edittext/)(这个简单来说就是edittext 在获得焦点后，hint 会有个上升动画变成一个放置在 edittext上方的 textview 作为 label)，floating action buttons(Android L时代推出的产物，总算有了官方的低版本兼容支持)以及 支持Android 2.1及其以上版本的snackbar。   \n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 10.24.57-nwuvL0kS8W.png) \n\nAndroid L 介绍了对 vector drawables 的支持。而通过 Android Studio 1.3+给 Android Gradle plugin 带来的新功能，我们可以让编译打包系统通过 开发者提供的SVG图 或者 vector drawables 生成不同 dpi 的光栅图。   \n最后，整个开发团队开始重写了所有的可视化设计编辑器。从而支持开发者所见即所得的编辑与设计而不是被 XML 文件搞的无比心烦。下面会有更详细的内容。   \n\n##  提升 Gradle 插件以及编译打包系统   \n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 10.30.01-Grz81dvubp.png)\n\nAndroid Gradle plugin  有的时候给人一种靠不住的感觉，特别是它的依赖管理。处理测试依赖时一些极端情况带来的问题现在已经被修复。同样，可选依赖 Gradle 还没有做到真正的支持。然而在 Android Gradle plugin 上已经解决了这一问题。      \n\n开发者对 Android Gradle plugin 抱怨最多的就是它居高不下的打包消耗时间了。开发团队从多个层级入手追踪这个问题。       \n\n[Jack](http://tools.android.com/tech-docs/jackandjill) , Java Android Compiler Kit 的缩写，可以将 Java 源码直接编译并转换为 Dex 文件。Jack 基于 Eclipse Java 编译器。缩减了打 Dex 之前把 Java 源码编译成 JVM 字节码的步骤。 额外的，Jack 还支持增量编译打包。     \n\n缩减 PNG 图片是另外一个考虑来缩减打包时间的步骤。开发团队提高了它的性能，将500张 PNG 图片和.9 图的缩减时间从 4秒 降低到了 400ms。     \n\naapt, 打包应用的dex 和 资源文件的工具同样得到了提升。     \n\n另外一个影响打包时间的因素是 Gradle 本身的巨大开销。当 Gradle 开始它的打包任务时，它必须先创建描述模型比如 variant (flavor + build type)，解决所有 variants 的依赖关系（即使你只有一个）以及执行开发者自定义的逻辑。开发团队和 Gradle Ware 紧密合作工作从而提高这些步骤的性能。以下是最后的结果：   \n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 10.42.57-cl8MWwzzYC.png)\n\n这还没完。目前开发者团队正在为了一个新的  Android Gradle plugin 而工作, 它基于 Gradle Ware 正在研发的新API  。而这些新的 API 允许这些模型被 Gradle 直接管理，从而可以做诸如缓存，并发以及增量构建这些事。下面是尚未完成的新 Android Gradle plugin 的结果数据：\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 10.46.01-KoHuJyeq0F.png)\n\n以上的数据并没有算上用上缓存的带来的提升，目前缓存的特性还没有提供出来。新插件有一个小瑕疵，因为它使用了全新的 DSL 所以无法做到完全的向后兼容。新插件的预览版会在几周内开放，最后的正式版会在今年晚一点的时候开放。   \n\n开发者团队还介绍了一个新的东西，叫做 [Data Binding Library](https://developer.android.com/tools/data-binding/guide.html)。这个库需要编译构建系统的支持，因为它会通过XML中特殊声明的一些代码去生成对应的 Java 代码。老版本和新版本的 Android Gradle plugin 都能支持它。下面会有更详细的内容。   \n\n最后，我们实现了对 NDK C以及C++完整的支持。这是基于Gradle native 代码的支持，所以只会在新版本中提供，它重写了Android Gradle plugin。NDK支持也带入到了Android Studio中，同时还打包了[CLion](https://www.jetbrains.com/clion/)（JetBrain旗下的C,C++ IDE）, 并免费提供。下面会有更详细的内容。   \n\n## 测试有关   \n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/testing_device.jpeg)  \n\n今年Android 测试最大的亮点便是最近公布的 [Cloud Test Lab](https://developers.google.com/cloud-test-lab/) 。它可以帮助开发者使用 Google 测试云平台上的虚拟设备以及真机设备测试他们开发的应用。无论是虚拟设备还是真机设备，测试实验室(test lab)都支持机器爬虫遍历应用而不需要手动的去写测试用例。就当作是有一只猴子在各种设备上玩你的应用吧=w=。当然，你还是可以自己写测试用例的。     \n\n到目前为止，单元测试主要还是用于跑在 JVM 上老的Junit 测试。虽然那样可以提高测试的迭代速度，但是它并不能测试到 Android 中一些特定的代码，若没有类似的Android 库的支持的话（Roboelectric就是为此而生的）。      \n\n为了支持在虚拟机或者设备上的集成测试，开发团队增加了对外部测试工程的支持。他们就在你的应用程序工程边上，引用它以及它的依赖然后进行集成测试。   \n\n## 模拟器\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.03.20-c1DKPt2w8Y.png)\n\n并没有大的进展。开发团队主要工作集中在稳定性，正确性以及配置上。 Android Studio 会下载安装 [HAXM](https://01.org/zh/android-ia/q-and-a/what-haxm)（Intel 为 Android x86平台提供的硬件加速执行管理器） 并且为你配置好它。同时，还有大量的用于OpenGL ES 模拟器的提升，相信它会给开发者带来更好的更接近于真机的模拟器表现。[指纹识别支持](http://www.droid-life.com/2015/05/28/google-announces-native-fingerprint-support-in-android-m/)以及一个特殊的 [Android Auto](http://www.android.com/auto/) 模拟器也已有提供。  \n\n##Android Studio对C以及C++的支持\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.13.41-NPAwYYVN8B.png)\n\n除了在打包系统中集成了 NDK 的支持，开发团队还与 [JetBrains](http://robovm.com/google-io-summary-whats-new-in-android-development-tools/jetbrains.comhttp://robovm.com/google-io-summary-whats-new-in-android-development-tools/jetbrains.com) 合作，将他们新的C/C++ IDE [CLion](https://www.jetbrains.com/clion/) 直接集成在了Android Studio 。并且这一切都是免费的。     \n\n这次结合满足了你对一个 JetBrains的产品抱有的所有期待: 强大的重构能力，代码分析工具，代码导航，语义查询代码用例遗迹更多的特性。除此以外，还有两个能改变游戏规则的重量级功能来给native 层的开发带来便捷。     \n\nAndroid Studio 现在支持使用 [GDB](http://www.gnu.org/software/gdb/) 或者 [LLDB](http://lldb.llvm.org/) 来debug代码。Debug native 的代码在以前是一件非常麻烦的事情，而现在，耳目一新的感觉吧？      \n\n更为称道的消息是 C/C++代码 和 Java/JNI 代码的紧密融合。为了让 C/C++ 代码可以与 Java 代码相互调用。你需要为它们创建连接的桥梁。这便要通过 Java Native Interface（即JNI来实现），本质上就是一个可以让我们从Java 代码调用 C/C++代码的 C/C++ API，反之亦然。你可以在声明 Java 方法时，带上 native 的 修饰词，这样就意味着该方法是在C/C++ 层面去实现的。而对应的方法则需要按照严格的规范命名，并规定管理好传参的规则。    \n\n而现在， Android Studio 帮助你用简单的方式完成以上所有工作。你可以在 Java 层声明好方法，然后 Android Studio 就会自动的帮你生成 JNI 代码。还包括了一些预定义类型的处理，比如 string，arrays。更令人印象深刻的是， 重构以及查找用例都如你所想要的可以使用。在某个文件上做重构，另外一侧的文件也会被修改到。    \n\nJNI 层的开发依然十分烦杂，但这些改变可以让它变得好受一点。   \n\n## 新支持的Annotations\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.15.21-WL33x5ckez.png) \n\nJava annotations（注解，即使@后面的那个）可以让开发者在编译期以及运行时做一些神奇的事情。开发者团队新增了13个 annotations 来帮助开发者避免一些特定类型的 bug 。    \n\n举个例子，@WorkerThread 这个 annotation, 带有该 annotation 的方法会自动检查该方法中是否有代码职能在UI线程上执行的。Android Studio 会高亮出类似错误，比如设置一个按钮的文字，在一个worker 线程执行了。 开发团队已经将很多 Android 运行库中的方法标记了该 annotation 所以开发者并不需要自己去动手添加。   \n  \n\n另外一个例子就是@RequiresPermission。和@WorkerThread 一样，大多数的Android 运行库方法已经被注解过了。这是防止开发者使用了一个需要特定权限的API，但是却没有在 manifest 文件中声明权限，Android Studio 会弹框告诉你问题并为你提供插入对应权限的选项。在 Android M 中，权限管理有一定的修改，用户可以在应用运行时决定给予或禁止一些权限。这就意味着，你的应用需要处理无权限的情况。对于这种情况，Android Studio 也会自动的插入处理代码的框架。        \n\n还有一些 annotations 是用于 debug 中将标志值对应标志名的问题，或者识别 编码过的颜色，资源或者view id的问题。   \n\n这个讲解新 annotations 的文档目前还没有完全完工，你应该可以很快在[这里](https://developer.android.com/reference/android/support/annotation/package-summary.html)看到它们。\n\n## 数据绑定\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.25.52-zEtMZXmuok.png)\n\n当开发UI界面时，通常会将布局和视图从代码中分离到 xml 文件中然后写一些简单Java 对象(POJOs) 以及 一些 Java 代码。例如使用 findViewById() 在布局被加载后去获取对应的UI 视图的对象。   \n\nData Binding 库可以帮助开发者更简单的完成工作。不需要手动的去处理数据和视图的合作，而是声明一些简单的对象，通过一系列简单对象引用一些值并且在 布局 XML 文件中去监听这些值。这个很像 .NET里[XAML](https://msdn.microsoft.com/en-us/library/ms752059%28v=vs.110%29.aspx)的做法。    \n\n为了调解 Java 类和布局 xml 文件，编译打包系统会编译期生成一个捆绑 Java 类。这个可以帮助开发者debug 调试问题。   \n   \n\n你还可以将这变成一个双向的过程，通过让你的简单 Java 对象实现 android.databings.Observable 接口，改变简单对象的值会 影响 UI展现，反之亦然。（setText 后，bind的值也会变化的意思）   \n\ndata binding 库目前还处于 beta 阶段，需要 Android Studio 1.3.0-beta1 以及最新的Android Gradle plugin.\n点击[这里](https://developer.android.com/tools/data-binding/guide.html)查看更多信息。   \n\n## 分析工具\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.34.27-cSn9mmzu7x.png)   \n\n内存和性能分析工具得到了全面的改变。现在你可以在Android Studio中通过简单的图形界面操作直接获取并保存堆栈以及方法调用情况(method traces)的快照并且找出问题原因了。新的分析工具还具备了可视化视图查询的功能。你再也不需要手动的去转换 [HPROF](http://docs.oracle.com/javase/7/docs/technotes/samples/hprof.html) 文件了。   \n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.37.27-Mz8F5wmRnK.png)\n\n新的内存快照具备简洁明了的向下钻能力（drill down我理解是一层一层的点下去。。）。它提供了对象调试视图，从而让你可以查看到还在堆内存中的对象们。它还允许你顺着引用链一直找到最近的GC 节点从而轻松的找到是什么被持有住了。   \n\n## 开发者服务\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.39.04-fWLlwnIlHC.png)\n\nGoogle 提供了广告，数据分析等服务。而如今 Android Studio 1.3 可以帮助你通过一系列定制 UI 更容易的使用这些服务，比如API Keys等。这个新功能同样会帮助添加所需要的依赖，权限以及模版类从而可以花最少的时间使用这些服务。   \n\n## 即将发布的新功能\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.41.57-iWXIUx6z6S.png) \n\n新的可视化设计视图暂时还没有被集入到Android Studio 1.3中。尽管如此，它们还是非常令人兴奋的，因为可以为开发者减轻创建UI界面的负担。   \n上面的截图展示了新的主题编辑器。它让你可以可视化的检查以及修改主题文件。它还可以展示使用该主题的控件的预览。   \n\n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.46.16-xrH2DGgL94.png)\n\n而布局编辑器则被完全的重写，多了很多新功能。上述截图展示了蓝图模式，这可以让你专注于你的布局，字体风格等。它还与设计库集成，从而可以通过拖拉直接添加设计元素到布局中。   \n\n![image](http://7xiegl.com1.z0.glb.clouddn.com/Screen Shot 2015-05-30 at 11.48.28-98HlY6FuwA.png)\n\n而 XML 文件预览模式现在有能力展示系统Preference了(以前是无法预览Preference的)。更重要的一个功能是完全的对布局文件预览窗口直接操作的所见即所得编辑，包括一些控件的拖动。   \n\n\n## Q&A \n\n很不幸，Q&A环节十分的短暂。而在其中最有意思的问题是 Android 什么时候支持 Java 8。而回答则是，“这是一个平台性问题”(\"that's a platform issue\")。   \n\n\n\n\n\n"
  },
  {
    "path": "issue-15/Android-C++引用计数介绍.md",
    "content": "Android C++ 引用计数介绍, part 1\n---\n\n> * 原文链接 : [Introduction to Android C++ reference counting, part 1](http://pierrchen.blogspot.jp/2015/06/introduction-to-android-reference.html)\n* 原文作者 : [Bin Chen](https://plus.google.com/+PierrChen/posts)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [BownX](https://github.com/BownX) \n\n\n任何一个基于Android 的native框架做开发的人都不可避免的会碰到一些几乎到处使用的native层C++工具类。`sp`(或者称为`StrongPointer`)，就是其中之一。了解它是如何工作非常重要，这样你才能更清晰的理解代码，并且写出精简、没有资源泄漏的代码。在这篇文章中，我们就要根据实例来了解`sp`的基本概念和使用方法。\n\n```cpp\n#include <utils/RefBase.h>\n#include <utils/Log.h>\n#include <cstdlib>\n#include <cassert>\n\nusing namespace android;\n\n// 我们自定义了一个 Memory 类继承自 RefBase [1]，以保证其拥有引用计数的能力，并且能被 sp<T> [2] 模板类接收，这里 sp 指的是强引用指针。\n// [1]https://android.googlesource.com/platform/frameworks/native/+/jb-mr1-dev/include/utils/RefBase.h\n// [2]https://android.googlesource.com/platform/frameworks/native/+/jb-mr1-dev/include/utils/StrongPointer.h\n\nclass Memory: public RefBase {\npublic:\n    Memory(int size) : mSize(size), mData(NULL) {\n        ALOGD(\"        Memory constructor %p \",this);\n    }\n    virtual ~Memory() {\n        ALOGD(\"        Memory destructor %p\", this);\n        if (mData)  free(mData);\n    }\n    virtual void onFirstRef() {\n        ALOGD(\"        onFirstRef on %p\",this);\n        mData = malloc(mSize);\n    }\n    int size() {return mSize;}\nprivate:\n    int mSize;\n    void *mData;\n};\n\n// 用于输出标记log\n#define L(N)   ALOGD(\"LINE %d TRIGGER:\",N);\n// 输出object的强引用数量\n#define C(obj) ALOGD(\"        Count of %p : %d\", (void*)obj, obj->getStrongCount());\n\nint main()\n{\n    {\n        // 创建一个 Memory 的实例，并且赋值给一个原始指针。\n        L(1)\n        Memory *m1 = new Memory(4);\n        // 使用 sp(T* other) 构造函数创建一个强引用指针，这样会使 m1 的引用计数值加1，并且调用 m1::onFirstRef，这里你可以做一些延迟初始化操作。\n        L(2)\n        sp<Memory> spm1 = m1;\n        C(m1);\n        // 通常，我们会把上面两步合并为一行代码。\n        // 然后我们创建另外一个强引用指针，spm2, 并且初始化。\n        // 要拿到原始的object，可以使用 sp<T>::get() 方法。\n        L(3)\n        sp<Memory> spm2 = new Memory(128);\n        Memory *m2 = spm2.get();\n        // 要想调用原object中的方法，使用 sp 就像用原始指针一样方便。\n        int size = spm2->size();\n        // 创建第三个 sp, spm3, 这里会调用构造函数 sp(const sp<T>& other)，这样会使 spm1 的引用计数加1，现在 m1 被两个强引用指针指向，spm1 和 spm3。\n        L(4)\n        sp<Memory> spm3 = spm1;\n        C(m1);\n        // 下面这段代码和 L(4) 差不多，区别在于 spm4 的作用域被限制在一个代码块中。\n        L(5)\n        {\n            sp<Memory> spm4 = spm1;\n            C(m1);\n            // 这里 m1 被 spm1, spm3 和 spm4 指向。\n        }\n        // 到了这里，spm4 离开了作用域被销毁，不再指向 m1，因此 m1 的引用数变回了2，既被 spm1 和 spm3 指向。\n        L(6)\n        C(m1);\n        // trigger sp& operator = (const sp<T>& other);\n        L(7)\n        // 在下面这行赋值之前，spm2 指向 m2，smp3 指向 m1。\n        spm3 = spm2;\n        // 赋值之后， spm3 不再指向 m1 而是 指向 m2，因此 m1 的引用计数减1，m2 的加1。\n        C(m1);\n        C(m2);\n        // spm5 是 spm1 的引用，没有新的对象创建，因此 m1 的引用计数没变。\n        L(8)\n        sp<Memory> &spm5 = spm1;\n        C(m1);\n        // 我们也可以创建一个智能指针初始为空，后续再赋值。我们也可以调用 sp::clear() 来显式的清除引用。\n        L(9)\n        sp<Memory> spm6;\n        assert(spm6.get() == NULL);\n        spm6 = spm1;\n        C(m1);\n        L(10)\n        spm6.clear();\n        assert(spm6.get() == NULL);\n        C(m1);\n    }\n    // 上述代码块结束之后，所有的智能指针都脱离了作用域，因此它们都将会被销毁，并且触发它们各自指向的object的引用计数减1。例如，当 spm1 和 spm6 都销毁时，m1 的引用计数减到了0，然后将会触发 m1 的析构。\n    L(-1)\n    return 0;\n}\n```\n\n下面是这个程序的输出。\n\n```\nLINE 1 TRIGGER:\n        Memory constructor 0x558f06b050 \nLINE 2 TRIGGER:\n        onFirstRef on 0x558f06b050\n        Count of 0x558f06b050 : 1\nLINE 3 TRIGGER:\n        Memory constructor 0x558f06b0c0 \n        onFirstRef on 0x558f06b0c0\nLINE 4 TRIGGER:\n        Count of 0x558f06b050 : 2\nLINE 5 TRIGGER:\n        Count of 0x558f06b050 : 3\nLINE 6 TRIGGER:\n        Count of 0x558f06b050 : 2\nLINE 7 TRIGGER:\n        Count of 0x558f06b050 : 1\n        Count of 0x558f06b0c0 : 2\nLINE 8 TRIGGER:\n        Count of 0x558f06b050 : 1\nLINE 9 TRIGGER:\n        Count of 0x558f06b050 : 2\nLINE 10 TRIGGER:\n        Count of 0x558f06b050 : 1\n        Memory destructor 0x558f06b0c0\n        Memory destructor 0x558f06b050\nLINE -1 TRIGGER:\n```\n\n下一篇文章，我们会来研究一下循环引用的问题以及如何通过`弱引用指针`来处理它，也就是 Android 里所称呼的`wp`。\n"
  },
  {
    "path": "issue-15/Android-M的App-Links实现详解.md",
    "content": "# Android M的App Links实现详解\n-----\n\n> * 原文链接 : [THINGS YOU MAY NOT KNOW: ONRESUMEFRAGMENTS](http://www.randomlytyping.com/blog/2015/6/5/things-you-may-not-know-about-onresumefragments)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [jianghejie](https://github.com/jianghejie) \n\n\n谷歌2015年的I/O大会上宣布了一个[新特性](http://www.androidpolice.com/2015/05/28/io-2015-android-m-will-support-app-deep-linking-without-that-annoying-selector-prompt/)：允许开发者将app和他们的web域名关联。这一举措是为了最小化用户遇到“打开方式”对话框的概率。\n\n比如，我们安装了两个Twitter应用 - 官方的和[Falcon Pro](https://play.google.com/store/apps/details?id=com.jv.materialfalcon)。当你在某个地方点击了Twitter URL的时候，你会看到如下的对话框：\n\n![Clicking a Twitter link in the Android messaging app](https://chris.orr.me.uk/img/posts/app-links-sms.png) ![app-links-intent-chooser-dialog.png](https://chris.orr.me.uk/img/posts/app-links-intent-chooser-dialog.png \"1433755732968129.png\")\n\n但是在安卓M中，如果一个app明确的指定了App链接-这个对话框将不复存在。点击一个链接将立即打开官方的app，没有第三方app的机会，更不会打开一个浏览器。\n\n在上图的例子中，当你点击了那个链接，安卓系统会检查是否有一个app可以处理twitter.com URL，然后跟twitter.com核对哪个app（s）可以处理该域名的链接，这样我们就能避免影响用户。\n\n注意安卓并不会在点击链接的时候才核对这些链接，因此在安卓决定使用哪个app之前并不会有网络阻塞。关于这点后面有更多讨论。\n\n虽然这会使安卓更方便- 多数情况下，你确实希望点击一个链接打开的是最合适的那个app- 但是对于那些喜欢使用第三方app的人来说，似乎是一件坏事。不过这种行为可以在Android M的系统设置中关掉。\n\n##  app开发者如何实现App Links\n\n实现的细节可以在[Android Developers&#39; Preview 网站](https://developer.android.com/preview/features/app-linking.html)上找到。\n\n如果你有一个需要处理链接（比如example.com）的app，你应该：\n\n*  有上传文件到example.com根路径的权限，如果没有，你无法让你的app成为这些链接的默认打开方式。\n*  在build.gradle文件中设置compileSdkVersion &#39;android-MNC&#39;。\n*  为每个这样的&lt;intent-filter&gt;标签\n\n<pre class=\"brush:js;toolbar:false\">&lt;intent-filter&gt;\n    &lt;data android:scheme=&quot;http&quot; /&gt;\n    ...\n&lt;/intent-filter&gt;</pre>\n\n添加属性android:autoVerify=&quot;true&quot;。http也可以是https。\n\n注：这里对filter的写法略去了很多，其实官网（上面的那个链接）有完整的示例：\n\n<pre class=\"brush:js;toolbar:false\">&lt;activity ...&gt;\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; /&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;\n        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; /&gt;\n        &lt;data android:scheme=&quot;http&quot; android:host=&quot;www.android.com&quot; /&gt;\n        &lt;data android:scheme=&quot;https&quot; android:host=&quot;www.android.com&quot; /&gt;\n    &lt;/intent-filter&gt;\n&lt;/activity&gt;</pre>\n\n认证是以主机名为单位的而不是以intent filter为单位的，因此从技术上讲，并不需要为每个标签都添加该属性，但是添加了也不会出什么问题。\n\n### 创建JSON文件\n\n为了能让安卓可以认证，你的app需要被允许使用app链接行为，为此，需要提供一个JSON文件，JSON文件中需要包含app的ID以及APK的公钥证书。\n\n这个文件必须包含一个JSON数组，数组中可以有一个或者多个对象，每个对象对应一个你想认证的app ID:\n\n<pre class=\"brush:js;toolbar:false\">[\n  {\n    &quot;relation&quot;: [&quot;delegate_permission/common.handle_all_urls&quot;],\n    &quot;target&quot;: {\n      &quot;namespace&quot;: &quot;android_app&quot;,\n      &quot;package_name&quot;: &quot;com.example.myapp&quot;,\n      &quot;sha256_cert_fingerprints&quot;: [&quot;6C:EC:C5:0E:34:AE....EB:0C:9B&quot;]\n    }\n  }\n]</pre>\n\n比如，你现在有一个com.example.myapp的发行版app，同时还有一个叫com.example.myapp.beta的beta版app，你可以通过在数组中设置两个对象来允许两者都可以接受认证，每个对象带有各自的app ID和公钥值。\n\n注意，这个文件的验证是非常严格的：数组中的每个对象都必须和上面的那个一模一样。在数组中添加了任意其他的对象，或者对象中有额外的属性都会导致整个验证失败。- 即便这个app对象是有效的。\n\n为了防止为同一个构建注册了不同的key，貌似一个app可以指定多个SHA256指纹证书，不管怎样，指纹证书可以通过如下方式获得：\n\n<pre class=\"brush:js;toolbar:false\">echo | keytool -list -v -keystore app.keystore 2&gt; /dev/null | grep SHA256:</pre>\n\n### 上传JSON文件\n\n创建完文件之后，你需要上传它同时保证它可以使用这个URL访问http://example.com/.well-known/statements.json。\n\n目前这个URL是http的，最终的M版本将只允许通过HTTPS访问该URL。\n在第一个M预览版中，重定向到HTTPS，或者任何其他重定向（301，302或者307）貌似都会被忽略并被视为失败。\n\nURL的scheme和&lt;intent-filter&gt;标签中的android:scheme值是互不相干的，即使你有一个只接受HTTPS URLs的filter，认证URL仍然需要通过HTTP访问。\n\n在了解了安卓如何使用这些信息之后，我们来看看如果debug这个过程。\n\n## Android M是如何实现App Links的\n\nApp链接认证涉及到安卓系统的两个组建：Package Manager和Intent Filter Verifier。\n\n**PackageManager**是一个无处不在的标准组建 - 它负责验证所安装的apk是否有效，授予app权限，另外还可以通过它知道系统上安装了些什么app。\n\n而**Intent Filter Verifier**则是Android M上才有的新玩意儿。这个组建负责获取链接指向的JSON认证，解析它，验证它，然后将报告返回给PackageManger。\n\n虽然这个组建不是用户轻易就能替换的，但似乎系统中只能有一个活动状态的Intent Filter Verifier - 想要注册成为一个verifier，必须要有android.permission.INTENT_FILTER_VERIFICATION_AGENT权限，而这个权限只有签名了系统密钥的app才能得到。\n\n你可以通过如下的命令查看当前激活的intent filter verifier：\n\n<pre class=\"brush:js;toolbar:false\">adb shell dumpsys package ifv</pre>\n\n在第一个M预览版中，com.android.statementservice完全扮演了这个角色。\n\n###  How App Links are verified\n\nApp链接认证在安装的时候就一次性完成。这就是为什么刚刚我们说不必在每次点击链接的时候都阻塞网络。\n\n![](https://chris.orr.me.uk/img/posts/app-links-system-diagram.png)\n \n当一个package安装的时候，或者现有的package升级的时候：\n\n* 1.PackageManager对即将安装的apk做常规的验证。\n* 2.如果成功，这个package将被安装，同时发出一个带有android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION的广播intent，intent中还携带有该package的信息。\n* 3.Intent Filter Verifier的广播接收器将获取这个广播。\n* 4.从package的&lt;intent-filter&gt;标签中编译出一个特有主机名的列表。\n* 5.verifier尝试从每个特有的主机名中获取statements.json。\n* 6.每一个被获取的JSON文件都会检查它的application ID和安装包的证书。\n* 7.只有当所有文件同时满足时，才会发送成功信息到PackageManager，否则失败。\n* 8.PackageManager存储结果。\n\n如果认证失败，app链接将无法指向你的app - 你的app会像往常一样出现在“打开方式”对话框中（除非另一个app通过了同一域名的验证）。\n\n就我所了解的而言，认证只会在安装和升级的时候会发生，因此对大多数用户来说，再次通过验证的机会是在app下一次升级的时候。\n\n###  Intent Filter Verifier的行为\n\n**主机名**\n\nexample.com和www.example.com会被认为是两个独立的主机名。因此要求statements.json在两个主机名下都是可直达的。\n\n比如，如果你将所有的请求都重定向到http://www.example.com/* to https://example.com/*，则会让这个主机名的认证失败，从而导致整个app链接认证失败。\n\n这种情况下，你可能需要为你的web服务器做特殊的配置，确保每个对于statements.json的请求都有能直接返回HTTP 200。这个约束在后面的版本中可能会有所放松。\n\n**响应时间**\n\n如果verifier不能在5秒之内和你的web服务器建立链接并接收到HTTP响应，认证会失败。\n\n**缺少链接环境**\n\n同样的，如果在认证开始的时候设备是离线的，或者网络环境很差，认证也会失败。\n\n**HTTP 缓存**\n\n目前Intent Filter Verifier的实现基本遵循HTTP缓存规则。\n\n如果你的statements.json响应包含了Cache-Control: max-age=[seconds]头部，那么这个响应将被verifier缓存到磁盘。虽然 60秒以下的'max-age'会被忽略，但是60秒似乎也足够了。同样的一个`Expires` header 也会被缓存。\n\n如果你有ETag或者最近更新的headers，那么verifier将在下一次视情况使用这些值来验证相应的主机。据我所知，如果这些header子退出之后没有明确指定缓存控制头，那么响应的缓存时间将是不确定的。\n\n缓存头部只理会http 200的响应，如果是一个404响应，那么下次将忽略，verifier需要直接连接主机。\n\n### Debugging App Links\n\n当安卓系统试图认证你的app链接时，PackageManager除了向logcat中报告true/false值之外，还有一些其他反馈，比如：\n\n<pre class=\"brush:js;toolbar:false\">IntentFilter ActivityIntentInfo{1a61a0a com.example.myapp/.MainActivity}\n verified with result:true and hosts:example.com www.example.com</pre>\n\n但是，你可在任意时刻向系统查询package的app链接认证状态：\n\n<pre class=\"brush:js;toolbar:false\">adb shell dumpsys package d</pre>\n\n这会返回如下的认证条目信息：\n\n<pre class=\"brush:js;toolbar:false\">Package Name: com.example.myapp\nDomains: example.com www.example.com\nStatus: always</pre>\n\n可能会有多个关于你package的条目：一个是系统的，零个或者多个用户的- 有些用户的偏好覆盖了系统参数。\n\n可能会产生的状态值大致如下：\n\n*   **undefined** —&nbsp; app没有在manifest中启用链接自动验证功能。\n\n*   **ask** — app验证失败（会通过打开方式对话框询问用户）\n\n*   **always** — app通过了验证（点击这个域名总是打开这个app）\n\n*   **never** — app通过了验证，但是系统设置关闭了此功能。\n\n如果你没能通过认证，你可以再次尝试重新安装同一版本：\n\n<pre class=\"brush:js;toolbar:false\">adb install -r app/build/outputs/apk/app-debug.apk</pre>\n\n如果你在安装的时候没有在服务器上看见statements.json URL的请求，你可以清空Intent Verifier服务的HTTP缓存，这样下次就会直接请求服务器：\n\n<pre class=\"brush:js;toolbar:false\">adb shell pm clear com.android.statementservice</pre>\n\n如果你不知道statements.json的内容是否正确返回，可以使用安卓模拟器的-tcpdump选项来检查网络上发送的是什么 - 注意安卓M最终版出来之后就没那么容易了，因为数据是加密的。\n\n还有一种选择，那就是使用模拟器的-http-proxy选项，让所有的网络请求都通过代理，比如[Charles](http://charlesproxy.com/)代理。\n\n##  总结\n\n虽然在开始我担心App Links会取代目前安卓上非常酷的intent机制，但是也乐于看到这对于大多数情况都是有用的，况且如果用户不喜欢，使用简单的方法就可以把它关掉。\n\n考虑到verifier服务对JSON解析和HTTP请求异常严格，希望这里所提到的一些细节对你实现app linking有所帮助。祝你好运！\n\n    \n"
  },
  {
    "path": "issue-15/facebook代码分析工具-infer.md",
    "content": "# facebook开源项目Facebook Infer: 静态代码检查工具\n-----\n\n> * 原文链接 : [Open-sourcing Facebook Infer: Identify bugs before you ship](https://code.facebook.com/posts/1648953042007882)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Mr.Simple](http://github.com/bboyfeiyu) \n\n\n今天，我们开源了一个名叫[Facebook Infer](http://fbinfer.com/)的静态程序分析库，该库用于在程序分发之前定位代码bug。静态分析器是一个不需要运行代码就可以准确地找出代码bug的自动化工具。它是传统的动态测试的补充，传统的动态测试允许每次只运行一个独立的代码单元来检测程序的正确性，静态分析则允许一次性检测多个、甚至所有代码。Facebook Infer使用数学逻辑来推理程序的运行，当在查找程序时来推测程序员在代码中所要做的操作。我们内部使用Facebook Infer来分析facebook的Android和iOS应用，例如facebook messagers, Instagram等等。现在，这个分析器能够发现空指针、内存泄漏等能够使应用奔溃的大量代码bug.\n\n\n每个月我们都会修复使用Facebook Infer发现的数百个潜在的bug，从而避免这些bug提交到我们的代码仓库。这些成果大大减少了我们研发人员查找bug的时间，也使得我们的用户能够使用更高质量的应用。\n\n![](https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xta1/t39.2365-6/11405195_1607248792888456_614155255_n.gif)\n\n\n## 动机\n\nfacebook喜欢在产品完成之后尽可能快的分发出去，而不是在一连串冗长的手动测试之后。这种方式在移动应用端的风险比web端更大。当一个web的bug被发现时，我们可以马上修复并且提交到服务器上。但是为了修复移动应用的bug,我们需要像应用市场更新应用安装文件。因此，这也使得在将应用分发给用户之前发现bug变得更有价值。结合静态分析和自动测试能够帮助Facebook在将产品分发出去之前找到crashed和内存泄漏，这使得我们的应用端应用的研发更为有效、迅速。\n\n\n为了符合开发者的工作流程，当开发者提交了代码后Facebook Infer增量地执行以分析代码的修改。当发现潜在bug时分析器会自动地在代码中添加评论。这些bug报告都是高质量的，因此我们可以帮助开发者快速的找到问题所在。在最近的几个月中，bug的修复率已经高达80%,对于一个自动化工具来说那是相当高了。\n\n\n在现在的开发模式中，我们运行分析器来分析我们的Android和iOS代码，但是我们还可以做更多的事。Facebook Infer还可以分析c语言项目和java代码，我们计划在未来扩展它的兼容性。有你的帮助，我们希望Facebook Infer 能够有更广阔的用途。\n\n## 核心技术 : 逻辑分离和bi-abduction\n\nfacebook infer使用逻辑来推理程序的执行,但是通过这种方式来推理上百万行代码量的应用时会变得非常困难。从理论上讲，需要检查的代码数量会多过预计的数量。此外，在Facebook我们的代码并不是手工修复代码，而是一个不断进化的系统，这个系统被很多开发者频繁的更新。一天之内大量代码的修改在我们的移动开发中并不常见。因此这个代码分析器的需求变得更有挑战，因为我们期望一个能够快速检查代码修改的工具，这个时间应该在10分钟之内。这样的规模和速度需要更高级的数学模型，Facebook Infer使用了两种技术: 逻辑分离和bi-abduction。\n\n逻辑分离是使Facebook Infer分析能够推断出应用存储的独立小部分的一种理论，从而不用考虑每一步存储的完整性。因为考虑每一步的存储完整性对当今的大型可寻址虚拟内存处理器来说是一个很蛋疼的事。\n\nBi-abduction是一种逻辑推理技巧，可以使Facebook Infer挖掘到应用代码独立部分的行为特性。在其运行时把这些特性存储下来之后，Facebook只需要分析软件中发生改变的部分，其他的部分可以直接套用先前的分析结果。\n\n结合这几种方法，我们的分析器能够在数分钟之内在上百万行代码中找到被修改的代码中存在的复杂问题。\n\n## 历史 : 从代码检查理论到为十多亿人服务的蜕变\n\n软件的自动检查在计算机科学社区是一个长期依赖的目标。Facebook Infer在这个领域构建了一个基本实现， 包含霍尔逻辑和抽象解释。在加入facebook之前，我们参与到了其他基础设施的开发工作，“逻辑分离”作为能够实现软件自动检查的结果出现在人们的视野中。\n\n\n逻辑分离是在计算机科学领域的一个重大突破-一种新的数学逻辑被用于描述计算机内存的变化（类似于布尔逻辑用来描述电路）。我们专注于将这个理论运用和自动化,创建一系列原型工具(例如Smallfoot, Space Invader, Abductor)来支撑这些推理逻辑，最终发现了bi-abduction是模块化分析程序的一种有效形式。\n\n基于上述研究成果,我们2009年创建了一个名为Monoidics的公司。Monoidics在2013年加入Facebook，从那以后我们采用持续开发和部署的风格来开发我们的产品，在我们的分析器团队和facebook移动软件开发工程师的不断努力的迭代开发下我们的分析器得到了很大的提升。我们也展示了当运用到facebook代码库时代码检查技术能够得到快速的发展。\n\n## 展望未来\n\n程序检查是一个有着活跃研究社区和前景光明的领域。在facebook,我们说过这趟旅行我们只完成了1%。在程序检查领域还有很多的工作需要我们去完成。但是，随着我们的不断努力，我们相信这个领域的成果会让软件工程师解放出更多的价值。我们可以展望未来，有你的帮助，程序检查技术能够提供更多、更有用的技术来使得我们的代码更可靠、更高效。\n\n你可以下载和试用Facebook Infer或者移步到[fbinfer.com](fbinfer.com)\n以了解更多的详细情况。\n\n\n**致谢**： Infer工程@FB团队包括Sam Blackshear, Jeremy Dubreil, Andrzej Kotulski, Martino Luca, Irene Papakonstantinou, Dulma Rodriguez, and Jules Villard, in addition to Calcagno, Distefano, and O'Hearn. We thank our FB colleagues Mathieu Baudet, Dominik Gabi, and Pieter Hooimeijer for their help, and Bryan O'Sullivan, David Mortenson, and Jim Purbrick for their support. Outside of Facebook, we particularly acknowledge the scientific contributions of David Pym, John Reynolds, Hongseok Yang和Josh Berdine."
  },
  {
    "path": "issue-15/readme.md",
    "content": ""
  },
  {
    "path": "issue-15/你可能漏掉的知识点-onResumeFragments.md",
    "content": "你可能漏掉的知识点: onResumeFragments\n---\n\n> * 原文链接 : [THINGS YOU MAY NOT KNOW: ONRESUMEFRAGMENTS](http://www.randomlytyping.com/blog/2015/6/5/things-you-may-not-know-about-onresumefragments)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [jianghejie](https://github.com/jianghejie) \n\n长话短说：如果你在使用FragmentActivity的任何子类（比如最新的AppCompatActivity），并且你正在考虑要在onResume方法中做fragment transaction操作，那么请在onResumeFragment里做这件事情。\n\n如果你想知道详情或者一些注意事项，继续阅读。如果不想，没关系，下篇文章见。\n\n\n还在看？那么 ok。\n\nonResume和onResumeFragments的区别是什么呢？下面是[官方文档](http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()) 对FragmentActivity.onResume的解释：\n\n>将onResume() 分发给fragment。注意，为了更好的和旧版本兼容，这个方法调用的时候，依附于这个activity的fragment并没有到resumed状态。着意味着在某些情况下，前面的状态可能被保存了，此时不允许fragment transaction再修改状态。从根本上说，你不能确保activity中的fragment在调用Activity的OnResume函数后是否是onresumed状态，因此你应该避免在执行fragment transactions直到调用了onResumeFragments函数。\n\n总的来说就是，你无法确定activity当前的fragment在activity onResume的时候也跟着resumed了，因此要避免在onResumeFragments之前进行fragment transaction，因为到onResumeFragments的时候，状态已经恢复并且它们的确是resumed了的。\n\n这样做可以避免发生IllegalStateException异常，在一个fragment的状态已经保存的情况下（通过onSaveInstanceState），再试图进行fragment transaction操作就会抛出这个异常。\n如果fragment的activity销毁并重建，前面保存的变量将丢失。要想更深的理解这个问题，可以阅读Alex Lockwood的 [\"Fragment Transactions 与 Activity状态的丢失\"](http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html)  一文。\n\n\n其实我是在知道fragments和 fragment transaction了很久之后才知道onResumeFragments这个东西的。activity的绝大部分生命周期中都不涉及到它，因为它只存在于兼容包里面的FragmentActivity，而SDK里面的Activity则没有。不过onResumeFragments仍然值得去了解。\n\n说了这么多，我这几天只能给一点很粗贱的建议：尽可能的避免在生命周期事件中尤其是onResume或者onResumeFragments中进行fragment transaction，如果你正在考虑这样做，很可能你的UI和事务逻辑都需要反思一遍了。\n\n不过那时以后要说的事了。\n\n"
  },
  {
    "path": "issue-15/如何修复编译时的MultiDex崩溃.md",
    "content": "如何修复编译时的MultiDex崩溃\n---\n\n> * 原文链接 : [PSA: fix MultiDex build crashes](https://medium.com/sebs-top-tips/psa-fix-multidex-build-crashes-ae2b81bcf711)\n* 原文作者 : [Sebastiano Poggi](https://medium.com/@seebrock3r)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [zijianwang90](https://github.com/zijianwang90) \n\n[“太长不想看了”在文章末尾]\n\n是否有那么一天，你曾经在编译你的项目时候因为以下这样的错误而失败了：\n> trouble writing output:\nToo many field references: 131000; max is 65536.\nYou may try using --multi-dex option.\n\n为啥？也许是最新的Play服务更新造成的，又或许是你应用中的某某统计SDK造成的。但是有一点是可以肯定的：你的应用成为了拥有超过六万五千个方法或变量的应用之一。\n\n若在一年之前，这也许是个大问题。当时有一些办法可以避免这个问题，包括一些拆分dex文件的方法，但这些方法往往并不完全奏效。\n\n#MultiDex?\n幸运的是，自从谷歌引入了**MultiDex**机制，上述情况就非常容易解决了。在你的build.gradle中做如下配置即可：\n```\nandroid {\n  // ...\n  defaultConfig {\n    // ...\n    multiDexEnabled true\n  }\n}\n```\n\n##缺点\n**Multidex也有一些缺点**。首先，**编译时间增加**。其次，在Delvik虚拟机下（非ART），由于classloader需要读取多个dex文件，所以**应用的启动时间会大幅增加**。\n\n但更严重的是，他有时候会造成你的**编译崩溃**。是的，编译崩溃，不是应用崩溃。\n> UNEXPECTED TOP-LEVEL ERROR:\njava.lang.OutOfMemoryError: GC overhead limit exceeded\n  at com.android.dx.cf.code.ExecutionStack.copy(ExecutionStack.java:66)\n  at com.android.dx.cf.code.Frame.makeExceptionHandlerStartFrame(Frame.java:397)\n  at com.android.dx.cf.code.Ropper.processBlock(Ropper.java:916)\n  at com.android.dx.cf.code.Ropper.doit(Ropper.java:742)\n  at com.android.dx.cf.code.Ropper.convert(Ropper.java:349)\n  at com.android.dx.dex.cf.CfTranslator.processMethods(CfTranslator.java:280)\n  at com.android.dx.dex.cf.CfTranslator.translate0(CfTranslator.java:137)\n  at com.android.dx.dex.cf.CfTranslator.translate(CfTranslator.java:93)\n  at com.android.dx.command.dexer.Main.processClass(Main.java:729)\n  at com.android.dx.command.dexer.Main.processFileBytes(Main.java:673)\n  at com.android.dx.command.dexer.Main.access\\$300(Main.java:83)\n  at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602)\n  at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)\n  at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)\n  at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)\n  at com.android.dx.command.dexer.Main.processOne(Main.java:632)\n  at com.android.dx.command.dexer.Main.processAllFiles(Main.java:505)\n  at com.android.dx.command.dexer.Main.runMultiDex(Main.java:334)\n  at com.android.dx.command.dexer.Main.run(Main.java:244)\n  at com.android.dx.command.dexer.Main.main(Main.java:215)\n  at com.android.dx.command.Main.main(Main.java:106)\n  \n以上这些错误大致意思就是我们在生成Dex阶段内存不足了。\n\n#解决办法（太长不想看了）\n感谢那谁给我意见（不好意思忘了是谁了，时间有点久了），在绞尽脑汁了一段时间之后，我找到了一个非常简单的解决办法。\n\n那就是在你相应module的build.gradle文件中做出如下配置：\n```\nandroid {\n  // ...\n  dexOptions {\n    javaMaxHeapSize “2048M”\n  }\n}\n```\n搞定！\n"
  },
  {
    "path": "issue-16/Android一体机模式：规则限制.md",
    "content": "# Android 一体机模式：规则限制\n-----\n\n> * 原文链接 : [Android Kiosk Mode: Rules for Restrictions](http://cases.azoft.com/android-kiosk-mode-rules-restrictions/)\n* 原文作者 : [Anna Voronova](http://cases.azoft.com/android-kiosk-mode-rules-restrictions/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Rocko](https://github.com/zhengxiaopeng) \n*  校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 :  完成\n   \n   \n   \n![Android Kiosk Mode: Rules for Restrictions](http://cases.azoft.com/images/2015/06/Android-Kiosk-Mode-Rules-for-Restrictions.png)\n\n生活中我们随处都可见到一体机，当然了我们在这谈论的并不是卖冰激凌和柠檬水的售货一体机，我们这篇文章是关于交互式信息一体机的。\n\n电脑的终端就是一个典型的信息一体机，用户可以使用它来执行一组有限的动作。其它常见的例子还有如 ATM 机、照相亭、自动售票机、自助登机服务终端系统、等等。\n\n交互式信息一体机使用了各种技术：触摸屏、纸币识别器、相机、打印机、扫描仪、Wi-Fi、NFC 等等。它们的共同特点是对未经授权的活动有强有力的保障体系。这样的终端下使用的软件不允许用户去改变系统的设置，重置或安装其它应用程序。 \n\n\n## 移动一体机\n\n[移动技术](http://www.azoft.com/mobile-application-development.htm) 的快速发展也使一体机有了革命性的发展。智能手机和平板电脑是现在常用的一体机。移动一体机跟标准的终端相比有很多明显的优势：由于紧凑的尺寸和大量生产而更加便宜，但是也有更多的功能。移动一体机在餐馆中被用作电子菜单，在商店和展厅中的销售助手等等。无论目的是什么，重要的是当一个移动设备提供一体机的功能为目的时它就不应该被用于任何其它目的。\n\n[Share to Twitter: Mobile kiosks have a number of significant advantages over standard terminals](https://twitter.com/share?text=Mobile+kiosks+have+a+number+of+significant+advantages+over+standard+terminals&via=azoft&related=azoft&url=http://bit.ly/1Gwux0z)\n\n为了使设备在一体机模式（kiosk mode，或说展台模式）下工作，需要运行一个程序，此程序会冻结掉操作系统的常见功能且它不允许用户退出程序。一体机的应用程序可能有一个秘密隐藏的管理面板或者从服务器控制的远程配置。你也可以配置系统报告：程序会将有关用户行为的数据发送给服务器，并将相关状态通知给管理员。\n\n下面，我们将分享一些我们在 [Android 一体机应用程序](http://www.azoft.com/google-android-development.htm) 方面的经验。我们也会讨论下开发中的一些陷阱和解决方案。\n\n\n## Android一体机模式\n\n把一台 Android 设备变为一体机，这是锁定其所有按键和连接器的好方法。在这种情况下，最普通也最有效的解决办法是把你的设备放置在一个防爆盒或者一个专门的地方中，然而这可能都是不可选的方法。此外，状态栏，系统对话框和虚拟小键盘仍然可以由任何用户访问，并且设置也可以更改。\n你如何避免这些问题？\n\n\n##  Android 5.0: 期待已久的 API\n\n让我们先说个好消息开始吧：Android 5.0 中已有锁住屏幕 API 的介绍了。这个 API “pins”（别针）别住屏幕并阻止用户离开所选的应用程序。该功能可用于为职员创建的一体机，开发用于评估和审查的教育应用。\n\n当你激活了屏幕固定模式，用户不会被系统对话框和通知所打断，同时不能打开其它应用程序，也不能回到主屏幕，状态栏也不再可见。\n\n你可以通过设置或者软件来激活这一模式\n\n- 在设置中（安全 -> 屏幕固定）开启屏幕固定模式，选择所需的应用并确认你的选择。\n\n- 软件中激活则调用  `startLockTask()` 方法并确认列入锁定模式。\n\n## Android 5.0 之前如何做到兼容？\n\n在早些的 Android SDK 中并没有提供一体机模式，也没有综合全面的 API 来冻结系统。因此，所有的不同组件应分别锁住而且在不同的版本中也是不同的方法。\n\n定制操作系统可以大大的简化一体机模式的实现，但是我们想介绍在 Android 5.0 版本下不使用特殊固件或者 root 权限的情况下如何冻结掉不需要的东西。\n\n\n### 重启\n\n当设备被冻结时用户首先想到的就是重启设备了。我们的任务是确保平板电脑或者智能手机重启后，一体机应用应该自动启动。\n\n这并不算难：在 Manifest 中声明广播接收器，重启后接受信息的权限，然后还有写个继承 Broadcastreceiver 的子类使你的程序跑起来。\n\n`AndroidManifest.xml:`\n``` XML\n<receiver android:name=\".BootReceiver\">  \n    <intent-filter >  \n        <action android:name=  \n             \"android.intent.action.BOOT_COMPLETED\"/>  \n   </intent-filter>  \n</receiver>  \n  \n<uses-permission  \n android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n```\n\n`BootReceiver.java :`\n``` Java\npublic class BootReceiver extends BroadcastReceiver {  \n  \n  @Override  \n  public void onReceive(Context context, Intent intent) {  \n    Intent myIntent = new Intent(context, MyKioskModeActivity.class);  \n    myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  \n    context.startActivity(myIntent);  \n  }  \n}  \n```\n\n### 返回键\n\n返回键则重写如下方法。\n\n``` Java\n@Override\npublic void onBackPressed() {  \n    // here we do nothing  \n}  \n```\n\n### HOME 键\n\nHOME 键并不能截获，因此为阻止它被按下时返回主屏幕，我们指定一体机程序作为 Launcher。在 Manifest 中添加 3 行：\n\n``` XML\n<intent-filter>  \n    <action android:name=\"android.intent.action.MAIN\" />  \n  \n    <category android:name=\"android.intent.category.HOME\" />  \n    <category android:name=\"android.intent.category.LAUNCHER\" />  \n    <category android:name=\"android.intent.category.DEFAULT\" />  \n</intent-filter>  \n```\n\n完成以上工作后，当你按下 HOME 键时，系统会弹个窗口让你选择一个 launcher 作为默认的，然后选择我们的程序就好了，HOME 键这一部分也完成了！\n\n\n### 电源键\n\n电源键的问题是最多的，解决掉这些问题的一个办法是把一体机程序窗口设置为锁屏屏幕。然而这个方法只能保证在 Android 4.0 版本下的系统中使用。\n\n``` Java\n@Override  \npublic void onAttachedToWindow() {  \n    getWindow().addFlags(  \n        WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);  \n    getWindow().addFlags(  \n        WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);  \n}     \n```\n\n\n###  系统对话框\n\n长按 HOME 或电源键将会弹出系统对话框，这可以让你退出程序。此外还有系统升级和低电量提示窗口，这对一体机也是危险的因为可以访问到系统设置。\n\n为了彻底摆脱系统弹框的困扰，推荐如下方法：当 Activity 失去焦点时，发送一个关闭所有系统对话框的广播。\n\n``` Java\n@Override  \npublic void onWindowFocusChanged(boolean hasFocus) {  \n  super.onWindowFocusChanged(hasFocus);  \n  if(!hasFocus) {  \n    Intent closeDialog =   \n          new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);  \n    sendBroadcast(closeDialog);  \n  }  \n}  \n```\n\n\n### 虚拟键盘\n\n在虚拟键盘中可能有一个设置按键。如果键盘是需要的，最好的解决方案是实现自己的版本或者整合现有的但须限制功能。\n\n\n### 状态栏\n\n状态栏中存在许多可以让你退出程序的机会，所以你必须避免它。首先要做的就是让你的程序在全屏模式下工作。\n\n在 Android 4.0 下的系统中你也可以指定窗口的类型为 `TYPE_SYSTEM_ALERT ` - 这种情况下一体机程序将会显示在所有系统元素的顶部。\n\n另一个方法是当状态栏一出现时就隐藏掉，做这个工作需要到 Manifest 中申请相应权限。\n\n``` Java\n<uses-permission  \n        android:name=\"android.permission.EXPAND_STATUS_BAR\"/>  \n  \n@Override  \npublic void onWindowFocusChanged(boolean hasFocus)<br>  \n{  \n   if(!hasFocus)  \n   {  \n           Object service  = getSystemService(\"statusbar\");  \n           Class<?> statusbarManager =   \n              Class.forName(\"android.app.StatusBarManager\");  \n           Method collapse = <br>  \n              statusbarManager.getMethod(\"collapse\");  \n           collapse .setAccessible(true);  \n           collapse .invoke(service);  \n   }  \n}  \n```\n\nAndroid 4.1 开始你可以使用 SDK 中隐藏状态栏的方法。\n\n``` Java\nView decorView = getWindow().getDecorView();  \nint uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;  \ndecorView.setSystemUiVisibility(uiOptions);  \nActionBar actionBar = getActionBar();  \nactionBar.hide(); \n```\n\n另一个比较流行的方法是创建一个透明的视图对象，拦截掉状态栏上的点击操作。实现它要求视图有 `SYSTEM_ALERT_WINDOW` 标识（flag）。\n\n以上这些在 Android 中锁定系统元素的技术内容仅仅是几个可选的方案。为了开发出不被用户破解的一体机应用，开发者门经常发现或发明新的方法并分享它们的经验给其它开发者\n\n与我们一起分享你在开发 Android 一体机程序的 tips 吧。你有用到我们上面说到的方法吗或者你知道其它有用的黑科技方法吗？"
  },
  {
    "path": "issue-16/readme.md",
    "content": ""
  },
  {
    "path": "issue-16/为什么你应该停止使用EventBus.md",
    "content": "为什么你应该停止使用EventBus\n---\n\n> * 原文链接 : [Why you should avoid using an event bus](http://endlesswhileloop.com/blog/2015/06/11/stop-using-event-buses/)\n* 原文作者 : [Tony Cosentini](http://endlesswhileloop.com/)\n* 译文出自 :  [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Zhaoyy](https://github.com/Zhaoyy) \n\n\n``文中的EventBus多指事件总线这种设计模式，而非EventBus这个具体的类库。``\n\n我经常看到EventBus被作为一种通用模式应用在Android开发中。Otto和EventBus这样的类库经常被用来省去编写不同层之间来封装代码的模板类。尽管EventBus刚开始看起来的确带来了方便，但是很快这些纠缠的事件会被弄成一堆乱麻，很难跟踪更别说调试。\n\nEventBus通常被宣扬可以降低模块之间的耦合度，但是，事实上带给你的是降低耦合度带来的混乱和困惑。\n\n## 嵌套的事件\n\n一个常见的弊端是处理嵌套的事件。不要在事件订阅者里面发布事件，这看起来很容易避免，但是经常会不容易理解。一个订阅者很可能会通过调用其他的方法来间接地抛出其他的事件。这样事件会纠缠成一个复杂地难以置信的球，很难进行调试。\n\nFacebook的Flux框架是另外一个事件驱动的设计框架，但是它[明令禁止发送嵌套事件](https://github.com/facebook/flux/blob/ac1e4970c2a85d5030b65696461c271ba981a2a6/src/Dispatcher.js#L184)。希望Otto和EventBus未来能够检测嵌套事件。\n\n## 以同步的方式处理生产者\n\n（我不认为这跟GreenRobot的EventBus有关系。）\n\n这是另外一个在大代码集中很难处理的常见模式。经常在许多Activity和Fragment里面，我们假定事件可以立即被EventBus里面的订阅者进行处理。它们会设置一个基于事件的属性，以假定这个属性不会为空的方式在其他的生命周期函数中使用。\n\n当你这样做的时候，你对于事件的发送传递作了一些大胆的假设，这些所有的假设并不能都被证明一定是这样或者是明确地被API备注，这些假设都是不可靠的。\n\n当你重构改变这些处理事件的代码的时候会发生什么呢？很可能重构处理事件代码的那个人进行实现的时候并不知道你的这个假设。\n\n问题的根本在于生产者是否处理好一下两个方面：\n\n- 要求把数据作为依赖。\n- 处理好数据未获取到时情况。\n\n在事件处理中组件可以把事件数据作为依赖，仅仅是作为是否可用的依赖。这需要一个随时可用的依赖构造函数或者是通过依赖注入注入数据（这个可以继续深入讨论）。\n\n如果还没有请求到任何数据，组件需要处理好这种还未得到信息的情况（对于UI组件通常是处理等待加载的情况）。\n\n## 其他选择\n\n没有类库或者工具会毫无代价的处理好这些问题，但是一些工具或者设计模式会鼓励你使用正确的处理方式来处理这些问题。\n\n正确地使用EventBus可能会避免这些问题。然而，相比大多数其他工具更鼓励实用的做法。尽管使用简单的监听类会增加一些额外的代码，可能会让业务的处理更明确。\n\n对于更加复杂的情况，[RxJava](http://reactivex.io/)提供了更好的解决办法。[Dan Lew有一些非常好的博客来介绍这个框架](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)。\n\n**2015-06-14更新**：为了减少偏激我更改了标题（原来的标题是“EventBus 一个反面的设计模式”）。需要重申的是并不是所有的易错问题都是由EventBus导致的。特别是同步获取的问题在任何方式中都有可能发生，但是我认为通过RxJava结合一些配置和可空的注解（``@Nullable``）能够更清晰地处理好这个问题。\n\n\n"
  },
  {
    "path": "issue-16/手动实现布局Transitions动画-第一部分.md",
    "content": "# 手动实现布局Transitions动画-第一部分\n----\n\n> * 原文链接 : [Manual Layout Transitions – Part 1](https://blog.stylingandroid.com/manual-layout-transitions-part-1/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Mr.Simple](https://github.com/bboyfeiyu)\n \n\n布局切换动画在Material design中是一个重要的方面,因为它们能够指明应用的工作流程，并且能够将UI上的可视化元素绑定在一起作为用户的导航。两个重要的工具可以实现这种效果，分别为Activity转场动画和布局动画（Layout Transitions）。然后布局动画需要在API 19及其之后才支持。在这一系列文章中，我们会学习到即使在无法调用transitions APIs时如何实现很好的转场动画。\n \n\n在我们开始之前，值得指出的是有一个后向兼容的Transitions API提供了到API 14的兼容。然而我决定不使用它，因为我从来没有尝试过使用它。我坚持使用核心的Android API来完成此功能，这个系列文章的目的就是探索transitions API本身使用的技术，从而达到运用自如的效果。\n\n在[上一个系列](https://blog.stylingandroid.com/dirty-phrasebook-part-1/)中当进行转场时会有一些简单的动画。可以到这个[视频地址](https://youtu.be/vXuY7q3Y5zw)进行观看效果 。\n\n\n我决定手动地实现这些效果，这种实现必须要具备后向兼容性。在开始处理更复杂的动画之前我们先来看看这些简单动画是如何实现的。\n\n\n让我们来看看上述视频示例中的布局。    \n\nres/layout/activity_main.xml\n\n```xml\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  xmlns:sa=\"http://schemas.android.com/apk/res-auto\"\n  android:id=\"@+id/layout_container\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:orientation=\"vertical\"\n  tools:context=\".MainActivity\">\n \n  <android.support.v7.widget.Toolbar\n    android:id=\"@+id/toolbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"?attr/actionBarSize\"\n    android:background=\"?attr/colorPrimary\"\n    android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n    sa:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\">\n \n    <Spinner\n      android:id=\"@+id/language\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\" />\n \n  </android.support.v7.widget.Toolbar>\n \n  <android.support.v7.widget.CardView\n    android:id=\"@+id/input_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    android:clipChildren=\"false\">\n \n    <RelativeLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:clipChildren=\"false\"\n      android:padding=\"@dimen/card_padding\">\n \n      <View\n        android:id=\"@+id/focus_holder\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:focusableInTouchMode=\"true\" />\n \n      <EditText\n        android:id=\"@+id/input\"\n        style=\"@style/Widget.TextView.Input\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:inputType=\"textMultiLine\"\n        android:imeOptions=\"flagNoFullscreen|actionDone\"\n        android:gravity=\"top\"\n        android:hint=\"@string/type_here\" />\n \n      <ImageView\n        android:id=\"@+id/clear_input\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignTop=\"@id/input\"\n        android:layout_alignEnd=\"@id/input\"\n        android:layout_alignRight=\"@id/input\"\n        android:padding=\"8dp\"\n        android:src=\"@drawable/ic_clear\"\n        android:visibility=\"invisible\"\n        android:contentDescription=\"@string/clear_input\" />\n \n      <ImageView\n        android:id=\"@+id/input_done\"\n        android:layout_width=\"32dip\"\n        android:layout_height=\"32dip\"\n        android:background=\"@drawable/done_background\"\n        android:src=\"@drawable/ic_arrow_forward\"\n        android:padding=\"2dp\"\n        android:layout_margin=\"8dp\"\n        tools:ignore=\"UnusedAttribute\"\n        android:elevation=\"4dp\"\n        android:visibility=\"invisible\"\n        android:layout_alignBottom=\"@id/input\"\n        android:layout_alignEnd=\"@id/input\"\n        android:layout_alignRight=\"@id/input\"\n        android:contentDescription=\"@string/done\" />\n \n    </RelativeLayout>\n \n  </android.support.v7.widget.CardView>\n \n  <FrameLayout\n    android:id=\"@+id/translation_panel\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\"\n    android:padding=\"@dimen/translation_outer_margin\">\n \n    <android.support.v7.widget.CardView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\">\n \n      <FrameLayout\n        android:id=\"@+id/translation_copy\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:foreground=\"@drawable/click_foreground\">\n \n        <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"vertical\"\n          android:background=\"?attr/colorPrimary\"\n          tools:ignore=\"UselessParent\">\n \n          <FrameLayout\n            android:id=\"@+id/translation_speak\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:foreground=\"@drawable/click_foreground\"\n            android:padding=\"@dimen/translation_inner_margin\">\n \n            <TextView\n              android:id=\"@+id/translation_label\"\n              style=\"@style/Widget.TextView.Label\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:textAllCaps=\"true\"\n              android:drawableStart=\"@drawable/ic_tts\"\n              android:drawableLeft=\"@drawable/ic_tts\"\n              android:drawablePadding=\"4dip\"\n              android:text=\"@string/sample_language\" />\n          </FrameLayout>\n \n          <TextView\n            android:id=\"@+id/translation\"\n            style=\"@style/Widget.TextView.Translation\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"@dimen/translation_inner_margin\"\n            android:layout_marginStart=\"@dimen/translation_inner_margin\"\n            android:layout_marginRight=\"@dimen/translation_inner_margin\"\n            android:layout_marginEnd=\"@dimen/translation_inner_margin\"\n            android:layout_marginBottom=\"@dimen/translation_inner_margin\"\n            android:text=\"@string/sample_translation\"/>\n        </LinearLayout>\n      </FrameLayout>\n \n    </android.support.v7.widget.CardView>\n \n  </FrameLayout>\n \n</LinearLayout>\n```\n\n这里需要我们关心的关键组件是Toolbar、id为input_view的CardView、ID为input_done的ImageView以及id为translation_panel的FrameLayout。其他的我们需要关心的就是id为focus_holder且可视状态为invisible的用来抢占焦点的视图。在EditText和focus_holder之间触发焦点时触发进入或者退出输入模式，以此来决定启动对应的动画。\n\n该动画将input_view上移到能够覆盖Toolbar的位置，然后将input_done视图以淡入的形式显示出来，并且将translation_panel淡出。当用户退出输入模式时则执行该动画的反向形式。在上述视频中你可以看到它的具体效果。\n\n我们先看看MainActivity : \n\n\nMainActivity.java\n\n```java\npublic class MainActivity extends AppCompatActivity {\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n \n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n        setTitle(R.string.sample_language);\n \n        View input = findViewById(R.id.input);\n        View inputDone = findViewById(R.id.input_done);\n        final View focusHolder = findViewById(R.id.focus_holder);\n \n        input.setOnFocusChangeListener(Part1TransitionController.newInstance(this));\n        inputDone.setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(@NonNull View v) {\n                        focusHolder.requestFocus();\n                    }\n                });\n    }\n}\n```\n\n这个实现直截了当： 它所做的几UI是初始化Toolbar和视图的焦点逻辑。创建transitions的逻辑是执行在Part1TransitionController类中，我将这部分逻辑抽象到Part1TransitionController中使得我们在该系列的后续文章中能够更容易的包装其他实现。Part1TransitionController类继承自包含了通用逻辑的TransitionController类。\n\nTransitionController.java\n\n```java\npublic abstract class TransitionController implements View.OnFocusChangeListener {\n    private final WeakReference<Activity> activityWeakReference;\n    private final AnimatorBuilder animatorBuilder;\n \n    protected TransitionController(WeakReference<Activity> activityWeakReference, @NonNull AnimatorBuilder animatorBuilder) {\n        this.activityWeakReference = activityWeakReference;\n        this.animatorBuilder = animatorBuilder;\n    }\n \n    @Override\n    public void onFocusChange(View v, boolean hasFocus) {\n        Activity mainActivity = activityWeakReference.get();\n        if (mainActivity != null) {\n            if (hasFocus) {\n                enterInputMode(mainActivity);\n            } else {\n                exitInputMode(mainActivity);\n            }\n        }\n    }\n \n    protected AnimatorBuilder getAnimatorBuilder() {\n        return animatorBuilder;\n    }\n \n    protected abstract void enterInputMode(Activity mainActivity);\n \n    protected abstract void exitInputMode(Activity mainActivity);\n \n    protected void closeIme(View view) {\n        Activity mainActivity = activityWeakReference.get();\n        if (mainActivity != null) {\n            InputMethodManager imm = (InputMethodManager) mainActivity.getSystemService(\n                    Context.INPUT_METHOD_SERVICE);\n            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n        }\n    }\n \n    protected class ImeCloseListener extends AnimatorListenerAdapter {\n        private final View view;\n \n        public ImeCloseListener(View view) {\n            this.view = view;\n        }\n \n        @Override\n        public void onAnimationEnd(@NonNull Animator animation) {\n            super.onAnimationEnd(animation);\n            closeIme(view);\n        }\n \n    }\n}\n```\n\n在该类型中处理了onFocusChanged()事件，并且根据焦点调用相应的函数进入或者退出输入模式。在该类中包含了一个用于确认在退出输入模式时隐藏输入法的AnimatorListener类。另外还含有一个我们重复使用的、构建了一些原子属性的animators的AnimatorBuilder实例，我们看看AnimatorBuilder类的实现。\n\nAnimatorBuilder.java\n\n```java\npublic class AnimatorBuilder {\n    private static final String TRANSLATION_Y = \"translationY\";\n    private static final String ALPHA = \"alpha\";\n \n    private final int duration;\n \n    public static AnimatorBuilder newInstance(Context context) {\n        int duration = context.getResources().getInteger(android.R.integer.config_mediumAnimTime);\n        return new AnimatorBuilder(duration);\n    }\n \n    AnimatorBuilder(int duration) {\n        this.duration = duration;\n    }\n \n    public Animator buildTranslationYAnimator(View view, int startY, int endY) {\n        Animator animator = ObjectAnimator.ofFloat(view, TRANSLATION_Y, startY, endY);\n        animator.setDuration(duration);\n        return animator;\n    }\n \n    public Animator buildShowAnimator(View view) {\n        return buildAlphaAnimator(view, 0f, 1f);\n    }\n \n    public Animator buildHideAnimator(View view) {\n        return buildAlphaAnimator(view, 1f, 0f);\n    }\n \n    public Animator buildAlphaAnimator(View view, float startAlpha, float endAlpha) {\n        Animator animator = ObjectAnimator.ofFloat(view, ALPHA, startAlpha, endAlpha);\n        animator.setDuration(duration);\n        return animator;\n    }\n}\n```\n\n这里有两个基本的animator在这里被构建 : 一个是通过修改translationY属性移动View的动画，另一个是修改View的透明度实现修改alpha属性的动画。还有一个是组合了Alpha动画和提供了一些工具方法来将视图从完全不透明变化到透明，以及相反的过程。\n\n所有这些我们只需要看看Part1TransitionController类中如何将这些功能结合在一起运用。\n\npart1/Part1TransitionController.java\n\n```java\npublic class Part1TransitionController extends TransitionController {\n \n    public static TransitionController newInstance(Activity activity) {\n        WeakReference<Activity> mainActivityWeakReference = new WeakReference<>(activity);\n        AnimatorBuilder animatorBuilder = AnimatorBuilder.newInstance(activity);\n        return new Part1TransitionController(mainActivityWeakReference, animatorBuilder);\n    }\n \n    Part1TransitionController(WeakReference<Activity> mainActivityWeakReference, AnimatorBuilder animatorBuilder) {\n        super(mainActivityWeakReference, animatorBuilder);\n    }\n \n    @Override\n    protected void enterInputMode(Activity activity) {\n        View inputView = activity.findViewById(R.id.input_view);\n        View inputDone = activity.findViewById(R.id.input_done);\n        View translation = activity.findViewById(R.id.translation_panel);\n        View toolbar = activity.findViewById(R.id.toolbar);\n \n        inputDone.setVisibility(View.VISIBLE);\n \n        AnimatorSet animatorSet = new AnimatorSet();\n        AnimatorBuilder animatorBuilder = getAnimatorBuilder();\n        Animator moveInputView = animatorBuilder.buildTranslationYAnimator(inputView, 0, -toolbar.getHeight());\n        Animator showInputDone = animatorBuilder.buildShowAnimator(inputDone);\n        Animator hideTranslation = animatorBuilder.buildHideAnimator(translation);\n        animatorSet.playTogether(moveInputView, showInputDone, hideTranslation);\n        animatorSet.start();\n    }\n \n    @Override\n    protected void exitInputMode(Activity activity) {\n        final View inputView = activity.findViewById(R.id.input_view);\n        View inputDone = activity.findViewById(R.id.input_done);\n        View translation = activity.findViewById(R.id.translation_panel);\n        View toolbar = activity.findViewById(R.id.toolbar);\n \n        AnimatorSet animatorSet = new AnimatorSet();\n        AnimatorBuilder animatorBuilder = getAnimatorBuilder();\n        Animator moveInputView = animatorBuilder.buildTranslationYAnimator(inputView, -toolbar.getHeight(), 0);\n        Animator hideInputDone = animatorBuilder.buildHideAnimator(inputDone);\n        Animator showTranslation = animatorBuilder.buildShowAnimator(translation);\n        animatorSet.playTogether(moveInputView, hideInputDone, showTranslation);\n        animatorSet.addListener(new ImeCloseListener(inputDone));\n        animatorSet.start();\n    }\n}\n```\n\n在Part1TransitionController类中我们实现了两个抽象方法，分别为exitInputMode和enterInputMode方法。在这两个函数中我们会找到对应的View，在enterInputMode函数中我们会构建一个包含了移动View到toolbar位置、修改inputDone到不透明状态、translation到透明状态的动画集。在exitInputMode函数中，我们执行相反的动画，同时添加了一个ImeCloseListener实例来保证在动画完成时隐藏输入法。\n\n至此，我们就完成了所需的功能。通过一些基本的属性动画组合我们就完成了复杂的动画功能。\n\n然而，我们并不止步于此。这个示例非常的直截了当，但是TransitionController实例实现了运用于View上的动画逻辑。因此，相比transitions API提供的功能来说我们还有很长的路要走。在下一篇文章中我们会做一些小修改来实现根据View的状态来动态的构建Animators，而不是像这篇文章中的手动创建。\n\n完整的代码在[这里](https://github.com/StylingAndroid/ManualLayoutTransitions/tree/Part1) 。\n\n<p class=\"cc-block\"><a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"><img alt=\"CC BY-NC-SA 4.0\" class=\"cc-button\" src=\"https://blog.stylingandroid.com/wp-content/plugins/creative-commons-configurator-1/media/cc/by-nc-sa/4.0/88x31.png\" scale=\"0\"></a>\n<br>\n<a href=\"http://blog.stylingandroid.com/manual-layout-transitions-part-1/\" title=\"Permalink to Manual Layout Transitions – Part 1\"><span xmlns:dct=\"http://purl.org/dc/terms/\" href=\"http://purl.org/dc/dcmitype/Text\" property=\"dct:title\" rel=\"dct:type\">Manual Layout Transitions – Part 1</span></a> by <a xmlns:cc=\"http://creativecommons.org/ns#\" href=\"http://blog.stylingandroid.com/author/admin/\" property=\"cc:attributionName\" rel=\"cc:attributionURL\">Styling Android</a> is licensed under a <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>.\nPermissions beyond the scope of this license may be available at <a xmlns:cc=\"http://creativecommons.org/ns#\" href=\"http://blog.stylingandroid.com/license-information\" rel=\"cc:morePermissions\">http://blog.stylingandroid.com/license-information</a>.</p>"
  },
  {
    "path": "issue-16/手动实现布局Transitions动画-第三部分.md",
    "content": "# 手动实现布局Transitions动画-第三部分\n\n> * 原文链接 : [Manual Layout Transitions – Part 3](https://blog.stylingandroid.com/manual-layout-transitions-part-3/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Mr.Simple](https://github.com/bboyfeiyu)\n\n\n布局切换动画在Material design中是一个重要的方面,因为它们能够指明应用的工作流程，并且能够将UI上的可视化元素绑定在一起作为用户的导航。两个重要的工具可以实现这种效果，分别为Activity转场动画和布局动画（Layout Transitions）。然后布局动画需要在API 19及其之后才支持。\n\n上一篇文章中我们创建了两个布局代表两个视图状态，我们通过setContentView来切换它们。这篇文章我们在它们切换时添加动画效果。\n\n我们已经找掌握了关于动画的相关基础知识，现在我们就要在两个布局状态切换时加入动画。\n\n我们定义的两个布局都有相同的View以及id,两个状态的切换只是会修改这些视图的可见性以及位置。因此我们仅仅需要检测这些自然变化，然后应用合适的位置变换或者alpha动画到每个视图上。值得注意的是，由于我们加载了一个新的布局，但是两个布局中的视图类型和id都是一样的，它们代表的是两个不同的视图对象。此时，我们需要切换到一个新的布局视图中，旧的布局视图就不会出现在我们的视野中，所以我们不能确定旧布局视图的控件状态。因此我们需要一种机制来存储旧布局中特定View的状态属性。\n\npart3/ViewState.java\n\n```java\npublic final class ViewState {\n    private final int top;\n    private final int visibility;\n \n    public static ViewState ofView(View view) {\n        int top = view.getTop();\n        int visibility = view.getVisibility();\n        return new ViewState(top, visibility);\n    }\n \n    private ViewState(int top, int visibility) {\n        this.top = top;\n        this.visibility = visibility;\n    }\n \n    public boolean hasMovedVertically(View view) {\n        return view.getTop() != top;\n    }\n \n    public boolean hasAppeared(View view) {\n        int newVisibility = view.getVisibility();\n        return visibility != newVisibility && newVisibility == View.VISIBLE;\n    }\n \n    public boolean hasDisappeared(View view) {\n        int newVisibility = view.getVisibility();\n        return visibility != newVisibility && newVisibility != View.VISIBLE;\n    }\n \n    public int getY() {\n        return top;\n    }\n}\n```\n\n这段代码非常简单，因为我们只关心各视图的竖直偏移量和可见性。此外也就是写辅助方法以便于我们能够确定视图对象是否发生了改变。\n\n此时我们已经有了一套机制来存储不在可见范围的视图的状态,下面我们来看看我们如何运用的。当我们调用setContentView时，通过TransitionController我们已经有了切换布局的机制。下一步我们需要做的就是在我们切换布局之前捕获这些视图的状态，并且替换掉。我们会通过TransitionAnimator类来实现这些功能，它会计算并且执行动画。Part3TransitionController类的代码如下 : \n\n\npart3/Part3TransitionController\n\n```java\npublic class Part3TransitionController extends TransitionController {\n \n    Part3TransitionController(WeakReference<Activity> activityWeakReference, AnimatorBuilder animatorBuilder) {\n        super(activityWeakReference, animatorBuilder);\n    }\n \n    public static TransitionController newInstance(Activity activity) {\n        WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);\n        AnimatorBuilder animatorBuilder = AnimatorBuilder.newInstance(activity);\n        return new Part3TransitionController(activityWeakReference, animatorBuilder);\n    }\n \n    @Override\n    protected void enterInputMode(Activity activity) {\n        createTransitionAnimator(activity);\n        activity.setContentView(R.layout.activity_part2_input);\n    }\n \n    @Override\n    protected void exitInputMode(Activity activity) {\n        createTransitionAnimator(activity);\n        activity.setContentView(R.layout.activity_part2);\n    }\n \n    private void createTransitionAnimator(Activity activity) {\n        ViewGroup parent = (ViewGroup) activity.findViewById(android.R.id.content);\n        View inputView = parent.findViewById(R.id.input_view);\n        View inputDone = parent.findViewById(R.id.input_done);\n        View translation = parent.findViewById(R.id.translation);\n \n        TransitionAnimator.begin(parent, inputView, inputDone, translation);\n    }\n}\n```\n\n这里添加了一个createTransitionAnimator函数来查找视图，并且调用了TransitionAnimator的begin函数。这个函数在enterInputMode和exitInputMode 函数中调用Activity的setContentView之前被调用。你需要注意的是TransitionAnimator只是在两个视图状态之间进行切换，因此除了那些我们感兴趣的视图之外我们不需要对这两个布局有额外的了解。\n\n我们看看TransitionAnimator类 : \n\nSo let’s take a look at TransitionAnimator:\n\n```java\npublic final class TransitionAnimator implements ViewTreeObserver.OnPreDrawListener {\n    private final ViewGroup parent;\n    private final SparseArray<ViewState> startStates;\n    private final AnimatorBuilder animatorBuilder;\n \n    public static void begin(ViewGroup parent, View... views) {\n        SparseArray<ViewState> startStates = buildViewStates(views);\n        AnimatorBuilder animatorBuilder = AnimatorBuilder.newInstance(parent.getContext());\n        final TransitionAnimator transitionAnimator = new TransitionAnimator(animatorBuilder, parent, startStates);\n        ViewTreeObserver viewTreeObserver = parent.getViewTreeObserver();\n        viewTreeObserver.addOnPreDrawListener(transitionAnimator);\n    }\n \n    private TransitionAnimator(AnimatorBuilder animatorBuilder, ViewGroup parent, SparseArray<ViewState> startStates) {\n        this.animatorBuilder = animatorBuilder;\n        this.parent = parent;\n        this.startStates = startStates;\n    }\n \n    private static SparseArray<ViewState> buildViewStates(View... views) {\n        SparseArray<ViewState> viewStates = new SparseArray<>();\n        for (View view : views) {\n            viewStates.put(view.getId(), ViewState.ofView(view));\n        }\n        return viewStates;\n    }\n    .\n    .\n    .\n}\n```\n\n在TransitionController类中调用TransitionAnimator的begin函数来做一些准备。\n\n在begin函数中首先会调用buildViewStates函数来遍历所有传递进来的视图，并且将这些视图的状态以视图id为key存储到SparseArray对象中。然后通过AnimatorBuilder对象和parent视图和存储了视图状态的SparseArray对象来创建一个TransitionAnimator实例。\n\n现在的代码看起来聪明一点了。在旧布局还没有从我们的视野中消失时我们捕获了它的视图状态，但是需要在新的布局创建之前我们现在需要做些其他的事情。我们不能简单的加载一个布局并且运用它，因为布局中的子视图可能还在错误的位置，直到我们经过了测量和布局两个过程之后它们才会在正确的位置。但是现在我们做的只是在parent容器中注册了一个OnPreDrawListener.这使得我们在parent下次绘制之前能够触发一个OnPreDrawListener回调。当TransitionController类中调用setContentView函数时，这个回调会在新布局被加载、测量和布局过程完成之后被调用一次，但是这个调用会执行在视图绘制之前。\n\nTransitionAnimator类实现了ViewTreeObserver.OnPreDrawListener，并且被注册为OnPreDrawLister。它的onPreDraw函数会在新布局会绘制前调用。onPreDraw函数如下 : \n\npart3/TransitionAnimator.java\n\n```java\n@Override\npublic boolean onPreDraw() {\n    ViewTreeObserver viewTreeObserver = parent.getViewTreeObserver();\n    viewTreeObserver.removeOnPreDrawListener(this);\n    SparseArray<View> views = new SparseArray<>();\n    for (int i = 0; i < startStates.size(); i++) {\n        int resId = startStates.keyAt(i);\n        View view = parent.findViewById(resId);\n        views.put(view.getId(), view);\n    }\n    Animator animator = buildAnimator(views);\n    animator.start();\n    return false;\n}\n```\n\n在onPreDraw函数中首先将TransitionAnimator自身从ViewTreeObserver中注销，因为我们不需要在每次绘制之前都回调onPreDraw函数。如果我们忘记了注销那么它会变得相对重量级，以至于会影响动画的流畅度。我们只需要在替换布局时回调一次onPreDraw函数，然后我们会在次函数中开始执行切换动画。\n\n下一步我们要做的是迭代前面构建的SparseArray中的ViewStates，从ViewStates中取出视图id，然后根据这个id到parent中找到对应的视图，最后将视图存储到另一个SparseArray对象中。最后将这个SparseArray对象传递给buildAnimator函数。\n\npart3/TransitionAnimator.java\n\n```java\nprivate Animator buildAnimator(SparseArray<View> views) {\n    AnimatorSet animatorSet = new AnimatorSet();\n    List<Animator> animators = new ArrayList<>();\n    for (int i = 0; i < views.size(); i++) {\n        int resId = views.keyAt(i);\n        ViewState startState = startStates.get(resId);\n        View view = views.get(resId);\n        animators.add(buildViewAnimator(view, startState));\n    }\n    animatorSet.playTogether(animators);\n    return animatorSet;\n}\n```\n\n这会构建一个包含了所有独立视图Animator的集合，这些Animator会并行的执行。在构建合适的Animator时会又调用buildViewAnimator函数。\n\npart3/TransitionAnimator.java\n\n```java\nprivate Animator buildViewAnimator(final View view, ViewState startState) {\n    Animator animator = null;\n    if (startState.hasAppeared(view)) {\n        animator = animatorBuilder.buildShowAnimator(view);\n    } else if (startState.hasDisappeared(view)) {\n        final int visibility = view.getVisibility();\n        view.setVisibility(View.VISIBLE);\n        animator = animatorBuilder.buildHideAnimator(view);\n        animator.addListener(\n                new AnimatorListenerAdapter() {\n                    @Override\n                    public void onAnimationEnd(@NonNull Animator animation) {\n                        super.onAnimationEnd(animation);\n                        view.setVisibility(visibility);\n                    }\n                });\n    } else if (startState.hasMovedVertically(view)) {\n        int startY = startState.getY();\n        int endY = view.getTop();\n        animator = animatorBuilder.buildTranslationYAnimator(view, startY - endY, 0);\n    }\n \n    return animator;\n}\n```\n\n在该函数中会调用ViewState中的辅助方法确定每个视图的转换的类型。这些转换类型有三种,分别为一个invisible的视图变为visible、一个visible的视图变为invisible、在y轴上移动视图。每个转换动画我们都会构建一个对应的Animator对象。\n\n如果此时我们运行这个示例，我们会看到很好的效果。[视频地址](https://youtu.be/CuigoE_Hjb4)。\n\n这些代码能够很好的工作，但是有一个明显的问题它需要起始布局中的所有的视图在结束布局都有对应的视图，也就是两个布局中都含有类型和id的子view。但是这不是并不是所有的情况下都会这样。在下一篇文章中我们看看如何适配这个特定的场景。\n\n源代码在[这里](https://github.com/StylingAndroid/ManualLayoutTransitions/tree/Part3)。"
  },
  {
    "path": "issue-16/手动实现布局Transitions动画-第二部分.md",
    "content": "# 手动实现布局Transitions动画-第二部分\n----\n\n> * 原文链接 : [Manual Layout Transitions – Part 2](https://blog.stylingandroid.com/manual-layout-transitions-part-2/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Mr.Simple](https://github.com/bboyfeiyu)\n\n布局切换动画在Material design中是一个重要的方面,因为它们能够指明应用的工作流程，并且能够将UI上的可视化元素绑定在一起作为用户的导航。两个重要的工具可以实现这种效果，分别为Activity转场动画和布局动画（Layout Transitions）。然后布局动画需要在API 19及其之后才支持。在这一系列文章中，我们会学习到即使在无法调用transitions APIs时如何实现很好的转场动画。\n\n布局切换框架引入了代表特定布局状态的Scenes概念，也就是场景。我们会定义两个分离的布局来模仿这些，其中一个代表默认的视图，另一个代表我们进入输入模式的视图。我们先来创建两个布局，它们都是基于Dirty Phrasebook布局，当然我们会做一些小修改以便大家能够更容易理解。\n\n首先是默认布局。\n\nres/layout/activity_part2.xml\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:orientation=\"vertical\"\n  android:clipChildren=\"false\">\n \n  <RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\">\n \n    <View\n      android:id=\"@+id/toolbar\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"?attr/actionBarSize\"\n      android:background=\"?attr/colorPrimary\" />\n \n    <View\n      android:id=\"@+id/focus_holder\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:focusableInTouchMode=\"true\">\n \n      <requestFocus />\n    </View>\n \n    <android.support.v7.widget.CardView\n      android:id=\"@+id/input_view\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_alignParentBottom=\"true\"\n      android:layout_below=\"@id/toolbar\">\n \n      <EditText\n        android:id=\"@+id/input\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:inputType=\"textMultiLine\" />\n \n      <ImageView\n        android:id=\"@+id/input_done\"\n        android:layout_width=\"32dip\"\n        android:layout_height=\"32dip\"\n        android:layout_alignBottom=\"@id/input\"\n        android:layout_alignEnd=\"@id/input\"\n        android:layout_alignRight=\"@id/input\"\n        android:layout_gravity=\"bottom|end\"\n        android:layout_margin=\"8dp\"\n        android:background=\"@drawable/done_background\"\n        android:contentDescription=\"@string/done\"\n        android:padding=\"2dp\"\n        android:src=\"@drawable/ic_arrow_forward\"\n        android:visibility=\"invisible\" />\n \n    </android.support.v7.widget.CardView>\n \n  </RelativeLayout>\n \n  <FrameLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\">\n \n    <android.support.v7.widget.CardView\n      android:id=\"@+id/translation\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"8dp\">\n \n      <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"?attr/colorPrimary\" />\n    </android.support.v7.widget.CardView>\n  </FrameLayout>\n \n</LinearLayout>\n```\n\n这个布局与我们上一篇使用的布局基本一致，但是稍微有一点小修改。现在我们感兴趣只有id为toolbar, focus_holder, input, input_view, input_done和 translation的视图控件。\n\n它看起来是这样的:\n\n![](https://blog.stylingandroid.com/wp-content/uploads/2015/05/Screenshot_2015-05-17-22-17-21-169x300.png)\n\n\nThe layout for when we’re in input mode is:\n\n下面是进入输入模式的布局 : \n\nres/layout/activity_part2_input.xml\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:orientation=\"vertical\"\n  android:clipChildren=\"false\">\n \n  <RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\">\n \n    <View\n      android:id=\"@+id/toolbar\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"?attr/actionBarSize\"\n      android:background=\"?attr/colorPrimary\" />\n \n    <View\n      android:id=\"@+id/focus_holder\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      android:focusableInTouchMode=\"true\" />\n \n    <android.support.v7.widget.CardView\n      android:id=\"@+id/input_view\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_alignParentBottom=\"true\"\n      android:layout_alignParentTop=\"true\"\n      android:layout_marginBottom=\"?attr/actionBarSize\">\n \n      <EditText\n        android:id=\"@+id/input\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:inputType=\"textMultiLine\">\n \n        <requestFocus />\n      </EditText>\n \n      <ImageView\n        android:id=\"@+id/input_done\"\n        android:layout_width=\"32dip\"\n        android:layout_height=\"32dip\"\n        android:layout_alignBottom=\"@id/input\"\n        android:layout_alignEnd=\"@id/input\"\n        android:layout_alignRight=\"@id/input\"\n        android:layout_gravity=\"bottom|end\"\n        android:layout_margin=\"8dp\"\n        android:background=\"@drawable/done_background\"\n        android:contentDescription=\"@string/done\"\n        android:padding=\"2dp\"\n        android:src=\"@drawable/ic_arrow_forward\"\n        android:visibility=\"visible\" />\n    </android.support.v7.widget.CardView>\n \n  </RelativeLayout>\n \n  <FrameLayout\n        android:layout_width=\"match_parent\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_weight=\"1\">\n \n    <android.support.v7.widget.CardView\n      android:id=\"@+id/translation\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_margin=\"8dp\"\n      android:visibility=\"invisible\">\n \n      <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"?attr/colorPrimary\" />\n    </android.support.v7.widget.CardView>\n  </FrameLayout>\n \n</LinearLayout>\n```\n\n\n该布局和上一布局也是基本相同，它们的区别在于 : \n\n1. id为input_view的视图现在依附在它的父视图的顶部，而不是在toolbar的下面，也就是说它现在覆盖住了toolbar;\n2. input_done视图现在已经变为可见状态，上一个布局中它是invisible状态;\n3. translation视图变为invisible，上一布局中它是visible状态。\n\n\n效果如下 : \n\n![](https://blog.stylingandroid.com/wp-content/uploads/2015/05/Screenshot_2015-05-17-22-17-26-169x300.png)\n\n\n两个布局代表UI的两个状态，如果我们使用Transitions API那么它们就是我们所谓的场景。\n\n我们现在要做的就是在这两个布局之间进行状态切换，也就是进入、退出输入模式。首先我们需要检测MainActivity中的焦点 : \n\n\npart2/mainActivity.java\n\n```java\npublic class MainActivity extends AppCompatActivity {\n \n    private View input;\n    private TransitionController focusChangeListener;\n    private View.OnClickListener onClickListener;\n    private View focusHolder;\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        focusChangeListener = Part2TransitionController.newInstance(this);\n        onClickListener = new View.OnClickListener() {\n            @Override\n            public void onClick(@NonNull View v) {\n                focusHolder.requestFocus();\n            }\n        };\n \n        setContentView(R.layout.activity_part2);\n    }\n \n    @Override\n    public void setContentView(int layoutResID) {\n        if (input != null) {\n            input.setOnFocusChangeListener(null);\n        }\n        super.setContentView(layoutResID);\n        input = findViewById(R.id.input);\n        View inputDone = findViewById(R.id.input_done);\n        focusHolder = findViewById(R.id.focus_holder);\n        input.setOnFocusChangeListener(focusChangeListener);\n        inputDone.setOnClickListener(onClickListener);\n    }\n}\n```\n\n因为我们检测到焦点变化，因此我们还需要添加一些逻辑到setContentView函数中。这样一来我们在调用setContentView时就可以切换这两个布局，此时View的层级关系也会随着改变。因此我们每次都需要找到布局中的子视图，焦点listener我们也需要移除并且重新设置一个到input视图中。\n\n和原来一样，我们需要一个TransitionController来处理焦点变化: \n\n.part2.Part2TransitionController.java\n\n```java\npublic class Part2TransitionController extends TransitionController {\n \n    Part2TransitionController(WeakReference<Activity> activityWeakReference, AnimatorBuilder animatorBuilder) {\n        super(activityWeakReference, animatorBuilder);\n    }\n \n    public static TransitionController newInstance(Activity activity) {\n        WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);\n        AnimatorBuilder animatorBuilder = AnimatorBuilder.newInstance(activity);\n        return new Part2TransitionController(activityWeakReference, animatorBuilder);\n    }\n \n    @Override\n    protected void enterInputMode(Activity activity) {\n        activity.setContentView(R.layout.activity_part2_input);\n    }\n \n    @Override\n    protected void exitInputMode(Activity activity) {\n        activity.setContentView(R.layout.activity_part2);\n    }\n}\n```\n\n现在我们需要做的只是调用Activity的setContentView函数来切换两个布局。\n\n如果我们现在运行上面的代码，我们可以看到两个布局毫无过度的切换，这必然不是我们想要的。在下一篇文章中，我们将讲解如何在布局切换时添加动画。\n\n本文的源代码在[这里](https://github.com/StylingAndroid/ManualLayoutTransitions/tree/Part2)。\n\n\n<p class=\"cc-block\"><a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"><img alt=\"CC BY-NC-SA 4.0\" class=\"cc-button\" src=\"https://blog.stylingandroid.com/wp-content/plugins/creative-commons-configurator-1/media/cc/by-nc-sa/4.0/88x31.png\" scale=\"0\"></a>\n<br>\n<a href=\"http://blog.stylingandroid.com/manual-layout-transitions-part-1/\" title=\"Permalink to Manual Layout Transitions – Part 1\"><span xmlns:dct=\"http://purl.org/dc/terms/\" href=\"http://purl.org/dc/dcmitype/Text\" property=\"dct:title\" rel=\"dct:type\">Manual Layout Transitions – Part 1</span></a> by <a xmlns:cc=\"http://creativecommons.org/ns#\" href=\"http://blog.stylingandroid.com/author/admin/\" property=\"cc:attributionName\" rel=\"cc:attributionURL\">Styling Android</a> is licensed under a <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>.\nPermissions beyond the scope of this license may be available at <a xmlns:cc=\"http://creativecommons.org/ns#\" href=\"http://blog.stylingandroid.com/license-information\" rel=\"cc:morePermissions\">http://blog.stylingandroid.com/license-information</a>.</p>"
  },
  {
    "path": "issue-16/结合RxJava更简单地使用SQLite.md",
    "content": "结合RxJava更简单地使用SQLite\n---\n\n> * 原文链接 : [Easy SQLite on Android with RxJava][source]\n* 原文作者 : [Cédric Beust](http://beust.com/weblog/about-2/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime) \n* 校对者:  [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 :   完成\n\n\n我经常想在项目中使用 ORM 来简化操作，但是最终都将这个念头打消。主要有以下几点原因：\n\n- 我的数据模型远远没有复杂到需要 ORM 帮助\n- 出于 Android 性能上的考虑，自动生成的SQL语句可能没有被优化\n\n不过最近，我开始使用一个简单的设计模式，使用 RxJava 来提供一个简单的数据库访问管理。\n我称它为 “Async Rx Read” 设计模式，很渣的一个名字哈，不过是我能想到最好的了，命名总是程序员的难题嘛。\n\n\n#Easy Read\nAndroid 开发中有一条很重要的设计原则，永远不要在主线程执行 I/O 操作，显然这条规则对数据库访问也适用。\nRxJava 很适用于解决这个问题。\n在以前，我通常为每个 table 创建一个 Java 类，然后通过我的 [SQLiteOpenHelper][SQLiteOpenHelper] 来管理这些 table。\n现在使用这个新的方法后，我将这个工具类扩展成所有事物读写 SQL table 的唯一入口。\n我们来想个简单的例子：一个由 UserTable 类管理的 USERS 表：\n\n```java\n// UserTable.java\nList<User> getUsers(SQLiteDatabase db, String userId) {\n  // select * from users where _id = {userId}\n}\n```\n\n上面的方法有一个缺点，就是你一不小心可能会在主线程调用它，这里面是由调用者确保它是在后台线程中被执行的\n(如果需要更新 UI 的话，再将结果返回给主线程)。而不是依赖一个线程池管理，或者更糟点，使用 `AsyncTask` 管理，\n我们将使用 RxJava 替我们管理线程模型。\n\n让我们重写这个方法返回一个 callback ：\n\n```java\n// UserTable.java\nCallable<List<User>> getUsers(SQLiteDatabase db, String userId) {\n  return new Callable<List<User>>() {\n    @Override\n    public List<User> call() {\n      // select * from users where _id is userId\n    }\n  }\n}\n```\n\n\n实际上，我们只是重构了一下这个方法返回一个 lazy result ，使得 database helper 可以将这个\nresult 转变成一个 `Observable`：\n\n```java\n// MySqliteOpenHelper.java\nObservable<List<User>> getUsers(String userId) {\n  return makeObservable(mUserTable.getUsers(getReadableDatabase(), userId))\n    .subscribeOn(Schedulers.computation()) // note: do not use Schedulers.io()\n}\n```\n\n注意到将 lazy result 转换成一个 `Observable` 时，helper 强制 subscription 运行在后台线程\n(这里使用的是 computation scheduler;不要使用 `Schedulers.io()` 因为它是由 unbounded executor 支持的)。\n这样调用者就不必担心这个方法会阻塞主线程了。\n\n最后，`makeObservable()` 方法的实现很简单(而且是完全通用的)：\n\n```java\n// MySqliteOpenHelper.java\nprivate static <T> Observable<T> makeObservable(final Callable<T> func) {\n  return Observable.create(\n      new Observable.OnSubscribe<T>() {\n          @Override\n          public void call(Subscriber<? super T> subscriber) {\n            try {\n              subscriber.onNext(func.call());\n            } catch(Exception ex) {\n              Log.e(TAG, \"Error reading from the database\", ex);\n            }\n          }\n    });\n}\n```\n\n现在，我们的 database 读取已经变成了 `observables`，保证查询是执行在后台线程的。\n访问数据库操作也是很标准的 Rx code：\n\n```java\n// DisplayUsersFragment.java\n@Inject\nMySqliteOpenHelper mDbHelper;\n\n// ...\n\nmDbHelper.getUsers(userId)\n  .observeOn(AndroidSchedulers.mainThread())\n  .subscribe(new Action1<List<User>>()) {\n    @Override\n    public void onNext(List<User> users) {\n      // Update our UI with the users\n    }\n  }\n}\n```\n\n如果你不需要返回结果更新你的 UI，那么你只需要 observe 一个后台线程。\n然后你的 database 层会返回 observables，当获得结果时很容易将它组合变换。\n例如，假定 `ContactTable` 是一个底层类， model (User class) 对它是不可见的，\n它应该返回底层 object (可能是 `Cursor` 或者 `ContentValues`)。然后你可以用\nRx 去 `map` 这些底层值，把它们转换成你的 model 类，实现更加清晰的分离层。\n\n两个提示：\n\n- 你的 Table 类不应该包含 public 方法：只能有 package 的 protected 方法(仅允许相同 package 内的 Helper 访问 ) 和 private 方法，\n其他类不能直接访问 Table 类。\n\n- 这个方法对依赖注入兼容性很好：很轻松就可以同时实现 database helper 和注入单个 Table \n(意外收获：使用 Dagger 2，你的 table 可以拥有自己的组件，因为 database helper\n是实例化它们所需的唯一参考资料 )。\n\n\n这是一个很简单的设计模式，它显著提升了 RxJava 在项目中发挥的效果。\n我正在扩展这层结构，让它可以为 list view adapter update 提供更灵活的通知机制\n(和 SQLBrite 提供的不同)，会在以后的文章中介绍。\n\n这项工作还在进行中，欢迎反馈。\n\n\n---\n\n[source]:http://beust.com/weblog/2015/06/01/easy-sqlite-on-android-with-rxjava/\n[SQLiteOpenHelper]:http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html"
  },
  {
    "path": "issue-17/Android UI 自动化测试.md",
    "content": "#Android UI 自动化测试\n\n* 原文链接 : [Automating User Interface Testing on Android](http://code.tutsplus.com/tutorials/automating-user-interface-testing-on-android--cms-23969)\n* 原文作者 : [Ashraff Hathibelagal](http://tutsplus.com/authors/ashraff-hathibelagal)\n* 译文出自 : [tuts+](http://code.tutsplus.com/)\n* 译者 : [Doris](https://github.com/DorisMinmin)\n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n##介绍\nAndroid测试支持库包含**UI自动化模块**，它可以对Android应用进行自动黑盒测试。在API Level 18中引入了自动化模块，它允许开发者在组成应用UI的控件上模仿用户行为。\n\n在这个教程中，我将展示如何使用此模块来创建和执行一个基本的UI测试，选择默认的计算器模块进行测试。\n\n##先决条件\n在使用前，需要具备以下条件：\n  1.最新版本的[Android Studio](https://developer.android.com/sdk/index.html)\n  2.运行Android 4.3或者更高版本的设备或者虚拟器\n  3.理解[JUnit](http://junit.org/)\n\n##1. 安装依赖库\n\n工程中使用UI自动化模块，需要编辑你的工程下*app*目录下的文件*build.gradle*，添加如下信任：\n```xml\n1  androidTestCompile 'com.android.support.test:runner:0.2'                       \n2  androidTestCompile 'com.android.support.test:rules:0.2'                        \n3  androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.0'\n```\n现在屏幕上应该有*Sync Now*按钮了，但点击它时，会看到如下错误信息：\n\n![p1](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest1.png)\n\n点击 **Install Repository and sync project** 链接来安装 **Android Support Repository**。\n\n如果使用的是库**appcompat-v7** 且其版本号是**22.1.1**，你需要添加如下依赖以确保应用本身和测试应用都使用相同版本的```com.android.support:support-annotations```:\n\n```xml\n1   androidTestCompile 'com.android.support:support-annotations:22.1.1'\n```\n接下来，由于Android Studio自身的一个bug，你需要通过 ```packagingOptions``` 执行一个名为 **LICENSE.txt** 的文件。这个执行失败的话，在运行测试时将引起如下错误：\n\n```\n1   Execution failed for task ':app:packageDebugAndroidTest'.                                                                                   \n2   Duplicate files copied in APK LICENSE.txt                                                                                                   \n3                                                                                                                                               \n4   File 1: ~/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.1/860340562250678d1a344907ac75754e259cdb14/hamcrest-core-1.1.jar  \n5   File 2: ~/.gradle/caches/modules-2/files-2.1/junit/junit-dep/4.10/64417b3bafdecd366afa514bd5beeae6c1f85ece/junit-dep-4.10.jar               \n```\n在你的**build.gradle**文件底部增加如下代码段：\n```Java\n1   android {                                       \n2       packagingOptions {                          \n3           exclude 'LICENSE.txt'                   \n4       }                                           \n5   }                                               \n```\n##2、创建测试类\n\n创建一个新的测试类，```CalculatorTester```，通过在 **androidTest** 目录下创建名为 **CalculatorTester.java** 的文件实现。创建的UI自动化测试用例，必须继承自```InstrumentationTestCase```。\n\n![P2](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest2.png)\n\n按 **Alt+Insert**后选择 **SetUp Method** 来重写```setUp```方法。\n\n![P3](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest3.png)\n\n再次按 **Alt+Insert** 后选择 **Test Method** 来生成新的测试方法，命名为```testAdd```。到此```CalculatorTester```类定义如下：\n```Java\n01  public class CalculatorTester extends InstrumentationTestCase{\n02                                                                \n03      @Override                                                 \n04      public void setUp() throws Exception {                    \n05                                                                \n06      }                                                         \n07                                                                \n08      public void testAdd() throws Exception {                  \n09                                                                \n10      }                                                         \n11  }                                                                    \n```\n##3、查看Launcher UI\n\n连接你的Android设备到电脑商，点击home按键，进入主界面。\n\n返回到你的电脑，使用文件管理或者终端浏览你安装Android SDK的目录，进入到 **tools** 目录下，点击 **uiautomatorviewer** 。这个会启动 **UI Automater Viewer** ，你将看到如下界面：\n\n![P4](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest4.png)\n\n点击上方手机图标来获取Android设备截屏。注意到此时获取到的截屏是可交互的。点击下面的Apps图标。在右方的 **Node Detail** 区域，你就可以看到根据选择图标的不同显示不同的详细信息，如下图所示：\n\n![P5](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest5.jpg)\n\n与屏幕上的应用交互，UI自动化测试需要能唯一识别它们。在这个教程中，可以使用应用的```text```、```content-desc```或者```class```字段来唯一的区分。\n\n从上图可以看到Apps图标没有```text```字段，但有```content-desc```。记下它的值，后面将用到这个值。\n\n拿起Android设备，触摸Apps图标，进入设备安装的所有应用界面。使用 **UI Automater Viewe** 获取另外一张屏幕截图。因为要写一个计算器应用的测试，点击计算器图标查看详细界面。\n\n![P6](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest6.jpg)\n\n这次```content-desc```是空的，但是```text```的值为**Calculator**，同样记住这个值。\n\n如果你的Android设备运行不同的主界面或者不同的Android版本，界面和显示的细节会有所不同。这意味着后续代码中需要做一些修改，以匹配你的操作系统。\n\n##4、准备测试环境\n\n返回到Android Studio，给```setUp```方法中添加代码。如同其名字，```setUp```方法是用来准备测试环境的。换句话说，这个方法是在真正测试之前指定具体需要执行什么动作的。\n\n现在需要写代码来模拟刚才在Android设备上执行的几个动作：\n1、按home键进入主界面\n2、按Apps图标进入应用界面\n3、点击计算器图标启动它\n\n在你的类中声明类型为```UiDevice```的变量```device```。它代表你的Android设备，后续使用它来模拟用户行为。\n```Java\n1  private UiDevice device;\n```\n在```setUp```方法中，通过调用```UiDevice.getInstance method```来初始化```device```，传递```Instrumentation```实例，如下所示：\n```Java\n1  device = UiDevice.getInstance(getInstrumentation());\n```\n模拟点击设备home键，需要调用```pressHome```方法。\n```Java\n1  device.pressHome();\n```\n接下来，需要模拟点击Apps图标的动作。不能立即做这个动作，因为Android设备需要一个反应时间来加载界面。如果在屏幕显示出来之前执行这个动作就会引起运行时异常。\n\n等待一些事情发生时，需要调用```UiDevice```实例的```wait```方法。等待Apps图标显示到屏幕，使用```Until.hasObject```方法。\n\n识别Apps图标需要使用```By.desc```方法并传递值为**Apps**的参数。你还需要指定最长等待时间，单位为毫秒。此处设置为3000。\n至此形成如下代码段：\n```Java\n1  // Wait for the Apps icon to show up on the screen\n2  device.wait(Until.hasObject(By.desc(\"Apps\")), 3000);\n```\n要获取Apps图标的引用，需要使用```findObject```方法。一旦有了Apps图标的引用，就可以调用```click```方法来模拟点击动作了。\n```Java\n1  UiObject2 appsButton = device.findObject(By.desc(\"Apps\"));\n2  appsButton.click();\n```\n和前面一样，我们需要等待一些时间，保证计算器图标显示到屏幕上。在之前的步骤中，我们看到可以通过```text```字段唯一的识别计算器图标。我们调用```By.text```方法来找到图标，传递参数为```Calculator```。\n```Java\n1  // Wait for the Calculator icon to show up on the screen\n2  device.wait(Until.hasObject(By.text(\"Calculator\")), 3000);\n```\n##5、检查计算器UI\n\n在你的Android设备上启动计算器应用，使用 **UI Automater Viewer** 来查看显示。获取到一个截屏后，点击不同的按钮来观察使用何值可以唯一的区分它们。\n\n在本次测试用例中，使用计算器计算 **9+9=** 的值并确认结果是否为 **18**。这意味着你需要知道怎么区分按键 **9**、**+** 和 **=**。\n\n![P7](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest7.jpg)\n\n在我的设备上，如下是我收集到的信息：\n1. 数字按键匹配```text```值\n2. **+** 和 **=** 使用```content-desc```值，分别对应 **plus** 和 **equals**\n3. 返回值显示在```EditText```控件中\n\n如果你使用不同版本的计算器应用，请注意这些值有可能不一样。\n\n##6、创建测试类\n\n在前面几步操作中，你已经学会了使用```findObject```方法通过```By.text```或者```By.desc```来获取屏幕上不同对象的引用。还学会了通过```click```方法来模拟点击对象的动作。下面的代码使用这些方法来模拟 **9+9=**。添加这些到类```CalculatorTester```的方法```testAdd```中。\n```Java\n01  // Wait till the Calculator's buttons are on the screen        \n02  device.wait(Until.hasObject(By.text(\"9\")), 3000);              \n03                                                                 \n04  // Select the button for 9                                     \n05  UiObject2 buttonNine = device.findObject(By.text(\"9\"));        \n06  buttonNine.click();                                            \n07                                                                 \n08  // Select the button for +                                     \n09  UiObject2 buttonPlus = device.findObject(By.desc(\"plus\"));     \n10  buttonPlus.click();                                            \n11                                                                 \n12  // Press 9 again as we are calculating 9+9                     \n13  buttonNine.click();                                            \n14                                                                 \n15  // Select the button for =                                     \n16  UiObject2 buttonEquals = device.findObject(By.desc(\"equals\")); \n17  buttonEquals.click();                                          \n```\n现在就等待运行结果。此处不能使用```Until.hasObject```，因为包含计算结果的```EditText```已经显示在屏幕上了。取而代之，我们使用```waitForIdle```方法来等待计算完成。同样，最长等待时间是3000毫秒。\n```Java\n1 device.waitForIdle(3000);\n```\n使用```findObject```和```By.clazz methods```方法获取```EditText```对象的引用。一旦有了此引用，就可以调用```getText``` 方法来确定计算结果是否正确。\n```Java\n1  UiObject2 resultText = device.findObject(By.clazz(\"android.widget.EditText\"));\n2  String result = resultText.getText();\n```\n最后，使用```assertTrue```来检验范围值是否为**18**。\n```Java\n1  assertTrue(result.equals(\"18\"));\n```\n测试到此结束。\n\n##6、执行测试\n\n执行测试，需要在Android Studio的工具栏中选择```CalculatorTester```，点击它右方的**play**按钮。\n\n![P9](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest9.png)\n\n一旦编译结束，测试就成功运行完整。当测试运行时，在你的Android设备上就会看到UI自动化运行界面。\n\n![P10](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest10.png)\n\n##总结\n\n在这篇教程中，我们学会了如何使用UI自动化测试模块和 **UI Automater Viewer** 来创建用户界面测试。你也看到了使用Android Studio执行测试是如此简单。虽然我们测试了一个相对简单的应用，但可以将从中学到的概念用到几乎所有Android应用的测试中。\n\n你可以在[Android 开发者网站中](http://developer.android.com/tools/testing-support-library/index.html) 学习更多关于测试支持库的知识。"
  },
  {
    "path": "issue-17/Android中的帧动画.md",
    "content": "Android中的帧动画（Frame Animation）\n---\n\n> * 原文链接 : [Frame Animations in Android](https://www.bignerdranch.com/blog/frame-animations-in-android/)\n* 译文出自 :  [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [jianghejie](https://github.com/jianghejie) \n* 校对 : [LangleyChang](https://github.com/LangleyChang) \n\n\n动画可以为你的app注入活力与个性。让我们来看看实现动画的一个子类：帧动画(Frame Animation)，从名字上可以看出，这种动画是一帧一帧的绘制出来的。\n\n在谷歌的Material Design官方专题中，花了整整一页来介绍 [Delightful Details](http://www.google.com/design/spec/animation/delightful-details.html#delightful-details-delightful-details)  ，其中有帧动画的绝佳例子。\n\n![](https://www.bignerdranch.com/img/blog/2015/05/frame_animation_example_1.gif)\n![](https://www.bignerdranch.com/img/blog/2015/05/frame_animation_example_2.gif)\n\n\n真是精美的动画！不幸的是，这个页面上没有丁点关于如何实现这种效果的参考链接，所以我就来帮忙了！我们将讲解一遍如何制作空心心形到实心心形的过渡动画，然后讲解与之反向的动画。效果如下：\n\n![](https://www.bignerdranch.com/img/blog/2015/05/heart_looping.gif)\n\n## 图片序列\n\n帧动画的原理很简单:就像老式电影胶卷那样，快速掠过一些列的图片，“帧”其实就是一张图片，因此创建一个自定义帧动画的第一步就是建立图片序列。\n\n我们有两种选择：使用xml的drawable（比如shape drawable）或者是使用实际的图片。简便起见，我们直接使用下面的一些列PNG图片：\n\n![](https://www.bignerdranch.com/img/blog/2015/05/ic_heart_0.png)\n![](https://www.bignerdranch.com/img/blog/2015/05/ic_heart_25.png)\n![](https://www.bignerdranch.com/img/blog/2015/05/ic_heart_50.png)\n![](https://www.bignerdranch.com/img/blog/2015/05/ic_heart_75.png)\n![](https://www.bignerdranch.com/img/blog/2015/05/ic_heart_100.png)\n\n在产品级的应用中，我们还需要保证图片尺寸可以适配不同的屏幕分辨率。但是现在，我们将所有的图片都扔到res/drawable-mdpi目录下完事。我推荐图片的命名采用自描述的方式，比如ic_heart_0.png, ic_heart_1.png以此类推。。。这样我们就不需要查看图片就知道图片的顺序。\n\n但谁叫我是屌丝呢，我选择将图片按照填充的百分比程度命名。\n\n## XML Drawable\n\n现在我们已经有了要掠一遍的图片，下一步就是为动画定义一个XML的Drawable，我们又遇到了两种选择：Animation-list和Animated-selector。\n\n## Animation-List\n\nAnimation-list是帧动画的默认选择，因为在API 1的时候就有了，同时它非常简单。就是简单的掠过指定顺序和持续时间的图片序列。\n\n这里是填充到实心效果的Animation-list的例子，在res/drawable/animation_list_filling.xml中：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                android:oneshot=\"true\">\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_0\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_25\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_50\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_75\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_100\"/>\n \n</animation-list>\n```\n\n列表中的每一个item都指向图片序列中的一张图片。我们只需把它们的顺序摆放正确并且添加一个毫秒为单位的持续时间即可。\n\n下面是实现变为空心效果的Animation-list，在res/drawable/animation_list_emptying.xml中：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                android:oneshot=\"true\">\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_100\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_75\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_50\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_25\"/>\n \n    <item\n        android:duration=\"500\"\n        android:drawable=\"@drawable/ic_heart_0\"/>\n \n</animation-list>\n```\n\n你可能注意到了，在两个代码片段中都有android:oneshot=”true”,这是animation-list的一个属性，表示播放完一次动画之后便停止动画。如果这个属性值设置为“false”，则动画会重复播放。\n\n在实际产品中，500毫秒时间太长，但是作为演示，我有意夸大了这个时间。还有一点，5帧图片对于产生流畅的过渡来说还是不够多。使用多少帧以及每帧的显示时间取决于个人。作为参考，我觉得15毫秒的15帧图片就可以非常流畅了。\n\n## Animated-Selector\n\nAnimated-selector要稍微复杂一些，因为它是基于状态的。根据View的状态（比如选中与激活状态），selector将使用提供的Transition来过渡到正确的状态。Animated-selector只在Lollipop上有效，因此我们需要在-v21 package中也定义一个xml。\n\n下面是一个Animated-selector的例子，放在res/drawable-v21/selector.xml中：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n \n    <item\n        android:id=\"@+id/on\"\n        android:state_activated=\"true\">\n        <bitmap\n            android:src=\"@drawable/ic_heart_100\"/>\n    </item>\n \n    <item\n        android:id=\"@+id/off\">\n        <bitmap\n            android:src=\"@drawable/ic_heart_0\"/>\n    </item>\n \n    <transition\n        android:fromId=\"@+id/on\"\n        android:toId=\"@+id/off\"\n        android:drawable=\"@drawable/animation_emptying\">\n    </transition>\n \n    <transition\n        android:fromId=\"@id/off\"\n        android:toId=\"@id/on\"\n        android:drawable=\"@drawable/animation_filling\">\n    </transition>\n \n</animated-selector>\n```\n\n仔细观察它是如何将前面定义的Animation-list引用为Transition的。\n\n这个animated-selector没有任何问题，但是我们需要考虑非Lollipop设备。我们在res/drawable/selector.xml中定义一个没有动画的selector：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n \n    <item\n        android:state_activated=\"true\">\n        <bitmap android:src=\"@drawable/ic_heart_100\"/>\n    </item>\n \n    <item\n        android:state_activated=\"false\">\n        <bitmap android:src=\"@drawable/ic_heart_0\"/>\n    </item>\n \n</selector>\n```\n\n现在我们的selector在任意设备上都可以工作。因为我们只使用了一般的selector，如果在Lollipop以前的设备上，animated-selector将直接跳过过渡动画，直接到结束状态。当然Lollipop设备是有我们在animated-selector中定义的过渡效果的。\n\n在上面的代码片段中，animated-selector只关注了android:state_activated属性。就如一般的selector一样，我为不同的状态定义了不同的item，但不同的是，我们还定义了不同状态间动画过渡的transition。在这个例子中，我直接将transition指向前面定义好了的animation-list。\n\n现在我们有了四个xml文件：一个充到实心效果的xml，实心到空心的xml，两个在空心实心之间切换的xml。\n\n## 设置ImageView\n\n现在可以设置一些图片来玩了。我们这里有三个ImageView，分别对应前面定义的三个XML Drawable。将下面的代码放到你的Activity的布局中：\n\n```xml\n<ImageView\n    android:id=\"@+id/imageview_animation_list_filling\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/animation_list_filling\"\n    />\n \n<ImageView\n    android:id=\"@+id/imageview_animation_list_emptying\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/animation_list_emptying\"\n    />\n \n <ImageView\n    android:id=\"@+id/imageview_animated_selector\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/selector\"\n    />\n```\n\n这只是几个id唯一，背景为我们定义的xml Drawable的ImageView。\n\n## 开始动画\n\n开始动画的方式在两种实现方法中是不一样的，我们先从animation-list开始：\n\n## Animation-List\n\n在Activity中，我们得到ImageView的引用，然后开始动画。如下：\n\n```java\nImageView mImageViewFilling = (ImageView) findViewById(R.id.imageview_animation_list_filling);\n((AnimationDrawable) mImageViewFilling.getBackground()).start();\n```\n\n\n下面是效果：\n\n![](https://www.bignerdranch.com/img/blog/2015/05/heart_filling.gif)\n\n接下来是它的搭档-反向过程（除了id都是一样的）\n\n```java\nImageView mImageViewEmptying = (ImageView) findViewById(R.id.imageview_animation_list_emptying);\n((AnimationDrawable) mImageViewEmptying.getBackground()).start();\n```\n\n效果如下：\n\n![](https://www.bignerdranch.com/img/blog/2015/05/heart_emptying.gif)\n\n\n这些代码可以放在onCreate（在Activity开始的时候自动开始）或者一个OnClickListener（等待用户触发）中，取决于你自己！\n\n\n## Animated-Selector\n\n当使用Animated-selector的时候，动画将在状态条件满足selector的时候被触发。在我们这个简单的例子中，我们在Activity的onCreate方法中为ImageView添加一个click listener：\n\n```java\nmImageViewSelector.setOnClickListener(new View.OnClickListener() {\n    @Override\n    public void onClick(View v) {\n        mImageViewSelector.setActivated(!mImageViewSelector.isActivated());\n    }\n});\n```\n\n当用户点击心形，它将会根据当前的状态在实心与空心之间切换，下面是我的心形循环显示的gif图：\n\n\n![](https://www.bignerdranch.com/img/blog/2015/05/heart_looping.gif)\n\n## Delightful Details\n\nFrame Animations have the power to surprise and delight users, plus it’s fun to add little personal touches to an app. Go forth and animate!\n帧动画可以取悦你的用户，为app增添个性，搞起！\n\n"
  },
  {
    "path": "issue-17/Android开发III-规范与性能.md",
    "content": "Android开发, III: 规范: 性能\n---\n\n> * 原文链接 : [Developing for Android, III: The Rules: Performance](https://medium.com/google-developers/developing-for-android-iii-2efc140167fd)\n* 原文作者 : [Google](https://medium.com/google-developers)\n* 译文出自 :  [Medium.com](https://medium.com)\n* 译者 : [dustookk](https://github.com/dustookk)\n* 状态 : 完成\n\n在Android设备上, 性能和内存是密不可分的, 系统的总内存占用会影响到系统的每个进程的性能表现, 垃圾回收机制也会对runtime的性能表现产生重要影响. 但是本章节要讨论的重点是一些和内存无关的的runtime性能表现.\n\n## 在动画和用户交互时避免复杂操作\n\n之前在[Context](https://medium.com/google-developers/developing-for-android-i-understanding-the-mobile-context-fd2351b131f8)章节的 \"UI Thread\" 部分已经提到过, UI线程中的复杂操作会引起渲染过程的卡顿. 连锁反应会影响到动画效果, 因为动画的每一帧都是一次渲染. 这就意味着当有动画出现时UI线程里更应该避免复杂的操作. 下面是一些应该避免的常见情况:\n\n#### Layout:\nMeasurement和Layout是非常复杂的操作, view的层级关系越复杂, 处理起来就越耗时. Measurement和layout发生在UI线程 (所有需要改动activity里view的操作都在UI线程中进行). 这就意味着如果一个程序正在执行一个流畅的动画的时候被告知需要在UI线程中同时执行layout操作, 结果动画肯定要受罪了.\n\n假设你的程序可以在13毫秒内绘制完成一个指定的动画, 这是在16毫秒的规定范围内的(Google官方推荐每秒60帧的刷新频率). 如果这时一个事件触发了需要耗时5毫秒的一个layout动作, 那么这个layout操作会在动画的下一帧绘制之前执行, 这就会将总绘制耗时增加到18毫秒, 结果就是动画效果有一个明显的跳帧.\n\n\n为了避免这种情况的发生, layout操作要在动画开始前或动画完成后进行.  还有就是, 尽量使用不会触发layout操作的动画效果. 比如, view的translationX和translationY属性会影响post-layout属性. 而LayoutParams的属性又会触发一个layout操作去产生作用, 所以类似这种属性的动画效果会影响已经比较合理的ui显示.\n\n#### Inflation:\n\nview的inflation过程只能在UI线程完成, 如果操作不当会变成一个非常耗时的过程 (view的层级关系越深, inflation过程就越耗时) Inflation 过程可以通过主动inflate一个view或view树触发, 也可以通过启动一个不同的的activity时隐性触发, 隐性触发会发生在UI线程中, 进而会造成activity在inflation过程中的动画卡顿.\n\n为了避免这种情况, 应该等待当前的动画结束后再触发view的inflation操作或者activity的启动操作. 还有一种情况就是, 为了避免多type的list在滚动时的inflation相关问题, 可以考虑预先inflate不同type的view. 比如, RecyclerView支持预设一个可以产生不同type的ItemView的RecycledViewPool.\n\n## 快速启动\n\nview的inflation过程有些耗时. 并不只是解析一些资源文件那么简单, 更包含了实例化潜在的许许多多的view和各个view初始化时自身耗时的操作. 包括bitmap的解码过程, 绘制layout的过程, 还有第一次初始化时的draw过程. ui写的越复杂, view树状结构层级关系越深,整体的inflation过程就会越耗时.\n\n以上的这些都会拖慢程序的启动过程. 当用户启动一个程序时, 他们期望看到的是几乎瞬时的反馈可以告知他们程序已经跑起来了. Android系统用了一个\"启动界面\" 来实现这一效果, 包括一个程序设定主题的空白窗口和一些特定的背景图画. \"启动界面\"是在程序在后台加载以及inflation的过程中在系统进程展示的. 当activity准备好可以被展示了, 启动界面就切换到了真正的内容, 用户就可以开始使用程序了.\n\n启动界面的加入确实可以给用户一个快速反馈告诉用户程序正在启动, 但是这并不足以应付需要两三秒甚至更长时间启动的程序; 结果就是用户被迫坐在那里盯着空空的启动界面直到程序真正启动起来.\n\n解决这个问题的办法就是用最快的速度启动程序. 如果有一些UI界面并不需要在第一次启动的时候展示, 那么就不要初始化它们. 用ViewStub 作为sub-hierarchies的临时占位对象, 这样随时可以填充为真正的UI元素. 只要有可能就尽量避免类似解码很大的bitmap这样的耗时操作. 尽量避免因为内存分配或垃圾回收引起的内存抖动. 用工具去监控程序的启动时间, 发现并消除遇到的瓶颈.\n\n同时, 避免在Application对象中的初始化操作. 只要有新的进程启动, Application类就会创建新的对象. 会潜在的引起许多超出实际需要(只展示初始化UI)的操作. 比如, 当某个用户在查看一张图片时想分享它, 于是选中了你的程序, 这时你的程序需要做的只是展示一个分享界面就可以了.  现在Application的子类越来越变成了一个初始化一切操作的仓库, 做着很多多余的工作. 相反, 正确的做法是用单例去控制初始化操作, 这样的话初始化操作只会在该单例第一次被调用时执行. 还有就是, 永远不要在Application对象中触发网络请求. 因为Application对象也许会在Service或者BroadcastReceiver启动时被创建; 此时触发网络操作会使一段特定频率下请求数据更新的代码变成对服务器的DDoS攻击代码.\n\n还有就是, 程序启动前所处在的状态不同, 启动时间也大不相同. 最坏一种情况是程序完全没有被启动: 此时进程需要被启动, 所有的状态也需要被重新初始化, 程序需要完成所有的inflation, layout和drawing过程. 另外一个极端情况是, 程序已经启动并在后台运行着, 这时要开启它, 系统只需要把它从后台切换到前台就可以了 - 甚至省去了大量的layout和rendering过程. 除了两种极端情况外, 还有另外两种场景. 一个是当用户退出程序时, 进程可能还在跑, 但是要再次开启程序, 所有任务需要从头来过(从调用Activity.onCreate()方法开始). 还有一种场景是当系统将程序任务从内存中删掉时, 进程和任务都需要重新启动, 但是任务结束时保存的状态会通过Activity.onCreate()传给程序并使之受益. 当你测试你的程序的启动时间时, 要确保优化的是最坏场景下的启动过程, 此时进程和任务都需要重新启动. 制造此场景的方法就是, 你可以在最近的任务列表中划掉你的程序杀掉任务和进程, 这就保证了程序下次启动时是完全重新启动的.\n\n## 避免View复杂的层级关系\n\n界面层级关系中的view越多, 系统进行一般的操作需要消耗的时间就越长, 比如inflation, layout和rendering过程(许多无用的内容占据了很多内存; view本身也很能占据内存, 尤其是自定义控件带来的更多数据). 要找到最节约资源的方式去组织view中的控件. 在某些场景下用自定义view或者自定义layout可以避免复杂的view层级关系. 用一个单一的view去画一些文字和icon也许比用一系列组合viewgroup来实现一样的效果更节省资源.  在交互界面中如何组合控件有一个准则: 如果用户可以和某一UI元素产生交互(比如touch事件, 获取focus等), 那么这个UI元素应该是一个独立的view 而不应该和其他元素组合.\n\n## 避免在靠近view层级关系顶层的地方使用RelativeLayout\n\nRelativeLayout是一个用起来很方便的控件, 因为它允许工程师们用相对布局摆放子控件. 在许多情况下, 这是个解决问题非常有必要的方案. 但是, 一定要明白使用RelativeLayout非常消耗资源. 因为RelativeLayout会触发两次measurement过程来保证正确的处理了所有子元素的关系. 更糟的是, 它会和view层级关系中其他的RelativeLayout一起产生更坏的后果. 想象一下一个view层级关系的顶部是一个RelativeLayout; 这本来就将所有子view的measurement次数变成了原来的两倍. 此时如果另外一个RelativeLayout是顶部那个RelativeLayout的子view,那么这时它下面所有子view的measurement次数又变成了原来的两倍, 也就是所它下面的所有子view都经历了四次measurement过程.\n\n所以要尽量使用不需要两次measure过程的控件, 比如LinearLayout或者自定义layout. 如果一定要用相对布局的方案, 可以考虑用一个自定义的GridLayout, 可以预处理view的相对关系, 从而避免了两次measure的问题.\n\n## 避免在UI线程中的复杂操作\n\n拖延UI线程会导致动画和界面绘制过程的滞后, 造成用户可以感知到的卡顿. 在UI线程(比如 onDraw()方法 onLayout()方法, 或者一些UI线程中被调用的和view展示有关的方法)避免一些众所周知的耗时操作. 比如调用web service 或执行其它网络请求(会抛出NetworkOnMainThreadException), 或者是访问数据库. 相反, 应该考虑用Loader或者其它模块异步操作完成后再通知UI线程修改界面. 可以用StrictMode模块监控这种问题.\n\n不可以在UI线程访问数据库和文件的另外一个重要原因是Android设备通常并不善于处理IO的并发. 即使你的程序闲置的时候, 其它的程序也许在高负荷的访问磁盘I/O (比如谷歌商店在更新软件). 结果就是有可能会导致ANR发生, 或者至少会导致你的程序出现的严重卡顿.\n\n总的来说, 只要可以放在异步处理的任务就尽量放在异步处理; UI线程需要做的应该只是和UI相关的核心操作, 比如控制界面上元素的属性或者是绘制过程.\n\n## 把程序的唤醒次数降到最低\n\n广播接收者被用来接受其它程序发来的消息或者事件. 但是如果超出实际需要, 响应过多的广播会导致程序被频繁的唤醒, 从而影响整个系统的性能表现和资源消耗. 应该在程序不需要接受某个广播的时候反注册掉该广播接收者. 注册广播接收者时也要只选择程序需要监听的Intent.\n\n## 为低端设备开发\n\n这与前面在[Context](https://medium.com/google-developers/developing-for-android-i-understanding-the-mobile-context-fd2351b131f8)章节关于低端设备的讨论有关.  也许大多数的用户的设备都不如你每天用的设备性能好, 也许比你的设备用的更久或者更便宜. 为这部分低端手机开发非常的重要, 一些在高端设备上很难察觉的性能差异在低端设备上会非常明显. 你的首选开发设备不应该是市面上最快最新的设备, 而且你也应该持有各种不同的测试设备, 这样就可以保证你的程序在不同速度不同厂商的设备上都有足够的性能表现.\n\n这些需要测试的其它低性能设备包括内存较小的设备或者屏幕分辨率较小的设备. 比如512MB内存的设备, 或者拥有768x480或者更低的屏幕分辨率的设备.\n\n## 测试性能表现\n\n市面上有许多工具可以用来测试你的程序的性能表现, 比如rendering的性能(程序可以达到60fps的刷新频率吗?), 内存回收性能(动画的过程中会因为持续的内存非配所引发的垃圾回收而发生卡顿吗?), 或者程序启动性能(用户会因为你的程序第一次启动做了大量的工作而耽误很长时间吗?). 找到问题. 修复他们.\n"
  },
  {
    "path": "issue-17/readme.md",
    "content": ""
  },
  {
    "path": "issue-17/为什么需要在你的Crash报告中使用git-SHA.md",
    "content": "为什么需要用 GIT SHA 管理Crash\n---\n\n> * 原文链接 : [Why You Should Use a GIT SHA in Your Crash Reporting](http://www.donnfelker.com/why-you-should-use-a-git-sha-in-your-crash-reporting/)\n* 原文作者 : [DONN FELKER](http://www.donnfelker.com/author/donn/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [sjyin](https://github.com/yinshijian-kkb) \n* 状态 :  完成\n\n开发人员在开发中常常会遇到一个问题：使用一个像Crashlytics的crash管理工具来判断一个特定的crash/bug是否被修复或解决。\n\n例如：     \n假设你从当前发布的版本上发现一个crash，但是本周你已经发布了三次...那么当前的crash来自于哪一个版本呢？\n\n通常通过查看Crashlytics中版本号和版本名称来解决。但是，你必须正确的匹配发布的标签。如果你可以做到，跟踪到发布时具体的提交，然后再研究。\n\n然而 。。。说实话，并不是每个人都能做到这一点。不幸的是，在我的经验中只有极少数公司会做这些，因为公司会不断的减少开发人员，甚至会减少到一个人。这种情况还在持续，所以很难做到正确的匹配发布的标签。从长远来看，找到一个标签，然后找到提交的版本，好吧。。。我不得不说这是一种痛苦。如果某个人忘记了提交的版本，就会什么也做不了。\n\n也就是说，这里需要一个快速的小窍门可以在你使用像Crashlytics等工具处理crash和bug分类的时候帮你节省大量的时间。\n\n**添加GIT SHA**\n在你的Android工程中，打开build.gradle，然后添加如下的anroid代码块：\n\n\n```java \n1 // Hat tip to Jake Wharton for this - found it in the u2020 app\n2 def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()\n```\n\n退回到android代码块中，然后添加一个git sha构建配置常量。\n\n\n```java\n1 android {\n2 compileSdkVersion 19\n3 buildToolsVersion \"21.1.0\"\n4 \n5 defaultConfig {\n6 applicationId \"co.your.appname\"\n7 minSdkVersion 19\n8 targetSdkVersion 19\n9 \n10 buildConfigField \"String\", \"GIT_SHA\", \"\\\"${gitSha}\\\"\"\n11\t }\n12 \n13 }\n```\n\n现在返回到你创建Crashlytics实例的代码中（例子如下）。只需要在初始化脚本的下面添加如下的代码：\n\n\n```java\n1 Crashlytics.setString(\"git_sha\", BuildConfig.GIT_SHA);\n```\n\n这行脚本的作用是：从你的源码管理中获取到git-sha的字符串值，然后将字符串设置成“git-sha”的值。\n\n现在，当你的应用崩溃，你将会在Crashlytics中得到bug汇报，并且可以查看最近提交的代码。\n\n**查看Crashlytics**\n\n打开Crashlytics，进到崩溃汇报。然后，点击“more details”。在这里，你会看到（屏幕下方）应用构建的git_sha。\n\n![p1](http://www.donnfelker.com/why-you-should-use-a-git-sha-in-your-crash-reporting/)\n\n然后，你将会看到这个。。。\n\n![p2](http://www.donnfelker.com/wp-content/uploads/2015/06/1435168870_full.png)\n\n**补习**\n\n一旦你识别到crash和git-sha，你就可以通过争论点checkout准确的版本。\n\n\n```java\n1 git checkout git_sha_goes_here\n```\n\n这时，你将可以在分离头的状态中查看造成崩溃的原因。然后，你将可以返回当前开发或标记的分支到修复过的问题和发布过的修复。\n\ngit_sha节省了你的大量时间，并且很容易创建。从此，你再也不用通过git logs,tag,patches 等等来寻找“提交了什么引起了崩溃？我们修复这个崩溃了吗？我们是怎么发现这个崩溃的？“，只需要简单的查看git sha来查找bug，查看当前的bug是否修复。如果修复了，很好。如果，还没有，用你的方式修复它。\n\n希望这个能帮助到你！"
  },
  {
    "path": "issue-17/使用RxJava从几个数据源中加载数据.md",
    "content": "# 使用RxJava从多个数据源中加载数据\n---\n\n> * 原文链接 : [Loading data from multiple sources with RxJava](http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/)    \n* 作者 : [Dan Lew](http://blog.danlew.net/author/dan-lew/)    \n* 译者 : [sjyin](https://github.com/yinshijian-kkb)     \n\n假设我需要从网络上获取一些数据。每次需要数据的时候，我都可以简单的访问网络，但是，将数据缓存到磁盘或内存则可以更有效率。\n\n更明确的说，我希望是这样的：\n\n1、偶尔的从网络上获取新数据。\n2、然而可以尽快的恢复数据（通过缓存网络数据的结果）。\n\n我想要使用[RxJava](https://github.com/ReactiveX/RxJava)实现。\n\n\n## **基本模式**\n给每一数据源（网络、磁盘和内存）一个```Observable<Data>```接口，我们可以通过两个操作：```concat()```和```first()```，来实现一个简单的解决方案。\n\n```concat()```持有多个```Observables```，并且把它们连接在队列里。```first()```仅从队列里中获取到第一个条目。因此，如果你使用```concat().first()```可以从多个数据源中获取到第一个。\n\n让我们看看它是如何实现的：\n\n```java\n// Our sources (left as an exercise for the reader)\nObservable<Data> memory = ...;  \nObservable<Data> disk = ...;  \nObservable<Data> network = ...;\n\n// Retrieve the first source with data\nObservable<Data> source = Observable  \n  .concat(memory, disk, network)\n  .first();\n```\n\n这种模式的关键是```concat()```只在需要资源的时候才会订阅每个子```Observable```。如果数据被缓存，就不需要通过速度慢的数据源来获取数据。\n\n注意```concat()```中```Observables```数据源的顺序问题，因为它们是被一个接一个检索出来的。\n\n## **保存数据**\n\n很显然，下一步就是保存数据源。如果，你没有将网络请求的结果保存到磁盘，将磁盘的地址保存在内存中，那就再也没法挽救啦！上面所有的代码就是让网咯请求持久化。\n\n我的解决方式是在每次发出请求的时候保存或缓存数据源：\n\n```java\nObservable<Data> networkWithSave = network.doOnNext(data -> {  \n  saveToDisk(data);\n  cacheInMemory(data);\n});\n\nObservable<Data> diskWithCache = disk.doOnNext(data -> {  \n  cacheInMemory(data);\n});\n```\n\n现在，如果你使用```networkWithSave ```和```diskWithCache```，数据都将会在你下载的时候自动保存。\n\n（这种策略的另外一个好处就是```networkWithSave/diskWithCache ```可以在任何地方使用，不仅仅在我们的多个数据源模式下。）\n\n## **旧数据**\n\n很不幸，现在我们保存数据的代码运行的太好啦！它总是会返回相同的数据，不管数据有没有过期。记着，我想要返回到偶尔从服务器获取新的数据。\n\n这个解决方案在```first()```中，我们可以执行筛选，只需要过滤掉那些过期的数据：\n\n```java\nObservable<Data> source = Observable  \n  .concat(memory, diskWithCache, networkWithSave)\n  .first(data -> data.isUpToDate());\n```\n\n现在，我们只会获取被证明是最新的条目。因此，如果我们的资源有一条旧数据，我们就会略过，直接到下一条数据，直到我们找到新数据。\n\n**first()vs.takeFirst()**\n\n你可以选择使用```first()```，你也可以选择使用```takeFirst()```。\n\n这两种调用的区别是当没有可用资源的时候，```first()```会抛出一个```NoSuchElementException```的异常，然而，```takeFirst()```只会简单的结束。\n\n你使用哪种就看你是否需要明确的处理数据是否用完。\n\n\n## **代码案例**\n\n这里是以上代码的运用，你可以在这儿checkout出代码：[https://github.com/dlew/rxjava-multiple-sources-sample](https://github.com/dlew/rxjava-multiple-sources-sample)\n\n如果你需要一个实际案例，checkout出[Gfycat](https://github.com/dlew/android-gfycat)的应用，它是使用这种模式[查看Gfycat中的动态数据](https://github.com/dlew/android-gfycat/blob/6154ab4bf056a080a0d4fbc69c63594d2b3a4387/Gfycat/src/main/java/net/danlew/gfycat/service/GfycatService.java#L61-L76)。Gfycat中的代码并没有使用以上的所有功能（因为它不需要），但是它演示了```concat().first()```的基本用法。\n"
  },
  {
    "path": "issue-17/如何使用Android-Studio把自己的Android-library分发到jCenter和Maven-Central.md",
    "content": "# 如何使用Android Studio把自己的Android library分发到jCenter和Maven Central\n---\n\n> * 原文链接 : [How to distribute your own Android library through jCenter and Maven Central from Android Studio](http://inthecheesefactory.com/blog/how-to-upload-library-to-jcenter-maven-central-as-dependency/en)\n* 译文出自 :  [开发技术前线 www.devtf.cn](http://www.devtf.cn)与 [泡在网上的日子http://www.jcodecraeer.com/](http://www.jcodecraeer.com)\n* 译者 : [jianghejie](https://github.com/jianghejie) \n\n如果你想在Android Studio中引入一个library到你的项目，你只需添加如下的一行代码到模块的build.gradle文件中。\n\n<pre>dependencies {\n    compile &#39;com.inthecheesefactory.thecheeselibrary:fb-like:0.9.3&#39;\n}</pre>\n\n就是如此简单的一行代码，你就可以使用这个library了。\n\n酷呆了。不过你可能很好奇Android Studio是从哪里得到这个library的。这篇文章将详细讲解这是怎么回事，包括如何把你的库发布出去分享给世界各地的其他开发者，这样不仅可以让世界更美好，还可以耍一次酷。\n\n##  Android studio 是从哪里得到库的？\n\n先从这个简单的问题开始，我相信不是每个人都完全明白Android studio 是从哪里得到这些library的。莫非就是Android studio 从google搜索然后下载了一个合适的给我们？\n\n呵呵，没那么复杂。Android Studio是从build.gradle里面定义的Maven 仓库服务器上下载library的。Apache Maven是Apache开发的一个工具，提供了用于贡献library的文件服务器。总的来说，只有两个标准的Android&nbsp;library文件服务器：jcenter&nbsp;和 &nbsp;Maven Central。\n\n##  jcenter\n\njcenter是一个由&nbsp;bintray.com维护的Maven仓库 。你可以在[这里](http://jcenter.bintray.com/)看到整个仓库的内容。\n\n我们在项目的build.gradle&nbsp;文件中如下定义仓库，就能使用jcenter了：\n\n<pre class=\"brush:js;toolbar:false\">allprojects {\n    repositories {\n        jcenter()\n    }\n}</pre>\n\n##  Maven Central\n\nMaven Central 则是由[sonatype.org](https://sonatype.org/)维护的Maven仓库。你可以在[这里](https://oss.sonatype.org/content/repositories/releases/)看到整个仓库。\n\n注：不管是jcenter还是Maven Central ，两者都是Maven仓库\n\n我们在项目的build.gradle&nbsp;文件中如下定义仓库，就能使用Maven Central了：\n\n<pre class=\"brush:js;toolbar:false\">allprojects {\n    repositories {\n        mavenCentral()\n    }\n}</pre>\n\n 注意，虽然jcenter和Maven Central 都是标准的&nbsp;android library仓库，但是它们维护在完全不同的服务器上，由不同的人提供内容，两者之间毫无关系</span>。在jcenter上有的可能&nbsp;Maven Central 上没有，反之亦然。\n\n除了两个标准的服务器之外，如果我们使用的library的作者是把该library放在自己的服务器上，我们还可以自己定义特有的Maven仓库服务器。Twitter的Fabric.io 就是这种情况，它们在[https://maven.fabric.io/public](https://maven.fabric.io/public)上维护了一个自己的Maven仓库。如果你想使用Fabric.io的library，你必须自己如下定义仓库的url。\n\n<pre class=\"brush:js;toolbar:false\">repositories {\n    maven { url &#39;https://maven.fabric.io/public&#39; }\n}</pre>\n\n然后在里面使用相同的方法获取一个library。\n\n<pre class=\"brush:js;toolbar:false\">dependencies {\n    compile &#39;com.crashlytics.sdk.android:crashlytics:2.2.4@aar&#39;\n}</pre>\n\n但是将library上传到标准的服务器与自建服务器，哪种方法更好呢？当然是前者。<span style=\"color: rgb(0, 112, 192);\">如果将我们的library公开，其他开发者除了一行定义依赖名的代码之外不需要定义任何东西。因此这篇文章中，我们将只关注对开发者更友好的jcenter 和 Maven Central </span>。\n\n实际上可以在Android Studio上使用的除了Maven 仓库之外还有另外一种仓库：[Ivy 仓库](http://ant.apache.org/ivy/)&nbsp;。但是根据我的经验来看，我还没看到任何人用过它，包括我，因此本文就直接忽略了。\n\n## 理解jcenter和Maven Central\n\n为何有两个标准的仓库？\n\n事实上两个仓库都具有相同的使命：提供Java或者Android&nbsp;library服务。上传到哪个（或者都上传）取决于开发者。\n\n起初，Android Studio 选择Maven Central作为默认仓库。如果你使用老版本的Android Studio创建一个新项目，mavenCentral()会自动的定义在build.gradle中。\n\n但是Maven Central的最大问题是对开发者不够友好。上传library异常困难。上传上去的开发者都是某种程度的极客。同时还因为诸如安全方面的其他原因，Android Studio团队决定把默认的仓库替换成jcenter。正如你看到的，一旦使用最新版本的Android Studio创建一个项目，jcenter()自动被定义，而不是mavenCentral()。\n\n有许多将Maven Central替换成jcenter的理由，下面是几个主要的原因。\n\n    - jcenter通过CDN发送library，开发者可以享受到更快的下载体验。\n\n    - jcenter是全世界最大的Java仓库，因此在Maven Central 上有的，在jcenter上也极有可能有。换句话说jcenter是Maven Central的超集。\n\n    - 上传library到仓库很简单，不需要像在&nbsp;Maven Central上做很多复杂的事情。\n\n    - 友好的用户界面\n\n    - 如果你想把library上传到&nbsp;Maven Central ，你可以在bintray网站上直接点击一个按钮就能实现。\n\n基于上面的原因以及我自己的经验，可以说替换到jcenter是明智之举。\n\n所以我们这篇文章将把重心放在jcenter，反正如果你能成功把library放在jcenter，转到&nbsp;Maven Central 是非常容易的事情。\n\n##  gradle是如何从仓库上获取一个library的？\n\n在讨论如何上传library到jcenter之前，我们先看看gradle是如何从仓库获取library的。比如我们在&nbsp;build.gradle输入如下代码的时候，这些库是如果奇迹般下载到我们的项目中的。\n\n<pre class=\"brush:js;toolbar:false\">compile &#39;com.inthecheesefactory.thecheeselibrary:fb-like:0.9.3&#39;</pre>\n\n一般来说，我们需要知道library的字符串形式，包含3部分\n\n<pre class=\"brush:js;toolbar:false\">GROUP_ID:ARTIFACT_ID:VERSION</pre>\n\n上面的例子中，GROUP_ID是com.inthecheesefactory.thecheeselibrary&nbsp;，ARTIFACT_ID是fb-like，VERSION是0.9.3。\n\nGROUP_ID定义了library的group。有可能在同样的上下文中存在多个不同功能的library。如果library具有相同的group，那么它们将共享一个GROUP_ID。通常我们以开发者包名紧跟着library的group名称来命名，比如com.squareup.picasso。然后ARTIFACT_ID中是library的真实名称。至于VERSION，就是版本号而已，虽然可以是任意文字，但是我建议设置为x.y.z的形式，如果喜欢还可以加上beta这样的后缀。\n\n下面是Square&nbsp;library的一个例子。你可以看到每个都可以很容易的分辨出library和开发者的名称。\n\n<pre class=\"brush:js;toolbar:false\">dependencies {\n  compile &#39;com.squareup:otto:1.3.7&#39;\n  compile &#39;com.squareup.picasso:picasso:2.5.2&#39;\n  compile &#39;com.squareup.okhttp:okhttp:2.4.0&#39;\n  compile &#39;com.squareup.retrofit:retrofit:1.9.0&#39;\n}</pre>\n\n那么在添加了上面的依赖之后会发生什么呢？简单。Gradle会询问Maven仓库服务器这个library是否存在，如果是，gradle会获得请求library的路径，一般这个路径都是这样的形式：GROUP_ID/ARTIFACT_ID/VERSION_ID。比如可以在[http://jcenter.bintray.com/com/squareup/otto/1.3.7](http://jcenter.bintray.com/com/squareup/otto/1.3.7)和[https://oss.sonatype.org/content/repositories/releases/com/squareup/otto/1.3.7/](https://oss.sonatype.org/content/repositories/releases/com/squareup/otto/1.3.7/)\n\n下获得com.squareup:otto:1.3.7的library文件。\n\n然后Android Studio 将下载这些文件到我们的电脑上，与我们的项目一起编译。整个过程就是这么简单，一点都不复杂。\n\n我相信你应该清楚的知道从仓库上下载的library只是存储在仓库服务器上的jar&nbsp;或者aar文件而已。有点类似于自己去下载这些文件，拷贝然后和项目一起编译。但是使用gradle依赖管理的最大好处是你除了添加几行文字之外啥也不做。library一下子就可以在项目中使用了。\n\n## 了解aar文件\n\n等等，我刚才说了仓库中存储的有两种类型的library：jar&nbsp;和&nbsp;aar。jar文件大家都知道，但是什么是aar文件呢？\n\naar文件时在jar文件之上开发的。之所以有它是因为有些Android&nbsp;Library需要植入一些安卓特有的文件，比如AndroidManifest.xml，资源文件，Assets或者JNI。这些都不是jar文件的标准。\n\n因此aar文件就时发明出来包含所有这些东西的。总的来说它和jar一样只是普通的zip文件，不过具有不同的文件结构。jar文件以classes.jar的名字被嵌入到aar文件中。其余的文件罗列如下：\n- /AndroidManifest.xml (mandatory)\n- /classes.jar (mandatory)\n- /res/ (mandatory)\n- /R.txt (mandatory)\n- /assets/ (optional)\n- /libs/*.jar (optional)\n- /jni/<abi>/*.so (optional)\n- /proguard.txt (optional)\n- /lint.jar (optional)\n可以看到.aar文件是专门为安卓设计的。因此这篇文章将教你如何创建与上传一个aar形式的library。\n\n## 如何上传library到jcenter\n\n我相信你已经知道了仓库系统的大体工作原理。现在我们来开始最重要的部分：上传。这个任务和如何上传library文件到[http://jcenter.bintray.com](http://jcenter.bintray.com/)一样简单。如果做到，这个library就算发布了。好吧，有两个需要考虑：如何创建aar文件以及如何上传构建的文件到仓库。\n\n虽然需要若干步骤，但是我还是想强调这事并不复杂，因为已经准备好了所有事情。整个过程如下图：\n\n![](http:/www.jcodecraeer.com/uploads/20150619/1434678347320974.png \"1434678347320974.png\")\n\n\n因为细节比较多，我分为7部分，一步一步的详细解释清楚。\n\n## 第一部分：在bintray上创建package\n\n首先，你需要在bintray上创建一个package。为此，你需要一个bintray账号，并在网站上创建一个package。\n\n第一步：在[bintray.com](https://bintray.com/)上注册一个账号。（注册过程很简单，自己完成）\n\n第二步：完成注册之后，登录网站，然后点击maven。\n\n![blob.png](http:/www.jcodecraeer.com/uploads/20150619/1434678368900440.png \"1434678368900440.png\")\n\n第三步：点击Add New Package，为我们的library创建一个新的package。\n\n![maven2](http:/www.jcodecraeer.com/uploads/20150619/1434707028338100.png)\n\n第四步：输入所有需要的信息\n\n![maven3](http:/www.jcodecraeer.com/uploads/20150619/1434707031430619.png)\n\n虽然如何命名包名没有什么限定，但是也有一定规范。所有字母应该为小写，单词之间用－分割，比如，fb-like。\n\n当每项都填完之后，点击Create Package。\n\n第五步：网页将引导你到&nbsp;Package编辑页面。点击&nbsp;Edit Package文字下的Package名字，进入Package详情界面。\n\n![maven4](http:/www.jcodecraeer.com/uploads/20150619/1434707035128807.png)\n\n完工！现在你有了自己在Bintray上的Maven仓库，可以准备上传library到上面了。&nbsp;\n\n![maven5](http:/www.jcodecraeer.com/uploads/20150619/1434707039327170.png)\n\nBintray账户的注册就完成了。下一步是Sonatype，Maven Central 的提供者。\n\n## 第二部分：为Maven Central创建个Sonatype帐号\n\n    注：如果你不打算把library上传到Maven Central，可以跳过第二和第三部分。不过我建议你不要跳过，因为仍然有许多开发者在使用这个仓库。\n\n和jcenter一样，如果你想通过Maven Central,贡献自己的library，你需要在提供者的网站Sonatype上注册一个帐号。\n\n你需要知道的就是这个帐号，你需要在Sonatype网站上创建一个IRA Issue Tracker 帐号。请到[Sonatype Dashboard](https://issues.sonatype.org/secure/Dashboard.jspa)&nbsp;注册这个帐号。\n\n完成之后。你需要请求得到贡献library到Maven Central的权限。不过这个过程对我来说有点无厘头，因为你需要做的就是在JIRA中创建一个issue，让它们允许你上传匹配Maven Central提供的GROUP_ID的library。\n\n要创建上述所讲到的issue，访问[Sonatype Dashboard](https://issues.sonatype.org/secure/Dashboard.jspa)，用创建的帐号登录。然后点击顶部菜单的Create。\n\n填写如下信息：\n> * Project:&nbsp;Community Support - Open Source Project Repository Hosting\n* Issue Type:&nbsp;New Project\n* Summary:&nbsp;你的&nbsp;library名称的概要，比如The Cheese Library。\n* Group Id: 输入根GROUP_ID，比如，com.inthecheeselibrary 。一旦批准之后，每个以com.inthecheeselibrary开始的library都允许被上传到仓库，比如com.inthecheeselibrary.somelib。\n* Project URL: 输入任意一个你想贡献的library的URL，比如，&nbsp;[https://github.com/nuuneoi/FBLikeAndroid](https://github.com/nuuneoi/FBLikeAndroid)。\n* SCM URL:&nbsp;版本控制的URL，比如&nbsp;[https://github.com/nuuneoi/FBLikeAndroid.git](https://github.com/nuuneoi/FBLikeAndroid.git)。\n\n其余的不用管，然后点击Create。现在是最难的部分...耐心等待...平均大概1周左右，你会获准把自己的library分享到&nbsp;Maven Central。\n\n最后一件事是在[Bintray Profile](https://bintray.com/profile/edit)的帐户选项中填写自己的Sonatype OSS用户名。\n\n![sonatypeusername](http://jcodecraeer.com/uploads/20150621/1434820514748752.png)\n\n点击Update，完成。\n\n##  第三部分：启用bintray里的自动注册\n\n就如我上面提到的，我们可以通过jcenter上传library到Maven Central ，不过我们需要先注册这个library。bintray提供了通过用户界面让library一旦上传后自动注册的机制。\n\n第一步是使用下面的命令行产生一个key。（如果你用的是windows，请在[cygwin](https://www.cygwin.com/)下做这件事情）\n\n<pre class=\"brush:js;toolbar:false\">gpg --gen-key</pre>\n\n有几个必填项。部分可以采用默认值，但是某些项需要你自己输入恰当的内容，比如，你的真实名字，密码 等等。\n\n创建了key之后，调用如下的命令查看被创建key的信息。\n\n<pre class=\"brush:js;toolbar:false\">gpg --list-keys</pre>\n\n如果没没问题的话，可以看到下面的信息：\n\n<pre class=\"brush:js;toolbar:false\">pub   2048R/01ABCDEF 2015-03-07\nuid                  Sittiphol Phanvilai &lt;yourmail@email.com&gt;\nsub   2048R/98765432 2015-03-07</pre>\n\n现在你需要把key上传到keyserver让它发挥作用。为此，请调用如下的命令并且将其中的PUBLIC_KEY_ID替换成上面pub一行中2048R/ 后面的&nbsp;8位16进制值，譬如本例是01ABCDEF。\n\n<pre class=\"brush:js;toolbar:false\">gpg --keyserver hkp://pool.sks-keyservers.net --send-keys PUBLIC_KEY_ID</pre>\n\n然后，使用如下的命令以ASCII形式导出公共和私有的key，请将yourmail@email.com替换成你前面用于创建key的email。\n\n<pre class=\"brush:js;toolbar:false\">gpg -a --export yourmail@email.com &gt; public_key_sender.asc\ngpg -a --export-secret-key yourmail@email.com &gt; private_key_sender.asc</pre>\n\n打开Bintray的[Edit Profile](https://bintray.com/profile/edit)页面点击GPG 注册。分别在Public Key和&nbsp;Private Key中填入上一步导出的public_key_sender.asc和&nbsp;private_key_sender.asc文件中的内容。\n\n![1434992443352430.png](http://jcodecraeer.com/uploads/20150623/1434992443352430.png \"1434992443352430.png\")\n\n点击Update保存这些key。\n\n最后一步就是启用自动注册。到[Bintray](https://bintray.com/)的主页点击maven。\n![blob.png](http://jcodecraeer.com/uploads/20150623/1434992792183444.png \"1434992792183444.png\")\n\n点击编辑\n![blob.png](http://jcodecraeer.com/uploads/20150623/1434992817106843.png \"1434992817106843.png\")\n\n勾选中GPG Sign uploaed files automatically以启用自动注册。\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1434992984179141.png \"1434992984179141.png\")\n\n点击Update保存这些步骤。完成。现在只需点击一下，每个上传到我们Maven仓库的东西都会自动注册并做好转向Maven Central 。\n\n请注意这是一次性的操作，以后创建的每一个library都要应用此操作。\n\nBintray和Maven Central 已经准备好了。现在转到Android Studio部分。\n\n##  第四部分：准备一个Android Studio项目\n\n很多情况下，我们需要同时上传一个以上的library到仓库，也可能不需要上传东西。因此我建议最好将每部分分成一个Module。最好分成两个module，一个Application Module一个Library Module。Application Module用于展示库的用法，Library Module是library的源代码。如果你的项目有一个以上的library，尽管创建另外的module：1个 module对应1 个library。\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435029593473349.png \"1435029593473349.png\")\n\n我相信大家知道如何创建一个新的module，因此就不会深入讲解这个问题了。其实很简单，基本就是选择creating an&nbsp;Android Library&nbsp;module ，然后就完了。\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435029727305525.png \"1435029727305525.png\")\n\n下一步是把bintray插件应用在项目中。我们需要修改项目的build.gradle文件中的依赖部分，如下：\n\n<pre class=\"brush:js;toolbar:false\">dependencies {\n    classpath &#39;com.android.tools.build:gradle:1.2.3&#39;\n    classpath &#39;com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2&#39;\n    classpath &#39;com.github.dcendents:android-maven-plugin:1.2&#39;\n}</pre>\n\n有一点非常重要，那就是gradle build tools的版本设置成1.1.2以上，因为以前的版本有严重的bug，我们将使用的是最新的版本1.2.3。\n\n接下来我们将修改local.properties。在里面定义api key的用户名以及被创建key的密码，用于bintray的认证。之所以要把这些东西放在这个文件是因为这些信息时比较敏感的，不应该到处分享，包括版本控制里面。幸运的是在创建项目的时候local.properties文件就已经被添加到.gitignore了。因此这些敏感数据不会被误传到git服务器。\n\n下面是要添加的三行代码：\n\n<pre class=\"brush:js;toolbar:false\">bintray.user=YOUR_BINTRAY_USERNAME\nbintray.apikey=YOUR_BINTRAY_API_KEY\nbintray.gpg.password=YOUR_GPG_PASSWORD</pre>\n\nbintray username 放在第一行，&nbsp;API Key放在第二行，&nbsp;API Key可以在[Edit Profile](https://bintray.com/profile/edit)页面的API Key 选项卡中找到。\n\n最后一行是创建&nbsp;GPG key的密码。保存并关闭这个文件。\n\n最后要修改的是module的build.gradle文件。注意前面修改的是项目的build.gradle文件。打开它，在apply plugin: &#39;com.android.library&#39;之后添加这几行，如下：\n\n<pre class=\"brush:js;toolbar:false\">apply plugin: &#39;com.android.library&#39;\n\next {\n    bintrayRepo = &#39;maven&#39;\n    bintrayName = &#39;fb-like&#39;\n\n    publishedGroupId = &#39;com.inthecheesefactory.thecheeselibrary&#39;\n    libraryName = &#39;FBLike&#39;\n    artifact = &#39;fb-like&#39;\n\n    libraryDescription = &#39;A wrapper for Facebook Native Like Button (LikeView) on Android&#39;\n\n    siteUrl = &#39;https://github.com/nuuneoi/FBLikeAndroid&#39;\n    gitUrl = &#39;https://github.com/nuuneoi/FBLikeAndroid.git&#39;\n\n    libraryVersion = &#39;0.9.3&#39;\n\n    developerId = &#39;nuuneoi&#39;\n    developerName = &#39;Sittiphol Phanvilai&#39;\n    developerEmail = &#39;sittiphol@gmail.com&#39;\n\n    licenseName = &#39;The Apache Software License, Version 2.0&#39;\n    licenseUrl = &#39;http://www.apache.org/licenses/LICENSE-2.0.txt&#39;\n    allLicenses = [&quot;Apache-2.0&quot;]\n}</pre>\n\nbintrayRepo使用默认的，即maven。bintrayName修改成你上面创建的&nbsp;package name。其余的项也修改成和你library信息相匹配的值。有了上面的脚本，每个人都能通过下面的一行gradle脚本使用这个library。\n\n<pre class=\"brush:js;toolbar:false\">compile &#39;com.inthecheesefactory.thecheeselibrary:fb-like:0.9.3&#39;</pre>\n\n最后在文件的后面追加两行如下的代码来应用两个脚本，用于构建library文件和上传文件到bintray（为了方便，我直接使用了github上连接到相关文件的链接）：\n\n<pre class=\"brush:js;toolbar:false\">apply from: &#39;https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle&#39;\napply from: &#39;https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle&#39;</pre>\n\n完成！你的项目现在设置好了，准备上传到bintray吧！\n \n\n## 第五部分：把library上传到你的bintray空间\n\n现在是上传library到你自己的bintray仓库上的时候了。请到Android Studio的终端（Terminal）选项卡。\n\n![terminal](http://jcodecraeer.com/uploads/20150623/1435031390224841.png \"terminal\")\n\n第一步是检查代码的正确性，以及编译library文件（aar，pom等等），输入下面的命令：\n\n<pre class=\"brush:js;toolbar:false\">&gt; gradlew install</pre>\n\n如果没有什么问题，会显示：\n\n<pre class=\"brush:js;toolbar:false\">BUILD SUCCESSFUL</pre>\n\n现在我们已经成功一半了。下一步是上传编译的文件到bintray，使用如下的命令：\n\n<pre class=\"brush:js;toolbar:false\">gradlew bintrayUpload</pre>\n\n如果显示如下你就大喊一声eureka吧！\n\n<pre class=\"brush:js;toolbar:false\">SUCCESSFUL</pre>\n\n在bintray的网页上检查一下你的package。你会发现在版本区域的变化。\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435032943140911.png \"1435032943140911.png\")\n\n点击进去，进入Files选项卡，你会看见那里有我们所上传的library文件。\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435033005347653.png \"1435033005347653.png\") \n\n恭喜，你的library终于放在了互联网上，任何人都可以使用了！\n\n不过也别高兴过头，library现在仍然只是在你自己的Maven仓库，而不是在jcenter上。如果有人想使用你的library，他必须定义仓库的url，如下：\n\n<pre class=\"brush:js;toolbar:false\">repositories {\n    maven {\n        url &#39;https://dl.bintray.com/nuuneoi/maven/&#39;\n    }\n}\n\n...\n\ndependencies {\n    compile &#39;com.inthecheesefactory.thecheeselibrary:fb-like:0.9.3&#39;\n}</pre>\n\n    译者注：前面都没怎么看懂，看到上面的代码之后一下子全懂了，呵呵。\n\n你可以在bintray的web界面找到自己Maven仓库的url，或者直接吧nuuneoi替换成你的bintray用户名（因为前面部分其实都是一样的）。我还建议你直接访问那个链接，看看里面到底是什么。\n\n但是，就如我们前面所讲的那样，让开发者去定义url这种复杂的事情并不是分享library的最佳方式。想象一下，使用10个library不得添加10个url？所以为了更好的体验，我们把library从自己的仓库传到jcenter上。\n\n##  第六部分：同步bintray用户仓库到jcenter\n\n把library同步到jcenter非常容易。只需访问网页并点击Add to JCenter\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435033973124955.png \"1435033973124955.png\")\n\n什么也不做直接点击Send。\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435033989118978.png \"1435033989118978.png\")\n\n现在我们所能做的就是等待bintray团队审核我们的请求，大概2-3个小时。一旦同步的请求审核通过，你会收到一封确认此更改的邮件。现在我们去网页上确认，你会在&nbsp;Linked To&nbsp;部分看到一些变化。\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435034011137594.png \"1435034011137594.png\")\n\n从此之后，任何开发者都可以使用jcenter() repository 外加一行gradle脚本来使用我们的library了\n\n<pre class=\"brush:js;toolbar:false\">compile &#39;com.inthecheesefactory.thecheeselibrary:fb-like:0.9.3&#39;</pre>\n\n想检查一下自己的library在jcenter上是否存在？你可以直接访问[http://jcenter.bintray.com](http://jcenter.bintray.com/)，然后进入和你library的group id 以及artifact id匹配的目录。在本例中就是com&nbsp;-&gt;&nbsp;inthecheesefactory -&gt; thecheeselibrary -&gt; fb-like -&gt; 0.9.3。\n\n![blob.png](http://jcodecraeer.com/uploads/20150623/1435034049131913.png \"1435034049131913.png\")\n\n请注意链接到jcenter是一个只需做一次的操作。如果你对你的package做了任何修改，比如上传了一个新版本的binary，删除了旧版本的binary等等，这些改变也会影响到jcenter。不过毕竟你自己的仓库和jcenter在不同的地方，所以需要等待2－3分钟让jcenter同步这些修改。\n\n同时注意，如果你决定删除整个package，放在jcenter仓库上的library不会被删除。它们会像僵尸一样的存在，没有人再能删除它了。因此我建议，如果你想删除整个package，请在移除package之前先在网页上删除每一个版本。\n\n##  第七部分：上传library到Maven Central\n\n并不是每个安卓开发者都使用jcenter。仍然有部分开发者还在使用mavenCentral()&nbsp;，因此让我们也把library上传到Maven Central 吧。\n\n    要从jcenter到Maven Central，首先需要完成两个任务：\n\n    1) Bintray package 已经连接到jcenter。\n\n    2) Maven Central上的仓库已经认证通过\n\n如果你已经通过了这些授权，上传library&nbsp;package到Maven Central就异常简单了，只需在package的详情页面点击Maven Central 的链接。\n\n![syncmavencentral](http://jcodecraeer.com/uploads/20150623/1435034730371235.png)\n\n输入你的Sonatype用户名和密码并点击Sync。\n\n![syncmavencentral2](http://jcodecraeer.com/uploads/20150623/1435034732252381.png)\n\n如果成功，在Last Sync Status中会显示Successfully synced and closed repo（见图），但是如果遇到任何问题，则会在Last Sync Errors显示出来。你需要根据情况修复问题，能上传到Maven Central 的library的条件是相当严格的，比如+ 号是不能在ibrary版本的依赖定义中使用的。\n\n完成之后，你可以在&nbsp;&nbsp;[Maven Central Repository](https://oss.sonatype.org/content/repositories/releases/)&nbsp;上找到你的library。在那些匹配你ibrary的group id以及artifact id的目录中。比如本例中就是com&nbsp;-&gt;&nbsp;inthecheesefactory -&gt; thecheeselibrary -&gt; fb-like -&gt; 0.9.3。\n\n恭喜！虽然需要许多步骤，但是每一步都很简单。而且大部分操作都是一劳永逸的。\n\n如此长篇的文章！希望对你有所帮助。我的英语也许有点晦涩，不过希望至少内容是可以理解的。\n\n期待能在上面看到你的library大作！\n\n    \n"
  },
  {
    "path": "issue-17/当钢铁侠变得反应更快-RxJava.md",
    "content": "当钢铁侠反应更灵敏-RxJava\n---\n\n> * 原文链接 : [When Iron Man becomes reactive, RxJava](http://saulmm.github.io/when-Iron-Man-becomes-Reactive-Avengers2/)\n* 原文作者 : [Saúl Molinero](https://twitter.com/_saulmm)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [BownX](https://github.com/BownX)   \n* 状态 :  完成 \n\n\n本系列的这个部分主要是讲一些函数式技巧能给我们的项目带来的好处。\n\n[ReactiveX](http://reactivex.io/) 中的 ``RxJava``  是一个可以帮助我们轻松处理不同运行环境下的后台线程或UI线程任务的框架。这在 Android 上一直是我们所有人的噩梦。\n\n这篇文章主要会谈谈其中（RxJava）的一些 operators 如何能在常见开发任务中为我们节省时间，[Reactive Extensions](http://reactivex.io/) 提供了很多种类的 operators 来让我们用的更方便。\n\n像以前一样，大部分代码和片段都已经上传到了 Github, 请随意评论、提 issue 或吐槽！\n\n[Avengers app on Github](https://github.com/saulmm/Avengers)\n\n在本系列[第一部分](http://saulmm.github.io/when-Thor-and-Hulk-meet-dagger2-rxjava-1/)中我们介绍了 [Dagger 2](http://google.github.io/dagger/)，现在我们更进一步会看到如何降低各层代码逻辑之间的耦合、增加可扩展性。\n\n#### RetroLambda\n\n有时，在 Java 开发的大型应用程序中，或 Android 这样的大型框架中，使用 Java 8 中的 Lambda 表达式这类特性是非常困难或是几乎不可能的(Android中)。\n\nRetrolambda 就是来解决这个问题的，它会把 Java 8 的字节码翻译成低版本 Java 像v7甚至v5、v6的字节码，这样可以让我们在这些低版本中使用到 Lambda 表达式的特性。\n\n![](http://androcode.es/wp-content/uploads/2015/06/retrolambda.png)\n\n[Retrolambda](https://github.com/orfjackal/retrolambda) 可以通过 Gradle 或 Maven 的方式来使用，我选择 Gradle 是因为 Android Studio 对其支持的很好。要使用它你只需要将 ``[Retrolambda](https://github.com/orfjackal/retrolambda)`` 插件加到项目根目录的 ``build.gradle``， 同时在 module 的 build 脚本中应用它，再设置 Android Studio 的语言级别到 ``1.8``，这就完成了。\n\n*build.gradle (root)*\n```groovy\ndependencies {\n    classpath 'me.tatarka:gradle-retrolambda:3.1.0'\n}\n```\n\n*{your module}/build.gradle*\n```groovy\napply plugin: 'me.tatarka.retrolambda'\nandroid { \n    ...\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n```\n\n[Retrolambda](https://github.com/orfjackal/retrolambda)可以让你少写很多重复的代码，同时理顺我们的代码让它有更好的可读性。在 [Dan Lew](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)给出的这个例子中，你可以感受到它所带来的不同。\n\n*没有 Retrolambda*\n```java\nObservable.just(\"Hello, world!\")\n    .subscribe(new Action1<String>() {\n        @Override\n        public void call(String s) {\n              System.out.println(s);\n        }\n    });\n```\n\n*有 Retrolambda*\n```java\nObservable.just(\"Hello, world!\") .subscribe(\n    s -> System.out.println(s)\n);\n```\n\n*在我们的 Avengers 示例中*\n```java\nmCharacterSubscription = mGetCharacterInformationUsecase\n    .execute().subscribe(\n        character -> onAvengerReceived(character),\n        error     -> manageError(error)\n    );\n\nmComicsSubscription = mGetCharacterComicsUsecase\n    .execute().subscribe(\n        comics -> Observable.from(comics).subscribe(\n            comic -> onComicReceived(comic)),\n        error  -> manageError(throwable)\n    );\n```\n\n#### ReactiveX\n\n[ReactiveX](http://reactivex.io/) 是一个开源项目的集合，它们所遵循的主要原则都是 [Observer](http://en.wikipedia.org/wiki/Observer_pattern) 模式、[Iterator](http://en.wikipedia.org/wiki/Iterator_pattern#Java) 模式以及函数式编程。\n[ReactiveX](http://reactivex.io/) 同时也提供了可用于异步编程的API，事实上使用这些框架来实现异步任务非常简单。\n\n#### ReactiveX 的异步客户端用法\n\n在使用 [ReactiveX](http://reactivex.io/) 时最棒的是你可以创建一个完全异步的API或客户端，在实现具体逻辑的时候再决定是把代码写成异步的、放到线程池中的一个独立线程执行、还是同步来执行。\n因此我们可以得到一个观察者模式的API而不是一个阻塞调用的API。\n\n```java\npublic interface Usecase<T> {\n\n    Observable<T> execute();\n\n}\npublic interface Repository {\n\n    Observable<Character> getCharacter (final int characterId);\n\n    Observable<List<Comic>> getCharacterComics (final int characterId);\n\n}\n```\n\n#### RxJava是什么\n\n[RxJava](https://github.com/ReactiveX/RxJava) 是一个由 **Netflix** 开发的 [Reactive Extensions](https://github.com/ReactiveX)的(Java版)实现。其他还有大量主流编程语言的实现，包括 Javascript, Python, Ruby, Go 等等。\n\n#### Observables 和 Observers\n\n一个 ``Observable`` 可以输出一个或一系列的 objects，这些 objects 会被订阅到这个 ``Observable`` 的 ``Observer`` 所处理或接收。\n\n![](http://androcode.es/wp-content/uploads/2015/06/observable-observer2.png)\n\n把一个 ``Observer`` 注册到一个 ``Observable`` 上是很必要的，如果不这么做的话 ``Observable`` 什么都不会输出。当 ``Observer`` 注册之后，一个 ``Subscription`` 类型的实例会创建，它可以用来取消对 ``Observable`` 的订阅，这通常在 ``Activities`` 和 ``Fragments`` 的 ``onStop`` 或 ``onPause`` 方法中非常有用，例如：\n\n```java\nmCharacterSubscription = mGetCharacterInformationUsecase\n    .execute().subscribe( ... );\n\n@Override\npublic void onStop() {\n\n    if (!mCharacterSubscription.isUnsubscribed())\n        mCharacterSubscription.unsubscribe();\n\n    if (!mComicsSubscription.isUnsubscribed())\n        mComicsSubscription.unsubscribe();\n\n}\n```\n\n无论何时 ``Observer`` 订阅 ``Observable`` 的消息，它都需要考虑处理3个方法：\n- ``onNext (T) `` 方法用来接收 ``Observable`` 发出的 objects.\n- ``onError (Exception)``，这个方法会在内部抛出异常的时候调用。\n- ``onCompleted()``，这个方法会在 ``Observable`` 停止释放 objects 的时候调用。\n\n我喜欢这张图 :)\n![](http://androcode.es/wp-content/uploads/2015/06/observable_observer.png)\n\n#### 组件间通信\n\n让我们来看看如何使用 ``GetCharacterInformationUsecase`` 这个用例，所有的用例都实现了 ``Usecase <T>`` 接口。\n\n```java\npublic interface Usecase<T> {\n\n    Observable<T> execute();\n\n}\n```\n\n这个例子被调用的时候会返回一个 ``Observable`` 类型的实例，它可以轻松的和其他的 observables 和 operators 组合成链式结构，我们很快将会看到这些 operators 的强大威力。\n\n当我们调用 ``GetCharacterInformationUsecase`` 的时候请求我们的仓库产生一个对应类型的数据源：\n\n```java\n@Override\npublic Observable<Character> execute() {\n\n    return mRepository.getCharacter(mCharacterId);\n        // .awesomeRxStuff();\n\n}\n```\n\n``AvengerDetailPresenter`` 这个 presenter 将会成为我们这个用例的 ``Observer``，它将会订阅这个 ``Observable`` 发出的所有事件，这个操作可以通过调用 ``subscribe`` 方法来完成，这样就把 ``Observer`` 和 ``Observable`` 关联在一起了。\n\n实现 ``onNext`` 和 ``onError`` 方法可以来处理操作结果。``onCompleted`` 方法并没有实现，因为在这个例子中不需要。\n\n```java\nmCharacterSubscription = mGetCharacterInformationUsecase\n        .execute().subscribe(\n            character   -> onAvengerReceived(character),\n            error       -> manageError(error));\n```\n\n#### Retrofit 和 RxJava\n\n[Square](http://square.github.io/) 出品了 [Retrofit](https://github.com/square/retrofit)，[RxJava](https://github.com/ReactiveX/RxJava) 支持其中的 ``rx.Observable`` 方法，这样网络请求的结果就可以通过 ``Observer`` 的方式来订阅、修改或通过 operators 加工。\n\n你一定得十分清楚在什么地方来调用它，[Retrofit](https://github.com/square/retrofit) 的请求会在 ``Observable`` 所在线程上来执行，因此当你在 UI 线程(Activity 或 Fragment 中)来调用的话就会报错。接下来我们讲讲 ``Schedulers``!\n\n#### Schedulers\n\n[Schedulers](http://reactivex.io/documentation/scheduler.html) 可以让你在多线程中使用 ``operators`` 和 ``Observables``。它可以被用在不同的线程、一个 线程 Executor 或是预设的 ``[Schedulers](http://reactivex.io/documentation/scheduler.html)`` 中。例如，对于输入或输出这类操作会在 ``Schedulers.io ()`` 来执行。\n\n[RxAndroid](https://github.com/ReactiveX/RxAndroid) 是由 [Jake Wharton](http://jakewharton.com/) 和 [Matthias Käppler](https://plus.google.com/u/0/+MatthiasK%C3%A4ppler/posts) 开发的一些 Android 下专用的 [RxJava](https://github.com/ReactiveX/RxJava) 的工具，其中包括一些用来处理 Android 平台下多进程调用的 ``Schedulers``。\n\n它也提供了使用 Android 的 ``Handler`` 来处理并发的方式。\n\n```java\n@Override\npublic Observable<Character> execute() {\n\n    return mRepository.getCharacter(mCharacterId)\n        .subscribeOn(Schedulers.newThread())\n        .observeOn(AndroidSchedulers.mainThread());\n\n}\n```\n\n这个例子演示了 Rx 提供的处理 Android 中多进程调用的方式，这真是非常的炫酷 :D\n\n#### Operators\n\n[ReactiveX](http://reactivex.io/) 最厉害的要数它的 ``operators`` 了，它们可以用来操作、变换、合并 ``Observables`` 输出的 objecs。\n\n我们来看看一个漫画列表的例子，漫画都有一个特定的出版年份，我们想要展示特定年份出版的漫画，这时 [ReactiveX](http://reactivex.io/) 就能大显身手了！\n\n这个过滤过程是通过 ``filter`` 这个 operator 来完成的，它可以作为漫画的一个约束条件来加以判断。在这个过程中，询问用户要过滤出哪一年，然后使用这个年份来判定一个漫画是否允许被展示。\n\n```java\npublic Observable<Comic> filterByYear(String year) {\n\n    if (mComics != null) {\n        return Observable.from(mComics).filter(\n            comic -> {\n                for (ComicDate comicDate : comic.getDates())\n                    if (comicDate.getDate().startsWith(year))\n                        return true;\n\n                return false;\n        });\n\n    }\n\n    return null;\n}\n```\n\n#### 异常处理\n\n另一个很好的 Rx 的 operators 能帮我们节省时间提升效率的例子是异常处理的 operators。\n\n设想一个用户要请求网络，但是在网络通道上发生了一些偶然因素，网络连接在这种情况下受到了影响。\n\n当我们接收到了 [Retrofit](http://square.github.io/retrofit/) 抛出的 ``SocketTimeoutException`` 异常时，我们可以利用 [retry](http://reactivex.io/documentation/operators/retry.html#collapseRxJava) 这个 operator 来处理。\n\n[retry](http://reactivex.io/documentation/operators/retry.html#collapseRxJava) 可以接收一个判定条件，就像之前我们在 [filter](http://reactivex.io/documentation/operators/filter.html) 中所做的一样，如果返回 true，那么 Rx 就会神奇的再次调用 ``Observable`` 重新执行 Retrofit 的网络请求。\n\n如果最多抛出了3次 ``SocketTimeoutExceptions`` 异常，程序会继续执行后续的 ``onError`` 来处理异常。\n\n```java\n@Override\npublic Observable<List<Comic>> getCharacterComics(int characterId) {\n\n    final String comicsFormat   = \"comic\";\n    final String comicsType     = \"comic\";\n\n    return mMarvelApi.getCharacterComics(\n        characterId, comicsFormat, comicsType)\n            .retry((attemps, error) -> \n                error instanceof SocketTimeoutException && \n                attemps < MAX_ATTEMPS);\n}\n```\n\n#### 一些参考资料\n\n- [ReactiveX](http://reactivex.io/)\n- [Reactive Programming in the Netflix API with RxJava](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)\n- [Use Lambdas on Java 7 and older](https://www.youtube.com/watch?v=DUdhfPh9V_s) - Esko Luontola\n- [Grokking RxJava](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/), Part1: The basics - Dan Lew"
  },
  {
    "path": "issue-18/Service测试.md",
    "content": "# Service测试\n---\n\n> * 原文链接 : [Service Testing](http://developer.android.com/tools/testing/service_testing.html)\n* 原文作者 : [google develper](http://developer.android.com/)\n* 译文出自 :  [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [sjyin](https://github.com/yinshijian-kkb) \n* 校对者 : [Mr.Simple](https://github.com/bboyfeiyu) \n\nAndroid为Service对象提供了一套测试框架，框架可以独立的运行，并且提供了模拟对象。这个测试用例类的名字叫做[ServiceTestCase](http://developer.android.com/tools/testing/service_testing.html)。因为这个Service类是独立于客户端的，所以，你不用使用`instrumentation`就可以测试Service对象。\n\n\n这篇文档描述了测试Service对象的技术。如果你对Service不熟悉，请阅读[Service](http://developer.android.com/guide/components/services.html)文档。如果你不熟悉Android测试，请阅读介绍Android测试和instrumentation框架的[测试文档](http://developer.android.com/tools/testing/testing_android.html)。\n\n## ***Service设计和测试***\n\n当你设计一个Service的时候，你应该考虑你的测试用例怎么样才能测试Service的整个生命周期。如果启动了Service，比如调用了`onCreate()`或者`onStartCommand()`，但并没有设置一个检测是否成功的全局变量，你可能想要提供一个以测试为目的的变量。\n\n大多数其他的测试可以通过[ServiceTestCase](http://developer.android.com/tools/testing/service_testing.html)的测试用例类来方便地进行测试。例如，`getService()`方法在测试中返回一个处理Service的句柄，通过这个句柄你可以确定Service是否正在运行。\n\n##***ServiceTestCase***\n\nServiceTestCase扩展了JUnit[测试用例](http://developer.android.com/reference/junit/framework/TestCase.html)，添加测试应用权限、控制应用和Service的方法。它还提供了独立于系统的mock application和Context对象。\n\nServiceTestCase)会在你调用`ServiceTestCase.startService()`或`ServiceTestCase.bindService()`的时候初始化测试环境。这是为了允许你在启动Service之前初始化测试环境，尤其是你的mock对象。\n\n注意：`Service.bindService()`的参数不同于`ServiceTestCase.bindService()`。因为`ServiceTestCase`的版本，你只能提供一个`Intent`。`ServiceTestCase.bindService()`会返回一个IBinder子对象，代替一个`boolean`值。\n\nServiceTestCase中的`setUp()`方法会在每次测试的时候调用。任何方法调用它，它都会创建一个当前系统的上下文的拷贝。你可以通过调用`getSystemContext()`查看这个上下文对象。如果，你重写该方法，你必须在重写的方法中首先调用`super.setUp()`。\n\n`setApplication()`和`setContext(Context) setContext()`允许你在开启Service之前为Service设置一个模拟的`Context`和`Application`（或者两者）。这些模拟的对象在[模拟对象集合](#mock)中。\n\n默认情况下，ServiceTestCase运行`testAndroidTestCaseSetupProperly()`方法，去声明在测试启动之前已经成功的创建`Context`。\n\n<b id=\"mock\"></b>\n## ***模拟对象集合***\n\nServiceTestCase假定你在测试环境中将会使用模拟的`Context`和`Application`（或者两者）。这些对象是跟系统分离的。如果你在启动Service之前不创建这些对象的实例，那么，ServiceTestCase会创建它内部的实例并将它们注入到Service中。你可以在开启Service之前通过创建和注入你自己的实例对象来实现这一功能。\n\n在测试的时候，将一个模拟对象注入到Service中。\n首先，创建一个[MockApplication](http://developer.android.com/reference/android/test/mock/MockApplication.html)的子类。`MockApplication`是Applicatoin的子类。然后，使用`setApplication()`将它注入到Service中。这个模拟对象允许你操作Service中的值，并且不受真实的系统影响。另外，当你运行测试的时候，你的Service中任何隐藏的应用依赖将会被当做异常处理。\n\n在测试中，你使用`setContext()`方法注入一个模拟`Context`到Service中。在[基础测试](http://developer.android.com/tools/testing/testing_android.html#MockObjectClasses)中，模拟的`Context`可以描述更多细节。\n\n## ***测试什么***\n\n测试什么这一主题下列出了测试android组件一般需要考虑的问题。这些是测试Service的具体指导：\n\n确保调用`Context.startService()`或`Context.bindService()`时`onCreate()`做出了响应。同理，调用`Context.stopService()`,`Context.unbindService()`,`stopSelf()`, 或者`stopSelfResult()`时`onDestroy()`做出了响应。\n\n多次调用`Context.startService()`只有第一次才会触发`Service.onCreate()`方法，但是所有的调用都会触发`Service.onStartCommand()`。\n\n另外，记着`startService()`不能够嵌套调用。所以，`Context.stopService()`或者`Service.stopSelf()`（不是`stopSelf(int)`）任意一个方法都可以停止Service。你应该测试你的Service是不是在正确的时间点关闭。\n测试Service中的每个业务逻辑。业务逻辑包括无效值的检查，算法的计算等等。\n\n\n"
  },
  {
    "path": "issue-18/readme.md",
    "content": "# 开发技术前线 第18期\n\n| 文章名称 |   译者  | \n|---------|--------|\n| [用组合代替继承能为Activity带来什么](用组合代替继承能为Activity带来什么.md)  | [chaossss](https://github.com/chaossss) \n| [Android-UI自动化测试](Android-UI自动化测试.md)  | [Ashraff Hathibelagal](https://github.com/http://tutsplus.com/authors/ashraff-hathibelagal) \n| [Service测试](Service测试.md)  | [sjyin](https://github.com/yinshijian-kkb) \n| [如何提高你的代码质量](如何提高你的代码质量.md)  | [dengshiwei](https://github.com/dengshiwei) \n| [拖拽RecyclerView](拖拽RecyclerView.md)  | [objectlife](https://github.com/objectlife) \n| [还在用Toast？你Out啦，试试Snackbar吧！](还在用Toast？你Out啦，试试Snackbar吧！.md)  | [objectlife](https://github.com/objectlife) \n\n"
  },
  {
    "path": "issue-18/如何提高你的代码质量.md",
    "content": "如何提高你的代码质量\n---\n> * 原文链接 : [How to improve quality and syntax of your Android code](http://vincentbrison.com/2014/07/19/how-to-improve-quality-and-syntax-of-your-android-code/)\n* 原文作者 : [VINCENT BRISON](http://vincentbrison.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [dengshiwei](https://github.com/dengshiwei) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 :  完成 \n\n在这篇文章中，我将通过不同的自动化工具如CheckStyle，FindBugs，PMD以及Android Lint来介绍(如何)提高你的安卓代码质量。通过自动化的方式检查你的代码非常有用，尤其当你在一个团队中工作，为了在你的代码中保持严格的语法格式以及避免很多坏习惯和错误。我将仔细地介绍如何在你空闲的时候直接运用这些工具通过Gradle构建脚本以及如何配置它们。\n\n\n\t\t\t目录\n\t0.1 Fork示例\n\t0.2 Gradle任务\n\t0.3 示例项目的层次结构\n\t1 Checkstyle\n\t1.1 简介\n\t1.2 Gradle的形式\n\t1.3 Checkstyle的使用技巧\n\t2 Findbugs\n\t2.1 简介\n\t2.2 Gradle的形式\n\t2.3 Findbugs的使用技巧\n\t3 PMD\n\t3.1 简介\n\t3.2 Gradle的形式\n\t3.3 PMD的使用技巧\n\t4 Android Lint\n\t4.1 简介\n\t4.2 Gradle的形式\n\t4.3 Android Lint的使用技巧\n\t5 实例演示\n\t6 总结\n\n###Fork该示例\n我强烈建议你拷贝下[这个项目工程](https://github.com/vincentbrison/vb-android-app-quality.git)，尽管我将介绍的案例都是来自它。与此同时，你将能够测试下自己对这些工具的了解情况。\n\n###关于Gradle任务\nGradle任务的概念(在Gradle中的含义)是理解该篇文章(以及如何以一种通用的方式写Gradle脚本)的基础。我强烈建议你去看下这两篇关于Gradle任务的文档（[这篇](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html)和[这篇](https://docs.gradle.org/current/userguide/more_about_tasks.html)）。这个文档包含了大量的例子，因此它非常容易开始学习。现在，我假定你拷贝了我的Repo，你导入这个工程到你的Android Studio，并且你熟悉Gradle任务。如果不是，别担心，我将尽我最大的努力让我的讲解更有意义。\n\n###关于示例项目的层次结构\n\n你可以将gradle脚本文件分割成很多文件，我现在已经有3个gradle文件：\n\n* [根文件夹中的文件](https://github.com/vincentbrison/vb-android-app-quality/blob/master/build.gradle)，这些文件或多或少都是关于这个项目的配置的(用的哪个Maven Repos，用的哪个版本的Gradle)。\n* [App子文件夹中的文件](https://github.com/vincentbrison/vb-android-app-quality/blob/master/app/build.gradle),这些文件是典型的用于创建安卓应用的gradle文件。\n* [config子文件夹中的文件](https://github.com/vincentbrison/vb-android-app-quality/blob/master/config/quality.gradle)，这里的文件才是我们关系的重点，因为我用这里的文件保存和配置项目中的所有工具。\n\n##Checkstyle\n\n[![Checkstyle](http://checkstyle.sourceforge.net/images/logo.png)](http://checkstyle.sourceforge.net/)\n\n###简介\n\n\"Checkstyle是一个开发工具用来帮助程序员编写符合代码规范的Java代码。它能自动检查Java代码为空闲的人进行这项无聊(但重要)的任务。\"\n\n正如Checkstyle的开发者所言，这个工具能够帮助你在项目中定义和维持一个非常精确和灵活的代码规范形式。当你启动CheckStyle，它会根据所提供的配置文件分析你的Java代码并告诉你发现的所有错误。\n\n###Gradle的形式\n\n下面的代码向你展示了在你的项目中使用Checkstyle的最基本的配置(如Gradle任务):\n\n\ttask checkstyle(type: Checkstyle) {\n\tconfigFile file(\"${project.rootDir}/config/quality/checkstyle/checkstyle.xml\") // Where my checkstyle config is...\n\tconfigProperties.checkstyleSuppressionsPath = file(\"${project.rootDir}/config/quality/checkstyle/suppressions.xml\").absolutePath // Where is my suppressions file for checkstyle is...\n\tsource 'src'\n\tinclude '**/*.java'\n\texclude '**/gen/**'\n\tclasspath = files()\n\t}\n\n所以，基本上这个任务会根据checkstyle.xml和suppressions.xml分析你的代码。通过Android Studio执行它仅仅需要从工具面的CheckStyle来启动它。\n\n![gradle panel](http://vincentbrison.com/wp-content/uploads/2014/07/checkstyle.jpg)\n     How to execute your gradle task checkstyle\n\n启动CheckStyle之后，你讲收到一个报告用于展示在你项目中发现的每个错误。这是非常直接的方式。\n\n如果你想在checkstyle上做更多的配置，可以参考[这篇文档](https://docs.gradle.org/current/dsl/org.gradle.api.plugins.quality.Checkstyle.html)。\n\n###Checkstyle的使用技巧\nCheckstyle会发现大量的问题，特别是在你运用了大量的规则配置，如同你设置了一个非常精确的语法。尽管我通过Gradle使用checkstyle，例如在我进行推送之前，我仍然推荐你为IntellJ/Android Studio使用checkstyle插件(你可以通过Android Studio的工作面板文件/设置/插件直接安装插件)。这种方式下，你可以根据那些为Gradle配置的相同文件在你的工程中使用checkstyle，但是远不止这些，你可以直接在Android Studio中获取带有超链接结果，这些结果通过超链接在你的代码中对应，这是非常有用的(Gradle的这种方式仍然很重要的，因为你可以使用它自动构建系统，如Jenkins)。\n\n##Findbugs\n[![Findbugs](http://findbugs.sourceforge.net/umdFindbugs.png)](http://findbugs.sourceforge.net/)\n\n###简介\n\nFindbugs是否需要一个简介呢？我想它的名称已经让人顾名思义了。“FindBugs使用静态分析方法为出现bug模式检查Java字节码”。FindBugs基本上只需要一个程序来做分析的字节码，所以这是非常容易使用。它能检测到常见的错误，如错误的布尔运算符。FindBugs也能够检测到由于误解语言特点的错误，如Java参数调整（这不是真的有可能因为它的参数是传值）。\n\n###Gradle的形式\n下面的代码向你展示了在你的项目中使用Findbugs的最基本的配置(以Gradle任务为例):\n\n\ttask findbugs(type: FindBugs) {\n\tignoreFailures = false\n\teffort = \"max\"\n\treportLevel = \"high\"\n\texcludeFilter = new File(\"${project.rootDir}/config/quality/findbugs/findbugs-filter.xml\")\n\tclasses = files(\"${project.rootDir}/app/build/classes\")\n \n\tsource 'src'\n\tinclude '**/*.java'\n\texclude '**/gen/**'\n \n\treports {\n\txml.enabled = false\n\thtml.enabled = true\n\txml {\n\tdestination \"$project.buildDir/reports/findbugs/findbugs.xml\"\n\t}\n\thtml {\n\tdestination \"$project.buildDir/reports/findbugs/findbugs.html\"\n\t}\n\t}\n \n\tclasspath = files()\n\t}\n\n它是如此的像一个Checkstyle任务。尽管Findbugs支持HTML和XML两种报告形式，我选择HTML形式，因为这种形式更具有可读性。而且，你只需要把报告的位置设置为书签就可以快速访问它的位置。这个任务也会失败如果发现Findbgus错误失败(同样生成报告)。执行FindBugs任务，就像执行CheckStyle任务（除了任务的名称是“FindBugs”）。\n\n###Findbugs的使用技巧\n\n由于Android项目是从Java项目略有不同，我强烈推荐使用FindBugs过滤器(规则配置)。你可以在这一个例子（例如项目之一）。它基本上忽略了R文件和你的Manifest文件。顺便说一句，由于(使用)FindBugs分析你的代码，你至少需要编译一次你的代码才能够测试它。\n\n##PMD\n\n[![PMD](http://pmd.sourceforge.net/pmd_logo.png)](http://pmd.sourceforge.net/)\n\n###简介\n\n这个工具有个有趣的事实：PMD不存在一个准确的名称。(所以)在官网上你可以发现很有有趣的名称，例如:\n\n* Pretty Much Done\n* Project Meets Deadline\n\n事实上，PMD是一个工作有点类似Findbugs的强大工具，但是(PMD)直接检查源代码而不是检查字节码(顺便说句，PMD适用很多语言)。(PMD和Findbugs)的核心目标是相同的，通过静态分析方法找出哪些模式引起的bug。因此为什么同时使用Findbugs和PMD呢？好吧！尽管Findbugs和PMD拥有相同的目标，(但是)他们的检查方法是不同的。所以PMD有时检查出的bug但是Findbugs却检查不出来，反之亦然。\n\n###Gradle的形式\n下面的代码向你展示了在你的项目中使用PMD的最基本的配置(以Gradle任务为例):\n\n\ttask pmd(type: Pmd) {\n\truleSetFiles = files(\"${project.rootDir}/config/quality/pmd/pmd-ruleset.xml\")\n\tignoreFailures = false\n\truleSets = []\n \n\tsource 'src'\n\tinclude '**/*.java'\n\texclude '**/gen/**'\n \n\treports {\n\txml.enabled = false\n\thtml.enabled = true\n\txml {\n\tdestination \"$project.buildDir/reports/pmd/pmd.xml\"\n\t}\n\thtml {\n\tdestination \"$project.buildDir/reports/pmd/pmd.html\"\n\t}\n\t}\n\t}\n\n就PMD来说，它几乎与Findbugs相同。PMD支持HTML和XML两种报告形式，所以我再次选择HTML形式。我强烈建议你使用自己的通用配置集文件，正如同我在这个例子([check this file](https://github.com/vincentbrison/vb-android-app-quality/blob/master/config/quality/pmd/pmd-ruleset.xml))中一样。所以，你当然应该去看下这些[通用配置集文件](http://pmd.sourceforge.net/pmd-5.1.1/howtomakearuleset.html)。我建议你，因为PMD可比FindBugs更有争议的很多，例如：如果你不声明\"if statement\"或\"if statement\"为空，它基本上会给你警告信息。如果这些规则是正确的，或这对于您的项目(来说是正确的)，我真的认可你和你队友的工作。我不希望程序因为\"if statement\"崩溃，我认为这样程序的可读性很差。执行PMD任务，就像是(执行)CheckStyle任务（除了任务的名称是“PMD”）。\n\n###PMD的使用技巧\n\n我建议你不要使用默认的规则配置集，你需要添加这行代码(已经加上)：\n\n\truleSets = []\n\n否则，因为默认值是这些基本的规则配置集，基本的规则配置集会和你定义的规则集一起执行。所以，如果你的自定义规则集不在那些基本配置集中，他们仍然会执行。\n\n\n##Android Lint\n\n###简介\n\n“Android lint工具是一个静态代码分析工具，它能检查安卓项目源文件的潜在缺陷和优化改进的正确性，安全性，性能，可用性，可访问性和国际化。”\n\n正如官方网站所说，Android Lint是另一种静态分析工具，专门为Android服务。它是非常强大的，能给你大量的建议以提高你的代码质量。\n\n###Gradle的形式\n\n\tandroid {\n\tlintOptions {\n\tabortOnError true\n \n\tlintConfig file(\"${project.rootDir}/config/quality/lint/lint.xml\")\n \n\t// if true, generate an HTML report (with issue explanations, sourcecode, etc)\n\thtmlReport true\n\t// optional path to report (default will be lint-results.html in the builddir)\n\thtmlOutput file(\"$project.buildDir/reports/lint/lint.html\")\n\t}\n\n我建议你使用一个单独的文件来定义哪些配置需要使用和不使用。[这个网站](http://tools.android.com/tips/lint-checks)根据最新的ADT版本定义了全部的配置。我的演示项目中的lint文件包含所有这些规则（ADT 21），包含等级为\"ignore\"的\"severity\":\n\n* IconDensities：这个规则配置确保你定义每个图像资源中的(分辨率)密度（除了ldpi）。\n\n* IconDipSize:这个规则配置确保你为每个dip定义合适的资源(换句话来说，如果你没有为每个density设置相同的图片资源，则不需要重新设置图片大小)。\n\n所以你可以重用这个lint文件并激活你想要的所有规则。执行Android Lint任务，就像执行CheckStyle任务（除了任务的名称是\"lint\"）。\n\n###Android Lint的使用技巧\n\n对于Android Lint没有什么特别的技巧，只需要牢记Android Lint会测试所有配置规则，除了那些等级为“ignore”的“severity”的配置。因此如果发布了新版本ADT下的新配置规则，他们将被检查，而不是忽视。\n\n##实例演示\n\n现在，你有所有的方法为您的项目使用这四个工具。显然，如果我们能同时使用这四个工具会更好。你可以添加你的gradle任务之间的依赖，比如当你执行一个任务，其他任务则是第一个完成后执行。通常在Gradle中，通过让工具具有“check”任务来达到工具之间的相互关系：\n\n\tcheck.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint'\n\n现在，当执行“check” 任务的时候，Checkstyle, Findbugs, PMD, and Android Lint将会同时执行。在你执行/ commiting / pushing / ask merge request 之前进行质量检查是一个很棒的方式。\n\n你可以在[这个Gradle文件](https://github.com/vincentbrison/vb-android-app-quality/blob/master/config/quality.gradle)中找到所有任务的一个完整例子。你可以把所有的质量配置文件和Gradle文件从你看到的演示实例中分开，这些演示的实例把一起都放在“config/quality” 文件夹下。\n\n#总结\n在这篇文章中，利用Gradle对Android使用代码质量检查工具是非常容易。比使用质量工具局部检查您的项目在您自己的计算机上，这些工具可以用于自动构建如Jenkins/Hudson这样的平台，让你自动进行质量检查，同时自动建立过程。执行所有我从CLI展现的测试，如同在Jenkins/Hudson上执行，简单地执行：\n\n\tgradle check\n\n请随时对这篇文章发表评论，或者问任何有关Android的问题。\n"
  },
  {
    "path": "issue-18/拖拽RecyclerView.md",
    "content": "#拖拽RecyclerView\n\n---\n\n> * 原文链接 : [Drag and Swipe with RecyclerView](https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf)\n* 原文作者 : [Paul Burke](https://medium.com/@ipaulpro)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [objectlife](https://github.com/objectlife) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 : 完成\n\n<video loop video autoplay class=\"graf-image\" data-image-id=\"1*4Cqfs-75ibhvC_3BMEIvVA.gif\" data-width=\"1440\" data-height=\"600\"><source src=\"https://d262ilb51hltx0.cloudfront.net/max/2000/1*4Cqfs-75ibhvC_3BMEIvVA.mp4\" type=\"video/mp4\"></video>\n\n\n\n现在有很多使用RecyclerView实现“**拖拽(drag & drop)**”和“**滑动消失(swipe-to-dismiss)**”效果的教程、库、和示例代码。虽然现在有更新的、更好的方式可以实现但是大部分的代码仍旧使用了旧的API*[View.OnDragListener](http://developer.android.com/guide/topics/ui/drag-drop.html)*或者使用Roman Nurik’s *[SwipeToDismiss](https://github.com/romannurik/Android-SwipeToDismiss)*这个库中的处理方式。有一部分使用了新的API，但主要是依赖GestureDetectors 和 onInterceptTouchEvent或者实现的方式很复杂。实际上有非常简单的方式就可以把新特性添加到RecyclerView中。只需要一个类，并且它已经包含在Android Support Library之中。\n\n\n\n###[ItemTouchHelper](https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.html)\n\n\n\nItemTouchHelper是一个非常强大的工具类用于处理当你添加drag&drop 和 swipe-to-dismiss特性到RecyclerView时所关心的那些问题。它是[RecyclerView.ItemDecoration](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html)的子类,意味着可以向已经存在的LayoutManager 和 Adapter(!)中添加任何东西，并且也可以处理已经存在的item animations、type-restricted、drop settling animations甚至更多。\n在这篇文章我将示范一下ItemTouchHelper的简单实现，稍后，在这一系列的文章中我们将延伸扩展更多特性。\n\n\n\n##忽略开头\n\n\n只对代码感兴趣？ Github源码: *[Android-ItemTouchHelper-Demo](https://github.com/iPaulPro/Android-ItemTouchHelper-Demo)*,\nApk下载:*[from here](https://github.com/iPaulPro/Android-ItemTouchHelper-Demo/releases)*.\n\n\n##配置\n\n\n首先我们需要对RecyclerView进行配置，如果没有准备好，请先更新你的build.gradle，添加RecyclerView依赖。\n\n```java\ncompile 'com.android.support:recyclerview-v7:22.2.0'\n\n```\n\n\n在任意RecyclerView.Adapter 和 LayoutManager都可以使用ItemTouchHelper，这篇文章代码的基本文件在这：\n\n[https://gist.github.com/iPaulPro/2216ea5e14818056cfcc](https://gist.github.com/iPaulPro/2216ea5e14818056cfcc)\n\n\n\n\n##使用ItemTouchHelper 和 ItemTouchHelper.Callback\n\n\n为了使用ItemTouchHelper，你必须得先创建*[ItemTouchHelper.Callback](https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.Callback.html)*。这是一个接口用于监听“move” 和 “swipe”的状态。也可用于你要控制所选的view的状态和重写默认动画。如果你只是想使用基本实现你可以使用系统提供的一个帮助类*[SimpleCallback](https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.SimpleCallback.html)*，但是本着学习的目的，我们还是自己来实现。\n\n\n\n实现基本的drag & drop 和 swipe-to-dismiss必须实现以下主要的回调函数:\n\n```java\n\ngetMovementFlags(RecyclerView, ViewHolder)\nonMove(RecyclerView, ViewHolder, ViewHolder)\nonSwiped(ViewHolder, int)\n\n```\n\n\n使用两个帮助函数:\n\n```java\nisLongPressDragEnabled()\nisItemViewSwipeEnabled()\n\n```\n\n\n我们将逐个进行介绍.\n\n```java\n\n@Override\npublic int getMovementFlags(RecyclerView recyclerView, \n        RecyclerView.ViewHolder viewHolder) {\n    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;\n    int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;\n    return makeMovementFlags(dragFlags, swipeFlags);\n}\n\n```\n\n\nItemTouchHelper允许非常简单的判断屏幕事件的走向。必须重写**getMovementFlags()**来说明支持哪个方向的拖拽和滑动。使用帮助类**ItemTouchHelper.makeMovementFlags(int, int)**来管理returned标志。这样就可以在同一个方向进行拖拽和滑动了。\n\n```java\n\n@Override\npublic boolean isLongPressDragEnabled() {\n    return true;\n}\n\n```\n\n\n\nItemTouchHelper可以只支持drag而不支持swipe(or vice versa)，所以你必须明确指出你希望支持的类型。为了支持长按 RecyclerView item 对其进行拖动，**isLongPressDragEnabled()**函数实现应该返回true. 此外在开始拖拽时**ItemTouchHelper.startDrag(RecyclerView.ViewHolder)**将会被调用。这个我们稍后再讨论。\n\n```java\n\n@Override\npublic boolean isItemViewSwipeEnabled() {\n    return true;\n}\n\n```\n\n\n如果在viwe中可以随意滑动，**isItemViewSwipeEnabled()**函数返回true. 此外手动拖动时**ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)**会被调用。\n\n\n**onMove()** 和 **onSwiped()**负责主要数据的更新。故首先我们需要创建一个允许传递事件回调的接口。\n\n\n\n```java\n\npublic interface ItemTouchHelperAdapter {\n\n    void onItemMove(int fromPosition, int toPosition);\n\n    void onItemDismiss(int position);\n}\n\n```\n\n*[ItemTouchHelperAdapter.java Gist](https://gist.github.com/iPaulPro/5d43325ac7ae579760a9)*\n\n\n最简单的方式就是下面这样，让我们的RecyclerListAdapter实现ItemTouchHelperAdapter。\n\n```java\n\npublic class RecyclerListAdapter extends \n        RecyclerView.Adapter<ItemViewHolder> \n        implements ItemTouchHelperAdapter {\n// ... code from [gist]()\n\n\n```\n\n```java\n\n@Override\npublic void onItemDismiss(int position) {\n    mItems.remove(position);\n    notifyItemRemoved(position);\n}\n\n@Override\npublic void onItemMove(int from, int to) {\n    Collections.swap(mItems, from, to);\n    notifyItemMoved(from, to);\n}\n\n```\n\n\n**notifyItemRemoved()** 和 **notifyItemMoved()**的调用是非常重要，因此Adapter是可以感受到这些变化的,同样重要的是要注意当我们改变item的Position的时候view的index也时刻在改变，**并不是在“drop”事件结束后再改变**。\n\n\n\n现在我们回过头来创建我们自己的**SimpleItemTouchHelperCallback**并且必须override onMove() 和 onSwiped()。首先添加一个构造函数和Adapter变量。\n\n```java\n\nprivate final ItemTouchHelperAdapter mAdapter;\n\npublic SimpleItemTouchHelperCallback(\n        ItemTouchHelperAdapter adapter) {\n    mAdapter = adapter;\n}\n\n```\n\n之后 override the remaining events and notify the adapter:\n\n```java\n\n@Override\npublic boolean onMove(RecyclerView recyclerView, \n        RecyclerView.ViewHolder viewHolder, \n        RecyclerView.ViewHolder target) {\n\n```\n\n```java\n\nmAdapter.onItemMove(viewHolder.getAdapterPosition(), \n            target.getAdapterPosition());\n    return true;\n}\n\n```\n\n```java\n\n@Override\npublic void onSwiped(RecyclerView.ViewHolder viewHolder, \n        int direction) {\n    mAdapter.onItemDismiss(viewHolder.getAdapterPosition());\n}\n\n```\n\n\n写完之后回调类应该像下面这样：\n\n```java\n\npublic class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {\n \n    private final ItemTouchHelperAdapter mAdapter;\n \n    public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {\n        mAdapter = adapter;\n    }\n    \n    @Override\n    public boolean isLongPressDragEnabled() {\n        return true;\n    }\n \n    @Override\n    public boolean isItemViewSwipeEnabled() {\n        return true;\n    }\n \n    @Override\n    public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {\n        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;\n        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;\n        return makeMovementFlags(dragFlags, swipeFlags);\n    }\n \n    @Override\n    public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, \n            ViewHolder target) {\n        mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());\n        return true;\n    }\n \n    @Override\n    public void onSwiped(ViewHolder viewHolder, int direction) {\n        mAdapter.onItemDismiss(viewHolder.getAdapterPosition());\n    }\n \n}\n\n```\n\n\n随着我们Callback的完成，再创建ItemTouchHelper并且调用**attachToRecyclerView(RecyclerView)** (e.g. in [MainFragment.java](https://gist.github.com/iPaulPro/2216ea5e14818056cfcc#file-mainfragment-java)):\n\n\n```java\n\nItemTouchHelper.Callback callback = \n    new SimpleItemTouchHelperCallback(adapter);\nItemTouchHelper touchHelper = new ItemTouchHelper(callback);\ntouchHelper.attachToRecyclerView(recyclerView);\n\n```\n\n\n程序运行结果：\n\n<video loop video autoplay class=\"graf-image\" data-image-id=\"1*FdJbZnF5I-iOw0wgiuVJGQ.gif\" data-width=\"360\" data-height=\"508\">\n<source src=\"https://d262ilb51hltx0.cloudfront.net/max/1600/1*FdJbZnF5I-iOw0wgiuVJGQ.mp4\" type=\"video/mp4\">Your browser does not support the video tag.</video>\n\n\n##结束语\n\n\n这是一种极其简单的对ItemTouchHelper的实现写法。但是应该清楚的知道，使用RecyclerView实现基本的拖拽和滑动消失效果是不需要引入三方library的。在下一篇中我们将对拖拽进行更加丰富的控制实现。\n\n\n\n##源代码\n\n[Android-ItemTouchHelper-Demo](https://github.com/iPaulPro/Android-ItemTouchHelper-Demo).\n\n\n关注我: [Google+](http://google.com/+PaulBurke) and [Twitter](https://twitter.com/ipaulpro)\n\n\n© 2015 Paul Burke\n\n\n\n"
  },
  {
    "path": "issue-18/用组合代替继承能为Activity带来什么.md",
    "content": "#用组合代替继承能为 Activity 带来什么\n\n---\n\n> * 原文链接 : [Composition over Inheritance，What it means for your Activities](https://plus.google.com/+JoshBrown42/posts/FzNghPbKk2s)\n* 原文作者 : [Josh Brown](https://plus.google.com/100411279961902366927)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n事实上我们在很多 Java 进阶书籍上看到过“开发时应该更倾向于选择组合而不是继承”的建议，为什么建议我们**更倾向于**而不是**完全代替**呢，因为当类 A 能完全代替另一个类 B（我们想让 B 成为 A 的父类）时，我们就应该使用继承，如果 A 仅仅是和 B 有着某些共同的**行为**，是不应该使用继承的（更多的讨论[戳我](http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance)）。然而，在我阅读别人的源码时，滥用继承的情况实在是太多了，多少人创建了一个 BaseActivity 后，就让所有 Activity 继承它，在子 Activity 中实现业务逻辑。\n\n而且这样做问题大大的有，最鲜活的例子就是：Joe Newguy 加入到我们组，并实现了 ShinyFeatureActivity，那会组里没有任何规定强迫他必须让 ShinyFeatureActivity 继承于 BaseActivity，但他还是这么干了……万幸我们在 Code Review 时发现了这个问题。此外，如果每一个 Activity 都继承于 BaseActivity，在某些情况下，你可能要继承的是其他 Activity（例如：PreferenceActivity、ListActivity）。尽管大部分 Activity 的子类都有相应的 Fragment 代替，但还是有一部分是没有对应 Fragment 的，某些库仍然需要相应的 Activity 子类。\n\n有些更潜在的问题是：有时候某些 Activity 需要这些行为，其他的 Activity 需要其他行为，而 Java 并不支持多继承，这就意味着我们得将行为有交集的 Activity 的所有行为放到一个独立的类里面。但这样做会降低可维护性，甚至带来性能影响。\n\n事实上，我们会这么干的动机很简单：代码复用。确实，代码复用很重要。而我们大部分公共逻辑需要在 Activity 生命周期的某一环实现。但 Application.ActivityLifecycleCallbacks 是一个让人很蛋疼的玩意，而且可能需要在 Application.onCreate() 方法里注册它，最讽刺的是：我们想尽办法避免在 Application.onCreate() 方法里注册它……\n\n这也是无绑定 View 的 Fragment 的由来了，当无数 Android 开发者把 Fragment 看作 UI 组件时，事实上 Fragment 更像生命周期的组件。为什么要说这些 Fragment 是无绑定 View 的呢？因为在这些开发者的手里，Fragment 的 onCreateView() 方法既没有被重写，也没有返回 null。本质上，Fragment 就是一个能够处理或操控事件的组件，而它自身没有对应的 View。\n\n为了区分无绑定 View 的 Fragment 和面向 View 的 Fragemnt，我在命名时会将无绑定 View 的 Fragemnt 命名为 ** XXHelper**，其他的就命名为**XXFragment**。例如，AnalyticsHelper 代表的就是关联分析逻辑的 Fragment，而 HeaderFragment 则显示了一个标题栏。当然了，大家可以尝试这么干，也可以无视我的建议，我自己是感觉满有用的哈～\n\n因为这些无绑定 View 的 Fragment 里面没有 UI 组件，也就意味着在这些 Fragment 里我们不需要考虑初始化布局所需的 Layout-ID，或者是 View 需要的动画，那么我们完全可以用工厂模式开发这些 Fragment，提高工厂方法的易用性和可操控性。就这一点来说，他们还能完成添加 Fragment 自身的操作，我创建了一个 [Gist](https://gist.github.com/keyboardr/ddf35148ca2c1a2bfbde) 来为大家介绍要怎么做到，有兴趣的话可以点进去看看哈。如果使用 Android-Studio 进行开发的话，你可以将它添加到设置的 `File and Code Templates` 选项中，然后当你创建一个新的类时，在 **Kind** 下拉选项中选择它。\n\n将 FooHelper 添加到它的父类中非常简单，只要调用 FooHelper.attach(this) 就可以了。但如果相应的父类没有实现 FooHelper 的回调接口的话会出现编译错误，此外，如果 attach() 方法已经被调用了，该方法的返回值会是之前的 Fragment。这个 Gist 包含对 Fragment 和 Activity 的重载，而且将它们转换为使用支持的 Fragement 和 FragmentActivity，其中的意义非常中大。而且它还包含了 FragmentUtils.getParent() 的简化版 —— getParent() 方法（详情[戳我](https://gist.github.com/keyboardr/5455206)）。\n\n显然，无绑定 View 的 Fragment 比 BaseActivity 好用得多，它们很好地封装了需要生命周期回调（或者是 onActivityResult()，FragmentManager）的处理方法。最棒的是，我们可以将 Activity 共用的某些逻辑分解到职责单一的模块组件中，Activity 需要什么逻辑，就选择什么模块使用。如果你的 Activity 大部分都需要许多相同的模块，那么你没有理由不实现 CommonComponentsHelper 用于处理这些共用逻辑，而且你也不需要把 Activity 的所有共用依赖放在一个基类中。\n"
  },
  {
    "path": "issue-18/还在用Toast？你Out啦，试试Snackbar吧！.md",
    "content": "#还在用Toast？你Out啦，试试Snackbar吧！\n\n---\n\n> * 原文链接 : [Welcome Snackbar, Goodbye Toast!](http://www.technotalkative.com/part-2-welcome-snackbar-goodbye-toast/)\n* 原文作者 : [Paresh Mayani](http://en.gravatar.com/pareshnmayani)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [objectlife](https://github.com/objectlife) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 : 完成\n\n\n欢迎来到Android design support library系列教程第二部分，第一部分我们讨论了[Floating action button](http://www.technotalkative.com/part-1-floating-action-button/)的一些attributes 和 常见issues。\n\n\n今天我们讨论一下另一个组件\"Snackbar\".\n\nWelcome Snackbar, Goodbye Toast!\n\n> “Providing lightweight, quick feedback about an operation is a perfect opportunity to use a snackbar.”\n\n\nSnackbar是design support library中另一个组件，使用Snackbar我们可以在屏幕底部(大多时候)快速弹出消息，它和Toast非常相似，但是它更灵活一些。\n\n\n* 当它显示一段时间后或用户与屏幕交互时它会自动消失。\n* 可以自定义action-可选操作。\n* swiping it off the screen可以让FAB消失\n* 它是context sensitive message(自己理解吧),所以这些消息是UI screen的一部分并且它是显示在所有屏幕其它元素之上(屏幕最顶层)，并不是像Toast一样覆盖在屏幕上。\n* 同一时间只能显示一个snackbar。\n\nSnackbar基本上继承了和Toast一样的方法和属性，例如LENGTH_LONG 和 LENGTH_SHORT用于设置显示时长。\n\n\n##如何使用\n\n\n我们看一下如何使用:\n\n```java\n\nSnackbar.make(view, message, duration)\n       .setAction(action message, click listener)\n       .show();\n\n```\n\n###方法:\n\n* *<font color=\"red\">make()</font>*  – 生成Snackbar消息\n* *<font color=\"red\">setAction()</font>*  – 设置action\n* *<font color=\"red\">make()</font>*  – 显示Snackbar消息\n\n###属性:\n\n* make()方法的第一个参数是一个view, snackbar会试着寻找一个父view来hold这个view. Snackbar将遍历整个view tree 来寻找一个合适的父view，它可能是一个coordinatorLayout也可能是window decor’s content view,随便哪一个都行。\n* 正如上面所提到，duration参数和Toast中的duration参数类似，只能是LENGTH_SHORT 或 LENGTH_LONG，不能是其它任何随机数。\n\n\n###示例:\n\n```java\n\nSnackbar.make(rootlayout, \"Hello SnackBar!\", Snackbar.LENGTH_SHORT)\n       .setAction(\"Undo\", new View.OnClickListener() {\n           @Override\n           public void onClick(View v) {\n               // Perform anything for the action selected\n           }\n       })\n       .show();\n\n```\n\n\n部局文件中rootlayout是framelayout并且添加了FAB(Floating action button)，看一下FAB示例：\n\n点击FAB查看结果：\n\n\n![](http://www.technotalkative.com/wp-content/uploads/2015/06/Snackbar-framelayout1.gif)\n\n程序没问题，但是对于用户体验来说并不太好，它应该向上移一些，如下图所示：\n\n> Having a CoordinatorLayout in your view hierarchy allows Snackbar to enable certain features, such as swipe-to-dismiss and automatically moving of widgets like FloatingActionButton.\n\n\n我们在该系列文章的下一部分讨论CoordinatorLayout。\n\n![](http://www.technotalkative.com/wp-content/uploads/2015/06/Snackbar-with-CoordinatorLayout1.gif)\n\n\n###配置Snackbar可选操作\n\n\n我们可以使用额外的可选操作来配置snackbar，比如*<font color=\"red\">setActionTextColor</font>* 和 *<font color=\"red\">setDuration</font>*:\n\n```java\n\nSnackbar.make(rootlayout, \"Hello SnackBar!\", Snackbar.LENGTH_SHORT)\n       .setAction(\"Undo\", new View.OnClickListener() {\n           @Override\n           public void onClick(View v) {\n               // Perform anything for the action selected\n           }\n       })\n       .setActionTextColor(R.color.material_blue)\n       .setDuration(4000).show();\n       \n```\n\n\n下载示例代码：[https://github.com/PareshMayani/DesignSupportLibraryExamples](https://github.com/PareshMayani/DesignSupportLibraryExamples)\n\n参考文档：\n[https://developer.android.com/reference/android/support/design/widget/Snackbar.html](https://developer.android.com/reference/android/support/design/widget/Snackbar.html)\n\n\n###总结\n\n在这部分文章中，我们讨论了Snackbar，它和TOAST很相似，但是它更灵活一些。Snackbar中可以定义action，当用户与屏幕交互时或者显示一段时间后会自动消失。\n\n通过 CoordinatorLayout我们可以看到更多的effects 和 behaviours，在该系列文章中后续会讨论它。\n\n\n"
  },
  {
    "path": "issue-19/Android-UI自动化测试.md",
    "content": "#Android UI 自动化测试\n\n* 原文链接 : [Automating User Interface Testing on Android](http://code.tutsplus.com/tutorials/automating-user-interface-testing-on-android--cms-23969)\n* 原文作者 : [Ashraff Hathibelagal](http://tutsplus.com/authors/ashraff-hathibelagal)\n* 译文出自 : [tuts+](http://code.tutsplus.com/)\n* 译者 : [Doris](https://github.com/DorisMinmin)\n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n##介绍\nAndroid测试支持库包含**UI自动化模块**，它可以对Android应用进行自动黑盒测试。在API Level 18中引入了自动化模块，它允许开发者在组成应用UI的控件上模仿用户行为。\n\n在这个教程中，我将展示如何使用此模块来创建和执行一个基本的UI测试，选择默认的计算器模块进行测试。\n\n##先决条件\n在使用前，需要具备以下条件：\n\n  1. 最新版本的[Android Studio](https://developer.android.com/sdk/index.html)\n  2. 运行Android 4.3或者更高版本的设备或者虚拟器\n  3. 理解[JUnit](http://junit.org/)\n\n##1. 安装依赖库\n\n工程中使用UI自动化模块，需要编辑你的工程下*app*目录下的文件*build.gradle*，添加如下信任：\n\n```xml\n  androidTestCompile 'com.android.support.test:runner:0.2'                       \n  androidTestCompile 'com.android.support.test:rules:0.2'                        \n  androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.0'\n```\n现在屏幕上应该有*Sync Now*按钮了，但点击它时，会看到如下错误信息：\n\n![p1](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest1.png)\n\n点击 **Install Repository and sync project** 链接来安装 **Android Support Repository**。\n\n如果使用的是库**appcompat-v7** 且其版本号是**22.1.1**，你需要添加如下依赖以确保应用本身和测试应用都使用相同版本的```com.android.support:support-annotations```:\n\n```xml\n   androidTestCompile 'com.android.support:support-annotations:22.1.1'\n```\n接下来，由于Android Studio自身的一个bug，你需要通过 ```packagingOptions``` 执行一个名为 **LICENSE.txt** 的文件。这个执行失败的话，在运行测试时将引起如下错误：\n\n```\n   Execution failed for task ':app:packageDebugAndroidTest'.                                                                                   \n   Duplicate files copied in APK LICENSE.txt                                                                                                   \n                                                                                                                                               \n   File 1: ~/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.1/860340562250678d1a344907ac75754e259cdb14/hamcrest-core-1.1.jar  \n   File 2: ~/.gradle/caches/modules-2/files-2.1/junit/junit-dep/4.10/64417b3bafdecd366afa514bd5beeae6c1f85ece/junit-dep-4.10.jar               \n```\n在你的**build.gradle**文件底部增加如下代码段：\n\n```Java\n   android {                                       \n       packagingOptions {                          \n           exclude 'LICENSE.txt'                   \n       }                                           \n   }                                               \n```\n\n##2、创建测试类\n\n创建一个新的测试类，```CalculatorTester```，通过在 **androidTest** 目录下创建名为 **CalculatorTester.java** 的文件实现。创建的UI自动化测试用例，必须继承自```InstrumentationTestCase```。\n\n![P2](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest2.png)\n\n按 **Alt+Insert**后选择 **SetUp Method** 来重写```setUp```方法。\n\n![P3](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest3.png)\n\n再次按 **Alt+Insert** 后选择 **Test Method** 来生成新的测试方法，命名为```testAdd```。到此```CalculatorTester```类定义如下：\n\n```Java\n  public class CalculatorTester extends InstrumentationTestCase{\n                                                                \n      @Override                                                 \n      public void setUp() throws Exception {                    \n                                                                \n      }                                                         \n                                                                \n      public void testAdd() throws Exception {                  \n                                                                \n      }                                                         \n  }                                                                    \n```\n\n##3、查看Launcher UI\n\n连接你的Android设备到电脑商，点击home按键，进入主界面。\n\n返回到你的电脑，使用文件管理或者终端浏览你安装Android SDK的目录，进入到 **tools** 目录下，点击 **uiautomatorviewer** 。这个会启动 **UI Automater Viewer** ，你将看到如下界面：\n\n![P4](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest4.png)\n\n点击上方手机图标来获取Android设备截屏。注意到此时获取到的截屏是可交互的。点击下面的Apps图标。在右方的 **Node Detail** 区域，你就可以看到根据选择图标的不同显示不同的详细信息，如下图所示：\n\n![P5](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest5.jpg)\n\n与屏幕上的应用交互，UI自动化测试需要能唯一识别它们。在这个教程中，可以使用应用的```text```、```content-desc```或者```class```字段来唯一的区分。\n\n从上图可以看到Apps图标没有```text```字段，但有```content-desc```。记下它的值，后面将用到这个值。\n\n拿起Android设备，触摸Apps图标，进入设备安装的所有应用界面。使用 **UI Automater Viewe** 获取另外一张屏幕截图。因为要写一个计算器应用的测试，点击计算器图标查看详细界面。\n\n![P6](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest6.jpg)\n\n这次```content-desc```是空的，但是```text```的值为**Calculator**，同样记住这个值。\n\n如果你的Android设备运行不同的主界面或者不同的Android版本，界面和显示的细节会有所不同。这意味着后续代码中需要做一些修改，以匹配你的操作系统。\n\n##4、准备测试环境\n\n返回到Android Studio，给```setUp```方法中添加代码。如同其名字，```setUp```方法是用来准备测试环境的。换句话说，这个方法是在真正测试之前指定具体需要执行什么动作的。\n\n现在需要写代码来模拟刚才在Android设备上执行的几个动作：\n1、按home键进入主界面\n2、按Apps图标进入应用界面\n3、点击计算器图标启动它\n\n在你的类中声明类型为```UiDevice```的变量```device```。它代表你的Android设备，后续使用它来模拟用户行为。\n\n```Java\n  private UiDevice device;\n```\n\n在```setUp```方法中，通过调用```UiDevice.getInstance method```来初始化```device```，传递```Instrumentation```实例，如下所示：\n\n```Java\n  device = UiDevice.getInstance(getInstrumentation());\n```\n\n模拟点击设备home键，需要调用```pressHome```方法。\n\n```Java\n  device.pressHome();\n```\n\n接下来，需要模拟点击Apps图标的动作。不能立即做这个动作，因为Android设备需要一个反应时间来加载界面。如果在屏幕显示出来之前执行这个动作就会引起运行时异常。\n\n等待一些事情发生时，需要调用```UiDevice```实例的```wait```方法。等待Apps图标显示到屏幕，使用```Until.hasObject```方法。\n\n识别Apps图标需要使用```By.desc```方法并传递值为**Apps**的参数。你还需要指定最长等待时间，单位为毫秒。此处设置为3000。\n至此形成如下代码段：\n\n```Java\n  // Wait for the Apps icon to show up on the screen\n  device.wait(Until.hasObject(By.desc(\"Apps\")), 3000);\n```\n\n要获取Apps图标的引用，需要使用```findObject```方法。一旦有了Apps图标的引用，就可以调用```click```方法来模拟点击动作了。\n\n```Java\n  UiObject2 appsButton = device.findObject(By.desc(\"Apps\"));\n  appsButton.click();\n```\n\n和前面一样，我们需要等待一些时间，保证计算器图标显示到屏幕上。在之前的步骤中，我们看到可以通过```text```字段唯一的识别计算器图标。我们调用```By.text```方法来找到图标，传递参数为```Calculator```。\n\n```Java\n  // Wait for the Calculator icon to show up on the screen\n  device.wait(Until.hasObject(By.text(\"Calculator\")), 3000);\n```\n\n##5、检查计算器UI\n\n在你的Android设备上启动计算器应用，使用 **UI Automater Viewer** 来查看显示。获取到一个截屏后，点击不同的按钮来观察使用何值可以唯一的区分它们。\n\n在本次测试用例中，使用计算器计算 **9+9=** 的值并确认结果是否为 **18**。这意味着你需要知道怎么区分按键 **9**、**+** 和 **=**。\n\n![P7](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest7.jpg)\n\n在我的设备上，如下是我收集到的信息：\n\n1. 数字按键匹配```text```值\n2. **+** 和 **=** 使用```content-desc```值，分别对应 **plus** 和 **equals**\n3. 返回值显示在```EditText```控件中\n\n如果你使用不同版本的计算器应用，请注意这些值有可能不一样。\n\n##6、创建测试类\n\n在前面几步操作中，你已经学会了使用```findObject```方法通过```By.text```或者```By.desc```来获取屏幕上不同对象的引用。还学会了通过```click```方法来模拟点击对象的动作。下面的代码使用这些方法来模拟 **9+9=**。添加这些到类```CalculatorTester```的方法```testAdd```中。\n\n```Java\n  // Wait till the Calculator's buttons are on the screen        \n  device.wait(Until.hasObject(By.text(\"9\")), 3000);              \n                                                                 \n  // Select the button for 9                                     \n  UiObject2 buttonNine = device.findObject(By.text(\"9\"));        \n  buttonNine.click();                                            \n                                                                 \n  // Select the button for +                                     \n  UiObject2 buttonPlus = device.findObject(By.desc(\"plus\"));     \n  buttonPlus.click();                                            \n                                                                 \n  // Press 9 again as we are calculating 9+9                     \n  buttonNine.click();                                            \n                                                                 \n  // Select the button for =                                     \n  UiObject2 buttonEquals = device.findObject(By.desc(\"equals\")); \n  buttonEquals.click();                                          \n```\n\n现在就等待运行结果。此处不能使用```Until.hasObject```，因为包含计算结果的```EditText```已经显示在屏幕上了。取而代之，我们使用```waitForIdle```方法来等待计算完成。同样，最长等待时间是3000毫秒。\n```Java\ndevice.waitForIdle(3000);\n```\n使用```findObject```和```By.clazz methods```方法获取```EditText```对象的引用。一旦有了此引用，就可以调用```getText``` 方法来确定计算结果是否正确。\n\n```Java\n  UiObject2 resultText = device.findObject(By.clazz(\"android.widget.EditText\"));\n  String result = resultText.getText();\n```\n最后，使用```assertTrue```来检验范围值是否为**18**。\n\n```Java\n  assertTrue(result.equals(\"18\"));\n```\n测试到此结束。\n\n##6、执行测试\n\n执行测试，需要在Android Studio的工具栏中选择```CalculatorTester```，点击它右方的**play**按钮。\n\n![P9](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest9.png)\n\n一旦编译结束，测试就成功运行完整。当测试运行时，在你的Android设备上就会看到UI自动化运行界面。\n\n![P10](http://7xk12r.com1.z0.glb.clouddn.com/Android_UI_autotest10.png)\n\n##总结\n\n在这篇教程中，我们学会了如何使用UI自动化测试模块和 **UI Automater Viewer** 来创建用户界面测试。你也看到了使用Android Studio执行测试是如此简单。虽然我们测试了一个相对简单的应用，但可以将从中学到的概念用到几乎所有Android应用的测试中。\n\n你可以在[Android 开发者网站中](http://developer.android.com/tools/testing-support-library/index.html) 学习更多关于测试支持库的知识。"
  },
  {
    "path": "issue-19/RxJava-Observables单元测试.md",
    "content": "RxJava Observables单元测试\n---\n> * 原文链接 : [Unit Testing RxJava Observables](https://medium.com/ribot-labs/unit-testing-rxjava-6e9540d4a329)\n* 原文作者 : [Iván Carballo](https://medium.com/@ivanc)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [dengshiwei](https://github.com/dengshiwei) \n* 校对者 : [chaossss](https://github.com/chaossss)\n* 状态 : 完成\n\n##RxJava Observables单元测试\n\nRxJava是一个非常棒的类库，但是它不容易上手，在这里，我们列出了不同的方法来对Observables进行单元测试。\n\n在[ribot](http://ribot.co.uk/)时，我们几个月前开始使用RxJava，而现在它已经成为我们开发的安卓应用的核心架构元素。使用RxJava有很多益处，但是学习(使用)的过程是曲折的，很多时候，我们发现我们试图让自己的头脑中环绕着那些解释操作过程如何工作的“美丽”蓝图。\n\n改变我们架构的第一步就是改变那些定义在数据层的方法，让他们返回Observables。下一个问题是：我们如何对此进行单元测试？\n\n###丑陋的方式\n\n我们头脑中首先想到的是以同样的方式简单地描述我们的外部测试结果，然后将结果保存在一个全局变量，可以在以后进行断言。例如，假设我们有一个方法在一个database helper类中，负责加载用户对象。\n\n```\npublic Observable<User> loadUser() {\n    ...\n}\n```\n\n然后，针对这个方法的测试就会如同下面这样：\n\n```\n\tUser mUser;\n\t@Test\n\tpublic void shouldLoadUser() throw Exception {\n    databaseHelper.loadUser()\n        .subscribe(new Action1<User>() {\n            @Override\n            public void call(User user) {\n                mUser = user;\n            }\n         });\n    assertNotNull(mUser);\n\t}\n```\n\n默认情况下，这段代码将会执行，因为Observable会在同一个线程上执行。\n因此，在结果设置为全局变量后断言会一直执行。\n\n###更好的方式\n我们很快意识到先前的解决方式是不完美的。尽管先前的方法能够工作，(但是)它需要大量的模版代码。所以，我们决定创建一个类，该类提供一个名称为subscribeAssertingThat()的静态方法。这个类允许我们subscribe 一个observable，保存测试结果并且以一种清晰的方式执行一些断言。例如：\n\n```\n\t@Test\n\tpublic void shouldLoadTwoUsers() throw Exception {\n  \t subscribeAssertingThat(databaseHelper.loadUser())\n       .completesSuccessfully()\n       .hasSize(2)\n       .emits(user1, user2)\n\t}\n```\n\n通过100行左右的代码，我们称为RxAssertions的这个类使我们的测试可读性和可写性更好。你可以在[这里](https://gist.github.com/ivacf/874dcb476bfc97f4d555)找到RxAssertions类的代码。\n\n###标准方式\n后来，我们发现RxJava 提供了一个称为[TestSubscriber](http://reactivex.io/RxJava/javadoc/rx/observers/TestSubscriber.html#TestSubscriber%28rx.Subscriber%29)的subscriber专用类型。\n\nTestSubscriber由各种各样的[Subscriber](http://reactivex.io/RxJava/javadoc/rx/Subscriber.html)(组成)，你可以用于单元测试，执行断言，检查接收到的事件，或封装mocked类型的Subscriber。\n\n与第二个解决方案类似，测试subscriber允许你通过结果执行断言。例如：\n\n```\n\t@Test\n\tpublic void shouldLoadTwoUsers() throw Exception {\n  \t \tTestSubscriber<User> testSubscriber = new TestSubscriber<>();\n   \t\tdatabaseHelper.loadUser().subscribe(testSubscriber);\n  \t \ttestSubscriber.assertNoErrors();\n   \t\ttestSubscriber.assertReceivedOnNext(Arrays.asList(user1, user2))\n\t}\n```\n\n我们还没有频繁的使用TestSubscriber，但你可以看到上面的代码是相当优雅和可读。除了不同的断言，通过 Observable调用getOnNextEvents()方法，它也允许你恢复整个发现的问题列表。\n\n当使用TestSubscriber时，你会发现链接的断言是不可能的，因为断言方法不返回TestSubscriber。这个能力将是一个很好的改善(方向)。\n\n###总结\nTestSusbcriber可能是目前最好的选择，因为它是RxJava codebase的一部分，它会不断的改进以及将新功能合并。能够了解别的开发者如何进行Observables的单元测试时很棒的。除了那些我在这里提到的其他方法，你还有什么方法(用于测试)？\n\n"
  },
  {
    "path": "issue-19/gradle技巧之语法浅谈.md",
    "content": "gradle技巧之语法浅谈\n---\n\n> * 原文链接 : [Gradle tip #2: understanding syntax](http://trickyandroid.com/gradle-tip-2-understanding-syntax/)\n* 原文作者 : [Pavlo Dudka](http://trickyandroid.com/#blog)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [FTExplore](https://github.com/FTExplore) \n* 校对者 : [chaossss](https://github.com/chaossss)\n* 状态 :  完成 \n\n\n# 回顾\n\n在上一篇的博文(Gradle tip #2 : Tasks)中,我们讨论了gradle构建的基本单位Task. 并且介绍了构建过程的各个阶段及其生命周期.而本文会重点介绍gradle的语法.只有具备了gradle\n的相关语法知识,才会大幅度的提高对于阅读、学习或者编写gradle脚本的效率,正所谓\"磨刀不误砍柴工\"是也.\n\n# 引言\n\ngradle 是groovy语言实现的构建工具. groovy是运行在jvm平台的一门敏捷开发语言.其语法和java有诸多类似之处,然而其具备一些java没有的概念需要读者细细体会.下面会详细的介绍\ngroovy的基本语法,当然如果您已经对groovy的语法有了一定的了解.可以直接跳过这一小节.\n\n## 1、闭包的基本语法\n\n闭包是groovy中最重要的概念之一. 简单地说闭包(Closures)是一段代码块. 这个代码块可以接受参数并具有返回值. 有一点要非常注意的是, 闭包往往不是在需要使用的时候才写出来\n这么一段代码(就像Java的匿名类那样), 通过def 关键字可以声明一个变量代表一个闭包,然后在需要的时候直接使用该变量即可,多说无益,请看如下的例子:\n\n#### 一个简单的hello world闭包:\n\n```\ndef myClosure = { println 'Hello world!' }\n\n//execute our closure\nmyClosure()\n```\n\noutput: Hello world!\n\n#### 如下是一个接受参数的闭包的例子:\n\n```\ndef myClosure = {String str -> println str }\n\n//execute our closure\nmyClosure('Hello world!')\n```\n\noutput: Hello world!\n\n#### 如果闭包只接受一个参数,这个参数在代码块中可以用it代替:\n\n```\ndef myClosure = {println it }\n\n//execute our closure\nmyClosure('Hello world!')\n```\n\noutput: Hello world!\n\n#### 如下是接受多个参数的闭包的例子:\n\n```\ndef myClosure = {String str, int num -> println \"$str : $num\" }\n\n//execute our closure\nmyClosure('my string', 21)\n```\n\noutput: my string : 21\n\n#### 闭包里面的参数类型可以省略不写,例子如下:\n\n```\ndef myClosure = {str, num -> println \"$str : $num\" }\n\n//execute our closure\nmyClosure('my string', 21)\n```\n\noutput: my string : 21\n\n#### 闭包还有一个比较酷的写法就是,可以直接调用context里面的变量,默认的context就是创建这个闭包的类(class) 例子如下:\n\n```\ndef myVar = 'Hello World!'\ndef myClosure = {println myVar}\nmyClosure()\n```\n\noutput: Hello world!\n\n#### 上面提到了闭包可以直接调用context的变量,这个context可以通过setDelegate()方法来改变,极大的增加了闭包的灵活性！\n\n```\ndef myClosure = {println myVar} //I'm referencing myVar from MyClass class\nMyClass m = new MyClass()\nmyClosure.setDelegate(m)\nmyClosure()\n\nclass MyClass {\n    def myVar = 'Hello from MyClass!'\n}\n\n```\n\noutput: Hello from MyClass!\n\n## 2、闭包可以作为参数进行传递\n\n在groovy中,将闭包作为参数传递进函数,是将逻辑进行分离解耦的重要手段.在上述的例子中,我们已经尝试了如何将闭包作为参数进行传递.下面我们总结一下传递闭包的方法:\n\n#### 1 接受一个参数的函数\nmyMethod(myClosure)\n#### 2 如果函数只接受一个参数,括号可以忽略\nmyMethod myClosure\n#### 3 可以将闭包以插入语的形式创建\nmyMethod {println 'Hello World'}\n#### 4 函数接受两个参数\nmyMethod(arg1, myClosure)\n#### 5 接受两个参数,同样可以用插入语创建闭包\nmyMethod(arg1, { println 'Hello World' })\n#### 6 如果存在多个参数,且最后一个参数是闭包,闭包可以不写在括号内\nmyMethod(arg1) { println 'Hello World' }\n\n\n细心的朋友们是不是觉得上述的六种用法中,第三条和第六条很眼熟？很像gradle中的scripts了？\n\n# Gradle\n\n在知道了groovy的基本语法(尤其是闭包)之后,下面我们就以一个简单的gradle 脚本作为例子具体感受一下:\n\n```\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.2.3'\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n```\n\n结合前文的例子,我们可以很容易的理解到,buildscript是一个接受闭包作为参数的函数,这个函数会在编译的时候被gradle调用.这个函数的定义就类似于:def buildscript(Closure closure).  而allprojects 同理也是一个接受闭包作为参数的函数.\n\n那么问题来了,这些函数具体会在什么时候被gradle调用呢?要回答这个问题就需要介绍另一个知识点：Project\n\n# Project\n\n在这里,我觉得逐句翻译作者查阅文档的步骤没有太大的意义，我自己总结了一下作者的概念如下：\n\n理解gradle配置文件中的script如何调用的关键就是理解project的相关概念.在gradle执行某个\"任务\"的时候,会按照各个task的依赖关系来依次执行. 而执行这些task的对象就是Project.说的在通俗一些,project就是你希望gradle为你做的事情,而要完成这些事情,需要将事情分成步骤一步一步的做,这些步骤就是task.\n\n# Script blocks\n\n通过前文的学习,我们已经很清楚的了解到scipt block就是一段接受闭包的函数,这些函数会被Project调用,默认的情况下,gradle 已经准备\n好了很多script用于我们对项目进行配置,例如buildScript{} ... ... 当然你也可以自己写出符合规范的task来在编译的过程中被调用.\n\n下面我们先看一下Android Studio中默认的script:\n\n```\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        applicationId \"com.trickyandroid.testapp\"\n        minSdkVersion 16\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n```\n\n按照我们已经有的知识,上面的脚本说明有一个名称为android的函数,该函数接收闭包作为参数,然而其实在Gradle的文档中是不存在这个函数\n的. 那么android脚本怎么会出现在这里呢? 答案就是最上面的apply plugin:\n'com.android.application'.这个插件提供了android构建所需要的各种script.\n\n既然gradle官方的文档中没有android相关的script信息,那我们该怎么查阅呢? 您可以去官方的android网站上查阅,如果懒得找的话请点击这个链接:https://developer.android.com/shareables/sdk-tools/android-gradle-plugin-dsl.zip\n\n您下载了前文连接的文档后，可以发现有一个html格式的文档的名字是AppExtension, 这个文档主要就是介绍了Android configuration blocks. 即在gradle官方文档中没有的关于Android 配置的各种gradle script都可以在这里进行查阅(几个例子):\n1、 compileSdkVersion 在文档中的描述是Required. Compile SDK version. 即这个脚本是gradle进行Android构建之必需,并且这个脚本是\n是用来描述编译的时候使用的sdk版本.\n2、buildToolsVersion在文档中的描述是Required. Version of the build tools to use. 即该脚本是构建之必需,其用于告诉gradle使用\n哪个版本的build tools\n3 ... ... (详细情况请参阅文档吧:))\n\n\n# Exercise\n\n有了前文的学习作为基础,我们已经了解了gradle语法以及android 插件的脚本查阅方法. 那么接下来我们实际运用这些知识,自定义的对我们\n的Android项目进行一些配置. 在上述的AppExtension文档中,我查阅到了一个脚本的名字是testOptions. 这段脚本代表的是TestOption class\n调用,TestOption class里有三个属性:reportDir、resultsDir 和unitTests. 而reportDir就是测试报告最后保存的位置,我们现在就来改一下\n这个地方.\n\n```\nandroid {\n......\n    testOptions {\n        reportDir \"$rootDir/test_reports\"\n    }\n}\n```\n\n在这里,我使用了\"$rootDir/test_reports\"作为测试结果的储存位置, $root 指向的就是项目的根目录.现在如果我通过命令行执行\n./gradlew connectedCheck. gradle就会进行一系列的测试程序并且将测试报告保存在项目根目录下的test_reports文件中.\n\n注意一点的是,这个关于测试的小例子,别用在你真是的生产环境中,尽量保持你项目结构的\"清洁\"\n\n"
  },
  {
    "path": "issue-19/readme.md",
    "content": "# 开发技术前线 第19期\n\n| 文章名称 |   译者  | \n|---------|--------|\n| [RxJava-Observables单元测试](RxJava-Observables单元测试.md)  | [dengshiwei](https://github.com/dengshiwei) \n| [gradle技巧之语法浅谈](gradle技巧之语法浅谈.md)  | [FTExplore](https://github.com/FTExplore) \n| [Android-UI自动化测试](Android-UI自动化测试.md)  | [Doris](https://github.com/DorisMinmin) \n\n"
  },
  {
    "path": "issue-20/readme.md",
    "content": ""
  },
  {
    "path": "issue-20/在你开发应用前应知道的六件事情.md",
    "content": "开发第一个应用之前你需要知道的六件事\n---\n> * 原文链接 : [6 THINGS I WISH I KNEW BEFORE I WROTE MY FIRST ANDROID APP](http://www.philosophicalhacker.com/2015/07/09/6-things-i-wish-i-knew-before-i-wrote-my-first-android-app/)\n* 原文作者 : [Philosophical Hacker](http://www.philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [dengshiwei](https://github.com/dengshiwei) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  未完成 / 校对中 / 完成 \n\n我的第一个应用非常糟糕。事实上，它非常的糟糕以致于我从应用市场上删除它，同时我甚至不在我的简历上罗列出它。如果我在开发之前知道一些Android开发的事情它就不会糟糕到这步田地。\n\n这列罗列的事情在你开发第一个Android应用的时候需要牢记在大脑中。这些我接下来将展示的实际错误来自于我的第一个应用程序代码中。把这些错误经验牢记心头能够帮助你开发一个你可以引以为豪的应用。\n\n当然，正如codestandards所说：“如果你所做的工作和你作为学生开发的Android应用类似，你很有可能会讨厌你的应用”。\n\n *如果一年前你写的代码对于你来说感觉还不错，你很大程度上没有进行足够的学习。*\n\n*-Code Standards  2015.5.21*\n\n如果你是一位经验丰富的Java开发者，第1、2、5条很有可能对你没有吸引力。另一方面，即使你从来没有犯过这些例子中的错误，第3、4条也可能向你展示一些很酷的事物，你可以利用一款也许你不知道的软件Android Studio去实现这些事物。\n\n###1.不要持有Context的静态引用\n\n\tpublic class MainActivity extends LocationManagingActivity implements ActionBar.OnNavigationListener,\n        GooglePlayServicesClient.ConnectionCallbacks,\n        GooglePlayServicesClient.OnConnectionFailedListener {\n    \t//...\n    \tprivate static MeTrackerStore mMeTrackerStore; \n  \t  \t//...\n   \t \t@Override\n   \t\tprotected void onCreate(Bundle savedInstanceState) {\n        \t//...\n       \t\tmMeTrackerStore = new MeTrackerStore(this);\n    \t}\n\t}\n\n这对于每个人来说看似是一个不可能犯的错误。它不是，我犯了这个错误。我也看到过别人犯这个错误，同时我也采访过那些不能很快指出为什么这是放在第一位的错误的人。不要这样做，它是会变的。\n\n如果MeTrackerStore 通过它的构造函数保持一个指向Activity的引用，这个Activity将不会被垃圾回收(GC)。（除非静态变量被从新分配到不同的Activity）。这是因为mMeTrackerStore 是静态变量，而静态变量的内存是不会被回收，直到应用程序退出才回收。如果你正在试图做这样的事情，你的代码很有可能有严重的错误。寻找帮助吧，可能看看谷歌的Udacity 课程“[Android Development for Beginners](https://www.udacity.com/course/android-development-for-beginners--ud837)”能够帮助你。\n\n注：从技术上说，你可以对一个Application Context进行静态变量引用而不引起内存泄露，[但我不建议你这样做](http://www.philosophicalhacker.com/2015/07/14/why-static-references-to-application-contexts-are-probably-not-the-best-idea/)。\n\n###2.注意那些你无法控制生命周期的对象的隐式引用\n\n\tpublic class DefineGeofenceFragment extends Fragment {\n    \tpublic class GetLatAndLongAndUpdateMapCameraAsyncTask extends \tAsyncTask<String, Void, LatLng> {\n\n       \t \t@Override\n        \tprotected LatLng doInBackground(String... params) {\n           \t\t //...\n            \t try {\n\t                //Here we make the http request for the place search suggestions\n\t                httpResponse = httpClient.execute(httpPost);\n\t                HttpEntity entity = httpResponse.getEntity();\n\t                inputStream = entity.getContent();\n\t                //..\n            \t}\n        \t}\n    \t}\n\t}\n\n这段代码有很多问题，我将只会把重点问题放在“隐式引用”那些问题上。在Java中，（非静态）内部类有个对外部类实例有个隐式引用。\n\n在这个例子中，任何GetLatAndLongAndUpdateCameraAsyncTask都有一个外部类DefineGeofenceFragment的引用。对于匿名类是同样的，它们也有一个对包含它们的类的实例的一个隐式引用。\n\nGetLatAndLongAndUpdateCameraAsyncTask对生命周期我们无法控制的Fragment对象有一个隐式引用。Android SDK负责创建和销毁Fragment，如果GetLatAndLongAndUpdateCameraAsyncTask 因为正在运行而不能被垃圾回收，那么DefineGeofenceFragment 也将因为具有隐式引用而保留不能被垃圾回收。\n\n这里有一个很棒的谷歌视频，[解释它为什么会发生这种事情](https://www.youtube.com/watch?v=_CruQY55HOk)。\n\n###3.使用Android Studio进行工作\n\n这段代码是我使用“Generate Getter”在Android Studio中进行生成的。这些getter保持了'm'前缀的实例变量，同样通过它也能为一个方法产生相同的效果，这已经不是空想。\n\n（如果你想知道为什么'm'是实例变量的名称的第一个字母,'m'往往是实例变量的公认约定。它代表了'member'(成员)的意思）。\n\n不管你是否认为实例变量的前缀'm'是一个好注意，在这有一个知识，Android Studio能够帮助你编写任何你想要实现的公认约定。例如，在你为实例变量生成getters、setters和connstructor参数时，你可以使用Android Studio代码风格对话框的设置使Android Studio在你的实例变量前自动添加'm'和移除'm'。\n\n![AndroidStudio](http://i1.wp.com/www.philosophicalhacker.com/wp-content/uploads/2015/07/Screen-Shot-2015-07-09-at-4.16.13-PM.png)\n\nAndroid Studio能够做的远不止于此。学习Android Studio从学习快捷键和模版是不错的开始。\n\n###4.一个函数只做一件事\n\n在我写的众多类中的一个类存在一个方法我写了有100多行。这类的方法是非常难以读懂、修改和重用。努力让一个方法只做一件事情。显然，这意味着你应该对超过20行的方法持有怀疑态度。这里，你可以使用Android Studio来帮助你发现有问题的方法：\n\n![Method](http://i2.wp.com/www.philosophicalhacker.com/wp-content/uploads/2015/07/Screen-Shot-2015-07-09-at-4.25.00-PM.png)\n\n###5.向聪明和有经验的人学习\n\n这可能听起来微不足道，但是这是我开发我的第一个应用时候犯下的错误。\n\n当你开发一个应用的时候，你会犯别人已经犯过的错误。向别人学习，你可以避免犯别人犯过的错误来节约你的时间。我在我的第一个应用中浪费了大量的时间犯错，这些错误如果我花点时间向有经验的软件开发工程师学习就可以避免。\n\n阅读[Pragmatic Programmer](http://www.amazon.com/The-Pragmatic-Programmer-Journeyman-Master/dp/020161622X)，然后阅读[Effective Java](http://www.amazon.com/Effective-Java-Edition-Joshua-Bloch/dp/0321356683)。这两本书会帮助你避免开发新手常犯的错误。在你学习了这两本书后，不停地寻找聪明的人并向他们学习。\n\n###6.使用类库\n\n当你开发应用的时候，你可能会遇到一些聪明人和有经验人已经解决过的问题。而且，许多这些问题的解决方案是可以作为开源库的，充分利用它们。\n\n在我的第一个应用中，我写了一些类库已经提供的功能代码。其中一些是java标准库，还有一些是第三方类库，如Retrofit和Picasso。如果你不确定你使用什么样的类库，你可以做下面3件事情：\n\n1. 听[Google IO Fragmented](http://fragmentedpodcast.com/episodes/9/)广播。在这期间，询问这些开发者什么第三方类库类库对Android很重要。\n\n2. 订阅[Android周刊](http://androidweekly.net/)。这里包含了一部分最新的类库，时刻注意哪些对自己有用。\n\n3. 寻找那些能够解决与你在开发应用中遇到问题类似的开源应用。你可能发现某个应用使用的第三方类库就是你想要的，或者你会发现一个你所不知道的java类库。\n\n###总结\n\n开发优秀的Android应用是非常困难的。不要犯曾经犯下的错误来难为自己。如果你发现我写的代码中的错误请在评论中告诉我。（误导性评论比没有评论更糟糕）。如果你认为这篇文章对于新手开发者有用，请分享它。解决他们的一些令人头疼的难题。\n\n"
  },
  {
    "path": "issue-20/检测和解决Android应用的性能问题.md",
    "content": "# 检测和解决Android应用的性能问题\n-------------\n\n> * 原文链接 : [Detect and Resolve Performance Problems on Android](http://code.tutsplus.com/tutorials/detect-and-resolve-performance-problems-on-android--cms-24058)\n> * 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n> * 译者 : [Mr.Simple](https://github.com/bboyfeiyu) \n\n\n## 前言\n\n无论你的应用多么有创新性、有用，如果它卡得要命，或者非常消耗内存，那么没人将会愿意使用它。\n\n因此，性能变得尤为重要。当你忙碌于构建精美的用户界面或者完成新的特性时，你可能容易忘却掉一些性能相关的事情。\n\n这也是为什么有Google Play的应用审核机制的原因之一。\n\n这篇文章中，你会看到每个Android工程师需要了解的一些性能问题。你将会学会使用Android SDK提供的、已安装在你的设备中的工具来测试这些问题是否发生在你自己的应用中。\n\n如果在你的应用中发现了一个性能问题，你肯定会想修复它。我们还会看一看如何使用Android SDK 工具来获取更多关于那些没有覆盖到的性能问题的相关信息。一旦你有了这些信息，你将会对如何提升应用性能有一个更深刻的理解，并且能够构建出让用户喜爱的App。\n\n## 过度绘制\n### 步骤1 : 问题描述\n\n你应用的用户界面是连接用户的纽带，但是创建漂亮的界面只是挑战的其中一面，你还需要确保用户界面流畅的运行。\n\n一个常见的问题就是用户界面卡顿，出现这种情况的原因可能是**overdraw**。Overdraw是屏幕上的某个像素在同一帧的时间内被绘制了多次。\n\n例如，想象一下一个有蓝色的背景文本，Android不仅会绘制对用户可见的蓝色区域，而是会绘制整个蓝色的背景以及上面的文本。这意味着一些像素会被两次绘制，这就是过度绘制。\n\n一些如上述例子所说的过度绘制示例是不可避免的。然而，过多的多度绘制会引发明显的性能问题，因此你必须将过度绘制的可能性降到最小。\n\n检测应用中的过度绘制相对来说比较简单。大量的过度绘制会引出其他用户界面相关问题，例如视图层级过于复杂等。基于这些原因，当你测试你的App的性能问题时，从过度绘制开始是一个明智的选择。\n\n### 步骤2 : 检测过度绘制\n\n好消息是你的Android设备上已经内置了检测过度绘制的工具。\n\n因此你需要做的第一步就是安装你要测试的App到你的设备中。然后打开设置页面，选择开发选项（Developer Options）->调试GPU 过度绘制（Debug GPU Overdraw），然后选择“显示过度绘制区域（Show overdraw area）”。如下图所示。\n\n![On your device select Settings Developer Options Debug GPU Overdraw\nShow overdraw\narea](http://img.blog.csdn.net/20150722114732363)\n\n这个工具使用色块来代表不同数量的过度绘制。剩下的事情就是启动你要测试的应用，然后观察它的过度绘制情况。\n\n![Launch your app and check the amount of\noverdraw](http://img.blog.csdn.net/20150722114815803)\n    \n-   **没颜色 :** 没有过度绘制，也就是说一个像素只被绘制了一次。\n-   **蓝色 :** 过度绘制了一次，也就是一个像素点被绘制了两次。\n-   **绿色 :** 过度绘制了2次. 也就是一个像素点被绘制了三次，通常，你需要集中精力优化过度绘制次数大于等于2次的情况。\n-   **浅红色 :** 过度绘制3次。这取决于你的应用，小范围的3次过度绘制可能是不可避免的，但是如果你看到了中等或者大量的红色区域那么你就需要查找出现这个问题的原因了。\n-   **深红色 :** 过度绘制4次，像素点被绘制了5次，甚至更多次。出现这种问题你绝逼要找到原因，并且解决它。\n\n![](http://img.blog.csdn.net/20150722125805062)\n\n### 步骤3 ： 最小化过度绘制\n\n一旦你发现了某个区域有严重的过度绘制，最简单的方法就是打开你应用的xml文件找到过度重叠的区域，特别是那些不可见的drawable对象和被绘制在其他控件上的背景，以此来降低这些地方的过度绘制。\n\n你也应该查找那些背景属性设置为白色，并且它的父视图背景也设置为白色的区域。所有这些都会引起严重的过度绘制。\n\nAndroid系统能自动的降低一些简单的过度绘制，但是这些对于复杂的自定义View并没有什么价值，因为Android不会知道你如何绘制你的内容。\n\n如果你在App中使用了复杂的自定义View，你可以为使用`clipRect`函数为你的视图定义可绘制区域的边界。更新相关信息可以参考[official Android\ndocumentation](http://developer.android.com/reference/android/graphics/Canvas.html).\n\n\n## 2. Android 图形渲染\n-------------------------------------------------------------\n\n### 步骤1 : 问题描述\n\n另一个常见的性能问题就是应用的视图层级。为了渲染每个视图，Android都会经历这三个阶段 : \n\n1. 测量\n2. 布局\n3. 绘制\n\n花在这三个阶段的时间与View层级中的View的数量成正比，这就意味着降低App渲染时间的最简单的方法就是识别和移除那些并没有什么卵用的UI元素。\n\n即使在你的视图层级上的所有View都是必须的，不同的布局方式也可能对测量过程产生重要的影响。通常来说，你的视图层级越深，花在测量视图的时间就越长。\n\n在视图渲染期间，每个View都要向它的父View提供它自己的尺寸。如果父view发现了任意一个尺寸问题，那么它会强制要求所有的子视图重新测量。\n\n即使没有错误发生，重新测量也可能出现。例如，为了正确的进行布局RelativeLayout通常会对它们的子视图进行两次测量。子视图使用了`layout_weight`属性的LinearLayout也会对它的子视图进行两次测量。\n\n这些都取决于你的布局方式，测量和重新测量的代价非常昂贵，它会严重影响你的渲染速度。\n\n确保你的用户界面渲染流畅的关键就是移除任何非必须的View以及减少你的View层级。\n\n**Hierarchy Viewer**是一个能够将你完整的View层级可视化的工具，这个工具能够帮助你发现冗余的View以及嵌套的布局。\n\n\n### 步骤2：使用 Hierarchy Viewer\n\n在我们进一步了解**Hierarchy Viewer**之前，你需要知道它的一些规则。首先**Hierarchy Viewer**只能与正在运行的App进行交互，而不是你的源代码。这就是说你需要将App安装到你的设备或者模拟器上。\n\n还有一个最重要的问题，就是默认情况下**Hierarchy Viewer**只能与运行开发版Android系统的设备进行交互（译者注: 一般来说，使用模拟器即可）。如果你没有开发设备，那你需要添加[`ViewServer`\nclass](https://github.com/romainguy/ViewServer)到你的应用中。\n\n了解这些之后就让我们打开Android Studio，并且选择\"tools\" -> \"Android\" -> \"Android Device Monitor\"，如图所示。\n\n![Select Tools Android Android Device\nMonitor](https://cms-assets.tutsplus.com/uploads/users/369/posts/24058/image/optimise-Android-performance-open-Android-Device-Monitor.jpg)\n\n然后点击**Hierarchy View**按钮，如下图所示。\n\n![Select the Hierarchy View button from the\ntoolbar](https://cms-assets.tutsplus.com/uploads/users/369/posts/24058/image/optimise-Android-performance-select-Hierarchy-viewer.jpg)\n\n屏幕左边的**Windows**标签下列出了所有Android设备和模拟器。选择你的设备后，你会看到你设备上运行的所有进程。选中你要检测的进程，然后你会看到三个自动更新的视图层级区域。\n\n这三个窗口提供了视图层级的三个不同可视化展示。\n\n-   **Tree View:** **** 视图层级窗口，每个节点代表了一个View;\n-   **Tree Overview:** 整个视图层级的缩略布局;\n-   **Layout View:** 当前视图层级的轮廓.\n\nHierarchy View中有三个窗口。如果你在一个窗口中选择了一个View，那么它会在另外两个中高亮显示。你能同时使用这三个窗口查找View层级中的冗余视图。\n\n![Select a view in one window and itll appear highlighted in the other\ntwo](https://cms-assets.tutsplus.com/uploads/users/369/posts/24058/image/optimise-Android-performance-tree%20overview.jpg)\n\n如果你不确定一个View是否是UI界面中的必须元素，最简单的方法就是到Tree View窗口点击这个节点。你将会看到该View是如何显示在屏幕的预览，此时你就可以确切地知道该View是否是必须的。\n\n但是即使一个View对最终的渲染效果有贡献也并不意味着它不会引起严重的性能问题。你已经看到了如何通过Hierarchy Viewer来找到明显的嵌套布局，但是如果这引起的性能问题并不那么明显呢？或者还有其他的原因使得该视图渲染得非常慢？\n\n好消息就是你还可以通过Hierarchy Viewer来剖析每个View在不同的渲染阶段的耗时。当性能问题的原因不那么明显时，这是你发现问题的另一途径。\n\n下个章节我将为你展示如何通过Hierarchy Viewer来剖析每个View的渲染时间来找到潜伏在问题表面的性能问题。\n\n### 步骤3 : 节点的性能分析\n\n定位你的用户界面瓶颈的最简单方法就是收集每个View分别完成测量、布局、绘制的时间。\n\n你不仅可以通过Hierarchy Viewer收集这些信息，Hierarchy Viewer还可以通俗易懂地向你展示这些数据，因此你可以通过这种形式来找到性能问题。\n\nHierarchy Viewer默认并不会显示渲染时间。你需要到Tree View窗口添加这个信息，然后选择你想要测试的根节点。下一步，在Hierarchy Viewer上点击由绿、红、紫的三个圆形色块组成的按钮，如图所示。\n\n![](https://cms-assets.tutsplus.com/uploads/users/369/posts/24058/image/optimise-Android-performance-venn-diagram.jpg)\n\n三个圆点色块就会显示在每个节点上，从左到右，这些圆点分别代表 :\n\n- 用于测量的时间\n- 用于布局的时间\n- 用于绘制的时间\n\n\n每个圆点都有颜色 : \n\n- **绿色** 代表该View的渲染速度至少要快于一半以上的其他参与测试的节点。例如，一个在布局位置上的绿色的圆点代表它的布局速度要快于50%以上的其他节点；\n- **黄色** 代表该View慢于50%以上的其他节点;\n- **红色** 代表该View的渲染速度比其他所有参与测试的节点都慢。\n\n![](http://img.blog.csdn.net/20150723140449454)\n\n当收集了这些数据之后，你不仅知道哪些View需要优化，你还会确切地知道是在渲染的哪个阶段导致的问题。\n\n哪些黄色、红色的地方就是你需要开始优化的地方，这些性能指标与该视图层级下的其他剖析节点也有关系。换句话说，你肯定会有一些视图渲染得比其他的慢。\n\n在开始改良你的View相关的代码之前，摸着你的良心问一句该View渲染得比其他视图慢是否有一个合理的原因，如果答案是否定的，那么就开始你的View优化之旅吧。\n\n\n3. Memory Leaks 内存泄漏\n-----------------------------------------------\n\n### 步骤1：问题描述\n\nAndroid是一个自动管理内存的开发环境，不要让这个句话蒙蔽了，因为内存泄漏依旧是可能发生的。这是因为垃圾回收器只会移除那些不可达的对象。如果它不是一个不可达的对象，那么该对象就不会被释放掉。\n\n这些不可达的对象阴魂不散，聚集在你的堆内存中，占用App的内存控件。如果你继续泄漏对象，那么可用的内存空间将会越来越小，GC操作就会频繁触发。\n\n有两个原因表明这是一个坏消息。首先，GC操作通常不会明显地影响你的App性能，但是当内存控件较小时大量的GC操作会使你的App变慢，此时UI就不会那么流畅了。第二问题是移动设备的内存空间相对来说较小，因此内存泄漏会快速地升级为内存溢出，导致应用Crash。\n\n内存泄漏难以被检测出。可能只有当用户开始抱怨你的应用时你才能发觉内存泄漏问题。幸运地是，Android SDK提供了一些有用的工具来让你找到这些问题。（译者注 : Square的开源库[LeakCanary](https://github.com/square/leakcanary)是查找内存泄漏的优秀工具,强烈建议大家使用）。\n\n### 步骤2 : 内存监视器 （Memory Monitor）\n\n**Memory Monitor**是一个能够实时获取应用内存使用情况的工具。需要注意的是这个工具只能作用于正在运行的应用，因此确保你的要测试的应用已经安装到你的设备中，并且你的设备已经连接到你的电脑上。\n\n**Memory Monitor**已经内置在Android Studio中，因此你可以点击Android Studio的底部的\"Memory\"这个tab来切换到内存监视页面。当你切换到该页面的时候，Memory Monitor就开始记录你的内存使用情况了。\n\n![Memory Monitor is built into Android Studio you can access it by\nclicking the Memory\ntab](https://cms-assets.tutsplus.com/uploads/users/369/posts/24058/image/optimise-Android-memory-monitor.jpg)\n\n如果Memory Monitor没有开始记录，那么确保你的设备是已经被选中的状态。\n\n如果Memory Monitor提示**No debuggable applications**，那么你可以打开Android Studio的\"Tools\"菜单，选择\"Android\"，然后确保选中了**Enable adb integration**。这个功能还不是很稳定，所以有时候你需要手动切换它的状态。你也可以断开设备与电脑的连接，然后再重连，这样可能就OK了。\n\n一旦Memory Monitor检测到正在运行的应用，它就会显示这个应用的内存使用情况。已使用的内存会被表示为深蓝色，未分配的内存则会变为浅蓝色。\n\n花一些时间与你的设备交互，并且关注你的内存使用情况。最终已分配的内存会增长，直到没有内存可用。此时，系统就会释放触发GC释放内存，当你看到已分配的内存明显的下降时就代表GC操作被触发了。\n\nGC通常情况下会将无用的内存释放掉，但是当你看到App在短时间内快速增长或者GC变得非常频繁，此时你就需要倍加小心了，这就是发生内存泄漏的信号！\n\n如果你通过Memory Monitor来追踪一个可疑的内存泄漏问题，你可能会看到Android系统会为你的App增大可用内存，TODO : 。\n\n最终，你可能会看到你的App消耗了非常多的内存以至于系统无法再给你的应用更多的可用内存。如果你看到这种场景，那么说明你在内存使用上犯了很严重的错误。\n\n### 步骤3 : Android Device Monitor\n\n另一个能够帮助你收集更新关于内存泄漏信息和其他内存相关问题的工具是Android Device Monitor的DDMMS工具下的**Heap**。\n\n**Heap**工具能够通过显示系统为你分配了多少内存来帮助你诊断内存泄漏问题。正如上面提到的，如果已分配的内存不断地增长，那么这是发生内存泄漏的明显信号。\n\n但是这个工具还提供了许多关于你的应用堆内存使用情况的数据，包含你的App内分配的各种对象、分配的对象数量以及这些对象占用了多少空间。这些额外的信息对于你追踪内存泄漏极为有用。\n\n你可以在Android Device Monitor工具中选择DDMS，在Devices中选择你要检测的App。然后选择Heap标签，如图所示。然后花一些时间与你的App进行交互以收集内存信息。\n\n![Launch Android Debug Monitor select DDMS and then click the Heap\ntab](https://cms-assets.tutsplus.com/uploads/users/369/posts/24058/image/optimise-Android-performance-heap-tab.jpg)\n\nheap输出信息会在GC事件之后，因为你可以手动点击**Cause GC**来触发GC，使得Heap内存数据尽快地显示出来。\n\n一旦GC事件被触发了，heap标签下就会更新App的堆内存使用信息，这些信息会在每次GC时更新。\n\n## 总结\n-------------\n\n在这篇文章中，我们学习了一些开发中最常见的性能问题，过度绘制、内存泄漏、缓慢的UI渲染。\n\n相信你已经掌握了如何使用工具来检查这些问题，以及如何获取更新的信息来判断你的应用中是否出现了这些性能问题。你有越多的信息，就越容易追踪到问题的原因并且修复它。\n\nAndroid SDK有很多工具可以供你诊断和定位性能问题。如果你想学习更多这方面的知识，你可以访问u这两篇官方文档 [Traceview and dmtracedump](http://developer.android.com/tools/debugging/debugging-tracing.html)和 [Allocation Tracker](http://developer.android.com/tools/debugging/ddms.html#alloc)."
  },
  {
    "path": "issue-21/Android数据绑定-再见Presenter,你好ViewModel.md",
    "content": "# Android DataBinding：再见Presenter，你好ViewModel！\n\n> @author ASCE1885的 [Github](https://github.com/ASCE1885)  [简书](http://www.jianshu.com/users/4ef984470da8/latest_articles) [微博](http://weibo.com/asce885/profile?rightmod=1&wvr=6&mod=personinfo) [CSDN](http://blog.csdn.net/asce1885)\n[原文链接](http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/)\n\n最近一段时间[MVP模式](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)已经成为Android应用开发UI层架构设计的主流趋势。类似[TED MOSBY](http://hannesdorfmann.com/android/mosby/)，[nucleus](https://github.com/konmik/nucleus)和[mortar](https://github.com/square/mortar)之类的框架都引入了Presenters来帮助我们搭建简洁的app架构。它们也（在不同的程度上）帮助我们处理Android平台上臭名昭著的设备旋转和状态持久化等问题。MVP模式也有助于隔离样板代码，虽然这并不是MVP模式的设计初衷。\n\n在Google I/O 2015上，伴随着Android M预览版发布的[Data Binding](https://developer.android.com/tools/data-binding/guide.html)兼容函数库改变了这一切。\n\n根据维基百科上关于MVP的词条描述，Presenter作用如下：\n\n*Presenter作用于model和view，它从仓库（Model）中获取数据，并格式化后让view进行显示。*\n\nData Binding框架将会接管Presenter的主要职责（作用于model和view上），Presenter的其他剩余职责（从仓库中获取数据并进行格式化处理）则由ViewModel（一个增强版的Model）接管。ViewModel是一个独立的Java类，它的唯一职责是表示一个View后面的数据。它可以合并来自多个数据源（Models）的数据，并将这些数据加工后用于展示。我之前写过一篇[关于ViewModel的短文](http://tech.vg.no/2015/04/06/dont-forget-the-view-model/)，讲述了它与Data Model或者Transport Model之间的区别。\n\n我们今天要讲述的架构是MVVM（[Model－View－ViewModel](https://en.wikipedia.org/wiki/Model_View_ViewModel)），它最初是在2005年（不要吓到哦）由微软提出的一个被证明可用的概念。下面我将举例说明从MVP到MVVM的改变，容我盗用下Hanne Dorfmann在他介绍[TED MOSBY框架](http://hannesdorfmann.com/android/mosby/)的文章中的插图。\n\n![](http://tech.vg.no/files/2015/07/mvp.png)\n\n![](http://tech.vg.no/files/2015/07/mvvm.png)\n\n可以看到对view中数据的所有绑定和更新操作都是通过Data Binding框架实现的。通过[ObservableField类](https://developer.android.com/tools/data-binding/guide.html#observablefields)，View在model发生变化时会作出反应，在XML文件中对属性的引用使得框架在用户操作View时可以将变化推送给对应的ViewModel。我们也可以通过代码订阅属性的变化，这样可以实现例如当CheckBox被点击后，TextView被禁用这样的功能。像这样使用标准Java类来表示View的视觉状态的一个很大优势是明显的：你可以很容易对这种视觉行为进行单元测试。\n\n上面关于MVP的插图中有一个名为Presenter.loadUsers()的方法，这是一个命令。在MVVM中这些方法定义在ViewModel中。从维基百科文章中可以看到：\n\n*view model是一个抽象的view，它对外暴露公有的属性和命令。*\n\n因此这可能跟你以前熟悉的东西有些不同。在MVP模式中models很可能只是纯粹用于保存数据的“哑”类。对于把业务逻辑放到Models或者View Models中的行为不要感到害怕。这是[面向对象编程](https://en.wikipedia.org/wiki/Object-oriented_programming)的核心准则。回到Presenter.loadUsers()函数，现在它是一个放在ViewModel中的函数，它可能被View的后置代码（code－behind）调用，或者被位于View的XML文件中的数据绑定命令调用。如果android-developer-preview问题跟踪里面[这个issue](https://code.google.com/p/android-developer-preview/issues/detail?id=2097)描述的问题得到支持的话。如果我们没能得到数据绑定到命令功能的支持，那就只能使用以前的android:onClick语法，或者手动在view中添加监听器了。 \n\n> 代码后置（code－behind），微软的一个概念，经常与早期的ASP.NET或者WinForms联系在一起。我想它也可以作为Android上的一个描述术语，View由两个元素组成：View的布局文件（XML）和后置代码（Java），这通常是指Fragments，Activities或者继承自View.java的其他类。\n\n###处理系统调用\n\nView的后置代码还需要完成一系列用例－初始化系统，打开对话框的函数，或者任何需要引用Android Context对象的调用。但不要把这样的代码调用放到ViewModel中。如果ViewModel包含\n\n```\nimport android.content.Context;\n```\n这段代码，说明你用错了，千万不要这么做，好奇害死猫。\n \n我还没有完全决定解决这个问题的最好办法，不过这是因为有几个好的选择。一个方法是通过在ViewModel中持有View的一个引用来保存Mosby中的presenter元素。这个方案不会降低可测试性。但跟在Mosby中持有一个单独的Presenter类不同，我坚持认为将View作为接口的具体实现可以起到简化代码的作用。另一个方法可能是使用[Square的Otto](http://square.github.io/otto/)之类的事件总线机制来初始化类似\n\n```\nnew ShowToastMessage(\"hello world\")\n```\n\n的命令。这将会很好的分离view和viewmodel，不过这是一件好事吗？\n\n###我们不需要框架了吗？\n\n那么Data Binding框架已经接管了类似Mosby或者Mortar等框架的工作了吗？只是一部分。我希望看到的是这些框架进化或者新增分支变成MVVM类型的框架，这样我们在充分利用Data Binding的同时，可以最低限度依赖第三方框架，并保持框架的小而美。虽然Presenter的时代可能已经结束了，但这些框架在管理声明周期和view（或者ViewModel）的状态持久化方面还在发挥作用，这一点并没有改变。（如果Google引入一个LifeCycleAffected接口让Fragment, Activity 和 View进行实现，那将是多么酷的一件事！这个接口由一个名为addOnPauseListener()和addOnResumeListener()的函数，在我们例子中如何使用这个接口将留给你来实现。）\n\n> 更新：最近了解到[AndroidViewModel框架](https://github.com/inloop/AndroidViewModel)，它实际上可能很适合MVVM和Android的Data Binding。不过我还没有时间试用它。\n\n###总结\n\n当我首次听说Android M致力于改进SDK并重点关注开发者时，我真的很激动。当我听说他们引入了Data Binding，我被震惊了。在其他平台如WinForms, WPF, Silverlight 和 Windows Phone上面我已经用了好几年Data Binding技术。我知道这可以帮助我们写出简洁的架构和更少的样板代码。这个框架是站在开发者这边的，而不是阻碍我们的，很久以前我就感受到这一点了。\n\n但Data Binding不是银弹，它也有缺点。在XML文件中定义绑定本身就是一个问题。XML不会被编译，它也不能进行单元测试。因此你将会经常在运行时才发现错误，而不是在编译期间。忘记将属性绑定到View了？很不幸。但工具可以发挥很大的帮助－这是为什么我希望Google能够尽量让Android Studio最大程度支持Data Binding。XML绑定的语法和引用检查，自动完成和导航支持。XML字段的重命名支持。从我测试Android Studio 1.3 beta来看，我至少可以肯定他们有在考虑这件事情。某些功能已经支持了，但还有很多没有支持，不过1.3版本仍然处于beta阶段，我们可以有更多的期待。\n\n###代码示例\n\n接下来我将给出一个示例，演示从MVP架构迁移到MVVM架构的结果。在MVP版本工程中，我使用Mosby框架并使用Butterknife实现视图注入。在MVVM例子中我使用Android M Data Binding并移除工程中对Mosby和Butterknife的依赖。结果是Presenter可以丢掉了，Fragment中代码减少了，不过ViewModel接管了很多代码。\n\n在这个例子中我直接引用View来生成toast消息。这也许不是我以后提倡的一种方法， 但理论上这么做没什么问题。使用Robolectric和Mockito来对Fragment进行mock，这样是可测试的，而且不会泄露内存，除非你错误的引用了ViewModels。\n\n下面这个app只是起一个演示的作用，它具有一个简单的登陆页面，后台会加载一些异步数据，views之间会有一些依赖。\n\n![](http://tech.vg.no/files/2015/07/illustration.png)\n\n如果你希望在Android Studio中阅读代码，可以到[Github上](https://github.com/Nilzor/mvp-to-mvvm-transition)分别检出MVP和MVVM的标签。\n\n下面准备好接受代码轰炸吧😏\n\nMVP – VIEW – XML\n\n```\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n                android:paddingRight=\"@dimen/activity_horizontal_margin\"\n                android:paddingTop=\"@dimen/activity_vertical_margin\"\n                android:paddingBottom=\"@dimen/activity_vertical_margin\"\n                tools:context=\".MainActivityFragment\">\n \n    <TextView\n        android:text=\"...\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"true\"\n        android:id=\"@+id/loggedInUserCount\"/>\n \n    <TextView\n        android:text=\"# logged in users:\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"false\"\n        android:layout_toLeftOf=\"@+id/loggedInUserCount\"/>\n \n    <RadioGroup\n        android:layout_marginTop=\"40dp\"\n        android:id=\"@+id/existingOrNewUser\"\n        android:gravity=\"center\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:orientation=\"horizontal\">\n \n        <RadioButton\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Returning user\"\n            android:id=\"@+id/returningUserRb\"/>\n \n        <RadioButton\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"New user\"\n            android:id=\"@+id/newUserRb\"\n            />\n \n    </RadioGroup>\n \n    <LinearLayout\n        android:orientation=\"horizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/username_block\"\n        android:layout_below=\"@+id/existingOrNewUser\">\n \n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\"\n            android:text=\"Username:\"\n            android:id=\"@+id/textView\"\n            android:minWidth=\"100dp\"/>\n \n        <EditText\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/username\"\n            android:minWidth=\"200dp\"/>\n    </LinearLayout>\n \n    <LinearLayout\n        android:orientation=\"horizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentStart=\"false\"\n        android:id=\"@+id/password_block\"\n        android:layout_below=\"@+id/username_block\">\n \n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\"\n            android:text=\"Password:\"\n            android:minWidth=\"100dp\"/>\n \n        <EditText\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:inputType=\"textPassword\"\n            android:ems=\"10\"\n            android:id=\"@+id/password\"/>\n \n    </LinearLayout>\n \n    <LinearLayout\n        android:orientation=\"horizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/password_block\"\n        android:id=\"@+id/email_block\">\n \n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\"\n            android:text=\"Email:\"\n            android:minWidth=\"100dp\"/>\n \n        <EditText\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:inputType=\"textEmailAddress\"\n            android:ems=\"10\"\n            android:id=\"@+id/email\"/>\n    </LinearLayout>\n \n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Log in\"\n        android:id=\"@+id/loginOrCreateButton\"\n        android:layout_below=\"@+id/email_block\"\n        android:layout_centerHorizontal=\"true\"/>\n</RelativeLayout>\n```\n\nMVP – VIEW – JAVA\n\n```\npackage com.nilzor.presenterexample;\n \nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.CompoundButton;\nimport android.widget.RadioButton;\nimport android.widget.TextView;\nimport android.widget.Toast;\nimport com.hannesdorfmann.mosby.mvp.MvpFragment;\nimport com.hannesdorfmann.mosby.mvp.MvpView;\nimport butterknife.InjectView;\nimport butterknife.OnClick;\n \npublic class MainActivityFragment extends MvpFragment implements MvpView {\n    @InjectView(R.id.username)\n    TextView mUsername;\n \n    @InjectView(R.id.password)\n    TextView mPassword;\n \n    @InjectView(R.id.newUserRb)\n    RadioButton mNewUserRb;\n \n    @InjectView(R.id.returningUserRb)\n    RadioButton mReturningUserRb;\n \n    @InjectView(R.id.loginOrCreateButton)\n    Button mLoginOrCreateButton;\n \n    @InjectView(R.id.email_block)\n    ViewGroup mEmailBlock;\n \n    @InjectView(R.id.loggedInUserCount)\n    TextView mLoggedInUserCount;\n \n    public MainActivityFragment() {\n    }\n \n    @Override\n    public MainPresenter createPresenter() {\n        return new MainPresenter();\n    }\n \n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        return inflater.inflate(R.layout.fragment_main, container, false);\n    }\n \n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        attachEventListeners();\n    }\n \n    private void attachEventListeners() {\n        mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                updateDependentViews();\n            }\n        });\n        mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                updateDependentViews();\n            }\n        });\n    }\n \n    /** Prepares the initial state of the view upon startup */\n    public void setInitialState() {\n        mReturningUserRb.setChecked(true);\n        updateDependentViews();\n    }\n \n    /** Shows/hides email field and sets correct text of login button depending on state of radio buttons */\n    public void updateDependentViews() {\n        if (mReturningUserRb.isChecked()) {\n            mEmailBlock.setVisibility(View.GONE);\n            mLoginOrCreateButton.setText(R.string.log_in);\n        }\n        else {\n            mEmailBlock.setVisibility(View.VISIBLE);\n            mLoginOrCreateButton.setText(R.string.create_user);\n        }\n    }\n \n    public void setNumberOfLoggedIn(int numberOfLoggedIn) {\n        mLoggedInUserCount.setText(\"\"  + numberOfLoggedIn);\n    }\n \n    @OnClick(R.id.loginOrCreateButton)\n    public void loginOrCreate() {\n        if (mNewUserRb.isChecked()) {\n            Toast.makeText(getActivity(), \"Please enter a valid email address\", Toast.LENGTH_SHORT).show();\n        } else {\n            Toast.makeText(getActivity(), \"Invalid username or password\", Toast.LENGTH_SHORT).show();\n        }\n    }\n}\n```\n\nMVP – PRESENTER\n\n```\npackage com.nilzor.presenterexample;\n \nimport android.os.Handler;\nimport android.os.Message;\nimport com.hannesdorfmann.mosby.mvp.MvpPresenter;\n \npublic class MainPresenter implements MvpPresenter {\n    MainModel mModel;\n    private MainActivityFragment mView;\n \n    public MainPresenter() {\n        mModel = new MainModel();\n    }\n \n    @Override\n    public void attachView(MainActivityFragment view) {\n        mView = view;\n        view.setInitialState();\n        updateViewFromModel();\n        ensureModelDataIsLoaded();\n    }\n \n    @Override\n    public void detachView(boolean retainInstance) {\n        mView = null;\n    }\n \n    private void ensureModelDataIsLoaded() {\n        if (!mModel.isLoaded()) {\n            mModel.loadAsync(new Handler.Callback() {\n                @Override\n                public boolean handleMessage(Message msg) {\n                    updateViewFromModel();\n                    return true;\n                }\n            });\n        }\n    }\n \n    /** Notifies the views of the current value of \"numberOfUsersLoggedIn\", if any */\n    private void updateViewFromModel() {\n        if (mView != null && mModel.isLoaded()) {\n            mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);\n        }\n    }\n}\n```\n\nMVP – MODEL\n\n```\npackage com.nilzor.presenterexample;\n \nimport android.os.AsyncTask;\nimport android.os.Handler;\nimport java.util.Random;\n \npublic class MainModel {\n    public Integer numberOfUsersLoggedIn;\n    private boolean mIsLoaded;\n    public boolean isLoaded() {\n        return mIsLoaded;\n    }\n \n    public void loadAsync(final Handler.Callback onDoneCallback) {\n        new AsyncTask() {\n            @Override\n            protected Void doInBackground(Void... params) {\n                // Simulating some asynchronous task fetching data from a remote server\n                try {Thread.sleep(2000);} catch (Exception ex) {};\n                numberOfUsersLoggedIn = new Random().nextInt(1000);\n                mIsLoaded = true;\n                return null;\n            }\n \n            @Override\n            protected void onPostExecute(Void aVoid) {\n                onDoneCallback.handleMessage(null);\n            }\n        }.execute((Void) null);\n    }\n}\n```\n\nMVVM – VIEW – XML\n\n```\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n    <data>\n        <variable name=\"data\" type=\"com.nilzor.presenterexample.MainModel\"/>\n    </data>\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n        android:paddingRight=\"@dimen/activity_horizontal_margin\"\n        android:paddingTop=\"@dimen/activity_vertical_margin\"\n        android:paddingBottom=\"@dimen/activity_vertical_margin\"\n        tools:context=\".MainActivityFragment\">\n \n        <TextView\n            android:text=\"@{data.numberOfUsersLoggedIn}\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentEnd=\"true\"\n            android:id=\"@+id/loggedInUserCount\"/>\n \n        <TextView\n            android:text=\"# logged in users:\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentEnd=\"false\"\n            android:layout_toLeftOf=\"@+id/loggedInUserCount\"/>\n \n        <RadioGroup\n            android:layout_marginTop=\"40dp\"\n            android:id=\"@+id/existingOrNewUser\"\n            android:gravity=\"center\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerHorizontal=\"true\"\n            android:orientation=\"horizontal\">\n \n            <RadioButton\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"Returning user\"\n                android:checked=\"@{data.isExistingUserChecked}\"\n                android:id=\"@+id/returningUserRb\"/>\n \n            <RadioButton\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"New user\"\n                android:id=\"@+id/newUserRb\"\n                />\n \n        </RadioGroup>\n \n        <LinearLayout\n            android:orientation=\"horizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/username_block\"\n            android:layout_below=\"@+id/existingOrNewUser\">\n \n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textAppearance=\"?android:attr/textAppearanceMedium\"\n                android:text=\"Username:\"\n                android:id=\"@+id/textView\"\n                android:minWidth=\"100dp\"/>\n \n            <EditText\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:id=\"@+id/username\"\n                android:minWidth=\"200dp\"/>\n        </LinearLayout>\n \n        <LinearLayout\n            android:orientation=\"horizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentStart=\"false\"\n            android:id=\"@+id/password_block\"\n            android:layout_below=\"@+id/username_block\">\n \n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textAppearance=\"?android:attr/textAppearanceMedium\"\n                android:text=\"Password:\"\n                android:minWidth=\"100dp\"/>\n \n            <EditText\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textPassword\"\n                android:ems=\"10\"\n                android:id=\"@+id/password\"/>\n \n        </LinearLayout>\n \n        <LinearLayout\n            android:orientation=\"horizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@+id/password_block\"\n            android:id=\"@+id/email_block\"\n            android:visibility=\"@{data.emailBlockVisibility}\">\n \n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textAppearance=\"?android:attr/textAppearanceMedium\"\n                android:text=\"Email:\"\n                android:minWidth=\"100dp\"/>\n \n            <EditText\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textEmailAddress\"\n                android:ems=\"10\"\n                android:id=\"@+id/email\"/>\n        </LinearLayout>\n \n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@{data.loginOrCreateButtonText}\"\n            android:id=\"@+id/loginOrCreateButton\"\n            android:layout_below=\"@+id/email_block\"\n            android:layout_centerHorizontal=\"true\"/>\n    </RelativeLayout>\n</layout>\n```\n\nMVVM – VIEW – JAVA\n\n```\npackage com.nilzor.presenterexample;\n \nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.CompoundButton;\nimport android.widget.Toast;\n \nimport com.nilzor.presenterexample.databinding.FragmentMainBinding;\n \npublic class MainActivityFragment extends Fragment {\n    private FragmentMainBinding mBinding;\n    private MainModel mViewModel;\n \n    public MainActivityFragment() {\n    }\n \n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.fragment_main, container, false);\n        mBinding = FragmentMainBinding.bind(view);\n        mViewModel = new MainModel(this, getResources());\n        mBinding.setData(mViewModel);\n        attachButtonListener();\n        return view;\n    }\n \n    private void attachButtonListener() {\n        mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                mViewModel.logInClicked();\n            }\n        });\n    }\n \n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        ensureModelDataIsLodaded();\n    }\n \n    private void ensureModelDataIsLodaded() {\n        if (!mViewModel.isLoaded()) {\n            mViewModel.loadAsync();\n        }\n    }\n \n    public void showShortToast(String text) {\n        Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();\n    }\n}\n```\n\nMVVM – VIEWMODEL\n\n```\npackage com.nilzor.presenterexample;\n \nimport android.content.res.Resources;\nimport android.databinding.ObservableField;\nimport android.os.AsyncTask;\nimport android.view.View;\n \nimport java.util.Random;\n \npublic class MainModel {\n    public ObservableField numberOfUsersLoggedIn = new ObservableField();\n    public ObservableField isExistingUserChecked = new ObservableField();\n    public ObservableField emailBlockVisibility = new ObservableField();\n    public ObservableField loginOrCreateButtonText = new ObservableField();\n    private boolean mIsLoaded;\n    private MainActivityFragment mView;\n    private Resources mResources;\n \n    public MainModel(MainActivityFragment view, Resources resources) {\n        mView = view;\n        mResources = resources; // You might want to abstract this for testability\n        setInitialState();\n        updateDependentViews();\n        hookUpDependencies();\n    }\n    public boolean isLoaded() {\n        return mIsLoaded;\n    }\n \n    private void setInitialState() {\n        numberOfUsersLoggedIn.set(\"...\");\n        isExistingUserChecked.set(true);\n    }\n \n    private void hookUpDependencies() {\n        isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {\n            @Override\n            public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {\n                updateDependentViews();\n            }\n        });\n    }\n \n    public void updateDependentViews() {\n        if (isExistingUserChecked.get()) {\n            emailBlockVisibility.set(View.GONE);\n            loginOrCreateButtonText.set(mResources.getString(R.string.log_in));\n        }\n        else {\n            emailBlockVisibility.set(View.VISIBLE);\n            loginOrCreateButtonText.set(mResources.getString(R.string.create_user));\n        }\n    }\n \n    public void loadAsync() {\n        new AsyncTask() {\n            @Override\n            protected Void doInBackground(Void... params) {\n                // Simulating some asynchronous task fetching data from a remote server\n                try {Thread.sleep(2000);} catch (Exception ex) {};\n                numberOfUsersLoggedIn.set(\"\" + new Random().nextInt(1000));\n                mIsLoaded = true;\n                return null;\n            }\n        }.execute((Void) null);\n    }\n \n    public void logInClicked() {\n        // Illustrating the need for calling back to the view though testable interfaces.\n        if (isExistingUserChecked.get()) {\n            mView.showShortToast(\"Invalid username or password\");\n        }\n        else {\n            mView.showShortToast(\"Please enter a valid email address\");\n        }\n    }\n}\n```\n\n"
  },
  {
    "path": "issue-21/TextView预渲染研究.md",
    "content": "\n# TextView预渲染研究\n-------------------------------------\n\n> * [原文链接](http://ragnraok.github.io/textview-pre-render-research.html)\n\nAndroid中的TextView是整个framework中最复杂的控件之一，负责Android中显示文本的大部分工作，framwork中的许多控件也直接或者间接的继承于TextView，例如Button，EditText等。其内部实现也相当复杂，单论代码行数来说，android-22中TextView有足足9509行，另外，TextView中许多操作都非常繁重，例如`setText`操作，需要设置SpanWatcher，或者需要重现创建一个SpannableString，还需要根据情况重新创建Text\nLayout，这些操作加起来之后令一次setText操作非常耗时。为了提升TextView的渲染效率，最近研究了一下预渲染的方法，接下来给大家讲解一下原理。\n\n### TextView渲染基本原理\n\n首先来介绍下TextView的基本渲染原理，总的来说，TextView中负责渲染文字的主要是这三个类：\n\n1.  [BoringLayout](http://developer.android.com/intl/zh-cn/reference/android/text/BoringLayout.html)\n\n    主要负责显示单行文本，并提供了isBoring方法来判断是否满足单行文本的条件。\n\n2.  [DynamicLayout](http://developer.android.com/intl/zh-cn/reference/android/text/DynamicLayout.html)\n\n    当文本为Spannable的时候，TextView就会使用它来负责文本的显示，在内部设置了SpanWatcher，当检测到span改变的时候，会进行reflow，重新计算布局。\n\n3.  [StaticLayout](http://developer.android.com/intl/zh-cn/reference/android/text/StaticLayout.html)\n\n    当文本为非单行文本，且非Spannable的时候，就会使用StaticLayout，内部并不会监听span的变化，因此效率上会比DynamicLayout高，只需一次布局的创建即可，但其实内部也能显示SpannableString，只是不能在span变化之后重新进行布局而已。\n\n另外，以上三个类都继承于[Layout](http://developer.android.com/intl/zh-cn/reference/android/text/Layout.html)类，在此类中统一负责文本的具体绘制，在Layout.draw方法中，会对文本一行一行的进行渲染：\n\n```java\n    TextLine tl = TextLine.obtain();\n\n    // Draw the lines, one at a time.\n    // The baseline is the top of the following line minus the current line's descent.\n    for (int i = firstLine; i <= lastLine; i++) {\n          ....\n          Directions directions = getLineDirections(i);\n          if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {\n             // XXX: assumes there's nothing additional to be done\n              canvas.drawText(buf, start, end, x, lbaseline, paint);\n          } else {\n              tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);\n              tl.draw(canvas, x, ltop, lbaseline, lbottom);\n          }\n    }\n    TextLine.recycle(tl);\n\n```\n\n可以看出来对于Spannble，或者包含emoji的文本的话，实际渲染操作是交给了`TextLine`去绘制，否则直接使用`canvas.drawText`，`TextLine`负责单行复杂文本的绘制，其中Spannable,\nEmoji之类的绘制逻辑都包含在里面，TextLine的绘制逻辑也并非十分高效，这里后续将会继续说明其应该如何优化。\n\n### TextLayoutCache\n\nCanvas在`drawText`的时候，如果需要每次都计算字体的大小，边距等之类的话，就会非常耗时，导致drawText时间会拉的很长，为了提高效率，android在4.0之后引入了TextLayoutCache，使用LRU\nCache缓存了字形，边距等数据，提升了drawText的速度，在4.4中，这个cache的大小是0.5M，全局使用，并且会在Activity的`configurationChanged`,\n`onResume`, `lowMemory`,\n`updateVisibility`等时机，会调用`Canvas.freeTextLayoutCache`来释放这部分内存。由于这部分的cache是系统底层控制的，我们无法做具体的控制。\n\n### TextView的预渲染优化\n\n从TextView的渲染原理来看，如果只是单纯的显示文本，我们根本不需要另外设置SpanWatcher来监听span的变化，因此我们可以直接使用BoringLayout或者StaticLayout来直接显示文本内容，但是BoringLayout只能显示单行文本，因此这里最好的选择是直接用StaticLayout\n\n我们选择了自定义View，并希望最终有这样的一个接口：\n\n```java\n    public class StaticLayoutView extends View {\n        private Layout layout = null;\n        public void setLayout(Layout layout) {\n            this.layout = layout;\n            requestLayout();\n        } \n        \n        @Override\n        protected void onDraw(Canvas canvas) {\n            super.onDraw(canvas);\n\n            canvas.save();\n            if (layout != null) {\n                layout.draw(canvas, null, null, 0);\n            }\n            canvas.restore();\n        }\n    }\n\n```\n\n我们可以直接通过设置这个view的Layout来绘制文本，并在`onDraw`方法中直接使用这个Layout对象来绘制文本。在这里我们摒弃了`setText`方法，直接通过Layout来绘制文本，而这里的Layout对象，我们可以通过预先创建之后才设置进去（这里可以放到单独的一个线程中创建），这样对比起普通TextView的`setText`方法，我们减少了`setText`中的许多消耗，可以大幅度的提升效率。\n\nStaticLayout的创建非常简单，只需要给定文本，宽度等就能直接创建。另外，为了预先填充TextLayoutCache，我们也可以在创建完StaticLayout对象之后，预先在一个dummy\ncanvas中draw出来：\n\n\n```java\n    StaticLayout layout = new StaticLayout(TestSpan.getSpanString(i), textPaint, hardCodeWidth, alignment, 1.0f, 0f, true);\n    layout.draw(dummyCanvas);\n```\n\n### 性能对比\n\n接下来我们测试一下具体的性能，这里的testcase放到了Github上：[StaticLayoutView](https://github.com/ragnraok/StaticLayoutView)\n\ntestcase的内容为，在一个ListView中，显示300个Item，每个item都是一段纯文本，里面全都是包含有大量ImageSpan的SpannableString，进行两边的对比，一边是直接使用StaticLayout，一边是使用普通的TextView，并且这300段文本不全相同，长度不同，随机生成，在StaticLayout的testcase中，StaticLayout都是预先在另外一个线程创建好之后才设置进去的，另外SpannableString也是预先生成好的。\n\n另外，在这里为了模拟真实app繁重的后台工作，另外创建了3个线程，不停在做浮点预算以尝试抢占CPU资源。\n\n测量性能的指标为，ListView连续向下滚动，测量其平均帧率为多少，分别测量五次，计算其平均值，最终性能测试结果如下：\n\n![](http://ragnraok.github.io/static/images/staticLayoutPerformance.png)\n\n这里测试的机器是MX3，左侧是直接使用StaticLayout的方案，右侧是系统的默认方案，Y轴是FPS，可以看出来，使用优化之后的方案，帧率提升了许多。\n\n### References\n\n[Instagram是如何提升TextView渲染性能的](http://codethink.me/2015/04/23/improving-comment-rendering-on-android/)\n\n这篇文章介绍了Instagram如何优化他们的TextView渲染的效率，这也是这里优化方法的来源，Instagram也是直接使用StaticLayout并通过预先创建Layout的方法来减少了ListView滚动过程中的掉帧率，并且效果非常显著。这篇文章算是给出了这里的原理解析以及一个简单的实现。\n"
  },
  {
    "path": "issue-21/Yelp是如何通过Glide优化图片加载的.md",
    "content": "# Yelp是如何通过Glide优化图片加载的\n-------\n> * 原文链接 : [Glide – How Yelp’s Android App Loads Images](http://engineeringblog.yelp.com/2015/07/glide-how-yelps-android-app-loads-images.html)\n> * 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n> * 译者 : [tiiime](https://github.com/tiiime) \n\n动态加载图片是是很多应用的基础。在yelp，图片是连接消费者和企业的关键。\n随着网络和硬件设备的发展，用户对图片质量要求越来越高，数量越来越大。图片很容就\n消耗了大量的硬件资源和网络流量，下载和管理这些数据变成了一个很麻烦的问题。\n我们尝试了一些现有的解决方案，最终选择了 [Glide][glide] 这个项目。\n\n在最简单的使用场景中，Glide 会将来自服务器或本地文件的图片加载到磁盘和内存的缓存中，\n然后再把它们载入到 view 里。虽然 Glide 适用场景很多， 但它主要致力于优化\n带有图片的 scrolling list ，使其尽可能流畅。\n\n## 对象池\nGlide 的核心部分就是维护一个 bitmap 的对象池。对象池通过减少大块对象分配\n来提升性能，而不是重用对象。(关于对象池的介绍:[this Android performance pattern video][object-pool] )。\n\n目前为止，Dalvik 和 ART 都没有使用压缩垃圾回收机制\n([compacting garbage collector][compact-gc])，压缩垃圾回收算法会检查 heap，\n然后将内存中所有存活对象移到相邻的位置，把大块的可用空间空出来以备后用。\n因为 Android 没有采用这套机制，所以内存有可能被各种对象占满，对象间的可用\n空间很小。如果应用在这时申请一个比内存里现在最大的空闲块还大的对象，\n就会 OutOfMemoryError ，即使剩余内存总和远大于分配这个对象所需的空间。\n\n使用对象池也提升了 list 滚动时的性能，因为重用 bitmaps 就意味着减少了创建和回收对象。\n当系统执行垃圾回收时会让整个系统\"时间静止\"，回收器运行时所有的线程(包括 UI 线程)\n都会被暂停。这段时间里，动画帧不能绘制，UI 会卡顿，这在滚动动画的过程中会更加明显明显。\n\n![img](http://engineeringblog.yelp.com/wp-content/uploads/2015/07/scrolling-slight.gif)\n\n---\n\n## 使用Glide\nGlide 上手很容易，不需要任何配置，默认就会使用 bitmap 对象池。\n\n```java\nDrawableRequestBuilder requestBuilder = Glide.with(context).load(imageUrl);\nrequestBuilder.into(imageView);\n```\n\n很简单的两步图片就载入完成了。在 Android 中， with() 方法里传递的 context\n可能是 Application 的 context ，也可能是 Activity 的 context。区别这一点很重要，\n如果传递的是 Activity 的 context，Glide 会监测 activity 的生命周期，\n当 Glide 检测到 Activity 被销毁时， 会自动取消等待中的请求。如果你传递的是一个\nApplication context，Glide 就不能对其进行优化。\n\n## 优化特性\n和上面一样，当图片从 ListView 中移出屏幕时， Glide 也会取消其对应的请求。\n由于大多数开发者在 adapter 中重用 View，Glide 会给在请求数据时给对应的\nImageView 附加一个 tag，然后再载入其他图片时检查这个 tag，\n如果存在的话取消第一个请求。\n\nGlide 提供一些特性，可以提升图片加载速度。首先是在图片展示前预读取数据，\n它提供了一个 ListPreloader，通过预加载 item 的数量初始化。接着通过\n`setOnScrollListener(OnScrollListener)` 把 [ListPreloader][ListPreloader] 设置给 ListView。如果你想在 ListView 之外预载图片，只要调用上面 `DrawableRequestBuilder`\n对象的 downloadOnly() 方法就好，像这样 `builder.downloadOnly();`。\n\n总之，我们发现了一个强大的工具 Glide。后面都是各种夸 Glide 的话啦~\n\n[glide]:https://github.com/bumptech/glide\n[object-pool]:https://www.youtube.com/watch?v=bSOREVMEFnM\n[compact-gc]:https://en.wikipedia.org/wiki/Mark-compact_algorithm\n[ListPreloader]:https://github.com/bumptech/glide/blob/30c92551ee75c2109955ee653e8795c7c1d60bf8/library/src/main/java/com/bumptech/glide/ListPreloader.java\n"
  },
  {
    "path": "issue-21/使用Mockito对异步方法进行单元测试.md",
    "content": "# 使用Mockito对异步方法进行单元测试\n=====================================================\n\n> * 原文链接 : [Unit testing asynchronous methods with Mockito](http://fernandocejas.com/2014/04/08/unit-testing-asynchronous-methods-with-mockito/)\n> * 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n> * 译者 : [Mr.Simple](https://github.com/bboyfeiyu) \n\n之前我拍着胸脯承诺要维护的我博客，因此才有了这篇文章。但是请忘记我的那些承诺，我今天要写的是关于[Mockito](https://code.google.com/p/mockito/),这是一个当你写单元测试时经常会用到的对象Mock框架。\n\n### **介绍**\n\n这篇文章假设你已经知道了什么是[单元测试](http://en.wikipedia.org/wiki/Unit_testing)以及为什么你要写单元测试。另外，我强烈建议你阅读[Martin\nFowler的这篇文章](http://martinfowler.com/articles/mocksArentStubs.html)。\n\n### **常见的场景**\n\n有些时候我们需要测试有回调的函数,这意味着它们是异步执行的。这些方法测试起来并不那么容易，使用`Thread.sleep(milliseconds)`来等待它们执行完成只能说是一种蹩脚的实现，并且会让你的测试具有不确定性。那么我们如何来对异步函数进行测试呢？[Mockito](https://code.google.com/p/mockito/)拯救了我们！\n\n### 翠花，上一个示例\n\n假设我们有一个实现了`DummyCallback`接口的`DummyCaller`类，在`DummyCaller`中有一个`doSomethingAsynchronously()`函数，该函数会调用`DummyCollaborator`类的`doSomethingAsynchronously(DummyCallback callback)`函数，在调用该函数时将这个callback参数设置为该`DummyCaller`对象。当`doSomethingAsynchronously(DummyCallback callback)`的任务在后台线程中执行完成之后就会回调这个callback。\n \n 还是直接看代码会更容易理解 : \n \n DummyCallback接口 : \n \n```java\npublic interface DummyCallback {\n\tpublic void onSuccess(List<String> result);\n\tpublic void onFail(int code);\n}\n```\n\nDummyCaller类 : \n\n```java\npublic class DummyCaller implements DummyCallback {\n \t// 执行异步操作的代理类\n\tprivate final DummyCollaborator dummyCollaborator;\n \t// 执行结果\n\tprivate List<String> result = new ArrayList<String>();\n \n\tpublic DummyCaller(DummyCollaborator dummyCollaborator) {\n\t\tthis.dummyCollaborator = dummyCollaborator;\n\t}\n \n\tpublic void doSomethingAsynchronously() {\n\t\tdummyCollaborator.doSomethingAsynchronously(this);\n\t}\n \n\tpublic List<String> getResult() {\n\t\treturn this.result;\n\t}\n \n\t@Override\n\tpublic void onSuccess(List<String> result) {\n\t\tthis.result = result;\n\t\tSystem.out.println(\"On success\");\n\t}\n \n\t@Override\n\tpublic void onFail(int code) {\n\t\tSystem.out.println(\"On Fail\");\n\t}\n}\n\n```\n真正的异步执行操作的DummyCollaborator类。\n\n```java\npublic class DummyCollaborator {\n \n\tpublic static int ERROR_CODE = 1;\n \n\tpublic DummyCollaborator() {\n\t\t// empty\n\t}\n \n\tpublic void doSomethingAsynchronously (final DummyCallback callback) {\n\t\tnew Thread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\ttry {\n\t\t\t\t\tThread.sleep(5000);\n\t\t\t\t\tcallback.onSuccess(Collections.EMPTY_LIST);\n\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\tcallback.onFail(ERROR_CODE);\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n\t\t}).start();\n\t}\n}\n```\n\n### 创建我们的测试类\n\n我们有2种不同的选择来测试我们的异步函数，但是首先我们先创建一个DummyCollaboratorCallerTest测试类。\n\n```java\npublic class DummyCollaboratorCallerTest {\n \n\t// 要测试的类型\n\tprivate DummyCaller dummyCaller;\n \n\t@Mock\n\tprivate DummyCollaborator mockDummyCollaborator;\n \n\t@Captor\n\tprivate ArgumentCaptor<DummyCallback> dummyCallbackArgumentCaptor;\n \n\t@Before\n\tpublic void setUp() {\n\t\tMockitoAnnotations.initMocks(this);\n\t\tdummyCaller = new DummyCaller(mockDummyCollaborator);\n\t}\n}\n```\n\n在setup函数中我们使用MockitoAnotations来初始化 Mock和ArgumentCaptor,我们暂时还不需要关心它们。\n\n在这里我们需要关心的是在测试执行之前初始化了Mock对象和被测试的类，这些初始化代码都在setup中。记住，所有要测试的函数都要被测试两次。\n\n让我们来看看下面的两种测试方案。\n\n### 为我们的回调设置一个Anwser\n\n这是我们使用`doAnswer()`来为一个函数进行打桩以测试异步函数的测试用例。这意味着我们需要理解返回一个回调（同步的），当被测试的方法被调用时我们生成了一个通用的anwser,这个回调会被执行。\n\n最后，我们调用了doSomethingAsynchronously函数，并且验证了状态和交互结果。\n\n```java\n\t@Test\n\tpublic void testDoSomethingAsynchronouslyUsingDoAnswer() {\n\t\tfinal List<String> results = Arrays.asList(\"One\", \"Two\", \"Three\");\n\t\t// 为callback执行一个同步anwser\n\t\tdoAnswer(new Answer() {\n\t\t\t@Override\n\t\t\tpublic Object answer(InvocationOnMock invocation) throws Throwable {\n\t\t\t\t((DummyCallback)invocation.getArguments()[0]).onSuccess(results);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}).when(mockDummyCollaborator).doSomethingAsynchronously(\n\t\t\t\tany(DummyCallback.class));\n \n\t\t// 调用被测试的函数\n\t\tdummyCaller.doSomethingAsynchronously();\n \n\t\t// 验证状态与结果\n\t\tverify(mockDummyCollaborator, times(1)).doSomethingAsynchronously(\n\t\t\t\tany(DummyCallback.class));\n\t\tassertThat(dummyCaller.getResult(), is(equalTo(results)));\n\t}\n```\n\n`[译者注 : 在doAnswer函数中当调用mockDummyCollaborator对象的doSomethingAsynchronously (final DummyCallback callback)函数时会触发Answer匿名内部类的answer(InvocationOnMock invocation)函数]`。\n\n### 使用ArgumentCaptor\n\n第二种实现是使用ArgumentCaptor。在这里我们的callback是异步的: 我们通过ArgumentCaptor捕获传递到DummyCollaborator对象的DummyCallback回调。\n\n最终，我们可以在测试函数级别进行所有验证，当我们想验证状态和交互结果时可以调用`onSuccess()`。\n\n```java\n\t@Test\n\tpublic void testDoSomethingAsynchronouslyUsingArgumentCaptor() {\n\t\t// 调用要被测试发函数\n\t\tdummyCaller.doSomethingAsynchronously();\n \n\t\tfinal List<String> results = Arrays.asList(\"One\", \"Two\", \"Three\");\n \n\t\t// Let's call the callback. ArgumentCaptor.capture() works like a matcher.\n\t\tverify(mockDummyCollaborator, times(1)).doSomethingAsynchronously(\n\t\t\t\tdummyCallbackArgumentCaptor.capture());\n \n\t\t// 在执行回调之前验证结果\n\t\tassertThat(dummyCaller.getResult().isEmpty(), is(true));\n \n\t\t// 调用回调的onSuccess函数\n\t\tdummyCallbackArgumentCaptor.getValue().onSuccess(results);\n \n\t\t// 再次验证结果\n\t\tassertThat(dummyCaller.getResult(), is(equalTo(results)));\n\t}\n```\n\n### **结论**\n\n两种实现的主要的不同点是当使用[DoAnswer()](http://mockito.googlecode.com/svn/branches/1.6/javadoc/org/mockito/Mockito.html)方案时我们创建了一个匿名内部类,并且将它的元素从`invocation.getArguments()[n]`转换到我们需要的类型，但是万一我们修改了我们的参数类型，那么这个测试就会'fail fast'。另一方面，当我们使用[ArgumentCaptor](http://docs.mockito.googlecode.com/hg/org/mockito/ArgumentCaptor.html)时我们可能能够更精细的控制测试用例，因为我们能在测试用例中手动调用回调对象。\n\n面对这两种方案，有时候我们不知道作何选择，但是不用担心，因为这是一个常见的问题。以我们的经验来看，同时使用这两种方案来测试异步函数会是一个更可靠的途径。\n\n我希望这篇文章对你有用，另外，请记住，欢迎给这篇文章的反馈，或许还有更好的实现。当然，如果你有任何的疑问也可以联系我。\n\n### **示例代码**\n\n[代码这里](https://github.com/android10/Inside_Android_Testing)。这些代码都是关于Java和Android的，因为我们在几个月前做了一场相关的演讲。\n\n### 深入拓展\n\n我强烈建议你阅读[Mockito文档](http://mockito.googlecode.com/svn/branches/1.6/javadoc/org/mockito/Mockito.html)以对这个框架有更深入的了解，这份文档非常清晰，并且有非常棒的示例！\n"
  },
  {
    "path": "issue-22/Android-Activity测试.md",
    "content": "#Activity测试\n---\n\n> * 原文链接 : [Activity Testing](http://developer.android.com/intl/zh-cn/tools/testing/activity_testing.html)\n* 原文作者 : [Android Developers](http://developer.android.com/index.html)\n* 译者 : [desmond1121](https://github.com/desmond1121)\n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)\n\n\nActivity测试依赖于Android Instrumentation测试框架。有其他组件不同的是Activity有更复杂的生命周期，这些生命周期函数不能直接地被调用，而只能通过Instrumentation发送事件来触发它们。\n\n这篇文档描述了怎么样使用Instrumentation和其他测试工具来进行自动化测试。在阅读它之前，你应该先阅读[Android测试基础](http://developer.android.com/tools/testing/testing_android.html)，它介绍了何为Android自动化测试与Instrumentation框架。\n\n\n## Activity测试API介绍\n\n\nActivity测试的基类是[InstrumentationTestCase](http://developer.android.com/reference/android/test/InstrumentationTestCase.html), 它为Activity测试所用的几个类提供Instrumentation支持。\n\n\n在Activity测试中，这个基类能够提供以下三个功能：\n\n- 生命周期控制：你可以使用Instrumentation来调用Activity的onResume、onPause、onDestroy等方法, 从而控制它的生命周期。\n\n- 依赖注入：你可以使用Instrumentation制造出伪Context或伪Application，这样能够帮助你更好地控制测试环境，通过自定义Intent启动Activity。\n\n- 用户界面交互：你可以使用Instrumentation直接发送按键信息和触屏事件。\n\nActivity测试类通过继承[TestCase](http://developer.android.com/reference/junit/framework/TestCase.html)和[Assert](http://developer.android.com/reference/junit/framework/Assert.html)实现了JUnit测试框架，你可以在测试的过程中使用。\n\n两个主要用的测试子类是[ActivityInstrumentationTestCase2](http://developer.android.com/reference/android/test/ActivityInstrumentationTestCase2.html)和[ActivityUnitTestCase](http://developer.android.com/reference/android/test/ActivityUnitTestCase.html)，不过当Activity的`launchMode`设置为非`standard`属性时，你需要用[SingleLaunchActivityTestCase](http://developer.android.com/reference/android/test/SingleLaunchActivityTestCase.html);\n\n### ActivityInstrumentationTestCase2\n\n这个类可以用来测试同一个应用的多个Activity，它拥有正常的应用实例环境和Context，可以接触到正常的系统结构（文件、数据库等）。你可以发送伪装的Intent给Activity，测试你的程序是否对接收到的各种Intent进行了正确处理。 **注意:这个类中不能伪Context和伪Application，所以你无法将测试从系统环境中独立出来。**\n\n### ActivityUnitTestCase\n\n这个类是用来测试单个Activity的。它可以运行在独立的测试环境中，你可以通过在启动Activity前设置Context或Application（当然都设置也可以）来实现模拟环境。你可以在自己的虚拟环境中测试程序而不会对系统、文件等造成实际的影响。在这个测试类下你无法向正在测试的Activity发送Intent，不过你可以在启动Activity的时候调用`Activity.startActivity(Intent)`来查看接收到的参数。\n\n### SingleLaunchActivityTestCase\n\n这个类可以很方便地用来测试单个Activity。由于它只会调用一次`setUp()`和`tearDown()`（而不是每次方法都中都会调用），所以在这个实例中的所有测试方法共享一个测试环境。在这个类中不允许你加入任何的伪Object。\n\n这个类在测试Activity的`launchMode`属性不是`standard`的时候非常有用，它保证了测试环境的不变，所以你可以用它来测试Activity是否对多次重复调用做出了正确应对。\n\n### Activity中的伪Object使用\n\n这一段内容是要告诉你怎么在Android测试中使用`android.test.mock`包中所定义的一系列伪Object。\n\n[MockApplication](http://developer.android.com/reference/android/test/mock/MockApplication.html)只可以在`ActivityUnitTestCase`中使用，你可以通过`setApplication()`来指定它（必须在startActivity之前调用）。若不指定的话，ActivityUnitTestCase会自己生成一个伪Application。(MockContext可以通过`setActivityContext()`指定)\n\n### Activity测试中的判断语句\n\n[ViewAsserts](http://developer.android.com/reference/android/test/ViewAsserts.html)定义了供View使用的一些判断语句，它可以检查View内容的位置、对其情况和状态。\n\n## 我们要测试什么？\n\n- 输入合法性：你可以测试Activity是否对EditText的各类输入做出了正常反应。通过模拟输入一串信息发送给Activity，使用`findViewById(int)`来检查View的状态。你可以通过设置一个`Button`当输入异常时disable，输入正常时enable来，然后通过`assertEquals(button.isEnabled(), true)`检测。你还可以通过设置错误输入检查Activity是否进行了合适的处理。\n\n- 生命周期控制：你可以测试程序中的Activity是否正常处理了生命周期的各个轮转情况。一个合格的Activity应该在pause或destroy时保存一些下次运行时需要的状态。 **记住若屏幕布局方向改变（如竖屏变为横屏）会引起Activity的destroy过程**，你应该保证设备外部移动不会引起应用数据丢失。\n\n- Intent测试：你可以测试每个Activity是否正常处理了它在manifest文件下<intent-filter>属性中的Intent.你可以在`ActivityInstrumentationTestCase2`测试中发送伪Intent给Activity。\n\n- 运行时设置改动： 你可以模拟设备的设置改变（如屏幕方向改变、语言改变等）来测试正在运行中的Activity的应对是否正确。在[Handling Runtime Changes](http://developer.android.com/guide/topics/resources/runtime-changes.html)一文中讲述了Activity如何应对运行时设备设置改变。\n\n- 设备尺寸以及分辨率的适配: 在你的应用要发布之前，你应该保证它在各式各样的屏幕上运行正常，可以通过`ViewAsserts`来自动化检测布局情况。你可以在各式各样的安卓虚拟机上运行测试，或在直接目标机型上运行。你可以在[Support Multiple Screens](http://developer.android.com/guide/practices/screens_support.html)上查看应用如何支持多样化屏幕。\n\n## 接下来该做的事\n\n在[Testing from Eclipse with ADT](http://developer.android.com/tools/testing/testing_eclipse.html)一文中讲述了怎么使用Eclipse来进行Android自动化测试，[Testring from Other IDEs](http://developer.android.com/tools/testing/testing_otheride.html)一文中讲述了其他开发工具的Android测试使用。\n\n你可以通过[Activity Testing Tutorial](http://developer.android.com/training/testing.htm)来循序渐进地学习如何做Activity自动化测试，这篇文章为你提供了测试以Activity为主的应用的方案。\n\n## 附录：关于UI测试中需要注意的地方\n\n以下这部分内容是对于一些UI测试的tips，特别是当你要测试主线程中的动作（触屏、输入和锁屏等事件）时，你应该仔细看一下这部分内容。\n\n### 在主线程上做测试\n\n应用的Activities是运行在主线程上的。一旦UI界面实例化（举个简单的例子：当Activity的onCreate方法经调用后UI即会实例化），那么所有和UI界面交互的事件都必须在主线程里面发生。当你正常启动应用时，必须按照这个规则程序才能够正常运行。但是在以Instrumentation为基础的测试中（其他类测试不可以），你可以在测试程序中对UI进行操作。\n\n如果要让整个测试函数都在主线程中运行，你可以给函数加上注解`@UiThreadTest`，但是这种情况下会让整个函数中的所有语句都运行在主线程中，这种情况下不和UI组件交互的语句是不允许的（如`Instrumentation.waitForIdleSync()`）。\n\n让一个测试函数中的一部分在主线程中运行，你可以调用的`appActivity.runOnUiThread()`办法（`appActivity`是你在测试中拥有的Activity实例），将你想要运行在主线程中的东西放入一个匿名内部类`Runnable`中然后传给它。\n\n举个例子，这段代码测试了一个Activity实力，控制Spinner获取焦点，并发送给它一个触屏事件。注意`waitForIdleSync`和`sendKeys`是不允许运行在主线程中的。\n\n      private MyActivity mActivity; // MyActivity is the class name of the app under test\n      private Spinner mSpinner;\n\n      ...\n\n      protected void setUp() throws Exception {\n          super.setUp();\n          mInstrumentation = getInstrumentation();\n\n      mActivity = getActivity(); // get a references to the app under test\n\n      /*\n       * Get a reference to the main widget of the app under test, a Spinner\n       */\n      mSpinner = (Spinner) mActivity.findViewById(com.android.demo.myactivity.R.id.Spinner01);\n\n      ...\n\n      public void aTest() {\n          /*\n           * request focus for the Spinner, so that the test can send key events to it\n           * This request must be run on the UI thread. To do this, use the runOnUiThread method\n           * and pass it a Runnable that contains a call to requestFocus on the Spinner.\n           */\n          mActivity.runOnUiThread(new Runnable() {\n              public void run() {\n                  mSpinner.requestFocus();\n              }\n          });\n\n      mInstrumentation.waitForIdleSync();\n\n      this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);\n\n###屏蔽掉物理按键和触屏\n\n为了让模拟器或者设备接收到测试程序的按键，你需要屏蔽掉设备的物理的按键和触屏事件。如果不这么做的话，测试程序发来的此类事件是不会起作用的。\n\n你可以通过调用`ActivityInstrumentationTestCase2.setActivityTouchMode(false)`来做到这点（在`getActivity()`之间调用这条语句才会起到作用）。 **注意，你不能在运行在主线程的语句中调用它**，所以它不可以出现在含有`@UiThreadTest`注解的函数中，实际上最好的办法就是在`setUp()`方法中做这件事。\n\n###先将设备解锁再进行测试\n\n你可能发现UI测试在屏幕锁屏（或其他加密手段）时是无法使用的，因为在这种情况下应用无法接收到`sendKeys()`发送的事件。最好的解决办法就是先解锁设备再进行测试。\n\n当然你也可以通过在onCreate中加上以下这段代码来显式地禁用锁屏，不过你需要为应用加上权限`<uses-permission android:name=\"android.permission.DISABLE_KEYGUARD\"/>`。\n\n      mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);\n      mLock = mKeyGuardManager.newKeyguardLock(\"activity_classname\");\n      mLock.disableKeyguard();\n\n###疑难解答\n\n这部分列出了一些在UI测试中比较多发生的错误。\n\n**`WrongThreadException`:**\n\n**问题:**\n\n遇到错误消息：`android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.`而导致测试崩溃。\n\n**可能的原因：**\n\n如果从非UI线程中和UI进行交互就会发生这个问题。当在测试中UI控件交互而你不加`@UiThreadTest`注解或者不在`runOnUiThread()`方法中进行的话，它会在非主进程中执行这些命令，这个错误就会发生。\n\n**建议：**\n\n在主进程中和UI交互，并且使用支持Instrumentation的测试类。你可以在[Testing on UI Thread](http://developer.android.com/tools/testing/activity_testing.html#RunOnUIThread)找到更多消息。\n\n**`java.lang.RuntimeException`:**\n\n**问题：**\n\n由错误消息`java.lang.RuntimeException: This method can not be called from the main application thread`所导致的测试崩溃。\n\n**可能的原因：**\n\n这个错误经常发生在你在`@UiThreadTest`注解的函数中调用了`runOnUiThread()`或其他不在UI进程中运行的语句。\n\n**建议：**\n\n你可以通过尝试去掉`@UiThreadTest`注解，或去掉`runOnUiThread()`，或重写测试程序来尝试解决。\n\n"
  },
  {
    "path": "issue-22/Android中的AOP编程.md",
    "content": "Android 中的 AOP 编程\n---\n\n> * 原文链接 : [Aspect Oriented Programming in Android](http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android)\n* 原文作者 : [Fernando Cejas](http://fernandocejas.com)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [byronwind](https://github.com/byronwind) \n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu) \n* 状态 :  校对完成 \n\n\n**面向切面编程（AOP，Aspect-oriented programming）**需要把程序逻辑分解成『**关注点**』（concerns，功能的内聚区域）。这意味着，在 AOP 中，我们不需要显式的修改就可以向代码中添加可执行的代码块。这种编程范式假定『横切关注点』（cross-cutting concerns，多处代码中需要的逻辑，但没有一个单独的类来实现）应该只被实现一次，且能够多次注入到需要该逻辑的地方。\n\n**代码注入是 AOP 中的重要部分：**它在处理上述提及的横切整个应用的**『关注点』**时很有用，例如日志或者性能监控。这种方式，并不如你所想的应用甚少，相反的，每个程序员都可以有使用这种注入代码能力的场景，这样可以避免很多痛苦和无奈。\n\n**AOP** 是一种已经存在了很多年的编程范式。我发现把它应用到 Android 开发中也很有用。经过一番调研后，我认为我们用它可以获得很多好处和有用的东西。\n\n## 术语（迷你术语表）\n在开始之前，我们先看看需要了解的词汇：\n\n* **Cross-cutting concerns（横切关注点）:** 尽管面向对象模型中大多数类会实现单一特定的功能，但通常也会开放一些通用的附属功能给其他类。例如，我们希望在数据访问层中的类中添加日志，同时也希望当UI层中一个线程进入或者退出调用一个方法时添加日志。尽管每个类都有一个区别于其他类的主要功能，但在代码里，仍然经常需要添加一些相同的附属功能。\n\n* **Advice（通知）:** 注入到class文件中的代码。典型的 Advice 类型有 before、after 和 around，分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 除了在方法中注入代码，也可能会对代码做其他修改，比如在一个class中增加字段或者接口。\n\n* **Joint point（连接点）:** 程序中可能作为代码注入目标的特定的点，例如一个方法调用或者方法入口。\n\n* **Pointcut（切入点）:** 告诉代码注入工具，在何处注入一段特定代码的表达式。例如，在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个，比如执行某一个方法，也可以有多个选择，比如，标记了一个定义成@DebguTrace 的自定义注解的所有方法。\n\n* **Aspect（切面）:** Pointcut 和 Advice 的组合看做切面。例如，我们在应用中通过定义一个 pointcut 和给定恰当的advice，添加一个日志切面。\n\n* **Weaving（织入）:** 注入代码（advices）到目标位置（joint points）的过程。\n\n\n下面这张图简要总结了一下上述这些概念。\n\n![](http://upload-images.jianshu.io/upload_images/30689-55846998f4f5b4ce.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)\n\n## 那么...我们何时何地应用AOP呢？\n一些示例的 cross-cutting concerns 如下：\n\n* **日志**\n* **持久化**\n* **性能监控**\n* **数据校验**\n* **缓存**\n* [其他更多](http://en.wikipedia.org/wiki/Cross-cutting_concern)\n\n取决于你所选的其中一种或其他方案 :)。\n\n## 工具和库\n有一些工具和库帮助我们使用 AOP:\n\n* [AspectJ:](https://eclipse.org/aspectj/) 一个 JavaTM 语言的面向切面编程的无缝扩展（适用Android）。\n\n* [Javassist for Android:](https://github.com/crimsonwoods/javassist-android) 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。\n\n* [DexMaker:](https://code.google.com/p/dexmaker/) Dalvik 虚拟机上，在编译期或者运行时生成代码的 Java API。\n\n* [ASMDEX:](http://asm.ow2.org/asmdex-index.html) 一个类似 ASM 的字节码操作库，运行在Android平台，操作Dex字节码。\n\n## 为什么用 AspectJ？\n\n我们下面的例子选用 AspectJ，有以下原因：\n\n* **功能强大**\n* **支持编译期和加载时代码注入**\n* **易于使用**\n\n## 示例\n\n比方说，我们要测量一个方法的性能（执行这个方法需要多长时间）。为此我们用一个 **@DebugTrace** 的注解标记我们的这个方法，并且无需在每个注解过的方法中编写代码，就可以通过 logcat 输出结果。我们的方法是使用 AspectJ 达到这个目的。\n\n我们看下在底层到底发生了什么：\n\n* **我们在编译过程中增加一个新的步骤处理注解。**\n* **注解的方法内会生成和注入必要的样板代码。**\n\n在此，我必须要提到当我研究这些时，发现了[Jake Wharton’s Hugo Library](https://github.com/JakeWharton/hugo) 这个项目，支持做同样的事情。因此，我重构了我的代码，看上去和它类似。尽管，我的代码是一个更加原始和简化的版本（顺便提一下，通过看这个项目的代码，我学到了很多）。\n\n![](http://upload-images.jianshu.io/upload_images/30689-77fa4ba34c4afe60.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)\n\n### 工程结构\n我们会把一个简单的示例应用拆分成两个 modules，第一个包含我们的 Android App 代码，第二个是一个 Android Library 工程，使用 AspectJ 织入代码（代码注入）。\n\n你可能会想知道为什么我们用一个 Android Library 工程，而不是用一个纯的 Java Library：原因是为了使 AspectJ 能在 Android 上运行，我们必须在编译时做一些 hook。这只能使用 andorid-library gradle 插件完成。（先不要为此担心，后面我会给出更多细节。）\n\n### 创建注解\n首先我们创建我们的Java注解。这个注解周期声明在 class 文件上（RetentionPolicy.CLASS），可以注解构造函数和方法（ElementType.CONSTRUCTOR 和 ElementType.METHOD）。因此，我们的 DebugTrace.java 文件看上是这样的：\n\n```java\n@Retention(RetentionPolicy.CLASS)\n@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })\npublic @interface DebugTrace {}\n```\n\n### 我们的性能监控计时类\n我已经创建了一个简单的计时类，包含 `start/stop` 方法。下面是 StopWatch.java 文件:\n\n```java\n/**\n * Class representing a StopWatch for measuring time.\n */\npublic class StopWatch {\n  private long startTime;\n  private long endTime;\n  private long elapsedTime;\n\n  public StopWatch() {\n    //empty\n  }\n\n  private void reset() {\n    startTime = 0;\n    endTime = 0;\n    elapsedTime = 0;\n  }\n\n  public void start() {\n    reset();\n    startTime = System.nanoTime();\n  }\n\n  public void stop() {\n    if (startTime != 0) {\n      endTime = System.nanoTime();\n      elapsedTime = endTime - startTime;\n    } else {\n      reset();\n    }\n  }\n\n  public long getTotalTimeMillis() {\n    return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0;\n  }\n}\n```\n\n### DebugLog 类\n我只是包装了一下 “android.util.Log”，因为我首先想到的是向 android log 中增加更多的实用功能。下面是代码：\n\n```java\n/**\n * Wrapper around {@link android.util.Log}\n */\npublic class DebugLog {\n\n  private DebugLog() {}\n\n  /**\n   * Send a debug log message\n   *\n   * @param tag Source of a log message.\n   * @param message The message you would like logged.\n   */\n  public static void log(String tag, String message) {\n    Log.d(tag, message);\n  }\n}\n```\n\n### Aspect 类\n现在是时候创建我们的 Aspect 类（TraceAspect.java）了。Aspect 类负责管理注解的处理和代码织入。\n\n```java\n/**\n * Aspect representing the cross cutting-concern: Method and Constructor Tracing.\n */\n@Aspect\npublic class TraceAspect {\n\n  private static final String POINTCUT_METHOD =\n      \"execution(@org.android10.gintonic.annotation.DebugTrace * *(..))\";\n\n  private static final String POINTCUT_CONSTRUCTOR =\n      \"execution(@org.android10.gintonic.annotation.DebugTrace *.new(..))\";\n\n  @Pointcut(POINTCUT_METHOD)\n  public void methodAnnotatedWithDebugTrace() {}\n\n  @Pointcut(POINTCUT_CONSTRUCTOR)\n  public void constructorAnnotatedDebugTrace() {}\n\n  @Around(\"methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()\")\n  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {\n    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();\n    String className = methodSignature.getDeclaringType().getSimpleName();\n    String methodName = methodSignature.getName();\n\n    final StopWatch stopWatch = new StopWatch();\n    stopWatch.start();\n    Object result = joinPoint.proceed();\n    stopWatch.stop();\n\n    DebugLog.log(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));\n\n    return result;\n  }\n\n  /**\n   * Create a log message.\n   *\n   * @param methodName A string with the method name.\n   * @param methodDuration Duration of the method in milliseconds.\n   * @return A string representing message.\n   */\n  private static String buildLogMessage(String methodName, long methodDuration) {\n    StringBuilder message = new StringBuilder();\n    message.append(\"Gintonic --> \");\n    message.append(methodName);\n    message.append(\" --> \");\n    message.append(\"[\");\n    message.append(methodDuration);\n    message.append(\"ms\");\n    message.append(\"]\");\n\n    return message.toString();\n  }\n}\n```\n\n几个在此提到的重点：\n\n* 我们声明了两个作为 pointcuts 的 public 方法，筛选出所有通过 ```“org.android10.gintonic.annotation.DebugTrace”``` 注解的方法和构造函数。\n* 我们使用 `“@Around”` 注解定义了`“weaveJointPoint(ProceedingJoinPoint joinPoint)” `方法,使我们的代码注入在使用`\"@DebugTrace\"`注解的地方生效。\n* `“Object result = joinPoint.proceed();” `这行代码是被注解的方法执行的地方。因此，在此之前，我们启动我们的计时类计时，在这之后，停止计时。\n* 最后，我们构造日志信息，用 Android Log 输出。\n\n###使 AspectJ 运行在 Anroid 上\n现在，所有代码都可以正常工作了，但是，如果我们编译我们的例子，我们并没有看到任何事情发生。原因是我们必须使用 AspectJ 的编译器（ajc，一个java编译器的扩展）对所有受 aspect 影响的类进行织入。这就是为什么，我之前提到的，我们需要在 gradle 的编译 task 中增加一些额外配置，使之能正确编译运行。\n\n我们的 build.gradle 文件如下：\n\n```java\nimport com.android.build.gradle.LibraryPlugin\nimport org.aspectj.bridge.IMessage\nimport org.aspectj.bridge.MessageHandler\nimport org.aspectj.tools.ajc.Main\n\nbuildscript {\n  repositories {\n    mavenCentral()\n  }\n  dependencies {\n    classpath 'com.android.tools.build:gradle:0.12.+'\n    classpath 'org.aspectj:aspectjtools:1.8.1'\n  }\n}\n\napply plugin: 'android-library'\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile 'org.aspectj:aspectjrt:1.8.1'\n}\n\nandroid {\n  compileSdkVersion 19\n  buildToolsVersion '19.1.0'\n\n  lintOptions {\n    abortOnError false\n  }\n}\n\nandroid.libraryVariants.all { variant ->\n  LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)\n  JavaCompile javaCompile = variant.javaCompile\n  javaCompile.doLast {\n    String[] args = [\"-showWeaveInfo\",\n                     \"-1.5\",\n                     \"-inpath\", javaCompile.destinationDir.toString(),\n                     \"-aspectpath\", javaCompile.classpath.asPath,\n                     \"-d\", javaCompile.destinationDir.toString(),\n                     \"-classpath\", javaCompile.classpath.asPath,\n                     \"-bootclasspath\", plugin.project.android.bootClasspath.join(\n        File.pathSeparator)]\n\n    MessageHandler handler = new MessageHandler(true);\n    new Main().run(args, handler)\n\n    def log = project.logger\n    for (IMessage message : handler.getMessages(null, true)) {\n      switch (message.getKind()) {\n        case IMessage.ABORT:\n        case IMessage.ERROR:\n        case IMessage.FAIL:\n          log.error message.message, message.thrown\n          break;\n        case IMessage.WARNING:\n        case IMessage.INFO:\n          log.info message.message, message.thrown\n          break;\n        case IMessage.DEBUG:\n          log.debug message.message, message.thrown\n          break;\n      }\n    }\n  }\n}\n```\n\n### 我们的测试方法\n我们添加一个测试方法，来使用我们炫酷的 aspect 注解。我已经在主 Activity 类中增加了一个方法用来测试。看下代码：\n\n```java\n  @DebugTrace\n  private void testAnnotatedMethod() {\n    try {\n      Thread.sleep(10);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n  }\n```\n\n### 运行我们的应用\n我们用 gradle 命令编译部署我们的 app 到 android 设备或者模拟器上：\n\n```\ngradlew clean build installDebug\n```\n\nIf we open the logcat and execute our sample, we will see a debug log with:\n如果我们打开 logcat，执行我们的例子，会看到一条 debug 日志：\n\n```\nGintonic --> testAnnotatedMethod --> [10ms]\n```\n\n**我们的第一个使用 AOP 的 Androd 应用可以工作了！**\n你可以用 Dex Dump 或者任何其他的逆向工具反编译 apk 文件，看一下生成和注入的代码。\n\n## 回顾\n回顾总结如下：\n\n* 我们已经对面向切面编程（AOP）这一范式有了初步体验。\n* 代码注入是 AOP 中的重要部分。\n* AspectJ 是在 Android 应用中进行代码织入的强大且易用的工具。\n* 我们已经使用 AOP 能力创建了一个可以工作的示例。\n\n\n## 结论\n面向切面编程很强大。通过正确使用，你可以在开发你的 Android 应用时，避免在『cross-cutting concerns』处复制大量代码，比如我们在示例中看到的性能监控部分。我非常鼓励你尝试一下，你会发现它非常有用。\n\n我希望你能喜欢这篇文章，文章的目的是分享我学到的东西，所以，欢迎评论和反馈，如果能 fork 代码玩一下就更好了。\n\n我确信我们能在示例 app 的 AOP 模块里增加些有趣的东西，欢迎提出你的想法;)。\n\n## 源码\n你可以在 https://github.com/android10/Android-AOPExample 下载示例 app 代码。另外我还有一个使用动态代理的 Java AOP 示例（也可以用在Android上）：https://github.com/android10/Android-AOPExample\n\n## 资源\n* [Aspect-oriented programming.](http://en.wikipedia.org/wiki/Aspect-oriented_programming)\n* [Aspect-oriented software development.](http://en.wikipedia.org/wiki/Aspect-oriented_software_development)\n* [Practical Introduction into Code Injection with AspectJ, Javassist, and Java Proxy.](http://www.javacodegeeks.com/2011/09/practical-introduction-into-code.html)\n* [Implementing Build-time Bytecode Instrumentation With Javassist.](http://java.dzone.com/articles/implementing-build-time)\n* [Frequently Asked Questions about AspectJ.](http://www.eclipse.org/aspectj/doc/released/faq.php)\n* [AspectJ Cheat Sheet.](http://blog.espenberntsen.net/2010/03/20/aspectj-cheat-sheet/)\n\n\n-----\n译注\n> * AOP 中的术语并没有统一的中文翻译，翻译过程中，术语一节我选取了用的比较多的中文名称注释在括号中帮助理解，正文中其他部分出现的术语，使用原始英文命名。\n* 这篇文章是2014年发布的，2015年7月，阿里巴巴刚刚开源了一个强大的 Android 平台 AOP 框架 [Dexposed](https://github.com/alibaba/dexposed)，该项目基于著名的[ Xposed 项目](https://github.com/rovo89/Xposed)。"
  },
  {
    "path": "issue-22/Binder框架解析.md",
    "content": "#Why are you here?\n\n*  你想要更好的理解Android是怎样工作的\n\n    。Intent、ContextProvider、Messenger   \n\t。访问系统的服务   \n\t。生命周期中的回调   \n\t。安全\n\n* 你想要通过高效率和低延时的IPC框架打破应用程序模块化的业务逻辑  \n* 你想要添加新的系统服务，可以更好地暴露给开发人员 \n* 你只是感觉IPC和Binder是不可缺少的，有趣的   \n* 你没有其它的事要做啦\n\n#Objectives\n\n* Binder Overview\n* IPC\n* Advantages of Binder\n* Binder vs Intent/ContentProvider/Messenger-based IPC Binder Terminology\n* Binder Communication and Discovery AIDL\n* Binder Object Reference Mapping Binder by Example\n* Async Binder Memory Sharing Binder Limitations Security\n\n>Slides and screencast from this class will be posted to: http://mrkn.co/bgnhg\n\n\n\n#Who am I?\n亚历山大 加尔根塔\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/author.png)\n\nDeveloper and instructor of Android Internals and Security training at\nMarakana\n\n* 马里亚纳（位于西太平洋）Android Internals and Security training的开发者和指导师\n* San Francisco Android User Group (sfandroid.org)的创始人和组织者\n* San Francisco Java User Group (sfjava.org) 的创始人和组织者\n* San Francisco HTML5 User Group (sfhtml5.org) 的合作发起人和组织者\n* AnDevCon, AndroidOpen, Android Builders Summit 等等的演讲者\n* Server-side Java and Linux, since 1997\n* Android/embedded Java and Linux, since 2009\n* 曾就职于 SMS, WAP Push, MMS, OTA provisioning\n* Follow \n\n\t。@agargenta     \n\t。+Aleksandar Gargenta   \n\t。http://marakana.com/s/author/1/aleksandar_gargenta \n\n\n#What is Binder?\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/what_is_Binder_01.png)\n\n\n* 一个IPC组件，开发面向对象的操作系统服务\n\n\t。没有另外一个面向对象内核   \n\t。代替运行在传统内核上，面向对象的操作系统环境，如：Linux\n\n* Android的关键\n* 来自OpenBinder\n\t\n\t* 发源于 Be,Inc ,作为 \"next generation BeOS\"(~ 2001) 的关键部分\n\t* 被 PalmSource 收购\n\t* 最初使用在 Palm Cobalt (micro-kernel based OS)\n\t*  Palm 切换到 Linux，所以，Binder被移植到Linux，开源(~ 2005)\n\t* 谷歌雇用了OpenBinder的核心工程师Dianne Hackborn，加入Android团队(~ 2008) \n\t* OpenBinder不再维护 -- Binder长存\n\n* 专注于可伸缩性、稳定性、灵活性、低延迟和开销、简单的编程模型\n\n\n#Why Binder?\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/why_binder_01.png)\n\n\n* 出于安全性、稳定性和内存管理的考虑，Android的应用和系统服务运行在分离的进程中，但是它们之间需要通信和共享数据\n\n\t。安全性：每一个进程就是一个沙盒，运行在一个不同的系统标识中\n\t。稳定性：如果一个进程失常（例如：崩溃），它不影响其它的进程\n\t。内存管理：“不需要”的进程会被移除，为新的释放资源（主要是内存）\n\t。事实上，一个单独的Android应用可以让它的组件运行在不同的进程中\n\n\n* IPC来拯救\n\t\n\t。如果我们需要避免传统IPC开销和服务拒绝的问题\n\n* Android的libc(a.k.a bionic)库不支持System V IPCs\n\n\t。没有SysV信号量，共享内存、消息队列等等\n\t。当一个进程终止时，“忘记”释放IPC共享资源，System V IPC会报内存资源泄露的错误\n\t。bug，恶意代码或者一个正常的应用在低内存的情况下都会无条件终止\n\n\n* Binder来拯救\n\n\t\n\t。其内置的“对象”引用的引用计数器，加上消亡提醒机制，让它适用于“敌对的”环境（低内存强杀）\n\t。当一个Binder服务没有任何终端引用时，它的所有者可以自动提醒它去处理自己\n\n* 很多其它的特性：\n\t\n    。\"线程迁移\" - 如，编程模式:\n    \n\t  * 远程对象可以像本地的一样调用自动管理线程池方法\n\t  * “跳转”到其它的进程中\n\t  * 同步和异步（单向）的调用模式\n\n\n    。分辨发送者和接受者（通过UID/PID）- 对于安全很重要\n    。独特的跨进程边界对象映射\n\t。一个远程对象的引用可以传递到的另外的进程中，并且可以用作一个标志令牌\n    。各个进程之间发送文件描述符的能力\n    。简单的Android接口定义语言（AIDL）\n    。内置支持很多编组的常见数据类型\n    。通过自动生成的代理和存根简化事务调用模型（只有Java）\n    。跨进程递归 - 例如：当调用本地对象上的方法时就跟递归的语义一样\n    。如果客户端和服务器运行在同样的进程中，就会是本地执行模式（不是IPC数据信号编集）\n\n\n\n\n* 但是：\n\n\t。不支持RPC（只有本地）\n\t。客户端与服务之间是基于消息的通信 - 不适合流\n\t。没有被POSIX或任何其他标准定义\n\n* 大多数应用程序和核心系统服务依赖于Binder\n\n\t。应用程序大多数生命周期的回调（例如：onResume(), onDestory(), etc.）会通过Binder被ActivityManagerService调用\n\t。关闭binder，然后整个系统慢慢停止（无显示，无音频，无输入，无传感器，...）\n\t。有些情况下使用Unix域中的socket（例如：RILD）\n\n\n#IPC with Intents and ContentProviders?\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/ipc_with_intents_01.png)\n\n\n* 通过Intent和content provider Android支持一个简单形式的IPC\n* 意图的消息传递是Android组件之间异步通信的一个框架\n\n\t。这些组件可能运行在相同的或不同的应用中（例如：多进程）\n\t。支持点对点和发布-订阅消息传递域\n\t。意图本身代表一个包含操作的描述和传递给接受者的数据\n\t。隐式意图能够给APIs解耦合\n\n\n* ContentResolver通过稳定的（CRUB）API 与 ContentProviders （典型的运行在不同的应用中的）同步通信\n\n* 所以的Android组件都可以是发送者，但是大多数是接受者\n* 所有通信发生在循环线程（又称主线程）中\n* 但是：\n\n\t。不是真实的面向对象\n\t。只有基于intent的异步通信\n\t。不适合低延时\n\t。因为，API定义地松散，所以容易发生运行时错误\n\t。所有的底层通信都是基于Binder的\n\t。事实上，Intent和ContentProvider只是Binder的高级抽象\n\t。基于系统服务方便扩展：ActivityManagerService和PackageManagerService\n\nFor example:    \n*src/com/marakana/shopping/UpcLookupActivity.java* \n\n```java\n\npublic class ProductLookupActivity extends Activity {\n\n    private static final int SCAN_REQ = 0; ...\n\n    public void onClick(View view) {\n        Intent intent = new Intent(\"com.google.zxing.client.android.SCAN\"); //1\n        intent.setPackage(\"com.google.zxing.client.android\"); //1 \n        intent.putExtra(\"SCAN_MODE\", \"PRODUCT_MODE\"); //2 \n        super.startActivityForResult(intent, SCAN_REQ); //3\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) { //4\n        if (requestCode == SCAN_REQ && resultCode == RESULT_OK) { //5 \n                String barcode = data.getStringExtra(\"SCAN_RESULT\"); //6 \n                String format = data.getStringExtra(\"SCAN_RESULT_FORMAT\"); //6\n                ...\n                super.startActivity(\n                    new Intent(Intent.ACTION_VIEW,\n                    ￼￼￼￼￼￼￼￼Uri.parse(\"http://www.upcdatabase.com/item/\" + barcode))); //7\n￼        }\n        ... \n    }\n}\n\n```\n\n*src/com/google/zxing/client/android/CaptureActivity.java:* \n```java\n\n...\n\npublic class CaptureActivity extends Activity {\n\n    ...\n\n    private void handleDecodeExternally(Result rawResult, ...) {\n\n        Intent intent = new Intent(getIntent().getAction()); \n        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); \n        intent.putExtra(Intents.Scan.RESULT, rawResult.toString()); //8 \n        intent.putExtra(Intents.Scan.RESULT_FORMAT,\n        rawResult.getBarcodeFormat().toString()); \n        ...\n        super.setResult(Activity.RESULT_OK, intent);\n        super.finish(); //9 \n    }\n}\n￼\n```\n\n\n*  1 指定我们要调用的是谁\n*  2 为我们的调用指定输入参数\n*  3 启动异步调用\n*  4 通过回调接收响应\n*  5 确认这是我们预期的响应\n*  6 得到响应\n*  7 启动另一个IPC请求,但不期望结果\n*  8 在服务方面,把结果放在一个新的Intent中\n*  9 异步发回结果\n\n#Messenger IPC\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/messenger_ipc_01.png)\n\n\n* Android的Messenger表示一个可以通过Intent发送到远程进程中的Handler引用\n* Messenger的引用可以通过Intent传递，使用前面提到的IPC机制来实现\n* 远程进程发送消息，通过Messenger，发送到本地的处理程序中\n* Messenger就像是Intent，它们可以指定“行为”（aMessge.what）和数据（aMessage.getData()）\n* 仍然是异步，但却是更低的延迟/开销\n* 从服务端到客户端高效的回调\n* 消息默认会在循环器线程中处理\n* 所有底层的通信依然基于Binder\n* 例如，如何定义客户端：\n\n*src/com/marakana/android/download/client/DownloadClientActivity.java:* \n\n```java\n￼...\npublic class DownloadClientActivity extends Activity {\n\n    private static final int CALLBACK_MSG = 0; \n    ...\n\n    @Override\n    public void onClick(View view) {\n\n        Intent intent = new Intent( \"com.marakana.android.download.service.SERVICE\"); //1\n        ArrayList<Uri> uris = ...\n        intent.putExtra(\"uris\", uris); //2\n        Messenger messenger = new Messenger(new ClientHandler(this)); //3\n        intent.putExtra(\"callback-messenger\", messenger); //4\n        super.startService(intent); //5\n\n    }\n\n    private static class ClientHandler extends Handler {\n\n        private final WeakReference<DownloadClientActivity> clientRef; //6\n        public ClientHandler(DownloadClientActivity client) {\n        this.clientRef = new WeakReference<DownloadClientActivity>(client);\n    \n        }\n\n        @Override\n        public void handleMessage(Message msg) { //7\n\n            Bundle data = msg.getData();\n            DownloadClientActivity client = clientRef.get();\n            if (client != null && msg.what == CALLBACK_MSG && data != null) {\n\n                Uri completedUri = data.getString(\"completed-uri\"); //8 \n                // client now knows that completedUri is done\n                    ...\n            }\n        }\n    }\n} \n\n```\n    \n\n* 1 指定我们想要调用的对象（返回使用Intent）\n* 2 为我们的调用指定输入参数\n* 3 在我们的handler中创建一个messenger\n* 4 传递的messenger也是输入的参数\n* 5 启动异步的调用\n* 6 我们的handler保存客户端的引用\n* 7 通过一个handler中的回调收接收响应\n* 8 获取响应数据\n* 和我们的服务端可以看下面：\n\n*src/com/marakana/android/download/service/DownloadService.java:* \n\n```java\n￼...\n\npublic class MessengerDemoService extends IntentService {\n    \n    private static final int CALLBACK_MSG = 0;\n\n    ...\n\n    @Override\n    protected void onHandleIntent(Intent intent) { //1\n\n        ArrayList<Uri> uris = intent.getParcelableArrayListExtra(\"uris\"); //2\n        Messenger messenger = intent.getParcelableExtra(\"callback-messenger\"); //3\n        for (Uri uri : uris) {\n            // download the uri\n                ...\n            if (messenger != null) {\n                \n                Message message = Message.obtain(); //4\n                message.what = CALLBACK_MSG;\n                Bundle data = new Bundle(1);\n                data.putParcelable(\"completed-uri\", uri); //5\n                message.setData(data); //4\n            \n                try {\n\n                    messenger.send(message); //6\n                    \n                } catch (RemoteException e) {\n                    ...\n                } finally {\n                    message.recycle(); //4 }\n                }\n            }\n    } \n}\n\n```\n\n\n* 1 处理客户端的请求（可能是本地的或远程的）\n* 2 获取请求数据\n* 3 获取messenger的引用\n* 4 用Message作为数据的通用信封\n* 5 设置我们的应答\n* 6 发送我们的应答\n\n\n#Binder 术语\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_terminology_01.png)\n\n* Binder (Framework) \n\n        所有的IPC架构\n\n* Binder Driver\n\n        内核级别的驱动，处理各个进程之间的通信\n\n* Binder Protocol\n\n        底层协议（基于ioctl），用于与Binder驱动通信\n\n* IBinder Interface\n\n        定义良好的行为（例如：方法），Binder对象必须实现\n\n* AIDL\n\n        Android接口定义语言，用于描述IBinder接口的业务操作\n\n* Binder (Object)\n\n        通用IBinder接口的实现\n\n* Binder Token\n\n        一个抽象的32位数值，在系统的所有进程中唯一的标识一个Binder对象\n\n* Binder Service\n\n        真正实现Binder（对象）的业务操作\n\n* Binder Client\n\n        一个对象，使用Binder服务提供的行为\n\n* Binder Transaction\n\n        远程Binder对象调用一个行为（例如：一个方法），基于Binder协议，可能涉及发送、接受的数据\n\n* Parcel\n \n        \"可以在IBinder中发送消息的容器（数据和对象的引用）\"，事务处理的数据单元——一个用作流出请求，另一个用作流入响应\n\n* Marshalling\n\n        将高级的应用程序数据结构（例如：请求、响应参数）转化成parcel对象的过程，目的是将它们嵌套进Binder的事务中\n￼\n* Unmarshalling\n\n        将Binder事务中获取到的parcel对象重构成高级应用的数据结构的过程（例如：请求、响应参数）\n\n* Proxy\n\n        一个AIDL接口的实现，编组、解组数据，映射调用事务的方法，将一个封装的IBinder引用指向Binder对象\n\n* Stub\n\n        一个AIDL接口局部的实现，当编组/解组数据时，映射事务到Binder Service调用\n\n* Context Manager (a.k.a. servicemanager)\n\n        一个特殊的已知处理的Binder对象，被用作为其它Binder注册、查询\n\n\nBinder Communication and Discovery\n#Binder通信挖掘\n\n\n* 对于关注的客户端，它只是想使用服务：    \n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_01.png)\n\n* 当进程不能直接操作其它的进程（或者读写数据），内核可以做到，因此他们使用Binder驱动：\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_02.png) \n\n\n>警告：因为服务端可能从多个客户端得到并发的请求，所以，它需要保护（同步访问的）其变量的状态\n\n* Binder驱动通过/dev/binder暴露出来，并提供了相关联的基于open、release、poll、mmap、flush、ioctl的操作\n* 事实上，大多数的通信都是通过ioctl（binderFd, BINDER_WRITE_READ, &bwd）进行的，bwd被定义为： \n\n```C\n\n￼struct binder_write_read {\n    \n    signed long write_size; /*  bytes to write */\n    signed long write_consumed; /*  bytes consumed by driver */ \n    unsigned long write_buffer;\n    signed long read_size; /*  bytes to read */\n    signed long read_consumed; /*  bytes consumed by driver */ \n    unsigned long read_buffer;\n\n};\n\n```\n\n\n* write_buffer包含一系列操作驱动的命令\n    \n    。Book-keeping命令，例如：增加、减少binder对象的引用，请求、清除死亡通知\n    。请求响应的命令，例如：BC_TRANSACTION\n\n* read_buffer包含用户操作的命令\n    \n    。book-keeping命令  \n    。操作响应的命令或执行嵌套（递归）请求处理\n\n* 客户端通过事务与服务端通信，事务中包含一个binder token、方法体、原始缓存数据和PID/UID的发送者（驱动添加的）\n* 客户端和服务端使用的大多数底层的操作和数据结构（例如：Parcel）是libbinder的抽象（在底层）\n* 为了避免客户端和服务知道Binder协议和libbinder所有事情，它们使用代理和存根：  \n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_03.png)\n\n\n>注意：基于java的代理和存根可以通过描述服务的aidl工具自动生成\n\n* 事实上，大多数的客户端都不知道它们正在使用IPC，从来没有提过Binder或者代理，因为，它们依靠抽象来管理它们的复杂性：   \n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_04.png)\n\n￼\n>注意：对系统服务来说，这是非常正确的，它们使用管理者只向客户端暴露了API的一个子集\n\n* 但是，客户端是怎么获取到它想要会话的handle的呢？只需要问Servicemanager（Binder's CONTEXT_MGR），希望当前的服务已经注册了handle：\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_05.png)\n\n\n>注意：出于安全、健康的原因，Binder驱动一次只会接受一个CONTEXT_MGR注册，这就是为什么Android中的Servicemanager是第一批启动的服务\n\n* 想使用servicemanager获取当前注册的服务清单，运行：\n\n```shell\n\n$ adb shell service list\nFound 71 services:\n0 sip: [android.net.sip.ISipService]\n1 phone: [com.android.internal.telephony.ITelephony]\n...\n20 location: [android.location.ILocationManager]\n...\n55 activity: [android.app.IActivityManager]\n56 package: [android.content.pm.IPackageManager]\n...\n67 SurfaceFlinger: [android.ui.ISurfaceComposer]\n68 media.camera: [android.hardware.ICameraService]\n69 media.player: [android.media.IMediaPlayerService] \n70 media.audio_flinger: [android.media.IAudioFlinger]\n\n```\n\n\n* 另外一种查看方式： \n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_commnuication_06.png)\n\n#Location Service: An Example    \n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/location_service_01.png)\n\n\n#AIDL\n\n\n* AIDL是Android的语言，定义了基于Binder的服务接口\n* AIDL遵循类似Java的接口语法，允许我们定义自己的“业务”方法\n* 每一个基于Binder的服务都被定义在.aidl文件中，经典的命名如：IFooService.aidl，并且保存在src/目录下\n\n\n*src/com/example/app/IFooService.aidl* \n\n```aidl\n\npackage com.example.app; \n\nimport com.example.app.Bar; \n\ninterface IFooService {\n\n    void save(inout Bar bar); \n    Bar getById(int id);\n    void delete(in Bar bar); \n    List<Bar> getAll();\n\n}\n\n```\n\n* aidl的编译工具（Android SDK 部分）被用作从每个.aidl文件中提取真正的java接口（连同提供Android‘s android.os.IBinder的Stub）放到我们的/gen目录下\n\n*gen/com/example/app/IFooService.java* \n\n```java\n\n￼package com.example.app;\n\npublic interface IFooService extends android.os.IInterface {\n\n    public static abstract class Stub extends android.os.Binder implements com.example.app.IFooService {\n            ...\n        public static com.example.app.IFooService asInterface(\n            android.os.IBinder obj) {\n                ...\n        return new com.example.app.IFooService.Stub.Proxy(obj);\n\n        }\n\n        ...\n\n        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { \n\n            switch (code)\n                ...\n                case TRANSACTION_save: {\n                    ...\n                    com.example.app.Bar _arg0;\n                    ...\n                    _arg0 = com.example.app.Bar.CREATOR.createFromParcel(data); this.save(_arg0);\n                    ...\n                }\n            ... \n            }\n        ... \n        }\n\n        ...\n    \n        private static class Proxy implements com.example.app.IFooService {\n        \n            private android.os.IBinder mRemote;\n            ...\n            public void save(com.example.app.Bar bar) throws android.os.RemoteException {\n\n                ...\n                android.os.Parcel _data = android.os.Parcel.obtain();\n                ...\n                bar.writeToParcel(_data, 0);\n                ...\n                mRemote.transact(Stub.TRANSACTION_save, _data, _reply, 0); \n                ...\n            } \n        }\n    }\n\n    void save(com.example.app.Bar bar) throws android.os.RemoteException; \n    com.example.app.Bar getById(int id) throws android.os.RemoteException; \n    void delete(com.example.app.Bar bar) throws android.os.RemoteException;\n    java.util.List<Bar> getAll() throws android.os.RemoteException;\n\n}\n\n```\n\n\n>注意：每次Eclipse ADT在src/目录下发现.aidl文件,它就会自动调用\n\n\n* AIDL支持以下的类型：\n\n    。null   \n    。boolean，boolean[]，byte，byte[]，char[]，int，int[]，long，long[]，float，float[],\n    double，double[]    \n    。java.lang.CharSequence，java.lang.String (sent as UTF-20)  \n    。java.io.FileDescriptor - transferred as a dup of the original file descriptor (points to the same underlying stream and position)  \n    。java.io.Serializable - not efficient (too verbose)     \n    。java.util.Map<String, Object> - of supported types (always reconstructed as\n    java.util.HashMap)      \n    。android.os.Bundle - a specialized Map-wrapper that only accepts AIDL-supported data types  \n    。java.util.List - of supported types (always reconstructed as java.util.ArrayList)  \n    。java.lang.Object[] - of supported types (including primitive wrappers)     \n    。android.util.SparseArray，android.util.SparseBooleanArray   \n    。android.os.IBinder，android.os.IInterface - transferred by (globally unique) reference (as a \"strong binder\"，a.k.a. handle) that can be used to call-back into the sender   \n    。android.os.Parcelable - 自定义类型:     \n\n*src/com/example/app/Bar.java* \n\n```java\n\npackage com.example.app; \nimport android.os.Parcel; \n\nimport android.os.Parcelable;\npublic class Bar implements Parcelable { \n    \n    private int id;\n    private String data;\n    public Bar(int id, String data) { this.id = id;\n    this.data = data;\n\n}\n\n// getters and setters omitted\n    ...\npublic int describeContents() {\n    return 0; \n}\n\npublic void writeToParcel(Parcel parcel, int flags) { \n\n    parcel.writeInt(this.id); \n    parcel.writeString(this.data);\n\n}\n\npublic void readFromParcel(Parcel parcel) { \n    \n    this.id = parcel.readInt();\n    this.data = parcel.readString();\n\n}\n\npublic static final Parcelable.Creator<Bar> CREATOR = new Parcelable.Creator<Bar>() { \n\n    public Bar createFromParcel(Parcel parcel) {\n        return new Bar(parcel.readInt(), parcel.readString()); \n    }\n\n    public Bar[] newArray(int size) { \n\n    return new Bar[size];\n\n    } \n};\n￼\n}\n\n```\n\n\n>注意：public void readFromParcel(Parcel) 方法不是 Parcelable 接口定义的。这里是出于Bar的易变性考虑 -- 比如：我们期望远程的那端能够在 void save(inout Bar bar) 方法中修改它。\n\n* 同理，public static final Parcelable.Creator<Bar> CREATOR 也不是 Parcelable 接口定义的（很明显）。它从 save 的事务中获取 _data ，从 getById 操作中获取 _reply ，重构了 Bar。\n\n* 这些自定义的类必须在它们自己的.aidl文件中申明\n\n*src/com/example/app/Bar.aidl* \n\n```java\n\npackage com.example.app; \nparcelable Bar;\n\n```\n\n>注意：AIDL接口必须导入parcelable自定义的类，即使它们在同一个包中。对于前面的示例，src/com/example/app/IFooService.aidl 必须导入com.example.app.Bar。\n\n* AIDL定义的方法可以不传或传入多个参数，但是，必须返回一个值或者Void\n* 所有的非基本类型的参数需要一个指定方向的标签，in、out、inout，这其中的一个\n    \n    。基本数据类型的指向一直是in(可以省略)\n    。当编组数据的时候指向标签会传递给Binder，因此它会对性能有直接影响\n\n\n* 所有的.aidl注释都应该被拷贝到生成的Java接口中（除了import和package语句前的）\n\n\n* 只有以下的异常会默认支持：SecurityException, BadParcelableException, IllegalArgumentException, NullPointerException, IllegalStateException\n* .aidl文件中，不支持静态成员\n\n\n#跨进程映射Binder对象引用\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_object_reference_01.png)\n\n\n* Binder对象的引用是以下的一种：\n\n    。在同一个进程中，Binder对象的一个真实的虚拟地址\n    。在另外一个进程中的，Binder对象的一个抽象的32位handle\n\n\n* 在每个的事务中，Binder驱动都会自动的将远程的binder handle映射成本地的地址，将本地的地址映射成远程的Binder handle\n\n这种映射实现:\n    \n    。Binder事务的目标\n    \n    。IBinder对象的引用作为参数或返回值跨进程共享（嵌套在事务数据中）\n\n\n* 为了能够工作\n    \n    。binder驱动在进程之间维持本地地址和远程handle（每一个进程都作为一个二进制树），因此，binder可以传送\n    。嵌套在事务中的引用是基于客户端提供的偏移\n\n\n* Binder驱动不知道不与远程进程共享的Binder对象\n    。一旦一个新的Binder对象引用在事务中被发现，它就会记录在Binder驱动中\n    。任何时候，Binder引用与其它的进程共享，Binder驱动的引用计数器就会增长\n    。当进程消亡的时候，Binder驱动的计数器就会明确的减少或自动地减少\n    。当一个引用不再需要的时候，它的所有者就会提醒它可以释放掉，并且Binder会删除自己的映射\n\n#构建基于Binder的服务端和客户端\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/building_a_binder_01.png)\n\n\n* 为了演示基于Binder的服务端和客户端（基于Fibonacci），我们将会创建三个独立的工程：\n\n    1.FibonacciCommon库工程 - 定义AIDL接口，自定义类型作为参数和返回值\n    2.FibonacciService工程 - 我们实现AIDL接口，并把它暴露给客户端\n    3.FibonacciClient工程 - 用它连接我们的AIDL服务 \n\n\n* 以下的代码是可用的 \n\n    。一个ZIP归档文件  ：https://github.com/marakana/FibonacciBinderDemo/zipball/master\n    。通过Git：git clone https://github.com/marakana/FibonacciBinderDemo.git\n\n\n* UI显示大概如下：  \n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/building_a_binder_02.png)\n\n\nFibonacciCommon - Define AIDL Interface and Custom Types\n#FibonacciCommon - 定义AIDL接口和自定义类型\n\n\n* 我们开始创建一个新的Android（库）工程，这是服务端和客户端可以共享的API文件（作为参数和返回值的一个AIDL接口和自定义类型）\n\n    。工程名：FibonacciCommon\n    。构建目标：Android 2.2+（API 8+）\n    。包名：com.marakana.android.fibonaccicommon\n    。最低SDK版本：8+\n    。不需要指定应用名或activity\n\n\n* 要转换成库工程，我们需要访问properties → Android → Library，然后，选中Library\n* 我们也可以手动地添加 android.library=true 到 FibonacciCommon/default.properties 中，然后刷新该工程\n* 因为库工程从来不会进入到实际的应用（APKS），所以我们可以简化清单文件：\n\n*FibonacciCommon/AndroidManifest.xml* \n\n```xml\n\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.marakana.android.fibonaccicommon\" android:versionCode=\"1\"\n    android:versionName=\"1.0\"> \n</manifest>\n\n```\n\n* 并且我们可以移除 FibonacciCommon/res/ 目录（例如：rm -fr FibonacciCommon/res/* ）下的任何文件\n\n* 我们现在已经准备好创建AIDL接口了\n\n*FibonacciCommon/src/com/marakana/android/fibonaccicommon/IFibonacciService.aidl* \n\n```java\n\npackage com.marakana.android.fibonaccicommon;\n\nimport com.marakana.android.fibonaccicommon.FibonacciRequest; \nimport com.marakana.android.fibonaccicommon.FibonacciResponse;\n\ninterface IFibonacciService {\n    \n    long fibJR(in long n);\n    long fibJI(in long n);\n    long fibNR(in long n);\n    long fibNI(in long n);\n    FibonacciResponse fib(in FibonacciRequest request);\n\n}\n\n```\n\n\n* 很明显，我们的接口依赖两个自定义的Java类型，但是定义在它们自己的.aidl文件中\n\n*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciRequest.java* \n\n```java\n\npackage com.marakana.android.fibonaccicommon;\n\nimport android.os.Parcel; import android.os.Parcelable;\n\npublic class FibonacciRequest implements Parcelable {\n    \n    public static enum Type {\n        RECURSIVE_JAVA, ITERATIVE_JAVA, RECURSIVE_NATIVE, ITERATIVE_NATIVE\n    }\n\n    private final long n; \n    private final Type type;\n    public FibonacciRequest(long n, Type type) {\n        \n        this.n = n;\n\n        if (type == null){\n            throw new NullPointerException(\"Type must not be null\");\n        }  \n        this.type = type;\n    }\n\n    public long getN() { \n        return n;\n    }\n\n    public Type getType() { \n        return type;\n    }\n\n    public int describeContents() { \n        return 0;\n    }\n\n    public void writeToParcel(Parcel parcel, int flags) { \n\n        parcel.writeLong(this.n); \n        parcel.writeInt(this.type.ordinal());\n\n    }\n\n    public static final Parcelable.Creator<FibonacciRequest> CREATOR = new Parcelable.Creator<FibonacciRequest>() {\n\n        public FibonacciRequest createFromParcel(Parcel in) { \n    \n            long n = in.readLong();\n            Type type = Type.values()[in.readInt()];\n            return new FibonacciRequest(n, type);\n\n        }\n\n        public FibonacciRequest[] newArray(int size) { \n            return new FibonacciRequest[size];\n        } \n    };\n}\n\n\n```\n\n*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciRequest.aidl* \n\n```java\n\n￼package com.marakana.android.fibonaccicommon; \nparcelable FibonacciRequest;\n\n```\n\n*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciResponse.java* \n\n```java\n\npackage com.marakana.android.fibonaccicommon;\n\nimport android.os.Parcel; import android.os.Parcelable;\n\npublic class FibonacciResponse implements Parcelable {\n    \n    private final long result;\n    private final long timeInMillis;\n    public FibonacciResponse(long result, long timeInMillis) { \n\n        this.result = result;\n        this.timeInMillis = timeInMillis;\n\n    }\n\n    public long getResult() { \n        return result;\n    }\n\n    public long getTimeInMillis() { \n        return timeInMillis;\n    }\n\n    public int describeContents() { \n        return 0;\n    }\n\n    public void writeToParcel(Parcel parcel, int flags) { \n\n        parcel.writeLong(this.result); parcel.writeLong(this.timeInMillis);\n\n    }\n\n    public static final Parcelable.Creator<FibonacciResponse> CREATOR = new Parcelable.Creator<FibonacciResponse>() {\n\n        public FibonacciResponse createFromParcel(Parcel in) {\n\n            return new FibonacciResponse(in.readLong(), in.readLong());\n    \n        }\n\n        public FibonacciResponse[] newArray(int size) {\n            return new FibonacciResponse[size];\n        } \n    };\n}\n\n```\n\n*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciResponse.aidl* \n\n```java\n\npackage com.marakana.android.fibonaccicommon;\nparcelable FibonacciResponse;\n\n```\n\n\n* 最后，我们可以准备查看自动生成的Java接口\n\n*FibonacciCommon/gen/com/marakana/android/fibonaccicommon/IFibonacciService.java* \n\n```java \n\npackage com.marakana.android.fibonaccicommon;\npublic interface IFibonacciService extends android.os.IInterface {\n\n    public static abstract class Stub extends android.os.Binder\n        implements com.marakana.android.fibonacci.IFibonacciService {\n    \n            ...\n\n        public static com.marakana.android.fibonacci.IFibonacciService asInterface(android.os.IBinder obj) {\n            ... \n        }\n\n        public android.os.IBinder asBinder() { \n            return this;\n        }\n        ... \n    }\n\n    public long fibJR(long n) throws android.os.RemoteException; \n    public long fibJI(long n) throws android.os.RemoteException; \n    public long fibNR(long n) throws android.os.RemoteException; \n    public long fibNI(long n) throws android.os.RemoteException; \n    public com.marakana.android.fibonaccicommon.FibonacciResponse fib(\n        com.marakana.android.fibonaccicommon.FibonacciRequest request) throws android.os.RemoteException;\n\n}\n\n```\n\n\n#FibonacciService - 实现AIDL接口，并暴露给我们的客户端\n\n* 我们开始创建一个新的Android工程，工程中有AIDL服务的实现和访问服务实现的机制（例如：绑定）\n\n    。工程名：FibonacciService\n    。编译目标：Android 2.2+（API 8+）\n    。包名：com.marakana.android.fibonacciservice \n    。应用名称：Fibonacci Service\n    。最小SDK: 8+\n    。不需要指定一个Activity\n\n* 为了能够访问common APIS，我们需要将工程跟FibonacciCommon链接：   \n* 工程名称* → Android → Library → Add... → FibonacciCommon* \n\n    。最终， FibonacciService/default.properties 有了 android.library.reference.1=../FibonacciCommon ，并且 FibonacciService/.project 也链接到了 FibonacciCommon \n\n* 我们的服务端将会使用实现了斐波那契算法的 com.marakana.android.fibonaccinative.FibLib  \n\n* 我们将FibonacciNative工程从Java类（和jni/implementation）中拷贝出（或移出）\n\n    。不要忘记在*FibonacciService/*下运行*ndk-build*去生成需要的本地库\n\n* 我们现在已经准备好实现AIDL接口，通过扩展自动生成的*com.marakana.android.fibonaccicommon.IFibonacciService.Stub*(继承于*android.os.Binder*)\n\n* FibonacciService/src/com/marakana/android/fibonacciservice/IFibonacciServiceImpl.java* \n\n```java\n\n￼package com.marakana.android.fibonacciservice; \n\nimport android.os.SystemClock;\nimport android.util.Log;\nimport com.marakana.android.fibonaccicommon.FibonacciRequest; \nimport com.marakana.android.fibonaccicommon.FibonacciResponse; \nimport com.marakana.android.fibonaccicommon.IFibonacciService; \nimport com.marakana.android.fibonaccinative.FibLib;\n\npublic class IFibonacciServiceImpl extends IFibonacciService.Stub { \n\n    private static final String TAG = \"IFibonacciServiceImpl\";\n\n    public long fibJI(long n) {\n        Log.d(TAG, String.format(\"fibJI(%d)\", n)); return FibLib.fibJI(n);\n    }\n\n    public long fibJR(long n) {\n        Log.d(TAG, String.format(\"fibJR(%d)\", n)); return FibLib.fibJR(n);\n    }\n\n    public long fibNI(long n) {\n        Log.d(TAG, String.format(\"fibNI(%d)\", n)); return FibLib.fibNI(n);\n    }\n\n    public long fibNR(long n) {\n        Log.d(TAG, String.format(\"fibNR(%d)\", n)); return FibLib.fibNR(n);\n    }\n\n    public FibonacciResponse fib(FibonacciRequest request) { \n\n        Log.d(TAG,String.format(\"fib(%d, %s)\", request.getN(), request.getType())); \n        long timeInMillis = SystemClock.uptimeMillis(); \n        long result;\n    \n        switch (request.getType()) {\n            case ITERATIVE_JAVA:\n                    result = this.fibJI(request.getN()); break;\n            case RECURSIVE_JAVA:\n                result = this.fibJR(request.getN()); break;\n            case ITERATIVE_NATIVE:\n                result = this.fibNI(request.getN()); break;\n            case RECURSIVE_NATIVE:\n                result = this.fibNR(request.getN()); break;\n            default: return null;\n        }\n\n        timeInMillis = SystemClock.uptimeMillis() - timeInMillis; \n\n        return new FibonacciResponse(result, timeInMillis);\n    } \n}\n\n```\n\n#暴露我们定义的AIDL服务实现给客户端\n\n* 为了让客户端（调用者）使用我们的服务，首先它们需要绑定服务\n* 但是，为了让它们绑定服务，首先我们需要暴露服务，通过*android.app.Service's\nonBind(Intent)*实现\n\n*FibonacciService/src/com/marakana/android/fibonacciservice/FibonacciService.java* \n\n```java\n\npackage com.marakana.android.fibonacciservice;\n\nimport android.app.Service; \nimport android.content.Intent; \nimport android.os.IBinder; \nimport android.util.Log;\n\npublic class FibonacciService extends Service { //1 \n\n    private static final String TAG = \"FibonacciService\"; \n    private IFibonacciServiceImpl service; //2\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        this.service = new IFibonacciServiceImpl(); //3 \n        Log.d(TAG, \"onCreate()'ed\"); //5\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        Log.d(TAG, \"onBind()'ed\"); //5\n        return this.service; //4\n    }\n\n    @Override\n    public boolean onUnbind(Intent intent) {\n        Log.d(TAG, \"onUnbind()'ed\"); //5\n        return super.onUnbind(intent); \n    }\n\n    @Override\n    public void onDestroy() {\n        Log.d(TAG, \"onDestroy()'ed\"); \n        this.service = null; super.onDestroy();\n    } \n}\n\n```\n\n\n1、我们通过继承*android.app.Service*创建另外一个“service”。*FibonacciService* 对象的目的是提供访问我们的基于Binder的*IFibonacciServiceImpl* 对象\n\n2、我们在这里简单的申明了一个本地的*IFibonacciServiceImpl* 引用，该引用是一个单例（例如：所有的客户端将会共享一个实例）。因为，我们的*IFibonacciServiceImpl* 不需要初始化的位置，所以我们可以在这里初始化，但是，我们选择推迟到*onCreate（）* 方法中    \n\n3、现在，我们初始化了提供给客户端（在onBind（Intent）方法中）的*IFibonacciServiceImpl* 。如果，我们的*IFibonacciServiceImpl* 需要访问*Context*，我们可以传递一个引用给它（例如：*android.app.Service* ，实现了*android.content.Context* ）。很多基于Binder的服务端使用*context*去访问其它平台的方法\n\n4、我们在这里提供客户端访问*IFibonacciServiceImpl* 对象的接口。我们选择，只拥有一个*IFibonacciServiceImpl* 实例对象（因此所有的客户端都可以共享它），但是，我们也可以给每一个客户端提供一个*IFibonacciServiceImpl* 实例对象\n\n5、我们添加一些日志，这样跟踪服务的生命周期就会简单\n\n\n* 最后，我们在*AndroidManifest.xml* 中注册*FibonacciService* ，所以，客户端可以找到它\n\n* FibonacciService/AndroidManifest.xml* \n\n```xml\n\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        package=\"com.marakana.android.fibonacciservice\" android:versionCode=\"1\" android:versionName=\"1.0\">\n    <uses-sdk android:minSdkVersion=\"8\" />\n    <application android:icon=\"@drawable/icon\" android:label=\"@string/app_name\">\n        <service android:name=\".FibonacciService\"> \n            <intent-filter>\n                <action     android:name=\"com.marakana.android.fibonaccicommon.IFibonacciService\" /> <!-- 1 -->\n            </intent-filter> \n        </service>\n    </application> \n</manifest>\n\n```\n\n\n1、名称可以随便定义，但是，我们通常使用派生的AIDL接口的全名\n\n\n#FibonacciClient - 使用基于Binder的AIDL服务\n\n* 我们开始创建一个新的Android工程，这个工程是我们之前实现的AIDL Service的客户端\n\n    。工程名：FibonacciClient\n    。构建的目标：Android 2.2+（API 8+）\n    。包名：com.marakana.android.fibonacciclient\n    。应用名：Fibonacci Client\n    。创建的Activity：FibonacciActivity  \n\n        * Activity中的大部分代码来自于 FibonacciNative\n\n    。最小SDK版本：8+\n\n* 我们需要将工程链接到*FibonacciCommon*上，以便能够访问公有的APIs：\n* project properties → Android → Library → Add... → FibonacciCommon\n\n    。结果，*FibonacciClient/default.properties* 中有*android.library.reference.1=../FibonacciCommon* 和*FibonacciClient/.classpath* ，并且*FibonacciClient/.project* 也链接到了*FibonacciCommon* \n    。作为一种替代，我们可以避免首先创建*FibonacciCommon* \n    。FibonacciService 和 FibonacciClient 都需要拷贝：IFibonacciService.aidl、 FibonacciRequest.aidl、 FibonacciResponse.aidl、 FibonacciResult.java、 FibonacciResponse.java++\n    。但是，我们不喜欢重复的代码（即使在运行时二进制文件会重复）\n\n\n* 我们的客户端将会使用 FibonacciNative 应用中的字符串和布局资源\n\n* FibonacciClient/res/values/strings.xml* \n\n```xml\n\n￼<?xml version=\"1.0\" encoding=\"utf-8\"?> \n    <resources>\n        <string name=\"hello\">Get Your Fibonacci Here!</string>\n        <string name=\"app_name\">Fibonacci Client</string>\n        <string name=\"input_hint\">Enter N</string>\n        <string name=\"input_error\">Numbers only!</string>\n        <string name=\"button_text\">Get Fib Result</string>\n        <string name=\"progress_text\">Calculating...</string>\n        <string name=\"fib_error\">Failed to get Fibonacci result</string> \n        <string name=\"type_fib_jr\">fibJR</string>\n        <string name=\"type_fib_ji\">fibJI</string> \n        <string name=\"type_fib_nr\">fibNR</string> \n        <string name=\"type_fib_ni\">fibNI</string>\n    </resources>\n\n```\n\n* FibonacciClient/res/layout/main.xml* \n\n```xml\n\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:orientation=\"vertical\" android:layout_width=\"fill_parent\" android:layout_height=\"fill_parent\">\n\n        <TextView android:text=\"@string/hello\" android:layout_height=\"wrap_content\"\n            android:layout_width=\"fill_parent\" android:textSize=\"25sp\" android:gravity=\"center\"/> \n        <EditText android:layout_height=\"wrap_content\"\n            android:layout_width=\"match_parent\" android:id=\"@+id/input\" android:hint=\"@string/input_hint\" android:inputType=\"number\" android:gravity=\"right\" />\n\n        <RadioGroup android:orientation=\"horizontal\" android:layout_width=\"match_parent\" android:id=\"@+id/type\" android:layout_height=\"wrap_content\">\n\n        <RadioButton android:layout_height=\"wrap_content\"\n            android:checked=\"true\" android:id=\"@+id/type_fib_jr\" android:text=\"@string/type_fib_jr\"\n            android:layout_width=\"match_parent\" android:layout_weight=\"1\" /> \n            \n            <RadioButton android:layout_height=\"wrap_content\"\n                android:id=\"@+id/type_fib_ji\" android:text=\"@string/type_fib_ji\"\n                android:layout_width=\"match_parent\" android:layout_weight=\"1\" /> \n            <RadioButton android:layout_height=\"wrap_content\"\n                android:id=\"@+id/type_fib_nr\" android:text=\"@string/type_fib_nr\"\n                android:layout_width=\"match_parent\" android:layout_weight=\"1\" /> \n            <RadioButton android:layout_height=\"wrap_content\"\n                android:id=\"@+id/type_fib_ni\" android:text=\"@string/type_fib_ni\"\n                android:layout_width=\"match_parent\" android:layout_weight=\"1\" /> \n        </RadioGroup>\n\n        <Button android:text=\"@string/button_text\" android:id=\"@+id/button\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" />\n\n        <TextView android:id=\"@+id/output\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" android:textSize=\"20sp\"\n            android:gravity=\"center|top\"/> \n    </LinearLayout>\n\n```\n\n* 我们现在准备实现客户端\n\n*FibonacciClient/src/com/marakana/android/fibonacciclient/FibonacciActivity.java* \n\n```java\n\npackage com.marakana.android.fibonacciclient;\n\nimport android.app.Activity;\nimport android.app.ProgressDialog;\nimport android.content.ComponentName; import android.content.Intent;\nimport android.content.ServiceConnection; import android.os.AsyncTask;\n￼￼￼\n￼￼import android.os.Bundle;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.os.SystemClock;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener; import android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.RadioGroup;\nimport android.widget.TextView;\nimport android.widget.Toast;\nimport com.marakana.android.fibonaccicommon.FibonacciRequest; \nimport com.marakana.android.fibonaccicommon.FibonacciResponse; \nimport com.marakana.android.fibonaccicommon.IFibonacciService;\n\npublic class FibonacciActivity extends Activity implements OnClickListener, ServiceConnection {\n\n    private static final String TAG = \"FibonacciActivity\";\n    private EditText input; // our input n\n    private Button button; // trigger for fibonacci calcualtion \n    private RadioGroup type; // fibonacci implementation type \n    private TextView output; // destination for fibonacci result \n    private IFibonacciService service; // reference to our service\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n\n        super.onCreate(savedInstanceState); \n        super.setContentView(R.layout.main);\n        // connect to our UI elements\n        this.input = (EditText) super.findViewById(R.id.input); \n        this.button = (Button) super.findViewById(R.id.button); \n        this.type = (RadioGroup) super.findViewById(R.id.type); \n        this.output = (TextView) super.findViewById(R.id.output); // request button click call-backs via onClick(View) method \n        this.button.setOnClickListener(this);\n        // the button will be enabled once we connect to the service\n        this.button.setEnabled(false); \n    }\n\n    @Override\n    protected void onResume() {\n\n        Log.d(TAG, \"onResume()'ed\");\n        super.onResume();\n        // Bind to our FibonacciService service, by looking it up by its name \n        // and passing ourselves as the ServiceConnection object\n        // We'll get the actual IFibonacciService via a callback to\n        // onServiceConnected() below\n        if (!super.bindService(new Intent(IFibonacciService.class.getName()),\n            this, BIND_AUTO_CREATE)) {\n            Log.w(TAG, \"Failed to bind to service\");\n        }\n    }\n\n￼￼  @Override\n    protected void onPause() {\n        Log.d(TAG, \"onPause()'ed\");\n        super.onPause();\n        // No need to keep the service bound (and alive) any longer than \n        // necessary\n        super.unbindService(this);\n    }\n\n    public void onServiceConnected(ComponentName name, IBinder service) { \n\n        Log.d(TAG, \"onServiceConnected()'ed to \" + name);\n        // finally we can get to our IFibonacciService\n        this.service = IFibonacciService.Stub.asInterface(service);\n        // enable the button, because the IFibonacciService is initialized\n        this.button.setEnabled(true); }\n\n    public void onServiceDisconnected(ComponentName name) { \n\n        Log.d(TAG, \"onServiceDisconnected()'ed to \" + name);\n        // our IFibonacciService service is no longer connected this.service = null;\n        // disabled the button, since we cannot use IFibonacciService\n        this.button.setEnabled(false); }\n        // handle button clicks\n    \n    public void onClick(View view) {\n        // parse n from input (or report errors) final long n;\n        String s = this.input.getText().toString(); \n        if (TextUtils.isEmpty(s)) {\n            return; }\n    \n        try {\n            n = Long.parseLong(s);\n        } catch (NumberFormatException e){\n            this.input.setError(super.getText(R.string.input_error)); \n            return;\n        }\n\n    // build the request object\n    final FibonacciRequest.Type type;\n\n    switch (FibonacciActivity.this.type.getCheckedRadioButtonId()) { \n\n        case R.id.type_fib_jr:\n            type = FibonacciRequest.Type.RECURSIVE_JAVA;\n        break;\n\n        case R.id.type_fib_ji:\n            type = FibonacciRequest.Type.ITERATIVE_JAVA;\n        break;\n\n        case R.id.type_fib_nr:\n            type = FibonacciRequest.Type.RECURSIVE_NATIVE;\n        break;\n\n        case R.id.type_fib_ni:\n            type = FibonacciRequest.Type.ITERATIVE_NATIVE;\n        break; \n\n        default:\n            return; \n    }\n\n    final FibonacciRequest request = new FibonacciRequest(n, type);\n    \n    // showing the user that the calculation is in progress\n    final ProgressDialog dialog = ProgressDialog.show(this, \"\",\n\n    ￼super.getText(R.string.progress_text), true);\n    // since the calculation can take a long time, we do it in a separate \n    // thread to avoid blocking the UI\n    new AsyncTask<Void, Void, String>() {\n    \n        @Override\n        protected String doInBackground(Void... params) {\n    \n            // this method runs in a background thread\n            try {\n                    long totalTime = SystemClock.uptimeMillis(); \n                    FibonacciResponse response = FibonacciActivity.this.service.fib(request);\n                    totalTime = SystemClock.uptimeMillis() - totalTime; // generate the result\n                    return String.format(\n                        \"fibonacci(%d)=%d\\nin %d ms\\n(+ %d ms)\", n, response.getResult(), response.getTimeInMillis(), totalTime - response.getTimeInMillis());\n                } catch (RemoteException e) {\n                    Log.wtf(TAG, \"Failed to communicate with the service\", e); return null;\n                } \n            }\n\n            @Override\n            protected void onPostExecute(String result) {\n    \n                // get rid of the dialog\n                dialog.dismiss();\n                if (result == null) {\n                // handle error\n                Toast.makeText(FibonacciActivity.this, R.string.fib_error, Toast.LENGTH_SHORT).show();\n                } else {\n                    // show the result to the user FibonacciActivity.this.output.setText(result);\n                }   \n            }\n        }.execute(); // run our AsyncTask \n    }\n}\n\n```\n\n>注意：我们应该尽量避免在Activity中调用匿名的AsyncTask。这里我们走了一个捷径。\n\n* 我们的Activity应该已经在 AndroidManifest.xml 文件中注册过\n\n* FibonacciClient/AndroidManifest.xml* \n\n```xml\n\n￼<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:versionCode=\"1\" android:versionName=\"1.0\" package=\"com.marakana.android.fibonacciclient\">\n    <uses-sdk android:minSdkVersion=\"8\" />\n    <application android:icon=\"@drawable/icon\" android:label=\"@string/app_name\">\n        <activity android:name=\"com.marakana.android.fibonacciclient.FibonacciActivity\" android:label=\"@string/app_name\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" /> \n            </intent-filter>\n        </activity> \n    </application>\n</manifest>\n\n```\n* 结果应该是这样的：  \n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/feibo_client.png)\n\n\n#异步 Binder IPC\n\n* 在 AIDL 接口中申明，Binder 允许客户端与服务端之间异步通信\n* 当然，我们仍然关心结果，通常异步回调都是通过监听器回调的方式\n* 客户端给提供了一个引用作为回调的监听器，然后，当监听器调用的时候，客户端和服务端的角色就会调换：客户端端的监听器变成了服务端的，服务端的监听器变成了客户端的\n* 最好的解释是通过一个示例（基于 Fibonacci）\n* 代码是可用的 \n\n    。一个ZIP存档文件：https://github.com/marakana/FibonacciAsyncBinderDemo/zipball/master  \n\n    \n    。通过Git：git clone https://github.com/marakana/FibonacciAsyncBinderDemo.git\n\n\n#FibonacciCommon - 定义一个单向的 AIDL 服务\n\n\n* 首先，我们需要一个监听器，它自己是单向的\"服务\": \n\n*FibonacciCommon/src/com/marakana/android/fibonaccicommon/IFibonacciServiceResponseListener.aidl:* \n\n```java\n\npackage com.marakana.android.fibonaccicommon;\nimport com.marakana.android.fibonaccicommon.FibonacciRequest;\nimport com.marakana.android.fibonaccicommon.FibonacciResponse;\n\noneway interface IFibonacciServiceResponseListener { \n    void onResponse(in FibonacciResponse response);\n}\n\n```\n\n* 现在我们可以创建我们自己的单向的(例如：异步的) 接口：\n\n*FibonacciCommon/src/com/marakana/android/fibonaccicommon/IFibonacciService.aidl:* \n\n```java\n\npackage com.marakana.android.fibonaccicommon;\n\nimport com.marakana.android.fibonaccicommon.FibonacciRequest;\nimport com.marakana.android.fibonaccicommon.FibonacciResponse;\nimport com.marakana.android.fibonaccicommon.IFibonacciServiceResponseListener;\n\noneway interface IFibonacciService {\n    void fib(in FibonacciRequest request, in IFibonacciServiceResponseListener listener);\n}\n\n```\n\n#FibonacciService - 实现异步的 AIDL 服务\n\n\n* 服务的实现调用这个监听器，而不是返回一个结果：\n\n*FibonacciService/src/com/marakana/android/fibonacciservice/IFibonacciServiceImpl.java:* \n\n```java\n\npackage com.marakana.android.fibonacciservice;\n\nimport android.os.RemoteException; \nimport android.os.SystemClock; import android.util.Log;\nimport com.marakana.android.fibonaccicommon.FibonacciRequest;\nimport com.marakana.android.fibonaccicommon.FibonacciResponse;\nimport com.marakana.android.fibonaccicommon.IFibonacciService;\nimport com.marakana.android.fibonaccicommon.IFibonacciServiceResponseListener; \nimport com.marakana.android.fibonaccinative.FibLib;\n\npublic class IFibonacciServiceImpl extends IFibonacciService.Stub {\n\n    private static final String TAG = \"IFibonacciServiceImpl\";\n\n    @Override\n    public void fib(FibonacciRequest request,\n        IFibonacciServiceResponseListener listener) throws RemoteException { \n\n        long n = request.getN();\n        Log.d(TAG, \"fib(\" + n + \")\");\n        long timeInMillis = SystemClock.uptimeMillis();\n        long result;\n\n        switch (request.getType()) { \n\n            case ITERATIVE_JAVA:\n                result = FibLib.fibJI(n);\n            break;\n\n            case RECURSIVE_JAVA:\n                result = FibLib.fibJR(n);\n            break;\n\n            case ITERATIVE_NATIVE:\n                result = FibLib.fibNI(n);\n            break;\n\n            case RECURSIVE_NATIVE:\n                result = FibLib.fibNR(n);\n            break;\n\n            default:\n                result = 0; \n        }\n\n        timeInMillis = SystemClock.uptimeMillis() - timeInMillis; \n        Log.d(TAG, String.format(\"Got fib(%d) = %d in %d ms\", n, result,timeInMillis));\n\n        listener.onResponse(new FibonacciResponse(result, timeInMillis));\n    }\n}\n\n```\n\n\n>注意：服务不会因监听返回而阻塞，因为，监听器本身也是单向的\n\n\n#FibonacciClient - 实现异步的 AIDL   客户端\n\n\n* 最后，实现的客户端，它本身也必然实现服务端的监听器\n\n*FibonacciClient/src/com/marakana/android/fibonacciclient/FibonacciActivity.java:* \n\n```java\n\npackage com.marakana.android.fibonacciclient;\n\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.app.ProgressDialog; \nimport android.content.ComponentName; \nimport android.content.Intent;\nimport android.content.ServiceConnection; \nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.RemoteException;\nimport android.os.SystemClock;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener; \nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.RadioGroup;\nimport android.widget.TextView;\nimport com.marakana.android.fibonaccicommon.FibonacciRequest;\nimport com.marakana.android.fibonaccicommon.FibonacciResponse;\nimport com.marakana.android.fibonaccicommon.IFibonacciService;\nimport com.marakana.android.fibonaccicommon.IFibonacciServiceResponseListener;\n\npublic class FibonacciActivity extends Activity implements OnClickListener, ServiceConnection {\n\n    private static final String TAG = \"FibonacciActivity\";\n    ￼// the id of a message to our response handler\n    private static final int RESPONSE_MESSAGE_ID = 1;\n    // the id of a progress dialog that we'll be creating\n    private static final int PROGRESS_DIALOG_ID = 1;\n    private EditText input; // our input n\n    private Button button; // trigger for fibonacci calcualtion \n    private RadioGroup type; // fibonacci implementation type \n    private TextView output; // destination for fibonacci result \n    private IFibonacciService service; // reference to our service \n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        \n        super.onCreate(savedInstanceState); \n        super.setContentView(R.layout.main);\n        // connect to our UI elements\n        this.input = (EditText) super.findViewById(R.id.input); \n        this.button = (Button) super.findViewById(R.id.button); \n        this.type = (RadioGroup) super.findViewById(R.id.type); \n        this.output = (TextView) super.findViewById(R.id.output); // request button click call-backs via onClick(View) method\n        this.button.setOnClickListener(this);\n        \n        // the button will be enabled once we connect to the service\n        this.button.setEnabled(false); \n    }\n\n    @Override\n    protected void onResume() {\n    \n        Log.d(TAG, \"onResume()'ed\");\n        super.onResume();\n        // Bind to our FibonacciService service, by looking it up by its name \n        // and passing ourselves as the ServiceConnection object\n        // We'll get the actual IFibonacciService via a callback to\n        // onServiceConnected() below\n\n        if (!super.bindService(new Intent(IFibonacciService.class.getName()),this, BIND_AUTO_CREATE)) {\n            Log.w(TAG, \"Failed to bind to service\");\n        } \n    }\n\n    ￼￼@Override\n    protected void onPause() {\n        Log.d(TAG, \"onPause()'ed\");\n        super.onPause();\n        // No need to keep the service bound (and alive) any longer than \n        // necessary\n        super.unbindService(this);\n    }\n\n    public void onServiceConnected(ComponentName name, IBinder service) { \n        Log.d(TAG, \"onServiceConnected()'ed to \" + name);\n        // finally we can get to our IFibonacciService\n        this.service = IFibonacciService.Stub.asInterface(service);\n        // enable the button, because the IFibonacciService is initialized\n        this.button.setEnabled(true); \n    }\n\n    public void onServiceDisconnected(ComponentName name) { \n        Log.d(TAG, \"onServiceDisconnected()'ed to \" + name);\n        // our IFibonacciService service is no longer connected \n        this.service = null;\n        // disabled the button, since we cannot use IFibonacciService\n        this.button.setEnabled(false); \n    }\n\n    // handle button clicks\n    public void onClick(View view) {\n        // parse n from input (or report errors) \n        final long n;\n        String s = this.input.getText().toString(); \n        if (TextUtils.isEmpty(s)) {\n            return; \n        }\n\n        try {\n            n = Long.parseLong(s);\n        } catch (NumberFormatException e)\n        { \n            this.input.setError(super.getText(R.string.input_error)); \n            return;\n        }\n\n        // build the request object\n        final FibonacciRequest.Type type;\n        switch (FibonacciActivity.this.type.getCheckedRadioButtonId()) { \n\n            case R.id.type_fib_jr:\n                type = FibonacciRequest.Type.RECURSIVE_JAVA;\n            ￼break;\n\n            case R.id.type_fib_ji:\n                type = FibonacciRequest.Type.ITERATIVE_JAVA;\n            break;\n\n            case R.id.type_fib_nr:\n                type = FibonacciRequest.Type.RECURSIVE_NATIVE;\n            break;\n\n            case R.id.type_fib_ni:\n                type = FibonacciRequest.Type.ITERATIVE_NATIVE;\n            break; \n            \n            default:\n                return; \n        }\n\n        final FibonacciRequest request = new FibonacciRequest(n, type); \n        try {\n            Log.d(TAG, \"Submitting request...\");\n            long time = SystemClock.uptimeMillis();\n            // submit the request; the response will come to responseListener this.service.fib(request, this.responseListener);\n            time = SystemClock.uptimeMillis() - time;\n            Log.d(TAG, \"Submited request in \" + time + \" ms\");\n            // this dialog will be dismissed/removed by responseHandler super.showDialog(PROGRESS_DIALOG_ID);\n            } catch (RemoteException e) {\n                Log.wtf(TAG, \"Failed to communicate with the service\", e);\n            } \n    }\n}\n\n```\n\n>注意：我们的监听器不应该持有 Activity 的强引用（但是它确实是，因为它是匿名的内部类），但是，在当前情况下，为了简明起见，我们忽略了它的正确性\n\n\n#通过Binder共享内存\n\n* Binder事务处理的数据是通信中的部分拷贝——如果传送很多数据是不明智的\n\n    。事实上，binder通过事务强制限制了发送数据的大小\n\n* 如果我们想共享文件中的数据，我们只需要发送文件的描述符\n\n    。这是我们要求媒体播放器去播放音频、视频文件的方式——我们只是发送文件的描述符到软件驱动中\n\n* 如果我们想要发送的数据在内存中，我们可以分多次发送，而不是试图一次发送所有的数据\n\n    。让我们的设计复杂化\n\n\n* 可选，我们可以利用Android's ashmem（Ashmem匿名共享内存）工具\n\n    。它的Java封装器*android.os.MemoryFile* 不是用于第三方app共享内存\n    。丢到本地（通过JNI），然后是直接使用ashmem\n\n\n* 本地共享内存通过 frameworks/base/libs/binder/Parcel.cpp 来实现：\n\n    。*void Parcel::writeBlob(size_t len, WritableBlob outBlob)*   \n    。*status_t Parcel::readBlob(size_t len, ReadableBlob outBlob)*    \n\n* 以下是简略的实现：\n\n* Client\n\n```C++\n\nsize_t len = 4096;\nint fd = ashmem_create_region(\"Parcel Blob\", len); \nashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);\nvoid*  ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); \nashmem_set_prot_region(fd, PROT_READ);\nwriteFileDescriptor(fd, true);\n// write into ptr for len as desired\n...\nmunmap(ptr, len);\nclose(fd);\n\n```\n\n* Service\n\n```C\n\nint fd = readFileDescriptor();\nvoid*  ptr = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); // read from ptr up to len as desired\n...\nmunmap(ptr, len);\n\n```\n\n\n>注意：为了方便删除错误处理。并且，*writeFileDescriptor(...) readFileDescriptor(...)* 是libbinder提供的\n\n\n#Binder的局限性\n\n* 每个进程最多支持15个Binder线程\n\n*frameworks/base/libs/binder/ProcessState.cpp* \n\n```C\n\n...\nstatic int open_driver() {\n    int fd = open(\"/dev/binder\", O_RDWR); if (fd >= 0) {\n        ...\n        size_t maxThreads = 15;\n        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); ...\n    } else { \n        ...\n    }\n\n    return fd; \n}\n...\n\n```\n\n    \n    。避免阻塞Binder线程\n\n     \n    。如果我们需要执行一个长连接的任务，最好创建我们自己的线程\n\n\n* Binder限制运行在每个进程中的并发事务的缓存为1M\n\n    \n    。如果参数或返回值的大小超过了缓存，就会抛出*TransactionTooLargeException* \n\n    \n    。因为指定进程中的所有事务的缓存都是共享的，所以很多中等大小的事务也会耗尽缓存\n\n    \n    。当*TransactionTooLargeException* 异常抛出的时候，我们不知道是发送请求出了错，还是接收响应出了错\n\n    \n    。让事务数据尽量小，或者使用共享内存（ashmem）\n\n\n#Binder - 安全机制\n\n\n* Binder确实需要直接处理“安全”问题，但是，它只会在“可信的”执行环境和DAC中运行\n\n\n* Binder驱动只允许注册一个 CONTEXT_MGR（例如：servicemanager ）\n\n*drivers/staging/android/binder.c:* \n\n```C\n\n...\nstatic long binder_ioctl(struct file * filp, unsigned int cmd, unsigned long arg) {\n    ...\n    switch (cmd) {\n        ...\n        case BINDER_SET_CONTEXT_MGR:\n            if (binder_context_mgr_node != NULL) {\n                printk(KERN_ERR \"binder: BINDER_SET_CONTEXT_MGR already set\\n\"); \n                ret = -EBUSY;\n                goto err;\n            }\n\n        ...\n        binder_context_mgr_node = binder_new_node(proc, NULL, NULL); ...\n    }\n    \n    ... \n    ...\n\n```\n\n\n*  servicemanager同样也只允许从信任的UID中注册（比如：system、radio、media 等等）\n\n*frameworks/base/cmds/servicemanager/service_manager.c:* \n\n```C\n\n￼...\nstatic struct {\n    \n    unsigned uid;\n    const char * name; \n\n} allowed[] = {\n#ifdef LVMX\n    { AID_MEDIA,\"com.lifevibes.mx.ipc\" },\n#endif\n    { AID_MEDIA, \"media.audio_flinger\" },\n    { AID_MEDIA, \"media.player\" },\n    { AID_MEDIA, \"media.camera\" },\n    { AID_MEDIA, \"media.audio_policy\" },\n    { AID_DRM,  \"drm.drmManager\" },\n    { AID_NFC,  \"nfc\" },\n    { AID_RADIO, \"radio.phone\" },\n    { AID_RADIO, \"radio.sms\" },\n    { AID_RADIO, \"radio.phonesubinfo\" },\n    { AID_RADIO, \"radio.simphonebook\" },\n    /*  TODO: remove after phone services are updated: */ \n    { AID_RADIO, \"phone\" },\n    { AID_RADIO, \"sip\" },\n    { AID_RADIO, \"isms\" },\n    { AID_RADIO, \"iphonesubinfo\" },\n    { AID_RADIO, \"simphonebook\" }, \n};\n...\nint svc_can_register(unsigned uid, uint16_t * name) \n{\n    unsigned n;\n    if ((uid == 0) || (uid == AID_SYSTEM))\n        return 1;\n\n    for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)\n        if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))\n            return 1; \n\n    return 0;\n}\n...\nint do_add_service(struct binder_state * bs,\n    uint16_t * s, unsigned len, void * ptr, unsigned uid)\n{\n    ...\n    if (!svc_can_register(uid, s)) {\n        LOGE(\"add_service('%s',%p) uid=%d - PERMISSION DENIED\\n\",\n        str8(s), ptr, uid); return -1;\n    }\n    ... \n}\n...\n\n```\n\n\n* 每个Binder事务发送者的UID和PID很容易被访问到：\n\n    。*android.os.Binder.getCallingPid()*    \n    。*android.os.Binder.getCallingUid()*  \n\n\n* 一旦我们知道如何访问UID，我们就可以很容易的解决调用app的问题，通过PackageManager.getPackagesForUid(int uid)\n\n\n* 一旦我们知道如何调用app，我们就可以很容易的检查app是否有权限，通过PackageManager.getPackageInfo(String packageName, int flags) (检查PackageManager.GET_PERMISSIONS 标记)\n\n* 但是，更简单的方式是：\n\n    。*Context.checkCallingOrSelfPermission(String permission)*，如果调用的进程授予了权限就会返回*PackageManager.PERMISSION_GRANTED*，否则返回*PackageManager.PERMISSION_DENIED*\n\n    \n    。*Context.enforceCallingPermission(String permission, String message)*——如果调用者没有请求的权限就会抛出*SecurityException*异常\n\n* 以下显示应用框架服务是如何执行权限的\n\n![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_security.png)\n\nFor example:    \n*frameworks/base/services/java/com/android/server/VibratorService.java:* \n\n```java\n\n￼package com.android.server;\n...\npublic class VibratorService extends IVibratorService.Stub {\n    ...\n    public void vibrate(long milliseconds, IBinder token) {\n    if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {\n            throw new SecurityException(\"Requires VIBRATE permission\");\n        }\n    ... \n    }\n    ... \n}\n\n```\n\n*frameworks/base/services/java/com/android/server/LocationManagerService.java:* \n\n```java\n\npackage com.android.server;\n...\npublic class LocationManagerService extends ILocationManager.Stub implements Runnable {\n    ...\n    private static final String ACCESS_FINE_LOCATION =\n    android.Manifest.permission.ACCESS_FINE_LOCATION; \n    private static final String ACCESS_COARSE_LOCATION =\n    android.Manifest.permission.ACCESS_COARSE_LOCATION; \n    ...\n    private void checkPermissionsSafe(String provider) { \n        if ((LocationManager.GPS_PROVIDER.equals(provider)\n            || LocationManager.PASSIVE_PROVIDER.equals(provider))\n            && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION)\n            != PackageManager.PERMISSION_GRANTED)) {\n\n            throw new SecurityException(\"Provider \" + provider\n                + \" requires ACCESS_FINE_LOCATION permission\");\n            }\n\n        if (LocationManager.NETWORK_PROVIDER.equals(provider)\n            && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)\n            && (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {\n        \n            throw new SecurityException(\"Provider \" + provider\n                + \" requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission\");\n            } \n    }\n\n    ...\n\n    private Location _getLastKnownLocationLocked(String provider) {\n        checkPermissionsSafe(provider);\n        ... \n    }\n\n    ...\n    public Location getLastKnownLocation(String provider) {\n        ... \n        _getLastKnownLocationLocked(provider); \n        ...\n    } \n}\n\n```\n\n\n#Binder消亡通知\n\n\n* 持有IBinder对象的引用，我们可以：\n* 询问远程对象是否存活，通过 Binder.isBinderAlive()and Binder.pingBinder() \n* 获取到远程对象消亡的通知，通过 Binder.linkToDeath(IBinder.DeathRecipient recipient, int flags)\n* 如果我们想要清除与远程Binder对象关联的不可用资源（如：监听器）\n\nFor example:    \n*frameworks/base/services/java/com/android/server/LocationManagerService.java:* \n\n```java\n\npublic class LocationManagerService extends ILocationManager.Stub implements Runnable \n{ \n    ...\n    private Receiver getReceiver(ILocationListener listener) { \n        IBinder binder = listener.asBinder();\n        Receiver receiver = mReceivers.get(binder);\n        \n        if (receiver == null) {\n            receiver = new Receiver(listener);\n            ...\n            receiver.getListener().asBinder().linkToDeath(receiver, 0); \n            ...\n        }\n\n        return receiver; \n    }\n\n    private final class Receiver implements IBinder.DeathRecipient, \n        PendingIntent.OnFinished {\n        \n        final ILocationListener mListener;\n        ...\n        Receiver(ILocationListener listener) {\n            mListener = listener;\n            ... \n        }\n        ...\n\n        public void binderDied() {\n            ...\n            removeUpdatesLocked(this); \n        }\n    \n        ... \n    }\n\n... \n\n}\n\n```\n\n\n#Binder报告\n\n\n* 在 active/failed 事务中，Binder驱动通过 /proc/binder/ 报告多种状态\n\n    。/proc/binder/failed_transaction_log    \n    。/proc/binder/state     \n    。/proc/binder/stats     \n    。/proc/binder/transaction_log   \n    。/proc/binder/transactions \n    。/proc/binder/proc/<pid>    \n\n\n>注意：在设备中将 /sys/kernel/debug/binder 替换成 /proc/binder 的时候，设置 debugfs 为 enable \n\n\n#Additional Binder Resources\n\nAndroid Binder by Thorsten Schreiber from Ruhr-Universität Bochum      \nAndroid Binder IPC Mechanism - 0xLab by Jim Huang (黄敬群￼￼￼) from 0xlab   \nAndroid’s Binder by Ken from Ken’s Space    \nDianne Hackborn on Binder in Android on Linux Kernel Mailing List archive (LKML.ORG)    \nAndroid Binder on elinux.org    \nShare memory using ashmem and binder in the android framework   \nIntroduction to OpenBinder and Interview with Dianne Hackborn   \nOpen Binder Documentation   \nBinder IPC - A walk-though native IAudioFlinger::setMode call   \n\n\n#Summary\n\nWe learned about:\n\nWhy Android needs IPC   \nWhat is Binder and how it differs from other forms of IPC   \nBinder vs Intent/ContentProvider/Messenger-based IPC    \nBinder Terminology  \nBinder Communication and Discovery Model    \nAIDL    \nBinder Object Reference Mapping \nSynchronous vs Async Binder Invocations \nMemory Sharing  \nBinder Limitations  \nSecurity Implications   \nDeath Notification  \nReporting   \n\n\n#Questions?\n\nDidn’t we run out of time by now? :-)   \nThank you for your patience!    \nSlides and screencast from this class will be posted to: http://mrkn.co/bgnhg You can follow me here:   \n@agargenta  \n+Aleksandar Gargenta http://marakana.com/s/author/1/aleksandar_gargenta \nThis slide-deck is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.\n\n"
  },
  {
    "path": "issue-22/深入剖析Android网络开发库-part1.md",
    "content": "深入剖析Android网络开发库-part1: OkHttp, Volley and Gson\n---\n\n> * 原文链接 : [Android Networking I: OkHttp, Volley and Gson](https://medium.com/@sotti/android-networking-i-okhttp-volley-and-gson-72004efff196)\n* 原文作者 :[Pablo Costa](https://medium.com/@sotti)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成 \n\n\n##动机\n\n在开发 Android App 时网络模块是必不可少的，无论你的 App 是需要读取图片、向服务器请求数据，或者从网络中获取单字节数据，你都需要网络。\n\n既然网络模块对 Android App 这么重要，Android 开发者需要考虑的一个问题就是：我该用什么方式去开发网络模块呢？事实上在这一方面有许多不错的库可供开发者使用，而且你甚至能把多个库混合在一起用。\n\n对 Android 开发者来说，使用这些优秀的网络开发库是因为 Android 框架层提供的 API 不太好用，而且有许多麻烦需要解决，每当开发者需要使用网络模块，就需要不断地编写模板代码。在这样的前提下，开发者们确实可以考虑尝试找到一种处理所有这些麻烦的解决方案，于是各种各样的网络开发库诞生了。\n\n> *在网络开发库出现之前，在 Android 中完成网络模块简直是噩梦，而现在只需要找到最符合项目需求的解决方案就够了。*\n\n这篇博文的目的在于分享我个人对网络开发的积累和思考，这些东西有些是我自己想到，有些是别人告诉我的，我希望能给你带来一些帮助。\n\n在这篇博文中，我们将研究一个特别的解决方案：OkHttp, Volley 和 Gson，在后面的博文中我会研究其他的组合。\n\n##假设\n\n- JSON 是与服务器 API 通信的方式\n\n- 你在使用 Android Studio 和 Gradle\n\n##OkHttp\n\n\n[OkHttp](https://goo.gl/ZPX7X4)是一个全新的、快速、有效的 Http 客户端，它支持 HTTP/2 和 SPDY，而且能帮你完成许多事情。如果想要知道联网有多麻烦的话，看一看 OkHttp 的源码你就知道了，OkHttp 帮我们完成了诸如连接池、解压缩、缓存……等等操作。OkHttp 表现得就像传输层。\n\n\nOkHttp 使用了 [Okio](https://goo.gl/F7rG1V)，Okio 是对 java.io 和 java.nio 进行补充的库，主要用于简化对数据的访问、存储和处理。\n\n\nOkHttp 和 Okio 都是由 [Square](https://square.github.io/) 的工程师开发的。\n\n##Volley\n\nVolley 是一个简化网络任务的库，负责处理请求、加载、缓存、线程、异步等等操作，它能处理 JSON 格式的数据，图片，缓存，纯文字以及允许开发者实现一些自定制服务。\n\nVolley 被设计用于 RPC 模式的网络操作，在耗时短的操作中表现的很好。\n\nVolley在Android 2.2以及之前版本的系统上运行时传输层是Apache HttpClient，大于Android 2.2的系统版本则是 HttpURLConnection。问题在于这些 Http 栈在不同 Android 版本中存在许多问题。\n\nVolley 允许开发者轻松地配置 OkHttp 为它的传输层。\n\nVolley 由 Google 开发。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*dWGwx6UUjc0tocYzFNBLEw.jpeg)\n\n在 Ficus Kirkpatrick 眼里，Android 的网络模块就像上面这张图，一大堆并行和串行的调用。\n\n##Gson\n\nGson was developed by Google.\n\n[Gson](https://goo.gl/gqAAi) 是通过 Java 的反射机制使你用 Java 实现的数据 Model 对象与 JSON 数据对象能够相互转换的 JSON 序列化与反序列化的库。而且你还能根据自己的需求实现自定义的序列化转换器和反序列化转换器。\n\nGson 由 Google 开发。\n\n###安装\n\n因为 Gradle 与 Android Studio 之间的依赖关系，你需要在需要开发的 App 的 build.gradle 文件中添加以下的 Gradle 命令：\n\n> compile 'com.squareup.okio:okio:1.5.0'\n> compile 'com.squareup.okhttp:okhttp:2.4.0'\n> compile 'com.mcxiaoke.volley:library:1.0.16'\n> compile 'com.google.code.gson:gson:2.3.1'\n\n当这些库更新，命令中对应的版本号也会发生改变。所以最好在输入这类命令的时候避免输入版本号。\n\n除了 Volley，上面的所有依赖都由官方提供，但通过上面的命令获得的 Volley 绝对是可靠的，至于为什么 Volley 没有官方提供的依赖，在我写这篇博文的时候也还搞不清楚，反正就是没有……\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*QUO-bbhxtocaU0XC_dap_Q.png)\n\n##Volley\n\nVolley 执行网络操作的方式是：创建请求，然后把请求添加到一个队列中。对于一个应用来说，一个队列就够了。所以每当你想要创建一个请求，你只能得到唯一的 Volley 队列，并把该请求添加到这个队列里面。\n\n我在下面的方法中使用了一个该队列的全局单例：\n\n```java\n/**\n * Returns a Volley request queue for creating network requests\n *\n * @return {@link com.android.volley.RequestQueue}\n */\npublic RequestQueue getVolleyRequestQueue()\n{\n   if (mRequestQueue == null)\n   {\n      mRequestQueue = Volley.newRequestQueue(this, new OkHttpStack(new OkHttpClient()));\n   }\n   return mRequestQueue;\n}\n```\n\n在我们用于创建新请求队列的方法的参数中，有一个是 HttpStack。如果你使用了不提供 HttpStack 的方法，Volley 则会创建一个受当前 Android API 版本影响的栈。（如果当前 Android API 版本低于9，使用的是 AndroidHttpClient；如果大于9，则是 HttpURLConnection）\n\n正如我之前所说，我们希望让 OkHttp 成为我们的传输层，而这也是我们让 OkHttpStack 成为其中一个参数的原因。我使用的 OkHttpClient 实现是[这样的](https://gist.github.com/bryanstern/4e8f1cb5a8e14c202750)。\n\n下面是将请求添加到 Volley 的请求队列的方法：\n\n```java\n/**\n * Adds a request to the Volley request queue with a given tag\n * \n * @param request is the request to be added\n * @param tag is the tag identifying the request\n */\npublic static void addRequest(Request<?> request, String tag)\n{\n    request.setTag(tag);\n    addRequest(request);\n}\n/**\n * Adds a request to the Volley request queue\n * \n * @param request is the request to add to the Volley queue\n */\npublic static void addRequest(Request<?> request)\n{\n    getInstance().getVolleyRequestQueue().add(request);    \n}\n```\n\n同时这个方法也是用于取消应该在 onStop 生命周期方法中正常使用的请求。\n\n```java\n/**\n * Cancels all the request in the Volley queue for a given tag\n *\n * @param tag associated with the Volley requests to be cancelled\n */\npublic static void cancelAllRequests(String tag)\n{\n    if (getInstance().getVolleyRequestQueue() != null)\n    {\n        getInstance().getVolleyRequestQueue().cancelAll(tag);\n    }\n}\n```\n\n到这里我们已经用上了 Volley 和 OkHttp，所以我们可以开始让 String，JsonObject，JsonArray 这类对象成为我们请求的对象，那么 JsonOject 对象的实现如下：\n\n```java\nJsonObjectRequest jsonObjectRequest =\n        new JsonObjectRequest(Request.Method.GET, mUrl, new Response.Listener<JSONObject>()\n        {\n            @Override\n            public void onResponse(JSONObject response)\n            {\n                // Deal with the JSONObject here\n            }\n        },\n        new Response.ErrorListener()\n        {\n            @Override\n            public void onErrorResponse(VolleyError error)\n            {\n                // Deal with the error here\n            }\n        });\n\nApp.addRequest(jsonObjectRequest, mTAG);\n```\n\n现在我们需要解析 JSON 对象，转换为我们的 Java 数据 Model。我们从每一个 Volley 请求中获得的响应（String，JsonObject，JsonArray）并没有那么好用。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/1280/1*ve4BJ_IfQ3BIKHLL-3HGqg.png)\n\n但你在 Android 网络开发的领域并不孤独，还有许多开发者陪你翻山越岭：\n\n##Gson\n\n有了 Gson，我们能固自定义请求去获得符合我们 Java 数据 Model 的 Java 对象响应，这无疑会让我们很爽。我们需要的只是一个 Volley Request 类的子类：GsonRequest 类，[就像这样](https://goo.gl/Bo2ahN)。\n\n在下面的例子中，我们将展示怎么通过 GET 调用转换并解析 Json 对象：\n\n```java\n/**\n * Returns a dummy object parsed from a Json Object to the success  listener and a Volley error to the error listener\n *\n * @param listener is the listener for the success response\n * @param errorListener is the listener for the error response\n *\n * @return @return {@link com.sottocorp.sotti.okhttpvolleygsonsample.api.GsonGetRequest}\n */\npublic static GsonRequest<DummyObject> getDummyObject\n(\n        Response.Listener<DummyObject> listener,\n        Response.ErrorListener errorListener\n)\n{\n    final String url = \"http://www.mocky.io/v2/55973508b0e9e4a71a02f05f\";\n\n    final Gson gson = new GsonBuilder()\n            .registerTypeAdapter(DummyObject.class, new DummyObjectDeserializer())\n            .create();\n\n    return new GsonRequest<>\n            (\n                    url,\n                    new TypeToken<DummyObject>() {}.getType(),\n                    gson,\n                    listener,\n                    errorListener\n            );\n}```\n\n在下面的代码中，我们将展示怎么通过 GET 调用转换并解析 JsonArray：\n\n```java\n/**\n * Returns a dummy object's array in the success listener and a Volley error in the error listener\n *\n * @param listener is the listener for the success response\n * @param errorListener is the listener for the error response\n *\n * @return @return {@link com.sottocorp.sotti.okhttpvolleygsonsample.api.GsonGetRequest}\n */\npublic static GsonRequest<ArrayList<DummyObject>> getDummyObjectArray\n(\n        Response.Listener<ArrayList<DummyObject>> listener,\n        Response.ErrorListener errorListener\n)\n{\n    final String url = \"http://www.mocky.io/v2/5597d86a6344715505576725\";\n\n    final Gson gson = new GsonBuilder()\n            .registerTypeAdapter(DummyObject.class, new DummyObjectDeserializer())\n            .create();\n\n    return new GsonRequest<>\n            (\n                    url,\n                    new TypeToken<ArrayList<DummyObject>>() {}.getType(),\n                    gson,\n                    listener,\n                    errorListener\n            );\n}\n```\n\nGson 解析 GsonRequest 类对象在后台的工作线程中完成，而不是在主线程中。\n\n我在上面的示例中提供了反序列化转换器，但要注意的是：并不是强制需要提供序列化，反序列化转换器的，而且 Gson 能够通过 JSON 文件很好的处理与类对应的数据域名（包括 case）。我只是更喜欢用我自己定义的序列化，反序列化转换器而已。\n\n在上面的例子中我们都是使用 GET 调用，POST 调用相应的代码则在这：[GsonPostRequest](https://goo.gl/6t4nJM) and [使用方法](https://goo.gl/Rp8TMx).\n\n> *OkHttp 作为 Volley 的传输层，便利地创建用于通过 Gson 在将请求提交到主线程之前解析 Java 对象的网络请求*\n\n##读取图片\n\n###ImageLoader and NetworkImageView\n\nVolley 有一个便于读取图片的叫做 NetworkImageView 的自定义 View（ImageView 的子类）。你可以设置一个 URL，默认的 View Holder 和读取错误显示的图片。例如：\n\n```java\nmNetworkImageView = (NetworkImageView) itemView.findViewById(R.id.networkImageView);\nmNetworkImageView.setDefaultImageResId(R.drawable.ic_sun_smile);\nmNetworkImageView.setErrorImageResId(R.drawable.ic_cloud_sad);\nmNetworkImageView.setImageUrl(imageUrl, App.getInstance().getVolleyImageLoader());\n```\n\n上面的代码的重点在 setImageUrl 方法中，该方法接收两个参数：图片的网络地址和 ImageLoader（Volley 用于加载和缓存从 URL 中获取的图片的辅助类）。\n\n```java\n/**\n * Returns an image loader instance to be used with Volley.\n *\n * @return {@link com.android.volley.toolbox.ImageLoader}\n */\npublic ImageLoader getVolleyImageLoader()\n{\n    if (mImageLoader == null)\n    {\n        mImageLoader = new ImageLoader\n                (\n                        getVolleyRequestQueue(),\n                        App.getInstance().getVolleyImageCache()\n                );\n    }\n\n    return mImageLoader;\n}\n\n/**\n * Returns a bitmap cache to use with volley.\n *\n * @return {@link LruBitmapCache}\n */\nprivate LruBitmapCache getVolleyImageCache()\n{\n    if (mLruBitmapCache == null)\n    {\n        mLruBitmapCache = new LruBitmapCache(mInstance);\n    }\n    return mLruBitmapCache;\n}\n```\n\n还有一个重要的类则是 LruBitmapCache。Volley 没有为我们提供具体实现，但[我们可以在这里得到](https://developer.android.com/training/volley/request.html)，这可以根据设备的内存空间设置缓存大小，非常方便。\n\n###ImageRequest\n\n某些时候我们可能不想使用 NetworkImageView。例如我们想要显示圆形的图片，此时使用的库是 [CircleImageView](https://github.com/hdodenhof/CircleImageView)。那么在这种情况下我们就得使用 ImageRequest 来满足需求：\n\n```java\nfinal CircleImageView circleImageView =\n            (CircleImageView) findViewById(R.id.circularImageView);\n\n    // Retrieves an image specified by the URL, displays it in the UI.\n    final com.android.volley.toolbox.ImageRequest imageRequest =\n            new ImageRequest\n            (\n                    mImageUrl,\n                    new Response.Listener<Bitmap>()\n                    {\n                        @Override\n                        public void onResponse(Bitmap bitmap)\n                        {\n                            circleImageView.setImageBitmap(bitmap);\n                        }\n                    },\n                    0,\n                    0,\n                    ImageView.ScaleType.CENTER_INSIDE,\n                    null,\n                    new Response.ErrorListener()\n                    {\n                        public void onErrorResponse(VolleyError error)\n                        {          circleImageView.setImageResource(R.drawable.ic_cloud_sad);\n                        }\n                    }\n            );\n    // Access the RequestQueue through your singleton class.\n    App.getInstance().getVolleyRequestQueue().add(imageRequest);\n}\n```\n\n##Curiosities\n\n- 在这片博文中提到的所有组件（Okio, OkHttp, Volley 和 Gson）都可以独立被使用，并不一定非要绑定在一起用。\n\n- 我在篇博文引用的文章中的第一篇是由 Jesse Wilson 写的。Jesse Wilson 是唯一站在 Android HTTP, Gson, OkHttp 和 Okio 前面的男人，我认为他值得被我特别提出来并予以夸耀。\n\n- OkHttp 的实现在 Android 4.4 版本会退化为使用 HttpURLConnection。Twitter, Facebook 和 Snapchat 的 Bundle 也会这样。\n\n##在2015年这个解决方法有用吗？\n\nVolley/Gson 解决办法是成熟的而且在2013年和2014年都广泛被使用，原因在于：它们是 Google 官方推荐的方法，而且在各类 Android 开发者网站中被提及。到了今天，它们仍是网络开发的不错选择，因为它们很简便，运行效果也不错。但需要考虑的是，Volley 和 Gson 的更新频率已经越来越低了（而且好久没更新了）。\n\n- Android Networking II: OkHttp, Retrofit, Moshi and Picasso. (coming article)\n\n- Android Networking III: ION (coming article)\n\n分析不同的解决方案，并比较各种解决方案的优劣，如：效率、简便性、能否深度定制都是判断某个解决方案能否被采用的标准\n\n你可能会对下面的博文感兴趣：\n\n- Android Networking II: OkHttp, Retrofit, Moshi and Picasso. (coming article)\n\n- Android Networking III: ION (coming article)\n\n##Github project sample\n\n- [源代码](https://github.com/Sottti/OkHttpVolleyGsonSample)\n\n##Sources\n\n- [Okio](https://github.com/square/okio)\n\n- [OkHttp 网页](https://square.github.io/okhttp/)\n\n- [Volley 在 Google IO 2013 中的介绍](https://goo.gl/MvNPQn)\n\n- [Android Developers Volley training](https://goo.gl/9HXqJw)\n\n- [OkHttp 作为 Volley 传输层被使用](https://goo.gl/ZjtY6Q)\n\n- [A Few Ok Libraries (Jake Wharton at Droidcon Montreal)](https://www.youtube.com/watch?v=WvyScM_S88c)\n\n- [HttpStack 用 OkHttp 自带的请求/响应 API 的实现，而不是依赖于 HttpURLConnection](https://plus.google.com/+JakeWharton/posts/31jhDwaCvtg)"
  },
  {
    "path": "issue-23/Android-MVPR-架构模式-Part1.md",
    "content": "Android MVPR 架构模式-Part1\n---\n\n> * 原文链接 : [MVPR: A FLEXIBLE, TESTABLE ARCHITECTURE FOR ANDROID (PT. 1)](http://www.philosophicalhacker.com/2015/07/07/mvpr-a-flexible-testable-architecture-for-android-pt-1/)\n* 原文作者 :[Matthew Dupree](http://philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成 \n\n> 全面的单元测试能提高内部系统的代码质量，因为系统的每一个组件都需要被测试，因此每个单元都需要在系统外被构建，在测试环境中进行测试。对对象进行单元测试需要创建该对象，提供该对象需要的依赖，并与它进行交互，最终检验测试环境的输出是否与预期一致。因此，为了让一个类易于进行单元测试，类的依赖必须明确，而且能够轻易地被替代和明确被调用和验证的责任。在软件工程领域中，这就意味着代码必须松耦合、高内聚，也就是说：设计优秀的。\n\n> Steve Freeman 和 Nat Pryce，也就是测试驱动的面向对象开发。\n\n最近我在尝试让 Google 的 IO App 变得可单元测试，我这样做的其中一个原因是验证 Freeman 和 Pryce 在引用中对单元测试的总结。即使现在我还是没有把 IOSched 中的任何一个 Activity 重构，但我已经在重构代码的过程中感受到他们所说的东西了。\n\n我现在在重构的 Activity 是 SessionDetailActivity，如果你一直有在关注我的话就会知道我说的是哪个 Activity，但如果你只是第一次看我的博文，你可以看看下面这张图了解下 SessionDetailActivity 的界面是咋样的。\n\n![](http://i0.wp.com/www.philosophicalhacker.com/wp-content/uploads/2015/05/io-testing-talk-04.png?resize=169%2C300)\n\n就像我在这个系列博文的序中所说，要让 SessionDetailActivity 可被单元测试，有几个麻烦必须解决。我在这个系列的上一篇博文中说过，对它动态构建的 View 进行单元测试是一个挑战，但在那篇博文中，我提到我解决这个问题的办法并不能治本，因为在 View 和 Presenter 之间存在着循环依赖。\n\n循环依赖是 Android 应用架构存在大问题的征兆：Activity 和 Presenter 都违反了单一职责原则，它们至少需要完成两件事：为 View 绑定数据并对用户的输入作出相应。这也是为什么 SessionDetailActivity 这个类会作为 Android 开发的 Model 被使用，使得类的代码数超过1000行。\n\n我坚信有更好的办法架构我们的应用，在接下来的博文里，我会提出一种拥有以下特性的新架构：\n\n1. 将通常由 Presenter 和 Activity 负责的多重职责打破\n\n2. 打破一般存在于 View 间或 Activity 和 Presenter 之间的循环依赖\n\n3. 允许我们用构造方法对所有为用户展示数据以及相应用户输入的对象进行依赖注入\n\n4. 让 UI 相关的业务逻辑易于进行单元测试，而且不可能在没有必要的依赖时被构建以履行他们的职责，而且通过利用聚合和多态性修改对象的行为。\n\n在这片博文中，我会尝试总结开发新的 Android 应用架构的原因。\n\n##为什么需要新的架构？\n\n##Activity/Fragment/Presenter 会变得臃肿\n\nActivity 和 Fragment（接下来我会统称为 Activities，但我说的也适用于 Fragment）是违反单一职责原则的典型：\n\n- 处理 View 的事件\n\n- 更新数据 Model\n\n- 调用其他 View\n\n- 与系统组件交互\n\n- 处理系统事件\n\n- 基于系统事件更新 View\n\n正如 Richa 所说，这些职责大部分从 Activities 中剥离，但即使我们这样做了，Activities 还是违反了单一职责原则。即使是最简单的 Activities 还是需要将 Model 的数据和 View 绑定，并对用户输入作出相应，例如：\n\n```java\npublic class SessionDetailActivity extends BaseActivity implements\n        LoaderManager.LoaderCallbacks<Cursor>,\n        ObservableScrollView.Callbacks {\n    \n    //...\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        //Responsibility 1: Responding to user's action (in this case, a click)\n        mAddScheduleButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                boolean starred = !mStarred;\n                SessionsHelper helper = new SessionsHelper(SessionDetailActivity.this);\n                showStarred(starred, true);\n                helper.setSessionStarred(mSessionUri, starred, mTitleString);\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n                    mAddScheduleButton.announceForAccessibility(starred ?\n                            getString(R.string.session_details_a11y_session_added) :\n                            getString(R.string.session_details_a11y_session_removed));\n                }\n\n                /* [ANALYTICS:EVENT]\n                 * TRIGGER:   Add or remove a session from My Schedule.\n                 * CATEGORY:  'Session'\n                 * ACTION:    'Starred' or 'Unstarred'\n                 * LABEL:     Session title/subtitle.\n                 * [/ANALYTICS]\n                 */\n                AnalyticsManager.sendEvent(\n                        \"Session\", starred ? \"Starred\" : \"Unstarred\", mTitleString, 0L);\n            }\n        });\n\n        //...\n\n        //Responsibility 2: Fetching and binding data to the view\n        LoaderManager manager = getLoaderManager();\n        manager.initLoader(SessionsQuery._TOKEN, null, this);\n        manager.initLoader(SpeakersQuery._TOKEN, null, this);\n        manager.initLoader(TAG_METADATA_TOKEN, null, this);\n    }\n```\n\n\nGoogle IOSched 应用中的 SessionDetailActivity 就是 Activity 即使只负责绑定数据到 View 中和响应用户输入也会变得臃肿的绝佳范例。即使我们把这部分代码从 SessionDetailActivity 中剥离，还是有一个类有700多行代码。不信我？你大可以去看看源码，Presenter 也会因为 Activity 那样的原因变得臃肿：Presenter 通常负责绑定数据以及响应用户输入，所以 Presenter 也需要像 Activity 那样通过剥离额外的职责被瘦身。\n\n###Activities/Fragment/Presenter 通常在 View 间存在循环依赖\n\nActivities 通常通过它们和 View 之间的循环依赖履行绑定数据到 View 和响应用户输入的职责（例如：作为 setContentView() 方法参数的 View）。下面是范例：\n\n```java\nmAddScheduleButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                boolean starred = !mStarred;\n                SessionsHelper helper = new SessionsHelper(SessionDetailActivity.this);\n                showStarred(starred, true);\n                helper.setSessionStarred(mSessionUri, starred, mTitleString);\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n                    mAddScheduleButton.announceForAccessibility(starred ?\n                            getString(R.string.session_details_a11y_session_added) :\n                            getString(R.string.session_details_a11y_session_removed));\n                }\n \n                /* [ANALYTICS:EVENT]\n                 * TRIGGER:   Add or remove a session from My Schedule.\n                 * CATEGORY:  'Session'\n                 * ACTION:    'Starred' or 'Unstarred'\n                 * LABEL:     Session title/subtitle.\n                 * [/ANALYTICS]\n                 */\n                AnalyticsManager.sendEvent(\n                        \"Session\", starred ? \"Starred\" : \"Unstarred\", mTitleString, 0L);\n            }\n        });\n```\n\nSessionDetailActivity 持有对 mAddScheduleButton 的引用，而且 mAddScheduleButton 也持有对 SessionDetailActivity 的引用。我等会会说，这样的循环依赖限制我们通常用于 Activities 中实现 UI 相关的业务逻辑的方法。\n\nMVP 的 Presenter 有着和它们和 View 相同的循环依赖，在我能详细解释之前，我必须简单地介绍传统 Android 应用架构中 View 和 MVP 模式中 View 的区别。\n\nMVP 模式中的 View 就像我定义的，只是 MVP 模式三巨头其中之一，通常被定义为一个接口，而且一般会在 Activity，Fragment 或 Android 传统架构中的 View 中实现。Android 传统架构中的 View 就像它的名字，是一个 View 的子类。\n\n使用 MVP 模式中的 View 和 Presenter 仅仅是在它们之间无形中重新创建了和 Android 传统架构中 View 和 Activities 之间相同的循环依赖。\n\n![](http://i2.wp.com/www.philosophicalhacker.com/wp-content/uploads/2015/07/CircularDependency-011.png?resize=300%2C222)\n\n![](http://i2.wp.com/www.philosophicalhacker.com/wp-content/uploads/2015/07/CircularDependency-021.png?resize=300%2C222)\n\nPresenter 需要 MVP 模式中的 View 使得它们能绑定数据到 MVP 模式中的 View，MVP 模式 中的 View 需要对 Presenter 的引用，使得它能传递点击和其他 UI 相关的事件给 Presenter。Square 的[博文](https://corner.squareup.com/2014/10/advocating-against-android-fragments.html)就有存在着循环依赖的 MVP 模式的实现。\n\n循环依赖在你想要为单元测试构建对象（或通常情况下）都会产生问题。然而，通常情况下，我们都不会把 MVP 模式的 View 和 Presenter 或 Activities 和 View 间的循环依赖当作问题，因为 Activities 和 Fragment 被系统初始化，而且因为我们并没有用依赖注入去注入 Activity 和/或 Fragment 的依赖。相反的是，我们只是初始化了 Activity 在 onCreate() 方法中需要的任何依赖：\n\n```java\npublic class MyActivity extends Activity implements MVPView {\n\n    View mButton;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_browse_sessions);\n        //...\n        final Presenter presenter = new Presenter(this);\n        mButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                presenter.onButtonClicked();\n            }\n        });\n    }\n}\n```\n\n初始化在 onCreate() 方法中依赖的混合类，然而，限制我们使用组合和多态性去实现 UI 相关的业务逻辑。下面是一个你应该使用多态性实现 UI 相关的业务逻辑的例子：假设你开发了一个被用户使用的应用，而且用户在不同的等级时有不同的特权，那么他们需要通过邮件验证或回答其他用户提的问题以提高等级。我们可以想象有许多按钮用于完成依赖等级完成的不同功能，或 View 由用户等级决定的初始状态。多态性为我们提供整洁，可拓展的方式去实现这样的逻辑：我们创建一个 Presenter 用于为用户绑定不同的等级，不管用户在什么等级中，我们都能把 MVP 模式中的 View 传到特定的 Presenter 子类中，并让该子类处理相应的点击事件或者基于用户的等级呈现 UI。当然了，还有许多架构 Android 应用的方式，使得我们能够在存在 Presenter 和 MVP 模式中的 View 间循环依赖的情况下利用多态性，但这些方法都不够优雅，或者说他们为了完成单元测试作出了极大的贡献。\n\n这篇博文剩下的篇幅已经不足以让我一一细述我记得的那些解决方法，但我能简要的说说为什么解决 MVP 模式中的 View 和 Presenter 间循环依赖的方法不理想。你可以想象我们可以只创建一个 MVP 模式的 View 或 Presenter，而没有它们履行职责所需的任何依赖。换句话说，我们可以像下面这样：\n\n```java\npublic class MyActivity extends Activity implements MVPView {\n \n    View mButton;\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_browse_sessions);\n        //...\n        final Presenter presenter = new Presenter();\n        //****\n        presenter.setView(this);\n        //****\n        mButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                presenter.onButtonClicked();\n            }\n        });\n    }\n}\n```\n\n这样我们就能通过多态性解决上面提到的问题，但这并没有打破循环依赖。它能做的是允许我们在无效状态创建一个对象。这并不是最简洁的解决办法，把这放在 Freeman 和 Pryce 话里：\n\n> “创建或不创建，不需要尝试”\n\n> 我们想要确保总是创建有效的对象，部分地创建对象然后通过设置它的属性完成它是脆弱的……\n\n##结论\n\nPresenter 和 Activities 违反了单一职责原则，他们常常负责绑定数据到 View 中和响应用户的输入，这些都会使 Activities 和 Presenter 变得臃肿。\n\nPresenter 和 Activities 常常会因为他们和 View 间的循环依赖拥有多重职责，即使这样的循环引用不会带来什么问题，但这会更难以对 View 和/或 Presenter 进行单元测试，而且会限制我们使用多态性实现 UI 相关的业务逻辑。\n\n就像我之前说的，我认为会有一种架构应用的办法不会有上面这些烈士，在下一篇博文中，我会提出可供选择的架构。"
  },
  {
    "path": "issue-23/Android-MVVM模式.md",
    "content": "#Android MVVM模式\n\n---\n> * 原文链接 : [MVVM on Android using the Data Binding Library](http://stablekernel.com/blog/mvvm-on-android-using-the-data-binding-library/)\n* 原文作者 : [Ross Hambrick](http://stablekernel.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [shenyansycn](https://github.com/shenyansycn)\n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :   完成\n\nGoogle 2015开发者大会终于来了，其中只有一个开发工具真的让我兴奋。我们看到了一系列的改善，例如Android M和以用户为中心的特性，Android Studio支持NDK（C/C++），矢量图，heap分析，改进的主题和layout编辑器，Gradle性能改善，等等。我高兴的是我们终于有了一个[Design Support Library][3] 可以实现[Material Design UI patterns][4]。但大多数这些都已经被其他社区工具和库实现。\n\n有一件事是社区渴望的，但是还没有一个好的模式或工具，那就是如何在我们的项目中改进model和views相互协调的代码。直到现在，Activities和Fragment 通常包含许多脆弱的，不可检测的和乏味的与views关联的代码。但[Data Binding Library][6]改变了这一切。\n\n自从这个新的库被公布，我就在工作中用到了，并深入的去发现它能做什么和不能做什么和你能做什么和不能做什么。我将目前学到的东西分享出来了，我也希望听到你的分享。\n\n#目标和效率\n\n我们应该对这个库感兴趣因为它允许我们用更多的方法处理views。这有助于去除那些索然无味的代码，而伴随着这些代码的很可能是与UI相关的bug。更少的代码意味着更少的错误，对吧？对的。\n\n我的另一个更大的目标和某些社区需要的是一致的，那就是降低view和应用逻辑的单元测试消耗。测试总归是可以测试点，但它是如此艰难,需要这么多额外的工作,很多人(当然不是我)就跳过吧。这是我们做的更好的机会。\n\n#MVVM\n\n在这个library官方文档中，提供了一个例子直接绑定一个实体类User到layout的一个属性上。他们给你一个想法，你可以想象下边这样：\n\n\n```xml\nandroid:visibility=\"<strong>@{age &lt; 13 ? View.GONE : View.VISIBLE}</strong>\"\n```\n\n但是我们这里要弄清楚：我没有推荐实体类直接绑定或者将逻辑放入这些绑定的layout文件中。如果你做了这些事情会更难测试view逻辑和调试。我们是相反的，更容易的测试和调试。\n\n这是MVVM模式到来的情景。简化事情，通过在引进一个在绑定View和响应事件之间的ViewModel层解耦。ViewModel将是一个POJO（简单的面向对象）并且包含了和view相关的所有逻辑，使得[更容易的测试][7]和调试。在这个模式中，绑定将仅仅是一个ViewModel方法结果和View属性设置的一对一的映射。此外，这使测试和调试view在单元测试中是逻辑简单和可以实现的。\n\n#项目设置\n\n让我们开始吧。注意：我喜欢简洁或许会遗漏一些有用信息，所以我建议以[官方文档][9]作为参考。\n\n增加这些依赖到你项目的**build.gradle**中：\n\n```gradle\nclasspath 'com.android.databinding:dataBinder:1.0-rc1'\nclasspath 'com.neenbedankt.gradle.plugins:android-apt:1.4'\n```\n\n然后再你的app module的**build.gradle**文件中增加这些；\n\n```gradle\napply plugin: 'com.android.databinding'\napply plugin: 'com.neenbedankt.android-apt'\n\ndependencies {\n  apt 'com.android.databinding:compiler:1.0-rc1'\n}\n```\n\n*注意*：如果你有任何像dagger-compiler **provided** 依赖项，你为了阻止他们被添加到你的classpath中需要改变**provided** 为**apt**\n\n官方文档中没有提到[android-apt][10]，但你会需要的。android-apt插件会使Android Studio在编译时知道生成的类。当试图调试问题时，这是至关重要的，了解更多绑定机制是如何工作的。\n\n#绑定设置\n\n用来接受初始化值和更新的view和接受来自View处理事件的ViewMdel是通过绑定实现的。Data Binding Library自动生成一个绑定类替你处理大部分的繁重工作。让我们看看绑定发生的细节。\n\n#变量声明\n\n在你的layout文件中，你需要添加一个新的顶层**layout**封装你已经存在的布局结构。其中第一个元素是一个**data**元素，包含了在你的布局绑定中用到的所有类型。\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <data>\n        <variable name=\"viewModel\" type=\"com.example.viewmodels.CustomerViewModel\" />\n    </data>\n    ...\n    <!-- the rest of your original layout here -->\n</layout>\n```\n\n这里我们声明了一个**viewModel**变量，我们将稍后设置为我们Fragment的一个特定实例。\n\n#绑定声明\n\n我们可以使用这个**viewModel**做许多有趣的事情，它的内容绑定到layout的widget属性\n\n```xml\n<EditText\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:hint=\"@string/customer_name\"\n  android:inputType=\"textCapWords\"\n  android:text=\"@{viewModel.customerName}\"\n  app:enabled=\"@{viewModel.primaryInfoEnabled}\"\n  app:error=\"@{viewModel.nameError}\"\n  app:addTextChangedListener=\"@{viewModel.nameWatcher}\"\n  app:onFocusChangeListener=\"@{viewModel.nameFocusListener}\" />\n```\n\n这里我们绑定了text值，enable状态，错误信息，text改变监听事件和焦点改变的监听事件。\n\n*注意*：**android**命名空间可以在view的任何标准xml中使用。但是**app**命名空间必须在没有相对应的xml属性时被使用。同样，使用**app**命名空间代替**android**标准属性看起来像在IDE中去掉错误高亮提示。\n\n**警告**：由于绑定代码产生的顺序，在生成的绑定代码中你应使用**text**属性的**android**命名空间预防排序问题。否则，在setError()后发生setText()的明显错误。\n\n#ViewModel实现\n\n现在让我们看下ViewModel中相对应的方法。ViewModel继承了**BaseObservable**(这不是必须的，但它帮你节省了很多工作)，公共方法的名字是layout要绑定的并且返回类型要与view的setter方法的类型一致。\n\n\n```java\npublic class CustomerViewModel extends BaseObservable {\n\n  public String getCustomerName() {\n    return customer.getName();\n  }\n\n  public boolean isPrimaryInfoEnabled() {\n    return editMode && !customer.isVerified();\n  }\n\n  @Bindable\n  public String getNameError() {\n    if (customer.getName().isEmpty()) {\n      return \"Must enter a customer name\";\n    }\n    return null;\n  }\n\n  public TextWatcher getNameWatcher() {\n    return new SimpleTextWatcher() {\n      @Override\n      public void onTextChanged(String text) {\n        customer.setName(text);\n      }\n    };\n  }\n\n  public EditText.OnFocusChangeListener getNameFocusListener() {\n    return (v, hasFocus) -> {\n      if (!hasFocus) notifyPropertyChanged(BR.nameError);\n    };\n  }\n}\n```\n\n第一个方法仅仅返回了一个值。第二个和第三个做了运算确定返回值。其余的观察者或监听者对view的改变做出反应。最伟大的是EditText被ViewModel的值自动填充，如果没有通过条件的验证显示一个错误并给ViewModel发送一个事件改变的更新通知。\n\n#通知\n\n注意上边的**validate()**方法。这个监听调用**notifyPropertyChanged(...)**。这将触发view重新绑定这个属性和可能显示一个然后返回错误。**BR**类是为你生成的，好像R文件。允许你在代码中引用可绑定的属性。更详细的通知不是可能的除非用 **@Bindable**注释属性。因为我在layout中仅指定了viewModel变量，它默认创建了唯一“bindable”值。\n\n你也可以通过使用通用的**notifyChange()**方法触发view重新绑定所有的属性。\n\n**小心的是，**当TextWatcher调用了**notifyChange()**会进入引起text重复调用的情况，哪个调用TextWatcher，哪个引起**notifyChange()**,你看看会发生什么。\n\n这看起来像一个有下列情形的最好练习：\n\n- 短路的通知周期将被查看，是否值在通知前发生了变更。\n\n- 避免在view的自己通知自己发生了变更。如果其他view需要在这个情况下被通知，你需要绑定和通知再更细的级别\n\n#统一处理。\n\n到目前为止，我们建立的说明都相互做出了反应，做了对的事情。剩下的事情就是引导绑定途径。这个将在你的Activity或Fragment中。自从我用Fragment加载所有view，我将像这个样子。\n\n```android\nFragmentCustomerBinding binding = DataBindingUtil.bind(view);\nviewModel.init(this, customerId);\nbinding.setViewModel(viewModel);\n```\n\n设想这里有一个添加了依赖的**viewModel**实例，并且知道如何通过ID进行加载Cusromer。我也通过这实现了一个监听接口，当Activity相关联的事情发生时fragment会被通知，像使用了FragmentManager，发送了一个Intent，结束当前Activity，等等。\n\n\n#更进一步的说\n\n我们看到了创建UI的基本构成反应ViewModel同时在变。因为你没有在更新UI浪费时间，你能花费时间做：\n\n - 利用ViewModel有效性开启或关闭按键状态。\n - 在Viewmodel中基于工作的完成显示或隐藏加载进度条。\n - 在你的逻辑的局限性的各个方面运用单元测试。\n\n这里仍有一些不完善的地方，比如，你不能很容易的绑定ActionBar给ViewModel\n（有可能放弃了旧的ActionBar接口而直接使用了Toolbar？）\n\n我们在回到需要Activity Context的特定环境下。你能在ViewModel实现接口或设置Activity/Fragment作为一个监听。或者就是在Fragment中使用ViewModel和调用它的方法。每一种方法都能用ViewModel实现你的UI逻辑。\n\n试想下现在Fragment你写的绑定代码，之前是什么样的，节省下来的时间，你可以用在ViewModel的[自动化测试][11]上面\n\n#缺少了什么\n\n这个库工作的很好但现在仍然是测试版。我期待它成熟并且提供更好的开发体验。这些是我想看到的：\n\n - CMD+B layout页面中的方法导航快捷键可以在ViewMode中使用\n - 出错的时候有更清晰的错误信息\n - layout中的自动完成和类型检查\n - 通过双向绑定来简化引用\n - 绑定支持AdapterViews\n\n\n  [1]: http://android-developers.blogspot.com/2015/05/android-design-support-library.html\n  [2]: http://www.google.com/design/spec/material-design/introduction.html\n  [3]: http://android-developers.blogspot.com/2015/05/android-design-support-library.html\n  [4]: http://www.google.com/design/spec/material-design/introduction.html\n  [5]: https://developer.android.com/tools/data-binding/guide.html\n  [6]: https://developer.android.com/tools/data-binding/guide.html\n  [7]: http://stablekernel.com/blog/unit-testing-continuous-delivery-for-android-part-3/\n  [8]: https://developer.android.com/tools/data-binding/guide.html\n  [9]: https://developer.android.com/tools/data-binding/guide.html\n  [10]: https://bitbucket.org/hvisser/android-apt\n  [11]: http://stablekernel.com/blog/unit-testing-continuous-delivery-for-android-part-3/\n"
  },
  {
    "path": "issue-23/TextView的TextLayout.md",
    "content": "多文本布局\n---\n> * 原文链接 : [Multiple Text Layout](https://sriramramani.wordpress.com/2014/08/14/multiple-text-layout/)\n* 原文作者 : [Sriram Ramani](https://sriramramani.wordpress.com/author/sriramramani/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [dengshiwei](https://github.com/dengshiwei) \n* 校对者 :[chaossss](https://github.com/chaossss)\n* 状态 : 已完成\n\n在Android中开发UI的最基本的单元就是一个视图(View)。但是，如果我们仔细观察，视图(View)是一个与用户交互的UI控件。它包含Drawables和text [Layouts](http://developer.android.com/reference/android/text/Layout.html)。我们随处可见drawables —— 视图(View)的背景(backgroud)。TextView同样由drawables组成。然而，TextView只有一个布局(layout)。在一个View/TextView中是否可能有多于一个的(布局)layout?\n![FIRST](https://sriramramani.files.wordpress.com/2014/08/badges.png)\n\n让我们看一个例子。我们有一个简单的ListView，它每行中包含一个image、text以及一些sub-text。由于TextView中显示默认只有一个文本布局(text Layout)，我们需要一个包含2个或3个视图(views)的LinearLayout来实现这种布局(layout)。如果TextView能够容纳一个以上的布局(layout)将会是什么(情况)？即使它(TextView)能够容纳和绘制，它也是一个可以创建和绘制的私有变量(private variable)。我们如何才能够让TextView的原始布局(original layout)实现这种布局(layout)呢?\n\n如果我们仔细看TextView的onMeasure方法，布局(layout)的可用width占据着compound drawables所占用的空间。如果我们让TextView占有compound drawable右侧的大部分，这个布局(layout)将会包含自己更多。现在空间划分出来了，我们可以在这个空间绘制(draw)布局。\n\n\tprivate Layout mSubTextLayout;\n\n    @Override\n    public int getCompoundPaddingRight() {\n        // Assumption: the layout has only one line.\n        return super.getCompoundPaddingRight() + mSubTextLayout.getLineWidth(0);\n    }\n\n现在我们需要为sub-text制造一个布局(layout)并绘制它。理想情况下，在onMeasure()方法中去创建一个对象是不好的。但是如果我们注意何时以及如何创建布局(layouts)，我们不需要担心这个限制。\n我们可以创建什么种类的布局(layouts)呢？TextView允许创建BoringLayout，BoringLayout是一个 StaticLayout或DynamicLayout。BoringLayout用于文本(text)如果只有一行的情况下。StaticLayout 用于multi-line的布局(layouts)，它创建后不能改变。DynamicLayout 用于可编辑的文本(editable text),如同EditText。\n\n\t@Override\n    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int width = MeasureSpec.getSize(widthMeasureSpec);\n\n        // Create a layout for sub-text.\n        mSubTextLayout = new StaticLayout(\n                mSubText,\n                mPaint,\n                width,\n                Alignment.ALIGN_NORMAL,\n                1.0f,\n                0.0f,\n                true);\n\n        // TextView doesn't know about mSubTextLayout.\n        // It calculates the space using compound drawables' sizes.\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n    }\n\n此处的mPaint包含对于sub-text的所有属性，例如字体颜色(text-color)、阴影(shadow)、字体大小(text-size)等等。这些决定用于text layout的大小。\n\n\t@Override\n    public void onDraw(Canvas canvas) {\n        // Do the default draw.\n        super.onDraw(canvas);\n\n        // Calculate the place to show the sub-text\n        // using the padding, available width, height and\n        // the sub-text width and height.\n        // Note: The 'right' padding to use here is 'super.getCompoundPaddingRight()'\n        // as we have faked the actual value.\n\n        // Draw the sub-text.\n        mLayout.draw(canvas);\n    }\n\n但是，我们不能只使用Spannable文本(text),好吧！如果这个名字确实很长，并且已经形成多行或者需要ellipsized。\n\n通过这样，我们使用同一个TextView可以绘制两个布局(layouts)，同时也帮助我们移除了2个视图(Views)! Happy hacking!\n\nP.S: 这些图标来自: [http://www.tutorial9.net/downloads/108-mono-icons-huge-set-of-minimal-icons/](http://www.tutorial9.net/downloads/108-mono-icons-huge-set-of-minimal-icons/)"
  },
  {
    "path": "issue-23/使用Espresso进行UI测试.md",
    "content": "#使用Espresso进行UI测试\n---\n\n> * 原文链接 : [Using Espresso for Easy UI Testing](http://www.michaelevans.org/blog/2015/08/03/using-espresso-for-easy-ui-testing/)\n* 原文作者 : [Michael Evans](http://www.michaelevans.org/)\n* 译者 : [desmond1121](https://github.com/desmond1121)\n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)\n* 状态 :  完成\n\n在我和很多Android开发者聊天的时候我注意到他们在开发的过程中并不注重测试这一环节，原因是他们认为Android测试太难实现或者难以集成到现有的工程中等等。但是实际上写一个[Espresso](https://code.google.com/p/android-test-kit/wiki/Espresso)并不是一件很难的事情，而且它能够非常方便地集成到你的工程之中。\n\n##容易实现\n\nEspresso测试是非常容易实现的，它由三部分组成：\n\n- ViewMachers：寻找用来测试的View。\n\n- ViewActions：发送交互事件。\n\n- ViewAssertions：检验测试结果。\n\n举个例子，接下来这部分代码向id为`name_field`的EditText输入\"Steve\"，并点击id为`greet_button`的按钮，最后检查屏幕上是否有\"Hello Steve!\"字样。\n\n    @Test\n    public void testSayHello() {\n      onView(withId(R.id.name_field)).perform(typeText(\"Steve\"));\n      onView(withId(R.id.greet_button)).perform(click());\n      onView(withText(\"Hello Steve!\")).check(matches(isDisplayed()));\n    }\n\n是不是很简单？我们再来看看在多线程情况下如何进行测试。\n\n##集成测试\n\nEspresso官方文档有这样一段话：\n\n>*Espresso测试有个很强大的地方是它在多个测试操作中是线程安全的。Espresso会等待当前进程的消息队列中的UI事件，并且在任何一个测试操作中会等待其中的AsyncTask结束才会执行下一个测试。这能够解决程序中大部分的线程同步问题。*\n\n我一般使用[Retrofit](http://square.github.io/retrofit/)来处理我的Http请求，而不是`AsyncTask`（虽然大多数人还是使用`AsyncTask`），在这种情况下也有别的办法来实现线程安全的测试。Espresso中有个API叫做`registerIdlingResource`，它可以让你使用自定义的线程安全逻辑。\n\n通过`IdlingResource`，我们可以通过以下这段代码来实现线程同步的Retrofit接口：\n\n    public class MockApiService implements ApiService, IdlingResource {\n\n      private final ApiService api;\n      private final AtomicInteger counter;\n      private final List<ResourceCallback> callbacks;\n\n      public MockApiService(ApiService api) {\n          this.api = api;\n          this.callbacks = new ArrayList<>();\n          this.counter = new AtomicInteger(0);\n      }\n\n      @Override\n      public Response doWork() {\n          counter.incrementAndGet();\n          return decrementAndNotify(api.doWork());\n      }\n\n      @Override\n      public String getName() {\n          return this.getClass().getName();\n      }\n\n      @Override\n      public boolean isIdleNow() {\n          return counter.get() == 0;\n      }\n\n      @Override\n      public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {\n          callbacks.add(resourceCallback);\n      }\n\n      private <T> T decrementAndNotify(T data) {\n          counter.decrementAndGet();\n          notifyIdle();\n          return data;\n      }\n\n      private void notifyIdle() {\n          if (counter.get() == 0) {\n              for (ResourceCallback cb : callbacks) {\n                  cb.onTransitionToIdle();\n              }\n          }\n      }\n    }\n\n这个类告诉了Espresso你的应用将会在（`doWork()`）方法调用后才能够进入下一个步骤。但是你看完这段代码应该马上意识到一件事情：这样的写法太过啰嗦了，你需要实现很多方法才能做到这线程同步一个小功能。一定有其他更好的办法来实现（接下来就是了！）。\n\n实际上技巧就隐藏在之前的官方文档中，“Expresso会等待UI事件……并等待AsyncTask的结束才会执行下一个测试”。 **实际上我们只要在AsyncTask的ThreadPoolExecutor中执行Retrofit请求就可以了！**\n\n幸运的是Retrofit的BaseAdapter.Builder类提供了这样一种方法：\n\n    new RestAdapter.Builder()\n       .setExecutors(AsyncTask.THREAD_POOL_EXECUTOR, new MainThreadExecutor())\n       .build();\n\n如此简单，你还有理由不写Espresso测试吗？\n"
  },
  {
    "path": "issue-23/使用TDD的方式开发一个Hackernews客户端.md",
    "content": "使用TDD的方式开发一个Hackernews客户端\n---\n\n\n> * 原文链接 : [MAKING A TDD-BASED HACKERNEWS CLIENT FOR ANDROID](http://www.philosophicalhacker.com/2015/07/17/making-a-tdd-based-hackernews-client-for-android/)\n* 原文作者 : [K. Matthew Dupree](http://www.philosophicalhacker.com)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Anthonyeef](https://github.com/Anthonyeef) \n* 校对者: [Mr.simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成 \n\n我正在运用TDD的方法去写一个 HackerNews 的 Android 客户端。这篇文章（以及后续会有的一系列文章）分享了一些我基于TDD工作流开发这个应用程序时的所用到的相关技术。本文同时也讨论了具有可测试性的 Android 程序架构从一开始构思到最后成型的过程。\n\n##测试“可行骨架”\n根据 Steve Freeman 和 Nat Pryce 在 Growing Object Oriented Software Guided by Tests 一书中的表述，开始 TDD 工作流的第一步，是“测试可行骨架”。根据他们的定义，一个“可行骨架”的意思是：\n>“可行骨架”是我们能够完成自动化构建、部署，以及端到端之间测试等实际功能的最小可能实现。\n\n>来自第69-70页\n\n在我看来，对一个 HackerNews 客户端而言，“可行骨架”应该具有能够显示出一个 HackerNews 的故事 id 列表的功能。为了实现这个测试，我写了一个像这样的“超浓缩”测试代码：\n```Java\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class MainActivityTests {\n\n    @Rule\n    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);\n\n    @Test\n    public void loadHackerNewsPostsOnStartup() {\n        onView(withId(R.id.recyclerView)).check(matches(isDisplayed()));\n        onView(withText(\"9897306\")).check(matches(isDisplayed()));\n    }\n}\n```\n\n##我怎么得到一致的测试数据\n\n在我写这个测试代码的时候，有一个问题立刻就出现了：我们怎么能够确定 `MainActivity` 为每一个测试单元取回的数据都是一样，以确保代码中 9897306 这个检验数值一直都是合适的呢？你能想到的在你的 Android 对象里头被注入的那些依赖，在这个[Jake Wharton 关于 Dagger 模块的讨论](https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=jake%20wharton%20dagger%20parley)里都可以找到。所以我决定使用下面的这个方法，来确保测试过程中所用到的数据能够保持一致性。\n\n让我简要地描述一下我是怎么在我的项目里使用这个方法的。\n\n`PhiHackerNews` 里的对象通过 `PhilHackerNewsApplication` 这个子类访问 Dagger 对象图谱。该类负责生成 ObjectGraph：\n```Java\npublic class PhilHackerNewsApplication extends Application {\n\n    private ObjectGraph mObjectGraph;\n\n    public final ObjectGraph getObjectGraph() {\n        if (mObjectGraph == null) {\n            mObjectGraph = makeObjectGraph();\n        }\n        return mObjectGraph;\n    }\n\n    protected ObjectGraph makeObjectGraph() {\n        return mObjectGraph = ObjectGraph.create(new PhilHackerNewsAppModule(getApplicationContext()));\n    }\n}\n```\n \n所以，当我正在运行一个测试的时候，我使用了一个自定义的测试器生成了一个 `PhilHackerNewsApplication` 的子类，通过重新定义的模块生成了 `ObjectGraph` ：\n```Java\npublic class DaggerModuleOverridingAndroidJUnitRunner extends AndroidJUnitRunner {\n\n    @Override\n    public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        String testApplicationClassName = TestApplication.class.getCanonicalName();\n        return super.newApplication(cl, testApplicationClassName, context);\n    }\n}\n```\n\n这个`TestApplication`的类生成的 `ObjectGraph` 对象，其中包含着的模块能够重新定义那些负责取回 HackerNews 数据的依赖：\n```Java\npublic class TestApplication extends PhilHackerNewsApplication {\n    @Override\n    protected ObjectGraph makeObjectGraph() {\n        return ObjectGraph.create(new PhilHackerNewsAppModule(getApplicationContext()), new TestLoaderModule());\n    }\n}\n```\n\n`TestLoaderModule` 模块则提供了那些被重新定义的依赖。它提供了一个 `HackerNewsRestAdapter` 可以直接从内存里载入 HackerNews 的数据，而不用重复访问服务器：\n```Java\n@Module(overrides = true, library = true, complete = false)\npublic class TestLoaderModule {\n    @Provides\n    HackerNewsRestAdapter provideHackerNewsRestAdapter() {\n        final Gson gson = new Gson();\n        return new HackerNewsRestAdapter() {\n            @Override\n            public List<Integer> getTopStories() {\n                Type listType = new TypeToken<List<Integer>>() {\n                }.getType();\n                return gson.fromJson(\"[9897329,9899313,9899549,9897306,9899369,9899348,9898075,9897513,9898504,9897751,9897159,9896815,9896760,9896402,9898796,9898310,9897860,9896590,9896369,9896853,9897838,9897658,9898745,9896436,9898201,9896689,9898981,9898275,9895694,9896558,9896938,9897054,9895609,9898249,9895108,9898839,9895094,9899726,9899636,9899237,9896910,9899317,9895713,9895790,9897220,9895767,9894237,9899386,9897929,9897585,9895834,9899548,9895931,9899815,9898924,9893412,9895681,9897796,9895888,9894570,9895558,9898430,9899626,9894508,9894226,9896538,9898560,9899707,9894336,9899766,9896956,9899503,9899676,9898363,9896141,9896758,9893561,9895438,9899603,9895903,9891705,9897681,9893730,9897565,9899141,9899366,9899359,9898935,9898926,9899309,9898954,9894993,9899203,9895887,9894841,9898575,9898999,9899016,9897849,9896873,9892887,9892810,9890824,9896903,9898064,9897635,9897937,9896434,9897732,9897930,9898548,9896139,9891998,9897156,9883246,9896709,9898502,9889057,9898426,9898412,9894152,9882587,9895917,9896943,9897024,9897966,9898186,9891366,9894014,9898176,9897555,9892325,9894588,9897044,9896491,9895278,9896326,9897891,9893989,9894342,9897875,9896748,9896936,9892049,9896138,9894748,9896972,9879632,9889777,9897817,9893867,9893359,9896440,9894198,9893972,9897062,9892200,9895181,9895698,9896808,9892970,9895513,9892013,9897030,9892633,9891311,9894213,9896050,9892515,9896822,9889598,9892333,9880694,9884074,9884616,9896796,9890980,9893903,9895759,9895861,9890850,9890188,9896514,9896762,9896921,9895507,9892340,9878160,9895311,9896495,9896839,9891695,9891927,9890476,9894647,9896858,9896854,9895590,9895116,9895730,9891509,9896119,9895778,9894457,9895602,9891856,9887548,9894301,9891115,9891133,9896131,9895894,9895053,9893385,9892157,9889152,9892449,9891531,9894051,9896393,9894177,9891709,9891537,9895591,9889019,9892463,9894994,9895578,9895046,9889365,9895854,9892159,9889979,9892099,9891068,9895204,9896067,9889609,9895922,9891487,9890952,9870408,9890574,9891921,9894255,9889039,9891492,9894320,9892251,9892552,9892724,9889548,9895071,9891220,9884915,9891874,9887802,9887040,9895791,9895075,9888442,9891868,9892438,9895473,9889213,9886555,9894991,9886817,9891837,9890180,9891431,9894288,9893499,9890974,9893131,9889588,9893098,9893401,9894513,9886103,9890364,9895298,9895623,9891759,9888012,9885353,9894423,9892488,9893349,9888231,9865835,9885503,9895253,9870349,9895838,9895572,9891901,9890566,9891406,9891681,9894171,9894430,9891624,9885896,9886101,9879153,9883882,9892221,9888743,9879336,9881453,9892574,9893490,9895119,9891015,9891352,9895319,9883313,9895420,9894787,9895376,9888387,9895374,9893432,9891989,9895235,9894242,9879685,9869871,9893437,9893884,9894532,9889399,9888528,9894922,9881213,9893741,9892989,9878061,9879580,9887358,9893602,9893778,9891373,9886640,9891560,9892135,9894942,9893389,9893773,9893640,9878302,9875549,9894948,9884005,9894235,9892436,9874521,9894524,9894341,9892701,9891258,9884417,9894552,9888035,9890808,9876561,9889352,9891064,9891262,9840647,9892891,9889582,9893965,9893203,9893732,9890673,9893894,9888153,9879715,9869755,9888945,9893354,9894111,9893927,9889328,9894024,9894015,9893507,9889909,9892079,9891992,9893722,9891280,9892491,9885625,9883747,9891175,9889149,9887664,9893206,9886067,9848124,9840682,9876940,9830773,9881929,9894287,9889964,9891625,9892051,9885413,9884057,9891088,9884244,9891600,9892288,9891217,9891680,9885992,9884715,9883893,9882277,9888553,9880585,9886682,9876554,9891670,9887469,9887285,9876016,9890147,9888162,9881696,9887164,9890603,9889370,9812245,9890176,9872504,9857662,9854076,9890123,9888899,9851685,9848645,9882617,9794548,9720813,9803790,9797918,9826386,9835195,9851757,9805220,9854123,9889854,9876999,9808480,9770362,9856637,9826131,9835375,9836942,9843500,9893496,9791272,9857332,9800809,9884165,9894153,9893498,9831680,9422033,9851953,9817160,9810641,9481211,9805150,9420135,7539390,9895010,9840468,9456094,5624727,1627246,9892364,9891323,9891869,9891558,9889647,9893525,9889785,9892652,9891495,9892629,9890953,9882135,9884970,9891832]\", listType);\n            }\n\n        };\n    }\n}\n```\n\n##应用的当前架构情况\n让我指出几个使这个程序的构建能够顺利通过测试环节的关键之处。首先，我想说目前的程序架构可能会进一步改变，基于 Pryce 和 Freeman 所说的同样原因：\n>当在测试“可行骨架”的时候，我们并不是尝试在实际写代码之前将全部的算法和所需要用到的类都考虑进去。我们现在所冒出来的想法都有可能是错误的，所以我们更偏向于在进一步发展整个体系的过程中去发现那些细节。\n>\n>第73页\n\n\n有一些关于当前架构情况的趣事：它使用了 RxJava 和 Loaders 的组合，用来确保：\n- 即使因为设置改变的原因导致 MainActivity 和 Fragment 被销毁，网络请求仍然可以被正确地建立并恰当部署；\n- 该程序应用层的类不用再担心应用组件在异步数据加载方面的一些 Android 具体问题，从而能够随时销毁及重建。\n\n\n这个决定的灵感来自于 Freeman 和 Pryce 的建议：\n>我们不希望技术性的概念会泄露到应用模型当中，所以我们写了一些接口去描述它们与外界世界的联系（Cockburn的端口）。然后我们写了一些连接应用核心和各个技术域的桥梁（Cockburn的适配器）。\n>\n>来自第90页\n\n在我看来，`Loader` 尝试去解决的是一个不属于应用层的技术问题。为了能够将应用层对象与这里的技术细节隔绝开，根据订阅情况，我使用 `LoaderManager` 从 `Loader` 里生成并传递了一个 `Observable（观察者）`，初始化了一次加载：\n```Java\npublic class LoaderInitializingOnSubscribe<T> implements Observable.OnSubscribe<T> {\n    private int mLoadId;\n    private final LoaderManager mLoaderManager;\n    private Loader<T> mLoader;\n\n    public LoaderInitializingOnSubscribe(int loadId, LoaderManager loaderManager, Loader<T> loader) {\n        mLoadId = loadId;\n        mLoaderManager = loaderManager;\n        mLoader = loader;\n    }\n\n    @Override\n    public void call(final Subscriber<? super T> subscriber) {\n        mLoaderManager.initLoader(mLoadId, null, new LoaderManager.LoaderCallbacks<T>() {\n            @Override\n            public Loader<T> onCreateLoader(int id, Bundle args) {\n                return mLoader;\n            }\n\n            @Override\n            public void onLoadFinished(Loader<T> loader, T data) {\n                subscriber.onNext(data);\n            }\n\n            @Override\n            public void onLoaderReset(Loader<T> loader) {\n            }\n        });\n    }\n}\n```\n相比于直接处理loader的方式，客户端更希望能够处理那些已经加载过的数据。这部分的数据由`Observable（观察者）` 订阅并由 `LoaderInitializingOnSubscribe` 类生成。但在我的项目中，Activity, Fragment, Presenter都不会与 `Observable` 进行直接交互。相反，它们会利用一个 `StoryRepository` 的类来进行互动。这个类会最终决定到底是从缓存中读取数据，还是从网络中得到数据。在目前，这个类的代码是这样的：\n```Java\npublic class ConnectivityAwareStoryRepository implements StoryRepository {\n\n    private ConnectableObservable<List<Integer>> mStoriesObservable;\n\n    public ConnectivityAwareStoryRepository(ConnectableObservable<List<Integer>> apiStoriesObservable) {\n        mStoriesObservable = apiStoriesObservable;\n    }\n\n    @Override\n    public Subscription addStoriesSubscriber(Subscriber<List<Integer>> observer) {\n        return mStoriesObservable.subscribe(observer);\n    }\n\n    @Override\n    public void loadTopStories() {\n        mStoriesObservable.connect();\n    }\n}\n```\n这里是另一个与之相关的代码块。这个 Fragment 也使用了 `StoryRepository` 这个类来加载 HackerNews 的数据：\n```Java\npublic class MainActivityFragment extends Fragment {\n    //...\n    @SuppressWarnings(\"WeakerAccess\")\n    @Inject\n    StoryRepository mStoryRepository;\n    private Subscription mSubscription;\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\n                             Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.fragment_main, container, false);\n        injectDependencies(view, getLoaderManager());\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n        mSubscription = mStoryRepository.addStoriesSubscriber(mStoriesSubscriber);\n        mStoryRepository.loadTopStories();\n        return view;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        mSubscription.unsubscribe();\n    }\n    //...\n}\n```\n如果你想进一步地了解我到目前为止完成的工作，可以到这个 [Github 地址](https://github.com/kmdupr33/PhilHackerNews)查看。\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "issue-24/Android LayerDrawable 和 Drawable.Callback.md",
    "content": "#Android LayerDrawable 和 Drawable.Callback\n\n> * 原文链接 : [Android LayerDrawable and Drawable.Callback](http://www.roman10.net/android-layerdrawable-and-drawable-callback/)\n* 原文作者 : [Liu Feipeng](www.roman10.net)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](www.devtf.cn)\n* 译者 : [Desmond1121](https://github.com/desmond1121) \n* 校对者：[bboyfeiyu](https://github.com/bboyfeiyu)\n\n`LayerDrawable`是一个特殊的`Drawable`，它内部保持着一个`Drawable`数组，其中每一个`Drawable`都是视图中的一层。如果你不了解`LayerDrawable`的机制，当程序出了问题后是很难去找到bug在哪里的。我发这些文章就是为了分享在使用`LayerDrawable`与`Drawable.Callback`时可能出现的一个bug。\n\n##Callback调用链\n\n在`LayerDrawable`中，每层视图（`Drawable`）都会将`LayerDrawable`注册为它的`Drawable.Callback`。这允许`Drawable`能够在需要重绘自己的时候告知`LayerDrawable`重绘它。我们可以在下面这个`Callback.invalidateSelf()`函数中看到是由注册callback端（在此处为`LayerDrawable`）来执行`invalidateDrawable(Drawable drawable)`的。\n\n```java\npublic void invalidateSelf() {\n    /* 获取注册的Callback实例，如果无则返回null。 */\n    final Callback callback = getCallback();\n    if (callback != null) {\n        callback.invalidateDrawable(this);\n    }\n}\n```\n\n我们知道`View`是实现了`Drawable.Callback`接口的，所以当图片需要重绘的时候就能够告知`View`。如果我们把`View`的背景图片设置成了`LayerDrawable`，在`Drawable`需要更新的时候callback的调用将有一个传递的过程，首先会调用注册的`LayerDrawable`的`invalidateDrawable(Drawable drawable)`方法，`LayerDrawable`又会调用`View`的`invalidateDrawable(Drawable drawable)`方法。如下图所示：\n\n![Alt text](http://img.blog.csdn.net/20150818142917049)\n\n##View改变背景时移除原背景Callback\n\n在`View`的`setBackgroundDrawable(Drawable background)`中有这么一段代码：\n\n```java\n    if (mBackground != null) {\n        mBackground.setCallback(null);\n        unscheduleDrawable(mBackground);\n    }\n\n    …\n    if (background != null) {\n        background.setCallback(this);\n    }\n```\n\n我们可以看出：当`View`改变背景时将会**无条件将原背景（如果原背景是Drawable的话）的`Drawable.Callback`设置为`null`。**\n\n##什么情况下会出现Bug？\n\n有了上面这些知识，我们可以通过下面这个步骤产生一个Bug：\n\n> 1. 把`Drawable`**A **设置成`View`**V**的背景。**现在A的callback指向V**。\n2. 将A设置成`LayerDrawable` **L**中的一层。**现在A的callback指向L**。\n3. 现在为**V**设置另一个背景，**V会把原背景(A)的callback强制设置成null，破坏了A与L之间的联系。**\n4. **BUG出现了**：更新`Drawable`**A**不会让**L**更新了。\n\n\n解决方法就是在更新**V**的背景之后再创造`LayerDrawable`**L**。Bug发生与解决的例子可以在[这里](https://github.com/roman10/blog-android-source-code/tree/master/LayerDrawableCallback)下载。\n\n为了方便看官，我也贴了一部分关键代码到这边来，你可以通过注释理解这段代码。\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        Button btn1 = (Button) findViewById(R.id.button1);\n        btn1.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                // 1. 将 launcherIconDrawable.callback 赋值给 actionBar\n                actionBar.setBackgroundDrawable(launcherIconDrawable);\n                animateActionBarWorking();\n            }\n        });\n        Button btn2 = (Button) findViewById(R.id.button2);\n        btn2.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                // 1. 将 launcherIconDrawable.callback 赋值给 actionBar\n                actionBar.setBackgroundDrawable(launcherIconDrawable);\n                animateActionBarNotWorking();\n            }\n        });\n        actionBar = getSupportActionBar();\n        launcherIconDrawable = getResources().getDrawable(R.drawable.launcher_repeat);\n        colorLayer = new ColorDrawable(Color.rgb(0, 255, 0));\n        actionBar.setBackgroundDrawable(colorLayer);\n    }\n    \n    /* 这个函数运行后ActionBar不会得到更新。 */\n    private void animateActionBarNotWorking() {\n        Drawable[] layers = new Drawable[] { colorLayer, launcherIconDrawable };\n        LayerDrawable layerDrawable = new LayerDrawable(layers);\n        actionBar.setBackgroundDrawable(layerDrawable);\n        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 255);\n        valueAnimator.setDuration(1000);\n        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                // 4. Updates launcherIconDrawable will not trigger action bar background to update\n                // as launcherIconDrawable.callback is null\n                launcherIconDrawable.setAlpha((Integer) animation.getAnimatedValue());\n            }\n        });\n        valueAnimator.start();\n    }\n    \n    /* 由于先移除了launcherIconDrawable与ActionBar的联系，这个函数运行后会让ActionBar得到更新。\n    private void animateActionBarWorking() {\n        actionBar.setBackgroundDrawable(null);\n        animateActionBarNotWorking();\n    }\n  \n参考文章：\n\n[LayerDrawable - Android Developers](http://developer.android.com/reference/android/graphics/drawable/LayerDrawable.html)"
  },
  {
    "path": "issue-24/Android双向数据绑定.md",
    "content": "Android 双向 Data Binding\n---\n\n> * 原文链接 : [Two-way Android Data Binding](https://medium.com/@fabioCollini/android-data-binding-f9f9d3afc761)\n* 原文作者 : [@fabioCollini](https://medium.com/@fabioCollini)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime)\n* 校对者: [Mr.simple](https://github.com/bboyfeiyu)  \n* 状态 :   完成\n\n###如何使用双向数据绑定管理布局\n\n今年 Google I/O 大会中发布的 Data Binding 框架吸引了很大一部分 Android 开发者的注意力。关于它的讨论已经持续了很长时间，现在终于可以用实例来测试这个框架了。在[官网][official website]和很多开发者的 blog 里都有示例应用展示如何使用 Data Binding，其中用了这个框架的许多特性。因此他们突出的是这个框架的优势。我们今天的文章会从两个实例开始(一个普通的\n和一个复杂些的)，展示怎样在 Android 开发中使用 Data Binding。\n\n---\n\n###文本字段的双向绑定\nEcho 是一个典型的 data binding 示例:两个 `EditText` 绑定到同一个 bean 中。\n这样可以在一个 EditText 中输入的同时在另一个 EditText 即时的显示输入的内容。\n下面是 bean 的示例代码:\n\n```java\npublic class Echo {\n  public String text;\n}\n```\n\n简便起见，这里没有包含 getter 和 setter，有也没关系。\n\nlayout 也很简单，主要由绑定了同一个 data 的两个 EditText 组成。\n\n```xml\n<layout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\">\n  <data>\n    <variable\n      name=\"echo\"\n      type=\"it.cosenonjaviste.databinding.echo.Echo\"/>\n  </data>\n  <LinearLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n    <EditText\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n    android:hint=\"Text 1\"\n      android:text=\"@{echo.text}\"/>\n    <EditText\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:hint=\"Text 2\"\n      android:text=\"@{echo.text}\"/>\n  </LinearLayout>\n  </layout>\n```\n\n在 Activity 里面需要用 **DataBindingUtil** 这个工具类来设置 layout，然后绑定对象( **EchoBinding** 是根据布局文件自动生成的)。\n\n```java\npublic class EchoActivity extends AppCompatActivity {\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    EchoBinding binding = DataBindingUtil.setContentView(\n      this, R.layout.echo);\n    binding.setEcho(new Echo());\n  }\n}\n```\n\n我们的理想效果是只要给 bean 和 View 设置好正确的 listener ，数据就应该同步起来了。不过实际运行这个例子时， 并不是这个效果，在一个输入框输入另一个输入框也没有变化。这个场景中唯一有效的绑定是最开始时 Echo 的 string 和这两个文本字段。当 `setEcho` 方法在 EchoBinding 对象上被调用时，如果 Echo 的 text 不为空，它就会被设置到图形界面的两个 EditText 中。绑定只对 object 到 layout，并且仅当使用 `setEcho` 方法设置 object 时生效。后面无论 Echo 对象中的 text 有什么变化都不会映射到 layout 中。\n\n如果想要自动接收后续更新，那么就要用 ObservableField<String> 定义一个字段，\n而不是仅仅使用 String。\n```java\npublic class Echo {\n  public ObservableField<String> text = new ObservableField<>();\n}\n```\n\nObservableField 这个类 (还有其他为原生类型准备的类，比如 ObservableInt 和 ObservableBoolean) 可以当作典型的观察者模式看待(你最好知道，这和 RxJava 管理的 `Observables` 可不一样)。事实上，在 EchoBinding 对象上调用 `setEcho` 方法的同时会在 Observable 上注册一个 listener。这个 listener 会在每次更新时被调用，它还会更新 layout 中的 View。\n\n```xml\n<EditText\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:hint=\"Text 1\"\n  android:text=\"@{echo.text}\"\n  android:addTextChangedListener=\"@{echo.watcher}\"/>\n  ```\n\n  ---\n\n使用这种方法就需要在类中定义一个 TextWatcher:\n\n```java\npublic class Echo {\n  public ObservableField<String> text = new ObservableField<>();\n  public TextWatcher watcher = new TextWatcherAdapter() {\n    @Override public void afterTextChanged(Editable s) {\n      if (!Objects.equals(text.get(), s.toString())) {\n        text.set(s.toString());\n      }\n    }\n  };\n}\n```\n\n这里面的检查主要是为了防止死循环: layout 更新了 object ，然后再调用 listener\n去更新 View。\n虽然这么做双向绑定可以正常工作，不过有还是有些东西让我有所担心:\n\n- Echo 类不应该和 layout 有联系，使用 `ObservableField` 不是最简洁的做法，还有\n    它需要定义一个 TextWatcher 也不是很好。\n- 每个必需绑定的字段都要写大量的代码(两个互相连接的字段)。\n\n    ---\n\n###自定义绑定规范\n\n想要解决以上问题，我们可以使用自定义的 binding，Data Binding 框架允许我们这么干，定义起来也很容易。\n\n首先，要声明一个我们自己的 `ObservableField` 不过是 string-specific 的(让我们把名字也重新搞一下，避免把原生类和 RxJava 的 Observables 弄混) :\n\n```java\npublic class BindableString extends BaseObservable {\n  private String value;\n  public String get() {\n    return value != null ? value : \"\";\n  }\n  public void set(String value) {\n    if (!Objects.equals(this.value, value)) {\n      this.value = value;\n      notifyChange();\n    }\n  }\n  public boolean isEmpty() {\n    return value == null || value.isEmpty();\n  }\n}\n```\n\n在这里加入一个 `isEmpty` 工具方法检查 value，避免不必要的代码。\n想要在 `android:text` 属性里直接使用这个类需要在一个类里使用 **@BindingConversion** 注解声明一个方法进行转换:\n\n```java\n@BindingConversion\npublic static String convertBindableToString(\n    BindableString bindableString) {\n  return bindableString.get();\n}\n```\n\n为了简化上面代码，也可以使用 **@BindingAdapter** 注解创建一个自定义属性。比如，我们可以声明一个属性 `app:binding` 设置 value 添加 `TextWatcher`:\n\n```java\n@BindingAdapter({\"app:binding\"})\npublic static void bindEditText(EditText view,\n    final BindableString bindableString) {\n  if (view.getTag(R.id.binded) == null) {\n    view.setTag(R.id.binded, true);\n    view.addTextChangedListener(new TextWatcherAdapter() {\n      @Override\n      public void onTextChanged(CharSequence s, int start,\n          int before, int count) {\n        bindableString.set(s.toString());\n      }\n    });\n  }\n  String newValue = bindableString.get();\n  if (!view.getText().toString().equals(newValue)) {\n    view.setText(newValue);\n  }\n}\n```\n\n两个需要注意的地方:\n- 这个方法会在绑定的对象被改变时调用;添加了一个 tag 来避免给相同字段设置多个 TextWatcher。\n- 在设置新的 value 之前要检查 value 是否真的改变;这样可以避免 text view 中\n 由于光标位置改变而引起的问题。\n\n现在我们可以简单的把 layout 文件重写一下，用我们新定义的 `app:binding` 属性:\n\n```xml\n<EditText\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:hint=\"Text 1\"\n  app:binding=\"@{echo.text}\"/>\n  ```\n\n###管理屏幕方向切换\n\n在这里还有一个很微妙的东西要注意，就是应用在横竖屏切换的时候，我们需要特别处理。我们都知道屏幕方向切换时 Activity 会被销毁重建，Echo 对象是新生成的，绑定也是重新绑定的。所以之前用户输入的内容就消失了。:(\n\n处理这种情况正确的做法是重写 Activity 的 `onSaveInstanceState` 方法，只在第一次启动时创建新的 Echo 对象:\n\n```java\npublic class EchoActivity extends AppCompatActivity {\n  public static final String ECHO = “ECHO”;\n  private Echo echo;\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    EchoBinding binding = DataBindingUtil.setContentView(\n      this, R.layout.echo);\n    if (savedInstanceState == null) {\n      echo = new Echo();\n    } else {\n      echo = Parcels.unwrap(\n        savedInstanceState.getParcelable(ECHO));\n    }\n    binding.setEcho(echo);\n  }\n  @Override protected void onSaveInstanceState(Bundle outState) {\n    super.onSaveInstanceState(outState);\n    outState.putParcelable(ECHO, Parcels.wrap(echo));\n  }\n}\n```\n\n这里使用 [Parceler][Parceler] 框架是为了简化 Parcelable 对象的操作。BindableString 类也要用 Parceler 映射；并不是很复杂，因为它只是管理 String ，当方向改变时可以忽略。\n\n###使用数据绑定的登录功能\n\n下面做一个复杂点的，类似 Amazon 的登陆界面:\n\n![img](https://d262ilb51hltx0.cloudfront.net/max/800/1*mcgP_N5m490BGqz7g_9i1g.png)\n\n验证从第一个提交开始，后续的改变也会触发验证，只有当第二个 radio button 选中时 passwd 的字段才有效。\n\n我们从 radio button 开始，这种情况下需要对 radio group 进行绑定:\n\n```xml\n<RadioGroup\n  android:layout_width=”match_parent”\n  android:layout_height=”wrap_content”\n  android:orientation=”vertical”\n  app:binding=”@{loginInfo.existingUser}”>\n  <RadioButton\n    android:layout_width=”wrap_content”\n    android:layout_height=”wrap_content”\n    android:text=”@string/new_customer”/>\n  <RadioButton\n    android:layout_width=”wrap_content”\n    android:layout_height=”wrap_content”\n    android:text=”@string/i_have_a_password”/>\n    </RadioGroup>\n```\n\n我们需要自定义一个 bind，这次使用一个 `BindableBoolean` 对象。因为这个 radio group 只有两个 button，所以我们这里使用了 boolean，你也可以使用其它的类型(比如 int)。这个 bind 的\n和我们之前实现的 `BindableString` 很像。\n\n关于文本字段，使用了一个包含 EditText 的 TextInputLayout (由[Design Support Library][dsl]提供)。\n在使用密码字段时需要处理三个绑定:\n- text:和前面例子中的绑定一样\n- error:指向一个字段(比如 `BindableString`)的另一个绑定\n- enabled:这里和 RadioGroup 使用了同一个 boolean，可以在用户选中 radio button 时自动更新。\n\n对应代码如下:\n\n```xml\n<android.support.design.widget.TextInputLayout\n  android:layout_width=”match_parent”\n  android:layout_height=”wrap_content”\n  app:error=”@{loginInfo.passwordError}”>\n    <EditText\n      android:id=”@+id/password”\n      android:layout_width=”match_parent”\n      android:layout_height=”wrap_content”\n      android:enabled=”@{loginInfo.existingUser}”\n      android:hint=”@string/password”\n      android:inputType=”textPassword”\n      app:binding=”@{loginInfo.password}”/>\n      </android.support.design.widget.TextInputLayout>\n```\n\n绑定是通过一个包含 layout 所有字段需要信息的 LoginInfo 类实现的:\n```java\npublic class LoginInfo {\n    public BindableString email = new BindableString();\n    public BindableString password = new BindableString();\n    public BindableString emailError = new BindableString();\n    public BindableString passwordError = new BindableString();\n    public BindableBoolean existingUser = new BindableBoolean();\n    public void reset() {\n        email.set(null);\n        password.set(null);\n        emailError.set(null);\n        passwordError.set(null);\n    }\n  }\n```\n\n点击 `reset` button 要调用 `reset` 方法，想要实现这个效果 reset 方法需要返回一个 `OnClickListener` 。如果可以使用一个 `android:onClick=\"@{loginInfo.reset}\"` 来标注就好了，但是目前的 Data Binding 还不可以，它还只是个测试版，希望下个版本可以加入这个特性。\n我们仍然用框架生成的 object 添加 listener:\n\n```java\nbinding.reset.setOnClickListener(new View.OnClickListener() {\n  @Override public void onClick(View v) {\n    loginInfo.reset();\n  }\n  });\n```\n\nlayout 中每一个设置了 id 的 view present 都会在自动生成的 binding 对象里有一个对应的字段，可以避免使用 findViewById 方法。使用 Data Binding 弱化了 [ButterKnife][butterknife] 和 [Holdr][holdr] 这类框架的优势(Holdr 已经不推荐使用)。\n\n值得注意的是框架内部并没有使用 `findViewById` 方法，在生成的代码中有一个方法，可以遍历 layout，搜索所有 view。\n\nEDIT: 1.0-rc1 版本之后可以方便的定义一个 listener:\n\n```xml\n    android:onClick=\"@{loginInfo.reset}\"\n```\n\nreset 方法的签名必须和 OnClickListener 接口的 onClick 方法相兼容:\n需要一个类型为 View 的参数。使用自定义 binding 可以免去这个参数。\n\n```java\n@BindingAdapter({\"app:onClick\"})\npublic static void bindOnClick(View view, final Runnable runnable) {\n    view.setOnClickListener(new View.OnClickListener() {\n        @Override public void onClick(View v) {\n            runnable.run();\n        }\n    });\n}\n```\n\n第二个参数是一个 Runnable，只是一个带有唯一无参方法接口的占位符。\n类似的方法也被用来处理验证过程的 listener，通过给 Activity 内的 email 和 passwd  \n字段添加listener:\n\n```java\nTextWatcherAdapter watcher = new TextWatcherAdapter() {\n  @Override public void afterTextChanged(Editable s) {\n    loginInfo.validate(getResources());\n  }\n};\nbinding.email.addTextChangedListener(watcher);\nbinding.password.addTextChangedListener(watcher);\n```\n\nLoginInfo 内部有一个 boolean 字段用来储存用户是否已经尝试登录。验证过程\n通过检查这个值来判断是否显示错误提示:\n\n```java\npublic boolean loginExecuted;\npublic boolean validate(Resources res) {\n  if (!loginExecuted) {\n    return true;\n  }\n  int emailErrorRes = 0;\n  int passwordErrorRes = 0;\n  if (email.get().isEmpty()) {\n    emailErrorRes = R.string.mandatory_field;\n  } else {\n    if (!Patterns.EMAIL_ADDRESS.matcher(email.get()).matches()) {\n      emailErrorRes = R.string.invalid_email;\n    }\n  }\n  if (existingUser.get() && password.get().isEmpty()) {\n    passwordErrorRes = R.string.mandatory_field;\n  }\n  emailError.set(emailErrorRes != 0 ?\n    res.getString(emailErrorRes) : null);\n  passwordError.set(passwordErrorRes != 0 ?\n    res.getString(passwordErrorRes) : null);\n  return emailErrorRes == 0 && passwordErrorRes == 0;\n}\n```\n\nlogin 按钮上的 click listener 会对这个 boolean 进行设置，验证被执行，\n传递一个 message ，通过 Snackbar([Design Support Library ][dsl]提供) 展示给用户:\n\n```java\nbinding.login.setOnClickListener(new View.OnClickListener() {\n  @Override public void onClick(View v) {\n    loginInfo.loginExecuted = true;\n    if (loginInfo.validate(getResources())) {\n      Snackbar.make(\n        binding.getRoot(),\n        loginInfo.email.get() + “ — “ + loginInfo.password.get(),\n        Snackbar.LENGTH_LONG\n      ).show();\n    }\n  }\n});\n```\n\n###结论\n\nAndroid 的 Data Binding 框架还在 beta 阶段，Android Studio 对其内部支持也不是很完整，\n进步的空间还很大。不过它被设计和开发的很好，将会改变 Android 应用开发方式(如果顺利的话)。\n允许自定义 attributes 这个功能十分强大，我非常好奇第三方库开发者将会怎样使用这个特性。\n\n这篇文章完整的代码在这里[Github][databinding]\n\nAn Italian version of this post is available on cosenonjaviste.it.\n[一个 Italian 版本翻译][Italian]\n\n\n---\n\n[official website]:https://developer.android.com/tools/data-binding/guide.html\n[dsl]:http://android-developers.blogspot.it/2015/05/android-design-support-library.html\n[butterknife]:https://github.com/JakeWharton/butterknife\n[holdr]:https://github.com/evant/holdr\n[databinding]:https://github.com/commit-non-javisti/databinding\n[Italian]:http://www.cosenonjaviste.it/gestione-di-una-form-con-il-data-binding-android/\n[Parceler]:http://parceler.org/\n"
  },
  {
    "path": "issue-24/Android设计与开发工作流.md",
    "content": "Android设计与开发工作流\n---\n> * 原文链接 : [Wutson: Exploring Design And Development Workflows](http://novoda.com/blog/londroid-wutson/)\n* 原文作者 : [Ataul Munim](http://novoda.com/blog/author/ataulm/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [dengshiwei](https://github.com/dengshiwei) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 : 完成\n\n在这个月的Londroid，Qi和我一起发布了Wutson，这是一款我们从年初就为此工作的app。我们分享我们的工作流程，包含我们如何一起工作、我们发现的哪些工具最有用以及改进开发设计过程的技巧。\n\nWutson是你的私人TV指南，它帮助你找到什么在上映。我们决定开发它是因为我们没有发现现有的app选项是合适的：一些原本体贴的功能和用户体验(UX)但是看起来显得过时并且夹杂着广告，还有一些看起来App看起来非常棒，但是用户体验设计(UX)不明确并且有着糟糕的用户使用流程。\n\n![Home](http://novoda.com/blog/content/images/2015/07/wutson_photo.png)\n\n开始开发的时间在Android TV启动的时间前后 - Wutson将是家居生活中的完美app应用：非常直观、很简单，当然它是非常适合TV指南。\n\n今年里，我们也非常专注于探索Android在我们的一些新项目中的可行性。大量的关于App如何适用于TV的考虑也会使做出的App容易使用，所以这是一个实验和学习的理想情况。\n\n我们开始寻找现有的app应用 - [SeriesAddict](https://play.google.com/store/apps/details?id=com.zenstyle.seriesaddict) 和 [SeriesGuide](https://play.google.com/store/apps/details?id=com.battlelancer.seriesguide)是最有名的两款。\n\nSeriesGuide对于UI华丽的app应用是一个很棒的范例。它是我现在使用的一个应用。不幸的是一些简单的工作并不直观（寻找一个节目）或不可能的（寻找一个你没跟踪的节目的情节描述）。\n\n在法国，SeriesAddict是一款基于[BetaSeries](https://www.betaseries.com/introduction) API的很受欢迎的app。它不跟随最先进的UI模式，但是有一些非常精心设计元素，如watchlist，它会展示出你正在追的节目中没看过的前五集。\n\n![Flow](http://novoda.com/blog/content/images/2015/07/userflow.png)\n\nQi和我列举了其他app的所有要素，然后确认我们感觉很重要的要素包含在Wutson的初始版本中，把他们映射到一起作为用户指南形成一个粗略的信息框架。\n\n在最初项目的引导任务完成后，是时候开始在要素上工作了。我们遵循一个过程，与我们在Novoda中使用的一样：<br>\n1、计划(Plan)<br>\n2、草图/原型(Scribbles/prototype)<br>\n3、视觉设计和规格(Visual design and specs)<br>\n4、实现视觉(Implement visual refinements)<br>\n5、回顾(Review)(返回步骤1) (then back to 1)\n\n![trello](http://novoda.com/blog/content/images/2015/07/trello.png)\n\n虽然我们倾向于使用Atlassian JIRA去管理项目，但是在私人项目上我们一般使用[Trello](https://trello.com/)。\n\n我们的Trello模版由四列组成：\n\n* 一个to-do列表\n* Qi的当前任务\n* 我的当前任务\n* 完成的任务\n\n我们的计划会议将以审查上周的工作为开始，浏览那些完成的任务或者那些存档的、或放置在to-do列表中的，如果它不是[done-done](http://chrislema.com/what-is-done-done/)。\n\n然后，我们会通过剩余的任务，删除那些在不久的将来不工作的任务，并且选择那些接下来开始工作的任务到我们的列表中。\n\n通过计划和审查一起进行（而不是分开），我们能够：\n\n* 组织任务，所以我们不会阻碍对方\n* 反馈产品的发展方向\n* 展示那些我们花费时间和努力完成的工作，收到赞美和反馈\n\n我们提出了我们工作流程中的搜索功能：\n![scribbles](http://novoda.com/blog/content/images/2015/07/scribbles.png)\n\n基于用户工作流程，Qi能够快速的描述出基本结构页面。Search Overlay和Search Results在屏幕上显示。\n\n对于这个阶段，细节是不重要的 - 显示的内容文字、app bar的颜色、用到哪个图片、甚至到是否绘制直线都不重要，只要用笔和纸画出来就好。\n\n当Qi正在做这些，我让自己忙于建立数据面。在规划阶段，我们已经讨论了什么功能应该包括，所以我知道哪些APIs我将会使用。在Wutson中，我使用[Retrofit](http://square.github.io/retrofit/)和[RxJava](https://github.com/ReactiveX/RxJava)来更快的工作。\n\n这个时候，我也应该开始写测试 - 它是我们每天在Novoda必须要做的事情，但我不后悔做它(测试)，它永远不会太晚。\n\n当Qi做好一些UI设计的手稿，我们将再次会面讨论他们。\n![single](http://novoda.com/blog/content/images/2015/07/single.png)\n\n现在是时候确定我将需要实现的设计，包含focueds和pressed状态下的overlays，各种尺寸的图标(icons)，图像占位符(image placeholders)和字体文件(font-files)，这些都是我列举清单上的事物需要从Qi得到的。\n\n在可访问性方面，我们确定哪些组件可以在归入单一文档内容描述类别下。为了确保我们有keyboard/trackball/switch方式(非触摸模式)，我们决定哪些是核心元素在屏幕上显示。例如，如果用户在非触摸模式下我们可能会隐藏星图标(star icon)，因为它会让整个列表导航需要点击两次每个项目而不是一次。我们必须小心不要删除功能 - 它有一个功能追踪不同屏幕的展现(展示细节)。\n\n现在做这些，应用已经为TV准备好了 - 在Nexus Player上调试了8个月之后，应用已经能够在TV上使用了。但是我非常肯定的说，我更倾向要一个功能与UI都一样的手机版App，否则不如不要。\n![implementedscribbles](http://novoda.com/blog/content/images/2015/07/implementedscribbles.png)\n\nQi将界面进行分割，我将它们实现到APP中，就像下面说的这样:\n\n* Qi做界面设计，我实现功能。\n* 当一切都是光秃秃的框架，我可以考虑加入一些验收测试(acceptance tests)。\n* 我可以确定我已经为所有的交互元素添加了focus和press状态，尽管它们只是暂时的，以后我能够进行替换\n![geny](http://novoda.com/blog/content/images/2015/07/geny.png)\n\n为了测试focus状态，你需要一个拥有方向键或轨迹球的设备，以及一个连接键盘的USB-OTG的适配器或者模拟机(你不使用[触屏模式](http://android-developers.blogspot.co.uk/2008/12/touch-mode.html))。前者在最近的Android版本中不存在，后者操作起来很麻烦。我推荐使用Genymotion虚拟机，它有以下几个优点。\n\n* 我可以用键盘来实现non-touch(非接触)模式，这样我就可以检查所有focus/press状态\n* 它有一个可调整大小的窗口\n* 拥有一个可变的窗体\n* 截屏与录屏(screenshots/screen)\n* 运行速度非常快\n\n我创建了一个160像素密度(mdpi)的大小为360x640 px的设备(device)来匹配Qi的输出。除了Android的字体渲染(font rendering)，这使得它很容易被发现设计和实现之间的差异。\n\n我将在我的minSdkVersion下创建一个设备(device)，如果你使用的是向上兼容版本的Theme或Style等涉及到AppCompat类型的话，最好使用targetSdkVersion创建设备。\n\n那么我的工作就是实现UI设计稿，Qi的工作就是做出漂亮的UI设计稿。：\n![sketchoutput](http://novoda.com/blog/content/images/2015/07/sketchoutput.png)\n\nQi说现在Sketch是她最喜欢的视觉设计工具(visual design)，尤其喜爱极大的改善了loading/running速度(超过了Photoshop/Illustrator)。Hover Guide可以让你看到你选择的要素与其他要素之间的距离：\n\nSketch也使用符号。你创建一个符号(例如：图标)，并在多个地方使用它。但是当你改变这个符号的时候，它会改变所有的实例。类似的现象存在于styles、对于多文本框使用相同的属性(attributes)集(例如文本颜色(text color)、字体(font)等等)。\n\nSketch本身提供30天的免费使用(仅限Mac)，并有大量的在线设计套件供你使用。\n\n让设计采用开发团队格式对他们来说是有用的，但过去是一个麻烦。我们需要为每一个屏幕(screen)、PDF展示margins和paddings、颜色(colors)、文本样式(text style)以及尺寸(dimentions)。这里是一个例子，当我们为 Sun Mobile app工作的时候Dave创造的：\n![redlines](http://novoda.com/blog/content/images/2015/07/redlines.png)\n\n这不是很好。对设计师来说，这是浪费时间，而且很容易错过事情。作为一名开发人员，规格表是很杂乱的 - 翻转在不同项目之间意味着必须习惯不同设计师的规格表。\n\nZeplin是一个有希望解决红线/规格表的程序，。它可以为使用Sketch的设计者作为一个Web应用程序，生产Zeplin项目很简单就如同使用“CMD+ E”导出他们的画板。\n\nZeplin提供了一个互动门户网站变成了我们用来获取静态规格表 - 我们现在可以查询我们需要的信息，当我们需要的时候：\n\n它虽然需要照顾设计者 - Zeplin是不够聪明对于知道我们需要什么样的信息 - 作为开发者，我们知道有哪些东西用户可以在屏幕上什么看到以及什么样的views/view出现在屏幕上。这些看起来是一样的：\n![bounding_before](http://novoda.com/blog/content/images/2015/07/bounding_before.png)\n\n但是在Sketch中，Qi已经在数字周围添加了不可见的边框，所以Zeplin可以捕捉到它：\n![bounding-after](http://novoda.com/blog/content/images/2015/07/bounding-after.png)\n\n一但我们让设计(designs)导入到Zeplin，是时候更新将应用从scribbles更新到匹配design。\n\n我从[Amazon上买了这个whiteboard](http://www.amazon.co.uk/Brainstorm-Toys-Magnetic-Wipe-Board/dp/B00368CGL4)) ，你需要得到一个细笔(thin pens)，粗笔(fat pens)是没用的。\n![whiteboard](http://novoda.com/blog/content/images/2015/07/whiteboard.png)\n\n我画我需要实现一个lo-fi sketch的一部分;即使Zeplin隐藏了许多默认的信息，我仍然认为这是繁杂，并通过一个名单，我发现它更容易从A到B.\n\nTa-da!\n\n![visualdesign](http://novoda.com/blog/content/images/2015/07/wutson-visualdesign.gif)\n\n接下来的步骤将包括发布到Google+的测试版本和发布轻量级App应用。\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "issue-24/使用Kotlin对ViewGroup中的View进行函数式操作.md",
    "content": "使用 Kotlin 对 ViewGroup 中的 View 进行函数式操作\n---\n\n> * 原文链接 : [Functional operations over Views in ViewGroup using Kotlin](http://antonioleiva.com/functional-operations-viewgroup-kotlin/)\n* 原文作者 : [Antonio](https://plus.google.com/+AntonioLeivaGordillo)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime)\n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)\n* 状态 :  完成\n\n\nCollections, iterators, arrays, sequences...都对 **转换，排序或者其他对 item\n的操作** 提供了完整的支持。不过由于这些类在 Android 中被构造的方式不同，\n某些部分在 Android SDK 中使不能使用的。\n\n比如，我们只能获取一个 ViewGroup 而不能直接获得一个 View 的 list，像这样的操作\n是不可以的。但是我们还可以使用其他的特性。使用 Kotlin，我们可以 **使用这些操作\n准备任何类型的数据**。这个窍门很容易:我们只需创建一个 **Sequence**。\n在我们的例子中，sequence 是一组按顺序排列的 View 集合。我们只需要去\n**实现一个返回 Iterator 的函数**。\n\n假设我们有一个 **Sequence**，函数世界的大门就已经为我们打开。那么让我们开始吧。\n\n>**Note: 请看文章结尾部分**\n>\n> `lakedaemon666` 在评论里提到了一种更简单的方式获取相同的结果，而且不必使用\n>  Sequence 。原文这里不会修改，不过我建议你去看一看他的解决办法。\n\n---\n\n##从 ViewGroup 中创建一个 Sequence\n\n前面提到了，我们想创建一个 iterator，而且需要知道它是否有下一个 item，\n下一个 item 是什么。可以通过创建一个 **extension function**(扩展函数)，\n为所有 ViewGroup 和它的子类提供一个简单的方式完成这项工作:\n\n```kotlin\nfun ViewGroup.asSequence(): Sequence<View> = object : Sequence<View> {\n\n    override fun iterator(): Iterator<View> = object : Iterator<View> {\n        private var nextValue: View? = null\n        private var done = false\n        private var position: Int = 0\n\n        override public fun hasNext(): Boolean {\n            if (nextValue == null && !done) {\n                nextValue = getChildAt(position)\n                position++\n                if (nextValue == null) done = true\n            }\n            return nextValue != null\n        }\n\n        override fun next(): View {\n            if (!hasNext()) {\n                throw NoSuchElementException()\n            }\n            val answer = nextValue\n            nextValue = null\n            return answer!!\n        }\n    }\n}\n```\n\n---\n\n##检索 View 的递归 list\n\n获取一个 view 的list 对其进行函数操作的 。我们首先创建一个所有一级 view 的 list，\n然后使用它们去遍历搜索 ViewGroups 里的其它 view。需要给 ViewGroup 新建一个\n **extension property** (扩展属性)。extension property 和\n[extension function][extension-functions] 很像，可以被添加到任意一个 class 里:\n\n```kotlin\npublic val ViewGroup.views: List<View>\n    get() = asSequence().toList()\n```\n\n接下来，创建一个递归函数返回 layout 中每个 ViewGroup 里的所有 view。\n\n```kotlin\npublic val ViewGroup.viewsRecursive: List<View>\n    get() = views flatMap {\n        when (it) {\n            is ViewGroup -> it.viewsRecursive\n            else -> listOf(it)\n        }\n    }\n```\n\n\n使用 **flatap** 操作将所有结果中的多个 list 转换成单个的 list。\n它会遍历所有 view，返回仅有一个 item 的 list，\n如果遍历的对象是一个 **ViewGroup**，就会请求获取 ViewGroup 内的 view。\n\n##用例\n\n\n现在我们可以对 viewsRecursive 属性执行我们想要的操作了。这里有两个例子,\n我创建了下面这样的一个 layout:\n\n![img](http://antonioleiva.com/wp-content/uploads/2015/07/views-from-viewgroup-e1437489098403.png)\n\n```xml\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                android:id=\"@+id/container\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:paddingBottom=\"@dimen/activity_vertical_margin\"\n                android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n                android:paddingRight=\"@dimen/activity_horizontal_margin\"\n                android:paddingTop=\"@dimen/activity_vertical_margin\"\n                tools:context=\".MainActivity\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/hello_world\"/>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Hello Java\"/>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:text=\"Hello Kotlin\"/>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"end\"\n            android:text=\"Hello Scala\"/>\n\n    </FrameLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:orientation=\"horizontal\">\n\n        <CheckBox\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Check 1\"/>\n\n        <CheckBox\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Check 2\"/>\n\n        <CheckBox\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Check 3\"/>\n\n        <CheckBox\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Check 4\"/>\n\n    </LinearLayout>\n\n</RelativeLayout>\n```\n\n---\n\n下面是代码部分，可以在  MainActivity.onCreate() 执行。它把 `Hello Kotlin`\n字符串转换成大写，然后还设置了 Checkox 的点击事件:\n\n\n\n```kotlin\nval container: ViewGroup = find(R.id.container)\nval views = container.viewsRecursive\n\n// Set Kotlin TextView to Upper\nval kotlinText = views.first {\n    it is TextView && it.text.toString().contains(\"Kotlin\")\n} as TextView\nkotlinText.text = kotlinText.text.toString().toUpperCase()\n\n// Set even checkboxes as checked, and odd as unchecked\nviews filter {\n    it is CheckBox\n} forEach {\n    with(it as CheckBox) {\n        val number = text.toString().removePrefix(\"Check \").toInt()\n        setChecked(number % 2 == 0)\n    }\n}\n```\n\n![img](http://antonioleiva.com/wp-content/uploads/2015/07/views-modified-e1437489038699.png)\n\n##另一种思路\nlakedaemon666 在评论中提到(感谢回复)，创建并遍历一个 sequence 没有什么意义。\nSequences 意味着 lazy iteration，比如，当读取 file 里的某一行时，只有需要的\nitem 被请求。而在我们设定的场景中，所有 item 都被使用了，所以一个普通的 list\n就可以满足我们的需求。\n\n此外，还有更简单的方法创建一个 view 的 list。\n\n```kotlin\npublic val ViewGroup.views: List<View>\n    get() = (0..getChildCount() - 1) map { getChildAt(it) }\n```\n\n---\n\n\n##结语\n\n这个例子看起来可能有点傻，不过你可以从中学到些东西让你的代码更加函数化，\n而不是循环或者其他更典型的迭代编程中的流程控制。\n\n你可以通过我正在写的这本书: [Kotlin for Android Developers][kotlin-for-android-developers]\n来了解更多关于 Kotlin 的内容，这本书会教你从零开始使用 Kotlin 来完成一个 Android App。\n\n\n[kotlin-for-android-developers]:https://leanpub.com/kotlin-for-android-developers/\n[extension-functions]:http://antonioleiva.com/kotlin-android-extension-functions/\n"
  },
  {
    "path": "issue-24/适用于Android的Flux架构.md",
    "content": "\n# Flux Architecture on Android \n# 适用于Android的Flux架构\n============================\n\n> * 原文链接 : [Flux Architecture on Android](http://lgvalle.github.io/2015/08/04/flux-architecture/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Mr.Simple](https://github.com/bboyfeiyu)  \n\nFinding a good architecture for Android applications is not easy. Google\nseems to not care much about it, so there is no official recommendation\non patterns beyond Activities lifecycle management.\n\n找到一个好的应用架构对于Android来说并非易事，Goodle似乎并不那么关心这方面，因为他们并没有推荐一个合适的应用架构。\n\nBut defining an architecture for your application is important. Like it\nor not, **every application is going to have an architecture**. So you'd\nbetter be the one defining it than let it just emerge.\n\n但是对于应用来说一个良好的架构是非常重要的。不管你是否同意，每个应用都应该有一个架构。因此，你最好为你的应用设计一个架构，而不是任由它发展。\n\n\n## Today: Clean Architecture\n## 清晰的软件架构\n=========================\n\nCurrent trend is to adapt **[Clean\nArchitecture](https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)**,\na 2012 Uncle Bob proposal for web applications.\n\n现在比较流行的架构是Bob大叔在2012年发表的、针对于Web应用的[清晰的软件架构](https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)。\n\nI find Clean Architecture a little bit **over-engineered** for most of\nthe Android apps out there.\n\n但是我发现这个清晰的软件架构对于Android应用来说太过重量级了。\n\nTypically **mobile apps live shorter than web apps**. Mobile technology\nis evolving so fast that any app released today is going to be\ncompletely deprecated in twelve months.\n\n通常来说，移动应用都比web应用要简单。移动技术变化得很快，以至于今天发布的应用可能在一年之后就过时了。\n\nMobile apps usually **do very little**. A very high percent of use cases\nare just for data consuming. Get data from API, show data to user. Lot\nof reads, very little writes.\n\n移动应用通常比较简单，比较多的使用场景只是对数据的处理。从API获取数据、向用户展示数据。更多的是阅读，很少有写内容的需求。\n\nAs a result its **business logic is not complex**. At least not as\ncomplex as backend apps. Well you have to deal with platform issues:\nmemory, storage, pause, resume, network, location, etc. But that is not\nyour app business logic. You have all of that in every app.\n\n这也使得移动应用的`业务逻辑不会太过于复杂`，至少不会比后端的应用复杂。当然，你也需要处理一些移动平台的问题:内存，存储，暂停，重新运行，网络，地理位置等等。但是这些并不是你的业务逻辑。 \n\nSo it seems that most of the apps out there will not benefit from things\nlike complex layer divisions or job priority execution queues.\n\n因此，对于大多数应用来说并不会从复杂的分层架构或者具有优先级的任务队列中获得太多益处。\n\nThey may just need a **simple way to organise code, work together\nefficiently and find bugs easily**.\n\n它们可能只是需要简单的方式来组织代码，使得各部分组件之间高效的一起工作，并且容易查找bug。\n\n## Introducing Flux Architecture\n## Flux 应用架构简介\n=============================\n\n**[Flux\nArchitecture](https://facebook.github.io/flux/docs/overview.html)** is\nused by Facebook to build their client- side web applications. Like\n*Clean Architecture* it is not intended for mobile apps, but its\nfeatures and simplicity will allow us to adapt it very well to Android\nprojects.\n\n**[Flux应用架构](https://facebook.github.io/flux/docs/overview.html)**被facebook用于架构客户端Web应用，与*Clean Architecture*类似它也不是针对于移动应用的，它更简洁性能够让我们很方面的适用于Android应用。\n\n![flux-graph-simple](http://img.blog.csdn.net/20150810172158465)\n\nThere are two **key features** to understand Flux:\n\n有两个关键特性能够帮助我们理解Flux : \n\n-   The data flow is always **unidirectional**.\n\n    An [unidirectional data\n    flow](https://www.youtube.com/watch?v=i__969noyAM) is the **core**\n    of the Flux architecture and is what makes it so easy to learn. It\n    also provides great advantages when testing the application as\n    discussed below.\n- 数据流总是单向的\n\t一个 [单向数据流](https://www.youtube.com/watch?v=i__969noyAM)是Flux 应用架构的核心，这也使得它很容易学习。当你需要测试应用时它也提供了很便利的条件。\n\n-   The application is divided into **three main parts**:\n\n    -   **View**: Application interface. It create actions in response\n        to user interactions.\n    -   **Dispatcher**: Central hub through which pass all actions and\n        whose responsibility is to make them arrive to every Store.\n    -   **Store**: Maintain the state for a particular application\n        domain. They respond to actions according to current state,\n        execute business logic and emit a *change* event when they are\n        done. This event is used by the view to update its interface.\n- 应用分为三部分 : \n\t* **View** : 应用接口,它用于将用户的交互转换为Action。\n\t* **Dispatcher** : 中央分发器,将所有action分发到处理它们每个Store;\n\t* **Store** : 维护特定应用领域的状态。它们根据当前的状态对action做出处理,执行业务逻辑,并且在处理完成时发出*change*事件。这个事件用于通知View更新UI。\n\nThis three parts communicate through **Actions**: Simple plain objects,\nidentified by a type, containing the data related to that action.\n\n这三部分通过**Action**交互,Action是通过type标识的简单对象,它包含了一些与action相关的数据。\n\n## Flux Android Architecture\n## 适用于Android的Flux架构\n=========================\n\nThe main target of using Flux principles on Android development is to\nbuild an architecture with a good balance between simplicity and ease of\nscale and test.\n在Android开发中使用Flux原则的主要目标就是创建一个简单、可伸缩、易于测试的应用架构。\n\nFirst step is to **map Flux elements with Android app components**.\n\n第一步需要做的就是**在Android组件之间建立Flux元素的关系映射**。\n\nTwo of this elements are very easy to figure out and implement.\n\n这两类元素非常容易实现 : \n\n-   **View**: Activity or Fragment\n-   **Dispatcher**: An event bus. I will use Otto in my examples but any\n    other implementation should be fine.\n\n- **View**: Activity 或 Fragment;\n- **Dispatcher**: 一个事件总线,我在示例中使用了Otto，但是其他实现也是可以考虑的，例如[AndroidEventBus](https://github.com/bboyfeiyu/AndroidEventBus)。\n\n### Actions\n-------\n\nActions are not complex either. They will be implemented as simple POJOs\nwith two main attributes:\n\nAction也不复杂,它们通常是一些含有如下两个主要属性的简单的对象: \n\n-   Type: a `String` identifying the type of event.\n-   Data: a `Map` with the payload for this action.\n\n- Type : 标识事件类型的一个String字段;\n- Data : 一个含有该Action信息的map字段。\n\nFor example, a typical action to show some User details will look like\nthis:\n\n例如,一个显示一些用户详细的action大致如下所示 : \n\n```java\n    Bundle data = new Bundle();\n    data.put(\"USER_ID\", id);\n    Action action = new ViewAction(\"SHOW_USER\", data);\n```\n\n### Stores\n------\n\nThis is perhaps the **most difficult** to get Flux concept.\n\nStores应该是Flux概念里最难理解的了。\n\nAlso if you have worked with Clean Architecture before it also will be\nuncomfortable to accept, because Stores will assume responsibilities\nthat were previously separated into different layers.\n\n如果你以前使用过Clean Architecture,你会感觉它也不是那么容易理解，因为Stores会假设职责已经被分割到不同的层。\n\nStores contain the **status of the application and its business logic**.\nThey are similar to *rich data models* but they can manage the status of\n**various objects**, not just one.\n\nStores包含了**应用的状态以及业务逻辑**，它们很像**功能完备的数据模型**,但它们能够管理各种对象的状态,而不仅仅是其中一个。\n\nStores **react to Actions emitted by the Dispatcher**, execute business\nlogic and emit a change event as result.\n\nStores对Dispatcher发出的Action做出响应,执行业务逻辑,处理完成之后发出一个change事件。\n\nStores only output is this single event: *change*. Any other component\ninterested in a Store internal status must listen to this event and use\nit to get the data it needs.\n\nStores只会输出一个**change**事件,任何对一个Store的内部状态感兴趣的组件都需要监听该事件,并且使用它来获取数据。\n\nNo other component of the system should need to know anything about the\nstatus of the application.\n\n系统中的其他部分并不需要了解应用的状态。\n\nFinally, stores must **expose an interface** to obtain application\nStatus. This way, view elements can query the Stores and update\napplication UI in response.\n\n最终,Store必须暴露一个接口来获取应用状态.这样一来，View元素就能够查询Store的状态以及更新UI。\n\n![flux-graph-store](http://img.blog.csdn.net/20150810172207045)\n\nFor example, in a Pub Discovery App a SearchStore will be used to keep\ntrack of searched item, search results and the history of past searches.\nIn the same application a ReviewedStore will contain a list of reviewed\npubs and the necessary logic to, for example, sort by review.\n\n例如，在一个酒吧查找App中，SearchStore被用于追踪搜索到的酒吧、搜索结果和经过的酒吧历史数据。另一个ReviewedStore包含查看过的酒吧列表以及必须的逻辑，例如排序。\n\nHowever there is one important concept to keep in mind: **Stores are not\nRepositories**. Their responsibility is *not* to get data from an\nexternal source (API or DB) but only keep track of data provided by\nactions.\n\n然后，有有一个重要的概念你必须要记住 : **Store不是Repositories**。它们的职责不是从外部资源（API或者数据库）中获取数据，只是追中action提供的数据。\n\nSo how Flux application obtain data?\n\n那么Flux如何获取数据呢 ？\n\n## Network requests and asynchronous calls\n## 网络请求与异步调用\n---------------------------------------\n\nIn the initial Flux graph I intentionally skipped one part: **network\ncalls**. Next graph completes first one adding more details:\n\n在前面的Flux图表中我特意省略了一部分: **网络调用**。下一个图表将会完善这部分细节。\n\n![flux-graph-complete](http://img.blog.csdn.net/20150810172211077)\n\nAsynchronous network calls are triggered from an **Actions Creator**. A\nNetwork Adapter makes the asynchronous call to the corresponding API and\nreturns the result to the Actions Creator.\n\n异步网络调用会从**Actions Creator**触发，一个网络适配器会触发一个异步的网络调用并且将结果返回到**Actions Creator**中。\n\nFinally the Actions Creator dispatch the corresponding typed Action with\nreturned data.\n\n最终**Actions Creator**将含有相应type与数据的Action分发出去。\n\nHaving all the network and asynchronous work out of the Stores has has\n**two main advantages**:\n\n所有的网络请求和异步操作从Store中隔离出来有两个优点 : \n\n-   **Your Stores are completely synchronous**: This makes the logic\n    inside a Store very easy to follow. Bugs will be much easier to\n    trace. And since **all state changes will be synchronous** testing a\n    Store becomes an easy job: launch actions and assert expected final\n    state.\n\n- **Store中的操作都是是完全同步的** : 这使得Store中的业务逻辑非常简单，bug也易于发现。并且，自从\"所有的状态变化都必须是同步的\"的运用到实际中，测试Store变得非常容易，只需要加载action，然后对最终的状态做一个断言判断;\n\n-   **All actions are triggered from an Action Creator**: Having a\n    single point at which you create and launch all user actions greatly\n    simplifies finding errors. Forget about digging into classes to find\n    out where an action is originated. **Everything starts here**. And\n    because asynchronous calls occur *before*, everything that comes out\n    of ActionCreator is synchronous. This is a huge win that\n    significantly improves traceability and testability of the code.\n\n- **所有Action都是从Action Creator被触发** : 从一个统一的点创建和启动所有用户的action使得查找错误变得很简单。省去了在各种Class之间查找该action的发出点,**所有的action都出自Action Creator**。因为异步调用在action发出之前已经调用，因此其他ActionCreator的函数都是同步的，这很大程度上提升了代码的可追踪性和可测试性。\n\n## Show me the code: To-Do App\n===========================\n\n[In this example](https://github.com/lgvalle/android-flux-todo-app) you\nwill find a classical **To-Do App** implemented on Android using a Flux\nArchitecture.\n\n[这个例子](https://github.com/lgvalle/android-flux-todo-app)是按照Flux架构实现的To-Do Android应用。\n\nI tried to keep the project as simple as possible just to show how this\narchitecture can produce **very well organised apps**.\n\n我尝试着尽量简单的演示如何使用Flux架构来构建一个组织良好的应用。\n\nSome comments about implementation:\n\n关于这个实现的一些解释 : \n\n-   The `Dispatcher` is implemented using Otto Bus. Any bus\n    implementation will mostly work. There is a **Flux restriction** on\n    events I’m not applying here. On original Flux definition\n    dispatching an event before previous one has finish is forbidden and\n    will throw an exception. To keep the project simple I’m not\n    implementing that here.\n\n- Dispatcher是使用了Otto事件总线实现。任何的事件总线也都可以的啦。在原来的Flux定义中,在上一个事件结束前就分发一个新的事件是不允许的,此时会抛出异常。为了代码比较简单，我在这个项目中并没有实现这个功能;\n\n-   There is an `ActionsCreator` class to help creating `Actions` and\n    posting them into the `Dispatcher`. It is a pretty common pattern in\n    Flux which keeps things organised.\n- ActionsCreator类用于创建Action和将它们投递到`Dispatcher`。在Flux中这是一种比较常见的模式。\n\n-   `Actions` types are just `String` constants. It is probably not the\n    best implementation but is quick and helps keeping things simple.\n- Action类型只有字符串常量。这也许不是最好的实现,但是是最快、最能保持简洁性的。\n\nSame thing with `Actions` data: they are just a `HashMap` with a\n`String` key and `Object` as a value. This forces ugly castings on\nStores to extract actual data. Of course, this is not type safe but\nagain, keeps the example easy to understand.\n\nAction中的数据也只是使用key为String、value为Object类型的`HashMap`来存储。因此这需要你在Store中进行强制类型转换，一般得到具体的Action数据。当然，这并不是类型安全的,还是那句话 : 为了保持简单性。\n\n## 结论\n==========\n\nThere is **no such thing as the Best Architecture for an Android app**.\nThere *is* the Best Architecture for your current app. And it is the one\nthat let you collaborate with your teammates easily, finish the project\non time, with quality and as less bugs as possible.\n\n没有什么最好的Android应用架构，只有最适合你应用的架构。这个架构能够使你你和你的队友很方便地一起协作,在规定的时间内高质量的完成你的应用。\n\nI believe Flux is very good for all of that.\n\n我相信Flux架构在这些方面还是比较靠谱的，不信就自己动手试试吧！\n\n## 示例代码\n\n[代码在这里](https://github.com/lgvalle/android-flux-todo-app)\n\n## 深度阅读\n\n-   [Facebook Flux\n    Overview](https://facebook.github.io/flux/docs/overview.html)\n-   [Testing Flux\n    Applications](https://facebook.github.io/flux/docs/testing-flux-applications.html#content)\n-   [Flux architecture Step by\n    Step](http://blogs.atlassian.com/2014/08/flux-architecture-step-by-step/)\n-   [Async Requests and\n    Flux](http://www.code-experience.com/async-requests-with-react-js-and-flux-revisited/)\n-   [Flux and\n    Android](http://armueller.github.io/android/2015/03/29/flux-and-android.html)\n\n\n\n"
  },
  {
    "path": "issue-25/Android主题动态切换开源库Prism基本原理1-核心功能.md",
    "content": "#Android主题动态切换开源库Prism基本原理1-核心功能\n\n> * 原文链接 : [Prism Fundamentals Part 1](https://blog.stylingandroid.com/prism-fundamentals-part-1/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](www.devtf.cn)\n* 译者 : [Desmond1121](https://github.com/desmond1121)  \n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)\n\n**重要提示：Prism源码目前停止更新了（你可以在[Prism-Github](https://github.com/StylingAndroid/Prism) 描述文件中看到）。不过我还是决定写出这一系列的文章来介绍Prism现在的版本，因为它很可能还有用。**\n\n我很高兴地宣布Prism问世了，这是一个全新的Android主题切换框架。虽然现在只是初步的发布，只具有一些基础功能，但已经足够强大了！在本系列文章中我会全方位地介绍Prism的使用方法，并告诉你怎样将它拓展到自己的工程中。\n\n在开始之前，先说说Prism诞生的背景吧。其实一开始我并没有打算做一个框架，我当时正在处理Styling Android中一些关于ViewPager的动态颜色的代码。我将它重构成一些方便使用的部件，在重构之后我感觉这是一个很简洁可用的API，Prism框架的想法由此诞生。我将这部分代码向一些我比较认同能力的人分享了之后，他们也觉得这是一个很实用清晰框架，在这之后我开始写Prism。直到现在，它具备了很多强大的功能，而仍然保持着简单清晰的API，我觉得是时候发布它了。\n\n有一个单词拼写的提示：你可能从通篇的`colour`来表示颜色(而不是`color`）中猜到我是英国人。不过我也知道同时有很多人在使用美式英语，为了让人们有选择自己熟悉语言的权利，英式、美式英语拼写都会Prism接受。所以你可以使用`setColor(int color)`或`setColour(int colour)`，它们的效果是一样的。不过使用`setColor(int color)`的效率稍微低一点点因为它其实是通过调用`setColour(int colour)`实现的，如果你使用英式拼写，会提高些许性能。\n\nPrism下含有三个库：\n\n- prsim 库含有一些核心功能；\n- prism-viewpager 库实现了核心库与ViewPager的对接；\n- prism-palette 库实现了核心库与Palette的对接。\n\n分成三个库是为了区分依赖条件：核心库不依赖外部条件，它能够很容易地添加到你的工程之中；但是prism-viewpager和prism-palette需要依赖相应的support库。所以当你的程序不使用这些依赖库时，你可以只使用prism库来省去不必要的依赖条件。不过当你的程序中使用了`ViewPager`时，即已经对相关的support库有了依赖，那么添加prism-viewpager库就不需要额外的依赖条件。\n\nPrism在jCenter和Maven中央仓库都有发布，假设你已经在工程中设置了它们其中一个为依赖仓库，那么你可以在`build.gradle`的`dependencies`项下添加Prism库，具体参考下面这个代码：\n\n\tapply plugin: 'com.android.application'\n\t \n\tandroid {\n\t    compileSdkVersion 22\n\t    buildToolsVersion \"23.0.0 rc3\"\n\t \n\t    defaultConfig {\n\t        applicationId \"com.stylingandroid.prism.sample.palette\"\n\t        minSdkVersion 7\n\t        targetSdkVersion 22\n\t        versionCode 1\n\t        versionName \"1.0\"\n\t    }\n\t    buildTypes {\n\t        release {\n\t            minifyEnabled false\n\t            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t        }\n\t    }\n\t}\n\t \n\tdependencies {\n\t    compile 'com.android.support:appcompat-v7:22.2.0'\n\t    compile 'com.android.support:design:22.2.0'\n\t    compile 'com.android.support:support-v4:22.2.0'\n\t    compile 'com.stylingandroid.prism:prism:1.0.1'\n\t    compile 'com.stylingandroid.prism:prism-viewpager:1.0.1'\n\t}\n\n以上代码添加了prism和prism-viewpager库，现在最新的版本是1.0.1（[下载链接](https://bintray.com/stylingandroid/maven/prism/1.0.1/view)）。\n\n当你添加了依赖库后你就可以使用Prism了。\n\nPrism的基本思想是围绕这三个概念：Setter, Filter 和 Triggers。\n\nSetter可以设置UI组件的颜色（通常是`View`，不过也可以设置其他东西，我下面会讲到）。它的功能就是将`setColor(int color)`（或`setColour(int colour)`）封装到具有不同功能的函数中。举个例子，内置的`ViewBackgroundSetter`中就有一个函数：`setBackgroundColor(int color)`。有时候Setter会在不同的Android版本中有不同的效果，比如内置的`StatusBarSetter`会在Android Lollipop(5.0.1)以下的版本中都不起作用，因为在5.0.1以下的版本不支持StatusBar的颜色改变。不过你不要担心，Prism会判断运行时版本从而避开程序死机的情况，你可以放心地使用它。\n\nPrism中内置了几个基础的Setter：\n\n- `FabSetter（FloatingActionButton fab)` - 为[Android Design Support Library](http://android-developers.blogspot.hk/2015/05/android-design-support-library.html)中的`FloatingActionButton`设置颜色。\n- `StatusBarSetter(Window window)` - 在支持状态栏颜色改变的窗口中设置状态栏的颜色，注意它的操作对象并不是`View`。\n- `TextSetter(TextView textView)` - 设置`TextView`中文字的颜色。\n- `ViewBackgroundSetter(View view)` - 设置`View`的背景颜色。\n\n当然你也可以实现自己的Setter来为自定义的`View`设置颜色，甚至你可以为同一个`View`设置多个Setter来为`View`不同的部件设置不同的颜色。你只需要实现这些Setter，然后将它们添加到Prism中即可。\n\nFilter可以多样化颜色调整。Prism框架通常只使用单个颜色为所有组件进行转换，为组件设定Filter可以帮助你改变Prism输入的颜色。它会在内部处理好颜色变换的逻辑，再输出转换好的颜色。Prism内置的一些基础Filter有：\n\n- `IdentifyFilter()` - 返回与输入相同的颜色；\n- `ShadeFilter(float amount)` - 将输入颜色与黑色混合。`amount`是在0到1之间的浮点数，它决定了黑色的比例。当`amount`为0时，输出颜色就是输入颜色；当为1时，输出颜色为纯黑色。\n- `TintFilter(float amount)` - 将输入颜色与白色混合。`amount`是在0到1之间的浮点数，它决定了白色的比例。当`amount`为0时，输出颜色就是输入颜色；当为1时，输出颜色为纯白色。\n\nTrigger触发了Prism的颜色变换。特别的，它是通过调用`setColour(int colour)`，让所有注册过的Setter的来让UI组件产生相应的颜色变换。\n\n在Prism核心库内没有提供内置的Trigger，因为那需要额外的依赖库。不过在ViewPager和Palette相应的扩展库中是有提供的。\n\n现在我们需要将这三个组件整合起来，幸运的是`Prism`实例会帮我们做这件事情。每个`Prism`实例可能没有Trigger也可能有多个Trigger，有一个或多个Setter。每个Setter可以有一个Filter来在不同情况下产生不同的颜色。\n\nPrism提供了智能的工厂方法，它会根据你传入的数据来智能选择Setter。比如你传一个`FloatingActionButton`给`Prism.Builder.background()`，它会为你自动生成一个`FabSetter`。\n\n`Prism`使用了建造者模式来构建组件，并将他们与Trigger绑定在一起，会响应相应的触发事件。\n\n让我们看一下怎么创造一个`Prism`实例：\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        TextView textView = (TextView) findViewById(R.id.text_view);\n        AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar);\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);\n\n        setSupportActionBar(toolbar);\n\n        Filter tint = new TintFilter(TINT_FACTOR_50_PERCENT);\n        prism = Prism.Builder.newInstance()\n                .background(appBar)\n                .background(getWindow())\n                .text(textView)\n                .background(fab, tint)\n                .build();\n\n        fab.setOnClickListener(this);\n        setColour(currentColour);\n    }\n\n    @Override\n    protected void onDestroy() {\n        if (prism != null) {\n            prism.destroy();\n        }\n        super.onDestroy();\n    }\n\n上面代码中的大部分都不需要解释（这是一些Android开发的基本内容）。其中在新建`Prism`实例的时候，我们首先做了一个50%的`TintFilter`（与白颜色混合的Filter）。\n\n然后我们新建了一个`Prism.Builder`实例，为它添加了一个AppBar实例（会相应地产生一个Setter专门用于设置AppBar的背景），然后添加了一个Window实例（相应地产生一个Setter专门用于设置状态栏背景颜色），再添加了一个TextView以及FloatingActionButton（它们都产生了相应的Setter），并为FloatingActionButton设置了之前定义的`TintFilter`。\n\n最终我们使用`Prism.Builder.build()`办法来产生一个`Prism`实例。\n\n现在我们把几个组件都结合起来了，然后调用`Prism.setColour(int colour)`来为组件转换颜色。\n\n注意要在`onDestroy()`中销毁`Prism`实例。不过实际上不注销也没关系，因为当Activity被销毁之后`Prism`不会持有任何引用防止GC收集，它不会在我们不需要的它时候妨碍到内存清理活动。\n\n这样我们就了解了Prism的基础工作原理：在`onCreate()`中添加6行代码就可以让`FloatingActionButton`和你的`Toolbar`、`TextView`转换颜色：\n\n![Demo](http://img.blog.csdn.net/20150816235240096)\n\n怎么样，Prism厉害吧？下一篇文章的内容是怎样Trigger集合到代码中，尽请期待！\n\n这里面所有的例子都会在[源码Github](https://github.com/StylingAndroid/Prism)中的[示例代码](https://github.com/StylingAndroid/Prism/tree/master/sample) 中看到。\n\n"
  },
  {
    "path": "issue-25/Android主题动态切换开源库Prism基本原理2-搭配ViewPager使用.md",
    "content": "#Android主题动态切换开源库Prism基本原理2-搭配ViewPager使用\n\n> * 原文链接 : [Prism Fundamentals Part 2](https://blog.stylingandroid.com/prism-fundamentals-part-2/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](www.devtf.cn)\n* 译者 : [Desmond1121](https://github.com/desmond1121)  \n* 校对者: [chaossss](https://github.com/chaossss)\n\n**重要提示：Prism源码目前停止更新了（你可以在[Prism-Github](https://github.com/StylingAndroid/Prism) 描述文件中看到）。不过我还是决定写出这一系列的文章来介绍Prism现在的版本，因为它很可能还有用。**\n\n在之前的一章中我介绍了用`Prism`实例将UI组件链接起来并通过调用`setColour(int colour)`切换颜色主题。那么你也可以看到，使用`Setter`和`Filter`可以让事情（切换主题）变得多么简单，省去了很多冗杂的代码。现在我们来看看加入`Trigger`以后会发生什么有趣的事情！\n\n首先我们在工程中添加`prism-viewpager`的依赖库，`build.gradle`示例如下：\n\n\tapply plugin: 'com.android.application'\n\n\tandroid {\n\t    compileSdkVersion 22\n\t    buildToolsVersion \"23.0.0 rc3\"\n\t\n\t    defaultConfig {\n\t        applicationId \"com.stylingandroid.prism.sample.viewpager\"\n\t        minSdkVersion 7\n\t        targetSdkVersion 22\n\t        versionCode 1\n\t        versionName \"1.0\"\n\t    }\n\t    buildTypes {\n\t        release {\n\t            minifyEnabled false\n\t            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t        }\n\t    }\n\t}\n\t\n\tdependencies {\n\t    compile 'com.android.support:appcompat-v7:22.2.0'\n\t    compile 'com.android.support:design:22.2.0'\n\t    compile 'com.stylingandroid.prism:prism:1.0.1'\n\t    compile 'com.stylingandroid.prism:prism-viewpager:1.0.1'\n\t}\n\t\nTrigger是Prism触发颜色主题变换时必须的组件。我们首先来看`ViewPagerTrigger`搭配`ViewPager`时是怎么随着用户输入而触发颜色主题变换的。为了实现颜色变换，`ViewPager`的`Adapter`需要为它的每一个页面位置提供颜色属性，`ColourProvider`接口提供了这个功能(或者是`ColorProvider`，如果你不介意使用它带来的些许性能损失的话)。\n\n\t/* ColourProvider.java */\n\tpublic interface ColourProvider {\n\t    @ColorInt int getColour(int position);\n\t    int getCount();\n\t}\n\t\n\t/* ColorProvider.java */\n\tpublic interface ColorProvider {\n\t    @ColorInt int getColor(int position);\n\t    int getCount();\n\t}\n\n如果你使用过`PagerTitleStrip`或者新的Android设计库中的`TabLayout`的话，你不会对为`ViewPager`的每个页面提供标题这件事感到陌生。而使用`ColourProvider`接口仅仅就是将 **为每个页面提供标题**变成了 **为每个页面提供颜色值**而已。当继承`Adapter`的时候你不需要提供`getCount()`方法，因为这个方法在`Adapter`中已经定义了。你所需要做的就是像下面这些代码一样实现你的`Adapter`：\n\n\tpublic class RainbowPagerAdapter extends FragmentPagerAdapter implements ColourProvider {\n\t    \n\t    private static final Rainbow[] COLOURS = {\n\t            Rainbow.Red, Rainbow.Orange, Rainbow.Yellow, Rainbow.Green,\n\t            Rainbow.Blue, Rainbow.Indigo, Rainbow.Violet\n\t    };\n\t\n\t    private final Context context;\n\t\n\t    public RainbowPagerAdapter(Context context, FragmentManager fragmentManager) {\n\t        super(fragmentManager);\n\t        this.context = context;\n\t    }\n\t\n\t    @Override\n\t    public Fragment getItem(int position) {\n\t        Rainbow colour = COLOURS[position];\n\t        return ColourFragment.newInstance(context, getPageTitle(position), colour.getColour());\n\t    }\n\t\n\t    @Override\n\t    public void destroyItem(ViewGroup container, int position, Object object) {\n\t        FragmentManager manager = ((Fragment) object).getFragmentManager();\n\t        FragmentTransaction trans = manager.beginTransaction();\n\t        trans.remove((Fragment) object);\n\t        trans.commit();\n\t        super.destroyItem(container, position, object);\n\t    }\n\t\n\t    @Override\n\t    public int getCount() {\n\t        return COLOURS.length;\n\t    }\n\t\n\t    @Override\n\t    public CharSequence getPageTitle(int position) {\n\t        return COLOURS[position].name();\n\t    }\n\t    \n\t\t/* 重写ColourProvider中的方法 */\n\t    @Override\n\t    public int getColour(int position) {\n\t        return COLOURS[position].getColour();\n\t    }\n\t\t\n\t\t/* 提供颜色值的枚举 */\n\t    private enum Rainbow {\n\t        Red(Color.rgb(0xFF, 0x00, 0x00)),\n\t        Orange(Color.rgb(0xFF, 0x7F, 0x00)),\n\t        Yellow(Color.rgb(0xCF, 0xCF, 0x00)),\n\t        Green(Color.rgb(0x00, 0xAF, 0x00)),\n\t        Blue(Color.rgb(0x00, 0x00, 0xFF)),\n\t        Indigo(Color.rgb(0x4B, 0x00, 0x82)),\n\t        Violet(Color.rgb(0x7F, 0x00, 0xFF));\n\t\n\t        private final int colour;\n\t\n\t        Rainbow(int colour) {\n\t            this.colour = colour;\n\t        }\n\t\n\t        public int getColour() {\n\t            return colour;\n\t        }\n\t    }\n\t}\n\n当我们拥有一个实现`ColourProvider`接口的`Adapter`后我们就可以将它和Prism的`ViewPagerTrigger`一起使用了：\n\n\tpublic class MainActivity extends AppCompatActivity {\n\t    private static final float TINT_FACTOR_50_PERCENT = 0.5f;\n\t    private DrawerLayout drawerLayout;\n\t    private View navHeader;\n\t    private AppBarLayout appBar;\n\t    private Toolbar toolbar;\n\t    private TabLayout tabLayout;\n\t    private ViewPager viewPager;\n\t    private FloatingActionButton fab;\n\t\n\t    private Prism prism = null;\n\t\n\t    @Override\n\t    protected void onCreate(Bundle savedInstanceState) {\n\t        super.onCreate(savedInstanceState);\n\t        setContentView(R.layout.activity_main);\n\t\t\t\n\t\t\t/* 获取View实例 */\n\t        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);\n\t        navHeader = findViewById(R.id.nav_header);\n\t        appBar = (AppBarLayout) findViewById(R.id.app_bar);\n\t        toolbar = (Toolbar) findViewById(R.id.toolbar);\n\t        tabLayout = (TabLayout) findViewById(R.id.tab_layout);\n\t        viewPager = (ViewPager) findViewById(R.id.viewpager);\n\t        fab = (FloatingActionButton) findViewById(R.id.fab);\n\t\n\t        setupToolbar();\n\t        setupViewPager();\n\t    }\n\t\n\t    @Override\n\t    protected void onDestroy() {\n\t        if (prism != null) {\n\t            prism.destroy();\n\t        }\n\t        super.onDestroy();\n\t    }\n\t\n\t    private void setupToolbar() {\n\t        setSupportActionBar(toolbar);\n\t        ActionBar actionBar = getSupportActionBar();\n\t        if (actionBar != null) {\n\t            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);\n\t            actionBar.setDisplayHomeAsUpEnabled(true);\n\t            actionBar.setTitle(R.string.app_title);\n\t        }\n\t    }\n\t\n\t    @Override\n\t    public boolean onOptionsItemSelected(MenuItem item) {\n\t        switch (item.getItemId()) {\n\t            case android.R.id.home:\n\t                drawerLayout.openDrawer(GravityCompat.START);\n\t                return true;\n\t            default:\n\t                return super.onOptionsItemSelected(item);\n\t        }\n\t    }\n\t\n\t    private void setupViewPager() {\n\t        RainbowPagerAdapter adapter = new RainbowPagerAdapter(this, getSupportFragmentManager());\n\t        viewPager.setAdapter(adapter);\n\t        Filter tint = new TintFilter(TINT_FACTOR_50_PERCENT);\n\t        Trigger trigger = ViewPagerTrigger.newInstance(viewPager, adapter);\n\t        prism = Prism.Builder.newInstance()\n\t                .add(trigger)\n\t                .background(appBar)\n\t                .background(getWindow())\n\t                .background(navHeader)\n\t                .background(fab, tint)\n\t                .colour(viewPager, tint)\n\t                .build();\n\t        tabLayout.setupWithViewPager(viewPager);\n\t        viewPager.setCurrentItem(0);\n\t    }\n\t}\n\n在`setupViewPager()`中我们首先创建一个`RainbowPagerAdapter`并应用到`ViewPager`中。之后新建了一个`TintFilter`，它能够让我们的`FloatingActionButton`的颜色更加亮一点。然后新建一个与`ViewPager`和`Adapter`实例相关联的Trigger。\n\n在做好上述准备工作之后，我们开始建立`Prism`实例。与上次唯一不同的是我们为`Prism`绑定了更多组件，以及添加了刚才新建的Trigger。你可能注意到我们为`ViewPager`实例直接设置了颜色，实际上它的作用是设置当`ViewPager`滑到两边极限时的“溢出颜色”（这个函数很巧妙，因为在不同的系统版本中处理`ViewPager`“溢出颜色”的手段不一样，但Prism会在内部就帮你处理好这些逻辑！）。\n\n之后我们将`TabLayout`与`ViewPager`绑定到一起（这是`TabLayout`设计中要求的，而不是`Prism`的逻辑需要），之后将`ViewPager`的初始页面设置成了第一页。\n\n简简单单的几行代码就能够让我们在滑动页面的时候改变主题颜色，看一下Demo吧：\n\n![Demo1](http://img.blog.csdn.net/20150817115052504)\n\n可能有细腻的程序员会发现颜色转换并不是跳跃式的，它会有一个渐变的过程，而且渐变过程是跟随着你的拖拽过程的！\n\n![Demo2](http://img.blog.csdn.net/20150817115119108)\n\n这里面所有的例子都会在[源码Github](https://github.com/StylingAndroid/Prism)中的[示例代码](https://github.com/StylingAndroid/Prism/tree/master/sample-veiwpager) 中看到。\n\n"
  },
  {
    "path": "issue-25/Android主题动态切换开源库Prism基本原理3-搭配Palette使用.md",
    "content": "#Android主题动态切换开源库Prism基本原理3-搭配Palette使用\n\n> * 原文链接 : [Prism Fundamentals Part 3](https://blog.stylingandroid.com/prism-fundamentals-part-3/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](www.devtf.cn)\n* 译者 : [Desmond1121](https://github.com/desmond1121)  \n* 校对者: [chaossss](https://github.com/chaossss)\n\n---\n\n**译者注：在阅读本章之前你应该确定你已经了解[Android Palette](https://developer.android.com/reference/android/support/v7/graphics/Palette.html)。**\n\n关于Palette，这里有一篇参考文章写得不错:[Extracting Colors to a Palette with Android Lollipop](https://www.bignerdranch.com/blog/extracting-colors-to-a-palette-with-android-lollipop/) （我后续会翻译这篇文章）\n\n---\n\n**重要提示：Prism源码目前停止更新了（你可以在[Prism-Github](https://github.com/StylingAndroid/Prism) 描述文件中看到）。不过我还是决定写出这一系列的文章来介绍Prism现在的版本，因为它很可能还有用。**\n\n\n在前面一章中我们介绍了怎么使用Prism和ViewPager结合起来。在Prism 1.0.0版本中同样也有和Palette相结合的库，我们现在就来看看怎么去使用它。\n\n首先我们向`build.gradle`中添加相关的依赖：\n\n```\n\tapply plugin: 'com.android.application'\n\n\tandroid {\n\t    compileSdkVersion 22\n\t    buildToolsVersion \"23.0.0 rc3\"\n\t\n\t    defaultConfig {\n\t        applicationId \"com.stylingandroid.prism.sample.palette\"\n\t        minSdkVersion 7\n\t        targetSdkVersion 22\n\t        versionCode 1\n\t        versionName \"1.0\"\n\t    }\n\t    buildTypes {\n\t        release {\n\t            minifyEnabled false\n\t            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t        }\n\t    }\n\t}\n\t\n\tdependencies {\n\t    compile 'com.android.support:appcompat-v7:22.2.0'\n\t    compile 'com.android.support:design:22.2.0'\n\t    compile 'com.stylingandroid.prism:prism:1.0.0'\n\t    compile 'com.stylingandroid.prism:prism-palette:1.0.0'\n\t}\n```\n\n`PaletteTrigger`本身的使用是非常简单的，我们只要新建一个`PaletteTrigger`实例然后将它加入到`Prism.Builder`就好了，代码如下：\n\n```java\n\tpaletteTrigger = new PaletteTrigger();\n\tprism = Prism.Builder.newInstance()\n        .add(paletteTrigger)\n        /* 添加其他组件到Prism */\n        .build();\n```\n\n当加入`PaletteTrigger`后我们就可以通过调用`PaletteTrigger.setBitmap(Bitmap bitmap)`来提取目标Bitmap的特征颜色。这个函数会新建一个`Palette`实例来处理支持的图片格式，并在处理结束后触发Prism颜色变换。\n\n由于`Palette`的特殊机制我们需要用一点手段将UI组件和`Prism`结合起来，从而它们能够被正常染色。首先让我们回顾一下`Palette`是怎么工作的：\n\n`Palette`从图片中抽取出6种不同的颜色样本`Swatch`：vibrant(鲜艳色)、dark vibrant(鲜艳色中的暗色)、light vibrant(鲜艳色中的亮色)、muted(柔和色)、dark muted(柔和色中的暗色)、light muted(柔和色中的亮色)。\n你可以通过颜色样本`Swatch`获取三个颜色值：\n\n- 抽取出的初始颜色(`Swatch.getRgb()`或`Swatch.getHsl()`)\n- 与抽取出颜色搭配的标题字体色(`Swatch.getTitleTextColor()`)\n- 与抽取出颜色搭配的正文字体色(`Swatch.getBodyTextColor()`)\n\n所以我们一共能从`Palette`中获取到18个颜色值。\n\n`PaletteTrigger`提供了一系列工厂方法来获取`Swatch`，并返回一个`Filter`。这个`Filter`会告诉`Setter`要给View设置的颜色是初始颜色、标题字体色、正文字体色中的哪一个。所以我们使用`Filter`来帮助`Prism`链接UI组件是非常必要的。\n\n要获取与图片中dark vibrant色相搭配的正文色，只需要下面这一行代码就可以实现：\n\n```java\n\tFilter darkVibrantTitle = paletteTrigger.getDarkVibrantFilter(paletteTrigger.getTextFilter()); \n```\n\n如果不为View设置`Filter`的话，那么会默认使用初始颜色（不过我还是建议你根据使用目的进行设置）。目前的版本中如果对应的颜色种类在`Palette`中没有抽取到的话会使用透明色（即绑定的UI组件会在屏幕上暂时消失），这点还不是很令人满意，我会在后续的开发中进行改进。\n\n\n那么我们已经清楚了`PaletteTrigger`是怎么使用的了，为了有个更具体的理解，下面这段`onCreate()`中的代码片段中展示了整个使用过程：\n\n```java\n\tView vibrant = findViewById(R.id.swatch_vibrant);\n\tView vibrantLight = findViewById(R.id.swatch_vibrant_light);\n\tView vibrantDark = findViewById(R.id.swatch_vibrant_dark);\n\tView muted = findViewById(R.id.swatch_muted);\n\tView mutedLight = findViewById(R.id.swatch_muted_light);\n\tView mutedDark = findViewById(R.id.swatch_muted_dark);\n\t\n\ttitleText = (TextView) findViewById(R.id.title);\n\tbodyText = (TextView) findViewById(R.id.body);\n\n\tpaletteTrigger = new PaletteTrigger();\n\tprism = Prism.Builder.newInstance()\n\t    .add(paletteTrigger)\n\t    .background(vibrant, paletteTrigger.getVibrantFilter(paletteTrigger.getColour()))\n\t    .background(vibrantLight, paletteTrigger.getLightVibrantFilter(paletteTrigger.getColour()))\n\t    .background(vibrantDark, paletteTrigger.getDarkMutedFilter(paletteTrigger.getColour()))\n\t    .background(muted, paletteTrigger.getMutedFilter(paletteTrigger.getColour()))\n\t    .background(mutedLight, paletteTrigger.getLightMutedFilter(paletteTrigger.getColour()))\n\t    .background(mutedDark, paletteTrigger.getDarkMutedFilter(paletteTrigger.getColour()))\n\t    .background(titleText, paletteTrigger.getVibrantFilter(paletteTrigger.getColour()))\n\t    .text(titleText, paletteTrigger.getVibrantFilter(paletteTrigger.getTitleTextColour()))\n\t    .background(bodyText, paletteTrigger.getLightMutedFilter(paletteTrigger.getColour()))\n\t    .text(bodyText, paletteTrigger.getLightMutedFilter(paletteTrigger.getBodyTextColour()))\n\t    .add(this)\n\t    .build();\n```\t\n\n这段代码将六个`View`组件分别赋予了`Palette`解析后的六种初始颜色，并分别为两个`TextView`的文本赋予了 *鲜艳色搭配的标题文本色*与 *亮柔和色搭配的正文文本色*。\n\n\n你可能注意到了我们将`Acitivty`实现成了`Setter`（通过`add(this)`这一行看出）。这是为了实现在`Palette`处理好图片后的回调函数（即`setColour(int colour)`)中设置`ImageView`的图片，由于`Palette`在解析大图片的时候需要花费一些时间，这么做可以让解析颜色与显示图片的结果同时显示到屏幕上，用起来更加舒服。\n\n我们来看看运行起来是什么效果吧：\n\n![Demo](http://img.blog.csdn.net/20150817155549561)\n\n你也可以看到，我们实际上没有将其他UI组件（`Toolbar`等）加入到这个Demo中，只是为获取颜色样本做了示例。如果你已经阅读并理解了前面几篇文章的话，你能够很容易地掌握如何将`PaletteTrigger`结合到主题颜色变换的应用中。\n\n那么基础的部分就介绍完毕了，现在我们会把Prism的开发告一段落。当Prism恢复开发之后，我会再写更多的东西来介绍它的更多应用。\n\n这里面所有的例子都会在[源码Github](https://github.com/StylingAndroid/Prism)中的[示例代码](https://github.com/StylingAndroid/Prism/tree/master/sample-palette) 中看到。\n\n"
  },
  {
    "path": "issue-25/一个内存泄漏引发的血案-Square.md",
    "content": "一个内存泄漏引发的血案-Square\n---\n\n> * 原文链接 : [A small leak will sink a great ship](https://corner.squareup.com/2015/08/a-small-leak.html)\n* 原文作者 : [Pierre-Yves Ricau](corner.squareup.com)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  完成 \n\n\n\n\n在开发 LeakCanary 时我发现一处奇怪的内存泄漏，为了搞清楚到底是什么原因导致这个问题我一边 Debug，一边在邮件中和小伙伴们沟通，最后形成了这篇博文。\n\n嫌弃篇幅太长懒的看的，概述在此：在 Android Lollipop 之前使用 AlertDialog 可能会导致内存泄漏。\n\n##The Artist\n\nLeakCanary 通知我存在内存泄漏：\n\n`\n* GC ROOT thread com.squareup.picasso.Dispatcher.DispatcherThread.<Java Local>\n* references android.os.Message.obj\n* references com.example.MyActivity$MyDialogClickListener.this$0\n* leaks com.example.MyActivity.MainActivity instance\n`\n\n简单来说就是：一个 [Picasso](https://github.com/square/picasso) 线程正在站内持有一个 Message 实例的本地变量，而 Message 持有 DialogInterface.OnClickListener 的引用，而 DialogInterface.OnClickListener 又持有一个被销毁 Activity 的引用。\n\n本地变量通常由于他们仅存在于栈内存活时间较短，当线程调用某个方法，系统就会为其分配栈帧。当方法返回，栈帧也会随之被销毁，栈内所有本地变量都会被回收。如果本地变量导致了内存泄漏，一般意味着线程要么死循环，要么阻塞了，而且线程在这种状态时持有着 Message 实例的引用。\n\n于是 [Dimitris](https://github.com/square/picasso/graphs/contributors) 和我都去 Picasso 源码中一探究竟：\n\nDispatcher.DispatcherThread 是一个简单的 HandlerThread：\n\n```java\nstatic class DispatcherThread extends HandlerThread {\n  DispatcherThread() {\n    super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);\n  }\n}\n```\n\n这个线程用标准的方式通过 Handler 接收 Message：\n\n```java\nprivate static class DispatcherHandler extends Handler {\n  private final Dispatcher dispatcher;\n\n  public DispatcherHandler(Looper looper, Dispatcher dispatcher) {\n    super(looper);\n    this.dispatcher = dispatcher;\n  }\n\n  @Override public void handleMessage(final Message msg) {\n    switch (msg.what) {\n      case REQUEST_SUBMIT: {\n        Action action = (Action) msg.obj;\n        dispatcher.performSubmit(action);\n        break;\n      }\n      // ... handles other types of messages\n    }\n  }\n}\n```\n\n显然 `Dispatcher.DispatcherHandler.handleMessage()` 里面没有明显会让本地变量持有 Message 引用的 Bug。\n\n##Queue Tips\n\nLet's look at how HandlerThread works:\n\n后来越来越多内存泄漏的通知出现了，这些通知不仅仅来自 Picasso，各种各样线程中的本地变量都存在内存泄漏，而且这些内存泄漏往往和 Dialog 的 OnClickListener 有关。发生内存泄漏的线程有一个共同的特性：他们都是工作者线程，而且通过某种阻塞队列接收各自的工作。\n\n```java\nfor (;;) {\n    Message msg = queue.next(); // might block\n    if (msg == null) {\n        return;\n    }\n    msg.target.dispatchMessage(msg);\n    msg.recycleUnchecked();\n}\n```\n\n通过源码可以发现，肯定存在本地变量持有 Message 的引用，然而它的生命周期本应很短，而且在循环结束时被清除。\n\n我们尝试通过利用阻塞队列实现一个简单的工作者线程来重现这个 Bug，它只发送一个 Message：\n\n```java\nstatic class MyMessage {\n  final String message;\n  MyMessage(String message) {\n    this.message = message;\n  }\n}\n\nstatic void startThread() {\n  final BlockingQueue<MyMessage> queue = new LinkedBlockingQueue<>();\n  MyMessage message = new MyMessage(\"Hello Leaking World\");\n  queue.offer(message);\n  new Thread() {\n    @Override public void run() {\n      try {\n        loop(queue);\n      } catch (InterruptedException e) {\n        throw new RuntimeException(e);\n      }\n    }\n  }.start();\n}\n\nstatic void loop(BlockingQueue<MyMessage> queue) throws InterruptedException {\n  while (true) {\n    MyMessage message = queue.take();\n    System.out.println(\"Received: \" + message);\n  }\n}\n\n```\n\n一旦 Message 被打印到 Log 中，MyMessage 实例应该被回收，然而还是发生了内存泄漏：\n\n`\n* GC ROOT thread com.example.MyActivity$2.<Java Local> (named 'Thread-110')\n* leaks com.example.MyActivity$MyMessage instance\n`\n\n我们发送新的 Message 到阻塞队列的瞬间，前一个 Message 就被回收，而新的 Message 就泄漏了。\n\n在 VM 中，每一个栈帧都是本地变量的集合，而垃圾回收器是保守的：只要存在一个存活的引用，就不会回收它。\n\n在循环结束后，本地变量不再可访问，然而本地变量仍持有对 Message 的引用，interpreter/JIT 理论上应该在本地变量不可访问时将其引用置为 null，然而它们并没有这样做，引用仍然存活，而且不会被置为 null，使得它不会被回收。\n\n为了验证我们的结论，我们手动将引用设为 null，并打印它，使得 null 不会是最优化办法：\n\n```java\nstatic void loop(BlockingQueue<MyMessage> queue) throws InterruptedException {\n  while (true) {\n    MyMessage message = queue.take();\n    System.out.println(\"Received: \" + message);\n    message = null;\n    System.out.println(\"Now null: \" + message);\n  }\n}\n```\n\n在测试上面的代码时，我们发现 MyMessage 实例在 Message 被设为 null 时立刻被回收。也就是说我们的结论似乎是正确的。\n\n因为这样的内存泄漏会在各种各样的线程和阻塞队列的实现中发生，我们现在确定这是一个存在于 VM 中的 Bug。基于这个结论，我们只能在 Dalvik VM 中复现这个 Bug，在 ART VM 或 JVM 中则无法复现。\n\n##Message in a (recycled) bottle\n\n我们发现了一个会导致内存泄漏的 Bug，但这会导致严重的内存泄漏吗？不妨看看我们最初的泄漏信息：\n\n`\n* GC ROOT thread com.squareup.picasso.Dispatcher.DispatcherThread.<Java Local>\n* references android.os.Message.obj\n* references com.example.MyActivity$MyDialogClickListener.this$0\n* leaks com.example.MyActivity.MainActivity instance\n`\n\n我们发送给 Picasso Dispatcher 线程的 Message，我们从未将 Message.obj 设为 DialogInterface.OnClickListener，那它是怎么结束的？\n\n此外，当 Message 被处理，它应该立刻被回收，而且 Message.obj 应该被设为 null。只有那样 HandlerThread 才会等待下一个 Message，并暂时泄漏前一个 Message：\n\n```java\nfor (;;) {\n    Message msg = queue.next(); // might block\n    if (msg == null) {\n        return;\n    }\n    msg.target.dispatchMessage(msg);\n    msg.recycleUnchecked();\n}\n```\n\n因而我们知道泄漏的 Message 会被回收，因此不会持有之前的内容。\n\n一旦被回收，Message 就会回到常量池中：\n\n```java\nvoid recycleUnchecked() {\n    // Mark the message as in use while it remains in the recycled object pool.\n    // Clear out all other details.\n    flags = FLAG_IN_USE;\n    what = 0;\n    arg1 = 0;\n    arg2 = 0;\n    obj = null;\n    replyTo = null;\n    sendingUid = -1;\n    when = 0;\n    target = null;\n    callback = null;\n    data = null;\n\n    synchronized (sPoolSync) {\n        if (sPoolSize < MAX_POOL_SIZE) {\n            next = sPool;\n            sPool = this;\n            sPoolSize++;\n        }\n    }\n}\n```\n\n我们此时拥有一个泄漏的空 Message，它可能会被重用，并填充不同的内容。Message 常常以相同的方式被使用：在池中被调用，填充内容，放入 MessageQueue，然后被处理，最后被回收，并置回到池中。\n\n它理应在很长一段时间内不会持有它的内容，那我们为什么总会在 DialogInterface.OnClickListener 实例中发生内存泄漏呢？\n\n##Alert Dialogs\n\n我们先创建一个简单的 AlertDialog：\n\n```java\nnew AlertDialog.Builder(this)\n    .setPositiveButton(\"Baguette\", new DialogInterface.OnClickListener() {\n      @Override public void onClick(DialogInterface dialog, int which) {\n        MyActivity.this.makeBread();\n      }\n    })\n    .show();\n```\n\n注意到 ClickListener 持有 Activity 的引用，该匿名内部类实际完成的工作与下面的代码是一样的：\n\n```java\n// First anonymous class of MyActivity.\nclass MyActivity$0 implements DialogInterface.OnClickListener {\n  final MyActivity this$0;\n  MyActivity$0(MyActivity this$0) {\n    this.this$0 = this$0;\n  }\n  @Override public void onClick(DialogInterface dialog, int which) {\n    this$0.makeBread();\n  }\n}\n\nnew AlertDialog.Builder(this)\n    .setPositiveButton(\"Baguette\", new MyActivity$0(this));\n    .show();\nInternally, AlertDialog delegates the work to AlertController:\n\n/**\n * Sets a click listener or a message to be sent when the button is clicked.\n * You only need to pass one of {@code listener} or {@code msg}.\n */\npublic void setButton(int whichButton, CharSequence text,\n        DialogInterface.OnClickListener listener, Message msg) {\n    if (msg == null && listener != null) {\n        msg = mHandler.obtainMessage(whichButton, listener);\n    }\n    switch (whichButton) {\n        case DialogInterface.BUTTON_POSITIVE:\n            mButtonPositiveText = text;\n            mButtonPositiveMessage = msg;\n            break;\n        case DialogInterface.BUTTON_NEGATIVE:\n            mButtonNegativeText = text;\n            mButtonNegativeMessage = msg;\n            break;\n        case DialogInterface.BUTTON_NEUTRAL:\n            mButtonNeutralText = text;\n            mButtonNeutralMessage = msg;\n            break;\n    }\n}\n```\n\n所以 OnClickListener 被包裹到 Message 中，并被设置到 AlertController.mButtonPositiveMessage，现在我们看看该 Message 在什么时候被使用：\n\n```java\nprivate final View.OnClickListener mButtonHandler = new View.OnClickListener() {\n    @Override public void onClick(View v) {\n        final Message m;\n        if (v == mButtonPositive && mButtonPositiveMessage != null) {\n            m = Message.obtain(mButtonPositiveMessage);\n        } else if (v == mButtonNegative && mButtonNegativeMessage != null) {\n            m = Message.obtain(mButtonNegativeMessage);\n        } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {\n            m = Message.obtain(mButtonNeutralMessage);\n        } else {\n            m = null;\n        }\n        if (m != null) {\n            m.sendToTarget();\n        }\n        // Post a message so we dismiss after the above handlers are executed.\n        mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)\n                .sendToTarget();\n    }\n};\n```\n\n\n注意这：`m = Message.obtain(mButtonPositiveMessage).`\n\nMessage 被克隆，也就是说后面使用的是它的拷贝。这就以为着原始的 Message 从没有被发送，因此不会被回收，所以永久保存着它的内容，直到发生垃圾回收。\n\n现在假设 Message 已经由于 HandlerThread 本地引用在被回收池调用之前发生内存泄漏，Dialog 最终会被垃圾回收，并释放由 mButtonPositiveMessage 持有的 Message 的引用。\n\n然而，由于 Message 已经泄漏，它并不会被垃圾回收。同样的，它持有的内容也不会被回收，而 OnClickListener 持有对 Activity 的引用，导致 Activity 不会被回收。\n\n##Smoking Gun\n\n我们能证明这个结论么？\n\n我们需要发送一个 Message 到 HandlerThread 中，让他被处理和回收，并不要再发送任何 Message 到那个线程中，使最后一个 Message 发生内存泄漏。然后，我们需要显示一个带 Button 的 Dialog ，并希望它会从池中获取到相同的 Message。这确实很可能会发生，因为一旦被回收，Message 就会变成池内的第一个可调用的 Message。\n\n```java\nHandlerThread background = new HandlerThread(\"BackgroundThread\");\nbackground.start();\nHandler backgroundhandler = new Handler(background.getLooper());\nfinal DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {\n  @Override public void onClick(DialogInterface dialog, int which) {\n    MyActivity.this.makeCroissants();\n  }\n};\nbackgroundhandler.post(new Runnable() {\n  @Override public void run() {\n    runOnUiThread(new Runnable() {\n      @Override public void run() {\n        new AlertDialog.Builder(MyActivity.this) //\n            .setPositiveButton(\"Baguette\", clickListener) //\n            .show();\n      }\n    });\n  }\n});\n```\n\n如果我们运行上面的代码，然后旋转屏幕销毁当前 Activity，就很有可能会使该 Activity 发生内存泄漏。\n\nLeakCanary 准确地检测到了内存泄漏：\n\n`\n* GC ROOT thread android.os.HandlerThread.<Java Local> (named 'BackgroundThread')\n* references android.os.Message.obj\n* references com.example.MyActivity$1.this$0 (anonymous class implements android.content.DialogInterface$OnClickListener)\n* leaks com.example.MyActivity instance\n`\n\n现在我们成功地重现了这个 Bug，不妨看看该怎么修复它。\n\n##The Startup Fix\n\n只支持 ART VM的设备当然不会有这个 Bug，当然，也没多少人用……\n\n##The Won't Fix\n\n你可能会觉得这些内存泄漏没啥影响，而且你有其他更值得做的事情，或许更简单的内存泄漏需要修复。LeakCanary 默认无视了所有 Message 泄漏。但要注意的是，Activity 持有其整个 View 的资源，那可是有好几兆的。\n\n##The App fix\n\n确保 DialogInterface.OnClickListener 不会持有对 Activity 实例的强引用，例如在 Dialog 退出后清除对 Listener 的引用，下面是一个简化它的包裹类：\n\n```java\npublic final class DetachableClickListener implements DialogInterface.OnClickListener {\n\n  public static DetachableClickListener wrap(DialogInterface.OnClickListener delegate) {\n    return new DetachableClickListener(delegate);\n  }\n\n  private DialogInterface.OnClickListener delegateOrNull;\n\n  private DetachableClickListener(DialogInterface.OnClickListener delegate) {\n    this.delegateOrNull = delegate;\n  }\n\n  @Override public void onClick(DialogInterface dialog, int which) {\n    if (delegateOrNull != null) {\n      delegateOrNull.onClick(dialog, which);\n    }\n  }\n\n  public void clearOnDetach(Dialog dialog) {\n    dialog.getWindow()\n        .getDecorView()\n        .getViewTreeObserver()\n        .addOnWindowAttachListener(new OnWindowAttachListener() {\n          @Override public void onWindowAttached() { }\n          @Override public void onWindowDetached() {\n            delegateOrNull = null;\n          }\n        });\n  }\n}\n```\n\n然后你可以包裹所有 OnClickListener 实例：\n\n```java\nDetachableClickListener clickListener = wrap(new DialogInterface.OnClickListener() {\n  @Override public void onClick(DialogInterface dialog, int which) {\n    MyActivity.this.makeCroissants();\n  }\n});\n\nAlertDialog dialog = new AlertDialog.Builder(this) //\n    .setPositiveButton(\"Baguette\", clickListener) //\n    .create();\nclickListener.clearOnDetach(dialog);\ndialog.show();\n```\n\n##The Plumber's Fix\n\n在一个常用的基础清晰你的工作者线程：当 Handler 闲置就向它发送空 Message，以确保不会发生 Message 的内存泄漏。\n\n```java\nstatic void flushStackLocalLeaks(Looper looper) {\n  final Handler handler = new Handler(looper);\n  handler.post(new Runnable() {\n    @Override public void run() {\n      Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {\n        @Override public boolean queueIdle() {\n          handler.sendMessageDelayed(handler.obtainMessage(), 1000);\n          return true;\n        }\n      });\n    }\n  });\n}\n```\n\n在使用库时这个办法很好似用，因为你不能控制开发者在 Dialog 中写的代码。我们在 Picasso 中就用这个办法解决了这个 Bug。\n\n##Conclusion\n\nMany thanks to [Josh Humphries, Jesse Wilson, Manik Surtani](https://twitter.com/jhumphries_sq), and [Wouter Coekaerts](https://twitter.com/WouterCoekaerts) for their help in our internal email thread.\n\n正如我们所见，一个小小的，难以发现的 VM 行为会导致内存泄漏，而这又会导致大量内存被泄漏，最终使 App 崩溃。\n\n感谢 [Josh Humphries, Jesse Wilson, Manik Surtani](https://twitter.com/jhumphries_sq), 和 [Wouter Coekaerts](https://twitter.com/WouterCoekaerts) 在邮件沟通中帮助我解决了这个 Bug。\n"
  },
  {
    "path": "issue-25/在Android Lollipop中使用Palette抽取Bitmap颜色.md",
    "content": "#在Android Lollipop中使用Palette抽取Bitmap颜色\n\n> * 原文链接 : [Extracting Colors to a Palette with Android Lollipop](https://www.bignerdranch.com/blog/extracting-colors-to-a-palette-with-android-lollipop/)\n* 原文作者 : [Brian Gardner](https://www.bignerdranch.com/about-us/nerds/brian-gardner/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Desmond1121](https://github.com/desmond1121)\n* 校对者: [chaossss](https://github.com/chaossss)\n\n一些Support库随着Android Lollipop的发布而诞生了，其中就有Palette。这个库可以让你很轻松地从一幅图中抽取特征颜色，这在你希望界面的颜色风格适应指定图片时非常有用，它还会提供与指定颜色相搭配的字体颜色。\n\nPalette有一个用法我特别喜欢：它可以定制图像波纹的颜色。这是其实一个微不足道的效果，但是我认为相比于纯灰色波纹来说是一个很大的提升。你需要在工程下的`build.gradle`里添加依赖才可以使用Palette，像如下代码所示：\n\n    dependencies {\n      compile 'com.android.support:palette-v7:21.0.0'\n    }\n\n生成一幅图像的Palette有两种方法：\n\n- `generate(Bitmap)` 生成含有16种颜色种类的Palette；\n- `generate(Bitmap, int)` 生成含有指定数量颜色种类的Palette，数量越多需要的时间越久。\n\n这两个方法是同步方法，由于他们很可能会比较耗时（在分析大图片或者所需颜色较多时），所以它们不应该在主线程中执行。你应该先在别的线程中使用这两个函数进行解析，解析成功之后再使用。\n\n不过有时候你不会在加载图片的线程（非主线程）中使用解析出的颜色，所以Palette提供了异步方法，他们与之前的函数的区别就是需要传入`PaletteAsyncListener`，提供在图片解析完成后的回调函数：\n\n- `generateAsync(Bitmap, PaletteAsyncListener)`\n- `generateAsync(Bitmap, int, PaletteAsyncListener)`\n\n`PaletteAsyncListener`的实现是非常简单的（参考下面这几行代码），它只要重写`onGenerated`就好了。如此一来你就可以在任何需要的时候使用这两个函数创建Palette。\n\n    Palette.PaletteAsyncListener listener = new Palette.PaletteAsyncListener() {\n      public void onGenerated(Palette palette) {\n        // 使用Palette对象，获取解析出的颜色\n      }\n    }\n\nPalette默认会解析出图像的16种特征颜色种类，但是这六种颜色是你最经常用到的：\n\n- vibrant(鲜艳色)\n- dark vibrant(鲜艳色中的暗色)\n- light vibrant(鲜艳色中的亮色)\n- muted(柔和色)\n- dark muted(柔和色中的暗色)\n- light muted(柔和色中的亮色)\n\n这是一个Palette解析六个主颜色种类的例子：\n\n![Six Color](http://img.blog.csdn.net/20150827183303088)\n\n你获取Palette对象之后，可以通过以下这些内置getter函数直接获取这六个颜色。你需要传入默认颜色防止Palette无法解析到指定颜色种类，返回的类型是24位RGB颜色数值。\n\n    Palette palette = Palette.generate(myBitmap);\n    int vibrant = palette.getVibrantColor(0x000000);\n    int vibrantLight = palette.getLightVibrantColor(0x000000);\n    int vibrantDark = palette.getDarkVibrantColor(0x000000);\n    int muted = palette.getMutedColor(0x000000);\n    int mutedLight = palette.getLightMutedColor(0x000000);\n    int mutedDark = palette.getDarkMutedColor(0x000000);\n\n###Swatch\n\nPalette解析出的颜色都来自于对应的`Swatch`，在`Swatch`里面含有很多关于对应颜色的有用信息。你可以从`Swatch`中获取RGB颜色值、HSL颜色向量、对应颜色在图像中所占的比例、与对应颜色搭配的标题字体颜色和正文字体颜色（这两个颜色和对应颜色的对比值是处理好的，你不必再去操心）。如下面这段代码所示：\n\n    Palette palette  = Palette.generate(myBitmap);\n    Palette.Swatch swatch = palette.getVibrantSwatch();\n    //rgb颜色值\n    int rgbColor = swatch.getRgb();\n    //hsl颜色向量\n    float[] hslValues = swatch.getHsl();\n    //该颜色在图像中所占的像素数\n    int pixelCount = swatch.getPopulation();\n    //对应的标题字体颜色\n    int titleTextColor = swatch.getTitleTextColor();\n    //对应的正文字体颜色\n    int bodyTextColor = swatch.getBodyTextColor();\n\n获取六个主颜色的`Swatch`要使用与之前直接获取颜色不同的getter函数，如下面这部分代码所示。它们不需要提供默认值，如果Palette没有解析到对应`Swatch`，它会直接返回null。\n\n    Palette palette = Palette.generate(myBitmap);\n    Palette.Swatch vibrantSwatch = palette.getVibrantSwatch();\n    Palette.Swatch vibrantLightSwatch = palette.getLightVibrantSwatch();\n    Palette.Swatch vibrantDarkSwatch = palette.getDarkVibrantSwatch();\n    Palette.Swatch mutedSwatch = palette.getMutedSwatch();\n    Palette.Swatch mutedLightSwatch = palette.getLightMutedSwatch();\n    Palette.Swatch mutedDarkSwatch = palette.getDarkMutedSwatch();\n\nPalette只为六种主颜色种类`Swatch`提供了getter，如果你要使用其他颜色种类的`Swatch`（一共有16种颜色种类），你需要手动获取它。调用`getSwatchs()`会返回一个列表，里面有所有获取到的`Swatch`。\n\n这里是一个Palette获取所有`Swatch`的例子，里面展示了它们分别在图像中所占的比例：\n\n![all color](http://img.blog.csdn.net/20150827183359416)\n\nPalette的异步方法使得它非常容易去使用，而且我也看到了它用在很多地方。它真是一个非常棒的工具，能够收集一幅图中所有的颜色，并将它们总结到几个不同种类的颜色中。我建议你阅读[源码](https://android.googlesource.com/platform/frameworks/support/+/master/v7/palette/src/android/support/v7/graphics)来更多地学习它！\n\n\n\n"
  },
  {
    "path": "issue-26/30分钟搭建一个android的私有Maven仓库.md",
    "content": "30分钟搭建一个android的私有Maven仓库\n---\n\n> * 原文链接 : [A PRIVATE MAVEN REPOSITORY FOR ANDROID IN 30 MIN](原文url)\n* 原文作者 : [JEROEN MOLS](http://jeroenmols.github.io/blog/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [MiJack](https://github.com/mijack)\n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  已完成\n\n![](http://jeroenmols.github.io/img/blog/artifactory.png)\n\n\n建立你自己的Maven库和上传artifacts，这是一个相当艰巨的任务。最近,我在这方面获得了一定的经验，希望和大家分享：通过[Artifactory](http://www.jfrog.com/open-source/)建立Maven库并使用Gradle自动上传artifact。\n\n\n在不到30分钟，你将有一个可以运作的私有Maven库,通过配置gradle的buildscripts自动上传artifact。\n\n请注意，这里提出的有关内容包括但不仅局限于安卓，他实际的使用范围更广。\n\n**设置一个库管理器**\n\n首先，我们需要确保我们有一个Maven库可以用于上传我们的artifact。根据[Maven](https://maven.apache.org/repository-management.html),你应该使用一个库管理工具：\n\n\n> 最佳实践-使用库管理器\n> 库管理器是一个专门的服务器应用程序，用于管理二进制组件的库。The usage of a repository manager is considered an essential best practice for any significant usage of Maven.\n\n\n**为什么是ARTIFACTORY?**\n\n虽然有一些其他的选项可供选择，我个人选择[artifactory](http://www.jfrog.com/open-source/)因为：\n\n* 清晰且有吸引力的用户界面\n* 超快速配置\n* Gradle插件\n* 用户访问控制\n* 自由和开放来源\n\n更多信息请查看[alternatives](https://maven.apache.org/repository-management.html), checkout[feature comparison matrix](http://www.jfrog.com/blog/artifactory-vs-nexus-integration-matrix/)或者回顾 [Artifactory的特性](https://www.jfrog.com/confluence/display/RTF/Artifactory+Comparison+Matrix).\n\n**确定你安装了JAVA SDK 8**\n\n\n在你开始之前，请确定你现在已经安装了Java 8，否则Artifactory将无法运行。你可以通过`java -version`这个命令获取Java的版本:\n\n```\n$ java -version\njava version \"1.8.0_51\"\nJava(TM) SE Runtime Environment (build 1.8.0_51-b16)\nJava HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)\n```\n\n如果输出版本小于1.8.X，你应该[下载](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)和安装一个新的Java SDK。\n请注意，如果你没有Java 8的错误，看起来有点诡异：\n\n```\nAug 05, 2015 9:29:31 AM org.apache.catalina.core.StandardContext startInternal\nSEVERE: One or more listeners failed to start. Full details will be found in the appropriate container log file\n```\n\n**安装 artifactory**\n\n\n这一步，简单的难以置信。只需下载最新版本的[artifactory](http://www.jfrog.com/open-source/)，解压文件，然后运行与你的平台对应的`artifactory`可执行文件即可。\n\n[youtube](http://img.youtube.com/vi/aa4YBDUDWy0/0.jpg)\n\n\n通过访问`http://localhost:8081/artifactory/`，你就可以知道是否安装正确。在该页面，你可以体验到artifactory的一些特性。现在，不要担心所有的设置，我们稍后将配置我们所需要的设置。\n\n**配置Gradle自动上传Android artifact**\n\n通过配置一个新的Gradle任务，我们可以上传一个简单的文件。\n\n\n在最上层的`build.gradle`文件（这里指文件路径）添加一个关于Artifactory Gradle 插件的引用：\n\n```\nbuildscript {\n    dependencies {\n        classpath \"org.jfrog.buildinfo:build-info-extractor-gradle:3.1.1\"\n    }\n}\n```\n\n接下来，在你的库中，我们将需要两个新的插件：一个准备Maven artifact ` maven-publish`，另一个上传archives到artifactory `com.jfrog.artifactory`：\n\n```\napply plugin: 'com.jfrog.artifactory'\napply plugin: 'maven-publish'\n```\n\n每一个Maven artifact都由以下三个参数确定：\n* artifactId：库的名称\n* groupId：通常库的包名\n* version：区别同一artifact的不同版本\n\n一般的，我们将在build.gradle文件定义最后两个变量。\n```\ndef packageName = 'com.jeroenmols.awesomelibrary'\ndef libraryVersion = '1.0.0'\n```\n\n`artifactId`需要和` assemblerelease `任务输出的文件名相匹配。因此我们要[重命名库模块](https://stackoverflow.com/questions/26936812/renaming-modules-in-android-studio)或[指定输出文件名](https://stackoverflow.com/questions/24728591/how-to-set-name-of-aar-output-from-gradle)。我个人比较喜欢第一种方法，可以通过下面这种方式得到` artifactId `：\n\n```\nproject.getName() // the ArtifactId\n```\n\n现在我们需要配置`maven-publish`，这样就知道哪一个artifactory将发布到Artifactory。我们的目的，我们是引用``***-release.aar`文件，他是由`assemblerelease`任务生成。请注意，我们可以通过更改库项目名称来预测这个名称：\n\n```\npublishing {\n    publications {\n        aar(MavenPublication) {\n            groupId packageName\n            version = libraryVersion\n            artifactId project.getName()\n\n            // Tell maven to prepare the generated \"* .aar\" file for publishing\n            artifact(\"$buildDir/outputs/aar/${project.getName()}-release.aar\")\n      }\n    }\n}\n\n```\n\n最后，我们需要配置`com.jfrog.artifactory` 插件,来指定artifact发布到的库。为简单起见，我们将上传一个artifact到本地运行的Artifactory实例(`http://localhost:8081/artifactory`),默认放在`libs-release-local`库中。请注意，在本例中，用户名`admin`，密码`password`是硬编码的形式。我们希望以后有一个更好的解决方案。\n\n```\nartifactory {\n    contextUrl = 'http://localhost:8081/artifactory'\n    publish {\n        repository {\n            // The Artifactory repository key to publish to\n            repoKey = 'libs-release-local'\n\n            username = \"admin\"\n            password = \"password\"\n        }\n        defaults {\n            // Tell the Artifactory Plugin which artifacts should be published to Artifactory.\n            publications('aar')\n            publishArtifacts = true\n\n            // Properties to be attached to the published artifacts.\n            properties = ['qa.level': 'basic', 'dev.team': 'core']\n            // Publish generated POM files to Artifactory (true by default)\n            publishPom = true\n        }\n    }\n}\n```\n\n**部署 ARTIFACTS**\n\n现在，我们通过配置Gradle的buildscripts，运行以下命令轻松部署artifactory：\n```\ngradle assembleRelease artifactoryPublish\n```\n\n注意，我们在调用调用的实际` artifactoryPublish `任务前，会先调用` assembleRelease `，这取决于我们在上一节定义的方式。\n\n你可以通过管理员凭据登陆`localhost:8081 `，知道有没有上传成功。\n\n![](http://jeroenmols.github.io/img/blog/artifactory_screenshot.png)\n\n**使用ARTIFACTS**\n\n\n为了保证另一个项目也可以引用这个artifact，我们需要在根目录下的`build.gradle`文件中，把我们的仓库信息添加到仓库列表中。\n```\nallprojects {\n    repositories {\n        maven { url \"http://localhost:8081/artifactory/libs-release-local\" }\n    }\n}\n```\n\n然后，我们只需要在主工程的`build.gradle`文件中添加artifact作为依赖就可以了：\n\n```\ndependencies {\n    compile 'com.jeroenmols.awesomelibrary:1.0.0'\n}\n```\n**WRAP-UP**\n**总结**\n\n\n恭喜你！你完成了利用gradle生成和上传你的artifact 带Maven仓库。\n\n\n在 [下一篇博客](http://jeroenmols.github.io/blog/2015/08/13/artifactory2/) ，我将继续学习一下几点:\n\n* 库项目的依赖\n* 配置你自己的仓库\n* 使用权限管理\n\n\n记得更改`build.gradle` 里的账号、密码\n\n\n我已经上传了一份 [例子](https://github.com/JeroenMols/ArtifactoryExample) 到github供你参考。\n"
  },
  {
    "path": "issue-26/Android架构演化之路.md",
    "content": "Android架构演化之路\n---\n\n> * 原文链接 : [Architecting Android…The evolution](http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/)\n* 原文作者 : [Tuenti](https://twitter.com/tuenti)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](www.devtf.cn)\n* 译者 : [dustookk](https://github.com/dustookk)\n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 : 完成\n\n大家好!  过了一好阵子了(在此期间我收到了大量的读者反馈) 我决定是时候回到手机程序架构这个话题上了(这里用android代码举例),  ***给大家另一个我认为好的解决方案.***\n\n在开始之前, 我这里假设大家都读过了我之前[用简洁的办法架构Android程序](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)一文. 如果你还没有读过, 现在应该去读一下那篇文章, 读过之后可以更好的理解我下面要讲的内容.\n\n\n![上一篇文章截图](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture1.png)\n\n## 架构的演化\n\n***演化是指一个事物变化成为另一个不同的事物的一个平缓过程, 通常情况下会变得更加复杂或者变成更好.***\n\n软件开发一直在进化和改变.  ***实际上, 一个好的代码结构必须帮助我们成长, 这意味着不用重新写所有代码就可以扩展功能.*** (尽管有些情况下应该大量的重写代码, 但那又是另一回事了, 这里先不做探讨).\n\n这篇文章的重点是***如何保持android代码的清晰直观***, 为了阐述这一问题, 我将会带着大家看几个我认为重要的关键点. 记住下面这个图我们就可以开始了.\n\n![Presentation,Domain,Data三层架构示意图](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_android.png)\n\n### 反应式方法: RxJava\n\n在这里我就不讲RxJava的好处了([我猜大家都已经自己体会过了](https://github.com/ReactiveX/RxJava/wiki)) , 因为已经有很多[文章](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)和[坏蛋们](https://speakerdeck.com/benjchristensen)都讲过了, 而且讲的还都不错. ***这里我要讲的是它是怎么使得android开发变得非常有趣的, 还有它是如何帮助我完成搭建第一个干净简洁的架构的.***\n\n首先, 我选择一个反应式模式让用例(在简洁的架构命名规范中叫做interactor) 都返回Observables<T>, 这样的话所有子类都必须遵守这个规则也返回Observables<T>\n\n```java\n        public abstract class UseCase {\n\n        private final ThreadExecutor threadExecutor;\n        private final PostExecutionThread postExecutionThread;\n\n        private Subscription subscription = Subscriptions.empty();\n\n        protected UseCase(ThreadExecutor threadExecutor,\n            PostExecutionThread postExecutionThread) {\n            this.threadExecutor = threadExecutor;\n            this.postExecutionThread = postExecutionThread;\n        }\n\n        protected abstract Observable buildUseCaseObservable();\n\n        public void execute(Subscriber UseCaseSubscriber) {\n            this.subscription = this.buildUseCaseObservable()\n            .subscribeOn(Schedulers.from(threadExecutor))\n            .observeOn(postExecutionThread.getScheduler())\n            .subscribe(UseCaseSubscriber);\n        }\n\n        public void unsubscribe() {\n            if (!subscription.isUnsubscribed()) {\n                subscription.unsubscribe();\n                }\n            }\n        }\n```\n\n可以看出,所有的子用例都继承自这个抽象类,并在***buildUseCaseObservable()***这个抽象方法中构造一个可以完成耗时操作并返回需要数据的***Observable<T>***\n\n***需要注意的是execute()这个方法, 我们保证了Observable<T>让自己执行在一个单独的线程中***, 这样的话就可以最小限度的减少在主线程耗时.  然后通过主线程的scheduler机制把Observable<T>的执行结果返回给主线程.\n\n***到现在为止, 我们已经有了Observable<T> , 但是它产生的数据得有人来处理*** . 所以我这里将presenter(***MVP*** 三层架构中的presentation层的一部分)改为了 ***Subscribers*** ,当用例产生数据后可以及时更新UI.\n\n也就是下面这样的subscriber:\n\n```java\n        private final class UserListSubscriber extends\n            DefaultSubscriber<List<User>> {\n\n          @Override public void onCompleted() {\n              UserListPresenter.this.hideViewLoading();\n          }\n\n          @Override public void onError(Throwable e) {\n                UserListPresenter.this.hideViewLoading();\n                UserListPresenter.this.showErrorMessage(new DefaultErrorBundle((Exception) e));\n                UserListPresenter.this.showViewRetry();\n          }\n\n          @Override public void onNext(List<User> users) {\n                UserListPresenter.this.showUsersCollectionInView(users);\n            }\n        }\n```\n\nDefaultSubscriber<T>只是简单实现了对错误的处理, 每一个subscriber都是presenter中的一个继承自 ***DefaultSubscriber<T>*** 的内部类.\n\n从下面这张图中, 你可以得到一个比较完整的思路.\n\n![使用RxJava的完整思路](http://fernandocejas.com/wp-content/uploads/2015/07/clean_architecture_evolution.png)\n\n我们来总结一下RxJava带给我们的好处:\n\n* ***实现了Observables 和 Subscribers的解耦***: 保持了结构稳定并简化了测试.\n\n* ***使得异步任务变得简单***: 多层异步任务被执行时, java的thread和future的操作和同步会变得非常复杂, 使用 scheduler 可以让我们在异步线程和主线程之间跳转变得非常简单(省去了多余的步骤), 特别是我们需要更新UI界面的时候. 同时也避免了使代码变成非常难以理解的\"回调地狱\".\n\n* ***数据的传递/组合***: 我们可以使多个Observables<T>组合起来而不影响到client端, 这样提高了整套解决方案的可扩展性.\n\n* ***异常处理***: 任何一个Observable<T>.出现异常都会通知到consumer.\n\n从我的角度看这里有一个小问题, 也是必须要付出的代价, 就是***对这一概念不太熟悉的开发者的学习过程***. 但是你会从中学到非常有价值的内容.  ***为了成功学习Reactive吧!***\n\n### 依赖注入: Dagger 2\n\n我这里不会讲太多关于依赖注入的例子, 因为我之前写过[一篇专门说依赖注入的文章](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/), 为了跟上我这里的脚步, 强烈推荐大家读一读这篇文章.\n\n值得强调的是, 像Dagger 2一样的依赖注入框架可以带给我们:\n\n* ***组件的重用***, 因为依赖可以被从外部配置和注入.\n\n* 最为合作者对抽象进行依赖注入时, 我们可以单单修改任何对象的实现, 而不用大量修改底层代码, 因为***类的实现对象独立而解耦的存在于另一个地方***.\n\n* 依赖可以被注入到组件中去: ***注入依赖的测试实现是有可能的, 这就使得测试变得更加容易.***\n\n### Lambda 表达式: Retrolambda\n\n***没有人会反对在我们的代码中使用Java 8 的 Lambdas***, 使用Lambdas可以省去大量的样板代码, 就像下面的代码块:\n\n```java\n        private final Action1<UserEntity> saveToCacheAction =\n            userEntity -> {\n                 if (userEntity != null) {\n                     CloudUserDataStore.this.userCache.put(userEntity);\n                 }\n           };\n```\n\n但是这个问题我非常纠结. 在我们[@SoundCloud](https://developers.soundcloud.com/blog/), 曾经有过一次关于\n [Retrolambda](https://github.com/orfjackal/retrolambda)的讨论, ***主要的分歧是要不要用它*** 讨论的结果是:\n\n 1. 利:\n     * Lambda 和方法的引用\n     * 尝试着用资源的方式.\n     * Dev karma\n\n 2. 弊:\n     * java 8新特性的意外使用.\n     * 第三方jar包很扰人.\n     * 要在Android工程中使用它,必须引入第三方gradle插件.\n\n最终我们决定Retrolambda并不是一个能解决我们任何问题的库: ***使用了Retrolambda后代码的确好看易理解, 但这对我们来说并不是必须的, 因为现在大部分的IDE已经可以是实现这一功能, 至少是以可以接受的方式***\n\n老实说, 我在这里提到Retrolambda的主要原因是想用一用它, 体验一把在Android代码里使用lambda是什么感觉. 也许***在我的业余项目中可能会用到这个库***. 我只是把我的想法放在这里, 用不用它的最终决定权在大家手里. ***当然了, 该[作者](https://github.com/orfjackal)创造了这么伟大的一个库也非常值得赞扬***\n\n### 测试途径\n\n说到测试, 和之前的例子并没有什么太大的不同.\n\n* ***Presentation层*** : 用Espresso 2 和 Android Instrumentation 测试UI界面.\n\n* ***Domain 层:*** 因为只是正常的java模块,所以用JUnit + Mockito测试就好了.\n\n* ***Data层***: 用 Robolectric 3 + JUnit + Mockito做迁移测试. 因为以前(案例第一个版本的时候)***没有内置单元测试支持,手动构造一个类似robolectric的框架非常复杂而且为了使它正常工作,还要一系列hack操作.***\n\n庆幸的是那都过去了, 现在所有的东西都可以直接用, 所以我重新把它们放在了数据模块中, 特别是可以放在默认的测试文件夹 ***src/test/java*** 下.\n\n### 包结构组织\n\n我认为代码/包的组织是一个良好架构的关键要素之一: ***包结构是一个程序员看项目代码时最先注意到的*** 其他的一切要素都由它而来,也都取决于它.\n\n下面是组织包结构常见的两种方式:\n\n* ***根据层级关系的不同***: 单独看每个包下面的代码通常情况下并没有什么联系, 这就降低了单个包里的内聚性和模块性,而提高了包与包之间的耦合程度. 修改一个功能需要同时修改多个包下的多个文件.而且, 要删除一个功能也变得不是那么简单.\n\n* ***根据功能的不同***: 根据不同的包名可以找到对应的功能, 将功能(而且是只有此功能)下的所有组件全都放在了一起. 这就提高了包里的内举性和模块性,而降低了包与包之间的耦合程度. 将协同工作的代码放在了一起,而不是将它们分布在程序的各个地方.\n\n***我的建议是根据功能的不同来组织包结构***, 可以带来下面这些好处:\n\n* ***更完善的模块化***\n\n* ***代码更加容易查阅***\n\n* ***最小化代码的作用域***\n\n有趣的是, 如果你在一个所谓的 ***功能性团队*** 工作,(比如[@SoundCloud](https://twitter.com/soundcloud)), ***代码结构的分配会变得更加容易更加模块化***, 如果许多工程师在同样的基础代码上开发时这个优点就变得格外明显.\n\n\n![包结构组织样图](http://fernandocejas.com/wp-content/uploads/2015/07/package_organization-795x1024.png)\n\n大家可以看出, 我的包结构看起来像是根据层级关系组织的: ***这里可能有举例不太恰当的地方(比如将一切都放在'user'下面)*** 但是我会***原谅自己这一次***, 因为举这个例子是为了供大家学习,为了表达我的观点,主要目的是包含简洁的架构思路. ***照我说的做, 而不是照我做的做*** :)\n\n### 附加彩蛋: 组织你的打包逻辑\n\n***我们都知道房子都是从地基开始修筑的***. 软件开发也是一个道理, 我这里要强调的是,  ***代码架构中, 打包系统(及其组织架构)是非常重要的一部分***\n\n在Android开发中, 我们使用一个叫做gradle的非常强大的打包系统.  这里有 ***一系列窍门*** 帮助大家在组织打包脚本时变得格外轻松:\n\n* 根据功能的不同, 将打包系统分成多个脚本文件.\n\n![脚本结构截图](http://fernandocejas.com/wp-content/uploads/2015/07/gradle_organization-283x300.png)\n\nci.gradle:\n\n```\n        def ciServer = 'TRAVIS'\n        def executingOnCI = \"true\".equals(System.getenv(ciServer))\n\n        // Since for CI we always do full clean builds, we don't want to pre-dex\n        // See http://tools.android.com/tech-docs/new-build-system/tips\n        subprojects {\n          project.plugins.whenPluginAdded { plugin ->\n            if ('com.android.build.gradle.AppPlugin'.equals(plugin.class.name) ||\n                'com.android.build.gradle.LibraryPlugin'.equals(plugin.class.name)) {\n              project.android.dexOptions.preDexLibraries = !executingOnCI\n            }\n          }\n        }\n```\n\nbuild.gradle:\n\n```\n        apply from: 'buildsystem/ci.gradle'\n        apply from: 'buildsystem/dependencies.gradle'\n\n        buildscript {\n          repositories {\n            jcenter()\n          }\n          dependencies {\n            classpath 'com.android.tools.build:gradle:1.2.3'\n            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'\n          }\n        }\n\n        allprojects {\n          ext {\n        \t...\n          }\n        }\n        ...\n```\n\n如上图, 你可以利用 \"***apply from: 'buildsystem/ci.gradle'***\" 将配置文件导入任意build脚本中. ***不要将所有打包脚本写在一个build.gradle文件中, 否则你会慢慢制造出一个怪物. 我已经受过教训了***.\n\n* 将依赖整合到map中\n\ndependencies.gradle:\n\n```\n        ...\n\n        ext {\n          //Libraries\n          daggerVersion = '2.0'\n          butterKnifeVersion = '7.0.1'\n          recyclerViewVersion = '21.0.3'\n          rxJavaVersion = '1.0.12'\n\n          //Testing\n          robolectricVersion = '3.0'\n          jUnitVersion = '4.12'\n          assertJVersion = '1.7.1'\n          mockitoVersion = '1.9.5'\n          dexmakerVersion = '1.0'\n          espressoVersion = '2.0'\n          testingSupportLibVersion = '0.1'\n\n          ...\n\n          domainDependencies = [\n              daggerCompiler:     \"com.google.dagger:dagger-compiler:${daggerVersion}\",\n              dagger:             \"com.google.dagger:dagger:${daggerVersion}\",\n              javaxAnnotation:    \"org.glassfish:javax.annotation:${javaxAnnotationVersion}\",\n              rxJava:             \"io.reactivex:rxjava:${rxJavaVersion}\",\n          ]\n\n          domainTestDependencies = [\n              junit:              \"junit:junit:${jUnitVersion}\",\n              mockito:            \"org.mockito:mockito-core:${mockitoVersion}\",\n          ]\n\n          ...\n\n          dataTestDependencies = [\n              junit:              \"junit:junit:${jUnitVersion}\",\n              assertj:            \"org.assertj:assertj-core:${assertJVersion}\",\n              mockito:            \"org.mockito:mockito-core:${mockitoVersion}\",\n              robolectric:        \"org.robolectric:robolectric:${robolectricVersion}\",\n          ]\n        }\n```\n\nbuild.gradle:\n\n```\n        apply plugin: 'java'\n\n        sourceCompatibility = 1.7\n        targetCompatibility = 1.7\n\n        ...\n\n        dependencies {\n          def domainDependencies = rootProject.ext.domainDependencies\n          def domainTestDependencies = rootProject.ext.domainTestDependencies\n\n          provided domainDependencies.daggerCompiler\n          provided domainDependencies.javaxAnnotation\n\n          compile domainDependencies.dagger\n          compile domainDependencies.rxJava\n\n          testCompile domainTestDependencies.junit\n          testCompile domainTestDependencies.mockito\n        }\n```\n\n***如果你希望在不同的模块中重复利用相同的依赖的版本,上面的建议就会变得非常有用***  或者你想将不同的依赖版本放到不同的模块中去也是一样的. 另一个好处是 ***可以在一个地方控制所有的依赖***\n\n### 总结\n\n这差不多就是我要说的了, 大家要记住 ***并没有包治百病的药, 但是一个好的程序架构可以帮助我们保持代码的整洁和健康, 同时也保证整了灵活性和可维护性***\n\n下面是一些我想指出的当你遇到程序问题时应有的态度:\n\n* ***遵守 SOLID 原则***\n\n* ***不要想的太多(不要过度开发)***\n\n* ***要实际***\n\n* ***最大限度的在工程中减少对 android 框架的依赖***\n\n### 源代码\n\n1. [Clean architecture github repository – master branch](https://github.com/android10/Android-CleanArchitecture)\n\n2. [Clean architecture github repository – releases](https://github.com/android10/Android-CleanArchitecture/releases)\n\n### 更多阅读:\n\n1. [Architecting Android..the clean way](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)\n2. [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/)\n3. [The Mayans Lost Guide to RxJava on Android](https://speakerdeck.com/android10/the-mayans-lost-guide-to-rxjava-on-android)\n4. [It is about philosophy: Culture of a good programmer](https://speakerdeck.com/android10/it-is-about-philosophy-culture-of-a-good-programmer)\n\n### 引用\n\n1. [RxJava wiki by Netflix](https://github.com/ReactiveX/RxJava/wiki)\n2. [Framework bound by Uncle Bob](https://blog.8thlight.com/uncle-bob/2014/05/11/FrameworkBound.html)\n3. [Gradle user guide](https://docs.gradle.org/current/userguide/userguide.html)\n4. [Package by feature, not layer](http://www.javapractices.com/topic/TopicAction.do?Id=205)\n"
  },
  {
    "path": "issue-26/Tinting drawables.md",
    "content": "\n#Tinting drawables\n\n---\n\n> * 原文链接 : [Tinting drawables](http://andraskindler.com/blog/2015/tinting_drawables/)\n* 原文作者 : [Andras Kindler](http://andraskindler.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [objectlife](https://github.com/objectlife) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 : 完成\n\n\n\n这篇文章主要介绍如何根据当前主题来给drawables和bitmaps配色。\n当设计[Ready](https://play.google.com/store/apps/details?id=com.ready.android)这款应用主题模块的时候,我们想通过一种方式不仅仅是改变基础色还要改变图标和其它drawables。常规的方法就是对每种颜色提供一套图片，在切换主题的时候切换图标-需要写大量的代码并且会增加apk的体积。我们也想在以后添加颜色的时候简单方便，不至于总创建新的资源文件。\n\n\n\n##DrawableCompat\n\n\n在谷歌发布的v4库中介绍了[DrawableCompat](https://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html)类，展示了针对Lollipop版本以前的绘图能力。这是一套非常成熟的API.甚至还支持绘制图像list,和绘制RTL布局镜像,但是对于我们的场景来说它太重了，并且你还得使用`wrap()`方法wrap当前Drawable。\n\n\n##TintedBitmapDrawable介绍\n\n\n所以我们通过我们自己的实现，实现了一个轻量级的**TintedBitmapDrawable**,它是[BitmapDrawable](http://developer.android.com/reference/android/graphics/drawable/BitmapDrawable.html)的子类，我们重写了`draw()`方法部分，在我们所关心的绘制部使用了[LightingColorFilter](http://developer.android.com/reference/android/graphics/LightingColorFilter.html)，它只有三个方法，所以在方法的数量不会让人太过注意。\n要绘制的颜色可以通过两个构造函数指明或者使用`setTint()`方法。\n\n\n```java\n\npublic final class TintedBitmapDrawable extends BitmapDrawable {\n  private int tint;\n  private int alpha;\n\n  public TintedBitmapDrawable(final Resources res, final Bitmap bitmap, final int tint) {\n    super(res, bitmap);\n    this.tint = tint;\n    this.alpha = Color.alpha(tint);\n  }\n\n  public TintedBitmapDrawable(final Resources res, final int resId, final int tint) {\n    super(res, BitmapFactory.decodeResource(res, resId));\n    this.tint = tint;\n    this.alpha = Color.alpha(tint);\n  }\n\n  public void setTint(final int tint) {\n    this.tint = tint;\n    this.alpha = Color.alpha(tint);\n  }\n\n  @Override public void draw(final Canvas canvas) {\n    final Paint paint = getPaint();\n    if (paint.getColorFilter() == null) {\n      paint.setColorFilter(new LightingColorFilter(tint, 0));\n      paint.setAlpha(alpha);\n    }\n    super.draw(canvas);\n  }\n}\n\n```\n\n像这样来使用:\n\n```java\n\ntintedDrawable = new TintedBitmapDrawable(resources, R.drawable.ic_arrow_back_white_24dp, Color.GREEN);\n\n```\n\n##Benefits and tips\n\n\n* 对于白色的图片和透明的颜色都有效。\n* 不需要根据主题提供相应图片，减小了APK的占用空间。\n* 完美适配Google’s[material icon pack](https://www.google.com/design/icons/)只需下载white .png版本就可以对其相应的处理。\n* 对于[Palette](https://developer.android.com/reference/android/support/v7/graphics/Palette.html)库也进了完美的适配\n* 如果使用item列表会对drawables进行缓存\n* 如果使用代码的形式代替menu.xml那么也适用于TooBar\n* 使用StateListDrawable对同一张图片进行不同颜色的状态改变，缩小了APK的体积。"
  },
  {
    "path": "issue-26/使用Systrace分析UI性能.md",
    "content": "#使用Systrace分析UI性能\n---\n\n> * 原文链接 : [Analyzing UI Performance with Systrace](http://developer.android.com/intl/zh-cn/tools/debugging/systrace.html)\n* 原文作者 : [Android Developers](http://developer.android.com//)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [desmond1121](https://github.com/desmond1121) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n\n开发应用的时候，应该检查它是否有流畅的用户体验，即60fps的帧率。如果由于某种原因丢帧，我们首先要做的就是知道系统在做什么（造成丢帧的原因）。\n\nSystrace允许你监视和跟踪Android系统的行为(trace)。它会告诉你系统都在哪些工作上花费时间、CPU周期都用在哪里，甚至你可以看到每个线程、进程在指定时间内都在干嘛。它同时还会突出观测到的问题，从垃圾回收到渲染内容都可能是问题对象，甚至提供给你建议的解决方案。本文章将介绍如何导出trace以及使用它来优化UI的办法。\n\n##总览\n\nSystrace可以帮助你分析应用在不同Android系统上的运行情况。它将系统和应用的线程运行情况放置在同一条时间线上分析。你首先需要收集系统和应用的trace（后面会告诉你怎么做），之后Systrace会帮你生成一份细致、直观的报告，它展示了设备在你监测的这段时间内所发生的事情。\n\n![overview](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/systrace-overview.png)\n**图1.** 连续滑动应用5秒的Trace，它并没有表现得很完美。\n\n图1展示了应用在滑动不流畅的时候生成的trace。默认缩放成全局显示，你可以放大到自己所关注的地方。横轴代表着时间线，事件记录按照进程分组，同一个进程内按线程进行纵向拆分，每个线程记录自己的工作。\n\n在本例中，一共有三个组：Kernel, SurfaceFlinger, App，他们分别以包名为标识。每个应用进程都会包含其中所有线程的记录信号，你可以看到从InputEvent到RenderThread都有。\n\n##生成Trace\n\n在获取trace之前需要做一些启动工作。首先，设备要求API>=16(Android 4.1)，之后通过正常的Debug流程（开启调试、连接工作环境、安装App）连接设备。由于需要记录磁盘活动和内核工作，你可能需要root权限。不过大部分时候你只要能够正常Debug即可。\n\nSystrace 可以通过[命令行](http://developer.android.com/intl/zh-cn/tools/help/systrace.html#options)或者[图形界面](http://developer.android.com/intl/zh-cn/tools/help/systrace.html#gui)启动，本篇文章重点介绍通过命令行使用Systrace。\n\n###在Android 4.3及以上的系统中获取trace\n\n在4.3以上的系统获取Trace步骤：\n\n1. 保证设备USB连接正常，并可以debug；\n2. 在命令行中设置选项，开启trace，比如：\n```\n    $ cd android-sdk/platform-tools/systrace\n    $ python systrace.py --time=10 -o mynewtrace.html sched gfx view wm\n```\n3. 在设备上做任何你想让trace记录的操作。\n\n你可以通过[Systrace选项](http://developer.android.com/intl/zh-cn/tools/debugging/systrace.html#options-4.3)来了解更多命令行选项。\n\n###在Android 4.2及以下的系统中获取trace\n\n在4.2及以下的系统中高效地使用Systrace的话，你需要在配置的时候显式指定要trace的进程种类。一共有这两类种类：\n\n- 普通系统进程，比如图形、声音、输入等。（通过tags设置，具体在[Systrace命令行](http://developer.android.com/intl/zh-cn/tools/help/systrace.html#options)中有介绍）\n- 底层系统进程，比如CPU、内核、文件系统活动。（通过options设置，具体在[Systrace命令行](http://developer.android.com/intl/zh-cn/tools/help/systrace.html#options)中有介绍）\n\n你可以通过以下命令行操作来设置tags：\n\n1. 使用 `--set-tags`选项:\n```\n    $ cd android-sdk/platform-tools/systrace\n    $ python systrace.py --set-tags=gfx,view,wm\n```\n2. 重启adb shell来trace这些进程：\n```\n    $ adb shell stop\n    $ adb shell start\n```\n\n你也可以通过手机上的图形界面设置tags：\n\n1. 在设备上进入设置> 开发者选项 > 监控 > 启用跟踪（部分手机上没有这个选项）；\n2. 选择追踪进程类型，点击确认。\n\n>注意: 在图形界面中设置tag时adb shell不用重新启动。\n\n在配置完tags后，你可以开始收集操作信息了。\n\n如何在当前设置下启动trace：\n\n1. 保证设备的usb连接正常，并且可以正常debug；\n2. 使用低系统等级的命令行选项开启trace，比如：\n    ```$ python systrace.py --cpu-freq --cpu-load --time=10 -o mytracefile.html```\n3. 在设备上做任何你想让trace记录的操作。\n\n你可以通过[Systrace选项](http://developer.android.com/intl/zh-cn/tools/debugging/systrace.html#options-4.3)来了解更多命令行选项。\n\n##分析trace报告\n\n在你获取trace之后你可以在网页浏览器中打开它。这部分内容告诉你怎么通过trace去分析和解决UI性能。\n\n###监视帧数\n\n每个应用都有一行专门显示frame，每一帧就显示为一个绿色的圆圈。不过也有例外，当显示为黄色或者红色的时候，它的渲染时间超过了16.6ms（即达不到60fps的水准）。'w'键可以放大，看看这一帧的渲染过程中系统到底做了什么。\n\n>提示：你可以按右上角的'?'按钮来查看界面使用帮助。\n\n![frame-unselected](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/systrace-frame-unselected.png)\n**图2.** Systrace显示长渲染时间的帧\n\n单击该帧可以高亮它，这时候跟该帧有关的内容会被突出显示。在5.0及以上的系统中，显示工作被拆分成UI线程和Render线程两部分；在5.0以下的系统中，所有的显示工作在UI线程中执行。\n\n点击单个Frame下面的组件可以看他们所花费的时间。每个事件（比如`performTraversals`）都会在你选中的时候显示出它们调用了哪些方法及所用的时间。\n\n###调查警告事件\n\nSystrace会自动分析事件，它会将任何它认为性能有问题的东西都高亮警告，并提示你要怎么去优化。\n\n![frame-selected](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/systrace-frame-selected.png)\n**图3.** 选择一个被高亮帧，它会显示出检测到的问题（回收ListView消耗时间太长）。\n\n在你选择类似图三中的问题帧之后，它就会提示你检测出的问题。在这个例子中，它被警告的主要原因是ListView的回收和重新绑定花费太多时间。在Systrace中也会提供一些对应链接，它们会提供更多解释。\n\n如果你想知道UI线程怎么会花费这么多时间的话，你可以使用[TraceView](http://developer.android.com/intl/zh-cn/tools/debugging/debugging-tracing.html)，它会告诉你都是哪些函数在消耗时间。\n\n你可以通过右侧的'Alert'选项卡来查看整个trace过程中发生的所有问题，并进行快速定位。\n\n![frame-selected-alert](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/systrace-frame-selected-alert-tab.png)\n**图4.** 点击Alert选项卡。\n\n你可以将Alert面板中的问题视为需要处理的bug，很有可能每一次微小的优化能够去除整个应用中的警告！\n\n##应用级别调试\n\nSystrace并不会追踪应用的所有工作，所以你可以在有需求的情况下自己添加要追踪的代码部分。在Android 4.3及以上的代码中，你可以通过[`Trace`](http://developer.android.com/reference/android/os/Trace.html)类来实现这个功能。它能够让你在任何时候跟踪应用的一举一动。在你获取trace的过程中，`Trace.beginSection()`与`Trace.endSection()`之间代码工作会一直被追踪。\n\n下面这部分代码展示了使用`Trace`的例子，在整个方法中含有两个Trace块。\n\n    public void ProcessPeople() {\n        Trace.beginSection(\"ProcessPeople\");\n        try {\n            Trace.beginSection(\"Processing Jane\");\n            try {\n               // 待追踪的代码\n            } finally {\n                Trace.endSection(); // 结束 \"Processing Jane\"\n            }\n    \n            Trace.beginSection(\"Processing John\");\n            try {\n                // 待追踪的代码\n            } finally {\n                Trace.endSection(); // 结束 \"Processing John\"\n            }\n        } finally {\n            Trace.endSection(); // 结束 \"ProcessPeople\"\n        }\n    }\n\n>注意：在Trace是被嵌套在另一个Trace中的时候，`endSection()`方法只会结束理它最近的一个`beginSection(String)`。即在一个Trace的过程中是无法中断其他Trace的。所以你要保证`endSection()`与`beginSection(String)`调用次数匹配。\n\n>注意：Trace的begin与end必须在同一线程之中执行！\n\n当你使用应用级别追踪的时候，你必须通过`-a`或者`-app=`来显式地指定应用包名。可以通过[Systrace指南](http://developer.android.com/intl/zh-cn/tools/help/systrace.html)查看更多关于它的信息。\n\n你在评估应用的时候应该开启应用级别跟踪，即使当你没有手动添加`Trace`信号。因为很多库函数里面是有添加Trace信号的（比如`RecyclerView`），它们往往能够提供很多信息。\n\n"
  },
  {
    "path": "issue-26/在Android M中权限被拒绝时该如何处理.md",
    "content": "#在Android M中权限被拒绝时该如何处理\n\n> * 原文链接 : [How to deal with permission denial on Android M](https://plus.google.com/+AndroidDevelopers/posts/8aaudh5n1zM)\n* 原文作者 : [Wojtek Kaliciński](https://plus.google.com/+WojtekKalicinski/posts)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](www.devtf.cn)\n* 译者 : [Desmond1121](https://github.com/desmond1121)\n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 : 完成\n\nAndroid M Preview 2 的SDK中引入了一个方法来处理运行时权限：`Activity.shouldShowRequestPermissionRationale()`。\n\n这个函数的作用是告知App在调用需要权限的功能前是否要显示相应理由。\n\n当App刚安装的时候，这个方法会返回false，这时候它可以直接调用任何需要权限的功能而不需要解释，此时会正常弹出权限对话框。如果用户之前拒绝过App使用权限，这个方法就会返回true。这时候你应该在调用相应权限功能前显示出理由，不过需要注意，仅仅当此权限没有显示需要它的原因时你才需要自己解释。\n\n当App被永远拒绝获取某权限时（比如你点击了“不再提示”），`shouldShowRequestPermissionRationale()`就会返回false，这时候你也不必要提供任何解释了。\n\n不过需要注意：**由于一个bug，在Android M Preview 2的SDK中`Fragment.shouldShowRequestPermissionRationale()`会一直返回false。**这在未来的版本中会修复。你可以在Fragment中使用`getActivity().shouldShowRequestPermissionRationale()`来绕过这个问题。\n\n你可以在[Github Demo](https://goo.gl/9xpwqN)中查看我们处理运行时权限的示例。"
  },
  {
    "path": "issue-27/为什么不仅继承Observale而且使用Observable.create.md",
    "content": "#为什么不仅继承Observale而且使用Observale.create()？\n---\n\n> * 原文链接 : [Why use Observable.create() and not just inherit from Observable?](http://www.grokkingandroid.com/why-use-observable-create-and-not-just-inherit-from-observable/?utm_source=Android+Weekly&utm_campaign=553bcbfc02-Android_Weekly_174&utm_medium=email&utm_term=0_4eb677ad19-553bcbfc02-337955857)\n* 原文作者 :[Wolfram Rittmeyer](http://www.grokkingandroid.com/author/writtmeyer/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [dengshiwei](https://github.com/dengshiwei) \n* 校对者: [desmond1121](https://github.com/desmond1121)\n* 状态 :  已完成\n\n在你开始使用[RxJava](https://github.com/ReactiveX/RxJava/wiki)的时候，你需要创建Observables。它们是RxJava的核心，但是应该怎么做呢？\n\n看下[Observable](http://reactivex.io/RxJava/javadoc/rx/Observable.html)类可能会让你头晕，看到Observable类的源码更是如此。这个类不仅包含了近10000行代码（虽然其中7600行是注释）而且包含了大量的final methods。实际上仅final methods就有330个。但是你可以继承Observable，奇怪，很奇怪。\n\n接下来你可能会想：没关系，让我继承Observable来看看我能继承多少。\n\n但是你应该不介意看看这个类的构造函数说明文档，你可以看到：\n\n```\n\t注意: 除了你明确的需要继承的情况下，使用create(OnSubscribe)方法替代这个构造函数来创建一个 Observable对象。\n```\n\n好吧！既然你这么想知道这到底是怎么回事，你需要看一看onCreate()方法的说明文档：\n\n```\n\t返回一个Observable对象，当一个Subscriber订阅它时执行特定的功能。\n\t...\n\t编写你传递给create()方法的函数以便它的行为作为一个Observable：它应该适当地唤醒Subscriber的onNext、onError、和onCompleted方法。\n\t...\n\t一个标准的Observable必须恰好唤醒一次Subscriber的onCompleted方法或它的onError方法。\n```\n\n额，看下这个方法的代码可能有些帮助？\n\n\tpublic final static  Observable create(OnSubscribe f) {\n\t\t   return new Observable(hook.onCreate(f));\n\t}\n\n什么？它将参数传递给构造函数？这警告又是什么呢？\n\n好吧，第一：为什么你把继承Observable放在第一位？它的所有方法都是final类型的，因此你通过继承基本不可能给Observable增加更多的功能函数。坚持RxJava的方式做事情是不错的：通过chaining API调用它的fluent API。\n\n另一方面：它是不明确的，使用create()方法你可以直观看到创造的Observable对象。\n\n此外：如果你仔细看看源代码，你回注意到一个小小的引用hook.onCreate()。这是非常重要的，因为RxJava允许你提供包含特定方法的hook对象和允许你替换具体的RxJava的工作。\n\n有次我在调试模式下使用hook来记录哪个线程创建observables对象以及在什么线程上进行工作。在服务器环境下，你可能希望为你的hooks对象添加监视逻辑，通过使用构造方法，你可以解决这一问题同时提高自己做的可能性以及简单化、\n\n说了这么多关于：继承Observable是不被禁止的，在所有规则条件下，总有一些情况下它是不适用的和一些情况下它是有意义的。例如，[Subject](http://reactivex.io/RxJava/javadoc/rx/subjects/Subject.html)类是继承于Observable。只要仔细点同时使用时多想想。"
  },
  {
    "path": "issue-27/为你的应用加速 - 安卓优化指南.md",
    "content": "Speed up your app\n\n---\n\n原文链接 : [Speed up your app][1]\n原文作者 : [UDI COHEN][2]\n译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!][3]\n译者 : [zijianwang90][4]\n校对者: \n状态 : 完成\n\n几周之前，我在Droidcon NYC上有过一次关于Android性能优化的演讲。\n\n我在这个演讲中花费了大量的时间，因为我想通过真实的例子展现性能问题，以及我是通过什么样的工具去发掘这些问题的。因为时间原因，在演讲中我不得不舍弃一半的内容。在这篇文章中，我会总结在演讲中我所讨论的所有内容，并且给出实例\n\n[点此链接进入演讲视频][5]\n\n现在，我们来逐一讨论我在演讲中提及的一些重点内容，希望我的阐述足够的清晰。首先，在我进行性能优化的时候我遵循如下原则：\n\n#原则\n每当我遇到性能问题，或者尝试发现性能问题的时候，我会遵循如下原则：\n\n- 坚持性能测试 - 不要用你的眼睛去优化性能。也许在你盯着同一个动画看了几次之后，你会开始相信他运行的越来越流畅了。数据不会说谎。在你优化你的代码之前以及之后，使用我们将要介绍的一系列工具，去多次的测试你的app到底性能几何。\n- 使用低端设备 - 如果你想要你想暴露你应用的性能问题，低端设备往往会更加的容易。性能强大的设备往往不会太在意你应用上面的一些优化问题，且不是所有用户都在使用这些旗舰设备。\n- 权衡 - 性能的优化始终围绕着权衡这两个字。你在某一个点上的优化可能会造成另一点上出现问题。在很多情况下，你会花大量的时间寻找并解决这些问题，但造成这些问题的原因也可能使因为例如bitmaps的质量，或是你没有使用正确的数据结构去存储你的数据。所以你要时刻准备好作出一定的牺牲\n\n#Systrace\nSystrace是一个非常好但却有可能被你忽视的工具，这是因为开发者们往往不确定Systrace能够为他们提供什么样的信息。\n\nSystrace会展示一个运行在手机上程序状况的概览。这个工具提醒了我们手机其实是一个可以在同一时间完成很多工作的电脑。在最近的一次SDK更新中，这个工具在数据分析能力上得到了提升，用以帮助我们寻找性能问题之所在。\n\n下面让我们来看看Systrace长什么样子：\n\n![systrace-overview][6]\n\n你可以通过Android Device Monitor Tool或者是命令行来生成Systrace文件，想了解更多[猛戳此处][7]。\n\n在视频中，我向大家介绍了Systrace中不同区域的功能。当然最有趣的还是Alerts和Frames两栏，它们展示了通过手机来的数据而生成出来的可视化分析结果。让我们来选择最上方的alerts瞧瞧：\n\n![systrace-alert][8]\n\n这个警告指出了，有一个View#draw()方法执行了比较长的时间。我们可以在下面看到问题的描述，链接，甚至是相关的视频。下面我们看Frames这一行，可以看到这里展示了被绘制出来的每一帧，并且用绿、黄、红三颜色来区分它们在绘制时的性能。我们选一个红色帧来瞅瞅：\n\n![systrace-frame][9]\n\n在最下方，我们看到了与这一帧所相关的一些警告。在这三个警告中，有一个是我们上面所提到的（View#draw()）。接下来我们在这一帧处放大并在下方展开“Inflation during ListView recycling”这条警告：\n\n![systrace-frame-zoomin][10]\n\n我们可以看到警告部分的总耗时，32毫秒，远高于了我们对保障60fps所需的16毫秒绘制时间。同时还有更多的ListView每个条目的绘制时间，大约是6毫秒每个条目，总共五个。而Description描述项中的内容会帮助我们理解问题，甚至提供问题的解决方案。回到我们上一张图片，我们可以在“inflate”这一个块区处放大，并且观察到底是哪些View在被填充过程中耗时比较严重。\n\n下面是另外一个渲染过慢的实例：\n\n![systrace-2-frame][11]\n\n在选择了某一帧之后，我们可以按“m”键来高亮这一帧，并且在上方看到了这一部分的耗时，如图，我们看到了这一阵的绘制总共耗时超过19毫秒。而当我们展开这一帧唯一的一个警告时，我们发现了“Scheduling delay”这条错误。\n\nScheduling delay（调度延迟）的意思就是一个线程在处理一块运算的时候，在很长一段时间都没有被分配到CPU上面做运算，从而导致这个线程在很长一段时间都没有完成工作。我们选择这一帧中最长的一块，从而得到更加详细的信息：\n\n![systrace-2-slice][12]\n\n在红框区域内，我们看到了**“Wall duration”**，他代表着这一区块的开始到结束的耗时。之所以叫作“Wall duration”，是因为他就像是墙上的一个时钟，从线程的一开始就为你计时。\n\n但是，CPU Duration一项中显示了实际CPU在处理这一区块所消耗的时间。\n\n很显然，两个时间的差距还是非常大的。整个区块耗时18毫秒，而在这之中CPU只消耗了4毫秒的时间去运算。这就有点奇怪了，所以我们应该看一下在这整个过程之中，CPU去干吗了。\n\n![systrace-2-cpu][13]\n\n可以看到，所有四个线程都非常的繁忙。\n\n选择其中的一个线程会告诉我们是哪个程序在占用他，在这里是一个包名为com.udinic.keepbusyapp的程序。在这里，由于另外一个程序占用CPU，导致了我们的程序未能获得足够的CPU资源。\n\n但是这种情况其实是暂时的，因为被其他后台应用占用CPU的情况并不多见（- -），但仍有其他应用的线程或是主线程占用CPU。而Traceview也只能为我们提供一个概览，他的深度是有限的。所以要找到我们app中到底是什么让我们的CPU繁忙，我们还要借助另一个工具——Traceview。\n\n#Traceview\n\nTraceview是一个性能测试工具，展示了所有方法的的运行时间。下面让我们来瞅瞅他是啥样的：\n\n![traceview-overview][14]\n\n这个工具可以从Android Device Monitor中打开也可以通过代码打开。更多的消息信息清[看这里][15]。\n\n下面让我们来看看每一列的含义：\n\n- Name - 方法名，以及他们在上面图表中所对应的颜色。\n- Inclusive CPU Time - CPU在处理这个方法以及所有子方法（如被他调用的所有方法）的总耗时。\n- Exclusive CPU Time - CPU在处理这一个单独方法的总耗时。\n- Inclusive/Exlusive Real Time - 从方法的开始执行到执行结束的总耗时，和Systrace中的“Wall duration”类似\n- Calls+Recursion - 这个方法被调用的次数，以及被递归调用的次数。\n- CPU/Real time per Call - 在处理这个方法时的CPU耗时的平均值以及实际耗时的平均值。另外的列展示了这个方法所有调用的累计耗时\n\n我打开一个滑动不太顺滑的应用。开启记录，滑动一点后停止记录。展开getView()方法，如下图：\n\n![traceview-getview][16]\n\n这个方法被调用了12次，每次CPU会消耗3毫秒左右，但是每次调用的总耗时却高达162毫秒！绝对有问题啊！\n\n而看看这个方法的children，我们可以看到这其中的每个方法在耗时方面是如何分布的。Thread.join()方法战局了98%的inclusive real time。这个方法在等待另一个线程结束的时候被调用。在Children中另外一个方法就是Tread.start()方法，而之所以整个方法耗时很长，我猜测是因为在getView()方法中启动了线程并且在等待它的结束。\n\n但是这个线程在哪儿？\n\n我们在getView()方法中并不能看到这个线程做了什么，因为这段逻辑不在getView()方法之中。于是我找到了Thread.run()方法，就是在线程被创建出来时候所运行的方法。而跟随这个方法一路向下，我找到了问题的元凶。\n\n![traceview-thread][17]\n\n我发现了BgService.doWork()方法的每次调用花费了将近14毫秒，并且有四十个这东西！而且getView()中还有可能调用多次这个方法，这就解释了为什么getView()方法执行时间如此之长。这个方法让CPU长时间的保持在了繁忙状态。而看看Exclusive CPU time，我们可以看到他占据了80%的CPU时间！此外，根据Exclusive CPU time排序，可以帮我们更好的定位那些耗时很长的方法，而他们很有可能就是造成性能问题的罪魁祸首。\n\n关注这些耗时方法，例如getView()，View#onDraw()等方法，可以很好的帮助我们寻找为什么应用运行缓慢的原因。但有些时候，还会有一些其他的东西来占用宝贵的CPU资源，而这些资源如果被运用在UI的绘制上，也许我们的应用会更加流畅。Garbage Collector垃圾回收机制会不时的运行，回收那些没用的对象，通常来讲这不会影响我们在前台运行的程序。但如果GC被运行的过于频繁，他同样可以影响我们应用的执行效率。而我们该如何知道回收的是否过于频繁了呢...\n\n#内存调优 Memory Profiling\n\nAndroid Studio在最近的更新中给予了我们更加强大的工具去分析性能问题。在底部Android选项中的Memory选项卡，会显示有多大的数据在什么时候被分配到了堆内存之中，他是长成这个样子的：\n\n![mem-graph][18]\n\n而当图表中出现一个小的下滑的时候，说明GC回收发生了，他清除了不必要的对象并且腾出了一定的堆空间。而在这张图表的左侧有两个工具供我们使用，Head dump和Allocation Tracker。\n\n#Heap dump\n\n为了找出到底是什么正在占用我们的堆内存，我们可以使用左边的heap dump按钮。他会提供一个堆内存占用情况的快照，并且会在Android Studio中打开一个单独的报告界面。\n\n![heap-overview][19]\n\n在左侧，我们看到一个图标展示了堆中所有的实例，按照类进行分组。而对于每一个实例，会展示有多少个实例的对象被分配到堆中，以及他们的所占用的空间（Shallow size浅尺寸），以及这些对象在内存中仍然占用的空间，后者告诉了我们多少的内存空间将会被释放如果这些实例被释放。这个工具可以让我们直观的观察处内存是被如何占用的，帮助我们分析我们使用的数据结构和对象之间的关系，以便发现问题并使用更加高效的数据结构，解开和对象之间的关联，并且降低Ratained Memory的占用。而最终目的，就是尽可能的降低我们的内存占用。\n\n回过头来看图表，我们发现MemoryActivity存在39个实例，这对于一个Activity来说有点奇怪。在右边选择其中的一个实例，会在下方看到所有的对这个实例的引用树状列表。\n\n![heap-reftree][20]\n\n其中一个是ListenersManager对象中的一个集合。而观察这个activity的其他实例，就会他们都因为这个对象而被保留在了内存之中。这也解释了为什么这些对象占用了如此多的内存：\n\n![heap-retained][21]\n\n这个现象就叫做“内存泄露”，我们的activity已经被销毁，但是他们的对象却因为始终被引用着而无法被垃圾回收。我们可以避免这种情况，例如确保这些对象再被销毁后不会被其他对象一直引用着。在我们这个例子中，在Activity被销毁后，ListernesManager并不需要保持着对这些对象的引用。所以解决办法就是在onDestroy()回调方法中移除这些引用。\n\n内存泄露以及其他较大的对象会在堆中占据很多的控件，它们减少着可用内存的同时也频繁的造成垃圾回收。而垃圾回收又回造成CPU的繁忙，而堆内存并不会变得更大，最终就会导致更悲剧的结果发生：OutOfMemoryException内存溢出，并导致程序崩溃。\n\n另外一个更先进的工具就是Eclipse Memory Analyzer Tool (Eclipse MAT)：\n\n![eclipse-mat][22]\n\n这个工具可以做所有Android Studio可以做的，并且辨别可能出现的内存泄露，以及提供更加高级的搜索功能，例如搜索所有大于2MB的Bitmap实例，或是搜索所有[空的Rect对象][23]。\n\n另外一个很好的工具是[LeakCanary][24]，是一个第三方库，可以观察应用中的对象并且确保它们没有造成泄漏。而如果造成泄漏了，会有一个推送来提醒你在哪里发生了什么。\n\n![leakcanary][25]\n\n#Allocation Tracker\n\n我们可以在内存图表的左侧找到Allocation Tracker的启动和停止按钮。他会生成一个在一定时间内被生成的所有实例的报告，并且按照类分型分组：\n\n![alloc-class][26]\n\n或者按照方法分组：\n\n![alloc-method][27]\n\n同时它还能通过美观的可视化界面，告诉我们哪些方法或类拥有最多的实例。\n\n利用这些信息，我们可以找到哪些占用过多内存，引发过多次垃圾回收且又对耗时非常敏感的方法。我们也可以利用这个工具找到很多短命的相同类的实例，从而可以考虑使用对象池的思想去尽量的减少过多的实例被创建。\n\n#常见内存小技巧\n\n以下是一些我写代码时候遵循的规律或是技巧：\n\n- **枚举**在性能问题上一直是一个被经常讨论的话题。这里是一个讨论枚举的[视频][28]，指出了枚举所消耗的内存空间，这还有一段关于这个视频的[讨论][29]，当然其中存在着一些误导。但是回过头来，枚举真的比一般的常量更加占用空间吗？肯定的。但是这一定不好吗？未必。如果你在编写一个library库并且需要很强的类型安全性，那么也许可以使用枚举而非其他办法，例如[@IntDef][30]。而如果你只是有一堆的常量，使用枚举也许就不能么明智了。还是那句话，在你做决定之前一定要权衡与取舍。\n- **自动装箱** - 自动装箱是一个从原始数据类型到对象型数据的装箱过程（例如int到Integer）。每当一个原始类型数据被装箱到一个对象类型数据，一个新的对象就产生了（震惊吧。。）。所以如果发生了很多次的自动装箱，势必会加快GC的执行频率，而且自动装箱是很容易被我们忽视的。而解决办法，在使用这些类型的时候尽量一致，如果你在应用中完全使用原始数据类型，那么尽量避免他被无缘无故的自动封装。你可以使用我们上面提到的memery profiling工具去寻找这些过于大量的对象类型数据，也可以通过Traceview去寻找类似Integer.valueOf()，Long.valueOf()这样的方法来判断是否发生了大量不必要的自动封装。\n- **HashMap vs ArrayMap / Sparse*Array** - 既然提到了自动装箱的问题，那么使用HashMap的话，就需要我们使用对象类型作为键。而如果我们在整个应用中使用的都是基本数据类型的“int”，那么在我们使用HashMap时候就会发生自动装箱，而这时也许我们就可以考虑使用SparseIntArray。而假如我们仍然需要键为对象类型，那么我们可以使用ArrayMap。ArrayMap和HashMap很类似，[但是在底层的实现原理却不尽相同][31]，这也会让我们更加高效的使用内存，但要付出一定的性能代价。两种方法都会比HashMap更加节省内存空间，但是相比于HashMap，查询和增删的速度上会有一定的牺牲。当然，除非你具有至少1000条的数据源，否则在运行时也不会对速度造成太大的影响，这也是你使用他们替代HashMap的原因之一。\n- **注意Context** - 在我们前面也看到了，Activity是非常容易造成内存泄露的。在Android中，最容易造成内存泄露的当属Activity。并且这些内存泄露会浪费大量的内存，因为他们持有着他们UI中所有的View，而这些View通常会占据很多的控件。在开发过程中的很多操作需要Context，而我通常也会使用Activity来传递。所以一定要搞清楚你对这个Activity做了什么。如果一个引用被缓存起来了，且这个对象的生命周期比你的Activity还要长，那么在我们解除这个引用之前，就会造成内存泄露了。\n- **避免非静态** - 当我们创建非静态内部类，并且初始化它的时候，在其内部会创建一个外部类的隐式引用。而如果内部类的生命周期比外部类还要长，那么外部类也同样会被保留在内存之中，尽管我们已经完全不需要它了。例如，在Activity内创建了一个继承自AsyncTask的内部类，完后在Activity运行的时候启动这个async task，再杀掉Activty。那么这时候这个async task会保持着这个Activity直到执行结束。而解决办法也很简单，不要这么做，尽量使用静态内部类。\n\n#监测GPU（GPU Profiling）\n\n在Android 1.4中的一个全新工具，就是可以查看GPU绘制。\n\n![gpu-overview][32]\n\n每一条线意味着一帧被绘制出来，而每条线中的不同颜色又代表着在绘制过程中的不同阶段：\n\n- **Draw (蓝色)** 代表着View#onDraw()方法。在这个环节会创建/刷新DisplayList中的对象，这些对象在后面会被转换成GPU可以明白的OpenGL命令。而这个值比较高可能是因为view比较复杂，需要更多的时间去创建他们的display list，或者是因为有太多的view在很短的时间内被创建。\n- **Prepare (紫色)** - 在Lollipop版本中，一个新的线程被加入到了UI线程中来帮助UI的绘制。这个线程叫作RenderThread。它负责转换display list到OpenGL命令并且送至GPU。在这过程中，UI线程可以继续开始处理后面的帧。而在UI线程将所有资源传递给RenderThread过程中所消耗的时间，就是紫色阶段所消耗的时间。如果在这过程中有很多的资源都要进行传递，display list会变得过多过于沉重，从而导致在这一阶段过长的耗时。\n- **Process (红色)** - 执行Display list中的内容并创建OpenGL命令。如果有过多或者过于复杂的display list需要执行的话，那么这阶段会消耗较长的时间，因为这样的话会有很多的view被重绘。而重绘往往发生在界面的刷新或是被移动出了被覆盖的区域。\n- **Execute (黄色)** - 发送OpenGL命令到GPU。这个阶段是一个阻塞调用，因为CPU在这里只会发送一个含有一些OpenGL命令的缓冲区给GPU，并且等待GPU返回空的缓冲区以便再次传递下一帧的OpenGL命令。而这些缓冲区的总量是一定的，如果GPU太过于繁忙，那么CPU则会去等待下一个空缓冲区。所以，如果我们看到这一阶段耗时比较长，那可能是因为GPU过于繁忙的绘制UI，而造成这个的原因则可能是在短时间内绘制了过于复杂的view。\n\n在Marshmallow版本中，有更多的颜色被加了进来，例如Measure/Layout阶段，input handling输入处理，以及一些其他的：\n\n![gpu-colors-marsh][33]\n\n在使用这些功能之前，你需要在开发者选项中开启GPU rendering(GPU呈现模式分析)：\n\n![gpu-settings2][34]\n\n接下来我们就可以通过以下这条adb命令得到我们想要得到的所有信息：\n\n```\nadb shell dumpsys gfxinfo <PACKAGE_NAME>\n```\n\n我们可以自己收集这些信息并创建图表。这个命令也会打印出一些其他有用的信息，例如view层级中的层数，display lists的大小等等。在Marshmallow中，我们也会得到更多的信息：\n\n![gpu-adb][35]\n\n如果我们需要自动化测试我们的app，那么我们可以自己创建服务器去运行在特定节点执行这些命令（如列表滚动，重度动画等），并观察这些数值的变动。这可以帮助我们找出在哪里出现了性能的下降，并且产品上线之前找到问题的所在。我们也能够通过\"framestats\"关键字来找到更多更加精确的数据，这里有[更详尽的解释][36]。\n\n但这可不是获取GPU Rendering数据的唯一方式！\n\n我们在开发者选项中看过了GPU呈现模式分析内的Profile GPU Rendering”选项后，还有另外一个选项就是\"On screen as bars\"（在屏幕上显示为条形图）。打开这个后，我们就可以直观的看到每一帧在绘制过程中所消耗的时间，绿色的横线则代表16ms的60fps零界值。\n\n![gpu-onscreen][37]\n\n在右边的例子中，我们可以看到很多帧都超出了绿线，这也意味着它花了多余16毫秒的时间去绘制。而蓝色占据了这些线条的主体，我们知道这可能是因为过多或是过于复杂的view在被绘制。在这种情况下，当我滑动列表，因为列表中view的结构比较复杂，有一些view已经被绘制完成而一些因为过于复杂还处于绘制阶段，而这可能就是造成这些帧超过绿线的原因——绘制起来实在太复杂了。\n\n#Hierarchy Viewer\n\n我非常喜欢这个工具，同时也因为那么多人完全不用而感到一丝的悲凉。\n\n使用Hierarchy Viewer，我们可以获得性能数据，观察View层级中的每一个View，并且可以看到所有View的属性。我们同样可以导出theme数据，这样可以看到每一个style中的属性值，但是我们只能在单独运行Hierarchy Viewer的时候才能这么干，而非通过Android Monitor。通常在我进行布局设计以及布局优化的时候，我会使用到这个工具。\n\n![hierview-overview][38]\n\n在正中间我们看到的树状结构就代表了View的层级。View的层级可以很宽，但如果太宽的话（10层左右），也许会在布局和测量阶段消耗大量的性能。在每一次View通过View#onMeasure()方法测量的时候，或是通过View#onLayout()方法布局他的所有子view的时候，这些方法又回传递到它所有的子view上面并且重头来过。有的布局会将上述步骤做两次，例如RelativeLayout以及某些通过配置的LinearLayout，而如果它们又层层嵌套，那么这些方法的传递会大量的增加。\n\n在右下方，我们看到了一个我们布局的“蓝图”，标注了每一个view的位置。当我们点击这里（或者从树状结构中），我们会在左侧看到他所有的属性。在设计布局时候，有时候我不确定为什么一个view被摆在那里，而使用这个工具，我可以在树状图中找到这个view，选择，并观察他在预览窗口中的位置。我还通过view在屏幕上最终的绘制尺寸，来设计有趣的动画，并且使用这些信息让动画或者View的位置更加的精准。我也可以通过这个工具来寻找被其他View不小心盖住从而找不到的View，等等等等。\n\n![hierview-colors][39]\n\n对于每一个view我们可以获得他测量、布局以及绘制的用是和它所包含的所有子view。在这里颜色代表了这个view在绘制过程中，相比树中其他view的性能表现，这是我们找到这些性能不足view的最佳途径。鉴于我们能够看到所有view的预览，我们可以沿着树状图，跟随view被创建的顺序，找寻那些可以被舍弃的多余步骤。而其中之一，也是对性能影响非常大的，就是过度绘制。\n\n#过度绘制\n\n正如我们在GPU Profiling部分看到的，在Execute黄色阶段，如果GPU有过多的东西要在屏幕上绘制，整个阶段会消耗更多的时间，同事也增加了每一帧所消耗的时间。过度绘制往往发生在我们需要在一个东西上面绘制另外一个东西，例如在一个红色的背景上画一个黄色的按钮。那么GPU就需要先画出红色背景，再在他上面绘制黄色按钮，此时过度绘制就是不可避免的了。如果我们有太多层需要绘制，那么则会过度的占用GPU导致我们每帧消耗的时间超过16毫秒。\n\n![overdraw-gif][40]\n\n使用“Debug GPU Overdraw”（调试过度绘制）功能，所有的过度绘制会以不同颜色的形式展示在屏幕上。1x或是2x的过度绘制没啥问题，即便是一小块浅红色区域也不算太坏，但如果我们看到太多的红色区域在屏幕上，那可能就有问题了，让我们来看几个例子：\n\n![overdraw-examples][41]\n\n在左边的例子中，我们看到列表部分是绿色的，通常还OK，但是在上方发生覆盖的区域是一片红色，那就有问题了。在右边的例子中，整个列表都是浅红色。在两个例子中，都各有一个不透明的列表存在2x或3x的过度绘制。这些过度绘制可能发生在我们给Activity或Fragment设置了全屏的背景，同时又给ListView以及ListView的条目设置了背景色。而通过只设置一次背景色即可解决这样的问题。\n\n注意：默认的主题会为你指定一个默认的全屏背景色，如果你的activity又一个不透平的背景盖住了默认的背景色，那么你可以移除主题默认的背景色，这样也会移除一层的过度绘制。这可以通过配置主题配置或是通过代码的方法，在onCreate()方法中调用getWindow().setBackgroundDrawable(null)方法来实现。\n\n而使用Hierarchy Viewer，你可以导出一个所有view层级的PSD文件，在Photoshop中打开，并且调查不同的layout以及不同的层级，也能够发现一些在布局中存在的过度绘制。而使用这些信息可以移除不必要的过度绘制。而且，不要看到绿色就满足了，冲着蓝色去！\n\n#透明度\n\n使用透明度可能会影响性能，但是要去理解为什么，让我们瞅瞅当我们给view设置透明度的时候到底发生了什么。我们来看一下下面这个布局：\n\n![alpha-before][42]\n\n我们看到这个layout中又三个ImageView并且重叠摆放。在使用最常规的设置透明度的方法setAlpha()时，方法会传递到没一个子view上面，在这里是每一个ImageView。而后，这些ImageView会携带新的透明值被绘制入帧缓冲区。而结果就是：\n\n![alpha-direct][43]\n\n这并不是我们想要看到的结果。\n\n因为每一个ImageView都被赋予了一个透明值，导致了本应覆盖的部分被融合在一起。幸运的是，系统为我们解决了这个问题。布局会被复制到一个非屏幕区域缓冲区中，并且以一个整体来接收透明度，其结果再被复制到帧缓冲区。结果就是：\n\n![alpha-complex][44]\n\n但是，我们是要付出性能上面的代价的。\n\n假如在帧缓冲区内绘制之前，还要在off-screen缓冲区中绘制一遍的话，相当于增加了一层不可见的绘制层。而系统并不知道我们是希望这个透明度以何种的形式去展现，所以系统通常会采用相对复杂的一种。但是也有很多设置透明度的方法能够避免在off-screen缓冲区中的复杂操作：\n\n- TextView - 使用setTextColor()方法替代setAlpha()。这种方法使用Alpha通道来改变字色，字也会直接使用它进行绘制。\n- ImageView - 使用setImageAlpha()方法替代setAlpha()。原理同上。\n- 自定义控件 - 如果你的自定义控件并不支持相互覆盖，那就无所谓了。所有的子view并不会想上面的例子中一样，因为覆盖而相互融合。而通过复写hasOverlappingRendering()并将其返回false后，便会通知系统使用最直接的方式绘制view。同时我们也可以通过复写onSetAlpha()返回true来手动操控设置透明度后的逻辑。\n\n#硬件加速\n\n在Honeycomb版本中引入了硬件加速（Hardware Accleration）后，我们的应用在绘制的时候就有了全新的[绘制模型][45]。它引入了DisplayList结构，用来记录View的绘制命令，以便更快的进行渲染。但还有一些很好的功能开发者们往往会忽略或者使用不当——View layers。\n\n使用View layers（硬件层），我们可以将view渲染入一个非屏幕区域缓冲区（off-screen buffer，前面透明度部分提到过），并且根据我们的需求来操控它。这个功能主要是针对动画，因为它能让复杂的动画效果更加的流畅。而不使用硬件层的话，View会在动画属性（例如coordinate, scale, alpha值等）改变之后进行一次刷新。而对于相对复杂的view，这一次刷新又会连带它所有的子view进行刷新，并各自重新绘制，相当的耗费性能。使用View layers，通过调用硬件层，GPU直接为我们的view创建一个结构，并且不会造成view的刷新。而我们可以在避免刷新的情况下对这个结构进行进行很多种的操作，例如x/y位置变换，旋转，透明度等等。总之，这意味着我们可以对一个让一个复杂view执行动画的同时，又不会刷新！这会让动画看起来更加的流畅。下面这段代码我们该如何操作：\n\n```\n// Using the Object animator\nview.setLayerType(View.LAYER_TYPE_HARDWARE, null);\nObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f);\nobjectAnimator.addListener(new AnimatorListenerAdapter() {\n    @Override\n    public void onAnimationEnd(Animator animation) {\n        view.setLayerType(View.LAYER_TYPE_NONE, null);\n    }\n});\nobjectAnimator.start();\n\n// Using the Property animator\nview.animate().translationX(20f).withLayer().start();\n```\n\n很简单，对吧？\n\n是的，但是再使用硬件layers的时候还是有几点要牢记在心：\n\n- **回收** - 硬件层会占用GPU中的一块内存。只在必要的时候使用他们，比如动画，并且事后注意回收。例如在上面ObjectAnimator的例子中，我们增加了一个动画结束监听以便在动画结束后可以移除硬件层。而在Property animator的例子中，我们使用了withLayers()，这会在动画开始时候自动创建硬件层并且在结束的时候自动移除。\n- 如果你在调用了硬件View layers后**改变了View**，那么会造成硬件硬件层的刷新并且再次重头渲染一遍view到非屏幕区域缓存中。这种情况通常发生在我们使用了硬件层暂时还不支持的属性（目前为止，硬件层只针对以下几种属性做了优化：otation、scale、x/y、translation、pivot和alpha）。例如，如果你另一个view执行动画，并且使用硬件层，在屏幕滑动他们的同时改变他的背景颜色，这就会造成硬件层的持续刷新。而以硬件层的持续刷新所造成的性能消耗来说，可能让它在这里的使用变得并不那么值。\n\n而对于第二个问题，我们也有一个可视化的办法来观察硬件层更新。使用开发者选项中的“Show hardware layers updates”（显示硬件层更新）\n\n![hwl-devoptions2][46]\n\n当打开该选项后，View会在硬件层刷新的时候闪烁绿色。在很久以前我有一个ViewPager在滑动的时候有点不流畅。在开发者模式启动这个选项后，我再次滑动ViewPager，发现了如下情况：\n\n![hwl-calproblem][47]\n\n左右两页在滑动的时候完全变成了绿色！\n\n这意味着他们在创建的时候使用了硬件层，而且在滑动的时候也界面也进行了刷新。而当我在背景上面使用时差效果并且让条目有一个动画效果的时候，这些处理确实会让它进行刷新，但是我并没有对ViewPager启动硬件层。在阅读了ViewPager的源码后，我发现了在滑动的时候会自动为左右两页启动一个硬件层，并且在滑动结束后移除掉。\n\n在两页间滑动的时候创建硬件层也是可以理解的，但对我来说小有不幸。通常来讲加入硬件层是为了让ViewPager的滑动更加流畅，毕竟它们相对复杂。但这不是我的app所想要的，我不得不通过[一些编码][48]来移除硬件层。\n\n硬件层其实并不是什么酷炫的东西。重要的是我们要理解他的原理并且合理的使用他们，要不然你确实会遇到一些麻烦。\n\n#DIY\n\n在准备上述这一系列例子的过程中，我进行了很多的编码去模拟这些情景。你可以在[这个Github项目][49]中找到这些代码，同时也可以在[Google Play][50]中找到。我用不同的Activity区分了不同的情景，并且尽量将他们的用文档解释清楚，以便于帮助大家理解不同的Activity中是出现哪种问题。大家可以边阅读各个Activity的javadoc的同时，利用我们前面讲到的工具去玩儿这个App。\n\n#更多信息\n\n随着安卓系统的不断进化，你有话你的应用的手段也在不断变多。很多全新的工具被引入到了SDK中，以及一些新的特性被加入到了系统中（好比硬件层这东西）。所以与时俱进和懂得取舍是非常重要的。\n\n这是一个非常棒的[油管播放列表][51]，叫Android Performance Patterns，一些谷歌出品的短视频，讲解了很多与性能相关的话题。你可以找到不同数据结构之间的对比（HashMap  vs ArrayMap），Bitmap的优化，网络优化等等，吐血推荐！\n\n加入[Android Performance Patterns的G+社群][52]，和大家一起讨论，分享心得，提出问题！\n\n更多有意思的链接：\n\n- 了解安卓中的[图形结构（Graphics Architecture）][53]。例如关于UI的渲染，不同的系统组件，比如SurfaceFlinger，以及他们之间是如何交互的。比较长，但是值得一看！\n- [Google IO 2012上的一段演讲][54]，展示了绘制模型（Drawing model）是如何工作的。\n- [一段来自Devoxx 2013的关于Anrdroid性能的研讨][55]，展示了一些在Anrdroid 4.4对绘制模型的一些优化，并且通过demo的形式展示了对不同优化工具的使用（Systrace，Overdraw等等）。\n- [一篇非常好的关于“预防性优化”][56]（Preventative Optimizations）的文章，阐述了他和“不成熟的优化”有和区别。很多的开发者并不优化他们的代码，因为他们认为这些影响并不明显。但是记住，问题也是积少成多的。如果你有机会去优化很小的一点，即便是非常微不足道的一点，也是应该的。\n- [安卓中的内存管理][57] - 一个2011年的Google IO视频，仍然值得一看。视频展示了安卓是如何管理不同app的内存的，以及如何使用Eclipse MAT去发现问题。\n- 一个叫做Romain Guy的谷歌工程师的[案例研究][58]，通过优化一个第三方的推特客户端。在这个研究中，Romain展示了他是如何发现问题的，并且建议了相应的解决方案。[另一篇文章][59]跟进了这个问题，展示了这个app在重新制作后的一些其他问题。\n\n我真心希望你通过这篇文章获得到了足够丰富的信息和信心，从今天开始优化你的应用吧！\n\n尝试用工具去记录，并通过一些开发者选项中的选项，开搞吧。欢迎来G+上分享你在安卓性能优化上面的心得！\n\n\n  [1]: http://blog.udinic.com/2015/09/15/speed-up-your-app\n  [2]: http://blog.udinic.com/\n  [3]: http://www.devtf.cn/\n  [4]: https://github.com/zijianwang90\n  [5]: https://www.youtube.com/watch?v=v3DlGOQAIbw\n  [6]: http://blog.udinic.com/assets/media/images/speed-up-your-app/systrace-overview.png\n  [7]: http://developer.android.com/tools/help/systrace.html\n  [8]: http://blog.udinic.com/assets/media/images/speed-up-your-app/systrace-alert.png\n  [9]: http://blog.udinic.com/assets/media/images/speed-up-your-app/systrace-frame.png\n  [10]: http://blog.udinic.com/assets/media/images/speed-up-your-app/systrace-frame-zoomin.png\n  [11]: http://blog.udinic.com/assets/media/images/speed-up-your-app/systrace-2-frame.png\n  [12]: http://blog.udinic.com/assets/media/images/speed-up-your-app/systrace-2-slice.png\n  [13]: http://blog.udinic.com/assets/media/images/speed-up-your-app/systrace-2-cpu.png\n  [14]: http://blog.udinic.com/assets/media/images/speed-up-your-app/traceview-overview.png\n  [15]: http://developer.android.com/tools/debugging/debugging-tracing.html\n  [16]: http://blog.udinic.com/assets/media/images/speed-up-your-app/traceview-getview.png\n  [17]: http://blog.udinic.com/assets/media/images/speed-up-your-app/traceview-thread.png\n  [18]: http://blog.udinic.com/assets/media/images/speed-up-your-app/mem-graph.png\n  [19]: http://blog.udinic.com/assets/media/images/speed-up-your-app/heap-overview.png\n  [20]: http://blog.udinic.com/assets/media/images/speed-up-your-app/heap-reftree.png\n  [21]: http://blog.udinic.com/assets/media/images/speed-up-your-app/heap-retained.png\n  [22]: http://blog.udinic.com/assets/media/images/speed-up-your-app/eclipse-mat.png\n  [23]: http://kohlerm.blogspot.com/2009/04/analyzing-memory-usage-off-your-android.html\n  [24]: https://corner.squareup.com/2015/05/leak-canary.html\n  [25]: http://blog.udinic.com/assets/media/images/speed-up-your-app/leakcanary.png\n  [26]: http://blog.udinic.com/assets/media/images/speed-up-your-app/alloc-class.png\n  [27]: http://blog.udinic.com/assets/media/images/speed-up-your-app/alloc-method.png\n  [28]: https://youtu.be/Hzs6OBcvNQE\n  [29]: https://plus.google.com/+JakeWharton/posts/bTtjuFia5wm\n  [30]: https://developer.android.com/reference/android/support/annotation/IntDef.html\n  [31]: https://www.youtube.com/watch?v=ORgucLTtTDI\n  [32]: http://blog.udinic.com/assets/media/images/speed-up-your-app/gpu-overview.png\n  [33]: http://blog.udinic.com/assets/media/images/speed-up-your-app/gpu-colors-marsh.png\n  [34]: http://blog.udinic.com/assets/media/images/speed-up-your-app/gpu-settings2.png\n  [35]: http://blog.udinic.com/assets/media/images/speed-up-your-app/gpu-adb.png\n  [36]: https://developer.android.com/preview/testing/performance.html\n  [37]: http://blog.udinic.com/assets/media/images/speed-up-your-app/gpu-onscreen.png\n  [38]: http://blog.udinic.com/assets/media/images/speed-up-your-app/hierview-overview.png\n  [39]: http://blog.udinic.com/assets/media/images/speed-up-your-app/hierview-colors.png\n  [40]: http://blog.udinic.com/assets/media/images/speed-up-your-app/overdraw-gif.gif\n  [41]: http://blog.udinic.com/assets/media/images/speed-up-your-app/overdraw-examples.png\n  [42]: http://blog.udinic.com/assets/media/images/speed-up-your-app/alpha-before.png\n  [43]: http://blog.udinic.com/assets/media/images/speed-up-your-app/alpha-direct.png\n  [44]: http://blog.udinic.com/assets/media/images/speed-up-your-app/alpha-complex.png\n  [45]: http://developer.android.com/guide/topics/graphics/hardware-accel.html\n  [46]: http://blog.udinic.com/assets/media/images/speed-up-your-app/hwl-devoptions2.png\n  [47]: http://blog.udinic.com/assets/media/images/speed-up-your-app/hwl-calproblem.gif\n  [48]: http://blog.udinic.com/2013/09/16/viewpager-and-hardware-acceleration\n  [49]: https://github.com/Udinic/PerformanceDemo\n  [50]: https://play.google.com/store/apps/details?id=com.udinic.perfdemo\n  [51]: https://www.youtube.com/playlist?list=PLOU2XLYxmsIKEOXh5TwZEv89aofHzNCiu\n  [52]: https://plus.google.com/communities/116342551728637785407\n  [53]: http://source.android.com/devices/graphics/architecture.html\n  [54]: https://www.youtube.com/watch?v=Q8m9sHdyXnE\n  [55]: https://www.parleys.com/tutorial/part-2-android-performance-workshop\n  [56]: https://medium.com/google-developers/the-truth-about-preventative-optimizations-ccebadfd3eb5\n  [57]: https://www.youtube.com/watch?v=_CruQY55HOk\n  [58]: http://www.curious-creature.com/docs/android-performance-case-study-1.html\n  [59]: http://www.curious-creature.com/2015/03/25/android-performance-case-study-follow-up/"
  },
  {
    "path": "issue-27/使用Gradle将项目发布到Bitbucket上.md",
    "content": "使用Gradle将项目发布到Bitbucket上\n---\n\n> * 原文链接 : [Publish with Gradle on Bitbucket](https://medium.com/@Mul0w/publish-with-gradle-on-bitbucket-1463236dc460)\n* 原文作者 : [Stef](https://medium.com/@Mul0w)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [MiJack](https://github.com/mijack)\n* 校对者: [MiJack](https://github.com/mijack)\n* 状态 :  已完成\n\n大家好！\n\n我们经常看见一些开源库放在MavenCentral, JCenter等仓库上，但是对于一些现有的库例如公司的库和工具，有没有类似的处理方法？\n\n当然，你可能知道一些私有的仓库，例如（Nexus, Archiva），但是，有时候，你可能没有时间去接触公司的基础架构，比方说，你在一家PHP公司做android开发，还有很多事要做。\n\n就只有这些了吗？是的，因为如果你使用过像Github或者Bitbucket这样的Git仓库，当你开始使用Maven库的时候将会如鱼得水。很棒，是不是？\n\n[Chris Banes](https://twitter.com/chrisbanes)  曾经写过[一篇介绍使用Gradle将项目发布到Maven上的文章](https://chris.banes.me/2013/08/27/pushing-aars-to-maven-central/) 。\n\n我们来看一下究竟是怎么做的吧！\n\n###发布到仓库\n\nNote : 如果你只使用一个build.gradle文件，你可以在上面设置任何东西，或者，也可以将他们写入不同的文件，然后用下面一段代码帮助你部署。\n```\napply from: '<path-to-your-file>'\n```\n首先要使用Maven插件\n```\napply plugin: 'maven'\n```\n\n然后配置相应的信息（下面是上传到Bitbucket的例子，大写的地方是你项目中对应的属性）\n\n```\nuploadArchives {\n    configuration = configurations.archives\n    repositories.mavenDeployer {\n        pom.groupId = GROUP\n        pom.artifactId = POM_ARTIFACT_ID\n        pom.version = VERSION_NAME\n        configuration = configurations.deployerJar\n        repository(url: \"git:releases://git@bitbucket.org:<bitbucket-username>/<your-repo>.git\")\n        snapshotRepository(url: \"git:snapshots://git@bitbucket.org:<bitbucket-username>/<your-repo>.git\")\n        pom.project {\n            name POM_NAME\n            packaging POM_PACKAGING\n            description POM_DESCRIPTION\n            url POM_URL\n            scm {\n                url POM_SCM_URL\n                connection POM_SCM_CONNECTION\n                developerConnection POM_SCM_DEV_CONNECTION\n            }\n            licenses {\n                license {\n                    name POM_LICENCE_NAME\n                    url POM_LICENCE_URL\n                    distribution POM_LICENCE_DIST\n                }\n            }\n            developers {\n                developer {\n                    id POM_DEVELOPER_ID\n                    name POM_DEVELOPER_NAME\n                    email POM_DEVELOPER_EMAIL\n                }\n            }\n        }\n    }\n}\n```\n当然，你可以发现deployerJar，我们也需要设置它\n\n首先，我们需要添加依赖Synergian Wagon-Git:\n```\nallprojects {\n    repositories {\n        mavenCentral()\n        maven { url \"https://raw.github.com/synergian/wagon-git/releases\"}\n    }\n}\n```\n&\n\n```\ndependencies {\n    deployerJar \"ar.com.synergian:wagon-git:0.2.3\"\n}\n```\n\n然后，下一步需要说明如下配置\n```\nconfigurations { \n    deployerJar\n}\n```\n现在，你可以部署了：\n```\n$ gradle [clean build] uploadArchives\n```\n\nAnd … you’re done !\n你完成了！\n\n###获取你的lib !\n\n\n当然，现在你可以发布你的库（不论私有与否）托管到Bitbucket上（或其他）的对应仓库上，这很容易做到：\n```\nallprojects {\n    repositories {\n        mavenCentral()\n        maven {\n            url \"https://api.bitbucket.org/1.0/repositories/<bitbucket_username>/<bitbucket-repository>/raw/snapshots\"\n            credentials {\n                username REPOSITORY_USERNAME\n                password REPOSITORY_PASSWORD\n            }\n        }\n        maven {\n            url \"https://api.bitbucket.org/1.0/repositories/<bitbucket_username>/<bitbucket-repository>/raw/releases\"\n            credentials {\n                username REPOSITORY_USERNAME\n                password REPOSITORY_PASSWORD\n            }\n        }\n    }\n}\n```\n\n当然，最好不要在那些需要提交的build.gradle文件里设置您的凭据，但是，你可以在你的gradle.properties设置。其中最好的办法是将是有一个专门的账户来获取你的库，他只有库的读取权限。顺便说一下，发现证书只有在使用私有库的时候才会用到。\n\n然后，像平常一样添加依赖，就可以使用你的库了：\n\n```\ndependencies {\n      compile \"GROUP-ID:ARTIFACT-ID:VERSION\"\n}\n```\n你会在Github上找到deploy.gradle文件。\n\n就是这样;-)"
  },
  {
    "path": "issue-27/开发你自己的Android授权管理器.md",
    "content": "开发你自己的Android 授权管理器\n---\n\n> * 原文链接 : [Write your own Android Authenticator](http://blog.udinic.com/2013/04/24/write-your-own-android-authenticator)\n* 原文作者 : [UDI COHEN](http://blog.udinic.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [kevinhong](https://github.com/kevinhong) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n\n\n\n18个月之前，我在开发Any.DD同步系统时，打算使用安卓提供的``AccountManager`` API实现认证的相关功能并存储用户秘钥。当我用它来访问Google账号时，一切都非常简单，所以我想Any.DD就用这个API来做吧。确实，使用SyncAdapter进行同步操作进展很顺利，看起来的确是一个完美的方案。但它的问题也随之出现——没有好的文档，开发者社区也没能提供太多可供参考的经验，而我们也没有太多时间来研究这个“无人之地“引发的各种问题。所以当时决定使用其他方案。\n\n但是，今非昔比...\n\n\n因为一个最近着手的项目，我最近又开始研究相关功能，我突然发现这方面知识的丰富程度有了巨大的提升。包括Android.com上的更好的文档，外面的教程（  [教程1][1]  [教程2][2]）也逐渐丰富了起来。让我们了解到声名狼藉的``AccountManager``的神秘之处。坊间传闻的用来创建个人账户的方式，我几乎全都读了。\n[1]: http://www.finalconcept.com.au/article/view/android-account-manager-step-by-step-2        \"教程1\" \n[2]: http://www.c99.org/2010/01/23/writing-an-android-sync-provider-part-1/        \"教程2\" \n\n但是，好像还是缺点什么。\n\n我感觉整个流程并非尽知，有些部分并没有足够清楚。所以我决定用我的方法调查它——就像我平时想要了解一件事情所用的方法一样——用“杰克鲍尔“的方式。我发表了这篇深入调查后的结论性文章，包含了所有这个服务所能提供的功能和一些我觉得重要的需要发掘的细节。后面，我还会贴出一篇关于“SyncAdapter“的文章，所以如果读者感兴趣，我建议读者通过RSS或Twitter来订阅（我的博客）。我还是比较了解这方面的诸多细节，不仅仅是教程提供的简单功能。但如果我遗漏了什么，请在评论中指出。\n\n## 为什么选择 Account Manager?\n\n为什么？\n\n为什么不是写一个简单的登录表单，实现一个提交按钮，发送（post）所有信息到服务器，然后服务器返回一个鉴权令牌（auth token）？原因在于有很多（与用户鉴权相关的）附加功能和小细节你未必能考虑周全。这些容易被开发者忽略的小细节可能导致用户重新登录，或者被“100000个用户才会出现一次，无所谓“的声音 忽略掉。用户如果在另外一个客户端修改密码该如何处理？auth-token的过期判断呢？是要运行一个没有用户交互UI的后台服务吗？\n想要用户登录一次，相关APP就可以自动登录的便利吗？（就像Google的APP那样）\n\n读这篇文章之前或许让你感觉有些东西太复杂，但其实不是。对于绝大多数应用场景来说， Account Manager都简化了登录过程。而且我也给你提供了代码样例，还有什么理由不用呢？\n\n好吧，让我们来看看（使用 ``AccountManager``）都有哪些好处：\n好处：标准的用户鉴权方式；为开发者简化了登录的流程；处理访问拒绝的场景；可以为一个账户处理不同类型的访问令牌（如：只读、全权限）；轻松的在不同程序间共享令牌；有如Sync Adapter这样的后台处理的良好支持；并且，在手机的Setting界面中有一个很酷的入口：\n\n![alt text](http://blog.udinic.com/assets/media/images/2013-04-24-write-your-own-android-authenticator/accounts2.png \"Title\")\n\n看，妈妈，设置屏幕上有我的\"名纸\"！\n\n缺陷：需要学习它！但是，嗨，这不就是你读此文的目的吗？\n\n要实现这些功能，需要下面几步：\n1. 创建``Authenticator``，这是所有操作的核心\n2. 创建若干``Activity``，用户可以在其中输入验证需要的信息\n3. 创建``Service``，通过``Service``我们可以与``Authenticator``进行交互\n\n首先，（来看）一些概念。\n\n## Authenti..啥?\n\n**授权令牌 (Authentication Token，[auth-token](http://en.wikipedia.org/wiki/Security_token))** -  是由服务器提供的一个临时的访问令牌。所有需要识别用户的请求，在发送到服务器时都要带着这个令牌。在这篇文章中，我们使用[OAuth2](http://en.wikipedia.org/wiki/OAuth)，它也是现在最为流行的方法。\n\n**授权服务器** - 用来管理所有用户的服务器。它将会为登录到服务器的用户生成授权令牌（auth-token），并且校验所有的用户请求（是否合法）。授权令牌有时间限制，过期后将时效。\n\n**AccountManager** - 管理设备上的所有账户，也是这项功能的核心。App从``AccountManager``获得auth-token，它也将决定是否该打开登录、创建用户的``Activity``，或者从之前的请求中返回一个已经存储好的auth-token。``AccountManager``了解不同场景下该调用何种操作。\n\n**AccountAuthenticator** - 是一个为具体账户类型提供鉴权处理过程的组件。``AccountManager``查找合适的``AccountAuthenticator``，与其通信，并根据账户类型执行所有动作。``AccountAuthenticator``知道哪个``Activity``用来让用户输入登录信息，也知道服务器上次返回的auth-token在哪里存储。在一个账户类型下，多个不同的服务也会共用同一个``AccountAuthenticator``。比如Google的``AccountAuthenticator``为GMail提供认证服务，也为其他的Google程序，如：Google Calendar和Google Drive提供授权服务。\n\n**AccountAuthenticatorActivity** - “登录／创建用户“``Activity``的基类，当用户需要认证的时候，``authenticato``r调用会这个``Activity``。这个``Activity``负责用户登录或用户创建过程，并将auth-token返回给\n``authenticator``。\n\n当你的App需要auth-token时，只需调用 ``AccountManager#getAuthToken()``。``AccountManager``将负责一切必须的步骤直到给你拿到auth-token。Google提供了一个流程图展现了整个过程：\n\n\n![Google-``AccountManager``-Process-Daigram](http://blog.udinic.com/assets/media/images/2013-04-24-write-your-own-android-authenticator/oauth_dance1.png \"Title\")\n\n这图看起来有点繁琐，但其实它很简单。我将通过用户首次在设备上登陆的场景来进行解释。\n\n用户首次登陆时\n>- App向``AccountManager``请求auth-token。\n>- ``AccountManager`` 询问与其关联的 ``AccountAuthenticator`` 是否保存了有效的token\n>- 由于目前还没有（用户还没有登陆嘛），他将调用 ``AccountAuthenticatorActivity ``来让用户登录。\n>- 用户正常登陆，服务器返回了auth-token。\n>- ``AccountManager``存储了auth-token以备将来使用。\n>- App获得了它想要的auth-token\n\n皆大欢喜！\n\n在用户已经登陆的情况下，上述的第二步将直接返回auth-token。你可以在[这里](http://developer.android.com/training/id-auth/authenticate.html)获得更多如何使用OAuth2进行认证的文章。\n\n现在，我们已经了解了基础知识。现在来看看如何建立一个自有账户类型的authenticator。\n\n##建立我们自己的Authenticator\n\n\n如前文所述， Account Authenticator 由``AccountManager``管理并满足账户相关的所有任务：存储auth-token；展现账户登录屏幕；处理服务器的用户登录。\n\n建立我们自己的``Authenticator``需要继承Abstract``AccountAuthenticator``并实现一些方法。我们现在来关注其中两个主要方法：\n\n###addAccount\n\n当用户打算登录并在一个设备上新建账户时，会调用这个方法。\n\n我们需要返回一个Bundle，其中包含一个会启动我们自己的_``AccountAuthenticatorActivity``（稍后解释）的Intent，这个方法在app通过调用``AccountManager#addAccount()`` (需要特殊权限)时被调用。或者在手机设置   中，用户点击“添加新用户“时被调用，即： \n\n\n![SettingScreen](http://blog.udinic.com/assets/media/images/2013-04-24-write-your-own-android-authenticator/account_add_from_setting.png \"Title\")\n\n例如:\n``` Java\n@Override\npublic Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {\n    final Intent intent = new Intent(mContext, AuthenticatorActivity.class);\n    intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);\n    intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);\n    intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);\n    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);\n    final Bundle bundle = new Bundle();\n    bundle.putParcelable(AccountManager.KEY_INTENT, intent);\n    return bundle;\n}\n``` \n\n\n\n\n###getAuthToken\n\n如上面的流程图所示，根据`account type`获取之前成功登录后存储在这台设备上的`auth-token`。如果auth-token不存在，将会提示用户登录。在成功登陆之后，请求auth-token的app会获取到它等待已久的auth-token。为了完成这个过程，我们应该通过 ``AccountManager#peekAuthToken()``来检查``AccountManager``是否已经存在一个有效的auth-token。如果没有，我们应该返回与``addAccount()``相同的结果。\n\n\n``` Java\n\n@Override\npublic Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {\n\n    // Extract the username and password from the Account Manager, and ask\n    // the server for an appropriate AuthToken.\n    final AccountManager am = AccountManager.get(mContext);\n\n    String authToken = am.peekAuthToken(account, authTokenType);\n\n    // Lets give another try to authenticate the user\n    if (TextUtils.isEmpty(authToken)) {\n        final String password = am.getPassword(account);\n        if (password != null) {\n            authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);\n        }\n    }\n\n    // If we get an authToken - we return it\n    if (!TextUtils.isEmpty(authToken)) {\n        final Bundle result = new Bundle();\n        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);\n        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);\n        result.putString(AccountManager.KEY_AUTHTOKEN, authToken);\n        return result;\n    }\n\n    // If we get here, then we couldn't access the user's password - so we\n    // need to re-prompt them for their credentials. We do that by creating\n    // an intent to display our AuthenticatorActivity.\n    final Intent intent = new Intent(mContext, AuthenticatorActivity.class);\n    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);\n    intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);\n    intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);\n    final Bundle bundle = new Bundle();\n    bundle.putParcelable(AccountManager.KEY_INTENT, intent);\n    return bundle;\n}\n\n```\n\n如果我们通过此方法获得的auth-token已经无效了，比如过期了或者用户从其他客户端修改了密码。我们应该调用``AccountManager#invalidateAuthToken()``使当前存储在``AccountManager``的auth-token失效，并调用``getAuthToken() ``再次请求auth-token。再次调用 ``getAuthToken() ``时会尝试使用之前存储的密码进行登陆，如果失败，用户将必须再次输入登陆信息。\n\n所以，用户要在哪输入验证信息？这就是````AccountAuthenticatorActivity``了。\n\n##创建Activity\n\n[AccountAuthenticatorActivity](http://developer.android.com/reference/android/accounts/AccountAuthenticatorActivity.html)是整个过程中唯一直接与用户交互的``Activity``。\n\n``Authenticator``首先调用这个``Activity``，此``Activity``将展现一个用户登录表单，发送到服务器鉴权用户，并将结果传给``authenticator``。我们继承``AccountAuthenticatorActivity``，不仅要实现常规Activity的功能，还要实现``setAccountAuthenticatorResult()``方法。此方法负责将鉴权过程的结果发送给``Authenticator``。此方法也为我们省掉了与``Authenticator``直接交互。\n\n我在我的``Activity``中构建了一个简单的用户名／密码表单。你可以使用Android官方网站上建议使用的登录``Activity``模版，提交时，我进行了以下操作：\n\n```Java\npublic void submit() {\n    final String userName = ((TextView) findViewById(R.id.accountName)).getText().toString();\n    final String userPass = ((TextView) findViewById(R.id.accountPassword)).getText().toString();\n    new AsyncTask<Void, Void, Intent>() {\n        @Override\n        protected Intent doInBackground(Void... params) {\n            String authtoken = sServerAuthenticate.userSignIn(userName, userPass, mAuthTokenType);\n            final Intent res = new Intent();\n            res.putExtra(AccountManager.KEY_ACCOUNT_NAME, userName);\n            res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);\n            res.putExtra(AccountManager.KEY_AUTHTOKEN, authtoken);\n            res.putExtra(PARAM_USER_PASS, userPass);\n            return res;\n        }\n        @Override\n        protected void onPostExecute(Intent intent) {\n            finishLogin(intent);\n        }\n    }.execute();\n}\n```\n\n``sServerAuthenticate``是与服务器进行认证的接口，我实现了了其中例如``userSignIn``（用户登录）和``userSignUp``（用户注册）的方法，这些方法会在登录成功时获得服务器返回的auth-token。\n\n``mAuthTokenType``是我从服务器请求的令牌的类型。我可以让服务器给我不同的令牌例如只读或全访问，或者在相同的账户下的不同服务。一个好的列子是Google‘账号，它的令牌类型包括：“_Manage your calendars”（管理日历）, “Manage your _tasks”（管理任务）, “View your calendars”（查看日历）等等。在这个列子中，我不会为不同类型的令牌区分不同的操作。\n\n\n完成后，调用 finishLogin():\n\n```Java\nprivate void finishLogin(Intent intent) {\n    String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);\n    String accountPassword = intent.getStringExtra(PARAM_USER_PASS);\n    final Account account = new Account(accountName, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));\n    if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) {\n        String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);\n        String authtokenType = mAuthTokenType;\n        // Creating the account on the device and setting the auth token we got\n        // (Not setting the auth token will cause another call to the server to authenticate the user)\n        mAccountManager.addAccountExplicitly(account, accountPassword, null);\n        mAccountManager.setAuthToken(account, authtokenType, authtoken);\n    } else {\n        mAccountManager.setPassword(account, accountPassword);\n    }\n    setAccountAuthenticatorResult(intent.getExtras());\n    setResult(RESULT_OK, intent);\n    finish();\n}\n\n```\n\n通过上面的方法我们获得了一个全新的auth-token，具体细节如下：\n\n1. 在这个案例中，``AccountManager``已经存在了一条记录，它是一个已经失效了的auth-token。新的auth-token会替代原有的，此时你不需要做任何操作。但如果用户的密码已经修改，你需要向``AccountManager``更新用户密码，就像上面代码中实现的那样。\n\n2. 添加了一个新的账户到设备 —— 这是有技巧的部分。当新账户创建时，auth-token并没有立刻保存到``AccountManager``，你需要显示的设置auth-token。这也是我在添加完账户之后，又明确的设置auth-token的原因。如果设置失败了，``AccountManager``将会再到服务器执行获取auth-token的过程，这时getAuthToken会被调用，并再次进行用户鉴权。\n\n注意：addAccountExplicitly() 的第三个参数，是用户数据Bundle，它可以用在``AccountManager``保存其他认证信息的同时存储一些自定义信息，比如你的服务的API Key。当然这些信息也可以通过``setUserData()``设置。\n\n在Activity完成登陆之后，我们已经为``AccountManager``建立了我们的账户。最后调用 ``setAccountAuthenticatorResult() ``将信息传回给``Authenticator``。\n\n现在一切流程准备就绪，那谁来启动这个过程呢？（其他应用）如何来访问它？我们需要让我们的``Authenticator``对其他想使用它的应用可用，因此我们需要让``Authenticator``在后台运行（还可以调用登录屏幕），使用``Service``是一个明显的选择。\n\n##创建Service\n\nService非常简单。\n\n我们要做的，是让其他的进程与我们的服务绑定，并于``Authenticator``交互。幸运的是，``Authenticator``的父类``AbstractAccountAuthenticator``提供了``getIBinder()`` 方法，该方法返回了一个``IBInder``实现。我们的服务需要在``onBind()``方法中调用它。这个基本的实现保证了其他进程请求``Authenticator``时进行适当的操作（原文为：“调用合适的方法“。译者注）。如果读者想了解其中的细节，可以看看``Transport``，一个``AbstractAccountAuthenticator``的内部类，并了解关于AIDL——进程间通信的一些知识。\n\n现在我们的服务是这样的：\n\n\n``` JAVA\npublic class UdinicAuthenticatorService extends Service {\n    @Override\n    public IBinder onBind(Intent intent) {\n        UdinicAuthenticator authenticator = new UdinicAuthenticator(this);\n        return authenticator.getIBinder();\n    }\n}\n```\n\n..and on the manifest we need to add our service with the\n\n在manifest文件中，需要对Service声明\n\n``` XML\n<service android:name=\".authentication.UdinicAuthenticatorService\">\n    <intent-filter>\n        <action android:name=\"android.accounts.AccountAuthenticator\" />\n    </intent-filter>\n    <meta-data android:name=\"android.accounts.AccountAuthenticator\"\n               android:resource=\"@xml/authenticator\" />\n</service>\n```\n\n\n很简单，是吧？\n作为资源引用的``authenticator.xml``用来定义``Authenticator``用到的一些属性。\n\n``` XML\n<account-authenticator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                       android:accountType=\"com.udinic.auth_example\"\n                       android:icon=\"@drawable/ic_udinic\"\n                       android:smallIcon=\"@drawable/ic_udinic\"\n                       android:label=\"@string/label\"\n                       android:accountPreferences=\"@xml/prefs\"/>\n``` \n\n\n\n让我们来解释一下：\n>- **accountType（账户类型）** 是一个独一无二的名字，用来识别我们的账户类别。当其他app要通过我们的应用进行鉴权操作时。它需要明确知道这个名字来使用``AccountManager``。\n\n>- **icon and smallIcon（图标和小图标）** 是在设备的Setting画面中，账户条目显示的图标。在账户确认画面（稍后会解释），它也会出现。\n\n>- **label（标签）** 是在设备Setting画面中显示的代表我们账户的的字符串。\n\n>- **accountPreferences（账户偏好）** 是一个偏好XML的引用。它将在通过设备的Setting画面中访问账户偏好时展现，它允许用户更好的控制庄户。作为例子，你可以看看Google及Dropbox的账户偏好画面，里面包含了一些可供调整的项。我的例子如下：\n\n![SettingScreen](http://blog.udinic.com/assets/media/images/2013-04-24-write-your-own-android-authenticator/account_prefs.png \"Title\")\n\n##你需要了解的其他特性\n\n在我调查的过程中，我发现了一些有意思的场景。为了让你使用相关API时不致想破头，我把它分享出来。\n\n1. 检查账户的有效性 - 如果你打算为一个账户获取auth-token并自己存储起来，你应该先调用``AccountManager#getAccounts*()``方法来看看该账户是否存在。这里引用``AccountManager``的官方文档\n\n“为一个设备上不存在的账户请求auth-token，将会导致未定义的失败。“\n\n在我这，这个“未定义的失败“虽热调用了登录画面，但当我输入认证信息后却没有任何反应，所以你得注意一下。\n\n1. 先进先服务 - 如果你复制了authenticator的相关代码到你的两个应用，二者具备相同的逻辑，但在每个应用中修改了自己的登录画面。在这种情况下，无论哪个应用需要请求auth-token，都会调用先安装的app的authenticator。如果你卸载了第一个应用，第二个应用的authenticator才会起作用（因为它是唯一有个了）。克服这个问题的一个技巧，是将不同的登陆页放到同一个authenticator中，并在调用``addAccount()`` 时，把各自的设计需求通过addAccountOptions 的参数传过去。\n\n2. 为了安全，小心共享 - 如果你打算获取其他应用的``authenticator``生成的auth-token，并且该应用与你的应用具有不同的签名秘钥，用户必须显示的同意这个操作。用户将会看到如下画面：\n\n![SettingScreen](http://blog.udinic.com/assets/media/images/2013-04-24-write-your-own-android-authenticator/access_request.png \"Title\")\n\n“Full access to..”字符串是通过我们的``Authenticator``的`` _getAuthTokenLabel()``方法获得。你可以为每一个auth-token类型指定一个标签，以便在类似的场景下对用户更加友好。\n\n1, 存储密码 - ``AccountManager``并没有使用加密方法存储密码。所有的密码都明文存储。你不能对其他的``Authenticator``调用``peekAuthToken()`` 方法（获得其他``Authenticator``的auth-token）（会出现“caller uid X is different than the authenticator’s uid”错误），但root权限或adb命令却可以得到。在示例代码中，存储明文密码是为了让已经过期的token自动登录更方便。在大多数场合中，我选择更加安全的方式，但有的时候，为用户的错误牺牲便利性是不值得的——如果有人获得了root权限并能在设备上执行adb命令，那与获得用户的“最高分“比起来，他可以做到更大的危害。\n\n\n##接下来？\n\n\n现在，你已经对这个很棒的服务比较熟悉了。你可以在Google Play上[下载我开发的样例程序](https://play.google.com/store/apps/details?id=com.udinic.accounts_example)。它会在你的设备创建“Udinic account”类型的账户，验证则会通过Parse.com账户来进行。样例应用提供了下面几项功能：\n\n![SettingScreen](http://blog.udinic.com/assets/media/images/2013-04-24-write-your-own-android-authenticator/sample-app.png \"Title\")\n\n**getAuthToken**按钮首先会查询设备上是否有“Udinic”类型的账户。如果有，它通过调用``AccountManager#getAuthToken()``来返回token。如果有多个token，它将弹出一个对话框让用户选择打算使用哪一个。\n\n\n**getAuthTokenByFeatures** 调用了``AccountManager``提供的一个很棒很方便的同名方法，它会为你完成所有的工作。它也会查询``AccountManager``中是否包含目标类型“Udinic”。它的行为遵循以下步骤：\n>- 没有账户时: 调用``_ addAccount()_ ``让用户创建一个账户。此后，自动调用``getAuthToken()``获取token。\n>- 存在账户时：获取auth-token。\n>- 有两个账户或更多时：创建一个账户选择对话框，并返回用户所选账户的token。\n\n\n如果你打算让token失效，你可以用``invalidateAuthToken``按钮。注意：Udinic authenticator知道如何恢复失效token，就像之前示例代码中所展示的那样，通过``getAuthToken()``方法。那意味着，在让token失效后，``getAuthToken``按钮仍然可以返回token，但只在它向服务器请求成功之后。你可以在LogCat中通过查看网络状态来确认。删除帐户只能通过设备的Setting菜单。\n\n你可以在[Github](https://github.com/Udinic/AccountAuthenticator)上下载相关源代码。里面包含了2个样例应用，所以你可以尝试下2个应用之间共享相同的``Authenticator``。比如：使用不同的签名密钥打包程序，一个应用请求由另一个应用创建的auth-token。你也可以尝试创建一个``authenticator``的apklib（apk库），并在不同的应用中重用它。如果你有意见或建议，别犹豫直接在文章后面指出或在Github上提出PR吧。\n\n"
  },
  {
    "path": "issue-28/数据绑定(Data Binding)-Part1.md",
    "content": "#数据绑定(Data Binding)-Part1\n---\n\n> * 原文链接 : [Data Binding - Part 1](https://blog.stylingandroid.com/data-binding-part-1/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [desmond1121](https://github.com/desmond1121) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n\n2015年的Google I/O大会发布了很多新的Android库和工具，Data Binding就是其中之一。在本系列文章中，我们会探索它的强大之处。\n\n**值得注意的是：我写这篇文章的时候，Data Binding库正在测试中，所以很多API可能会在发布的时候有所改动。**\n\nData Binding库提供了一个链接待显示数据与UI组件的机制，将原本非常被动的数据变成某种形式的数据源。在本篇文章中我们使用一个非常简单的Twitter客户端作为例子，将Twitter API与Data Binding一起使用。我不会在这里介绍API以及App设计，我仅仅是使用Twitter4J库抽取了你的Twitter主页上50条按时间排序的信息，并将他们展示在`RecyclerView`中。所有的代码都是公开发布的，你可以放心地使用它理解它。这里面最有意思的就是其中的数据及它们与View绑定的过程。\n\n我们来看看这篇例子：\n\n    public class Status {\n    \n        private final String name;\n        private final String screenName;\n        private final String text;\n        private final String imageUrl;\n    \n        public Status(@NonNull String name, @NonNull String screenName, @NonNull String text, @NonNull String imageUrl) {\n            this.name = name;\n            this.screenName = screenName;\n            this.text = text;\n            this.imageUrl = imageUrl;\n        }\n    \n        public String getName() {\n            return name;\n        }\n    \n        public String getScreenName() {\n            return screenName;\n        }\n    \n        public String getText() {\n            return text;\n        }\n    \n        public String getImageUrl() {\n            return imageUrl;\n        }\n    }\n\n这个类维持了几个需要展示给用户的基本元素，每一个在`RecyclerView`中的单位都会绑定一个`Status`对象。它里面的信息可以从`twitter4j.Status`（由Twitter4j的API获取）中得到，所以我们还要创建一个将`twitter4j.Status`转化为`Status`的类：\n\n    public class StatusMarshaller {\n    \n        public List<Status> marshall(List<twitter4j.Status> statuses) {\n            List<Status> newStatuses = new ArrayList<>(statuses.size());\n            for (twitter4j.Status status : statuses) {\n                newStatuses.add(marshall(status));\n            }\n            return newStatuses;\n        }\n    \n        private Status marshall(twitter4j.Status status) {\n            User user = status.getUser();\n            return new Status(user.getName(), user.getScreenName(), status.getText(), user.getBiggerProfileImageURL());\n        }\n    }\n\n这里没有用到任何技巧，只是一个简单的Java操作，跟Data Binding无关，甚至跟Android也无关。\n\n不过需要指出的是，我们本可以直接将View与`twitter4j.Status`绑定在一起，这样无疑效率会更高。但是Data Binding库使用了MVVM(Model-View-ViewModel)设计模式 - Model是`twitter4j.Status`，View是UI组件，ViewModel是我们的`Status`对象。ViewModel代表着专门为View设计的一个数据结构，它与View的适配性比Model更好。虽然Model与ViewModel很像，在目前你可能还感觉不出他们的区别，但是随着我们一步步深入下去，我相信你会明白这样设计的深意。\n\n接下来我们看一下`RecyclerView`的Adapter是怎么设计的：\n\n    public class StatusAdapter extends RecyclerView.Adapter<StatusViewHolder> {\n        private final List<Status> statuses;\n        private final StatusMarshaller marshaller;\n    \n        public static StatusAdapter newInstance() {\n            List<Status> statuses = new ArrayList<>();\n            StatusMarshaller marshaller = new StatusMarshaller();\n            return new StatusAdapter(statuses, marshaller);\n        }\n    \n        StatusAdapter(List<Status> statuses, StatusMarshaller marshaller) {\n            this.statuses = statuses;\n            this.marshaller = marshaller;\n        }\n    \n        @Override\n        public StatusViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n            Context context = parent.getContext();\n            LayoutInflater inflater = LayoutInflater.from(context);\n            View statusContainer = inflater.inflate(R.layout.status_item, parent, false);\n            return new StatusViewHolder(statusContainer);\n        }\n    \n        @Override\n        public void onBindViewHolder(StatusViewHolder holder, int position) {\n            Status status = statuses.get(position);\n            holder.bind(status);\n        }\n    \n        @Override\n        public int getItemCount() {\n            return statuses.size();\n        }\n    \n        public void setStatuses(List<twitter4j.Status> statuses) {\n            this.statuses.clear();\n            this.statuses.addAll(marshaller.marshall(statuses));\n            notifyDataSetChanged();\n        }\n    \n    }\n\n终于看到了跟Android有关的地方了吧，实际上没什么特殊的地方——这只是一个基本的`RecyclerView.Adapter`而已，跟Data Binding其实关系不大。这里面唯一跟MVVM有关系的就是在`setStatuses()`中将`twitter4j.Status`转换成`Status`。我们马上将会在StatusViewHolder中看到怎么进行数据绑定，首先我们来看看layout是怎么定义的。\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    \n      <data>\n        <import type=\"android.view.View\" />\n        <variable\n          name=\"status\"\n          type=\"com.stylingandroid.databinding.data.Status\" />\n      </data>\n    \n      <RelativeLayout\n        android:id=\"@+id/status_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n    \n        <ImageView\n          android:id=\"@+id/status_avatar\"\n          android:layout_width=\"64dp\"\n          android:layout_height=\"64dp\"\n          android:layout_alignParentLeft=\"true\"\n          android:layout_alignParentStart=\"true\"\n          android:layout_alignParentTop=\"true\"\n          android:layout_margin=\"8dip\"\n          android:contentDescription=\"@null\" />\n    \n        <TextView\n          android:id=\"@+id/status_name\"\n          style=\"@style/Status.Name\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignParentTop=\"true\"\n          android:layout_marginTop=\"8dip\"\n          android:layout_toEndOf=\"@id/status_avatar\"\n          android:layout_toRightOf=\"@id/status_avatar\"\n          android:text=\"@{status.name}\" />\n    \n        <TextView\n          android:id=\"@+id/status_screen_name\"\n          style=\"@style/Status.ScreenName\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignBaseline=\"@id/status_name\"\n          android:layout_marginLeft=\"4dip\"\n          android:layout_marginStart=\"4dip\"\n          android:layout_toEndOf=\"@id/status_name\"\n          android:layout_toRightOf=\"@id/status_name\"\n          android:text=\"@{&quot;@&quot; + status.screenName}\" />\n    \n        <TextView\n          android:id=\"@+id/status_text\"\n          style=\"@style/Status.Text\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignLeft=\"@id/status_name\"\n          android:layout_alignParentEnd=\"true\"\n          android:layout_alignParentRight=\"true\"\n          android:layout_alignStart=\"@id/status_name\"\n          android:layout_below=\"@id/status_name\"\n          android:maxLines=\"2\"\n          android:singleLine=\"false\"\n          android:text=\"@{status.text}\" />\n    \n      </RelativeLayout>\n    </layout>\n\n这部分代码是Data Binding运作的核心。其中id为`status_container`的`RelativeLayout`是一个普通的layout控件，但是它的父控件`<layout>`就不是那么熟悉了。`<layout>`是Data Binding库的一个组件，它含有一个`<data>`子组件说明了需要绑定的数据对象（在此处为`Status`），然后我们就可以在此layout中引用该数据对象。\n\n在此布局中TextView的`android:text`属性值可能跟你正常见到样子的不大一样-它们其实都使用了`Status`中的getter。使用`@{}`包装说明这是一个Data Binding表达式，`status.name`等同于Java中的`status.getName()`。这是Data Binding工作的核心，但是这只是冰山一角，它还有更多有趣的功能值得我们探索。\n\n你看id为`status_screen_name`的TextView，它的text设置为`@{&quot;@&quot; + status.screenName}`。你可能看到觉得很困惑，实际上`&quot;@&quot;`就代表着@，用`&quot;`将它包装起来是为了防止@被xml转义。这个语句告诉我们Data Binding语句是很强大的，我们会进一步挖掘它的强大之处。\n\n在定义完layout之后，需要将它和实际数据对象结合，这也是`StatusViewHolder`做的事：\n\n    public class StatusViewHolder extends RecyclerView.ViewHolder {\n        private StatusItemBinding binding;\n    \n        public StatusViewHolder(View itemView) {\n            super(itemView);\n            binding = DataBindingUtil.bind(itemView);\n        }\n    \n        public void bind(Status status) {\n            binding.setStatus(status);\n        }\n    \n    }\n\n它比正常使用的`ViewHolder`（通常用于维持子View对象）更简单一点，在`bind()`方法中将对象赋予它绑定的layout。首先要注意到我们使用了`StatusItemBinding`，可以通过`DataBindingUtil.bind()`函数获取到它，这个函数及`StatusItenBinding`类都是Data Binding库生成的。\n\n在下一章中我们会讨论更多的细节，不过此处我们已经有一个基本的应用能够让你对data binding的作用有一定认识：\n\n![part-1](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/Part11.png)\n\n本文章中的例子在[这里](https://github.com/StylingAndroid/DataBinding/tree/Part1)可以看到。"
  },
  {
    "path": "issue-28/数据绑定(Data Binding)-Part2.md",
    "content": "#数据绑定(Data Binding)-Part2\n---\n\n> * 原文链接 : [Data Binding - Part 2](https://blog.stylingandroid.com/data-binding-part-2/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [desmond1121](https://github.com/desmond1121) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n\n在之前我们做了一个简单的Twitter客户端，但是简单地介绍ViewHolder实现，可能没有充分地让你明白Data Binding的使用方法。那么我们现在就来看看怎么样将Data Binding引入到项目中。\n\n首先我们要在工程目录下的gradle脚本添加依赖：\n     \n    buildscript {\n        repositories {\n            jcenter()\n        }\n        dependencies {\n            classpath 'com.android.tools.build:gradle:1.3.0'\n            classpath \"com.android.databinding:dataBinder:1.0-rc1\"\n        }\n    }\n     \n    allprojects {\n        repositories {\n            jcenter()\n        }\n    }\n\n在要在使用Data Binding的module下的gradle脚本中添加依赖：\n\n    apply plugin: 'com.android.application'\n    apply plugin: 'com.android.databinding'\n     \n    android {\n        compileSdkVersion 23\n        buildToolsVersion \"23.0.0\"\n     \n        defaultConfig {\n            applicationId \"com.stylingandroid.databinding\"\n            minSdkVersion 7\n            targetSdkVersion 23\n            versionCode 1\n            versionName \"1.0\"\n     \n            buildConfigField 'String', 'TWITTER_CONSUMER_KEY', \"\\\"${twitterConsumerKey}\\\"\"\n            buildConfigField 'String', 'TWITTER_CONSUMER_SECRET', \"\\\"${twitterConsumerSecret}\\\"\"\n            buildConfigField 'String', 'TWITTER_ACCESS_KEY', \"\\\"${twitterAccessKey}\\\"\"\n            buildConfigField 'String', 'TWITTER_ACCESS_SECRET', \"\\\"${twitterAccessSecret}\\\"\"\n        }\n        buildTypes {\n            release {\n                minifyEnabled false\n                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            }\n        }\n        packagingOptions {\n            exclude 'META-INF/LICENSE.txt'\n        }\n        lintOptions {\n            disable 'InvalidPackage'\n        }\n    }\n     \n    dependencies {\n        compile 'com.android.support:support-annotations:23.0.0'\n        compile 'com.android.support:design:23.0.0'\n        compile 'com.android.support:appcompat-v7:23.0.0'\n        compile 'com.android.support:recyclerview-v7:23.0.0'\n        compile 'org.twitter4j:twitter4j-core:4.0.4'\n        compile 'org.twitter4j:twitter4j-async:4.0.4'\n        compile 'com.github.bumptech.glide:glide:3.6.1'\n    }\n    \n你会发现在后面这个gradle脚本中没有添加data binding依赖——它实际上只是在构建项目的时候附加的工具。\n\n你应该在这个操作方法中领悟到一些东西：实际上Data Binding只是一个代码生成工具，它会在build项目的时候生成一些绑定View和数据对象的模板代码。\n\n在我们的例子中，它解析目标是layout/status_item.xml中的<layout>节点。**根据布局文件的文件名**，它会自动生成一个数据绑定类`StatusItemBinding`，我们在`StatusViewHolder`使用的就是这个神秘的类。\n\n之后Data Binding将<layout>包装去掉，由于在`StatusItemBinding`类中已经解析出了<data>属性，它同时也会移除<data>节点，只剩下`RelativeLayout`。换句话说，它将这个文件变回了普通的XML layout文件，并添加了一个数据绑定类实时更新其中的内容。\n\n我们再重温一下`StatusViewHolder`的代码，现在应该明白一点它的运作机制了吧：\n\n    public class StatusViewHolder extends RecyclerView.ViewHolder {\n        private StatusItemBinding binding;\n     \n        public StatusViewHolder(View itemView) {\n            super(itemView);\n            binding = DataBindingUtil.bind(itemView);\n        }\n     \n        public void bind(Status status) {\n            binding.setStatus(status);\n        }\n     \n    }\n    \n`DataBindingUtil.bind(itemView)`方法会在build的时候自动生成。这时候我们需要做的是在`bind(Status status)`中调用`StatusItemBinding`的setter方法，将我们在<data>中声明的数据对象（`.data.Status`）赋值给它。之后我们要显式地调用`bind(Status status)`方法将数据实体与它绑定。\n\n你可以从我们的例子中初步看出数据绑定的效果：\n\n![part-1](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/Part11.png)\n\n这一切都运作的非常好，就是一个含有三个子view的布局被绑定了，data binding看起来也没为我们省去很多工作，也只是省去了findView与`setText()`两个操作而已。但是它所能做的并不仅仅是这样！可能有人注意到了我们同时从twitter的API中获取了头像的url，layout中我们没有对`ImageView`进行绑定。在下一章中我们将会引入Glide来加载图像，并将它与Data Binding结合起来。\n\n本文章主要用于解释原理，没有添加额外代码。前文中的例子在[这里](https://github.com/StylingAndroid/DataBinding/tree/Part1)可以看到。"
  },
  {
    "path": "issue-28/数据绑定(Data Binding)-Part3.md",
    "content": "#数据绑定(Data Binding)-Part3\n---\n\n> * 原文链接 : [Data Binding - Part 3](https://blog.stylingandroid.com/data-binding-part-3/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [desmond1121](https://github.com/desmond1121) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n\n**勘误：原文中使用ModelView一词，但实际上MVVM是Model-View-ViewModel，故应为ViewModel。**\n\n在之前的文章中，我们使用Data Binding与布局中的`TextView`配合搭建了简单的App，并说到接下来将会加入图片的加载。这会比绑定文字更加麻烦，因为我们从Twitter服务器上拿下来的是URL而不是直接的图片。现在是时候介绍在MVVM模式中Model和ViewModel的区别了。在这个例子中，Model获取的是URL，但是View需要的是一个Bitmap，那么Model就需要转换成ViewModel来适应View的需求。要将他们进行如此细致的区分，是因为从url到Bitmap是一个比较并不简单的过程，需要根据url网络上去获取这个图片的具体内容。这部分的逻辑已经被我们将要使用的第三方图像加载库Glide封装了，不过我们还需要添加一些转换逻辑。\n\n在这了使用Data Binding + RecyclerView还有一个原因。加载一幅网络图片的代价是很大的，如果你将一个List里面所有的图片都进行加载，而不论它是否要显示到屏幕上，这是非常浪费性能的。所以应该只加载要显示到屏幕上的图片，数据绑定也应该只在这种情况下进行。\n\n我在上一篇中说了，我将使用Glide来进行图像加载。通过Glide，你可以使用以下这行简单的代码来实现图像的下载、转码、加载过程：\n\n    Glide.with(context).load(url).into(imageView);\n\n它会自动异步下载图片，并将它显示到`ImageView`上。Glide还能够帮你进行图片缓存等操作，不过我们在此处仅使用它最简单的图片加载功能。\n\n现在看起来我们可以将Data Binding和ImageView结合起来了，这是一个很自然的想法，你可能会这么做：\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    \n      <data>\n    \n        <variable\n          name=\"status\"\n          type=\"com.stylingandroid.databinding.data.Status\" />\n    \n        <variable\n          name=\"glide\"\n          type=\"com.bumptech.glide.Glide\" />\n    \n      </data>\n    \n      <RelativeLayout\n        android:id=\"@+id/status_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n    \n        <!--\n          注意：这个里的Data Binding将不起作用\n        -->\n    \n        <ImageView\n          android:id=\"@+id/status_avatar\"\n          android:layout_width=\"64dp\"\n          android:layout_height=\"64dp\"\n          android:layout_alignParentLeft=\"true\"\n          android:layout_alignParentStart=\"true\"\n          android:layout_alignParentTop=\"true\"\n          android:layout_margin=\"8dip\"\n          android:contentDescription=\"@null\"\n          app:imageUrl=\"@{glide.load(status.imageUrl).into(this)}\" />\n        .\n        .\n        .\n      </RelativeLayout>\n    </layout>\n    \n\n这里实际上有一个问题，我们可以查看一下Data Binding的表达式文档：\n\n    A few operations are missing from the expression syntax that you can use in Java.\n    \n    this\n    super\n    new\n    Explicit generic invocation\n    \n\n你会发现：\n\n>在Data Binding语句中，**`this`符号是不起作用的！**\n\n虽然`this`没法使用了，但是我们可以用**自定义Setter**来替代它。当你希望为一个View定义特殊的Setter时可以这么干。你可以在自定义Setter里面利用Glide将获取的url转化成Bitmap显示到ImageVIew中。\n\n这是一个自定义Setter的例子：\n\n    public final class DataBinder {\n    \n        private DataBinder() {\n            //NO-OP\n        }\n    \n        @BindingAdapter(\"imageUrl\")\n        public static void setImageUrl(ImageView imageView, String url) {\n            Context context = imageView.getContext();\n            Glide.with(context).load(url).into(imageView);\n        }\n    }\n    \n\n以上是一个很简单的工具类，它里面有个函数名为`setImageView()`，以`ImageView`和`String`作为输入参数。不过你应该也注意到了，在这个函数上有一个注解：`@BindingAdapter(\"imageUrl\")`，这个注解的作用是向Data Binding库声明了一个名为imageUrl的自定义Setter。我们不需要再做其他任何配置，这个注解会在编译时就帮我们打理好了一切。在加入这条注解之后，我们就可以在xml中使用它：\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    \n      <data>\n        <variable\n          name=\"status\"\n          type=\"com.stylingandroid.databinding.data.Status\" />\n      </data>\n    \n      <RelativeLayout\n        android:id=\"@+id/status_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n    \n        <ImageView\n          android:id=\"@+id/status_avatar\"\n          android:layout_width=\"64dp\"\n          android:layout_height=\"64dp\"\n          android:layout_alignParentLeft=\"true\"\n          android:layout_alignParentStart=\"true\"\n          android:layout_alignParentTop=\"true\"\n          android:layout_margin=\"8dip\"\n          android:contentDescription=\"@null\"\n          app:imageUrl=\"@{status.imageUrl}\"/>\n        .\n        .\n        .\n      </RelativeLayout>\n    </layout>\n\n这个Data Binding的自定义属性`imageUrl是`在app命名空间下的（也就是res-auto）。它仅仅需要一个参数——URL字符串，它所处的`ImageView`会自动加到自定义Setter中。在<data>中也不需要再做其他改动，甚至也不需要加入Glide实例变量。\n\n这样就完成了我们的图像绑定和加载过程，如下图所示：\n\n![Data Binding 3](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/data%20binding%203.png)\n\n虽然解释了很多，实际上写的代码却很少——几行自定义Setter，再在layout文件中添加两行代码，就足够了！在下一篇文章中我们再看看怎么将Data Binding用在更加有意思的地方。\n\n从我写这篇文章开始到现在已经有很多人利用自定义Setter做了很多有意思的事情。在Droidcon NYC会议上，Roman Nurik向我展示了与本文类似的代码。我的代码灵感来源于官方的Data Binding手册，特别是使用Picasso结合自定义Setter进行图像加载的那部分。看起来使用Glide比Picasso更容易去理解和使用自定义Setter，我相信除了我和Roman还有很多人会这么想，并且都会创作出类似的代码。\n\nLisa Wray利用自定义Setter做了一件很有意思的事情：她利用Data Binding为TextView绑定了字体属性！她的例子完美地展示了自定义Setter可以实现多么灵活的功能！\n\n本文中的例子在[这里](https://github.com/StylingAndroid/DataBinding/tree/Part3)可以看到。"
  },
  {
    "path": "issue-28/数据绑定(Data Binding)-Part4.md",
    "content": "#数据绑定(Data Binding)-Part4\n---\n\n> * 原文链接 : [Data Binding - Part 4](https://blog.stylingandroid.com/data-binding-part-4/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [desmond1121](https://github.com/desmond1121) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n\n为了探索Data Binding的能力，我们现在要利用它来改变View的其他属性。在阅读后面的内容之前你需要了解我们新引入的一个数据概念：转发状态。每一条Twitter都可能是原创或者转发，Twitter API会返回一个twitter4j.Status对象来表示被转发的状态(若发表的状态不是转发则为空)，我们将它加入到自己的数据结构Status中：\n\n    public class Status {\n        private final String name;\n        private final String screenName;\n        private final String text;\n        private final String imageUrl;\n        private final Status quotedStatus;\n     \n        public Status(@NonNull String name, @NonNull String screenName, @NonNull String text, @NonNull String imageUrl, @Nullable Status quotedStatus) {\n            this.name = name;\n            this.screenName = screenName;\n            this.text = text;\n            this.imageUrl = imageUrl;\n            this.quotedStatus = quotedStatus;\n        }\n     \n        public String getName() {\n            return name;\n        }\n     \n        public String getScreenName() {\n            return screenName;\n        }\n     \n        public String getText() {\n            return text;\n        }\n     \n        public String getImageUrl() {\n            return imageUrl;\n        }\n     \n        public boolean hasQuotedStatus() {\n            return quotedStatus != null;\n        }\n    }\n\n我们还加入了`hasQuotedStatus()`这个函数来查看是否此条状态是转发别人状态的。\n\n同时也要将新加入的字段转化逻辑加入`Marshaller`中：\n\n    public class StatusMarshaller {\n        public List<Status> marshall(List<twitter4j.Status> statuses) {\n            List<Status> newStatuses = new ArrayList<>(statuses.size());\n            for (twitter4j.Status status : statuses) {\n                newStatuses.add(marshall(status));\n            }\n            return newStatuses;\n        }\n     \n        private Status marshall(twitter4j.Status status) {\n            User user = status.getUser();\n            Status quotedStatus = null;\n            if (status.getQuotedStatus() != null) {\n                quotedStatus = marshall(status.getQuotedStatus());\n            }\n            return new Status(user.getName(), user.getScreenName(), status.getText(), user.getBiggerProfileImageURL(), quotedStatus);\n        }\n    }\n\n值得注意的是：并不是每条Tweet状态都是转发的。=所以我们需要在存在转发的时候显示转发布局，在原创状态时隐藏转发布局。我们可以根据`Status.hasQuotedStatus()`改变`android:visibility`来实现这个功能：\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n     \n      <data>\n     \n        <import type=\"android.view.View\" />\n     \n        <variable\n          name=\"status\"\n          type=\"com.stylingandroid.databinding.data.Status\" />\n     \n      </data>\n     \n      <RelativeLayout\n        android:id=\"@+id/status_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n     \n        <ImageView\n          android:id=\"@+id/status_avatar\"\n          android:layout_width=\"64dp\"\n          android:layout_height=\"64dp\"\n          android:layout_alignParentLeft=\"true\"\n          android:layout_alignParentStart=\"true\"\n          android:layout_alignParentTop=\"true\"\n          android:layout_margin=\"8dip\"\n          android:contentDescription=\"@null\"\n          app:imageUrl=\"@{status.imageUrl}\" />\n     \n        <TextView\n          android:id=\"@+id/status_name\"\n          style=\"@style/Status.Name\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignParentTop=\"true\"\n          android:layout_marginTop=\"8dip\"\n          android:layout_toEndOf=\"@id/status_avatar\"\n          android:layout_toRightOf=\"@id/status_avatar\"\n          android:text=\"@{status.name}\" />\n     \n        <TextView\n          android:id=\"@+id/status_screen_name\"\n          style=\"@style/Status.ScreenName\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignBaseline=\"@id/status_name\"\n          android:layout_marginLeft=\"4dip\"\n          android:layout_marginStart=\"4dip\"\n          android:layout_toEndOf=\"@id/status_name\"\n          android:layout_toRightOf=\"@id/status_name\"\n          android:text=\"@{&quot;@&quot; + status.screenName}\" />\n     \n        <TextView\n          android:id=\"@+id/status_text\"\n          style=\"@style/Status.Text\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignLeft=\"@id/status_name\"\n          android:layout_alignParentEnd=\"true\"\n          android:layout_alignParentRight=\"true\"\n          android:layout_alignStart=\"@id/status_name\"\n          android:layout_below=\"@id/status_name\"\n          android:maxLines=\"2\"\n          android:singleLine=\"false\"\n          android:text=\"@{status.text}\" />\n     \n        <RelativeLayout\n          android:id=\"@+id/status_quoted\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignLeft=\"@id/status_text\"\n          android:layout_alignStart=\"@id/status_text\"\n          android:layout_below=\"@id/status_text\"\n          android:layout_marginTop=\"8dp\"\n          android:background=\"@color/light_grey\"\n          android:padding=\"8dp\"\n          android:visibility=\"@{status.hasQuotedStatus ? View.VISIBLE : View.GONE}\">\n     \n          <TextView\n            android:id=\"@+id/quoted_tap\"\n            style=\"@style/Status.Name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentStart=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:text=\"@string/tap_to_load\" />\n        </RelativeLayout>\n     \n      </RelativeLayout>\n    </layout>\n\n很简单吧！我们只要在Data Binding语句中通过`hasQuotedStatus()`来判断是否有转发，再设置`android:visibility`即可。由于Data Binding库事先并不知道View类中的常量定义值，所以需要在`<data>`中显式import View类。你可以将它看做类似Java的import语句。\n\n实际上我不喜欢用**三元运算符**，我认为这种表达不利于debug。我个人倾向于使用自定义Setter去实现情景判断的逻辑，至少这样下可以设置断点进行调试。但为了表达Data Binding语言的能力，在这里使用了三元运算符。\n\n**Data Binding语法还可以为View添加事件响应！**为实现这个功能，首先需要定义一个类来处理响应逻辑：\n\n    public class ClickHandler {\n        private final Status status;\n        public ClickHandler(Status status) {\n            this.status = status;\n        }\n        public void onClick(View view) {\n            Snackbar.make(view, \"Item clicked\", Snackbar.LENGTH_LONG).show();\n        }\n    }\n\n它初始化需要传入`Status`。在这里就先使用一个最简单的按键响应作为例子：点击View弹出SnackBar。\n\n我们来看看在layout文件中是怎么将`ClickHandler`与Data Binding配合使用的吧：\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n      <data>\n        <import type=\"android.view.View\" />\n        <variable\n          name=\"status\"\n          type=\"com.stylingandroid.databinding.data.Status\" />\n        <variable\n          name=\"handler\"\n          type=\"com.stylingandroid.databinding.ClickHandler\" />\n      </data>\n\n      <RelativeLayout\n        android:id=\"@+id/status_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n        .\n        .\n        .\n        <RelativeLayout\n          android:id=\"@+id/status_quoted\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignLeft=\"@id/status_text\"\n          android:layout_alignStart=\"@id/status_text\"\n          android:layout_below=\"@id/status_text\"\n          android:layout_marginTop=\"8dp\"\n          android:background=\"@color/light_grey\"\n          android:onClick=\"@{handler.onClick}\"\n          android:padding=\"8dp\"\n          android:visibility=\"@{status.hasQuotedStatus ? View.VISIBLE : View.GONE}\">\n\n          <TextView\n            android:id=\"@+id/quoted_tap\"\n            style=\"@style/Status.Name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentStart=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:text=\"@string/tap_to_load\" />\n        </RelativeLayout>\n      </RelativeLayout>\n    </layout>\n\n你会注意到在`<data>`中引入了一个新的变量`handler`，它代表着一个`ClickHandler`实例。设置`android:onClick`属性为`@{handler.onClick}`后，当该布局被点击时就会调用`ClickHandler.onClick()`。\n\n由于新引入了变量`handler`，所以需要在`StatusViewHolder`中对它进行初始化：\n\n    public class StatusViewHolder extends RecyclerView.ViewHolder {\n        private StatusItemBinding binding;\n        public StatusViewHolder(View itemView) {\n            super(itemView);\n            binding = DataBindingUtil.bind(itemView);\n        }\n        public void bind(Status status) {\n            binding.setStatus(status);\n            binding.setHandler(new ClickHandler(status));\n        }\n    }\n\n现在你会发现，仅仅只有一些发布的状态会显示转发那部分的布局，并且点击转发布局后会弹出Snackbar！你可以从这段视频中看到我们的demo app运行方式：\n\n[视频原始地址(Youtube，需翻墙)](https://www.youtube.com/watch?v=Tawl0E6qtfg&feature=youtu.be)\n\n译者下载了该youtube视频，发布到[优酷](http://v.youku.com/v_show/id_XMTM2MTI1Mzk0NA==.html)上方便读者观看：\n\n<embed src=\"http://player.youku.com/player.php/sid/XMTM2MTI1Mzk0NA==/v.swf\" allowFullScreen=\"true\" quality=\"high\" width=\"480\" height=\"400\" align=\"middle\" allowScriptAccess=\"always\" type=\"application/x-shockwave-flash\"></embed>\n\n这篇文章展示了Data Binding的重要用法：我们可以在View的点击事件中绑定Model，这也是MVVM重要特性之一。为什么这个功能这么有用，我们将在下节分解。\n\n本文中的例子在[这里](https://github.com/StylingAndroid/DataBinding/tree/Part4)。"
  },
  {
    "path": "issue-28/数据绑定(Data Binding)-Part5.md",
    "content": "#数据绑定(Data Binding)-Part5\n---\n\n> * 原文链接 : [Data Binding - Part 5](https://blog.stylingandroid.com/data-binding-part-5/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [desmond1121](https://github.com/desmond1121) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n\n直到现在，我们已经见识到Data Binding的很多功能了。但是还有一个很强大的特点没有介绍，那就是观察者模式的应用。\n\n观察者模式在数据会变化的时候非常有用。为了演示这个功能，我们假设Twiter API返回的`twitter4j.Status`中不会返回被转发的状态，并需要通过额外的网络请求来获取被转发的状态。并且在获取`twitter4j.Status`的同时去获取这条引用状态的话与之前的工作就没有区别了，我们要让用户在点击该条转发状态时，显示被转发的状态。之前已经展示过了怎么在Data Binding中使用点击响应，那么这次的工作就是在点击的时候获取被转发的状态，并更新UI。\n\n应用观察者模式能够在数据更新的时候自动更新UI内容。每一个`Observable`是可以被许多个观察者(`Observer`)“观察”的，只要`Observable`的内容改变，它就会调用每一个观察者的回调函数，从而做出相应的应对。\n\n我们可以让`Model`中的相关类变成`Observable`，或者也可以仅仅让类里面有可能发生改变的域变成`Observable`。\n\n现在扩展一下`Status`类：\n\n    public class Status {\n    \n        private final String name;\n        private final String screenName;\n        private final String text;\n        private final String imageUrl;\n        private final Status quotedStatus;\n        private ObservableField<Status> observableQuotedStatus;\n    \n        public Status(@NonNull String name, @NonNull String screenName, @NonNull String text, @NonNull String imageUrl, @Nullable Status quotedStatus) {\n            this.name = name;\n            this.screenName = screenName;\n            this.text = text;\n            this.imageUrl = imageUrl;\n            this.quotedStatus = quotedStatus;\n            observableQuotedStatus = new ObservableField<>();\n        }\n    \n        public String getName() {\n            return name;\n        }\n    \n        public String getScreenName() {\n            return screenName;\n        }\n    \n        public String getText() {\n            return text;\n        }\n    \n        public String getImageUrl() {\n            return imageUrl;\n        }\n    \n        public boolean hasQuotedStatus() {\n            return quotedStatus != null;\n        }\n    \n        public void updateQuotedStatus() {\n            observableQuotedStatus.set(quotedStatus);\n        }\n    \n        public void clearQuotedStatus() {\n            observableQuotedStatus.set(null);\n        }\n    \n        public ObservableField<Status> getObservableQuotedStatus() {\n            return observableQuotedStatus;\n        }\n    }\n    \n\n之前我们已经在Status里面加入了被转发的状态对象，它会在存在的时候显示到界面上。而这一次，我们没有把它直接暴露给外部，而是将其包装在了`ObservableFiled`中，通过`updateQuotedStatus()`来获得。\n\n我们将在点击事件中将`Observable`在set与null状态之间转换。如果这部分需要进行网络请求，你可以在开始网络请求的时候更新UI至一个等待状态，在网络请求返回的时候再更新UI。\n\n    public class ClickHandler {\n        private final Status status;\n     \n        public ClickHandler(Status status) {\n            this.status = status;\n        }\n     \n        public void onClick(View view) {\n            if (status.getObservableQuotedStatus().get() == null) {\n                status.updateQuotedStatus();\n            } else {\n                status.clearQuotedStatus();\n            }\n        }\n    }\n    \n\n最后我们需要为转发布局绑定变量。它和之前的布局代码十分相似，区别只不过绑定的对象是`Observable`而已。并且它会根据`Observable`变量是否为null值来判断是否要显示在屏幕上。\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    \n      <data>\n    \n        <import type=\"android.view.View\" />\n    \n        <variable\n          name=\"status\"\n          type=\"com.stylingandroid.databinding.data.Status\" />\n    \n        <variable\n          name=\"handler\"\n          type=\"com.stylingandroid.databinding.ClickHandler\" />\n    \n      </data>\n    \n      <RelativeLayout\n        android:id=\"@+id/status_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n        .\n        .\n        .\n        <RelativeLayout\n          android:id=\"@+id/status_quoted\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:layout_alignLeft=\"@id/status_text\"\n          android:layout_alignStart=\"@id/status_text\"\n          android:layout_below=\"@id/status_text\"\n          android:layout_marginTop=\"8dp\"\n          android:background=\"@color/light_grey\"\n          android:onClick=\"@{handler.onClick}\"\n          android:padding=\"8dp\"\n          android:visibility=\"@{status.hasQuotedStatus ? View.VISIBLE : View.GONE}\">\n    \n          <TextView\n            android:id=\"@+id/quoted_tap\"\n            style=\"@style/Status.Name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentStart=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:text=\"@string/tap_to_load\"\n            android:visibility=\"@{status.observableQuotedStatus == null ? View.VISIBLE : View.GONE}\" />\n    \n          <ImageView\n            android:id=\"@+id/quoted_status_avatar\"\n            android:layout_width=\"64dp\"\n            android:layout_height=\"64dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentStart=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:contentDescription=\"@null\"\n            android:visibility=\"@{status.observableQuotedStatus == null ? View.GONE : View.VISIBLE}\"\n            app:imageUrl=\"@{status.observableQuotedStatus.imageUrl}\" />\n    \n          <TextView\n            android:id=\"@+id/quoted_status_name\"\n            style=\"@style/Status.Name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentTop=\"true\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_toEndOf=\"@id/quoted_status_avatar\"\n            android:layout_toRightOf=\"@id/quoted_status_avatar\"\n            android:text=\"@{status.observableQuotedStatus.name}\"\n            android:visibility=\"@{status.observableQuotedStatus == null ? View.GONE : View.VISIBLE}\" />\n    \n          <TextView\n            android:id=\"@+id/quoted_status_screen_name\"\n            style=\"@style/Status.ScreenName\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignBaseline=\"@id/quoted_status_name\"\n            android:layout_marginLeft=\"4dip\"\n            android:layout_marginStart=\"4dip\"\n            android:layout_toEndOf=\"@id/quoted_status_name\"\n            android:layout_toRightOf=\"@id/quoted_status_name\"\n            android:text=\"@{&quot;@&quot; + status.observableQuotedStatus.screenName}\"\n            android:visibility=\"@{status.observableQuotedStatus == null ? View.GONE : View.VISIBLE}\" />\n    \n          <TextView\n            android:id=\"@+id/quoted_status_text\"\n            style=\"@style/Status.Text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignLeft=\"@id/quoted_status_name\"\n            android:layout_alignStart=\"@id/quoted_status_name\"\n            android:layout_below=\"@id/quoted_status_name\"\n            android:singleLine=\"false\"\n            android:text=\"@{status.observableQuotedStatus.text}\"\n            android:visibility=\"@{status.observableQuotedStatus == null ? View.GONE : View.VISIBLE}\" />\n        </RelativeLayout>\n    \n      </RelativeLayout>\n    </layout>\n    \n\n这样就做好了全部工作，UI界面会在网络请求返回的时候自动更新！以下是Demo视频链接：\n\n[Demo视频链接 From Youtube](https://youtu.be/LXXyFqJ8owo)\n\n[Demo视频链接 From Youku](http://v.youku.com/v_show/id_XMTM2MzMwNzU0OA==.html)\n\n至此我们就将Data Binding库的基本功能都介绍完毕了。我要承认，一开始我对Data Binding的使用是持怀疑态度的，因为它让我联想到了JSP。所以我并不支持大家在layout布局文件中加入太多代码逻辑。但是在使用了这个库之后，我发现它太强大了，在布局文件中嵌入代码逻辑的好处也是显而易见的。当它结束测试正式发布之后，我一定会考虑将其应用到商业工程中！\n\n本文中的例子在[这里](https://github.com/StylingAndroid/DataBinding/tree/Part5)可以看到。"
  },
  {
    "path": "issue-29/Chrome自定义Tabs-让App和Web之间的转场更平顺.md",
    "content": "Chrome自定义Tabs，让App和Web之间的转场更平顺\n---\n\n> * 原文链接 : [Chrome custom tabs smooth the transition between apps and the web](http://android-developers.blogspot.sg/2015/09/chrome-custom-tabs-smooth-transition.html)\n* 原文作者 : [Yusuf Ozuysal, Chief Tab Customizer](htTabstp://blog.chromium.org/2015/09/chrome-custom-tabs-smooth-transition_2.html)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [嗣音君](https://github.com/xiaolangpapa) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n--\n\n当要在App中显示网页内容时，Android应用开发者将面临一个困难选择：在浏览器中打开链接是无疑是一个用户熟悉且非常容易实现的方式，但是却会引发app和web之间一个重量级的转场；而在Android WebView基础上建立起一套新的浏览机制可以获得更细颗粒度的控制，但是这将增加技术复杂度，同时给用户带来一种不甚熟悉的浏览体验。最新版本的Chrome的一个新特性可以解决了这个两难的选择，它就是自定义tabs，可以让app自定义Chrome的外观和感觉，从而实现app到web内容之间快速无缝的转场。\n\n![](http://7xjy6x.com1.z0.glb.clouddn.com/CCT_Large%202.gif)\n\n预加载的Chrome自定义Tabs VS Chrome 和 WebView\n\nChrome自定义tabs使得app可以给用户提供一种快速，完整和熟悉的web体验。自定义tabs是经过优化的，加载速度比WebView和传统方式启动的Chrome都要快。如上图所示，由于app可以在后台预加载网页，当用户访问的时候会感觉几乎是瞬间就加载完毕了。此外，app还可以通过自定义Chrome Tabs的外观和感觉来和自身风格保持一致，如改变toolbar的颜色，调整转场特效，甚至是给toolbar加上自定义的操作，让用户直接通过自定义的tabs来触发app自身特有的功能。\n\n自定义tabs受益于Chrome先进的安全特性，包括多线程架构和健壮的权限模型。另外它们使用了和Chrome一样的cookie jar，这将营造出熟悉的浏览体验，同时又保证了用户的信息安全。举个例子，如果用户已经使用Chrome登录了某个网站，那么用户在app自定义tab中访问同样的网站时也将保持登录状态。其他能帮助用户更好地浏览网页的特性，如保存密码，自动填充，轻点进行搜素和同步等，在自定义tabs中都是可用的。\n\n[视频：Chrome自定义tabs：在你的Android应用中显示第三方内容](https://youtu.be/QOxIdbNwpx0)\n\n开发者只要调整现有的一些VIEW intents参数就可以轻易地把自定义tabs页集成到他们的app当中去。基本的集成只需几行额外的代码,而加入支持库（support library）则可以让更复杂的集成同样简单地被实现。由于自定义tabs是Chrome的特性，所以在任何拥有最新版本Chrome的Android上都是可用的。\n\n在未来几周内，用户可以在[Feedly](https://play.google.com/store/apps/details?id=com.devhd.feedly)，[The Guardian](https://play.google.com/store/apps/details?id=com.guardian)，[Medium](https://play.google.com/store/apps/details?id=com.medium.reader)，[Player.fm](https://play.google.com/store/apps/details?id=fm.player)，[Skyscanner](https://play.google.com/store/apps/details?id=net.skyscanner.android.main)，[Stack Overflow](https://play.google.com/store/apps/details?id=com.stackexchange.marvin)，[\nTumblr](https://play.google.com/store/apps/details?id=com.tumblr)和[Twitter](https://play.google.com/store/apps/details?id=com.twitter.android)体验到自定义tabs，当然，即将到来的还有更多App。想要开始往你的App中集成自定义tabs，请查看[开发者指引](https://developer.chrome.com/multidevice/android/customtabs)。\n\n[Reto Meier](https://plus.google.com/113601876824575481275) 在 [10:01 AM](http://android-developers.blogspot.sg/2015/09/chrome-custom-tabs-smooth-transition.html) 发布  \n标签：[chrome](http://android-developers.blogspot.sg/search/label/chrome), [Develop](http://android-developers.blogspot.sg/search/label/Develop), [Featured](http://android-developers.blogspot.sg/search/label/Featured), [Web](http://android-developers.blogspot.sg/search/label/Web), [WebView](http://android-developers.blogspot.sg/search/label/WebView)\n\n\n"
  },
  {
    "path": "issue-29/开发安全的Android应用.md",
    "content": "开发安全的Android应用\n---\n\n> * 原文链接 : [Develop a secured Android application](http://blog.octo.com/en/develop-secured-android-application/?utm_source=Android+Weekly&utm_campaign=0903213dbd-Android_Weekly_175&utm_medium=email&utm_term=0_4eb677ad19-0903213dbd-337955857)\n* 原文作者 : [Rémi Pradal](http://blog.udinic.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [kevinhong](https://github.com/kevinhong) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n安卓应用已经广泛用于处理非常敏感的信息。保证用户的信息不被居心叵测的人轻易截获是每个Android开发者的责任。“开放网络应用安全项目“ (The Open Web Application Security Project )(OWASP) (引用[9], 引用[10]) 试图列举移动应用的安全风险。有些是系统架构的责任（比如由于服务器缺陷产生的问题），有些与后端开发者有关（比如认证检查等），还有些则与移动开发者有关。这篇文章，我们将会关注与Android开发者相关的那些问题。\n\n因此，我们将会再次讨论三个潜在的风险源头：\n\n>- 与WebService通信的风险\n>- 存储在设备上的信息被泄漏的风险\n>- 我们的应用呗第三方修改的风险\n\n## 1. 安全的WebService请求\n\n在使用WS请求的敏感应用中，最重要的事情是确保与后端通信的数据的安全性。事实上，就算应用本身是安全的，但其通过Internet发送的请求能被轻易截获，那也是没有用的。\n\n###威胁 : 中间人攻击 (MITM)\n\n当一个应用被中间人攻击时，将会产生两个主要的风险。\n\n\n1. **信息泄露**\n\n    如果一个攻击者可以控制用户使用应用的本地网络，他就可以偷偷的轻易截获这个应用和WebService之间通信的所有内容。\n\n2. **Webservice (WS) 模仿**\n\n    对于了解WS格式的攻击者，则可以阻止应用程序的正常请求并为应用程序提供假的返回值。在这种情况下，用户以为请求已经正常执行了，然而请求却从未到达后端。\n\n    测试你的应用是否能被中间人轻易攻击非常容易。你只需要用一个代理服务器软件， (如：CharlesProxy （引用[12]）)，然后将测试设备的代理服务器设置为安装了这个软件的电脑上。如果你的应用没有做针对MITM的保护，你将会很容易看到你的App发起的每一个请求（的内容）。现在，假设有一个你的App的用户通过一个不安全的网络去连接WebServices，攻击者可以轻易在路由上安装一个这样的代理软件，它将会清楚的嗅探到所有的请求。\n\n###攻击的起源：TLS/SSL证书链\n\n确保通信安全，最起码要使用HTTPS协议，例如使用TLS的加密通信或者其前身SSL加密。同时，如果条件允许，这并不是我们的系统需要遵守的唯一约束。想弄明白原因，我们需要先来看看SSL协议的工作原理。\n\n一组SSL证书至少包括下面三个证书\n\n>- **根证书（Root certificate）.** 是由证书授权机构(Certification Authority ，CA)签发的证书。证书授权机构是一个能够确保整个传输过程安全的一个可信机构。\n>- **中间证书（Intermediate certificate(s)）. ** 中间证书可以有多个。它是最终用户证书和跟证书的链路。该证书专门用于服务器发布WS服务，该证书由**根证书**签发。\n>- **最终用户证书（End-user certificate）. ** 最终用户证书可以用于与WebService服务器通信。\n\n**Android 原生的SSL保护:**\nAndroid网络层有嵌入了一些CA证书的列表（有一百多个，你可以在设备的设置菜单查看）。每一个HTTPS请求的证书链的根证书，都必须是其中之一。\n\n然而，与服务器通信过程的请求链中的其他证书的安全性，仍然无法确认。恶意用户还是可以通过向CA买一个中间证书来实现中间人攻击。系统会认为网络传输的过程是有效的。这种方式的恶意攻击非常常见，有研究显示（引用[1]），使用HTTPS请求的应用程序，有73％没有使用适当的方式来检查证书。\n\n###如何确认我们对后端服务的请求过程是安全的\n\n解决上述问题的方法，在于手动检查中间证书（该证书对服务器来说应该是特定的）是一个已知的证书。这意味着，我们不得不在应用程序中存储那个特定的服务器证书。它可以作为资源文件，或者直接在代码中作为常量，\n\n也许你会奇怪，为什么我们必须检查中间证书而不是检查最终用户证书。这有两个原因：第一个，稍后我们会看到，最终用户证书的生命周期非常短暂。第二个原因也是处于安全考虑：假设黑客控制了你的系统，他们将会获取你的私钥（private key）。应用会认为这些请求是由正确的终端用户签名的，就会允许链接。如果确认过程由中间服务器完成，中间服务器就可以远程的通过中间CA回收证书。\n\n\n在JAVA中，可以使用``SSLSocketFactory``类，来确认SSL链接是否安全。创建来执行中间证书检查，需要经过以下几步：\n\n－\n\n1. 继承``X509TrustManger``，这个位于 java.net.ssl 包中的抽象类用于检查SSL链接在服务端的有效性。\n\n\n```JAVA\npublic class MyX509TrustManager implements X509TrustManager {\n    private X509Certificate certificate;\n    \n    public MyX509TrustManager (InputStream knownIntermediateCertificate) throws CertificateException {\n        CertificateFactory certFactory = CertificateFactory.getInstance(\"X.509\");\n        certificate = certFactory.generateCertificate(knownIntermediateCertificate);\n    }\n    \n    @Override\n    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        // Do nothing. We only want to check server side certificate.\n    }\n    \n    @Override\n    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        // Verify that the certificate domain name matches the expected one\n        if (!chain[0].getIssuerDN().equals(certificate.getSubjectDN())) {\n            throw new CertificateException(\"Parent certificate of server was different than expected signing certificate\");\n        }\n        \n        try {\n            // Verifiy that the certificate key matches the expected one\n            chain[0].verify(certificate.getPublicKey());\n            \n            // Verify that the certificate has not expired\n            chain[0].checkValidity();\n        } catch (Exception e) {\n            throw new CertificateException(\"Parent certificate of server was different than expected signing certificate\");\n        }\n    }\n    \n    @Override\n    public X509Certificate[] getAcceptedIssuers() {\n        // Do nothing\n        return new X509Certificate[0];\n    }\n}\n```\n\n2. 设置一个``SSLSocketFactory``，这段代码需要在网络请求前运行。\n\n``` Java\nTrustManager[] trustManagerArray = new TrustManager[1];\nMyTrustManager trustManager = new MyTrustManager(TRUSTED_CERTIFICATE);\ntrustManagerArray[0] = trustManager;\n\nfinal SSLContext sslc;\n\n// TLS is the last SSL protocol and is used by all the CA\nsslc = SSLContext.getInstance(\"TLS\");\n\n// We only need to give a TrustManager list as we don't need to perform client authentification\nsslc.init(null, trustManagers, null);\nHttpsURLConnection.setDefaultSSLSocketFactory(sslc.getSocketFactory());\n``` \n\n###证书检查的潜在缺点\n\n1. 中间证书可能过期（他们的生命周期是大约十年）。解决方案是在旧证书过期前，将新证书加入白名单。\n\n2. 中间证书可能存在风险。如果中间CA被黑，整个安全机制将会毫无用处。事实上，如果中间CA的私钥被黑客获取，它就可以伪造一个证书链，该证书链将会与你的证书链由相同的中间证书。这样，攻击者就可以实现中间人攻击。甚至CA是理论安全的，也不排除会发生诸如2011年DigiNotar被黑的事件（引用[13]）。那样的话，就必须更换服务器的SSL证书链，并上线包含新的中间证书的版本。\n\n3. ``SSLSocketFactory``的信任策略会应用到应用程序的所有请求。如果有SDK嵌入，也需要为SDK的远程服务器嵌入中间证书。让其他的服务器使用你的证书，可能没那么容易，这就会产生问题。\n\n    这个问题，可以通过动态证书注入来解决。应用程序只允许一个证书（主服务器的那个），并且在应用启动时动态获得一个授权中间证书的列表。然后，将这些证书加入``SSLContext``的``TrustManager``中。\n\n总之，在大多数情况下，中间检查机制能够确保对中间人（MITM）攻击的保护。当黑客截获了通信的信息，如果他使用了TrustManager无法识别的证书，将会拒绝HTTPS连接。\n\n## 2. 设备端安全存储\n\n安卓平台提供了一种方便的存储偏好设置深知大文件的方式——``SharedPreferences`` 接口。虽然这些设置已经被隐藏到一个隐蔽的路径了，但如果设备root了，这些数据还是有可能被获取到。\n\n因此，如果应用要保存敏感信息，在``SharedPreference``中就必须加密存储。一般有两种方式：\n\n1. 使用一个密码库，来加密和解密``SharedPreferences``中存储的健和值。有很多JAVA的密码库可以使用，如： javax.crypto, Bouncycastle（引用[2]） 和 Concealed（引用[3]）\n\n2. 使用``SharedPreferences``的包装类库（wrapper）。这些库对开发者非常方便，不用考虑算法选择之类的事情。但是，使用这些库可能会导致缺乏灵活性并且有些库也没有使用安全的算法，这将会导致敏感信息并没有被安全存储。其中一个这方面最常用的类库是``SecurePrefences``（引用[4]）。在这个解决方案中，你可以采用下面的方式使用``SecurePreferences``，它继承于SharedPreferences。\n\n```JAVA\nSecurePreferences securePreferences = new SecurePreferences(context, \"MyPassword\", null);\n\n```\n\n这两种对称加密算法，像AES（需要一个合适的key size）会引发另一个需要思考的问题：应该用什么密钥。确实如果我们使用一个静态密钥，这些设置可以通过反编译程序来破解。所以，最好的方法是在应用启动时让用户来输入。另一个选择是让用户使用指纹API （引用[15]） (API 23以后可以使用) ，它将会提供一个安全流畅的方式来鉴权。\n\n不幸的是，这些方法并不能适合每一个应用的用户体验。比如，如果我们打算在用户输入密码前显示一些被存储的加密信息，我们就不能使用这种加密系统。\n\n幸运的是，安卓提供了一个安全生成密钥的方式，它保证了每个应用／设备对生成的密钥的唯一性，那就是Keystore。Android Keystore的目的是为了允许应用程序将私钥放到一个其他应用程序不能获得的地方。这种机制很简单，第一次启动，你的应用检查该私钥是否存在，如果没有，就生成一个并存储在keystore中。如果私钥已经存在，你可以使用它来做为之前提到的加密算法的安全码来加密SharedPreferences存取的数据。Obaro Ogbo 写了一篇文章 （引用[11]） ，详细描述了如何使用KeyStore来生成私钥／公钥（Private/Public Key ）对。KeyStore的主要缺陷在于，其api只支持Api 18以上版本。不过，还是有一个针对API 14 （引用[14]）兼容的移植版（这不是官方的移植，所以请谨慎使用）。\n\n\n因此，我们可以根据下面的决策图来决定是否对设置项进行加密：\n\n![process](https://ooo.0o0.ooo/2015/10/28/5630856759b9c.png)\n\n##3. 保护应用程序不被代码分析和修改\n\n安卓开发者想确保它的应用程序没有被分析、读取甚至被修改，一般出于以下原因：\n\n>- 我们希望黑客没有办法解开那些需要用户付费才能使用的功能。\n>- 有时候我们开发了具有敏感信息的程序，黑客可以将所有用户输入的信息发送给他。即使（这些被修改的应用）不能轻易放到Play市场，用户也能从许多其他的地方下载到这个被修改的应用，这将以很隐秘的方式偷窥用户信息。\n\n在开发具有敏感数据应用时，安卓开发者需要关注的是Android应用是非常容易被一些有经验的人反编译的。大多数安卓应用程序（采用java字节码），即通过“原生“安卓建立的应用确实如此。反编译及阅读这些字节码非常容易，以至于修改、重新构建一个修改过的应用（引用[5]）。\n\n在这个部分，我们着重研究一些技巧和方法，以及架构规则，来避免可能产生的风险。同时，我们需要明白的是，并不是每一个客户端设备都会100％面对这些风险。\n\n1. **将有价值的算法放到服务端**\n这是一条架构规则。你的应用的价值都是基于核心算法体现，你肯定不想让其他人可以轻易读取、拷贝、并将其放倒自己的应用程序中。在这种情况下，最好的方案是在服务端实现这些算法。应用仅通过给WS提供数据，在服务端处理后，得到算法的返回值。这样做的显著缺陷是以这样的架构为核心功能的程序，不能离线使用。\n\n2. **不要暴露你的WS（WebService）**\n如果你的应用的价值依赖于从WS获取的数据，你就不得不为WS设置安全访问，如，通过为每个请求附带使用密码短语或用户名密码获取的会话令牌的方式。如果你只是在app的preference设置中放了一个认证标志，那这个标志就很容易通过修改应用代码的方式，被设置成“永久连接“。这样做的风险，是用户不得不经常输入用户名和密码来延长会话时间。\n\n3. **使用Proguard来混淆代码**\nProguard是一个常用的JAVA工具。Proguard之行三个过程：\n>- 瘦身：未用到的代码被移除\n>- 优化：内联一些方法，未使用的方法参数被移除等\n>- 混淆：最后这步，Proguard将会重命名所有java源文件中的类、属性、方法名，来确保即使字节码被反编译了，这些代码也几乎不可读。当然Proguard也会确保JVM能够识别这些不同的编译元素。\n\n    这个工具非常有趣，因为它让读取反编译后的字节码非常困难。然而，尽管一些代码元素被重命名了，但反编译后，被混淆的方法和属性的作用还是有可能被猜到。另外，Proguard也生成了一个映射文件，用来将混淆后的代码转换为可读的代码（引用[6]）。\n\n    网络上有很多教程，来介绍关于配置Proguard的相关细节。如Android官方文档（引用[7]）。\n\n4. **使用编译后的库**\n借助于Java本地接口技术（JNI），使用C或C＋＋编写的编译后的本地代码可以作为JAVA代码的接口实现。而借助Android NDK，你可以更方便的使用此功能。总的来说，这机制很简单：先编译好C/C++代码（必须包括标准的JNI入口），得到一个.so文件。然后将这个库文件包含到你的应用程序工程中，以及它的java接口。这里我们谈到编译好的类库，最主要的目的就是，对于反编译后的代码的可读性，so的机器码将比Java的字节码更难读。一个好的实践是（如果实现起来方便的话）是，将应用中具有高安全级别的部分（原文为：更敏感的部分）用C或C＋＋来开发（例如需要保密的算法或者安全层）并以接口形式暴露，而应用中的其他部分再使用JAVA开发。\n\n诚然，使用NDK也还是有缺点的：我们必须为不同的目标硬件架构分别编译本地库。如果漏掉了其中某种，程序就会崩溃。另外这也会使代码架构变的更复杂些。\n\n\n##结论\n\n\n在这篇文章中，我们为OWASP提出的《10大移动安全问题中》（引用[9]）中的3个提出了解决方案。像在导语中提到的那样，只有应用程序链接到的后端系统架构安全，应用的安全才能保证。我们可以从技术上开发一个安全的应用，但如果服务端的糟糕设计导致认证系统的安全性孱弱，所有的努力也将费日。同时，应用开发者有责任确保应用的安全边界没有瑕疵，这篇文章为其提供了合适的解决方案。\n\n\n\n[1]:  https://www.fireeye.com/blog/threat-research/2014/08/ssl-vulnerabilities-who-listens-when-android-applications-talk.html\n\n[2]: http://www.bouncycastle.org/\n\n[3]: https://code.facebook.com/posts/1419122541659395/introducing-conceal-efficient-storage-encryption-for-android/\n\n[4]: https://github.com/scottyab/secure-preferences\n\n[5]: http://geeknizer.com/decompile-reverse-engineer-android-apk/\n\n[6]: http://proguard.sourceforge.net/manual/retrace/examples.html\n\n[7]: http://developer.android.com/tools/help/proguard.html\n\n[8]: http://www.javaworld.com/article/2076513/java-concurrency/enhance-your-java-application-with-java-native-interface–jni-.html\n\n[9]: https://www.owasp.org/index.php/OWASP_Mobile_Security_Project#tab=Top_10_Mobile_Risks\n\n[10]: https://www.owasp.org/index.php/About_OWASP\n\n[11]: http://www.androidauthority.com/use-android-keystore-store-passwords-sensitive-information-623779/\n\n[12]: http://www.charlesproxy.com/\n\n[13]: https://threatpost.com/final-report-diginotar-hack-shows-total-compromise-ca-servers-103112/77170/\n\n[14]: https://github.com/pprados/android-keychain-backport\n\n[15]: https://developer.android.com/about/versions/marshmallow/android-6.0.html#fingerprint-authentication\n"
  },
  {
    "path": "issue-29/注意API21(Android5.0)上的EditText.md",
    "content": "注意API 21 (Android 5.0) 上的EditText\n---\n\n> * 原文链接 : [Beware EditText on API 21](http://blog.danlew.net/2015/10/12/beware-edittext-on-api-21/?utm_source=Android+Weekly&utm_campaign=0903213dbd-Android_Weekly_175&utm_medium=email&utm_term=0_4eb677ad19-0903213dbd-337955857)\n* 原文作者 : [Dan Lew](http://blog.danlew.net/about/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [嗣音君](https://github.com/xiaolangpapa) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :   完成\n\n\n请仔细看看这两个EditText，一个是在API 21（Android 5.0）的设备上，另一个是在API 22 （Android 5.1）的设备上。\n\n![](http://blog.danlew.net/content/images/2015/10/edittexts.png)\n\n发现它们之间的差别了吗？打开“显示布局边界”可以看得更清楚：\n\n![](http://blog.danlew.net/content/images/2015/10/edittexts-layout.png)\n\n这两个EditText的高度和垂直对齐都不一样！这是由v21和v22之间EditText的背景变更而引起的差异。\n\n如果你的EditText使用了垂直对齐的方式来和其他控件对齐，那么这个背景变更将会引起一些令人不快的后果，就像Trello的这个例子一样：\n![](http://blog.danlew.net/content/images/2015/10/checkitem.png)\n\n正常来说，文本应该和图标对齐，但很显然它并没有。这个是在Android 5.0上面的截图，而在其他版本的Android上看起来还是很完美的。\n\n即使你使用的是AppCompat，这个问题也会突然出现。在v21+上，AppCompat遵循的是系统的material风格，这恰恰是产生这个问题的原因。\n\n#解决办法\n我想出来的处理API 21这个问题的两种方法都是通过特定方式来使用resource qualifiers。\n\n一种是在API 21上引入你自己的EditText背景资源。除非你的App遍布了垂直对齐的EditText，不值得花费这么多的时间，因为单独为一个API版本去精确适配EditText的背景是非常棘手的。\n\n另一种取巧但更容易的方法是为该API版本上定义不同的margin和padding。 例如，我发现它们相隔距离为6dp，那么只需在其资源文件的尾部分别加上以下内容：  \n\n\t<!-- values/dimens.xml -->  \n\t<dimen name=\"edit_text_spacing\">6dp</dimen>\n\t<!-- values-v21/dimens.xml --> \n\t<dimen name=\"edit_text_spacing\">0dp</dimen>\n\t<!-- values-v22/dimens.xml -->  \n\t<dimen name=\"edit_text_spacing\">6dp</dimen>  \n\t\n首先我得承认这样做很不优雅，但是只需一点点更改就能解决这个问题，还不算太差吧。\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "issue-30/Android开发生僻却实用的知识点Part1.md",
    "content": "Android 开发生僻却实用的知识点 Part 1\n---\n\n> * 原文链接 : [Android Development Tidbits // No. 1](http://willowtreeapps.com/blog/android-development-tidbits-no-1/)\n* 原文作者 : [Charlie](http://willowtreeapps.com/category/development-blog/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n\n我所在的 WillowTree 的 Android 开发团队素来会在 Slack channel 上每周开设一个小课程分享自己新学到的生僻技巧，秉着分享，开源的思想，从今天开始我会在本博客连载一个“Android 开发生僻却实用的知识点”专栏博文，用以分享我们所了解到的知识点。\n\n不仅如此，我们还鼓励任何人来参与这个课程，分享他们的所知所得（尽管他们要分享的东西是显而易见的，抑或是早就被分享过了），因为不管怎样，他们都会让关注这个课程的人学习到一些知识。在这个课程中，有一些技巧你可能早就知道了，但总有一些你是不知道的。但无论如何，我们希望在这个课程中分享的开发技巧能够帮助观看的人提升写代码的能力，也希望你能从中获益。随意在下边的留言板留言提问哈，我们会回答你的问题的。\n\n##Tidbit One\n\n你知道 Android Studio 有一个 asset 生成器可以生成常用的 Action Bar 图标么？要用这个功能能简单，只需要点击：\n\n[File] >[New] >[Image Asset]\n\n![](http://willowtreeapps.com/wp-content/uploads/2015/10/blog-post-image_android-tidbits_CF.png)\n\n##Tidbit 2\n\n你需要单独运行一个 Gradle 测试？运行下面的代码吧：\n\n```\n./gradlew testDebug --tests='*.<testname>'\n```\n\n##Tidbit 3\n\n在开发应用的过程中使用 Strict 模式以确保我们没有在主线程做某些不该做的事情（如耗时任务，网络访问等……），但要注意的是，应用的非 Debug 版本，即 Release 版本所用的代码必须把 Strict 模式关掉，要不然会影响应用性能甚至导致崩溃。\n\n[http://developer.android.com/reference/android/os/StrictMode.html](http://developer.android.com/reference/android/os/StrictMode.html)\n\n```java\nif (BuildConfig.DEBUG) {\n StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()\n       .detectAll()\n       .penaltyLog()\n       .build());\n StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()\n       .detectAll()\n       .penaltyLog()\n       .penaltyDeathOnNetwork()\n       .build());\n}\n```\n\n##Tidbit 4:\n\n在使用 Picasso 的时候可以设置 RequestTransformer 以修改请求的 Url。例如，我们可以添加图片的宽高到 Url 参数中。\n\n##Tidbit 5:\n\n如果你有在 manifest 中为某一个 Activity 设置 android:windowSoftInputMode=\"adjustResize\"，那么 ScrollView（或其他可以滚动的 ViewGroup）会收缩以显示软键盘。但如果你在 Activity 的 Theme 中设置了 android:windowFullscreen=\"true\"，ScrollView 就不会这样了，因为此时 ScrollView 已经被甚至为填充满整个屏幕。此外，在 Theme 中设置 android:fitsSystemWindows=\"false\" 也会使 adjustResize 失效。\n"
  },
  {
    "path": "issue-30/Android开发生僻却实用的知识点Part2.md",
    "content": "Android 开发生僻却实用的知识点 Part 2\n---\n\n> * 原文链接 : [Android Development Tidbits // No. 2](http://willowtreeapps.com/blog/android-development-tidbits-no-2/)\n* 原文作者 : [Charlie](http://willowtreeapps.com/category/development-blog/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成 \n\n\n\n欢迎大家来看“Android 开发生僻却实用的知识点”系列博文的第二部分！就像在我上周发布的[第一部分]()中所说，与其说这是一个博文专栏，倒不如说这是我们团队所开办的一次 Android 开发分享会，为 Android 开发者提供机会以分享自己在开发过程中收获的小技巧或者是生僻的知识点。再次重申，我们鼓励任何人以任何形式来分享他的所知所得，不管你所想要分享的知识点是高深还是浅显。毕竟你所熟悉的故土总会是某些人即将遇见的新大陆，所以请不要吝啬，我们希望有干货的你能来给我们提供博文的素材！\n\n\n##Tidbit 1\n\n在应用的运行过程中如果 Android 系统决定要更新系统的 WebView，那你的应用可能会发生崩溃，详细的问题[**请戳我**](http://stackoverflow.com/questions/29575313/namenotfoundexception-webview)。\n\n##Tidbit 2\n\nMaterial Design 中有对 padding 和 margin 的要求，除非你是专业的交互设计师，懂得 padding 在什么情况下取什么值才是正确，什么时候是错误的，而且有注意到在这个知识点中，单词间的间距是两个空格，那你可以下载这个 App，并研究其中的细节，了解清楚之后再看看你的 App，看看哪些部分是不符合规范的。\n\n##Tidbit 3\n\n即使你已经在 Adapter 里将 List 中的某一项 Item 移除，getChildLayoutPosition 还是会返回 position 值，这是因为被移除的 View 可能还处于显示移除动画的状态，因此还存在于 RecyclerView 之中。\n\n##Tidbit 4\n\nGET_ACCOUNTS 这个运行时权限不能通过权限页面将它在 API-23 之前的设备中设置为关闭（事实上，如果这是联系人组里唯一的权限，你甚至不能在联系人组里看到这个权限成为一个可选选项）。由于这个细节，假设 account 存在而导致的崩溃的例子实在太多了。\n\n##Tidbit 5\n\nChrome 改变了它处理深层链接的方式。现在我们不会因为在地址栏中输入一个 Url 而偶然地打开某个深层链接。举例来说，从前你能够在 Chrome 的地址栏中输入 pandora.com，然后会触发 Pandora 应用的打开，而不是直接打开该网页。在新的版本中，用户只会看到 Pandora 的网页。\nintent:\n\n```\nintent:\n   HOST/URI-path // Optional host \n   #Intent; \n      package=[string]; \n      action=[string]; \n      category=[string]; \n      component=[string]; \n      scheme=[string]; \n   end; \n```\n\n例如:\n\n\n```\n<a href=\"intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end\"> Take a QR code </a>\n```\n\n专家建议：你可以在链接中添加回退 Url 和/或包名，在用户没有安装 App 时就能获得回退 Url。首先 Chrome 会检查是否有回退 Url，然后导航用户到该 Url。如果没有回退 Url，Chrome 就会寻找包名，使得用户会打开 Play Store，并进入对应包名的页面。如果用户没有安装该 App，而且没有回退 Url，就会出现404错误。\n\n##Tidbit 6\n\n使用 23.0.0 开发库时使用 Loader 的话要注意：由于 Fragment 和 Activity 交互的方式被改变了，当手机方向改变，发生横竖屏切换时会发生一个 Bug，使 loader 被丢失。在 support 23.1.0 中这个 Bug 被修复了，然而，当 Loader 在子 Fragment 中被使用是，方向改变还是会出现这个 Bug。所以现在唯一的解决办法就是使用 23.0.0 之前的库。\n\n##Tidbit 7\n\n在方法返回一个空集合的时候尽可能使用 Collections.emptyList() 和 Collections.emptySet()。Collection 类会返回一个单例空 list 或 set，这样我们就不需要总是创建新的空集合，浪费内存了。\n"
  },
  {
    "path": "issue-30/Flux and Android.md",
    "content": "Flux and Android\n---\n\n> * 原文链接 : [Flux and Android](http://armueller.github.io/android/2015/03/29/flux-and-android.html)\n* 原文作者 : [Austin Mueller](http://armueller.github.io/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成 \n\n##Motivation\n\n互联网的浪潮让许多IT从业者走上创业的路，我就是其中一员。我最近加入了一个创业公司，并从零开始制作一个产品。过去我一直在做外包，所以很难有机会真正为一个应用实施某种架构或技术。但现在全权负责一个应用开发事务的机会就在我的手中，我对此很激动，也迫不及待要把我这些年通过不断的技术积累所完成的库以及架构模式应用到这个初生的应用之中。\n\n去年的大部分时间我都在开发一个网页项目，而且没有太多的机会让自己保持学习Android方面的新技术。当时和我一起进行开发的团队用 [AngularJS](https://angularjs.org/) 进行开发，在项目的开发过程中，我们最看重的就是确保所有的代码模块能像乐高积木部件一样（低耦合，大量的依赖注入，只有在某种情况下才产生代码副作用）组装成最终的项目。这样写代码能极大程度让开发者在项目的迭代和需求更迭所带来的代码修改、重构中被解放，因此我想要保持这个习惯，并把这种编程思想应用到Android开发中。当然了，这肯定不容易，毕竟罗马不是一天就能建成的。\n\n一番考量后，我决定从依赖注入入手，因为我知道不论是Java抑或是Android，依赖注入都有许多开源的库。但遗憾的是，依赖注入一直是我没有掌握得很好的知识点，所以我阅读了许多优秀的讨论：\n\n- [DAGGER 2 - A New Type of dependency injection by Gregory Kick](https://www.youtube.com/watch?v=oK_XtfXPkqw)\n- [The Future of Dependency Injection with Dagger 2 by Jake Wharton](https://www.parleys.com/tutorial/5471cdd1e4b065ebcfa1d557/)\n- [Dagger: A Fast Dependency Injector for Android and Java by Jesse Wilson](http://www.infoq.com/presentations/Dagger)\n- [Android App Anatomy by Eric Burke](http://www.infoq.com/presentations/Android-Design/)\n\n而且读了一些优秀的博文：\n\n- [Android Dependency Injection](http://blog.andresteingress.com/2014/08/31/android-dependency-injection/)\n- [Blog List: Adding a Blog View Activity, Butterknife, and Dagger](http://www.pinnsg.com/blog-list-view-activity-butterknife-dagger/)\n- [Dagger 2 has Components](https://publicobject.com/2014/11/15/dagger-2-has-components/)\n- [Dependency injection on Android: Dagger (Part 1)](http://antonioleiva.com/dependency-injection-android-dagger-part-1/)\n- [Dagger: dependency injection on Android (Part 2)](http://antonioleiva.com/dagger-android-part-2/)\n- [Android Unit Test Idioms](http://tech.pristine.io/android-unit-test-idioms/)\n- [Android Testing With Robolectric](http://www.peterfriese.de/android-testing-with-robolectric/)\n\n最终我觉得 [Dagger 2](http://google.github.io/dagger/) 会是最切合我的开发思路的依赖注入库，后来我又看了一些相关信息，还用 [Butterknife](http://jakewharton.github.io/butterknife/) 来对 View 进行注入。但现在最大的问题是，我找不到 Dagger 2 各方面相关的最佳用例（可能是 Dagger 2 还处于 Beta 版本的原因）。不过我还是找到了一丢丢的，虽说他们大部分都关注于如何将 Dagger 2 应用到项目中，而不是怎么在实际的 Android 应用被运用。\n\n这些是我找到的一些用例：\n\n- [Dagger 2 - Simple](https://github.com/google/dagger/tree/master/examples/simple)\n- [Dagger 2 - Android Simple](https://github.com/google/dagger/tree/master/examples/android-simple)\n- [Dagger 2 - Android Activity Graphs](https://github.com/google/dagger/tree/master/examples/android-activity-graphs)\n- [U2020](https://github.com/cgruber/u2020)\n- [U2021](https://github.com/cgruber/u2020)\n- [Android Studio Robolectric Test Example](https://github.com/mutexkid/android-studio-robolectric-example)\n- [Tutorial: Set up android + gradle project with dagger](https://github.com/frankdu/android-gradle-dagger-tutorial)\n- [Instrumentation Testing with Dagger, Mockito, and Espresso](https://github.com/bryanstern/dagger-instrumentation-example)\n- [Dagger2 Example](https://github.com/mgrzechocinski/dagger2-example)\n- [SOLID: Noun Project Browser](https://github.com/blad/solid-android)\n\n在我想清楚我想用依赖注入干什么之后，我就开始想应该用一个怎样的软件架构去达到松耦合，并且能让 View 和业务逻辑相互独立（就像我在用 Angular 时做的那样）。实现这样的软件架构还有一个好处就是：简化测试驱动开发……因为如果模拟 Activity 对象和 View 对象只能够进行测试，并且给出相应的测试结果，这样的测试其实是没有什么意义的。\n\n最终我决定使用的架构是 [Flux](https://facebook.github.io/flux/)。很显然，Facebook 开发Flux是为了将它应用到网页和javaScript 上，所以如果我想把它应用到Android项目中，就必须这个项目作一定程度的修改。经过一些调整（主要是 dispatcher 和 view 访问数据的方式），结合 Square 开发的 [Otto](http://square.github.io/otto/) 库，Android版的Flux架构库就诞生了。\n\n后来我又想到了一些有用的东西，但我不敢把这些想法在这个阶段投入到应用的开发中。毕竟我现在使用的架构模式是没有经过测试的，而且我很不熟悉 Dagger 2，Butterknife 和 Otto 这些库，所以我决定以测试驱动的方式决定这些想法的实施。于是我遵循Facebook的用例（还有一些其他人的）创建了一个简单，但功能完备的应用，并把它命名为FluxyTodo。作为最想要看到这种架构模式成功的人，我想要用另一个有关如何组合使用Dagger 2，Butterknife和Otto的例子向所有关注的人证明这个架构的可行性。因为这些库的用例非常少，在功能完备的应用中更甚，我觉得我最终提供的用例肯定会成为Android开发社区的一笔财富。\n\n因此，如果越多的人采用这种架构，Android开发就会变得没那么痛苦了。下面来看干货吧！\n\n##Architecture\n\n在看了 Flux 和尝试把相关概念应用到Android中后，我想到了这个架构，所以如果你以前有用过Flux的话，这部分的内容对你来说会非常熟悉。下面是基本的架构图：\n\n![](http://armueller.github.io/images/Architecture-General.png)\n\n如你所见，这和Flux非常相似，毕竟我只是基于Android进行了一点点的修改，主要的区别在于dispatcher的运作方式。没用创建一个dispatcher去模仿Facebook应用创建的行为，我用事件总线代替了这个机制（Otto）。还有一个细微的差别是：我使用了 models 和 stores，而不是只使用 stores。stores 只用于存储 View 的状态，而 models 存储其他应用数据和业务逻辑。\n\n##本系统每个部分的详解\n\n###View\n\nView层一般用于给用户显示界面，在Android系统中，View层的表示就是Activity和Fragment，及它们对应的xml组件。纯理论来看，View的职责只包含创建和显示呈现给用户的界面，并且监听用户在View上的操作并返回用户产生的交互行为。也就是说，在View层绝不应该存在任何数据操作或业务逻辑。View层的逻辑应该尽可能少地与View的状态产生交集。\n\n当用户与View的某个区域产生交互，View应该将用户产生的动作交由操作事件产生部件进行处理。假如我们要正确地完成View中产生的动作的事件处理链，View则应该只与一个部件产生内部通信，即操作事件产生部件。举例来说把，假如一个用户点击了上传按钮，那么View应该让行为产生部件创建一个上传行为，以完成用户所需要完成的操作。\n\n至于在View中填充控件及相关的数据，View则只应该监听一个数据源的状态，数据总线。一旦View被创建，View就应该订阅来自数据总线的事件，当数据总线流入View将用到的数据，View就应该取得该数据，并将它绑定到对应的控件中。\n\n###Action Creator\n\n操作事件产生部件有两个职责：1、为View提供API，用以响应用户在View上可能产生的操作事件。例如你开发了一个计算器App，相应的操作事件产生部件就应该有 createAddAction, createSubtractAction 等方法，具体有哪些方法依赖于计算器的功能需求，当然了，这些方法可能会需要一个参数，例如 public void createAddAction(float numberToAdd)。\n\n2、创建行为对象，并将它们投递到事件总线上。\n\n###Action Bus\n\n事件总线是所有事件被投递、流动的部件，stores 和 models 监听投递到事件总线上的特定事件。当特定事件产生，就会触发它们处理View的逻辑或业务逻辑。\n\n###Stores & Models\n\nmodels用于数据的存储和转换，在我的架构中期时和传统的MVC模式中的models相似。models会订阅投递到事件总线的事件，还是以计算器为例子吧，如果一个加法运算的操作事件对象被投递到事件总线上，Model就会接收该事件，无论当前数字是多少，就会将作为参数传入的数字与当前数字进行加法运算，然后将运算结果投递到数据总线上。\n\n而stores与models的不同之处在于：存储在store的数据与当前的View状态呈强相关关系，继续以计算器为例，此时你可以在store中存储一个操作事件的撤销缓冲区，因为此时数据不与应用的实际状态关联。再举个例子，如果你有一堆App，而且想要过滤掉某些App，那么此时你可以将过滤后的App列表放到store，而原来的App列表仍然在model中。\n\n###Data Bus\n\n数据总线是所有model和store投递数据更新事件的地方，View订阅某些数据，并将数据投递到数据总线上，从而达到异步更新View数据的目的。\n\n##TDD!\n\n这个架构解耦了Android应用的所有组成部件，使Android开发变得更轻松。此外，以测试驱动开发和依赖注入也变得简单。\n\n下面这张图列举了一些你可以在我的架构模式中使用的常被推荐来进行测试驱动开发的办法。该测试驱动开发的架构模板如下：\n\n![](http://armueller.github.io/images/Architecture-General-TDD.png)\n\n###Views\n\n下面是一些使用本架构的人想在View中测试的东西：\n\n- 首先，你想要确保你的View组件被正确绑定到数据总线中，而且一旦View被初始化或被填充完成就总会呈现正确的结果。要测试这个非常简单，我们只需要将测试数据投递到数据总线中哦你，然后测试只需要确保数据被正确地用于初始化View和填充View。\n\n###Actions\n\n对创建的每一个操作事件产生部件，你应该为每一个部件创建对应的操作事件，投递到事件总线中让部件的方法进行测试。而要完成这个工作，你只需要简单地调用部件的方法，监听事件总线以确保正确的操作事件对象被投递到事件总线中，并让它装载正确的数据。\n\n###Models & Stores\n\nmodels 和 stores 是应用的核心，因此，需要的测试是最多的。每一个model和store对象都应该进行充分的单元测试。对于没有监听操作事件或投递数据到数据总线上的简单对象，标准的单元测试就足够了。而监听操作事件、投递数据到数据总线上和具有某些业务逻辑的那些model和store，则需要在测试的时候关联对应的总线以完成测试。\n\n##Implementation\n\n为了将这个新的架构投入测试，我决定将它应用到一个简单的待办事项App中（绝对是我原创的）\n\n下面这张架构图详细地说明了每一个部件是如何和其他部件交互的：\n\n![](http://armueller.github.io/images/Architecture-Impl.png)\n\n如果你想详细了解这个架构是怎么被应用的，可以到[这里](https://github.com/armueller/FluxyAndroidTodo)下载我开发的待办事项App源码进行学习。\n\n##Future work\n\n尽管通过这个架构方式开发的App已经可以正常使用了（和用Facebook的Flux架构开发的待办事项App相似），我也挺满意实际的效果的。我想到了几个与服务器通信的新办法，感觉能应用到这个架构中，但这些办法我都不太满意。显然，我理想中的与服务器通信的办法，必须能在某些情况下能用某个方程表示，这也是我接下来要研究的东西。一旦我想到了一个满意的解决方案，我就会更新博客和大家分享！\n\n##Todo:\n\n- 找到我认为的和服务器通信的最优方案\n- 用数据库存储大量的待办事项\n- 让应用能异步处理大量数据\n- 更新博客\n\n##Code\n\n[待办事项App源码](https://github.com/armueller/FluxyAndroidTodo)\n"
  },
  {
    "path": "issue-30/通过硬件层提高Android动画的性能.md",
    "content": "通过硬件层提高Android动画的性能\n---\n\n> * 原文链接 : [Using hardware layers to improve Android animation performance](http://blog.danlew.net/2015/10/20/using-hardware-layers-to-improve-animation-performance/)\n* 原文作者 : [Dan Lew](http://blog.danlew.net/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n曾有许多人问我为什么在他们开发的应用中，动画的性能表现都很差。对于这类问题，我往往会问他们：你们有尝试过在硬件层解决动画的性能问题么？\n\n我们都知道，在播放动画的过程中View在每一帧动画的显示时重绘自身。但如果你使用 View layer，使得View被渲染一次后就放到一个屏幕外的缓冲区中（即 layer），让View不断被重用，而不是一次又一次的重绘的话，这类动画性能问题就迎刃而解了。\n\n此外，硬件层对图像的处理都会在GPU上进行缓存，使得我们在播放动画的过程中对View的特定操作的执行效率更高。简单的变换动画（例如平移、旋转、缩放和淡化）能够通过利用硬件加速很快被渲染出来，而大多数动画都只是这些简单动画的组合，所以我们能尝试利用硬件加速来提高这些动画的性能。\n\n##用法\n\n为View设置layer缓存的API比较简单：只需要调用View.setLayerType()。但我们只应该短暂地使用硬件层，因为硬件层不仅仅负责我们这一个页面的绘制（完成动画绘制后，还可能在其他地方被调用），基本流程如下：\n\n1. 为每一个在动画过程中要被缓存的 View 调用 View.setLayerType(View.LAYER_TYPE_HARDWARE, null)\n2. 运行动画\n3. 当动画结束，通过 View.setLayerType(View.LAYER_TYPE_NONE, null) 结束对硬件的占用\n\n```java\n// Set the layer type to hardware \nmyView.setLayerType(View.LAYER_TYPE_HARDWARE, null);\n\n// Setup the animation\nObjectAnimator animator = ObjectAnimator.ofFloat(myView, View.TRANSLATION_X, 150);\n\n// Add a listener that does cleanup \nanimator.addListener(new AnimatorListenerAdapter() {  \n  @Override\n  public void onAnimationEnd(Animator animation) {\n    myView.setLayerType(View.LAYER_TYPE_NONE, null);\n  }\n});\n\n// Start the animation\nanimator.start();  \n```\n\n如果应用的最低面向版本大于16，而且你在应用中使用了 ViewPropertyAnimator，那么你可以用更简单的 withLayer() 方法完成上面的操作：\n\n```java\nmyView.animate()  \n  .translationX(150)\n  .withLayer()\n  .start();\n```\n\n这样弄下来，你的动画就会丝般光滑了！\n\n##注意事项\n\n哈哈哈，你真以为那么简单吗？too young, too naive 啦！\n\n虽说硬件加速对动画性能表现的提升会让人感觉不可思议，然而，如果你使用的姿势不对，比起动画性能的问题，它们带来的问题会让你头疼一百倍。记住我这句话，千万别滥用 layer 缓存 View！\n\n原因很简单：\n\n第一，在某些情况下，通过硬件加速完成的绘制任务会比普通的 View 绘制流程更复杂。使得缓存 layer 会花费更多的时间，因为整个渲染过程变成以下两个过程：1、View 绘制完成后缓存到 GPU 的 layer 中；2、GPU 将 layer 绘制到 Window 中。如果 View 的绘制过程比 GPU 将 layer 的内容绘制到 Window 中更快完成，那么就会在绘制的初始化过程中带来不必要的开销。\n\n第二，正如所有的缓存操作，我们在这里所完成的缓存操作也会面临缓存失效的问题。任何在动画播放过程中调用 View.invalidate() 的操作都会让 layer 重绘一遍。重复地刷新会让 layer 优势全无，还不如不用，因为（就像前面提到的）硬件在设置缓存的过程中增加了不必要的开销。如果你需要不断地重新缓存内容到 layer 中，肯定会对性能造成很大的影响。\n\n由于动画通常都有多个会变换的部分，所以这个问题很常见。假如你正设置一个动画，它具有下面三个发出变换的部分：\n\n`\n父布局：ViewGroup  \n--> Child View 1 (向左平移)  \n--> Child View 2 (向右平移)  \n--> Child View 3 (向上平移) \n`\n\n如果你为 ViewGroup 设置了一个单独的 layer，该 layer 就会不断地因为缓存内容的失效而不断地缓存内容，因为该 ViewGroup （把它看成一个整体）正因为它的子 View 的动画效果不断地发生变换。而每一个发生变换的子 View，只不过是完成了一个平移操作而已。在这种情况下，我们应该为每一个子 View 设置 layer，而且不要为作为父布局的 ViewGroup 设置 layer。\n\n因为我刚开始就没有搞清楚这一点，所以我现在重申一遍：通常应该为多个 View 设置 layer 进行硬件加速，使得这些 View 不会在动画播放的过程中被刷新。\n\n[\"Show hardware layers updates\"](http://www.curious-creature.com/2013/09/13/optimizing-hardware-layers/) 是一个很好用的开发工具，我们能用它来检查应用是否存在刚刚我所提到的问题。只要 View 绘制了 GPU 的 layer，它就会让屏幕闪烁一次。在这篇博文的应用场景中，它应该在动画开始的时候闪烁一次（即 layer 的第一次绘制）。然而，如果 View 在动画的播放过程中始终显示整片的绿色，说明我们的代码中存在上面提到的缓存失效问题。\n\n第三，使用硬件 layer 会占用一定的 GPU 内存，而每一个开发者最不希望看到的内存泄漏在这里就可能会发生了。所以我们应该只在必要的时候，如播放动画的过程中，使用硬件加速。\n\n总的来说，使用硬件加速没有什么硬性的规则。事实上，Android 的绘制系统比我想象中复杂多了，在所有的性能问题中，View 的测量都是关键点。开启“显示 GPU 绘制信息”和“显示硬件刷新”这两个开发者选项能很好地帮助我们判断 layer 到底是提升还是降低了性能表现。\n\n##Sample\n我开发了一个示例 App 来教大家怎么使用基本的硬件加速，[源码在此](https://github.com/dlew/android-hw-layers-sample)。\n\n下面是在我的 Galaxy Nexus 手机（设备有些老，而且很卡）上开启了“显示 GPU 绘制信息”选项的实际运行图：\n\n![](http://i.imgur.com/MZaXOPS.png)\n\n在没有开启硬件加速时，动画的性能表现真是触目惊心啊。其性能评价不断地超过绿线，让人很头疼。相反，开启了硬件加速后性能评价一直在绿线以下，这是让人很满意的。\n\n第三张图为我们展示了动画播放过程中，layer 缓存失效带来的危害。硬件加速带来的性能提升很大一部分都被缓存失效浪费了。\n\n(这里还有一点需要为大家解释 - 如果缓存失效了，为什么它的性能表现没有退化成第一张图那样呢？说实话我也不是特别理解其中的奥秘，但很显然是硬件层作了某种程度的优化，使得每一次重绘没有像之前那样耗费资源。即便如此，我们还是应该正确地使用硬件加速！)\n\n一句话总结本文：硬件加速是福也是祸，所以一定要正确使用！\n"
  },
  {
    "path": "issue-31/Android 中的依赖注入框架.md",
    "content": "Android 中的依赖注入框架\n\n---\n\n> * 原文链接 : [Dependency Injection on Android](http://tech.just-eat.com/2015/10/26/dependency-injection-on-android/)\n* 原文作者 : [Just Eat](http://tech.just-eat.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n\n09年我刚开发 App 那会，情况和现在不太一样。App 作为新生的 IT 领域，一切事物都处于从低级向高级演化的阶段。那会儿哪有人会把开发 App 当成吃饭的家伙呀，大家都只是想打发打发时间，找点乐子。\n\n而今天，移动 App 已经发生了翻天覆地的变化。App 不但在当今互联网时代和 PC 分庭抗礼，甚至在许多公司中成为战略发展的核心，而 JUST EAT 公司就是其中之一。我们知道用户都很喜欢我们的 App，所以我们一直很重视我们的 App，这也促使我们的 App 在保证性能表现的同时提供了绝佳的用户体验。\n\n某种程度上你也可以说我们使用专业的软件开发工具和技术严肃地对待着开发我们 App 的每一项事业。而我们用于开发的其中一项技术就是依赖注入。\n\n##依赖注入是一种设计模式\n\n虽然依赖注入早已作为一种设计模式为人所知，但到了近段时间它才被广泛应用到 Android 应用的开发中，主要是因为最近才有了各种各样依赖注入框架的优秀实现。依赖注入让开发者的代码低耦合，且能够容易地进行测试。开发者开发的 Android 应用存活的时间越长，应用易于被高效地进行测试就显得越重要。在开发 JUST EAT 的时候，我们认为：让代码能够被配置因而能被测试的关键就是依赖注入，这样才能在应用变得越来越庞大的时候代码依然可靠。正因为我们通过依赖注入使我们的测试变得有效，鲁棒性高，才能定期更新应用。JUST EAT 的成功让我们确信依赖注入是一个有用的工具，但在继续吹B之前，我还是介绍一下以来注入把。\n\n##依赖注入的基础\n\n我们写代码的时候，我们的类总会不可避免地拥有其他类的依赖。这样类 A 就可能需要一个类 B 的引用或依赖。为了简化介绍的难度，我下面就举个车的例子来解释吧：\n\n```java\npublic class Car {  \n \n    private Engine engine;\n \n    public Car() {\n        engine = new PetrolEngine();\n    }\n}\n```\n\n这段代码毫无疑问能正常地运作，但我们会发现 Car 类和 Engine 类高度耦合。Car 类一旦被实例化，就会伴随着 Engine 类的实例化，也就使得 Car 类在实例化时必须知道 Engine 类的实例化需要哪些条件，在我们的例子中就是 PetrolEngine 类实例化需要的条件。为了降低耦合，我们可以把代码改成下面这样：\n\n```java\npublic class Car {   \n \n    private Engine engine;\n \n    public Car(Engine engine) {\n        this.engine = engine;\n    }\n}\n```\n\n现在我们只需要在实例化 Car 对象的时候传入一个 Engine 对象的引用，这就意味着 Car 类和 Engine 类的耦合度降低了。Car 类不再需要知道 Engine 类的实例化条件是什么，Car 类能调用的任何类型的 Engine 类。在这个例子中，因为我们通过 Car 类的构造方法把 Engine 类传递（或者称为注入）给 Car 实例，使得我们完成了构造器注入的实现，我们当然还可以通过使用依赖注入框架的方法直接注入到类的域里。上面就是依赖注入的概念了。它的思想就是将依赖直接传递给类，而不是由类来初始化依赖。\n\n##如果依赖注入这么简单，为什么需要专门开发一个框架？\n\n现在我们知道依赖注入是什么了，也就知道要怎么在代码中应用依赖注入了，所以我们就看看在我们的构造方法或者调用的方法中需要传递哪些依赖把。对于一些简单的依赖，这部分工作确实很好完成，但依赖越复杂，我们需要完成的工作就越繁复。\n\n还是回到刚刚的例子吧，假设 Engine 类也有它所需要的依赖集，例如：曲柄轴，活塞，块和头。如果我们遵循依赖注入的原则，就会将这些类的实例传递给 Engine 类，这样的情况倒还好，我们只需要先创建这些对象，然后在创建 Engine 类实例的时候把它们传递给 Engine 对象就好了，最后我们还是可以将 Engine 类的实例传递给 Car 类。\n\n现在我们让这个例子变得更复杂些，如果我们想为 Engine 类的每一个部件创建类，那么我们很容易就会因此创建几百个类，这些类甚至呈现为一颗复杂的树状图（准确来说是一张图）结构的依赖关系。\n\n![](http://je-ict-live-techblog-assets-eu-west-1.s3.amazonaws.com/wp-content/uploads/2015/10/di_graph.png)\n\n上面是这种情况的简化图，有图可知，为了得到根对象，我们必须创建叶接对象，并把它们传递给各自的父对象，而且要以正确的顺序去创建，要不然肯定会出现问题。\n\n换句话说，为了创建依赖，我们如履薄冰，一旦顺序搞错了就会代码爆炸。\n\n所以现在你就会发现情况已经变得糟糕起来了，如果我们使用了诸如工厂模式或者建造者模式这样的设计模式去创建我们的类，我们很块就会发现代码变得很复杂、臃肿，充斥着依赖的传递，而且大量的代码都是无意义的、反复的模板代码，这样的代码无疑是开发者不应该写出来的。\n\n从我们的例子里就可以了解到以简单的方式实现依赖注入的坏处在哪里了：复杂的依赖关系、大量的模板代码。也正是如此，依赖注入之前没有流行起来。但不可否认，依赖注入确实是值得使用的，也正因如此，有几个大牛开发了依赖注入框架来解决传统依赖注入用法存在的问题。这些框架大大简化了配置依赖以及生成工厂和建造者对象的过程，是之变得直观和简单。\n\n##在 Android 中应该使用什么依赖注入框架呢？\n\n因为依赖注入这个概念是有一段历史的，所以有一些依赖注入框架可以用很正常，在 Java 中，有着 Spring，Guice 和 Dagger 这些依赖注入框架，那么我们该如何选择呢？\n\nSpring 已经有一段时日了，为了解决声明依赖和初始化对象，Spring 运用了 XML。但这样做有一个问题，就是我们必须写下冗长的 XML 代码来完成这部分工作，而且要在运行时确保它完成了这些工作。所以 Spring 在尝试解决因此衍生的种种问题时也提出了使用依赖注入存在的问题。\n\n在 Java 依赖注入框架的发展历程中，Guice 确实能算作对 Spring 的革新。Guice 不需要通过　XML 来配置类的成员域，而是直接通过注解完成了配置的工作，例如 @Inject 和 @Provides。这样看起来依赖注入框架确实要变得更好用了，但 Guice 还是存在一些问题：Debug 和追踪错误有些困难。另外，Guice 使用了大量的反射，虽说对服务器来说这些都不是太大的问题，但在追求用户体验的客户端就会造成很大的开销，影响应用的性能表现从而降低用户体验。\n\n虽说 Guice 在依赖注入框架的发展史上踏出了一大步，但它没有解决任何实际的问题，而且它的设计也不是特别适合在移动端开发中使用。因此，Square 开发了 Dagger，造福万千 Android 开发者。\n\nDagger 这个名字的灵感来源于我们依赖的树状关系，依赖关系呈现的图实际上是有向非循环图，英语简称为：DAG，而在我们的场景中，图呈现上尖下宽的形状，有点像蒙古的圆顶帐篷，所以就叫作 DAGger—— Dagger了。Dagger 的目的是解决将 Guice 应用到移动端存在的问题。\n\nDagger 将大部分工作负担投到编译时完成而不是运行时，而且尝试尽可能不使用反射，这两部分工作能完成的话就能极大提高依赖注入框架的性能表现。最后 Dagger 完成了这样的工作，但为此也牺牲了一些 Guice 中的特性，但总体来说还是值得的。但 Google 认为这部分工作还能完成地更好，所以他们在尝试开发 Dagger 2。\n\nDagger 2 在编译时完成更多的工作，而且将移除反射这部分工作完成地更好，最终完成的 Dagger 2 在 Debug 中的表现也比 Dagger 要好。我真心觉得 Dagger 2 是 Android 中优秀的依赖注入方案了，所以如果你有使用依赖注入框架的话，或者正想要选一个依赖注入框架使用，我认为 Dagger 2 是你的最佳选择。\n\n##在 Android 开发中使用\n\n那么要怎么用它们呢？Dagger 和 Dagger 2 都有丰富的教程能帮助你快速入门。\n\nDagger 2 的首页有丰富的 Dagger 2 的相关概览和特性，这都得感谢 Jake Wharton。上面告诉你要怎么用 Dagger 2 完成依赖注入中的各种工作。下面是一些简单的教程：\n\n[Dagger 2 概览](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android)\n\n什么是 Dagger 2 以及如何使用：\n\n[http://konmik.github.io/snorkeling-with-dagger-2.html](http://konmik.github.io/snorkeling-with-dagger-2.html)\n\nDagger 2 中的域：\n\n[http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/](http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/)\n\n结合使用Dagger、Espresso 和 Mockito 来完成测试：\n\n[http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html](http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html)\n"
  },
  {
    "path": "issue-31/Android中调试RxJava.md",
    "content": "# Android中调试RxJava\n\n> * 原文链接 : [Debugging RxJava on Android](http://fernandocejas.com/2015/11/05/debugging-rxjava-on-android/?utm_source=Android+Weekly&utm_campaign=038d344835-Android_Weekly_178&utm_medium=email&utm_term=0_4eb677ad19-038d344835-337955857)\n* 原文作者 : [Fernando Cejas](http://fernandocejas.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [shenyansycn](https://github.com/shenyansycn) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  未完\n\n**调试**是查找和分析bug的过程或者预防软件的正确操作出现问题[Wikipedia](https://en.wikipedia.org/wiki/Debugging)。\n\n当前调试不是一件容易的事情，我们在处理**Android的异步操作**时，这一切将变得更为困难。\n\n正如你可能知道的，在[@SoundCloud](https://soundcloud.com/),**Android开发**的核心代码中我们大量的使用[RxJava](https://github.com/ReactiveX/RxJava)，因此在这篇文章我会轻松的带你走进如何调试Rx [Observables](http://reactivex.io/documentation/observable.html)和[Subscribers](http://reactivex.io/RxJava/javadoc/rx/Subscriber.html)。\n\n##热烈欢迎Frodo\n\n让我们先介绍下**Frodo**，如果你已经看过[Matthias Käppler talk at GOTO Conference](https://www.youtube.com/watch?v=R16OHcZJTno)(如果你还没有看过，我强烈推荐)，你应该已经注意到了的[Gandalf](https://github.com/android10/gandalf)[(第41:15分钟)](https://youtu.be/R16OHcZJTno?t=2475)。开始时我要说的是[Gandalf是我在**Android中面向方面编程**的失败尝试](https://github.com/android10/gandalf)，幸运的是在艰苦的工作和收到有益的反馈后，它成为了我在[@SoundCloud](https://soundcloud.com/)中使用的**Android开发工具箱**，然而我想要一个**较小的只解决一个问题**的工具，所以我决定提取[RxJava](https://github.com/ReactiveX/RxJava)的日志细节，我一只在努力，献身于**Frodo**。\n\n**Frodo**仅是一个**调试RxJava Observables 和 Subscribers的Android库**(目前来看)，比如说成是**Gandalf**的小儿子或者兄弟。它的灵感产生于[Jake Wharton’s Hugo库](https://github.com/JakeWharton/hugo)库。\n\n![frodo_one](http://fernandocejas.com/wp-content/uploads/2015/11/frodo_one.jpg)\n\n##调试RxJava\n\n首先，我假设你有**RxJava**和它的核心组件：**Observables** 和 **Subscribers**的基础知识。\n\n**调试是一个切入点**，我们知道它是如何令人沮丧和痛苦的。此外，很多时候你为了写**调试相关**的代码（不属于业务逻辑的部分）而使得事情更复杂。特别是当涉及到**异步代码执行**。\n\n**Frodo就是为了避免编写用于调试RxJava对象的代码而出现的。它依赖Java注解和Gradle Plugin**，程序Debug编译时会检测，它会将**RxJava对象的相关信息**输出到logcat。例如，即使是在生成release版的app时，**在你的代码库中保留Frodo注解也是安全的**。现在，让我们具体看看。\n\n##使用Frodo\n\n使用**Frodo**第一步是在我们Android项目中将**Gradle Plugin**修改为如下这样：\n\nbuild.gradle\n\n```java\nbuildscript {\n  repositories {\n    jcenter()\n  }\n  dependencies {\n    classpath 'com.android.tools.build:gradle:1.3.1'\n    classpath \"com.fernandocejas.frodo:frodo-plugin:0.8.1\"\n  }\n}\n\napply plugin: 'com.android.application'\napply plugin: 'com.fernandocejas.frodo'\n```\n\n正如你看到的，我们在classpath中添加了**“com.fernandocejas.frodo:frodo-plugin:0.8.1”**然后我们声明了**‘com.fernandocejas.frodo’** plugin。这足够使用**这个库提供的Java注解**。\n\n##检验@RxLogObservable\n\n**Frodo第一个核心功能是通过Java注解@RxLogObservable 记录RxJava Observables日志**。比方说我们有一个会产出`DummyClass`列表的`Observable`。\n\nObservableSample.java\n\n```Java\npublic class ObservableSample {\n\n  @RxLogObservable\n  public Observable<List<MyDummyClass>> list() {\n    return Observable.just(buildDummyList());\n  }\n  \n  public List<MyDummyClass> buildDummyList() {\n    return Arrays.asList(new MyDummyClass(\"Batman\"), new MyDummyClass(\"Superman\"));\n  }\n\n  public static final class MyDummyClass {\n    private final String name;\n\n    MyDummyClass(String name) {\n      this.name = name;\n    }\n\n    @Override\n    public String toString() {\n      return \"Name: \" + name;\n    }\n  }\n}\n```\n\n然后我们订阅到我们的observable例子中：\n\nMyClass.java\n\n```java\nfinal ObservableSample observableSample = new ObservableSample();\nobservableSample.list()\n  .subscribeOn(Schedulers.newThread())\n  .observeOn(AndroidSchedulers.mainThread())\n  .subscribe(new Action1<List<ObservableSample.MyDummyClass>>() {\n    @Override\n    public void call(List<ObservableSample.MyDummyClass> myDummyClasses) {\n      toastMessage(\"onNext() List--> \" + myDummyClasses.toString());\n    }\n  });\n```  \n\n编译并运行应用后，这是我们在logcat中看到的信息：\n\nLogcatShell\n\n```\n      ObservableSample  D  Frodo => [@Observable :: @InClass -> ObservableSample :: @Method -> list()]\n                        D  Frodo => [@Observable#list -> onSubscribe() :: @SubscribeOn -> RxNewThreadScheduler-3]\n                        D  Frodo => [@Observable#list -> onNext() -> [Name: Batman, Name: Superman]]\n                        D  Frodo => [@Observable#list -> onCompleted()]\n                        D  Frodo => [@Observable#list -> onTerminate() :: @Emitted -> 1 element :: @Time -> 0 ms]\n                        D  Frodo => [@Observable#list -> onUnsubscribe() :: @ObserveOn -> main]\n```\n\n**主要意思是在ObservableSample类中我们订阅了一个返回Observable的list()方法**。然后通过带注释的Observable我们获得了关于被发送的元素、执行方法和事件被触发的信息。\n\n##检验@RxLogSubscriber\n\n**现在让我们看下@RxLogSubscriber能做什么。**\n\n下面例子，让我们创建一个用**@RxLogSubscriber**做注释的**RxJava**虚拟Subscriber。\n\nMySubscriberBackpressure.java\n\n```java\n@RxLogSubscriber\npublic class MySubscriberBackpressure extends Subscriber<Integer> {\n\n  @Override\n  public void onStart() {\n    request(16);\n  }\n\n  @Override\n  public void onNext(Integer value) {\n    //empty\n  }\n\n  @Override\n  public void onError(Throwable throwable) {\n    //empty\n  }\n\n  @Override\n  public void onCompleted() {\n    if (!isUnsubscribed()) {\n      unsubscribe();\n    }\n  }\n}\n```\n\n现在我们不提这个Subscriber的名字[backpressure](https://github.com/ReactiveX/RxJava/wiki/Backpressure)，因为这个话题得说一整篇文章。只要知道这个Subscriber仅请求16个元素和**onNext()**方法被调用时什么都不会做。可是，**我仍然想看到当被调用时会发生什么。**\n\nObservableSample.java\n\n```java\npublic class ObservableSample {\n\n  public Observable<Integer> numbersBackpressure() {\n    return Observable.create(new Observable.OnSubscribe<Integer>() {\n      @Override\n      public void call(Subscriber<? super Integer> subscriber) {\n        try {\n          if (!subscriber.isUnsubscribed()) {\n            for (int i = 1; i < 10000; i++) {\n              subscriber.onNext(i);\n            }\n            subscriber.onCompleted();\n          }\n        } catch (Exception e) {\n          subscriber.onError(e);\n        }\n      }\n    });\n  }\n}\n```\n\n这里订阅到SampleObservable：\n\nMyClass.java\n\n```java\nfinal ObservableSample observableSample = new ObservableSample();\nobservableSample.numbersBackpressure()\n  .onBackpressureDrop()\n  .subscribeOn(Schedulers.newThread())\n  .observeOn(AndroidSchedulers.mainThread())\n  .subscribe(new MySubscriberBackpressure());\n```  \n\n当我们再次编译并运行程序后，这是在logcat得输出信息：\n\nLogcatShell\n\n```\nSubscriberBackpressure  D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onStart()]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> @Requested -> 40 elements]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 1 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 2 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 3 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 4 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 5 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 6 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 7 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 8 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 9 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 10 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 11 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 12 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 13 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 14 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 15 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onNext() -> 16 :: @ObserveOn -> main]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> onCompleted() :: @Received -> 16 elements :: @Time -> 3 ms]\n                        D  Frodo => [@Subscriber :: MySubscriberBackpressure -> unSubscribe()]\n```\n\n这里得信息包括每一个被接收的item，元素的数量、调用函数，执行事件和触发事件。\n\n正如你所看到了在[backpressure](https://github.com/ReactiveX/RxJava/wiki/Backpressure)中有用的信息，或看到哪个线程发出的item又或者当我们想是否Subscriber被成功订阅，从而避免了内存泄漏。\n\n##Frodo高级选项\n\n在这篇文章中，我没有说明这个库内部工作的细节，如果你感兴趣的话，可以看下我一年前写的[一篇文章](http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/)，它里面包括我同样使用Frodo的一个例子。\n\n[你也可以浏览我已经准备好的一个描述](https://speakerdeck.com/android10/android-aspect-oriented-programming)关于[AOP和这个库](https://speakerdeck.com/android10/android-aspect-oriented-programming)的介绍，更深入的可以看源码。\n\n##免责说明：初期\n\n**Frodo刚刚诞生，未来还有很长的路。它仍然处在一个非常早期的阶段，所以你可能会发现问题或者可以提升的地方。**\n\n实际上，开源的一个主要原因是**在社区中收到反馈或建议**完善自己，让它更好用。我不得不非常激动的说，我已经在3个不同的项目中使用并没有发现很多问题（更多信息可以查看下边的已知问题）。\n\n##已知问题\n\n**目前为止知道的一个已知问题**：因为**Frodo**依赖**Gradle Plugin**（正如之前说的）检测**Android Debug编译版本和植入代码，如果你做为Android Library项目使用**，当你编译应用（即使是debug编译类型），官方的Android Gradle Plugin总是会编译Android Library项目的release版本，这样，**这就阻止了Frodo在注释的方法／类上植入代码**。当然这不会引起你的应用崩溃但在logcat中你看不到任何输出。既然你不希望在商用的release版本中到处都是暴露重要信息的log日志，这里有一个应对方案，你使用的时候要注意。\n在你项目的Android Library module的**build.gradle**文件中添加下边标记：\n**(编者注：新版本的Androdi Studio的library module也可以设置为不混淆了。)**\n\nbuild.gradle\n\n```java\nandroid {\n  defaultPublishConfig \"debug\"\n}\n```\n\n##Frodo应用例子\n\n这个[repository](https://github.com/android10/frodo)包括一个**应用例子**，你可以看到不同情况下的使用实例，比如**Observable的错误和其他log信息**。我也在我的[Android Clean Architecture repo](https://github.com/android10/Android-CleanArchitecture)使用了**Frodo**，你可以去看看。\n\n##总结\n\n**这是我在这边文章中尽力提供的，希望你能找到Frodo的好处**。\n\n最新的版本你可以在这里找到：\nhttps://github.com/android10/frodo\n\n一如既往，欢迎任何反馈，也欢迎贡献，再会。\n\n##相关链接\n> * [Frodo Project website](https://github.com/android10/frodo)\n> * [Aspect Oriented Programming in Android](http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/)\n> * [AO Programming and Frodo Presentation](https://speakerdeck.com/android10/android-aspect-oriented-programming)\n> * [Matthias Käppler Talk at GOTO Conference: Going Reactive](https://www.youtube.com/watch?v=R16OHcZJTno)\n\n\n\n\n"
  },
  {
    "path": "issue-31/Android应用架构.md",
    "content": "> - 原文链接： [Android Application Architecture](https://medium.com/ribot-labs/android-application-architecture-8b6e34acda65#.5k3ch8chj)\n> - 原文作者： [Iván Carballo](https://medium.com/@ivanc)\n> - 译文出自： [小鄧子的简书](http://www.jianshu.com/users/df40282480b4/latest_articles)\n> - 译者：       小鄧子\n> - 状态： 完成\n\nAndroid开发生态圈的节奏非常之快。每周都会有新的工具诞生，类库的更新，博客的发表以及技术探讨。如果你外出度假一个月，当你回来的时候可能已经发布了新版本的**Support Library**或者**Play Services**\n\n我与[Ribot Team](http://ribot.co.uk/us/)一起做Android应用已经超过三年了。这段时间，我们所构建的Android应用架构和技术也在不断地演变。本文将向您阐述我们的经验，错误以及架构变化背后的原因。\n\n**曾经的架构**\n\n追溯到2012年我们的代码库使用的是基本结构，那个时候我们没有使用任何第三方网络类库，而且`AsyncTask`也是我们的好朋友。当时的架构可以大致表示为下图。\n\n![](http://upload-images.jianshu.io/upload_images/268450-d0779c07dd977f18.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n代码被划分为两层结构：**Data Layer（数据层）**负责从REST API或者持久数据存储区检索和存储数据；**View Layer（视图层）**的职责是处理并将数据展示在UI上。\n\n**APIProvider**提供了一些方法，使Activity和Fragment能够很容易的实现与REST API的数据交互。这些方法使用URLConnection和AsyncTask在一个单独的线程内执行网络请求，然后通过回调将结果返回给Activity。\n\n\n按照同样的方式，**CacheProvider** 所包含的方法负责从SharedPreferences和SQLite数据库检索和存储数据。同样使用回调的方式，将结果传回Activity。\n\n存在的问题：\n\n使用这种结构，最主要的问题在于**View Layer**持有太多的职责。想象一个简单且常见的场景，应用需要加载一个博客文章列表，然后缓存这些条目到SQLite数据库，最后将他们展示到ListView等列表视图上。Activity要做到以下几个步骤：\n\n1. 通过APIProvider调用`loadPosts`方法（回调）\n\n2. 等待APIProvider的回调结果，然后调用CacheProvider中的`savePosts`方法（回调）\n\n3. 等待CacheProvider的回调结果，然后将这些文章展示到ListView等列表视图上\n\n4. 分别处理APIProvider和CacheProvider回调中潜在的异常。\n\n这是一个非常简单的例子，在实际开发环境中REST API返回的数据可能并不是View直接需要的。因此，Activity在进行展示之前不得不通过某种方式将数据进行转换或过滤。另一个常见的情况是，调用`loadPosts( )`所需要的参数，需要事先从其他地方获取到，比如，需要Play Services SDK提供一个Email地址参数。就像SDK通过异步回调的方式返回Email地址，这就意味着现在我们至少有三层嵌套的回调。如果继续添加复杂的业务逻辑，这种架构就会陷入众所周知的**Callback Hell（回调地狱）**。\n\n总结：\n\n- Activitty和Fragment变得非常庞大并且难以维护。\n\n- 太多的回调嵌套意味着丑陋的代码结构而且不易读懂和理解。如果在这个基础上做更改或者添加新特性会感到很痛苦。\n\n- 单元测试变得非常有挑战性，如果有可能的话，因为很多逻辑都留在了Activity或者Fragment中，这样进行单元测试是很艰难的。\n\n**RxJava驱动的新型架构**\n\n我们使用上文提到的组织架构差不多两年的时间。在那段时间内，我们做了一些改进，稍微缓解了上述问题。例如，我们添加了一些**Helper Class（帮助类）**用来减少Activity和Fragment中的代码，在**APIProvider**中使用了[Volley](http://developer.android.com/training/volley/index.html)。尽管做出了这些改变，我们应用程序的代码还是不能进行友好的测试，并且**Callback Hell（回调地狱）**的问题还是经常发生。\n\n直到2014年我们开始了解[RxJava](http://reactivex.io/)。在尝试了几个示例项目之后，我们意识到她可能最终帮助我们解决掉嵌套回调的问题。如果你还不熟悉响应式编程，可以阅读[本文](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)（译者注：译文点这里[那些年我们错过的响应式编程](https://github.com/yaoqinwei/android-tech-frontier/tree/master/androidweekly/%E9%82%A3%E4%BA%9B%E5%B9%B4%E6%88%91%E4%BB%AC%E9%94%99%E8%BF%87%E7%9A%84%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B)）。简而言之，**RxJava允许通过异步流的方式处理数据，并且提供了很多操作符，你可以将这些操作符作用于流上从而实现转换，过滤或者合并数据等操作**。\n\n考虑到经历了前几年的痛苦，我们开始考虑，一个新的应用程序体系架构看起来会是怎样的。因此，我们想出了这个。\n\n![](http://upload-images.jianshu.io/upload_images/268450-ed92e832ea029341.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n类似于第一种架构，这种体系架构同样被划分为**Data Layer**和**View Layer**。**Data Layer**持有`DataManager`和一系列的**Helper classe** 。**View Layer**由Android的Framework组件组成，例如，Fragment，Activity，ViewGroup等。\n\n**Helper classes**（图标中的第三列）有着非常特殊的职责以及简洁的实现方式。例如，很多项目需要一些帮助类对REST API进行访问，从数据库读取数据，或者与三方SDK进行交互等。不同的应用拥有不同数量的帮助类，但也存在着一些共性：\n\n- PreferencesHelper：从`SharedPreferences`读取和存储数据。\n\n- DatabaseHelper：处理操作SQLite数据库。\n\n- [Retrofit](https://github.com/square/retrofit) services：执行访问REST API，我们现在使用Retrofit来代替Volley，因为它天生支持RxJava。而且也更好用。\n\n帮助类里面的大多数public方法都会返回RxJava的`Observable`。\n\n**DataManager**是整个架构中的大脑。它广泛的使用了RxJava的操作符用来合并，过滤和转换从帮助类中返回的数据。DataManager旨在减少Activity和Fragment的工作量，它们（译者注：指Activity和Fragment）要做的就是展示已经准备好的数据而不需要再进行转换了。\n\n下面这段代码展示了一个DataManager方法可能的样子。这个简单的示例方法如下：\n\n1. 调用Retrofit service从REST API加载一个博客文章列表\n\n2. 使用DatabaseHelper保存文章到本地数据库，达到缓存的目的\n\n3. 筛选出今天发表的博客，因为那才是**View Layer**想要展示的。\n\n    \n    public Observable<Post> loadTodayPosts() {\n                return mRetrofitService.loadPosts()\n                        .concatMap(new Func1<List<Post>, Observable<Post>>() {\n                            @Override\n                            public Observable<Post> call(List<Post> apiPosts) {\n                                return mDatabaseHelper.savePosts(apiPosts);\n                            }\n                        })\n                        .filter(new Func1<Post, Boolean>() {\n                            @Override\n                            public Boolean call(Post post) {\n                                return isToday(post.date);\n                            }\n                        });\n        }\n    \n\n在**View Layer**中诸如Activity或者Fragment等组件只需调用这个方法，然后订阅返回的`Observable`即可。一旦订阅完成，通过Observable发送的不同博客，就能够立即被添加进Adapter从而展示到RecyclerView或其他类似控件上。\n\n这个架构的最后元素就是**Event Bus（事件总线）**。它允许我们在**Data Layer**中发送事件，以便**View Layer**中的多个组件都能够订阅到这些事件。比如DataManager中的退出登录方法可以发送一个事件，订阅这个事件的多个Activity在接收到该事件后就能够更改它们的UI视图，从而显示一个登出状态。\n\n为什么这种架构更好？\n\n- RxJava的Observable和操作符避免了嵌套回调的出现。\n\n![](http://upload-images.jianshu.io/upload_images/268450-ee2fd0d4f3e136d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- DataManager接管了以前**View Layer**的部分职责。因此，它使Activity和Fragment变得更轻量了。\n\n- 将代码从Activity和Fragment转移到了DataManager和帮助类中，就意味着使写单元测试变得更简单。\n\n- 明确的职责分离和DataManager作为唯一与**Data Layer**进行交互的点，使这个架构变得**Test-Friendly**。帮助类和DataManager能够很容易的被模拟出来。\n\n我们还存在什么问题？\n\n- 对于庞大和复杂的项目来讲，DataManager会变得非常的臃肿和难以维护。\n\n- 尽管**View Layer**诸如Activity和Fragment等组件变得更轻量，它们让然要处理大量的逻辑，如管理RxJava的订阅，解析错误等方面。\n    \n**集成MVP**\n\n在过去的一年中，几个架构设计模式，如[MVP](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)或者[MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel)在Android社区内已经越来越受欢迎了。通过在[示例工程]( [sample project](https://github.com/ivacf/archi))和[文章](https://medium.com/ribot-labs/approaching-android-with-mvvm-8ceec02d5442#.x98dqfu6m)中进行探索后，我们发现MVP，可能给我们现有的架构带来非常价值的改进。因为当前我们的架构已经被划分为两个层（视图层和数据层），添加MVP会更自然些。我们只需要添加一个新的presenter层，然后将View中的部分代码转移到presenter就行了。\n\n![](http://upload-images.jianshu.io/upload_images/268450-8fb3ea676790c25c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n留下的**Data Layer**保持不变，只不过为了与这种模式保持一致性，它现在被叫做**Model**。\n\n**Presenter**负责从**Model**中加载数据，然后当数据准备好之后调用**View**中相对应的方法。还负责订阅DataManager返回的Observable。所以，他们还需要处理[schedulers](http://reactivex.io/documentation/scheduler.html)和[subscriptions](http://reactivex.io/RxJava/javadoc/rx/Subscription.html)。此外，它们还能分析错误代码或者在需要的情况下为数据流提供额外的操作。例如，如果我们需要过滤一些数据而且这个相同的过滤器是不可能被重用在其他地方的，这样的话在**Presenter**中实现比在DataManager中或许更有意义。\n\n下面你将看到在**Presenter**中一个public方法将是什么样子。这段代码订阅我们在前一节中定义的`dataManager.loadTodayPosts( )`所返回的Observable。\n\n    public void loadTodayPosts() {\n        mMvpView.showProgressIndicator(true);\n        mSubscription = mDataManager.loadTodayPosts().toList()\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribeOn(Schedulers.io())\n                .subscribe(new Subscriber<List<Post>>() {\n                    @Override\n                    public void onCompleted() {\n                        mMvpView.showProgressIndicator(false);\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        mMvpView.showProgressIndicator(false);\n                        mMvpView.showError();\n                    }\n\n                    @Override\n                    public void onNext(List<Post> postsList) {\n                        mMvpView.showPosts(postsList);\n                    }\n                });\n        }\n\n`mMvpView`是与**Presenter**一起协助的View组件。通常情况下是一个`Activity`，`Fragment`或者`ViewGroup`的实例。\n\n像之前的架构，**View Layer**持有标准的Framework组件，如ViewGroup，Fragment或者Activity。最主要的不同在于这些组件不再直接订阅Observable。取而代之的是通过实现*MvpView*接口，然后提供一些列简洁的方法函数，比如`showError( )`或者`showProgressIndicator( )`。这个View组件也负责处理用户交互，如点击事件和调用相应**Presenter**中的正确方法。例如，我有一个按钮用来加载博客列表，Activity将会在点击事件的监听中调用`presenter.loadTodayPosts( )`\n\n> 如果你想看到一个完整的运用MVP基本架构的工作示例，可以从Github检出我们的[Android Boilerplate project](https://github.com/ribot/android-boilerplate)。也可以从这里阅读关于它的更多信息[Ribot的架构指导](https://github.com/ribot/android-guidelines/blob/master/architecture_guidelines/android_architecture.md)\n\n为什么这种架构更好？\n\n- Activity和Fragment变得非常轻量。他们唯一的职责就是建立/更新UI和处理用户事件。因此，他们变得更容易维护。\n\n- 现在我们通过模拟**View Layer**可以很容易的编写出单元测试。之前这些代码是**View Layer**的一部分，所以我们很难对它进行单元测试。整个架构变得测试友好。\n\n- 如果DataManager变得臃肿，我们可以通过转移一些代码到**Presenter**来缓解这个问题。\n\n我们依然存在哪些问题？\n\n- 当代码库变得非常庞大和复杂时，单一的DataManager依然是一个问题。虽然我们还没有走到这一步，但这是一个真正值得注意的问题，我们已经意识到了这一点，它可能发生。\n\n值得一提的是它并不是一个完美的架构。事实上，不要天真的认为这是一个独特且完美的方案，能够解决你所有的问题。Android生态系统将保持快速发展的步伐，我们必须继续探索。不断地阅读和尝试，这样我们才能找到更好的方法来继续构建优秀的Android应用程序。\n"
  },
  {
    "path": "issue-31/Android开发生僻却实用的知识点Part3.md",
    "content": "Android 开发生僻却实用的知识点 Part 3\n---\n\n> * 原文链接 : [Android Development Tidbits // No. 3](http://willowtreeapps.com/blog/android-development-tidbits-no-3/)\n* 原文作者 : [Charlie](http://willowtreeapps.com/category/development-blog/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n欢迎大家阅读“Android 开发生僻却实用的知识点”系列博文第三部分，非常感谢各位能够关注本系列博文，以及在邮件和留言上表达的支持！\n\n> 如果你是第一次阅读本系列博文：我们团队每周都会队内讨论、分享一些 Android 开发生僻的知识点，而最近我们决定在博客中分享我们的所见所得。在 [Part 1](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-30/Android%E5%BC%80%E5%8F%91%E7%94%9F%E5%83%BB%E5%8D%B4%E5%AE%9E%E7%94%A8%E7%9A%84%E7%9F%A5%E8%AF%86%E7%82%B9Part1.md) 和 [Part 2](https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-30/Android%E5%BC%80%E5%8F%91%E7%94%9F%E5%83%BB%E5%8D%B4%E5%AE%9E%E7%94%A8%E7%9A%84%E7%9F%A5%E8%AF%86%E7%82%B9Part2.md) 里我们已经分享了一些知识点了，有兴趣的话你可以去看看。\n\n##Tidbit One\n\n如果你正在使用由 ZXing 开发的二维码生成器库来生成二维码，你会发现用它来生成大图片有点慢。但你可以换一种办法来生成大图片，如果你传递 0 x 0 的图片大小给库，它会返回一个最小尺寸的 BitMatrix （每一个块都是1像素）。然后你可以把该 Matrix 写入 BitmapDrawable，并将它设为某些 View 的背景。使用这个办法前确保已经对 Drawable 调用 setFilterBitmap(false)，不然的话在缩放的时候图片会模糊。\n\n```java\nBitMatrix matrix = new QRCodeWriter().encode(\"content here\", BarcodeFormat.QR_CODE, 0, 0);\nint height = matrix.getHeight();\nint width = matrix.getWidth();\nBitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);\nfor (int x = 0; x < width; x++) {\nfor (int y = 0; y < width; y++) {\nbmp.setPixel(x, y, matrix.get(x, y) ? Color.BLACK : Color.TRANSPARENT);\n}\n}\nBitmapDrawable qrCodeDrawable = new BitmapDrawable(getResources(), bmp);\nqrCodeDrawable.setFilterBitmap(false);\nimgQrCode.setBackground(qrCodeDrawable);\n```\n\n由于我需要二维码的背景是透明的，所以我使用了 Bitmap.Config.ARGB_4444。如果你想让二维码只有黑白两种颜色，可以用 Bitmap.Config.RGB_565。如果你怕乱改会有什么麻烦，你可以把二维码的创建方法改为：BitMatrix matrix = new QRCodeWriter().encode(\"content here\", BarcodeFormat.QR_CODE, 10, 10)。但有一点一定要注意，库可能在未来被更新为不接受 0 x 0 参数，毕竟二维码最小也不可能比 10 x 10 还小吧。\n\n– Tidbit contributor, James Sun\n\n##Tidbit Two\n\n\n键入 adb hell 和 adb shell 的结果是一样的\n\n– Tidbit contributor, Tyler Romeo\n\n##Tidbit Three\n\nTextUtils.concat() 能将连接输入的 CharSequences 连接在一起，并保持它们的间距，并且返回值仍为 CharSequences。\n\n– Tidbit contributor, Walker Hannan\n\n##Tidbit Four\n\n一般子 View 处理的点击事件都是由父 View 拦截并分发下来的，所以如果你需要使用某个子 View 正在处理的点击事件，就使用拦截事件的方法。如果子 View 调用了 setRequestDisallowInterceptTouchEvent，而你又不希望自己的拦截被禁止，那就重载 setRequestDisallowInterceptTouchEvent 这个方法吧。\n\n– Tidbit contributor, Frank Doyle\n\n##Tidbit Five\n\n在执行一些耗时操作的时候可以调用 SqliteDatabase 的 beginTransaction() 和 endTransaction() 方法，但要确保调用了 setTransactionSuccessful()，要不然在调用 endTransaction() 的时候你的操作作出的改变会被回滚。\n\n– Tidbit contributor, Walker Hannan\n\n##Tidbit Six\n\n如果你在添加测试用例，千万要小心使用静态方法！因为某些奇奇怪怪的原因，Android 框架层提供的很多方法在测试单元里根本不能用，而且由于它们是静态方法，你甚至不能模拟它们。\n\n– Tidbit contributor, Frank Doyle\n\n##Tidbit Seven\n\n如果你在给应用添加测试用例，不妨试试 Mockito，它会大大简化你之前那些测试用例中复杂的对象依赖。\n\n– Tidbit contributor, Frank Doyle\n"
  },
  {
    "path": "issue-31/Espresso保存和恢复状态.md",
    "content": "Espresso:保存和恢复状态\n---\n\n> * 原文链接 : [Espresso: Save and restore state](http://blog.sqisland.com/2015/10/espresso-save-and-restore-state.html?utm_source=Android+Weekly&utm_campaign=553bcbfc02-Android_Weekly_174&utm_medium=email&utm_term=0_4eb677ad19-553bcbfc02-337955857)\n* 原文作者 : [Chiu-Ki Chan](http://blog.sqisland.com)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成  \n  \n你保存并且恢复activities,fragments和自定义视图的状态吗?你有测试过吗?\n  \n一种测试保存和恢复状态的方法就是在你的Espresso测试中旋转屏幕.\n\n\tprivate void rotateScreen() {\n\t\tContext context = InstrumentationRegistry.getTargetContext();\n\t\tint orientation = context.getResources().getConfiguration().orientation;\n\t\t\n\t\tActivity activity = activityRule.getActivity();\n\t\tactivity.setRequestedOrientation(\n\t\t\t\t(orientation == Configuration.ORIENTATION_PORTRAIT) ?\n          \t\tActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :\n          \t\tActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n\t}\n\n \n有了上面的辅助方法,你可以写出如下的测试:\n\n\t@Test\n\tpublic void incrementTwiceAndRotateScreen() {\n\t\tonView(withId(R.id.count))\n      \t.check(matches(withText(\"0\")));\n      \t\n      \tonView(withId(R.id.increment_button))\n      \t.perform(click(), click());\n      \tonView(withId(R.id.count))\n      \t.check(matches(withText(\"2\")));\n      \t\n      \trotateScreen();\n      \t\n      \tonView(withId(R.id.count))\n      \t.check(matches(withText(\"2\")));\n\t}\n \n[源码](https://github.com/chiuki/espresso-samples/tree/master/rotate-screen)"
  },
  {
    "path": "issue-31/使用ClassyShark压缩你的项目.md",
    "content": "﻿使用ClassyShark压缩你的项目\n---\n\n> * 原文链接 : [Shrinking Your Build With No Rules\nand do it with Class(yShark)](https://medium.com/@_tiwiz/shrinking-your-build-with-no-rules-8d9fb88281ac#.z596cgoll)\n* 原文作者 : [Roberto Orgiu](https://medium.com/@_tiwiz)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [XWZack](https://github.com/XWZack) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)     \n* 状态 :   校对中  `\n\n\n最近，我们的项目中用到了一个重要的框架，它为我们的产品提供了一个非常关键的功能。\n\n你可以想象一下，这个框架的SDK是相当巨大的，它包含**45K的方法**：这足以使我们的项目（已经用到了Support Library和Play Services）产生使用MultiDex分包的冲动。\n\n## 深入引用库\n去掉引用库中不需要的部分是相当简单的，但是理解如何修剪上面提到的框架又是另外一回事了：该框架需要作为我们项目的子模块被引入，从而使**4个.so文件和3个.jar包**被正确关联。在特定平台上构建的本地库带有一个.so拓展文件，这个文件通常放置在与系统架构，诸如x86或armeabi有关的文件夹中。\n\n现在唯一的选择就是让这个框架脱离ProGuard的混淆范围，但是这样一来，即便不用MultiDex分包，至少最终APK的大小也会达到将近50MB。也就是说，这样做的效果肯定不理想：每当我们发布更新，所有用户必须下载整个APK，之后APK被解压，并在每个设备上占用更多的空间，而这些空间我们本不需要占用。\n\n## 以前的处理方法\n如果放在几个星期前，我们可能会这样处理：用[ApkTool](http://ibotpeaches.github.io/Apktool/)**反编译每个引用库**，手动查找所有的引用，然后用Atom或者Sublime Text浏览每个文件。如果真的这么做了，可能会浪费大量的时间：因为在反编译APK的时候，ApkTool是一个非常棒的工具，但对于这种特殊情况，我们需要一个更灵活，可能也更有帮助的工具。\n\n## ClassyShark介绍\n幸运的是，有这样一个工具：[ClassyShark](https://github.com/google/android-classyshark)。\n这个软件是正是我们所需要的：通过简单地打开菜单根目录的.jar，我们可以很容易地跟踪每个被我们调用的方法的依赖关系，**几乎就像是用IDE浏览源代码**。    \n\n![我们可以看到一个本地库正在被ClassyShark反编译](https://cdn-images-1.medium.com/max/1000/1*o3JmaZMrKyUjAXJrC7WcKg.png)\n\n例如，通过双击反编译库中声明的类型，就可以轻松地打开了这个类，并跟踪相关的依赖列表。\n\n此外，如果你由于某种原因无法找到想要的文件，通过窗口顶部的大小写敏感的输入框你总能搜到你想要的类。\n\n在ClassyShark的帮助下，我们可以“轻松地”获取我们需要保存的所有依赖，并在很短的时间内为ProGuard添加正确的规则，使我们从令人头疼的手动跟踪依赖链接，甚至编译一遍只是想看看少了哪些引用中解救出来。\n\n## 结语\n不幸的是，我们无法摆脱MultiDex库，但我们的确缩小了我们的项目，并且我们**发行版本的APK大小，现在大概13 MB**，相比于初始大小减少了约75％。就算没有完美解决，也算得上是功德圆满了。\n\n感谢我的同事 Giuseppe和我的朋友 Boris, Mario 和 Sebastiano校对这篇文章。\n\n\n\n\n\n"
  },
  {
    "path": "issue-32/AppBarLayout的越界滚动行为.md",
    "content": "AppBarLayout的越界滚动行为\n---\n\n> * 原文链接 : [Overscroll AppBarLayout Behavior](https://medium.com/@nullthemall/overscroll-appbarlayout-behavior-e58f1ee2807#.gtmxsk7sw)\n* 原文作者 : [Nikola Despotoski](https://medium.com/@nullthemall)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [liuling07](https://github.com/liuling07) \n* 校对者: [desmond1121](https://github.com/desmond1121)\n* 状态 :  完成 \n\n很不幸，Youtube音乐应用在我们国家不可使用，我尝试着通过各种盗版网站来获取该应用，但我仍然无法看到在这个应用上发生了什么。感谢这位[redditor](https://www.reddit.com/user/IanSan5653)，在我的请求下，他在[/r/materialdesign](https://www.reddit.com/r/materialdesign)打开了一个[thread](https://www.reddit.com/r/MaterialDesign/comments/3slct5/youtube_music_has_tons_of_animations_and/)并且发表一段录制的视频，我才有机会看到这个行为。\n\n![Youtube视频app的真实截图，可能的行为](https://cdn-images-1.medium.com/max/1200/1*lEMS5RiBLGk3Q72FhXBwxA.gif)\n\n根据我所看到的，我首先想到的就是专辑封面是放到一个AppBarLayout里面，并且在滚动区域拖到边界的时候尺寸会发生变化。让我们假定这个猜想是正确的并且用“Behavior”这个术语表示它。依鄙人之见，如果我的猜想是正确的，谷歌应该会在Material Design文档的[滚动](https://www.google.com/design/spec/patterns/scrolling-techniques.html)部分提供一个越界滚动的使用说明。\n\n我们的目标就是保证AppBarLayout.Behavior的完整性，在此基础上再创建一个扩展的行为。因此：\n\n```\npublic class OverscrollScalingViewAppBarLayoutBehavior extends AppBarLayout.ScrollingViewBehavior\n```\n\n因为这是默认的AppBarLayout.Behavior，所以建议只有在依赖视图是AppBarLayout的时候起作用。\n\n```\n@Override\npublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {\n return dependency instanceof AppBarLayout;\n}\n```\n\n接下来，我们需要获取想要在拖到边界时要改变尺寸的视图的一个实例。最好的方法就是在onLayoutChild()方法中获取：\n\n\n```\n@Override\npublic boolean onLayoutChild(CoordinatorLayout parent ....) {\n    boolean superLayout = super.onLayoutChild(parent, abl, layoutDirection);\n    if (mTargetScalingView == null) {\n        mTargetScalingView = parent.findViewByTag(TAG);\n        if(mTargetScalingView != null){\n             mScaleImpl.obtainInitialValues();\n         }\n     }\n    return superLayout;\n}\n```\n\n而且我们需要保证只有在垂直滚动的时候起作用：\n\n```\n@Override\npublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,... int nestedScrollAxes) {\n    return nestedScrollAxes == View.SCROLL_AXIS_VERTICAL;\n}\n```\n\n如果我们先前没有在程序中显示设置，会设置ViewScaler为默认的Scaler。\n\n在内容滚动的瞬间，真正重要的问题就有头绪了。CoordinatorLayout.Behavior提供了一个onNestedScroll()方法，当滚动进行的时候这个方法会被调用，并且当内容滚动到边界的时候也会调用。最后两个参数dyUnconsumed和dxUnconsumed提供了未被该行为的目标视图填满的像素值。\n\n这个方法对我们实现尺寸改变来说太重要了。所以我列出了哪些情况需要改变尺寸，哪些情况不需要：\n\n#### 需要改变尺寸\n1. 存在未填满的像素，如dyUnconsumed小于0  \n2. AppBarLayout是展开的，getTopAndBottomOffset() >= mScaleImpl.getInitialParentBottom()\n\n#### 不需要改变尺寸\n1. AppBarLayout中没有子视图可以改变尺寸\n2. 有填充的像素，如dyConsumed不等于0\n\n```\n@Override\npublic void onNestedScroll(CoordinatorLayout ... int dxUnconsumed, int dyUnconsumed) {\n    if (mTargetScalingView == null || dyConsumed != 0) {\n        mScaleImpl.cancelAnimations();\n        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);\n        return;\n    }\n\n    if (dyUnconsumed < 0 && getTopAndBottomOffset() >= mScaleImpl.getInitialParentBottom()) {\n        int absDyUnconsumed = Math.abs(dyUnconsumed);\n        mTotalDyUnconsumed += absDyUnconsumed;\n        mTotalDyUnconsumed = Math.min(mTotalDyUnconsumed, mTotalTargetDyUnconsumed);\n        mScaleImpl.updateViewScale();\n    } else {\n        mTotalDyUnconsumed = 0;\n        mScaleImpl.setShouldRestore(false);\n        if (dyConsumed != 0) {\n            mScaleImpl.cancelAnimations();\n        }\n        super.onNestedScroll(coordinatorLayout, .... dxUnconsumed, dyUnconsumed);\n    }\n}\n```\n\n当嵌套的overscroll停止的时候，我们需要将视图的边界和大小重置到它们的原始值。\n\n```\n@Override\npublic void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {\n    mScaleImpl.retractScale();\n    super.onStopNestedScroll(coordinatorLayout, child, target);\n\n}\n```\n\n# ViewScaler\n这个类实现了AppBarLayout应该如何改变它的底部以及视图应该如何改变尺寸的逻辑。大多数行为都依赖累积的未填充的像素。我们可以为最大累积值设置一个约束值，这样可以很容的找到要如何改变AppBarLayout底部和改变视图的尺寸。ParentScaler是ViewScaler的父类，它能让AppBarLayout近乎平滑的改变尺寸。我就不在这里贴大量代码了，如果你有兴趣，[可以从这里获取代码](https://gist.github.com/NikolaDespotoski/7d6a019e5aafe60ebade)。\n\n#### Bonus \n大神们，这里有个MatrixScaler类，我没有时间去完成它。如果想要改变尺寸的视图是ImageView，并且设置了ScaleType为MATRIX，这个类将可以用使用矩阵的方式来改变图像的尺寸。  \n\n# Demo\n[Demo演示地址](https://youtu.be/2udXoC8AXSM)\n\n"
  },
  {
    "path": "issue-32/剪刀手—Android平台上的图片裁剪库.md",
    "content": "剪刀手:Android平台上的图片裁剪库\n---\n\n> * 原文链接 : [Scissors: an image cropping library for Android](https://eng.lyft.com/scissors-an-image-cropping-library-for-android-a56369154a19#.ebe64l3dy)\n* 原文作者 : [Evelio Tarazona](https://medium.com/@eveliotc)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Damonzh](https://github.com/Damonzh) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  未完成 / 校对中 / 完成 \n\n\n几个月前我们往我们的App中[引入个人简介功能](http://blog.lyft.com/posts/profiles)后，Helen——我们其中一个特性团队的工程师——接到了对个人简介进行第二次迭代的任务。这次版本迭代包括了众多的改进，其中包括支持自定义个人头像功能，头像可以用相机直接拍摄，也可以从Gallery或者Photos这类相册App中选择。\n\n![Lyft的Android版本自定义图像的早期设计](https://cdn-images-1.medium.com/max/1600/1*An_iDXn6RtufzwUIdSUc-w.png)\nLyft的Android版本自定义头像的早期设计\n\n在图片被上传到服务器之前，图片必须被裁剪以符合一定的要求，这其中包括:\n\n* 放大到原尺寸的200%\n* 移动和截图\n* 不管在什么样的屏幕密度上都保持固定的比例\n* 基于当前屏幕尺寸进行裁剪\n\n因为在Lyft我们都喜欢开源，所以自然而然的想到了搜索现成的解决方案。但是没有一个能满足我们的需求，所以我们决定自己动手实现这个需求。一晃几个月过去了，我们现在打算将这个库([Scissors](https://github.com/lyft/scissors))的核心代码开源。\n\n## Scissors\n![](https://cdn-images-1.medium.com/max/1600/1*o9wj6y7Xt5zn6nHI4p5t7Q.png)\n\n### 这个库能做什么？\n[Scissors](https://github.com/lyft/scissors)提供了一个叫做**CropView**的控件，它继承于[ImageView](https://developer.android.com/reference/android/widget/ImageView.html)并且提供了熟悉的方式来提供用于裁剪的图像，比如使用[setImageBitmap](https://developer.android.com/reference/android/widget/ImageView.html#setImageBitmap%28android.graphics.Bitmap%29)来设置要裁剪的图像。一旦用户设置好了要裁剪的位置和缩放比例(这受限于cropviewMaxScale和cropviewMinScale)只需调用\n\n~~~java\nBitmap croppedBitmap = cropView.crop();\n~~~\n该方法返回的Bitmap符合视图的尺寸，这个尺寸可以通过cropviewViewportHeightRatio来进行控制。  \n\n![](https://cdn-images-1.medium.com/max/1600/1*SbhkZppPqhMwj4CdcrFS_w.gif)  \n\n### 扩展\n我们也提供了一些实用的扩展来进行一些常见的任务，比如:  \n\n* 使用[Picasso](https://github.com/square/picasso)或者[Glide](https://github.com/bumptech/glide)往CropView里加载Bitmap并且让图片适应视图的尺寸\n\n ~~~java\ncropView.extensions()\n .load(galleryUri);\n ~~~ \n \n 你也可以用你喜欢的方式创建自定义的BitmapLoader来提供Bitmap\n \n *在不阻塞主线程的前提下保存裁剪好的Bitmap到文件或者流中\n \n ~~~java\n cropView.extensions()\n .crop()\n .quality(87)\n .format(PNG)\n .into(croppedFile);\n ~~~\n \n 你也可以指定裁剪后输出到文件或者流中的图片格式和质量\n\n### 未来的计划\n我们想让Scissors越来越好用，所以以后Scissors将会支持[双击拖动与缩放](https://github.com/lyft/scissors/issues/1)，同时也会[修复一些bug和进行一些优化](https://github.com/lyft/scissors/issues/)。我们希望Scissors对你来说是有用的并且能够满足你所有关于图片裁剪的需求。\n\n[开始使用Scissors吧](https://github.com/lyft/scissors)\n\n\n"
  },
  {
    "path": "issue-32/实现安卓6.0的直接分享（Direct Share ）功能.md",
    "content": "实现Android6.0的直接分享（Direct Share ）功能\n---\n\n> * 原文链接 : [Implementing Android Marshmallow Direct Share](https://www.bignerdranch.com/blog/implementing-android-marshmallow-direct-share/)\n* 原文作者 : [Matt Compton](https://www.bignerdranch.com/about-us/nerds/matt-compton/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [Davidrou](https://github.com/davidrou) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  翻译完成 \n\n\n\n\n直接分享（Direct Share ）是在Android6.0（棉花糖版本）的一个新功能，允许用户在一个应用里面分享内容到其他地方，比如联系人。\n核心思想是，用户可以直接分享相关内容而无需先打开一个的应用程序再去分享，这样直接分享允许用户跳过通常的分享流程中的一个步骤。\n\n\n\n我遇到的直接分享这个功能是在研究[Android Platform Features](https://training.bignerdranch.com/classes/one-day-android-platform-features) 和 [Community Tools](https://training.bignerdranch.com/classes/one-day-android-community-tools) 课程的时候，现在它成了我的最喜欢的Android6.0的新特性。\n\n\n\n\n##直接分享概要\n直接分享的一个很好的例子是分享内容给一个社交应用的联系人。最终就像这样：\n![](https://www.bignerdranch.com/img/blog/2015/12/direct-share-example.png)\n\n\n\n随着Hangouts的出现，在短信和收件箱底部，通常会出现分享选择对话框。然而，直接分享是在这个分享对话框顶部列出的各种联系人。而不是点击分享，然后点击分享到的应用，比如说点击Hangouts。最重要的联系人出现在选择对话框里面。在这个流程，用户没有点击进入Hangouts应用并选择一个联系人，而是能够立即选择联系人。\n\n举一个例子比较一下：使用一个示例应用程序，只分享一点文本到选定的联系人。\n\n这是Android5.0的分享对话框：\n\n![](https://www.bignerdranch.com/img/blog/2015/12/direct-share-L.png)\n\n\n这是Android6.0使用直接分享的对话框：\n\n![](https://www.bignerdranch.com/img/blog/2015/12/direct-share-M.png)\n\n\n现在，在这两个Android系统运行的是同一个应用，在低版本的系统中，默认显示了低版本的Android的标准选择对话框。所以，直接分享功能可以被添加到新版本，同时也兼容旧版本。\n\n\n##建立一个ChooserTargetService\n所以直接分享可以节省用户的时间，使分享过程更加方便。那我们如何实现直接分享呢？\n\n第一步是创建一个服务，继承ChooserTargetService，例如以下SampleChooserTargetService.java：\n~~~java\npublic class SampleChooserTargetService extends ChooserTargetService {\n  @Override\n  public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {\n    final List<ChooserTarget> targets = new ArrayList<>();\n    for (int i = 0; i < length; i++) {\n      final String targetName = ...\n      final Icon targetIcon = ...\n      final float targetRanking = ...\n      final ComponentName targetComponentName = ...\n      final Bundle targetExtras = ...\n      targets.add(new ChooserTarget(\n          targetName, targetIcon, targetRanking, targetComponentName, targetExtras\n      ));\n    }\n    return targets;\n  }\n}\n~~~\n\n\n\n重写ongetchoosertargets()方法，返回一个ChooserTarget的List，作为直接分享对话框的选项。\n\n每个ChooserTarget有几个参数：\n* 目标名称\n* 目标图标\n* 这一目标的排序分数（0.0f，1.0f之间），在直接分享的目标太多时使用，低分的选项直接忽略。\n* 选定目标后，启动的组件\n* 在启动下一个组件之前，将添加到原始Intent中的一个附加Bundle\n\n\n\n在选择对话框中目标列表的顺序，和我们提供的List的顺序相同。在创建列表的时候，所有的目标已经完成排序。\n\n你如何创造这些目标有你决定，但通常ChooserTarget信息是由具体应用的模型层model layer决定。\n\n##更新 Android Manifest\n\n创建ChooserTargetService之后，我们需要把他挂载到我们的应用上，这样系统才知道这个service ，才能去使用这个service \n\n首先，你需要在AndroidManifest.xml声明 自定义的ChooserTargetService ：\n\n~~~java\n<service\n    android:name=\".SampleChooserTargetService\"\n    android:label=\"@string/app_name\"\n    android:permission=\"android.permission.BIND_CHOOSER_TARGET_SERVICE\">\n    <intent-filter>\n        <action android:name=\"android.service.chooser.ChooserTargetService\" />\n    </intent-filter>\n</service>\n~~~\n\n\n这样就实现了两点：\n>* 给这个Service绑定了android.permission.BIND_CHOOSER_TARGET_SERVICE权限\n* 引入了一个IntentFilter ，action是android.service.chooser.ChooserTargetService \n\n\n然后，你需要使用 meta-data 标签更新你的AndroidManifest.xml，\n\n~~~java\n\n<activity\n    android:name=\".SendMessageActivity\"\n    android:label=\"@string/app_name\">\n    <intent-filter>\n        <action android:name=\"android.intent.action.SEND\" />\n        <category android:name=\"android.intent.category.DEFAULT\" />\n        <data android:mimeType=\"text/plain\" />\n    </intent-filter>\n    <meta-data\n        android:name=\"android.service.chooser.chooser_target_service\"\n        android:value=\".SampleChooserTargetService\" />\n</activity>\n\n~~~\n\n\n\n对于每一个你想要暴露给ChooserTargetService的Activity,都需要加上 meta-data 。指定name 是 android.service.chooser.chooser_target_service，在Service定义之前指明他的value.当一个隐式的Intent和其中一个Activity匹配，这个intent的选择对话框 将会显示包含这个Activity的应用的ICON同时还会显示 ChooserTarget 列表的图标。选择应用的图标会像标准的分享Intent一样，选择ChooserTarget icon的时候会打开对应的Activity，根据Intent传递过来的数据初始化数据（在ChooserTargetService中指定的）。\n\n\n\n##直接分享流程\n\n让我们快速从用户的角度看一下谷歌提供的直接分享的示例应用的分享流程。用户打开了一个应用，在屏幕中，有一个分享按钮，点击后会发出一个隐式的Intent。\n\n![](https://www.bignerdranch.com/img/blog/2015/12/direct-share-series-1.png)\n\n\n然后用户点击了分享按钮，弹出了分享对话框：\n\n![](https://www.bignerdranch.com/img/blog/2015/12/direct-share-series-2.png)\n\n\n当我们通过metedata绑定到Chooser Service 的 Activity可以响应 隐式 Intent响应的时候，\nChooser Service将会根据我们自定义ChooserTargetService的onGetChooserTargets（）方法生成一个选择列表。\n这个列表会以弹框的形式展示给用户，供用户选择去使用哪种方式响应Intent.\n\n除此之外，真正的通过mete-data绑定的Activity会显示在已经生成的选择目标列表的旁边，用于用户选择Activity完成直接分享。通常来说，这个Activity是一个联系人列表或者是其他的用户列表。\n\n![](https://www.bignerdranch.com/img/blog/2015/12/direct-share-series-3.png)\n\n\n当用户点击了一个目标后，将会略过 “从以下列表选择一个联系人”的步骤，直接分享内容。\n\n\n##避免混乱\n我们必须时刻记住要做一个好市民，直接分享是一个很整洁很一目了然的功能，你不应该把一些不必要的选项加到选择对话框，这样会变得很混乱。否则的话，用户就很难选出自己想要分享到的地方了。\n\n![](https://www.bignerdranch.com/img/blog/2015/12/direct-share-cluttered.png)\n\n\n\n在上面的示例中，应用列出了八个不同的用户作为选择的目标，但也许只有最经常联系的短信联系人应该显示出来。\n\n\n##更多\n\n直接分享，可以简化用户分享体验，更容易的将内容分享到最常用的地方。\n\n如果你感觉这篇文章很有趣，想了解更多关于Android6.0的令人兴奋的功能或各种新的社区工具，你可以去看看我们的一天的 [Android Platform](https://training.bignerdranch.com/classes/one-day-android-platform-features) 和 [Community Tools](https://training.bignerdranch.com/classes/one-day-android-community-tools)课程。作为一个额外的福利，我们将带上这些课程去访问美国大陆的城市。在那里见哦！\n"
  },
  {
    "path": "issue-32/重构Plaid-响应式的MVP模式.md",
    "content": "\n#重构Plaid-响应式的MVP模式(一)\n---\n\n> * 原文链接 :  [REFACTORING PLAID APP - A REACTIVE MVP APPROACH (PART 1)](http://hannesdorfmann.com/android/plaid-refactored-1/)\n* 原文作者 : [HANNES DORFMANN](http://hannesdorfmann.com/)\n* 译文出自 : [hanks.xyz](http://hanks.xyz/2015/12/08/refactoring-plaid-1/)\n* 译者 : [hanks-zyh](https://github.com/hanks-zyh)\n* 校对者: [desmond1121](https://github.com/desmond1121)\n* 状态 :  完成\n\nNick Butcher 在 [github](https://github.com/nickbutcher/plaid) 上开源了Plaid。这个app不仅很酷,还拥有极致的 UI / UX。如此炫酷的app的代码对开发者来说copy下来阅读一下可谓是最佳实践。于是我也copy下来,决定深入Plaid的代码。和往常一样，你会发现里面的一些代码可以用其他的方式实现。因此不只是谈论代码,我决定花一些时间来重构Plaid的部分源代码。我将写3篇文章来分享我关于如何重构Plaid的,这篇文章是第一部分。\n\n前言: 开始我以为自己能重构整个app. 然而,事实是我太天真了,我低估了这个应用的复杂度和新特性的数量。在我花了几个小时阅读 Nick Butcher 的代码之后, 我发现有些功能仅仅通过使用应用程序很难发现。一句话: 我意识到我没有时间把所有的想法付诸行动。因此我的“重构”集中于应用程序的“主页”和“搜索屏幕”。本来想看看有没有更多模块可以重构，但是我没有太多空闲时间来做了。重构代码可以在[github上](https://github.com/sockeqwe/plaid)找到\n\n## 初见\n整体的用户体验和用户交互都非常赞.看下面这条tweet:\n ![图片](https://dn-coding-net-production-pp.qbox.me/754a0ecf-5545-4c24-9d4a-3f16bf89ed8e.png)\n\n使用这个app真是在享受. 它的 UI / UX 对每个开发者或者设计师很激励, 然而,在使用过程中我遇到了一些bug:\n- 同时显示了加载提示和错误提示:\n<iframe width=\"880\" height=\"660\" src=\"https://www.youtube.com/embed/zCwESjEpNdk\" frameborder=\"0\" allowfullscreen></iframe>\n\n- 在筛选面板中你可以从 Dribbble, Designer News 和 Product Hunt 选择你想要显示的来源.但是当取消选择正在加载数据的\"源\"的时候,你会遇到item还在显示的问题:\n<iframe width=\"880\" height=\"660\" src=\"https://www.youtube.com/embed/nJ3VUjpW0N0\" frameborder=\"0\" allowfullscreen></iframe>\n\n- 而且app没有考虑屏幕旋转.当屏幕旋转界面会被重新创建,重新执行http请求,你可以看到加载提示\n<iframe width=\"880\" height=\"660\" src=\"https://www.youtube.com/embed/tuIDrtvL0lg\" frameborder=\"0\" allowfullscreen></iframe>\n\n通常这样的“问题”是由于**面条式代码** 和架构不够好。所以我们来看看显示项目列表的源代码: 在 `HomeActivity` (大约750行代码)处理UI元素的可见性,如 `RecylcerView` 或 `ProgressBar`. `HomeActivity` 包含了何时显示Source-Filters-Drawer(右边侧滑栏)的逻辑。此外,它的 `onactivityresult()`方法里面  做了很多事情,包括发布新的post到designers news。而且它使用 `DataManager`为选定的 filter 加载数据。你看, `HomeActivity` 有很多责任,可能太多了。`DataManager` 基本上使用 `retrofit` 和 `asynctask` 来执行http请求加载数据。这里的最头疼的问题是分页,尤其是RecyclerView滑动到底部时需要加载更多数据。`DataManager` 内部使用`HashMap`来记录每个源显示的页面数据(后端的endpoint 如 “Dribbble Popular” , “Dribbble Recent” 或者 “Designer News Popular”)。项目中使用`FeedAdapter`来显示到 RecyclerView上。 并且`SearchActivity`跟`HomeActivity`非常相似:它也使用`DataManager`和`FeedAdapter`。\n\n## 架构\n\n我觉得当前的项目架构不够清晰。`HomeActivity`管理了太多事情,简直就是神一样的存在。另一个“问题”是,`HomeActivity`从不同的方法和不同的事件中调用相同的(内部)方法去改变UI状态,如 `checkEmptyState()` 在`HomeActivity`的4个不同的地方被调用.\n\n我们将使用 `Model-View-Presenter` 来重构一下.  passive view 仅仅用来展示, `presenter` 告诉 `passive view` 什么时候展示. 我比较热衷于MVP架构. 经常有人问我为什么我建议使用 passive view,而不是控制器或MVP派生的其他模式。好吧,如果你使用没有 passive view的MVP,你基本上是把**面条式代码**写成了一半的 presenter 一半的 passive view。\n\n## HomeActivity in MVP\n\n我们使用 `MVP +  passive view` 分出两个类: `HomeActivity` 和 `HomeView`,  `HomeActivity`作为View (passive view ) 实现了`HomeView`接口:\n\n```kotlin\ninterface HomeView : MvpView {\n    fun showLoading()\n    fun showContent()\n    fun showError()\n    fun showLoadingMore(showing: Boolean)\n    fun showLoadingMoreError(t: Throwable)\n    fun addOlderItems(items: List<PlaidItem>)\n}\n```\n\n现在 `HomeActivity` 负责管理UI(是否可见,位置,动画),但是由 `presenter` 决定什么时候去执行, 所以View的状态由`HomePresenter` 管理, `HomePresenter` 的代码如下:\n\n```kotlin\nclass HomePresenter(private val itemsLoader: ItemsLoader<List<PlaidItem>>) : RxPresenter<HomeView, List<PlaidItem>>() {\n\n    fun loadItems() {\n\n        view?.showLoading()\n        subscribe(\n                itemsLoader.firstPage(),\n                { // onError\n                    view?.showError()\n                },\n                { // onNext\n                    view?.addOlderItems(it)\n                    view?.showContent()\n                }\n        )\n\n    }\n\n    fun loadMore() {\n\n        view?.showLoadingMore(true)\n        subscribe(\n                itemsLoader.olderPages(),\n                { // onError\n                    view?.showLoadingMoreError(it)\n                },\n                { // onNext\n                    view.addOlderItems(it)\n                    view.showLoadingMore(false)\n                }\n        )\n    }\n}\n```\n\n可能你注意到了,我使用的是kotlin语言,主要是因为我喜欢这个语言并且有幸看到kotlin的发展,由于kotlin的互操作性(与java相互调用)使得我很容易的重用 Nick Butcher的 java 代码(主要是UI 或者View 方面的东西).\n\n为了实现 MVP, 我们使用了 **Mosby** 库,这是一个MVP 库,当旋转屏幕的时候允许我们保持 presenters, 这样就不会在旋转屏幕时重新启动界面,加载提示,进行请求了, **Mosby** 也可以在旋转屏幕的时候保持view的状态.\n\n此外我决定在Model层使用  `RxJava` (这样在 presenters中可以使用 `subscribe()` 方法). `ItemsLoader`类 是我重构的 reactive版本的 DataManager. 我来花一分钟解释一下.\n\n## SearchActivity in MVP\n\n就像前面所说的, `SearchActivity` 和 `HomeActivity` 很像, 它展示了一个列表(grid),并且会在  `RecyclerView`滑动到底部的时候加载更多items, 所以 `SearchActivity` 应用MVP跟前面一样:\n\n```kotlin\npublic interface SearchView extends MvpView {\n  void showLoading();\n  void showContent();\n  void showError(Throwable t);\n  void showLoadingMore(boolean showing);\n  void showLoadingMoreError(Throwable t);\n  void addOlderItems(List<PlaidItem> items);\n  void showSearchNotStarted();\n}\nclass SearchPresenter(private val itemsLoaderFactory: SearchItemsLoaderFactory) : RxPresenter<SearchView, List<PlaidItem>>() {\n\n    private var itemsLoader: ItemsLoader<List<PlaidItem>>? = null\n\n    fun search(query: String) {\n\n        // Create items loader for the given query string\n        itemsLoader = itemsLoaderFactory.create(query)\n\n        view?.showLoading()\n\n        subscribe(itemsLoader!!.firstPage(), { // Error handling\n            view?.showError(it)\n        }, { // onNext\n            view?.addOlderItems(it)\n        }, {\n            view?.showContent()\n        })\n    }\n\n    fun searchMore(query: String) {\n\n        view?.showLoadingMore(true)\n        subscribe(itemsLoader!!.olderPages(), { // Error handling\n            view?.showLoadingMore(false)\n            view?.showLoadingMoreError(it)\n        }, { // onNext\n            view?.addOlderItems(it)\n        }, { // onComplete\n            view?.showLoadingMore(false)\n        })\n\n    }\n\n    fun clearSearch() {\n        // Unsubscribe any previous search subscriptions\n        unsubscribe()\n\n        view.showSearchNotStarted()\n    }\n}\n```\n\n跟 `HomePresenter` 唯一不同的是,  `SearchPresenter` 通过传入一个`SearchItemsLoaderFactory` 作为构造方法的参数,为每次搜索查询创建了一个 `ItemsLoader`. 我们来简单看一下它是怎么工作的.\n\n## ItemsLoader 和 Pagination\n\n目前我们完成了 `View` 和 `Presenter`, 现在讨论一下如何重构 \"Model\"\n\n在我们开始之前: 了解一下 `PlaidItem`类 ( 包含title和 image url). 这个类是单个item的基类\n`Shot` extends `PlaidItem` :代表从Dribbble加载的item\n`Story` extends `PlaidItem`: 代表从Designer News加载的item\n`Post` extends `PlaidItem`: 代表从 Product Hunt 加载的item\n现在我们讨论一下使用`RxJava` 来高效的重写一下 `DataManager`, 我使用RxJava是因为它简直酷毙了, 你将会体会到它的强大(特别是这个系列的第二篇)并且从中受益.\n\n加载项的困难的部分是,我们从不同的后端支持分页和加载items。我们“自下而上”构建 `ItemsLoader` 。“底部”有一个执行 http 的 Retrofit 接口。现在你可以搜索Dribbble和DesignerNews。分页的问题: Dribbble 调用 loadItems(0,100) 来加载100个items, 调用loadItems(100、200)加载下一个页面的100个, 而DesignerNews是每次page加1,通过调用 loadItems(0,1)来加载第一个页面, 调用loadItems(1,2)加载下一个页面。我们需要一个通用的API。我们可以使用kotlin的高级函数(传递函数参数或者匿名函数)。所以我们需要的是一个组件,这个组件包含一个可以执行http请求并返回一个Observable的方法。所以我们需要这样的:  `backendMethodToCall:(Int,Int)-> Observable<T>`, 第一个参数代表page,第二个代表limit(每页加载多少条), T 泛型代表返回结果的类型(事实上类型总是< PlaidItem>)。\n 我们把这个组件叫做 `RouteCaller`:\n\n```kotlin\nclass RouteCaller<T>(private val startPage: Int = 0,\n                     private val itemsPerPage: Int,\n                     private val backendMethodToCall: (Int, Int) -> Observable<T>) {\n\n    /**\n     * Offset for loading more\n     */\n    private val olderPageOffset = AtomicInteger(startPage)\n\n    /**\n     * A queue that is used to retry \"older\"\n     * pages if they have failed before continue with even more older\n     */\n    private val olderFailedButRetryLater: Queue<Int> = LinkedBlockingQueue<Int>()\n\n    /**\n     * Get an observable to load older data from backend.\n     */\n    fun getOlderWithRetry(): Observable<T> {\n\n        val pageOffset = if (\n        olderFailedButRetryLater.isEmpty()) {\n            olderPageOffset.addAndGet(itemsPerPage)\n        } else {\n            olderFailedButRetryLater.poll()\n        }\n\n        return backendMethodToCall(pageOffset, itemsPerPage)\n                .doOnError {\n                    olderFailedButRetryLater.add(pageOffset)\n                }\n    }\n\n    /**\n     * Get an observable to load the newest data from backend.\n     * This method should be invoked on pull to refresh\n     */\n    fun getFirst(): Observable<T> {\n        return backendMethodToCall(startPage, itemsPerPage)\n    }\n}\n```\n\n`RouteCaller` 接受一个`method(Int, Int) -> Observable<T>` 作为构造函数的参数, 根据传入不同的参数执行不同的动作:\n- getFirst() 加载第一页\n- getOlderWithRetry(): 加载老的数据.\n我们使用 `olderFailedButRetryLater ` 字段来记录当前页码, 当执行加载更多的时候加1, 此外当加载下一页数据失败后,我们在 `.doOnError()` 方法中进行重试\n所以 `RouteCaller`的职责就是传输参数,执行实际的后端请求,于是有下面的结构:\n\n![RouteCaller](http://hannesdorfmann.com/images/plaid/routing1.png)\n\n我们有两个接口 `DesignerNewsService` 和 `DribbleService` 去执行搜索, 这意味着我们需要两个 `RouteCaller`:\n\n![RouteCaller](http://hannesdorfmann.com/images/plaid/routing1.png)\n\n下一个问题是:如何实例化一个 `RouteCalls`? 我们为每一个后端服务各定义一个 `RouteCallerFactory`,\n它提供一个方法 `getAllBackendCallers() ` 来得到一个 Observable的 `List<RouteCaller>`.\n\n```kotlin\ninterface RouteCallerFactory<T> {\n\n    /**\n     * Get all available backend route callers\n     */\n    fun getAllBackendCallers(): Observable<List<RouteCaller<T>>>\n}\n\nclass DesignerNewsSearchCallerFactory(private val searchQuery: String, private val backend: DesignerNewsService) : RouteCallerFactory<List<PlaidItem>> {\n\n    val extractPlaidItemsFromStory = fun(story: StoriesResponse): List<PlaidItem> {\n        return story.stories\n    }\n\n    // The method to execute from RouteCaller\n    val searchCall = fun(pageOffset: Int, itemsPerPage: Int): Observable<List<PlaidItem>> {\n        return backend.search(searchQuery, pageOffset).map(extractPlaidItemsFromStory)\n    }\n\n    // Create a list with one single RouteCaller() with \"searchCall\" as method reference\n    private val callers = arrayListOf(RouteCaller(0, 100, searchCall))\n\n    override fun getAllBackendCallers(): Observable<List<RouteCaller<List<PlaidItem>>>> {\n        return Observable.just(callers)\n    }\n}\n```\n\n如果你是初学kotlin,乍一看 `DesignerNewsSearchCallerFactory` 有点奇怪,我们没有使用Lambda而是创建一个searchCall属性,它实际上是一个函数 `(Int, Int) -> Observable<List<PlaidItem>>`。\n\n这样做是有原因的: 最近我看到Android团队在`Android2015开发者峰会`被问到何时添加Java 8支持。**Reto Meier** 回答说,许多开发人员主要是对Lambda 感兴趣, 当他问观众:想要Android加入Lambda 的请举手,然后几乎所有人举手了。我认为,对于Lambda 有一种普遍的误解: Lambda如此强大是因为它的高阶函数和函数传递。Lambda 只不过是一个是匿名函数。实际上,Lambda并不能测试,它们是硬编码的。例如,我还会实现这样的:\n\n```\nRouteCaller(0, 100, { pageOffset, limit -> backend.search(searchQuery, pageOffset) })\n```\n\nNext we introduce a Router which is responsible to combine all RouteCaller from different RouteCallerFactories to one single Observable list:\n\n我们应该怎么写单元测试呢? 不可能给匿名函数写单元测试,因为它们是\"硬编码的\",  然而,如果我们传递一个函数作为参数,那么很容易为这个函数写一个单元测试. 在Java8中,传递一个函数参数这样写 `::searchCall`. 不幸的是, kotlin还不支持,目前仅仅支持静态方法,因此这里定义一个函数作为属性. 在这个系列文章的最后我会介绍一下我使用kotlin的体会.\n\n好了,看一下\"搜索\"的结构\n\n![RouteCallerFactory](http://hannesdorfmann.com/images/plaid/routing3.png)\n\n注意,`getAllBackendCallers()` 返回一个列表,其中包含一个RouteCaller,但这个想法是 RouteCallerFactory创建所有RouteCallers。稍后我们将看到,在Dribbble上RouteCallers HomeDribbbleCallerFactory返回一个RouteCaller的列表,代表每个端点加载item,如受欢迎的项目,最近的,动画等.\n\n![RouteCallerFactory](http://hannesdorfmann.com/images/plaid/routing4.png)\n\n下面我们介绍`Router`, 它负责组合所有的 RouteCaller, 将不同的 RouteCallerFactories 返回一个单个的 Observable的list:\n\n```kotlin\nclass Router<T>(private val routeFactories: List<RouteCallerFactory<T>>) {\n\n    fun getAllRoutes(): Observable<List<RouteCaller<T>>> {\n        val callers = ArrayList<Observable<List<RouteCaller<T>>>>()\n\n        routeFactories.forEach {\n            callers.add(it.getAllBackendCallers())\n        }\n\n        return Observable.combineLatest(callers, { calls ->\n            val items = ArrayList<RouteCaller<T>>(calls.size)\n            calls.forEach {\n                items.addAll(it as List<RouteCaller<T>>)\n            }\n\n            items // return items\n        })\n    }\n}\n```\n\n如你所见 `Route` 接收一个`RouteCallerFactory`列表,换句话说:通过构造函数可以配置Route。下面来完成“路由图”:\n\n![](http://hannesdorfmann.com/images/plaid/routing5.png)\n\n好了,到目前为止,我们重构了“routing 部分”。最后`Router`提供了一个 `Observable<List<RouteCaller<T>>>` 。那么什么时候在RecyclerView中显示item呢?这就是`ItemsLoader`的职责了。正如它的名字,该组件用来加载item:\n\n```kotlin\nclass ItemsLoader<T>(protected val router: Router<T>) {\n\n    fun firstPage(): Observable<T> {\n        return FirstPage<T>(router.getAllRoutes()).asObservable()\n    }\n\n    fun olderPages(): Observable<T> {\n        return OlderPage<T>(router.getAllRoutes()).asObservable()\n    }\n}\n```\n\n`ItemsLoader` 接受一个 `Router` 作为构造函数的参数, 此外接受两个方法, firstPage()返回一个代表first page的 `Observable`, olderPages()返回一个代表older pager的 `Observable`.一个page代表RecyclerView显示的一页items,当我们滑动到底部的时候加载下一页数据.\n看一下 `Page`, `FirstPage` 和 `OlderPage` 的代码:\n\n```\nabstract class Page<T>(val routeCalls: Observable<List<RouteCaller<T>>>) {\n\n    private var failed = AtomicInteger()\n    private var backendCallsCount: Int = 0\n\n    /**\n     * Return an observable for this page\n     */\n    fun asObservable(): Observable<T?> {\n\n        return routeCalls.flatMap { routeCalls ->\n\n            backendCallsCount = routeCalls.size\n            val observables = arrayListOf<Observable<T>>()\n            routeCalls.forEach { call ->\n                    val observable = getRouteCall(call).onErrorResumeNext { error ->\n                      // Suppress errors as long as not all fail\n                        error.printStackTrace()\n                        val fails = failed.incrementAndGet()\n\n                        if (fails == backendCallsCount) {\n                            Observable.error(error) // All failed so emmit error\n                        } else {\n                            Observable.empty() // Not all failed, so ignore this error and emit nothing\n                        }\n                    }\n                    observables.add(observable);\n                }\n\n                // return the created Observable\n                Observable.merge(observables)\n        }\n    }\n\n    protected abstract fun getRouteCall(caller: RouteCaller<T>): Observable<T>\n}\nclass FirstPage<T>(routeCalls: Observable<List<RouteCaller<T>>>) : Page<T>(routeCalls) {\n\n    override fun getRouteCall(caller: RouteCaller<T>): Observable<T> {\n        return caller.getFirst();\n    }\n}\n\nclass OlderPage<T>(routeCalls: Observable<List<RouteCaller<T>>>) : Page<T>(routeCalls) {\n\n    override fun getRouteCall(caller: RouteCaller<T>): Observable<T> {\n        return caller.getOlderWithRetry();\n    }\n}\n```\n\n`Page`类负责调用`RouteCaller`的方法来执行http请求,我们不希望因为某个后端的调用失败导致整个页面数据获取失败,因此我们使用 `onErrorResumeNext()` 来拦截错误,然后返回一个http请求调用失败的 error.然后 `Presenter`通过`ItemsLoader` 订阅页面的 `observable`.\n\n## 依赖注入\n\n你可能注意到了,到目前为止几乎所有的组件都拥有一个接受其他组件作为参数的构造方法. 这是设计好的,现在我们使用 `Dagger`(我使用的 `Dagger1`) 来组合我们需要的元素:\n```\n@Module(\n    injects = {\n        HomePresenter.class\n    },\n    addsTo = ApplicationModule.class // contains Retrofit interfaces\n)\npublic class HomeModule {\n\n  @Provides @Singleton HomePresenter provideSearchPresenter(SourceDao sourceDao,\n      DribbbleService dribbbleBackend,\n      DesignerNewsService designerNewsBackend,\n      ProductHuntService productHuntBackend ) {\n\n    // Create the router\n    List<RouteCallerFactory<List<? extends PlaidItem>>> routeCallerFactories = new ArrayList<>(3);\n    routeCallerFactories.add(new HomeDribbbleCallerFactory(dribbbleBackend, sourceDao));\n    routeCallerFactories.add(new HomeDesingerNewsCallerFactory(designerNewsBackend, sourceDao));\n    routeCallerFactories.add(new HomeProductHuntCallerFactory(productHuntBackend, sourceDao));\n\n    Router<List<? extends PlaidItem>> router = new Router<>(routeCallerFactories);\n\n    ItemsLoader<List<? extends PlaidItem>> itemsLoader = new ItemsLoader<>(router);\n\n    return new HomePresenterImpl(itemsLoader);\n  }\n}\n```\n\n正如你所见, 我们可以使用 `Dagger`来配置 `Router` 和 `ItemsLoader`. 我们给 `SearchModule`的`SearchPresenter`  配置一个 `ItemsLoader`和`Router`. 优势就是,如果有一天我们想要添加另一个“源”,如reddit,我们唯一要做的就是定义一个`RedditService`(Retrofit), `RedditCallerFactoy`并在`Router`中添加这个`CallerFactory`。我们可以在一个具体的 dagger module中,而不用修改其他类(开闭原则)。换句话说:我们已经通过依赖注入建立了一个可配置“插件系统”。\n您可能已经注意到 `SourceDao` 类的代码。我们将在第二篇博客中讨论如何将响应式编程进行得更彻底。\n\n\n## 第一部分总结\n这是这个系列博客的第一篇.这篇文章中我们使用了MVP模式,并且重构了一下从后端加载数据的方法. 主要任务就是将大的复杂的任务拆分成小的可重用的组件,如 `ItemsLoader`, `Page`, `Router` 和 `RouteCaller` ,更加遵循 SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) ,如 DataManager 的实现.\n\n总之,有更好的方法去实现这样一个app. 特别是 `ItemsLoader` 可以以完全不同的方式实现,我首先想到的是使用`RxJava`的 `SwitchOnNext()`或者 `merge`操作符去创建一个Observable去加载下一页数据. 但是,关于UI和错误处理,如果我可以单一观测分割成两个可见(first Page,older Page )会更容易实现\n\n总之,欢迎提建议或反馈!\n在第二部分, 我将使用`RxJava` 去重构Plaid 来实现一个 \"真正意义上的响应式\"的app.\n"
  },
  {
    "path": "issue-32/高性能ListViews.md",
    "content": "高性能ListViews\n---\n\n> * 原文链接 : [Performance ListViews](http://willowtreeapps.com/blog/performance-listviews/?utm_source=Android+Weekly&utm_campaign=038d344835-Android_Weekly_178&utm_medium=email&utm_term=0_4eb677ad19-038d344835-337955857)\n* 原文作者 : [Brandon](http://willowtreeapps.com/blog/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [liuling07](https://github.com/liuling07) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n列表展示功能几乎在所有app中都会被用到，使用列表可以很方便的展示一些列表项，比如菜谱、联系人，或者任意类型的类别。所以Android有一个内置的方式来展示此类型的数据，也是在情理之中的。RecyclerView是一种最新的展示列表数据的方式，它非常高效，因为它重用视图而不是每一行出现在屏幕上都重新创建。在RecyclerView出现之前，我们可以使用ListView，即使到了现在，ListView也是广泛的被开发者所使用。虽然ListView也是可以回收视图的，但它也一直都是Android中最容易被错误使用的一个控件。我们知道在此之前这个话题已经被写过无数遍了，但是今天我还是要在博客中提出来，因为我们仍然发现很多app在错误的使用它们。\n\n关于ListView中ArrayAdapter的用法，标准的新手写法是这样子的：\n\n```\n@Override\npublic View getView(int position, View convertView, ViewGroup parent) {\n\n    LayoutInflater inflater = (LayoutInflater) context\n            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n    View rowView = inflater.inflate(R.layout.view_test_row, parent, false);\n\n    TextView testName = (TextView)rowView.findViewById(R.id.text_view_test_name);\n    TextView testDesc = (TextView)rowView.findViewById(R.id.text_view_test_desc);\n\n    //modify TextViews, in some arbitrary way\n\n    return rowView;\n}\n```\n\n当所有列表项都能够一次性在一屏中显示的时候，这种写法并没有什么问题，但这样你就创建了一个基本视图，并完全避免了ArrayAdapter的麻烦了吗？当ListView需要显示一个很大的列表集，而且列表子项是一个非常复杂的视图的时候，上面的方式会消耗大量的性能。当用户滑动屏幕的时候，每个视图都会被inflate并且调用findViewById()方法。当findViewById()方法被调用的时候，会遍历整个视图层级，直到找到正确的Id。每个子视图都要执行上述过程！并且用户滑动的越快，卡顿现象愈加明显。为了解决这个问题，我们可以使用一个静态类来绑定还没被使用的convertView。\n\n```\nstatic class ViewHolder(){\n\n        TextView testName;\n        TextView testDesc;\n        \n}\n\n@Override\n public View getView(int position, View convertView, ViewGroup parent) {\n\n    View rowView = convertView;  //reference to one of the previous Views in the list that we can reuse.\n\n    if(convertView == null) {\n\n        LayoutInflater inflater = (LayoutInflater) context\n                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n        rowView = inflater.inflate(R.layout.view_test_row, parent, false);\n\n        ViewHolder viewHolder = new ViewHolder();\n        viewHolder.testName = (TextView) rowView.findViewById(R.id.text_view_test_name);\n        viewHolder.testDesc = (TextView) rowView.findViewById(R.id.text_view_test_desc);\n\n        rowView.setTag(viewHolder);\n    }\n\n    ViewHolder holder = (ViewHolder) rowView.getTag();\n    \n    //in real code these strings should be in res\n    holder.testName.setText(\"Test\"+position); \n    holder.testDesc.setText(\"This is number \"+position);\n\n    return rowView;\n}\n```\n\n那convertView又是什么呢？它可以让ListView跳过一些显示一行内容所需要的设置。如果某一行的视图不在屏幕中显示，我们可以重复使用这个视图来显示一个新行。当ListView刚开始显示的时候，一切都是正常的。既然没有视图可以被用来复用，convertView为空。视图也像前面版本一样被inflate，但是TextViews会被找到且它的引用被保存在一个ViewHolder中。然后我们可以调用setTag()方法将ViewHolder存储在视图中。正如修订过后的getView()方法中后半段代码所示，我们可以在视图中存储后面我们需要用到的数据。\n\n我们所做的更改可能看起来并没有太大的效果，但是随着布局越来越复杂并且数量也越来越多，效果将变得越来越明显。作为开发者，我最不想做的事就是开发一个用户体验很差的app。所以请记住，仅仅一个低水平的ListView都有可能让一个app死掉，我们一定得避免这种情况发生。\n"
  },
  {
    "path": "issue-33/Android Libraries的依赖管理.md",
    "content": "Android Libraries的依赖管理\n---\n\n> * 原文链接 : [Dependency Management for Android Libraries](http://johnpetitto.com/android-lib-dependency-management/?utm_source=Android+Weekly&utm_campaign=9ed0cecaff-Android_Weekly_186&utm_medium=email&utm_term=0_4eb677ad19-9ed0cecaff-337955857)\n* 原文作者 : [ John Petitto](http://johnpetitto.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [嗣音君](https://github.com/xiaolangpapa) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu) \n* 状态 :  完成\n\n\n当Android开发者为他们的项目选择一个 Library 时, 他们不仅仅是在追寻诸如功能、可用性、性能、文档和支持等，同时也关心这个 Library 的体积和多少个方法即将被添加到项目中去。因为随着项目的发展，它的依赖也在增长。开发者越来越难将他们app的方法总数控制在65K以下。然而对于尚未发布的 build版本来说，Proguard 实在是慢得令人难以等待。开发者就像躲避瘟疫一样在努力避免多个 dex。这就是 library 的作者应该重视他们项目大小的原因。\n\n让你的 library 方法数降下来的最简单做法就是不要包含任何没有必要的依赖。任何你包含进来的依赖都将被加到你的用户的项目里。举个例子，如果你只是需要一些简单的通用方法，例如安静地关闭一个资源，不要直接把 [Guava](https://github.com/google/guava) 加进来。而是应该引入你自己实现的方法或者是从现有的 library 中（确保得到了授权）提取所需的，你的用户一定会非常感激你排除了其他14K+的方法。\n\n然而那并不表明你应该一直避免使用以下外部的libraries, 你只需要聪明一点对待它即可。不要偏离了原本的路线去写一个HTTP client这种已经实现好的libraries，与其浪费时间在这上面还不如去提高你的library。\n\n避免简单地决定哪些 liraries 该引入，你可以采用一些策略去让你的 library 保持小体积。其中一个策略是在声明依赖的时候去使用一些提供的范围。这是 gradle 里 Android 构建系统的一部分。和编译相比，提供的范围仅仅是在编译时引入了依赖。这意味着当用户构建他们的项目时，依赖将不会被打包进 APK 文件。用户需要在 app 的 build.gradle 文件中显式地声明依赖，如此运行时依赖方可用。\n\n注意：另外有一个 package scope 和默认提供的做了相反的事情，它能将把依赖打包进 APK 文件，但在编译时却不可用。\n\n你在library中引入一个可选依赖可能有好几个原因，其中一个便是某些特定的功能只有部分用户才用得到。一个显而易见的例子就是 [Retrofit 1.x](https://github.com/square/retrofit/tree/version-one)，它通过消费反应式的REST调用来替代回调。那些想要使用 RxJava 的用户可以自行添加上，而那些不需要的用户则不用被额外的依赖所负累。虽然这个配置自从 Retrofit 用了 [maven 构建系统](https://github.com/square/retrofit/blob/version-one/retrofit/pom.xml#L35)后发生一些轻微的变化，但内在的思想是类似的。\n\n我需要提醒的是，如果你发现自己引入了一些不是所有用户都能使用到的特性，你应该认真地考虑把那些特性变为你的 library 的一部分。稍后会有更多关于这个的说法。\n\n另外一个你可能想引入可选依赖的原因是:虽然 Android Framework 已经提供某个功能的实现，但是在外部 library 中有一个更高效的实现。已经依赖了这个 library 的用户，或者是那些想引入额外增加的方法来获得更高性能的用户可以引入该 library。\n\n最近我就在 [PlacesAutocompleteTextView library](https://github.com/seatgeek/android-PlacesAutocompleteTextView) 遇到了这个问题——内置的 HTTP client 可以是 OkHttpClient 或者是 HttpURLConnection。前者拥有更高的性能，但需要引入 [OkHttp ](http://square.github.io/okhttp/) 作为依赖。如果用户不愿意引入 OKHttp 的话，它将会自动用回标准库的 HttpURLConnection。\n\n为了实现这个特性，在运行时，一个叫做 \"resolver\"的类被用来确定使用哪个依赖。举个例子，这就是决定使用哪个 HTTP client 的类：\n\n```\npublic final class PlacesHttpClientResolver {\n  public static final PlacesHttpClient PLACES_HTTP_CLIENT;\n\n  static {\n    boolean hasOkHttp;\n    \n    try {\n      Class.forName(\"com.squareup.okhttp.OkHttpClient\");\n      hasOkHttp = true;\n    } catch (ClassNotFoundException e) {\n      hasOkHttp = false;\n    }\n\n    PlacesApiJsonParser parser = JsonParserResolver.JSON_PARSER;\n\n    PLACES_HTTP_CLIENT = hasOkHttp ? new OkHttpPlacesHttpClient(parser) : new HttpUrlConnectionMapsHttpClient(parser);\n  }\n\n  private PlacesHttpClientResolver() {\n    throw new RuntimeException(\"No Instances!\");\n  }\n}\n```\n\n当该类加载的时候，会通过完整的类名去检测 OkHttpClient 是否是可用的。如果抛出了 ClassNotFoundException 的异常，我们就知道 OKHttp 并没有被用户加入到项目中去，此时将会用回 HttpURLConnection。PlacesHttpClient 以普通接口的方式，封装了两种实现，如此一来，在整个代码库里，它们都可以交换地使用。同样的方法也用在了 JSON 解析上，使得 [Gson](https://github.com/google/gson) 成为了可选的依赖。\n\n当性能和包大小之间的权衡是有意义的时候，这种方法是非常好的。在备选实现方式使用起来更麻烦的情况下（例如 JSON 解析），我建议优先使用外部 library 来节省时间，然后考虑在后续的 release 中加入备选的实现。\n\n在此前我提到了你应该睿智地决定哪个特性需引入你的library。 如果一个功能特性并没有被几乎所有的用户都使用到，那么最好就不要把引入它。这使得第一种方法——使用可选依赖的变得不太可靠。再以Retrofit 作为一个例子，在2.x的 release 版本的核心 library 里将不再提供反应式调用 REST 的功能。这个功能被转移到了一个分离的模块，并以它的 maven artifact 的形式来发布。\n\n同样的，不同的响应转换器也被分离到了各自的依赖里。例如，那些需要转换 JSON 响应而且已经依赖了Gson 的 Retrofit 用户可以把以下依赖加入到他们的 build.gradle 文件:\n\n```\ndependencies {\n  compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'\n}\n```\n那些用了不同 JSON库（如 Jackson）的用户或者需要解析不同格式数据（如 XML, 协议缓存区等）的用户，也可以通过类似的方式去解决，而无需被额外的依赖所负累。同等重要地，核心库将不会被那些额外的功能影响，可以保持专注在它打算解决的主要问题上。\n\n如果你发现自己在写一个Android开发者用得上的library, 请在设计它的时候把这几条策略牢记于心。不要把你的library的体积大小单纯看待成一个属性，而是作为一个特性。你的用户将会因此而感激你的！\n\n\n\n\n\n"
  },
  {
    "path": "issue-33/下雪动画.md",
    "content": "\n# 下雪动画\n\n---\n\n> * 原文链接 :  [Snowfall](https://blog.stylingandroid.com/snowfall/)\n* 原文作者 : [Styling Android](https://blog.stylingandroid.com)\n* 译文出自 : [hanks.xyz](http://hanks.xyz)\n* 译者 : [hanks-zyh](https://github.com/hanks-zyh)\n* 校对者: [desmond1121](https://github.com/desmond1121)\n* 状态 : 完成\n\n\n这本是一个愉快的季节，但是，呵呵，胡扯！ 因为这篇文章的发表时间是2015年的圣诞节，所以我们需要给Style Android用制造出一些节日气氛。感谢读者们，因为有的读者可能没有在庆祝圣诞，有些读者可能还是6月份。\n那么问题来了，我们应该做些什么来让这个节日像是真正的节日呢？ 最简单的方法：带上圣诞帽，拍个照。\n\n![tree](https://blog.stylingandroid.com/wp-content/uploads/2015/12/tree-300x200.jpg)\n\n看我多么欢乐！\n但是我感觉这个图片有些单调，所以来弄点雪花，让雪花飘下来。\n我们可以添加一个包含这个图片的自定义View\n\n**res/layout/activity_main.xml**\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  tools:context=\"com.stylingandroid.snowfall.MainActivity\">\n\n  <ImageView\n    android:id=\"@+id/image\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_centerInParent=\"true\"\n    android:contentDescription=\"@null\"\n    android:scaleType=\"fitCenter\"\n    android:src=\"@drawable/tree\" />\n\n  <com.stylingandroid.snowfall.SnowView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_alignBottom=\"@id/image\"\n    android:layout_alignEnd=\"@id/image\"\n    android:layout_alignLeft=\"@id/image\"\n    android:layout_alignRight=\"@id/image\"\n    android:layout_alignStart=\"@id/image\"\n    android:layout_alignTop=\"@id/image\" />\n</RelativeLayout>\n```\n\n尽管可以通过继承ImageView来实现自定义View，但我决定将 `SnowView` 和图片分开，这样每次刷新动画的时候不用重新渲染图片，只刷新 `SnowView` 就行了\n\n\n\n`SnowView.java`\n\n```\npublic class SnowView extends View {\n    private static final int NUM_SNOWFLAKES = 150;\n    private static final int DELAY = 5;\n\n    private SnowFlake[] snowflakes;\n\n    public SnowView(Context context) {\n        super(context);\n    }\n\n    public SnowView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public SnowView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    protected void resize(int width, int height) {\n        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        paint.setColor(Color.WHITE);\n        paint.setStyle(Paint.Style.FILL);\n        snowflakes = new SnowFlake[NUM_SNOWFLAKES];\n        for (int i = 0; i < NUM_SNOWFLAKES; i++) {\n            snowflakes[i] = SnowFlake.create(width, height, paint);\n        }\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        if (w != oldw || h != oldh) {\n            resize(w, h);\n        }\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        for (SnowFlake snowFlake : snowflakes) {\n            snowFlake.draw(canvas);\n        }\n        getHandler().postDelayed(runnable, DELAY);\n    }\n\n    private Runnable runnable = new Runnable() {\n        @Override\n        public void run() {\n            invalidate();\n        }\n    };\n}\n```\n\n代码很简单。 在View 的 `onSizeChanged` 方法中初始化 150 个随机位置的雪花对象。 在`onDraw`方法中画出雪花，然后每隔一段时间就刷新一下位置，需要注意的是`onDraw`没有立即去执行，而是通过创建一个runnable，这样不会阻塞UI线程\n雪花下落是基于Samuel Arbesman的[雪花下落的算法](http://www.openprocessing.org/sketch/84771)。\n\n**SnowFlake.java**\n```\nclass SnowFlake {\n    private static final float ANGE_RANGE = 0.1f;\n    private static final float HALF_ANGLE_RANGE = ANGE_RANGE / 2f;\n    private static final float HALF_PI = (float) Math.PI / 2f;\n    private static final float ANGLE_SEED = 25f;\n    private static final float ANGLE_DIVISOR = 10000f;\n    private static final float INCREMENT_LOWER = 2f;\n    private static final float INCREMENT_UPPER = 4f;\n    private static final float FLAKE_SIZE_LOWER = 7f;\n    private static final float FLAKE_SIZE_UPPER = 20f;\n\n    private final Random random;\n    private final Point position;\n    private float angle;\n    private final float increment;\n    private final float flakeSize;\n    private final Paint paint;\n\n    public static SnowFlake create(int width, int height, Paint paint) {\n        Random random = new Random();\n        int x = random.getRandom(width);\n        int y = random.getRandom(height);\n        Point position = new Point(x, y);\n        float angle = random.getRandom(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;\n        float increment = random.getRandom(INCREMENT_LOWER, INCREMENT_UPPER);\n        float flakeSize = random.getRandom(FLAKE_SIZE_LOWER, FLAKE_SIZE_UPPER);\n        return new SnowFlake(random, position, angle, increment, flakeSize, paint);\n    }\n\n    SnowFlake(Random random, Point position, float angle, float increment, float flakeSize, Paint paint) {\n        this.random = random;\n        this.position = position;\n        this.angle = angle;\n        this.increment = increment;\n        this.flakeSize = flakeSize;\n        this.paint = paint;\n    }\n\n    private void move(int width, int height) {\n        double x = position.x + (increment * Math.cos(angle));\n        double y = position.y + (increment * Math.sin(angle));\n\n        angle += random.getRandom(-ANGLE_SEED, ANGLE_SEED) / ANGLE_DIVISOR;\n\n        position.set((int) x, (int) y);\n\n        if (!isInside(width, height)) {\n            reset(width);\n        }\n    }\n\n    private boolean isInside(int width, int height) {\n        int x = position.x;\n        int y = position.y;\n        return x >= -flakeSize - 1 && x + flakeSize <= width && y >= -flakeSize - 1 && y - flakeSize < height;\n    }\n\n    private void reset(int width) {\n        position.x = random.getRandom(width);\n        position.y = (int) (-flakeSize - 1);\n        angle = random.getRandom(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;\n    }\n\n    public void draw(Canvas canvas) {\n        int width = canvas.getWidth();\n        int height = canvas.getHeight();\n        move(width, height);\n        canvas.drawCircle(position.x, position.y, flakeSize, paint);\n    }\n}\n```\n初始化的时候，雪花的随机位置就已经确定了。这是为了确保雪花不会每次画的时候都在开始的位置。当一个雪花的位置超出Canvas的边界之后，它就会被重新放到顶部的一个随机位置，这样就可以循环利用了，避免了重复创建。\n当画雪花下落的每一帧的时候，我们首先给`SnowFlake`添加一个随机数来改变位置，这样可以模仿有小风吹雪花。\n在把雪花画到canvas上之前，我们会进行边界检查(如果需要的话，超出边界的就重新放到顶部)\n\n我一直在不断的调整里面的常量来改变下雪的效果直到我感觉满意为止。\n\n\n最终效果如下：\n[youtube](https://youtu.be/pk66ZziTfOw)\n\n当然了，在canvas里面塞这么多东西不是一个好的方法（有其他更好的 比如OpenGL），但是，我现在要去吃火鸡了，所以可能要等下一次了。\n\n[源文件地址](https://github.com/StylingAndroid/Snowfall)\n\n版权声明：\nPart of this code is based upon “Snowfall” by Sam Arbesman, licensed under Creative Commons Attribution-Share Alike 3.0 and GNU GPL license.\nWork: http://openprocessing.org/visuals/?visualID= 84771\nLicense:\nhttp://creativecommons.org/licenses/by-sa/3.0/\nhttp://creativecommons.org/licenses/GPL/2.0/\n\n© 2015, Mark Allison. All rights reserved. This article originally appeared on Styling Android.\n\nPortions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License\n"
  },
  {
    "path": "issue-33/使用ADB-Shell的效率和乐趣-part1.md",
    "content": "# 使用ADB Shell的效率和乐趣-Part1\n> * 原文链接 : [Efficiency and fun from using ADB Shell, Part 1](https://ar-g.github.io/ADB-Shell-Part-1/?utm_source=Android+Weekly&utm_campaign=9ed0cecaff-Android_Weekly_186&utm_medium=email&utm_term=0_4eb677ad19-9ed0cecaff-337955857)\n* 原文作者 : [Andrii Rakhimov](https://ar-g.github.io)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n在这篇文章中，我想展示在开发和测试中如何使用常用的adb命令实现`install`，`uninstall`，`copy`，`clean`。这对我们在持续集成服务器上进行自动化构建也是有帮助的，并且能确保一切干净合法。\n\n[Android Debug Bridge](https://developer.android.com/tools/help/adb.html)(adb)是一个可以帮你和模拟器或者真机通讯的多功能命令行工具。它是一个client-server程序并且提供了Unix [shell](https://developer.android.com/tools/help/shell.html)，使得你可以在模拟器或真机上运行多种命令。ADB包含在[SDK](https://developer.android.com/sdk/index.html)中，你可以在`OurSdkPath/platform-tools `中找到它。根据系统的不同，我们可能还需要做一些额外的[调整](https://developer.android.com/tools/device.html#setting-up)。  \n![ADB Shell](https://github.com/DroidWorkerLYF/Translate/blob/master/Efficiency%20and%20fun%20from%20using%20ADB/adb_shell.png?raw=true)  \n\n##安装和删除\n\n首先，让我们安装一个apk，我们通常需要从多个连接的设备中选一个：\n\n    adb devices\n    //output\n    List of devices attached\n    106a6a4f    device\n    //now specifying device to install simple apk\n    adb -s 106a6a4f install /OurLocalPath/sample.apk\n\n我们可以为任何adb命令指定设备，比如显示并更新进程的分类信息\n\n    adb -s 106a6a4f shell top\n\n删除应用我们只需要在uninstall命令后加上包名\n\n    adb uninstall our.package.name\n\n想要查看设备上安装的包以及相关联的文件，我们使用远程shell和package manager命令\n\n    adb shell pm list packages -f\n\n几乎每个adb命令都有额外的参数，你可以通过adb help查看\n\n     adb help\n     …\n     adb install [-lrtsdg] <file>\n                                    - push this package file to the device and install it\n                                       (-l: forward lock application)\n                                       (-r: replace existing application)\n                                       (-t: allow test packages)\n                                       (-s: install application on sdcard)\n                                       (-d: allow version code downgrade)\n                                       (-g: grant all runtime permissions)\n\n## 复制文件\n\n安装完应用后，你会在[Package Manager](https://dzone.com/articles/depth-android-package-manager)的对应路径下找到文件：\n\n-   应用在`/data/app`目录下\n-   数据库，shared preference和其他缓存数据在`/data/data/<package name>`目录下\n\n大部分的文件在测试时都是有用的，你可以向数据库中添加数据，或者通过编辑XML改变应用的设置。\n\n想要从设备上复制文件你首先需要通过shell在设备上找到他们\n\n    1 adb shell \n    2 //navigate and figure out what to copy\n    3 adb pull /data/data/ua.slando/databases/ /OurLocalPath\n\n同样的原理把文件复制到设备\n\n    1 adb push /OurLocalPath/ourFile.txt /data/data/our.package.name/databases/\n\n通常我们没有权限从内部存储器上复制数据，会得到错误提示例如remote object /data/data/ua.slando/databases/ does not exist，我们可以这样做：\n\n    1 adb kill-server\n    2 adb root\n\n记住只有模拟器和root过的设备才能取得root权限。\n\n##清理\n\n我们经常需要清理应用的数据，方便使用同一个build并且节省时间，你可以使用PackageManager命令：\n\n    adb shell pm clear our.package.name\n\n以上就是part1全部内容了，你已经可以应对基本的使用了。在下一部分，我会讲一些关于adb和Android更有用的建议和tricks。敬请期待，继续研究这美妙的Android世界吧 ;)"
  },
  {
    "path": "issue-33/如何自定义Lint规则.md",
    "content": "#如何自定义Lint规则\n> * 原文链接 : [Help developers with custom Lint rules](http://jeremie-martinez.com/2015/12/15/custom-lint-rules/)\n* 原文作者 : [Jeremie Martinez](http://jeremie-martinez.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [desmond1121](https://github.com/desmond1121)  \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n上个月，我有幸旁听了[Mattew Compton](https://twitter.com/ambergleam)在Droidcon的演讲，是关于自定义Lint规则的。我被这个话题深深的吸引住了，并希望能够更加深入地探索它。于是我写了这篇文章来分享我的一些想法，并结合几个具体例子讲解如何将自定义Lint引入工程中。\n\n##定义\n\n我相信作为一个Android开发者都应该知道[Lint](http://developer.android.com/tools/help/lint.html)。这是Lint的简介：\n\n>Lint是一个静态代码分析工具，它能够帮你查出可能发生的bug以及可以优化的地方。\n\n举几个简单的例子吧，Lint能够提醒你`Toast`忘记加`show()`，告诉你`ImageView`需要添加`contentDescription`才能保证它可以被文字描述...举不完的例子。你会发现，Lint可以帮你做的事情太多了，它在逻辑的正确性、应用的安全性、性能、交互、国际化等等方面都有所涉及。\n\n你只需要在命令行中输入`./gradlew lint`就可以很容易地使用Lint。它会生成一份报告，按种类、优先级、严重程度来区分。你需要经常查看这份报告来保证你的代码质量与降低bug产生概率。\n\n在简单的介绍之后，我希望我们能达成共识：Lint是一个非常好的帮助我们理解与使用Android API的工具。\n\n##为什么需要自定义Lint规则？\n\n大部分Android开发者并不清楚Lint的规则是可以自定义的。这里是几个自定义规则能够派上用场的情景：\n\n- 当你发布一个库并且希望别人正确使用它的时候，自定义的Lint规则可以很好地提醒他们什么事情忘记做了或者做错了。\n- 当你的团队有新的开发者加入时，自定义的Lint规则会是帮助新成员更好地理解你的代码规范、命名规范。\n\n##示例\n\n我最近加入了[CaptainTrain](https://play.google.com/store/apps/details?id=com.capitainetrain.android)的Android开发团队。接下来的例子是基于我为我们的app实现的两个Lint自定义规则。我认为这已经足够展示Lint在规范代码风格上的强大作用。\n\n让我们开始吧！\n\n###Gradle中配置\n\n自定义的Lint规则必须在新模块里面实现。一个简单的gradle脚本如下面这段代码所示：\n\n    apply plugin: 'java'\n\n    targetCompatibility = JavaVersion.VERSION_1_7\n    sourceCompatibility = JavaVersion.VERSION_1_7\n\n    configurations {\n        lintChecks\n    }\n\n    dependencies {\n        compile 'com.android.tools.lint:lint-api:24.3.1'\n        compile 'com.android.tools.lint:lint-checks:24.3.1'\n\n        lintChecks files(jar)\n    }\n\n    jar {\n        manifest {\n            attributes('Lint-Registry': 'com.captaintrain.android.lint.CaptainRegistry')\n        }\n    }\n\n    defaultTasks 'assemble'\n\n    task install(type: Copy, dependsOn: build) {\n        from configurations.lintChecks\n        into System.getProperty('user.home') + '/.android/lint/'\n    }\n\n正如你所看到的一样，我们需要引入两个依赖库来实现Lint自定义规则。同时我们需要指定一个Lint-Registry，我会在之后解释它，现在你需要记住有这么个东西。最后我们新建了一个任务来安装规则。\n\n你可以通过`../gradlew clean install`(在module目录下)这条简单的命令来编译、应用这个模块的配置。\n\n现在我们已经配置好了模块，来看看第一条自定义规则是啥。\n\n###第一条规则：属性必须加前缀\n\n在CaptainTrain工程中，我们总是在属性前加上ct前缀来避免和其他库冲突。这很容易被像我一样的新员工遗漏，所以我加了下面这条规则：\n\n    public class AttrPrefixDetector extends ResourceXmlDetector {\n\n     public static final Issue ISSUE = Issue.create(\"AttrNotPrefixed\",\n            \"You must prefix your custom attr by `ct`\",\n            \"We prefix all our attrs to avoid clashes.\",\n            Category.TYPOGRAPHY,\n            5,\n            Severity.WARNING,\n            new Implementation(AttrPrefixDetector.class,\n                                   Scope.RESOURCE_FILE_SCOPE));\n\n     // Only XML files\n     @Override\n     public boolean appliesTo(@NonNull Context context,\n                              @NonNull File file) {\n       return LintUtils.isXmlFile(file);\n     }\n\n    // Only values folder\n     @Override\n     public boolean appliesTo(ResourceFolderType folderType) {\n        return ResourceFolderType.VALUES == folderType;\n    }\n\n    // Only attr tag\n     @Override\n     public Collection<String> getApplicableElements() {\n        return Collections.singletonList(TAG_ATTR);\n     }\n\n    // Only name attribute\n     @Override\n     public Collection<String> getApplicableAttributes() {\n        return Collections.singletonList(ATTR_NAME);\n     }\n\n     @Override\n     public void visitElement(XmlContext context, Element element) {\n        final Attr attributeNode = element.getAttributeNode(ATTR_NAME);\n        if (attributeNode != null) {\n            final String val = attributeNode.getValue();\n            if (!val.startsWith(\"android:\") && !val.startsWith(\"ct\")) {\n                context.report(ISSUE,\n                        attributeNode,\n                        context.getLocation(attributeNode),\n                        \"You must prefix your custom attr by `ct`\");\n            }\n        }\n     }\n    }\n\n你会发现它集成了`ResourceXmlDetector`。`Detector`是帮助我们发现问题并作出反应的类。首先需要指定我们想要探测的是什么位置：\n\n- 第一个`appliesTo`方法指明了只看XML文件；\n- 第二个`appliesTo`方法指明了只看资源文件夹下的文件；\n- `getApplicableElements`指明了只看XML中的属性；\n- `getApplicableAttributes`指明了只看XML属性中的名字；\n\n在实现完这些筛选之后，我们需要实现`visitElement`方法来处理筛选后的元素。在本例中的实现很简单，我们对非`android:`开头或者`ct`开头的XML的属性名都抛出一个`Issue`。这个Issue在类顶部已经进行了声明：\n\n    public static final Issue ISSUE = Issue.create(\"AttrNotPrefixed\",\n                \"You must prefix your custom attr by `ct`\",\n                \"To avoid clashes, we prefixed all our attrs.\",\n                Category.TYPOGRAPHY,\n                5,\n                Severity.WARNING,\n                new Implementation(AttrPrefixDetector.class,\n                                    Scope.RESOURCE_FILE_SCOPE));\n\n这里面每个参数都很重要，都是必须的：\n\n- `AttrNotPrefixed` Lint规则名，必须是唯一的；\n- `You must prefix your custom attr by ct` 是规则的简单描述；\n- `To avoid clashes, we prefixed all our attrs` 是规则的进一步描述；\n- `TYPOGRAPHY` 是Issue种类；\n- `5` 是优先级，它必须是1-10之的数；\n- `WARNING` 是严重性。在这个严重性下，代码会被警告，但是依然可以运行。\n- `Implementation` 是连接`Detector`（生成Issue）和`Scope`（分析Issue的上下文）的桥梁。在这个例子中，我们将在资源文件中区分析前缀问题。\n\n正如你所想，这段代码很简单。你唯一需要注意的就是分析Issue的上下文(`Scope`)与`Issue`中输入的提示。\n\n最后你的Lint提示将像下图一样：\n\n![attr-rules](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/attr_rules_result.png)\n\n###Second rule: Log in production is forbidden\n###第二条规则\n\n由于实际使用中`Log`可能会泄露用户隐私与影响性能，在CaptainTrain中，我们包装了`Log`逻辑。在`BuildConfig.DEBUG`为`false`时不输出`Log`。同时还可以统一处理我们自己`Log`的一些格式。这条规则大概是这样的：\n\n    public class LoggerUsageDetector extends Detector\n                                     implements Detector.ClassScanner {\n\n        public static final Issue ISSUE = Issue.create(\"LogUtilsNotUsed\",\n                \"You must use our `LogUtils`\",\n                \"Logging should be avoided in production for security and performance reasons. Therefore, we created a LogUtils that wraps all our calls to Logger and disable them for release flavor.\",\n                Category.MESSAGES,\n                9,\n                Severity.ERROR,\n                new Implementation(LoggerUsageDetector.class,\n                                    Scope.CLASS_FILE_SCOPE));\n\n        @Override\n        public List<String> getApplicableCallNames() {\n            return Arrays.asList(\"v\", \"d\", \"i\", \"w\", \"e\", \"wtf\");\n        }\n\n        @Override\n        public List<String> getApplicableMethodNames() {\n            return Arrays.asList(\"v\", \"d\", \"i\", \"w\", \"e\", \"wtf\");\n        }\n\n        @Override\n        public void checkCall(@NonNull ClassContext context,\n                              @NonNull ClassNode classNode,\n                              @NonNull MethodNode method,\n                              @NonNull MethodInsnNode call) {\n            String owner = call.owner;\n            if (owner.startsWith(\"android/util/Log\")) {\n                context.report(ISSUE,\n                               method,\n                               call,\n                               context.getLocation(call),\n                               \"You must use our `LogUtils`\");\n            }\n        }\n    }\n\n就像你在上一条规则中看到的一样。首先我们用两个方法`getApplicableCallNames`和` getApplicableMethodNames`来约束我们的查找目标。之后在有问题的情况下创建Issue。与之前唯一的不同就是我们这回只是简单的继承了`Detector`并实现`ClassScanner`来处理Java类。（实际上并没与太多的不同，如果你追朔`XmlResourceDetector`的实现，你会发现它也是继承了`Detector`并实现`XmlScanner`）。所以总的来说，自定义Lint规则就是继承`Detector`并实现正确地`Scanner`。\n\n最终，我们将`Scope`变成了`CLASS_FILE_SCOPE`，因为我们只需要逐个文件扫描就可以找出这个`Issue`。有时候你需要同时扫描所有文件，那么你要将`Scope`变成`ALL_CLASS_FILES`。看，`Scope`很重要吧，你可以在[这里](https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java)找到所有`Scope`的定义。\n\n如果在一次扫描中存在多个问题，那么他们都会被抛出。虽然这么做可能会让问题变得不精确，不过这也能够帮助我们一次性解决掉文件中的所有问题！\n\n最终报出的Issue长这样：\n\n![log-lint](http://desmondtu.oss-cn-shanghai.aliyuncs.com/translation/log_rules_result.png)\n\n###注册规则\n\n最后还有一件事：注册规则！我们需要将新建的自定义规则加到Lint规则列表中：\n\n    public final class CaptainRegistry extends IssueRegistry {\n        @Override\n        public List<Issue> getIssues() {\n            return Arrays.asList(LoggerUsageDetector.ISSUE, AttrPrefixDetector.ISSUE);\n        }\n    }\n\n你可以看到这非常简单，我们只需要继承`IssueRegistry`并且实现`getIssues()`方法就好了。这个类名必须与我们之前在`build.gradle`中声明的那个类名一致。\n\n###总结\n\n当然，我只是展示了两个非常简单的例子。不过我希望它已经能够展示出Lint的强大之处。它对你有多适合，只取决于你怎么写规则。\n\n我们现在只看到了两种`Detector/Scanner`，不过还有`GraldeScanner`, `OtherFileScanner`等等，探索它们，并使用正确的类来辅助开发。\n\n在开始写你自己的Lint规则之前，我推荐先阅读系统Lint规则的写法，这能帮助你理解怎么去实现规则。代码在[这里](https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks)\n\n最后，Lint是一个非常棒的帮助你减少错误的工具，使用它吧！\n\n以下几条链接对我理解Lint很有帮助，供你参考：\n\n- https://github.com/bignerdranch/linette\n- https://speakerdeck.com/ambergleam/linty-fresh\n- [Source code](https://android.googlesource.com/platform/tools/base/+/master/lint/)\n- http://tools.android.com/tips/lint-custom-rules\n- https://github.com/googlesamples/android-custom-lint-rules"
  },
  {
    "path": "issue-33/高效地配置okhttp.md",
    "content": "高效地配置OkHttp\n---\n\n> * 原文链接 : [Effective OkHttp](http://omgitsmgp.com/2015/12/02/effective-okhttp/)\n* 原文作者 : [Michael Parker](http://omgitsmgp.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Damonzh](https://github.com/Damonzh) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n当我为[可汗学院](https://www.khanacademy.org/)开发[Android app](https://play.google.com/store/apps/details?id=org.khanacademy.android)的时候，[OkHttp](http://square.github.io/okhttp/)是一个十分有用的第三方库。虽然它的默认设置已经提供了很大的便利，但我们还是采取了以下的步骤使OkHttp更加高效与智能:  \n\n### 1. 开启响应数据缓存到文件系统功能  \n允许缓存响应数据时需要往请求头里加入`Cache-Control`，默认情况下OkHttp不会缓存响应数据。所以，客户端就会浪费时间和流量去多次请求同样的数据。相反的，如果缓存了响应数据，只需要在第一次请求的时候从网络获取，以后就可以直接从缓存文件中获取数据。 \n \n\n为了开启缓存响应数据功能，你需要创建一个`com.squareup.okhttp.Cache`对象并将其作为参数传给`OkHttpClient`的`setCache`方法。在创建`Cache`对象的时候，你必须为其指定一个`File`参数和以byte为单位的最大容量参数，这个File代表了缓存的路径。缓存的数据会被存储在这个路径中。当缓存的大小超过指定的最大容量时，OkHttp会根据[LRU算法](https://en.wikipedia.org/wiki/Cache_algorithms#LRU)对缓存数据进行清理操作。  \n\n根据[Jesse Wilson](http://stackoverflow.com/a/32752861/400717)的推荐，我们把缓存的数据存放在`context.getCacheDir()`的子目录中:\n\n~~~java\n// Base directory recommended by http://stackoverflow.com/a/32752861/400717.\n// Guard against null, which is possible according to\n// https://groups.google.com/d/msg/android-developers/-694j87eXVU/YYs4b6kextwJ and\n// http://stackoverflow.com/q/4441849/400717.\nfinal @Nullable File baseDir = context.getCacheDir();\nif (baseDir != null) {\n  final File cacheDir = new File(baseDir, \"HttpResponseCache\");\n  okHttpClient.setCache(new Cache(cacheDir, HTTP_RESPONSE_DISK_CACHE_MAX_SIZE));\n}\n~~~\n\n在可汗学院App中，我们指定`HTTP_RESPONSE_DISK_CACHE_MAX_SIZE`的值为`10 * 1024 * 1024`，即10MB。  \n\n### 2. 集成Stetho\n[Stetho](http://facebook.github.io/stetho/)是由Facebook开发的一个实用的库，它可以让你使用Chrome提供的[Chrome Developer Tools](https://developers.google.com/web/tools/setup/workspace/setup-devtools)来审查你的Android应用的代码。  \n\n除了可以让你审查应用中的SQLite数据库和View的继承层次外，Stetho也可以审查OkHttp发起的每一个请求和收到的每一个响应:  \n\n![](http://omgitsmgp.com/assets/images/posts/stetho-inspector-network.png)  \n\n这个拦截器可以确保服务端返回的HTTP头允许缓存相关数据，而且也可以保证如果有缓存数据的话不会进行网络请求。  \n\n使用Stetho也很简单，往网络拦截器列表中添加一个`StethoInterceptor`对象就可以了:  \n\n~~~java\nokHttpClient.networkInterceptors().add(new StethoInterceptor());\n~~~  \n\n然后运行你的应用并且打开Chrome，导航到`chrome://inspect`。\n这时应该就会有一个设备和应用id的列表。点击'inspect'链接来打开Developer Tools，然后打开NetWork标签就可以观察OkHttp的请求了。   \n\n### 3. 在你的应用中使用Picasso and Retrofit\n如果你也像我们一样使用[Picasso](http://square.github.io/picasso/)加载网络图片，或者使用[Retrofit](http://square.github.io/retrofit/)简化网络请求和数据解析。在你没有明确指定一个`OkHttpClient`的情况下，这些库会自己创建一个默认的供内部使用。比如2.5.2的Picasso中`OkHttpDownloader`类是如下这样:  \n\n~~~java\nprivate static OkHttpClient defaultOkHttpClient() {\n  OkHttpClient client = new OkHttpClient();\n  client.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);\n  client.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);\n  client.setWriteTimeout(Utils.DEFAULT_WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);\n  return client;\n}\n~~~\nRetrofit有一个类似的工厂方法来创建自己的`OkHttpClient`。   \n\n图片是一个应用中比较大的资源。Picasso使用LRU算法对图片进行了缓存，严格来说是内存缓存。如果客户端试图使用Picasso加载一张图片，但是Picasso没有在内存缓存中找到这张图片。那么它就会委托内部的`OkHttpClient`来加载这张图片。默认情况下它总是会从网络加载图片，因为上文提到的`defaultOkHttpClient`并没有配置缓存响应数据到文件系统。  \n\n指定你自己的`OkHttpClient`可以让你从文件系统中获得缓存的响应数据，而不用每次都从服务端去获取数据。在App完成第一次启动后，这个特性特别重要。这个时候Picasso还没有[内存缓存](http://stackoverflow.com/a/22756972/400717)，所以它会频繁的让`OkHttpClient`去加载图片。   \n\n为了实现上面说的，你只需要用`OkHttpDownloader`对`OkHttpClient`进行包裹，并把它作为参数传给`Picasso.Builder`的`downloader`方法：  \n\n~~~java\nfinal Picasso picasso = new Picasso.Builder(context)\n    .downloader(new OkHttpDownloader(okHttpClient))\n    .build();\n\n// The client should inject this instance whenever it is needed, but replace the singleton\n// instance just in case.\nPicasso.setSingletonInstance(picasso);\n~~~  \n\n为了在Retrofit 1.9.x中结合`RestAdapter`使用`OkHttpClient`，把`OkHttpClient`包裹在`OkClient`中，再把它作为`RestAdapter.Builder`的`setClient`方法的参数传进去尽可以了：  \n\n~~~java\nrestAdapterBuilder.setClient(new OkClient(httpClient));\n~~~\n\n在Retrofit 2.0中就更简单了，直接把`OkHttpClient`作为`Retrofit.Builder`的`client`方法的参数传递进去就行了。   \n\n在可汗学院App中，我们借助[Dagger](http://google.github.io/dagger/)来确保只有一个`OkHttpClient`的实例。这个实例被Picasso和Retrofit共同使用。我们使用`@Singleton`注解为`OkHttpClient`实例创建了一个提供者：  \n\n~~~java\n@Provides\n@Singleton\npublic OkHttpClient okHttpClient(final Context context, ...) {\n  final OkHttpClient okHttpClient = new OkHttpClient();\n  configureClient(okHttpClient, ...);\n  return okHttpClient;\n}\n~~~\n\n然后使用Dagger将`OkHttpClient`注入到其他的提供者当中。这些提供者又创建了`RestAdapter`和`Picasso`实例。 \n\n### 4. 指定用户代理拦截器  \n当用户在每个请求里指定一个详细的`User-Agent`请求头的值的时候，日志文件和日志分析就变得更加有用了。默认情况下，OkHttp包含了一个`User-Agent`的请求头，但这个头仅仅标识出了OkHttp的版本信息。你可以创建一个自定义个拦截器来取代这个默认值，以下是[StackOverflow](http://stackoverflow.com/a/27840834/400717)上的一段可供参考的代码:  \n\n~~~java\npublic final class UserAgentInterceptor implements Interceptor {\n  private static final String USER_AGENT_HEADER_NAME = \"User-Agent\";\n  private final String userAgentHeaderValue;\n\n  public UserAgentInterceptor(String userAgentHeaderValue) {\n    this.userAgentHeaderValue = Preconditions.checkNotNull(userAgentHeaderValue);\n  }\n\n  @Override\n  public Response intercept(Chain chain) throws IOException {\n    final Request originalRequest = chain.request();\n    final Request requestWithUserAgent = originalRequest.newBuilder()\n        .removeHeader(USER_AGENT_HEADER_NAME)\n        .addHeader(USER_AGENT_HEADER_NAME, userAgentHeaderValue)\n        .build();\n    return chain.proceed(requestWithUserAgent);\n  }\n}\n~~~    \n\n你可以使用任何你觉得有用的信息作为参数传递到`UserAgentInterceptor`的构造方法中来构造`User-Agent`头的值。我们使用了一下的信息：  \n\n* `Android`系统的`os`值，用它来明确额标识出这是来自Android设备的请求\n* `Build.MODEL`, 或“终端用户可见的产品名称”\n* `Build.BRAND`, 或“消费者可见的产品或者硬件品牌名”\n* `Build.VERSION.SDK_INT`, 或“用户可见的AndroidSDK版本号”\n* `BuildConfig.APPLICATION_ID`\n* `BuildConfig.VERSION_NAME`\n* `BuildConfig.VERSION_CODE`   \n\n最后三个参数我们通过Gradle构建脚本里的`applicationId`, `versionCode`和`versionName`来指定。你可以查看[这里](http://developer.android.com/tools/publishing/versioning.html)和[这里](http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename)的文档来获取更多的信息。  \n\n需要注意的是，如果你的App中用到了`WebView`,你可以配置你的WebView使用与`UserAgentInterceptor`的构造参数相同的`User-Agent`的值：  \n\n~~~java\nWebSettings settings = webView.getSettings();\nsettings.setUserAgentString(userAgentHeaderValue);\n~~~\n\n### 5. 配置合理的超时时间\n在2.5.0之前，OkHttp的请求默认不会超时。从2.5.0开始，如果一个请求建立连接后从这个连接中读取数据或者向这个连接写入数据超过10秒钟就认为超时。更新到2.5.0之后，我们的代码中出现了一些bug，因为一开始想要确定出错的具体位置。要覆盖这些默认行为，只需要对应的去调用`setConnectTimeout`、`setReadTimeout` 或者 `setWriteTimeout`方法即可。  \n\n需要注意的是Picasso和Retrofit的默认`OkHttpClient`的超时时间是不同的。Picasso的默认值分别是：  \n\n* 连接超时15秒\n* 读数据超时20秒\n* 写数据超时20秒\n\n而Retrofit的默认值是：\n\n* 连接超时15秒\n* 读数据超时20秒\n* 写数据不会超时\n\n你可以使用自定义的`OkHttpClient`来配置Picasso和Retrofit，这样就可以保证所有的请求超时时间都是统一的。  \n\n### 总结\n再次声明，默认的OkHttp的配置方式已经提供了很大的便利。但是通过以上步骤你可以提高它更加高效与智能，从而提升你的App质量。"
  },
  {
    "path": "issue-34/Android逆向工程101 – Part 1.md",
    "content": "#Android逆向工程101 – Part 1\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 1](http://www.fasteque.com/android-reverse-engineering-101-part-1/)\n* 原文作者 : [Daniele Altomare](http://www.fasteque.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [MiJack](https://github.com/MiJack) \n* 校对者: \n* 状态 :  已翻译\n\n\n这篇文章是关于android应用逆向系列的第一片文章。\n\n在这个系列里，我将讲解以下内容：**APK**、**AAR**的文件格式，一些常用的逆向工程和检测工具，**dex2jar**、 **AAPT**、**androguard**和 **apktool**。\n\n[Part 1 – APK and AAR format](http://www.fasteque.com/android-reverse-engineering-101-part-1/)\n\n[Part 2 – aapt](http://www.fasteque.com/android-reverse-engineering-101-part-2/)\n\n[Part 3 – dex2jar](http://www.fasteque.com/android-reverse-engineering-101-part-3/)\n\n[Part 4 – apktool](http://www.fasteque.com/android-reverse-engineering-101-part-4/)\n\n[Part 5 – Androguard](http://www.fasteque.com/android-reverse-engineering-101-part-5/)\n\n## 只有黑客使用吗？\n\n很明显，答案是否定的。\n\n如果你是一名开发者，在很多情况下，你需要hack应用或者向其中注入代码。\n\n首先，应用中有一些你感兴趣的布局或者动画效果，使用这些工具你可以拿到对应的XML资源文件。\n\n其次，你可以看看本人的应用是如何实现具体的业务逻辑的：这并不是抄袭他人的代码，而是通过学习可以可以提高你的编码水平或者得到一些有用的改进提示。\n\n最后但并非最不重要的一点，它可以检查你的应用程序是否安全可靠，可以检查代码或资源被有效地混淆，或是不需要的文件没有被包装到最终apk。你会惊讶，别人可以知道居然有这么多的信息，例如API密钥信息，认证令牌或闲置资源等。\n\n##APK 格式\n\n你开发的应用将被打包成**APK**文件，你可以从谷歌获得Play商店或其他渠道找到它。换句话说，对于手机上的任何一个应用程序，有相应的APK文件（包括预装的应用程序也不例外）。\n\napk文件实际上也是一个zip文件，所以你拿到它以后，可以将其重命名，然后解压得到里面的文件。\n\n\n|条目 |\t说明|\n|----|---|\n|AndroidManifest.xml |  manifest的二进制文件|\n|classes.dex|程序的代码将被编译成dex文件|\n|resources.arsc\tfile|包含预编译的程序资源，二进制的xml格式|\n|res/|文件夹中包括未被编译打包进入resources.arsc的资源|\n|assets/\t|可选的文件夹，包含应用的assets文件，由AssetManager进行检索|\n|lib/|可选的文件夹,包含已编译的代码，例如jni的libraray|\t\n|META-INF/|文件夹中包含MANIFEST.MF 文件，APK的签名文件也在这个文件夹中|\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-10-at-22.00.18.png)\nAPK里的文件\n\n##什么是DEX?\n\n简单讲，DEX/Dalvik执行文件是一种Android平台上的文件格式，里面包含编译好的代码，可以被Dalvik虚拟机或者[Android Runtime](https://source.android.com/devices/tech/dalvik/index.html) (ART)读取识别。\n\n当一个APK文件是由Android编译系统产生的（就像当你在Android Studio 上运行你的应用）。首先，Java类会被编译成`.class`文件，后来，`DX `将在这些文件转换DEX格式。 DX是Android交叉编译器`Build Tools`的一部分，你可以在以下位置找到它：\n\n`$ANDROID_SDK/build-tools`\n\n关于DEX文件的详细信息请点击 [这里](https://source.android.com/devices/tech/dalvik/dex-format.html).\n\n##如何得到apk文件\n\n这里有以下几种方式：\n\n- 如果你想随便找一个应用，可以使用[一些网站](https://apkpure.com/)，通过浏览器直接下载到你的桌面\n- 如果应用已经装在你的手机上了，你可以使用备份软件，例如[这个](https://play.google.com/store/apps/details?id=mobi.infolife.appbackup)，然后把他拷贝到你的手机内存或者SD卡的公共文件夹\n- 在`/system/app`文件夹中，你可以找到预装应用 ，例如calculator, Chrome, camera, … 这取决于你手机上的具体ROM。\n- 在`/data/app`中，你可以找到用户安装的应用。为了从手机中导出apk，你想要使用命令行列出可用的应用(记住使用usb连接手机):\n\n`adb shell pm list packages -f`\n\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-23-at-19.24.57.png)\n\nHaving the path of the APK file, you can now pull it:\n\n有了apk文件的路径，你就可以将其导出了：\n\n`adb pull -p PATH/base.apk OUTPUT.apk`\n\n`-p`选项可以展示传输文件的进度，如果导出文件的名称没有指定，默认会使用`base。apk`\n\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-23-at-19.29.46.png)\n\n`adb pull -p`\n\n你可能对应用如何备份你的apk感到好奇：事实上，如果用手机上安装的文件管理系统去访问`/data/app`文件夹， 你会发现很多情况下是无法访问的。\n\n但是，确实是可以通过编程方式访问用户安装应用的apk文件。\n\n首先，您需要检索应用程序的列表：\n\n```\nfinal Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); \nmainIntent.addCategory(Intent.CATEGORY_LAUNCHER); \n \nList<ResolveInfo> infos = getPackageManager().queryIntentActivities(mainIntent, 0);\n```\n\n然后，通过`resolveinfo`，您可以访问`applicationinfo`类中的`publicsourcedir`字段，这是`SourceDir`中公开部分的完整路径，包括资源和manifest。\n\n\n```\nFile apkFile = new File(pkgInfo.activityInfo.applicationInfo.publicSourceDir);\nif(apkFile.exists()) {\n    ...\n}\n```\n##AAR 格式\n\n**aar**包是一个Android Library 项目的二进制分发：例如，Android Support Library ，你可以使用此格式将其添加到你的应用程序中。另外，如果你的Android项目的发布形式是一个Library，而不是商店的应用程序，采用这种格式再好不过了。\n\naar文件实际上也是一个zip文件，所以你拿到它以后，可以将其重命名，然后解压得到里面的文件。\n\n你可以在aar文件中找到以下内容：\n\n|条目\t|\t必选|说明|\n| -- | -- | -- |\n|AndroidManifest.xml\t|必须的|\tmanifest的XML源文件|\n|classes.jar\t|必须的|\tJava classes打出的jar文件|\n|res/\t|必须的|\t该文件夹用于存放使用到的资源|\n|R.txt\t|必须的|使用 aapt ``--output-text-symbols``的输出内容.它是library使用到的资源的清单 ( 包括strings, colors, dimens, attrs, layouts, ...). |\n|assets/|\t可选的|\t存放assert资源文件的文件夹|\n|libs/*.jar\t|可选的|该文件夹用于存放library使用到的jar文件\t|\n|jni//*.so\t|可选的|\t该文件夹用于存放library使用到的jni文件|\n|proguard.txt\t|可选的|Proguard配置文件|\n|lint.jar\t|可选的|\t自定义Lint规则.|\n \n\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-09-at-22.23.31.png)\nAppCompat-v7 23.1.0 aar 的文件内容\n\naar和apk相比，唯一的区别是的`AndroidManifest.xml`和`res`文件夹下的XML文件，他们都是普通的XML，所以你可以很容易地打开它们。\n\n请注意，例如，经常被我们作为项目依赖的Support Library是AAR的格式，您可以在以下路径找到他们：\n\n`$ANDROID_SDK/extras/android/m2repository/com/android`\n\n在本系列的其余部分，我将需要关注APK格式，因为装在手机上的应用就是这种格式的，也是这种格式分发到谷歌Play商店或其他渠道。\n\n在下一篇文章中，我将介绍**aapt**和**dex2jar** 两个工具，你可以使用它们通过分析从apk文件中获取很多重要的信息。\n"
  },
  {
    "path": "issue-34/Android逆向工程101 – Part 2.md",
    "content": "Android逆向工程101 – Part 2\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 2](http://www.fasteque.com/android-reverse-engineering-101-part-2/)\n* 原文作者 : [Daniele Altomare](http://www.fasteque.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [MiJack](https://github.com/MiJack) \n* 校对者: \n* 状态 :  已翻译\n\n\n在这个系列的[第一篇文章](http://www.fasteque.com/android-reverse-engineering-101-part-1/) 中，我们已经探讨过了**APK**和**AAR** 组件的组成格式.\n\n正如之前所提到的，Google Play商店中可用的应用（或者几乎装在你手机上的所有的应用）都是一个apk文件。在第二部分，我们告诉你如何使用 **aapt** 读取apk中与value部分有关的信息。\n\n###AAPT\n\n如果你安装了Android SDK，那么你就有**aapt**了。事实上，“**Android Asset Packaging Tool**”是android 构建工具中的一部分，你可以在以下路径中找到它，例如：\n\n`ANDROID_SDK_HOME/build-tools/23.0.2`\n\n请注意，你会发现编译工具的每个版本都会有一个单独的文件夹：当你使用 Android SDK Manager去安装新版本的Build tools的时候，现有的版本并没有被覆盖，而是为其单独创建文件夹。这让你可以在不同版本的工具之间切换。\n\n在使用Android Studio的时候，你一定记得在model对应的`build.gradle`脚本文件中设置Build Tools的版本。\n\n这个工具是Android构建系统中的一部分，它允许您查看、创建和更改ZIP兼容文件（例如zip，jar，APK）。它还可以将资源编译成二进制资源。\n\nDetails about how the Android Build System works are beyond this article, but **aapt** is mainly used in the process to:\n\n关于Android构建过程的具体细节不在本文的讨论范围之内，但是，你需要知道**aapt**主要有以下几个作用：\n\n- 生成**R.java**文件，这样是对资源文件的初步处理\n- 将Android manifest、资源、assets等装入**APK**文件\n- Add to the **APK** file the compiled classes, which have been already converted to the dex format by the **dx** tool.\n- 编译class，使用**dx**工具将其转化成dex，添加到**APK**文件中。\n更为详细的构建过程请看[官方文档](http://developer.android.com/sdk/installing/studio-build.html ).\n\n同时，**aapt**还可以用来从一个apk文件提取一些信息。\n\n\nIf you would like to try the same commands, you just need to get one APK file as I have already explained in the [first article](http://www.fasteque.com/android-reverse-engineering-101-part-1/).\n\n如果你还想尝试那些命令，你可以使用我在[第一篇](http://www.fasteque.com/android-reverse-engineering-101-part-1/)中提供的apk文件。\n\n\n###PACKAGE CONTENT\n\n获取**apk**文件中的文件清单，只需要如下简单的指令：\n\n`aapt list FILENAME.apk`\n\n在其后面添加 `-v`，你将知道更多关于上述文件的信息，例如文件的大小，创建的日期时间，CRC-32循环冗余校验码等。\n\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-14-at-14.11.53-1024x410.png)\naapt list -v\n###PACKAGE DETAILS\n\n使用`dump`指令，你可以找到更为详细的信息。\n\n添加`badging`选项，会打印更多信息，如包名称、版本名称、版本号、权限、支持屏幕、启动时的`Activity`、应用程序名称和图标文件，…\n\n`aapt dump badging FILENAME.apk`\n\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-14-at-14.26.44.png)\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-14-at-14.28.04-1024x234.png)\n`aapt dump badging`\n \n`permissions` 选项可以打印（和Android manifest文件中的包名称对应）应用所需的权限。请注意，只有在清单中显式声明的权限才会被列出来。例如，\n`android.permission.WRITE_EXTERNAL_STORAGE`隐式要求权限`android.permission.READ_EXTERNAL_STORAGE`，但是该权限却不会出现在列表中。\n`aapt dump permissions FILENAME.apk`\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-14-at-14.38.35-1024x87.png)\n`aapt dump permissions`\n \n`configurations` 选项会打印出apk的configurations:\n\n`aapt dump configurations FILENAME.apk`\n\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-14-at-16.50.59-1024x300.png)\n`aapt dump configurations`\n \n\n`resources`指令会打印出APK文件的resource table 。\n因此，你所得到的将是应用引用的所有资源列表，包括attributes, strings, dimens, layouts, styles, menus, drawables, …\n\n您还可以从应用的依赖库得到对应的资源：例如，如果` appcompat-v7 `是项目中的一个依赖，那么他的资源也会被列出来。\n\n`aapt dump resources FILENAME.apk`\n\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-14-at-16.54.30-1024x425.png)\n`aapt dump resources`\n\n最后一个命令` xmltree `，他十分有用：它可以打印出asset中编译好的xml文件。正如我在第一篇文章所提到的，xml文件是以二进制文件的形式被打包到apk文件中。所以你不能使用编辑器或者阅读器把它打开。但是，使用这个命令，你至少读起来更容易一些。\n\n`aapt dump xmltree FILENAME.apk RESOURCE.xml`\n![](http://www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-14-at-17.07.31-1024x398.png)\n`aapt dump xmltree`\n\n\n这就是**aapt**中比较重要的命令，但是，我觉得你还是有必要去看看所有的参数。\n\n正如你所看到的，使用这些简单的命令，就可以获取应用程序的一些细节信息，但它只是只读的，而你不能改变任何东西的apk文件。\n在这篇文章中，我原本打算提过一下** dex2jar **以及如何使用它来反编译Android应用程序，但现在看有很多信息需要整理消化，所以这是顺延到下一个[博客](http://www.fasteque.com/android-reverse-engineering-101-part-3/)."
  },
  {
    "path": "issue-34/Context是怎么泄露的Handlers & Inner Classes.md",
    "content": "Context是怎么泄露的:Handlers & Inner Classes\n---\n\n> * 原文链接 : [How to Leak a Context: Handlers & Inner Classes](http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html)\n* 原文作者 : [Alex Lockwood](http://www.androiddesignpatterns.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Damonzh](https://github.com/Damonzh)  \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成   \n\n先瞅下下面这段代码：\n\n~~~java\npublic class SampleActivity extends Activity {\n\n  private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ... \n    }\n  }\n}\n~~~\n尽管不是那么明显，但这段代码会导致大量内存泄露。Android的Lint工具会给出如下警告：\n>在Android中，Handler类应该被静态修饰，否则可能会出现内存泄露。\n\nBUT,到底是哪里泄露了？它是怎么发生的?下面让我们根据已有的知识来分析下问题的原因：\n\n1.当Android应用首次启动时，framework会在应用的UI线程创建一个[Looper](http://developer.android.com/reference/android/os/Looper.html)对象。Looper实现了一个简单的消息队列并且一个接一个的处理队列中的[消息](http://developer.android.com/reference/android/os/Message.html)。应用的所有事件(比如Activity生命周期回调方法，按钮点击等等)都会被当做一个消息对象放入到Looper的消息队列中，然后再被逐一执行。UI线程的Looper存在于整个应用的生命周期内。  \n2.当在UI线程中创建[Handler](http://developer.android.com/reference/android/os/Handler.html)对象时，它就会和UI线程中Looper的消息队列进行关联。发送到这个消息队列中的消息会持有这个Handler的引用，这样当Looper最终处理这个消息的时候framework就会调用[Handler#handleMessage(Message)](http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message))方法来处理具体的逻辑。  \n3.在Java中，非静态的内部类或者匿名类会隐式的持有其外部类的引用，而静态的内部类则不会。  \n\n那么，到底是哪里发生泄漏了呢？其实泄漏发生的还是比较隐晦的，但是再看看下面这段代码：\n\n~~~java\npublic class SampleActivity extends Activity {\n \n  private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ...\n    }\n  }\n \n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n \n    // Post a message and delay its execution for 10 minutes.\n    mLeakyHandler.postDelayed(new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n    }, 1000 * 60 * 10);\n \n    // Go back to the previous Activity.\n    finish();\n  }\n}\n~~~\n当activity被finish的时候，延迟发送的消息仍然会存活在UI线程的消息队列中，直到10分钟后它被处理掉。这个消息持有activity的Handler的引用，Handler又隐式的持有它的外部类(这里就是SampleActivity)的引用。这个引用会一直存在直到这个消息被处理，所以垃圾回收机制就没法回收这个activity，内存泄露就发生了。需要注意的是：15行的匿名Runnable子类也会导致内存泄露。非静态的匿名类会隐式的持有外部类的引用，所以context会被泄露掉。  \n\n解决这个问题也很简单：在新的类文件中实现Handler的子类或者使用static修饰内部类。静态的内部类不会持有外部类的引用，所以activity不会被泄露。如果你要在Handler内调用外部activity类的方法的话，可以让Handler持有外部activity类的弱引用，这样也不会有泄露activity的风险。关于匿名类造成的泄露问题，我们可以用static修饰这个匿名类对象解决这个问题，因为静态的匿名类也不会持有它外部类的引用。\n\n~~~java\npublic class SampleActivity extends Activity {\n\n  /**\n   * Instances of static inner classes do not hold an implicit\n   * reference to their outer class.\n   */\n  private static class MyHandler extends Handler {\n    private final WeakReference<SampleActivity> mActivity;\n\n    public MyHandler(SampleActivity activity) {\n      mActivity = new WeakReference<SampleActivity>(activity);\n    }\n\n    @Override\n    public void handleMessage(Message msg) {\n      SampleActivity activity = mActivity.get();\n      if (activity != null) {\n        // ...\n      }\n    }\n  }\n\n  private final MyHandler mHandler = new MyHandler(this);\n\n  /**\n   * Instances of anonymous classes do not hold an implicit\n   * reference to their outer class when they are \"static\".\n   */\n  private static final Runnable sRunnable = new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n  };\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);\n    \n    // Go back to the previous Activity.\n    finish();\n  }\n}\n~~~\n静态和非静态内部类的区别是非常微妙的，但这个区别是每个Android开发者应该清楚的。那么底线是什么？**如果要实例化一个超出activity生命周期的内部类对象，避免使用非静态的内部类。**建议使用静态内部类并且在内部类中持有外部类的弱引用。\n"
  },
  {
    "path": "issue-34/在Android开发中使用RxJava.md",
    "content": "#在Android开发中使用RxJava\n\n---\n\n- 原文链接 : [Getting Started with RxJava and Android](http://www.captechconsulting.com/blogs/getting-started-with-rxjava-and-android?utm_source=Android+Weekly&utm_campaign=8b4ba9e51d-Android_Weekly_177&utm_medium=email&utm_term=0_4eb677ad19-8b4ba9e51d-337955857)\n- 原文作者 : [ALEX TOWNSEND](https://github.com/alex-townsend)\n- 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](www.devtf.cn)\n- 译者 : [desmond1121](https://github.com/desmond1121)\n- 校对者: [desmond1121](https://github.com/desmond1121)\n- 状态 : 完成\n\n[ReactiveX](http://reactivex.io/)是专注于异步工作的API，它将异步事件的处理与观察者模式、迭代器模式及函数式编程相结合了起来。实时地处理返回数据是在工程中经常出现的情景，所以使用高效、可拓展的方式来解决这种问题非常重要。ReactiveX通过观察者模式以及操作符来提供灵活地处理异步通信的方式，你不用再去关注线程创造与同步这些繁琐的事情。\n\n##RxJava介绍\n\n[RxJava](https://github.com/ReactiveX/RxJava)是一个开源的实现ReactiveX的工具。这里面有两种主要的类：`Observalbe`和`Subscriber`。在RxJava中，`Observable`类产生异步数据或事件，`Subscriber`类对这些数据和事件进行操作。正常的工作流程就是`Observable`产生一系列的数据或事件，然后完成或者产生异常。一个`Observable`可以拥有多个`Subscriber`，每一个被生成的事件都会触发被绑定的`Subscriber`的`onNext()`方法。当一个`Observable`生成完所有事件后，所有绑定的`Subscriber`的`onCompleted()`方法会被调用（在发生异常时会调用`onError()`方法）。现在我们对这两个类有了初步的认识，可以开始了解如何创建与订阅一个`Observer`了：\n\n\tObservable integerObservable = Observable.create(new Observable.OnSubscribe() {\n\t   @Override\n\t   public void call(Subscriber subscriber) {\n\t       subscriber.onNext(1);\n\t       subscriber.onNext(2);\n\t       subscriber.onNext(3);\n\t       subscriber.onCompleted();\n\t   }\n\t});\n\n我们创建的这个`Observable`产生了1、2、3三条数据，现在需要创建`Subscriber`来处理它们：\n\n\tSubscriber integerSubscriber = new Subscriber() {\n\t   @Override\n\t   public void onCompleted() {\n\t       System.out.println(\"Complete!\");\n\t   }\n\t\n\t   @Override\n\t   public void onError(Throwable e) {\n\t\n\t   }\n\t\n\t   @Override\n\t   public void onNext(Integer value) {\n\t       System.out.println(\"onNext: \" + value);\n\t   }\n\t};\n\n你可以看到，这个`Subscriber`将每一个收到的数据打印了出来。当你创建完`Observable`与`Subscriber`后，你需要将它们用`Observable.subscribe()`方法连接起来。\n\n\tintegerObservable.subscribe(integerSubscriber);\n\t// 输出:\n\t// onNext: 1\n\t// onNext: 2\n\t// onNext: 3\n\t// Complete!\n\n这整个过程，可以使用`Observable.just()`来简化数据的生成过程，再创建`Subscriber`的匿名内部类来简化处理过程：\n\n\tObservable.just(1, 2 ,3).subscribe(new Subscriber() {\n\t   @Override\n\t   public void onCompleted() {\n\t       System.out.println(\"Complete!\");\n\t   }\n\t\n\t   @Override\n\t   public void onError(Throwable e) {}\n\t\n\t   @Override\n\t   public void onNext(Integer value) {\n\t       System.out.println(\"onNext: \" + value);\n\t   }\n\t});\n\n##操作符\n\n只是创建与订阅`Observable`非常简单，然而这只是RxJava的冰山一角。任何`Observable`都可以将它的输出通过“操作符”来进行预处理，一个`Observable`可以绑定多个操作符来进行链式处理。举个例子，前面使用的`Observable`只是输出简单的数字，但是我们只想对奇数进行处理，可以通过设置`filter`来实现：\n\n\tObservable.just(1, 2, 3, 4, 5, 6) // add more numbers\n\t       .filter(new Func1() {\n\t           @Override\n\t           public Boolean call(Integer value) {\n\t               return value % 2 == 1;\n\t           }\n\t       })\n\t       .subscribe(new Subscriber() {\n\t\t\t\t@Override\n\t\t\t\tpublic void onCompleted() {\n\t\t\t\t\tSystem.out.println(\"Complete!\");\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\t\tpublic void onError(Throwable e) {\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void onNext(Integer value) {\n\t\t\t\t\tSystem.out.println(\"onNext: \" + value);\n\t\t\t\t}\n\t\t\t});\n\t\t\t \n\t\t\t \n\t// Outputs:\n\t// onNext: 1\n\t// onNext: 3\n\t// onNext: 5\n\t// Complete!\n\n`filter()`操作符定义了一个函数，它只会在输出为奇数的时候返回true，输出为偶数的时候返回false。返回false的输出是不会发送到`Subscriber`的。值得注意的是`filter`函数返回了一个`Observable`，我们可以进行链式处理。比如现在希望找到所有奇数的平方根，你可以在`Subscriber`获取到数字的时候来处理这个逻辑，但是这样的话就不能对做平方根后的数字进行进一步处理了。你可以在filter操作符后加一个map操作符来实现这个逻辑：\n\n\tObservable.just(1, 2, 3, 4, 5, 6) // add more numbers\n\t       .filter(new Func1() {\n\t           @Override\n\t           public Boolean call(Integer value) {\n\t               return value % 2 == 1;\n\t           }\n\t       })\n\t       .map(new Func1() {\n\t           @Override\n\t           public Double call(Integer value) {\n\t               return Math.sqrt(value);\n\t           }\n\t       })\n\t       .subscribe(new Subscriber() { // notice Subscriber type changed to \n\t           @Override\n\t           public void onCompleted() {\n\t               System.out.println(\"Complete!\");\n\t           }\n\n               @Override\n               public void onError(Throwable e) { }\n\n               @Override\n               public void onNext(Double value) {\n                   System.out.println(\"onNext: \" + value);\n               }\n           });\n       \n\t// Outputs:\n\t// onNext: 1.0\n\t// onNext: 1.7320508075688772\n\t// onNext: 2.23606797749979\n\t// Complete!\n\n链式操作符是RxJava不可分割的一块内容，它简便、可拓展，功能强大。现在你对`Observable`与`Subscriber`的联系有了进一步的认识，我们可以开始下一个话题：将RxJava与Android开发结合起来。\n\n##简化Android多线程开发\n\n在Android开发中，我们常常需要在后台线程中完成一些耗时工作，当后台线程完成工作之后通知主线程来显示结果。Android Sdk中有很多类可以完成多这种工作，Asynctask、Loader、Service等等。然而它们有时候并不是最好的选择。Asynctask经常会造成内存泄露；与ContentProvider搭配的CursorLoader使用起来非常繁琐，并且需要大量样板代码；Service通常是用于执行需要长时间在后台运行的任务。那么我们来看看RxJava怎么来解决这些问题。\n\n在下面这个布局中，有一个按钮，它会启动一个耗时任务，并且将任务的进度展示到ProgressBar上。\n\n\t<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t   xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\t   android:id=\"@+id/root_view\"\n\t   android:layout_width=\"match_parent\"\n\t   android:layout_height=\"match_parent\"\n\t   android:fitsSystemWindows=\"true\"\n\t   android:orientation=\"vertical\">\n\t\n\t   <android.support.v7.widget.Toolbar\n\t       android:id=\"@+id/toolbar\"\n\t       android:layout_width=\"match_parent\"\n\t       android:layout_height=\"?attr/actionBarSize\"\n\t       android:background=\"?attr/colorPrimary\"\n\t       app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n\t       app:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\" />\n\t\n\t   <Button\n\t       android:id=\"@+id/start_btn\"\n\t       android:layout_width=\"wrap_content\"\n\t       android:layout_height=\"wrap_content\"\n\t       android:layout_gravity=\"center_horizontal\"\n\t       android:text=\"@string/start_operation_text\" />\n\t\n\t   <ProgressBar\n\t       android:layout_width=\"wrap_content\"\n\t       android:layout_height=\"wrap_content\"\n\t       android:layout_gravity=\"center_horizontal\"\n\t       android:indeterminate=\"true\" />\n\t\n\t</LinearLayout>\n\n一旦按钮按下之后，我们禁用按钮，同时在后台线程中开始做耗时任务，当任务结束之后会弹出一个`SnackBar`。现在使用一个简单的AsyncTask来完成如下这个耗时任务`longRunningOperation()`。\n\n\tpublic String longRunningOperation() {\n\t   try {\n\t       Thread.sleep(2000);\n\t   } catch (InterruptedException e) {\n\t       // error\n\t   }\n\t   return \"Complete!\";\n\t}\n\t\n\tprivate class SampleAsyncTask extends AsyncTask {\n\t\n\t   @Override\n\t   protected String doInBackground(Void... params) {\n\t       return longRunningOperation();\n\t   }\n\t\n\t   @Override\n\t   protected void onPostExecute(String result) {\n\t       Snackbar.make(rootView, result, Snackbar.LENGTH_LONG).show();\n\t       startAsyncTaskButton.setEnabled(true);\n\t   }\n\t}\n\n现在要怎么用RxJava替换掉呢？首先需要在app模块的gradle脚本中添加依赖：`io.reactivex:rxjava:1.0.14`。然后创建一个`Observable`来运行耗时任务，调用`Observable.create`方法来实现这个功能：\n\n\tfinal Observable operationObservable = Observable.create(new Observable.OnSubscribe() {\n\t   @Override\n\t   public void call(Subscriber subscriber) {\n\t       subscriber.onNext(longRunningOperation());\n\t       subscriber.onCompleted();\n\t   }\n\t});\n\n现在创建的这个`Observable`会执行`longRunningOperation()`，在执行完毕后通知`Subscriber`，并且调用`Subscriber`的`onCompleted()`方法（注意：耗时任务在`Subscriber`与`Observable`绑定之前不会被执行）。接下来，我们需要在按钮点击事件上将`Observable`与`Subscriber`绑定。\n\n\tstartRxOperationButton.setOnClickListener(new View.OnClickListener() {\n\t   @Override\n\t   public void onClick(final View v) {\n\t       v.setEnabled(false);\n\t       operationObservable.subscribe(new Subscriber() {\n\t           @Override\n\t           public void onCompleted() {\n\t               v.setEnabled(true);\n\t           }\n\n\t\t\t   @Override\n\t\t\t   public void onError(Throwable e) {}\n\n\t\t\t   @Override\n\t\t\t   public void onNext(String value) {\n\t\t\t\t   Snackbar.make(rootView, value, Snackbar.LENGTH_LONG).show();\n\t\t\t   }\n\t\t   });\n\t   }\n\t});\n\n接下来我们就可以运行这个应用了。但是你会发现，点击按钮之后ProgressBar并不会有反应！（这是因为UI主线程被耗时任务阻塞了）。我们还没有指定`Observable`的运行线程，也没有指定`Subscriber`的运行线程。RxJava调度的功能就在这里体现：我们可以指定两个不同的线程，一个是运行线程，一个是监视线程。使用`Observable.observeOn()`函数，你可以定义一个线程用来监视`Observable`运行，并且查看是否`Observable`有新的输出（Subscriber的`onNext`，`onCompleted`， `onError`方法都是在这个监视线程中执行的）。调用`Observable.subscribeOn()`方法可以指定一个线程运行耗时任务。RxJava原本将两个任务都安排在一个线程中执行，而你可以通过调用`observeOn`与`subcribeOn`方法来进行多线程操作。RxJava封装了很多调度工具，比如`Schedulers.io()`（阻塞I/O操作），`Schedulers.computation()`（进行计算工作），`Schedulers.newThread()`（创建新线程）。不过在Android开发中，你应该关心的是怎么将代码执行在主线程中。我们可以通过RxAndroid库来完成这个工作！\n\nRxAndroid是RxJava的轻量级拓展工具，它提供了运行在主线程上的Scheduler，或者运行在任意Handler线程上的Scheduler！这样一来，我们就可以在后台线程中运行耗时工作，同时在主线程中处理结果。要使用RxAndroid，你需要添加依赖`io.reactivex:rxandroid:1.0.1`。\n\n\tfinal Observable operationObservable = Observable.create(new Observable.OnSubscribe() {\n\t   @Override\n\t   public void call(Subscriber subscriber) {\n\t       subscriber.onNext(longRunningOperation());\n\t       subscriber.onCompleted();\n\t   }\n\t}).subscribeOn(Schedulers.io()) // subscribeOn the I/O thread\n\t  .observeOn(AndroidSchedulers.mainThread()); // observeOn the UI Thread\n\t       \n以上经过修改代码会在`Schedulers.io`线程上运行耗时工作，Subscriber会在主线程中处理输出结果。现在我们运行应用，点击按钮，我们的耗时任务没有阻塞UI线程。RxJava 1.0.13同时提供了Single类，它只会产生一个结果，之后马上执行`onComplete()`方法。示例如下：\n\n\tSubscription subscription = Single.create(new Single.OnSubscribe() {\n\t           @Override\n\t           public void call(SingleSubscriber singleSubscriber) {\n\t               String value = longRunningOperation();\n\t               singleSubscriber.onSuccess(value);\n\t           }\n\t       })\n\t       .subscribeOn(Schedulers.io())\n\t       .observeOn(AndroidSchedulers.mainThread())\n\t       .subscribe(new Action1() {\n\t           @Override\n\t           public void call(String value) {\n\t               // onSuccess\n\t               Snackbar.make(rootView, value, Snackbar.LENGTH_LONG).show();\n\t           }\n\t       }, new Action1() {\n\t           @Override\n\t           public void call(Throwable throwable) {\n\t               // handle onError\n\t           }\n\t       });\n\n在订阅Single的时候，只有`onSuccess()`、`onError()`可以监听。同时你可以调用`Single.mergeWith()`操作符来将多个Single合成一个`Observable`，这个被合成的`Observable`会依次输出所有Single的结果。\n\n##防止内存泄露\n\n我们之前提到，使用`AsyncTask`的一大缺点就是它可能会造成内存泄露。当它们持有的Activity/Fragment的引用没有正确处理时就会这样。不幸的是，RxJava并不会自动防止这种情况发生，好在它可以很容易地防止内存泄露。`Observable.subscribe()`方法会返回一个`Subscription`对象，这个对象仅仅有两个方法：`isSbscribed()`与`unsubscribe()`。你可以在Activity/Fragment的`onDestroy`方法中调用`Subscription.isSubscribed()`检测是否这个异步任务仍在进行。如果它仍在进行，则调用`unsubscribe()`方法来结束任务，从而释放其中的强引用，防止内存泄露。如果你使用了多个`Observable`与`Subscriber`，那么你可以将它们添加到`CompositeSubscription`中，并调用`CompositeSubscription.unsubscribe()`结束所有的任务。\n\n##结束语\n\nRxJava为Android提供了一个非常棒的多线程同步的解决方案。在Android开发中，让耗时任务在后台线程运行，并将结果在主线程中展示，是非常重要的。同时它的操作符模式为这个工具起到了锦上添花的作用。使用RxJava的时候，你需要对整个工具库有一定理解，才能将其发挥最大的作用。所以花时间去系统地学习这个库是非常值得的。未来我还将会在我的博客中讨论RxJava的几个特性：冷热`Observable`的比较、处理反压力、Rx子类。本节中的样本代码可以从[我的Github](https://github.com/alex-townsend/GettingStartedRxAndroid_)中找到。\n\n##特别介绍：RetroLambda\n\nJava 8 引入了Lambda表达式，可惜的是Android不支持Java 8，所以我们不能使用RxJava与这一特性结合的功能。幸运的是，有一个工具叫[RetroLambda](https://github.com/orfjackal/retrolambda)可以将Lambda表达式与低版本的Java兼容。同时还有一个[Gradle插件](https://github.com/evant/gradle-retrolambda)可以在Android开发中使用。使用了Lambda表达式，你的RxJava代码会更加简洁：\n\n\tfinal Observable operationObservable = Observable.create(\n\t       (Subscriber subscriber) -> {\n\t           subscriber.onNext(longRunningOperation());\n\t           subscriber.onCompleted();\n\t       })\n\t       .subscribeOn(Schedulers.io())\n\t       .observeOn(AndroidSchedulers.mainThread());\n\t\n\tstartRxOperationButton = (Button) findViewById(R.id.start_rxjava_operation_btn);\n\tstartRxOperationButton.setOnClickListener(v -> {\n\t   v.setEnabled(false);\n\t   operationObservable.subscribe(\n\t           value -> Snackbar.make(rootView, value, Snackbar.LENGTH_LONG).show(),\n\t           error -> Log.e(\"TAG\", \"Error: \" + error.getMessage()),\n\t           () -> v.setEnabled(true));\n\t});\n\nLambda表达式减少了很多RxJava的样本代码，我强烈推荐将RetroLambda引入工程中使用。它给开发带来的好处不仅仅于RxJava上（你会注意到`OnCliclListener`也是用Lambda表达式设置的）!\n"
  },
  {
    "path": "issue-35/为什么我们要用fitsSystemWindows.md",
    "content": "我们为什么要用fitsSystemWindows?\n---\n\n> * 原文链接 : [Why would I want to fitsSystemWindows?](https://medium.com/google-developers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec)\n* 原文作者 : [Ian Lake](https://medium.com/@ianhlake)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [LionelCursor](https://github.com/LionelCursor) \n* 校对者: \n* 状态 :  校对中\n\n\nSystem windows 指的就是屏幕上status bar、 navigation bar等系统控件所占据的部分。\n\n绝大多数情况下，你都不需要理会status bar或者navigation bar 下面的空间，不过你需要注意不能让你的交互控件（比如Button）藏在status bar 或者 navigation bar下面。而`android:fitsSystemWindows=\"true\"`的默认行为正好解决了这种情况，这个属性的作用就是通过设置View的padding，使得应用的content部分——Activity中setContentView()中传入的就是content——不会与system window重叠。\n\n还有一些事情需要注意：\n\n- **`fitsSystemWindows` 需要被设置给根View**——这个属性可以被设置给任意View，但是只有根View（content部分的根）外面才是SystemWindow，所以只有设置给根View才有用。\n- **`Insets`始终相对于全屏幕**——`Insets`即边框，它决定着整个Window的边界。对`Insets`设置padding的时候，这个padding始终是相对于全屏幕的。因为`Insets`的生成在View `layout`之前就已经完成了，所以系统对于View长什么样一无所知。\n- **其它padding将通通被覆盖**。需要注意，如果你对一个View设置了`android:fitsSystemWindows=\"true\"`，那么你对该View设置的其他padding将通通无效。\n\n在绝大多数情况下，默认情况就已经够用了。比如一个全屏的视屏播放器。如果你不想被ActionBar 或者其他System View遮住的话，那么在MatchParent的ViewGroup上设置`fitsSystemWindows=\"true\"`即可。\n\n或者，也许你希望你的RecyclerView能够在透明的navigation bar 下面滚动。那么只需将`android:fitsSystemWindows=\"true\"` `android:clipToPadding=\"false\"`同时使用即可,\n滚动的内容会绘制在navigation bar下面，同时当滚动到最下面的时候，最后一个item下面依旧会有padding，使其可以滚到navigation bar上方（而不是在navigation bar下面滚不上来！）。\n\n译者注：`clipToPadding`是`ViewGroup`的属性。这个属性定义了是否允许ViewGroup在padding中绘制,该值默认为true,即不允许。\n\n## 自定义 fitsSystemWindows\n但是默认毕竟只是默认。\n在KITKAT及以下的版本，你的自定义View能够通过覆盖`fitsSystemWindows() : boolean`函数，来增加自定义行为。如果返回`true`，意味着你已经占据了整个`Insets`，如果返回`false`，意味着给其他的View依旧留有机会。\n\n而在Lollipop以及更高的版本，我们提供了一些新的API，使得自定义这个行为更加的方便。与之前的版本不同，现在你只需要覆盖`OnApplyWindowInsets()`方法，该方法允许View消耗它想消耗的任意空间（Insets），同时也能够为子方法，调用`dispatchApplyWindowInsets()`\n\n更妙的是，利用新的API，你甚至不需要拓展View类，你可以使用`ViewCompat.setOnApplyWindowInsetsListener()`，这个方法优先于`View.onApplyWindowInsets()`调用。`ViewCompat` 同时也提供了 `onApplyWindowInsets()` 和`dispatchApplyWindowInsets()` 的兼容版本，无需冗长的版本判断。\n\n## 自定义fitsSystemWindows例子\n\n绝大多数基本的layouts（FrameLayout）都是使用默认的行为，然而依然有一部分layouts已经使用了自定义`fitsSystemWindow`来实现自定义的功能。\n\n`navigation drawer`就是一个例子，它需要充满整个屏幕，绘制在透明的status bar下面。\n\n![enter image description here](http://7othru.com1.z0.glb.clouddn.com/why-would-i-want-to-fitssystemwindows.png)\n\n如上图所示，`DrawerLayout`使用了`fitsSystemwindows`，他需要让它的子View依旧保持默认行为，即不被actionbar或其他system window遮住，同时依照Material Design的定义，又需要在透明的statusbar下面进行绘制（默认是你在theme中设置的`colorPrimaryDark`颜色）\n\n你会注意到，在Lollipop及以上版本，`DrawerLayout`为每一个子View调用了`dispatchApplyWindowInsets()`，使每一个子View都收到`fitsSystemWindows`。这与默认行为完全不同，默认行为会使得根View消耗所有的insets，同时子View们永远不会收到`fitsSystemWindows`。\n\n`CoordinatorLayout`也利用了这一特性，使得其子View有机会截断并对`fitsSystemWindows`做出自己的反应。同时，它也利用`fitsSystemWindows`这一flag看其是否需要在statusbar的下方绘制。\n\n同样的，`CollapsingToolbarLayout`以`fitsSystemWindows `什么时候把变小的View放在什么地方。\n\n如果你对这些[`Design Library`](http://android-developers.blogspot.com/2015/05/android-design-support-library.html)里的东西感兴趣，请查看[Cheesesquare Sample](https://github.com/chrisbanes/cheesesquare)\n\n## 积极使用系统，而不是老想着Hack\n\n有一件事需要始终牢记，这个属性毕竟不是`fitsStatusBar`或者`fitsNavigationBar`。不管是尺寸还是位置，在不同版本间，系统控件都有很大的差距。\n\n但是尽管放心，无论在什么平台上，`fitsSystemWindows`都会影响`Insets`，使你的content和system ui不会重叠——除非你自定义这一行为。\n\n\n"
  },
  {
    "path": "issue-35/星球大战：原力觉醒或者用原力粉碎Android的视图.md",
    "content": "星球大战：原力觉醒或者用原力粉碎Android的视图\n---\n\n> * 原文链接 : [Star Wars: The Force Awakens Or How to Crumble View Into Tiny Pieces on Android](https://yalantis.com/blog/star-wars-the-force-awakens-or-how-to-crumble-view-into-tiny-pieces-on-android/)\n* 原文作者 : [Artem Kholodnyi](https://twitter.com/share)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n我们上个月在iOS上[发布了](https://yalantis.com/blog/uidynamics-uikit-or-opengl-3-types-of-ios-animations-for-the-star-wars/)史诗般的星战动画，你一定确信我们会为Android带来同样的内容。现在万众期待的Android版本已经[上线](https://github.com/Yalantis/StarWars.Android)，并且如往常一样，我们乐于和你分享开发中的小秘密。\n \n首先，最有挑战的两部分就是将视图粉碎成一块块碎片和飞行的星空。实现这两部分非常有趣。\n\n![Star Wars animation for\nAndroid](https://github.com/DroidWorkerLYF/Translate/blob/master/star%20wars/shot.gif?raw=true)\n \n### 如何使视图分裂成一块块\n \n星战动画中的视图在被原力击中后，分裂成了4000块碎片。这意味着两件事：1）原力确实很强大，2）因为Canvas性能不够，使用Canvas创建如此复杂的图形会很慢。\n\n另一方面，OpenGL则强大的多。而且，[iOS版本](https://yalantis.com/blog/uidynamics-uikit-or-opengl-3-types-of-ios-animations-for-the-star-wars/)我们就是用OpenGL实现的，因为`UIDynamics` 和`UIKit`（Core Animation）无法处理必要的负载。\n\n考虑到动画的复杂度，我决定使用OpenGL。\n \n将Android的视图分裂成一块块，我们需要先将整个视图截图，将纹理传入OpenGL的内存，然后才可以渲染效果。让我们看看如何做到这些：\n \n1. 创建截图。常见的操作：\n\n    \tBitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888)\n    \tCanvas canvas = new Canvas(bitmap);\n    \tsuper.draw(canvas);\n \n2. 将纹理传入OpenGL内存中。\n \n3. 粉碎视图  \n\nAndroid Extension Pack中支持OpenGL ES 3.1，其中的tessellation shader(细分曲面着色器)可以完成我们需要的处理，那就是将一个平面分裂成很多三角形。而且，OpenGL ES 3.1在AEP的帮助下可以在GPU中生成三角形的顶点数据，而不像OpenGL ES 2.0只能在CPU中处理。\n  \n但不幸的是，目前多数的Android设备还不支持OpenGL ES 3.1 + AEP。因为56%的设备只支持OpenGL ES 2.0，所以我们决定使用更困难的方式实现。\n\n![1.png](https://github.com/DroidWorkerLYF/Translate/blob/master/star%20wars/1.png?raw=true)\n\n我们如何将视图粉碎成4000块呢？我们可以直接把图片切开！当然，我只是开个玩笑。如果我们创建上千的纹理可能会让手机直接融化的。代替方案是，我们使用一个大图作为纹理并且把纹理坐标分配给每个顶点。\n\n    final float stepX = 1f / mStarWarsRenderer.sizeX;\n    final float stepY = 1f / mStarWarsRenderer.sizeY;\n\n-   SizeX - 横向碎片的数量\n-   SizeY - 纵向碎片的数量\n\n\t\tfor (int x = 0; x < mStarWarsRenderer.sizeX; x++) {\n\n            for (int y = 0; y < mStarWarsRenderer.sizeY; y++) {\n\n                final float u0 = x * stepX;\n\n                final float v0 = y * stepY;\n\n                final float u1 = u0 + stepX;\n\n                final float v1 = v0 + stepY;\n\n                // push values to buffer\n\n            }\n\n    \t}\n \n我们希望将更多的计算放到适合并行任务的GPU中来进行。我通过vertex shader（顶点着色器）进行所有位置的计算。这只需要在Android的interpolator帮助下对一个变量进行变化：\n\n    // from 0 to plane height in OpenGL coordinates\n    animator = ValueAnimator.ofFloat(0, -Const.PLANE_HEIGHT * 2);\n    animator.setDuration(mAnimationDuration);\n    animator.setInterpolator(new DecelerateInterpolator(1.3f));\n    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n     @Override\n     public void onAnimationUpdate(ValueAnimator animation) {\n       float value = (float) animation.getAnimatedValue();\n       mDeltaPosX = value;\n       mGlSurfaceView.requestRender();\n     }\n    };\n    animator.start();\n\n最后，在vertex shader（顶点着色器）中我们把这个数值赋给碎片的位置：\n\n    vec4 pos = a_Position;\n    pos.y += u_DeltaPos;\n    gl_Position = u_MVPMatrix * calcPos;\n \n### 如何让星星飞起来\n  \n剩下的部分就是飞行的星空了。绘制星空,我考虑过使用[Leonids](https://github.com/plattysoft/Leonids)这个库.它使用了简单易用的Android画布来实现。然而，为这么多星星使用动画将不可避免的导致性能问题，让我们的动画变得卡顿，尤其是在旧手机上。下图是我使用Leonids时GPU的使用情况\n\n![2.png](https://github.com/DroidWorkerLYF/Translate/blob/master/star%20wars/2.png?raw=true)\n\n*[绿线表示60FPS（16ms）。要避免卡顿，我们就不应该超过这条线]*\n\n由于性能原因,我决定使用上面分裂视图的方式,在vertex shader （顶点着色器）中处理.\n\n我发现星星的纹理非常简单并且可以使用fragment shader（片段着色器）替代-通过公式渲染出一个星星。\n\n    // Render a star\n\n    float color = smoothstep(1.0, 0.0, length(v_TexCoordinate - vec2(0.5)) / v_Radius);\n\n    gl_FragColor = vec4(color);\n\n正如你看到的，我们得到了和图片一样的效果。这还使得我们每秒提升了30%的帧率。这个解决方法可能不总是比纹理寻址快(例如使用一张图片)，但通常是更快的。唯一能检测的方法就是实际测量。\n\n![3.png](https://github.com/DroidWorkerLYF/Translate/blob/master/star%20wars/3.png?raw=true)\n \n*左：图片纹理*\n  \n*右：生成的星星*\n \n当我在3年前的Nexus 4上测试这个方案时，我可以在60帧下渲染100 000个星星。\n\n### 如何使用我们的星战库\n  \n1. 在`TilesFrameLayout`中嵌套你的主视图\n\n    \t<com.yalantis.starwars.TilesFrameLayout\n\n        \tandroid:id=\"@+id/tiles_frame_layout\"\n\n        \tandroid:layout_height=\"match_parent\"\n\n        \tandroid:layout_width=\"match_parent\"\n\n        \tapp:sw_animationDuration=\"1500\"\n\n        \tapp:sw_numberOfTilesX=\"35\">\n\n                   <!-- Your views go here → -->\n\n    \t</com.yalantis.starwars.TilesFrameLayout>\n  \n2. 通过以下属性调整动画效果\n\n\t-   sw\\_animationDuration - 执行时间（ms）\n\t-   sw\\_numberOfTilesX - 横向要产生的碎片数量\n\n    \tmTilesFrameLayout = (TilesFrameLayout) findViewById(R.id.tiles_frame);\n\n    \tmTilesFrameLayout.setOnAnimationFinishedListener(this);\n \n3. 在activity或者fragment的`onPause`和`onResume`方法中调用对应方法:\n\n    \t@Override\n\n    \tpublic void onResume() {\n\n        \tsuper.onResume();\n\n        \tmTilesFrameLayout.onResume();\n\n    \t}\n\n    \t@Override\n\n    \tpublic void onPause() {\n\n        \tsuper.onPause();\n\n        \tmTilesFrameLayout.onPause();\n\n    \t}\n \n4. 调用`startAnimation()`就可以启动动画\n\n    \tmTilesFrameLayout.startAnimation();\n  \n5. 你的回调会在动画结束时调用:\n\n    \t@Override\n\n    \tpublic void onAnimationFinished() {\n\n      \t\t// Hide or remove your view/fragment/activity here\n\n    \t}\n \n以上就是全部内容啦!\n \n### 未来的计划\n\n当Android Extension Pack被更普遍支持时,使用tessellation shaders（细分曲面着色器）来重写将会很有趣。保持关注!"
  },
  {
    "path": "issue-35/用Transition完成Fragment共享元素的切换.md",
    "content": "用 Transition 完成 Fragment 共享元素的切换\n---\n\n> * 原文链接 : [Fragment transitions with shared elements](https://medium.com/@bherbst/fragment-transitions-with-shared-elements-7c7d71d31cbb#.fp2fgmvoi)\n* 原文作者 : [Austin Mueller](http://armueller.github.io/)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者:  [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\nMaterial Design 的基础之一是：让不同页面的切换具有意义，即让用户知道与界面发生交互后为何会从当前页面跳转到另一个页面。为此，Android Lolipop 为开发者提供了一个名为 Transition 的过渡动画框架，开发者能够通过这个框架为 Activity 和 Fragment 的跳转提供动画效果。我在了解 Transition 框架的过程中发现现在网上并没有太多将 Transition 应用到 Fragment 上的相关资料，所以我决定写一篇博客来分享一些我学到的知识～\n\n我在开发的一个产品非常简单，用格子的形状来展示图片瀑布流，当用户点击图片时，就会显示该图片的详细信息。多亏了 Transition 框架，我们才能按照我们的想法完成整个图片的交互和显示，效果图如下：\n\n![](https://d262ilb51hltx0.cloudfront.net/max/800/1*eh4DsUQSyeoAuKvLQ5NgWw.gif)\n\n如果你不想看下面的文字，而是想直接下载源码的话，[戳我去 Github 下载吧～](https://github.com/bherbst/FragmentTransitionSample)\n\n##在老版本的 Android 系统中会怎样呢？\n\n事实上，这篇博文既为大家带来了好消息，也为大家带来了坏消息。坏消息是：Lolipop 之前的版本并没有可调用的 Transition API；但好消息是：Android support library 提供了可以在 API 21 以上的手机版本中使用 Transition API 的方法，而且不需要为代码添加 API 版本的判断。\n\n##Setup\n\n不像为 Activity 添加 Transition，在为 Fragment 添加 Transition 的时候不需要用到 Window.FEATURE_ACTIVITY_TRANSITIONS，也不需要 Window.FEATURE_CONTENT_TRANSITIONS。事实上，你甚至不需要做任何额外的操作就可以完成这项操作了。\n\n##Transition Names\n\nTransition 框架需要一个能将当前页面的 View 关联到将要跳转到的页面的办法，而 Lolipop 为我们提供的办法就是：通过名为 “Transition Name” 的 View 属性将两者关联在一起。\n\n下面是为 View 添加 Transition Name 的两种方法：\n\n- 可以在代码中直接调用 ViewCompat.setTransitionName()，也可以在 Android Lolipop 系统版本以上的设备中直接调用 setTransitionName()。\n\n- 在布局的 XML 文件中，直接设置 android:transitionName 属性。\n\n在这里有一点是需要注意的：在一个给定的布局中，Transition Name 必须是唯一的，这一点你一定要牢记在心，特别是在使用 ListView 或 RecyclerView 的时候，要不然每一个 Item 都会有相同的 Transition Name。\n\n##Set up the FragmentTransaction\n\n为 Fragment 添加 Transition 就像下面的代码看起来那样简单：\n\n```java\ngetSupportFragmentManager()\n        .beginTransaction()\n        .addSharedElement(sharedElement, transitionName)\n        .replace(R.id.container, newFragment)\n        .addToBackStack(null)\n        .commit();\n```\n\n只需要调用 addSharedElement() 来关联要与 Fragment 共享的 View。\n\n作为参数传递给 addSharedElement() 的 View 就是第一个 Fragment 中你所想要与即将跳转的 Fragment 共享的 View。而这个方法的第二个参数要求填入的 Transition Name，就是在跳转的 Fragment 中的该共享 View 的 Transition Name。例如，当前 Fragment 中共享 View 的 Transition Name 为 \"foo\"，即将跳转的 Fragment 中的共享 View 的 Transition Name 为 \"bar\"，那么在 addSharedElement() 中传递的第二个参数就应该是 \"bar\"。\n\n##指定 Transition 动画\n\n最后我们需要指定 Fragment 切换时我们需要的 Transition 过渡动画。\n\n为共享元素添加的办法：\n\n- 调用 setSharedElementEnterTransition() 指定 View 如何从第一个 Fragment 转换为跳转 Fragment 中的 View。\n\n- 调用 setSharedElementReturnTransition() 指定 View 在用户点击返回按钮后如何从跳转 Fragment 中回到第一个 Fragment 中。\n\n记住，返回 Transition 需要在跳转 Fragment 中调用相应的方法，要不然你得不到你想要的效果的。\n\n当然了，你也可以为任何非共享的 View 设置 Transition 过渡动画，只不过调用的 API 变了：在对应的 Fragment 中调用 setEnterTransition(), setExitTransition(), setReturnTransition(), 和 setReenterTransition() 这些方法就可以了。\n\n这四个方法都只接收一个参数，也就是你所希望使用的 Transition 过渡动画。而我们希望整个切换过程动画不要太复杂，简简单单的就好，所以我们会使用一个自定义 Transition 来完成淡入淡出的弹出效果。\n\n##Transition animation classes\n\nAndroid 已经提供了一些适用于大多数场景的 Transition 动画了，淡入/淡出就是普通的淡入/淡出效果。滑动会让 View 从一端滑动到另一端。爆炸效果看起来蛮有趣的 - View 会从一个点散开。AutoTransition 则会淡入/淡出，移动，大小变化。这些都是简单的 Transition 用例，有兴趣的话可以到 Transition 包中发现更大的世界哦～\n\n```java\npublic class DetailsTransition extends TransitionSet {\n    public DetailsTransition() {\n        setOrdering(ORDERING_TOGETHER);\n        addTransition(new ChangeBounds()).\n                addTransition(new ChangeTransform()).\n                addTransition(new ChangeImageTransform()));\n    }\n}\n```\n\n这里用到的 Transition 就是三个不同的 Transition 过渡动画同时播放的效果：\n\n- ChangeBounds 显示 View 大小和位置变化的动画\n\n- ChangeTransform 显示 View 及其父布局的缩放动画\n\n- ChangeImageTransform 能修改图片的大小或者缩放\n\n如果你想知道这三个 Transition 是怎么交互到一起的，运行我的示例 App，然后把其中的某一个移除就会懂了。\n\n你当然也可以使用 XML 定义更多复杂的 Transition 过渡动画效果，如果你更喜欢用 XML 来定义过渡动画效果的话，不妨看看[文件](https://developer.android.com/reference/android/transition/Transition.html)\n\n##把所有上面提到的合并到一起\n\n显示示例图的过渡动画的最终代码很简单：\n\n```java\nDetailsFragment details = DetailsFragment.newInstance();\n\n// Note that we need the API version check here because the actual transition classes (e.g. Fade)\n// are not in the support library and are only available in API 21+. The methods we are calling on the Fragment\n// ARE available in the support library (though they don't do anything on API < 21)\nif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n    details.setSharedElementEnterTransition(new DetailsTransition());\n    details.setEnterTransition(new Fade());\n    setExitTransition(new Fade());\n    details.setSharedElementReturnTransition(new DetailsTransition());\n}\n\ngetActivity().getSupportFragmentManager()\n        .beginTransaction()\n        .addSharedElement(holder.image, \"sharedImage\")\n        .replace(R.id.container, details)\n        .addToBackStack(null)\n        .commit();\n```\n\n我们用自定义的 DetailsTransition 过渡动画来显示共享元素的跳转和返回动画，其他的 View 都会以淡入/淡出的形式随着动画一起切换。\n"
  },
  {
    "path": "issue-35/让EditText中的链接即可点击又可编辑.md",
    "content": "让EditText中的链接即可点击又可编辑\n---\n\n> * 原文链接 : [Making EditTexts with links both clickable and editable](http://blog.danlew.net/2015/12/14/making-edittexts-with-links-both-clickable-and-editable/)\n* 原文作者 : [Dan Lew](http://blog.danlew.net/about/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成\n\n最近，我正忙于让[`EditText`](http://developer.android.com/reference/android/widget/EditText.html)支持编辑链接。让用户添加的链接在非编辑状态下可以跳转。\n\n`EditText`没有焦点时，链接应该可点。然而，当获得焦点时，应该让用户可以编辑链接。\n\n添加一个[`OnFocusChangeListener`](http://developer.android.com/reference/android/view/View.OnFocusChangeListener.html)来获取焦点的改变非常容易，但是用户如何改变焦点呢?(因为点击链接会使得浏览器被打开)\n\n长按是一种解决方法。第二种办法是点击`EditText`的空白区域，如图：\n\n![image](https://github.com/DroidWorkerLYF/Translate/blob/master/Making%20EditTexts%20with%20links%20both%20clickable%20and%20editable/1.png?raw=true)\n \n不幸的是由于`LinkMovementMethod`的问题，**就算文本最后一部分是空白的,点击它也相当于点击了链接**\n\n我发现这个问题只发生在*最后*一个字符是链接的情况下，那么如果我在文本最后添加一些内容呢?\n\n    // Make links in the EditText clickable\n    editText.setMovementMethod(LinkMovementMethod.getInstance());\n\n    // Setup my Spannable with clickable URLs\n    Spannable spannable = new SpannableString(\"http://blog.danlew.net\");  \n    Linkify.addLinks(spannable, Linkify.WEB_URLS);\n\n    // The fix: Append a zero-width space to the Spannable\n    CharSequence text = TextUtils.concat(spannable, \"\\u200B\");\n\n    // Use it!\n    editText.setText(text);  \n  \n现在点击空白的部分不会导致链接跳转了，我也可以切换到编辑模式了。\n"
  },
  {
    "path": "issue-35/近乎通用的VectorDrawable.md",
    "content": "近乎通用的VectorDrawable\n---\n\n> * 原文链接 : [Vectors For All (almost)](https://blog.stylingandroid.com/vectors-for-all-almost/?utm_source=Android+Weekly&utm_campaign=0903213dbd-Android_Weekly_175&utm_medium=email&utm_term=0_4eb677ad19-0903213dbd-337955857)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF)\n* 校对者: [desmond1121](https://github.com/desmond1121)\n* 状态 :  完成\n\n[Styling Android](https://blog.stylingandroid.com/)的读者们将会了解到我对`VectorDrawable`和`AnimatedVectorDrawable`的喜爱。在我写这篇文章的时候，我们仍然在等待`VectorDrawableCompat`来支持老版本的系统，所以目前只能在API 21(Lollipop)及之后的系统上使用它们。不过Android Studio 1.4版本已经为编译工具加入了一些向下兼容的能力，使得我们可以开始在Lollipop之前的系统上使用`VectorDrawable`。在这篇文章中，让我们看看这是如何实现的。\n \n在我们开始之前，让我们先来简单了解一下什么是`VectorDrawable`。本质上来说就是对SVG的path元素在Android上的封装。path元素是通过声明来描述复杂图形元素的方式。path元素适合于线条的绘制和矢量图形，不适合照片。之前在Android上我们可以使用`ShapeDrawable`做一些[基础的东西](https://blog.stylingandroid.com/more-vector-drawables-part-2/)，但是我们经常需要将矢量图和线条转换成位图以适应多样的分辨率。\n \nAndroid Studio 1.4新加了导入SVG的功能并且可以自动将它们转为`VectorDrawable`。我们可以导入[material icons\npack](https://www.google.com/design/icons/)中的图标或者是单独的SVG文件。导入material icons pack的图标很容易，并且有丰富的资源。相比之下，导入单独的SVG文件就会有很多问题。原因就是`VectorDrawable`只支持SVG的一个子集,缺少了对常用的gradient,pattern fills,local IRI references(赋予元素唯一的引用并且在SVG中通过这个引用来复用此元素)和transformations的支持。\n \n举个例子，导入官方的[SVG logo](http://www.w3.org/2009/08/svg-logos.html)这种相对简单的图片(如下图)都会失败，因为缺少了对local IRI references的支持。\n\n![svg\\_logo](https://github.com/DroidWorkerLYF/Translate/blob/master/vectors%20for%20all/1.png?raw=true)\n  \n现在还不清楚这些遗漏的功能是由于对性能问题的考虑(比如gradients的渲染就很复杂)还是在开发中。\n  \n通过对SVG格式的了解(超出了这篇文章的讨论范围)，我们可以手动调整上面的图片，移除local IRI references。调整过的下图和上面SVG logo效果是相同的。\n\n![svg\\_logo2](https://github.com/DroidWorkerLYF/Translate/blob/master/vectors%20for%20all/2.png?raw=true)\n  \n但这仍然不能导入到Android Studio中,提示的错误信息是“premature end of file”。感谢[Wojtek Kaliciński](https://plus.google.com/+WojtekKalicinski)的建议，我将宽高从百分比修改为绝对数值之后就可以导入了。然而因为translations也是不支持的，所以所有的元素都不能正确的布局(如下图):\n\n![svg\\_logo2](https://github.com/DroidWorkerLYF/Translate/blob/master/vectors%20for%20all/3.png?raw=true)\n \n通过手动调整源文件中所有的translation和rotation transformations(使用支持transformations的`<group>`元素嵌套`<path>`),我终于可以把官方的SVG logo导入了并且在Marshmallow(Android 6.0)上能被`VectorDrawable`正确的渲染:\n\n![SVGLogo](https://github.com/DroidWorkerLYF/Translate/blob/master/vectors%20for%20all/4.png?raw=true)\n  \nJuraj Novák制作了一个[方便的工具](http://inloop.github.io/svg2android/)可以将SVG直接转换成`VectorDrawable`。但它也有着很多和导入一样的限制，不能处理gradients和local IRI references，但是在转换我手动调整过的SVG上表现的更好，不但没有因为使用百分比的宽高值而处理失败，而且还提供了一个实验模式来很好的处理transformations。但是仍然需要手动调整原始SVG文件的local IRI references。\n\n把处理后的图片放到`res/drawable`文件夹下，我们就可以把它当做drawable引用了。\n\n    res/layout/activity_main.xml\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <RelativeLayout\n\t\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\txmlns:tools=\"http://schemas.android.com/tools\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:paddingBottom=\"@dimen/activity_vertical_margin\"\n\t\tandroid:paddingLeft=\"@dimen/activity_horizontal_margin\"\n\t\tandroid:paddingRight=\"@dimen/activity_horizontal_margin\"\n\t\tandroid:paddingTop=\"@dimen/activity_vertical_margin\"\n\t\ttools:context=\".MainActivity\"> \n\n    \t<ImageView\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:contentDescription=\"@null\" \n\t\t\tandroid:src=\"@drawable/svg_logo2\"\n\t\t/> \n    </RelativeLayout>\n\n假设我们正在使用gradle plugin 1.4.0 或者更新的版本(写文章时1.4.0还没有发布，不过`1.4.0-beta6`同样可以)，就可以让`VectorDrawable`兼容到API 1!\n  \n所以究竟发生了什么呢?我们看一下build文件夹下生成的代码就很清楚了:\n\n![Screen Shot 2015-10-03 at 15.20.33](https://github.com/DroidWorkerLYF/Translate/blob/master/vectors%20for%20all/5.png?raw=true)\n  \n在API 21及之后的版本会使用我们导入的XML vector drawable,而之前的版本会使用PNG格式图片代替。\n\n但如果我们不需要对应全部的分辨率，而是更加关注由此导致APK增加的大小呢?我们可以通过build flavor的`generatedDensities`属性来控制需要生成的分辨率。\n\n\tapp/build.gradle\n\n\tapply plugin: 'com.android.application'\n    android {\n\t\tcompileSdkVersion 23\n\t\tbuildToolsVersion \"23.0.1\"\n        defaultConfig {\n        \tapplicationId \"com.stylingandroid.vectors4all\"\n       \t\tminSdkVersion 7 targetSdkVersion 23\n      \t\tversionCode 1\n     \t\tversionName \"1.0\"\n    \t\tgeneratedDensities = ['mdpi', 'hdpi']\n   \t\t}\n\t\tbuildTypes {\n        \trelease {\n       \t\t\tminifyEnabled false\n      \t\t\tproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n     \t\t}\n    \t}\n\t}\n\tdependencies {\n    \tcompile fileTree(dir: 'libs', include: ['*.jar'])\n\t\ttestCompile 'junit:junit:4.12' compile\n\t\t'com.android.support:appcompat-v7:23.0.1'\n    }\n  \n如果我们现在build一下（记得先clean来清除之前生成的资源），就会发现只生成了我们指定分辨率的资源：\n\n![Screen Shot 2015-10-03 at 15.27.08](https://github.com/DroidWorkerLYF/Translate/blob/master/vectors%20for%20all/6.png?raw=true)\n \n那么，现在让我们来看看实际生成的png:\n\n![svg\\_logo2](https://github.com/DroidWorkerLYF/Translate/blob/master/vectors%20for%20all/7.png?raw=true)\n\n这和我手动添加缺少的transformations之前导入的SVG渲染出的一样。我之前本该提到lint有一个警告指出`<group>`元素是不支持光栅化的，`VectorDrawable`是一个Android特定的格式，却没有被完整的支持，似乎令人费解。\n\n现在我们开始理解为什么transformations不被导入工具支持了，因为为了向下兼容而将`VectorDrawable`转换成光栅化图片时`<group>`中的transformations是不支持转换的。这应该是一个关键遗漏：在Lollipop以及之后的系统上可以完美渲染的有效的`VectorDrawable`资源转换成PNG图片就不正确了。\n  \n总结下：如果你使用这些新工具从material icons library中导入资源，那么一切都很完美。然而，声称导入工具可以导入SVG文件则是一种误导，因为它支持非常有限的SVG的子集，并且无法正确导入大部分实际真正使用的SVG文件。此外，缺少对`VectorDrawable`转换为光栅化视图的完整支持让人感觉这项功能没有真正完成，没有准备好投入使用。\n\n使用导入工具配合手动调整将官方的SVG logo转成`VectorDrawable`并没有比我纯手动调整需要更多工作。尽管我仍然需要自己调整SVG pathData中的所有transformations的坐标，来实现必要的transformations。\n\n希望这些问题中的一些会被尽快解决，这样这些新的潜在的实用工具就可以开始实现他们的用处了。\n\n此文的源码\n[here](https://github.com/StylingAndroid/Vectors4All/tree/master)."
  },
  {
    "path": "issue-36/Gradle小知识1.md",
    "content": "Gradle小知识#1：tasks\n---\n\n> * 原文链接 : [Gradle tip #1: tasks](http://trickyandroid.com/gradle-tip-1-tasks/)\n* 原文作者 : [Tricky Android](http://trickyandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Damonzh](https://github.com/Damonzh)  \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n从这篇博文开始我打算开启关于Gradle相关知识的一些列博文。现在想想，如果我刚开始接触Gradle的时候知道这些知识的话那该多好啊。 \n\n今天我们来聊聊Gradle的**任务**，尤其是任务的配置和执行部分。因为这块知识对很多读者来说还不是很清楚，所以通过一个真实的例子来说明下就再好不过了。大体上(抱歉有点超前)我们是想要弄清楚下面这三个例子到底有啥区别：\n\n~~~gradle\ntask myTask {\n    println \"Hello, World!\"\n}\n\ntask myTask {\n    doLast {\n        println \"Hello, World!\"\n    }\n}\n\ntask myTask << {\n    println \"Hello, World!\"\n}\n~~~  \n我的目的是创建一个任务，当这个任务被执行的时候它能打印\"Hello， World!\"。一开始，我是这样实现的：\n\n~~~gradle\ntask myTask {\n    println \"Hello, World!\"\n}\n~~~\n现在我们执行下这个任务！\n\n~~~gradle\nuser$ gradle myTask\nHello, World!\n:myTask UP-TO-DATE\n~~~  \n这好像没啥问题。正确的打印出了“Hello, World!”\n**但是，**这并没有按照我们预想的那样工作。下面让我告诉你为什么。我们试着调用下`gradle tasks`看看还有其他什么能执行的任务没：\n\n~~~gradle\nuser$ gradle tasks\nHello, World!\n:tasks\n\n------------------------------------------------------------\nAll tasks runnable from root project\n------------------------------------------------------------\n\nBuild Setup tasks\n-----------------\ninit - Initializes a new Gradle build. [incubating]\n..........\n~~~\n等等！为什么打印了\"Hello, World!\"？我只是调用了`tasks`，我并没有调用我定义的任务呀！\n\n原因就是在Gradle`任务`的生命周期中，有两个重要的阶段：\n\n* 配置阶段\n* 执行阶段\n\n我对这些专业术语可能不太熟悉，但类比一下可以帮助我更好的理解Gradle的任务。\n\n事情是这样的，Gradle在真正进行构建**之前**，**必须把构建脚本中定义的所有任务配置一下**。不管这个任务会不会被执行，它都要先配置一下。\n\n既然了解了这一点，但我又该怎么知道任务中哪部分是在配置阶段执行，哪部分又是在执行阶段执行呢？答案就是：任务中的顶层部分是**配置阶段**会执行的代码。比如：\n\n~~~gradle\ntask myTask {\n    def name = \"Pavel\" //<-- this is evaluated during configuration\n    println \"Hello, World!\"////<-- this is also evaluated during configuration\n}\n~~~\n\n这就是为什么我只是调用了`gradle tasks`，但却打印了\"Hello, World!\"。因为这部分代码在配置阶段执行了。但这并不是我想要的。我想要的是这段代码只有在我调用我的任务的时候才去执行。\n\n那么，我该怎么告诉Gradle当我执行我的任务的时候才去做一些事情呢？\n\n这时我就需要给任务指定一个动作。最简单的做法就是通过[Task#doLast()](https://www.gradle.org/docs/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:doLast(groovy.lang.Closure))方法：\n\n~~~gradle\ntask myTask {\n    def text = 'Hello, World!' //configure my task\n    doLast {\n        println text //this is executed when my task is called\n    }\n}\n~~~\n\n现在，\"Hello, World!\"只有在我显式的调用`gradle myTask`的时候才会打印出来。\n\nCool！现在我知道怎么配置任务并且让任务只有在我明确调用的时候才会做真正的工作。那么第三个例子中的`<<`又是啥？：\n\n~~~\ntask myTask2 << {\n    println \"Hello, World!\" \n}\n~~~\n\n这只是`doLast`的简写版本而已。这跟下面的写法完全一样：\n\n~~~gradle\ntask myTask {\n    doLast {\n        println 'Hello, World!' //this is executed when my task is called\n    }\n}\n~~~\n但是，既然所有的代码都在执行部分了，那我就不能像`doLast`写法那样配置我的任务了(放心吧，还是可以的，只是稍微有点不同而已)。这种简写方式对那些不需要进行配置的小任务来说很方便。但是，如果你的任务不仅仅像打印\"Hello, World!\"这么简单的话，那还是建议考虑使用`doLast`这种写法。"
  },
  {
    "path": "issue-36/Gradle小知识2.md",
    "content": "Gradle小知识#2：学学语法\n---\n\n> * 原文链接 : [Gradle tip #2: understanding syntax](http://trickyandroid.com/gradle-tip-2-understanding-syntax/)\n* 原文作者 : [Tricky Android](http://trickyandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Damonzh](https://github.com/Damonzh)  \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n在第一部分，我们聊了下Gradle中的任务以及构建过程中的不同阶段。但是，在我发布上篇文章之后我意识到在继续深入Gradle之前，我们最好了解下Gradle的语法。以免被复杂的`build.gradle`脚本吓到。所以这篇我们来聊聊Gradle的语法。\n\n### 语法\nGradle构建脚本是使用[Groovy](http://www.groovy-lang.org/)语言写的。所以在继续深入之前我想说几个Groovy中比较重要的概念。Groovy的语法跟Java语法很类似，所以希望你在理解上不会有大问题。 \n\n如果你对Groovy比较熟悉，那你可以跳过这部分。\n\n要理解Gradle脚本，你需要明白Groovy中很重要的一个概念——闭包。\n\n#### 闭包\n要理解Gradle就必须搞明白[`闭包`](http://www.groovy-lang.org/closures.html)这个重要的概念。闭包是一个独立的代码块，它可以接收参数、可以有返回值，而且还可以赋值给变量。有点像`Callable`接口、`Future`和方法指针的混合体，随便你怎么叫。\n\n大体来说就是个代码块，当你调用它的时候它才会执行，而非创建时就执行。下面是一个简单的闭包的例子：\n\n~~~gradle\ndef myClosure = { println 'Hello world!' }\n\n//execute our closure\nmyClosure()\n\n#output: Hello world!\n~~~ \n\n亦或是接收一个参数的闭包：\n\n~~~gradle\ndef myClosure = {String str -> println str }\n\n//execute our closure\nmyClosure('Hello world!')\n\n#output: Hello world!\n~~~\n如果闭包只有一个参数，那这个参数可以通过`it`来引用：\n\n~~~gradle\ndef myClosure = {println it }\n\n//execute our closure\nmyClosure('Hello world!')\n\n#output: Hello world!\n~~~ \n\n接收多个参数的闭包：\n\n~~~gradle\ndef myClosure = {String str, int num -> println \"$str : $num\" }\n\n//execute our closure\nmyClosure('my string', 21)\n\n#output: my string : 21\n~~~  \n\n顺便插一句，闭包的参数类型是可选的。所以上面的例子可以简写成下面这样：  \n\n~~~gradle\ndef myClosure = {str, num -> println \"$str : $num\" }\n\n//execute our closure\nmyClosure('my string', 21)\n\n#output: my string : 21\n~~~  \n\n闭包可以引用它所在上下文环境中的变量，这一点非常酷。这个上下文环境默认是闭包被创建时所在的类：\n\n~~~gradle\ndef myVar = 'Hello World!'\ndef myClosure = {println myVar}\nmyClosure()\n\n#output: Hello world!\n~~~\n\n另一个比较酷的特性是：闭包的上下文环境可以通过`Closure#setDelegate()`进行改变。这个特性在以后会非常重要：\n\n~~~gradle\ndef myClosure = {println myVar} //I'm referencing myVar from MyClass class\nMyClass m = new MyClass()\nmyClosure.setDelegate(m)\nmyClosure()\n\nclass MyClass {\n    def myVar = 'Hello from MyClass!'\n}\n\n#output: Hello from MyClass!\n~~~\n\n如你所见，在我们创建闭包的时候，`myVar`变量还不存在。但这都不是事，只要我们执行闭包的时候，它存在于闭包的上下文环境中就行。\n\n在这个例子中，在闭包执行之前，我修改了闭包的上下文环境。所以`myVar`就可以被引用到了。  \n\n#### 把闭包当做参数来传递\n闭包的真正好处是：你可以把它当做参数传递给不同的方法。这可以帮助我们解耦执行逻辑。\n\n其实前面我们已经使用过这个特性了。接下来我们详细说下方法接收闭包作为参数时的调用方式：\n\n1. 如果方法接收一个闭包参数   \n`myMethod(myClosure)`\n2. 如果方法只接收一个参数而且这个参数是闭包，那么圆括弧可以省略：  \n`myMethod myClosure`\n3. 也可以以内联的方式调用闭包  \n`myMethod {println 'Hello World'}`\n4. 接收两个参数的方法  \n`myMethod(arg1, myClosure)`\n5. 或者跟‘4’一样，但是闭包是内联的\n`myMethod(arg1, { println 'Hello World' })`\n6. 如果最后一个参数是闭包，那就可以把它移到圆括弧外面  \n`myMethod(arg1) { println 'Hello World' }`  \n\n尤其要注意下‘3’和‘6’，是否看起来有点眼熟？  \n\n### Gradle\n了解到这里也差不多了，下面我们结合真实的Gradle脚本来演示下，这也能让我们更好的理解上面讲到的东西：\n\n~~~gradle\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.2.3'\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n~~~\n\n瞅瞅！知道Groovy语法的话就大概能理解上面那些代码是啥意思了！\n\n* 在某个地方定义了一个`buildscript`方法，并且这个方法接收一个闭包：  \n`def buildscript(Closure closure)`\n* 在某个地方定义了一个`allprojects`方法，并且这个方法接收一个闭包：  \n`def allprojects(Closure closure)`\n\n....等等等等。\n\n挺不错的嘛，但仅仅知道这些并没多大用。在某个地方又是啥意思？我想知道定义这个方法的确切位置。\n\n问题的答案就是-[Project](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html)  \n\n### Project\nProject是理解Gradle脚本的关键：  \n> 构建脚本中所有的顶级语句都会被代理到`Project`实例上。\n \n这就意味着我们要从[Project](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html)开始找起。  \n\n我们先试着查找下`buildscript`方法。  \n\n如果我们查找`buildscript`就会找到[buildscript {}](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:buildscript(groovy.lang.Closure))脚本块。但是脚本块又是个什么鬼？[官方文档](https://docs.gradle.org/current/dsl/)是这么解释的：  \n> 脚本块就是接收一个闭包为参数的回调方法。  \n\n没错，就是它了。当我们调用`buildscript`方法的时候，就是调用了`buildscript { ... }`脚本块，它接收一个闭包作为参数。  \n\n如果我们继续查阅`buildscript`文档，有这么一句话：_从buildscript代理到[ScriptHandler](https://docs.gradle.org/current/javadoc/org/gradle/api/initialization/dsl/ScriptHandler.html)。_也就是说在执行阶段，作为参数穿进去的闭包会被变成ScriptHandler。在我们的例子中，我们传递了执行`repositories(Closure)` 和 `dependencies(Closure)` 方法的闭包。既然闭包会被代理到`ScriptHandler`上，那就在`ScriptHandler`类中查下`dependencies`方法。  \n\n我们找到了[void dependencies(Closure configureClosure)](https://docs.gradle.org/current/javadoc/org/gradle/api/initialization/dsl/ScriptHandler.html#dependencies(groovy.lang.Closure))，文档的解释是：给脚本配置依赖。这里我们又碰到个术语：在DependencyHandler上执行给定闭包。这跟**代理到某某某**是一个意思——这个闭包将会在另外一个类的作用域内执行。(在我们的例子中就是[DependencyHandler](https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/dsl/DependencyHandler.html))  \n\n> \"**代理到某某某**“和”**配置某某某**“这两句话完全是一个意思——闭包会在某个指定的类上执行。  \n\nGradle中大量用到这种代理策略，所以理解这个术语非常重要。  \n\n让我们把剩下的讲完，我们来看看在`DependencyHandler`上下文中执行`{classpath 'com.android.tools.build:gradle:1.2.3'}`闭包会发生什么。文档中是这么描述的：用给定的配置来配置依赖。语法是：`<configurationName> <dependencyNotation1>`  \n\n所以我们的闭包就是用`classpath`这个配置把`com.android.tools.build:gradle:1.2.3`配置为依赖。  \n\n### 脚本块\n`Project`中默认定义了一些脚本块，但是你可以通过Gradle插件定义新的脚本块。  \n\n这就意味着如果你在你的构建脚本中看到类似`something { ... }`的代码，但你却在文档中找不到这个脚本块或者类似的接收闭包的方法。很可能就是你使用了某个插件，这个插件定义了这个脚本块。  \n\n#### `android`脚本块\n我们来看一眼Android项目的默认构建脚本，它位于项目的`app/build.gradle`路径中：   \n\n~~~grale\napply plugin: 'com.android.application'\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        applicationId \"com.trickyandroid.testapp\"\n        minSdkVersion 16\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n~~~\n\n可以看到，好像应该有一个叫`android`的方法，这个方法接收一个闭包作为参数。但是，如果我们在`Project`文档中找不到这个方法。原因很简单，Project中本来就没有这个方法。 \n\n如果你仔细看看构建脚本，你会发现在执行`android`方法之前，我们使用了`com.android.application`插件！这就是问题的答案了！Android应用插件继承了`Project`，定义了`android`脚本块(接收闭包作为参数并且代理到`AppExtension`类[1](http://trickyandroid.com/gradle-tip-2-understanding-syntax/#fn:1))  \n  \n那我们去哪找Android插件的文档呢？你可以去[Android工具网](https://developer.android.com/tools/building/plugin-for-gradle.html)的官网下载这个文档，或者用这个[下载链接](https://developer.android.com/shareables/sdk-tools/android-gradle-plugin-dsl.zip)直接下载。  \n\n如果我们打开`AppExtension`文档看看，我们我可找到所有在构建脚本中看到的方法和属性：\n\n1. `compileSdkVersion 22`。如果搜索下`compileSdkVersion`就能找到这个属性。上面的例子中给这个属性赋值`22`  \n2. `buildToolsVersion`也是类似的  \n3. `defaultConfig`是个脚本块，它被代理给了`ProductFlavor`类  \n4. 等等......  \n\n现在我们就能理解Gradle构建脚本的语法并且会查阅文档了。  \n\n###练习\n学了这么多新技能，接下来我们来操练操练，试着重新配置下一些东西。\n\n在`AppExtension`中我发现了`testOptions`脚本块，它的闭包被代理到`TestOptions`类。查看`TestOptions`类，我们发现它有两个属性：`reportDir`和`resultsDir`。根据文档的解释：`reportDir`的作用是制定测试报告的存储路径。我们更改下它的值！\n\n~~~gradle\nandroid {\n......\n    testOptions {\n        reportDir \"$rootDir/test_reports\"\n    }\n}\n~~~\n\n这里使用了`Project`的`rootDir`属性，它指向工程的根目录。  \n\n所以，如果我们现在执行`./gradlew connectedCheck`,测试报告就会被存放到`[rootProject]/test_reports`目录底下。  \n\n为了避免破坏了你的项目结构，千万不要在你的实际项目中这么干。因为所有的构建内容默认都会存放在`build`目录中。"
  },
  {
    "path": "issue-36/Gradle小知识3.md",
    "content": "Gradle小知识#3：任务的顺序\n---\n\n> * 原文链接 : [Gradle tip #3: Tasks ordering](http://trickyandroid.com/gradle-tip-3-tasks-ordering/)\n* 原文作者 : [Tricky Android](http://trickyandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Damonzh](https://github.com/Damonzh)  \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n我发现在使用Gradle的过程中遇到的很多问题都跟任务的顺序有关系，不管是已经存在的任务还是我自定义的任务。很显然，如果任务能在正确的时间执行的话，构建任务就能更好的工作了。\n\n所以让我们深入了解下怎么更改任务的执行上顺序这个问题。\n\n### dependsOn\n我觉得让某个任务在另一个任务之后执行的最简单的方法就是使用[`dependsOn`](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:dependsOn(java.lang.Object[]))方法。  \n\n假设有一个任务`A`，现在我们要添加一个任务`B`并且任务`B`只有在任务`A`执行后它才会执行：  \n\n![](http://trickyandroid.com/content/images/2015/07/1-3.png)  \n\n先来定义两个任务，任务`A`和`B`：\n\n~~~gradle\ntask A << {println 'Hello from A'}\ntask B << {println 'Hello from B'}\n~~~\n\n然后你要做的就是告诉Gradle任务`B`依赖于任务`A`\n\n~~~gradle\nB.dependsOn A\n~~~\n\n这样不管什么时候我执行任务`B`，Gradle也会执行任务`A`\n\n~~~gradle\npaveldudka$ gradle B\n:A\nHello from A\n:B\nHello from B\n~~~  \n\n另外，你也可以在任务的配置部分声明类似的依赖关系：\n\n~~~gradle\ntask A << {println 'Hello from A'}\ntask B {\n    dependsOn A\n    doLast {\n        println 'Hello from B'  \n    }\n}\n~~~\n\n效果是一样的。  \n\n但是如果想把我们的任务插入到已经存在的任务流中该怎么做？  \n\n![](http://trickyandroid.com/content/images/2015/07/2.png)   \n\n步骤基本是一样的。  \n\n原本的任务流程是这样的：  \n\n~~~gradle\ntask A << {println 'Hello from A'}\ntask B << {println 'Hello from B'}\ntask C << {println 'Hello from C'}\n\nB.dependsOn A\nC.dependsOn B\n~~~\n\n我们的新任务是这样的：\n\n~~~gradle\ntask B1 << {println 'Hello from B1'}\nB1.dependsOn B\nC.dependsOn B1\n~~~\n输出：\n\n~~~gradle\npaveldudka$ gradle C\n:A\nHello from A\n:B\nHello from B\n:B1\nHello from B1\n:C\nHello from C\n~~~\n\n可以看到**dependsOn**方法把新任务添加到一个依赖序列当中去了。因此一个任务依赖多个任务也是完全可以的：  \n\n![](http://trickyandroid.com/content/images/2015/07/3.png)  \n\n~~~gradle\ntask B1 << {println 'Hello from B1'}\nB1.dependsOn B\nB1.dependsOn Q\n~~~  \n输出：\n\n~~~gradle\npaveldudka$ gradle B1\n:A\nHello from A\n:B\nHello from B\n:Q\nHello from Q\n:B1\nHello from B1\n~~~  \n\n### mustRunAfter\n想象一下我们的任务依赖其他两个任务。下面我会使用一个更真实点的例子。假如我有一个任务执行单元测试，另一个任务做UI测试，还有一个任务单元测试和UI测试都做：  \n\n![](http://trickyandroid.com/content/images/2015/07/4.png)  \n\n~~~gradle\ntask unit << {println 'Hello from unit tests'}\ntask ui << {println 'Hello from UI tests'}\ntask tests << {println 'Hello from all tests!'}\n\ntests.dependsOn unit\ntests.dependsOn ui\n~~~\n输出： \n\n~~~gradle\npaveldudka$ gradle tests\n:ui\nHello from UI tests\n:unit\nHello from unit tests\n:tests\nHello from all tests!\n~~~ \n\n尽管`unit`任务和`UI`测试任务会在`tests`任务之前执行，但是`unit`和`UI`这两个任务的执行顺序并不确定。现在我猜测他们是按照字母表的顺序执行的，但这种行为只是通过实践而猜测出来的，你不应该依赖于这种猜测。  \n\n我们知道UI测试的执行时间要比单元测试的执行时间长的多，所以我想让单元测试的任务先执行，只有单元测试执行正确然后才执行UI测试的任务。那么我应该怎么做才能达到这样的效果，即单元测试任务在UI测试任务之前执行？  \n\n![](http://trickyandroid.com/content/images/2015/07/5.png)  \n\n~~~gradle\ntask unit << {println 'Hello from unit tests'}\ntask ui << {println 'Hello from UI tests'}\ntask tests << {println 'Hello from all tests!'}\n\ntests.dependsOn unit\ntests.dependsOn ui\nui.dependsOn unit // <-- I added this dependency\n~~~\n输出\n\n~~~gradle\npaveldudka$ gradle tests\n:unit\nHello from unit tests\n:ui\nHello from UI tests\n:tests\nHello from all tests!\n~~~\n\n这样写单元测试就会在UI测试之前执行了！还不错！  \n\n**但是！** 这样写有一个比较恶心的问题！UI测试其实并不会依赖于单元测试。我想要UI测试可以单独的执行，但是现在的情况是每次我想执行UI测试的任务，单元测试的任务也会被执行！    \n\n[`mustRunAfter`](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:mustRunAfter(java.lang.Object[]))就可以解决这样的问题。它通过方法参数告诉Gradle该任务在哪个任务`之后`执行。因此，我们不需要为单元测试和UI测试指定依赖关系，而是当他们两个任务同时执行的时候，会给单元测试更高的执行优先级。这样单元测试就先于UI测试执行了：\n\n~~~gradle\ntask unit << {println 'Hello from unit tests'}\ntask ui << {println 'Hello from UI tests'}\ntask tests << {println 'Hello from all tests!'}\n\ntests.dependsOn unit\ntests.dependsOn ui\nui.mustRunAfter unit\n~~~\n输出\n\n~~~gradle\npaveldudka$ gradle tests\n:unit\nHello from unit tests\n:ui\nHello from UI tests\n:tests\nHello from all tests!\n~~~\n\n现在依赖关系就是这样的了：  \n\n![](http://trickyandroid.com/content/images/2015/07/6.png)  \n\n可以看到UI测试和单元测试没有显式的依赖关系！现在如果我单独执行UI测试，单元测试就不会被执行。\n\n> 需要注意的是： 在Gradle2.4中`mustRunAfter`被标记为_\"测试的\"_。这就意味着这个特性是个试验性质的，它的行为在将来的版本中可能被修改。\n\n### finalizedBy\n现在我有个可以运行UI测试和单元测试的任务。假如每个测试都会生成一份测试报告，所以我想创建一个任务把这两份测试报告合并成一份：  \n\n![](http://trickyandroid.com/content/images/2015/07/7.png)  \n\n~~~gradle\ntask unit << {println 'Hello from unit tests'}\ntask ui << {println 'Hello from UI tests'}\ntask tests << {println 'Hello from all tests!'}\ntask mergeReports << {println 'Merging test reports'}\n\ntests.dependsOn unit\ntests.dependsOn ui\nui.mustRunAfter unit\nmergeReports.dependsOn tests\n~~~ \n\n现在如果我想要两个测试的报告，我就会执行`mergeReports`这个任务：  \n\n~~~gradle\npaveldudka$ gradle mergeReports\n:unit\nHello from unit tests\n:ui\nHello from UI tests\n:tests\nHello from all tests!\n:mergeReports\nMerging test reports\n~~~  \n\n虽然正常运行了，但却有点问题。站在用户(指开发人员)的角度来看，`mergeReports`好像没啥实际意义。我想执行`tests`任务就能获得相应的报告。显然，我可以把合并报告的逻辑放在`tests`任务中，但是我更愿意把`mergeReports`的逻辑分离出来。  \n\n[finalizedBy](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:finalizedBy(java.lang.Object[]))方法可以帮你解决这个问题。从它的名字就可以看出，它是某个任务的终结者任务。  \n\n现在我们可以像下面这样修改我们的脚本：  \n\n~~~gradle\ntask unit << {println 'Hello from unit tests'}\ntask ui << {println 'Hello from UI tests'}\ntask tests << {println 'Hello from all tests!'}\ntask mergeReports << {println 'Merging test reports'}\n\ntests.dependsOn unit\ntests.dependsOn ui\nui.mustRunAfter unit\nmergeReports.dependsOn tests\n\ntests.finalizedBy mergeReports\n~~~\n\n现在我可以执行`tests`任务，而且还能得到测试报告：\n\n~~~gradle\npaveldudka$ gradle tests\n:unit\nHello from unit tests\n:ui\nHello from UI tests\n:tests\nHello from all tests!\n:mergeReports\nMerging test reports\n~~~  \n\n> 需要注意的是：在Gradle2.4中`finalizedBy`被标记为_\"测试的\"_，也就是说这是个实验性质的特性，它的行为在将来的版本中可能会被修改。\n\n差不多也就这么多内容了。使用这三个方法你就可以很好的控制你的构建过程了。\n\n"
  },
  {
    "path": "issue-36/Gradle小知识4.md",
    "content": "Gradle小知识#4：把单元测试的日志打印到控制台\n---\n\n> * 原文链接 : [Gradle tip #4: Log unit test execution events into console](http://trickyandroid.com/gradle-tip-4-log-unit-test-execution-events-into-console/)\n* 原文作者 : [Tricky Android](http://trickyandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Damonzh](https://github.com/Damonzh)  \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n今天的只是比较少，但希望会有用。  \n\n我个人经常使用命令行手动的进行Android的单元测试(`./gradlew test`),个人习惯而已。但是，默认情况下Gradle只是安静的运行着单元测试的各个步骤。当某个单元测试失败了测试任务也就失败了：  \n\n~~~gradle\n:app:processDebugJavaRes UP-TO-DATE\n:app:processDebugUnitTestJavaRes UP-TO-DATE\n:app:compileDebugUnitTestSources\n:app:mockableAndroidJar UP-TO-DATE\n:app:assembleDebugUnitTest\n:app:testDebugUnitTest\n\nBUILD SUCCESSFUL\nTotal time: 4.725 secs\n~~~\n\n我觉得如果让Gradle告诉我们什么测试正在运行，那一定很有用。向下面这样：  \n\n~~~gradle\n:app:testDebugUnitTest\ncom.trickyandroid.testproj.ExampleUnitTest > exampleTest1 PASSED\ncom.trickyandroid.testproj.ExampleUnitTest > exampleTest2 PASSED\ncom.trickyandroid.testproj.ExampleUnitTest > exampleTest3 PASSED\n\nBUILD SUCCESSFUL\nTotal time: 4.107 secs\n~~~\n\n你只需要把下面的这段脚本添加到你的`build.gradle`的顶级位置，而不是`android`闭包里：\n\n~~~gradle\ntasks.matching {it instanceof Test}.all {\n    testLogging.events = [\"failed\", \"passed\", \"skipped\"]\n}\n~~~ \n\n它的作用是：配置所有的单元测试的任务打印信息到控制台，不管是测试失败/测试通过/跳过测试都会打印响应的信息。  \n\n更多关于测试任务的配置请参见[官网](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/Test.html) \n\n"
  },
  {
    "path": "issue-36/Gradle提示和使用技巧.md",
    "content": "Gradle提示和使用技巧\n---\n\n> * 原文链接 : [Gradle tips & tricks to survive the zombie apocalypse](https://medium.com/@cesarmcferreira/gradle-tips-tricks-to-survive-the-zombie-apocalypse-3dd996604341#.k8a9o93ww)\n* 原文作者 : [César Ferreira](https://medium.com/@cesarmcferreira)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuweiguocn](https://github.com/yuweiguocn) \n* 校对者: [desmond1121](https://github.com/desmond1121)\n* 状态 :  完成 \n\n\n#Gradle提示和使用技巧\nRick Grimes不能帮助你，所以让我们一起获得它！\n\n[Gradle](http://www.gradle.org/) 虽然之前一直存在于[Android Studio](https://developer.android.com/sdk/installing/studio.html)中，但是它变成热门的转折点是Android Studio成为官方开发IDE。但是，我们充分利用这个伟大的构建自动化系统了吗？\n\n## 工程和构建专门的全局变量\n\n使用gradle，会自动生成一个**BuildConfig**类文件并且我们有能力在它里面生成额外的字段。这对于像配置服务器URL和切换开关这类功能都是非常有用的。\n\n```Gradle\ndefaultConfig {\n    buildConfigField \"String\", \"TWITTER_TOKEN\", '\"SDASJHDKAJSK\"'\n}\nbuildTypes {\n    debug {\n      buildConfigField \"String\", \"API_URL\", '\"http://api.dev.com/\"'\n      buildConfigField \"boolean\", \"REPORT_CRASHES\", \"true\"\n    }\n    release {\n      buildConfigField \"String\", \"API_URL\", '\"http://api.prod.com/\"'\n      buildConfigField \"boolean\", \"REPORT_CRASHES\", \"false\"\n    }\n}\n```\n\n**BuildConfig.TWITTER_TOKEN**, **BuildConfig.REPORT_CRASHES** 和 **BuildConfig.API_URL** 都是能够通过**BuildConfig**这个final class访问的。（后面两个变量的值根据构建类型而不同）\n\n\n## 每个构建类型不同的名称，版本名和APP ID\n\n这个可以让你有release和debug两个版本的应用在同一时间被安装（记住android不让你安装相同的包名的不同的应用）！\n\n你可以在你的崩溃报告工具中通过不同的版本名来过滤问题/崩溃。\n\n通过查看应用名称很容易发现你当前运行的是哪一个！\n\n```\nandroid {\n    buildTypes {\n        debug {\n            applicationIdSuffix \".debug\"\n            versionNameSuffix \"-debug\"\n            resValue \"string\", \"app_name\", \"CoolApp (debug)\"\n            signingConfig signingConfigs.debug\n        }\n        release {\n            resValue \"string\", \"app_name\", \"CoolApp\"\n            signingConfig signingConfigs.release\n        }\n    }\n```\n\n## 私有信息\n\nAndroid要求所有的应用被安装之前用一个证书进行数字签名。Android使用这个证书识别应用的作者，尽管这是一些不应该让其它人看见的敏感信息。\n\n你永远不应该提交这种信息到版本控制工具上。\n\n一些人认为你应该有一个本地配置文件或者甚至是一个全局~/.gradle/build.gradle使用这些值，但如果你正在做连续的集成/部署并且你没有自己专门的CI服务器，在你的版本控制器上不应该有任何带着证书用纯文本的方式展示的这种文件。\n\n```\nsigningConfigs {\n    release {\n        storeFile     \"${System.env.COOL_APP_PRIVATE_KEY}\"\n        keyAlias      \"${System.env.COOL_APP_ALIAS}\"\n        storePassword \"${System.env.COOL_APP_STORE_PW}\"\n        keyPassword   \"${System.env.COOL_APP_PW}\"\n    }\n}\n```\n\n通过这种方式我可以将敏感信息提交到我自己的CI服务器而不用担心提交到公司的CVS上。\n\n\n## 自动生成 versionName 和 versionCode\n\n把你的版本信息从逻辑组件分离出来并且分别管理它们。不用再烦恼如何放置正确的版本号与版本名称。\n\n\n```\ndef versionMajor = 1\ndef versionMinor = 0\ndef versionPatch = 0\nandroid {\n    defaultConfig {\n        versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch\n        versionName \"${versionMajor}.${versionMinor}.${versionPatch}\"\n    }\n}\n```\n\n## 添加git hash和构建时间到你的BuildConfig\n\n\n\n```\ndef gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()\ndef buildTime = new Date().format(\"yyyy-MM-dd'T'HH:mm:ss'Z'\", TimeZone.getTimeZone(\"UTC\"))\nandroid {\n    defaultConfig {\n        buildConfigField \"String\", \"GIT_SHA\", \"\\\"${gitSha}\\\"\"\n        buildConfigField \"String\", \"BUILD_TIME\", \"\\\"${buildTime}\\\"\"\n    }\n}\n```\n\n现在有两个变量可以用，BuildConfig.GIT_SHA 和BuildConfig.BUILD_TIME ，这在绑定提交/构建时间到log里面是非常方便的！\n\n## 扣紧安全带\n\n对于快速部署只需要创建一个叫做**dev**的flavour 并且设置minSdkVersion 为21.通过这么做需要注意的是你不会得到你真正的minSdk的linting检查提示，显而易见的是这仅仅会被用于日常工作而不是发布。这会允许Android gradle插件预编译（pre-dex） 每个module并且编译的APK可以在Android Lollipop（ 5.0）及以上进行测试而不需要花费dex 合并处理时间。\n\n```\nandroid {\n    productFlavors \n        dev {\n            minSdkVersion 21\n        }\n        prod {\n            // The actual minSdkVersion for the application.\n            minSdkVersion 14\n        }\n    }\n```\n\n## 单元测试直接输出到控制台\n\n一个小窍门因此我们可以看到android单元测试它们发生的日志结果。\n\n```\nandroid {\n  ...\n\n  testOptions.unitTests.all {\n    testLogging {\n      events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'\n      outputs.upToDateWhen { false }\n      showStandardStreams = true\n    }\n  }\n}\n```\n\n现在运行你的测试它们将输出类似于这样的东西：\n\n\n![image](https://cdn-images-1.medium.com/max/1600/1*njDARwtg9P9NdL5GfhIjqA.png)\n\n单元测试日志输出\n\n\n## Gradle,告诉我我很漂亮\n一个有组织地方式使用它：\n\n```\nandroid {\n    ...\n    buildTypes {\n        def BOOLEAN = \"boolean\"\n        def TRUE = \"true\"\n        def FALSE = \"false\"\n        def LOG_HTTP_REQUESTS = \"LOG_HTTP_REQUESTS\"\n        def REPORT_CRASHES = \"REPORT_CRASHES\"\n        def DEBUG_IMAGES = \"DEBUG_IMAGES\"\n \n        debug {\n            ...\n            buildConfigField BOOLEAN, LOG_HTTP_REQUESTS, TRUE\n            buildConfigField BOOLEAN, REPORT_CRASHES, FALSE\n            buildConfigField BOOLEAN, DEBUG_IMAGES, TRUE\n        }\n \n        release {\n            ...\n            buildConfigField BOOLEAN, LOG_HTTP_REQUESTS, FALSE\n            buildConfigField BOOLEAN, REPORT_CRASHES, TRUE\n            buildConfigField BOOLEAN, DEBUG_IMAGES, FALSE\n        }\n    }\n}\n```\n\n如果你有任何问题在 [@cesarmcferreira](https://twitter.com/cesarmcferreira)和我联系\n\n感谢[José Coelho](https://medium.com/@jacoelho).\n\n"
  },
  {
    "path": "issue-37/使用ACTION_PROCESS_TEXT创建自定义文本选择动作.md",
    "content": "使用ACTION_PROCESS_TEXT创建自定义文本选择动作\n---\n\n> * 原文链接 : [Creating custom Text Selection actions with ACTION_PROCESS_TEXT](https://medium.com/google-developers/custom-text-selection-actions-with-action-process-text-191f792d2999?linkId=20000023#.eqi4s7tvh)\n* 原文作者 : [Ian Lake](https://medium.com/@ianhlake)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuweiguocn](https://github.com/yuweiguocn) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n\n###使用ACTION_PROCESS_TEXT创建自定义文本选择动作\n\nAndroid 6.0引入了一个新的[浮动文本选择工具栏](http://www.google.com/design/spec/patterns/selection.html#selection-text-selection)，带来了标准的文本选择动作，如剪切，复制，和粘贴，可以更方便处理你选择的文本。甚至更好的是可以通过新的[_ACTION_PROCESS_TEXT_](http://developer.android.com/reference/android/content/Intent.html#ACTION_PROCESS_TEXT) 让 **任何应用** 添加自定义actions（动作）到文本选择工具栏上成为可能。\n\n![text_selection_toolbar](https://cloud.githubusercontent.com/assets/4308480/12228682/f420cad4-b879-11e5-8f64-e8f3d34fc765.gif)\n\n\nAndroid 6.0上的文本选择工具栏\n\n\n像 [Wikipedia](https://play.google.com/store/apps/details?id=org.wikipedia) （维基百科）和 [Google Translate](https://play.google.com/store/apps/details?id=com.google.android.apps.translate) （谷歌翻译）应用已经利用它用来立即查找或翻译选中的文本。\n\n\n你可能已经看到了关于确保文本选择工具条出现在你的应用中的[文档](http://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-text-selection)和[博客](http://android-developers.blogspot.com/2015/10/in-app-translations-in-android.html)。简而言之：使用一个标准的TextView/EditText就可以了，需要注意的是如果你使用的是 [_AppCompatActivity_](http://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html) 并且想在API 23+的设备上使用本地悬浮文本选择工具条，EditText 需要设置android:id并且得调用[_getDelegate()_](http://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html#getDelegate%28%29)_._[_setHandleNativeActionModesEnabled(false)_](http://developer.android.com/reference/android/support/v7/app/AppCompatDelegate.html#setHandleNativeActionModesEnabled%28boolean%29)。\n\n\n但是怎么找到**实现** _ACTION_PROCESS_TEXT_ 的信息并且**添加你自己的actions （动作）呢**？这就是本文要介绍的内容。\n\n\n###跨应用之间的通信-> Intent Filters\n\n如您所料，当构建跨应用的功能时，其它应用可以查询你的Android清单和每个组件的[intent filters](http://developer.android.com/guide/components/intents-filters.html)作为一个公共API。\n\n\nACTION_PROCESS_TEXT 也一样。你得在清单文件添加一个intent filter到一个Activity上。\n\n```\n<activity android:name=”@string/process_text_action_name”>\n  <intent-filter>\n    <action android:name=”android.intent.action.PROCESS_TEXT”/\n    <category android:name=”android.intent.category.DEFAULT” />\n    <data android:mimeType=”text/plain” />\n  </intent-filter>\n</activity>\n```\n\n\n如果你想要多个actions ，你需要添加对应的activities 。如果你的应用构建了特定的功能不想被其它应用使用，你可以添加android:exported=”false”来确保action 只会出现在你的应用中。\n\n\n注意**你的Activity 的 android:name 将会作为action 显示在文本选择工具栏上**，因此应该确保它是简短的，一个动作动词，并且能辨认出你的应用。例如，Google翻译使用了‘Translate’，这是一个不太常见的action（有多少人安装了多个翻译应用？），Wikipedia 使用的是 ‘Search Wikipedia’ 作为搜索，这可能对于很多应用来说是一个更常见的action 。\n\n###获取选择的文本\n\n一旦你设置了intent filter，其它应用就可以通过选择文本然后从文本选择工具栏选择你的action 打开你的activity 。但这并没有任何价值，除非你用到了所选择的文本。\n\n\n[_EXTRA_PROCESS_TEXT_](http://developer.android.com/reference/android/content/Intent.html#EXTRA_PROCESS_TEXT) 的出现：它是一个包含在Intent 内的  [_CharSequence_](http://developer.android.com/reference/java/lang/CharSequence.html)表示选择的文本。不要被欺骗——尽管你使用了text/plain intent filter，你会得到包含[_Spannable_](http://developer.android.com/reference/android/text/Spannable.html)的完整CharSequence，如果你在你的应用中直接使用CharSequence，你可能会注意到一些样式，因此不必惊讶（你也可以调用[_toString()_](http://developer.android.com/reference/java/lang/CharSequence.html#toString%28%29)来移除所有格式）。\n\n因此你的_onCreate()_方法可能和下面类似：\n\n<pre name=\"5687\" id=\"5687\" class=\"graf--pre graf-after--p\">@Override\nprotected void onCreate(Bundle savedInstanceState) {\n  super.onCreate(savedInstanceState);\n  setContentView(R.layout.process_text_main);\n  CharSequence text = getIntent()\n      .getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);\n  // process the text\n}</pre>\n\n\n如果你使用_android:launchMode=”singleTop”_会有一个警告，你也可以在[_onNewIntent()_](http://developer.android.com/reference/android/app/Activity.html#onNewIntent%28android.content.Intent%29)处理文本——常见的做法是在_onCreate()_ 和 _onNewIntent()_ 都调用 _handleIntent()_ 方法。\n\n\n如果你使用 ACTION_PROCESS_TEXT作为进入你应用的入口，上面就是所有你需要知道的：之后用来做什么取决于你。\n\n###返回一个结果\n\n在ACTION_PROCESS_TEXT Intent还有一个extra ：[_EXTRA_PROCESS_TEXT_READONLY_](http://developer.android.com/reference/android/content/Intent.html#EXTRA_PROCESS_TEXT_READONLY)。这个额外的_boolean_ 值表示你接收到的选择的文本是否可以被用户编辑（例如在一个_EditText_内）。\n\n你可以像下面的代码一样接收这个额外的值\n<pre name=\"a59d\" id=\"a59d\" class=\"graf--pre graf-after--p\">boolean readonly = getIntent()\n  .getBooleanExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false);</pre>\n\n\n你可以使用提供**返回修改后的文本给发送的应用**的能力作为一个提示，**替换选择的文本**。当你Activity 使用[_startActivityForResult()_](http://developer.android.com/reference/android/app/Activity.html#startActivityForResult%28android.content.Intent,%20int%29)方式开启才会起作用——你可以在Activity 结束之前调用[_setResult()_](http://developer.android.com/reference/android/app/Activity.html#setResult%28int,%20android.content.Intent%29)返回[一个结果](http://developer.android.com/training/basics/intents/filters.html#ReturnResult)：\n\n```\nIntent intent = new Intent();\nintent.putExtra(Intent.EXTRA_PROCESS_TEXT, replacementText);\nsetResult(RESULT_OK, intent);\n```\n\n\n你可以想象一个‘Replace’按钮调用 _setResult()_  随后调用[_finish()_](http://developer.android.com/reference/android/app/Activity.html#finish%28%29)返回到调用的Activity。\n\n###常见问题\n\n在你写回复之前，这有一些关于ACTION_PROCESS_TEXT的常见问题：\n\n####Q：我能使用ACTION_PROCESS_TEXT开启一个Service 吗？\n\nA: 不能直接开启——系统只会检查包含正确的intent filter的Activities。这不意味着你不能用一个[_Theme.Translucent.NoTitleBar_](https://developer.android.com/reference/android/R.style.html#Theme_Translucent_NoTitleBar)主题或[_Theme.NoDisplay_](https://developer.android.com/reference/android/R.style.html#Theme_NoDisplay)（只要你[立即销毁Activity](https://plus.google.com/105051985738280261832/posts/LjnRzJKWPGW)）主题的Activity 开启Service ，但要确保它们的action 可以接收到一些用户可见的提示——开启一个notification ，[Toast](http://developer.android.com/reference/android/widget/Toast.html)等。\n\n####Q：我能只用某些类型的文本触发它吗？\n\nA: 不能。任何人选择文本的时候你的选项都会出现。当然，用户可以不选择 ‘Translate’ 选项，除非他们想翻译等，当你不能确定你将会接收到什么类型的文本时需要很小心地作出处理。\n\n\n####Q：每个应用都应该实现ACTION_PROCESS_TEXT吗？会不会有点疯狂？\n\nA：是的，确实有点疯狂，不用每个应用都实现ACTION_PROCESS_TEXT。确保你实现的actions 是**通用的**并且对于安装你应用的用户来说是**真正有用的**。\n\n\n###了解更多\n除了上面提到的[Wikipedia](https://play.google.com/store/apps/details?id=org.wikipedia)和 [Google Translate](https://play.google.com/store/apps/details?id=com.google.android.apps.translate)包含了很好真实的例子，你也可以看看安装在Marshmallow 模拟器上的ApiDemos 应用或直接[查看代码](https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/content/ProcessText.java)。\n\n\n###开发优秀的应用\n\n在[Google+](https://plus.google.com/+AndroidDevelopers/posts/T4dgC9FRMNj)加入讨论并且关注[Android Development Patterns Collection](https://plus.google.com/collection/sLR0p)了解更多！\n"
  },
  {
    "path": "issue-37/使用RxJava缓存Rest请求.md",
    "content": "使用RxJava缓存Rest请求\n---\n\n> * 原文链接 : [Subscribe It While It's Hot: Cached Rest Requests With RxJava](http://fedepaol.github.io/blog/2016/01/01/cached-rest-requests-with-rxjava/)\n* 原文作者 : [fedepaol](fedepaol.github.io)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [rednels](https://github.com/rednels) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  已完成\n\n### 免责声明：\n\n在这篇文章中，我尝试去用正确的方法来解决一个常见的问题。我仍然正在整理我脑袋中关于RxJava的资料，所以我在这里写下的也许不是最好的解决问题的方式。\n\n# 使用Rxjava缓存请求\n\n最近，我尝试使用RxJava开发了一款闲时备份app。我必须承认，一旦你get到了正确的方式，RxJava几乎感觉就像作弊。一切看起来更简洁，多个请求能够被组合，且非常容易控制。通过在UI线程观察和在其他线程订阅的方式，能够通过严格模式的检测，而且，你能了解到所有最酷的好东西就是在Android上使用RxJava。我不能够很容易发现的是，如何储存我的请求的结果，确保即使没有网络连接时，能够为用户呈现缓存的内容，同时还是使用Reactive的方式处理一切事情。\n\n## 缓存vs未缓存\n\n直接从Rest获取结果显示在UI上在很多情况下是合适的，比如当要显示一个参数不可预测的搜索结果的时候（想想Ebay，或者亚马逊，用户每次查找的东西都是不一样的）。\n\n可是有一些情况，显示之前获取到的结果可以显著地提高用户体验（相比于显示加载进度条或者空白页面）。这种情况包括你的Twitter订阅，一个刚刚在5分钟之前获取过数据的本地天气预报，或者一个指定用户的github仓库列表。\n\n这里你可以看到，一个相同的activity使用缓存的版本和不使用缓存的版本之间的区别：\n\n![unchache](http://fedepaol.github.io/images/uncached.gif) ![chached](http://fedepaol.github.io/images/cached.gif)\n\n出于这个原因，我试图找出一个简洁地方式来缓存请求的结果，同时保持使用Reactive方式的流程。\n\n## 存储器是真理的唯一来源\n\n### 全部都是reactive\n\n如果我们想要缓存数据同时保持在相同的subscription中一切不变，事情变得有点凌乱。请求的结果抛给UI线程，并且响应结果也被储存在存储器（storage）中。UI也订阅了从存储器（storage）获取数据，它会检查哪个结果先返回，返回的数据是否过时。\n\n![messy](http://fedepaol.github.io/images/messy.jpg)\n\n### 缓存\n\n在这个混合使用的情况中，UI仅订阅存储器（storage）的数据，并且使用一个外观类类封装了存储器和向存储器中填充数据的retrofit客户端的subscription。一旦存储器中被填充了新数据，UI线程将会自动地收到所有改动的通知。\n\n![clean](http://fedepaol.github.io/images/clean.jpg)\n\n在这种情况下，observable作为一个hot observable，在它被订阅的第一时间，它发出存储器中的内容，和其他任何它可能会发生的改变。\n\n## 口说无凭，让我们来看下代码\n\n下面这些代码的一个可以运行的示例可以在[我的github仓库](https://github.com/fedepaol/RxRestSample)找到。为了写这个例子，我从看起来驱动了99% rest相关示例程序的被滥用的Github api开始。先对Github说声抱歉。\n\n首先得有一个存储器。 我封装了一个 SQLite帮助类(这是我用[手头的脚本](https://github.com/fedepaol/Android-sql-lite-helper)生成的)，它包含了一个[PublishSubject](http://reactivex.io/RxJava/javadoc/rx/subjects/PublishSubject.html)。当插入（insert）方法被调用时，PublicSubject能够收到订阅，并且我们会收到通知。\n\n```java\npublic class ObservableRepoDb {\n    private PublishSubject<List<Repo>> mSubject = PublishSubject.create();\n    private RepoDbHelper mDbHelper;\n\n    private List<Repo> getAllReposFromDb() {\n        List<Repo> repos = new ArrayList<>();\n        // .. performs the query and fills the result\n        return repos;\n    }\n\n    public Observable<List<Repo>> getObservable() {\n        Observable<List<Repo>> firstTimeObservable =\n                Observable.fromCallable(this::getAllReposFromDb);\n\n        return firstTimeObservable.concatWith(mSubject);\n    }\n\n    public void insertRepo(Repo r) {\n        // ...\n        // performs the insertion on the SQLite helper\n        // ...\n        List<Repo> result = getAllReposFromDb();\n        mSubject.onNext(result);\n    }\n}\n\n```\n\n我们现在已经得到拼图的第一块：一个能够被订阅的存储器(storage)。使用concat操作是因为我们想在它一被订阅就将存储的内容发出去。\n\n接下来是外观类，在这里我们能够得到我们订阅的数据，且我们能够开始一个新的更新操作。\n\n```java\npublic class ObservableGithubRepos {\n    ObservableRepoDb mDatabase;\n    private BehaviorSubject<String> mRestSubject;\n\n    // ...\n    public Observable<List<Repo>> getDbObservable() {\n        return mDatabase.getObservable();\n    }\n\n    public void updateRepo(String userName) {\n        Observable<List<Repo>> observable = mClient.getRepos(userName);\n        observable.subscribeOn(Schedulers.io())\n                  .observeOn(Schedulers.io())\n                  .subscribe(l -> mDatabase.insertRepoList(l));\n    }\n}\n```\n\n需要注意的是一切都是从UI线程发生的。这是因为我们打算将订阅到数据库的observable作为唯一的数据源。\n\n现在，假设observable现在是hot，我们不能为了停止我们可能放在那里的任意进度指示器而监听听其的onComplete方法。我们需要的是另一个subject，让我们必定能够更新请求，所以下面是新的外观类：\n\n```java\npublic class ObservableGithubRepos {\n    // ...\n\n    public Observable<List<Repo>> getDbObservable() {\n        return mDatabase.getObservable();\n    }\n\n    public Observable<String> updateRepo(String userName) {\n        BehaviorSubject<String> requestSubject = BehaviorSubject.create();\n\n        Observable<List<Repo>> observable = mClient.getRepos(userName);\n        observable.subscribeOn(Schedulers.io())\n                  .observeOn(Schedulers.io())\n                  .subscribe(l -> {\n                                    mDatabase.insertRepoList(l);\n                                    requestSubject.onNext(userName);},\n                             e -> requestSubject.onError(e),\n                             () -> requestSubject.onCompleted());\n        return requestSubject;\n    }\n}\n```\n\n在UI端（activity或者fragment）我们必须订阅存储器来获取数据，同时也得订阅请求的observable以停止进度指示器。每次一个更新被请求的时候，发出挂起请求的状态的一个observable就会被返回。\n\n```java\nmObservable = mRepo.getDbObservable();\nmProgressObservable = mRepo.getProgressObservable()\n\nmObservable.subscribeOn(Schedulers.io())\n               .observeOn(AndroidSchedulers.mainThread()).subscribe(l -> {\n                mAdapter.updateData(l);\n            });\n\nObservable<List<Repo>> progressObservable = mRepo.updateRepo(\"fedepaol\");\nprogressObservable.subscribeOn(Schedulers.io())\n                       .observeOn(AndroidSchedulers.mainThread())\n                       .subscribe(s -> {},\n                                  e -> { Log.d(\"RX\", \"There has been an error\");\n                                        mSwipeLayout.setRefreshing(false);\n                                  },\n                                  () -> mSwipeLayout.setRefreshing(false));\n```\n\n请记住`DbObservable`是一个hot的，所以每次调用`updateRepo`的时候，数据库将会被查询结果填充，并且UI接下来将收到通知。\n\n## SqlBrite\n\n如果你觉得所有这些封装看起来是非常费力的，来自Square的多产的伙计写了一个SqlBrite，它是一个为了和这个相同的目的而编写的超级通用的数据库封装。我保证它更好用，并且比我们自己写的个人版本更经得起考验。\n\n## 结论\n\n我不知道加入这是否是一个使用RxJava的良好的方式。也许我结束这个场景只是因为我对于RxJava没有100%的信心，而且我在中间加入了一些非Rx的东西以便更好地控制它。由于我们能够修改从http客户端填充存储器的流程，或者从存储器本身发出的流程。\n\n在任何情况下，拥有一个真理之源将会看起来更加清晰，并且我觉得使用这种方式来处理像预下载、计划更新以便给用户呈现最新的数据将更加容易。\n\n感谢Fabio Collini在这篇文章发表的第一时间指出了许多错误，并感谢Riccardo Ciovati帮我校对文章。\n"
  },
  {
    "path": "issue-37/在Android中使用并发来提高速度和性能.md",
    "content": "在Android中使用并发来提高速度和性能\n---\n\n> * 原文链接 : [Using concurrency to improve speed and performance in Android](https://medium.com/@ali.muzaffar/using-concurrency-and-speed-and-performance-on-android-d00ab4c5c8e3#.t1ynou5e3)\n* 原文作者 : [Ali Muzaffar](https://medium.com/@ali.muzaffar)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuweiguocn](https://github.com/yuweiguocn) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n\n#在Android中使用并发来提高速度和性能#\n\n\nAndroid框架提供了很实用的异步处理类。然而它们中的大多数在一个单一的后台线程中排队。当你需要多个线程时你是怎么做的？\n\n\n众所周知，UI更新发生在UI线程（也称为主线程）。在主线程中的任何操作都会阻塞UI更新，因此当需要大量计算时可以使用AsyncTask, IntentService 和 Threads。事实上，在不久前我写了[在android中异步处理的8种方式](https://medium.com/android-news/8-ways-to-do-asynchronous-processing-in-android-and-counting-f634dc6fae4e#.bkk6mudb4)。然而，Android中的[AsyncTasks](http://developer.android.com/reference/android/os/AsyncTask.html)运行在一个单一后台线程并且[IntentService](http://developer.android.com/reference/android/os/AsyncTask.html)也同样如此。因此，开发者应该怎么做？\n\n\n**更新：**[Marco Kotz](https://medium.com/u/b49242be2be7) [指出](https://medium.com/@mrcktz/hi-ali-nice-article-thanks-for-sharing-ba72b07f1fb3)，你可以通过ThreadPool Executor和AsyncTask使用多个后台线程。\n\n##大多数开发者所做的\n\n在大多数情况下，你不需要多个线程，简单地分离AsyncTasks 或者在IntentService 中排队操作就足够用了。然而，当你真正需要多个线程时，通常，我看到的开发者只是简单地平分旧的线程。\n\n\n```\nString[] urls = …\nfor (final String url : urls) {\n    new Thread(new Runnable() {\n        public void run() {\n            //Make API call or, download data or download image\n        }\n    }).start();\n}\n```\n\n使用这种方法有几个问题。第一个是操作系统限制了相同的域名的连接数量为4（我相信）。意思是这段代码不会按照你想的那样去执行。它创建的线程在开始执行操作之前不得不等待另一个线程执行完毕。还有就是每个线程被创建，用来执行一个任务，然后被销毁。这个没有被重用。\n\n\n##这为什么是一个问题？\n\n让我们说一个例子，你想开发一个急速连拍应用，从Camera 预览每秒捕获10张照片或更多。应用的功能如下：\n \n - 用byte[]存储10张照片，且不能阻塞UI。\n - 转换每个byte[]的格式从YUV 到RGB 。\n - 使用转换后的数组创建一个Bitmap 。\n - 修复Bitmap 的方向。\n - 生成一个缩略图大小的Bitmap 。\n - 把完整大小的Bitmap压缩成Jpeg写到磁盘上。\n - 排队把完整图片上传到服务器。\n\n可以理解的是，如果你在主UI线程做所有的操作，你的应用性能将会很低下。唯一的方法是当UI空闲时缓存camera预览数据并处理它。\n\n另一种可能是创建一个一直运行的HandlerThread，可以用来在后台线程接收camera预览并做这些所有的处理。虽然这样会更好，但在随后的急速连拍之间将会有太多的延迟，因为所有的操作都需要处理。\n\n\n```\npublic class CameraHandlerThread extends HandlerThread\n        implements Camera.PictureCallback, Camera.PreviewCallback {\n    private static String TAG = \"CameraHandlerThread\";\n   private static final int WHAT_PROCESS_IMAGE = 0;\n\n    Handler mHandler = null;\n    WeakReference<CameraPreviewFragment> ref = null;\n\n    private PictureUploadHandlerThread mPictureUploadThread;\n    private boolean mBurst = false;\n    private int mCounter = 1;\n\n    CameraHandlerThread(CameraPreviewFragment cameraPreview) {\n        super(TAG);\n        start();\n        mHandler = new Handler(getLooper(), new Handler.Callback() {\n\n            @Override\n            public boolean handleMessage(Message msg) {\n                if (msg.what == WHAT_PROCESS_IMAGE) {\n                    //Do everything\n                }\n                return true;\n            }\n        });\n        ref = new WeakReference<>(cameraPreview);\n    }\n\n   ...\n\n    @Override\n    public void onPreviewFrame(byte[] data, Camera camera) {\n        if (mBurst) {\n            CameraPreviewFragment f = ref.get();\n            if (f != null) {\n                mHandler.obtainMessage(WHAT_PROCESS_IMAGE, data)\n               .sendToTarget();\n                try {\n                    sleep(100);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n                if (f.isAdded()) {\n                    f.readyForPicture();\n                }\n            }\n            if (mCounter++ == 10) {\n                mBurst = false;\n                mCounter = 1;\n            }\n        }\n    }\n}\n```\n\n\n**注意：**如果你想了解更多HandlerThreads知识和怎样使用它，可以阅读我发表的[关于HandlerThreads文章](https://medium.com/@ali.muzaffar/handlerthreads-and-why-you-should-be-using-them-in-your-android-apps-dc8bf1540341#.co4ilm67m)。\n\n\n因为一切都是在一个后台线程完成的，我们的主要性能优势是我们的线程是长时间运行并且没有被销毁和重新创建。然而，许多耗时的操作只能通过线性方式在共享的线程中执行。\n\n我们可以创建第二个HandlerThread 处理图片和第三个将它们写到磁盘和第四个上传到服务器。我们可以快速捕获图片，然而，这些线程仍然将以线性的方式依赖其它的线程。这不是真正的并发。我们可以快速捕获图片，然而，因为处理每个图片需要时间，当用户点击按钮和缩略图被显示之间用户仍能感受到很大的滞后。\n\n##使用线程池提高性能\n\n虽然我们可以根据需要创建很多线程，但创建线程和销毁它是一个时间成本。我们也不想创建不需要的线程并且想要充分利用可用的硬件。太多的线程会通过消耗CPU周期影响性能。解决方案是使用一个线程池（ThreadPool）。\n\n在应用中创建一个直接使用的线程池，首先为你的线程池创建一个单例。\n\n```\npublic class BitmapThreadPool {\n    private static BitmapThreadPool mInstance;\n    private ThreadPoolExecutor mThreadPoolExec;\n    private static int MAX_POOL_SIZE;\n    private static final int KEEP_ALIVE = 10;\n    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();\n\n    public static synchronized void post(Runnable runnable) {\n        if (mInstance == null) {\n            mInstance = new BitmapThreadPool();\n        }\n        mInstance.mThreadPoolExec.execute(runnable);\n    }\n\n    private BitmapThreadPool() {\n        int coreNum = Runtime.getRuntime().availableProcessors();\n        MAX_POOL_SIZE = coreNum * 2;\n        mThreadPoolExec = new ThreadPoolExecutor(\n                coreNum,\n                MAX_POOL_SIZE,\n                KEEP_ALIVE,\n                TimeUnit.SECONDS,\n                workQueue);\n    }\n\n    public static void finish() {\n        mInstance.mThreadPoolExec.shutdown();\n    }\n}\n```\n\n然后在上面的代码简单地修改Handler 的回调：\n\n```\nmHandler = new Handler(getLooper(), new Handler.Callback() {\n\n    @Override\n    public boolean handleMessage(Message msg) {\n        if (msg.what == WHAT_PROCESS_IMAGE) {\n            BitmapThreadPool.post(new Runnable() {\n                @Override\n                public void run() {\n                    //do everything\n                }\n            });\n        }\n        return true;\n    }\n});\n```\n\n这样就OK了。性能明显提升，可以看看下面的视频！\n\n这里的优势在于我们可以定义池大小，甚至在回收之前指定线程保持多长时间。我们也可以针对不同的操作创建不同的线程池或只使用一个线程池。需要小心的是当你的线程执行完成后做适当的清理。\n\n我们甚至可以针对不同的操作创建不同的线程池，一个线程池转换数据到Bitmaps，一个线程池写数据到磁盘，第三个线程池上传Bitmaps 到服务器。在这一过程中，如果我们的线程池最大可以有4个线程，我们可以在同一时间转换，写和上传4张图片而不是1张。用户可以在同一时间看到4张图片而不是一张。\n\n\n上面是一个简化的例子，可以从[GitHub上查看完整的代码](https://github.com/alphamu/ThreadPoolWithCameraPreview)然后给我一些反馈。\n\n\n你也可以从[Google Play下载demo应用](https://play.google.com/store/apps/details?id=au.com.alphamu.camerapreviewcaptureimage)。\n\n\n**实现线程池前：**如果可以，当缩略图显示在底部时盯着屏幕顶部的定时器。因为我已经把除了 adapter的notifyDataSetChanged()之外的所有操作放到了主线程之外，计数器应该会运行得很流畅。\n\n![threadpool_before](https://cloud.githubusercontent.com/assets/4308480/12195036/71565b06-b62f-11e5-8091-d229e02470dc.gif)\n\n\n**实现线程池后：**屏幕顶部的定时器依然运行得很流畅，然而，图片缩略图的显示快了很多。\n\n\n![threadpool_after](https://cloud.githubusercontent.com/assets/4308480/12195086/b0188dc8-b62f-11e5-97d9-96a506f1a4a2.gif)\n\n##最后\n\n为了开发出优秀的Android应用，[阅读更多我的文章](https://medium.com/@ali.muzaffar)。\n\n\n耶！在最后你做到了!我们应该出去嗨！关注我，[LinkedIn](https://www.linkedin.com/in/alimuzaffar)，[Google+](https://plus.google.com/+AliMuzaffar) 或 [Twitter](https://twitter.com/ali_muzaffar)。"
  },
  {
    "path": "issue-37/如何理解RxJava中的Subjects(第一部分).md",
    "content": "如何理解RxJava中的Subjects(第一部分)\n----\n\n> * 原文链接 : [How to think about Subjects in RxJava (Part 1)](https://tech.instacart.com/how-to-think-about-subjects-part-1/)\n* 原文作者 : [Kaushik Gopal](https://tech.instacart.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [rednels](https://github.com/rednels) \n* 校对者: [desmond1121](https://github.com/desmond1121)   \n* 状态 :  完成 \n\n当我首次开始使用`RxJava`，并且听说`Subjects`的时候，我把它们当作神秘的饰品。在正确使用时，它们似乎有魔法一样，能做到不可能的事情。在使用不正确时，他们会把我的代码变成一堆热腾腾的狗屎。一个准备尝试Rxjava的朋友回应了相似的情绪：“Subjects对我来说就像一个（丰富多彩的形容词）黑盒子。我不确定我该什么时候使用它们。我冲进了RxJava的一个角落，在网页上搜寻，从StackOverflow上复制粘贴一些使用Subjects的代码，使我的代码能够工作，并且希望不要再一次回过头来看这些代码。”\n\n我们知道`Observables`和`Subscribers`是主要的组件。再加上一个（[或两个](https://github.com/ReactiveX/RxJava/wiki/Alphabetical-List-of-Observable-Operators)）操作符你就可以开始你愉快的Rx之旅了。但是加入`Subjects`的组合在他们之间打开了一个全新的交流渠道。你只需要开始思考他们的不同。\n\n这篇文章的目的就是帮助你打开思路。    \n让我们[官方的wiki定义](https://github.com/ReactiveX/RxJava/wiki/Subject)开始：    \n\n> 一个Subject是一个有序的桥或者代理，它同时可以作为一个Subscriber和Observable。因为他是一个Subscriber，它能够订阅一个或多个Observable，同时它也是一个Observable，它能够将之前作为Subscriber时接受到的对象重新发出，也可以发出新的对象。\n\n一个`Subscriber`和一个`Observable`？\n\n这是什么鬼！？你一端有一个生产者（Observable）向下发送事件流。在另一端，你有一个消费者（Subscriber）消费了这些事件。什么情况下你可能会需要某些东西同时做两种操作？\n\n> 想一下管道连接器\n\n举个特殊的例子吧。Android世界的开发有一个共同的需求：“恢复或者继续完成一个长时间操作的工作，比如在屏幕角度或配置变更后的网络调用”。我建议使用android 的api,[再加上一些RxJava](https://github.com/kaushikgopal/RxJava-Android-Samples#rotation-persist)来[保存fragment](http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html)的解决方案。\n\n你有一个基于UI的fragment（A）作为主fragment。当你想要启动你的网络调用是，你新建一个工作者fragment（B）来进行网络调用。与此同时，如果A发生了旋转、重建、销毁等，它不会有问题。当A最后与B建立连接或同步后，它只会受到来自B的后续事件，无需重新启动整个过程。\n\n让我们用下面的代码来模拟一下这个例子。假设我有一个长时间运行的Observable：\n\n```java\nObservable.interval(1, TimeUnit.SECONDS)\n    .map(new Func1<Long, Integer>() {\n        @Override\n        public Integer call(Long aLong) {\n            return aLong.intValue();\n        }\n    })\n    .take(20);\n```\n\n在真实的环境中，这个Observable将会是你自己的忘了调用或其他耗时操作。它在工作者fragment（B）的`onCreate`方法中会被立即执行，如下所示：\n\n```java\n/**\n* This method will only be called once when the retained Fragment is first created.\n*/\n@Override\npublic void onCreate(Bundle savedInstanceState) {\n\n  Observable.interval(1, TimeUnit.SECONDS)\n     .map(new Func1<Long, Integer>() {\n        @Override\n        public Integer call(Long aLong) {\n            return aLong.intValue();\n        }\n     })\n    .take(20)\n    .subscribe();    // Observable kicked off\n}\n```\n\n根据android的工作方式，只有在B中后面一点（比如onResume调用时）你能够真正地同任何关联的fragment，比如基于UI的fragment（A），进行通信。\n\n```java\n@Override\npublic void onResume() {\n    super.onResume();\n\n    // channel open for communication with master fragment\n    // send results (A)\n}\n```\n\n那么我们`onCreate`中启动调用，我们怎么把结果下发给`onResume`？假入我们有这个魔法饰品，它充当一个Subscriber在`onCreate`中消费所有的这些事件，但是又作为一个Observable，在`onResume`中下发事件到任意一个正在监听的对象？\n\n使用Subjects，我们可以像下面这样做：\n\n```java\n// consume events\n(...).subscribe(mSubject);\n\n// produce events\nmSubject.subscribe(...);\n```\n\n稍微完整一些：\n\n```java\nprivate Subject<Integer, Integer> mSubject = PublishSubject.create();\n\n@Override\npublic void onCreate(Bundle savedInstanceState) {\n\n    Observable.interval(1, TimeUnit.SECONDS)\n        .map(new Func1<Long, Integer>() {\n            @Override\n            public Integer call(Long aLong) {\n                return aLong.intValue();\n            }\n        })\n        .take(20) \n        .subscribe(mSubject);\n}\n\n@Override\npublic void onResume() {           \n    super.onResume();\n\n    // channel open for communication with master fragment\n    // send results to master fragment\n    mfragA.sendResultsBack(mSubject.asObservable);\n}\n```\n\n至此你有了它，一个针对你的所有Rx流的管道连接器。一个完整的可以运行的示例可以在[这里](https://github.com/kaushikgopal/RxJava-Android-Samples/tree/master/app/src/main/java/com/morihacky/android/rxjava/fragments)找到。\n\n## 一些建议：\n\n* 响应式处理你的subscription！\n\n在上面的片段里，为了简介和清晰，我省略了处理订阅的部分。可以在[演示代码](https://github.com/kaushikgopal/RxJava-Android-Samples/tree/master/app/src/main/java/com/morihacky/android/rxjava/fragments)中看到这部分应该如何做。\n\n* 呃...，为什么不只是在`onResume`中创建一个Observable？\n\n首先，我们希望尽可能地执行该调用。`onCreate`是我们能这么做的最早的地方。在另一方面，`onResume`是A和B真正彼此通信的地方。\n\n其次，更重要的是，如果我们在onResume中创建observable，每次A连接回B时，你将会简单地重启队列…这样达不到目的。\n\n* 因为Subjects默认是“hot”所以它才会生效：\n\n大多数observables是“cold”的。对于hot/cold observables的一个有意思的解释，可以参考[这个理论家的视频](https://egghead.io/lessons/rxjs-demystifying-cold-and-hot-observables-in-rxjs)。`mObservable`这里（尽管使用了一个interval操作符）是“cold”，所以每次你订阅流的时候，它就会重启序列。不像code observables，Subjects默认是“hot”的。\n\n* 使用哪一个Subject？\n\n有许[多种Subjects](http://reactivex.io/documentation/subject.html)。对于这个特殊的需求，`PublicSubject`会比较好用，因为我们希望从它离开的地方继续执行序列。所以，假设我们在B中发出了事件1，2，3，在A重新连接回来时我们只希望看到4，5，6.如果我们使用了一个`ReplaySubject`，我们将会看到[1,2,3],4,5,6;又或者我们使用了一个`BehaviorSubject`我们将会看到3，4，5，6等等。\n\n* 使用像.replay,.share这种操作符或者ConnectableObservable怎么样？\n\n一个[以前的解决方案](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1WorkerFragment.java)使用了一个非常相似的技术。然而，这也是我们进入“多播”领域的地方。多播并不简单:你不得不考虑线程安全或者去掌握像`.refCount`等的概念。Subjects相比之下要简单地多。在[这个优秀的系列文章](http://akarnokd.blogspot.com/2015/06/subjects-part-1.html)中，由于Karnok惊人地指出，当使用Subjects的时候，你 能够处理复杂的条件，比如，使用像`.onBackPressurexxx`这种简单的操作符就可以很方便地处理后退键按下事件。\n\n另一些聪明的绝地武士一样的Rxer也提到过使用多播技术是危险的，并且会使代码变质。几乎总是有一个现成的结构或操作符对你来说更容易完成任务。\n\n请继续关注本系列的更多内容。\n"
  },
  {
    "path": "issue-37/更加强大的Dagger2.md",
    "content": "更加强大的Dagger2\n---\n\n> * 原文链接 : [Dagger 2: Even sharper, less square](https://blog.gouline.net/2015/05/04/dagger-2-even-sharper-less-square/)\n* 原文作者 : [Mike Gouline](https://github.com/mgouline)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [lowwor](https://github.com/lowwor) \n* 校对者: [maoruibin](https://github.com/maoruibin)  \n* 状态 :  完成 \n\n著名的依赖注入库Dagger的2.0版本变为生产版本只是一个时间问题，这似乎给了我一个写一篇关于它的文章的好理由。\n\n## Dagger 1\n\n在这里，先做一个小小的历史回顾，Dagger是由[Square][1]公司的几个十分优秀的开发人员在2012年创建的，他们觉得将依赖注入应用到Java中是一个非常棒的点子，但是却觉得当时使用的[Guice][2]库（当时的标准）的速度稍微有点慢。所以，他们开发了一个依赖于基于注解的代码生成（JSR-330），拥有着和Guice相似的API，但是性能更强、更加灵活的库。\n\nDagger的工作原理就是通过声明一些module，这些module里包括了所有你想要注入的依赖的提供者方法（provider method），\n把这些module加载到一个对象图谱（object graph）里，最终将图谱中的内容根据需要注入到对象（target）中。\n足够简单的结构（当然实现起来还是不那么简单的）有利于开发者解耦他们的代码,并且通过把初始化代码移动到由库自动生成的injector中,从而删掉每个类开始的地方那些丑陋的工厂类初始化代码。\n\n然而，伴随着明显的优点（这些优点足以让大部分的Android开发者选择Dagger作为他们开发新项目中选择使用的第一个库），也存在着一些明显的问题，都是一些不可以简单地被一个或者两个的pull request就解决掉的问题，因为这些问题都是与整个底层架构相关的。\n\n- 图谱（Graph）是在运行时构建的    -影响性能，尤其是在频繁请求的用例中（后台服务器场景），\n\n- 使用了反射（比如说  用```Class.forName()```来获取已经生成的类型） - 使得生成的代码难以跟踪，同时ProGuard配置文件的编写成了噩梦\n\n- 生成的代码不美观 -特别是与那些人手写的工厂类初始化方法相比较时，这一点尤其明显。\n\n因而，在Github的问题跟踪系统中，由于上述问题导致的一些issues，都被标记为“它就是这样的，这个问题会在Dagger2中解决”\n\n## Dagger 2\n\n时间快进到现在，Google的core libraries团队（[Guava][4]的创造者）与原来的Square的创建者一起推出了新一代的Dagger，并在一个月内就发布了生产版本\n\n新的release版本，像之前所承诺的一样，解决了老版本的许多问题：\n\n- 不再使用反射 所有东西都是通过明确的方法调用来完成的（不需要配置ProGuard文件就可以正常混淆）\n\n- 不再在运行时构建图表（graph） - 提高了性能，包括在单请求（per-request）用例中（据[Gregory Kick][5]说，在谷歌的搜索产品中，它的表现比以前快了13%）\n\n- 可追溯的 - 生成的代码更优雅，同时没有使用到反射，使得代码可读性提高，并且容易跟踪\n\n\n\n## Usage 用法\n虽然Dagger也可以用在任何的Java工程中，但是在示例代码中，我们将专注于它在Android平台上的应用。不过，这些代码都可以很容易的就被改写，以用于其它地方，比如说，改写为服务器后端相关的代码。\n\n\n## Module\nDagger2中基本保持不变的部分是module。你仍然需要在这里定义可被注入的依赖的提供者方法（provider methods）。比如说，我们需要注入的是```SharedPreferences```对象。\n\n```\n@Module\npublic class ApplicationModule {  \nprivate Application mApp;\n\npublic ApplicationModule(Application app) {\nmApp = app;\n}\n\n@Provides \n@Singleton\nSharedPreferences provideSharedPrefs() {\nreturn PreferenceManager.getDefaultSharedPreferences(mApp);\n}\n}\n```\n类似的，你可以提供（provide）其他的东西（并不是所有的，只是提供一些想法）\n\n- Context\n- System services (e.g. LocationManager)\n- REST service (e.g. Retrofit)\n- Database manager (e.g. Realm)\n- Message passing (e.g. EventBus)\n- Analytics tracker (e.g. Google Analytics)\n\n## Component\n眼尖的读者可能发现了在```@Module```注解中，```injects = {}```参数不见了，那是因为Dagger2不再需要用到这个参数了。取而代之的是，\"component\"的概念，component是用来连接module和要注入依赖的对象（target）之间的桥梁。\n```\n@Singleton\n@Component(modules = {ApplicationModule.class})\npublic interface ApplicationComponent {  \nvoid inject(DemoApplication app);\nvoid inject(MainActivity activity);\n}\n```\n\n所以，你仍然需要注明（define）你要注入依赖的对象（target），但是，与之前不同的是，如果你现在不注明的话，在编译时就会提示\"cannot find method\"错误，而这个错误在以前，则是一个隐秘的运行时错误，只有在运行时才会报错。\n\n\n## Application 应用\n下一步，我们要了解的是你的component的容器。具体的方式由你的应用来决定，你可以选择用更复杂的方式保存你的components，但是对于这个例子来说，一个简单的保存在application中的component就足以说明情况了。\n```\npublic class DemoApplication extends Application {  \nprivate ApplicationComponent mComponent;\n\n@Override \npublic void onCreate() {\nsuper.onCreate();\nmComponent = DaggerApplicationComponent.builder()\n.applicationModule(new ApplicationModule(this))\n.build();\n}\n\npublic ApplicationComponent getComponent() { \nreturn mComponent;\n}\n}\n```\n\n这里没有什么新的东西：只是之前我们创建并初始化的的是对象图谱（```ObjectGraph```），而现在，我们则会创建并初始化对应的component。\n\n注意：你很有可能会在某个时候想到这个问题，我在这先提前解释一下。上面例子中的```DaggerApplicationComponent```是一个编译时生成的类（命名方式是 ```Dagger%COMPONENT_NAME%```），只要你对你的component做了改动，就需要重新编译工程，通过了，这个类才会被生成。所以，如果你找不到这个类，点击你的IDE的\"Rebuild Project\"，如果编译不出错的话，它就会出现了。类似的，```applicationModule()```方法对应于你的module的名字。\n\n\n## Injection 注入\n终于，经过前面几个部分为注入进行的准备后，我们来到了用到它们的地方了，我们要在activity中注入一个SharedPreferences对象。\n```\npublic class MainActivity extends Activity {  \n@Inject SharedPreferences mSharedPrefs;\n\n@Override \npublic void onCreate(Bundle savedInstanceState) {\nsuper.onCreate(savedInstanceState);\n((DemoApplication) getApplication()).getComponent().inject(this);\n\nmSharedPrefs.edit().putString(“status”, “success!”).apply();\n}\n}\n```\n和期待的一样，```@Inject```注解的使用没有任何改变，只是注入本身发生了一点变化。\n\n这里有一点小小的不便，是由于```inject()```方法是强类型关联的，这个我们在后面会讨论到。\n\n\n**Named injections 命名注入**\n如果你有几个对象是同一类型的，比如说两个不同的```SharedPreferences```实例（可能是指向了不同的文件），这时候，我们可以怎么做？\n这时候，我们就需要用到命名注入了。只要在module的提供者（provider）里像下面一样使用```@Named```注解\n\n```\n@Provides \n@Named(“default”) \nSharedPreferences provideDefaultSharedPrefs() { … }\n\n@Provides \n@Named(“secret”)\nSharedPreferences provideSecretSharedPrefs() { … }  \n```\n在要注入的对象中，注入的方法是一样的\n```\n@Inject @Named(“default”) SharedPreferences mDefaultSharedPrefs;\n@Inject @Named(“secret”) SharedPreferences mSecretSharedPrefs;\n```\n很简单，没有什么好解释的，但是这是一个非常有用的用法，值得一提。\n\n## Lazy injections 延迟注入\n说到性能，如果在一个要注入依赖的对象中，有很多不同的依赖需要被注入，有些依赖只会在某些特定的场景才会被用到（比如说 用户输入的时候）。在这种情况下，如果我们没有用到这个依赖，也要去注入的话，会导致资源的浪费，所以，我们可以选择用延迟注入（lazy injection）替代。\n```\n@Inject Lazy<SharedPreferences> mLazySharedPrefs;\n\nvoid onSaveBtnClicked() {  \nmLazySharedPrefs.get()\n.edit().putString(“status”, “lazy…”)\n.apply();\n}\n```\n这里的意思是说，```mLazySharedPrefs```只有在第一次调用```get()```方法的时候才会被注入。之后所有的```get()```调用都会是同一个mLazySharedPrefs实例。\n\n## Provider injections 注入提供者\n最后一个诀窍，如果你是在一个全新的你可以完全掌控住的工程中，你想要实现这样的注入的话，你可能会考虑用其它方法来替代，比如说[工厂方法][6]。\n但是，对于历史遗留代码来说，这个诀窍将会很有用。\n\n想象这样一个场景，你需要创建一个对象的多个实例，而不是仅仅注入单个实例。在这时候，你需要注入一个提供者（```Provider<T>```）\n```\n@Inject Provider<Entry> mEntryProvider;\n\nEntry entry1 = mEntryProvider.get();  \nEntry entry2 = mEntryProvider.get();  \n```\n在这个例子中，你的提供者（provider）会创建两个```Entry```对象的实例。有一点要注意的是，module中的实例如何创建完全是由你决定的\n\n## Annoyances 烦人的地方\n还好，我真的不能把这个部分叫做问题，因为都是一些不足以称为问题的东西。\n\n**烦人的地方#1：** 像是之前提到的，```inject()```方法与要注入的target，是强类型关联的。这对于debug来说很好，但是这对于我们希望在基类中实现注入的普遍做法变得复杂（如在 base activity，fragment等中）。\n\n直觉上，你觉得应该在基类中创建一个```inject()```方法，但是，这样做的话，它就仅仅会注入那些在基类中声明的依赖，在他的子类中声明的依赖并不会被注入。\n\n解决方法1：这也是我所使用的解决方案，就是在基类中声明一个抽象的注入方法来进行实际上的注入\n```\n@Override\npublic void onCreate(Bundle savedInstanceState) {  \nsuper.onCreate(savedInstanceState);\ninjectComponent(((DemoApplication) getApplication()).getComponent());\n}\n\nprotected void injectComponent(ApplicationComponent component);  \n```\n然后在你的子类中实现这个方法\n```\n\n@Override\nprotected void injectComponent(ApplicationComponent component) {  \ncomponent.inject(this);\n}\n```\n与Dagger1相比，需要多写一个额外的方法，来实现同样的事情。但是为了可以在编译时查错，这个代价是完全值得的。\n\n解决方法2：另一个选择就是利用反射。如果你看到反射就不想看下去的话，并不怪你：因为这也是Dagger2所提到的点，不利用反射。但是，如果你坚持要在基类中实现注入，而不想在子类中增加额外方法的话，请继续往下看。\n\n这个解决方法简而言之就是要找到你要注入的类型所对应的```inject()```方法。\n\n首先，你要将在component类中声明的所有方法对应上方法的参数类型来保存在缓存中（因为```inject()```方法只能有一个参数，所以只需保存第一个有一个参数的方法就行）\n```\n// Keep the returned cache in some helper.\nMap<Class, Method> buildCache() {  \nMap<Class, Method> cache = new HashMap<>();\nfor (Method m : ApplicationComponent.class.getDeclaredMethods()) {\nClass[] types = m.getParameterTypes();\nif (types.length == 1) {\ncache.put(types[0], m);\n}\n}\nreturn cache;\n}\n```\n最后，你只要在需要注入依赖的对象（target）中调用```getClass()```，找到缓存中对应的方法，然后使用```invoke()```来调用这个方法就行了。\n```\n// Use for injecting targets of any type.\nvoid inject(ApplicationComponent component, Object target) {  \nMethod m = cache.get(target.getClass());\nif (m != null) {\nm.invoke(component, target);\n}\n}\n```\n在这里，我想重申一下,这个解决方法只是为了省几行代码，却对整个流程进行了过度的设计，我个人来说是不会这样做的，但是对于那些想把注入的过程隐藏在基类中，或者是在旧的代码中已经是这样做的人来说，或许会觉得这个方法是有用的。\n\n\n### Annoyance #2:烦人的地方2：\nComponent的实现类（比如说：```DaggerApplicationComponent```）只有在重新编译工程之后才能出现，同时如果在编译时出现任何与注入相关的错误的话，都会提示找不到这个类（换句话说，这个类没有被生成）。\n\n我承认这虽然不是什么大问题，但是你会觉得这有点烦，至少在你刚开始使用Dagger2的那几个小时里会这样觉得，尤其是这几个小时还是你对component改动比较频繁的时候。因而，这个问题还是值得提一下的。\n\n解决方法：据我所知，没有。\n\n## Conclusion 结论\n我不想在这里长篇大论的总结，所以我会尽可能说的简洁精练\n\nDagger2是否比原来的版本有明显的进步？是的，提升非常大。\n\n是否还存在问题呢？是的，但是这些问题并没有严重到让你连试都不想试，甚至不会影响到你在生产环境的工程中使用它\n\n## Updates 更新\n2015-05-11: 根据Jake Wharton的评论，更正了对Dagger 1中反射使用的陈述。\n\n\n## Source 源码\n[Dagger 2 Demo][7] (GitHub)\n## Resources 资源\n[Documentation][8]\n[Video introduction][9] - by Gregory Kick (Google)\n\n\n[1]: https://corner.squareup.com/\n[2]: https://github.com/google/guice\n[3]: https://github.com/google/guava\n[4]: https://github.com/google/guava\n[5]: https://plus.google.com/+GregoryKick\n[6]: http://en.wikipedia.org/wiki/Factory_%28object-oriented_programming%29\n[7]: https://github.com/mgouline/android-samples/tree/master/dagger2-demo\n[8]: http://google.github.io/dagger/\n[9]: https://www.youtube.com/watch?v=oK_XtfXPkqw\n"
  },
  {
    "path": "issue-38/Android逆向工程.md",
    "content": "Android逆向工程-第三部分\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 3](http://www.fasteque.com/android-reverse-engineering-101-part-3/)\n* 原文作者 : [Daniele Altomare](http://www.fasteque.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [mr_dsw](https://github.com/dengshiwei) \n* 校对者: [Desmond Yao](https://github.com/desmond1121)  \n* 状态 : 完成 \n\n这个系列的头两篇文章中，我写了两篇关于[APK format](http://www.fasteque.com/android-reverse-engineering-101-part-1/)和[aapt tool](http://www.fasteque.com/android-reverse-engineering-101-part-2/)的文章.\n\n在这篇文章中我将重点讲述dex2jar，它是一个作用于Android .dex文件和Java .class文件的工具。已经有一些参照文章，但是你可以点击[这里](https://github.com/pxb1988/dex2jar)进入官方网站。\n\n正如你所期望的那样，这个工具的核心功能就是转换APK的classes.dex文件为classes.jar文件（或任何你选择的工程），反之亦然。所以使用任何Java反编译工具来查看Android应用的源代码是可能的。\n\n你从.class文件中得到的是什么，不要期望得到应用程序开发者所写的Java源代码，然后，正如稍后你所看到的，这些源代码是完全可以获得来阅读查看的。\n\n**安装**\n\n为了得到最新的可用版本，请到官方的[repositories](https://bitbucket.org/pxb1988/dex2jar/downloads)下载。\n\n安装进程非常简单：你只需要将这个安装包解压到你指定的文件夹同时添加这个路径到你的path环境变量下。这样，你就可以开始使用dex2jar！\n\n注：您需要文件夹包含脚本文件的执行权限。\n\n在写这篇文章时，dex2jar最新稳定的版本是2.0，因此，文本中使用的是最新的版本。\n\n\tversion: reader-2.0, translator-2.0, ir-2.0\n\n在挖掘dex2jar工具的核心功能之前，你如果仔细看了下dex2jar文件夹的内容，你能注意到一些可运行脚本（Unix/Mac和Windows系统版本)。这是因为dex2jar工具的每个核心功能通过分开的脚本提供，我认为这是一个很好的解决方案，所以你不需要为了一次执行传递太多的参数。\n\n![dex2jar folder](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-17-at-13.52.43.png)\n\n现在让我们转移到dex2jar工具的核心功能上：转换DEX classes文件为JAR文件。\n\n如果你阅读了[这个系列的第一篇文章](http://www.fasteque.com/android-reverse-engineering-101-part-1/)，你知道一个APK文件中可执行代码以dex格式存在，它被定义为Android字节码格式文件。当然，为了分析一个应用程序最好是能有一个容易阅读的代码格式，这就是dex2jar能帮助你的。\n\n在使用第一个命令之前，我们需要选择从Google Play Store上选择一个应用同时安装一个反编译工具来查看源码（另一方面我们获得是.class文件不是.java文件）。\n\n* [RGB Tool](https://play.google.com/store/apps/details?id=com.fastebro.androidrgbtool): 这是一个开源项目, 所以逆向工程反编译它没问题. 你可以在[这里](https://apkpure.com/rgb-tool/com.fastebro.androidrgbtool)获取APK安装文件。\n* [JD-GUI](http://jd.benow.ca/): 一个带UI界面的Java反编译工具。\n\n我们即将测试的第一个命令，让你提取应用程序的可执行代码转换为JAR格式：\n\n\td2j-dex2jar.sh -f -o classes.jar FILENAME.apk\n\n使用-o选项，可以让你指定这个命令的输出文件名称，同时-f选项告诉dex2jar如果文件存在就进行覆写。\n\n![dex2jar](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-17-at-22.16.35.png)\n\n如果你通过JD-GUI打开输出的文件，你可以看到应用程序的源代码，这次，你看到的并不是完全的Java类文件，但是它足够清楚地阅读。带下划线的项是可点击的，所以向导指引代码很容易。\n\n![JD-GUI](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-17-at-22.34.47.png)\n\n**源代码在哪里？**\n\n如果你仔细的看截图，你会注意到用数字替换资源名称，如R.id.SOMETHING, R.layout.SOMETHING, … 。正如[官方文档说明](http://developer.android.com/guide/topics/resources/accessing-resources.html)这些是资源文件在R.java文件中对应的所有的id、layout、drawable等索引。这些文件在编译的时候自动生成(不允许修改，自动生成！)，同时可以在你项目的.../build/generated/source/r/...文件夹下找到。本质上它是一个final class类，同时它为每个资源类型文件定义static final class内部类，例如strings, layouts, arrays, colors, dimens, …，同时为每个资源类型定义static final类型的int field（字段），即你的资源文件的IDS。这样，在实现应用程序的时候，你不必直接处理数字。\n\n![screen-shot](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-21-at-11.37.18.png)\n\nIDS被存储为16进制数，因此通过dex2jar文件的getStringArray方法（你可以在上面的截图中看到）提取出0x7f080000 ( 原始应用程序源代码中R.array.pick_color_array的资源文件)是2131230720 (decimal)\n\n**混淆代码**\n\n我选择这个应用程序是因为我知道它的代码已经通过[ProGuard](http://proguard.sourceforge.net/)进行混淆了。你能清楚地看到几个包和类的名称为a,b,c...\n\n在下面的截图中，它是ProGuard所做工作的的见证，ProGuard改变了方法和变量的名称来混乱代码的连贯性。然而，代码仍然是可读的同时它不是特别难找到和理解特定的算法或业务逻辑。\n\n当让，并不是所有的类都被混淆了，这是依靠声明在ProGuard配置文件中的特定的规则，例如这个例子的应用程序，你可以在GitHub的[这里](https://github.com/fasteque/rgb-tool/blob/master/android-rgb-tool/proguard-rules.txt)找到它。\n\n![obfuse](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-17-at-22.46.30.png)\n\n**内存溢出错误**\n\n当前的文档没有更新关于这个话题的，反正它是可能的，当转换DEX文件为JAR或viceversa，会得到OutOfMemoryError问题：就像你正在试图转换一个很大的dex文件。\n\n为了防止这个问题，你需要增加JVM的内存大小，你必须打开d2j_invoke脚本同时找到下面的一段话：\n\n\tjava -Xms512m -Xmx1024m -classpath \"${_classpath}\" \"$@\"\n\n根据你的系统和需求改变这个值，就我个人而言，我只需要改变内存池的大小，如：-Xmx2048m。\n\n**回到DEX**\n\ndex2jar也支持从.class文件到.dex文件的转换，这是非常有趣的，因为它让可执行代码从新打包到apk文件中成为可能。\n\n具体的指令是：\n\n\td2j-jar2dex.sh -f -o classes.dex classes.jar\n\n使用-o选项，你可以通过指令指定输出的名称，同时-f告知dex2jar如果文件存在则重写文件。\n\n![upload](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-19-at-22.03.18.png)\n\napk文件本质上是一个ZIP文件，所以新的DEX文件可以插入后很容易：\n\n\tzip -r FILENAME.apk classes.dex\t\n\n记得这个操作会改变原始的APK文件，所以你不能简单地将它安装在您的设备，你得使用Jarsigner重新为APK签名为了再生manifest.mf文件。这是一种不改变应用程序的安全保障：如果你这样做，你要放弃这个包，因为没有原来应用程序的密钥库和私钥，你更改后的APK不能作为原来APK的一个更新发布。\n\n当系统安装更新到一个应用程序时，它将与现有版本中的新版本的证书进行比较。如果证书匹配，该系统允许更新。\n\n**SMALI**\n\n来自[官方文档](https://github.com/JesusFreke/smali):\n\n> smali/baksmali是dex文件的assembler/disassembler程序，通过dalvik，Android’s Java VM实现。语法是基于松散的Jasmin’s/dedexer的语法，并支持对dex格式的全部功能（annotations, debug info, line info, etc.）。\n\n详细的细节讲解超出了本文的范围，但是我想让你至少知道smali和baksmali，只是想让你知道直接从classes.dex文件中获取.smali是可能的。\n\n这意味着可以直接更改应用程序的源代码并使其生效。\n\n指令非常简单：\n\n\td2j-dex2smali.sh FILENAME.apk\n\n![download](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-20-at-16.40.39.png)\n\n这个工具创建了一个新的文件夹，通过为APK名称新增后缀-out来命名，文件内部通过包名组织。\n\n有一个想法，就是smali看起来像这样：它不是那么难读，一旦你习惯了它的语法是完全可以理解的。\n\n请注意这是一个a.class类文件，我已经发布了截图上面，当谈到混淆代码。\n\n![afused](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-20-at-22.35.28.png)\n\napktool 同样可以操纵smali文件:我将在这个系列的下篇文章中介绍。\n\n这是所有在更新和dex2jar介绍：正如你所看到的，一个相对简单易用的工具，你已经可以从APK文件中提取（可能回退）大量信息。但该工具缺乏对XML资源的支持，它们是任何安卓应用程序的重要组成部分。\n\n在[下篇文章中](http://www.fasteque.com/android-reverse-engineering-101-part-4/)，我将向你介绍apktool同时也有些事情变得真的很有趣。"
  },
  {
    "path": "issue-38/Android高性能JSON数据解析.md",
    "content": "Android高性能JSON数据解析\n---\n\n> * 原文链接 : [Hi Performance JSON Parsing in Android](http://www.donnfelker.com/hi-performance-json-parsing-in-android/)\n* 原文作者 : [DONN FELKER](http://www.donnfelker.com/author/donn/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [mr_dsw](https://github.com/dengshiwei) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n有时Android开发中我们调用的1/3的API需要我们下载JSON数据。现在我有一个客户端调用一个API接口用于为设备同步数据。调用一个API返回成百上千行的JSON数据导致保存的时候形成一个8MB或者更大的JSON文件。将整个文件加载到内存中进行处理是很痛苦的，同时会导致大量内存受限的设备crash掉。然而，使用[Jackson](http://www.codehaus.org/) JSON数据解析类库能够一个个将[http-request](https://github.com/kevinsawicki/http-request)转换成[Reader](http://developer.android.com/reference/java/io/Reader.html)对象，[Reader](http://developer.android.com/reference/java/io/Reader.html)对象允许你处理每一个从pipe中下载下来JSON对象，它允许你从下载下来就可以处理数据而不是等到整个JSON文件加载完成。\n\n我使用Jackson中的[ObjectMapper](http://www.codehaus.org/)对象反序列化JSON为[POJO](https://en.wikipedia.org/wiki/Plain_Old_Java_Object)对象，这允许我在应用中使用domain objects，因此，我不必为任何情况下的JSON数据解析而担心或将数据存放到本地设备上（而担心）。如果你加载整个JSON文件为ObjectMapper对象，你必须创建一个很大的对象，足以占满你所有的可用内存，同时你可能会发生OutOfMemoryException异常。最终的目标是处理每一个下载的JSON对象，同时结合使用POJO的有点使用。下面是你如何使用它。\n\n**对象模型**    \n下面我们有一个简单的customer对象需要我们下载。假想一个app拥有数以百万计的customers。每一个customer都被表示为如下的一个POJO对象。\n\n    public class Customer {\n\t  String firstName;\n\t  String lastName;\n\t  // 20 Other properties\n\t\n\t  public String getFirstName() {\n\t    return firstName; \n\t  }\n\t\n\t  public void setFirstName(String firstName) {\n\t    this.firstName = firstName; \n\t  }\n\t\n\t  public String getLastName() {\n\t    return lastName; \n\t  }\n\t\n\t  public void setLastName(String lastName) {\n\t    this.lastName = lastName; \n\t  }\n\t\n\t  // all the other mutators and accessors\n\t\n\t}\n\n**连接API / 消费Stream**   \n这实际上很容易，通过你的http类库(我使用http-request)连接到到你的API接口，使用HttpRequest对象的reader()方法，以便我能以此reader作为参数传递给JsonParser对象（如下所示），然后让循环建立每一个Customer对象，最后处理每一个customer。\n\n\t// Connect to your API with your http lib, i use http-request: https://github.com/kevinsawicki/http-request\n\t// Then get the reader. The 'request' variable below is just a HttpRequest object \n\t// as shown here: http://kevinsawicki.github.io/http-request/\n\tfinal Reader reader = request.reader(); \n\t\n\tfinal ObjectMapper mapper = new ObjectMapper(); \n\tfinal JsonFactory factory = mapper.getFactory();\n\t\n\t// Create a new streaming parser with the reader\n\tJsonParser jp = factory.createParser(reader);\n\t\n\t// I'm working with a JSON Array that is returned from the API that I'm connecting to, so I need to advance the \n\t// parser to the start of the array.\n\t\n\t// Advance stream to START_ARRAY first:\n\tjp.nextToken();\n\t\n\t// Parse each value by looking for each start object. The Mapper will read the object and advance the parser\n\t// until it finds the END_OBJECT at which time the mapper then deserializes into a POJO. Then the loop will \n\t// continue to the next position. This will handle each JSON object as it becomes available and will be very \n\t// light on memory and high in performance. \n\twhile(jp.nextToken() == JsonToken.START_OBJECT) {\n\t    // Get customer POJO\n\t    final Customer c = mapper.readValue(jp, Customer.class);\n\t\n\t    // Update local db with customer info.\n\t    myDataLayer.updateCustomer(c);\n\t}\n\n**如何使用Jackson**    \n简单地将它添加为一个类库，或者如果你使用Maven添加Maven dependency在你的POM中同时关闭races。现在你可以下载巨大的JSON数据同时不必担心内存溢出。"
  },
  {
    "path": "issue-38/RecyclerView动画 第一部－动画效果是如何工作的.md",
    "content": "#RecyclerView动画 第一篇－动画是如何工作的\n\n> * 原文链接 : [RecyclerView Animations Part 1 – How Animations Work](http://www.birbit.com/recyclerview-animations-part-1-how-animations-work/)\n> * 原文作者 : [Yiğit Boyar](http://www.birbit.com/)\n> * 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n> * 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n> * 译者 : [shenyansycn](https://github.com/shenyansycn) \n> * 校对者: [这里校对者的github用户名](github链接)  \n> * 状态 :  未完\n\nLisetView是Android框架中最流程的控件之一。它有很多功能，然而它是很复杂的，修改难度很大。随着用户体验的发展和手机变的越来越快，它的局限性使它的特色黯然失色。\n\n在Lollipop中，Android团队决定发布一个新的控件，使得该控件的不同的行为更容易实现。以下的改变会很容易控制：\n\n> * items如何被展现\n> * 动画效果\n> * item修饰\n> * 回收策略\n> * ...\n\n\n极大的灵活性也伴随着一个巨大架构的复杂性。同样也有更多需要学习。\n\n在这篇文章中，我要深入RecyclerView的内部，特别在**动画如何实现**上。\n\n在Honeycomb上，Android框架引进了[LayoutTransition](http://developer.android.com/reference/android/animation/LayoutTransition.html)，ViewGroup内部动画的一个很简单的方法。它工作原理是布局改变前和改变后分别生成两个ViewGroup快照，然后在这两个状态间创建一个动画效果。这个过程和Adapter中RecyclerView的动画非常相似。\n\n*LayoutTransition例子：*\n\n![LayoutTransirion example](http://img.blog.csdn.net/20151229105047003)\n\n不幸的，List有一个主要的区别使得LayoutTransitions不适合它们的动画，list中的item和ViewGroup中的不一样。处理“items”动画机制和动态显示“views”的内容是很重要的差别。\n\n在一个标准的ViewGroup中，如果一个View是新被添加到视图层的，它可以被认为是一个新加入的View，因此可以设置相应的动态效果（例如，淡入）。对于集合，这是不同的。例如，在Adapter中一个item的View变为可见仅是因为之前有一个item被移除。在这个情况下，已经在列表中的运行淡入的动画效果的item会被误认为是一个新的view，因为它刚被看见。RecyclerView知道item是否是新的，但是不知道如果item**不是**新的它在哪。同样对于消失中的View也是一样，如果没有被Adapter移除，RecyclerView不知道View去哪里了。\n\n*对于列表LayoutTransition是无效的：*\n\n![LayoutTransirion bad example](http://img.blog.csdn.net/20151229105018461)\n\n为了修复这个问题，RecuclerView会问LayoutManager新View的之前的位置。虽然这会工作，它将会需要LayoutManager结尾的一些统计，对于更复杂的LauoutManager可能不需要繁琐的计算。\n\nRecyclerView控制item显示和消失动画的方式（就是说，view的显示和消失的动画是指它仍然在list中）是依赖LayoutManager处理layout逻辑的预测性。一方面，RecyclerView需要知道变化之前view被展示在哪一层。另一方面，RecyclerView想要知道变化之后view被展示在哪一层，如果LayoutManager展示items出现了问题，则item不可见。\n\n为了LayoutManager更容易的提供这个信息，RecyclerView使用两个层来配合动画效果。具体如下描述：\n    \n> * 在第一个层中的变化(**preLayout**)，RecyclerView询问LayoutManager当前层之前状态的更多信息。比如上面的例子，就好像请求“‘C’被移除了”。LayoutManager正常执行‘C’被移除的操作，用新的View填补‘C’的空档。\n> \n 出色的是RecyclerView仍然知道‘C’还在Adapter中。比如，当LayoutManager想要知道位置为‘**2**’的View是什么时，RecyclerView会返回‘C’(`getViewForPosition(2) == View('C')`)。如果问位置为‘**4**’的View是什么时，会返回来‘E’（虽然‘D’是Adapter第四个元素）。返回View的有一个**isItemRemoved**方法，LayoutManager可以通过检查这个方法来判断是否是消失的View。\n    \n> * 在第二个层中的变化（**postLayout**），RecycleView请求LayoutManager布置它自己的item。这时，‘C’已经不在Adapter中了。`getViewForPosition(2)`会返回‘D’，`getViewForPosition(4)`会返回‘F’。\n> \n    记住，‘C’已经被Adapter移除了。但是因为RecyclerView保留了它的引用，它表现的好像‘C’仍然存在。换句话说，RecyclerView做了LayoutManager统计的工作。\n\n每次调用LayoutManager中的`onLayoutChildren`的时候，它会**临时拆开**所有的View并从头开始布局。没有改变的View会被从废弃的缓存中返回，因此它们还是有效的。这使得重新布局相当高效。\n\n*LinearLayoutManager之前的布局结果：（红色框范围内对用户是可见的）*\n\n\n![LinearLayoutManager pre layout](http://img.blog.csdn.net/20160104165238477)\n\n*LinearLayoutManager之后的布局结果*\n\n![LinearLayoutManager post layout](http://img.blog.csdn.net/20160104165428678)\n\n这两个布局变化后，RecyclerView知道View来自哪里所以能运行正确的动画效果。\n\n![Predictive Animation](http://img.blog.csdn.net/20160104165622148)\n\n*你可能会问*：‘C’没有被LayoutManager展示，那为什么还会可见？\n\n要清楚，在pre-layout中被LayoutManager展示的‘C’是因为它看起来是在Adapter中。事实是在post-layout中不被LayoutManager展示的‘C’已经不再Adapter中存在了。也**就说**是‘C’不再是LayoutManager的孩子，但对于RecyclerView**不**是这样的。当LayoutManager移除了View，如果要实现动画效果，RecyclerView会使他做为孩子一直存在（所以动画会正确的显示）。更多的细节会在[Part 2](http://www.birbit.com/recyclerview-animations-part-2-behind-the-scenes/)。\n\n**消失的Items**\n\n通过了两个布局过程的变化，RecyclerView可以正确的执行新View的动画效果。但是，消失的View存在另一个问题。考虑一下新添加的元素在list什么位置，会把一些元素移出可见区域。它看起来像LayoutTransitions：\n\n![Add Animation Failure](http://img.blog.csdn.net/20160104165758583)\n\n当‘X’被添加在‘A’后，它把‘F’移出了可见区域。因为LayoutManager没有展示‘F’，LayoutTransition认为它已经被一个淡出的动画效果移出UI。实际上，‘F’仍然在Adapter中只是被移出了可见区域。\n\n为了解决这个问题，RecyclerView给LayoutManager提供了一个额外的API。在**postLayout**变化的末尾，LayoutManager会调用[getScrapList](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Recycler.html#getScrapList%28%29)获得列表中的View（没有被LayoutManager展示，但仍然在Adapter中）。然后，它展示了这些view，好像RecyclerView的规模大到足以展示它们。\n\n*LinearLayoutManager之后的布局结果：（红色框范围内对用户是可见的）*\n\n![LinearLayoutManager post layout](http://img.blog.csdn.net/20160104165955801)\n\n一个重要的细节是，因为这些View在动画效果结束后就不是必需的了，LayoutManager会调用**addDisappearingView**来代替`addView`。这就告诉了RecyclerView这个View的动画效果完成后就被移除。RecyclerView会把这个View加入到隐藏View的列表中，所以`postLayout`方法返回时在LayoutManager子View的列表中是不可见的。这样，LayoutManager就会忽视它。\n\n![Predictive Add Animation](http://img.blog.csdn.net/20160104170057136)\n\n首先，至少对于LinearLayoutManager来说，你可能认为它会计算View来自哪里和要去哪里（如果消失），这样就不需要两次布局计算。令人遗憾的是，许多类型的Adapter在同一个布局中发生变化时会有很多意外情况。除此之外，对于更复杂的LayoutManager，它不总是简单的计算一个item放在什么地方（例如StaggeredGridLayout）。这个方法会删除来自LayoutManager所有的负担，它可以很容易的支持一些适当的动画效果。\n\n到目前为止，我的主要想法是RecyclerView如何预测动画的运行。实际上有很多简单的实现（对于LayoutManager来说）。你可以在[Part 2 - 幕后](http://www.birbit.com/recyclerview-animations-part-2-behind-the-scenes/)中了解更多。\n\n"
  },
  {
    "path": "issue-38/RecyclerView动画 第二部－幕后.md",
    "content": "#RecyclerView Animations Part 2 – Behind The Scenes\n#RecyclerView动画 第二篇－幕后\n> * 原文链接 : [RecyclerView Animations Part 2 – Behind The Scenes](http://www.birbit.com/recyclerview-animations-part-2-behind-the-scenes/)\n> * 原文作者 : [Yiğit Boyar](http://www.birbit.com/)\n> * 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n> * 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n> * 译者 : [shenyansycn](https://github.com/shenyansycn) \n> * 校对者: [这里校对者的github用户名](github链接)  \n> * 状态 :  未完\n\n--\n    \n>这是系列文章的第二篇，请先阅读[第一篇](http://www.birbit.com/recyclerview-animations-part-1-how-animations-work/)\n\n在第一篇文章中，我主要介绍了在RecyleyView中如何预测动画的运行。实际上有很多简单实现（对于LayoutManager）。这里有一些你需要知道的关键点。\n\n**当LayoutManager的children被移除的时候RecyclerView仍然保留他们的引用。这是如何工作的？LayoutManager和RecyclerView的约定是无效的么**\n\n是的，有点违反了和LayoutManager的约定，但是：\n\nRecyclerView**保持**了View做为一个ViewGroup的child，但是对于LayoutManager又**隐藏**起来了。每次\nLayoutManager调用了访问children的方法时，RecyclerView都把**隐藏**的View考虑在内。让我们看下第一篇中的一个例子，‘C’正在被移出Adapter。\n\n![Predictive Animation](http://img.blog.csdn.net/20160104165622148)\n\n当‘C’淡出时，如果LayoutManager调用了`getChildCount()`，RecyclerView会返回6虽说LayoutManager有7个children。如果LayoutManager调用了`getChildAt(int)`，RecyclerView会跳过‘C’（或其他隐藏的children）。如果LayoutManager调用了`addView(View, position)`，RecyclerView会在调用`ViewGroup#addView`前也会跳过。\n\n当动画结束时，RecyclerView会移除并回收View。\n\n更多信息你可以查看[ChildHelper](https://android.googlesource.com/platform/frameworks/support/+/master/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java) 这个内部类。\n\n**在preLayout过程中RecyclerView如何处理item的位置，是因为他们与Adapter不匹配么？**\n\n一个特殊的通知事件被添加到Adapter是可行的。当Adapter分派了`notify**`事件时，RecyclerView纪录它们并发送布局请求。任何下一个布局显示前获得的事件都会被一起应用。\n\n当系统调用了onLayout时，RecyclerView做了如下动作：\n\n1. 纪录更新事件，例如`move`事件被增加到更新事件列表的末尾。移动`move`事件到列表末尾是一个简单步骤，所以我就不在这里展开了。如果你感兴趣你可以看[OpReorderer](https://android.googlesource.com/platform/frameworks/support/+/master/v7/recyclerview/src/android/support/v7/widget/OpReorderer.java)类。\n\n2. 依次处理事件和更新当前的ViewHolders’的位置。如果一个ViewHolder被移除，他会被标记为`removed`。执行时，RecyclerView判断这个adapter的改变是否会被发送到LayoutManager的preLayout之前或之后的步骤。流程如下：\n\n - 如果是`add`操作，会被推迟，因为preLayout中应该没有item。\n\n - 如果是 `update`或`remove` 操作并影响了已经存在的ViewHolders，它会被延期。如果没有影响当前的ViewHolders，它会被发送到LayoutManager，因为RecyclerView不能恢复item之前的状态（因为没有一个ViewHolder能表示item之前状态的）。\t\n\n - 如果是`move`操作，它会被推迟，因为在pre-layout处理过程中RecyclerView会伪造一个位置。例如，如果item从位置3移动到位置5，在pre-layout处理过程中当位置3的View被请求时RecyclerView会返回位置为5的View。\n\n - 如果有必要RecyclerView会重写更新操作。例如，如果一个更新或者删除操作影响到了一些ViewHolder，RecyclerView会把操作分离开。如果一个操作应该发送到LayoutManager但是一个推迟的操作有可能影响它，RecyclerView会重新排序这些操作以便前后一致。\n \t\n \t\t例如，如果已被推迟的`Add 1 at 3`动作后紧跟着一个不能推迟的`Remove 1 at 5`动作。RecyclerView会发送给LayoutManager一个`Remove 1 at 4`动作。原因是`Add 1 at 3`执行后`Remove 1 at 5`也被通知执行了，它们针对的都是同一个item。RecyclerView没有通知LayoutManager`Add 1 at 3`的动作，它会重写以便前后一致。\n\t\t\n\t\t这个方法使得LayoutManager跟踪item的消亡很简单。在Adapter和LayoutManager之间的抽象化使这一切成为可能，这就是为什么RecyclerView从来不发送Adapter到LayoutManager，替代的是，提供方法访问Adapter的状态和循环利用。\n\t\t\n\t\tViewHolder也有它自己的`old position`，`pre layout position`和最终Adapter中的位置。当`ViewHolder#getPosition`被调用时，根据当前布局状态（pre或post）要么返回preLayout的位置要么返回最终Adapter的位置。LayoutManager不关心这个因为发送给它的都是前后一致的。\n\n3. 当Adapter更新被处理后，RecyclerView保存了当前View的位置和大小用于之后的动画使用。\n\n4. 在`preLayout`RecyclerView调用`LayoutManager#onLayoutChildren`。就像我在第一篇文章中提到的，LayoutManager运行自己的布局规则。它所做的这些都是了布局那些被`deleted`或`changed`(`LayoutParams#isItemRemoved`，`LayoutParams#isItemChanged`)的item。在这里需要提醒的是，被删除或被改变的item仍然会被Adapter API提供给LayoutManager。这样，LayoutManager就会简单的认为是其他View（添加、测量、位置等）.\n\n5. preLayout结束后，RecyclerView再次纪录这些View的位置并告诉LayoutManager继续更新Adapter\n\n6. RecyclerView再次调用LayoutManager的`onLayout`(`postLayout`).这时，所有item的位置匹配Adapter现在的内容。LayoutManager再次用自己的规则显示布局。\n\n7. post layout结束后，RecyclerView再次检测这些View的位置，判断哪些item是被添加的，被删除的，被改变的和被移动的。它‘隐藏’了被删除的View，item没有被LayoutManager加入，加入到了RecyclerView里（因为它们应该开始动画了）\n\n8. 需要动画的Items被ItemAnimator开始运行它的动画效果。当动画执行完毕，ItemAnimator会调用RecyclerView里的回调方法，如果不再用了，View会被RecyclerView移除和回收。\n\n**如果item位置使用了LayoutManager保留的一些内部数据结构会发生什么？**\n\n一切工作...。感谢RecyclerView重写了Adapter的更新，当其中某一个adapter数据更新回调方法被调用时，所有的LayoutManager都可以自己处理更新。只要RecyclerView确保恰当的调用时机和顺序。\n\n在布局时的任何时候，如果LayoutManager需要访问adapter额外的数据（一些自定义的API）。可以调用[Recycler#convertPreLayoutPositionToPostLayout](http://developer.android.com/reference/android/support/v7/widget/RecyclerView.Recycler.html#convertPreLayoutPositionToPostLayout(int))获得item在adapter中的位置。例如，GridLayoutManager用这个API去获得item的范围大小。\n\n**如果调用 [notifyDataSetChanged](http://developer.android.com/reference/android/support/v7/widget/RecyclerView.Recycler.html#convertPreLayoutPositionToPostLayout(int)) 会发生什么？如何预测动画的运行？**\n\n什么也不会发生，这就是为什么`notifyDataSetChanged`应该最后调用。当adapter里的`notifyDataSetChanged`被调用时，RecyclerView不知道哪个的item被移动了所以他不能正确的假装`getViewForPosition`调用的样子。只是简单像LayoutTransition一样运行动画。\n\n--\n我希望这两篇文章可以帮助你理解RecyclerView里的动画是如何工作的和为什么这样工作。"
  },
  {
    "path": "issue-38/从网页中触发Android原生的分享Intent.md",
    "content": "# Triggering a native Share intent on Android from the web\n# 从网页中触发Android原生的分享Intent\n\n> * 原文链接 : [Triggering a native Share intent on Android from the web](https://paul.kinlan.me/sharing-natively-on-android-from-the-web/?utm_source=Android+Weekly&utm_campaign=faec756f5a-Android_Weekly_179&utm_medium=email&utm_term=0_4eb677ad19-faec756f5a-337955857)\n* 原文作者 : [Paul Kinlan](https://paul.kinlan.me/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明 : 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [MrSimple](https://github.com/bboyfeiyu) \n* 校对者 : [校对者的名字](https://github.com/这里写校对者的名字) \n* 状态 :  完成 \n\nThis story starts a long time, was tickled into existing after I visited FlipKart in Bangalore and was finalized after an internal conversation about the fact that it is impossible to trigger the share dialog in Android from the web. Lots of people want it, it turns out everyone thought it wasn’t possible. Any how…..\n\n这是很久之前的事了，在我访问了班加罗尔（印度南部城市）的FlipKart以及进行了一场关于是否存在一种从web触发Android分享Intent方式的内部交流。很多小伙伴都想要这样的功能，但是它给很多人的感觉是无法实现。那么它究竟怎样才能实现呢......\n\nI have fond memories of Web Intents and what it could have been…. Ok, I won’t lie, I cry in to my cup most nights about its failure.\n\n对于web的Intent我还是有很多美好的记忆以及它们能够做些什么...好吧，其实我曾经也为这方面的失败而在很多个夜晚泪流满面~\n\nWeb Intents modeled the successful Android Intents ecosystem pretty closely and the primary use-case for Web Intents, like Android, was to provide a good sharing experience on the web. Rather than have a sea of social widgets on your page — one for each service a user might use — you have a single button that the user clicks to share some content and their OS or browser will decide how to show a list of services that can fulfill the users request. It was a glorious idea, [even if I do say so myself](https://en.wikipedia.org/wiki/Paul_Kinlan).\n\nAndroid Intent系统成功地模仿了Web Intent，与Android一样，Web Intent的主要使用场景就是提供一种良好的分享体验。虽然有很多的社交组件在你的页面上，但是只有其中一个会平台会被用户选择。在页面中你定义了一个按钮，当用户点击这个按钮时就会触发分享操作，此时系统或者浏览器就会检测提供分享功能的服务，并且做出对用户的回应。不得不说这简直就是一个屌炸天的想法,即使想出这个方法的人是[我](https://en.wikipedia.org/wiki/Paul_Kinlan)。\n\nSkip forward, the Web Intents project is dead, Android is incredibly popular and already has an ecosystem built around sharing between apps. The most powerful attribute of Android Intents in my opinion is that for most parts an Intent can be encoded as a simple URL using the intent: scheme. Chrome and every other browser on the platform support. However the only time it used on the web is to open up a user’s installed native app. For example: [intent:paul.kinlan.me#Intent;scheme=https;package=com.chrome.beta;end](intent:paul.kinlan.me#Intent;scheme=https;package=com.chrome.beta;end) will open my site in the Chrome Beta browser on Android.\n\n不幸的是， Web Intents项目没有能够存活下来，而Android的Intent却坚持了下来，并且成为内置的应用间分享的形式。Android Intent最强悍它能够被解析成为类似URL的scheme，这个scheme能够被Chrome和其他浏览器支持。然而，在web上使用它的场景只是通过打开一个网页来让用户下载一个native的App。例如 [intent:paul.kinlan.me#Intent;scheme=https;package=com.chrome.beta;end](intent:paul.kinlan.me#Intent;scheme=https;package=com.chrome.beta;end) 会在Android版的Chrome打开一个站点。\n\nThe interesting thing is that you can’t just open any app on the users device. The app has to say to the system that it can be opened from the web by including <category android:name=\"android.intent.category.BROWSABLE\" /> in the intent-filter.\n\n有意思的是你不能直接启动系统中的其他app。这个app必须告诉系统它能够从web中被打开，这需要在intent-filter中声明 `<category android:name=\"android.intent.category.BROWSABLE\" />` 。\n\nA fuller example of what might be included in the Android Manifest follows.\n\n完整的例子是你需要在Android Manifest中添加如下声明 : \n\n```\n<intent-filter>\n    <action android:name=\"android.intent.action.VIEW\" />\n    <category android:name=\"android.intent.category.DEFAULT\" />\n    <category android:name=\"android.intent.category.BROWSABLE\" />\n</intent-filter>\n```\n\nThis is interesting, if you know Android you know that ACTION_SEND is by far the most popular Action and it is used mostly commonly for sharing data between apps.\n\n如果你了解Android，那么你应该知道ACTION_SEND是一个非常流行的Action，它被用于在应用间分享数据。\n\nIn fact, Chrome itself fires an ACTION_SEND when the user chooses to share from inside the Chrome App and if you have the following declaration in your Android Manifest your app will appear in the Chrome Intent picker.\n\n事实上，当用户在Chrome应用中进行分享时Chrome自己会触发一个ACTION_SEND，如果你在你的Android Manifest中定义了如下的声明，那么你的应用就会出现在备选应用列表中。\n\n```\n<!-- Used to handle Chrome then menu then share.-->\n<intent-filter>\n    <action android:name=\"android.intent.action.SEND\" />\n    <category android:name=\"android.intent.category.DEFAULT\" />\n    <data android:mimeType=\"text/plain\" />\n    <data android:mimeType=\"image/*\" />\n</intent-filter>  \n```\n\nSo. Big question. Does this just work from the web if I craft the correct looking intent url?\n\n那么问题来了。如果我们制造一个类似的Intent url，那么能直接从web中直接触发类似的分享操作吗？\n\nNo. Well. Yes. But…. the apps that handle link sharing need to declare <category android:name=\"android.intent.category.BROWSABLE\" /> and right now none do (that I can find) and they will need to define the following in their manifest:\n\n可以说能，也可以说不能。要实现这样的功能，这个app必须申明另外一个特性，`<category android:name=\"android.intent.category.BROWSABLE\" />`，现在我们的Android manifest看起来是如下这样的 : \n\n```\n<!-- Used to handle Chrome then menu then share.-->\n<intent-filter>\n    <action android:name=\"android.intent.action.SEND\" />\n    <category android:name=\"android.intent.category.DEFAULT\" />\n    <category android:name=\"android.intent.category.BROWSABLE\" />\n    <data android:mimeType=\"text/plain\" />\n    <data android:mimeType=\"image/*\" />\n</intent-filter>  \n```\n\nNow, when apps have this it is pretty easy to open up Native Sharing from Chrome on Android.\n\n现在，我们的app就可以从web直接从Android的Chrome打开native的分享Dialog了。\n\nSo let’s build up a share button.\n\n让我们来构建一个分享按钮吧。\n\n## 1. Create a basic share experience\n\n## 1. 创建一个最基本的分享\n\nTrigger the intent picker with using ACTION_SEND. Note: no data will be sent yet, it will just list apps that have said they can receive text (a URL for example).\n\n通过ACTION_SEND来触发分享操作。注意 : 这里我们没有数据会被发送，它只是列出了可以接受数据的app。此时Intent url为 : \n\n```\nintent:#Intent;action=android.intent.action.SEND;type=text/plain;end\n```\n\n<img src=\"https://paul.kinlan.me/images/intent-share-1.png\" width=\"240\">\n\n## 2. Add a link to the share\n## 2. 为这个分享添加一个链接\n\nAdd some data by encoding a String EXTRA_TEXT in to the Intent, for example: S.android.intent.extra.TEXT=https%3A%2F%2Fpaul.kinlan.me%2F\n\n通过EXTRA_TEXT字段为Intent添加一些数据，例如 : S.android.intent.extra.TEXT=https%3A%2F%2Fpaul.kinlan.me%2F，此时Intent url为 : \n\n```\nintent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT=https%3A%2F%2Fpaul.kinlan.me%2F;end\n```\n\n<img src=\"https://paul.kinlan.me/images/intent-share-2.png\" width=\"240\">\n\n## Add a Subject\n## 添加主题\n\nWe can also flesh out some extra information that you can send across to the app. For example Android lets you specify a “Subject” in the Intent, for example: S.android.intent.extra.SUBJECT=Amazing\n\n我们可以再添加一些额外的信息，例如可以指定一个主题，对应的示例为 : S.android.intent.extra.SUBJECT=Amazing ，此时Intent url为 : \n\n```\nintent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT=https%3A%2F%2Fpaul.kinlan.me%2F;S.android.intent.extra.SUBJECT=Amazing;end\n```\n\n<img src=\"https://paul.kinlan.me/images/intent-share-3.png\" width=\"240\">\n\n## 4. Fallback to a webpage if there are no apps installed.\n## 4. 当没有可以响应的App时反馈给网页\n\nQuite frequently the user might not have any apps installed that can handle the request. In this case you will want to fallback to a web url that still allows the user to complete the task. This can be achieved by add in an S.browser_fallback_url Extra. In the following link the fallback is Twitter’s sharing widget.\n\n经常出现没有能够响应用户的行为的app。在这种情况下我们想通过一个回调告诉发起该请求的网页，使得用户仍然能够完成这个操作。这项功能可以通过一个名为S.browser_fallback_url的属性设置。示例Intent url为 : \n\n```\nintent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT=https%3A%2F%2Fpaul.kinlan.me%2F;S.android.intent.extra.SUBJECT=Amazing;S.browser_fallback_url=https:%3A%2F%2Ftwitter.com%2Fintent%2Ftweet;end\n```\n\n## That’s a wrap or what comes first? The chicken or the egg.\n## 先有鸡还是先有蛋\n\nTechnically we have all the pieces. Our biggest challenge is not the URL string but the fact that Android apps for our favorite social sites and communications apps need to be updated. Once they are then this is a great way to invoke a sharing experience from the web.\n\n从技术上来说，我们掌握了所有的技术点。我们最大的挑战不是URL本身，而是那些社交平台的app以及其他我们需要交互的app可以被更新（译者注 : 指的是通过Intent发布消息）。一旦他们这样做了，我们就可以通过这种方式通过web实现良好的分享体验。\n\nMy ideal solution is that one or two apps update their manifest and a couple of big players on the web commit to making creating an intent share URL for Android. But in the mean time, there are two apps that I know support it:\n\n我的理想解决方案是如果一两个app更新了他们的manifest，那么如下两个大玩家就会更新对应的share url。我们知道的两个app是如下两个 : \n\n* [Plaid](http://plaidapp.io/)\n* [Intent Intercept](https://play.google.com/store/apps/details?id=uk.co.ashtonbrsc.android.intentintercept&hl=en)\n\nIf you want your app in this list, just add Category Browsable and let me know, otherwise just sit and wathc this demo.\n\n如果你想让你的app出现在上面的列表中，你只需要添加对应的Category，并且让我知道。否则你就好好待着，然后观看这个[Demo视频](https://youtu.be/weaNw_S7E4w)。\n\n## 拓展阅读\n\n* [知乎Android客户端拒绝服务漏洞（可远程攻击利用）](http://www.wooyun.org/bugs/wooyun-2015-0121081)\n* [Intent scheme URL 攻击](http://drops.wooyun.org/papers/2893)\n"
  },
  {
    "path": "issue-39/Android Studio提示和技巧.md",
    "content": "#Android Studio Tips and Tricks\n#Android Studio提示和技巧\n> - Jan 6th, 2016\n\n我最近参加了Goolge的[Android Dev Summit](https://androiddevsummit.withgoogle.com/)，一个工具组用于交流[Android Studio For Experts](https://www.youtube.com/watch?v=Y2GC6P5hPeA)的地方。这里都是90分钟的会议，分享了大量的Android Studio小窍门。这也让我有了分享我收藏的Android Studio小窍门的想法。\n\n###LANGUAGE INJECTION\n\n曾经是否需要一个JSON类型的String？可能你用一个固定的文本来做为GSON的解析器，你就应该知道管理所有的反斜杠是一个巨大的痛苦。幸运地，IntelliJ有一个叫*Language Injection*的特性，允许你在它自己的编辑器里编辑部分JSON代码，然后它会恰当的插入到你的代码中。\n\n![Intention Action](http://michaelevans.org/images/2016/01/06/fragment_intention.png)\n\nInject的Language/Reference是一个intention action[^intention action]，你可以通过⌥+Return(Win:alt+Enter)启动或⌘+⇧+A(Win:ctrl+shift+A)启动并搜索到它。\n\n![Editing JSON](http://michaelevans.org/images/2016/01/06/fragment_editor.png)\n\n###[CHECK REGEXP](https://xkcd.com/1171/)\n\n这非常类似于最后一个提示，但是如果你选择了一部分代码用于“RegExp”，就会显示一个便捷的测试正则表达式的浮动窗口。\n\n![Editing Regex](http://michaelevans.org/images/2016/01/06/reg_exp_1.png)\n\n![Valid Regex](http://michaelevans.org/images/2016/01/06/reg_exp_2.png)\n\n![Invalid Regex](http://michaelevans.org/images/2016/01/06/reg_exp_3.png)\n\n###SMART(ER) COMPLETION\n\n现在我肯定你已经使用了绝大部分的代码自动完成功能。按下⌥+Space(Win:Ctrl+Space)，IntelliJ/Android Studio弹出一个用以完成类、方法、字段名的属性列表，关键字会在列表范围内。但是可曾注意到这个建议好像是依据你输入的字符，而不是表达式实际的类型？好像下边这样：\n\n![Autocomplete](http://michaelevans.org/images/2016/01/06/basic_autocomplete.png)\n\n好了，如果你通过按下⌥+⇧+Space(Win:Ctrl+Shift+Space)使用代码完成，你会看到一个适用于当前表达式内容的的列表。下边的例子，你只会得到用于`BufferedReader`构造函数中的返回`Reader`类型的列表。\n\n![Better Autocomplete](http://michaelevans.org/images/2016/01/06/smart_autocomplete.png)\n\n更酷的是，你可以在其他情况下也可以用这个快捷键，IntelliJ会通过更深入的搜索（搜索静态方法，链式表达式等等）查找到更多提供给你的选项。\n\n![Chained Autocomplete](http://michaelevans.org/images/2016/01/06/chained_autocomplete.png)\n\n###自定义自己的提示和技巧\n\n另一个更酷的特性是Productivity Guide。它统计来你在IntelliJ使用情况，例如，你保存了多少按键或者痛过使用各种各样的快捷键你避免了多少可能存在的bug。也很有助于发现你可能不知道的特性；你可以滚动列表中的未使用过的特性看一看你有哪些错过了！要找到productivity guide，通过`Help -> Productivity Guide`。\n\n![Invalid Regex](http://michaelevans.org/images/2016/01/06/productivity_guide.png)\n\n奖金回合－仅INTELLIJ 15\n\n你知道IntelliJ有[它自己的REST client](https://www.jetbrains.com/idea/help/testing-restful-web-services.html)？超好用的可以不用像[Paw](https://luckymarmot.com/paw)或者[Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en)来测试我们的API。\n\n###有任何其他提示或技巧？让我知道吧！\n\n[^intention action]: [Intention Actions](https://www.jetbrains.com/idea/help/intention-actions.html)是在弹出菜单中显示的一些允许你快速修复例如没有导入classes等错误的一些建议。"
  },
  {
    "path": "issue-39/Android dex分包导致的App启动速度下降.md",
    "content": "#Android Multidex导致的App启动缓慢\n---\n\n> * 原文链接 : [Android’s multidex slows down app startup](https://medium.com/groupon-eng/android-s-multidex-slows-down-app-startup-d9f10b46770f#.wzy9fijs5)\n* 原文作者 : [mmadev](https://medium.com/@mmadev)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [desmond1121](https://github.com/desmond1121) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成\n\nAndroid社区中多次说到了dex包的65536方法数限制，现在针对这个问题的解决方法是dex分包(Multidexing)。虽然这是google提出的一个很好的解决办法，但是我注意到了它对App的启动速度影响很严重（这个问题现在还没有被Android社区所重视）。所以我写下了这篇文章，写给那些想实现dex分包但是不知道它的这个缺点或者已经实现了dex分包但是想看看它性能的开发者。\n\n##背景\n\n简单来说，构建Android应用时这样一个流程：Java代码=>.class文件（与依赖库）=>独立的.dex文件。这个.dex文件最后与资源文件一起打包成.apk文件，这就是你最后从应用商店下载下来的安装文件。具体可以参考[这里](https://github.com/dogriffiths/HeadFirstAndroid/wiki/How-Android-Apps-are-Built-and-Run)。\n\n对编译过程的一个限制就是在dex文件中系统允许的方法总数最多为65536。早期的Android开发者通过混淆来减少不必要的代码，从而避免方法数超过限制的问题。然而混淆在这方面能做到的事情比较有限，而且它只是延缓了方法数量过限的时间，并没有根治。所以后来Google在support library里面出了一个解决方案：dex分包（Multidex），这个方案可以很方便地处理方法数超过限制的问题，但是就如同我之前所说，它会极大地延缓App的启动速度。\n\n##使用Multidex\n\nMultidex现在是一个成熟的、文档丰富的工具。我强烈推荐通过[官网流程](http://developer.android.com/tools/building/multidex.html#mdex-gradle)来在工程中实现Multidex，你也可以在[我的github](https://github.com/mmadev/multidex-sample)。\n\n###NoClassDefFoundError?!\n\n当你在项目中使用了multidex的时候，你的app可能会产生`java.lang.NoClassDefFoundError`异常。这意味着你的app在启动的时候没有找到含有指定类的class文件。Android的Gradle插件首先需要SDK build tools 21.1及以上才支持multidex，它会在混淆工程之后列出一个主dex文件中包含的类的清单（`[buildDir]/intermediates/multi-dex/[buildType]/maindexlist.txt`）。但这里面可能没有包含所有在App启动时需要加载的类，这时启动App就会抛出这个异常。\n\n###如何解决？\n\n要解决这个问题，你要列出一份启动App时需要加载的类的清单，并告诉编译器这些类要保留在主dex文件中。你可以这么做：\n\n- 在工程文件夹下创建一个`multidex.keep`文件\n- 将`java.lang.NoClassDefFoundError`异常中报出的类列到`multidex.keep`中。（不要修改`maindexlist.txt`，这个文件每次都会重新生成，改动无效）\n- 在使用混淆的模块的gradle脚本中天下如下代码，它会每次在编译的时候将`multidex.keep`文件中的内容添加到`maindexlist.txt\"中。\n\n```\n    android.applicationVariants.all { variant ->\n        task \"fix${variant.name.capitalize()}MainDexClassList\" << {\n            logger.info \"Fixing main dex keep file for $variant.name\"\n            File keepFile = new File(\"$buildDir/intermediates/multi-dex/$variant.buildType.name/maindexlist.txt\")\n\n            keepFile.withWriterAppend { w ->\n                // Get a reader for the input file\n                w.append('\\n')\n                new File(\"${projectDir}/multidex.keep\").withReader { r ->\n                    // And write data from the input into the output\n                    w << r << '\\n'\n                }\n                logger.info \"Updated main dex keep file for ${keepFile.getAbsolutePath()}\\n$keepFile.text\"\n            }\n        }\n    }\n\n    tasks.whenTaskAdded { task ->\n        android.applicationVariants.all { variant ->\n            if (task.name == \"create${variant.name.capitalize()}MainDexClassList\") {\n                task.finalizedBy \"fix${variant.name.capitalize()}MainDexClassList\"\n            }\n        }\n    }\n```\n\n##Multidex对应用启动速度造成的影响。\n\n如果你使用了multidex，那么你需要注意它可能会加长App的启动速度。我们通过追踪App的启动时间（从点击icon到所有的图标被下载、显示的时间）。当使用multidex后，4.4及以下的系统启动时间会加长大约15%，你可以在Carlos Sessa的[这篇文章](https://medium.com/@Macarse/lazy-loading-dex-files-d41f6f37df0e#.6sa62ufed)中了解到更多信息。\n\n由于5.0以上的Android系统采用了ART运行时，它本身就支持multidex的加载，所以5.0以上系统影响较小。但是5.0以下的系统将会在加载主dex之外的类时会有比较明显的延迟。\n\n##解决multidex带来的启动时间影响\n\n在App启动到所有图片加载到屏幕上之间的这段时间内，有很多类既没有被混淆，也不在主dex文件中。我们要怎么知道哪些类已经被App加载了呢？\n\n幸运的是，在`ClassLoader`中有一个`findLoadedClass()`方法，我们的解决办法就是在启动结束的时候查看有没有不在主dex文件中却依然在启动阶段被加载的类，将它们添加到之前的`multidex.keep`文件中，手动将其加入主dex文件：\n\n- 将下面这个类添加到代码中，运行其中的`getLoadedExternalDexClasses`查看是否有一些副dex中的类在启动结束后被加载了；\n- 将上一步检测到的类添加到我们的`multidex.keep`文件中，重新编译。\n\n```\n    public class MultiDexUtils {\n        private static final String EXTRACTED_NAME_EXT = \".classes\";\n        private static final String EXTRACTED_SUFFIX = \".zip\";\n     \n        private static final String SECONDARY_FOLDER_NAME = \"code_cache\" + File.separator +\n                \"secondary-dexes\";\n     \n        private static final String PREFS_FILE = \"multidex.version\";\n        private static final String KEY_DEX_NUMBER = \"dex.number\";\n     \n        private SharedPreferences getMultiDexPreferences(Context context) {\n            return context.getSharedPreferences(PREFS_FILE,\n                    Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB\n                            ? Context.MODE_PRIVATE\n                            : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);\n        }\n     \n        /**\n         - get all the dex path\n         *\n         - @param context the application context\n         - @return all the dex path\n         - @throws PackageManager.NameNotFoundException\n         - @throws IOException\n         */\n        public List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {\n            final ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);\n            final File sourceApk = new File(applicationInfo.sourceDir);\n            final File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);\n     \n            final List<String> sourcePaths = new ArrayList<>();\n            sourcePaths.add(applicationInfo.sourceDir); //add the default apk path\n     \n            //the prefix of extracted file, ie: test.classes\n            final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;\n            //the total dex numbers\n            final int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);\n     \n            for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {\n                //for each dex file, ie: test.classes2.zip, test.classes3.zip...\n                final String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;\n                final File extractedFile = new File(dexDir, fileName);\n                if (extractedFile.isFile()) {\n                    sourcePaths.add(extractedFile.getAbsolutePath());\n                    //we ignore the verify zip part\n                } else {\n                    throw new IOException(\"Missing extracted secondary dex file '\" +\n                            extractedFile.getPath() + \"'\");\n                }\n            }\n     \n            return sourcePaths;\n        }\n     \n        /**\n         - get all the external classes name in \"classes2.dex\", \"classes3.dex\" ....\n         *\n         - @param context the application context\n         - @return all the classes name in the external dex\n         - @throws PackageManager.NameNotFoundException\n         - @throws IOException\n         */\n        public List<String> getExternalDexClasses(Context context) throws PackageManager.NameNotFoundException, IOException {\n            final List<String> paths = getSourcePaths(context);\n            if(paths.size() <= 1) {\n                // no external dex\n                return null;\n            }\n            // the first element is the main dex, remove it.\n            paths.remove(0);\n            final List<String> classNames = new ArrayList<>();\n            for (String path : paths) {\n                try {\n                    DexFile dexfile = null;\n                    if (path.endsWith(EXTRACTED_SUFFIX)) {\n                        //NOT use new DexFile(path), because it will throw \"permission error in /data/dalvik-cache\"\n                        dexfile = DexFile.loadDex(path, path + \".tmp\", 0);\n                    } else {\n                        dexfile = new DexFile(path);\n                    }\n                    final Enumeration<String> dexEntries = dexfile.entries();\n                    while (dexEntries.hasMoreElements()) {\n                        classNames.add(dexEntries.nextElement());\n                    }\n                } catch (IOException e) {\n                    throw new IOException(\"Error at loading dex file '\" +\n                            path + \"'\");\n                }\n            }\n            return classNames;\n        }\n     \n        /**\n         - Get all loaded external classes name in \"classes2.dex\", \"classes3.dex\" ....\n         - @param context\n         - @return get all loaded external classes\n         */\n        public List<String> getLoadedExternalDexClasses(Context context) {\n            try {\n                final List<String> externalDexClasses = getExternalDexClasses(context);\n                if (externalDexClasses != null && !externalDexClasses.isEmpty()) {\n                    final ArrayList<String> classList = new ArrayList<>();\n                    final java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod(\"findLoadedClass\", new Class[]{String.class});\n                    m.setAccessible(true);\n                    final ClassLoader cl = context.getClassLoader();\n                    for (String clazz : externalDexClasses) {\n                        if (m.invoke(cl, clazz) != null) {\n                            classList.add(clazz.replaceAll(\"\\\\.\", \"/\").replaceAll(\"$\", \".class\"));\n                        }\n                    }\n                    return classList;\n                }\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return null;\n        }\n    }\n```\n##实验结果\n\n这里是我们得出的实验结果。蓝色柱是不使用multidex时的启动时间，红色柱是使用multidex时的启动时间，你可以看到两者之间的巨大差距，仅仅是因为我们使用了multidex而已。之后我们进行了上述优化改进，得出的启动时间是绿色柱，你可以看到它回到了原先的启动速度，甚至比原先更快。你可以尝试一下，它会为你的app性能带来提升。\n\n![multidex](https://cdn-images-1.medium.com/max/1000/1*n8CB6MRbJDEvXVq0VBwanQ.png)\n\n##最后的话\n\n>你可以这么做不代表你必须这么做。\n\n你应该将multidex认为是一种走投无路的解决办法，因为它会严重影响App的启动速度，并且还要维护额外的代码来解决奇怪的异常（`java.lang.NoClassDefFoundError`）。当达到了65536的方法数限制时，我们不应该先想到multidex因为它会带来性能影响。我们检查工程并发现了很多没有用处的代码，进行删除、重构。仅仅当没有别的办法时，才引入multidex，并且从此我们都会十分注意代码质量、标准。与其直接使用multidex，不如花时间让你的代码变得简洁、高效，重构你的代码使其不要超过65536的方法数限制。"
  },
  {
    "path": "issue-39/FragmentTransaction 与 Activity 状态丢失？.md",
    "content": "FragmentTransaction 与 Activity 状态丢失？\n---\n\n> * 原文链接 : [Fragment Transactions & Activity State Loss](http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html)\n* 原文作者 : [Alex Lockwood](https://google.com/+AlexLockwood)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成\n\n\n下面的栈轨迹和异常信息自 Android 3.0（Honeycomb） 版本发布依赖就困扰着 StackOverflow：\n\n```\njava.lang.IllegalStateException: Can not perform this action after onSaveInstanceState\n    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)\n    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)\n    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)\n    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)\n```\n\n这篇博文将会解释这些异常在何时被抛出以及抛出的原因，在文章的结尾还会给各位阅读本文的开发者一些建议，以确保大家开发的应用不会再因为这些原因而崩溃。\n\n##为什么会抛出该异常？\n\n异常产生的原因是：开发者尝试在 Activity 的某个状态被存储后提交 FragmentTransaction 事务给 FragmentManager 处理，因此使得 Activity 保存的状态是不完整的（缺少了这个事务）。在了解其中细节之前，不妨先看看 `onSaveInstanceState()` 被调用时会进行哪些操作。就像我在上一篇有关 Binders 和 Death Recipients 的博文中所说，Android 应用在 Android 运行时环境中大多数时候像砧板上的肉，任 Android 系统宰割。因为 Android 系统具有在任意时间结束进程以释放内存空间的权限，而后台 Activity 在这种情况下只能被强制杀死而且几乎不会有 warning。为了确保此类不稳定的行为能让用户知晓/处理，Android 框架层提供了 `onSaveInstanceState()` 方法在 Activity 被结束前保存 Activity 的状态，以解决 Activity 的易被杀死性。当被保存的状态被恢复，无论 Activiy是否被杀死过，用户几乎不会察觉到前后台应用切换带来的改变。\n\n当框架层调用 `onSaveInstanceState()`，该方法通过参数接收到一个 Bundle 对象给 Activity 以存储其状态，而 Activity 就会将它此时的状态，如：Dialog，Fragment，View 这类内容存储进去。当该方法返回，系统通过 Parcel 机制打包该 Bundle 对象，通过 Binder 接口安全地存储在系统的服务端进程。在之后，当系统接收到重新创建该 Activity 的请求并准备这样做时，就会将该 Bundle 对象取出并传递给应用，以恢复 Activity 之前的状态。\n\n那为什么会抛出异常呢？问题在于：这些 Bundle 对象可以看作是 Activity 在调用 `onSaveInstanceState()` 时的快照，除此以外不存储其他内容。也就意味着当你在 `onSaveInstanceState()` 方法之后调用 `FragmentManager.commit()` 提交某个 FragmentTransaction 事务时，与该事务相关的内容是不会被存储到 Bundle 对象中的，也就使得 Activity 快照中没有相应的内容。从用户的角度来看，该事务就会被丢失，也就导致了 UI 状态的丢失。为了保护用户体验，Android 不惜牺牲一切代价以避免状态丢失。所以，只要出现了状态丢失的情况，就会抛出 IllegalStateException 异常。\n\n##何时会抛出该异常？\n\n如果你曾遇到这个异常，你可能会注意到在不同 Android 版本的系统中异常抛出的时机是不同的。例如，在老版本的设备中抛出该异常的频率会低一些，又或者是当你的应用更多地使用 android.support 库而不是框架提供的类时。平台版本带来的不一致会让人觉得 android.support 库很容易出现 Bug，难以信赖。但这些直觉，往往又是错的。\n\n存在平台版本不一致的原因是：Google 在 Android 3.0（Honeycomb） 版本中对 Activity 的生命周期作了些许修改。在 3.0 之前，Activity 在 `onPause()` 之前不被看作是可被杀死的。在 3.0 及之后，Activity 只在 `onStop` 之后被看作是可被杀死的，意味着 `onSaveInstanceState()` 将会在 `onStop()` 之前被调用，而不是在 `onPause()` 被调用前立刻被调用。其中的区别可见下表：\n\n|--------|---------|--------|\n| |pre-Honeycomb|post-Honeycomb|\n|Activities can be killed before onPause()?|NO|NO|\n|Activities can be killed before onStop()?|YES|NO|\n|onSaveInstanceState(Bundle) is guaranteed to be called before...|onPause()|onStop()\n\n因为对 Activity 生命周期进行的这一点点改变，android.support 库有时就需要根据平台版本修改它的某些行为了。例如，在大于等于 3.0 的设备中，只要 `FragmentManager.comit()` 方法在 `onSaveInstanceState()` 就会抛出异常以警告开发者状态丢失的情况。然而，在低于 3.0 版本中抛出此异常的条件却很苛刻。Android 开发团队必须作出妥协：为了平台兼容性，老版本设备需要地方可能在 onPause() 和 onStop() 周期之间可能产生的状态丢失。android.support 库在 3.0 版本前和 3.0 版本后的行为可总结如下表：\n\n| |pre-Honeycomb|post-Honeycomb|\n|commit()|before onPause()|OK|OK\n|commit()|between onPause() and onStop()| STATE LOSS|OK\n|commit()|after onStop()|EXCEPTION|EXCEPTION\n\n##如何避免该异常？\n\n当你理解了抛出该异常的原因，避免 Activity 状态丢失就变得容易多了。如果对此的了解比本博文更深入，希望你明白 android.support 库是如何工作的，以及避免状态丢失的重要性。下面的建议希望每一个开发者都能牢记在心，并且在使用 FragmentTransaction 时都能想起来：\n\n- 在 Activity 生命周期方法中提交 FragmentTransaction 时一定要谨慎。一般应用只会在 `onCreate()` 方法被调用时中提交 FragmentTransaction 事务，或者响应用户输入时，这种情况下是不会出现状态丢失问题的。但是，当你的 FragmentTransaction 事务在其他 Activity 生命周期的方法中被处理时，如：`onActivityResult()`, `onStart()`, and `onResume()`，出现异常的可能性就增大了。例如，你不能在 FragmentActivity 的 onResume() 方法中提交 FragmentTransaction 事务，因为该方法在某些情况下方法可能会在 Activity 的状态被恢复前被调用（文档有详细的信息）。如果应用需要在 Activity 的生命中除 `onCreate()` 以外的方法中提交 FragmentTransaction 事务，可以把相关的操作放到 FragmentActivity 的 onResumeFragments 或者 Activity 的 `onPostResume()` 方法中完成。这两个方法确保在 Activity 恢复到初始状态后被调用。（相关例子可以看我在 StackOverflow 中有关在 `Activity.onActivityResult()` 方法中响应 FragmentTransaction 事务的的回答）\n\n- 避免在异步回调接口中处理 FragmentTransaction 事务。这里包含了常用的 `AsyncTask.onPostExecute()` 方法和 `LoaderManager.LoaderCallbacks.onLoadFinished()` 方法。在这些方法中处理 FragmentTransaction 事务的问题在于：当这些方法被调用，它们无法知晓 Activity 当前处于生命周期的哪个状态。例如，考虑以下的情况：\n\n1. Activity 执行了一个 AsyncTask\n2. 用户按了 Home 键，使 Activity 的 `onSaveInstanceState()` 和 `onStop()` 方法被调用\n3. AsyncTask 的异步任务完成，并且调用了 `onPostExecute()` 方法，却无法意识到 Activity 已经被停止\n4. FragmentTransaction 在 `onPostExecute()` 方法中被提交，导致异常被抛出\n\n通常来说，在这些情况下避免此类异常最好的办法是避免在所有异步回调方法中提交 FragmentTransaction 事务。Google 开发者看起来也同意这样的做法。根据这篇博文在 Android 开发者群体里的讨论，大家都认可 UI 中的转换大多来源于 FragmentTransaction 事务的提交，而这些操作往往又通过异步回调接口方法完成，导致对用户体验的破坏。如果你的应用需要在这些回调方法中处理 FragmentTransaction 事务，而且没有其他简单的办法能够保证回调不会在 `onSaveInstanceState()` 方法只后被调用，你可能需要使用 `commitAllowingStateLoss()` 方法，而且自行设置处理可能发生的状态丢失的逻辑。（相关内容可以看 [憋说话，点我！](http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused) 和 [憋说话，点我！](http://stackoverflow.com/questions/7992496/how-to-handle-asynctask-onpostexecute-when-paused-to-avoid-illegalstateexception)）\n\n使用 `commitAllowingStateLoss()` 方法可以看作压箱底的大招了……调用 `commit()` 和 `commitAllowingStateLoss()` 方法的唯一区别在于：即使发生了状态丢失，后者也不会抛出异常。通常开发者都不需要使用该方法，因为使用它就代表着存在状态丢失发生的可能性。更好的解决方案是，让应用每一次调用 `commit()` 方法都能确保在 Activity 状态被存储之前被调用，这样也会带来更好的用户体验。除非状态丢失的可能性是确定且无法避免的，否则 `commitAllowingStateLoss()` 不应该被调用。\n\n希望这些建议能帮你解决之前/现在遇到的这个异常。如果你还是不知道该怎么办的话，可以到 StackOverflow 上面提个问题，然后在评论区放上链接，我有空的话会去看看的哦 ：）。\n\n感谢各位的阅读，如果有什么疑问可以在下面留言。如果大家觉得我写的好的话不妨给我一个赞，而且在 Google+ 分享给你的小伙伴！\n"
  },
  {
    "path": "issue-39/RxJava中repeatWhen 和 retryWhen 操作符的解释.md",
    "content": "RxJava中repeatWhen 和 retryWhen 操作符的解释\n---\n\n> * 原文链接 : [RxJava's repeatWhen and retryWhen, explained](http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/)\n* 原文作者 : [Dan Lew](https://github.com/dlew)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [lowwor](https://github.com/lowwor) \n* 校对者:  \n* 状态 :  完成 \n\n\n[```repeatWhen```][1] and [```retryWhen```][2] are fairly baffling at first glance. For starters, they are serious contenders for \"most confusing marble diagrams\":\n\n[```repeatWhen```][1] 和 [```retryWhen```][2] 操作符在第一眼看上去的时候，就会让人觉得相当困惑。 对于初学者来说，这两个操作符的marble图算是最难看懂的几个之一了。 \n\n![retryWhen][3]\n\nThey're useful operators: they allow you to conditionally resubscribe to ```Observables``` that have terminated. I recently studied how they worked and I want to try to explain them (since it took me a while to grasp).\n\n这两个都是非常有用的操作符：它们让你可以有条件地重新订阅（resubscribe）已经终止了的```Observables```。我最近一直在研究它们的工作原理，我将试着去尽可能地把它们解释清楚（因为我也花了不少时间才算掌握它们）。\n\nRepeat vs. Retry\n----------------\n\nFirst of all, what is the difference between their simpler counterparts: [```repeat()```][4] and [```retry```][5]? This part is simple: the difference is which terminal event causes a resubscription.\n\n首先，我们先来看看它们所对应的简单版本的操作符：[```repeat()```][4] 和 [```retry```][5]的区别?这部分很简单，主要的区别在于触发重新订阅（resubscription）的终止事件（terminal event）的类型不同。\n\n**```repeat()``` resubscribes when it receives ```onCompleted()```.**\n\n**```repeat()``` 在接收到```onCompleted()```时重新订阅.**\n\n**```retry()``` resubscribes when it receives ```onError()```.**\n\n**```retry()``` 在接收到 ```onError()```时重新订阅。**\n\n\nHowever, these simple versions can leave something to be desired. What if you want to delay resubscription by a couple seconds? Or examine the error to determine if you should resubscribe? That's where ```repeatWhen``` and ```retryWhen``` step in; they let you provide custom logic for the retries.\n\n但是，如果我们的需求比这更加复杂的话，比如说，如果你想将重新订阅的时间往后延迟个几秒怎么办？\n或者想通过判断错误（error）的类型来决定是否需要重新订阅。这时候我们就需要用到```repeatWhen``` 和 ```retryWhen```了，它们可以允许我们为重试（retry）添加自定义的逻辑。\n\nNotification Handler\n--------------------\n\nYou provide the retry logic through a function known as the ```notificationHandler```. Here's the method signature for ```retryWhen```:\n\n你需要通过一个function也就是```notificationHandler```来提供重试的逻辑，下面是```retryWhen```的方法签名（method signature）\n\n```retryWhen(Func1<? super Observable<? extends java.lang.Throwable>,? extends Observable<?>> notificationHandler)  ```\n\nThat's a mouthful! I found this signature hard to parse since it's a mess of generics.\n\n有点拗口，我也觉得这个签名（signature）很难去解析，因为它充满了各种泛型。\n\nSimplified, it consists of three parts:\n\n简单来说，它由三个部分组成\n\n1. The ```Func1``` is a factory for providing your retry logic.\n2. The input is an ```Observable<Throwable>```.\n3. The output is an ```Observable<?>```.\n\n<!--你看不到我-->\n\n1. ```Func1``` 是一个工厂（factory），用来提供你的重试逻辑。\n2. 输入是一个 ```Observable<Throwable>```。\n3. 输出是一个 ```Observable<?>```。\n\n\nLet's look at the last part first. The emissions of the ```Observable<?>``` returned determines whether or not resubscription happens. If it emits ```onCompleted``` or ```onError``` then it doesn't resubscribe. But if it emits ```onNext``` then it does (regardless of what's actually in the ```onNext```). That's why it's using the wildcard for its generic type: it's just the notification (next, error or completed) that matters.\n\n我们先来看一下最后一部分。返回的```Observable<?>```所发出的事件（emissions）决定了要不要重新进行订阅（resubscription）。如果它发出（emit）的是```onCompleted``` 或者 ```onError``` ，那么将不会重新订阅。如果它发出的是```onNext```，那么将重新订阅（无论所发出的```onNext```里的东西是什么）。这也是为什么它使用了通配符作为它的泛型类型：起作用的是这个事件（notification）本身。\n\nThe input is an ```Observable<Throwable>``` that emits anytime the source calls ```onError(Throwable)```. In other words, it triggers each time you need to decide whether to retry or not.\n\n输入是一个```Observable<Throwable>```，每当源（source）调用（call）```onError(Throwable)```的时候，这个Observable就会发出事件。换句话说，就是每当需要你决定是否重新订阅的时候，它就有事件发出。\n\n\nThe factory ```Func1``` is called on subscription to setup the retry logic. That way, when ```onError``` is called, you've already defined how to handle it.\n\n工厂 ```Func1```在订阅时（subscription）被调用，用以初始化重试逻辑。这样一来，每当 ```onError``` 被调用的时候，你就已经定义好了如何去处理这个事件了。\n\nHere's an example wherein we resubscribe to the source if the ```Throwable``` is an ```IOException```, but otherwise do not:\n\n下面是一个例子，在这个例子中，当```Throwable``` 是一个```IOException```的时候，我们将会去重新订阅源（source），其他情况下则不。\n```\nsource.retryWhen(errors -> errors.flatMap(error -> {  \n// For IOExceptions, we  retry\nif (error instanceof IOException) {\nreturn Observable.just(null);\n}\n\n// For anything else, don't retry\nreturn Observable.error(error);\n})\n)\n```\nEach error is flatmapped so that we can either return ```onNext(null)``` (to trigger a resubscription) or ```onError(error)``` (to avoid resubscription).\n\n每一个错误（error）都被flatmap了，所以我们可以选择在其中返回 ```onNext(null)```（来触发重新订阅）或者```onError(error)``` （不再重新订阅）。\n\nObservations\n------------\n\nHere's some key points about ```repeatWhen``` and ```retryWhen``` which you should keep in mind.\n\n下面是一些关于```repeatWhen``` 和 ```retryWhen``` 的关键点，我们要牢牢记住它们\n\n- **```repeatWhen``` is identical to ```retryWhen```, only it responds to ```onCompleted``` instead of ```onError```.** The input is ```Observable<Void>```, since ```onCompleted``` has no type.\n\n-  **```repeatWhen``` 跟```retryWhen```几乎是一样的,唯一的区别在于，```repeatWhen```响应的是 ```onCompleted``` ，而不是 ```onError```。** 而因为 ```onCompleted``` 是没有类型的，所以它的输入是一个 ```Observable<Void>```。\n\n- **The ```notificationHandler``` (i.e. ```Func1```) is only called once per subscription.** This makes sense as you are given an ```Observable<Throwable>```, which can emit any number of errors.\n\n- **```notificationHandler``` (也就是 ```Func1```)在每一个subscription中只会被调用一次** 这也说的通，因为你拿到的是一个 ```Observable<Throwable>```, 它可以发出任意数目的错误（error）。\n\n- **The output ```Observable``` has to use the input ```Observable``` as its source.** You must react to the Observable<Throwable> and emit based on it; you can't just return a generic stream. \n\n- **输出的 ```Observable``` 必须要利用到输入的```Observable``` 。** 你必须响应（react）输入的Observable<Throwable> ，并在它的基础上发出（emit）东西，你不能仅仅返回一个泛型的流（generic stream）\n\nIn other words, you can't do something like ```retryWhen(errors -> Observable.just(null))```. Not only will it not work, it completely breaks your sequence. You need to, at the very least, return the input, like ```retryWhen(errors -> errors)``` (which, by the way, duplicates the logic of the simpler ```retry()```).\n\n换句话说，你不能像```retryWhen(errors -> Observable.just(null))```这样做。不只是因为它不会按照你想象的那样去运行，而且它也完全打破了你的序列（sequence）。你至少也得像这样```retryWhen(errors -> errors)```，直接把输入返回去（顺便提一下，这跟```retry()```的逻辑是一样的）\n\n- The ```Observable``` input only triggers on terminal events (```onCompleted``` for ```repeatWhen``` / ```onError``` for ```retryWhen```). It doesn't receive any of the onNext notifications from the source, so you can't examine the emitted data to determine if you should resubscribe. If you want to do that, you have to add an operator like ```takeUntil()``` to cut off the stream.\n\n- 输入的 ```Observable``` 只有在遇到终止事件（terminal event）时才会被触发 ( 对于 ```repeatWhen```是```onCompleted``` /  对于 ```retryWhen```是```onError```)。 它不会接收任何来自源的onNext通知（notification）, 所以你不能通过校验发出的数据去决定是否重新订阅。如果你想这样做的话，你要加入像```takeUntil()```之类的操作符来截断这个流（stream）。\n\nUses\n----\n\nNow that you (vaguely) understand ```retryWhen``` and ```repeatWhen```, what sort of logic can you stick in the ```notificationHandler```?\n\n现在，你已经（大概）理解了```retryWhen``` 和 ```repeatWhen```，那么我们可以在```notificationHandler```里加入哪些逻辑呢？\n\n**Poll for data periodically using ```repeatWhen``` + ```delay```:**\n\n**利用```repeatWhen``` + ```delay```实现服务器轮询:**\n\n```source.repeatWhen(completed -> completed.delay(5, TimeUnit.SECONDS))  ```\n\nThe source isn't resubscribed until the ```notificationHandler``` emits ```onNext()```. Since delay waits some time before emitting ```onNext()```, this has the effect of delaying resubscription so you can avoid constantly polling data.\n\n只有在```notificationHandler``` 发出 ```onNext()```事件之后，源（source）才会被重新订阅。因为 delay 会让 ```onNext()```先等待一段时间再发出, 这也就实现了对重新订阅的延时， 从而避免了一直不停地去查询服务器的数据。\n\n**Alternatively, delay resubscription with ```flatMap``` + ```timer```:**\n\n**另一个方案，利用 ```flatMap``` + ```timer```来实现延迟重新订阅（resubscription）:**\n\n```source.retryWhen(errors -> errors.flatMap(error -> Observable.timer(5, TimeUnit.SECONDS)))  ```\n\nThis alternative is useful when combined with other logic, such as...\n\n这个方案在需要和别的逻辑结合起来使用时很有用。比如说...\n\n\n**Resubscribe a limited number of times with ```zip``` + ```range```:**\n\n**利用 ```zip``` + ```range```来限制重试的次数：**\n\n```source.retryWhen(errors -> errors.zipWith(Observable.range(1, 3), (n, i) -> i))```  \n\nThe end result is that each error is paired with one of the outputs of ```range```, like so:\n\n这样做最后的结果就是每一个错误（error）都会与 ```range```的一个输出成对结合起来, 像这样:\n```\nzip(error1, 1) -> onNext(1)  <-- Resubscribe  \nzip(error2, 2) -> onNext(2)  <-- Resubscribe  \nzip(error3, 3) -> onNext(3)  <-- Resubscribe  \nonCompleted()                <-- No resubscription  \n```\nSince ```range(1, 3)``` runs out of numbers on the fourth error, it calls ```onCompleted()```, which causes the entire zip to complete. This prevents further retries.\n\n在第四个错误的时候，因为```range(1, 3)```里所有数字都用掉了，所以它会调用```onCompleted()```，让整个zip结束掉（complete）。从而避免了再去重试。\n\n**Combine the above for limited retries with variable delays:**\n\n将上面的重试次数限制、重试延迟时间结合起来\n\n```\nsource.retryWhen(errors ->  \nerrors\n.zipWith(Observable.range(1, 3), (n, i) -> i)\n.flatMap(retryCount -> Observable.timer((long) Math.pow(5, retryCount), TimeUnit.SECONDS))\n);\n```\n```flatMap``` + ```timer``` is preferable over ```delay``` in this case because it lets us modify the delay by the number of retries. The above retries three times and delays each retry by ```5 ^ retryCount```, giving you exponential backoff with just a handful of operators!\n\n在这种情况下， 相较于```delay```，我更加推荐使用```flatMap``` + ```timer```，因为后者可以让我们根据重试的次数调整延时时间。上面的代码会进行三次重试，并且每一次重试都会有```5 ^ retryCount```的延时，寥寥几个操作符就实现了指数退避（exponential backoff）的功能。\n\n\n[1]: http://reactivex.io/RxJava/javadoc/rx/Observable.html#repeatWhen%28rx.functions.Func1%29\n[2]: http://reactivex.io/RxJava/javadoc/rx/Observable.html#retryWhen%28rx.functions.Func1%29\n[3]: https://danlew.ghost.io/content/images/2016/01/retryWhen-small.png\n[4]: http://reactivex.io/RxJava/javadoc/rx/Observable.html#repeat%28%29\n[5]: http://reactivex.io/RxJava/javadoc/rx/Observable.html#retry%28%29"
  },
  {
    "path": "issue-39/在滚动列表中播放视频.md",
    "content": "在滚动列表中播放视频\n---\n\n> * 原文链接 : [Implementing video playback in a scrolled list (ListView & RecyclerView)](https://medium.com/@v.danylo/implementing-video-playback-in-a-scrolled-list-listview-recyclerview-d04bc2148429#.giv0pte0s)\n* 原文作者 : [Danylo Volokh](https://medium.com/@v.danylo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成 \n\n\n\n本篇博文将会介绍如何实现在列表中播放视频，具体效果参见：Facebook，Instagram 或 Magiston：\n\n###Facebook:\n\n![](http://img.my.csdn.net/uploads/201601/30/1454131534_4403.gif)\n\n###Magisto:\n\n![](http://img.my.csdn.net/uploads/201601/30/1454131707_8101.gif)\n\n###Instagram:\n\n![](http://img.my.csdn.net/uploads/201601/30/1454131742_4068.gif)\n\n博文内容基于此Github 项目：[VideoPlayerManager](https://github.com/danylovolokh/VideoPlayerManager).\n\n博文中涉及的所有代码和范例都在该项目中，所以本篇博文不会详细讲解每一个细节。如果有人真的想知道实现的机制，最好是下载源码，结合 IDE 边看源码边阅读本文。不过就算你不结合源码，光看我在这里说的内容，也能理解的七七八八了。\n\n##两个问题\n\n为了实现这个功能，我们需要解决以下两个问题：\n\n1. 管理视频播放。在 Android 框架层中我们可以利用 MediaPlayer 和 SurfaceView 来播放视频，但这种实现方式有许多缺点。我们不能在列表中使用一般的 VideoView，因为 VideoView 是 SurfaceView 的子类，而 SurfaceView 不具有 UI 异步缓存。而这会导致我们无法在滚动时记录视频到底播放到哪个时间点。TextureView 具有 UI 异步缓存，但在 Android SDK 15 没有 VideoView 的实现方式是借助 TextureView 来完成的。因此，我们需要一个 TextureView 的子类与 MediaPlayer 协作完成视频播放的功能。当然了，MediaPlayer 中的所有方法（准备，开始，停止等等……）几乎都是调用与硬件交互的内核层代码。而硬件往往意味着复杂，如果我们需要完成的任何工作的耗时超过 16ms（一般都会），就会看到滞后的视频列表。这也是为什么需要在后台线程中调用它们的原因。\n\n2. 我们还需要知道滚动列表中的哪一个 View 要被激活（播放视频），所以我们还需要追踪用户的滚动行为并定义可见域最大的 View。\n\n##管理视频播放\n\n我们希望提供以下功能：\n\n假设某个视频正在播放，此时用户滚动了列表使得列表中某个子项目的可见域大于正在播放视频的子项目的可见域，这样我们就需要停止正在播放的视频，并开始播放新的视频（可见域更大的子项目对应的视频）。\n\n![](http://img.my.csdn.net/uploads/201601/30/1454131709_2495.gif)\n\n##VideoPlayerView\n\n我们要做的第一件事就是以 TextureView 为父类实现 VideoView。而且我们在滚动列表中不能使用系统的 VideoView，因为用户在视频播放的过程中滚动列表的话，视频渲染就会乱掉。\n\n我把这部分工作分为几个部分：\n\n1. 创建继承于 TextureView 的 ScalableTextureView，它能够调整 SurfaceTexture（正在播放视频的表面结构中）而且提供了一些类似于 ImageView 缩放类型的选项。\n\n```java\npublic enum ScaleType {\n    CENTER_CROP, TOP, BOTTOM, FILL\n}\n```\n\n2. 创建 ScalableTextureView 的子类 VideoPlayerView，它包含所有与 MediaPlayer 相关的功能。即，这是一个封装了 MediaPlayer 并提供与 VideoView 几近一致的 API 的自定义 View。VideoPlayerView 具有所有直接调用 MediaPlayer 的方法：setDataSource，prepare, start, stop, pause, reset, release。\n\n##ViedioPlayer 管理器及消息控制机制\n\n视频播放管理器与负责异步调用 MediaPlayer 方法的 MessageHandlerThread 协作。因为我们需要调用的方法，如：prepare(), start() 等等……与硬件直接相关，所以我们需要在一个独立的线程中完成这些调用。此外，当我们在 UI 线程中调用 MediaPlayer.reset() MediaPlayer 会发生一些奇怪的问题，使得该方法阻塞了将近 4 分钟！这也是我们不必须调用异步的 MediaPlayer.prepareAsync() 方法的原因，因为我们完全可以在另一个线程中调用 MediaPlayer.prepare() 方法，免得让这些奇怪的问题折腾自己。因此，我们将在一个独立的线程中异步完成所有任务。\n\n在开始新的视频播放任务的事件流中，下面是一些与 MediaPlayer 相关的步骤：\n\n1. 停止前一个播放任务，通过调用 MediaPlayer.stop() 方法完成。\n\n2. 通过调用 MediaPlayer.reset 方法重置 MediaPlayer。之所以需要这样做是因为：在滚动列表中，View 可能会被重用，而我们想要释放所有的资源。\n\n3. 通过调用 MediaPlayer.release() 方法释放 MediaPlayer 占用的资源。\n\n4. 清除 MediaPlayer 的实例，当 View 执行新的播放任务时，创建新的 MediaPlayer 实例。\n\n5. 为新的可见域最大的 View 创建 MediaPlayer 实例。\n\n6. 调用 MediaPlayer.setDataSource(String url) 为新的 MediaPlayer 设置数据源。\n\n7. 调用 MediaPlayer.prepare() 方法而不是 MediaPlayer.prepareAsync()。\n\n8. 调用 MediaPlayer.start()。\n\n9. 等待播放开始。\n\n以上所有行为都被包裹到 Message 中，交给一个独立的线程完成，例如这是一个 Stop 命令的 Message，它将调用 VideoPlayerView.stop()，实际上他调用的是 MediaPlayer.stop()。我们需要自定义 Message，因为我们可能需要设置当前状态，例如现在是正在停止，还是已经停止，或者是其他的状态……这有助于我们了解当前处理的 Message 是什么命令，如果我们需要用这个命令的话我们可以做什么，例如，开始一个新的播放任务。\n\n```java\n/**\n * This PlayerMessage calls {@link MediaPlayer#stop()} on the instance that is used inside {@link VideoPlayerView}\n */\npublic class Stop extends PlayerMessage {\n    public Stop(VideoPlayerView videoView, VideoPlayerManagerCallback callback) {\n        super(videoView, callback);\n    }\n\n    @Override\n    protected void performAction(VideoPlayerView currentPlayer) {\n        currentPlayer.stop();\n    }\n\n    @Override\n    protected PlayerMessageState stateBefore() {\n        return PlayerMessageState.STOPPING;\n    }\n\n    @Override\n    protected PlayerMessageState stateAfter() {\n        return PlayerMessageState.STOPPED;\n    }\n}\n```\n\n如果我们需要开始一个新的播放任务，我们只需要调用 VideoPlayerManager 的一个方法，它就会添加下列 Message 到 MessagesHandlerThread 中：\n\n```java\n// pause the queue processing and check current state\n// if current state is \"started\" then stop old playback\nmPlayerHandler.addMessage(new Stop(mCurrentPlayer, this));\nmPlayerHandler.addMessage(new Reset(mCurrentPlayer, this));\nmPlayerHandler.addMessage(new Release(mCurrentPlayer, this));\nmPlayerHandler.addMessage(new ClearPlayerInstance(mCurrentPlayer, this));\n// set new video player view\nmPlayerHandler.addMessage(new SetNewViewForPlayback(newVideoPlayerView, this));\n// start new playback\nmPlayerHandler.addMessages(Arrays.asList(\n        new CreateNewPlayerInstance(videoPlayerView, this),\n        new SetAssetsDataSourceMessage(videoPlayerView, assetFileDescriptor, this), // I use local file for demo\n        new Prepare(videoPlayerView, this),\n        new Start(videoPlayerView, this)\n));\n// resume queue processing\n```\n\n这些 Message 都是异步处理的，因此我们能在任意时刻停止消息队列对消息的处理，并投递新的消息，例如：\n\n当前一个影片处于准备状态（已经调用了 MediaPlayer.prepare() 方法，而且 MediaPlayer.start() 方法正在消息队列中等待被执行），此时用户滚动了列表，所以我们需要在一个新的 View 上开始新的播放任务。在这种情况下我们会：\n\n1. 停止正在执行的消息队列\n\n2. 移除所有等待执行的消息\n\n3. 投递 “Stop”, “Reset”, “Release”, “Clear Player instance” 这些消息给消息队列，它们将会在 Prepare 消息回调方法执行完成后立刻被执行。\n\n4. 投递 “Create new Media Player instance”, “Set Current Media Player”（将 MediaPlayer 对象绑定到当前需要播放视频的消息上），“Set data source”, “Prepare”, “Start”等消息。这样就会在新的 View 上播放相应的视频。\n\n这样我们就能依照我们设想的那样执行播放任务：停止前一个播放任务，并开始新的播放任务。\n\n下面是相关的依赖：\n\n```\ndependencies {\n    compile 'com.github.danylovolokh:video-player-manager:0.2.0'\n}\n```\n\n##区分列表中可见域最大的 View——列表可见域判断工具\n\n第一个问题是控制视频的播放，第二个问题则是判断哪一个 View 可见域最大，并切换该 View 对应的视频为新的播放视频。下面是调用 ListItemsVisibilityCalculator 的实体，它的具体实现 SingleListViewItemActiveCalculator 会完成对应的工作。\n\n在 Adapter 中被使用的 Model 类必须实现 ListItem 接口，以计算 List 中子项目的可见域：\n\n```java\n/**\n * A general interface for list items.\n * This interface is used by {@link ListItemsVisibilityCalculator}\n *\n * @author danylo.volokh\n */\npublic interface ListItem {\n    /**\n     * When this method is called, the implementation should provide a\n     * visibility percents in range 0 - 100 %\n     * @param view the view which visibility percent should be\n     * calculated.\n     * Note: visibility doesn't have to depend on the visibility of a\n     * full view. \n     * It might be calculated by calculating the visibility of any\n     * inner View\n     *\n     * @return percents of visibility\n     */\n    int getVisibilityPercents(View view);\n\n    /**\n     * When view visibility become bigger than \"current active\" view\n     * visibility then the new view becomes active.\n     * This method is called\n     */\n    void setActive(View newActiveView, int newActiveViewPosition);\n\n    /**\n     * There might be a case when not only new view becomes active,\n     * but also when no view is active.\n     * When view should stop being active this method is called\n     */\n    void deactivate(View currentView, int position);\n}\n```\n\nListItemsVisibilityCalculator 将追踪滚动方向，并在运行时计算子项目的可见域。子项目的可见域依赖于列表中的任意一个咨询项目，即取决于你实现 getVisibilityPercents() 方法的方式：\n\n```java\n/**\n * This method calculates visibility percentage of currentView.\n * This method works correctly when currentView is smaller then it's enclosure.\n * @param currentView - view which visibility should be calculated\n * @return currentView visibility percents\n */\n@Override\npublic int getVisibilityPercents(View currentView) {\n\n    int percents = 100;\n\n    currentView.getLocalVisibleRect(mCurrentViewRect);\n\n    int height = currentView.getHeight();\n\n    if(viewIsPartiallyHiddenTop()){\n        // view is partially hidden behind the top edge\n    percents = (height - mCurrentViewRect.top) * 100 / height;\n    } else if(viewIsPartiallyHiddenBottom(height)){\n        percents = mCurrentViewRect.bottom * 100 / height;\n    }\n\n    return percents;\n}\n```\n\n因此，每一个 View 都需要知道计算其可见域的细节。当发生滚动时，SingleListViewItemActiveCalculator 会要求每一个 View 计算其可见域大小，这也意味其开销之重。\n\n当任意一个子项目的可见域大小超过了当前播放视频项目的可见域大小，就会调用 setActive 方法切换播放任务。\n\n下面是与 Adapter 功能类似的的 ItemsPositionGetter 的抽象逻辑，它将建立 ListItemsVisibilityCalculator 与 ListView/RecyclerView 交互的桥梁。这样的话 ListItemsVisibilityCalculator 不需要了解它需要操作的 View 到底是 ListView 还是 RecyclerView。只需要完成它自己的工作，但仍需要一些由 ItemsPositionGetter 提供的信息。\n\n```java\n/**\n * This class is an API for {@link ListItemsVisibilityCalculator}\n * Using this class is can access all the data from RecyclerView / \n * ListView\n *\n * There is two different implementations for ListView and for \n * RecyclerView.\n * RecyclerView introduced LayoutManager that's why some of data moved\n * there\n *\n * Created by danylo.volokh on 9/20/2015.\n */\npublic interface ItemsPositionGetter {\n \n   View getChildAt(int position);\n\n    int indexOfChild(View view);\n\n    int getChildCount();\n\n    int getLastVisiblePosition();\n\n    int getFirstVisiblePosition();\n}\n```\n\n这样的实现无疑会让 Model 类混入一些业务逻辑，违反了设计模式的部分原则。但通过一些简单的修改就能将它们解耦。不过现在这样它用起来也没多大的问题。\n\n下面是预览图和依赖：\n\n![](http://img.my.csdn.net/uploads/201601/30/1454131709_9926.gif)\n\n\n```\ndependencies {\n    compile 'com.github.danylovolokh:list-visibility-utils:0.2.0'\n}\n```\n\n##组合使用上面完成的工作\n\n现在我们需要将上面的工具库结合起来使用以完成我们的功能，下面是使用 RecyclerView 实现的代码：\n\n1. 初始化 ListItemsVisibilityCalculator，并传递列表的引用\n\n```java\n/**\n * Only the one (most visible) view should be active (and playing).\n * To calculate visibility of views we use {@link SingleListViewItemActiveCalculator}\n */\nprivate final ListItemsVisibilityCalculator mVideoVisibilityCalculator = new SingleListViewItemActiveCalculator(\nnew DefaultSingleItemCalculatorCallback(), mList);\n```\n\n当被激活（播放视频）的 View 发生改变，DefaultSingleItemCalculatorCallback 只调用 ListItem.setActive 方法，但你可以重载该方法并按照你的需求实现相应逻辑：\n\n```java\n/**\n * Methods of this callback will be called when new active item is found {@link Callback#activateNewCurrentItem(ListItem, View, int)}\n * or when there is no active item {@link Callback#deactivateCurrentItem(ListItem, View, int)} - this might happen when user scrolls really fast\n */\npublic interface Callback<T extends ListItem>{\n    void activateNewCurrentItem(T item, View view, int position);\n    void deactivateCurrentItem(T item, View view, int position);\n}\n```\n\n2. 初始化 VideoPlayerManager\n\n```java\n/**\n * Here we use {@link SingleVideoPlayerManager}, which means that only one video playback is possible.\n */\nprivate final VideoPlayerManager<MetaData> mVideoPlayerManager = new SingleVideoPlayerManager(new PlayerItemChangeListener() {\n    @Override\n    public void onPlayerItemChanged(MetaData metaData) {\n\n    }\n});\n```\n\n3. 为 RecyclerView 设置滚动监听，并传递滚动事件给列表可见工具库处理。\n\n```java\n@Override\npublic void onScrollStateChanged(RecyclerView view, int scrollState) {\n mScrollState = scrollState;\n if(scrollState == RecyclerView.SCROLL_STATE_IDLE && mList.isEmpty()){\n\n mVideoVisibilityCalculator.onScrollStateIdle(\n          mItemsPositionGetter,\n          mLayoutManager.findFirstVisibleItemPosition(),\n          mLayoutManager.findLastVisibleItemPosition());\n }\n }\n\n@Override\npublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n if(!mList.isEmpty()){\n   mVideoVisibilityCalculator.onScroll(\n         mItemsPositionGetter,\n         mLayoutManager.findFirstVisibleItemPosition(),\n         mLayoutManager.findLastVisibleItemPosition() -\n         mLayoutManager.findFirstVisibleItemPosition() + 1,\n         mScrollState);\n }\n}\n});\n```\n\n4. 创建 ItemsPositionGetter\n\n```java\nItemsPositionGetter mItemsPositionGetter = \nnew RecyclerViewItemPositionGetter(mLayoutManager, mRecyclerView);\n```\n\n5. 在 onResume() 中调用方法以开始计算子项目的可见域，在我们显示出列表时选出可见域最大的子项目\n\n```java\n@Override\npublic void onResume() {\n    super.onResume();\n    if(!mList.isEmpty()){\n        // need to call this method from list view handler in order to have filled list\n\n        mRecyclerView.post(new Runnable() {\n            @Override\n            public void run() {\n\n                mVideoVisibilityCalculator.onScrollStateIdle(\n                        mItemsPositionGetter,\n                        mLayoutManager.findFirstVisibleItemPosition(),\n                        mLayoutManager.findLastVisibleItemPosition());\n\n            }\n        });\n    }\n}\n```\n\n然后就完成了，在滚动列表中播放许多视频：\n\n![](http://img.my.csdn.net/uploads/201601/30/1454131535_2903.gif)\n\n这个项目关键部分的讲解基本上已经完成了，下面是具体的代码和范例：\n\n[https://github.com/danylovolokh/VideoPlayerManager](https://github.com/danylovolokh/VideoPlayerManager)\n\n感谢阅读 ;)"
  },
  {
    "path": "issue-40/权限 - 第一篇.md",
    "content": "# 权限 － 第一篇\n\n> * 原文链接 : [Permissions – Part 1](https://blog.stylingandroid.com/permissions-part-1/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [shenyansycn](https://github.com/shenyansycn) \n* 校对者: \n* 状态 :  翻译中\n\n在Marshmallow（棉花糖，Android6.0版本）中Android添加了一个新的权限模块，需要开发者在授权的时候做一些不同的处理。在这里系列文章中，我们从技术角度看下如何处理请求的权限和如何提供流畅的用户体验。\n\n![Icon_no_permission](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Icon_no_permission.png?w=240) \n\n在我们开始前需要指出的是，App需要的权限集中下以下两部分：\n\n* App的操作核心－没有这些核心权限，App不能正确的运行；\n* 不是必须的权限。\n\n例如，一个相机App，CAMERA权限是核心功能的一部分 － 不能处理任何图片的相机App是没用的。然而，这里有额外的功能，比如，标记图片所在的位置会需要ACCESS_FINE_LOCATION权限，这是一个非常好的功能，但App没有这个权限也可以运行。\n\n我们的系列文章是以一个应用开始的，有两个权限是我们无论如何都需要使用的：**RECORD_AUDIO** 和 **MODIFY_AUDIO_SETTINGS**。为了获得这些权限，我们需要在Mainfest文件中声明它们：\n\nAndroidManifest.xml\n\n```java\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  package=\"com.stylingandroid.permissions\">\n\n  <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n  <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n\n  <application\n    android:allowBackup=\"false\"\n    android:fullBackupContent=\"false\"\n    android:icon=\"@mipmap/ic_launcher\"\n    android:label=\"@string/app_name\"\n    android:supportsRtl=\"true\"\n    android:theme=\"@style/AppTheme.NoActionBar\"\n    tools:ignore=\"GoogleAppIndexingWarning\">\n\n    <activity android:name=\".MainActivity\" />\n\n    <activity\n      android:name=\".PermissionsActivity\"\n      android:label=\"@string/title_activity_permissions\"\n      android:theme=\"@style/AppTheme.NoActionBar\">\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\" />\n\n        <category android:name=\"android.intent.category.LAUNCHER\" />\n      </intent-filter>\n    </activity>\n  </application>\n\n</manifest>\n```\n\n从Android Api 1.0版本开始，这就是标准的权限声明方法。然而，当把targetSdkVersion设置为23或更高，我们就需要在运行时请求需要的权限。这真的是重要的，因为已经有很多这样的例子了，开发者无意的设置targetSdkVersion为最新版本，然后正常运行的时候奔溃了，因为在运行时他们没有实现请求权限所必需的代码。更多问题中的一个是，当你一旦发布一个面向API23或更高的应用到GooglePlay上时，你不能再次发布一个面向低版本的APK。\n\n另一个值得提到的事情是有一些专门用于运行时权限请求处理的库。这些库在质量和实用性都是有差异的，但是我认为使用这样的库前有必要了解底层实现原理，否则你会被问题搞死，因为你不知道你选择的库实际上都做了什么。这是这个系列文章的主要目的。\n\n我们实际需要的两个权限分为两个不同的类别：**RECORD_AUDIO**是一个需要特别注意的危险权限，**MODIFY_AUDIO_SETTINGS**是一标准权限。一个危险的权限可能会影响用户安全或隐私。而一个需要访问App的外部资源的标准权限，对于用户隐私来说基本没有风险。标准权限会被系统自动授权，而危险权限在运行时需要用户明确的授权。\n\n我们首先需要做的事情是判断我们需要的权限是否已经被授权了。在API 23 Context中新添加了一些方法用于检测是否特殊的权限已经被授予。然而，一般都是用ContextCompat替代Context，包括你自己的API-level检测：\n\nPermissionChecker.java\n\n```java\nclass PermissionsChecker {\n    private final Context context;\n\n    public PermissionsChecker(Context context) {\n        this.context = context;\n    }\n\n    public boolean lacksPermissions(String... permissions) {\n        for (String permission : permissions) {\n            if (lacksPermission(permission)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private boolean lacksPermission(String permission) {\n        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED;\n    }\n}\n```\n\n这实际上是很简单的－`ContextCompat#checkSelfPermission`方法要么返回**PackageManager.PERMISSION_DENIED**要么返回**PackageManager.PERMISSION_GRANTED**。我添加了更进一步逻辑判断，检测是否没有被授予任何所需要的权限。\n\n需要重复说的是这里的ContextCompat为我们做了什么。在Marshmallow之前不支持新的运行时授权的设备上，`checkSelfPermission()`方法总是返回**PackageManger.PERMISSION_GRANTED**－在比较老的OS层这个权限被默认授予，所以在Manifest文件中声明后，我们仅用这一个方法调用可以在所有的OS层都工作，并且我们在代码中不必写任何API-level的检测。 \n\n以防万一你会疑惑我为什么为这个单独创建了一个类，因为过一会我需要在App中的所有Activity都用这个检测，所以把检测逻辑从Activity中分离出来会使我们有更少重复的代码和提高可维护性。\n\n因此在Activiy中使用这个我们仅用Activity所需要的权限列表做为参数的调用即可：\n\nMainActivity.java\n\n```java\npublic class MainActivity extends AppCompatActivity {\n    private static final String[] PERMISSIONS = new String[] {Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS};\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        PermissionsChecker checker = new PermissionsChecker(this);\n\n        if (checker.lacksPermissions(PERMISSIONS)) {\n            Snackbar.make(toolbar, R.string.no_permissions, Snackbar.LENGTH_INDEFINITE).show();\n        }\n    .\n    .\n    .\n    }\n}\n```\n\n实际上这是相当简单的。\n\n这个可以原样运行在Marshamllow（Android6.0）之前的设备上：\n\n![Part1-lollipop](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Part1-lollipop.png?resize=624%2C468)\n\n但是，在Marshmallow（Android6.0）和之后的版本对于任何丢失的权限我们还没有处理－我们仅是显示了一个Snackbar：\n\n![Part1-marshmallow](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Part1-marshmallow.png?resize=624%2C468)\n\n\n下一章，我们更会进行更复杂的请求丢失的权限处理。\n\n这篇文章的源码在 [这里](https://github.com/StylingAndroid/Permissions/tree/Part1)\n\n© 2016, [Mark Allison](https://blog.stylingandroid.com/). All rights reserved.\n\n"
  },
  {
    "path": "issue-40/权限 - 第三篇.md",
    "content": "# Permissions – Part 3\n# 权限 － 第三篇\n\n> * 原文链接 : [Permissions – Part 3](https://blog.stylingandroid.com/permissions-part-3/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [shenyansycn](https://github.com/shenyansycn) \n* 校对者: \n* 状态 :  翻译中\n\n在Marshmallow（棉花糖，Android6.0版本）中Android添加了一个新的权限模块，需要开发者在授权的时候做一些不同的处理。在这一系列中，我们从技术角度看下如何处理请求的权限和如何提供流畅的用户体验。\n\n![Icon_no_permission](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Icon_no_permission.png?w=240)\n\n我们已经知道了如何检测我们需要的权限，现在我们看下如何请求被拒绝的权限。\n\n令人高兴的流程是（至少对于开发者来说），我们直接询问用户授权需要的权限，用户授权了，大家都高兴。但是，就像我们之前讨论过的，我们需要考虑到用户没有授予权限的情况。\n\n现在让我们看下，我们直接提出请求的操作：\n\nPermissionsActivity.java\n\n```java\npublic class PermissionsActivity extends AppCompatActivity {\n    private static final int PERMISSION_REQUEST_CODE = 0;\n    private static final String EXTRA_PERMISSIONS = \"com.stylingandroid.permissions.EXTRA_PERMISSIONS\";\n    private static final String EXTRA_FINISH = \"com.stylingandroid.permissions.EXTRA_FINISH\";\n    private static final String PACKAGE_URL_SCHEME = \"package:\";\n\n    private PermissionsChecker checker;\n    private boolean requiresCheck;\n\n    public static void startActivityForResult(Activity activity, int requestCode, String... permissions) {\n        Intent intent = new Intent(activity, PermissionsActivity.class);\n        intent.putExtra(EXTRA_PERMISSIONS, permissions);\n        ActivityCompat.startActivityForResult(activity, intent, requestCode, null);\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getIntent() == null || !getIntent().hasExtra(EXTRA_PERMISSIONS)) {\n            throw new RuntimeException(\"This Activity needs to be launched using the static startActivityForResult() method.\");\n        }\n        setContentView(R.layout.activity_permissions);\n\n        checker = new PermissionsChecker(this);\n        requiresCheck = true;\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        if (requiresCheck) {\n            String[] permissions = getPermissions();\n\n            if (checker.lacksPermissions(permissions)) {\n                requestPermissions(permissions);\n            } else {\n                allPermissionsGranted();\n            }\n        } else {\n            requiresCheck = true;\n        }\n    }\n\n    private String[] getPermissions() {\n        return getIntent().getStringArrayExtra(EXTRA_PERMISSIONS);\n    }\n\n    private void allPermissionsGranted() {\n        setResult(PERMISSIONS_GRANTED);\n        finish();\n    }\n    .\n    .\n    .\n}\n```\n\n我们有一个非常实用的startActivityForResult()方法，其他Activity必须使用这个传入需要请求的一套权限来启动PermissionsActivity。所以我们可以直接一次都请求所需要的权限。然而，你可能会疑惑我们为什么需要这个－我们启动Activity是因为我们已经知道我们没有获得所需要的权限么？我们需要这个的原因是如下的这些小概率情况－如果用户临时离开了这个Activity，在设置中授权了权限，然后再回来的时候这个流程会次执行。\n\n但是当我们没有所需要的权限时怎么办－我们需要请求它们：\n\nPermissionsActivity.java\n\n```java\npublic class PermissionsActivity extends AppCompatActivity {\n    .\n    .\n    .\n    private void requestPermissions(String... permissions) {\n        ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);\n    }\n\n   @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        if (requestCode == PERMISSION_REQUEST_CODE && hasAllPermissionsGranted(grantResults)) {\n            requiresCheck = true;\n            allPermissionsGranted();\n        } else {\n            requiresCheck = false;\n            showMissingPermissionDialog();\n        }\n    }\n\n    private boolean hasAllPermissionsGranted(@NonNull int[] grantResults) {\n        for (int grantResult : grantResults) {\n            if (grantResult == PackageManager.PERMISSION_DENIED) {\n                return false;\n            }\n        }\n        return true;\n    }\n    .\n    .\n    .\n```\n\n这就是处理核心。我们调用requestPermissions()请求我们需要的权限。我这里总是强调标准和危险级别的权限，虽然我们自动获得标准的权限。背后的原因是，如果标准权限在未来变为一个危险的权限，那么仍会正常工作。\n\nrequestPermissions()的操作和startActivityForResult()的操作很像 － 我们在另一个Activity中处理，然后在本Activity中用一个回调做为结束。这个例子中的回调函数是onRequestPermissionResult()。它检测了所有被授权的权限和要么返回处理（要么通过finish()，要么重新唤醒MainActivity，就像之前那样），要么事情变为稍有复杂：我们请求所需要的权限但用户拒绝了。\n\n首先，安装时我们会问用户一个特殊的权限，用户可以选择：允许或拒绝。如果拒绝了权限，我们会在此请求，这时会有一个单选框“不再询问”。如果用户选择了一个单选框并按了“拒绝”，之后的任何权限请求都会被自动的拒绝。不幸的是，我们的权限被拒绝了，我们并不知道用户是否执行了上面的操作。考虑到这个特殊的权限是App运行所急需的，我们需要明确告诉用户为什么我们需要这个权限，如果仍然被拒绝，那就退出App：\n\nPermissionsActivity.java\n\n```java\npublic class PermissionsActivity extends AppCompatActivity {\n    .\n    .\n    .\n    private void showMissingPermissionDialog() {\n        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(PermissionsActivity.this);\n        dialogBuilder.setTitle(R.string.help);\n        dialogBuilder.setMessage(R.string.string_help_text);\n        dialogBuilder.setNegativeButton(R.string.quit, new DialogInterface.OnClickListener() {\n            @Override\n            public void onClick(DialogInterface dialog, int which) {\n                setResult(PERMISSIONS_DENIED);\n                finish();\n            }\n        });\n        dialogBuilder.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {\n            @Override\n            public void onClick(DialogInterface dialog, int which) {\n                startAppSettings();\n            }\n        });\n        dialogBuilder.show();\n    }\n\n     private void startAppSettings() {\n        Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);\n        intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName()));\n        startActivity(intent);\n    }\n}\n```\n\n我故意让这里的说明文本说的有点含糊，因为每一个app都要解释需要这个权限的具体原因。同样，为了清晰明了，当只有一个危险权限被请求时，我没有办法没有针对不同权限提供不同的信息。然而，一个真实的app，你很可能需要确定哪一个权限缺失了和针对每一个缺失的提供一个恰当的描述信息。\n\n这里我们可以看到当用户最初拒绝了这个权限，退出和后来的权限的授予。\n\n[video](https://youtu.be/0YBb_lmsyIM)\n\n如果用户永远拒绝了权限（通过“不再提醒”选择框）。接下来会稍微复杂，因为我们不能提供一个我们app权限设置页面的直接链接，所以我们需要给用户提供一些说明：\n\n[video](https://youtu.be/gqFIJvMqIpQ)\n\n需要注意的是是否我们改变了这两者之间的流程。第一个视频展示的是我们告知用户通过设置改变，即使我们随后看到了退出和重新启动后再次提示的允许或拒绝。不幸的，我们没有办法知道用户是否选择了“不再提示”，所以我们要做最坏情况的打算。\n\n即便如此我们现在有一个相对简单，可以重复使用的请求我们应用关键权限的方法。在这个系列的最后一篇文章我们来看看非关键权限和更多的练习。\n\n源代码在[这里](https://github.com/StylingAndroid/Permissions/tree/Part3)。\n\n© 2016, [Mark Allison](https://blog.stylingandroid.com/). All rights reserved.\n\n"
  },
  {
    "path": "issue-40/权限 - 第二篇.md",
    "content": "# Permissions – Part 2\n# 权限 － 第二篇\n\n> * 原文链接 : [Permissions – Part 2](https://blog.stylingandroid.com/permissions-part-2/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [shenyansycn](https://github.com/shenyansycn) \n* 校对者: \n* 状态 :  翻译中\n\n在Marshmallow（棉花糖，Android6.0版本）中Android添加了一个新的权限模块，需要开发者在授权的时候做一些不同的处理。在这一系列中，我们从技术角度看下如何处理请求的权限和如何提供流畅的用户体验。\n\n![Icon_no_permission](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Icon_no_permission.png?w=240)\n\n之前我们讨论了如何检查我们需要的权限是否被授予，但是我们没有讨论在适当的地方请求缺失的权限。在这篇文章中，我们来看一看在所有Acitivity中，在没有很多重复代码的情况下我们是如何请求和检测必要权限的。请记住，接下来的都是在Marshmallow或之后的版本（更早OS层Manifest声明的权限会被直接授权），如果你在builds文件中特别标明targetSdkVersion=23或更高，你就需要实现这种检测。\n\n所以第一件事情是我们要知道权限请求模块是如何工作的。就像我们已经讨论过的，标准权限会被直接授权，但是危险的权限的请求需要用户授予。用户授予需要的权限是很容易的事情，但我们需要考虑到用户没有授予权限。针对这个app，我们最终编写成一个对用户来说并不是非常明显的我们需要RECORD_AUDIO权限的原因，所以，我们要准备通知用户为什么这个是需要的。\n\n从用户的角度来看，工作方式是第一次运行app的时候请求用户授权需要的权限：\n\n![Part2-first-run](https://i1.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Part2-first-run.png?resize=624%2C468)\n\n如果他们授权了这个权限，万事好说，我们就不急了。但是如果他们拒绝了这个权限，我们就要反复的询问需要被授权的权限：\n\n![Part2-second-run](https://i1.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Part2-second-run.png?resize=624%2C468)\n\n请注意如果用户之前拒绝了权限申请，它们会显示一个不再显示这个权限请求的选项。如果用户选择了一个选项，代码中任何进一步的尝试权限请求会被自动拒绝。做为开发者要清楚这个问题，做好预留措施。\n\n用户可以进一步设置，进到应用的设置页面，可以授权或拒绝任何应用需要的权限。这就是为什么不仅仅在应用启动的时候检测需要的权限是否被授予的重要性，而且每个activity的权限都随时都有可能发生变化。\n\n\n我们将要管理这个的模式是一个单独的负责请求权限的Activity，app中所有的其他Activity都需要检测所需要的权限，如果他们没有被授权PermissionsActivity会被回调。\n\n让我稍微更新下*MainActivity*：\n\nMainActivity.java\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    static final String[] PERMISSIONS = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS};\n    private PermissionsChecker checker;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n\n        checker = new PermissionsChecker(this);\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n\n        if (checker.lacksPermissions(PERMISSIONS)) {\n            startPermissionsActivity();\n        }\n    }\n\n    private void startPermissionsActivity() {\n        PermissionsActivity.startActivity(this, PERMISSIONS);\n    }\n}\n```\n\n我们把权限检测代码放到`onResume()`。这就解决了用户暂停我们的app，切换到设置页面，拒绝了权限，然后又返回到我们的app的情况。好，这只是一个小概率事件，但对于预防崩溃是值得做的。 \t \t\n\n我们实现的这个基础模式是无论何时Activity被恢复我们都可以确定有操作Activity的所需要的权限。如果我们没有权限，我们将控制权交给负责获得需要的权限的PermissionsActivity。虽说这感觉像是一个防御性方案，但我认为这个一个不需要大量代码的明智的方案。PermissionsChecker内封装好了所有检测逻辑，请求调用是在PermissionsActivity。\n\n轻量级的权限检测组件是重要的，因为我们可以相对简单的检测需要的权限，在Activity有必要请求权限的地方请求是更浪费的。\n\n在下一篇文章中我们会看到PermissionsActivity如何处理这个权限的请求，探索如果用户拒绝了一个我们需要的权限，如何告知用户我们为什么需要。\n\n这篇文章的源码在[这里](https://github.com/StylingAndroid/Permissions/tree/Part2)。PermissionsActivity代码中有一个占位符，我会在下篇文章中扩展，所以它现在不是一个完整功能的代码。\n\n© 2016, [Mark Allison](https://blog.stylingandroid.com/). All rights reserved.\n\n"
  },
  {
    "path": "issue-40/权限 - 第四篇.md",
    "content": "# Permissions – Part 4\n# 权限 － 第四篇\n\n> * 原文链接 : [Permissions – Part 4](https://blog.stylingandroid.com/permissions-part-4/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [shenyansycn](https://github.com/shenyansycn) \n* 校对者: \n* 状态 :  翻译中\n\n在Marshmallow（棉花糖，Android6.0版本）中Android添加了一个新的权限模块，需要开发者在授权的时候做一些不同的处理。在这一系列中，我们从技术角度看下如何处理请求的权限和如何提供流畅的用户体验。\n\n\n![Icon_no_permission](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2015/12/Icon_no_permission.png?w=240)\n\n之前我们看了检测缺失权限的机制和运行时的请求。我们考虑了所需要的权限（即那些你app的主要核心功能），但是我们还讨论了非必需的权限 － 你app的一些更好的特性所需要的权限，但不是你核心功能的部分。例如，一个相机app被拒绝了“Camera”权限，它的主要功能拍照就会变得没有任何用处；但如果它也能设置地理标签（需要“Location”权限），如果用户没有授予这个权限，地理标签的这个特性也是不可用的。\n\n不幸的是关于非必需权限没有一个精确的定义，因为请求这样的权限的正确时间取决于你app的原理与架构。要记住更重要的事情是最好在它们被需要的时候请求。例如，地理标签的这个例子，当用户想要设置标签的时候才询问用户请求授予需要的权限。\n\n如何明显的说明需要特别的权限也是值得考虑的。对于地理标签要明显说明，为什么“Location”权限会被需要，是因为能标记一个图片所发生的位置。然而，我们下一个系列文章需要的“Record Audio权限的理由就不是那么明显，所以我们需要解释为什么需要这个权限，以便用户有更多的信息来决定是否允许或拒绝这个权限。如果用户不知道为什么一个特别权限被需要，那么会有很大机会用户会拒绝它。\n\n另一件值得考虑的是难对付的用户。看起来容易简单的预先请求所有的权限是因为那样更容易写代码。依我看来，这是错误的。如果用户对于你app的第一个印象是仅仅被请求一个又一个权限，又没有看到你的app的任何直接价值那么很有可能他们会退出并卸载你的应用。最好是最低限度的预先请求上下文中的其他权限。这不仅是帮助用户理解为什么每一个权限是被需要的（因此增加了他们授予权限的可能性），相对于他们费力的做完一堆权限的请求，这么做能使他们在第一次启动应用前更早的感受到你应用的一些价值。\n\n另一个关键点时仅请求你需要的权限。虽然这有时是显而易见的。随着软件的发展，曾经需要的权限有时会超出范围和不是必须的。定期查看你app需要的权限和清除任何多余的权限请求是非常有必要的。\n\n有人可能会疑惑，为什么我没有在例子代码中包含[shouldShowRequestPermissionRationale()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String))检测。简单的原因是我认为对于接口实现流程我已经有一个很好的架构更近一步说明了为什么需要请求权限。不管怎样，在当前正文中请求非必需的权限这个机制可能稍微更有作用。\n\n最终需要记住的是，如果用户清空了你app的数据，这不仅仅是清空了所有的数据，还重置了所有的权限。用户再次启动你的app时所有之前授权的权限都会被拒绝。这对于你测试权限申请非常有用，同时为客服提供了应对用户提出“为什么我已经同意了权限还需要重新申请？”问题的准备答案。\n\n总结下我们所看到的Marshmallow权限模型。在下一个系列文章中我们会用到我们这里用到的技术。\n\n本章没有代码，前一章的代码在[这里](https://github.com/StylingAndroid/Permissions/tree/Part3)。\n\n© 2016, [Mark Allison](https://blog.stylingandroid.com/). All rights reserved.\n\n"
  },
  {
    "path": "issue-41/Service十件你不知道的事.md",
    "content": "Service十件你不知道的事\n---\n\n> * 原文链接 : [10 things you didn’t know about Android’s Service component](https://medium.com/@workingkills/10-things-didn-t-know-about-android-s-service-component-a2880b74b2b3#.217tirtun)\n* 原文作者 : [Eugenio Marletti](https://medium.com/@workingkills)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n\n很抱歉我标题党了一回，但我就是忍不住要做标题党……\n\n多年以来，我常常会和一些 Android 开发同行讨论一些问题，其中最常讨论的就是菜鸡和大神对 Service 组件的种种误解。下面是讨论结果的汇总：这些内容我一直记在心里，而今天我决定写在博客上分享给大家。\n\n但这不意味着我要解释 Service 机制的所有细节，官方文档已经给出了很好的解释；我要指出的是被忽视/被误解/被遗忘的 Service 的相关概念，而这些概念又是掌握 Service 组件必须掌握的。\n\n##1. Service 不是更好用的 AsyncTask\n\nService 并不是用于完成通用的异步/后台操作：设计 Service 组件的目的在于，即使当前没有 Activity 是可见的，仍可以执行一些逻辑；不妨将它理解为不可见的 Activity。\n\n需要记住，每一个 Service 组件带来的开销并不仅仅会增加 App 的负担，还会增加整个 Android 系统的负担。\n\n##2. Service 默认运行在主线程中，即 App 进程中\n\n你可以让 Service 运行在不同的进程中，但你应该避免这样做，除非你非这样做不可且了解这样做将会带来的一切开销。\n\n##3. IntentService 并没有什么黑科技\n\nIntentService 通过创建 HandlerThread 置于队列中等待对应任务被完成，是在 Service 之外处理逻辑的一种技巧。\n\nIntentService 是一个简单的类，只有164行代码，其中有90行还是注释，你可以去看看它的源码！\n\n##4. 一次只能有一个 Service 实例\n\n不论你创建多少 Service，一次只能处理一个 Service 事务，即使有其他应用/进程与其交互。\n\n##5. 杀死 Service 很容易\n\n不要觉得内存压力是\"例外\"的条件：让你的 Service 组件能优雅地处理系统引起的重启，这是其生命周期很正常的一部分。\n\n你可以将 Service 标记为前台组件使其更难被系统杀死，但只在不得不这样做的情况下设置该标记。\n\n需要注意的是，当代码运行在 onCreate()，onStartCommand()，或 onDestroy() 方法中，无论 Service 此时是不是前台组件，它都会被视作前台组件。\n\n[这里](https://developer.android.com/guide/components/processes-and-threads.html#Lifecycle) 有助于理解进程有多容易被杀死。\n\n##6. Service 能被启动，束缚，或同时启动和绑定\n\n只要存在绑定关系，显式地停止 Service 就不会使其被终止，并且 unbind 所有组件也不会终止它，知道它被显式地停止（如果它曾被启动过）。此外，不论你调用 startService() 多少次，调用一次 stopService() 或 stopSelf() 都会将其停止。不妨看看下面这张 Service 生命周期图：\n\n![](https://cdn-images-1.medium.com/max/800/1*XBMj4XOw8SRecuvLcixZUQ.png)\n\n##7. START_FLAG_REDELIVERY 避免丢失输入数据\n\n如果你在启动 Service 时传入数据，在 onStartCommand() 方法中返回 START_FLAG_REDELIVERY 有助于避免输入数据的丢失，如果 Service 在处理它的时候被杀死。\n\n##8. 前台 Service 通知可能有一部分被隐藏\n\n前台 Service 必须显示持久的通知，但你可以给它一个 PRIORITY_MIN 优先级以在状态栏隐藏它（但它在通知的阴影处是可见的）。\n\n##9. Service 能启动 Activity\n\n就像所有非 Activity 的 Context 子类，在 Service 中使用 Intent 时为其添加 FLAG_ACTIVITY_NEW_TASK 标记后就能在 Service 组件内启动 Activity。\n\n##10. 你允许使用单一职责原则\n\n你不应该将业务逻辑放在 Service 类内处理，而是在一个独立的类中处理。这样的话，只要需要，你可以在任意类内处理业务逻辑，好处多多。\n\n记住这些内容，并把它们分享给你的小伙伴，减少 Service 的误解！"
  },
  {
    "path": "issue-41/自定义CoordinatorLayout的行为.md",
    "content": "自定义 CoordinatorLayout 的行为\n---\n\n> * 原文链接 : [Customizing CoordinatorLayout's Behavior](https://www.bignerdranch.com/blog/customizing-coordinatorlayouts-behavior/)\n* 原文作者 : [Chris Stewart](https://www.bignerdranch.com/about-us/nerds/chris-stewart/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n* 状态 :  完成 \n\n\n\n当 Android design support library 在前几个月被发布时，Android 开发者们获得了各种各样有趣的东西，而且其中大多数东西看名字就知道是啥：FloatingActionButton(FAB)，Snackbar，Tab，这些东西都不知道的话和咸鱼有什么区别。\n\n但 Android design support library 中有一个控件 - CoordinatorLayout 有一些绝大部分开发者都没发现的秘密，接下来就让我们来研究 CoordinatorLayout 吧。\n\n##CoordinatorLayout\n\n但你可能很少听说 CoordinatorLayout，大概是因为当你使用它时，往往不需要知道许多与它相关的细节，就能得到你想要的效果。然而 CoordinatorLayout 比它看起来 6 得多。不妨拿 SnackBar 和 FAB 作为例子，用 CoordinatorLayout 确保 FAB 会在 SnackBar 出现的时候会向上移动：\n\nxml 如下：\n\n```xml\n<android.support.design.widget.CoordinatorLayout\n  android:id=\"@+id/container\"\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\">\n\n  ...\n\n  <android.support.design.widget.FloatingActionButton\n    android:id=\"@+id/fab\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"bottom|right|end\"\n    android:layout_margin=\"8dp\"\n    android:src=\"@drawable/abc_ic_search_api_mtrl_alpha\"/>\n\n</android.support.design.widget.CoordinatorLayout>\n```\n\n然后显示你的 SnackBar：\n\n```java\nSnackbar.make(findViewById(R.id.container), \"Hey there!\", Snackbar.LENGTH_LONG).show();\n```\n\n然后你就会发现 FAB 会在 SnackBar 出现时移动了。\n\n![](https://www.bignerdranch.com/img/blog/2016/02/fab_normal_size.gif)\n\n除此以外，CoordinatorLayout 还能基于你的滚动位置增加 ToolBar 的大小或在用户滚动列表时隐藏 Toolbar。你可以用你自己的行为自定义。\n\n那么 CoordinatorLayout 是怎么知道他需要与 FAB 交互完成的操作呢？它是怎么知道当 SnackBar 出现时 FAB 要向上移动呢？CoordinatorLayout 是通过实现 FloatingActionButton 声明的 CoordinatorLayout.Behavior 完成的。\n\n##CoordinatorLayout.Behavior\n\nCoordinatorLayout 中的 View 可以指定该 View 如何与其他 View 交互的行为。\n\n```java\n@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)\npublic class FloatingActionButton extends ImageView {\n  ...\n}\n```\n\n如果你去看 FAB 的源码，你会发现它的行为是通过注解在类声明中定义的。\n\nFAB 将 FloatingActionButton.Behavior 作为其默认行为，除非你将它设置为其他的 behavior，而默认的 Behavior 了解在 SnackBar 出现时如何移动到它的上面。\n\n##自定义 Behavior\n\n那么你要怎么自定义 Behavior 呢？你可以创建一个 Behavior 的子类，现在我就创建一个 Behavior 在 SnackBar 出现时闪烁 FAB 而不是让它移动到 SnackBar 上面。\n\n![](https://www.bignerdranch.com/img/blog/2016/02/fab_shrink.gif)\n\n首先在 xml 中使用 app:layout_behavior 属性设置你的自定义 Behavior：\n\n```xml\n...\n\n<android.support.design.widget.FloatingActionButton\n  android:id=\"@+id/fab\"\n  android:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"bottom|right|end\"\n  android:layout_margin=\"8dp\"\n  android:src=\"@drawable/abc_ic_search_api_mtrl_alpha\"\n  app:layout_behavior=\"com.bignerdranch.android.custombehavior.ShrinkBehavior\"/>\n\n...\n```\n\n然后创建你的 Behavior 类：\n\n```java\npublic class ShrinkBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {\n\n  public ShrinkBehavior(Context context, AttributeSet attrs) {\n      super(context, attrs);\n  }\n\n}\n```\n\n当你定义你的 Behavior，你可以重载许多方法，在这里我们只需要重载两个方法：\n\n```java\n@Override\npublic boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {\n    return dependency instanceof Snackbar.SnackbarLayout;\n}\n```\n\nlayoutDependsOn() 方法帮助 CoordinatorLayout 知道 FAB 依赖于哪个 View。在这里例子中，就是指 SnackBar，通过返回 true 设置依赖。\n\nAndroid API 文档上说，设置依赖后，当一个独立的 View 改变了它的大小或位置，你会调用到onDependentViewChanged()，你可以在 CoordinatorLayout 中看到更多的细节。\n\n```java\n@Override\npublic boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {\n  float translationY = getFabTranslationYForSnackbar(parent, child);\n  float percentComplete = -translationY / dependency.getHeight();\n  float scaleFactor = 1 - percentComplete;\n\n  child.setScaleX(scaleFactor);\n  child.setScaleY(scaleFactor);\n  return false;\n}\n```\n\n当 SnackBar 出现，该方法不断被调用，然后通过简单的数学运算你就可以判断 SnackBar 离屏幕边缘有多远，并改变你的 FAB 的大小。[ShrinkBehavior](https://github.com/cstew/CustomBehavior/blob/master/app/src/main/java/com/bignerdranch/android/custombehavior/ShrinkBehavior.java) 可以在 Github 上看到完整的实现，你也可以直接下载旋转 FAB 的[例子的源码](https://github.com/cstew/CustomBehavior)。"
  },
  {
    "path": "issue-41/让你了解数据加载的生命周期.md",
    "content": "让你了解数据加载的生命周期\n---\n\n> * 原文链接 : [Making loading data lifecycle aware](https://medium.com/google-developers/making-loading-data-on-android-lifecycle-aware-897e12760832#.1sq0aovlj)\n* 原文作者 : [Ian Lake](https://medium.com/@ianhlake)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n* 状态 :  完成 \n\n\n\n\n创建动态应用需要动态数据，但我希望在 UI 线程加载数据前就已经把事情搞定（因为一些性能问题或其他类似的）。这样的讨论会因为情况/需求的不同而永远不会停止，但不妨只关注一种情况：只在加载 Activity/Fragment 的数据时使用 Loader。与 Loader 相关的讨论往往离不开 CursorLoader，但 Loader 可比 Cursor 厉害多了。\n\nLoader 需要 API 11 或更高版本的 API，而且是 Support v4 库的组成，将最新的特性及 Bug 的修复带给 API 4 和更高版本的机器。\n\n##为什么 Loader 这么特别\n一般情况下，设备配置只在屏幕旋转（涉及重新开始整个 Activity）时发生改变，原因之一是持有 Activity/View 的引用可能会导致内存泄漏。而 Loader 最大的优点就是能保存配置的改变。当 Activity 返回，你之前花费较大开销检索的数据依然在那。数据被组织成队列以被投递，所以你不会因为设备配置发生变化而丢失数据\n\n更好的是：Loader 不会引起内存泄漏，因为一旦请求 Loader 的 Activity/Fragment 被永久销毁，Loader 也会随之被清理，也就意味着 Loader 不会在 Activity/Fragment 被销毁后继续加载数据，增加 App 负担。\n\nLoader 的这两个优点完美匹配了 Activity/Fragment 这类具有生命周期的组件，使得你只需要考虑在生命周期的哪个时期开始/结束加载数据。\n\n##我不信，我不信\n\n可能来个例子会好理解点，不妨假设我们正将普通的 AsyncTask 变为 Loader 的等价物，把这个类命名为：AsyncTaskLoaders。\n\n```java\npublic static class JsonAsyncTaskLoader extends\n    AsyncTaskLoader<List<String>> {\n  // You probably have something more complicated\n  // than just a String. Roll with me\n  private List<String> mData;\n  public JsonAsyncTaskLoader(Context context) {\n    super(context);\n  }\n  @Override\n  protected void onStartLoading() {\n    if (mData != null) {\n      // Use cached data\n      deliverResult(mData);\n    } else {\n      // We have no data, so kick off loading it\n      forceLoad();\n    }\n  }\n  @Override\n  public List<String> loadInBackground() {\n    // This is on a background thread\n    // Good to know: the Context returned by getContext()\n    // is the application context\n    File jsonFile = new File(\n      getContext().getFilesDir(), \"downloaded.json\");\n    List<String> data = new ArrayList<>();\n    // Parse the JSON using the library of your choice\n    // Check isLoadInBackgroundCanceled() to cancel out early\n  return data;\n}\n  @Override\n  public void deliverResult(List<String> data) {\n    // We’ll save the data for later retrieval\n    mData = data;\n    // We can do any pre-processing we want here\n    // Just remember this is on the UI thread so nothing lengthy!\n    super.deliverResult(data);\n  }\n}\n```\n\n上面的代码看起来和 AsyncTask 很像，但我们可以将返回的结果保存在对象变量中，并在配置改变时通过在 onStartLoading() 方法中调用 deliverResult() 立刻返回。但有人可能会注意到：如果我们要缓存数据的话，为什么不调用 forceLoad() 呢？因为这样才能让我们避免不断地重新加载数据。\n\n##这样还不够好 - 如果数据发生了改变呢？\n\n我们简单的范例没有成功实现的是：我们没有被限制读取数据次数为一次 - Loader 是放入 BroadcastReceiver，ContentObserver（一些 CursorLoader 能处理的类），FileObserver，或 OnSharedPreferenceChangeListener。突然你的 Loader 就能意识到其他地方发生的改变，并重新加载对应的数据。为前面的 Loader 添加一个 FileObserver：\n\n```java\npublic static class JsonAsyncTaskLoader extends\n    AsyncTaskLoader<List<String>> {\n  // You probably have something more complicated\n  // than just a String. Roll with me\n  private List<String> mData;\n  private FileObserver mFileObserver;\n  public JsonAsyncTaskLoader(Context context) {\n    super(context);\n  }\n  @Override\n  protected void onStartLoading() {\n    if (mData != null) {\n      // Use cached data\n      deliverResult(mData);\n    }\n    if (mFileObserver == null) {\n      String path = new File(\n          getContext().getFilesDir(), \"downloaded.json\").getPath();\n      mFileObserver = new FileObserver(path) {\n          @Override\n          public void onEvent(int event, String path) {\n            // Notify the loader to reload the data\n            onContentChanged();\n            // If the loader is started, this will kick off\n            // loadInBackground() immediately. Otherwise,\n            // the fact that something changed will be cached\n            // and can be later retrieved via takeContentChanged()\n          }\n      };\n      mFileObserver.startWatching()\n    }\n    if (takeContentChanged() || mData == null) {\n      // Something has changed or we have no data,\n      // so kick off loading it\n      forceLoad();\n    }\n  }\n  @Override\n  public List<String> loadInBackground() {\n    // This is on a background thread\n    File jsonFile = new File(\n        getContext().getFilesDir(), \"downloaded.json\");\n    List<String> data = new ArrayList<>();\n    // Parse the JSON using the library of your choice\n    return data;\n  }\n  @Override\n  public void deliverResult(List<String> data) {\n    // We’ll save the data for later retrieval\n    mData = data;\n    // We can do any pre-processing we want here\n    // Just remember this is on the UI thread so nothing lengthy!\n    super.deliverResult(data);\n  }\n  protected void onReset() {\n    // Stop watching for file changes\n    if (mFileObserver != null) {\n      mFileObserver.stopWatching();\n      mFileObserver = null;\n    }\n  }\n}\n```\n\n于是通过钩入 onStartLoading() 回调开始我们的处理，并在 onReset() 结束，我们可以完美地异步处理依赖的数据。我们将 onStopLoading() 作为回调的结束，但 onReset() 确保我们完全地覆盖整个回调的生命周期（甚至是生命周期中发生的配置改变）。\n\n你可能注意到在 onStartLoading() 中使用的 takeContentChanged() - 这是 Loader 注意到某些数据发生改变（例如调用 onContentChanged（））而此时 Loader 已经停止，因此即使已经有缓存好的新数据，仍需要完成一次数据加载。\n\n> Note：在加载新数据之前我们依然传递酒的，缓存好的数据 - 确保 App 能表现正常，并在必要时改变 onStartLoading()。例如，你可能检查 takeContentChanged() 并立刻丢弃缓存的返回结果，而不是传递他们。\n\n##与 App 其他部分协作：LoaderManager\n\n当然，即便是最好的 Loader，如果没有与什么东西相关也没用。而 Activity 与 Fragment 需要的关联点往往是某种形式的 LoaderManager。通过调用 FragmentActivity 的 getSupportLoaderManager() 或 Fragment 的 getLoaderManager() 就可以获取 LoaderManager 的实例。\n\n几乎在任何情况下，你只需要调用一个方法：initLoader()，这个方法一般在 onCreate()/onActivityCreated() 中调用 - 基本上只要你需要加载一些数据时就要调用。调用时会传入一个唯一的 id (只在 Activity/Fragment 中唯一 - 而不是全局唯一），传入可选的 Bundle ，及一个 LoaderCallback 的回调。\n\n> Note: 别在 Fragment 的 onCreate() 方法中调用 initLoader() - 只在 onActivityCreated() 中调用，不然的话你使用的 Loader 将会是各个 Fragment 间共享的 Loader，就像 Lint 请求中提醒的那样，相关内容可以看这篇[ Google+ 的博文](https://plus.google.com/+JonFHancock/posts/bgXh4XEAeui?utm_campaign=adp_series_loaders_020216&utm_source=medium&utm_medium=blog)。你可能会注意到 LoaderManager 中的 restartLoader() 方法，这个方法能让你强行进行重新加载。大多数情况下，如果 Loader 掌管着它的监听这个方法都必要，但如果你想要传入一个不同的 Bundle，它就变得必要了，因为那你会发现你之前使用的已存在的 Loader 已经被销毁，而一个新的 onCreateLoader() 的调用已经完成。\n\n我们提到在上面的实例实现 FileObserver 时使用 onReset() 方法而不是 onStopLoading() - 这样我们才能知道它何时与正常的生命周期交互。仅仅通过调用 initLoader()，我们就钩入 Activity/Fragment 的生命周期，而且 onStopLoading() 会在相应的 onStop() 方法被调用时被调用。然而，onReset() 只在你在 Activity/Fragment 完全被销毁时特定/自动调用 destroyLoad() 方法时才会被调用。\n\n##LoaderCallbacks\n\nLoaderCallback 是所有事务真正发生的地方，具体有三个回调：\n\n- onCreateLoader() — 构造具体的 Loader 实例\n- onLoadFinished() — 传递数据\n- onLoaderReset() — 清理引用以重设 Loader 数据\n\n所以前面的例子会变成：\n\n```java\n// A RecyclerView.Adapter which will display the data\nprivate MyAdapter mAdapter;\n// Our Callbacks. Could also have the Activity/Fragment implement\n// LoaderManager.LoaderCallbacks<List<String>>\nprivate LoaderManager.LoaderCallbacks<List<String>>\n    mLoaderCallbacks =\n    new LoaderManager.LoaderCallbacks<List<String>>() {\n      @Override\n      public Loader<List<String>> onCreateLoader(\n          int id, Bundle args) {\n        return new JsonAsyncTaskLoader(MainActivity.this);\n      }\n      @Override\n      public void onLoadFinished(\n          Loader<List<String>> loader, List<String> data) {\n        // Display our data, for instance updating our adapter\n        mAdapter.setData(data);\n      }\n      @Override\n      public void onLoaderReset(Loader<List<String>> loader) {\n        // Loader reset, throw away our data,\n        // unregister any listeners, etc.\n        mAdapter.setData(null);\n        // Of course, unless you use destroyLoader(),\n        // this is called when everything is already dying\n        // so a completely empty onLoaderReset() is\n        // totally acceptable\n      }\n    };\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n  super.onCreate(savedInstanceState);\n  // The usual onCreate() — setContentView(), etc.\n  getSupportLoaderManager().initLoader(0, null, mLoaderCallbacks);\n}\n```\n\n当然，没有硬性要求说必须使用 LoaderManager，即便你会发现这样做会更方便和省时间。你可以去看 FragmentActivity 和 LoaderManager 的源码以了解其内在的机制。\n\n##上面说的都很对，但我并不需要后台线程\n\nAsyncTaskLoader 尝试简化摆脱后台线程需要完成的操作，但如果你已经自己实现了后台线程处理事务的功能，或通过 EventBus 这样的订阅模型处理事务，AsyncTaskLoader 提供的功能都显得很多余。不妨看看加载一个位置改变的例子，例子中没有将所有代码放入 Activity/Fragment 中：\n\n```java\npublic static class LocationLoader extends Loader<Location>\n    implements GoogleApiClient.ConnectionCallbacks,\n    GoogleApiClient.OnConnectionFailedListener,\n    LocationListener {\n  private GoogleApiClient mGoogleApiClient;\n  private Location mLastLocation;\n  private ConnectionResult mConnectionResult;\n  public LocationLoader(Context context) {\n    super(context);\n  }\n  @Override\n  protected void onStartLoading() {\n    if (mLastLocation != null) {\n      deliverResult(mLastLocation);\n    }\n    if (mGoogleApiClient == null) {\n      mGoogleApiClient = \n            new GoogleApiClient.Builder(getContext(), this, this)\n            .addApi(LocationServices.API)\n            .build();\n      mGoogleApiClient.connect();\n    }\n  }\n  @Override\n  protected void onForceLoad() {\n    // Resend the last known location if we have one\n    if (mLastLocation != null) {\n      deliverResult(mLastLocation);\n    }\n    // Try to reconnect if we aren’t connected\n    if (!mGoogleApiClient.isConnected()) {\n      mGoogleApiClient.connect();\n    }\n  }\n  @Override\n  public void onConnected(Bundle connectionHint) {\n    mConnectionResult = null;\n    // Try to immediately return a result\n    mLastLocation = LocationServices.FusedLocationApi\n        .getLastLocation(mGoogleApiClient);\n    if (mLastLocation != null) {\n      deliverResult(mLastLocation);\n    }\n    // Request updates\n    LocationServices.FusedLocationApi.requestLocationUpdates(\n        mGoogleApiClient, new LocationRequest(), this);\n  }\n  @Override\n  public void onLocationChanged(Location location) {\n    mLastLocation = location;\n    // Deliver the location changes\n    deliverResult(location);\n  }\n  @Override\n  public void onConnectionSuspended(int cause) {\n    // Cry softly, hope it comes back on its own\n  }\n  @Override\n  public void onConnectionFailed(\n      @NonNull ConnectionResult connectionResult) {\n    mConnectionResult = connectionResult;\n    // Signal that something has gone wrong.\n    deliverResult(null);\n  }\n  /**\n   * Retrieve the ConnectionResult associated with a null \n   * Location to aid inrecovering from connection failures.\n   * Call startResolutionForResult() and then restart the\n   * loader when the result is returned.\n   * @return The last ConnectionResult\n   */\n  public ConnectionResult getConnectionResult() {\n    return mConnectionResult;\n  }\n  @Override\n  protected void onReset() {\n    LocationServices.FusedLocationApi\n        .removeLocationUpdates(mGoogleApiClient, this);\n    mGoogleApiClient.disconnect();\n  }\n}\n```\n\n我们可以在上面的代码中注意到三个主要的组件：\n\n- onStartLoading() 启动订阅进程（在例子中，通过连接 GooglePlay Service 完成）\n- 当我们获得返回的结果，调用 deliverResult()\n- 最后在 onReset()中 关闭连接并清理引用\n\n此时 Loader 框架不知道 GooglePlay Service 的任何细节，但我们能够将其逻辑封装在一个地方，并依赖一个单独的 onLoadFinished() 得到更新后的位置。这种形式的封装当然有助于避免与位置数据提供者耦合 - 你剩余的代码不需要关心位置对象到底是如何得到，来自何方。\n\n> Note: 在这种情况下，数据加载失败将以返回空位置的形式报告，这个信号会使监听此加载活动的 Activity/Fragment 调用 getConnectionResult() 方法并处理该结果。需要提醒的一点是：onLoadFinished() 方法中包含对 Loader 的引用，因此你能在那个时期检索 Loader 的任何状态\n\n##Loader: 只读取数据\n\n所以 Loader 在生命周期中只有一个目的：给你提供最新的信息，它通过观察设备的配置改变并添加其数据观测者完成该目的。这意味着 Activity/Fragment 不需要关注这些细节。（当然，你的 Loader 也不需要了解数据将会怎么被使用！）\n\n如果你已经在使用保留的 Fragment（调用了 setRetainInstance(true) 的 Fragment）在配置发生改变时保存数据，强烈建议你改为使用 Loader。保留 Fragment，在了解整个 Activity 的生命周期后，应该被视作完全独立的实体，当 Loader 直接关联到一个 Activity 或 Fragment 的生命周期中（甚至子 Fragment！）而且更适合检索当前需要显示的数据时。例如你需要动态添加或删除一个 Fragment - Loader 允许你将加载进程关联到其生命周期中，而且始终避免配置改变摧毁了已加载的数据。\n\n这个关注点也意味着你能够在 UI 之外进行数据加载的测试。这里的例子只传入 Context，但你能确切地传入到任何需要的类中（或模拟方法！）去减少测试。成为完全的事件驱动，当然可以在任意时刻判断当前 Loader 的状态及只为测试暴露某些额外的状态。\n\n> Note: 当有一个 LoaderTestCase 是为框架类设计的，如果你想要在 support v4 库的 Loader 完成相同的工作，你将需要利用 LoaderTestCase 源码开发一个支持库。这当然有助于你在不用 LoaderManager 的情况下与 Loader 交互。\n\n值得一提的是，现在 Loader 是活性的数据接收器。它们不负责改变依赖的数据，但它们覆盖对应组件的生命周期，并关注配置改变，从而为 UI 获取最新数据。\n\n##开发更好的 App\n加入[ Google + ](https://plus.google.com/u/0/+AndroidDevelopers/posts/96FWwtN2Vhr)和我们一起讨论这个问题，关注 [Android Development Patterns](https://plus.google.com/collection/sLR0p?utm_campaign=adp_series_loaders_020216&utm_source=medium&utm_medium=blog) 以了解更多！"
  },
  {
    "path": "issue-42/IndeterminateProgressbar解析-Part 1.md",
    "content": "IndeterminateProgressbar解析 – Part 1\n---\n\n> * 原文链接 : [Indeterminate – Part 1](https://blog.stylingandroid.com/indeterminate-part-1/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n* 状态 :  完成 \n\n\n\n\nIndeterminateProgressBar 能在用户进行某项不定时长的耗时操作时提供绝佳的用户体验，之前我有教过大家怎么创建[水平的 IndeterminateProgressBar](http://blog.csdn.net/u012403246/article/details/49582789)，今天我就来教大家实现圆形的 IndeterminateProgressBar，这个控件将支持 API 11（Honeycomb）以上的设备。\n\n这系列博文将有别于传统的自定义控件方法，而是从 Lollipop+ 提供的 API 实现去理解其运作机制，最后我会将从中学习到的知识应用到开发中，创造一个在低版本设备中效果类似的控件。\n\n在开始研究之前可以看看我们期望实现的效果：\n\n[Youtube视频](https://youtu.be/g6Zo6WDS2Gg)\n\n所以我们要做的就是：进度条在旋转，然后由长变短最后消失，再出现，由短变长。不妨看看[ Google 的实现](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/drawable/progress_medium_material.xml)：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2014 The Android Open Source Project\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n          http://www.apache.org/licenses/LICENSE-2.0\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:drawable=\"@drawable/vector_drawable_progress_bar_medium\" >\n    <target\n        android:name=\"progressBar\"\n        android:animation=\"@anim/progress_indeterminate_material\" />\n    <target\n        android:name=\"root\"\n        android:animation=\"@anim/progress_indeterminate_rotation_material\" />\n</animated-vector>\n```\n\n在这里他们使用了 AnimatedVectorDrawable - Lollipop+ 才有的特性，在低版本设备中将使用 Holo 风格的 drawable 资源。因此这个效果应用了两个动画 - 一个完成进度条长度变化，另一个完成旋转，[具体实现](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/drawable/vector_drawable_progress_bar_medium.xml)可以参考：\n\n```xml\n<!--\n Copyright (C) 2014 The Android Open Source Project\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n          http://www.apache.org/licenses/LICENSE-2.0\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:height=\"48dp\"\n        android:width=\"48dp\"\n        android:viewportHeight=\"48\"\n        android:viewportWidth=\"48\"\n        android:tint=\"?attr/colorControlActivated\">\n    <group\n        android:name=\"root\"\n        android:translateX=\"24.0\"\n        android:translateY=\"24.0\" >\n        <path\n            android:name=\"progressBar\"\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0, 0 m 0, -19 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38\"\n            android:strokeColor=\"@color/white\"\n            android:strokeLineCap=\"square\"\n            android:strokeLineJoin=\"miter\"\n            android:strokeWidth=\"4\"\n            android:trimPathEnd=\"0\"\n            android:trimPathOffset=\"0\"\n            android:trimPathStart=\"0\" />\n    </group>\n</vector>\n```\n\n我不想在这里解释 pathData，但它确实绘制了一个圆。但控件不会被渲染成一个圆，因为 trimPath 相关的值都为 0（其实是因为 trimPathEnd=\"0\"，使得 Path 起点处的绘制被停止）。\n\n值得一提的是 path 的命名及其成员元素都与 Animator 关联并应用于 AnimatedVectorDrawable。旋转 Animator 也被应用到其中（在这里都是很常见的方法，所以我不打算过多地解释，有兴趣的话可以[看这里](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/anim/progress_indeterminate_rotation_material.xml)）。但用于进度条长度变化的 Aniamtor 值得学习：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright (C) 2014 The Android Open Source Project\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n          http://www.apache.org/licenses/LICENSE-2.0\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <objectAnimator\n        android:duration=\"1333\"\n        android:interpolator=\"@interpolator/trim_start_interpolator\"\n        android:propertyName=\"trimPathStart\"\n        android:repeatCount=\"-1\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"0.75\"\n        android:valueType=\"floatType\" />\n    <objectAnimator\n        android:duration=\"1333\"\n        android:interpolator=\"@interpolator/trim_end_interpolator\"\n        android:propertyName=\"trimPathEnd\"\n        android:repeatCount=\"-1\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"0.75\"\n        android:valueType=\"floatType\" />\n    <objectAnimator\n        android:duration=\"1333\"\n        android:interpolator=\"@android:anim/linear_interpolator\"\n        android:propertyName=\"trimPathOffset\"\n        android:repeatCount=\"-1\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"0.25\"\n        android:valueType=\"floatType\" />\n</set>\n```\n\n在这里用了三个 Animator，三者并行运行动画效果，且每一个修改 path 元素中 trimPath 前缀的值，最终就实现了我们想要的效果。\n\n其中第一和第二个 ObjectAnimator 控制圆的起点和终点，绘制出两点间的圆弧。所以当我们以不同的速率改变这两个 Animator 的值时，就会得到我们所说的变长变短的效果。除了应用的插值器，它们对应的参数都是相同的不同。\n\n第三个 Animator 用于显示间距，以显示长度变化时，长度渐变消失的效果。\n\n关注我的读者都知道我很喜欢用插值器，因为它们实在是太好用了！在下一篇博文中我会解释其中的细节。\n\n因为这篇博文都是基于[ Google 源码](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/anim/progress_indeterminate_rotation_material.xml)进行的，所以我就不提供源码了，但后面的文章我保证都会有源码！"
  },
  {
    "path": "issue-42/剖析okhttp缓存机制.md",
    "content": "剖析OkHttp缓存机制\n---\n\n> * 原文链接 : [WHAT’S UNDER THE HOOD OF THE OKHTTP’S CACHE?](http://www.schibsted.pl/2016/02/hood-okhttps-cache/)\n* 原文作者 : [Damian Petla](http://www.schibsted.pl/author/petla/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n* 状态 :  完成 \n\n\n\n现在应用市场上的 App 无一不需要网络操作，这些应用的开发者大多数都选择结合使用 [OkHttp](http://square.github.io/okhttp/) 和 [Retrofit](http://square.github.io/retrofit/) 来完成网络操作。okHttp 最为人称道的一个特性就是它的缓存机制，而我将在本篇博文对其进行剖析。\n\n每次我用 OkHttp 时我都需要一些时间想想我将怎么使用它，我该用哪一个 HTTP 报头，作为一个客户端 App 我有哪些职责，我期望从服务器上获得什么，等等……所以对我来说，这篇博文将是 OkHttp 缓存特性的引用文档，我写这篇文档不是因为我在之后的开发中可能会接触 OkHttp 的缓存机制，而是因为我很有可能在下次需要使用它的时候却把这些知识忘了。\n\n这篇博文用于帮助熟悉 OkHttp 的高级工程师，我希望这篇博文的读者最起码应该知道怎么使用 OkHttp 以及怎么开启缓存。如果你不知道的话，就去 OkHttp 的维基页面学习吧。当然了，这里面的解释可能有一部分是错的，我只是尝试去解释一些我所理解的内容，所以如果你发现我某些部分的解释是有问题的，请通过邮件或其他方式告诉我。\n\n##分析的起点\n\n我将会从以下类开始我的分析工作：CacheStrategy 和 HttpEngine，其中 CacheStrategy 包含缓存机制的所有逻辑，HttpEngine 是 CacheStrategy 被调用的地方。\n\n- **CacheStrategy.Factory** 理解缓存候选响应的报头并将其转换为类成员的构造器\n- **CacheStrategy.getCandidate()** - 检查缓存候选响应，如果需要的话讲修改原始请求的报头\n\n这两部分内容是 OkHttp 整个缓存机制最关键的部分，而且几乎包含了所有我们想要了解的信息。虽说其他方法也很重要，但如果你去分析的话会发现最终还是会回到这里。\n\n##什么是缓存候选响应？\n\n第一次进行 HTTP 请求时不会存在任何已缓存内容，相关 API 只在有缓存内容的时候才会被调用，这点相信不需要我再多解释了。\n\n一旦响应被存储我们就能尝试将它用到后续的调用中，当然了，我们不可能将所有响应码对应的响应都存储下来。我们只存储以下响应码对应的响应：200, 203, 204, 300, 301, 404, 405, 410, 414, 501, 308。除此以外还有 302, 307，但存储它们对应的响应时必须满足以下条件之一：\n\n- contains **Expires** header OR\n- **CacheControl** contains **max-age** OR\n- **CacheControl** contains **public** OR\n- **CacheControl** contains **private**\n\n- 包含 **Expires** 报头\n- **CacheControl** 包含 **max-age**\n- **CacheControl** 包含 **public**\n- **CacheControl** 包含 **private**\n\n需要注意：OkHttp 的缓存机制不支持缓存部分报文内容。\n\n当我们重复某个请求，OkHttp 会判断是否已经有已缓存的响应，后文中我将称其为缓存候选响应。\n\n##CacheStrategy 是什么？\n\nCacheStrategy 需要一个新的报文和一个缓存候选响应，评估这两个 HTTP 报头是否有效并比较它们。\n\n首先，存放在缓存候选响应报头中的部分成员是：\n\n- Date\n- Expires\n- Last-Modified\n- ETag\n- Age\n\n下面是需要检查的条件的汇总列表：\n\n1. 判断缓存候选响应是否存在。\n2. 如果接收的是 HTTPS 请求，如果需要的话，判断缓存候选响应是否已进行握手。\n3. 判断缓存候选响应是否已缓存；这和 OkHttp 存储响应时完成的工作是相同的。\n4. 如果没有缓存，在请求报头的 Cache-Control 中检查对应内容，如果该标记为 true，缓存候选响应将不会被使用，后面的检查也会跳过。\n5. 在请求报头中查找 If-Modified-Since 或 If-None-Match，如果找到其中之一，缓存候选响应将不会被使用，后面的检查也会跳过。\n6. 进行一些计算以得到诸如缓存响应缓存响应存活时间，缓存存活时间，缓存最大失活时间。我不希望在此解释所有对应实现的细节，因为这会让博文变得冗长，你只需要知道以上提到的报文内容（例如：Date, Expires 等等…）还有请求 Cache-Control 中的 max-age, min-fresh, max-stale 这部分相关的计算耗时是毫秒级的。完成检查最简单的办法是写这样的伪代码：if (\"cache candidate's no-cache\" && \"cache candidate's age\" + \"request's min-fresh\" < \"cache candidate's fresh lifetime\" + \"request's max-stale\")\n\n如果上述条件被满足，那么已缓存的响应报文将会被使用，后面的检查将跳过。\n7. 在需要进行网络操作时，下一次检查会判断它是否为“条件请求”。条件请求指的是：发送报文请求服务器响应，而服务器可能会也可能不会返回新的内容，或让我们使用已缓存的报文。\n\n#WE LOOK FOR ANDROID DEVELOPERS!\n##条件请求\n\n下一节看起来是新的内容，但在代码结构中和上一节所解释内容是在相同的方法里的。\n\n1. 如果缓存候选响应包含 ETag 报头，那么新的 If-None-Match 请求报文会使用相同的 ETag 值。\n2. 如果上一点没有被满足，且缓存候选响应包含 Last-Modified，那么新的 If-Modified-Since 请求报文会使用该值。\n3. 如果以上两点都没有被满足，且缓存候选响应包含 Date，那么新的 If-Modified-Since 请求报文会使用该值。\n\n##还有更多！\n\n上述描述过程的最后，也就是 **CacheStrategy.getCandidate()** 方法返回后，还会有一个检查。OkHttp 会在此时判断报头的 **Cache-Strategy** 是否包含 “**only-if-cached**“ 参数。如果有的话，不会请求新的数据。OkHttp 会强制使用缓存候选响应（如果有且可用），不然的话会抛出 HTTP **504 Unsatisfiable Request** 错误。\n\n##怎么运作？\n\n你可能自己已经知道，进行 HTTP 操作时就是设置正确的报头。但不添加额外的报头内容，使用默认的 HTTP 调用也能完成网络操作，我会在后面提及这部分内容。\n\n##无网络环境\n\n刚开始我遇到的问题就包含这个，那时我天真地以为只要允许进行缓存就会解决这个问题，然而并不行，我知道的两种解决方法是：\n\n- 使用 **Cache-Control : only-if-cached header**，报文将永远也不会到达服务器。只有存在可用缓存响应时才会检查并返回它。不然的话，会抛出 504 错误，所以开发的时候别忘了处理这个异常。\n- 使用 **Cache-Control : max-stale=[seconds]** 报头，这种办法更灵活一些，它向客户端表明可以接收缓存响应，只要该缓存响应没有超出其生命周期。而该缓存响应是否可用由 OkHttp 检查，所以不需要与服务器建立连接来判断。但即使如此，当缓存响应超出其生命周期，网络操作还是会进行，然后得到服务器返回的新内容。\n\n##关心用户的开销和运行效率\n\n上面提到的解决方法依赖 OkHttp 的验证机制能在离线时完成一些操作。通常 App 都是在线状态，而我们更希望得到新的数据，这样会一次又一次地从服务器上获取相同的内容，给用户产生冗余的开销并降低 App 的运行效率。\n\n理想状态下，我们发送一个请求，得到真正的新数据。当服务器的数据没有发生改变，将得到 304 响应码，而 OkHttp 返回缓存响应。如果服务器支持缓存的话，这种情况是可以实现的。所以你需要做的大体就是和你的同事研究 **ETag** 或 **Last-Modified-Since** 是否被支持，如果支持的话，OkHttp 会帮你解决剩下的麻烦！\n\n**NOTE**: 请不要自己设置 **If-Modified-Since** 或 **If-None-Match**。前文已经提到，设置这两个报头会跳过后续的许多检查，进行网络操作。\n\n##强制网络操作\n\n你也可以强制进行网络操作，但就我个人而言，我还没有这样用过 OkHttp，不过我发现了一些可能的应用场景。例如你在报头中使用了 max-stale，过了很久服务器接收到了该报文。当你想允许用户在某些特殊情况下强行进行刷新时，很简单，在请求中使用 Cache-Control : no-cache，你就能从服务器得到新的数据了。\n\n##结论\n\n在我看来，OkHttp 非常易用，极大简化了网络操作。而且有助于理解数据流，哪些报头能被设置以及它们的用处。我希望这篇博文能给你提供一些帮助。\n\n祝你好运！"
  },
  {
    "path": "issue-42/在Activity配置改变时保存状态.md",
    "content": "在Activity配置改变时保存状态\n---\n\n> * 原文链接 : [Activity Revival and the case of the Rotating Device](https://medium.com/google-developers/activity-revival-and-the-case-of-the-rotating-device-167e34f9a30d#.mmqbihfo6)\n* 原文作者 : [Joanna Smith](https://medium.com/@dontmesswithjo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n* 状态 :  完成 \n\n\n\n\n可能很多开发者知道旋转设备会让 Activity 重启，但知道这一点并不重要，因为这不能帮你解决什么问题，你需要知道的是，为什么旋转设备会让 Activity 重启，以及这种情况发生时你要怎么应对。\n\n##理解配置改变\n\n首先，“旋转屏幕”不是我们今天讨论的场景，因为任何配置的改变都会导致 Android 内核重启你的 Activity，配置改变可能是设备旋转（因为需要绘制不同的布局），也可能是系统语言切换（因为我们需要重新显示字符串，而这可能会需要更多/更少的空间以显示对应的字符），也可能是当前键盘的可用性。而以上所提到的种种，实质都是系统正尝试帮助你的 App 以正确的资源完成加载。\n\n##到底发生了什么？\n\n通过重新加载 App，系统真正做的是调用 onDestroy()，然后立刻调用 onCreate()，这样你的 Activity 上加载的都会是正确的数据。\n\n> Note: 此时理解 Activity 生命周期就变得重要了，如果你是初学者，不妨花点时间理解它。\n\n##那我该怎么办呢？\n\n现在你有几个可选项，我会一一交给你，你可以为你的 App 选择最优解。\n\n##让系统为你保存状态\n\n这是最快，也是最明显的解决办法（甚至可能是你现在应该做的）在 Activity 的生命周期中，onSaveInstanceState() 会在 onDestroy() 之前被调用。\n\n当 Activity 被创建，会自动调用 onRestoreInstanceState() 方法并在其中完成一次匹配，这会使系统为保存和加载你的数据担忧，因为你已经提前计划并映射重要的内容了。（你也可以跳过 onRestoreInstanceState() 并用 onCreate() 中的 Bundle 参数加载你保存的状态，选择哪种方式取决于你的偏好。如果你希望在尝试加载用户状态前 onCreate() 已经完全结束的话，onRestoreInstanceState() 会更有用些，要不然的话区别不大。）\n\n这样你可以管理所有相关的数据，即便你的用户是个连重力方向都搞不清楚的小孩。\n\n> Note: 但 onSaveInstanceState() 不能保证你的状态被保存，它只会在这些情况被调用。因为系统想在中断时保存你的状态（屏幕方向改变），但系统不太在意用户何时点击后退按钮，因为这是通过 App 完成的（怎么我听起来反倒像系统为我做了优化呢）。\n\n###但我需要更多的控制！\n\n或许你有大量数据需要存储，或者你并不仅仅尝试保存状态，但 App 确实在 portrait 和 landscape 之间发生了切换，此时布局会重绘按钮和创建左导航图标，但你可能想操控这些改变，或者区分屏幕旋转，语言改变，App 失去焦点。此时你可以用 onConfigurationChanged()，它能提供对应的 Configuration 对象，使得你能知道当前到底发生了什么，这样 App 就能响应你真正在意的那些配置改变。\n\n```java\n@Override\npublic void onConfigurationChanged(Configuration newConfig) {\n  super.onConfigurationChanged(newConfig);\n  if (newConfig.orientation ==\n      Configuration.ORIENTATION_LANDSCAPE) {\n    // Change things\n  } else if (newConfig.orientation ==\n      Configuration.ORIENTATION_PORTRAIT){\n    // Change other things\n  }\n}\n```\n\n但这也意味这你必须自己管理配置改变，任何你没有管理的改变都不会在你的应用上体现出来，所以你要重视这一点。在 Manifest 上你需要指出哪些配置改变是你需要观察的：\n\n```xml\n<activity android:name=\".MyActivity\"\n  android:configChanges=\"orientation|keyboardHidden\"\n  android:label=\"@string/app_name\">\n```\n\n现在你已经知道解决的手段了，其中一些办法和其他的办法由微妙的区别（例如 screenLayout vs orientation vs layoutDirection),所以你必须深思熟虑并作出你的决定，处理你真正在意的情况，其他的交给系统。\n\n> Note: 有些开发者认为他们完全可以通过声明哪些东西应该为屏幕旋转负责以避免处理屏幕旋转，这种想法是错的，因为：1、用户不希望被告知哪些能做，哪些不能做；2、如果应用只能在一个方向被使用，你就应该把应用锁定在那个方向上。\n\n###那后台任务怎么办？\n\n因为我们只需要处理 Activity 中存在的情况，所以这不是问题，除非你用的是 AsyncTask。因为 AsyncTask 一般与 Activity 关联，而你又不希望 AsyncTask 跟着 Activity 一次又一次重启，因为这可能会让你的下载任务无法完成，或者终止 AsyncTask 需要完成的任务。所以系统不会在 Activity 重启时销毁 AsyncTask。\n\n听起来有点恐怖，因为 AsyncTask 可能持有 被销毁 Activity 的引用（该 Activity 中任何被创建的 AsyncTask 非静态内部子类都会显示持有该引用），而这会带来内存泄漏的问题。\n\n可能你会说：那我在 Fragment 里用 AsyncTask 不就好了。这个想法看起来很 6，但不是最佳选项，更好的选择应该是：完全移除 AsyncTask，用 Loader 完成你的任务。\n\n###忘了 Fragment\n\n很容易误用被保存的 Fragment，为了避免这样的情况，我们还有更好的选择 - 用 Loader。或用被保存的 Fragment 保存 View 或 Adapter，但这样做是错的，因为这些对象都与 Activity 绑在一起，这就意味着你正在造成严重的内存泄漏。\n\n如果你正维持与聊天客户端的长连接，使用被保留的 Fragment 会是个好主意。因为你希望该连接存活地尽可能久，只要 Activity 没有被销毁。但你也希望该连接在 Activity 被销毁时终止，所以让你用前台服务不是个好建议。\n\n###这对 AsyncTask 意味着什么？\n\n把 AsyncTask 换成 Loader。如果你没有加载数据，而是进行网络连接，那你应该使用 JobScheduler 以完成更好的网络访问和后台任务。\n\n##但我的 App 是特别的\n\n是的，有些 App B格比较高，下面是更高级的建议。\n\n###我的 App 必须不改变方向！\n\n无论是设计得很酷的游戏还是媒体播放器，还是希望保护连旋转设备是啥不都不知道的小孩，你可以选择让 App 锁定一个布局，这样无论用户怎么玩弄他的设备，都不会发生改变。\n\n```xml\n<activity android:name=\".MyActivity\"\n  android:screenOrientation=\"landscape\"\n  android:label=\"@string/app_name\">\n```\n\n这样，你的应用可能在平板设备上出现问题，因为你把它锁定为 landscape，而此时显示区域大于你的布局。所以你就需要使用 setRequestedOrientation() 方法而不是在 Manifest 文件中完成该设置了。\n\n###等会，回来。屏幕旋转不是个坏设计，对吧？\n\n错。屏幕旋转可能会非常复杂。这里有多种选项，而这意味着你可以创建很酷的用户体验，基于 App 想要实现的效果。例如，依赖传感器或颠倒 View，或让 Activity 在旋转后停留在新 Activity 的背后。\n\n###然而我用的是 Fragment，而不是 Activity\n\n没关系，这里的目标是控制状态，对吧？Activity 有的一切 Fragment 都可以用，事实上，你也可以在 Fragment 中保存状态：onSaveInstanceState()。为了 恢复状态，你可以访问 onCreate()，onCreateView(), 或 onActivityCreated() 中的 Bundle。\n\n> Note: Fragment 的生命周期非常值得认真学习，如果你是初学者的话可以去看看文档。"
  },
  {
    "path": "issue-42/提高NYTimes的启动速度.md",
    "content": "提高NYTimes的启动速度\n---\n\n> * 原文链接 : [Improving Startup Time in the NYTimes Android App](http://open.blogs.nytimes.com/2016/02/11/improving-startup-time-in-the-nytimes-android-app/?_r=1)\n* 原文作者 : [Mike Nakhimovich](http://open.blogs.nytimes.com/author/mike-nakhimovich/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [desmond1121](https://github.com/desmond1121) \n* 状态 :  完成 \n\n\n\n和大部分应用一样，“提高应用启动和加载时间”成为 NYTimes 优先考虑的需求，因为设备制造商继续为市场提供更快更流畅的设备使用体验，因此用户也期望应用有更好的性能表现。\n\n我们组最近重写了我们的新闻 App 以发挥当今应用开发趋势的优点，我们使用了诸如依赖注入和响应式编程的技术。这次重写提高了代码的可维护性，降低代码耦合度以模块化，但也需要对代码进行一些调整以优化其表现。\n\n我们刚发布小名为 Phoenix 的新版本的 App 时，在 Nexus 5 上的启动时间为 5.6 秒，这个结果显然不能使我们满意，毕竟我们期望的是达到 2 秒或更少，但这也激励我们花费更多的时间和精力去提升该应用的性能。\n\n然后我们发现拖慢启动时间的原因是使用了反射的那部分代码。处理了这些使用反射的代码以及修复了一些其他的小问题后，启动时间被缩短到 1.6 秒。\n\n##我们是怎么做到的\n\n首先，我们跟踪 Android 应用的生命周期方法并进行特征提取得到 App 的启动时间，我们在 Application 类的构造方法中开始计时，进度条显示结束后停止计时，详见[文档](http://developer.android.com/tools/debugging/debugging-tracing.html#creatingtracefiles)。\n\n然后通过追踪 DDMS 收集追踪文件以分析启动时间，找到对启动时间影响最大的模块。但这样分析费时费力，所以我们换了个办法，使用了 [NimbleDroid](https://www.nimbledroid.com/) 来帮助我们完成分析。NimbleDroid 提供了更简单的办法以找出造成性能瓶颈的因素，简化了性能比较的操作。\n\n##The Low Hanging Fruit\n\n我们找到的降低启动时间的第一个原因就和大量的类关联在一起，内存密集型运行库，Groovy 加载 Jar 资源时造成昂贵的开销，以及之前 [Joda Time 发现的其他库存在的性能问题](http://blog.danlew.net/2013/08/20/joda_time_s_memory_issue_in_android/)。我们使用 Groovy 是为了闭包；但 Android Studio 提供的代码折叠功能已经解决了这个问题。因此我们不再需要使用 Groovy，所以我们用 Java 7 提供的 API 把原来的代码替换为纯 Java 语法实现的代码，把 Groovy 从项目中移除了。虽说我们现在还在想办法增强 IDE 的功能，使得我们能直接查看匿名类，但这个需求的优先级要小一点。\n\n然后我发现 RxJava 也带来了一些性能开销，在应用启动时大约增加了 1 秒。幸运的是，有人提了个 issue 指出这个问题，在 RxJava 的下一个版本会修复它。\n\n一些和我们合作的第三方数据分析客户端在 App 启动时也造成了阻塞，于是我们修改了他们初始化的方式，并与供应商合作提升预加载的性能。\n\n我们还在代码中发现了许多“技术债”带来的性能开销：由于 md5 计算使得对象初始化被阻塞；构造方法中的阻塞，一般是因为前台当前需要完成的操作太多。\n\n完成了这些调整后，应用启动时间降低到 3.2 秒了 - 大约是优化前的一半，应用也比之前流畅了。接下来我们的目标就是优化数据流，因为我们的 App 是数据密集型的，因此这是一个严峻的考验\n\n##介绍数据存储\n\n因为我们所有数据都是异步观测处理的，在演示和数据层我们用单例的“内容管理者”来作为抽象。虽说这样有助于封装，但在初始化时增加了时间，因为无论何时，需要加载持久性存储的数据时它就会成为“上帝对象”，完成加载。\n\n某些情况下，这就会带来性能问题，例如：在 App 启动时磁盘需要数据分析配置的属性值，但要获得该值需要等到 SSL 允许的网络客户端初始化完成。当应用渐渐变得庞大，添加的依赖越来越多，“内容管理者”初始化需要的时间就会越多，应用的启动时间也会因此变得更长。\n\n为了解决这个问题，我们决定不再使用一个“内容管理者”管理数据，而是移到几个不同的地方存储数据，使我们能尽可能快地加载缓存数据。与最近 Facebook 完成的工作类似，我们尽可能将代码路径从磁盘移动到 UI。\n\n我们首先将“内容管理者”分解成几个由磁盘和网络 DAO支持的独立的单例数据存储对象。例如，我们将 ConfigStore 分解成由 ConfigNetworkDAO 和 ConfigDiskDAO 支持完成其功能。\n\n```java\n@Singleton\npublic class ConfigStore extends Store {\n   @Inject\n   public ConfigStore(AppConfigParser parser,\n                      final ConfigDiskDAO loader,\n                      final Lazy<ConfigNetworkDAO> fetcher) {\n       super(loader,fetcher,parser);}\n```\n\nDagger 的延迟加载允许我们注入延迟的网络客户端，在我们真正需要进行网络操作时才初始化该类，在离线或第一次数据加载完成后这个特性很重要。我们的架构非常依赖后台服务所进行的数据下载。事实上，数据大部分是从磁盘存储区域中加载到 UI 上的，而不是通过网络调用获得。在我们能够创建从磁盘到屏幕的最优路径后，需要解决的问题就剩下反射了。\n\n##移除反射\n\n在尝试优化数据加载的性能时，我们发现为 Top Stories 解析数据需要花费大于等于 700 毫秒的时间，无论是从磁盘上获得数据，还是从网络中获得数据。我们现在才意识到 Gson 在 Android 上的性能表现是这么差，对我们这种数据驱动的应用来说，这简直是噩梦……在分析了启动时间轨迹后，我们发现反射型 Adapter 的调用非常耗时。\n\n我们尝试最小化对 Gson 的使用，并移除 Gson 中有关反射的调用，但唯一可行的方法是一个个重写那些 Adapter。于是我们花了很多时间精力去找不需要使用反射且不会给应用启动带来昂贵开销的序列化的办法。最后我们有几个可选项，但都需要给 Model 添加代码。因此我们回到最简单的解决办法：自定义 Gson 的 Adapter。\n\n完成这部分工作后，性能表现大概提高了十倍。为了保持开销为最小，我们使用了 [Immutables library](http://immutables.github.io/json.html)，它会在编译时为我们的数据 Model 创建 Adapter，并给我们带来像 AutoValue 那样的不变性。\n\n```java\n@Value.Immutable\n@Gson.TypeAdapters\npublic abstract class AppConfig {\n   public abstract Optional<DeviceGroups> deviceGroups();\n   public abstract Marketing marketing();\n   public abstract List<OverrideCondition> overrides();\n   public abstract LinkedHashMap<String, List<Integer>> imageCropMappings();\n}\n\npublic AppConfigParser() {\n     gson = new GsonBuilder()\n           .registerTypeAdapterFactory(new GsonAdaptersAppConfig()) //auto generated adapter\n                   .create();\n}\npublic void parse() {\nreader = new InputStreamReader(source.inputStream(), UTF_8);\nAppConfig appConfig = gson.fromJson(reader, AppConfig.class);\n}\n```\n\n现在数据流如下：\n\n后台服务订阅延迟初始化网络客户端的 RxStore，并以 JSon 数据格式下载新的数据，然后把数据转化为数据流存到磁盘，而不是将它作为一个 JSon 对象保存，这样避免加载超过 1m 的对象到内存中。\n\n当 UI 需要数据，就会订阅由内存缓存和磁盘支持的数据存储对象提供的不变数据，我们只需要初始化网络客户端，如果磁盘中存储的值不是不变的，或者数据格式发生了变化，而且永远不会直接从网络加载到 UI 上。该非定向数据流意味着 99% 的对数据存储对象的订阅不会产生任何影响，除了磁盘中存储的数据。\n\n之后我们还会继续探索诸如 FlatBuffer 的方法以序列化数据存储到磁盘中；然而，我们对现状挺满意的。重新发布的应用给用户显示首页的所有内容只需要2秒。\n\n##最后\n\n反射会在 Android 中带来显著的性能开销，特别是那些庞大，数据驱动的 App。因此，开发者应该尽可能避免使用反射开发这类应用，特别是在应用启动时。\n\n最后感谢我的同事们齐心协力解决了我们面对的性能问题：现在我们在 Nexus 5 上的启动时间只有 1.6 秒。\n\n对我们完成的工作感兴趣吗？来成为我们的一员把！"
  },
  {
    "path": "issue-43/IndeterminateProgressbar解析-Part 2.md",
    "content": "IndeterminateProgressbar解析-Part 2\n---\n\n> * 原文链接 : [Indeterminate – Part 2](https://blog.stylingandroid.com/indeterminate-part-2/)\n* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n\nIndeterminateProgressBar 能在用户进行某项不定时长的耗时操作时提供绝佳的用户体验，之前我有教过大家怎么创建[水平的 IndeterminateProgressBar](http://blog.csdn.net/u012403246/article/details/49582789)，今天我就来教大家实现圆形的 IndeterminateProgressBar，这个控件将支持 API 11（Honeycomb）以上的设备。\n\n在上一篇博文中我们学习了圆形 IndeterminateProgressBar 的 AOSP 实现方式，以及 AnimatedVectorDrawable 如何被用于显示 VectorDrawable 路径动画以得到圆形 IndeterminateProgressBar 的长度变化动画。在本篇博文中我们将把注意力转移到插值器上，即研究圆形 IndeterminateProgressBar 在显示长度变化动画时，是如何控制圆弧起点和终点的位置的。\n\n不妨直接看 AOSP 的源码吧，[首先看 控制 trimPathStart 的值的插值器](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/interpolator/trim_start_interpolator.xml):\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright (C) 2014 The Android Open Source Project\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n          http://www.apache.org/licenses/LICENSE-2.0\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<pathInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:pathData=\"L0.5,0 C 0.7,0 0.6,1 1, 1\" />\n```\n\n乍一看这代码怎么这么少，那个 PathInterpolator 到底是什么鬼？\n\nPathInterpolator 其实就是 Lollipop 中介绍的描述插值器功能最具代表性的插值器子类，看到这里不要绝望，在以后的博文中我们还会遇到 PathInterpolatorCompat 这个类。\n\nA PathInterpolator takes an SVG pathData parameter which describes a mapping function of the form y = f(x). That sounds rather complex, but it is actually much simpler than it sounds. Effectively it is a square canvas which is one unit in each direction from 0,0 to 1,1. The PathInterpolator works by returning the y value for any given x value. The input values range from 0.0-1.0, so there only stipulation is that each possible value for x can only map to a single value of y – the path cannot double back on itself in the horizontal plane in other words.\n\nPathInterpolator 需要 SVG pathData 参数以描述 y = f(x) 的映射关系，可能听起来有点复杂，但其实它非常简单。实际上，就是一个每个方向上都有一个从 0,0 到 1,1 的单元的方形画布，PathInterpolator 可以根据任意给定的 x 的值返回给定函数所映射的 y 值，其接收的输入的取值范围是 0.0 - 1.0，所以其输入的唯一约定就是：每一个可能的 x 和 y 必须是1对1映射。\n\n如果我们画了一条从 0,0 到 1,1 的直线，实际上就相当于创建了线性插值器（LinearInterpolator），因为每一个 x 的值对应的 y 值都相同。那么上述代码中 pathData 到底做了什么？运用一个有趣的技巧，我们可以创建 VectorDrawable 并添加该 path 元素，然后用 Android Studio 的预览功能就会看到下图：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:width=\"72dp\"\n  android:height=\"72dp\"\n  android:viewportWidth=\"1\"\n  android:viewportHeight=\"1\"\n  tools:ignore=\"UnusedResources\">\n\n  <path\n    android:strokeColor=\"#0000FF\"\n    android:strokeWidth=\"0.005\"\n    android:pathData=\"L0.5,0 C 0.7,0 0.6,1 1, 1\" />\n\n<vector>\n```\n\n![](https://i2.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/trim_start_interpolator.png?w=608)\n\n由图可知，0,0 就是路径的左上角，1,1 就是路径的右下角。当 x = [0.0 - 0.5], y = 0；当 x = (0.5, 1.0]，y 值的增长逐渐增加，然后超过中值，增长速率又渐渐变慢，最终为 1.0。其实际作用与 AccelerateDecelerateInterpolator 相同。如果我们研究 trimPathEnd 使用的插值器，会发现这两个阶段完成的工作比使用传统插值器要复杂：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright (C) 2014 The Android Open Source Project\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n          http://www.apache.org/licenses/LICENSE-2.0\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<pathInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:pathData=\"C0.2,0 0.1,1 0.5, 1 L 1,1\" />\n```\n\n看起来这段 xml 代码和上面的很类似，但 pathData 对应的值是不同的，不妨再次利用 VectorDrawable 将这个插值器对应的函数图像显示出来：\n\n![](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/trim_start_and_end_interpolators.png?resize=223%2C300)\n\n图中红色显示作加速的部分，y 值快速增加直到大于中值，然后减缓增大的速率直到值为 1.0，相当于把上面的过程反过来。\n\ntrimPathEnd 会控制长度变化动画中圆弧的终点，trimPathStart 则控制该圆弧的起点。利用这些插值器我们可以让圆弧的长度自动变化，以显示长度变化的动画。最终结合旋转动画就可以实现该圆形 IndeterminateProgressbar 的效果，即下面视频展示的效果：\n\n[视频](https://youtu.be/g6Zo6WDS2Gg)\n\n现在我们明白该控件是怎么实现的了，而且 AnimatedVectorDrawable 支持 Android 5.0 以前的版本，因此我们不用担心它的兼容性问题（在写下本文时 VectorDrawableCompat 由于兼容性的问题不是个好选择）。因此我们不需要 VectorDrawable 就可以利用到目前为止学习的知识在 API 11 上实现酷炫的动画效果，在下一篇博文中我将会介绍应该怎么去实现。\n\n因为这篇博文都是基于[ Google 源码](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/anim/progress_indeterminate_rotation_material.xml)进行的，所以我就不提供源码了，但后面的文章我保证都会有源码！"
  },
  {
    "path": "issue-43/LayoutInflater.inflate() 方法剖析.md",
    "content": "LayoutInflater.inflate() 方法剖析\n---\n\n> * 原文链接 : [Understanding Android's LayoutInflater.inflate()](https://www.bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/)\n* 原文作者 : [Sean Farrell](https://www.bignerdranch.com/about-us/nerds/sean-farrell/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n\n程序员很容易满足于写模板代码，因为这样最省事，但不愿走出舒适区，沉溺于这样的生活的话，往往会忽略许多细节，而我就是其中一员。LayoutInflater 相信没有 Android 开发者会陌生，它能将 xml 布局转化为相应的视图（如：ViewGroup 和 Widget），它还可以在 Fragment 的 onCreateView() 方法里面初始化 Fragment 的布局。在阅读了 Google 给出的有关 LayoutInflater 的文档说明以及网上对它的讨论，我发现有许多开发者都不了解 LayoutInflater 的 inflate() 的细节，而且一直以错误的方式使用它。\n\n使用 inflate() 方法的困惑大多来自 Google 文档对该方法的第三个参数 - attachToRoot 模糊的解释。\n\n> 被初始化的视图布局是否应该添加到方法的第二个参数 ViewGroup root 上？如果不应该，root 只能被用于创建根布局所使用的 LayoutParams。\n\n如果 attachToRoot 为 true，那么作为参数传入的 xml 布局就会被初始化，并添加到方法的第二个参数 - ViewGroup 上。\n\n而 inflate() 方法将返回该组合后的 View，该 View 的根布局就是该 ViewGroup。而当 attachToRoot 为 false，xml 布局将被初始化且作为 inflate() 方法的返回值返回。但此时该 View 的根布局是 xml 文件中的根布局，而不是 inflate() 方法的第二个参数 ViewGroup 所对应的布局。不管怎样，ViewGroup 的 LayoutParams 都需要准确地按照该 xml 布局显示视图。\n\n将 attachToRoot 设为 true 使得 xml 布局被添加到参数的 ViewGroup 中；将它设为 false，会使该布局以其他方式被添加到 ViewGroup 中。\n\n##将 attachToRoot 设为 true\n\n假设我们在 xml 布局中显示一个 Button，其 width 和 height 都是 match_parent\n\n```xml\n<Button xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:id=\"@+id/custom_button\">\n</Button>\n```\n\n现在我们要把该 Button 添加到 Activity/Fragment 中的 LinearLayout，假设此时 LinearLayout 已经是 Activity/Fragment 的成员，那么我们可以这样实现：\n\n```java\ninflater.inflate(R.layout.custom_button, mLinearLayout, true);\n```\n\n我们让 inflate() 方法通过 xml 文件得到 Button 布局，且通过设置 attachToRoot 让 LayoutInflater 将 Button 添加到 mLinearLayout 中，Button 的 LayoutParams 应该是 LinearLayout.LayoutParams。\n\n同样的，下面的方法和上面的效果一样，因为该方法默认将 attachToRoot 设为 true。\n\n```java\ninflater.inflate(R.layout.custom_button, mLinearLayout);\n```\n\n其他将 attachToRoot 设为 true 的正确用法是传入自定义 View，在下面的例子里，xml 布局中使用了 <merge> 标签允许布局将它里面嵌套的 View 直接 include 到它的父布局中，而没有多加一层 ViewGroup，减小了 ViewTree 的深度。\n\n```java\npublic class MyCustomView extends LinearLayout {\n    ...\n    private void init() {\n        LayoutInflater inflater = LayoutInflater.from(getContext());\n        inflater.inflate(R.layout.view_with_merge_tag, this);\n    }\n}\n```\n\n这样就是将 attachToRoot 设为 true 的完美用法了，布局文件没有设置 ViewGroup 去容纳控件，使得我们能将自定义的 LinearLayout 设为该布局的容器。如果我们没有使用 <merge> 标签，而是将 FrameLayout 设置为它的根布局，那么初始化完成的工作和政策情况是一样的。该 FrameLayout 和 子元素都会被添加到 LinearLayout 中，LinearLayout 将作为根 ViewGroup 容纳 FrameLayout。\n\n##将 attachToRoot 设为 false\n\n现在不妨学习应该在何时将 attachToRoot 设为 false 吧。此时，View 将不会被添加到第二个参数传递的 ViewGroup 中。\n\n回忆前面的例子吧，我们可以通过下面的办法将该 Button 布局添加到 LinearLayout 中。\n\n```java\nButton button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);\nmLinearLayout.addView(button);\n```\n\n这两行代码和我们之前将 attachToRoot 设为 true 时一行代码完成的工作相同，将 attachToRoot 设为 false，意味着我们表示：不想将 View 添加到根 ViewGroup 中。但如果想添加到根 ViewGroup 中的话，通过 addView() 方法就可以实现。\n\n虽说将 attachToRoot 设为 false 时，想要将 Button 添加到 ViewGroup 写的代码要比将 attachToRoot 设为 true 要多一些，但有些情况是必须将 attachToRoot 设为 false 的。\n\n例如 RecyclerView 在 onCreateViewHolder() 方法中初始化子布局时就必须将 attachToRoot 设为 false\n\n```java\npublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n    LayoutInflater inflater = LayoutInflater.from(getActivity());\n    View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false);\n    return new ViewHolder(view);\n}\n```\n\n何时初始化子布局，何时显示子布局，由 RecyclerView 决定，因此，将 View 添加到 ViewGroup 中的职责就不该由我们完成，所以 attachToRoot 就应该设为 false。\n\n当在 Fragment 的 onCreateView() 中初始化并返回布局时，必须将 attachToRoot 设为 false，不然会抛出 IllegalStateException，因为该 View 已经属于某个父布局了。\n\n```java\nFragmentManager fragmentManager = getSupportFragmentManager();\nFragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup);\n\nif (fragment == null) {\n    fragment = new MainFragment();\n    fragmentManager.beginTransaction()\n        .add(R.id.root_viewGroup, fragment)\n        .commit();\n}\n```\n\nroot_viewGroup 将在 Activity 中显示 Fragment，同时是 onCreateView() 中的 ViewGroup 参数，同时也是 inflate() 方法中的 ViewGroup 参数。将 Fragment 的 View 添加到 ViewGroup 的任务由 FragmentManager 完成，如果你不想两次进行该添加操作，就把 attachToRoot 设为 false 吧。\n\n```java\npublic View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {\n    View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);\n    …\n    return view;\n}\n```\n\n如果我们不想在 onCreateView() 中将它添加到 ViewGroup 中，为什么要在参数中提供 ViewGroup 参数呢？为什么 inflate() 方法需要根 ViewGroup？即使我们没有在初始化 View 后立刻将它添加到它的父 ViewGroup 中，我们也需要使用父 ViewGroup 的 LayoutParams 来初始化 View 的布局，即使它不会被添加到该 ViewGroup 中。\n\n网上有很多有关 LayoutInflater 的用法，如果你想将 attachToRoot 设为 false，就将 ViewGroup 参数那填入 null。但如果父 ViewGroup 是可用的，还是应该将它填入该方法。\n\n![](https://www.bignerdranch.com/img/blog/2016/02/null-root.png)\n\nLint 会警告你别填入 null，虽说 App 不会因此崩溃，但可能会出现些问题。当 View 不知道 ViewGroup 的 LayoutParams 到底该是什么时，它会尝试通过自身的 generateDefaultLayoutParams 判断。而这些默认的 LayoutParams 可能不是你想要的。你在 xml 文件中设置的 LayoutParams 都会被无视，导致布局不是你想要的。\n\n下面是你没有 ViewGroup 可传递的具体的场景。\n\n```java\nAlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);\nView customView = inflater.inflate(R.layout.custom_alert_dialog, null);\n...\ndialogBuilder.setView(customView);\ndialogBuilder.show();\n```\n\n这种情况下，传递 null 是没有问题的，因为 AlertDialog 将 LayoutParams 重载，使得无论如何都是 match_parent。但通常情况下，你都是要传入父 ViewGroup 的。\n\n##避免崩溃，错误布局以及对 LayoutInflater 的误解\n\n希望这篇博文能帮你避免崩溃、错误布局以及对 LayoutInflater 的误解，下面是一些建议：\n\n- 如果你有可传递的根 ViewGroup，那就传递\n- 避免在 ViewGroup 参数那填入 null\n- 当添加 View 到 ViewGroup 的职责不由我们负责时，attachToRoot 就设为 false\n- 若 View 已经被添加到 ViewGroup 中，别将 attachToRoot 设为 true\n- 自定义 View 很适合将 attachToRoot 设为 true"
  },
  {
    "path": "issue-43/结合motion和Transition实现共享元素的酷炫动画.md",
    "content": "结合motion和Transition实现共享元素的酷炫动画\n---\n\n> * 原文链接 : [Meaningful Motion with Shared Element Transition and Circular Reveal Animation](http://www.thedroidsonroids.com/blog/android/meaningful-motion-with-shared-element-transition-and-circular-reveal-animation/)\n* 原文作者 : [Mariusz Brona](http://www.thedroidsonroids.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n##概述\n\n水波纹动画是 Material Design 中最受欢迎的动画之一，下面是官方文档对它的描述：\n\n> 水波纹动画能在显示/隐藏一组 UI 元素时为用户提供连续的视觉体验。\n\n使用水波纹动画，我们可以创造更酷炫的动画效果，今天我会教大家组合使用水波纹动画，淡出/淡入动画以及共享元素 Transition，创造酷炫的 Transition 动画，下面是某个 UI 控件使用该动画前和使用动画后的效果：\n\n使用前：\n\n[视频](https://youtu.be/GtSFkEzPbDM)\n\n使用后：\n\n\n[视频](https://youtu.be/0KQfK3GatL8)\n\n##首先：共享元素 Transition\n\nAndroid Lollipop 介绍了新的设计理念 - Material Design，在本次更新中我们了解到新的，很酷的设计风格，例如共享元素 Transition。Transition 使我们能够根据用户的意向提供一致性体验，实现 Material Design 核心理念之一。具体实现可以通过下面三个步骤完成：\n\n##声明共享元素 Transition\n\n首先，我们需要声明使用 Transition 的 Activity 视图中某个 UI 控件为共享元素，这一步可以通过 xml 或 java 代码完成：\n\n```xml\n<android.support.design.widget.FloatingActionButton\n\tandroid:id=\"@+id/activity_main_fab\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_width=\"wrap_content\"\n\tandroid:layout_margin=\"@dimen/activity_horizontal_margin\"\n\t...\n\tandroid:transitionName=\"reveal\"/>\n```\n\n或\n\n```java\n\tmFab.setTransitionName(\"reveal\");\n```\n\n然后我们要在需要通过显示 Transition 动画启动的 Activity 中声明 Transition 的名称，然后通过这些方法启动 Activity：\n\n```java\nIntent intent = new Intent(this, ContactActivity.class);\nActivityOptionsCompat.makeSceneTransitionAnimation(Activity activity,Pair<View, String>... sharedElements);\nActivityCompat.startActivity(this, intent, option.toBundle());\n```\n\n或\n\n```java\nIntent intent = new Intent(this, ContactActivity.class);\nActivityOptionsCompat.makeSceneTransitionAnimation(Activity activity, (View)sharedElement, String transitionName);\nActivityCompat.startActivity(this, intent, option.toBundle());\n```\n\n##用 Transition 动画创建弧形 Motion\n\n在目标 Activity 中（在下面的例子中命名为 ContactActivity），需要通过以下代码设置正确的启动 Transition：\n\n```java\n\n\t@Bind(R.id.activity_contact_rl_container) RelativeLayout mRlContainer;        \n\t@Bind(R.id.activity_contact_fab) FloatingActionButton mFab;\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\t...\n\t\tsetupEnterAnimation();\n\t\t...\n\t}\n\n\tprivate void setupEnterAnimation() {\n\t\tTransition transition = TransitionInflater.from(this).inflateTransition(R.transition.change_bound_with_arc);\n\t\ttransition.setDuration(300);\n\t\tgetWindow().setSharedElementEnterTransition(transition);\n\t\ttransition.addListener(new Transition.TransitionListener() {\n\t\t\t@Override\n\t\t\tpublic void onTransitionStart(Transition transition) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onTransitionEnd(Transition transition) {\n\t\t\t\tanimateRevealShow(mRlContainer);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onTransitionCancel(Transition transition) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onTransitionPause(Transition transition) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onTransitionResume(Transition transition) {\n\n\t\t\t}\n\t\t});\n\t}\n```\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<transitionSet\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:duration=\"200\"\n\tandroid:interpolator=\"@android:interpolator/fast_out_linear_in\">\n\t<changeBounds>\n\t\t<!--suppress AndroidElementNotAllowed -->\n\t\t<arcMotion\n\t\t\tandroid:maximumAngle=\"90\"\n\t\t\tandroid:minimumHorizontalAngle=\"90\"\n\t\t\tandroid:minimumVerticalAngle=\"0\"/>\n\t</changeBounds>\n</transitionSet>\n```\n\n通过代码，我们将 xml 中声明的 Transition 初始化，设置其时长，并把它设为 SharedElementEnterTransition。然后就是最有趣的部分 - 组合实现动画，为此，我们必须实现 TransitionListener。在 onTransitionEnd() 回调方法中我们需要启动水波纹动画，在 xml 中我们声明了同时使用 changeBounds 和 arcMotion 的 transitionset，changeBounds 捕获两个 Activity 中目标 View 的边界，并显示动画；arcMotion 负责创建弧形弯曲路径。此外，我们还声明 minimumHorizontalAngle 和 minimumVerticalAngle 保证两点间的曲率。\n\n在 onTransitionEnd() 回调中我们使用了 animateRevealShow() 方法，也就是下文要讲的内容：\n\n##第二部：水波纹动画\n\n为了实现整个动画效果，我们先创建水波纹动画，然后淡入布局：\n\n```java\nprivate void animateRevealShow(final View viewRoot) {\n\tint cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;\n\tint cy = (viewRoot.getTop() + viewRoot.getBottom()) / 2;\n\tGUIUtils.animateRevealShow(this, viewRoot, mFab.getWidth() / 2, R.color.accent_color,\n\t\tcx, cy, new OnRevealAnimationListener() {\n\t\t\t@Override\n\t\t\tpublic void onRevealHide() {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRevealShow() {\n\t\t\t\tinitViews();\n\t\t\t}\n\t\t});\n}\n\n//GUIUtils method:\n\npublic static void animateRevealShow(final Context ctx, final View view, final int startRadius,\n\t\t@ColorRes int color, int x, int y, OnRevealAnimationListener listener) {\n\tfloat finalRadius = (float) Math.hypot(view.getWidth(), view.getHeight());\n\tAnimator anim =\tViewAnimationUtils.createCircularReveal(view, x, y, startRadius, finalRadius);\n\tanim.setDuration(ctx.getResources().getInteger(R.integer.animation_duration));\n\tanim.setStartDelay(80);\n\tanim.setInterpolator(new FastOutLinearInInterpolator());\n\tanim.addListener(new AnimatorListenerAdapter() {\n\t\t@Override\n\t\tpublic void onAnimationStart(Animator animation) {\n\t\t\tview.setBackgroundColor(ContextCompat.getColor(ctx, color));\n\t\t}\n\n\t\t@Override\n\t\tpublic void onAnimationEnd(Animator animation) {\n\t\t\tview.setVisibility(View.VISIBLE);\n\t\t\tif(listener != null) {\n\t\t\t\tlistener.onRevealShow();\n\t\t\t}\n\t\t}\n\t});\n\tanim.start();\n}\n```\n\n在第一个 animateRevealShow() 方法中我们必须得到 centerX 和 centerY，然后调用 GUIUtils 的 animateRevealShow() 方法跳过重复代码，下面是对该方法的解释：\n\ncontext 用于将 @ColorRes 注解的值转换为颜色对应的常量以及从资源文件中读取动画的持续时间。centerX,  centerY parameter, rootView 用于显示水波纹动画，自定义 Listener 用于 AnimatorListener 与 Activity 间的通信。当动画结束，通知 Listener 淡入 View，下面是用于淡入 View 的 initViews() 方法：\n\n```java\nprivate void initViews() {\n\tnew Handler(Looper.getMainLooper()).post(() -> {\n\t\tAnimation animation = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);\n\t\tanimation.setDuration(300);\n\t\tmLlContainer.startAnimation(animation);\n\t\tmIvClose.startAnimation(animation);\n\t\tmLlContainer.setVisibility(View.VISIBLE);\n\t\tmIvClose.setVisibility(View.VISIBLE);\n\t});\n}\n```\n\nmLLContainer 和 mIvClose 是用于显示关闭按钮的 LinearLayout 及 ImageView。\n\n##第三部：返回 Activity\n\n如你所见，整个动画的实现涉及多方面的处理。当我们点击关闭按钮，或者按下后退按钮，意味着要结束 ContactActivity，并以淡出效果显示水波纹动画，返回共享元素 Transition。第二步 Android 的框架层会帮我们完成，所以不用太在意，第一步才是我们真正要实现的，也就是下面解释的部分：\n\n重载 Activity 中的 onBackPressed() 方法：\n\n```java\n@Override\npublic void onBackPressed() {\n\tGUIUtils.animateRevealHide(this, mRlContainer, R.color.accent_color, mFab.getWidth() / 2,\n\t\tnew OnRevealAnimationListener() {\n\t\t\t@Override\n\t\t\tpublic void onRevealHide() {\n\t\t\t\tbackPressed();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRevealShow() {\n\n\t\t\t}\n\t\t});\n}\n\n//GUIUtils\n\npublic static void animateRevealHide(final Context ctx, final View view, @ColorRes int color,\n\t\tfinal int finalRadius, OnRevealAnimationListener listener) {\n\tint cx = (view.getLeft() + view.getRight()) / 2;\n\tint cy = (view.getTop() + view.getBottom()) / 2;\n\tint startRadius = view.getWidth();\n\tAnimator anim =\tViewAnimationUtils.createCircularReveal(view, cx, cy, startRadius, finalRadius);\n\tanim.setInterpolator(new FastOutLinearInInterpolator());\n\tanim.addListener(new AnimatorListenerAdapter() {\n\t\t@Override\n\t\tpublic void onAnimationStart(Animator animation) {\n\t\t\tsuper.onAnimationStart(animation);\n\t\t\tview.setBackgroundColor(ContextCompat.getColor(ctx,color));\n\t\t}\n\n\t\t@Override\n\t\tpublic void onAnimationEnd(Animator animation) {\n\t\t\tsuper.onAnimationEnd(animation);\n\t\t\tif(listener != null) {\n\t\t\t\tlistener.onRevealHide();\n\t\t\t}\n\t\t\tview.setVisibility(View.INVISIBLE);\n\t\t}\n\t});\n\tanim.setDuration(ctx.getResources().getInteger(R.integer.animation_duration));\n\tanim.start();\n}\n```\n\n点击后退按钮后，必须隐藏水波纹动画。因此 startRadius 为 View 的宽度，动画结束时 raidus 就变为 FAB 的宽度除2。当动画结束，需要通过 OnRevealAnimationListener 通知 Activity 调用 super.onBackPressed()，之后通过该弧形路径动画显示 MainActivity 的 FAB。\n\n##结论\n\nLollipop 给我们提供了许多工具，使我们可以利用 motion 和 Transition 实现许多酷炫的 UI 组件，虽说这只支持 30% 的设备，但在未来这个数字会越来越大，让越来越多人注意到 Material Design。\n\n感谢阅读！"
  },
  {
    "path": "issue-43/通过CoordinatorLayout的Behavior拦截一切.md",
    "content": "通过CoordinatorLayout的Behavior拦截一切\n---\n\n> * 原文链接 : [Intercepting everything with CoordinatorLayout Behaviors](https://medium.com/google-developers/intercepting-everything-with-coordinatorlayout-behaviors-8c6adc140c26#.i6alrxteu)\n* 原文作者 : [Ian Lake](https://medium.com/@ianhlake)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成 \n\n\n\n![](https://cdn-images-1.medium.com/max/600/1*voovH-sZjps4amAdRiwRuw.png)\n\n如果你不研究 CoordinatorLayout，那你在探索 Android Design Support Library 的路上肯定不会走太远 - 因为 Android Design Support Library 中大多数 View 都需要 CoordinatorLayout。但是为什么呢？CoordinatorLayout 自身不需要完成太多的工作：将它与 Android 标准 UI 框架结合使用，它的作用和 FrameLayout 区别不大，那它为什么能提供那么多酷炫的效果呢？答案是：CoordinatorLayout.Behavior。通过将 CoordinatorLayout.Behavior 绑定到 CoordinatorLayout 中的子元素上，你就可以拦截点击事件，窗口插入，测量，布局，还有嵌套滚动。可以说 Android Design Support Library 大多数酷炫的效果都是通过 Behavior 完成的。\n\n##创建 Behavior\n\n自定义 Behavior 很简单，创建 Behavior 子类就可以了：\n\n```java\npublic class FancyBehavior<V extends View>\n    extends CoordinatorLayout.Behavior<V> {\n  /**\n   * Default constructor for instantiating a FancyBehavior in code.\n   */\n  public FancyBehavior() {\n  }\n  /**\n   * Default constructor for inflating a FancyBehavior from layout.\n   *\n   * @param context The {@link Context}.\n   * @param attrs The {@link AttributeSet}.\n   */\n  public FancyBehavior(Context context, AttributeSet attrs) {\n    super(context, attrs);\n    // Extract any custom attributes out\n    // preferably prefixed with behavior_ to denote they\n    // belong to a behavior\n  }\n}\n```\n\n注意类需要绑定的泛型类型，不妨创建可以绑定到任意 View 上的 FancyBehavior；如果你想 Behavior 只能使用到某一种特定的 View 上，可以这样：\n\n```java\npublic class FancyFrameLayoutBehavior\n    extends CoordinatorLayout.Behavior<FancyFrameLayout>\n```\n\n通过 Behavior.setTag()/Behavior.getTag() 可以保存临时数据，onSaveInstanceState()/onRestoreInstanceState() 可以保存 Behavior 相关的实例状态。虽说我建议你创建自己的 Behavior，但这些方法让你能够创建状态性 Behavior。\n\n##关联 Behavior\n\n当然，Behavior 不能单干 - 它需要绑定到 CoordinatorLayout 的子元素才能被使用，下面是关联的三种办法。\n\n###通过代码绑定\n\n你可能觉得 Behavior 是 CoordinatorLayout 给每个 View 添加的某种额外属性/其他的东西，但如果我告诉你 Behavior 是每个 View 的 LayoutParams 存储的内容，请不要太惊讶 - 这也是 Behavior 必须关联到 CoordinatorLayout 子元素上的原因，因为只有它们才有 LayoutParams 中对应的属性。\n\n```java\nFancyBehavior fancyBehavior = new FancyBehavior();\nCoordinatorLayout.LayoutParams params =\n    (CoordinatorLayout.LayoutParams) yourView.getLayoutParams();\nparams.setBehavior(fancyBehavior);\n```\n\n在这种情况下，你会使用默认构造方法，当然，你也可以给构造方法设置任何你想要的参数 - 反正代码在手，天下我有嘛。\n\n###通过 xml 绑定\n\n不过不得不说，每次都用代码去完成绑定的工作有一丢丢麻烦，就像大多数自定义 LayoutParams，我们也有相应的 \nlayout_ 属性去设置 Behavior：\n\n```xml\n<FrameLayout\n  android:layout_height=”wrap_content”\n  android:layout_width=”match_parent”\n  app:layout_behavior=”.FancyBehavior” />\n```\n\n此时会调用 FancyBehavior(Context context, AttributeSet attrs) 构造方法，也就意味着可以声明任意自定义属性，然后在代码中获得这些属性，如果你想通过 xml 自定义 Behavior 的功能，这个办法就变得很棒了。\n\n> Note: 与使用 layout_ 类似，你也可以使用 behavior_ 指定 Behavior 使用的属性。\n\n###自动绑定\n\n如果你创建了需要自定义 Behavior 的自定义 View，那你可能想让该 View 绑定默认的 Behavior，而不用在每次使用时在代码或 xml 中声明。为了实现这个特性，代码需要改成下面这样：\n\n```java\n@CoordinatorLayout.DefaultBehavior(FancyFrameLayoutBehavior.class)\npublic class FancyFrameLayout extends FrameLayout {\n}\n```\n\n这样就会自动绑定 Behavior 了，相当于 layout_behavior 默认设置为 DefaultBehavior。\n\n##拦截点击事件\n\n一旦自定义 Behavior 开发完成，就可以用它完成一些任务了，例如拦截点击事件。\n\n不使用 CoordinatorLayout，总是需要创建 ViewGroup 的子类才能得到点击事件。但有了 CoordinatorLayout，就能在 Behavior 里调用 onInterceptTouchEvent() 以控制 CoordinatorLayout 的 onInterceptTouchEvent()，使 Behavior 能拦截点击事件。通过返回 true，Behavior 就能获取所有点击事件，这就是 SwipeDismissBehavior 的原理。\n\n在 blocksInteractionBelow() 方法中返回 true，就会阻塞所有事件传递的交互，拦截所有事件。当然了，你可能希望得到一些视觉上的信号让你知道交互产生的事件都被阻塞了（最起码要让用户知道 App 崩溃了） - 这也是 blocksInteractionBelow() 的默认功能依赖于 getScrimOpacity() 的值的原因 - getScrimOpacity() 方法返回一个非 0 的值，以在 View 上绘制覆盖颜色（getScrimColor() 方法返回对应的颜色，默认是黑色），而且一下子禁止了所有点击事件。很方便。\n\n##拦截窗口插入\n\n我先假设你读过“Why would I want to fitsSystemWindows”，在博文中我们讨论了 fitsSystemWindows 到底干了啥，但说穿了他就是让你得到避免在系统窗口下绘制的窗口插入（例如状态栏和导航栏）。如果 fitsSystemWindows=“true”，那么任何绑定的 Behavior 都会调用 onApplyWindowInsets()，让 Behavior 具有比 View 更高的优先级。\n\n> Note: 大多数情况下，Behavior 不会消耗所有的窗口插入，窗口插入应该通过 ViewCompat.dispatchApplyWindowInsets() 传递，以确保所有子 View 都有机会接触窗口插入。\n\n##拦截 Measurement 和 Layout\n\nMeasurement 和 Layout 是 Android 绘制机制的关键部分，这也意味着 Behavior 作为一切的拦截者，能够通过 onMeasureChild() 和 onLayoutChild() 回调先于 CoordinatorLayout 进行 measurement 和 layout 。\n\n例如，让 Behavior 接受任意类型的 ViewGroup，并添加 maxWidth：\n\n```java\n/*\n * Copyright 2015 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n \n package com.example.behaviors;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.support.design.widget.CoordinatorLayout;\nimport android.util.AttributeSet;\nimport android.view.ViewGroup;\n\nimport static android.view.View.MeasureSpec;\n\n/**\n * Behavior that imposes a maximum width on any ViewGroup.\n *\n * <p />Requires an attrs.xml of something like\n *\n * <pre>\n * &lt;declare-styleable name=\"MaxWidthBehavior_Params\"&gt;\n *     &lt;attr name=\"behavior_maxWidth\" format=\"dimension\"/&gt;\n * &lt;/declare-styleable&gt;\n * </pre>\n */\npublic class MaxWidthBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V> {\n    private int mMaxWidth;\n\n    public MaxWidthBehavior(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        TypedArray a = context.obtainStyledAttributes(attrs,\n                R.styleable.MaxWidthBehavior_Params);\n        mMaxWidth = a.getDimensionPixelSize(\n                R.styleable.MaxWidthBehavior_Params_behavior_maxWidth, 0);\n        a.recycle();\n    }\n    \n    @Override\n    public boolean onMeasureChild(CoordinatorLayout parent, V child,\n            int parentWidthMeasureSpec, int widthUsed,\n            int parentHeightMeasureSpec, int heightUsed) {\n        if (mMaxWidth <= 0) {\n            // No max width means this Behavior is a no-op\n            return false;\n        }\n        int widthMode = MeasureSpec.getMode(parentWidthMeasureSpec);\n        int width = MeasureSpec.getSize(parentWidthMeasureSpec);\n        \n        if (widthMode == MeasureSpec.UNSPECIFIED || width > mMaxWidth) {\n            // Sorry to impose here, but max width is kind of a big deal\n            width = mMaxWidth;\n            widthMode = MeasureSpec.AT_MOST;\n            parent.onMeasureChild(child,\n                    MeasureSpec.makeMeasureSpec(width, widthMode), widthUsed,\n                    parentHeightMeasureSpec, heightUsed);\n            // We've measured the View, so CoordinatorLayout doesn't have to\n            return true;\n        }\n\n        // Looks like the default measurement will work great\n        return false;\n    }\n}\n```\n\n泛型 Behavior 固然好用，但你要记住，不是所有 Behavior 都应该是泛型的。\n\n##理解 View 间依赖\n\n上述所有功能只需要一个 View，但 Behavior 的魔力真正来源于在 View 间建立以来 - 例如，当另一个 View 发生改变，Behavior 获得回调，基于外部条件改变其功能。\n\nBehavior 可以通过两种方式依赖于 View：当一个 View 被锚定到其他 View 上（被实现的依赖）或在 layoutDependsOn() 方法中返回 true。\n\n使用 CoordinatorLayout 的 layout_anchor 属性就可以完成锚定。此外，再使用 layout_anchorGravity 属性，你可以有效地将两个 View 的位置绑定在一起。例如，将 FAB 锚定到 AppBarLayout 中，此时 FloatingActionButton.Behavior 会通过隐式依赖，在 AppBarLayout 滚动到屏幕可见范围之外时隐藏自身。\n\n不管怎样，Behavior 都会在依赖的 View 被移除时获得 onDependentViewRemoved() 回调，而且只要依赖 View 发生了改变，onDependentViewChanged() 回调也会被触发。（例如改变自身大小，或者改变位置）\n\n将 View 绑定在一起的能力就是 Design Library 实现这么多酷炫效果的秘密 - 举例来说吧，FAB 和 Snackbar 的交互，FAB 的 Behavior 依赖于添加到 CoordinatorLayout 中的 Snackbar，通过调用 onDependentViewChanged() 回调就可以将 FAB 移动到 Snackbar 的上面，避免覆盖。\n\n> Note: 当你添加依赖，View 总会在被依赖 View 被放置后被放置，无论其布局关系如何。\n\n##嵌套滚动\n\n对于嵌套滚动，下面几件事你需要记住：\n1. 不需要在 NestedScrollView 中声明依赖，因为 CoordinatorLayout 的每一个子元素都能够获得 NestedScrollView 中的滚动事件\n2. NestedScrollView 不仅仅能应用于 CoordinatorLayout 的直接子元素，而能应用到 CoordinatorLayout 中的任意 View 上（CoordinatorLayout 的子布局的子布局的子布局的子布局……）\n3. 虽说我把它叫做嵌套滚动，但实际上它能滚动和挥动\n\n在 onStartNestedScroll() 方法中处理你感兴趣的嵌套滚动事件吧，你能在这得到滚动的坐标轴（例如水平和垂直坐标轴 - 不用在意到底在哪个方向发生了滚动），为了获得该方向上随后的滚动事件，必须返回 true。\n\nonStartNestedScroll() 返回 true 后，嵌套滚动会按如下步骤运行：\n\n- onNestedPreScroll() 在 ScrollView 获得滚动事件前被调用，允许 Behavior 消耗一部分或所有滚动事件（最后被消耗的滚动事件是 int[] out 参数，也就是你能得到你消耗了什么事件的参数）\n- onNestedScroll() 在滚动 View 滚动时被调用 - 你可以得到 View 已经滚动了多远以及还有多少没有滚动。\n\n此外，还有与挥动操作等价的情况（即使挥动前的回调必须消耗所有非挥动的滚动事件，或不消耗任何一个）。\n\n当嵌套滚动（挥动）结束，获得 onStopNestedScroll() 的回调调用，该回调标记了滚动的结束 - 同时期望下一次滚动发生前得到新的 onStartNestedScroll() 调用。\n\n例如，你想在向下滚动时隐藏 FAB，向上滚动时显示 FAB - 实现这个功能将涉及 onStartNestedScroll() 和 onNestedScroll()，就像 ScrollAwareFABBehavior 中实现的那样。\n\n###而这只是开始\n\nBehavior 的每一个部分都很有趣，把它们组合起来就能对界面施行魔法，让界面变得酷炫。我强烈建议大家去看看 Design Library 的源码以发现更多更高级的 Behavior - Android SDK 搜索的 Chrome 拓展一直是我最爱的 Android 开源项目源码。\n"
  },
  {
    "path": "issue-43/避免Android应用冷启动问题.md",
    "content": "避免Android应用冷启动问题\n---\n\n> * 原文链接 : [Avoiding cold starts on Android](http://saulmm.github.io/avoding-android-cold-starts)\n* 原文作者 : [Saúl Molinero](http://saulmm.github.io/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n在过去的几周里，Android 开发者社区有人在讨论 Android 应用的冷启动问题（即启动应用时有一段时间屏幕不显示内容，背景全为白/黑），在本篇博文中，我将解释解决这个问题是否必要，以及如何解决它以使用户得到最好的使用体验。\n\n本篇博文涉及的代码可以在 [Github](https://github.com/saulmm/onboarding-examples-android)\n 看到。\n\n##应用的冷启动问题\n\nColt McAnlis（Google 的一名工程师）再次开启了一个讨论帖来讨论有关 Splash/Launch 启动页面的正确用法，讨论的主题和 Cyril Mottier 之前开启的讨论相同，大体是说为什么我们在开发的时候应该避免使用 Splash 启动页。讨论的冲突点在于一部分人认为 Splash 启动页破坏了用户体验，增加了应用的体积等等……\n\n在我看来，用户在使用应用时，应用的内容应该尽可能快呈现给用户，但当用户启动某个应用，Android 内核总会创建一个进程，使得屏幕不可避免地显示黑/白（取决于应用的 theme 或入口 Activity 的 theme）。\n\n应用本身越复杂，或应用使用的 Application 类被重写过以需要完成更多的任务（如初始化数据分析，错误报告，等等……）时，这段时间会变得更长。\n\n![](https://github.com/saulmm/OnboardingSample/blob/master/art/airbnb.gif?raw=true)\n\nAirbnb 在初始化时就会像上图那样留白\n\n对用户来说，这样的界面显然不是他们想要看到的。假如应用的加载时间很长，我们可以通过 placeholder 用一些内容去填充它，或者显示 Logo 以加强品牌印象。\n\n![](https://github.com/saulmm/OnboardingSample/blob/master/art/aliexpress.gif?raw=true)\n\nAliExpress 在初始化时会显示其 Logo\n\n##你的新帮手，windowBackground\n\n就像我们之前讨论的，在进程处于加载状态时，WindowManager 显示的 Window 由应用的 theme 决定其显示内容，准确地说，是由 android:windowBackground 的值决定的。就像 [Ian Lake](https://plus.google.com/+AndroidDevelopers/posts/Z1Wwainpjhd) 的这篇博文所提到的，如果我们为该属性设置 <layer-list> 元素，该元素使用 MainActivity 的背景颜色，并在屏幕中间显示一个 Pizza 图像，可以实现下面的效果：\n\n![](https://github.com/saulmm/OnboardingSample/blob/master/art/simple.gif?raw=true)\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list \n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:opacity=\"opaque\">\n\n    <item android:drawable=\"@color/grey\"/>\n    <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@drawable/img_pizza\"/>\n    </item>\n</layer-list>\n```\n\n这里要将 <layer-list> 的 android:opacity 属性设置为 opaque，使 <layer-list> 为不透明的，要不然会出现一些问题，请读者记住这一点。此外，Activity 的布局背景应该填充了某个颜色，不然的话 <layer-list> 会留在你的 Activity 中。\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/grey\"\n    >\n\n    <android.support.v7.widget.Toolbar\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:background=\"?colorPrimary\"\n        android:elevation=\"4dp\"\n        />\n</LinearLayout>\n```\n\n##自定义启动页\n\n利用 windowBackground 属性可以给用户更好的体验。如果应用很复杂，那么可以显示一个独特的 Activity 来完成登录操作或进行选择操作，利用图片和动画可以实现下面这个酷炫的效果：\n\n![](https://github.com/saulmm/OnboardingSample/blob/master/art/center.gif?raw=true)\n\n该动画在竖直方向上移动 ImageView，该 ImageView 包含了 <layer-list> 元素。\n\n```java\nViewCompat.animate(logoImageView)\n    .translationY(-250)\n    .setStartDelay(STARTUP_DELAY)\n    .setDuration(ANIM_ITEM_DURATION).setInterpolator(\n         new DecelerateInterpolator(1.2f)).start();\n```\n\n\n从效果图可以看到，ImageView 要稍微高于屏幕的中心点，实际位置会受 SystemBar 的大小影响，在这里我为它设置了 12dp 的顶部间距，差不多是状态栏高度的一半。\n\n```java\n<ImageView\n    android:id=\"@+id/img_logo\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_marginTop=\"12dp\"\n    android:src=\"@drawable/img_face\"\n    tools:visibility=\"gone\"\n    />\n```\n\n##自定义 placeholder\n\n使用 <layer-list> 可以创建 placeholder 以显示 MainActivity 的内容，例如，可以通过 <layer-list> 模仿 Toolbar。\n\n![](https://github.com/saulmm/OnboardingSample/blob/master/art/toolbar_placeholder.png?raw=true)\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:opacity=\"opaque\"\n    >\n    <item>\n        <shape>\n            <solid android:color=\"@color/grey\"/>\n        </shape>\n    </item>\n\n    <item\n        android:height=\"180dp\"\n        android:gravity=\"top\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"?colorPrimary\"/>\n        </shape>\n    </item>\n</layer-list>\n```\n\n其中，第二个 <item> 模仿真正要被显示到屏幕上的 Toolbar，甚至我们可以将它的高度设置为 Toolbar 的高度（宽度小于状态栏一点点），应用一些酷炫的动画到 Toolbar，给用户更好的体验，效果图：\n\n![](https://github.com/saulmm/OnboardingSample/blob/master/art/placeholder.gif?raw=true)\n\n```java\nprivate void collapseToolbar() {\n    int toolBarHeight;\n    TypedValue tv = new TypedValue();\n    getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);\n    toolBarHeight = TypedValue.complexToDimensionPixelSize(\n        tv.data, getResources().getDisplayMetrics());\n\n    ValueAnimator valueHeightAnimator = ValueAnimator\n        .ofInt(mContentViewHeight, toolBarHeight);\n\n    valueHeightAnimator.addUpdateListener(\n        new ValueAnimator.AnimatorUpdateListener() {\n\n        @Override\n        public void onAnimationUpdate(ValueAnimator animation) {\n            ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();\n            lp.height = (Integer) animation.getAnimatedValue();\n            mToolbar.setLayoutParams(lp);\n        }\n    });\n\n    valueHeightAnimator.start();\n    valueHeightAnimator.addListener(\n        new AnimatorListenerAdapter() {\n\n        @Override\n        public void onAnimationEnd(Animator animation) {\n            super.onAnimationEnd(animation);\n\n            // Fire recycler animator\n            mAdapter.addAll(ModelItem.getFakeItems());\n\n            // Animate fab\n            ViewCompat.animate(mFab).setStartDelay(600)\n                .setDuration(400).scaleY(1).scaleX(1).start();\n\n        }\n    });\n}\n```"
  },
  {
    "path": "issue-44/Android-Reverse-Engineering-101-Part-4.md",
    "content": "Android逆向工程101 – Part 4\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 4](http://www.fasteque.com/android-reverse-engineering-101-part-4/)\n* 原文作者 : [Daniele Altomare](http://www.fasteque.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [MiJack](https://github.com/MiJack)\n* 校对者:\n* 状态 :  已翻译\n\n\n在Android应用逆向工程的系列博客中，我们已经讨论过了[APK的文件格式](http://www.fasteque.com/android-reverse-engineering-101-part-1/), [aapt](http://www.fasteque.com/android-reverse-engineering-101-part-2/) 和 [dex2jar](http://www.fasteque.com/android-reverse-engineering-101-part-3/)，接下来我们介绍**Apktool**。\n\n我们都知道，APK的资源文件是经过压缩以二进制的格式存储在文件中，我们无法通过**aapt**和**dex2jar**对其进行查看和编辑，前者实质是一个读取工具，用于从apk中提取有用的信息；而后者只能帮助我们获取到Apk中的执行代码并不能获取资源文件。\n\n下面是摘抄自Apktool的[官方网站](https://ibotpeaches.github.io/Apktool/):\n\n>一种用于逆向第三方闭源Android工程的工具，它可以反编译出和源文件形式相似的资源文件，并对其进行修改、重打包。可以对Smail代码进行单步调试。因为项目结构和开发时的较为相近以及自身所带的Apk构建等自动化操作，这让开发应用更加方便。\n\n所以，这个工具是逆向工程的必备工具，因为它能对资源进行解码。\n\n这里需要注意的是，记住，它**不能**用于涉及隐私或者其他非法的用途。\n\n## 安装\n\n[这个网站](http://ibotpeaches.github.io/apktool/install/)是关于如何安装的详细介绍。只需要检查一下Java版本，去环境说明章节看一下对应的平台（Window，Linux，Mac OS X）上的说明。\n\n本文使用的 **Apktool** 的版本是 `v2.0.2`.\n\n按照说明正确安装设置后，我们在命令行中输入`apktool`,得到如下输出：\n\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-23-at-22.11.54.png)\n\n\n在输入第一个命令之前，我们需要选择一个应用。在之前介绍[**dex2jar article**](http://www.fasteque.com/android-reverse-engineering-101-part-3/)，我选择了[RGB Tool](https://play.google.com/store/apps/details?id=com.fastebro.androidrgbtool) ，因为它是我的一个开源项目，对其进行逆向工程并不会有任何争议。我们可以在[这里](https://play.google.com/store/apps/details?id=com.fastebro.androidrgbtool)下载这个该应用。\n\n对于所有的实例，均运行在我的**Nexus 6**上，安装有**RGB Tool v1.4.3** ，系统版本**Android 6.0**。\n\n## FRAMEWORKS\n\n在反编译APK或者系统应用的过程中，Frameworks是非常重要的。事实上，在一台设备或者模拟器上，有一APK文件包括了ROM里的所有资源，包括图片、动画、声音、闪屏等，它是系统镜像的一部分.\n\n这个文件位于`/system/framework/framework-res.apk`\n\n简单的讲，这个文件包括关于设备界面风格所需的必要资源，被系统获取其他应用使用。对于任何一个制造商，包括三星，HTC，摩托罗拉，LG等，它都会提供自己的frameworkd的APK文件。\n\nApktool默认情况下使用的是AOSP的framework，并在如下路径（Mac OS X）有对应文件的拷贝:`$HOME/Library/apktool/framework/1.apk`\n\n如果我们需要反编译基于一个另外的Framework系统的应用，我们可能安需要装它，否则我们会得到下面的错误：\n\n\tW: Could not decode attr value, using undecoded value instead: ns=android, name=drawable\n\tW: Could not decode attr value, using undecoded value instead: ns=android, name=iconCan't find framework resources for package of id: 2. You must install proper framework files, see project website for more info.\n\n第一件事情就是使用`adb pull`从设备中导出framework的APK文件，一般情况下，我们可以在`/system/framework`找到它, 如果还是找不到它，可以在 [该网站](http://ibotpeaches.github.io/Apktool/documentation/#framework-files)查看所有可能的路径。\n\n然后，我们可以安装它： `apktool if FRAMEWORK.apk`\n\n在执行完这步操作后，我们可以反编译系统应用了。\n\n**Note**: 本文的后续章节使用的是默认的AOSP framework，所以无需重新安装。\n\n## 反编译\n\n运行如下命令，就可以反编译APK文件：\n\n\tapktool d FILENAME.apk\n\n![apktool decode](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-23-at-22.45.01.png)\n\n工具创建了一个文件夹，并在反编译好APK后对其进行重命名。\n\n![apktool decode output](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-25-at-21.29.38.png?resize=1024%2C218)\n\napktool 反编译的输出\n\n请注意,输出文件夹的内容取决于APK文件以及其包含的文件。\n\n首先要注意的是`apktool.yml`文件：它包含重要的信息,如应用**版本名称**、**代码**、**最小和支持SDK版本**...当我们重新构建APK文件的时候，此文件是必需的,特别如果我们想改变其中的某个值。\n\n我们有了XML格式的`AndroidManifest.xml`文件, **并不是二进制格式**, 所以我们可以对其进行浏览和编辑。\n\n`original`文件夹通常包含`AndroidManifest. xml`二进制格式文件和含有JAR文件清单和apk签名的`META-INF`文件夹。基本上，这两项是直接从apk里复制得到的，没有做过修改。\n\n`Smali`文件夹包含了应用程序中Smali格式的源代码：[在我以前的文章](HTTP：/ / www.fasteque。COM / android-reverse-engineering-101-part-3 /)，已经讨论过这一点了，所以我只提到几件事。首先，代码的目录结构是依据包名。从中我们可以得到所有的可执行代码，其中包括来自第三方依赖的代码也在这个文件夹中。其次，我们可以对其进行修改，并对其进行重打包操作，形成新的APK文件。\n\n最后一个文件夹是`res`，很明显，这是资源文件夹：**apktool** 能反编译以【二进制形式](http://www.fasteque.com/android-reverse-engineering-101-part-1/)存储在`resources.arsc`中的资源，所以我们可以看到，当然也可以修改他们。这里有一个重要的事情：因为我们解码是运行在设备里的apk文件，所以，程序所有依赖的资源文件均在这个文件夹中，所以不必惊讶得到的资源文件比我们在Android Studio中看到的还要多。说明一点，在开发时我们使用了**AppCompat V7**这个库作为其中的一个依赖，所以我们理所应当得到他的资源文件(它们的文件名以`abc_`开头)。\n\n最后要注意的是，资源文件的子文件夹的名称不一定和项目或原有的应用的对应文件夹完全匹配，这样设计主要是为了apktool能够将这些资源重新打包，构建一个可以运行的APK。\n\n## 构建\n\n使用如下命令就可以进行构建操作：\n\n\tapktool b APKTOOL_DECODE_OUTPUT_DIR\n\nApktool会根据反编译的输出文件夹进行重打包，将对应的APK文件放置在`APKTOOL_DECODE_OUTPUT_DIR/dist`路径下。\n\n或者指定对应的APK路径：\n![apktool build](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-25-at-22.47.35.png)\napktool build\n\n\n![apktool build output](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-25-at-22.48.26.png)\napktool build output\n\n请注意，如果Apktool构建的APK没有对应的签名，它将无法安装设备上。\n\n事实上，如果我们直接安装`dist`目录下的APK文件，我们会得到如下错误：\n\n![APK not signed error](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screen-Shot-2015-11-26-at-20.29.06.png)\nAPK not signed error\n\n所以，在安装以前，我们需要使用如下命令对其进行签名（他会向我们提示输入密码和keystore）:\n\n\tjarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore my_application.apk alias_name\n\n如果这不是一个发布版本中，我们可以使用用于调试的keystore，使用方法和使用Android Studio一样，keystore文件位于一个隐藏文件夹中（用户文件夹取决于对应的平台环境）：\n\n`USER_HOME/.android/debug.keystore`\n\nKeystore的password是 `android`, key alias 是 `androiddebugkey` .\n\n除此以外，我们还可以创建属于自己的keystore，请参考[官方文档](http://developer.android.com/tools/publishing/app-signing.html#signing-manually) (同时，他建议我们**在签名以后对APK进行zipalign**).\n\n## 牛刀小试\n\n现在，我们已经知道了如何对一个应用进行反编译和重打包，我们对应用做一些小的尝试，感受一下**ApkTool**的特性。\n\n首先，我们来看一下应用原来的主界面，方便通过对比得出我们所做的修改。\n\n![RGB Tool original main view](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screenshot_20151126-204826.png)\nRGB Tool original main view\n\n## 应用的名称和主题\n\n首先，我们来修改一下应用的名称和主题色。\n\n查看manifest文件, 我们发现应用的名称使用的是名为`app_name`的string 资源. 所以打开`values/string.xml`文件，将对应的值从 **RGB Tool** 改为 **MyRGB Tool**.\n\n关于主题的颜色，让我们再看看manifest文件，确认主题设置在应用层：它是` Theme.Rgbtool`。我们可以在`values/styles.xml`中找到它的声明，我们对如下部分比较感兴趣：\n\n\n\t<item name=\"colorPrimary\">@color/primary</item>\n\t<item name=\"colorPrimaryDark\">@color/primary_dark</item>\n\t<item name=\"colorAccent\">@color/accent</item>\n\n所以，我们将视线转移到`values/colors.xml`，更改其中的颜色。关于在主题色的选择，我建议使用[Material Palette](http://www.materialpalette.com/)。\n\n我定义了如下新的颜色：\n\n\t<color name=\"primary\">#4CAF50</color>\n\t<color name=\"primary_dark\">#388E3C</color>\n\t<color name=\"accent\">#FFC107</color>\n\n让我们来构建、签名apk。记住我们必须卸载已经安装好的应用，即便是覆盖安装也是这样。\n\n[]Modified RGB Tool](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screenshot_20151126-212522.png)\nModified RGB Tool\n\n## 应用的图标\n\n我们可以通过和之前同样的方法更改应用程序的图标，因为我们知道那只是一个drawable PNG文件，它的名字是设置在manifest文件中了。所以，我们要做的变化很小，事实上它只需要更新成新的PNG文件（在这里不考虑屏幕密度）并重新生成apk文件。\n\n在这里，图标设置成`@mipmap/ic_launcher`\n\n我们将使用[Android Asset Studio](http://jgilfelt.github.io/AndroidAssetStudio/index.html), 来生成新的启动图标，打开网页，选择**Launcher icons**。\n\n更新了新的PNG文件后，我们通过打包签名得到APK文件，新的应用图标展现在启动器上：\n\n![New application icon](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/myrgb_tool_icon.png)\nNew application icon\n\n### 新的资源\n\n到目前为止，我们只是替换同名的资源。如果，我们需要向应用添加新的资源，应该如何处理？我们来做一下这方面的尝试。\n\n我们打开应用，减小opacity（O）的值，我们可以看见背景里的Android机器人。\n\n![RGB Tool android](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screenshot_20151127-205409.png)\nRGB Tool android\n\n它对应的资源是`@drawable/robot`，有很多界面使用了它。我们把它提取出来，改改颜色后，命名为`robot_apktool`。然后，我们必须在所有的XML布局中的`robot`替换成`robot_apktool`。然后我们重新打包、签名、安装在我们的设备。\n\n![New robot](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screenshot_20151127-211610.png)\nNew robot\n\n我们的新Android替换好了！说实话，这很容易，因为资源只被XML文件引用，但从未在任何java类出现过。后者和前者属于两种不同的情况，这也是我们不在这里讨论的原因。\n\n### 我们可以修改源代码吗？\n\n当然，在文章的开头我们已经介绍过了，**Apktool** 可以反编译出应用的可执行代码，产生`.smali`文件,所以我们可以直接更改它们，并生成新的APK文件。当然，\n[Smail](https://github.com/jesusfreke/smali)并不及java那样容易入门，它的语法比较冗长。我们可以用任何文本编辑器打开这些文件，但是我们无法享受语法高亮和自动完成的编辑器特征。另一方面，我们可以很容易地用grep搜索字符串。\n\n\n在这里推荐一些工具：一个是开源的[syntax highlighter](https://github.com/ShaneWilton/sublime-smali)，而另一个是[全功能的解决方案]（http://virtuous-ten-studio.com/），不仅仅可以用来编辑`smali`。\n\n让我们通过smali尝试一个很简单的改变：如果我们点击 **Print color** 选项（需要点击溢出菜单图标调出它），下面的对话框出现在屏幕上。\n\n![](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screenshot_20151127-214535.png?resize=169%2C300)\n\n我们要做的是改变按钮文本从**Skip**改为 **Apktool**：在这个例子中，字符串资源用于java类,没有被XML资源文件引用。\n\n资源文件的名称是`action_common_skip`，所以我们在`res/values/strings.xml`中创建了`action_common_skip_apktool`的字符串. 整理还有意大利语和法语的资源。但是在这里，只要修改默认值即可。\n\n现在，我们搜索一下`action_common_skip`, 我们将在`public.xml`中找到3个String资源(默认，意大利语，法语)。比较有意思的是，我们在Smali文件中找不到搜索结果。查看[Java 类](https://github.com/fasteque/rgb-tool/blob/master/android-rgb-tool/src/main/java/com/fastebro/androidrgbtool/fragments/PrintJobDialogFragment.java),我都知道源代码中确实有用到这个String资源，所以，我打开xml文件和对话框对应的smali文件, `PrintJobDialogFragment.smali`.\n\n`public.xml`是应用程序的所有资源的列表，如字符串、布局、图片、色彩等。每个资源对应十六进制的ID。这些ID实际上是R.java文件中的值。而R文件是我们在创建项目时由Android构建系统帮助我们生成的：没错，apktool在提取APK资源和反编译代码时创建的。正如在这个系列中[前面的文章](http://www.fasteque.com/android-reverse-engineering-101-part-3/)所提到的，如果我们在Java类引用资源，比如Activity，它实际上是一个存储在R.java类中的整形ID。\n\n所以，我们在smali文件中查找如下的资源id：\n\n\t<public type=\"string\" name=\"action_common_skip\" id=\"0x7f070017\" />\n\n有趣的事又来了！他其实引用了（请注意，使用不同的应用或者通过克隆仓库构建新的APK，可能得到和例子中不一样的ID值）：\n\n\tconst v2, 0x7f070017\n\n\tinvoke-virtual {p0, v2}, Lcom/fastebro/androidrgbtool/fragments/PrintJobDialogFragment;->a(I)Ljava/lang/String;\n\n\tmove-result-object v2\n\nID的值存储在常量 **V2** 中，然后作为一个虚拟方法调用的参数（这将是`setTitle `）。操作码汇总列表请看[这里](http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html)。\n\n现在，我们理清了这些：我们知道我们必须更新`public.xml`文件，加入我们新定义的字符串，并在smali文件设置对应的ID值。\n\n对于第一步，我们注意很多细节：我们可以注意到，资源类型和 **id** 是连续的，一个所以我们不能随意设置ID值。我们需要寻找最后一个字符串资源，然后添加新的字符串资源，**ID** 值在其基础上加1（记得那些hexacedimal值）。在这个例子中，最后一个字符串是`0x7f070051` ,需要添加的**id**如下：\n\n\t<public type=\"string\" name=\"action_common_skip_apktool\" id=\"0x7f070052\" />\n\n第二步比起第一步简单很多。只需要将`PrintJobDialogFragment.smali`中的值改成新的就可以了.\n\n现在，我们重新打包，签名，安装新的APK：界面里展示就是新的字符串了。\n\n![New string resource](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/11/Screenshot_20151129-115055.png)\nNew string resource\n\n这次更新主要介绍了 **apktool** 的主要特点：我鼓励你用 APK 文件（由你开发，且有源码）试用 ApkTool 的功能。它很容易帮助你理解资源如何解码以及阅读smali代码。根据你的需求和应用的版本，对APK进行修改和重建。\n\n在[下一个章节](http://www.fasteque.com/android-reverse-engineering-101-part-5/)，我将介绍Androguard.\n"
  },
  {
    "path": "issue-44/Android-Support-Library-23.2.md",
    "content": "Android Support Library 23.2\n---\n\n> * 原文链接 : [Android Support Library 23.2](http://android-developers.blogspot.tw/2016/02/android-support-library-232.html)\n* 原文作者 : [Ian Lake](https://plus.google.com/+IanLake)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [rogero0o](https://github.com/Rogero0o) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  未完成 / 校对中 / 完成 \n\n\n###Android Support Library 23.2\n\n当我们说起 Android Support Library 时，必须清楚的认识到这不仅仅只是一个库,而是一个能对API向后兼容的，提供独特的功能, 而不需要最新平台(plathform)支持的 库的集合。23.2版本在许多现有库的基础上增加一些新的支持以及功能。    \n\n###Support Vector Drawables and Animated Vector Drawables\n\n定义在XML里的矢量图片能替换大量的png图片资源，之前只有 API21 以及更高版本才能支持的功能，现在通过两个新增支持库 support-vector-drawable 和 support-animated-vector-drawable，分别提供了 VectorDrawable(矢量图片) 和 AnimatedVectorDrawable(矢量图片动画) 两项功能。\n\nAndroid studio 1.4 通过在编译期间生成png图片来实现对矢量图的支持。关闭这项功能（以获得这一支持库的真正优势和节省空间,你需要在你的 build.gradle 文件添加代码:\n\n    // Gradle Plugin 2.0+  \n       android {  \n           defaultConfig {  \n           vectorDrawables.useSupportLibrary = true  \n           }  \n       }  \n\n你将发现以上代码只适用于Gradle 2.0版本。如果你使用的是Gradle 1.5则使用下列代码\n\n     // Gradle Plugin 1.5  \n     android {  \n         defaultConfig {  \n             generatedDensities = []  \n         }  \n    \n      // This is handled for you by the 2.0+ Gradle Plugin  \n        aaptOptions {  \n          additionalParameters \"--no-version-vectors\"  \n        }  \n     }  \n\nVectorDrawableCompat 最低支持到 API7 ,AnimatedVectorDrawableCompat 最低支持到 API 11 。受限于android的图片加载机制,不是所有支持图片id的地方(例如xml文件里)都能使用矢量图。值得庆幸的是,兼容包(AppCompat)已经增加了许多功能,可以很容易地使用新的矢量绘图资源。\n\n首先，当我们在ImageView（或者子类例如 ImageButton 和 FloatingActionButton）中使用兼容包时，你将会使用到新属性 app:srcCompat 来指定矢量图（所有 android:src 能够指定的图片 app:srcCompat都能指定）\n    \n     <ImageView  \n      android:layout_width=\"wrap_content\"  \n      android:layout_height=\"wrap_content\"  \n      app:srcCompat=\"@drawable/ic_add\" />  \n\n如果需要在运行时切换图片，使用和之前一样的方法 setImageResource()。使用兼容包和 app:srcCompat是将矢量图整合进APP中最简单有效的方法。\n\n 你会发现系统版本在 Android Lollipop 之前的设备中直接引用矢量图而不是使用 app:srcCompat 属性引用矢量图的话都会失败。然而，当矢量图被 另一个 drawable容器（例如 StateListDrawable, InsetDrawable, LayerDrawable, LevelListDrawable,和 RotateDrawable）引用时，兼容包是可以加载该矢量图的。所以，在一些无法直接使用兼容包（直接使用app:srcCompat）的地方，可以通过间接的方式来使用矢量图，例如 TextView 的 android:drawleLeft 属性。\n\n###AppCompat DayNight theme\n\n对矢量图的支持对兼容包已经是一个巨大的改变，在这个版本还增加了一个新的主题（Theme）：Theme.AppCompat.DayNight.\n\n![](https://3.bp.blogspot.com/-PCq6in0WXBs/Vsyp7EVfSsI/AAAAAAAACmQ/fMHWVrVibf0/s640/image05.png)  ![](https://1.bp.blogspot.com/-Ru64P9N2S_M/VsyqB1Fw5gI/AAAAAAAACmU/dYegY5HFn58/s640/image01.png)\n\n在API14之前，DayNight主题以及相关的 DayNight.NoActionBar, DayNight.DarkActionBar, DayNight.Dialog 等等，提供了相同的亮度（意思是不支持API14之前？）。但是 API14 及 以后的设备，DayNight主题支持应用切换 白天 和 夜晚 主题，根据 是否为 ‘夜晚’ 决定是否从白天主题有效的切换到夜晚主题。\n\n默认的，是否为 ‘夜晚’ 是由系统值（由方法 UiModeManager.getNightMode()取得）决定的，但是可以通过重写AppCompatDelegate中的方法来设置是否为‘夜晚’。您可以在app的整个生命周期（除非进程重启）设置这个默认值，设置该值的方法一种是通过静态方法 AppCompatDelegate.setDefaultNightMode() ，或者是 通过getDelegat()取得一个AppCompatDelegate 对象，然后调用方法setLocalNightMode() 来设置当前 activity 或者 dialog 的主题。\n\n当使用 AppCompatDelegate.MODE_NIGHT_AUTO 值时，您的手机时间和您最后定位的地点（如果您的手机开启了定位许可）将会被用于切换白天和黑夜的依据， MODE_NIGHT_NO 和 MODE_NIGHT_YES 则分别强制设定了从不或是一直使用夜晚主题。\n\n当使用白天主题时，彻底的测试是非常重要的，因为白天主题的亮度很可能导致一些文字或者图标变得不可阅读。如果将你的主题中标准的 TextAppearance.AppCompat 样式（例如 android:textColorPrimary）设置到文字或是颜色中，你将会发现他们已经自动更新了。\n\n尽管如此，当你需要为夜晚主题定制一些特殊化资源时，兼容包会重新加载 夜晚专用资源文件夹（night resource qualifier folder），让定制所有资源变得可能。请考虑使用标准色彩，或是遵循兼容包所推荐的色彩方案，这样能让使用这个主题变得更加简单。\n\n###Design Support Library: Bottom Sheets\n\n设计支持库提供了许多 Material Design 的格局，这个版本提供了对 bottom sheets 的支持。\n\n![](https://4.bp.blogspot.com/-tHhmGm8q1Qs/VsyqSo_IBDI/AAAAAAAACmY/EWy2HbMmGYg/s640/image06.png)\n\n在 CoordinatorLayout （即增加属性 app:layout_behavior=”android.support.design.widget.BottomSheetBehavior” ）的子view附上一个 BottomSheetBehavior ，你将会得到五个状态的触摸回调。\n\n- STATE_COLLAPSED 这个关闭的状态是默认的，沿着父视图的下沿显示一小部分，显示的高度可以通过 app:behavior_peekHeight 属性设置，默认是0\n- STATE_DRAGGING，中间状态，表示用户正在打开或者关闭抽屉（sheet）\n- STATE_SETTLING，抽屉从被放开运行到最终位置的状态\n- STATE_EXPANDED，抽屉被完全打开的状态，即抽屉的高度完全显示出来（当抽屉高度小于主视图时）或是主视图被抽屉完全充满时。\n- STATE_HIDDEN，抽屉完全不可见的默认状态，（app:behavior_hideable 属性可以设置），打开这个允许用户向下滑动直至完全关闭抽屉。\n\n如果你的抽屉（sheet）中有需要滑动的试图，请务必确保它能支持嵌套滑动（例如 NestedScrollView, RecyclerView, or ListView/ScrollView on API 21+）。\n\n如果你需要接受状态回调，可以添加一个 BottomSheetCallback:\n\n    // The View with the BottomSheetBehavior  \n     View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  \n     BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);  \n     behavior.setBottomSheetCallback(new BottomSheetCallback() {  \n    @Override  \n    public void onStateChanged(@NonNull View bottomSheet, int newState) {  \n      // React to state change  \n    }  \n      @Override  \n      public void onSlide(@NonNull View bottomSheet, float slideOffset) {  \n       // React to dragging events  \n       }  \n     });  \n\n此版本不仅提供了 BottomSheetBehavior 获取抽屉状态的回调，而且还提供了 BottomSheetDialog 和 BottomSheetDialogFragment 来覆盖所有应用场景。只需要简单的替换成 AppCompatDialog 或 AppCompatDialogFragment ，效果等于使用普通dialog并使用抽屉主题。\n\n###Support v4: MediaBrowserServiceCompat\n\nSupport v4 库为许多支持库提供了基础，并且为一些新版本介绍的特征提供支撑(backports).\n\n之前发布了一个播放媒体的可靠的工具类 MediaSessionCompat， 在这之上，这个版本添加了 MediaBrowserServiceCompat 和 MediaBrowserCompat ，使得支持的版本扩展到 支持API4及其之后版本。提供了这个标准的接口,使得Service及UI界面与媒体的连接更加紧密,无论在Android设备 还是 Android Wear 上播放媒体都变得更加的便捷。\n\n###RecyclerView\n\nRecyclerView 提供了先进、灵活的创建列表和网格的功能，并且支持动画。这个版本在 LayoutManager API中提供了一个令人兴奋的特性：auto-measurement.这使得 RecyclerView 可以根据内容的大小来决定自身的大小。RecyclerView之前不能使用的属性（例如WRAP_CONTENT）,现在可以使用了。现在你会发现在LayoutManagers 中构建的控件现在都将支持 auto-measurement.\n\n基于这个改变，请确保你的item的布局属性，之前忽略的布局属性（例如 在滑动方向上的MATCH_PARENT），现在将会完全的展开。\n\n如果你自定义了一个LayoutManager并且不是之前的拓展，你可以调用 setAutoMeasureEnabled(true) 以及做一些细小的变化（详情请见Javadoc）来支持这项特性。\n\n请注意，尽管 RecyclerView 可以设置子布局的动画，但是动画不会改变RecyclerView 自己的位置。如果你需要RecyclerView随着动画移动，请查看 Transition APIs.\n\n###Custom Tabs\n\nCustom Tabs 可以在保持你的app外观，在用户毫无知觉的情况下无缝的过度到 web 内容。随着这项功能的发布，现在可以在底栏增加一个在侧边显示网站内容的动作。\n\n![](https://1.bp.blogspot.com/-z_TM7Ch8fE0/VsyqZz2okNI/AAAAAAAACmc/3HT9_R_IhYU/s640/image04.png)\n\n随着新加的方法 addToolbarItem()，你现在可以在底栏增加五种动作，并且一旦会话开始就可以使用 setToolBarItem()更新他们。和之前的setToolbarColor()类似，你同样会发现一个setSecondaryToolbarColor()方法来自定义底栏的背景颜色。\n\n###Leanback for Android TV\n\nLeanback Library 提供一个你需要的工具来方便的将你的应用适配到 Android TV 中，通过针对电视体验优化了的标准组件。GuidedStepFragment 在此版本得到了显著的改进。\n\n![](https://4.bp.blogspot.com/-YmvEulQtB5o/VsyqlGkmHXI/AAAAAAAACmg/DZxERRItP0Q/s640/image02.png)\n\n最明显的变化可能是引入了一个两列可用的按钮动作（通过重写onCreatButtonActions()或者调用setButtonActions()添加的）。这使得到达列表底部更加简单，而不需要穿越整个list。\n\n说到GuideActions，有一些新的功能，支持更丰富的输入，包括可编辑的描述（通过 descriptionEditable()方法），下拉动作的子动作（通过subActions()），还有 GuidedDatePickerAction。\n\n![](https://4.bp.blogspot.com/-FahHAG7DavY/VszPhjBLnzI/AAAAAAAACm0/i7OXfxFI3-Q/s640/tv_combined_image.png)\n\n当你需要时,这些组件让你更容易获取用户的意图。\n\n\n\n\n"
  },
  {
    "path": "issue-44/使用Clean Architecture模型开发Android应用详细指南.md",
    "content": "使用Clean Architecture模型开发Android应用详细指南\n---\n\n> * 原文链接 : [A detailed guide on developing Android apps using the Clean Architecture pattern](https://medium.com/@dmilicic/a-detailed-guide-on-developing-android-apps-using-the-clean-architecture-pattern-d38d71e94029#.fmwe3kp22)\n* 原文作者 : [Dario Miličić](https://medium.com/@dmilicic)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuweiguocn](https://github.com/yuweiguocn)\n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成 \n\n\n\n### 使用Clean Architecture模型开发Android应用详细指南\n\n自从我开始开发Android应用就有这个感觉，它可以做得更好。在我的职业生涯中我见过很多糟糕的软件设计决策，有一些是我自己的 — 安卓系统的复杂性和糟糕的软件设计是一个灾难。但重要的是从你的错误中学习并且不断完善。在搜索了大量的好的方式开发应用后我找到了**Clean Architecture**。把它应用到Android后，从类似的工程中找到一些细化和灵感，我认为这种方法是可行的并且值得分享。\n\n\n这篇文章的**目标**是用Clean方法一步一步地指导开发Android应用。这个方法是我最近怎样构建我的应用客户端并取得巨大成功的。\n\n\n\n### 什么是Clean Architecture?\n\n\n我不会讲太多的细节，这里有篇文章比我解释得更好。但下一段落是你对于理解Clean需要知道的**关键所在**。\n\n\n通常在Clean模型下,代码用一个**依赖规则**分离到洋葱状的层：内层不应该知道任何关于外层的东西。意思就是说**依赖应该指向里面**。\n\n\n这是上一段落的可视化图形：\n\n![](https://cdn-images-1.medium.com/max/800/1*B7LkQDyDqLN3rRSrNYkETA.jpeg)\nClean Architecture的很棒的可视化的表现。图片来自[Uncle Bob](https://blog.8thlight.com/uncle-bob/archive.html)\n\n\n\nClean Architecture，在提供的文章中提到可以让你的代码：\n\n - **框架独立**\n - **可测试**\n - **UI独立**\n - **数据库独立**\n - **所有的外部代理独立**\n\n\n\n\n通过下面的例子我希望你可以理解这些特点是怎样实现的。对于Clean更详细的说明我真心推荐这篇[文章](https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)和这个[视频](https://vimeo.com/43612849)。\n\n\n\n\n#### 对于Android来说意味着什么\n\n\n一般地，你的应用可以有任意数量的层，但除非你有企业级的业务逻辑以便适用于每个Android应用，大部分情况下会有3层：\n\n\n* 外层：Implementation layer（实现层）\n* 中层：Interface adapter layer（接口适配层）\n* 内层：Business logic layer（业务逻辑层）\n\n\n**实现层**是框架内所有具体代码出现的地方。框架的具体代码包括**你设置解决的问题但没有解决的所有代码行**，这包括所有像创建activities 和 fragments，发送intents，和别的框架代码像访问网络代码和数据库等。\n\n\n\n**接口适配层**的目的是作为你的业务逻辑和框架具体代码之间的一个连接器。\n\n\n最重要的层是**业务逻辑层**。这是构建你的应用解决你实际上想解决的问题的地方。这层不包含任何框架的具体代码并且你**应该可以不用在模拟器上运行它**。这样的话你才可以有**方便测试，开发和维护**的业务逻辑代码。这也是Clean Architecture的主要优点。\n\n\n核心层上的每层，在低层可以使用它们之前负责转换模型到低层模型。内层不能有属于外层的模型类的引用。然而，外层可以使用和引用内层的模型。这是因为我们的**依赖规则**。它做了创建的开销但是有必要确保代码层之间的解耦。\n\n\n> **为什么这个模型转换是必要的?** 例如，你的业务逻辑模型直接显示给用户是不合适的。也许你需要展示多个业务逻辑模型的组合。因此，我建议你创建一个ViewModel类让它更方便地展示到UI上。然后，在外层使用_converter_类转换业务模型到合适的ViewModel。\n\n> 另一个例子可能是这样的：假设你在外部数据库层从**ContentProvider**得到一个**Cursor**对象。然后外层首先将转换它到内部业务模型，再然后发送它到你的业务逻辑层进行处理。\n\n\n在文章末尾我将添加更多的资源来学习。现在我们知道了Clean Architecture的基本原则，让我们来动手敲一些代码吧。在下一部分我将给你展示怎样使用Clean构建一个功能性的例子。\n\n\n### 我该怎样写Clean应用？\n\n\n\n我已经做了一个[模板工程](https://github.com/dmilicic/Android-Clean-Boilerplate)，给你写好了所有必需的东西。它作为一个**Clean启动包**并且从一开始它就被设计建立在包含最常见的工具之上。你可以**免费**下载它，修改它和用它构建自己的应用。\n\n\n\n你可以从这找到启动工程：[**Android Clean Boilerplate**](https://github.com/dmilicic/Android-Clean-Boilerplate)\n\n\n### 编写一个新的用例\n\n\n\n这部分将会说明在之前的部分提供的模板上面使用Clean 方法创建一个用例需要写的所有代码。一个用例只是应用的一些分离的功能。一个用例可能（用户点击等）或可能不是由用户启动。\n\n首先，让我们解释一下这个方法的结构和术语。这就是我怎样构建应用的，但它不是固定的，如果你想的话你可以组织不同的结构。\n\n#### 结构\n\n\nAndroid应用程序的总体结构是这样的:\n\n\n* 外层包：UI，存储，网络，等等。\n* 中层包： Presenters, Converters\n* 内层包：Interactors, Models, Repositories, Executor\n\n\n#### 外层\n\n\n刚才已经提到了，这是框架的细节所在。\n\n\n**UI — ** 这是放置所有的Activities, Fragments, Adapters 和别的用户界面相关的代码。\n\n\n**Storage — ** 实现访问数据和存储数据交互接口的数据库的具体代码。这包括，例如，[**ContentProviders**](http://developer.android.com/guide/topics/providers/content-providers.html)或ORM-s 如[**DBFlow**](https://github.com/Raizlabs/DBFlow)。\n\n\n\n**Network — ** 比如像[**Retrofit**](http://square.github.io/retrofit/)。\n\n\n#### 中间层\n\n\n粘合用来连接实现细节和你的业务逻辑的代码层。\n\n\n**Presenters — ** Presenters处理UI事件（用户点击等）和通常作为内层的回调（交互器）。\n\n\n**Converters — ** Converter对象负责转换内部模型到外部模型，反之亦然。\n\n\n\n#### 内层\n\n\n核心层包含最高级的代码。**所有的类都是简单的Java对象**。这层的类和对象不知道它们运行在Android应用并且可以很容易地移植到运行JVM的任何机器。\n\n\n\n**Interactors — ** 真实**包含你的业务逻辑代码**的类。这些类运行在后台，通过回调和上层进行通讯。它们也会在一些工程（可能是一个更好的名字）中被用例调用。在你的工程中有很多小的交互类用来解决特定的问题是很正常的。这符合**[单一职责原则](https://en.wikipedia.org/wiki/Single_responsibility_principle)**并且在我看来这更方便维护。\n\n\n**Models — ** 这是你的业务模型用来操作业务逻辑。\n\n\n**Repositories — ** 这个包只包含数据库或其他一些外层实现的接口。这些接口被用来通过Interactors访问和存储数据。这也被称为[repository pattern](https://msdn.microsoft.com/en-us/library/ff649690.aspx)\n\n\n**Executor — ** 这个包包含通过使用一个工作线程使Interactors运行在后台的代码。这个包里面的代码通常不需要修改。\n\n\n#### 简单的例子\n\n\n在这个例子中，我们的需求如下：**当应用启动时，从数据库中读取出消息用来欢迎用户。** 这个示例将展示如何编写以下三个包需要的代码让用例工作：\n\n* **presentation**包\n*  **storage**包\n*  **domain**包\n\n\n前两个属于外层,而最后一个是内部/核心层。\n\n\n**Presentation** 包负责一切在屏幕上显示的相关东西—它包含完整的[MVP](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)堆（它意味着它也包括 UI 和Presenter包即使它们属于不同的层）。\n\n\nOK—费话少说，上代码。\n\n### 写一个新的Interactor（内部/核心层）\n\n\n在实际开发中你可以先写结构的任何层，但我推荐你先写你的核心业务逻辑。你可以写它，测试并在不创建activity时确保它是没问题的。\n\n\n让我们先创建一个Interactor。Interactor是存放用例的主要逻辑的地方。所有的Interactors运行在后台因此这不应该对UI性能有任何的影响。创建一个名为**WelcomingInteractor**新的Interactor。\n\n```\npublic interface WelcomingInteractor extends Interactor { \n \n    interface Callback { \n \n        void onMessageRetrieved(String message);\n \n        void onRetrievalFailed(String error);\n    } \n}\n```\n\n **Callback**负责在主线程和UI通讯，我们把它放在Interactor的接口里因此我们不得不给它一个WelcomingInteractorCallback名称—为了和别的callback进行区分。现在让我们实现我们的获取信息的逻辑。我们有一个**MessageRepository** 可以给我们欢迎信息。\n\n```\npublic interface MessageRepository { \n    String getWelcomeMessage();\n}\n```\n\n让我们用我们的业务逻辑实现Interactor接口。**重要的是继承AbstractInteractor的实现需要运行在后台线程中**。\n\n```\npublic class WelcomingInteractorImpl extends AbstractInteractor implements WelcomingInteractor {\n    \n    ...\n    \n    private void notifyError() {\n        mMainThread.post(new Runnable() {\n            @Override\n            public void run() {\n                mCallback.onRetrievalFailed(\"Nothing to welcome you with :(\");\n            }\n        });\n    }\n\n    private void postMessage(final String msg) {\n        mMainThread.post(new Runnable() {\n            @Override\n            public void run() {\n                mCallback.onMessageRetrieved(msg);\n            }\n        });\n    }\n\n    @Override\n    public void run() {\n\n        // retrieve the message\n        final String message = mMessageRepository.getWelcomeMessage();\n\n        // check if we have failed to retrieve our message\n        if (message == null || message.length() == 0) {\n\n            // notify the failure on the main thread\n            notifyError();\n\n            return;\n        }\n\n        // we have retrieved our message, notify the UI on the main thread\n        postMessage(message);\n    }\n```\nWelcomingInteractor的运行方法。\n\n\n这只是尝试接收信息和发送信息或错误显示到UI上。我们使用回调通知UI实际上是Presenter。这是我们业务逻辑的关键。我们需要做的别的东西依赖于框架。\n\n\n让我们看看Interactor的依赖：\n\n```\nimport com.kodelabs.boilerplate.domain.executor.Executor;\nimport com.kodelabs.boilerplate.domain.executor.MainThread;\nimport com.kodelabs.boilerplate.domain.interactors.WelcomingInteractor;\nimport com.kodelabs.boilerplate.domain.interactors.base.AbstractInteractor;\nimport com.kodelabs.boilerplate.domain.repository.MessageRepository;\n```\n\n\n正如你看到的，这**没有涉及到任何的Android代码**。这也是这个方法的**主要优点**。你可以看到**框架的独立**特点。还有，我们不关心具体的UI或数据库，我们只是调用将在外层实现的接口方法。因此，我们是**UI独立**和**数据库独立**。\n\n\n\n\n###测试Interactor\n\n我们现在可以运行测试Interactor，不用运行在模拟器上。因此让我们写一个简单的 **JUnit** 测试确保它没问题：\n\n\n```\n...\n\n    @Test\n    public void testWelcomeMessageFound() throws Exception {\n\n        String msg = \"Welcome, friend!\";\n\n        when(mMessageRepository.getWelcomeMessage())\n                .thenReturn(msg);\n\n        WelcomingInteractorImpl interactor = new WelcomingInteractorImpl(\n            mExecutor, \n            mMainThread, \n            mMockedCallback, \n            mMessageRepository\n        );\n        interactor.run();\n\n        Mockito.verify(mMessageRepository).getWelcomeMessage();\n        Mockito.verifyNoMoreInteractions(mMessageRepository);\n        Mockito.verify(mMockedCallback).onMessageRetrieved(msg);\n    }\n```\n\n\n\n再次提醒，这个Interactor代码不知道它将会运行在Android应用中。这可以证明我们的业务逻辑是**可测试**的，这正是第二个特点。\n\n\n\n### 编写presentation层\n\n\n\n在Clean结构中Presentation代码属于**外层**。它由展示给用户的UI的框架依赖代码组成。当应用可见时我们将会使用**MainActivity** 类展示欢迎信息给用户。\n\n\n让我们开始写**Presenter**和**View** 的接口。我们的view唯一需要做的事情就是显示欢迎信息：\n\n```\npublic interface MainPresenter extends BasePresenter { \n \n    interface View extends BaseView { \n        void displayWelcomeMessage(String msg);\n    } \n}\n```\n\n\n当应用可见时我们应该怎样和在哪进行交互呢？所有和视图完全无关的应该放到Presenter类。这个会帮助我们实现[关系分离](https://en.wikipedia.org/wiki/Separation_of_concerns)并且可以防止Activity类臃肿。\n\n\n在 **MainActivity** 类我们重写了**_onResume()_** 方法：\n\n```\n@Override\nprotected void onResume() {\n    super.onResume();\n    // let's start welcome message retrieval when the app resumes\n    mPresenter.resume();\n}\n```\n\n\n所有继承BasePresenter的Presenter对象需要实现**_resume()_**方法。\n\n>**注意**：聪明的读者可能已经看到了我在BasePresenter接口添加了Android生命周期的方法作为帮助方法，尽管Presenter在低层。它不应该知道任何关于UI层的东西—例如,它有一个生命周期。然而，当每个UI显示给用户时我并没有指定Android具体的**事件**。假如我叫它**onUIShow()** 而不是**onResume()**。是不是好多了？:)\n\n我们在MainPresenter类的**_resume()_**方法内进行交互：\n\n```\n@Override\npublic void resume() {\n    mView.showProgress();\n    // initialize the interactor\n    WelcomingInteractor interactor = new WelcomingInteractorImpl(\n            mExecutor,\n            mMainThread, \n            this, \n            mMessageRepository\n    );\n    // run the interactor\n    interactor.execute();\n}\n```\n\n**_execute()_**方法将只是在后台线程执行**WelcomingInteractorImpl**的**_run()_**方法。**_run()_**方法可以在**_写一个新的Interactor_** 部分看到。\n\n\n你可能注意到了 Interactor 和**AsyncTask** 类有点类似。你提供它运行所需要的所有东西并且执行它。你可能会问我们为什么不使用AsyncTask？因为它是**Android特有代码** 并且你需要在模拟器上运行和测试它。\n\n在interactor里面我们提供了几个类：\n\n\n\n*   **ThreadExecutor**实例负责在后台线程执行交互。我通常把它弄成一个单例。这个类实际上在**domain** 包下并且不需要在外层实现。\n\n\n\n*   **MainThreadImpl**实例负责从Interactor发送runnables 在主线程中。可以使用框架具体的代码访问主线程因此我们应该在外层实现它。\n\n\n*   你可能也注意到了我们给Interactor提供了**_this_** —**MainPresenter** 是Interactor的回调对象用来通知UI事件。\n\n*   我们提供了一个实现 **MessageRepository** 接口的**WelcomeMessageRepository**实例用来交互。稍后在**_编写存储层_**部分会覆盖**WelcomeMessageRepository**。\n\n\n> **注意**：有很多东西每次都需要提供Interactor，依赖注入框架像[Dagger 2](https://github.com/google/dagger) 会有用的。为了简单起见在这里我没有包含它。实现这样一个框架是留给你的自由。\n\n关于这一点，**MainActivity** 的**MainPresenter**实际上实现了回调接口：\n\n```\npublic class MainPresenterImpl extends AbstractPresenter implements MainPresenter, WelcomingInteractor.Callback {\n```\n\n\n我们是怎样从Interactor监听事件的。这是**MainPresenter**的代码：\n\n```\n@Override \npublic void onMessageRetrieved(String message) {\n    mView.hideProgress(); \n    mView.displayWelcomeMessage(message);\n} \n \n@Override \npublic void onRetrievalFailed(String error) {\n    mView.hideProgress(); \n    onError(error);\n}\n```\n\n\n我们看到的这些代码段是在我们的**MainActivity**实现了这个接口：\n\n```\npublic class MainActivity extends AppCompatActivity implements MainPresenter.View {\n```\n\n然后负责显示欢迎消息,如下代码所示:\n\n```\n@Override \npublic void displayWelcomeMessage(String msg) {\n    mWelcomeTextView.setText(msg);\n}\n```\n\n并且这几乎就是表现层。\n\n### 编写存储层\n\n\n这是实现仓库的地方。所有数据库特定的代码应该放在这。仓库模型只是把数据来源给抽象了。对于数据源来说是不知道我们主业务逻辑的—来自数据库，服务器或文本文件。\n\n\n\n对于复杂的数据可以使用[**ContentProviders**](http://developer.android.com/guide/topics/providers/content-providers.html)或ORM工具例如[**DBFlow**](https://github.com/Raizlabs/DBFlow)。如果你需要从web获取数据，可以使用[**Retrofit**](http://square.github.io/retrofit/)。如果你需要简单的键-值存储可以使用[**SharedPreferences**](http://developer.android.com/training/basics/data-storage/shared-preferences.html)。你应该使用正确的工具来完成工作。\n\n我们的数据库并不是真正的数据库。它是一个非常简单的类做了一些模拟延时：\n\n```\npublic class WelcomeMessageRepository implements MessageRepository { \n    @Override \n    public String getWelcomeMessage() {\n        String msg = \"Welcome, friend!\"; // let's be friendly\n \n        // let's simulate some network/database lag \n        try { \n            Thread.sleep(2000);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        } \n \n        return msg;\n    } \n}\n```\n\n我们所关心的是**WelcomingInteractor**，滞后可能由于真正的网络或其它原因。只要实现了**MessageRepository**接口，它并不关心里面是怎样实现的。\n\n#### 总结\n\n\n这个例子可以从github上进行[访问](https://github.com/dmilicic/Android-Clean-Boilerplate/tree/example)。总结了所调用的类如下：\n\n\n\n> **MainActivity -&gt;MainPresenter -&gt; WelcomingInteractor -&gt; WelcomeMessageRepository -&gt; WelcomingInteractor -&gt; MainPresenter -&gt; MainActivity**\n\n\n重要的是要注意的流程控制:\n\n> **Outer — Mid — Core — Outer — Core — Mid — Outer**\n\n\n通常在一个用例中多次访问外层。假如你需要从web上显示，存储和访问一些数据，你的控制流至少访问外层3次。\n\n\n### 结论\n\n\n对于我来说，目前这是开发应用的最好方式。解耦代码便于集中你的注意力在具体问题上并且不会让软件变得臃肿。毕竟，我认为这是一个相当[SOLID（可靠的）](https://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29)方法但它需要一些时间去适应。这也是我写这些的原因，通过循序渐进的例子来帮助大家更好的理解。如果还有什么不清楚我很乐意解决这些问题，你的反馈对我来说很重要。我也很想听听哪些可以改善。一个健康的讨论将有利于所有的人。\n\n我也用Clean开发并开源了一个cost tracker应用，展示了如何看起来像一个真正的应用程序的代码。就特性而言真的没有什么创新但我认为它涵盖了我谈到的一个更复杂的例子，你可以从这找到： [**Sample Cost Tracker App**](https://github.com/dmilicic/android-clean-sample-app)\n\n\n再次提醒，示例应用构建在Clean启动包之上，可以从这找到：[**Android Clean Boilerplate**](https://github.com/dmilicic/Android-Clean-Boilerplate)\n\n\n### 扩展阅读\n\n\n\n这个指南是在这篇很棒的[文章](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)之上进行了扩展。不同之处是我在例子中使用了**普通Java**并且没有添加太多重写展示这个方法。如果你想要用Clean的**RxJava**的例子可以从[这里](http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/)看看。"
  },
  {
    "path": "issue-44/利用Retrofit和RxJava实现服务器轮询和出错重试.md",
    "content": "利用Retrofit和RxJava实现服务器轮询和出错重试\n---\n\n> * 原文链接 : [Server polling and retrying failed operations. With Retrofit and RxJava.](https://medium.com/@v.danylo/server-polling-and-retrying-failed-operations-with-retrofit-and-rxjava-8bcc7e641a5a#.ebhq12x34)\n* 原文作者 : [Danylo Volokh](https://github.com/danylovolokh)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [lowwor](https://github.com/lowwor) \n* 校对者:  \n* 状态 :  完成 \n\n在开发一个rest应用的时候，一种很常见的情形是，要对服务器进行轮询和重试。当服务器正在进行某些任务的时候，我们需要（以一定的延时）查询服务器这个任务是否完成了，同时，当我们出错的时候，有时我们要进行重试。当我在想着如何利用RxJava来正确地实现服务器轮询的时候，我就想写一篇关于这个话题的文章了。最终，我在[这个StackOverflow问题中][1]找到了一个很好的解决方案。\n\n在这篇文章中，我会解释用RxJava和Retrofit来实现这个功能是多么的容易。我假定你已经了解了RxJava，Retrofit的使用并且已经能够利用这些库来实现相应的应用架构。\n\n在文章中我将用到的一些定义：\n\n- “Predicate” 一个被传到Observable的一些方法中的类，举例来说：Observable.filter(/*在这里传进来predicate*/), Observable.takeUntil(/*在这里传进来predicate*/)\n\n- “Child of Observable” 一个被链接（chained）在父Observable后的Observer。例如：\n```\nObservable\n    .filter(/*predicate here*/)\n    .takeUntil(/*predicate here*/)\n    .subscribe(/*subscriber here/)\n```\n\n**takeUntil()**返回的Observable是**filter()**返回的Observable的子元素（child）。作为参数传递给**subscribe()**的Subscriber是**takeUntil()**返回的Observable的子元素（child）。\n\n服务器轮询\n---------------\n\n所谓服务器轮询，也就是，当你需要等待服务器去完成某项任务时，你就要周期性地调用API接口来查询该项任务是否已经完成。\n\n示例代码如下\n```\n\n    /**\n     * 这个类用来映射（map）从服务器返回的json数据\n     *\n     */\n    class ServerPollingResponse {\n        boolean isJobDone;\n\n        @Override\n        public String toString() {\n            return \"isJobDone=\" + isJobDone;\n        }\n    }\n\n    Subscription checkJobSubscription = mDataManager.pollServer(inputData)\n            .repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {\n                @Override\n                public Observable<?> call(Observable<? extends Void> observable) {\n                    Log.v(TAG, \"repeatWhen, call\");\n\t\t\t/**\n\t\t\t * 这个方法只会被调用一次。\n\t\t\t * 5 表示每次重复的调用（repeated call）会被延迟5s。\n\t\t\t */\n                    return observable.delay(5, TimeUnit.SECONDS);\n                }\n            })\n            .takeUntil(new Func1<ServerPollingResponse, Boolean>() {\n                @Override\n                public Boolean call(ServerPollingResponse response) {\n\t\t\t/** 在这里，我们可以检查服务器返回的数据是否正确，和决定我们是否应该\n\t\t\t *  停止轮询。\n\t\t\t *  当服务器的任务完成时，我们停止轮询。\n\t\t\t *  换句话说，“当任务（job）完成时，我们不拿（take）了”\n\t\t\t */\n                    Log.v(TAG, \"takeUntil, call response \" + response);\n                    return response.isJobDone;\n                }\n            })\n            .filter(new Func1<ServerPollingResponse, Boolean>() {\n                @Override\n                public Boolean call(ServerPollingResponse response) {\n\t\t\t/**\n \t\t\t* 如果我们在这里返回“false”的话，那这个结果会被过滤掉（filter）\n \t\t\t* 过滤（Filtering） 表示 onNext() 不会被调用.\n \t\t\t* 但是 onComplete() 仍然会被传递.\n \t\t\t*/\n                    Log.v(TAG, \"filter, call response \" + response);\n                    return response.isJobDone;\n                }\n            })\n            .subscribe(\n                    new Subscriber<ServerPollingResponse>() {\n                        @Override\n                        public void onCompleted() {\n                            Log.v(TAG, \"onCompleted \");\n                        }\n\n                        @Override\n                        public void onError(Throwable e) {\n                            Log.v(TAG, \"onError \");\n                        }\n\n                        @Override\n                        public void onNext(ServerPollingResponse response) {\n                            Log.v(TAG, \"onNext response \" + response);\n\t\t\t\t\t\t//服务器轮询停止了，你可以做些其他事情。\n                        }\n                    }\n            );\n```\n代码看起来很多，但是很容易理解，并且利用了优雅的链式操作符。\n\n假如服务器在三次请求后才返回 “**isJobDone=true**” , log打印如下:\n```\nrepeatWhen, call\n//在这里发起了api请求\nfilter, call response isJobDone=false\ntakeUntil, call response isJobDone=false\n```\n```\n//在这里再次发起了api请求\nfilter, call response isJobDone=false\ntakeUntil, call response isJobDone=false\n```\n```\n//在这里，第三次发起api请求\nfilter, call response isJobDone=true\nonNext response isJobDone=true\ntakeUntil, call response isJobDone=true\nonCompleted\n```\n在下一个部分中，我将解释为什么方法会那样被调用\n\n服务器轮询实现方法的RxJava内部原理解释\n-------------------------------------------------------------\n\n发起http请求后，在所有**call()**方法中， **filter()**的predicate中的**call()**方法会第一个被调用。\n\n如果我们在**filter()** 中返回“false”，表明我们对结果（result）不满意，不要把这个结果传给 **Subscriber<ServerPollingResponse>**。在这里，发生的链式事件如下：\n\n1. 我们返回“false”，表明这个结果（result）不会被传递到 **filter()**的子元素（child）**Subscriber<ServerPollingResponse>**中。\n\n2. 对**onNext()**的调用会被传递到 **takeUntil()**的Observable中，然后它的predicate的**call()** 方法会被调用\n\n3. 我们看到“job 还没有被完成”，所以我们在**takeUntil()**的**call()**方法中返回“false”\n\n4. 这表示**repeatWhen()**的**onNext()**和**onComplete()**会被调用\n\n5. 当**onComplete()** 被调用时，他会触发一个延时5s的对原始Observable的重新订阅（resubscriber）。也就是会再次发起http请求，也是我们要实现的目的。\n\n如果我们在**filter()**中返回“true”，表明我们对这个结果（result）是满意的，链式事件如下：\n\n1. 结果（result）被传递到了filter的子元素（child）**Subscriber<ServerPollingResponse>**的**onNext(ServerPollingResponse response)** 中\n\n2. 然后这个结果（result）被传递到了**takeUntil()**的predicate的**call()**方法中\n\n3. 在 **takeUntil()** 中，我们也返回 “true”，因为“任务完成了（job done）”\n\n4. 因为我们返回了“true”，**takeUntil()**操作符会调用它的子元素(child)**filter()**的**onComplete()** .\n\n5. **filter()** 调用它的子元素**Subscriber<ServerPollingResponse>**的 **onComplete()**方法\n\n6. **takeUntil()**会马上取消订阅（unsubscribe），这也是为什么**repeatWhen()**的Observable的**onNext()**或者**onComplete()**不会被调用的原因，http请求也就不会被再次发起。\n\n\n整个链是由takeUntil()操作符调用内部的unsubscribe()来终止的。\n\n服务器轮询的等待时间随着重试次数的增加而增加\n------------------------------------------------------------------\n\n基本原理是一样的。我们只要向**repeatWhen()**的predicate中增加一些链式方法（chaning method）就行了。\n```\n private static final int COUNTER_START = 1;\n    private static final int ATTEMPTS = 5;\n    private static final int ORIGINAL_DELAY_IN_SECONDS = 10;\n\n    // 这是链接在repeatWhen的predicate的call方法中的新的function\n    repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {\n        @Override\n        public Observable<?> call (Observable < ?extends Void > observable){\n            Log.v(TAG, \"repeatWhen, call\");\n\n            return observable.zipWith(Observable.range(COUNTER_START, ATTEMPTS), new Func2<Void, Integer, Integer>() {\n                @Override\n                public Integer call(Void aVoid, Integer attempt) {\n                    Log.v(TAG, \"zipWith, call, attempt \" + attempt);\n                    return attempt;\n                }\n            }).flatMap(new Func1<Integer, Observable<?>>() {\n                @Override\n                public Observable<?> call(Integer repeatAttempt) {\n                    Log.v(TAG, \"flatMap, call, repeatAttempt \" + repeatAttempt);\n\t\t\t\t\t// 增加等待时间\n                    return Observable.timer(repeatAttempt * ORIGINAL_DELAY_IN_SECONDS, TimeUnit.SECONDS);\n                }\n            });\n        }\n    })\n```\n在这里，每发起一次api请求前的延时时间是随着尝试次数的增长而乘法式地增加的。非常简单高效。\n\n打印信息如下\n```\nrepeatWhen, call\n//这里是我们的第一次API请求\nfilter, call response isJobDone=false\ntakeUntil, response isJobDone=false\nzipWith, call, attempt 1\nflatMap, call, repeatAttempt 1\n```\n```\n//等待10s后发起第二次请求\nfilter, call response isJobDone=false\ntakeUntil, response isJobDone=false\nzipWith, call, attempt 2\nflatMap, call, repeatAttempt 2\n```\n```\n//等待20s后发起第三次请求\nfilter, call response isJobDone=true\nonNext response isJobDone=true\ntakeUntil, response isJobDone=true\nonCompleted\n```\n解释如下\n\nRxJava中 zipWith()和flatMap()的解释\n-----------------------------------------\n\n我会略过前面讲过的东西，直接解释一下与**zipWith()** 和 **flatMap()**相关的东西。\n\n1. 当**takeUntil()** 完成它的工作以后，从**repeatWhen()** 返回的Observable会开始工作。这个Observable是**zipWith()** 和 **flatMap()**一起作用的结果（combined result）。\n\n2. **zipWith(parameter1, parameter2)**会拿到**repeatWhen()**里的Observable所发射的值，也就是**Void aVoid**，还会拿到由它的第一个参数，也就是**Observable.range(COUNTER_START, ATTEMPTS)** 所发射的值，然后将这两个值传递给函数（function） **call(Void, Integer)**。在**call()** 方法中，我们可以利用这两个参数做一些操作，然后返回一个值（虽然在我们的例子中，是一个Integer，但是它也可以是其它任何类型，如果我们想返回其它类型的话，只需要改一下**new Func2 < Void, Integer, /* 改这个 */ Integer>**) ）中的第三个泛型类型就行了），但是在这里，我们只要返回我们从Observable中获取的值就行了，这个值就是重复尝试的次数。\n\n3. **zipWith()**中返回的值会被封装到一个发射（emit）这些值的Observable中。然后我们在** flatMap()**中处理这些值。\n\n4. **flatMap()**拿到这个值，并利用它们来生成一个计时器Observable（timer Observable）。计时器Observable（timer Observable） 会先等待指定的时间，然后交给链的后续部分来继续处理（passes control down the chain），也就是原来发起api请求的Observable。\n\n我们可以忽略掉**zipWith()**而直接在**repeatWhen()**中使用 **flatMap()** \n```\nrepeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {\n        @Override\n        public Observable<?> call(Observable<? extends Void> observable) {\n            Log.v(TAG, \"repeatWhen, call\");\n\n            return observable.flatMap(new Func1<Void, Observable<?>>() {\n                @Override\n                public Observable<?> call(Void aVoid) {\n                    if(mCounter > ATTEMPTS){\n\t\t\t\t\t// 由我们自己终止\n                        throw new RuntimeException();\n                    }\n                    return Observable.timer(mCounter++ * ORIGINAL_DELAY_IN_SECONDS, TimeUnit.SECONDS);\n                }\n            });\n        }\n    })\n```\n\n一般这种情况，我们需要用一个计数器，自己去控制请求（attempt），并且在需要时终止这一系列操作（ terminate the sequence ）。而在这里，我们利用了**zipWith()**操作符，让RxJava帮我们做了这一切。\n\n出现错误时重试\n--------------------------\n\n众所周知，在Retrofit1 中每个网络错误都是交由**onError()**方法处理的\n\n为了在失去网络连接或者当返回的http状态是除了200 OK以外的不正常状态时实现重试，我们需要使用 **retryWhen()** 而不是**repeatWhen()**。同时，**zipWith()** 的参数也要做一点变化。\n\n```\n    retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {\n        @Override\n        public Observable<?> call(Observable<? extends Throwable> observable) {\n            Log.v(TAG, \"retryWhen, call\");\n\n            return observable.zipWith(Observable.range(COUNTER_START, ATTEMPTS), new Func2<Throwable, Integer, Integer>() {\n                @Override\n                public Integer call(Throwable throwable, Integer attempt) {\n                    Log.v(TAG, \"zipWith, call, attempt \" + attempt);\n                    return attempt;\n                }\n            })\n```\n\n**repeatWhen()** 和 **retryWhen()**的主要区别就是**repeatWhen()**会在接收到 **onNext()**时重新subscribe，**retryWhen()**则在接收到 **onError()**时重新subscribe。\n\n让我们将repeatWhen()和retryWhen()结合起来来实现服务器的轮询和错误重试\n------------------------------------------------------------------------\n\n下面是相关的代码\n\n```\n retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {\n        @Override\n        public Observable<?> call(Observable<? extends Throwable> observable) {\n            Log.v(TAG, \"retryWhen, call\");\n            return observable.compose(zipWithFlatMap());\n        }\n    }).repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {\n        @Override\n        public Observable<?> call(Observable<? extends Void> observable) {\n            Log.v(TAG, \"repeatWhen, call\");\n            return observable.compose(zipWithFlatMap());\n        }\n    })\n\n    <T> Observable.Transformer<T, Long> zipWithFlatMap() {\n        return new Observable.Transformer<T, Long>() {\n\n            @Override\n            public Observable<Long> call(Observable<T> observable) {\n                return observable.zipWith(Observable.range(COUNTER_START, ATTEMPTS), new Func2<T, Integer, Integer>() {\n                    @Override\n                    public Integer call(T t, Integer repeatAttempt) {\n                        Log.v(TAG, \"zipWith, call, repeatAttempt \" + repeatAttempt);\n                        return repeatAttempt;\n                    }\n                }).flatMap(new Func1<Integer, Observable<Long>>() {\n                    @Override\n                    public Observable<Long> call(Integer repeatAttempt) {\n                        Log.v(TAG, \"flatMap, call, repeatAttempt \" + repeatAttempt);\n\t\t\t\t\t\t//增加等待时间\n                        return Observable.timer(repeatAttempt * 5, TimeUnit.SECONDS);\n                    }\n                });\n            }\n        };\n    }\n```\n你可能注意到了，我将**zipWith()**和**flatMap()** 封装到了单独的方法中，并且利用compose让它可以在**repeatWhen()**和**retryWhen()**里能够重复使用。现在，如果我们请求失败了，我们会去重试（retry），如果成功了，但是“任务（job）还没完成”，我们会重复一遍（repeat）。\n\nRxJavas实在是太好用了!\n\n为它喝彩吧 :)\n\n\n[1]: http://stackoverflow.com/questions/34943734/using-of-skipwhile-combined-with-repeatwhen-in-rxjava-to-implement-server-po/34948978"
  },
  {
    "path": "issue-44/用RxJava替代EventBus.md",
    "content": "用RxJava替代EventBus\n---\n\n> * 原文链接 : [use Rxjava instead of Event Bus libraries](https://medium.com/mobiwise-blog/use-rxjava-instead-of-event-bus-libraries-aa78b5023097#.k0ecseaz7)\n* 原文作者 : [Muratcanbur](https://medium.com/@muratcanbur)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [XWZack](https://github.com/XWZack) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)     \n* 状态 :   校对中  `\n\n在Android社区，每个人都在讨论RxJava，以及为什么我们应该在Android项目中使用RxJava。当我们开始在Android项目中实现RxJava后，我们注意到，我们的项目不需要引入Otto（或任何其他事件总线库）。通过这篇博客，我将展示我们是如何在项目中用RxJava替代Otto的。\n\n\n# 我们项目中的MVP模式是怎么实现的\n\n开始开发我们的无线流媒体应用Radyoland的时候，我们决定使用MVP模式来设计我们的代码底层，项目架构等。我们已经分成了几层（domain，model，app等）\n\n![](https://cdn-images-1.medium.com/max/800/1*TuU0ZvYeLsaypR8PIeNeyw.png)\n\n在model层，我们有基于RESTful的类和接口。在domain层，我们试图实现应用程序的业务逻辑，所以我们写了一些用例类。\n\n# 为什么我们当初使用event bus？\n\n如果你的Android应用超过一层，这就意味着你需要在这些层之间传输数据。就我们而言，我们认为，如果我们为数据总线和UI总线定义一个BusUtil类，我们就可以轻松地在这些层（model，domain，presentation）之间传输数据。\n\n你可以“订阅” ，并从总线发布的特定事件“取消订阅” 。这种方法背后的逻辑是这样运作的。\n\n在我们的UsecaseController，PresenterImp类中，我们发出一个事件，请求REST实现类中描述的数据并且订阅此事件。\n\n```\n@Override\npublic void getRadioList() {\n    Call<RadioWrapper> radioWrapperCall = restInterface.getRadioList();\n    radioWrapperCall.enqueue(new Callback<RadioWrapper>() {\n        @Override\n        public void onResponse(Response<RadioWrapper> response, Retrofit retrofit) {\n            dataBus.post(response.body());\n        }\n\n        @Override\n        public void onFailure(Throwable t) {\n            Timber.e(t.getMessage());\n        }\n    });\n}\n```\n当我们调用了带有回调参数的异步数据请求方法，我们用总线发布结果数据，并订阅该事件。\n\n```\n@Subscribe\n@Override\npublic void onRadioListLoaded(RadioWrapper radioWrapper) {\n    new SaveDatabase(radioWrapper, databaseSource).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);\n    uiBus.post(radioWrapper);\n}\n```\n然后发布数据以更新UI 。activity或fragment应该在onResume()中订阅事件并在onPause()中解除订阅 。\n\n```\n@Subscribe\n@Override\npublic void onRadioListLoaded(RadioWrapper radioWrapper) {\n    radioListView.onListLoaded(radioWrapper);\n    radioListView.dismissLoading();\n}\n```\n这种实现方法效果还不错。但由于我们是优秀的Android程序猿，总是追求更好的解决方案。我们找到了一个方案，可以摆脱所有的回调和订阅方法。我们决定在代码中使用RxJava和RxAndroid。\n\n# let me tell you how we implemented Rxjava\n我们是怎么实现Rxjava的\n\n首先，我们需要改变所有RestInterface的返回类型 。\n\n在没有RxJava的情况下 ;\n\n```\n@GET(\"\")\nCall<RadioWrapper> getRadioList();\n```\n\n有了Rxjava\n\n```\n@GET(\"\")\nObservable<RadioWrapper> getRadioList();\n```\n\n在REST实现类中，我们有API调用方法。我分享了其中的一个。使用RxJava启动后，我们需要改变这种方法的实现。我们把方法的返回类型改成Observable。经过了必要的改变后，方法看起来像这样。\n\n```\n@RxLogObservable\n@Override\npublic Observable<RadioWrapper> getRadioList() {\n    return restInterface.getRadioList();\n}\n```\n我们完成了REST实现类。在此之后，我们需要改变用例的实现类和方法。\n\n```\n@Override\npublic Observable<RadioWrapper> getRadioList() {\n\n    /**\n     * Get radiolist observable from Cache\n     */\n    Observable<RadioWrapper> radioListDB =  databaseSource.getRadioList()\n            .filter(radioWrapper -> radioWrapper.radioList.size() > 0)\n            .subscribeOn(Schedulers.computation());\n\n    /**\n     * Load radiolist from api layer and save it to DB.\n     */\n    Observable<RadioWrapper> radioListApi = apiSource.getRadioList()\n            .doOnNext(radioWrapper -> {\n                Observable.create(subscriber -> {\n                    databaseSource.save(radioWrapper);\n                    subscriber.onCompleted();\n                }).subscribeOn(Schedulers.computation()).subscribe();\n            })\n            .subscribeOn(Schedulers.io());\n\n    /**\n     * concat db and api observables\n     */\n    return Observable\n            .concat(radioListDB, radioListApi)\n            .observeOn(AndroidSchedulers.mainThread());\n\n}\n```\n现在，我们的方法getRadioList返回一个Observable stream到我们的UI。正如你所看到的，我们根本不\n需要通过事件总线发送数据。你还可以过滤，合并，缓存，或根据你的需要操作这个数据流。\n\n# 我们学到了什么\n\n虽然RxJava也不是那么好用，但是通过使用RxJava替换Otto，我们已经从代码中删除了许多冗余的回调代码块。在我看来，对于任何REST API的异步数据请求，RxJava都是最佳选择。\n如果你有更好的解决方法，请随意在这里评论或分享例子。\n\n"
  },
  {
    "path": "issue-45/Android圆弧整容之谜.md",
    "content": "Android圆弧整容之谜\n---\n\n> * 原文链接 : [The mysterious case of who killed arcs on Android](https://medium.com/@workingkills/the-mysterious-case-of-who-killed-arcs-on-android-9155f49166b8#.j5h3dqc5p)\n* 原文作者 : [Eugenio Marletti](https://medium.com/@workingkills)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成 \n\n\n愤怒。挫败。悲伤。我感受了所有的情绪，却总得到同样的结果：在 Android 系统绘制圆弧总是出现奇怪的问题，而我甚至没有修复这个问题的一点点头绪。\n\n兴趣使然，我选择了 Android 开发工程师这份职业：每一天我都期待能在 Android 平台上创造一些很酷的东西，因为 Android 平台的开发性以及它的平台特性。\n\n有时候，上帝总会考验你的爱，例如两周以前，我对 Android 的爱就遭遇了大危机……\n\n我在 Clue 里工作，最近在开发 App，一般我要做的就是绘制 UI 设计的自定义图形(如下图)。虽说看起来很复杂，但再复杂的图形都能被分解，看作许多简单图形的组合，如：线，矩形，圆形，或……圆弧。\n\n![](https://www.helloclue.com/assets/images/index/title.png)\n\n而上帝给我考验的那一天，我需要做的就是绘制一个圆，上面有一些覆盖的圆弧；很自然的，我使用框架层提供的 Canvas.drawArc() 绘制好了。看起来很简单对吧？\n\n然后绘制出来的东西就把我吓哭了：\n\n![](https://cdn-images-1.medium.com/max/800/1*aT_QguKG18dUjX-wc70cdA.png)\n\n看到没有！那些圆弧的边缘总会有一些区域是空白的，而且，如果两条圆弧的起点/长度不相同，它们的起点/终点绝对不会完美贴合在一起！\n\n当然，作为一名老司机，一言不合我不飚车能行？于是我翻开了飚车秘籍：\n\n1. Google 一下 (然并卵)\n2. 喝口咖啡冷静一下\n3. 打开/关掉硬件加速\n4. 把圆弧添加到 Path 中，然后绘制出来\n5. 尝试旋转 Canvan/Path\n6. 再 Google 一下（然并卵）\n7. 看着手机发会呆\n8. 强制重启电脑/设备几次\n9. 找个黑暗的小角落放声大哭吧，没想到失业这一天来得这么快……\n\n因为以上办法都解决不了那个问题，走投无路，我猛地踩下刹车，拉拉手刹，狂打方向盘，漂移进入了源码，然后发现 Canvas.drawArc() 实际是在C/C++层对 Skia 库的调用，Skia 是 Android 用于绘制的库。研究了会 Skia，我就找到问题所在了 - Skia 不能绘制理想的圆弧，同理圆，举例来说吧 - 它只是利用贝塞尔曲线弄出近似的图形，然而效果一点也不好。\n\n所以，大家快上车啊，老司机又要发车了：\n\n1. 创建一个圆形的 Bitmap 对象，然后切掉不需要的部分：巨傻无比的方案，但好用的一B\n2. 用 OpenGL：有点过分……\n3. 继续在角落里哭\n\n虽说下班了，但我还是忘不掉这个 Bug。于是我整个周末都在搜索和实验，直到我找到[这篇博文](http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html)，它纠正了[这篇论文](http://itc.ktu.lt/itc354/Riskus354.pdf)。简单来说，该博文解释了贝塞尔曲线为什么不能得到理想的圆弧，却能弄出近似的图形，该曲线运用地越多，近似的效果越好。此外，该博文还提供了各种等价的实现，因而拯救了角落里默默哭泣的我。\n\n最终效果：\n![](https://cdn-images-1.medium.com/max/800/1*oAVAOgwAFpUUHCpuHICRew.png)\n\n现在我们就能得到理想的效果了，这一切都是因为爱，只有爱才能解决一切！要不然我怎么能看懂那些破论文……不过不得不说，Google 的工程师实在是太过分了，本是同根生，相煎何太急，跪求你们修复这些奇奇怪怪的 Bug 好吗！\n\nClue 文化中重要的一环就是：热衷分享，这也是我把本篇博文的研究以及具体的处理方法开源的原因。\n\n假如生活欺骗了你，不要悲伤，不要心急，忧郁的日子里需要镇静。相信吧，只要你怀揣希望，尽你一切努力去找到解决 Bug 的办法，快乐的日子将会来临。只要每个程序猿都献出一份坚持，就能创造没有 Bug 的世界！\n\n更新：\n\n现在这个问题已经被解决了：Skia 现在支持圆锥曲线以及圆弧以这样的方式绘制了，具体的内容可以看 Skia 官方介绍以及 Chromium issue tracker。不要心急，等 Android 下一个版本更新就可以使用这些新特性了。\n\n不过不得不说的是：我还是很希望听到其他人分享遇到类似问题的经历的，毕竟我能从中了解到，当某个人遇到这样的问题时，以什么样的反应去面对它，以及我该从哪些角度去考虑这个问题，解决这个问题。所以，如果你有类似的经历，很欢迎你联系我！"
  },
  {
    "path": "issue-45/IndeterminateProgressbar解析-Part 3.md",
    "content": "IndeterminateProgressbar解析-Part 3\n---\n\n> * 原文链接 : [Indeterminate – Part 3](https://blog.stylingandroid.com/indeterminate-part-3/)\n* 原文作者 : [Mark Allison](blog.stylingandroid.com)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n不吹不黑，当用户进行一项不定时长的耗时任务时，IndeterminateProgressBar 提供的视觉体验绝对是一流的。为此，前段时间我发了一篇博文讲解水平 IndeterminateProgressBar 的实现，而最近，我又写下这系列博文剖析圆形 IndeterminateProgressBar 的实现原理，这篇博文最终会创建能适配低版本设备的 IndeterminateProgressBar（支持 API 11+）。\n\n在前面的博文中我们已经学习了 IndeterminateProgressBar 在 Android Lollipop 以上的设备中的实现，所以今天我们就要尝试创建我们自己的 IndeterminateProgressBar - 即支持低版本设备的 IndeterminateProgressBar。\n\n首先了解负责绘制圆形部分的 Drawable 组件吧（具体实现类似于 AOSP 实现版本中使用的 VectorDrawable），由于 VectorDrawable 只支持 Android Lollipop 以上的设备，因此我们现在也不能通过 VectorDrawableCompat 使用它，但我们可以通过最传统的办法：组合使用 Canvas 和 Paint 去绘制我们需要的效果：\n\n```java\npublic class IndeterminateDrawable extends Drawable implements Animatable {\n    private static final int STROKE_WIDTH = 6;\n    private static final int PADDING = 3;\n\n    private final Paint paint;\n    private final RectF bounds;\n    private final float padding;\n    private float startAngle;\n    private float endAngle;\n    private float rotation;\n    private Animator animator;\n\n    public static IndeterminateDrawable newInstance(Context context) {\n        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();\n        float strokeWidthPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, STROKE_WIDTH, displayMetrics);\n        float paddingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, PADDING, displayMetrics);\n        Paint paint = createPaint(fetchAccentColour(context), strokeWidthPx);\n        RectF bounds = new RectF();\n        float padding = strokeWidthPx / 2 + paddingPx;\n        IndeterminateDrawable drawable = new IndeterminateDrawable(paint, bounds, padding);\n        drawable.createAnimator();\n        return drawable;\n    }\n\n    @ColorInt\n    private static int fetchControlColour(Context context) {\n        TypedArray typedArray = context.obtainStyledAttributes(0, new int[]{R.attr.colorControlActivated});\n        try {\n            return typedArray.getColor(0, 0);\n        } finally {\n            typedArray.recycle();\n        }\n    }\n\n    static Paint createPaint(@ColorInt int colour, float strokeWidth) {\n        Paint paint = new Paint();\n        paint.setColor(colour);\n        paint.setStyle(Paint.Style.STROKE);\n        paint.setStrokeWidth(strokeWidth);\n        paint.setAntiAlias(true);\n        paint.setStrokeCap(Paint.Cap.SQUARE);\n        return paint;\n    }\n\n    IndeterminateDrawable(Paint paint, RectF bounds, float padding) {\n        this.paint = paint;\n        this.bounds = bounds;\n        this.padding = padding;\n    }\n\n    private void createAnimator() {\n        startAngle = 0;\n        endAngle = 270;\n        animator = new AnimatorSet();\n    }\n    .\n    .\n    .\n}\n```\n\n首先我们需要判断 笔画宽度(stroke width) 和间距的 pixel 像素值是否和我们使用的 Material Drawable 匹配，在这里一定要在运行时通过 DisplayMetrics 去将 dip 转换为 px 以确保在不同设备中控件的现实效果保持一致性。\n\n然后我们需要创建 Paint 对象，为它设置颜色和笔画宽度，以绘制控件的圆形。当然，我们可以从 theme 中获得正确的颜色。（在 Design support library 里一般都会在 theme 中声明颜色值）\n\n记得给 Paint 设置抗锯齿，要不然边缘上会有奇奇怪怪的东西出现……此外，我们还需要在路径上设置方形覆盖块，以模仿 Material VectorDrawable 给 pathData 设置的效果。\n\n我们还创建了 RectF 对象，它将和 Paint 对象在 onDraw() 方法中被使用，提前创建它们是为了避免在 onDraw() 方法中重复创建它们，而且能在 onDraw() 方法中复用它们，以保持 onDraw() 方法的简洁，不需要处理不必要的事务，从而避免动画掉帧。\n\n然后我们在调用 createAnimator() 构建动画前创建了 IndeterminateDrawable，IndeterminateDrawable 的实现其实挺傻的，因为都是些固定值的设置。\n\n因为我们创建了 Drawable 对象的子类，因此我们需要重写一些抽象方法：\n\n```java\npublic class IndeterminateDrawable extends Drawable implements Animatable {\n    .\n    .\n    .\n    @Override\n    public void setAlpha(int alpha) {\n        paint.setAlpha(alpha);\n    }\n\n    @Override\n    public void setColorFilter(ColorFilter colorFilter) {\n        paint.setColorFilter(colorFilter);\n    }\n\n    @Override\n    public int getOpacity() {\n        return paint.getAlpha();\n    }\n    .\n    .\n    .\n}\n```\n\n我们将这些值设置给 Paint 就行了。\n\n由于我们还实现了 Animatable 接口，因此我们需要重写接口的方法以控制动画的状态：\n\n```java\npublic class IndeterminateDrawable extends Drawable implements Animatable {\n    .\n    .\n    .\n    @Override\n    public void start() {\n        animator.start();\n    }\n\n    @Override\n    public void stop() {\n        animator.end();\n    }\n\n    @Override\n    public boolean isRunning() {\n        return animator.isRunning();\n    }\n    .\n    .\n    .\n}\n```\n\n具体的操作我们委托给 Animator 完成。\n\n接下来就是 Animator 在展示动画时动态修改 Drawable 参数的一些 set 方法：\n\n```java\npublic class IndeterminateDrawable extends Drawable implements Animatable {\n    .\n    .\n    .\n    public void setStartAngle(float startAngle) {\n        this.startAngle = startAngle;\n        invalidateSelf();\n    }\n\n    public void setEndAngle(float endAngle) {\n        this.endAngle = endAngle;\n        invalidateSelf();\n    }\n\n    public void setRotation(float rotation) {\n        this.rotation = rotation;\n        invalidateSelf();\n    }\n    .\n    .\n    .\n}\n```\n\n如果你有看过前面的博文，下面的代码应该挺好理解的 - 我们通过 Animator 动态修改长度变化的起点和终点，并且旋转该圆弧。\n\nonDraw() 的实现：\n\n```java\npublic class IndeterminateDrawable extends Drawable implements Animatable {\n    .\n    .\n    .\n    @Override\n    public void draw(Canvas canvas) {\n        bounds.set(padding, padding, canvas.getWidth() - padding, canvas.getHeight() - padding);\n        canvas.drawArc(bounds, rotation + startAngle, endAngle - startAngle, false, paint);\n    }\n}\n```\n\n就两行代码，简单吧！首先我们填充之前创建的 RectF 对象，该对象的尺寸由 canvas 的尺寸（考虑了间距）决定。然后在该矩形区域中绘制了圆弧，然后根据给定的值设置该圆弧的起始、终止位置。最终通过前面创建的 Paint 将它绘制。\n\n其实在 Activity 中使用它非常简单，代码如下：\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        ImageView imageView = (ImageView) findViewById(R.id.image);\n        IndeterminateDrawable drawable = IndeterminateDrawable.newInstance(this);\n        imageView.setImageDrawable(drawable);\n        drawable.start();\n    }\n}\n```\n\n运行后我们就能看到如下效果，由于目前还没有添加 Animator，所以只是一个静态的图像：\n\n![](https://i2.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/static_IndeterminateDrawable.png?resize=300%2C225&ssl=1)\n\n有一点细节我需要在这里告诉大家，Android 用于绘制图形的 Skia 库在绘制圆弧时有一些问题，有兴趣的话可以去读[ Eugenio Marletti 的这篇博文](https://medium.com/@workingkills/the-mysterious-case-of-who-killed-arcs-on-android-9155f49166b8#.2uznv5mun)，这篇博文真心推荐各位认真看一看。但由于我们只需要绘制一条圆弧，所以不太需要考虑那些问题。\n\n在下一篇博文中我会介绍怎么结合 Animator 让它动起来！\n\n[源码点我](https://github.com/StylingAndroid/Indeterminate/tree/Part3)"
  },
  {
    "path": "issue-45/使用反射到底会对性能造成多大影响？.md",
    "content": "使用反射到底会对性能造成多大影响？\n---\n\n> * 原文链接 : [How Slow is Reflection in Android?](http://blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html)\n* 原文作者 : [Anton Krasov](http://blog.nimbledroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成 \n\n\n\n(最近我们分析了大量的应用，并发现了许多影响 App 性能的原因，从这篇博文开始，我会一个一个地介绍我们的发现)\n\n不论是 Java 开发还是 Android 开发，反射都是非常好用的工具，但反射同时也是影响 Android 应用性能的一大原因，下面就讲两个例子让大家了解反射的坏处吧：\n\n##两个真实的例子\n\n第一个例子就是 NYTimes Android App 了。在 NimbleDroid 的帮助下，NYTimes 的程序猿发现 Gson 中使用的反射型 Adapter 在应用启动时增加了大约 700ms 的延迟，最终他们通过手动地适配每一种数据类型对应的 Adapter 解决了这个问题。\n\n第二个例子就是 Photobucket，Photobucket 是一个大型的图片分享平台，而 Photobucket 的程序猿在开发时使用的反射技术也给他们带来了一些性能瓶颈。\n\n![](http://blog.nimbledroid.com/assets/slow-android-reflection/com.photobucket.android-iricle-graph-top.png)\n调用 com.photobucket.api.client.jersey.UserClient 构造器需要 660ms\n\ncom.photobucket.api.client.jersey.UserClient 构造器需要 660ms 才能执行完成，不妨细看该柱状图，我们会发现大部分开销都来自于反射：\n\n![](http://blog.nimbledroid.com/assets/slow-android-reflection/com.photobucket.android-iricle-graph-bottom.png)\n\n构造方法中有大量的反射调用，例如：java.lang.Class.getGenericInterfaces\n\n需要提醒的是：getGenericInterfaces() 返回类具体实现的接口类型，在该构造方法中，它被调用了5次，大约需要81ms。81ms看起来好像不多，但所有的反射调用加起来可就差不多 600ms了，不妨看看为什么需要这么多时间。\n\n从源码上看，该库似乎允许开发者通过注解配置 REST 客户端，问题就是：该库没有在编译期处理注解，而是在运行时解析和创建 REST 客户端（利用反射）。从性能的角度看，这就是灾难性的打击。\n\n##微基准\n\n\n下面我们创建一个简单的例子以测试反射到底有多慢。\n\n例子中使用的类是 Activity，然后重复执行某个操作大约10000次，具体代码如下：\n\n```java\nClass<?> clazz = android.app.Activity.class;\nfor (int i = 0; i < 10000; i++) {\n\tclazz.getFields();\n}\n```\n\n此外，我们还有关于创建对象的测试（DummyItem 是一个空类），以了解反射创建对象的开销：\n\n```java\ntry {\n    for (int i = 0; i < 1_000_000; i++) {\n        DummyItem.class.newInstance();\n    }\n} catch (InstantiationException e) {\n    e.printStackTrace();\n} catch (IllegalAccessException e) {\n    e.printStackTrace();\n}\n```\n\n下面是测试的结果（下面的数值单位都是 ms，而且都是真实设备测试的）：\n\n||NEXUS 5 (6.0) ART|GALAXY S5 (5.0) ART|GALAXY S3 mini (4.1.2) Dalvik|\n|getFields|1108|1626|27083|\n|getDeclaredFields|347|951|7687|\n|getGenericInterfaces|16|23|2927|\n|getGenericSuperclass|247|298|665|\n|makeAccessible|14|147|449|\n|getObject|21|167|127|\n|setObject|21|201|161|\n|createDummyItems|312|358|774|\n|createDummyItemsWithReflection\t1332|6384|2891|\n\n很显然反射在 Android 中真的非常慢，使用反射的时间开销是(1332ms, 6384ms, 2891ms)，没使用反射的时间开销是(312ms, 358ms, 774ms)。有趣的是，Android 5.0 使用的 ART 虚拟机在更好的设备上使用反射的性能甚至比 Android 4.0 使用的 Dalvik 在更老的设备上要差。而在 Android 6.0 ART 上性能表现就好很多，但开销还是很高。\n\n##更多的实例\n\nActiveAndroid 是一个使用反射实现的库，不妨分析应用市场中使用它的应用，看看它对应用启动时间的影响：\n\n这是 Scribd 的性能表现：\n\n![](http://blog.nimbledroid.com/assets/slow-android-reflection/Scribd.png)\n\ncom.activeandroid.ActiveAndroid.initialize 调用开销是 1093ms\n\nMyntra 也出现了一样的问题：\n\n![](http://blog.nimbledroid.com/assets/slow-android-reflection/Myntra.png)\n\ncom.activeandroid.ActiveAndroid.initialize 调用开销是 1421ms\n\n如你所见，该库需要超过1秒的时间以进行初始化，考虑到用户期待应用的启动时间的平均值为2秒，这个时间真的很多……\n\n总的来说，反射在 Android 终点性能表现真的不佳，为了尽可能给用户提供平滑的体验，我们建议：\n\n> 建议：避免使用反射（或者使用反射的库），特别是，别使用反射型 Adapter 去序列化 Java 对象。"
  },
  {
    "path": "issue-45/安卓Binder架构概述.md",
    "content": "安卓Binder架构概述\n---\n\n> * 原文链接 : [An Overview of Android Binder Framework](http://codetheory.in/an-overview-of-android-binder-framework/)\n* 原文作者 : [Rishabh](http://codetheory.in/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [seasonwong70](https://github.com/seasonwong70) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成 \n\n\n\n在Linux操作系统上，进程间通信（IPC）技术包含\n - 文件 （Files）\n - 信号 （Signals）\n - 套接字（Socket）   \n - 管道（Pipes）\n - 信号量（Semaphores）\n - 内存共享（Shared Memory）\n - 消息传递（Message passing, 包括队列、消息总线）\n\n虽然可以用的技术很多，但安卓还是选择定制Linux的内核，发明Binder架构（翻译注：实际上是先有Binder架构，随后被迁移到Linux内核上）。Binder架构实现了客户端和服务端之间进程的远程过程调用（RPC）机制，对于服务端的方法，客户端进程能像调用本地方法一样调用它们。因此可以传递数据到远程的方法调用，返还结果给客户端的调用线程。就像是客户端的线程“跳到”另一个进程当中去执行（线程迁移）。\n\n尽管底层的RPC机制非常地复杂，但是Binder架构把一切都封装起来，仅暴露简单的APIs，让整个进程间通信机制看起来简单。让我们看看底下都发生了些什么：\n\n\n- 将数据分解成操作系统能懂的原始形式，此过程亦被称为Marshalling（与序列化相似）\n\n\n- 将分解后的数据信息跨过进程边界传输给远程进程\n\n\n- 在远程进程将信息重构，此过程亦被称为Un/Demarshalling（与反序列化相似）\n\n- 将返回值传回给客户端调用进程\n\nIntents、ContentProviders、Messenger，所有的系统服务，包括电话、震动器、Wifi、电池、通知等，都利用了Binder提供的IPC底层框架。甚至Activity里的生命周期回调函数（例如onStart(), onResume(), onDestroy）也是由ActivityManagerServer经由Binder调用的。\n\n\n这里将覆盖Binder的一些术语，一旦你看过怎么使用AIDLs及完成IPC，借此或许能加深印象。客户端跟服务端不想知道关于Binder协议的任何事情，因此它们使用代理（proxy，在客户端）和存根（stub，在服务端）。代理接收你的高级Java/C++方法调用（请求）并把它们分解成Parcels（Marshalling，类序列化），随后提交事务（transaction）到Binder内核驱动并阻塞。在另一边（服务端进程）存根监听Binder内核驱动，并把从回调中接收到的Parcels重组成服务端能理解的对象和数据类型。\n\n\n当客户端向服务端发起一个调用时，它在分解后的数据中（Parcels）携带表示调用方法的代码。该调用称为事务（transaction）。客户端的Binder对象调用*transact()*，反之服务端Binder对象调用*onTransact()*来处理该调用。一个向*transact()*发起的调用在默认情况下会阻塞客户端线程，直到*onTransact()*在远程线程中执行完毕。\n\n\n*onTransact()*方法在Binder线程池里取出的一个单独线程中执行。线程池中的最大线程数是16，意味着可以并发处理16个远程方法调用（确保处理多线程问题及线程安全）。\n\n\n对于双向通信，服务端亦可发起事务，此时服务端变成了“客户端”，事务在客户端的Binder线程中被处理。只要服务端在执行*onTransact()*调用时发起一个事务，客户端将会在等待第一个事务完成的线程里接收该事务，而不是一个Binder线程。\n\n\n如果你不想*transact()*阻塞，可以传递IBinder.FlAG_ONEWAY标志来立即返回，不用等待任何返回值。\n\n注意：整个IPC通过在Linux内核中的Binder驱动完成。跨进程通信无法直接完成，只有通过内核。Binder驱动是让这成为可能的内核模块。\n\n\n在下一篇文章里，我们将会涉及AIDL的IPC，在那里会对上述的内容有更好的理解。\n"
  },
  {
    "path": "issue-45/简化复杂的视图层次.md",
    "content": "简化复杂的视图层级  \n---\n\n> * 原文链接 : [Simplify Complex View Hierarchies](https://medium.com/google-developers/simplify-complex-view-hierarchies-5d358618b06f#.kuqg1wpi1)\n* 原文作者 : [Aleks Haecky](https://medium.com/@alekshaecky)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) \n* 校对者: [desmond1121](https://github.com/desmond1121)  \n* 状态 :  完成\n\nApp的核心是使用视图层级来创造用户界面和视觉体验。随着app的功能越来越丰富，视图层级会越来越复杂，并且成为性能问题的来源之一。最常见的表象是app卡顿，尤其是渲染复杂视图时。\n\n简化或者重新布局你的视图层级可以提升app的性能表现，尤其是在低配设备和早期的Android版本上。额外的好处是，你的app会变得更容易维护。\n  \n**前期准备**阅读[通过移除无用的背景减少过度绘制](https://medium.com/google-developers/draw-what-you-see-and-clip-the-e11-out-of-the-rest-6df58c47873e#.d9t0874dv)这篇文章，来排除导致卡顿的这一常见原因，避免和视图层级带来的问题混淆在一起。\n\n### 分析视图层级  \n分析视图层级就是使用几个工具来定位并修复性能问题。所以，有时你只需要一个工具，有时则需要全部的工具一起使用来优化性能。\n\n#### GPU呈现模式分析\n  \n1. [**运行GPU呈现模式分析**](http://developer.android.com/tools/performance/profile-gpu-rendering/index.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)**并且观察柱状图蓝色的部分。** 如果蓝色的部分过高并且导致柱状图高度超过了16ms的标志线，那就是你的app花费了大量时间来更新display list。在Android 6.0版本中增加了额外的颜色，表示Measure/Layout的浅绿色也容易超乎预期的耗时。导致这些的一个原因就是试图层级过于复杂了。当然这只能告诉你有问题，而不能告诉你问题出在哪里。让我们接着往下看。\n\n#### 显示GPU视图更新\n  \n1. 在你的设备上**运行显示GPU视图更新工具**。在**开发者选项中**，找到**硬件加速渲染**然后打开**显示GPU视图更新**。\n2. 操作你的app。\n3. 在屏幕上的试图更新时会闪烁红色。如果你注意到屏幕上和正在更新的区域无关的视图闪烁，那很可能是两者之间在视图层级上相关联所以导致了视图被不正确的失效了。这样，你就可以关注在这些区域上来更快的查找到问题。\n\n#### Hierarchy Viewer \n这就是你要做大量工作的工具了\n  \n1. **开启** [**Hierarchy Viewer工具**](http://developer.android.com/tools/performance/hierarchy-viewer/index.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)。\n2. 找到你app中仍然有大量过度绘制的视图层级。考虑重新调整你的视图来减少过度绘制。\n3. 定位到视图层级复杂的区域，考虑如何能简化它。\n4. [**分析**](https://developer.android.com/tools/performance/hierarchy-viewer/profiling.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)视图层级中额外潜在的问题节点。\n\n#### 使用lint深入挖掘问题\n \n在布局文件上使用[lint](http://developer.android.com/tools/debugging/improving-w-lint.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)来搜寻对视图层级可能的优化。然而关于lint的好处需要另写一篇文章。\n\n### 简化视图层级\n#### 移除对最终图像无用的视图\n \n按照如下步骤来确定对最终呈现到屏幕上的图像无用的视图：\n \n1. 在Hierarchy Viewer中，从叶子节点像根节点查看视图层级。\n2. 点击每一个节点来查看屏幕当前的图像。在Layout View窗口查看视图是如何分层的\n3. 如果一个之前可见的视图被完全隐藏，那么你可能根本就不需要这个视图，如图1.\n4. 在你的代码中移除被完全遮盖，从来不显示或者在屏幕区域外的视图。\n\n![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/1.png?raw=true)\n\n图1. 视图2和3被完全遮盖可以安全的移除。\n\n#### 扁平化视图层级来减少嵌套 \n1. 你的视图层级中有没有看起来类似图2的嵌套？\n2. 当可行时，使用相对布局替代嵌套的线性布局来扁平化视图层级。查看[优化视图层级](http://developer.android.com/training/improving-layouts/optimizing-layout.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)这篇文章.\n\n![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/2.png?raw=true)\n  \n图2. 这个层次很深的视图层级可以变得扁平化来提升性能。\n\n#### 减少视图的数量\n \n1. 如果你的用户界面有很多简单的视图，你可以在不影响用户体验的前提下，像图3那样结合其中的一些。\n2. 所有的改变都会影响你呈现信息的方式当然设计上要做出权衡。不过要记住性能表现是你app成功与否最重要的一点，无论什么情况下，你都应该尽可能的选择优化它。\n3. 合并一些视图。比如：如果你减少字体和样式，就可以合并text view。\n4. 重新设计你的用户界面来使用更少的视图。\n\n![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/3.png?raw=true)\n\n图3. 合并视图的列子\n\n#### 简化会导致多次layout的嵌套布局\n  \n像RelativeLayout这样的父视图，需要两次布局来最终确定子视图的位置。结果就是，他们的子视图也需要两次布局。当你再把这些父视图嵌套在一起时，每一个视图层级的布局的次数就会以指数级增长。\n \n举个列子，RelativeLayout中嵌套一个ListView，ListView中嵌套一个GridView，GridView中嵌套一个view，这会导致8倍的布局次数，如图4.\n\n![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/4.png?raw=true)\n  \n- [RelativeLayout](http://developer.android.com/reference/android/widget/RelativeLayout.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)\n- [Linearlayouts](http://developer.android.com/reference/android/widget/LinearLayout.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)使用*measureWithLargestChild*方法\n- [GridView](http://developer.android.com/reference/android/widget/GridView.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)使用gravity\n- 继承自以上容器的自定义视图\n- 一些weights的使用也可以导致加倍的布局\n \n使用任何以上的容器作为一个复杂视图的根节点，比如一个很深的子树的父节点，或者在布局中大量使用，都会影响性能。所以，多考虑如何在避免布局次数指数级增长的情况下达到相同的布局效果。比如，使用不设置gravity的gridview来替代relative layout作为根节点。"
  },
  {
    "path": "issue-46/Android-Reverse-Engineering-101-Part-5.md",
    "content": "Android逆向工程101 – Part 5\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 5](http://www.fasteque.com/android-reverse-engineering-101-part-5/)\n* 原文作者 : [Daniele Altomare](http://www.fasteque.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn。本译文已授权开发者头条（链接：http://toutiao.io/download）享有独家转载权，未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [MiJack](https://github.com/MiJack)\n* 校对者:\n* 状态 :  已翻译\n\n到目前为止,在之前关于Android逆向工程的介绍中，我们已经知道了[APK文件的格式](http://www.fasteque.com/android-reverse-engineering-101-part-1/),如何使用使用[AAPT](http://www.fasteque.com/android-reverse-engineering-101-part-2/)提取应用程序中和Android SDK相关的有用信息，如何[将DEX字节码转化成更具可读性、易于编辑的格式](http://www.fasteque.com/android-reverse-engineering-101-part-3/)以及如何[反编译和修改Android应用程序的源代码和资源](http://www.fasteque.com/android-reverse-engineering-101-part-4/)。\n\n本文是这个系列的最后一篇文章，我们将介绍[**Androguard**](https://github.com/androguard/androguard)，一个可以处理Android文件的python工具\n\n根据官网的介绍，**Androguard** 可以做以下几件事：\n\n>* DEX, ODEX\n>* APK\n>* Android二进制格式的xml文件\n>* Android 资源\n>* 反汇编 DEX/ODEX 字节码\n>* DEX/ODEX文件的反编译\n\nAndroguard的功能是相当出色的，Github上的介绍并不完整，可以在[这里](https://code.google.com/p/androguard/#Features)找到更为全面的介绍。\n\n**Androguard** 可用于Linux，OS X,Windows等平台\n\n## 安装\n\n在[项目以前的主页](https://code.google.com/p/androguard/wiki/Installation)，详细介绍了安装过程，而GitHub上的文档不提供任何更新的信息。\n\n需要注意的是我们的机器上要安装了Python（`2.6`或更高版本），这用于反编译/反汇编APK文件。对于更高级的功能，它是需要安装其它模块：在安装页面上，我们可以找到关于各模块的需要的简要说明。\n\n注意在**Androguard**文件夹中运行如下命令：\n\n`sudo python setup.py install`\n\n在安装好以后，我们可以直接在 **Androguard** 的主文件夹下运行这些工具。\n\n如果机器的操作系统是Windows，建议在 **ARE**-Android Reverse Engineering virtual machine(还包含有其他的逆向工程工具) 上运行 **Androguard**:关于程序的下载和安装相关介绍可以在官方[wiki](https://redmine.honeynet.org/projects/are/wiki)页面找到。我们只需要使用[VirtualBox](https://www.virtualbox.org/)运行这个镜像就行了。\n\n文章中所有的例子使用的 **Androguard** 的版本都是 `2.0` 。它可以在[这里](https://github.com/androguard/androguard/releases/tag/v2.0)直接下载，也可以在[stable releases页面](https://github.com/androguard/androguard/releases)中选择另一个版本。\n\n要做的第一件事，就是检查我们的安装是否正常，所以让我们需要加入 **Androguard** 的主文件夹，然后输入以下命令：\n\n`androlyze.py -s`\n\n我们将看到下面这个输出：\n\n  ![](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-04-at-18.14.40.png)\n\n这是一个交互式的命令行，正在等待输入.\n\n在分析时，我们需要设置apk文件以及反编译的类型。事实上，有3个不同的反编译类型：**DAD** ，**DED** 和 **dex2jar** + **JED**。具体介绍请看[这里](https://code.google.com/p/androguard/wiki/Decompiler)。在下面的例子中，我们使用的是DAD，因为它是默认的反编译类型，所以无需额外的配置。\n\n`a,d,dx = AnalyzeAPK(\"FILENAME.apk\", decompiler=\"dad\")``\n\n![](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-04-at-18.30.51.png?resize=1024%2C118)\n\n反编译一个程序的时间取决于具体的apk文件：当程序完成后，我们就可以根据提示运行其他命令。\n\n现在，我们可以输入`a.`，然后按下TAB键，我们可以得到APK相关的自动补全提示。\n\n我们先用一些简单的命令来获得APK文件中的一些有用信息吧。\n\n## APK 文件\n\n它可以列出APK文件(其本质上是一个ZIP文件)中包含的所有文件：\n\n`a.get_files()`\n\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-14.52.30.png?resize=1024%2C587)\n\n## Application Activities\n\n使用如下命令，我们可以得到应用中包含的所有Activity：\n\n`a.get_activities()`\n\n## Application 的权限\n\n使用如下的命令，我们可以得到应用在manifest文件中声明的所有权限：\n\n`a.get_permissions()`\n\n![](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-14.47.29.png)\n\n它也可以得到关于每一项权限的详细描述：\n\n`a.get_requested_aosp_permissions_details()`\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-15.09.31.png)\n\n\n**Androguard** 的一个重要的特征就是可以找到应用程序的哪些代码请求了在manifest文件中声明的权限。\n\n它可以帮助我们做一下几件事：\n\n* 知道我们是否忘记了需要在manifest文件声明的权限\n* 知道我们是否在manifest文件中声明了多余的权限\n* 帮助我们review使用到权限的代码片段，再次确认我们可能遗漏的注解。\n\n`show_Permissions(dx)`\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-17.09.55.png)\n\n## Application Content providers\n\n使用如下的命令，我们可以得到应用在manifest文件中声明的所有Content Provider：\n\n`a.get_providers()`\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-14.42.07.png)\n\n## Application signature\n\n同时，我们也可以知道apk文件的签名以及其存放的位置：\n\n`a.get_signature_name()`\n\n`a.get_signature()`\n\n[这里](http://doc.androguard.re/html/index.html)是目前最为完整的命令列表的文档：它的版本是`1.9`，不管怎么说，它都是一个很好的参考文档。\n\n\n## Code dumping\n\n使用工具 **androdd**，我们可以得到APK文件中所有类的源代码（整理包括应用的代码和依赖）：在这种情况下，我们得到的是Java类，而不是我们使用[dex2jar](HTTP：//www.fasteque.com/android-reverse-engineering-101-part-3/)得到的`.class`文件。\n\n`androdd.py -i FILENAME.apk -o OUTPUT_DIR`\n![](http://i2.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-15.44.22.png?resize=1024%2C430)\n\n在输入文件中，我们可以根据包名找到对应的Java类。\n\n当然，我们得到的代码可能和之前开发者写的代码不完全一样，但无论如何，它的可读性提高了，更容易理解。请记住，如果原来的应用程序代码已经被混淆，方法和类的名称可能已被重命名，工具不能得到原来的名称。\n\n例如，下面是我们的一个Fragment：源代码可以在[这里](https://github.com/fasteque/rgb-tool/blob/master/android-rgb-tool/src/main/java/com/fastebro/androidrgbtool/fragments/SelectPictureDialogFragment.java)找到.\n\n![](http://i0.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-15.52.47.png?resize=1024%2C390)\n\n正如在本系列中之前的介绍中，我们知道，我们无法找到资源名称，如字符串，布局，数组，颜色，但是，我们可以得到的是一个整形值，在build的时候他和特定的资源相对应，并存储在`R.java`文件中。在这里，它表示为一个十进制值，而在原来的类文件是十六进制。\n\n## XML manifest\n\n使用下面的工具，我们可以得到manifest文件，在apk文件中，它是以二进制格式存在的。我们得到的是纯文本格式的xml文件，更容易阅读。\n\n`androaxml.py -i FILENAME.apk -o OUTPUT.xml`\n\n如果我们打开生成的XML文件，我们可以看到完整的APK manifest 文件，几乎和[原来文件](https://github.com/fasteque/rgb-tool/blob/master/android-rgb-tool/src/main/AndroidManifest.xml)一样。\n\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-16.10.44.png)\n\n## 比较两个apk文件\n\n**Androguard** 还提供了一个重要的工具 **androsim** ，可以用于比较两个应用里的文件。这是一个重要的功能，因为我们可以将一个源程序和另一个程序比较，检测其是否有所改变，可以用于检测恶意软件。同时也可以查看同一应用程序的两个不同版本之间的差异。\n\n`danielealtomare$ androsim.py -i FILENAME_1.apk FILENAME_2.apk -c ZLIB -n`\n\n\n这是两个APK文件比较的输出结果:\n\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-16.26.17.png)\n\n以下是同一应用程序的两个不同版本的比较结果：\n\n![](http://i1.wp.com/www.fasteque.com/wp-content/uploads/2015/12/Screen-Shot-2015-12-05-at-16.56.22.png)\n\n`--help`可以得到可用于该工具的所有选项，因此这可以让分析更加精确，集中于应用程序的具体方面。\n\n当然，**Androguard** 还可以做很多事情，但是，本文的目的只是简单介绍一下工具以及相关的实验和分析。\n\n## MENTIONS\n\n结束系列之前，还要介绍一些比较好的工具。\n\n第一个是[Bytecode Viewer](https://bytecodeviewer.com/)，它实际上是一套完整的逆向工程工具：它使我们通过smali/baksmali轻松地编辑apk文件以及浏览APK和DEX文件。同时也集成了 **dex2jar** 和**Apktool**。更多信息可以查看[这里](https://the.bytecode.club/wiki/index.php?title=Bytecode_Viewer)。\n\n第二个是[ClassyShark](https://github.com/google/android-classyshark)，这是为Android可执行文件设计的一个浏览器。它的客户端可以打开Android（APK）和桌面（JAR）文件。我们可以用ClassyShark打开APK/ ZIP/Class/Jar文件，并分析其内容。\n\n这个系列就结束了：正如之前强调的，这只是简单介绍了Android的逆向工程以及一些工具的简单使用。当然，这只是逆向工程的冰山一角。对Android逆向工程的了解程度取决于我们的实际需要,所以,我们可能会发现一些更适合的工具。\n"
  },
  {
    "path": "issue-46/IndeterminateProgressbar解析-Part 4.md",
    "content": "IndeterminateProgressbar解析-Part 4\n---\n\n> * 原文链接 : [Indeterminate – Part 4](https://blog.stylingandroid.com/indeterminate-part-4/)\n* 原文作者 : [Mark Allison](blog.stylingandroid.com)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\nIndeterminateProgressBar 能在用户进行某项不定时长的耗时操作时提供绝佳的用户体验，之前我有教过大家怎么创建[水平的 IndeterminateProgressBar](http://blog.csdn.net/u012403246/article/details/49582789)，今天我就来教大家实现圆形的 IndeterminateProgressBar，这个控件将支持 API 11（Honeycomb）以上的设备。\n\n在上一篇博文中，我们创建了一个自定义 Drawable - 它能绘制一段圆弧，并且能设置圆弧的起点和终点，以及完成圆弧的旋转。接下来我们要为该 Drawable 添加 ObjectAnimator，使得这些值能够随时间改变，以完成我们期望的动画。\n\n在看代码之前，我需要指出的是：我并不喜欢创建一个工具类，然后在里面放一大堆静态方法 - 因为它们不代表具体的对象，那么在面向对象编程中也就不能拥有清晰的职责。但在这里我还是创建了一个工具类，将 Animator 的构建过程从 Drawable 中剥离，使代码更可读。\n\n```java\nfinal class IndeterminateAnimatorFactory {\n    private static final String START_ANGLE = \"startAngle\";\n    private static final String END_ANGLE = \"endAngle\";\n    private static final String ROTATION = \"rotation\";\n\n    private static final int SWEEP_DURATION = 1333;\n    private static final int ROTATION_DURATION = 6665;\n    private static final float END_ANGLE_MAX = 360;\n    private static final float START_ANGLE_MAX = END_ANGLE_MAX - 1;\n    private static final int ROTATION_END_ANGLE = 719;\n\n    private IndeterminateAnimatorFactory() {\n        // NO-OP\n    }\n\n    public static Animator createIndeterminateDrawableAnimator(IndeterminateDrawable drawable) {\n        AnimatorSet animatorSet = new AnimatorSet();\n        Animator startAngleAnimator = createStartAngleAnimator(drawable);\n        Animator sweepAngleAnimator = createSweepAngleAnimator(drawable);\n        Animator rotationAnimator = createAnimationAnimator(drawable);\n        animatorSet.playTogether(startAngleAnimator, sweepAngleAnimator, rotationAnimator);\n        return animatorSet;\n    }\n    .\n    .\n    .\n}\n```\n类的实现非常简单，就是创建三个 Animator，并把它们添加到 AnimatorSet 中，然后同时播放。\n\n```java\nfinal class IndeterminateAnimatorFactory {\n    .\n    .\n    .\n    private static Animator createStartAngleAnimator(IndeterminateDrawable drawable) {\n        ObjectAnimator animator = ObjectAnimator.ofFloat(drawable, START_ANGLE, 0f, START_ANGLE_MAX);\n        animator.setDuration(SWEEP_DURATION);\n        animator.setRepeatCount(ValueAnimator.INFINITE);\n        animator.setRepeatMode(ValueAnimator.RESTART);\n        animator.setInterpolator(createStartInterpolator());\n        return animator;\n    }\n\n    private static Interpolator createStartInterpolator() {\n        return new LinearInterpolator();\n    }\n\n    private static Animator createSweepAngleAnimator(IndeterminateDrawable drawable) {\n        ObjectAnimator animator = ObjectAnimator.ofFloat(drawable, END_ANGLE, 0f, END_ANGLE_MAX);\n        animator.setDuration(SWEEP_DURATION);\n        animator.setRepeatCount(ValueAnimator.INFINITE);\n        animator.setRepeatMode(ValueAnimator.RESTART);\n        animator.setInterpolator(createEndInterpolator());\n        return animator;\n    }\n\n    private static Interpolator createEndInterpolator() {\n        return new LinearInterpolator();\n    }\n    .\n    .\n    .\n}\n```\n\n这两个 Animator 会控制圆弧的起点和终点，SWEEP_DURATION 是从 AOSP 实现的代码中直接拷过来的。创建插值器的方法会在后面实现一些有趣的效果。\n\n然后是展示旋转动画的 Animator：\n\n```java\nfinal class IndeterminateAnimatorFactory {\n    .\n    .\n    .\n    private static Animator createAnimationAnimator(IndeterminateDrawable drawable) {\n        ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(drawable, ROTATION, 0, ROTATION_END_ANGLE);\n        rotateAnimator.setDuration(ROTATION_DURATION);\n        rotateAnimator.setRepeatMode(ValueAnimator.RESTART);\n        rotateAnimator.setRepeatCount(ValueAnimator.INFINITE);\n        rotateAnimator.setInterpolator(new LinearInterpolator());\n        return rotateAnimator;\n    }\n    .\n    .\n    .\n}\n```\n\nROTATION_DURATION 也是从 AOSP 实现中拷贝的，在这里我们使用了线性插值器，因为我们需要规则的旋转。\n\n现在需要更新 IndeterminateDrawable 中的 createAnimator() 方法，用上这些 Animator：\n\n```java\npublic class IndeterminateDrawable extends Drawable implements Animatable {\n    .\n    .\n    .\n    private void createAnimator() {\n        animator = IndeterminateAnimatorFactory.createIndeterminateDrawableAnimator(this);\n    }\n    .\n    .\n    .\n}\n```\n\n运行的话就会看到一条旋转的线：\n\n[video](https://youtu.be/CEBTrOvsx-w)\n\n因为起点和终点使用相同的线性插值器，所以他们移动的步调一致。又由于方形覆盖块的起点和终点使用相同的值，所以终点会稍微绘制地比圆弧的长度要早一些。\n\n这一点可以通过使用不同的插值器优化：\n\n```java\nfinal class IndeterminateAnimatorFactory {\n    .\n    .\n    .\n    private static Interpolator createStartInterpolator() {\n        return new DecelerateInterpolator();\n    }\n    .\n    .\n    .\n    private static Interpolator createEndInterpolator() {\n        return new AccelerateInterpolator();\n    }\n    .\n    .\n    .\n}\n```\n现在起点会很快达到终点的位置，然后慢慢恢复，而终点则会慢慢开始，然后逐渐加速，直到到达终点，具体效果看视频：\n\n[video](https://youtu.be/GrobL-lzj4E)\n\n看起来很像了，但我们还能优化！\n\n如果我们把旋转动画去掉的话，能更好地感受刚刚的代码实现的效果，以及了解旋转动画的效果：\n\n[video](https://youtu.be/wP9T6gl9MMQ)\n\n在这系列的最后一篇博文中我会使用更多的插值器，让大家了解插值器的魔力。\n\n[源码可以在这里下载](https://github.com/StylingAndroid/Indeterminate/tree/Part4)。"
  },
  {
    "path": "issue-46/IndeterminateProgressbar解析-Part 5.md",
    "content": "IndeterminateProgressbar解析-Part 5\n---\n\n> * 原文链接 : [Indeterminate – Part 5](https://blog.stylingandroid.com/indeterminate-part-5/)\n* 原文作者 : [Mark Allison](blog.stylingandroid.com)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\nIndeterminateProgressBar 能在用户进行某项不定时长的耗时操作时提供绝佳的用户体验，之前我有教过大家怎么创建[水平的 IndeterminateProgressBar](http://blog.csdn.net/u012403246/article/details/49582789)，今天我就来教大家实现圆形的 IndeterminateProgressBar，这个控件将支持 API 11（Honeycomb）以上的设备。\n\nWe’re edging closer to get our material-like circular indeterminate drawable working, we just need to fine tune our Interpolators to get closer to what the Lollipop+ implementation is doing. The trick for doing that it to use something that I mentioned earlier in the series PathInterpolatorCompat.\n\n圆形 IndeterminateProgressbar 控件我们已经完成得差不多了，现在只需要将上一篇博文的代码中使用的插值器修改为 Android Lollipop+ 实现中的插值器就可以在低版本设备中复现该控件，而完成这一部分工作要用到的就是之前提到过的 PathInterpolatorCompat 类。\n\nPathInterpolatorCompat is part of the part of the v4 support library and we already have this included in our project because the design support library depends on it. It works in much the same way as the PathInterpolator which we looked at back in – it maps an input value of x to its corresponding y value based upon a a path drawn from 0,0, to 1,1. However rather than using SVG path data like PathInterpolator does, it actually uses android.graphics.Path instead!\n\nPathInterpolatorCompat 是 Android Support v4 库中的一部分，因为我们已经在本项目中添加了相关的依赖，所以直接调用它就可以。它的实际作用和 PathInterpolator 相似 - 将输入的 x 值按照给定的函数关系给出对应的映射值 y，该函数的 x 和 y 取值范围在 [0.0, 1.0]之间。它和 PathInterpolator 的区别在于：前者使用 android.graphics.Path，后者使用 SVG 路径数据。\n\nSo let’s have a look at how we can do this. The SVG path data that we looked at in the Lollipop+ implementation is actually divided in to to parts: a straight line, and a cubic bezier. Path supports both of these so we just need to map thing appropriately:\n\n那不妨来看看具体的实现吧，在 Android Lollipop+ 的实现中，SVG 路径数据可以分为两部分：一条直线和一条三次贝塞尔曲线。而 Path 支持绘制这两种路径，所以我们只需要这样：\n\n```java\nfinal class IndeterminateAnimatorFactory {\n    .\n    .\n    .\n    private static Interpolator createStartInterpolator() {\n        Path path = new Path();\n        path.cubicTo(0.2f, 0f, 0.1f, 1f, 0.5f, 1f);\n        path.lineTo(1f, 1f);\n        return PathInterpolatorCompat.create(path);\n    }\n    .\n    .\n    .\n    private static Interpolator createEndInterpolator() {\n        Path path = new Path();\n        path.lineTo(0.5f, 0f);\n        path.cubicTo(0.7f, 0f, 0.6f, 1f, 1f, 1f);\n        return PathInterpolatorCompat.create(path);\n    }\n    .\n    .\n    .\n}\n```\n\nIf we actually run that we’ll see that it’s close but not quite what we’re after. Even though we’ve copied the paths used to create the start and end point interpolation what we haven’t taken in to account is the trimPathOffset animation which was used to prevent from the leading edge catching up with the trailing edge. While we could try and factor that in to our Drawable we actually don’t need to if we factor it in to our Paths used for the interpolators instead. The trick is that rather than remaining static during the line phase of the Paths, we actually move slowly instead:\n\n将这段代码放入模拟环境就可以得到实际的函数图形，我们会发现图形实际上已经非常相似了，但还有一点点的瑕疵。虽说我们复制路径以设置插值器的起点和终点，但我们没有考虑用于避免尾缘追上前缘的 trimPathOffset 动画。如果我们在用于插值器的 Path 中考虑这个动画，其实我们不需要将它考虑到我们的 Drawable 中。因为我们只需要将速率不变部分去掉，然后将整体速率改变，而起点终点不变就行了，代码如下：\n\n```java\nfinal class IndeterminateAnimatorFactory {\n    .\n    .\n    .\n    private static Interpolator createStartInterpolator() {\n        Path path = new Path();\n        path.cubicTo(0.3f, 0f, 0.1f, 0.75f, 0.5f, 0.85f);\n        path.lineTo(1f, 1f);\n        return PathInterpolatorCompat.create(path);\n    }\n    .\n    .\n    .\n    private static Interpolator createEndInterpolator() {\n        Path path = new Path();\n        path.lineTo(0.5f, 0.1f);\n        path.cubicTo(0.7f, 0.15f, 0.6f, 0.75f, 1f, 1f);\n        return PathInterpolatorCompat.create(path);\n    }\n    .\n    .\n    .\n}\n```\n\n代码修改前插值器的函数图形如下：\n\n![](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/trim_start_and_end_interpolators.png?resize=223%2C300&ssl=1)\n\n代码修改后插值器的函数图形如下：\n\n![](https://i1.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/trim_start_and_end_with_offset.png?resize=230%2C300&ssl=1)\n\nIf we try it with those tweaks to the Paths then we get pretty close to the Lollipop+ implementation:\n\n对 Path 微调后，将它应用到之前的代码中，运行一下程序就会看到实际效果，和 Lollipop+ 已经非常非常接近了：\n\n[视频](https://youtu.be/kYlwJF0Qgwo)\n\nIt’s pretty difficult to tell the difference although there is a very occasional glitch when the rotation animator finishes and restarts (you can see it towards the end of the video) but it is only occasional so I can live with it.\n\n两种实现的不同接近于 0，但我们的版本有时候还是会发生小问题的，就是在旋转动画结束并重新启动动画的时候（在视频结束的时候你可以看到），但这种小问题我觉得可以忍受吧……\n\nJust to prove that it truly is backwardly compatible here’s the same thing running on a Genymotion 4.1.1 emulator. Note how the native ProgressBar is rendered as a Holo-styled one:\n\n为了证明我们的版本能支持低版本设备，我在一台 4.1.1 的虚拟机上运行了这个 Demo，原生版本的实现此时以 Holo-styled 绘制：\n\n[视频](https://youtu.be/pgexYY10agY)\n\nThat concludes our look at creating our own indeterminate progress bar and we’ve met a new friend along the way: PathInterpolator!\n\n这样就可以证明了吧！\n\nThe source code for this article is available [here](https://github.com/StylingAndroid/Indeterminate/tree/Part5).\n\n[源码可以在这里下载。](https://github.com/StylingAndroid/Indeterminate/tree/Part5)"
  },
  {
    "path": "issue-46/尝试结合Mosby和Flow代替Fragment.md",
    "content": "尝试结合 Mosby 和 Flow 代替 Fragment\n---\n\n> * 原文链接 : [LET MOSBY FLOW - AN ALTERNATIVE TO FRAGMENTS](http://hannesdorfmann.com/android/let-mosby-flow)\n* 原文作者 : [Hannes Dorfmann](http://hannesdorfmann.com)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :  完成 \n\n\n\n在 Android App 开发中，对 Fragment 的使用一直很有争议，有些人觉得它很好用，有的人则不然。这篇博文我会介绍怎么使用 Mosby 3.0 创建 MVP 模式的基本视图，以及怎么使用 Square 的 Flow 库替代 Fragment。\n\n前言：我在开发 App 时也经常用到 Fragment，99% 的情况下它都不会出现什么问题。当然，我也理解那些尽可能不使用 Fragment 的开发者。毕竟生活不易，每一个程序猿都希望自己能尽可能开发出自己能开发出最完美的应用，即便 Fragment 只有 1% 的可能会出现问题，但 Fragment 一旦出现问题，就会让人头疼无比。\n\n这篇博文我会教大家在不使用 Fragment 而是使用 Mosby 和 Flow 组合开发一个应用，应用只有两个部分：一个国家列表以及对应国家的详细信息。Demo 如下：\n\n[video](https://youtu.be/lNG1odHSNXg)\n\n##Flow for navigation\n\nApp 只有一个 Activity，但 Activity 里有两个视图：\n\n- CountriesListLayout: 显示国家列表，用户点击列表某一项后会进入显示国家详细信息的视图。\n-  CountryDetailsLayout: 显示国家对应的详细信息，例如：人口，货币比，以及一些照片。\n\n##Dispatcher and Keys\n\n将 Flow 整合到 Activity 中的办法：\n\n```java\nclass MainActivity : AppCompatActivity() {\n\n  override fun attachBaseContext(baseContext: Context) {\n    val flowContextWrapper = Flow.configure(baseContext, this)\n        .dispatcher(AtlasAppDispatcher(this))\n        .defaultKey(CountriesScreen())\n        .keyParceler(AtlasAppKeyParceler())\n        .install()\n    super.attachBaseContext(flowContextWrapper)\n  }\n\n  override fun onBackPressed() {\n    if (!Flow.get(this).goBack()) {\n      super.onBackPressed();\n    }\n  }\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n      super.onCreate(savedInstanceState)\n      setContentView(R.layout.activity_main)\n  }\n}\n```\n\n重载 onBackPressed() 方法可以让 Android 虚拟键盘的后退键触发后退操作。\n\n为了完整性，R.layout.activity_main 就是一个 FrameLayout 的容器。\n\n```java\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n```\n\n接下来我们会关注如何配置 Flow，为了在 Activity 中安装 Flow，我们必须重载 attachBaseContext() 方法。为什么呢？因为 Flow 会创建 ContextWrapper，而我们必须使用由 Flow 通过调用 super.attachBaseContext(flowContextWrapper) 返回的特定的 ContextWrapper。\n\nFlow 是高度可定制的库，但也意味着你必须写很多模板代码。为了让 Flow 完成视图导航的功能，我们需要定义一个 Dispatcher 类，它将负责调度 Flow 导航栈中的导航任务。\n\n```java\nclass AtlasAppDispatcher(private val activity: Activity) : Dispatcher {\n\n  override fun dispatch(traversal: Traversal, callback: TraversalCallback) {\n    val destination = traversal.destination.top<Any>() // destination key\n\n    val layoutRes = when (destination) {\n      is CountriesScreen -> R.layout.screen_countries\n      is CountryDetailsScreen -> R.layout.screen_countrydetails\n      else -> throw IllegalStateException(\"Unknown screen $destination\")\n    }\n\n    val newView = LayoutInflater.from(traversal.createContext(destination, activity))\n        .inflate(layoutRes, container, false)\n\n    // Update container: remove oldView, insert newView\n    val container = activity.findViewById(R.id.container) as ViewGroup\n\n    // Remove current screen from container\n    if (traversal.origin != null && container.childCount > 0) {\n      val currentScreen = container.getChildAt(0);\n      // Save the state manually\n      traversal.getState((traversal.origin as History).top()).save(currentScreen)\n      container.removeAllViews() // remove oldView\n    }\n\n    // Restore state before adding view (i.e. caused by onBackPressed)\n    traversal.getState(traversal.destination.top()).restore(newView)\n\n    // add new screen\n    container.addView(newView)\n\n    callback.onTraversalCompleted() // Tell Flow that we are done\n  }\n}\n```\n\n现在来聊一聊上面的代码的关键点吧：首先你得实现 Flow 中 Dispatcher 接口中定义的方法 dispatch()，我们通过 Flow 切换视图时就会调用该方法，因此我们必须准确地指定视图切换时 View 将发生的改变。在本例中，Atlas 应用有一个 FrameLayout 容器，只要我们从一个视图切换到另一个视图时（或返回上一个视图），我们只是将容器中显示的当前视图移除，并添加新视图。Flow 提供 Traversal 对象，该对象包含所有应用到视图导航栈中的信息，Flow 提供 traversal.destination.top() 方法通过键得到对应的视图，所以你需要为此作一个键值匹配：\n\n```java\nval layoutRes = when (destination) {\n  is CountriesScreen -> R.layout.screen_countries\n  is CountryDetailsScreen -> R.layout.screen_countrydetails\n  else -> throw IllegalStateException(\"Unknown screen $destination\")\n}\n```\n\n你可能会问，什么是键？基本上，任何 Java 对象都可以用作 Flow 中视图对应的键，而且用 Screen 来命名键似乎效果不错。在 Atlas App 中我们有两个视图，因此我们需要两个键：CountriesScreen 和 CountryDetailsScreen。键对象有两个职责：\n\n1. 与目标视图作键值对匹配\n2. 键包含所有视图需要的数据，可以把它理解为 Fragment 对象。例如，CountryDetailsScreen 包含用于加载数据的国家 id\n\n```java\nclass CountryDetailsScreen(val countryId: Int) : Parcelable {\n\n  private constructor(p: Parcel) : this(p.readInt())\n\n  override fun writeToParcel(parcel: Parcel, p1: Int) {\n    parcel.writeInt(countryId)\n  }\n\n  override fun describeContents(): Int = 0\n\n  companion object {\n    val CREATOR = object : Parcelable.Creator<CountryDetailsScreen> {\n      override fun createFromParcel(p: Parcel): CountryDetailsScreen = CountryDetailsScreen(p)\n\n      override fun newArray(size: Int): Array<out CountryDetailsScreen>? = Array(size,\n          { CountryDetailsScreen(-1) })\n    }\n  }\n}\n```\n\n我猜你接下来会问：为什么我们要实现序列化接口？因为很多东西都能作为键去匹配视图，然而（在进程被杀时，例如：Activity 在后台被销毁） Flow 必须在 Bundle 中持久地存储键（以序列化的形式），才能在 Activity 被恢复时显示之前的数据。因此，我们必须为 Flow 提供 KeyParceler，用于将键序列化。最简单的办法就是把键变成序列化：\n\n```java\nclass AtlasAppKeyParceler : KeyParceler {\n  override fun toParcelable(key: Any?): Parcelable = key as Parcelable\n  override fun toKey(parcelable: Parcelable) : Any = parcelable\n}\n```\n\n值得一提的是：Kotlin 中 Any 类型和 java.lang.Object 是一样的。\n\n```java\nclass MainActivity : AppCompatActivity() {\n\n  override fun attachBaseContext(baseContext: Context) {\n    val newBase = Flow.configure(baseContext, this)\n        .dispatcher(AtlasAppDispatcher(this))\n        .defaultKey(CountriesScreen())\n        .keyParceler(AtlasAppKeyParceler())\n        .install()\n    super.attachBaseContext(newBase)\n  }\n  ...\n}\n```\n\n通过 .defaultKey(CountriesScreen()) 能够指定启动键/视图。\n\n```java\nclass CountriesScreen : Parcelable // Doesn't have any data, it's just an empty object\n```\n\n##MVP with Mosby\n\n所以现在我们讲解了如何用 Flow 实现视图导航栈，接下来就需要讲解如何不通过 Fragment 显示我们的 UI 了。下面我们会用一个简单的自定义 View，它继承于 ViewGroup（例如：FrameLayout），以及一些解耦的概念，这部分也是 Mosby 发挥作用的地方。\n\nMosby 是 Android 的一个 MVP 架构库，在 Atlas App 中我们将使用 Mosby 3.0，虽说在写这篇博文时 3.0 版本还没有发布，但 3.0.0 的快照已经能看到了，而 API 一般都是稳定的，所以是可以使用的。\n\n###视图方向改变\nMosby 的一大特性是：Presenter 能够在设备重力方向发生改变后不被销毁，此外，Mosby 还提供连接 Presenter 和 View 的对象 - ViewState。一般来说，在 MVP 架构中，与 View 交互的都是 Presenter，因此在加载数据时，需要显示进度条的行为由 Presenter 告知 View，当数据加载完成，又通知 View 调用 RecyclerView view.showContent() 显示数据。Mosby 的 ViewState 有点像 View 和 Presenter 间某种形式的钩子，追踪 Presenter 被 View 调用的所有方法。视图发生重力方向改变后，我们能利用 ViewState 作出相应的改变，并调用 View 的方法返回到改变前的状态。\n\n如果你之前用过 Mosby 2.0，那下面没啥新鲜的东西。Mosby 的特性 Activity 和 Fragment 一直都可以用，但 ViewGroup 的子类只在 Mosby 3.0 以上的版本能够使用这些特性。\n\n不妨看看具体的代码：\n\n```java\nclass CountriesListLayout(c: Context, atts: AttributeSet) : CountriesView, MvpViewStateFrameLayout<CountriesView, CountriesPresenter>(\n    c, atts) {\n\n  private val recyclerView: RecyclerView by bindView(R.id.recyclerView)\n  private val swipeRefreshLayout: SwipeRefreshLayout by bindView(R.id.swipeRefreshLayout)\n  private val errorView: View by bindView(R.id.errorView)\n  private val loadingView: View by bindView(R.id.loadingView)\n\n  private val adapter = CountriesAdapter(\n      { // OnClickListener, navigates to details screen\n        country ->\n        Flow.get(this).set(CountryDetailsScreen(country.id))\n      })\n\n  init {\n    // inflates the layout containing a SwipeRefreshLayout, RecyclerView, ProgressBar etc.\n    LayoutInflater.from(context).inflate(R.layout.recycler_swiperefresh_view, this, true)\n\n    recyclerView.adapter = adapter\n    recyclerView.layoutManager = LinearLayoutManager(context)\n\n    errorView.setOnClickListener {\n      loadData(false)\n    }\n\n    swipeRefreshLayout.setOnRefreshListener {\n      loadData(true)\n    }\n  }\n\n  fun loadData(pullToRefresh: Boolean) = presenter.loadCountries(pullToRefresh)\n\n  override fun createPresenter(): CountriesPresenter =\n    AtlasApplication.getComponent(context).countriesPresenter() // We use dagger 2\n\n  override fun createViewState(): ViewState<CountriesView> = RetainingLceViewState<List<Country>, CountriesView>()\n\n  override fun showLoading(pullToRefresh: Boolean) {\n      loadingView.visibility = VISIBLE\n      errorView.visibility = GONE\n      swipeRefreshLayout.visibility = GONE\n  }\n\n  override fun showContent() {\n    loadingView.visibility = GONE\n    errorView.visibility = GONE\n    swipeRefreshLayout.visibility = VISIBILE\n    swipeRefreshLayout.isRefreshing = false\n  }\n\n  override fun showError(e: Throwable?, pullToRefresh: Boolean) {\n    swipeRefreshLayout.visibility = GONE\n    loadingView.visibility = GONE\n    errorView.visibility = VISIBLE\n    swipeRefreshLayout.isRefreshing = false\n  }\n\n  override fun setData(data: List<Country>) {\n    adapter.items = data\n    adapter.notifyDataSetChanged()\n  }\n}\n```\n\n更多细节可以在 Mosby 的文档中了解，就像你注意到的，我们创建了 MvpViewStateFrameLayout 的子类，在自定义 ViewGroup 中实现 ViewGroupViewStateDelegateCallback 的接口方法。听起来有点麻烦，其实很简单的，你可以看看 MvpViewStateFrameLayouts 的源码：\n\n```java\npublic abstract class MvpViewStateFrameLayout<V, P>\n    extends FrameLayout implements ViewGroupViewStateDelegateCallback<V, P> {\n\n  private ViewGroupMvpDelegate mvpDelegate = new ViewGroupMvpViewStateDelegateImpl<V, P>(this);\n\n  @Override protected void onAttachedToWindow() {\n      super.onAttachedToWindow();\n      mvpDelegate.onAttachedToWindow();\n  }\n\n  @Override protected void onDetachedFromWindow() {\n      super.onDetachedFromWindow();\n      mvpDelegate.onDetachedFromWindow();\n  }\n\n  // Implement in subclass\n  abstract ViewState<V> createViewState();\n  abstract P createPresenter();\n\n}\n```\nCountriesPresenter 将从 Atlas 中加载数据列表：\n\n```java\nclass CountriesPresenter @Inject constructor(val atlas: Atlas) : MvpBasePresenter<CountriesView>() {\n\n  var subscription: Subscription? = null\n\n  fun loadCountries(pullToRefresh: Boolean) {\n    view?.showLoading(pullToRefresh)\n    subscription = atlas.getCountries()\n        .subscribeOn(Schedulers.io())\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(\n            { view?.setData(it) },\n            { view?.showError(it, pullToRefresh) },\n            { view?.showContent() }\n        )\n  }\n\n  override fun detachView(retainInstance: Boolean) {\n    super.detachView(retainInstance)\n    if (!retainInstance) {\n      subscription?.unsubscribe()\n    }\n  }\n}\n```\n\n当重力方向改变，View 就会与 Presenter 绑定/解绑。Mosby 能够准确判断用户是否切换到别的视图，使得 Presenter 会永久被销毁。\n\n##Summary\n\n这篇博文的目的是证明我们能够组合使用 Mosby 和 Flow，不使用 Fragment 开发一个 App，此外，它们还能处理进程被杀死（Activity 在后台被销毁），然而，Mosby 需要让 ViewState 序列化（意味着已加载的数据，例如国家列表，必须实现 Parcelable 接口）。而大部分程序员可能懒的去弄这个，所以……\n\n使用 Flow 可能要多写很多代码（特别是 Dispatcher），但 Flow 真的很好用，Flow 还有很多很赞的特性我没有在这里展开。你需要知道的是，Flow 不适合给 Android 开发初学者使用，但 Flow 真的很棒！\n\nPancakes 是功能和 Flow 类似，但更轻量的库，如果你要使用它的话，代码会变成这样：\n\n```java\nclass CountriesListLayoutFactory implements ViewFactory {\n    @Override\n    public View createView(Context context, ViewGroup container) {\n        return LayoutInflater.from(context).inflate(R.layout.screen_countries, container, false);\n    }\n}\n```\n"
  },
  {
    "path": "issue-47/Building a Kotlin project.md",
    "content": "Building a Kotlin project\n---\n\n> * 原文链接 : [Building a Kotlin project](http://www.cirorizzo.net/2016/03/04/building-a-kotlin-project/)\n* 原文作者 : [Ciro Rizzo](http://www.cirorizzo.net/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [rogero0o](https://github.com/Rogero0o)\n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  未完成 / 校对中 / 完成\n\n**注意 : 翻译完之后请认真的审核一遍有没有错字、语句通不通顺，谢谢~**\n\n## Part 1\n\n学一门新语言最有效的方法就是写一个实际的例子.\n\n所以这个系列的博客将专注于使用 Kotlin 写一个小例子.\n\n## Scenario （使用场景）\n\n为了覆盖各种情景,这个DEMO必须要有以下要求：\n\n- 网络访问\n- 通过 REST API 请求数据\n- 反序列化数据\n- 在列表中显示图片\n\n为了符合这些要求为什么不做一个显示小猫的app呢？\n\n使用 http://thecatapi.com/ 的 API 我们可以检索到一些可爱的小猫的图片\n\n小猫app\n\n![](http://www.cirorizzo.net/content/images/2016/03/xkittenApp.png.pagespeed.ic.ulo4yWl6Cg.png)\n\n## Dependencies （依赖库）\n\n\n这可是个使用一些很腻害的依赖库的好机会，比如说：\n\n- Retrofit2 用来请求网络，访问REST API以及数据的反序列化\n- Glide 用来显示图片\n- RxJava 来绑定数据\n- RecyclerView CardView 支持界面显示\n- 整体框架将使用MVP\n\n## Set Up the Project （建立工程）\n\n\n\n使用 Android Studio 来创建新工程将会非常简单\n\n##### Start a new Android Project （创建一个新 Android 工程）\n\n![](http://www.cirorizzo.net/content/images/2016/03/xAndroidStudio_NewProject.png.pagespeed.ic.7fDR0qSTJd.png)\n\n##### Create a new project （ 创建一个项目）\n\n![](http://www.cirorizzo.net/content/images/2016/03/xAndroidStudio_NewProject_Create_NEW-1.png.pagespeed.ic.rtJ-FIVYiG.png)\n\n##### Select Target Android Device （选择需要的android版本）\n\n![](http://www.cirorizzo.net/content/images/2016/03/xAndroidStudio_NewProject_Target.png.pagespeed.ic.bXlb6fWH62.png)\n\n##### Add an activity （添加 activity）\n\n![](http://www.cirorizzo.net/content/images/2016/03/xAndroidStudio_NewProject_Empty.png.pagespeed.ic.VYxIdhZ3Xk.png)\n\n##### Customize the Activity （选择样式）\n\n![](http://www.cirorizzo.net/content/images/2016/03/xAndroidStudio_NewProject_Activity.png.pagespeed.ic.3g2X5Gs9Bn.png)\n\n\n点击完成，刚刚配置的模板工程将被创建。\n\n![](http://www.cirorizzo.net/content/images/2016/03/xAndroidStudio_Basic_Template.png.pagespeed.ic.3iX8nv51PP.png)\n\n我们的 Kitten APP 就建好了！\n\n然而这时候代码还是 java ， 接下来我们将它处理成 Kotlin.\n\n## Defining Gradle Build Tool\n\n下一步我们将升级 Build Tool 并且 将那些库我们将会用到库引用进来.\n\n开始这步之前，请查看 Android Kotlin 需要的环境支持 [post](http://www.cirorizzo.net/kotlin-code/)\n\n打开该项目 App中的 build.gradle （图片中指出的地方）\n\n![](http://www.cirorizzo.net/content/images/2016/03/xAndroidStudio_Basic_Gradle_High.png.pagespeed.ic.0SHrJn4YZc.png)\n\n\n将所有 引用库 和 andorid properties 的版本通过一个另外的 scripts 来管理是一个很好的习惯，可以使用Gradle提供的 ext 属性来使用和访问他们。\n\n\n最简单的方法是在 build.gradle 文件的开头加上下面的片段\n\n\tbuildscript {\n\t  ext.compileSdkVersion_ver = 23\n\t  ext.buildToolsVersion_ver = '23.0.2'\n\n\t  ext.minSdkVersion_ver = 21\n\t  ext.targetSdkVersion_ver = 23\n\t  ext.versionCode_ver = 1\n\t  ext.versionName_ver = '1.0'\n\n\t  ext.support_ver = '23.1.1'\n\n\t  ext.kotlin_ver = '1.0.0'\n\t  ext.anko_ver = '0.8.2'\n\n\t  ext.glide_ver = '3.7.0'\n\t  ext.retrofit_ver = '2.0.0-beta4'\n\t  ext.rxjava_ver = '1.1.1'\n\t  ext.rxandroid_ver = '1.1.0'\n\n\t  ext.junit_ver = '4.12'\n\n\t  repositories {\n\t      mavenCentral()\n\t  }\n\n\t  dependencies {\n\t      classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_ver\"\n\t  }\n\t}\n\n\n然后添加 Kotlin 插件 ， 如下所示\n\n\tapply plugin: 'com.android.application'\n\tapply plugin: 'kotlin-android'\n\tapply plugin: 'kotlin-android-extensions'\n\n在添加我们将使用到的项目引用库之前，将之前添加在头部的ext属性对应的版本设置正确\n\n\tandroid {\n\t  compileSdkVersion \"$compileSdkVersion_ver\".toInteger()\n\t  buildToolsVersion \"$buildToolsVersion_ver\"\n\n\t  defaultConfig {\n\t    applicationId \"com.github.cirorizzo.kshows\"\n\t    minSdkVersion \"$minSdkVersion_ver\".toInteger()\n\t    targetSdkVersion \"$targetSdkVersion_ver\".toInteger()\n\t    versionCode \"$versionCode_ver\".toInteger()\n\t    versionName \"$versionName_ver\"\n\t}\n\t...\n\n\n再改变一个 builTypes 选项\n\n\tbuildTypes {\n\t    debug {\n\t        buildConfigField(\"int\", \"MAX_IMAGES_PER_REQUEST\", \"10\")\n\t        debuggable true\n\t        minifyEnabled false\n\t        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t    }\n\n\t    release {\n\t        buildConfigField(\"int\", \"MAX_IMAGES_PER_REQUEST\", \"500\")\n\t        debuggable false\n\t        minifyEnabled true\n\t        shrinkResources true\n\t        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t    }\n\t}\n\tsourceSets {\n\t    main.java.srcDirs += 'src/main/kotlin'\n\t}\n\n下一步将是申明引用库在项目中的使用\n\n\tdependencies {\n\t  compile fileTree(dir: 'libs', include: ['*.jar'])\n\t  testCompile \"junit:junit:$junit_ver\"\n\n\t  compile \"com.android.support:appcompat-v7:$support_ver\"\n\t  compile \"com.android.support:cardview-v7:$support_ver\"\n\t  compile \"com.android.support:recyclerview-v7:$support_ver\"\n\t  compile \"com.github.bumptech.glide:glide:$glide_ver\"\n\n\t  compile \"com.squareup.retrofit2:retrofit:$retrofit_ver\"\n\t  compile (\"com.squareup.retrofit2:converter-simplexml:$retrofit_ver\") {\n\t    exclude module: 'xpp3'\n\t    exclude group: 'stax'\n\t}\n\n\t  compile \"io.reactivex:rxjava:$rxjava_ver\"\n\t  compile \"io.reactivex:rxandroid:$rxandroid_ver\"\n\t  compile \"com.squareup.retrofit2:adapter-rxjava:$retrofit_ver\"\n\n\t  compile \"org.jetbrains.kotlin:kotlin-stdlib:$kotlin_ver\"\n\t  compile \"org.jetbrains.anko:anko-common:$anko_ver\"\n\t}\n\n终于项目的 build.gradle 文件配置好了\n\n还有一件事，添加访问网络的权限，将以下代码添加到AndroidManifest.xml中\n\n\t<uses-permission android:name=\"android.permission.INTERNET\" />\n\n可以进入下一步了\n\n## Designing Project Structure （设计项目的结构）\n\n另一个好习惯是 根据在项目中类的不同用途来设计包和文件夹，将相同类型的类放在一个包中，我们可以这样设计项目的结构：\n\n![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARIAAAB1CAMAAAC1b55HAAAABGdBTUEAALGPC/xhBQAAAm1QTFRFPT9BPUBBPUBCPkBBPkFDQEJDQEJEQENFQkRFQkRGQkVHQ0ZHQ0ZIREdJRUhJRkhKRklLR0pLSEpMSEtNSUxNSUxOSk1OS05PS05QTE5QTVBRTlBSTlFTTl1mT1JTT11nUFJUUVJUUVRVUVRWUlRWUlVXU1VXU1ZXVFZYVFdZVVdZVVhZV1laV1lbV1pbWFpcWVtcWVtdWlxeWl1eW11eW11fW3ODXF5gXF9gXV9gXV9hX2BiX2FiX2FjYGJjYGJkYWNkYmRlYmRmY2VmY2VnZGZnZGZoZWdoZWdpZmhpZmhqZ2lqZ2lraGpraWtsaWttamxta21ta21ua21vbG5vbW9xbnBxb3FycHFycHJzcXN0cnR1c3R2c3V2c5y3dHV2dJ23dJ24dXd4dnh5d3l6eHl6eXp7emtUenx9eqfEe3x9e31+fH5/fX5/fX+Afn+Af4GCgIGCgYODgYOEgoOEg4WGhIWGhYaHhoeIh4iJiImKiYqLiouLiouMi4yNjI2NjI2OjY2OjY6Pjo+Pjo+QjpCQj5CRkJGSkZKSkZKTkpOTkpOUk5SUk5SVlJWVlJWWlZaWlpaXlpeXlpeYl5iYmJmZmJmamZqampubmpucm5ycnJ2dnJ2enZ2enZ6enp6fnp+gn6CgoKChoKGhoKGioaKioqKjoqOjo6Ojo6SkpKSkpKSlpKWlpaWlpaampqanpqenp6enp6ioqKipqampqaqqqqqrq6urrKysrKytra2tra6urq6vr6+vr7CwsLCxsbGxsrKzs7OztLS0tLS1tbW1tra2tra3t7e3uJZouLi4uLi5urq6urq7u7u7y01+uQAAB/NJREFUeNrtnftDU2Ucxr8sLnIZzKGZYWqGXUArb4kXRFspRuIyFcEsnFZeUiyVMs2sjCysKEDxklg6l5cojwR2ajEnYiHtb+q9nss2tiOM6c7e54dzOOf77gt7xjm8+/CcHQAhISGh+Gkr0XPCiGBLhCehljDhPV9sSaKn/4tMdSmiJdmjksiSMmbJAp0lwV/X1wBsueA9YIFPXwawXUijGybVSeJIG0S0BB84lQ/bfM/A+mMA6w+zDZPqUWLJxOiWIJ2ogfw7o8FdyjbMqs+RI59AdEsqGpp8yIW2msl/WfiGSZV1Xe7Oim7JU/8UwHHkwpozdbuVDbOqTt4B0S1Z7M0o7HwTwNrfM1XZMKtSW1MNzEvSz/rddb5igKaLoG4k71TtRCVa5ALkKWN0G8mn+p0+m5jT6zRzlVWYICQkJCQUJ/1L9LYwItiSoXmyacrgW8PuN/x2w7SEyeCDJId2pd8ZcU/4fWFrkQbGTuHg45AsyU2D05XRLUHDwMi+e2hJOPiotYF+Pb7J37MOxrf6pQqAlhcafbvsbd4NdMTE9huHjlZAS8mOfu+1VGlFQ089wNkigLZZIK1u9h9hNIL2aCmBloq2BqUX/rqlxHYNy8pWvMhcWHQ8E5b85N+PNmhzWv52PmR0OCDlXCYtxk5h4COy4ZHa2kdUS1KunCrMHWu5eMC2/HYxSF3lswJnS5f708mItv3WuTeq0M+ec7naDpJcPa1/CvhmAnQtAqlj6cLuKjKM9sBPUepaZld7LUOPcaTY7fZjJyxsxYvUkgneIrD8Pi/tIWDNWfn9PVB6Zx/M+IEVY6cw8BHZULt1a61qSVFgHloWB9A3PlUP0hqAW89DVmAcHpDdPx7g9Fr8XD34wHEBXHYqlqDX+t0vSSPag1hSrelVzY8Hl0zebuIVL5LxK6+uQqvOvfj/BrQ5KzuuwN76q7DLxYoxVCh8pJZsVC1Z+l8GXt7E75SPkmfQOx1GUUsW4r0aS1DRrVqCtl7tII1oD2KJQ9+LLObefhqXyYoXyXh3N/5lLJb+LOXNWTm73/arvXd0x0RWjKFC4SOyYdLG1yeplkwLTEfLZwPodWzeF2SJfcAOKZepJU7VkjmQ/je1ZNeHpBHtwS3R9sKLsT3k8KIrXiTjV3g+I+xrc18ma87L7Zt/hO83SbwYS4XAx5DTa6p0xJaany67LJNvLtZa8kY5wM9fOxq8xJLGOsWScxtyDt5Bluyx5F14CfA42oNbou2FFmnnPsLfjK14EXeXHPnydkgrhDF9Vtacl3f4t4HLv5sXRxQ+hv4Rfvxan28bzPiju+8d0FrScRCgsPnki54VeG9Zj4dbsrLXV4UPnIOS/xv0AuJxtAezRNsLLRYGfF6vt4CteBE/Cg2Y2ltplbs6XYrfrDw7UIROO7OBFeM+VbOTPy/5qWEfYBt4kqzTVR45isw1cixpueow2oNrkF6DFPMsEcp599k/Iw/s3ykdEe+CtJpQXl0iXBASEhISEvDxnsPHmun3ARYcAbU/NmT4iN4Tx5eBxUmyfNg6MpZgFpmglsjdb0V5d6zljQwMFrTfOORBlhScudmYpVhCASONRRIWGWsuGC9LZPm3BRHho4Y3cjB47IPsMn8VQKsrt3U1t4QBRhqLxCwy5lwwfpbI8muR4KOGNzLyl9E3jhw4ObeemLD9Y24JA4xAY5EYvMWcC8bNks5XIMSSjVpLFGzCyN88Pz2XLOo/f/58A7eEAUYWi8SWxJwLxsmS6+89EHwu0cNHjSWM/E0ZyAY4sxamDYzRnF4pYOSxSMwiY88F42LJdw9CtNOragknf15X+tq+Kki9Wp8JVjJAAYw8FtlYNxJcMA66NMfAvETDGxn5W9ffV49+S6Dwir+zmQxQACOPRZb1eOLBBeM5ex1sXkLJX2YO27TpDgwCGFksErPIPNNepCAkJCQkJCQUv3mJAI2hU7W798QkuHEYoDFEJsGNQwCNgxJE01hiBDQuIfFCShUxQWxSIobauCKLJJrAEgOgkWYXKVXEBFGNGOrjijiSaA5LooJGEi/kVNFTCUrEUB9XpJFEU1hiBDS6nZwqIkuUiKE+rkgjiSawxBhodDs5VcQEkUcM9XFFFklMeEsMgka3k1NFnGbkEcOguCKOJCa8DINGt5NTRZxm5BHDoLgijiSaePYabl5CqWK67urqiHFFISEhISEhIaHhz0sEaAydqiWtJ7EEjSbRMBKNoTpdaQ5LjIDGcnpZNMGJLMRIw4psg5JIEmLke/DQhLXEAGhkl0UTnEhxIwsr8kQjIZHkgmq+Bw9NYEuigkZ2WTTGiRw3krCikmikV1F7KtU91Yl84BgDjfiyaLzmuJGEFbWJRjexRJ9xTFRLjIFGfFk0XnPcSMKK2kQjtsQZlHFMUEuMgEZ2WTR+ngw30rCiJtGILWms02YcE1XGQONX9LJo8jwpbmRhRTXRiC3BCFKzx3SzVx171V4WzXAjCyvqEo0EQdoywfwy10seE83PFx4ICQkJCY3UvESAxtCpmgCNkWavyTVhuwvQGOnzFc1lSXTQyO4U01LCuKL6kYlmtSQqaGR3ikEHDqWISp4RzGtJFNDI7hQjORhFVD8y0byWRAON9E4xkoNRRPUjE81qSXTQSO4UgyzhFFH9yERzWmIANJI7xSBLOEXkeUZzyhBoJHeKwadXRhGVPGOSzV7D/wM0KSiikJCQkJCQQf0P6Bh9nhVvE5IAAAAASUVORK5CYII=)\n\n右键点击 com.github.cirorizzo.kshows 包，然后选择 New ->Package\n\n## Coding（写代码！）\n\n下一篇将介绍如何编写 Kitten app\n\n## Part 2\n\n上一篇我们介绍了如何创建一个项目，并且对 Kitten APP 需要的 build.gradle 文件进行设置\n\n下一步我们将开始对app进行编写\n\n## Data Model （数据模型）\n\n项目中的一个重要功能就是通过网络请求网站 http://thecatapi.com 中的数据\n\n完整的域名将是 http://thecatapi.com/api/images/get?format=xml&results_per_page=10\n\nAPI 返回一个 xml 文件\n\n![](http://www.cirorizzo.net/content/images/2016/03/xxmlAPI.png.pagespeed.ic.CABTBWB1Ch.png)\n\n必须对数据进行解析才能拿到我们需要的Kitten image的url\n\nKotlin 有一个非常适合的 class 叫做 data class 完美适合这样的需求\n\n让我们再包名.cats 中创建一个新的class，右键包名然后选择 New->Kotlin File/Class ，命名为cats然后选择为 class\n\n为了构建解析xml的class，Cats.kt 是这样的\n\n\tdata class Cats(var data: Data? = null)\n\n\tdata class Data(var images: ArrayList<Image>? = null)\n\n\tdata class Image(var url: String? = \"\", var id: String? = \"\", var source_url: String? = \"\")\n\n看到这是不是觉得特别简洁？\n\n如果用java代码将会长很多\n\nKotlin的data class 有很多特点，比如说 对 getter(), setter() 和 toString() 方法的自动生成，对于 equals() hashCode() 和 copy()也是一样的，所以对于解析数据这真是完美啊\n\n## API Call\n\n访问网络有许多种方法，也有很多支持库，其中有一个来自Square的Retrofit2\n\n这是一个非常强大的 HTTPClient 而且非常容易使用\n\n我们从接口开始，在 network package中创建它\n\n命名为CatAPI\n\n\tinterface CatAPI {\n\t    @GET(\"/api/images/get?format=xml&results_per_page=\" + BuildConfig.MAX_IMAGES_PER_REQUEST)\n\t    fun getCatImageURLs(): Observable<Cats>\n\t}\n\n这个接口将会处理对接口 /api/images/get?format=xml&results_per_page=. 的请求\n\n在这里 results_per_page 参数是从build.gradle中读取的，其中一个参数叫做 MAX_IMAGES_PER_REQUEST ，根据在buildTypes中设置不同值来定义它\n\n\tbuildTypes {\n\t    debug {\n\t        buildConfigField(\"int\", \"MAX_IMAGES_PER_REQUEST\", \"10\")\n\t        ...\n\n用这个方法来定义值是非常方便的，在我们编译 debug版本和release版本时候非常方便，特别是在你需要区分这两者的值的时候\n\nCatAPI 这个接口非常有趣，这个方法调用请求，并返回回调 ，从 fun getCatImageURLs(): Observable<Cats> 中\n\n所以下一步是将它实现\n让我们在同一个包（network）中创建一个新的class，命名为CatAPINetwork\n\n\tclass CatAPINetwork {\n\t    fun getExec(): Observable<Cats> {\n\t        val retrofit = Retrofit.Builder()\n\t            .baseUrl(\"http://thecatapi.com\")\n\t            .addConverterFactory(SimpleXmlConverterFactory.create())\n\t            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())\n\t            .build()\n\n\t        val catAPI: CatAPI = retrofit.create(CatAPI::class.java)\n\n\t        return catAPI.getCatImageURLs().\n\t            subscribeOn(Schedulers.io()).\n\t            observeOn(AndroidSchedulers.mainThread())\n\t    }\n\t}\n\nfun getExec(): Observable<Cats> 这个方法被设置成 public 的意味着它可以被外者调用\n\n.addConverterFactory(SimpleXmlConverterFactory.create())这一行说明了使用XML转换器来解析从API获得的数据\n\n然后 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 在AIP回调中调用了方法使 adapter 被使用\n\nreturn 的这一行请参照  RxJava Observable\n\n\treturn catAPI.getCatImageURLs().\n\t            subscribeOn(Schedulers.io()).\n\t            observeOn(AndroidSchedulers.mainThread())\n\n## Presenter（提供者）\n\n这个 Presenter 负责的是APP中的逻辑 还有将数据从model层绑定到试图层的业务逻辑\n\n在我们的使用中它将实现一些 被试图层调用返回数据的方法，并且将这些数据提供给adapter以供呈现\n\n为了和试图层的通信，我们将在presenter包中新建一个叫做MasterPresenter的接口\n\n\tinterface MasterPresenter {\n\t    fun connect(imagesAdapter: ImagesAdapter)\n\t    fun getMasterRequest()\n\t}\n\n第一个方法 fun connect(imagesAdapter: ImagesAdapter) 将被用于连接adapter的接口来显示数据，然后 fun getMasterRequest() 将被用于开始API请求\n\n我们在同一个包中新建一个实现类，并命名为 MasterPresenterImpl\n\n\tclass MasterPresenterImpl : MasterPresenter {\n\t    lateinit private var imagesAdapter: ImagesAdapter\n\n\t    override fun connect(imagesAdapter: ImagesAdapter) {\n\t        this.imagesAdapter = imagesAdapter\n\t    }\n\n\t    override fun getMasterRequest() {\n\t        imagesAdapter.setObservable(getObservableMasterRequest(CatAPINetwork()))\n\t    }\n\n\t    private fun getObservableMasterRequest(catAPINetwork: CatAPINetwork): Observable<Cats> {\n\t        return catAPINetwork.getExec()\n\t    }\n\t}\n\nlateinit private var imagesAdapter: ImagesAdapter ， 这一行代码十分有趣，Kotlin给我们提供了声明一个非空变量而不需要设定初始值的功能，使用 lateinit 即可，变量将在他被使用的时候设定初始值，在我们的例子中它调用了 fun connect(imagesAdapter: ImagesAdapter).\n\nfun getMasterRequest() 这个方法发起了网络请求，在启动了 catAPINetwork.getExec() 请求网络数据后 ，  设置Observable绑定到adapter中\n\n## View section\n\n在view包中的class主要负责对UI的管理\n\n#### Layouts\n\n在开始实现之前，让我们看看设计图先\n\n![](http://www.cirorizzo.net/content/images/2016/03/xkittenApp-1.png.pagespeed.ic.ulo4yWl6Cg.png)\n\n实现这个视图我们基本上需要两个视图容器和一个子布局容器\n\n最底层的视图应该是包含整个list的视图，我们将视图描述在 activity_main.xml 中并房子啊 res->layout文件夹中，这个文件在创建工程时是自动生成的\n\n在我们app中我们需要使用的时候 RecyclerView这个组件（一个十分强大，完美的组件）\n\nactivity_main.xml 将会长成这样\n\n\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t    xmlns:tools=\"http://schemas.android.com/tools\"\n\t    android:layout_width=\"wrap_content\"\n\t    android:layout_height=\"wrap_content\"\n\t    tools:context=\".view.MainActivity\"\n\t    android:gravity=\"center\">\n\n\t    <android.support.v7.widget.RecyclerView\n\t        android:id=\"@+id/containerRecyclerView\"\n\t        android:layout_width=\"wrap_content\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:scrollbars=\"vertical\"\n\t        android:layout_centerInParent=\"true\" />\n\t</RelativeLayout>\n\nRecylerView 的父视图组件就是这个list和item的主要视图\n\nrow_card_view.xml 则是item的布局，它大概长这样：\n\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<android.support.v7.widget.CardView\n\t    xmlns:card_view=\"http://schemas.android.com/apk/res-auto\"\n\t    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t    android:id=\"@+id/card_view\"\n\t    android:layout_gravity=\"center\"\n\t    android:layout_width=\"wrap_content\"\n\t    android:layout_height=\"wrap_content\"\n\t    card_view:cardCornerRadius=\"4dp\"\n\t    android:layout_margin=\"16dp\"\n\t    android:background=\"@android:color/transparent\"\n\t    android:layout_centerInParent=\"true\"\n\t    android:elevation=\"4dp\">\n\n\t    <RelativeLayout\n\t        android:layout_width=\"wrap_content\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:layout_centerInParent=\"true\"\n\t        android:gravity=\"center\"\n\t        android:foregroundGravity=\"center\">\n\n\t        <ImageView\n\t            android:layout_width=\"wrap_content\"\n\t            android:layout_height=\"wrap_content\"\n\t            android:id=\"@+id/imgVw_cat\"\n\t            android:padding=\"4dp\"\n\t            android:layout_centerInParent=\"true\"\n\t            android:scaleType=\"fitCenter\"\n\t            android:contentDescription=\"@string/cat_image\" />\n\t    </RelativeLayout>\n\t</android.support.v7.widget.CardView>\n\n如你所见item的父布局是一个card_view , 里面是一个 RelativeLayout 包含了一个 ImageView\n\n## Adapter\n\n现在我们完成了基本的layout，接下来将实现 MainActivity和adapter\n\n开始处理adapter的第一件事就是创建被MasterPresenterImpl调用的接口，在view 包中创建一个命名为ImagesAdapter的文件\n\n\tinterface ImagesAdapter {\n\t    fun setObservable(observableCats: Observable<Cats>)\n\t    fun unsubscribe()\n\t}\n\nsetObservable(observableCats: Observable<Cats>) 这个方法被MasterPresenterImpl调用来设置 Observalbe 并且让 adapter 来写入数据\n\nunsubscribe() 这个方法被 MainActivity 调用来解除 adapter 和 Observable 的绑定，在activity被销毁的时候\n\n现在让我们实现他们，在ImagesAdapterImpl 包中的一个新 class\n\n\tclass ImagesAdapterImpl : RecyclerView.Adapter<ImagesAdapterImpl.ImagesURLsDataHolder>(), ImagesAdapter {\n\t    private val TAG = ImagesAdapterImpl::class.java.simpleName\n\n\t    private var cats: Cats? = null\n\t    private val subscriber: Subscriber<Cats> by lazy { getSubscribe() }\n\n\t    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImagesURLsDataHolder {\n\t        return ImagesURLsDataHolder(\n\t                LayoutInflater.from(parent.context).inflate(R.layout.row_card_view, parent, false))\n\t    }\n\n\t    override fun getItemCount(): Int {\n\t        return cats?.data?.images?.size ?: 0\n\t    }\n\n\t    override fun onBindViewHolder(holder: ImagesURLsDataHolder, position: Int) {\n\t        holder.bindImages(cats?.data?.images?.get(position)?.url ?: \"\")\n\t    }\n\n\t    private fun setData(cats: Cats?) {\n\t        this.cats = cats\n\t    }\n\n\t    override fun setObservable(observableCats: Observable<Cats>) {\n\t        observableCats.subscribe(subscriber)\n\t    }\n\n\t    override fun unsubscribe() {\n\t        if (!subscriber.isUnsubscribed) {\n\t            subscriber.unsubscribe()\n\t        }\n\t    }\n\n\t    private fun getSubscribe(): Subscriber<Cats> {\n\t        return object : Subscriber<Cats>() {\n\t            override fun onCompleted() {\n\t                Log.d(TAG, \"onCompleted\")\n\t                notifyDataSetChanged()\n\t            }\n\n\t            override fun onNext(cats: Cats) {\n\t                Log.d(TAG, \"onNextNew\")\n\t                setData(cats)\n\t            }\n\n\t            override fun onError(e: Throwable) {\n\t                //TODO : Handle error here\n\t                Log.d(TAG, \"\" + e.message)\n\t            }\n\t        }\n\t    }\n\n\t    class ImagesURLsDataHolder(view: View) : RecyclerView.ViewHolder(view) {\n\n\t        fun bindImages(imgURL: String) {\n\t            Glide.with(itemView.context).\n\t                    load(imgURL).\n\t                    placeholder(R.mipmap.document_image_cancel).\n\t                    diskCacheStrategy(DiskCacheStrategy.ALL).\n\t                    centerCrop().\n\t                    into(itemView.imgVw_cat)\n\t        }\n\t    }\n\t}\n\n这个class为 row_card_view.xml 提供数据，你能看见在 onCreateViewHolder 方法中都是对 item 的容器的操作\n\ngetSubscribe() 这个方法提供了 Observable 写入adapter的数据， 在 private val subscriber: Subscriber<Cats> by lazy { getSubscribe() } 这一行被调用，注意一下 lazy 初始化（懒加载），,这声明了一个固定的object，它会通过括在大括号的函数来创建（即getSubscribe（））在第一次运行时调用。\n\nSubscriber 和 Observable 概念来自 RxJava,在后面的博客将深入研究\n\n最后，有一段十分有趣的代码，在ImagesURLsDataHolder这个类中，通过Glide library用填充 imgVw_cat ， 通过 API请求传回来的URL将绑定到imageView中被显示出来， bindImages(imgURL: String) 方法中包装了这部分内容， 在同一个类中的方法 onBindViewHolder 中被调用\n\n## Activity\n\n最后但同样重要的Activity\n\n\tclass MainActivity : AppCompatActivity() {\n\t    private val imagesAdapterImpl: ImagesAdapterImpl by lazy { ImagesAdapterImpl() }\n\n\t    private val masterPresenterImpl: MasterPresenterImpl\n\t            by lazy {\n\t                MasterPresenterImpl()\n\t            }\n\n\t    override fun onCreate(savedInstanceState: Bundle?) {\n\t        super.onCreate(savedInstanceState)\n\t        setContentView(R.layout.activity_main)\n\n\t        initRecyclerView()\n\t        connectingToMasterPresenter()\n\t        getURLs()\n\t    }\n\n\t    override fun onDestroy() {\n\t        imagesAdapterImpl.unsubscribe()\n\t        super.onDestroy()\n\t    }\n\n\t    private fun initRecyclerView() {\n\t        containerRecyclerView.layoutManager = GridLayoutManager(this, 1)\n\t        containerRecyclerView.adapter = imagesAdapterImpl\n\t    }\n\n\t    private fun connectingToMasterPresenter() {\n\t        masterPresenterImpl.connect(imagesAdapterImpl)\n\t    }\n\n\t    private fun getURLs() {\n\t        masterPresenterImpl.getMasterRequest()\n\t    }\n\t}\n\n注意这些方法\n\n- initRecyclerView()\n- connectingToMasterPresenter()\n- getURLs()\n\n各自用作于\n\n- 初始化主要布局\n- 建立MainActivity和MasterPresenterImpl的连接，并将它传给ImagesAdapterImpl\n- getURLs() 开始请求返回的xml数据，并运行接下来的步骤（解析数据，显示adapter中的图片）\n\nKitten app现在已经可以运行了\n\n整个项目在github上，请搜索 KShow\n\n其中也有java版本，方便进行对比\n"
  },
  {
    "path": "issue-47/Kotlin在Android上令人惊叹的技巧.md",
    "content": "# Kotlin在Android上令人惊叹的技巧\n\n> * 原文链接 : [Kotlin awesome tricks for Android](http://antonioleiva.com/kotlin-awesome-tricks-for-android/)\n* 原文作者 : [Antonio Leiva](http://antonioleiva.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [shenyansycn](https://github.com/shenyansycn) \n* 校对者: \n* 状态 :  翻译中\n\n![](http://i1.wp.com/antonioleiva.com/wp-content/uploads/2016/02/kotlin_featured.jpg?zoom=2&resize=795%2C185)\n\n我已经在这个[Blog](http://antonioleiva.com/kotlin)里讨论了很多Kotlin了，现在[Google](https://medium.com/google-developers/kotlin-android-a-brass-tacks-experiment-part-1-3e5028491bcc#.kohpnch41)也正在讨论Kotlin，[Kotlin 1.0 RC](http://blog.jetbrains.com/kotlin/2016/02/kotlin-1-0-release-candidate-is-out/) 已经发布了，毫无疑问，Kotlin不仅仅是Android的一个替代选择。Kotlin就在这里，我推荐你开始学习它。\n\n## 在Android上我从哪里开始学习Kotlin？\n\n这里已经有一些信息了，但是如果你想要真正的关注和快速学习，我推荐你这些资源：\n\n- [Kotlin reference](http://kotlinlang.org/docs/reference/)：如果你想深入了解这个语言的细节，这里是你能找到的最好的地方。我所知道关于这个语言的最好参考文献之一。\n\n- [This blog](http://antonioleiva.com/kotlin)：这个链接是我整理的所有关于Kotlin的文章的地方。你不应该错过它，文章属于初级和中级。\n\n- [**Kotlin for Android Developers, The book**](http://antonioleiva.com/kotlin-android-developers-book/)：如果你想快速和持续的学习，这本书是最好的方法。如果你已经了解了Andriod，这将是一个在你的项目中使用kotlin的快速途径。我已经编写很长一段时间了，当新版本发布的时候我就会更新它。已经更新到了Kotlin 1.0 RC。除此之外如果你[订阅了这个列表](http://antonioleiva.com/kotlin-android-developers-book/)，你将收到**免费的头5个章节和这本书末尾显示的一个购买折扣**。\n\n## 展示这个技巧\n\n在我要讲解之前，有一个要说明的是Kotlin能给你带来简化的Android代码。这里有一组没有特定顺序的独立的例子。\n\n### 简洁和趣味的编写点击监听事件\n\nJava 7上编写监听和回调事件非常繁琐的原因是缺少了lambdas。Kotlin有非常好的lambdas和**与Java库有非常好的兼容性**。用lambda一个方法即可映射接口。你可以这么做：\n\n```java\nmyButton.setOnClickListener { navigateToDetail() }\n```\n\n这就是所有。\n\n### 为什么layout要如此麻烦的inflate？再也不是了！\n\n当你在Adapter中实例化时，你需要inflate一个layout，你会这样写：\n\n```java\nLayoutInflater.from(parent.getContext()).inflate(R.id.my_layout, parent, false);\n```\n\n为什么父类不能inflate它自己的layout？好了，用Kotlin你可以。可以创建一个扩展的方法为你所用：\n\n```\nfun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {\n    return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)\n}\n```\n\n`ViewGroup`现在有了一个新的`inflate`方法，接受参数是一个layout资源和可选的`attachToRoot`。通过传入不同的值，你可以创建同一个函数的很多不同版本，但不需要重载函数。现在可以这么做：\n\n```\nparent.inflate(R.layout.my_layout)\nparent.inflate(R.layout.my_layout, true)\n```\n\n### ImageView, 加载图片!\n\n`ImageView`不能直接从网络上加载图片。我们有一些创建自定义view的库，比如：`NetworkImageView`，但这要求你使用继承，有时这会导致问题。现在我们知道我们可以在任何类上添加函数了，为什么不这样做呢？\n\n```\nfun ImageView.loadUrl(url: String) {\n    Picasso.with(context).load(url).into(this)\n}\n```\n\n现在你可以使用这个函数了，但神奇的是这个函数的本身。你现在有超级类`ImageView`：\n\n```\nimageView.loadUrl(\"http://....\")\n```\n\n很好。\n\n### 令人厌恶的菜单switches...再也不了！\n\n当重写`onOptionsItemSelected`时，我们通常创建一个设置好分支的`switch`，一般都返回true，最后都调用super。如果在这些action中有drawer设置，这是很糟糕的，因为你还要关闭这个drawer。一个可替代的方案（仅说明你能做什么，不是说这是最好的解决方案）是可以创建一个扩展的方法来做这些事情。\n\n首先，我们创建一个`consume`方法，意思是处理了这个event（返回true），接收一个lambda做我们分支要做的工作。对于drawer也一样可以这么做。\n\n```\noverride fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {\n    R.id.action_settings -> consume { navigateToSettings() }\n    R.id.nav_camera -> drawer.consume { navigateToCamera() }\n    R.id.nav_gallery -> drawer.consume { loadGallery() }\n    R.id.nav_slideshow -> drawer.consume { loadSlideshow() }\n    else -> super.onOptionsItemSelected(item)\n}\n```\n\n这些方法看起来是如何的？\n\n```\ninline fun consume(f: () -> Unit): Boolean {\n    f()\n    return true\n}\n```\n\n对于drawer也类似：\n\n```\ninline fun DrawerLayout.consume(f: () -> Unit): Boolean {\n    f()\n    closeDrawers()\n    return true\n}\n```\n\n好消息是这些函数是`内联的`，意思是编译时这个方法会被方法的代码替代，所以在调用的地方编写代码是高效的。\n\n### Snacks和toasts很像… 但更不好用\n\n这个代码显示了来自design support library的snack比toasts更不好用。但在Kotlin中我们可以做的更好。可以像下边这样写：\n\n```\nview.snack(\"This is my snack\")\nview.snack(\"This snack is short\", Snackbar.LENGTH_SHORT) \n```\n\n如果我们有一个action怎么办？别急，Kotlin这样解决：\n\n```\nview.snack(\"Snack message\") {\n    action(\"Action\") { toast(\"Action clicked\") }\n}\n```\n\n我们可以为了被困扰任何事情创建小的DSLs。我们需要什么？\n\n```\ninline fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Snackbar.() -> Unit) {\n    val snack = Snackbar.make(this, message, length)\n    snack.f()\n    snack.show()\n}\n```\n\n第一个方法是创建snackbar，让这个snackbar之行我们提供的扩展方法，并显示自己。\n\n这个方法会创建Snackbar action。这个action方法看起来是这样的：\n\n```\nfun Snackbar.action(action: String, color: Int? = null, listener: (View) -> Unit) {\n    setAction(action, listener)\n    color?.let { setActionTextColor(color) }\n}\n```\n\n你甚至可以指定这个action的文字的颜色。如果你没有设置，默认值是null，最后一行代码决定如何做。你不喜欢最后一行代码？\n\n```\ncolor?.let { setActionTextColor(color) }\n```\n\n `let`内的代码只有`color`不为空的时候才会被执行。\n\n### 现在我没有context...但谁关心呢？\n\n在Java中，例如当我们查找一个view时，我们必须等到activity的布局文件加载完毕才能赋值给一个变量。\n\n同样的也发生在context身上。如果一个object依赖context，你需要在class开始的地方声明它，然后在`onCreate`时候赋值。\n\n用Kotlin委托，你仅需要委托一个值给`lazy`委托，当第一次被使用的时候才会执行：\n\n```\noverride val toolbar by lazy { find<Toolbar>(R.id.toolbar) }\noverride val dataBase by lazy { DataBase(this) }\n \noverride fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_detail)\n    setSupportActionBar(toolbar)\n    dataBase.loadWhatever()\n}\n```\n\n`find`方法属于[Anko](https://github.com/Kotlin/anko)库。但你可以更容易的做类似的事情：\n\n```\ninline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T\n```\n\n### 更多的lambdas，更美丽\n\n在Java中所有的事情都要创建对象。一个例子就是在创建一个完整的`Runnable`时的`postDelayed`。Kotlin的interoperability仅需要创建一个lambda，它的可读性好很多：\n\n```\nview.postDelayed({ doWhatever() }, 200)\n```\n\n如果你想创建一个线程并运行一些东西该怎么做呢？\n\n```\nThread().run { \n    // Running in a thread    \n}\n```\n\n### “我恨AsyncTasks！！”。好了，那么就不用它们\n\n感谢[Anko](https://github.com/Kotlin/anko)库，我们有一个小的DSL来处理后台线程任务。\n\n```\nasync() {\n    // Do something in a secondary thread\n    uiThread {\n        // Back to the main thread\n    }\n}\n```\n\n它也是上下文感知的，所以如果它在antivity中被调用，如果activity关闭了，`uiThread` 也就不会被调用了。\n\n### 为什么处理集合是如此的困难？不再是了\n\n简单的回答就是缺少lambdas和功能操作。但Kotlin可以利用它们来工作，所以排序、转换、映射和过滤只是一个函数的调用。\n\n```\nreturn parsedContacts.filter { it.name != null && it.image != null }\n        .sortedBy { it.name }\n        .map { Contact(it.id, it.name!!, it.image!!) }\n```\n\n对于集合，你可以查看[complete list of operations](http://antonioleiva.com/collection-operations-kotlin/)。\n\n### 操作重载：让你的想象力飞翔\n\n谁说你不能像访问一个数组一样的访问`ViewGroup`中的view？这是不是很好？但一定很困难...当然不。你只需要用acts [as an operator](http://antonioleiva.com/operator-overloading-kotlin/).创建一个扩展方法。\n\n\n```\noperator fun ViewGroup.get(pos: Int): View = getChildAt(pos)\n```\n\n现在你可以这样做：\n\n```\nval view = viewGroup[2]\n```\n\n你甚至可以创建一个返回view列表的扩展属性：\n\n```\nval ViewGroup.views: List<View>\n    get() = (0 until childCount).map { getChildAt(it) }\n```\n\n现在你可以直接访问view：\n\n```\nval views = viewGroup.views\n```\n\n### 厌倦了这么多的getters, setters, toString(), equals()…?\n\nKotlin中的数据类给你提供了这些所有。\n\n```\ndata class Person(val name: String, val surname: String, val age: Int) \n```\n\n我们在这里做。\n\n### 简单的方法启动activity\n\nAnko也提供了一些好的方法可以不用创建intent、添加extras、调用方法...来启动另一个activity，所有的事情都可以用单独的一行代码做到：\n\n```\nstartActivity<DetailActivity>(\"id\" to 2, \"name\" to \"Kotlin\")\n```\n\n这回创建一个带有extras的intent，extras的值是该方法接收的一对列表参数值定义的。\n\n### Android扩展，或者如何忘记findViewById\n\n使用Kotlin的Android扩展，仅需要添加一个专门的import，插件将会给activity、fragment甚至一个view创建一组参数设置，因此你不必担心去申明或找到这些view。参数的名称将在导入的XML文件中定义。例如，在`RecyclerView` adapter里，`ViewHolder`里你可以这么做：\n\n```\nimport kotlinx.android.synthetic.main.item_contact.view.*\n \n...\n \nfun bindContact(contact: Contact) {\n    itemView.name.text = contact.name\n    itemView.avatar.loadUrl(contact.image)\n    itemView.setOnClickListener { listener(contact) }\n}\n```\n\n也可以更简洁。如果你多次使用同一个变量，你可以用标准库中的一些方法，例如`apply`：\n\n```\nfun bindContact(contact: Contact) = itemView.apply {\n    name.text = contact.name\n    avatar.loadUrl(contact.image)\n    setOnClickListener { listener(contact) }\n}\n```\n\n### 这个东西太好了，太值了！\n\n偷偷的看下Kotlin能为你做什么。很多事情仅是一个提高速度、帮助编写更清晰的代码，避免模版的语法糖果，最重要的是，让你感觉好像一个忍者。\n\nKotlin有许多你想学习的更惊叹的特性。这个语言真的是很有乐趣并富有创造力的，它注重实效（仅是一组不可思议的功能）并且完全集成了Android开发。\n\n我推荐你去看一本[书](http://antonioleiva.com/kotlin-android-developers-book/)，现在就获得他吧。\n\n"
  },
  {
    "path": "issue-47/Layouts-Attributes-and-you.md",
    "content": "# 你应该知道的布局和属性\n---\n\n> * 原文链接 : [Layouts, Attributes, and you](https://medium.com/google-developers/layouts-attributes-and-you-9e5a4b4fe32c#.egixi9sq1)\n* 原文作者 : [ianhlake](https://medium.com/@ianhlake)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [mijack](https://github.com/mijack)\n* 校对者: [mijack](https://github.com/mijack)  \n* 状态 :  未完成\n\n这是一个老生常谈的问题了：\n\n    [What layout should I be using?](http://stackoverflow.com/search?q=what+layout+should+I+use+%5Bandroid%5D&utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)\n\n\n对于一个Android开发者，知道如何将你印象中的界面（设计师给你的草图）转化成实际中的布局界面是十分重要的技能。\n\n## 什么是布局？\n\n仅仅是浏览developer.android.com查找Layout，你会发现很多以`Layout`结尾的类。他们都有什么共同点呢？他们都是ViewGroup的子类 - 一个支持添加子视图（通常被称为子视图）的[View](http://developer.android.com/reference/android/view/View.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)。\n\n正如你所想象的，一个ViewGroup的主要职责之一是对子视图进行布局：每个View有多大（以下简称`measure`）和将View放置ViewGroup内（以下简称`layout`阶段）。\n\n>**注意**：那并不是ViewGroup所有的事。当然，这可以有自己的定制行为，绘制自己以及子View。例如Toolbar除了支持子视图，他还有很多内置的功能。\n\n毫无疑问，你会选择较为合适的布局，布置子视图的位置，这是十分关键的。错误的布局可能会完成不了当前的布局或者性能不一定很好，而较好的布局可以知道简化事件的效果。\n\n## Layout_ Attributes\n\n和View一样，ViewGroup可以使用XML属性，如LinearLayout的android:orientation，改变他们的子View的布局，但是，这些属性将会引发每一个子View的变化。为了防止子视图间的递归传递，布局使用了不同的机制向子视图中添加使用形如`layout_`的属性。**这些属性是关于父布局的属性说明**，和View本身无关，我们可以从之前写的[建议](https://plus.google.com/+IanLake/posts/Hepj6KynZD5?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)中找到一个例子。\n\n```\n  <android.support.design.widget.AppBarLayout>\n    <android.support.v7.widget.Toolbar\n      app:layout_scrollFlags=\"scroll|enterAlways\" />\n  </android.support.design.widget.AppBarLayout >\n```\n\n如果你查看Toolbar，你不会找到任何和layout_scrollFlags有关的信息。但是你可以在[AppBarLayout](http://developer.android.com/reference/android/support/design/widget/AppBarLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)找到。这些布局属性实际存储在[LayoutParams](http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)（在这个案例中，是子类[AppBarLayout.LayoutParams](http://developer.android.com/reference/android/support/design/widget/AppBarLayout.LayoutParams.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)）。当视图附属在parent View时，就有和parent相关的LayoutParams，它存储着和布局位置相关的信息。默认情况下，这只是一个宽度和高度（即你可以在每一个视图中找到layout_width和layout_height），但每个ViewGroup中都可以在自己的LayoutParams的子类，声明新的属性（具体描述请见[文档](http://developer.android.com/guide/topics/ui/declaring-layout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#layout-params)\n\n>**注**：从XML文件中加载视图却传递参数parent（也就是调用LayoutInflater.inflate()中的parent=null），这是一个不好的习惯，因为没有这个参数，有没有人来解析和创建适当的LayoutParams对象，这就意味着所有的属性都扔掉 - 这可能不是你想要的。\n\n## 常用的AndroidLayouts\n\n大概知道文档中的LayoutParams和layout_ 属性足以帮助您选择合适的布局，但要是一个快速的总结再好不过了。\n### LinearLayout\n\nLinearLayout的功能比较单一：按照单一的行或列布局（具体取决于[android:orientation](http://developer.android.com/reference/android/widget/LinearLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#attr_android:orientation)是 [horizontal](http://developer.android.com/reference/android/widget/LinearLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#HORIZONTAL) 还是 [vertical](http://developer.android.com/reference/android/widget/LinearLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#VERTICAL)）。\n\n即便如此，LinearLayout只有一个方向，但是它还有一个绝招：[layout_weight](http://developer.android.com/guide/topics/ui/layout/linear.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#Weight) ，允许子View的大小拓展以填满整个布局——当你有一部分view是自适应时，而而其他需要尽可能多的空间时，它是非常有用的。\n### FrameLayout\n\n相比LinearLayout,[FrameLayout](http://developer.android.com/reference/android/widget/FrameLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)的行为就大相径庭了：这里所有的View都压在一起绘制。位置的唯一控制要素就是[layout_gravity](http://developer.android.com/reference/android/widget/FrameLayout.LayoutParams.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#attr_android:layout_gravity) - 可以将View放置在FrameLayout的一侧或中心。\n\n### RelativeLayout\n\n\n[RelativeLayout](http://developer.android.com/reference/android/widget/RelativeLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)几乎没有前面两个那么简单：看看[RelativeLayout.LayoutParams](http://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)，关于子view和RelativeLayout或者其他View的[位置关系](http://developer.android.com/guide/topics/ui/layout/relative.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#Position（例如[一个view放置在另一个view下面](http://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#attr_android:layout_below)），就有一大堆的属性（实际上和FrameLayout类似的）。\n\n\n这具有非常强大的的优势（子View之间可以相互依赖），但是[注意布局的性能](https://www.youtube.com/watch?v=dB3_vgS-Uqo?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)！\n\n### PercentFrameLayout 和 PercentRelativeLayout\n\n作为[Percent Support Library](https://plus.google.com/+AndroidDevelopers/posts/C8oaLunpEEj?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)的成员，[PercentFrameLayout](http://developer.android.com/reference/android/support/percent/PercentFrameLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog) 和 [PercentRelativeLayout](http://developer.android.com/reference/android/support/percent/PercentRelativeLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog) 允许你使用基于百分比的尺寸来设置尺寸或者margin，你可以使用layout_widthPercent=”50%”表示布局的一半，而无须其思考具体的值。\n\n\n它们还有一个最令人振奋的功能：[支持纵横比ratio](https://plus.google.com/+AndroidDevelopers/posts/ZQS29a5yroK?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)，使得它可以指定单个尺寸（高度或宽度）和与之对应的固定纵横比来指定尺寸。甚至对于一个维度上的wrap_content或match_parent它也是起作用的！\n\n### GridLayout\n\n\n在[2011](http://android-developers.blogspot.com/2011/11/new-layout-widgets-space-and-gridlayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)年的Ice Cream Sandwich，我们引入了[GridLayout](http://developer.android.com/reference/android/support/v7/widget/GridLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)，但现在它是[Support库](http://developer.android.com/tools/support-library/features.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#v7-gridlayout)的一部分（支持API 7）。支持将View防止在任意行和列，并和LinearLayout一样支持权重（weight），可以让你的层级变浅，同时避免由于使用RelativeLayout带来的复杂布局和性能下降。\n\n不像大多数的布局，GridLayout不要求子View有layout_height和layout_width每个视图 - 列和行会根据[基线](http://developer.android.com/reference/android/support/v7/widget/GridLayout.Alignment.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog) 调整布局。\n如果你想研究这个组件，我强烈建议你阅读[GridLayout.LayoutParams](http://developer.android.com/reference/android/support/v7/widget/GridLayout.LayoutParams.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)的文档和[博客](http://android-developers.blogspot.com/2011/11/new-layout-widgets-space-and-gridlayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog) （注意，文章是在GridLayout有了layout_weight属性之前写的）。\n\n### CoordinatorLayout\n\n\n[CoordinatorLayout](http://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog),是[Android Design Support Library](http://android-developers.blogspot.com/2015/05/android-design-support-library.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)中FrameLayout的子类，除了可以使用layout_gravity控制子View的位置外，还提供了[Behavior](http://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.Behavior.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)的概念。\n\n通过给View添加 [@DefaultBehavior](http://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.DefaultBehavior.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog) 注解、使用布局属性layout_behavior或者调用方法[setBehavior()](http://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.LayoutParams.html?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog#setBehavior%28android.support.design.widget.CoordinatorLayout.Behavior%29)可以给View添加behavior可以在子view之前拦截事件（包括measurement, layout, nested scrolling, touch events, 根据依赖的view做出对应的更改以及window insets）。\n\n深入理解Behaviors，请看博客[Intercepting everything with CoordinatorLayout Behaviors](https://medium.com/google-developers/intercepting-everything-with-coordinatorlayout-behaviors-8c6adc140c26?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)。\n\n# Layouts, layouts, layouts\n\n即使你只使用上述的一部分布局，你也可以搭建出很多高性能且易于维护的界面。剩下的时间，你可以针对特定的布局思考一下是否存在更简单的布局方式或者使用自定义视图（或者考虑一下扩展性）。\n\n不论怎么样，使用正确的布局和布局属性可以帮助我们更好的构建应用。\n\n更多信息，请关注[Android Development Patterns Collection](https://plus.google.com/collection/sLR0p?utm_campaign=android_series_layoutattributes_012116&utm_source=medium&utm_medium=blog)\n"
  },
  {
    "path": "issue-47/在Java和Android中使用Optional.md",
    "content": "﻿# 在Java和Android中使用Optional\n\n标签（空格分隔）： Java Optional\n\n---\n\n原文地址：http://fernandocejas.com/2016/02/20/how-to-use-optional-on-android-and-java/\n\n\n首先，这不是个新鲜的话题，已经有很多相关的讨论了。然而，在这篇文章里，我想解释`Optional<T>`是什么，展示一些用例，并且将它与其他语言中的不同替代品进行对比，最后，我特别想通过Arrow(轻量级的Java&Android工具箱库)让你们看到我们可以在Android里多么高效地使用`Optional<T> API`（虽然Arrow也可以用在任何`Java`项目中，特别是基于`Java7`的那些）。\n\n\n在开始之前，我要引用[Java8官方文档][2]里的一段话：\n“**一位智者曾说过：如果你没有处理过空指针异常，那你就不是真正的Java程序员，`null`因为常用来表示一个值的缺失因而也是许多问题的源头**”。\n\n\n虽然这段话所言不虚，Java8文档也确实把`Optional<T>`的使用当作解决**NullPointerException的救星**，但在我看来，它不仅能用来减少`NullPointerException`，而且能创造更多有意义、可读性高的API。\n\n众所周知，使用null时一旦大意就会导致种种bug，此外，**null的意义还是含糊不清的**，我们并不总是清楚它的意义：是表示值不存在吗？例如，当Map.get()方法返回null时，是表示值不存在，还是值存在且为null？\n\n我们接下来就试着来回答这些问题，开始动手吧！\n\n## Optional是什么？\n\n\n第一个定义来自[Java8文档][3]：\n**“`Optional`对象用来表示值缺失引起的`null`。它提供了各种各样的工具方法来帮助代码以“可用”或“不可用”的方式处理值，而不是判断`null`。”**\n\n\n这是来自[Guava官方文档][5]的类似的定义：\n**“一个不可改变的对象，它可能包含对另一个对象的非空引用。这个类型的每个实例，要么包含一个非空的引用，要么什么都不包含（在这种情况下我们可以说引用是“缺失的”）；它绝不是说“包含`null`”。一个非空的`Optional<T>`引用可以用来代替一个可空的T类型的引用。它允许你在程序中表达以下两种不同的概念以使得代码更清晰：“一个必须存在的T”和“一个可能缺失的T””**。\n\n\n一言以蔽之，**`Optional`类的API提供了一个用来包含非空对象的容器类对象**。我们来看个小例子，这样你会更加明白我在说什么。\n\n```java\nOptional<Integer> possible = Optional.of(5);\npossible.isPresent(); // returns true\npossible.get(); // returns 5\n```\n\n如你所见，我们把一个T类型的对象包装进一个Optional<T>中，所以我们可以在之后检查它的存在性。换句话说，**`Optional<T>`强迫你去关心值，因为为了获得它，你得调用`get()`方法**（作为一个好习惯，先检查它的存在性或者返回一个默认值）。很明显，我们这里用的是[Guava][6]'s中`Optional<T>`的概念。\n如果你还没明白，别担心，我们继续探索。\n\n## Java 8, Scala, Groovy和 Kotlin中的Optional/Option API\n\n我在上文提到过，本文中我们会专注于[Guava][7]的`Optional<T>`，但是快速浏览其他编程语言的实现方式也大有益处。\n\n\n让我们看看`Groovy`和`Kotlin`提出的概念。这两者在对待`null`安全时采取了相似的策略：“Elvis运算符”。它们添加了些语法糖，语法看起来差不多。看看`Kotlin`的做法：“**当我们有一个可为空的引用r，我们就说：'如果r不为null，就用r，否则就用某个非空值x'**”：\n\nElvis运算符\n```Groovy\nval l: Int = if (b != null) b.length else -1\n```\n\n除了完整的if表达式，这句代码也可以用Elvis运算符`?:`表示：\n\nElvis运算符\n```Groovy\nval l = b?.length ?: -1\n```\n\n\n**如果`？：`表达式的左边不为null，那么返回左边，否则返回值为右边。**注意，只有左边为`null`时才会计算右边。为准确起见，Kotlin在编译期进行`null`检查。\n\n你还可以阅读官方文档深入发掘，不过我不是搞Groovy或Kotlin的，所以我把这工作留给专业人士了：）。\n\n在[Java8][8]和[Scala][9]上我们发现针对`Optional<T>`的[`Monad`][10]方法，使得我们可以使用[`flatMap()`][11],[`map()`][12]等方法**。这意味着我们可以以函数式编程的风格传递数据流。`Kotlin`出于同样的目的也提供了`OptionIF<T>`的`Monad`。**\n\n看看[Sean Parsons][13]的Scala例子吧。\n\n### Scala中的Option\n```\ncase class Player(name: String)\ndef lookupPlayer(id: Int): Option[Player] = {\n  if (id == 1) Some(new Player(\"Sean\"))\n  else if(id == 2) Some(new Player(\"Greg\"))\n  else None\ndef lookupScore(player: Player): Option[Int] = {\n  if (player.name == \"Sean\") Some(1000000) else None\nprintln(lookupPlayer(1).map(lookupScore))  // Some(Some(1000000))\nprintln(lookupPlayer(2).map(lookupScore))  // Some(None)\nprintln(lookupPlayer(3).map(lookupScore))  // None\nprintln(lookupPlayer(1).flatMap(lookupScore))  // Some(1000000)\nprintln(lookupPlayer(2).flatMap(lookupScore))  // None\nprintln(lookupPlayer(3).flatMap(lookupScore))  // None\n```\n\n最后强调一点，我们是从[Guava][14]中借鉴的Optional<T>。Guava简化过的API完美契合Java7模型：缺乏first-class函数时才需要用到Optional<T>，这也是唯一的且最重要的要使用它的理由。\n\n\n我猜到现在为止一切都很顺利，但没有Android Java7的示范代码。。。耐心地看下去吧，接下来还有很多。如果你在疑心为了要在Android上用`Optional`是不是得编译Guava和它的20k方法，答案是否定的，我们还有一个[替代方案][15]。\n\n\n## 如何在Android里使用Optional<T>?\n\n第一个要强调的观点是：**我们已深陷于Java7中**，Java7没有内置的`Optional<T>`，所以我们不幸地要向[第三方库][16]求助。。。\n我们第一个想到的是[Guava][17]，但**在Android上不合适**，尤其是要把上文中提到的20k方法塞进你的.apk文件（我确定你已经知道了[65k方法限制的问题][18]了）。\n\n**第二个选择是Arrow**，它是我创建的一个轻量级的开源库，包含了我日常Android开发中的小技巧、自己写的小工具，如用于精简代码的注解等。你可以在Github上查看[本项目][19]。当然了，一切首先要感谢创作这些优秀API的人。\n\n## 如何创建Optional<T>?\n\n`Optional<T>`相当直白：\n\n![Optional Creation][20]\n\n这是`Optional<T>`的查询方法：\n\n![optional_query][21]\n\n不要走，开始放代码了。\n\n## 情景 #1\n\n这是一个广为人知的段子：[Tony Hoare][22]谈到当年自己创造null引用的事情时说：\n“1965年发明null引用是我犯过的价值10亿美元的错误。仅仅是因为实现起来太容易了，我脑子一发热就引入了null引用”。\n\n### Car的例子\n```java\npublic class Car {\n  private final String brand;\n  private final String model;\n  private final String registrationNumber;\n  public Car(String brand, String model, String registrationNumber) {\n    this.brand = brand;\n    this.model = model;\n    this.registrationNumber = registrationNumber;\n  public String information() {\n    final StringBuilder builder = new StringBuilder();\n    builder.append(\"Model: \").append(model);\n    builder.append(\"Brand: \").append(brand);\n    if (registrationNumber != null) {\n      builder.append(\"Registration Number: \").append(registrationNumber);\n    return builder.toString();\n```\n\n这段代码的问题是依赖一个`null`的引用来表示registration number的缺失（一种坏习惯），所以我们**可以用Optional<T>来完善它，并且根据值是否存在来打印**。\n\n### Car的例子\n```java\npublic class Car {\n  private final Optional<String> registrationNumber;\n  public Car(String brand, String model, String registrationNumber) {\n    this.registrationNumber = Optional.fromNullable(registrationNumber);\n  public String information() {\n    if (registrationNumber.isPresent()) {\n      builder.append(\"Registration Number: \").append(registrationNumber.get());\n    return builder.toString();\n```\n\n**最明显的用法是用来避免无意义的null**。[到GitHub上查看完整的实现][23]。\n进入下一个场景。\n\n\n## 情景 #2\n\n在移动开发中，我们经常需要解析来自API响应的JSON文件。在这个例子里，为了强迫客户端在使用值前关心它的存在性，我们可以在实体中使用`Optional<T>`。\n看看例子中的`nickname`域变量和`getter`方法。\n\n###JSON解析的例子\n```java\npublic class User {\n  @SerializedName(\"id\")\n  private int userId;\n  @SerializedName(\"full_name\")\n  private String fullname;\n  @SerializedName(\"nickname\")\n  private String nickname;\n  public int id() {\n    return userId;\n  public String fullname() {\n    return fullname;\n  public Optional<String> nickname() {\n    return Optional.fromNullable(nickname);\n```\n[完整的sample在GitHub上][24] .\n\n## 情景 #3\n\n另一个例子是我们常常在应用里遇到的[@SoundCloud][26]。\n当我们要构建我们的feed或列表并把它们展示到UI层时，我们的item来自不同的数据源，有些可能是`Optional<T>`,比如Facebook的邀请、一个赞同。\n\n本例使用`RxJava`来简化这种场景：\n\n### RxJava的例子\n\n```java\npublic class Sample {\n  public static final Func1<Optional<List<String>>, Observable<List<String>>> TO_AD_ITEM =\n      ads -> ads.isPresent()\n          ? Observable.just(ads.get())\n          : Observable.just(Collections.<String>emptyList());\n  public static final Func1<List<String>, Boolean> EMPTY_ELEMENTS = ads -> !ads.isEmpty();\n  public Observable<List<String>> feed() {\n    return ads()\n        .flatMap(TO_AD_ITEM)\n        .filter(EMPTY_ELEMENTS)\n        .concatWith(tracks())\n        .observeOn(Schedulers.immediate());\n  private Observable<Optional<List<String>>> ads() {\n    return Observable.just(Optional.fromNullable(Collections.singletonList(\"This is and Ad\")));\n  private Observable<List<String>> tracks() {\n    return Observable.just(Arrays.asList(\"IronMan Song\", \"Wolverine Song\", \"Batman Sound\"));\n```\n \n这里最重要的部分是：当我们要把两个`Observables<T>`结合时（分别来自`tracks()`和`ads()`）时，我们使用了[`flatMap()`][27]和[`filter()`][28]运算符来决定是否显示广告、把它们更新到UI层（我使用Java8的lambda表达式以提高代码可读性）。\n\n### RxJava的例子\n```java\npublic static final Func1<Optional<List<String>>, Observable<List<String>>> TO_AD_ITEM =\n      ads -> ads.isPresent()\n          ? Observable.just(ads.get())\n          : Observable.just(Collections.<String>emptyList());\npublic static final Func1<List<String>, Boolean> EMPTY_ELEMENTS = ads -> !ads.isEmpty();\n```\n\n[到Github上查看完整实现。][29]\n\n\n## 结论\n\n总结一下，软件开发里没有捷径可走，因为我们总是容易过度思考和滥用某样东西，所以**不要到处使用`Optional`,最后污染了代码，请谨慎地用在有意义的地方**。\n\n让我引用Joshua Bloch在他的演讲“[如何设计一个好的API，为何它很重要][30]”中的一段话：\n“**API应该易于使用、难于用错：它应该易于实现简单的事情，能胜任复杂的事情，不可能或者至少很难做错事情**”。\n\n我完全同意他的观点，并且从API设计的角度来讲，`Optional<T>`设计得很棒：**它帮助你表辞达意、防范NullPointerException（尽管不能完全避免），让你写出精确的可读性高的代码。**\n\n## 示例代码\n\n\n所有的sample代码都在这里: https://github.com/android10/java-code-examples，\n想看在Android里怎么用Optional<T>请看Arrow项目： https://github.com/android10/arrow\n\n## 参考文献\n\nhttp://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html \nhttps://github.com/google/guava/wiki/UsingAndAvoidingNullExplained \nhttps://dzone.com/articles/guavas-new-optional-class \nhttps://kerflyn.wordpress.com/2011/12/05/from-optional-to-monad-with-guava/ \nhttp://techblog.bozho.net/the-optional-type/ \nhttp://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html \nhttp://seanparsons.github.io/scalawat/Using+flatMap+With+Option.html \nhttp://www.nurkiewicz.com/2013/05/null-safety-in-kotlin.html \nhttps://kotlinlang.org/docs/reference/null-safety.html\n\n\n\n\n  [1]: https://dzone.com/articles/guavas-new-optional-class\n  [2]: http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html\n  [3]: https://github.com/google/guava\n  [4]: http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html\n  [5]: http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html\n  [6]: http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html\n  [7]: https://github.com/google/guava\n  [8]: http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html\n  [9]: http://alvinalexander.com/scala/using-scala-option-some-none-idiom-function-java-null\n  [10]: https://mttkay.github.io/blog/2014/01/25/your-app-as-a-function/\n  [11]: http://fernandocejas.com/2015/01/11/rxjava-observable-tranformation-concatmap-vs-flatmap/\n  [12]: http://fernandocejas.com/2015/01/11/rxjava-observable-tranformation-concatmap-vs-flatmap/\n  [13]: http://seanparsons.github.io/scalawat/\n  [14]: https://github.com/google/guava/\n  [15]: https://github.com/android10/arrow\n  [16]: https://github.com/android10/arrow\n  [17]: https://github.com/google/guava/\n  [18]: https://developers.soundcloud.com/blog/congratulations-you-have-a-lot-of-code-remedying-androids-method-limit-part-1\n  [19]: https://github.com/android10/arrow\n  [20]: http://fernandocejas.com/wp-content/uploads/2016/02/optional_creation-2.png\n  [21]: http://fernandocejas.com/wp-content/uploads/2016/02/optional_query.png\n  [22]: https://en.wikipedia.org/wiki/Tony_Hoare\n  [23]: https://github.com/android10/java-code-examples/blob/master/src/main/java/com/fernandocejas/java/samples/optional/UseCaseScenario02.java\n  [24]: https://github.com/android10/java-code-examples/blob/master/src/main/java/com/fernandocejas/java/samples/optional/UseCaseScenario02.java\n  [25]: https://developers.soundcloud.com/blog\n  [26]: https://developers.soundcloud.com/blog\n  [27]: http://fernandocejas.com/2015/01/11/rxjava-observable-tranformation-concatmap-vs-flatmap/\n  [28]: http://reactivex.io/documentation/operators/filter.html\n  [29]: https://github.com/android10/java-code-examples/blob/master/src/main/java/com/fernandocejas/java/samples/optional/UseCaseScenario03.java\n  [30]: http://www.infoq.com/articles/API-Design-Joshua-Bloch\n"
  },
  {
    "path": "issue-48/Android中的MVP-Part2.md",
    "content": "Android中的MVP-Part2\n-------------------\n\n> * 原文链接 : [Model View Presenter (MVP) in Android, Part 2](http://www.tinmegali.com/en/model-view-presenter-mvp-in-android-part-2/)\n* 原文作者 : [TinMegali](https://medium.com/@tinmegali)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n![Model View Presenter Class\nDiagram](https://github.com/DroidWorkerLYF/Translate/blob/master/Model-View-Presenter%20(MVP)/1.png?raw=true)\n  \n在上一篇[文章](http://www.tinmegali.com/model-view-presenter-mvp-no-android-introducao/)中我们谈论了[Model View Presenter\n(MVP)](https://pt.wikipedia.org/wiki/Model-view-presenter)的概念和在Android开发中的优点。这是系列文章的第二篇，我们来动手实践一下，将使用[典型的形式](https://en.wikipedia.org/wiki/Canonical_form)实现一个MVP结构，不使用任何Android SDK或JAVA以外的库。\n  \n我们会开发一个简单的工程，但是由于涉及大量的对象，可能使项目看起来有些复杂。但是，一旦你掌握了，你就会明白MVP模式如何能帮助你。如果你想直接看代码，[在这里](https://github.com/tinmegali/simple-mvp/tree/master/AndroidMVP/mvp/src/main/java/com/tinmegali/mvp/mvp)。\n\n-   [Model View Presenter (MVP) in Android, part\n    1](http://wp.me/p7gH7l-2x)\n-   [Model View Presenter (MVP) in Android,\n    part 3](http://wp.me/p7gH7l-34)\n\n设计MVP\n------\n\n##### MVP在Android中的主要概念\n\n> #### **Presenter**\n>\n> Presenter是View和Model的中间人。它从Model层获取数据，格式化后返回给View层。\n> 但是和典型的MVC模式不同的是，它还决定如何处理你和视图的交互。\n>\n> #### **View**\n>\n> 视图层，通常是由Activity实现，其中包含有presenter的引用。视图层唯一要做的事就是响应\n> 用户的操作，调用Presenter层的方法。\n>\n> #### **Model**\n>\n> 在有良好分层结构的应用中，Model层只是domain层或者业务逻辑的入口。把它看做视图层\n> 数据的提供者就好。\n>\n> *以上优秀的定义提取自[Antonio Leiva’s article](http://antonioleiva.com/mvp-android/)*\n \n使用MVP模式的最大任务是增加我们项目的[关注分离](https://en.wikipedia.org/wiki/Separation_of_concerns).因此我们需要确保Model层，View层和Presenter层的隔离。这种情况下，**View层和Model层无法直接通信**，因此**Presenter负责各层的通讯**\n\n### Model View Presenter行为图\n \n让我们设想一个简单的应用，它允许用户在旅途中做笔记。主要就是用户记录笔记，系统保存和展示数据。如果我们沿着输入笔记的行为，结合MVP模式，我们会得到下图：\n\n![Model View Presenter (MVP) action\ndiagram](https://github.com/DroidWorkerLYF/Translate/blob/master/Model-View-Presenter%20(MVP)/2.png?raw=true)\nModel View Presenter (MVP) 行为图\n\n1. 用户点击输入笔记。**视图层**将笔记内容传入**Presenter层**的newNote(textNote)方法。\n2. **Presenter层**调用**Model层**的insertNote(note, this)方法，将传入的string创建为一个新的笔记。\n3. **Model层**在数据库中插入笔记并且使用onSuccess()方法通知**Presenter层**成功/失败的结果\n4. **Presenter层**处理结果后调用showToast()方法让**View层**展示一个toast \n\n这个映射给了我们对类设计的灵感。上述不同层之间的通信过程实现可能会不一样：直接调用对象的方法，使用接口或者使用EventBus。然而，既然我们的实现方式遵循典型方式，并且意在增加关注分离，所以我们只用原始简单的接口。\n\n### Model View Presenter类图\n \n让我们根据上面的*行为图*来构造我们的MVP模式[类图](https://en.wikipedia.org/wiki/Class_diagram)。我们会对概念做一点改动，把*callback*换成*interface*，来将结果从**Model层**传回**Presenter层**。我相信这种方式更高效，但是有人会对此有争议，认为*callback*会增加关注分离。\n\n![Model View Presenter Class\nDiagram](https://github.com/DroidWorkerLYF/Translate/blob/master/Model-View-Presenter%20(MVP)/1.png?raw=true)\nModel View Presenter 类图\n\n1. **Presenter层**实现**PresenterOps接口**\n2. **View层**接受**PresenterOps**的引用来访问**Presenter**\n3. **Model层**实现**ModelOps接口**\n4. **Presenter层**接受**ModelOps**的引用来访问**Model**\n5. **Presenter层**实现**RequiredPresenterOps接口**\n6. **Model层**接受**RequiredPresenterOps**的引用来访问**Presenter**\n7. **View层**实现**RequiredViewOps接口**\n8. **Presenter层**接受**RequiredViewOps**的引用来访问**View**\n\n在Android中实现MVP\n----------------\n  \n事不宜迟，让我们动起来！先定义操作。为了更好的结构组织，我们使用一个“umbrella”类，包含所有层次间通讯的接口。\n\n> **注意**：由于实现MVP模式已经很复杂了，我不会实现其他多余的内容。我假设读者都对Android SDK有很好的理解，因此不需要我关注这些。\n\n### Interface MainMVP\n\n\t/*\n\t * Aggregates all communication operations between MVP pattern layer: \n\t * Model, View and Presenter \n\t */ \n\t public interface MainMVP { \n\t \t/**\n\t \t * View mandatory methods. Available to Presenter \n\t \t * Presenter -> View \n\t \t */ \n\t \t interface RequiredViewOps { \n\t \t \tvoid showToast(String msg); \n\t \t \tvoid showAlert(String msg); \n\t \t \t// any other ops \n\t \t } \n\t \t \n\t \t /** \n\t \t  * Operations offered from Presenter to View \n\t \t  * View -> Presenter \n\t \t  */ \n\t \t  interface PresenterOps { \n\t \t  \t  void onConfigurationChanged(RequiredViewOps view); \n\t \t  \t  void onDestroy(boolean isChangingConfig); \n\t \t  \t  void novaNota(String textoNota);\n\t \t  \t  void deletaNota(Nota nota); \n\t \t  \t  // any other ops to be called from View \n\t \t  }\n\t \t  \n\t \t  /** \n\t \t   * Operations offered from Presenter to Model \n\t \t   * Model -> Presenter \n\t \t   */ \n\t \t   interface RequiredPresenterOps { \n\t \t   \t\tvoid onNotaInserida(Nota novaNota); \n\t \t   \t\tvoid onNotaRemovida(Nota notaRemovida); \n\t \t   \t\tvoid onError(String errorMsg); \n\t \t   \t\t// Any other returning operation Model -> Presenter \n\t \t   }\n\t \t   \n\t \t   /** \n\t \t    * Model operations offered to Presenter \n\t \t    * Presenter -> Model\n\t \t    */ \n\t \t    interface ModelOps { \n\t \t    \t void insereNota(Nota nota); \n\t \t    \t void removeNota(Nota nota); \n\t \t    \t void onDestroy(); \n\t \t    \t // Any other data operation \n\t \t    } \n\t }\n\n### MainPresenter Class\n\n\tpublic class MainPresenter implements MainMVP.RequiredPresenterOps, MainMVP.PresenterOps {\n\n    \t// Layer View reference\n    \tprivate WeakReference&lt;MainMVP.RequiredViewOps&gt; mView;\n    \t// Layer Model reference\n    \tprivate MainMVP.ModelOps mModel;\n\n    \t// Configuration change state\n    \tprivate boolean mIsChangingConfig;\n\n    \tpublic MainPresenter(MainMVP.RequiredViewOps mView) {\n        \tthis.mView = new WeakReference&lt;&gt;(mView);\n        \tthis.mModel = new MainModel(this);\n    \t}\n\n    \t/**\n     \t * Sent from Activity after a configuration changes\n     \t * @param view  View reference\n     \t */\n    \t@Override\n    \tpublic void onConfigurationChanged(MainMVP.RequiredViewOps view) {\n        \tmView = new WeakReference&lt;&gt;(view);\n    \t}\n\n    \t/**\n     \t * Receives {@link MainActivity#onDestroy()} event\n     \t * @param isChangingConfig  Config change state\n     \t */\n    \t@Override\n    \tpublic void onDestroy(boolean isChangingConfig) {\n        \tmView = null;\n        \tmIsChangingConfig = isChangingConfig;\n        \tif ( !isChangingConfig ) {\n            \tmModel.onDestroy();\n        \t}\n    \t}\n\n    \t/**\n     \t * Called by user interaction from {@link MainActivity}\n     \t * creates a new Note\n     \t */\n    \t@Override\n    \tpublic void newNote(String noteText) {\n        \tNote note = new Note();\n        \tnote.setText(textoNota);\n        \tnote.setDate(getDate());\n        \tmModel.insertNote(note);\n    \t}\n\n    \t/**\n     \t * Called from {@link MainActivity},\n     \t * Removes a Note\n     \t */\n    \t@Override\n    \tpublic void removeNote(Note note) {\n        \tmModel.removeNote(note);\n    \t}\n\n    \t/**\n     \t * Called from {@link MainModel}\n     \t * when a Note is inserted successfully\n     \t */\n    \t@Override\n    \tpublic void onNoteInsert(Note newNote) {\n        \tmView.get().showToast(\"New register added at \" + newNote.getDate());\n    \t}\n\n    \t/**\n     \t * Receives call from {@link MainModel}\n     \t * when Note is removed\n     \t */\n    \t@Override\n    \tpublic void onNoteRemoved(Note noteRemoved) {\n        \tmView.get().showToast(\"Note removed);\n    \t}\n\n    \t/**\n     \t * receive errors\n     \t */\n    \t@Override\n    \tpublic void onError(String errorMsg) {\n        \tmView.get().showAlert(errorMsg);\n    \t}\n\t}\n\n### MainModel Class\n\n\tpublic class MainModel implements MainMVP.ModelOps {\n\n    \t// Presenter reference\n    \tprivate MainMVP.RequiredPresenterOps mPresenter;\n\n    \tpublic MainModel(MainMVP.RequiredPresenterOps mPresenter) {\n        \tthis.mPresenter = mPresenter;\n    \t}\n\n    \t/**\n     \t * Sent from {@link MainPresenter#onDestroy(boolean)}\n     \t * Should stop/kill operations that could be running\n     \t * and aren't needed anymore\n     \t */\n    \t@Override\n    \tpublic void onDestroy() {\n        \t// destroying actions\n    \t}\n\n    \t// Insert Note in DB\n    \t@Override\n    \tpublic void insertNote(Note note) {\n        \t// data business logic\n        \t// ...\n        \tmPresenter.onNoteInserted(note);\n    \t}\n\n    \t// Removes Note from DB\n    \t@Override\n    \tpublic void removeNote(Note note) {\n        \t// data business logic\n        \t// ...\n        \tmPresenter.onNoteRemoved(note);\n    \t}\n\t}\n\n### 处理Android相关的特性\n  \n在我们的MVP模式中，**视图层**负责创建**Presenter层**，**Presenter层**负责实例化**Model层**对象。考虑到使用**Activity**来实现**View层**，我们需要考虑一些Android的细节，尤其是销毁和创建activities及其对象的[生命周期](http://developer.android.com/training/basics/activity-lifecycle/index.html)。\n \n这就是说，我们需要增加第四个元素**StateMaintainer**，负责在生命周期的变化中维护Presenter和Model的状态。使用retained fragment来实现这个对象，如下是一个简化的MVP模式Activity生命周期：\n\n![MVP Objects destruction and reconstruction during Activity lifecycle\nchanges](https://github.com/DroidWorkerLYF/Translate/blob/master/Model-View-Presenter%20(MVP)/3.png?raw=true) \nActivity生命周期变化时MVP模式中对象的销毁和创建\n\n1. **Activity**创建一个**Presenter**的实例并持有**PresenterOps**的引用。**Presenter**存储在**StateMaintainer**中\n2. **Presenter**在创建时接受**RequiredViewOps**类型参数并且创建一个**Model**对象\n3. **Model**接受**RequiredPresenterOps**类型参数\n4. 当Activity被销毁时，会通知**Presenter**\n5. **Presenter**处理信息，作必要的处理后传递给**Model**\n6. **Activity**从**StateMaintainer**中恢复**Presenter**，并且传递**RequiredViewOps**，通知他的激活状态。\n\n### StateMaintainer Class\n  \n**StateMaintainer**的这种实现可以用来存储任何对象的状态。\n\n**StateMainainer**\n\n\tpublic class StateMaintainer {\n\t\tprotected final String TAG = getClass().getSimpleName();\n\n    \tprivate final String mStateMaintenerTag;\n    \tprivate final WeakReference&lt;FragmentManager&gt; mFragmentManager;\n    \tprivate StateMngFragment mStateMaintainerFrag;\n\n    \t/**\n     \t * Constructor\n     \t * @param fragmentManager       FragmentManager reference\n     \t * @param stateMaintainerTAG    the TAG used to insert the state maintainer fragment\n     \t */\n    \tpublic StateMaintainer(FragmentManager fragmentManager, String stateMaintainerTAG) {\n        \tmFragmentManager = new WeakReference&lt;&gt;(fragmentManager);\n        \tmStateMaintenerTag = stateMaintainerTAG;\n    \t}\n\n    \t/**\n     \t * Create the state maintainer fragment\n     \t * @return  true: the frag was created for the first time\n     \t *          false: recovering the object\n     \t */\n    \tpublic boolean firstTimeIn() {\n        \ttry {\n            \t// Recovering the reference\n            \tmStateMaintainerFrag = (StateMngFragment)mFragmentManager.get().findFragmentByTag(mStateMaintenerTag);\n\n            \t// Creating a new RetainedFragment\n            \tif (mStateMaintainerFrag == null) {\n                \tLog.d(TAG, \"Creating a new RetainedFragment \" + mStateMaintenerTag);\n                \tmStateMaintainerFrag = new StateMngFragment();\n                \tmFragmentManager.get().beginTransaction()\n                        .add(mStateMaintainerFrag,mStateMaintenerTag).commit();\n                    return true;\n            \t} else {\n            \t\tLog.d(TAG, \"Returns a existent retained fragment existente \" + mStateMaintenerTag);\n            \t\treturn false;\n            \t}\n        \t} catch (NullPointerException e) {\n            \tLog.w(TAG, \"Error firstTimeIn()\");\n            \treturn false;\n        \t}\n    \t}\n\n\n    \t/**\n     \t * Insert Object to be preserved during configuration change\n     \t * @param key   Object's TAG reference\n     \t * @param obj   Object to maintain\n     \t */\n    \tpublic void put(String key, Object obj) {\n        \tmStateMaintainerFrag.put(key, obj);\n    \t}\n\n    \t/**\n     \t * Insert Object to be preserved during configuration change\n     \t * Uses the Object's class name as a TAG reference\n     \t * Should only be used one time by type class\n     \t * @param obj   Object to maintain\n     \t */\n    \tpublic void put(Object obj) {\n        \tput(obj.getClass().getName(), obj);\n    \t}\n\n\n    \t/**\n     \t * Recovers saved object\n     \t * @param key   TAG reference\n     \t * @param &lt;T&gt;   Class type\n     \t * @return      Objects\n     \t */\n    \t@SuppressWarnings(\"unchecked\")\n    \tpublic &lt;T&gt; T get(String key)  {\n        \treturn mStateMaintainerFrag.get(key);\n    \t}\n\n    \t/**\n     \t * Verify the object existence \n    \t * @param key   Obj TAG\n     \t */\n    \tpublic boolean hasKey(String key) {\n        \treturn mStateMaintainerFrag.get(key) != null;\n    \t}\n\n\n    \t/**\n     \t * Save and manages objects that show be preserved\n     \t * during configuration changes.\n     \t */\n    \tpublic static class StateMngFragment extends Fragment {\n        \tprivate HashMap&lt;String, Object&gt; mData = new HashMap&lt;&gt;();\n\n        \t@Override\n        \tpublic void onCreate(Bundle savedInstanceState) {\n            \tsuper.onCreate(savedInstanceState);\n            \t// Grants that the frag will be preserved\n            \tsetRetainInstance(true);\n        \t}\n\n        \t/**\n        \t * Insert objects\n        \t * @param key   reference TAG\n        \t * @param obj   Object to save\n        \t */\n        \tpublic void put(String key, Object obj) {\n            \tmData.put(key, obj);\n        \t}\n\n        \t/**\n        \t * Insert obj using class name as TAG\n        \t * @param object    obj to save\n        \t */\n        \tpublic void put(Object object) {\n            \tput(object.getClass().getName(), object);\n        \t}\n\n        \t/**\n         \t * Recover obj\n        \t * @param key   reference TAG\n        \t * @param &lt;T&gt;   Class\n        \t * @return      Obj saved\n        \t */\n        \t@SuppressWarnings(\"unchecked\")\n        \tpublic &lt;T&gt; T get(String key) {\n            \treturn (T) mData.get(key);\n        \t}\n    \t}\n\t}\n\n### MainActivity Activity (View layer)\n\n\tpublic class MainActivity extends AppCompatActivity implements MainMVP.RequiredViewOps {\n\n    \tprotected final String TAG = getClass().getSimpleName();\n\n    \t// Responsible to maintain the Objects state\n    \t// during changing configuration\n    \tprivate final StateMaintainer mStateMaintainer = new StateMaintainer( this.getFragmentManager(), TAG );\n\n    \t// Presenter operations\n    \tprivate MainMVP.PresenterOps mPresenter;\n\n    \t@Override\n    \tprotected void onCreate(Bundle savedInstanceState) {\n        \tsuper.onCreate(savedInstanceState);\n        \tstartMVPOps();\n        \tsetContentView(R.layout.activity_main);\n        \tToolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        \tsetSupportActionBar(toolbar);\n        \tFloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab);\n    \t}\n\n    \t/**\n     \t * Initialize and restart the Presenter.\n     \t * This method should be called after {@link Activity#onCreate(Bundle)}\n     \t */\n    \tpublic void startMVPOps() {\n        \ttry {\n            \tif ( mStateMaintainer.firstTimeIn() ) {\n                \tLog.d(TAG, \"onCreate() called for the first time\");\n                \tinitialize(this);\n            \t} else {\n                \tLog.d(TAG, \"onCreate() called more than once\");\n                \treinitialize(this);\n            \t}\n        \t} catch ( InstantiationException | IllegalAccessException e ) {\n            \tLog.d(TAG, \"onCreate() \" + e );\n            \tthrow new RuntimeException( e );\n        \t}\n    \t}\n\n\n    \t/**\n     \t * Initialize relevant MVP Objects.\n     \t * Creates a Presenter instance, saves the presenter in {@link StateMaintainer}\n     \t */\n    \tprivate void initialize( MainMVP.RequiredViewOps view ) throws InstantiationException, IllegalAccessException{\n        \tmPresenter = new MainPresenter(view);\n        \tmStateMaintainer.put(MainMVP.PresenterOps.class.getSimpleName(), mPresenter);\n    \t}\n\n    \t/**\n     \t * Recovers Presenter and informs Presenter that occurred a config change.\n     \t * If Presenter has been lost, recreates a instance\n     \t */\n    \tprivate void reinitialize( MainMVP.RequiredViewOps view)\n            throws InstantiationException, IllegalAccessException {\n        \tmPresenter = mStateMaintainer.get( MainMVP.PresenterOps.class.getSimpleName() );\n\n        \tif ( mPresenter == null ) {\n            \tLog.w(TAG, \"recreating Presenter\");\n            \tinitialize( view );\n        \t} else {\n            \tmPresenter.onConfigurationChanged( view );\n        \t}\n    \t}\n\n    \t// Show AlertDialog\n    \t@Override\n    \tpublic void showAlert(String msg) {\n        \t// show alert Box\n    \t}\n\n    \t// Show Toast\n    \t@Override\n    \tpublic void showToast(String msg) {\n        \tToast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show;\n    \t}\n\t}\n\n#### [源码](https://github.com/tinmegali/simple-mvp/tree/master/AndroidMVP/mvp/src/main/java/com/tinmegali/mvp/mvp)\n\n### 下一篇文章\n \n我知道这篇文章有一点长了，很抱歉。但我真的希望可以帮到谁。下一遍文章，我们会讨论如何使用[最终的框架](https://github.com/tinmegali/Android-Model-View-Presenter-MVP)，它包含了一些可以加快MVP实现的抽象，我们也会谈论一些适配的小问题。\n \n回头见！"
  },
  {
    "path": "issue-48/Parcelable vs Serializable对比.md",
    "content": "[原文链接](http://www.developerphil.com/parcelable-vs-serializable/)\n\n> Parcelable vs Serializable\n\n当你开始写android时，我们所学到的是不能直接向Activities和Fragments传递对象，我们不得不借助Intent或者Bundle来传递它们。\n\n当我们看api文档的时候，我们认识到有两种选择，我们的对象要么是Parcelable或者Serializable型，作为一个java开发者，我们已经知道Serializable的机制，那为什么还要去研究Parcelable呢？\n\n为了回答这个问题，我们来看两个方法。\n\n####Serializable，简洁的鼻祖####\n\n```java\n// access modifiers, accessors and constructors omitted for brevity\npublic class SerializableDeveloper implements Serializable\n    String name;\n    int yearsOfExperience;\n    List<Skill> skillSet;\n    float favoriteFloat;\n\n    static class Skill implements Serializable {\n        String name;\n        boolean programmingRelated;\n    }\n}\n\n```\n\n仅仅需要在它和它的子类上实现Serializable接口就能完成一个漂亮的Serializable功能，他是一个标记接口，意味着不需要实现任何方法，java虚拟机将简单高效地完成序列化工作。\n\n这里面有个问题就是这种序列化是通过反射机制从而削弱了性能，这种机制也创建了大量的临时对象从而引起GC频繁回收调用资源。\n\n####Parcelable, 速度之王####\n```java\n// access modifiers, accessors and regular constructors ommited for brevity\nclass ParcelableDeveloper implements Parcelable {\n    String name;\n    int yearsOfExperience;\n    List<Skill> skillSet;\n    float favoriteFloat;\n\n    ParcelableDeveloper(Parcel in) {\n        this.name = in.readString();\n        this.yearsOfExperience = in.readInt();\n        this.skillSet = new ArrayList<Skill>();\n        in.readTypedList(skillSet, Skill.CREATOR);\n        this.favoriteFloat = in.readFloat();\n    }\n\n    void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(name);\n        dest.writeInt(yearsOfExperience);\n        dest.writeTypedList(skillSet);\n        dest.writeFloat(favoriteFloat);\n    }\n\n    int describeContents() {\n        return 0;\n    }\n\n\n    static final Parcelable.Creator<ParcelableDeveloper> CREATOR\n            = new Parcelable.Creator<ParcelableDeveloper>() {\n\n        ParcelableDeveloper createFromParcel(Parcel in) {\n            return new ParcelableDeveloper(in);\n        }\n\n        ParcelableDeveloper[] newArray(int size) {\n            return new ParcelableDeveloper[size];\n        }\n    };\n\n    static class Skill implements Parcelable {\n        String name;\n        boolean programmingRelated;\n\n        Skill(Parcel in) {\n            this.name = in.readString();\n            this.programmingRelated = (in.readInt() == 1);\n        }\n\n        @Override\n        void writeToParcel(Parcel dest, int flags) {\n            dest.writeString(name);\n            dest.writeInt(programmingRelated ? 1 : 0);\n        }\n\n        static final Parcelable.Creator<Skill> CREATOR\n            = new Parcelable.Creator<Skill>() {\n\n            Skill createFromParcel(Parcel in) {\n                return new Skill(in);\n            }\n\n            Skill[] newArray(int size) {\n                return new Skill[size];\n            }\n        };\n\n        @Override\n        int describeContents() {\n            return 0;\n        }\n    }\n}\n```\n按照google工程师的说话，这段代码将跑起来非常快，其中一个原因是运用真实的序列化处理代替反射，为了完成这个目的代码也做了大量的优化。\n\n然而，显而易见的是实现Parcelable接口并不是无成本的，创建了大量的引入代码从而导致整个类变得很重同时加大了维护成本。\n\n####Speed Tests####\n\n当然，我们想知道Parcelable到底有多快\n\n测试步骤\n1：模拟这个操作通过Bundle的writeToParcel（Parcel, int）向Activity传递对象，然后观察它。\n2：循环这个操作1000次。\n3：大概模拟10次，观察内存回收情况，以及app的cpu使用率，等等。\n4：这个被测试的对象分别是SerializableDeveloper和ParcelableDeveloper。\n5：在多种机型和版本上做测试\nLG Nexus 4 - Android 4.2.2 \nSamsung Nexus 10 - Android 4.2.2\nHTC Desire Z - Android 2.3.3\n\n测试结果：\n![这里写图片描述](http://img.blog.csdn.net/20160318115133497)\n\nNexus 10\n\nSerializable: 1.0004ms,  Parcelable: 0.0850ms - 10.16x improvement.\n\nNexus 4\n\nSerializable: 1.8539ms - Parcelable: 0.1824ms - 11.80x improvement.\n\nDesire Z\n\nSerializable: 5.1224ms - Parcelable: 0.2938ms - 17.36x improvement.\n\n分析：\nParcelable比Serializable速度快10倍，在Nexus 10测试过程中得到了有趣的结果，仅仅通过1毫秒就完成了整个的序列化与反序列化过程。\n\n总结：\n假如你想成为一个好的码农，找个时间替换成Parcelable吧，他将提高十倍的速度并且用更少的资源。\n\n然而，在大部分的案例里，这种缓慢的Serializable序列化过程并没有被注意到，有时候仅仅是为了成本比较低才会使用它但是要记住Serialization是要付出很大的代价的，所以还是尽量少使用这种方式。\n\n假如你想传递成百上千的序列化对象，整个序列化的过程可能会超过一秒，当你对手机屏幕进行横竖屏切换将感觉有点延迟。\n"
  },
  {
    "path": "issue-48/为什么在Android开发中我仍然不想使用Kotlin.md",
    "content": "#为什么在Android开发中我仍然不想使用Kotlin？\n---\n\n> * 原文链接 : [Why I don't want to use Kotlin for Android Development yet](http://artemzin.com/blog/why-i-dont-want-to-use-kotlin-for-android-development-yet/)\n* 原文作者 : [Artem Zinnatullin](http://artemzin.com/blog/author/artem/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [mr_dsw](https://github.com/dengshiwei) \n* 校对者:[chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n尽管Kotlin在很多方面比Java更好，但是在我看来它仍然有显著的缺点。\n\n>如果你有下面罗列问题的解决方案，就请你把它们看做为我个人意见和评论吧！\n\n**1）编译速度缓慢**    \n一个比较小的工程（共有100类左右，大部分采用Kotlin）花费1分钟进行编译，这是无法让人接受的。    \n[https://youtrack.jetbrains.com/issue/KT-6246](https://youtrack.jetbrains.com/issue/KT-6246)\n\n**2)Kotlin插件在IDEA编译器中的表现**  \n在编码过程中，语法分析和Kotlin在IDEA(Android Sutdio)中的高亮显示让开发机器经常卡顿，令人无法接受。\n\n**3)annotation（注解）处理有问题**\n有时它给出的错误提示太粗略，但你又不得不解决。几乎每天我都能在不同的Android开发社区上看到对此的抱怨。（这里resources应该是指不同的（信息）来源）\n\n**4)  通过Mockito模拟通过Kotlin创建的类是痛苦的**   \n在Kotlin中默认情况下，几乎所有的成员都是final类型,例如：classes, methods, 等等。而我确实喜欢因为它强制保持了不变性 ->减少bug。但是与此同时，它使）通过Mockito模拟通过Kotlin创建的类是痛苦的（一种JVM世界的黄金标准)同时它与语言设计相反。  \n\n是的，PowerMock是一个解决方案，但是它通过Robolectric这类工具进行交互，同时一般来说，它是一个不错的规则针对你模拟final classes和final method。\n\n我知道在Java开发中我们面对non-final的设计都会遇到那个问题，但是与此同时我不想仅仅为了测试去改变代码。\n\n**5) No static analyzers for Kotlin yet**  \n是的，Cotlinc比javac让代码更安全，如果你想在编译器上实现更好的体验，你又不想把它变成static analyzer(静态分析器)。\n\n静态代码非常适合CI，但是你可能不希望本地开发中每次点击run按钮都运行它。\n\nJava有：FindBugs, PMD, Checkstyle, Sonarqube, Error Prone, FB infer。\n\nKotlin有：kotlinc。\n\n>我认为以上观点都是客观的。下面的观点更倾向于主观的和个人看法。\n\n**6)==实现了Java的equals()方法提供的对象比较功能，而不是比较对象的引用是否相同**     \n如果Kotlin是更好的Java或\"Java on steroids\"，它应该变得更好，而不是去打破。\n\n想象一下你重写Java工程到Kotlin的过程，你会同时遇到Java和Kotlin代码。\n\n因开发语言不同你可能会阅读和编写相同的代码但是代码的功能效果不同。这也是我不喜欢Groovy的一个原因。\n \n**7)不正确的操作符重载可能会导致错误**   \n观点1：未来你需要处理使用Kotlin开发的久代码库。     \n观点2：你可以添加操作符来重载已存在的java类来扩展功能。\n\n想象一下，你需要处理类似val person3 = person1 + person2这类已经写好的代码。    \n\n每个你即将工作的项目，在一个相同的类中的操作符都有它独特的含义。\n\n操作符重载是有争议的，这些链接可以帮你决定（它们没有统一结论）：\n\n[Operator Overloading Considered Harmful](http://cafe.elharo.com/programming/operator-overloading-considered-harmful/)   \n[Operator Overloading Ad Absurdum](http://james-iry.blogspot.ru/2009/03/operator-overloading-ad-absurdum.html)     \n[Why Everyone Hates Operator Overloading](http://blog.jooq.org/2014/02/10/why-everyone-hates-operator-overloading/)   "
  },
  {
    "path": "issue-48/构建我的Presentation层.md",
    "content": "构建我的Presentation层\n---\n\n> * 原文链接 : [Modeling my presentation layer](http://panavtec.me/modeling-presentation-layer/)\n* 原文作者 : [panavtec](http://panavtec.me/author/christianpanadero/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n \n在构建domain层的文章之后，我们接下来说说构建presentation层。我之所以写这篇文章是因为看到有大量的工程从已有代码迁移到MVP结构时，对于什么该属于presentation层，什么该属于UI层的划分不清晰。我在之前的项目中看到一个评论：“我不能修改业务逻辑” 。如果你不能区分什么是属于domain层的，那么在分离责任时，你就会犯错，这是很糟糕的事情。你为什么要在不清楚最基本逻辑的情况下去试图创建一个清晰的结构。\n \n下面我会给出一些案列以及我是如何解决的。这篇文章主要是理清概念，你可以用多种方式去实现。\n\nAndroid view vs View vs Screen\n------------------------------\n \n- **Android view**：Android的组件，继承自android.view.view。\n- **View**：presenter和视图实现间的通讯接口，你可以在你喜欢的Android组件中实现它，有时使用Activity是个好的选择，有时则是Fragment或是一个自定义视图更合适。\n- **Screen**：界面更多是一个用户层面的概念，用户通过界面感知到正在不同窗口中切换，但是在Android中我们可以使用Activity来表示它，或者是在同一Activity下替换fragments/views。所以界面完全依赖于用户的感知，通常就是你能在视图中看到的所有内容。\n\n导航到不同的界面\n-------------\n \n两个界面间的切换可以是两个fragment之间，两个activity之间，打开一个dialog，打开一个activity等等。这是如何做到的属于实现的细节，是传递机制的责任。但是导致界面切换的行为呢？这个行为就是我们presentation层的责任。我们的presenter应该知道要**做什么**和我们的实现**如何**去做。这个情境中，要如何做呢？如何导航到另一个界面呢？打开一个新的activity，然后，问题来了，我之前说过我的presentation层是纯java的，所以我不能使用任何Android相关的东西，这如何实现呢？使用抽象。你可以创建一个只有navigate方法的NacigationCommand接口，在presenter需要时调用它，在视图层中实现它。假设我们有一个Activity A要在按钮点击后跳转Activity B，下面是序列图。\n\n![1](http://panavtec.me/wp-content/uploads/2016/02/navigation_command_sequence.png)\n\n代码是这样的：\n\n**Android module (View layer)**\n\n\tpublic class ActivityA extends Activity {\n\t\t@OnClick(R.id.someButton)\n \t\tpublic void onSomeButtonClicked() {\n    \t\tpresenter.onSomeButtonClicked();\n \t\t}\n \t}\n\n\tpublic class ToActivityB implements NavigationCommand {\n  \t\tprivate final Activity currentActivity;\n\n  \t\tpublic ToActivityB(Activity activity) {\n     \t\tcurrentActivity = activity;\n  \t\t}\n\n  \t\t@Override\n  \t\tpublic void navigate() {\n     \t\tcurrentActivity.startActivity();\n  \t\t}\n\t}\n\n**Java module (Presentation layer)**\n\n\tpublic interface NavigationCommand {\n \t\tpublic void navigate();\n\t}\n\n\tpublic class PresenterA {\n \t\tprivate final NavigationCommand toBNavigation;\n\n \t\tpublic PresenterA(NavigationCommand toBNavigation) {\n    \t\tthis.toBNavigation = toBNavigation;\n \t\t}\n\n \t\tpublic void onSomeButtonClicked() {\n    \t\ttoBNavigation.navigate();\n \t\t}\n\t}\n \n通过这种处理，我们解耦了不同层之间的责任。我们把导航到Activity B的操作提取成了一个可以在项目中复用的类。我们可以测试我们的presenter来调用导航命令，并且如果我们更改了界面的展现形式（例如从Activities转换为Fragments），我们的presentation层不需要改变。开闭原则万岁！\n\n另一个问题是如果你在一个presenter中有多个导航命令，那么构造函数的参数会变得很奇怪\n\n\tpublic class PresenterA {\n \t\tprivate final NavigationCommand toBNavigation;\n \t\tprivate final NavigationCommand toCNavigation;\n\n \t\tpublic PresenterA(NavigationCommand toBNavigation, NavigationCommand toCNavigation) {\n    \t\tthis.toBNavigation = toBNavigation;\n    \t\tthis.toCNavigation = toCNavigation;\n \t\t}\n\t}\n   \n实例化这个presenter时很难知道导航命令参数的顺序，你只能依靠命名来区分。但是我们可以定义一个继承自NavigationCommand接口的接口来表示导航的子类型。另一个解决方法是，如果你在使用依赖注入，那能不能实现一个[Qualifier](https://docs.oracle.com/javaee/6/tutorial/doc/gjbck.html)来指定你需要传递那种类型的参数。\n  \n某些场景下你可能需要导航到界面A或者界面B，那么我们可以修改NavigationCommand接口。\n\n\tpublic interface ToScreenBNavigationCommand extends NavigationCommand {\n \t\tvoid setMyParameterToNavigate(String parameter);\n\t}\n\n如果我们这么做，那么我们就可以在调用navigate方法前先设定好我们的目的地。\n \n这个想法来自于[Pedro](https://twitter.com/pedro_g_s)，他在自己的项目[EffetiveAndroidUI](https://github.com/pedrovgs/EffectiveAndroidUI/blob/1dadd276b094dffed2ae2e88602925c173ab59d7/app/src/main/java/com/github/pedrovgs/effectiveandroidui/ui/viewmodel/action/ActionCommand.java)中已经实现了.\n\n一个界面中有多个View接口\n-----------------\n\n有很多Android组件可作为视图元素，这都不是presenter要关心的。记住View接口就像是presenter使用的契约。我们的一个界面中可以有多个View接口吗？当然可以！我如何在一个Activity中拥有多个View接口/presenter呢？让我们来看看Spotify的浏览界面：\n\n![2](http://panavtec.me/wp-content/uploads/2016/02/Screenshot_2016-02-10-18-48-45.png)\n \n在我看来，这个界面有一个水平滚动的播放列表，然后是可选操作的菜单，底部是当前播放的歌曲。在这个界面中，我们可以清晰的有多个抽象，用我的方式来看这个界面，各个抽象之间是没有关联的。所以让我们来考虑一下为每个概念实现一个View接口/presenter。\n\n![3](http://panavtec.me/wp-content/uploads/2016/02/browse_colored.png)\n\n那么为什么创建那样的结构，这和创建一个Presenter包含所有的行为和自定义视图有什么区别？那么考虑一下谁负责填充视图，如果你打算重用这个组件你如何才能做到。RecommendedPlayLists可能需要访问网络来为当前用户获取推荐播放列表，那显然有必要为presenter创建一个自定义视图来实现它。这样就可以复用了。那么浏览菜单呢？这是另一个抽象了，他只是负责当你点击时导航到另一个界面（使用前面提到的navigation command！）最后，当前播放的歌曲也是一定会在其他地方复用的。\n\n如你所见，一个界面可以包含多个View接口/Presenter，因为界面是一个汇总，而且会有多个责任，这完全依赖于设计。(记住一个[责任](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html)就是一个变化的原因，这里的视图可能因为多种原因而变化。)\n\n一个视图可以有两种实现吗？\n---------------------\n\n当然！一个view接口可以有多种实现。比如在Spotify中，我展示给你的那个界面可以控制当前的播放和展示歌曲信息，但是我们点击这个区域的话，就会跳转：\n\n![4](http://panavtec.me/wp-content/uploads/2016/02/Screenshot_2016-02-10-21-24-51.png)\n \n这两者不就是使用了不同的展示方式吗。所以也许我们可以复用presenter，使用不同的Android组件来实现view接口。但是，这个界面似乎有些额外的新功能，那应不应该也添加到这个presenter中呢？根据你的实际使用情况有不同的做法：你可以用不同的行为组合成一个presenter；使用两个不同的presenter，一个用来对应行为，一个对应presentation；或者直接使用一个presenter处理所有。记住，没有完美的解决方案，软件就是要取舍。\n \n但是，这确实不是常见情况，通常一个Presenter只有一个view接口实现。\n\nMVP组成\n------\n\n总结下目前我们都经历了什么\n\n- view接口实现和presenter一对一\n- 一个界面拥有多个views/presenters\n- 一个view接口可以有多重实现方式来对应同一个presenter\n- 一个Android组件可以有一个视图接口实现。如果你实现了两个，那么也许是两个视图接口应该合并或者你应该分离开实现。\n\n让我们再来看看其他的关键点\n\nPresenter生命周期\n----------------\n\n下面是一张来自软件Citymapper的截图，当你点击“Get me somewhere”按钮后，会展示一个可以选择起点和终点的界面：\n\n![5](http://panavtec.me/wp-content/uploads/2016/02/citymapper_getmesomewhere.png)\n\n你如何分解这个界面？我首先想到的是，开始位置在没有结束位置时是否还有意义？我认为没有。我会创建一个“PickLocation” presenter，它可以获知何时开始和结束位置被填写了。一个Activity内部包含一个有两个fragment的view pager可以满足视图层的实现。两个fragment都可以接触到同一个presenter并且调用“presenter.startLocationChanged()”和“presenter.endLocationChanged()”。\n\n如果设计改变了怎么办？我们把两个tab换成了更适合的两步表单。然后我们就需要在开始位置fragment和结束位置fragment间切换。我们的视图层变了，但是presenter仍然是同一个，另外，我们也可以考虑在一个界面上展示两个地图，顶部展示开始位置，底部则是结束位置，这仍然是和之前相同的，变化的是视图层的实现，而不是我们的presenter。\n\n那么我的presenter的生命周期是什么样的？这依赖于我们实现视图层使用的组件。\n\n要解释生命周期，让我们来看一下[Selltag\napp](https://translate.google.com/translate?sl=es&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=http%3A%2F%2Fwww.fesja.me%2Fgracias-selltag%2F)，一个二手交易应用。如图是我们如何创建一个商品：\n\n![6](http://panavtec.me/wp-content/uploads/2016/02/Nuevo-Anuncio-Paso-1.png)\n![7](http://panavtec.me/wp-content/uploads/2016/02/Nuevo-Anuncio-Paso-2-con-varias-fotos.png)\n![8](http://panavtec.me/wp-content/uploads/2016/02/2016-02-11.png)\n\n如你所见是一个3步表单。忽视西班牙语。我想还是挺清晰的，“Siguiente”表示下一步，“Publicar”表示发布。\n\n第一步，你为商品拍照，第二部，你填写名称，描述和价格，最后你发布它。\n\n我的实现思路依然是使用一个presenter-“PublishProductPresenter”。这个presenter代表着完整的发布商品流程。在平板上又会是什么样的？也许这三步会整合到一起因为你的屏幕足够大。那如果是一个web应用呢？会不会因为你看到了不同的设计就认为是不同的实现方式呢？其实我们只需要改变视图层，presentation层则是同一个，因为它只对用户事件进行响应然后调用dmain层查询。\n\n但是！我可以把他拆成3个presenter来使用，和你使用一个presenter是同样的方式。好吧，也许你是对的，但是你如何从把信息从第一步传递到发布商品的最后一步？你要在一个presenter中持有另一个presenter的引用？也许你会创建一个共享的对象来记录每一步更改的属性？这些听起来很危险并且难以debug在你出错的时候。\n\n你可以创建一个拥有3个fragment的activity或者3个activity来实现视图层。\n\nPresenter状态\n------------\n\n如果屏幕方向发生变化了怎么办？你的activity会被销毁，presenter同样。问题大部分是关于presenter是否该有状态或者是因为它不应该属于domain层，下面的例子我会介绍：\n\n![9](http://panavtec.me/wp-content/uploads/2016/02/fdroid.png)\n\n这是F-Droid Android版，一个只包含开源应用的开源应用市场。展示的数据通过网络请求来刷新。想象一下，如果我们旋转屏幕，我们的presenter是无状态的那么列表会重新加载。你如何解决？其实很简单，创建一个内存缓存或者磁盘缓存来存储之前获得的数据，并且设定好失效时间。但是永远不要把获取的结果存在presenter中，因为当你需要重新创建presenter的时候，你需要保持对那些数据的引用来避免再次请求数据。总之，我总是让presenter无状态。\n\n回调地狱\n-------\n\n回调地狱是人们谈论presentation层时经常讨论的话题。我看到大部分关于回调地狱的讨论都是因为我们让presenter做了太多的事情。记住协调domain的行为不是presentation的责任，我们的presentation层应该只是调用domain的行为，这些行为应该是异步处理的，让我们的presentation层尽可能简单。你可以查看我之前的文章[modeling my domain\nlayer](http://panavtec.me/modeling-my-android-domain-layer/).在你添加例如[RxJava](https://github.com/ReactiveX/RxJava)和[Jdeferred](https://github.com/jdeferred/jdeferred)这样的库之前，想想是你需要那些工具还是只有使用那些工具才能修补你设计上的错误。\n\n为了说明这点我做了一个列子。想象一下如果从服务器获取的某个表示为true时，你需要展示一个列表。第一种处理主要有两个错误，第一是你的presenter不需要知道这个和domain层有关的标识。第二是，糟糕的组织协调导致我们需要些更多的代码来处理异步：\n\n![10](http://panavtec.me/wp-content/uploads/2016/02/diagram_2_.png)\n \n与其在presenter中处理，不如这样做：\n\n![11](http://panavtec.me/wp-content/uploads/2016/02/diagram.png)\n\n如你所见，现在我们没有任何问题了，行为调用也很清晰。另外，如果这个标识不需要了，我只需要更改我的行为。\n\n结论\n----\n\n构建presentation层是很简单的，但是你需要确定什么属于这一层，哪些责任是domain层的。当你有一个庞大的presenter时，问问自己这个界面是不是有如此多的用户操作需要处理或者你在presenter中做了domain层的行为，等等......"
  },
  {
    "path": "issue-49/Android上的网络响应日志技巧.md",
    "content": "# Android上的网络响应日志技巧\n----\n\n> * 原文链接 : [Effortless network response logging on Android](https://medium.com/@simonpercic/effortless-network-response-logging-on-android-cedf0ebbdae1#.b2in23bvt)\n* 原文作者 : [Simon Percic](https://medium.com/@simonpercic)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [Mijack](http://mijack.github.io)\n* 校对者: [Mijack](http://mijack.github.io)\n* 状态 :  完成 \n\n在开发Android应用程序的过程中，你或许需要从远处服务器中加载数据。而在开发过程中，你可能要经常查看应用从网络中获取到的数据的内容。\n\n如果你最近几年在开发Android应用，或许你使用过（或者听说过）[Retrofit](https://github.com/square/retrofit)来处理网络请求。如果没有，我建议你了解一下，因为他实在太棒了。\n\n所以，使用Retrofit时，在监听网络请求方面，你有哪些选择呢？\n\n## Retrofit / OkHttp的logging\n\nRetrofit 和 OkHttp 有一些内置的记录HTTP响应的方法。使用这些功能的方法取决于你使用的Retrofit的具体版本。\n\n## `Retrofit 1.x`\n\n如果你使用的是比较老的Retrofit 1.x版本。在创建RestAdapter时，你可以通过在RestAdapter.Buidler中设置LogLevel的属性直接开启响应日志这一功能。\n\n```java\nLogLevel logLevel = LogLevel.FULL;\nnew RestAdapter.Builder()\n        .setClient(client)\n        .setEndpoint(endpoint)\n        .setConverter(converter)\n        .setErrorHandler(errorHandler)\n        .setLogLevel(logLevel)\n        .build();\n```\n\nLegLevel是一个表示日志细节的枚举类型，值有NONE, BASIC, HEADERS, HEADERS_AND_ARGS 和 FULL。你可以根据需要打印的每一个网络请求的内容设置对应的值。\n\n## `Retrofit 2.x`\n[Retrofit 2.0的稳定版](https://twitter.com/JakeWharton/status/708401516063891456)在16年三月上旬已经发布，变化很大。如果你还没有升级它,我建议你​​升级吧：\n\n> [Retrofit 2.0与RxJava混合使用的快速入门](https://medium.com/p/ab7a11bc17df)\n您可能已经注意到Retrofit 2.0最终版本已经出来了，所以，还等什么，现在去升级吧！\n\n你升级以后，会发现Retrofit发生了很多变化。比如，你无法直接通过类Retrofit.Builder设置Log Level.\n\nRetrofit 2.x直接依赖于Square的另一个库（OkHttp3）进行HTTP的实际网络调用。与此不同的是，Retrofit 以前的版本(1.x)并没有直接依赖于OkHttp，因而你可以使用不同的HTTP客户端，只要它们实施了Retrofit的客户端接口。\n\n因此，**日志功能已经从Retrofit更改到OkHttp中去了**。\n\n现在，您可以调用OkHttp的拦截器 [HttpLoggingInterceptor]（https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor）实现和Retrofit 1.x一样的日志功能。\n\n如果要使用它，您必须先在Gradle中声明，因为它以独立的形式进行分发：\n\n```\ncompile 'com.squareup.okhttp3:logging-interceptor:<latest>'\n```\n然后将日志记录拦截器添加到OkHttp中：\n```\nLevel logLevel = Level.BODY;\nHttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();\ninterceptor.setLevel(logLevel);\nnew OkHttpClient.Builder()\n        .addInterceptor(interceptor)\n        .build();\n```\n##'日志的输出效果是什么样子？`\n\n网络响应的body以纯文本的形式出现在Logcat中。日志通常被分隔成了若干行，往往难以阅读：\n![](https://cdn-images-1.medium.com/max/800/1*OXQRErwU5UW-2IiiK56U8Q.png)\n`Retrofit 1.x / HttpLoggingInterceptor logcat output`\n\n通常,你需要拷贝整行的响应，然后再取出开头的时间戳，包名称以及标记。如果你遇到的JSON响应，您可以使用像[JsonFormatter]（https://jsonformatter.curiousconcept.com/）或[在线JSON查看器（http://jsonviewer.stack.hu/），提高复制的文本可读性。\n\n>对于我来说，每次检查网络请求的过程都十分繁琐、耗时。这就是我决定创建自己的拦截器的原因——这可以简化流程。\n\n## OkLog - 检查响应的快捷之道\n\n[OkLog]（https://github.com/simonpercic/OkLog）是针对OkHttp 网络响应的日志记录拦截器，可以在开发过程中简化了网络响应的调试。\n\nOKLog有两个独立的库，取决于OkHttp的版本：OkLog对应于OkHttp，OkLog3对应于OkHttp3。\n\nOkLog写了一个可访问的URL，可将网络响应作为URL路径的一部分进行日志记录。\n\n这样一来，您就可以点击Android Studio中的日志显示一个链接，你在浏览器中查看这个响应对应文本：\n\n![](https://cdn-images-1.medium.com/max/800/1*o7CdnntHqZ0zqHYXOBKNfQ.gif)\nOkLog 使用示例\n\n另一个好处是能够与其他开发者（通常是REST API的开发人员）分享这些URL。\n## 它是如何运作的？\n\nOkLog通过实现自己的[“应用程序”interceptor]（https://github.com/square/okhttp/wiki/Interceptors）截获OkHttp的纯网络响应。\n\n在得到响应的纯文本之后，将它转换对url友好的字符串，生成包含响应的URL。\n\n然后，该URL作为日志打印出来。OkLog可以选择依赖于Timber，只有你的项目依赖于它时，它才发挥作用。否则，它将调用Android内置的Log方法进行日志记录（当然，你也可以强制它使用内置Log方法）。如果你愿意的话，甚至可以定制[LogInterceptor](https://github.com/simonpercic/OkLog/blob/master/core/src/main/java/com/github/simonpercic/oklog/core/LogInterceptor.java)自定义日志记录。\n\n**那么，究竟响应需要如何转化呢？**\n\n纯文本响应首先要经过[gzip压缩]（https://en.wikipedia.org/wiki/Gzip），以使该字符串尽可能短。然后，将所得的字符串进行[Base64]（https://en.wikipedia.org/wiki/Base64）编码，从而可以 以使其网址友好。\n\n#点击URL时会发生什么？\n\n默认情况下，生成的URL指向一个Spring Web应用程序的托管实例，称为[ResponseEcho]（https://github.com/simonpercic/ResponseEcho）。该应用程序的工作，刚好和OKLog相反，对URL路径字符串参数进行Base64解码和对参数进行gzip解压，并将其作为一个普通的响应返回。\n\n如果普通的响应恰好是一个JSON，Web应用程序应该返回格式规整的JSON，这样更容易阅读。\n\n如果你愿意，你也可以自己搭建web应用程序，并设置OkLog匹配主机名网站的前缀。\n\n## 如何使用它？\n\n关于使用的具体流程请查看Github上的[Readme](https://github.com/simonpercic/OkLog#usage)，基本步骤如下：\n\n* 首先添加当前的OKLog的版本.\n```\n// pre-OkHttp3\ncompile 'com.github.simonpercic:oklog:<latest>'\n// OkHttp3\ncompile 'com.github.simonpercic:oklog3:<latest>'\n```\n* 使用 OkLogInterceptor的builder()方法构造一个实例\n```\nOkLogInterceptor interceptor = OkLogInterceptor.builder()\n        // set desired custom options\n        .build();\n```\n* 在OKHttp的 interceptors中添加一个 okLogInterceptor实例\n```\n// for pre-OkHttp3\nList<Interceptor> clientInterceptors = okHttpClient.interceptors();\nCollections.addAll(clientInterceptors, okLogInterceptor);\n// for OkHttp3\nnew OkHttpClient.Builder().addInterceptor(okLogInterceptor).build();\n```\n* 通常，你可以通过 okHttpClient 实例来构造 Retrofit/2 实例。\n\n## 已知的限制\nOKLog是通过Android的Logging系统实现日志功能的，但Logging有[约4000字符的长度限制](http://stackoverflow.com/a/8899735)。\n\n即使生成的url通过gzip压缩和Base64编码，最后在一些网络响应中，他们仍可能 **超出日志的行限制**。\n\n不幸的是，目前还没有针对性的解决方案。不过，大多数情况下，一切都是正常的。\n\n在升级的实际记录过程中，OkLog可以选择[Timber]（https://github.com/JakeWharton/timber），Timber可以对一些过长的日志进行切片分割，所以你还可以看到那些超过长度限制的响应。如果，它被分割成若干行，你可以把它们链接起来从而得到完整的url。\n\n这样会将太长线，所以你可以看到，如果一个反应是超过长度限制。如果它被分成多行，可以手动串接所有行，以获得有效的URL。\n\n## 另一种实现方式 - Facebook的Stetho\n\n和上述方案不同的是，[Facebook’s Stetho](http://facebook.github.io/stetho/) 使用的是另一种方案来记录网络请求。\n\n不同于直接在logcat中直接打印请求，Stetho 的运行依赖于OkHttp/3的网络连接器 interceptor。\n\n在这种方式下，Stetho通过Chrome Developer Tools调试网络请求。\n\n![Chrome Developer Tools through Stetho](https://cdn-images-1.medium.com/max/800/1*vs-fNOCQ7DzQMpoyekxWZA.png)\n\nChrome Developer Tools是一个十分棒的工具，他可以让你看见和网络活动相关的诸多信息。\n\n但是，如果你只是希望可以快速的查看网络请求，他可能不适合你。除此以外,你无法快捷地和别人共享这些信息，除非你复制这些信息，\n\n## 你应该选择哪一个呢？\n\n我觉得并没有一个放之四海而皆准的标准，相反，我相信，不同的场景有着不同的解决方案，上述的每一种方案都有对应的最佳应用场景。\n\n在我的项目中，我使用OKHttp的 HttpLoggingInterceptor，它可以在logcat中直接输出和请求相关的基本信息。同时，我也使用OKLog，他可以让我在浏览器中快速查看对应的网络请求，也可以和我的同事共享这些信息。\n\n\n注意,你只能在你的debug版本中，[不要在release版本中](http://developer.android.com/tools/publishing/preparing.html#publishing-configure)，打印网络响应。[在release版本中，你不应该输出任何日志](https://github.com/JakeWharton/timber/blame/a6b2e220a33da939da181de37a5cd68be843f63a/README.md#L16-L17)。\n\n这篇文章有趣不？记得在[Medium](https://medium.com/@simonpercic), [Twitter](https://twitter.com/simonpercic) 或 [GitHub](https://github.com/simonpercic)关注我哦。\n"
  },
  {
    "path": "issue-49/AutoValue简介.md",
    "content": "AutoValue简介\n---\n\n> * 原文链接 : [An Introduction to AutoValue](http://ryanharter.com/blog/2016/03/22/autovalue/)\n* 原文作者 : [Ryan Harter](http://ryanharter.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: \n* 状态 :  完成 \n\n\n\n不得不说，Java 的数据类型一致很单一，除了提供的九种基本数据类型，任何你想设计的数据类型都要通过创建类来完成。但 Google 新发布的 [AutoValue](https://github.com/google/auto/) 库似乎能改变这个困境，在最近的更新中，AutoValue 给该库增加了可拓展性。\n\n##Java 中的值类型\n\n在介绍 AutoValue 的优点之前，不妨先看看我们真正遇到的问题：数据类型。\n\n简单来说，数据类型就是一种不可变对象，且该对象能表达的内容基于对象的属性值描述，与身份相对。下面我创建一个 Money 对象来解释吧：\n\n```java\npublic class Money {\n  public Currency currency;\n  public long amount;\n}\n```\n\nMoney 是一个简单的数据类型，它含有两个属性，amount 和 currency。如果我们创建了两个 money 对象，它们的 amount 和 currency 值是相等的，那么在不考虑对象引用地址不同的情况下，我们可以把这两个对象看作相等的。\n\n虽说这很直观，但要在 Java 中实现却不容易。数据类型应该是不可变的，但我们当前的 Money 对象却没有实现这一点，因此下面把代码更新为：\n\n```java\npublic final class Money {\n  private Currency currency;\n  private long amount;\n  public Money(Currency currency, long amount) {\n    this.currency = currency;\n    this.amount = amount;\n  }\n  public Currency currency() {\n    return currency;\n  }\n  public long amount() {\n    return amount;\n  }\n}```\n\n此时，Money 类为 final 类，那么我们将不能创建它的子类（保证相等性），将数据域设置为 private，并通过 getter 获得值，添加构造器使用户能创建相应的 Money 对象。你可以看到，此时 Money 类的代码从 4 行变为 14 行。\n\n你以为这就完了吗？此时判断对象是否相等还是比较它们的引用（而不是比较属性）。这就意味着 $2 != $2 情况的发生。因此我们需要实现 equals() 和 hashCode() 方法，不然我们没法在 Set 或 Map（作为键） 里正常使用这个对象。\n\n```java\npublic final class Money {\n  private Currency currency;\n  private long amount;\n  public Money(Currency currency, long amount) {\n    this.currency = currency;\n    this.amount = amount;\n  }\n  public Currency currency() {\n    return currency;\n  }\n  public long amount() {\n    return amount;\n  }\n  @Override public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    Money money = (Money) o;\n    if (Double.compare(money.amount, amount) != 0) return false;\n    return currency.equals(money.currency);\n  }\n  @Override public int hashCode() {\n    int result;\n    long temp;\n    result = currency.hashCode();\n    result = 31 * result + (int) (amount ^ (amount >>> 32));\n    return result;\n  }\n}```\n\n添加了 equals() 和 hashCode() 方法后，代码变为 29 行。\n\n那如果我们想在 Log 里面了解这个对象的相关信息呢？我相信每一个程序员都希望看到 Money@12CE469 以外的一些相关性更高的 Log，因此，我们还需要添加 toString() 方法。\n\n```java\npublic final class Money {\n  private Currency currency;\n  private long amount;\n  public Money(Currency currency, long amount) {\n    this.currency = currency;\n    this.amount = amount;\n  }\n  public Currency currency() {\n    return currency;\n  }\n  public long amount() {\n    return amount;\n  }\n  @Override public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    Money money = (Money) o;\n    if (money.amount != amount) return false;\n    return currency.equals(money.currency);\n  }\n  @Override public int hashCode() {\n    int result;\n    long temp;\n    result = currency.hashCode();\n    result = 31 * result + (int) (amount ^ (amount >>> 32));\n    return result;\n  }\n  @Override public String toString() {\n    return \"Money{\" +\n        \"currency=\" + currency +\n        \", amount=\" + amount +\n        '}';\n  }\n}```\n\n此时，代码变为 34 行。如果你有看过一些有关软件工程书，你可能会听过这样一个结论“随代码行数的增加，Bug 出现的可能性也会增加”，于是你会不会想要简化你的代码呢？或者通过其他办法去优化？\n\n##AutoValue 来拯救你\n\n上面提到的问题就是 AutoValue 被开发的原因，AutoValue 是用于为你生成所有寻常的数据类型代码的注解处理器，使你能更多地关注其他更重要的事情。\n\n使用 AutoValue 需要在工程的注解处理器的 classpath 中添加依赖。在注解处理器的 classpath 中添加依赖的好处是，AutoValue 不会被添加到你的项目中（编译完成后），但由它生成的代码被添加到项目中了。\n\n```java\ndependencies {\n  apt 'com.google.auto.value:auto-value:1.2-rc1'\n}\n```\n\n同样是创建 Money 数据类型，使用 AutoValue 你只需要添加注解到抽象的 Money 类中：\n\n```java\n@AutoValue public abstract class Money {\n  public abstract Currency currency();\n  public abstract long amount();\n}```\n\n之后，AutoValue 就会为你生成所有私有数据域，构造器，hashCode()、equals() 和 toString() 方法，由 AutoValue 生成的类的类名将由 AutoValue_ 与你原来的类名组成，但 package 是 private，所以使用应用的用户并没有和使用注解的 Money 类交互。\n\n```java\n@AutoValue public abstract class Money {\n  public abstract Currency currency();\n  public abstract long amount();\n  public static Money create(Currency currency, long amount) {\n    return new AutoValue_Money(currency, amount);\n  }\n}```\n\n上面的代码就完成了前面的例子的功能，但只需要 7 行代码。\n\nAutoValue 的一个特性是：你能在类中添加任何你想要的额外的代码。因此，如果你有什么派生域，它们也能被添加到 Money 类中，而不需要额外的 helper 类。\n\n```java\n@AutoValue public abstract class Money {\n  public abstract Currency currency();\n  public abstract long amount();\n  public static Money create(Currency currency, long amount) {\n    return new AutoValue_Money(currency, amount);\n  }\n  public String displayString() {\n    return currency().symbol() + amount();\n  }\n}```\n\n##测试：隐藏的优点\n\n自动生成代码的一个好处是：不需要测试。然而在纯 Java 实现版的例子中我们需要添加测试以保证功能的正确性，但 AutoValue 却不需要这样，因为生成器本身就会被测试，并用于生成正确的代码，我们不需要测试那些模板代码。\n\n###拓展\n\n现在我们能轻易地创建数据类型了，那如果你想将生成的数据类型用到其他系统呢，例如 JSON 串行器或 Android Parcel 类？这就是拓展的作用了。\n\n在 AutoValue 1.2-rc1 版本中，AutoValue 支持拓展，意味着你能得到额外的功能，大多数情况下只能给注解处理器的 classpath 添加依赖。\n\n举个例子，例如你想让 Money 对象变为可序列化，那么你可以给 AutoValue 添加 Parcel 拓展到注解处理器的 classpath 中，并让类实现 Parcelable 接口，那么生成的代码也将是可序列化的：\n\n```java\ndependencies {\n  provided 'com.google.auto.value:auto-value:1.2-rc1' // needed for Android Studio\n  apt 'com.google.auto.value:auto-value:1.2-rc1'\n  apt 'com.ryanharter.auto.value:auto-value-parcel:0.2.0'\n}```\n\n```java\n@AutoValue public abstract class Money implements Parcelable {\n  public abstract Currency currency();\n  public abstract long amount();\n  public static Money create(Currency currency, long amount) {\n    return new AutoValue_Money(currency, amount);\n  }\n  public String displayString() {\n    return currency().symbol() + amount();\n  }\n}```\n\n相信这个新特性能减轻你的部分工作压力，不需要写过多的序列化模板代码，测试代码，优化代码。\n\n##结论\n\n现在已经有好几个 AutoValue 拓展可用了，都能提高你的工作效率，我列出其中一部分吧：\n\nAutoValue: [Gson Extension](https://github.com/rharter/auto-value-gson)\nAutoValue: [Moshi Extension](https://github.com/rharter/auto-value-moshi)\nAutoValue: [Cursor Extension](https://github.com/gabrielittner/auto-value-cursor)\nAutoValue: [With Extension](https://github.com/gabrielittner/auto-value-with)\nAutoValue: [Redacted Extension](https://github.com/square/auto-value-redacted)\n\n如果它们不能满足你需求的话，你可以参考它们创建你自己的版本。\n\n接下来我将研究拓展特性的 API，研究我们怎样才可以创建自己的 AutoValue 拓展，同时研究上面提到的拓展的实现细节。"
  },
  {
    "path": "issue-49/Jack&Jill的缺点.md",
    "content": "The dark side of Jack and Jill\n---\n\n> * 原文链接 : [The dark side of Jack and Jill](http://trickyandroid.com/the-dark-world-of-jack-and-jill/)\n* 原文作者 : [Tricky Android](http://tutsplus.com/authors/paul-trebilcox-ruiz)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: \n* 状态 :  完成 \n\n\n\n去年 Google 发布了新的开发工具链 - Jack (Java Android Compiler Kit) 和 Jill (Jack Intermediate Library Linker)，希望能用它们代替已有的 javac + dx 管道。\n\n本文将整理我对新工具链的想法和关注点。\n\n但在研究 Jack&Jill 机制之前，我想先给大家介绍现有的编译工具链是如何帮助你编译 Android 项目的。\n\n##Android code compilation 101\n\n老实说，我不会详述整个构建过程 - 而是把注意力放在与本文相关度较高的部分 - 将 Java 代码转化为 dex 文件。\n\n自计算机科学产生以来，程序的编译过程始终如下：\n\n![](http://trickyandroid.com/content/images/2016/03/javac-4.png)\n\n所以我们需要关注的是：如何将纯 Java 代码转换为设备 JVM 可理解的可执行指令。\n\n对纯 Java 应用（不是 Android）来说，这一点只通过 Java 编译器（javac）就可以完成，它会帮我们将 Java 代码编译为 Java 字节码（以 *.clcass 文件的形式存在），而常规的 JVM 都能理解和执行这些 Java 字节码，使得你的 Java 应用能在你的设备上运行。\n\n但问题是，Android 系统上使用的 JVM 不是标准的 JVM，而是为了适应移动设备的硬件环境，对其修改后的高度优化版本。Android 上的 JVM 也被称为 Dalvik/ART。\n\n由于 JVM 被修改了，Java 字节码也需要进行修改，要不然 Dalvik 无法理解和执行相应的指令。这也是 dx 工具的作用 - 得到 Java 字节码（*.class 文件）并将它转换为 Android 环境友好的字节码（*.dx 文件）。\n\n有趣的是：当你在项目中使用第三方库 - 该库会以 jar 或 arr 的形式被调用 - 但其实这些文件解压以后都是一堆 *.class 文件，使得这些第三方库可以直接交给 dx 工具处理，而不需要我们通过编译流程将它们转换为字节码。\n\n##Bytecode manipulation\n\n随时间推移，Android 开发者对 Android 开发的理解越来越深，也因此开发了许多酷炫的工具和插件以在 Java 字节码层增强代码的性能（例如字节码操作）。\n\n你听说过最有名的工具可能有：\n\n- Proguard\n- Jacoco coverage\n- Retrolambda\n- ...\n\n这些工具使我们能延后处理代码而且没有对原始代码作任何的更改。例如：Proguard 能分析 Java 字节码，并将没有使用的部分移除（最小化字节码）；Retrolambda 用匿名内部类替代 Java8 的 lambdas，使不支持 Java8 特性的 Android VM 能使用 lambdas。\n\n![](http://trickyandroid.com/content/images/2016/03/transform1-1.png)\n\n每个类（它的字节码）都被字节码操作插件处理，并将处理后的字节码交给 dx 工具产生最终的字节码。\n\n##Transform API\n由于类似的工具越来越多，Android Gradle 构建系统不能良好支持字节码操作的缺点就变得格外显眼 - 解决方案也只有通过 Android Gradle 插件将 Java 字节码转换完毕和交给 dx 工具处理之间给已存在的 Gradle 任务添加 Gradle 任务依赖。该任务的名称是一个实现的细节，它基于项目的配置动态产生，而 Google 在 Android Gradle 插件更新的过程中不断修改它。这也使得该问题在不同版本的 Android 插件上出现。\n\n因此，Google 需要采取行动了，而他们给出的解决方案是 - Transition API - 允许开发者添加 Transformer 的简单 API - 在构建过程中会在合适的时间被调用的类。该 API 的输入是 Java 字节码，它允许插件开发者使用更可靠的方式操作字节码，并停止使用没有文档的私有 API。\n\n##Jack & Jill\n与此同时，另一群 Googler 开发者正忙着开发新的，振奋人心的工具 - Jack&Jill！\n\nJack - 是与 javac 类似的编译器，但它完成的工作和 javac 稍微有些不同：\n\n![](http://trickyandroid.com/content/images/2016/03/jack1.png)\n\n如你所见，Jack 直接将 Java 代码编译为 Dex 文件！如果我们没有中间的 *.class 文件需要编译，就不需要使用 dx 工具了！\n\n但如果我的项目使用了第三方库呢（里面包含了许多 .class 文件）？\n\n别慌，还有 Jill 呢：\n\n![](http://trickyandroid.com/content/images/2016/03/jyll2.png)\n\nJill 能处理类文件，并将它们转换为 Jack 编译器能接受的特定的输入格式 Jayce。\n\n那引入 Jack&Jill 后，我们喜欢使用的插件会怎样呢？它们都需要 .class 文件，但 Jack 编译器又没有这些文件……\n\n幸运的是，Jack 提供了其他的特性一定程度上解决了这些问题：\n\n但下面的问题还是需要注意的：\n\n- Jack 不支持 Transform API - 由于没有中间 Java 字节码可以修改，所以我没有提到的插件都不能使用。\n- Jack 现在不支持注解的处理，所以如果你的项目重度依赖类似 Dagger，AutoValue，等等第三方库的话，使用 Jack 是你值得再三深思的问题。EDIT：正如 Jake Wharton 指出，Jack 在 Android N Preview 版本支持注解的处理，但该特性还不能在 Gradle 上使用。\n- 能在 Java 字节码层次操作的 Lint 检测器不被支持。Jack 现在效率比 javac + dx 要慢。但我发现 Jacoco 也有些问题（因为它的工作没有和你的期望一致），所以没有它也没有问题。\n- Dexguard - 企业级 Proguard 现在还不支持\n\n我认识到我刚刚提到的问题都是暂时的，Google 也在努力标记、解决这些问题，但不幸的是，当 Android 开发者开始认识到将项目使用的工具链切换为新的工具链需要的开销有多大时，Android 支持 Java8 所有特性的兴奋会很快消失。\n\nJack 确实是 Android 开发新的一页，它给 Google 的构建管道提供了更多的控制和可伸缩性，但同时它要想在 Android 开发者中获得名气，还有很长的一段路要走。\n\n1. AAR 准确来说不仅仅是 JAR - 它还包含了 Android 相关的数据，例如 assets, 资源, 和其他 JAR 不支持的数据\n\n2. 运行在最新的 Android N 设备上的 Android VM 支持部分 Java8 的指令\n\n3. Jack&Jill 的[图片来源在这](http://betterthanvoodoo.com/2011/11/14/reviewing-the-reviews-armond-white-loves-jack-jill/)"
  },
  {
    "path": "issue-49/为什么在Android使用ClassLoader.getResourceAsStream会如此影响性能.md",
    "content": "为什么在Android使用ClassLoader.getResourceAsStream会如此影响性能\n---\n\n> * 原文链接 : [Why Is ClassLoader.getResourceAsStream So Slow in Android?](http://blog.nimbledroid.com/2016/04/06/slow-ClassLoader.getResourceAsStream.html)\n* 原文作者 : [Anton Krasov | Sarvar Dhillon](http://blog.nimbledroid.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: \n* 状态 :  完成 \n\n\n\n\n通过对 NimbleDroid 进行各种各样的分析，我们找到一些可能会让 Android 应用性能，应用启动速度和响应时间受到较大负面影响的陷阱。而 ClassLoader.getResourceAsStream() 就是其中之一，该方法允许应用访问给定名称的对应资源。在 Java 开发中，这个方法很常用，但它在 Android 开发中真的不推荐使用，因为该方法会在应用首次调用它时对性能造成很大的负面影响。我们分析了大量的 App 和 SDK，结果显示：超过 10% 的 App 和 20% 的 SDK 都因为这个方法性能显著下降。这里面到底发生了什么呢？想知道的话就向下读吧！\n\n##具体例子\n\nAmazon 的 Kindle App Android 端，作为拥有数百万下载数的超级应用，却因为这个方法的使用造成了 1315ms 的延迟。（分析版本是 Kindle 4.15.0.48）\n\nTuneIn 13.6.1 则是 1447ms 的延迟，在 TuneIn 中，应用调用了该方法两次，但第二次调用的速度显然快多了（6ms）。\n\n下面是因为该方法性能受到影响的应用：\n\n![](http://img.my.csdn.net/uploads/201604/15/1460704256_2235.png)\n\n重要的事情说三遍：\n\n在我们分析的应用中，超过 10% 的应用都遇到了这个问题。\n在我们分析的应用中，超过 10% 的应用都遇到了这个问题。\n在我们分析的应用中，超过 10% 的应用都遇到了这个问题。\n\n##调用 getResourceAsStream 方法的 SDK\n\n为简短起见，我们用与某些确切的 Service 关联的库（例如 Amazon 的 AWS）和那些没有的库（例如 Joda-Time）的 SDK 去参考该方法对性能的影响。\n\n通常情况下，开发者不会直接调用 getResourceAsStream 方法，相反，该方法是由开发者使用的部分 SDK 调用的。由于开发者一般不会在意 SDK 的内部实现，因此他们也很难注意到他们开发的应用会存在这样的问题。\n\n下面我列出部分调用了 getResourceAsStream 方法的常用 SDK：\n\n- mobileCore\n- SLF4J\n- StartApp\n- Joda-Time\n- TapJoy\n- Google Dependency Injection\n- BugSense\n- RoboGuice\n- OrmLite\n- Appnext\n- Apache log4j\n- Twitter4J\n- Appcelerator Titanium\n- LibPhoneNumbers (Google)\n- Amazon AWS\n\n总的来说，在我们分析的 SDK 中，超过 20% 的 SDK 都存在这个问题 - 上面这张列表只列出其中一小部分 SDK，因为全列出来的话文章会变得很长很长很长……那为什么 getResourceAsStream 方法会在这么多 SDK 中被调用？它有什么特别的优势么？答案是：getResourceAsStream 方法在 Android 以外的平台使用时性能表现都很不错，然而在 Android 中并不是这样。因此，由于许多 Android 程序员都是 Java 程序员转行的，而且这些人在开发时更倾向使用一些以前用过的库，因此他们开发出来的 Android 应用/库就受到此问题的影响了。\n\n##为什么 getResourceAsStream 在 Android 中性能表现如此糟糕？\n\n那么很多人可能会问了，为什么这个方法到了 Android 就显得如此水土不服？我们团队在进行深入调查后发现，当它在 Android 中第一次被调用，Android 执行了三个非常慢的操作：\n\n1. 将 APK 文件作为 zip 文件打开，并索引所有 zip 表项\n2. 再一次执行 1 的操作\n3. 验证 APK 签名是否符合规范\n\n不得不说，这些操作都是非常非常非常耗时的，而且需要的时间与应用的大小正相关。例如，20MB 的 APK 会有 1-2 秒的延迟，我们会利用 Appendix 更好地描述我们的调查结果。\n\n> 建议：避免调用 ClassLoader.getResource*() 方法，而是使用 Android 的 Resources.get*(resId) 方法\n\n> 建议：搜索你的代码看看有没有 SDK 调用了 ClassLoader.getResource*() 方法，有的话把它们全换了，如果你懒的换，就让这些代码在子线程中执行\n\n**现在就去检查你的应用是否因为 ClassLoader.getResource*() 方法影响性能！**\n\n##我们是怎么在 getResourceAsStream 方法中找到这些操作的？\n\n为了完全理解这个问题，得分析实际的代码。此次分析使用的是 AOSP android-6.0.1_r11 的分支，看的代码是 ClassLoader 类的代码：\n\nlibcore/libart/src/main/java/java/lang/ClassLoader.java\n\n```java\npublic InputStream getResourceAsStream(String resName) {\n    try {\n        URL url = getResource(resName);\n        if (url != null) {\n            return url.openStream();\n        }\n    } catch (IOException ex) {\n        // Don't want to see the exception.\n    }\n\n    return null;\n}```\n\n代码要完成的工作简单直观：先找到资源的路径，如果不为空，就为它打开一个流。在此次分析中，路径是 java.net.URL 类，该类有 openStream() 方法。\n\n现在来检查 getResource() 方法的实现：\n\n```java\npublic URL getResource(String resName) {\n    URL resource = parent.getResource(resName);\n    if (resource == null) {\n        resource = findResource(resName);\n    }\n    return resource;\n}```\n\n还是很正常，那继续看 findResource(resName) 方法：\n\n```java\nprotected URL findResource(String resName) {\n    return null;\n}```\n\n在这里我们发现 findResource(resName) 方法并没有被实现。ClassLoader 是一个抽象类，因此我们需要找到真正实现了相关方法的，在应用中被使用的子类。如果我们去翻阅 Android 文档，我们会看到 Android 提供了几个具体的实现类，而 PathClassLoader 就是通常我们使用的那一个。\n\n既然如此，为了判断到底使用了哪个 ClassLoader，我们就进入 AOSP 看看源码中到底是哪个 ClassLoader 调用了 getResourceAsStream 和 getResource 方法：\n\n```java\npublic InputStream getResourceAsStream(String resName) {\n  try {\n      Logger.getLogger(\"NimbleDroid RESEARCH\").info(\"this: \" + this);\n      URL url = getResource(resName);\n      if (url != null) {\n          return url.openStream();\n      }\n      ...\n}\n```\n\n果然，Log 显示的是 dalvik.system.PathClassLoader。然而，检查 PathClassLoader 的方法会发现，并没有 \nfindResource 方法的实现。这是因为 findResource() 方法被 PathClassLoader 类的父类 - BaseDexClassLoader 实现了。\n\n/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java:\n\n```java\n@Override\nprotected URL findResource(String name) {\n    return pathList.findResource(name);\n}```\n\n让我们看看 pathList 是啥：\n\n```java\npublic class BaseDexClassLoader extends ClassLoader {\n  private final DexPathList pathList;\n\n  /**\n   * Constructs an instance.\n   *\n   * @param dexPath the list of jar/apk files containing classes and\n   * resources, delimited by {@code File.pathSeparator}, which\n   * defaults to {@code \":\"} on Android\n   * @param optimizedDirectory directory where optimized dex files\n   * should be written; may be {@code null}\n   * @param libraryPath the list of directories containing native\n   * libraries, delimited by {@code File.pathSeparator}; may be\n   * {@code null}\n   * @param parent the parent class loader\n   */\n  public BaseDexClassLoader(String dexPath, File optimizedDirectory,\n          String libraryPath, ClassLoader parent) {\n      super(parent);\n      this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);\n  }```\n\n那 DexPathList 是什么呢？\n\n/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java\n```java\n/**\n * A pair of lists of entries, associated with a {@code ClassLoader}.\n * One of the lists is a dex/resource path &mdash; typically referred\n * to as a \"class path\" &mdash; list, and the other names directories\n * containing native code libraries. Class path entries may be any of:\n * a {@code .jar} or {@code .zip} file containing an optional\n * top-level {@code classes.dex} file as well as arbitrary resources,\n * or a plain {@code .dex} file (with no possibility of associated\n * resources).\n *\n * <p>This class also contains methods to use these lists to look up\n * classes and resources.</p>\n */\n/*package*/ final class DexPathList {\nLet’s check out DexPathList.findResource:\n\n/**\n * Finds the named resource in one of the zip/jar files pointed at\n * by this instance. This will find the one in the earliest listed\n * path element.\n *\n * @return a URL to the named resource or {@code null} if the\n * resource is not found in any of the zip/jar files\n */\npublic URL findResource(String name) {\n    for (Element element : dexElements) {\n        URL url = element.findResource(name);\n        if (url != null) {\n            return url;\n        }\n    }\n\n    return null;\n}```\n\nElement 是 DexPathList 里的一个静态内部类，不妨看看它的代码：\n\n```java\npublic URL findResource(String name) {\n  maybeInit();\n\n  // We support directories so we can run tests and/or legacy code\n  // that uses Class.getResource.\n  if (isDirectory) {\n      File resourceFile = new File(dir, name);\n      if (resourceFile.exists()) {\n          try {\n              return resourceFile.toURI().toURL();\n          } catch (MalformedURLException ex) {\n              throw new RuntimeException(ex);\n          }\n      }\n  }\n\n  if (zipFile == null || zipFile.getEntry(name) == null) {\n      /*\n       * Either this element has no zip/jar file (first\n       * clause), or the zip/jar file doesn't have an entry\n       * for the given name (second clause).\n       */\n      return null;\n  }\n\n  try {\n      /*\n       * File.toURL() is compliant with RFC 1738 in\n       * always creating absolute path names. If we\n       * construct the URL by concatenating strings, we\n       * might end up with illegal URLs for relative\n       * names.\n       */\n      return new URL(\"jar:\" + zip.toURL() + \"!/\" + name);\n  } catch (MalformedURLException ex) {\n      throw new RuntimeException(ex);\n  }\n}```\n\n我们知道 APK 文件实质上就是 zip 文件，那么：\n\n```java\nif (zipFile == null || zipFile.getEntry(name) == null) {\nWe try to find ZipEntry by a given name. If we do this successfully, we return the corresponding URL. This can be a slow operation, but if we check the implementation of getEntry, we see that it’s just iterating over LinkedHashMap:\n\n/libcore/luni/src/main/java/java/util/zip/ZipFile.java\n\n...\n  private final LinkedHashMap<String, ZipEntry> entries = new LinkedHashMap<String, ZipEntry>();  \n...\n  public ZipEntry getEntry(String entryName) {\n      checkNotClosed();\n      if (entryName == null) {\n          throw new NullPointerException(\"entryName == null\");\n      }\n\n      ZipEntry ze = entries.get(entryName);\n      if (ze == null) {\n          ze = entries.get(entryName + \"/\");\n      }\n      return ze;\n  }```\n\n这并不是特别快的操作，但它也不能耗时过长。\n\n但我们忘了一件事 - 使用 zip 文件前，应该先打开它。回顾 DexPathList.Element.findResource() 方法的实现，你会注意到 maybeInit() 方法的调用，不妨看看它做了什么：\n\n```java\npublic synchronized void maybeInit() {\n  if (initialized) {\n      return;\n  }\n\n  initialized = true;\n\n  if (isDirectory || zip == null) {\n      return;\n  }\n\n  try {\n      zipFile = new ZipFile(zip);\n  } catch (IOException ioe) {\n      /*\n       * Note: ZipException (a subclass of IOException)\n       * might get thrown by the ZipFile constructor\n       * (e.g. if the file isn't actually a zip/jar\n       * file).\n       */\n      System.logE(\"Unable to open zip file: \" + zip, ioe);\n      zipFile = null;\n  }\n}```\n\n看到这行代码了么：\n\n```java\nzipFile = new ZipFile(zip);```\n\n打开 zip 文件：\n\n```java\npublic ZipFile(File file) throws ZipException, IOException {\n    this(file, OPEN_READ);\n}```\n\n构造器初始化一个名为 entries 的 LinkedHashMap 对象（为了了解更多有关 Zip 文件的内部结构，不妨看[这里](https://android.googlesource.com/platform/libcore/+/android-6.0.1_r21/luni/src/main/java/java/util/zip/ZipFile.java))。很显然，随 APK 文件体积变大，打开 zip 文件需要的时间也增多。\n\n到现在为止，我们找到了导致 getResourceAsStream 如此慢的第一个原因。如果我们将源码改为下面这样：\n\n```java\n  public InputStream getResourceAsStream(String resName) {\n    try {\n      long start; long end;\n\n      start = System.currentTimeMillis();\n      URL url = getResource(resName);\n      end = System.currentTimeMillis();\n      Logger.getLogger(\"NimbleDroid RESEARCH\").info(\"getResource: \" + (end - start));\n\n      if (url != null) {\n          start = System.currentTimeMillis();\n          InputStream inputStream = url.openStream();\n          end = System.currentTimeMillis();\n          Logger.getLogger(\"NimbleDroid RESEARCH\").info(\"url.openStream: \" + (end - start));\n\n          return inputStream;\n      }\n      ...\n```\n\n我们就会发现对 zip 文件的操作并不是 getResourceAsStream 方法造成延迟的罪魁祸首：因为 url.openStream() 花费的时间比 getResource() 多多了，不妨继续研究源码：\n\n追踪 url.openStream() 方法的调用栈，会进入 /libcore/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java\n\n```java\n@Override\npublic InputStream getInputStream() throws IOException {\n    if (closed) {\n        throw new IllegalStateException(\"JarURLConnection InputStream has been closed\");\n    }\n    connect();\n    if (jarInput != null) {\n        return jarInput;\n    }\n    if (jarEntry == null) {\n        throw new IOException(\"Jar entry not specified\");\n    }\n    return jarInput = new JarURLConnectionInputStream(jarFile\n            .getInputStream(jarEntry), jarFile);\n}```\n\n不妨先看看 connect() 方法\n\n```java\n@Override\npublic void connect() throws IOException {\n    if (!connected) {\n        findJarFile(); // ensure the file can be found\n        findJarEntry(); // ensure the entry, if any, can be found\n        connected = true;\n    }\n}```\n\n看起来没啥特别的，深入里面的子方法看看吧：\n\n```java\nprivate void findJarFile() throws IOException {\n    if (getUseCaches()) {\n        synchronized (jarCache) {\n            jarFile = jarCache.get(jarFileURL);\n        }\n        if (jarFile == null) {\n            JarFile jar = openJarFile();\n            synchronized (jarCache) {\n                jarFile = jarCache.get(jarFileURL);\n                if (jarFile == null) {\n                    jarCache.put(jarFileURL, jar);\n                    jarFile = jar;\n                } else {\n                    jar.close();\n                }\n            }\n        }\n    } else {\n        jarFile = openJarFile();\n    }\n\n    if (jarFile == null) {\n        throw new IOException();\n    }\n}```\n\n调用 getUseCaches() 方法应该返回 true，因为：\n\n```java\npublic abstract class URLConnection {\n...\n  private static boolean defaultUseCaches = true;\n  ...\nLet’s look at the openJarFile() method:\n\nprivate JarFile openJarFile() throws IOException {\n  if (jarFileURL.getProtocol().equals(\"file\")) {\n      String decodedFile = UriCodec.decode(jarFileURL.getFile());\n      return new JarFile(new File(decodedFile), true, ZipFile.OPEN_READ);\n  } else {\n    ...\n```\n\n如你所见，在该方法内我们打开了 Jar 文件，而不是 Zip 文件。然而，JarFile 是 ZipFile 的子类。所以我们找到了影响 getResourceAsStream 方法性能的第二个原因 - Android 需要再次将 APK 文件作为 Zip 文件打开，并索引所有表项。\n\n将 APK 文件作为 Zip 文件打开两次相当于将这部分时间开销加倍，此时对性能的影响就会变得明显了。然而，这部分开销仍然不足以造成 getResourceAsStream 方法的性能总开销，不妨看看 JarFile 的构造方法：\n\n```java\n/**\n * Create a new {@code JarFile} using the contents of file.\n *\n * @param file\n *            the JAR file as {@link File}.\n * @param verify\n *            if this JAR filed is signed whether it must be verified.\n * @param mode\n *            the mode to use, either {@link ZipFile#OPEN_READ OPEN_READ} or\n *            {@link ZipFile#OPEN_DELETE OPEN_DELETE}.\n * @throws IOException\n *             If the file cannot be read.\n */\npublic JarFile(File file, boolean verify, int mode) throws IOException {\n    super(file, mode);\n\n    // Step 1: Scan the central directory for meta entries (MANIFEST.mf\n    // & possibly the signature files) and read them fully.\n    HashMap<String, byte[]> metaEntries = readMetaEntries(this, verify);\n\n    // Step 2: Construct a verifier with the information we have.\n    // Verification is possible *only* if the JAR file contains a manifest\n    // *AND* it contains signing related information (signature block\n    // files and the signature files).\n    //\n    // TODO: Is this really the behaviour we want if verify == true ?\n    // We silently skip verification for files that have no manifest or\n    // no signatures.\n    if (verify && metaEntries.containsKey(MANIFEST_NAME) &&\n            metaEntries.size() > 1) {\n        // We create the manifest straight away, so that we can create\n        // the jar verifier as well.\n        manifest = new Manifest(metaEntries.get(MANIFEST_NAME), true);\n        verifier = new JarVerifier(getName(), manifest, metaEntries);\n    } else {\n        verifier = null;\n        manifestBytes = metaEntries.get(MANIFEST_NAME);\n    }\n}```\n\n现在我们找到第三个影响性能的操作，所有 APK 文件都被签名，因此 JarFile 会执行验证路径。该验证进程要花费的时间非常长。但这部分的研究已经超出了本文的讨论范围，如果你想了解的话不妨看[这里](https://android.googlesource.com/platform/libcore/+/android-6.0.1_r21/luni/src/main/java/java/util/jar/)。\n\n##总结\n\n总的来说，ClassLoader.getResourceAsStream 之所以慢，是因为这三个操作：\n\n1. 将 APK 文件作为 zip 文件打开，并索引所有 zip 表项\n2. 再一次执行 1 的操作\n3. 验证 APK 签名是否符合规范\n\n###其他问题\n\n**Q: ClassLoader.getResource*() 在 Dalvik 和 ART 上都很慢吗?**\n\nA: 是的。我们分析了 使用 ART 的 android-6.0.1_r11 和使用 Dalvik 的 android-4.4.4_r2，结果都是一样的。\n\n**Q: 为什么 ClassLoader.findClass() 没有性能的影响?**\n\nA: Android 在安装 APK 时就已经从 APK 文件中解压了 DEX 文件，因此，不需要额外的将 APK 作为 Zip 文件或 Jar 文件打开的操作以找到一个类文件。\n\n如果我们进入 DexPathList 类会看到：\n\n```java\npublic Class findClass(String name, List<Throwable> suppressed) {\n  for (Element element : dexElements) {\n      DexFile dex = element.dexFile;\n\n      if (dex != null) {\n          Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);\n          if (clazz != null) {\n              return clazz;\n          }\n      }\n  }\n  if (dexElementsSuppressedExceptions != null) {\n      suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));\n  }\n  return null;\n}```\n\n并没有 Zip 文件或 Jar 文件相关的操作\n\n**Q: 为什么 Android 提供的 Resources.get*(resId) 方法没有这个问题?**\n\nA: Android 拥有自己的索引和加载资源的方式，避免了 Zip 文件和 Jar 文件操作的开销。"
  },
  {
    "path": "issue-49/如何使用BottomSheet.md",
    "content": "如何使用BottomSheet\n---\n\n> * 原文链接 : [How to Use Bottom Sheets With the Design Support Library](http://code.tutsplus.com/articles/how-to-use-bottom-sheets-with-the-design-support-library--cms-26031)\n* 原文作者 : [Paul Trebilcox-Ruiz](http://tutsplus.com/authors/paul-trebilcox-ruiz)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: \n* 状态 :  完成 \n\n\n\n\n不得不说，自 2014 Google I/O 大会发布 Material Design 后，Android 设计规范产生了翻天覆地的变化。尽管 Google 只是简单地介绍了一些 Android UI 设计的基本规范，但 Android 开发者们各显神通，创造了许多兼顾交互体验和视觉体验的优秀控件。Google 也因此在 [2015 Google I/O 大会](http://code.tutsplus.com/articles/google-io-2015-aftermath--cms-24124)上发布了新的支持库 - [Design support library](http://code.tutsplus.com/articles/overview-of-the-android-design-support-library--cms-24234) 以帮助开发者们开发更多符合 Material Design 的控件。\n\nDesign support library 提供的 API 一定程度上帮助开发者在开发控件时节省时间，并在 23.2 发布的版本中提供了底部工作条（Bottom Sheets）。而本博文的目的就是教大家在自家的 App 中实现 [Bottom Sheet](https://www.google.com/design/spec/components/bottom-sheets.html)，博文中的示例代码可以在 [GitHub](https://github.com/tutsplus/Android-BottomSheets) 上面下载。\n\n##1. 创建 Bottom Sheet\n\n实现 Bottom Sheet 有两种办法：\n\n1. 在布局文件中为容器 View 添加 BottomSheetBehavior\n2. 使用 BottomSheetDialogFragment\n\n但不管你用哪种办法，都需要添加 Design support library 的依赖，且该支持库的版本要大于 23.2。在 build.gradle 中添加下面的代码就可以完成这一步操作：\n\n```java\n`\ncompile 'com.android.support:design:23.2.0'\n`\n```\n\n添加依赖并同步完成后，你就可以在布局文件中添加 Bottom Sheet，在本文的示例项目中，布局如下：\n\n```xml\n<android.support.design.widget.CoordinatorLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/main_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\">\n \n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n \n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"24dp\">\n \n            <Button\n                android:id=\"@+id/button_1\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"Button 1\"\n                android:padding=\"16dp\"\n                android:layout_margin=\"8dp\"\n                android:textColor=\"@android:color/white\"\n                android:background=\"@android:color/holo_green_dark\"/>\n \n            <Button\n                android:id=\"@+id/button_2\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"16dp\"\n                android:layout_margin=\"8dp\"\n                android:text=\"Button 2\"\n                android:textColor=\"@android:color/white\"\n                android:background=\"@android:color/holo_blue_light\"/>\n \n            <Button\n                android:id=\"@+id/button_3\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"16dp\"\n                android:layout_margin=\"8dp\"\n                android:text=\"Button 3\"\n                android:textColor=\"@android:color/white\"\n                android:background=\"@android:color/holo_red_dark\"/>\n \n        </LinearLayout>\n \n    </ScrollView>\n \n    <android.support.v4.widget.NestedScrollView\n        android:id=\"@+id/bottom_sheet\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"350dp\"\n        android:clipToPadding=\"true\"\n        android:background=\"@android:color/holo_orange_light\"\n        app:layout_behavior=\"android.support.design.widget.BottomSheetBehavior\"\n        >\n \n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:text=\"@string/ipsum\"\n            android:padding=\"16dp\"\n            android:textSize=\"16sp\"/>\n \n    </android.support.v4.widget.NestedScrollView>\n \n</android.support.design.widget.CoordinatorLayout>\n```\n\n该布局文件的显示效果如下：\n![](https://cms-assets.tutsplus.com/uploads/users/798/posts/26031/image/buttons.png)\n\n该 xml 中有几点需要注意：\n\n1. 要使用 Bottom Sheet，必须用 CoordinatorLayout 作为该布局的根布局。\n2. 在布局的底部，我们添加了一个容纳 TextView 的 NestedScrollView。Bottom Sheet 确实可以添加任意的 View，但前提是你使用了支持嵌套滚动的容器，否则不能显示出正确的滚动效果。（NestedScrollView 和 RecyclerView 都是具有此特性的可选项。）\n\n要让容器 View 被 Design Support Library 识别为 Bottom Sheet 容器 View，需要添加 layout_behavior 属性，并将该属性赋值为 android.support.design.widget.BottomSheetBehavior。\n\n此外，无论容器 View 使用什么尺寸值，或以什么方式控制 Bottom Sheet 的显示，都要注意 Bottom Sheet 容器 View 的 layout_height。如果你使用 CoordinatorLayout，为了让其他 View 移动，例如 CollapsingToolbarLayout，当 Bottom Sheet 的高度发生改变，可能出现布局跳动的情况。\n\n##2. 基于 Bottom Sheet 显示布局\n\n一旦你在布局文件中正确地创建了容器 View，你就可以在 Activity/Fragment 中使用 Bottom Sheet 了。示例项目在 MainActivity 中使用 Bottom Sheet。\n\n为了让 Bottom Sheet 处于可显示状态，需要创建 BottomSheetBehavior，该 BottomSheetBehavior 持有容器 View 的引用。示例项目中还创建了三个按钮对应的引用：\n\n```java\nprivate BottomSheetBehavior mBottomSheetBehavior;\n \n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n \n    View bottomSheet = findViewById( R.id.bottom_sheet );\n    Button button1 = (Button) findViewById( R.id.button_1 );\n    Button button2 = (Button) findViewById( R.id.button_2 );\n    Button button3 = (Button) findViewById( R.id.button_3 );\n \n    button1.setOnClickListener(this);\n    button2.setOnClickListener(this);\n    button3.setOnClickListener(this);\n \n    mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);\n}\n```\n\n创建 BottomSheetBehavior 后，就可以显示 Bottom Sheet 了，完成这一步只需要将 BottomSheetBehavior 设置为 STATE_EXPANDED。本例的代码如下：\n\n```java\n@Override\npublic void onClick(View v) {\n    switch( v.getId() ) {\n        case R.id.button_1: {\n            mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);\n            break;\n        }\n    }\n}\n```\n\n具体效果如下：\n\n![](https://cms-assets.tutsplus.com/uploads/users/798/posts/26031/image/bottomsheet.png)\n\n隐藏 Bottom Sheet 只需要将 BottomSheetBehavior 设置为 STATE_COLLAPSED。\n\n##3. 部分显示 Bottom Sheet\n\n在许多 Android 应用或 Google 控件中，都可以预览 Bottom Sheet 的部分信息，并且能展开该 Bottom Sheet 显示完整的信息，例如 Places API 提供的地点选择功能。这个效果可以通过 setPeekHeight() 方法为 View 设置 collapsed 尺寸，结合 Design Support Library 的 Bottom Sheet 实现。如果你想显示 Bottom Sheet 的部分内容，也可以将 BottomSheetBehavior 设置为 STATE_COLLAPSED。\n\n```java\nmBottomSheetBehavior.setPeekHeight(300);\nmBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);\n```\n\n当中间的按钮被点击，就会看到该效果，将它向上拉起就会看到完整的视图：\n\n![](https://cms-assets.tutsplus.com/uploads/users/798/posts/26031/image/peek.png)\n\n你可能注意到：当你将 Bottom Sheet 向下拉动时，只能折叠为之前的高度。如果你想让它完全隐藏的话，可以为 BottomSheetBehavior 添加 BottomSheetCallback 回调，在用户折叠 Bottom Sheet 时将 peekSize 设置为 0。\n\n```java\nmBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {\n    @Override\n    public void onStateChanged(View bottomSheet, int newState) {\n        if (newState == BottomSheetBehavior.STATE_COLLAPSED) {\n            mBottomSheetBehavior.setPeekHeight(0);\n        }\n    }\n \n    @Override\n    public void onSlide(View bottomSheet, float slideOffset) {\n    }\n});\n```\n\n##4. 使用 BottomSheetFragment\n\n正如本文开始时提到的，我们还能利用 BottomSheetDialogFragment 显示 Bottom Sheet，那么要怎么操作呢？首先创建 BottomSheetDialogFragment 的子类，在 setupDialog() 方法中初始化新的布局文件，并获得布局中的 BottomSheetBehavior。在获得 Behavior 的引用后，创建 BottomSheetCallback 并通过其回调方法在 Bottom Sheet 隐藏后关闭 Fragment。\n\n```java\npublic class TutsPlusBottomSheetDialogFragment extends BottomSheetDialogFragment {\n \n    private BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() {\n \n        @Override\n        public void onStateChanged(@NonNull View bottomSheet, int newState) {\n            if (newState == BottomSheetBehavior.STATE_HIDDEN) {\n                dismiss();\n            }\n \n        }\n \n        @Override\n        public void onSlide(@NonNull View bottomSheet, float slideOffset) {\n        }\n    };\n \n    @Override\n    public void setupDialog(Dialog dialog, int style) {\n        super.setupDialog(dialog, style);\n        View contentView = View.inflate(getContext(), R.layout.fragment_bottom_sheet, null);\n        dialog.setContentView(contentView);\n \n        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams();\n        CoordinatorLayout.Behavior behavior = params.getBehavior();\n \n        if( behavior != null && behavior instanceof BottomSheetBehavior ) {\n            ((BottomSheetBehavior) behavior).setBottomSheetCallback(mBottomSheetBehaviorCallback);\n        }\n    }\n}\n```\n\n最终可以通过 Fragment 实例的 show() 方法在 Bottom Sheet 中显示该 Fragment：\n\n```java\nBottomSheetDialogFragment bottomSheetDialogFragment = new TutsPlusBottomSheetDialogFragment();\nbottomSheetDialogFragment.show(getSupportFragmentManager(), bottomSheetDialogFragment.getTag());\n```\n\n![](https://cms-assets.tutsplus.com/uploads/users/798/posts/26031/image/fragment.png)\n\n##结论\n\n使用 Design Support Library 显示 Bottom Sheet 不但方法多样，还简单，我们能利用它显示详细的信息，也可以显示 picker，因此 Bottom Sheet 能替代 DialogFragment 被使用。希望本篇博文能帮助你理解 Bottom Sheet，并将它应用到你的 App 中。"
  },
  {
    "path": "issue-49/深入研究AutoValue.md",
    "content": "深入研究AutoValue\n---\n\n> * 原文链接 : [A Deeper Look at AutoValue](http://ryanharter.com/blog/2016/04/08/autovalue-deep-dive/)\n* 原文作者 : [Ryan Harter](http://ryanharter.com/)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: \n* 状态 :  完成 \n\n\n\n在上一篇博文中，我简单介绍了 AutoValue - 通过注解处理器简化 Java 不可变数据类型的代码生成库。现在我要来研究它的实现机制了。\n\n##编译时注解处理\n\n首先，AutoValue 是编译时注解处理器，意味着它只在你编译代码时运行，恰好与 App 运行时相对。这对应用来说有一些意义，因为 AutoValue 不会影响应用的性能/大小。\n\n我认为 AutoValue 不影响应用的原因是：AutoValue 将已生成的代码添加到你最终的二进制流文件中，而这往往都是你不需要担心的部分，因为如果你在创建数据类型，那么这部分代码无论如何都会存在。除此以外，AutoValue 自己会测试它的生成器及代码，所以你可以相信生成的代码没有错误而且具有高性能表现。\n\n如果你不够谨慎的话，还是很容易将 AutoValue ，以及 [shaded](https://maven.apache.org/plugins/maven-shade-plugin/) 依赖意外地包含到二进制流文件中。如果你是 Android 开发者，这可能会对应用/用户产生[恶劣的影响](http://stackoverflow.com/questions/15471772/how-to-shrink-code-65k-method-limit-in-dex)。\n\n\n包含 AutoValue 的正确方法是：正确配置它，使得它只在项目构建过程中的注解处理阶段被包含。\n\n对于 Android 项目，你需要安装 [android-apt](https://bitbucket.org/hvisser/android-apt) Gradle 插件，然后将 AutoValue 添加到 apt 配置中。你还需要将它添加到 provided 配置中以得到正确的代码实现。\n\n```java\ndependencies {\n  provided 'com.google.auto.value:auto-value:1.2'\n  apt 'com.google.auto.value:auto-value:1.2'\n}```\n\n> 注意：这一步对 Android 应用来说真的很关键，不然的话你很容易就会遇到 dex 方法数限制了。\n\n为了在 Java 项目中通过 Gradle 使用 AutoValue，你可以安装 apt 插件，并将依赖添加到 compileOnly 和 apt 配置中。\n\n```java\ndependencies {\n  compileOnly 'com.google.auto.value:auto-value:1.2'\n  apt         'com.google.auto.value:auto-value:1.2'\n}\n```\n\nMaven 用户可以这样添加依赖：\n\n```java\n<dependency>\n  <groupId>com.google.auto.value</groupId>\n  <artifactId>auto-value</artifactId>\n  <version>1.2</version>\n  <scope>provided</scope>\n</dependency>\n```\n\n列出多种依赖配置方式的原因是：不想大多数注解处理器，AutoValue 在相同的部件中包含了注解和注解处理器。这也是这个 [issue](https://github.com/google/auto/issues/268) 被提出的原因，这个 issue 解决以后就不需要这么多配置了。\n\n##扫描类\n\n当 AutoValue 作为依赖被添加，就会在编译应用时被运行以处理所有带有 @AutoValue 注解的类。如果你想了解 Java 注解是如何工作的，不妨看 Hannes Dorfmann 的[这篇博文](http://hannesdorfmann.com/annotation-processing/annotationprocessing101)，讲解的非常好。\n\n每一个带有 @AutoValue 注解的类，AutoValue 首先搜索所有的抽象方法去判断它应该生成什么。AutoValue 通过进入所有抽象方法完成，这些方法包含通过接口继承的方法。这些方法被看作属性，而且 AutoValue 会生成适当的域，将它们包含到 equals()、hashCode() 和 toString() 方法中。\n\n```java\n@AutoValue public abstract class Foo {\n\n  // This method takes no arguments and returns\n  // String, so it's a property\n  public abstract String foo();\n\n  // This method is not abstract, so it's not\n  // a property.\n  public boolean hasValues() {\n    return foo() != null && !foo().isEmpty();\n  }\n\n  // This method takes a parameter and returns void,\n  // so it's not a property.  Hopefully an extension\n  // generates the implementation.\n  public abstract void writeValues(OutputStream out);\n}```\n\n因为 AutoValue 知道属性和自定义方法间的区别，这就意味着你可以添加返回代码生成的数据类型，或在对象上执行其他操作。\n\n此外，属性方法不需要是 public。因为他们必须由 AutoValue 子类生成，它们也不是 private，但它们可以设为包 private 或 protected 以保证不会被公有 API 访问。\n\n```java\n@AutoValue public abstract class User {\n\n  // Package private property won't be\n  // visible outside of the package, but\n  // will still be generated.\n  abstract String internalFirstName();\n\n  // Publicly available method that uses the\n  // internally generated one.\n  public String firstName() {\n    String name = internalFirstName().trim();\n    return Character.toUpperCase(name.charAt(0)) + name.substring(1);\n  }\n}```\n\n其他需要考虑的就是 equals()、hashCode() 和 toString() 方法了。如果你想要实现你自己版本的这些方法（或其中之一），你可以在注解类中自己实现，AutoValue 会识别出来并不产生代码，你的实现也会被继承并在 final 类中被使用。\n\n```java\n@AutoValue public abstract class User {\n  public abstract String firstName();\n  public abstract String lastName();\n  public abstract int age();\n\n  // I'm the same person as me from 10 years ago...or am I?\n  @Override public boolean equals(Object other) {\n    if (!(other instanceof User)) return false;\n    User o = (User) other;\n    return firstName().equals(o.firstName())\n        && lastName().equals(o.lastName());\n  }\n}```\n\n理解 AutoValue 定义属性的方式给你的类增加了不少可伸缩性。\n\n##生成的代码\n\n正如我在上一篇文章中提到，AutoValue 通过创建构造器，成员变量和对应的 getter 方法（获得属性）生成不可变的数据类型，此外还根据上面提到的规则实现了 equals()、hashCode() 和 toString() 方法。不妨看看这些代码是怎么生成的吧\n\n###The Class\n\n```java\npackage com.example.autovalue;\n\nfinal class AutoValue_User extends User {\n\n  ...\n\n}\n```\n\n首先注意到的是该生成类继承了添加注解的抽象类，这意味着你可以传递用户对象，而不需要特定为 AutoValue 指明。这显然是 Google 工程师有意为之，当 AutoValue 不能满足你的需求时，简化依赖实现的修改。\n\n其他的就是生成的类是 final 的，而且带有 AutoValue_ 前缀。如果你想要在添加注解的类中访问生成的代码的话，这一点很重要。因为这样就像通过静态工厂方法访问构造器。\n\n最后，注意该类是包间私有的。因此在其他包里无法看到生成的类，当然，它也不应该在其他类中被使用。\n\n###The Constructor\n\n```java\nfinal class AutoValue_User extends User {\n\n  AutoValue_User(\n      String firstName,\n      String lastName,\n      int age) {\n    if (firstName == null) {\n      throw new NullPointerException(\"Null firstName\");\n    }\n    this.firstName = firstName;\n    if (lastName == null) {\n      throw new NullPointerException(\"Null lastName\");\n    }\n    this.lastName = lastName;\n    this.age = age;\n  }\n\n}\n```\n\n该构造器也是包间私有的，将所有参数作为属性。\n\n在构造器中对传入的参数作了判断，所有属性都不会是空。如果你想让某个属性为空，不妨添加一个 @Nullable 注解。\n\n```java\n@AutoValue public abstract class User {\n  @Nullable public abstract String middleName();\n}\n```\n\n###The Member variables\n\n```java\nfinal class AutoValue_User extends User {\n\n  private final String firstName;\n  private final String lastName;\n  private final int age;\n\n  @Override\n  public String firstName() {\n    return firstName;\n  }\n\n  @Override\n  public String lastName() {\n    return lastName;\n  }\n\n  @Override\n  public int age() {\n    return age;\n  }\n\n}```\n\n###equals(), hashCode() and toString()\n\n对于成员变量真的没什么特别的，唯一要说的就是数据域都是 final，以保证类型不变性。\n\n```java\nfinal class AutoValue_User extends User {\n\n  @Override\n  public String toString() {\n    return \"User{\"\n        + \"firstName=\" + firstName + \", \"\n        + \"lastName=\" + lastName + \", \"\n        + \"age=\" + age\n        + \"}\";\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o == this) {\n      return true;\n    }\n    if (o instanceof User) {\n      User that = (User) o;\n      return (this.firstName.equals(that.firstName()))\n           && (this.lastName.equals(that.lastName()))\n           && (this.age == that.age());\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    int h = 1;\n    h *= 1000003;\n    h ^= this.firstName.hashCode();\n    h *= 1000003;\n    h ^= this.lastName.hashCode();\n    h *= 1000003;\n    h ^= this.age;\n    return h;\n  }\n\n}```\n\n这样就得到了默认的 equals(), hashCode() 和 toString() 方法的实现了。\n\n##结论\n\n如你所见，AutoValue 为我们完成了许多的工作，它很聪明，能够了解我们希望他完成什么工作，并作出正确的决定。AutoValue 允许我们的类有大量的可伸缩性。\n\n而且这一切都不会给你的应用增加负担。"
  },
  {
    "path": "issue-7/Android-Lollipop-update.md",
    "content": "#Android 5.1 Lollipop 升级版：Android 5.1.1面世\n\n> * 原文连接 : [android-5-1-lollipop-update](http://www.androidpit.com/android-5-1-lollipop-update)\n> * 作者：Loie Favre\n> * [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n> * 译者 : [Mr.Simple](https://www.github.com/bboyfeiyu)\n\nAndroid Lollipop自2014年推出一来，仅应用在少数几款设备上。大多数人在兴奋迎接Android5.0的时候视野已经瞄准了Android 5.1。现在让我们来一睹Android 5.1.1的芳容。请点击[Android 5.1 Lollipop](http://www.androidpit.com/topic/android-5-1-lollipop)查看所有升级资料（以及即将来袭的Android 5.1.1）。继续往下阅读，来看看哪些设备会使用这个最新系统。\n\n* [Android 6.0](http://www.androidpit.com/android-6-release-date-news-rumors): 一起创造终极Android OS\n\n* [Android 5.0 Lollipop update](http://www.androidpit.com/android-5-0-lollipop-phone-update-news)：我的设备什么时候才能使用最新系统？ \n\n![pic-1](http://fs01.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-teaser-picture-2.jpg)\n\nAndroid 5.1发布日期及新特性曝光。/@ANDROIDPIT\n\n###Android 5.1.1升级版已经到来了吗？\n\n可以这么说。Android 5.1.1 Lollipop升级版尚未应用到手机上，但是Nexus Player的factory image已经曝光--这也是目前第一款应用5.1.1系统的设备。一旦Android 5.1.1升级版可以应用其他Nexus设备或摩托罗拉设备，我们会第一时间与大家分享最新消息。\n\n###Android 5.1.1 Lollipop升级版：最新系统什么时候能用？\n\n第一批使用Android 5.1升级版的设备为[T-Mobile](http://www.androidpit.com/topic/t-mobile) Nexus 6 和 [Verizon](http://www.androidpit.com/topic/verizon) Nexus 6，其他Nexus设备火速待定中。\n\n####Google Nexus设备\n\n* [Nexus 6 Android 升级版最新消息](http://www.androidpit.com/nexus-6-android-update)\n* [Nexus 5 Android 升级版最新消息](http://www.androidpit.com/nexus-5-android-update)\n* [Nexus 4 Android 升级版最新消息](http://www.androidpit.com/nexus-4-android-update)\n* [Nexus 7 (2013) Android 升级版最新消息](http://www.androidpit.com/nexus-7-2013-android-update)\n\n[Nexus 9](http://www.androidpit.com/device/google-nexus-9)尚未进入支持Android 5.1升级之列。Google Play Edition的设备很快会进行Android 5.1.1的升级。\n\n####摩托罗拉设备\n\n摩托罗拉最新的中高端设备已确认支持升级到Android 5.1，而且第一代Moto X 和 Moto G也即将步入升级行列（原本的Moto X目前正在巴西进行Android 5.1系统升级测试）。Moto E的一、二代均处在等待系统升级之列。\n\n* [Moto X (2014) Android 升级版最新消息](http://www.androidpit.com/moto-x-2014-android-update)\n* [Moto X (2013) Android 升级版最新消息](http://www.androidpit.com/moto-x-2013-android-update)\n* [Moto G (2014) Android 升级版最新消息](http://www.androidpit.com/moto-g-2014-android-update)\n* [Moto G (LTE, 2013) Android 升级版最新消息](http://www.androidpit.com/moto-g-android-update)\n* [Moto E Android 升级版最新消息](http://www.androidpit.com/moto-e-android-update)\n\n####Android One设备\n\n大家可能还记得，最初是Android One提醒了我们Android 5.1的存在。Google搜索页面把Android 5.1 Lollipop列为Evercoss One X、Mito Impact 和 Nexian Journey的Android即用版。\n\n###Android Wear 5.1\n\n[Android Wear 升级版](http://www.androidpit.com/android-wear-update)不久也会上线，新版特性包括挥手感控Android Wear智能手表（通过手腕控制通知栏，无需双手同时操作）、Wi-Fi、手绘emoji、程序切换器以及快速联系人搜索等。\n\nAndroid Wear智能手表通常依靠蓝牙与联网智能手机连接使用，但是大部分智能手表带有预装Wi-Fi芯片，但到目前为止，这块芯片仍处于睡眠状态，毫无用处。Android Wear 5.1升级版推出之后，这一Wi-Fi组件有望被激活。\n\n###Android 5.1 Lollipop 升级版有哪些新特性？\n\n####中断、优先级和静音模式\n\n新版系统的一个最小但最重要的改进在于其优先级设置。在新系统下我们可以将中断偏好设置为保持原样，直到下次警报为止。这在避免隔夜中断情况或“故障时间”情况下非常有用。\n\n![pic-2](http://fs04.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-interruptions.jpg)\n\nAndroid 5.1完善了Lollipop的优先级模式，但仍然未包含通知灯闪烁的静音模式。/@ANDROIDPIT\n\n\n####屏幕固定\n\n如果大家之前没体验过屏幕固定功能，那么现在可以试试。当别人在使用你的手机时，这是保护手机中信息隐私最好的办法。\n\n屏幕固定是指将某个应用或某个文件夹锁定，当别人使用你的手机时，在未获得相应PIN码的情况下他不能退出当前应用。PIN码很容易设置，这样一来，使用你的手机的朋友、小孩或者伴侣在未获得你的允许的情况下就无法浏览你手机里的其他内容了。新系统中的这个功能更容易找到，并且说明更详细。在设置>安全隐私项下可以找到。\n\n说到安全隐私...\n\n![pic-3](http://fs01.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-screen-pinning.jpg)\n\n屏幕固定激活后，我们可以从“最近应用”列表中进入。\n\n####安全隐私\n\nAndroid 5.1一个新特性是锁定功能，即使手机被偷后它也能保护手机内信息安全。激活“设备保护”后，除非使用者拥有你的Google账号登陆信息，否则设备将不可用，直到恢复出厂设置。 \n\n可惜的是，这个功能可不一定能帮你把手机找回来，但是能防止别人盗用你手机里的信息这一点是肯定的，这也算是一个小小的安慰吧。\n\n只要你手上有一台可用的设备（但遗憾的是目前还没确定是哪些设备），屏幕安全锁定（通过pattern、PIN码或密码）并且登陆了Google账号，你就可以自动激活这个功能。\n\n那如何才能确定这个功能在手机上被激活了呢？目前我们使用的唯一方法就是解除锁屏安全模式，如果跳出“设备保护功能将关闭”的通知，那么我们在锁屏的时候就可以知道这个功能是否激活了（经过一番周折，终于了解了！）。\n\n![pic-4](http://fs04.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-device-protection.jpg)\n\n兼容设备上的设备保护功能会自动激活。/@ANDROIDPIT\n\n####完善快速设置菜单\n\n为了增强可用性，快速设置菜单囊括了所有的的全新动画，Wi-Fi微调以及蓝牙菜单。\n\n选择Wi-Fi或蓝牙网络比之前更容易操作，按钮旁的下拉箭头可以直接从快速设置菜单中找出连接列表。这真是极好的。\n\n* [如何将Nexus 5的系统升级到Android 5.1 Lollipop](http://www.androidpit.com/how-to-get-android-5-1-lollipop-on-nexus-5)\n\n![pic-5](http://fs01.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-wi-fi-settings.jpg)\n\n快速设置中新添加的下拉式Wi-Fi连接功能的用户反馈很好。/@ANDROIDPIT\n\n####通话音质更好，支持双卡双待\n\n尽管运营商本身为设备提供了高清的语音质量，在这里高清音质正式被直接编入Android 5.1代码中，也就意味着大家能够享受更高质量的通话体验。\n\n其他值得一提的特性有...\n\n* 更好的双卡双待支持\n* Android 5.1 SDK提供了新的API支持（为迎接更好的app铺平道路）\n* 完善的通知中心管理（通知栏现在可以半滑动，而不是像之前那样完全滑到上方）\n* 更好的WiFi连接，Android现在知道哪个接入点的WiFi信号更好。聪明。\n\n目前还没有确切的关于什么设备能用、什么时间能用新版系统的消息，大家怀着美好的期待等待新系统的到来吧。赶紧去设置>关于手机>软件更新，燥起来吧！如果你玩的是摩托罗拉的手机，Nexus设备或Google Play Edition，很有可能你就是第一个体验新系统的人哟。\n\n* [Android 5.0 problems and solutions](http://www.androidpit.com/android-5-0-lollipop-problems-and-solutions)\n\n![pic-6](http://fs04.androidpit.info/userfiles/2692059/image/Blog/AndroidPIT-Galaxy-S5-Lollipop-KitKat.JPG)\n\nAndroid平台越来越优秀。/@ANDROIDPIT\n\n你的设备已经升级到Android 5.1了吗？觉得怎么样？\n\n\n\n"
  },
  {
    "path": "issue-7/Android-Support库22.1版.md",
    "content": "Android Support库 22.1 \n---\n\n> * 原文链接 : [Android Support Library 22.1](http://android-developers.blogspot.com/2015/04/android-support-library-221.html)\n* 原文作者 : [Ian Lake](https://plus.google.com/+IanLake)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Rocko](https://github.com/zhengxiaopeng) \n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  校对完成\n\n\n你可能听过这么一句话 “最好的代码就是没有代码。” 然而我想对你说的是：你写下的每一行代码应该能为应用增加独特的价值，而不是为应用添加一行又一行繁复、无趣的模板代码。Android提供支持库的初衷正是如此：让 Android 开发工程师把精力更多地放在逻辑实现上，而不是写业务代码。\n\n最新发布的Android支持库一如既往地添加了许多实用的组件，并对Support V4、AppCompat、Leanback、RecyclerView、Palette和Renderscript库的内部实现逻辑作出改变。从新的 [AppCompatActivity](http://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)和[AppCompatDialog](http://developer.android.com/reference/android/support/v7/app/AppCompatDialog.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog) 到Android TV全新的引导流程我们可以发现，新的库确实带来许多让我们耳目一新的惊喜。\n\n\n## Support V4\nSupport V4 库作为众多 Android 支持库的基础，包含许多向下兼容的类，大大简化了向下兼容的具体实现。\n\n[DrawableCompat](http://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)现在使drawable着色绘制向下兼容到了API 4：现在只需要通过[DrawableCompat.wrap(Drawable)](http://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog#wrap(android.graphics.drawable.Drawable))简单封装你的Drawable，然后[setTint()](http://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog#setTint(android.graphics.drawable.Drawable,%20int))、[setTintList()](http://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog#setTintList(android.graphics.drawable.Drawable,%20android.content.res.ColorStateList))、[setTintMode()](http://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog#setTintMode(android.graphics.drawable.Drawable,%20android.graphics.PorterDuff.Mode))就能完成着色绘制：完全不需要为了支持多种颜色而去创建和维护几个不同的 Drawable 文件！\n\n此外，我们正在通过 [ColorUtils](http://developer.android.com/reference/android/support/v4/graphics/ColorUtils.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog) 类做一些适用于所有使用场景的 [Palette](https://developer.android.com/reference/android/support/v7/graphics/Palette.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog) 内部构件，ColorUtils 可以很容易地计算出颜色之间的对比度，确定维持最小对比度的最小透明度值（完美地保证文字的阅读体验），或者将颜色转换为对应的 HSL 值（译者注：Hue[hju]色调，Saturation['sætʃə'reʃən]饱和度，Luminance['lumɪnəns]亮度）。\n\n插值器是所有动画系统的重要组成部分，它负责控制一个动画中某项数值改变的比率（例如加速、减速等）。Lollipop 中的[android.R.interpolator](http://developer.android.com/reference/android/R.interpolator.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)已经内置了许多插值器，例如用于[建立真实感的动效](http://www.google.com/design/spec/animation/authentic-motion.html)的fast_out_linear_in、fast_out_slow_in、and linear_out_slow_in。但现在我们可以用代码调用 [FastOutLinearInInterpolator](http://developer.android.com/reference/android/support/v4/view/animation/FastOutLinearInInterpolator.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)、[FastOutSlowInInterpolator](http://developer.android.com/reference/android/support/v4/view/animation/FastOutSlowInInterpolator.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)、[LinearOutSlowInInterpolator](http://developer.android.com/reference/android/support/v4/view/animation/LinearOutSlowInInterpolator.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog) 类为动画添加这些插值器。除了那些预建的插值器，我们还创建了允许你创建二次方或三次方贝塞尔曲线的 [PathInterpolatorCompat](http://developer.android.com/reference/android/support/v4/view/animation/PathInterpolatorCompat.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog) 类。\n\n这个版本的支持库还把[Space](http://developer.android.com/reference/android/support/v4/widget/Space.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)控件从GridLayout库移动到了Support V4，使其不需要在项目中添加单独的依赖。Space控件是一种轻量的、无形的控件，可用于创建控件间的间隙效果。\n\n\n## AppCompat\nAppCompat支持库开始地很低调，却是一个很重要的开端：为API 7及以上的设备提供了一个一致的Action Bar。\n在[版本21的修订中](http://android-developers.blogspot.com/2014/10/appcompat-v21-material-design-for-pre.html)，它承担了新的职责：带来了[material color palette](http://developer.android.com/training/material/theme.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog#ColorPalette)、控件着色、Toolbar支持，还有更多支持所有API 7+的设备。单从ActionBarActivity名字上看是体现不出它全部功能的。\n\n在此版本中，ActionBarActivity已经过时了，新的替代者是[AppCompatActivity](http://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)。然而，这不只是一个重命名。事实上，AppCompat的内在逻辑现在可以通过[AppCompatDelegate](http://developer.android.com/reference/android/support/v7/app/AppCompatDelegate.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)实现-这是一个可以在所有Activity中包含的类，与合适的生命周期方法挂钩，并得到一致的主题、着色等，而不需要使用AppCompatActivity （尽管这仍然是最简单的开始方式）。\n\n在全新AppCompatDelegate类的帮助下，我们继续增加了一致性体验的支持，通过[AppCompatDialog](http://developer.android.com/reference/android/support/v7/app/AppCompatDialog.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)类增加了材料设计规范对话框的支持。如果你之前使用过[AlertDialog ](http://developer.android.com/guide/topics/ui/dialogs.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog#AlertDialog)，你会很高心，因为现在支持库中也有其对应的版本：[support.v7.app.AlertDialog](http://developer.android.com/reference/android/support/v7/app/AlertDialog.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)，让你用相同的API享受 AppCompatDialog 带来的便利。\n\n在使用AppCompat时，自动为控件着色的能力是在你的应用程序中保持品牌烙印和一致性体验中的重要保证。因为在填充布局时AppCompat会自动地为你将诸如Button、TextView 这些传统控件替换为AppCompatButton、AppCompatTextView 等新控件，以确保布局内的每一个控件都能支持着色。而在新的支持库中，色彩感知控件现在已经被公开，让控件类对自动着色的支持能延续到子类中。\n\n这个列表囊括了目前所有的色彩感知控件：\n> - AppCompatAutoCompleteTextView\n> - AppCompatButton\n> - AppCompatCheckBox\n> - AppCompatCheckedTextView\n> - AppCompatEditText\n> - AppCompatMultiAutoCompleteTextView\n> - AppCompatRadioButton\n> - AppCompatRatingBar\n> - AppCompatSpinner\n> - AppCompatTextView\n\nLollipop增加了在一个view中通过view级别上的XML属性android:theme实现重写主题的能力-非常有用的特性，如在亮色activities上的黑色action bars。现在，AppCompat允许你为Toolbars使用android:theme（不赞成使用之前的app:theme）,更好地带来为API 11+的所有views的android:theme支持。\n\n如果你刚开始接触AppCompat，那么看看下面的视频，可以察觉出是多么容易上手，这就能为你所有的用户带来了一致性的设计：\n\n[![Android Support Library: Consistent Design with AppCompat](http://img.youtube.com/vi/5Be2mJzP-Uw/0.jpg)](https://youtu.be/5Be2mJzP-Uw)\n\n\n## Leanback\nLeanback库作为Android电视应用程序的最佳实践的集合，我们曾忽略去不使一个更美好的10的经验作为发行版的一部分。你会注意到加载后立即更新Leanback例子新功能的引导步骤。\n![](http://4.bp.blogspot.com/-I4Bjzlx8AzI/VS1fgphZnYI/AAAAAAAABhs/L5SfjRk_k40/s640/image00.png)\n\n这组类和主题可以用来构建一个多步骤的过程，这在Android TV上看起来很棒。它是由一个左边上的指导视图和右边的列表操作建立了起来。每一个都是可定制的，通过一些主题与Theme.Leanback.GuidedStep 的父类或其它，如果需要更多的定制，通过自定义一个[GuidanceStylist](http://developer.android.com/reference/android/support/v17/leanback/widget/GuidanceStylist.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)和[GuidedActionsStylist。](http://developer.android.com/reference/android/support/v17/leanback/widget/GuidedActionsStylist.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)。\n\n你还会发现大量的bug修复，性能改进，以及使它更完美贯穿在库中-所有与制作Leanback的经验，更多就是为用户和开发人员所喜欢。\n\n\n## RecyclerView\n除了一系列正确的bug修复，此版本增加了一个新的[SortedList](http://developer.android.com/reference/android/support/v7/util/SortedList.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)数据结构。此集合可以很容易地保持自定义对象的排序列表，通过[RecyclerView.Adapter](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)正确地分发数据改变的事件：维护item的 添加/删除/移动/改变 时RecyclerView提供的动画。\n\n此外，SortedList还支持成批地一起改变，调度只是适配器上一个单一的集操作，确保大量items改变时的最佳的用户体验。\n\n\n## Palette\n如果你已经使用Palette从图像中提取出颜色，你会很高兴地知道，现在在不会丢失品质下速度是之前的6~8倍！\n\nPalette现在使用建造者模式来实例化。不是直接调用Palette.generate(Bitmap)或者其它相似的操作，你会使用[Palette.from(Bitmap)](http://developer.android.com/reference/android/support/v7/graphics/Palette.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog#from(android.graphics.Bitmap))来取回一个[Palette.Builder](http://developer.android.com/reference/android/support/v7/graphics/Palette.Builder.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)实例。然后，在调用generate()或者generateAsync()检索颜色的色板之前，您可以随意更改的最大颜色数来生成并设置图像的最大尺寸来重新运行Palette。\n\n\n## Renderscript\nRenderscript给你巨大的计算潜力，此外这个支持库版本使得一些预先定义的脚本和调用脚本内部函数在API 8+的设备上变得可用。这个版本改善了所有设备的可靠性和性能，这些提升取决于本地Renderscript可用时通过一种改进的图像边缘检测算法实现-确保最快和最可靠的实现总是我们的选择。两个额外的内部函数也被添加在此版本中：[ScriptIntrinsicHistogram](http://developer.android.com/reference/android/support/v8/renderscript/ScriptIntrinsicHistogram.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)和[ScriptIntrinsicResize](http://developer.android.com/reference/android/support/v8/renderscript/ScriptIntrinsicResize.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)，完成采集到10。\n\n\n## SDK 现在可用了！\n没有比这更好的时间来开始使用Android支持库。今天你就可以使用这个库开始开发了，从Android SDK Manager下载Android支持库和Android支持资源吧。\n\n要了解更多关于Android的支持库和它提供给你的API，请访问Android开发者官网上的[支持库章节](http://developer.android.com/tools/support-library/index.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog)的网页。\n"
  },
  {
    "path": "issue-7/Android测试框架：Dagger2+Espresso2+Mockito/README.md",
    "content": "## Android测试框架: Dagger 2 + Espresso 2 + Mockito\n---\n\n>* 原文链接 : [Dagger 2 + Espresso 2 + Mockito](http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html)\n> * [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [yaoqinwei](https://github.com/yaoqinwei) \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成\n\n我一直在用Dagger, Espresso和Mockito做Android测试，爱死这个组合了！为了庆祝[Dagger 2](http://google.github.io/dagger/)的推出，我分享了一个用Dagger 2, Espresso 2和Mockito做Android测试的[Demo](https://github.com/chiuki/android-test-demo)\n\n### Dagger 组件(Components)\n\n[Dependency injection(依赖注入)](http://en.wikipedia.org/wiki/Dependency_injection) 允许我们在App开发和测试中可以获取到不同的模块，非常有利于创建可重用的测试用例，这个Demo App的功能是以`\"yyyy-MM-dd\"`格式显示今天的日期，我们需要测试一下来应对一些已知的日期，而非依赖于运行测试时的真实日期。\n\n在`Dagger 2`中，一个组件(Component)接口可以给整个App提供模块，并且定义了在哪注入它们。\n\n```java\npublic interface DemoComponent {\n\tvoid inject(MainActivity mainActivity);\n}\n\n@Singleton\n@Component(modules = ClockModule.class)\npublic interface ApplicationComponent extends DemoComponent {\n}\n\n@Singleton\n@Component(modules = MockClockModule.class)\npublic interface TestComponent extends DemoComponent {\n\tvoid inject(MainActivityTest mainActivityTest);\n}\n```\n\n**`ApplicationComponent`**组件用于App的正常运行, 而**`TestComponent`**组件则用于测试，这两个组件都可以被注入到**`MainActivity`**中。\n\n**`MainActivity`**如何知道使用的哪个组件(component)? 答案是通过**`DemoApplication`**来注入, 它保存着该组件(component)的引用。\n\n```java\nprivate DemoComponent component = null;\n\n@Override \npublic void onCreate() {\n\tsuper.onCreate();\n\tif (component == null) {\n\t\tcomponent = DaggerDemoApplication_ApplicationComponent\n\t\t\t\t\t.builder()\n\t\t\t\t\t.clockModule(new ClockModule())\n\t\t\t\t\t.build();\n  }\n}\n\npublic void setComponent(DemoComponent component) {\n\tthis.component = component;\n}\n\npublic DemoComponent component() {\n\treturn component;\n}\n```\n\n测试时，我们需要在**`onCreate()`**方法执行之前调用**`setComponent()`**方法，将组件设置为**`TestComponent`**。而App正常运行时,组件在**`onCreate()`**方法中就被设置为**`ApplicationComponent`**了。\n\n###\t Mockito\n\nApp中有一个**`Clock`**类，其中有一个方法可以返回当前的时间:\n\n```java\npublic DateTime getNow() {\n\treturn new DateTime();\n}\n```\n\n**`TestComponent`**组件中包含**`MockClockModule`**模块，后者使用[Mockito](http://mockito.org/)提供了一个模拟的**`Clock`**。这样[**`MainActivityTest`**](https://github.com/chiuki/android-test-demo/blob/master/app/src/androidTest/java/com/sqisland/android/test_demo/MainActivityTest.java)就可以在测试期间提供一个预先设置的日期了。\n\n```java\nMockito.when(clock.getNow()).thenReturn(new DateTime(2008, 9, 23, 0, 0, 0));\n```\n\n因为我们使用了单例, 相同的模拟**`Clock`**将为整个App提供日期，这样就能被显示提供的日期，而非今天的日期了:\n\n```java\nonView(withId(R.id.date)).check(matches(withText(\"2008-09-23\")));\n```\n\n###  更多\n\n这里还有很多示例, 包括使用intent启动的activity的测试和使用JUnit测试的单元测试，请点击下面链接查看:\n\n[Click Me](https://github.com/chiuki/android-test-demo)\n\n相关阅读:\n\n>* [Instrumentation Testing with Dagger, Mockito, and Espresso](http://engineering.circle.com/instrumentation-testing-with-dagger-mockito-and-espresso/)\n* [A JUnit @Rule which launches an activity when your test starts](https://gist.github.com/JakeWharton/1c2f2cadab2ddd97f9fb)\n* [EspressoStartGuide](https://code.google.com/p/android-test-kit/wiki/EspressoStartGuide) \n* [What’s new in Android Testing](http://wiebe-elsinga.com/blog/whats-new-in-android-testing/)\n* [https://github.com/googlesamples/android-testing](https://github.com/googlesamples/android-testing)\n"
  },
  {
    "path": "issue-7/Retrofit开发指南/README.md",
    "content": "## Retrofit指南\n---\n\n>* 原文链接 : [Retrofit Android Tutorial](http://themakeinfo.com/2015/04/retrofit-android-tutorial/)\n> * [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [yaoqinwei](https://github.com/yaoqinwei) \n* 校对者: [chaosssss](https://github.com/chaossss)\n* 状态 :  完成\n\n这是一篇关于如何使用Retrofit写一个Android的REST客户端的小教程。\n\n![Retrofit+Android](http://img.my.csdn.net/uploads/201504/13/1428932650_8819.jpg)\n\n## 我为什么选择Retrofit？\n\n在使用square的[Retrofit](http://square.github.io/retrofit/)之前，我尝试过Volley和AsyncTask。但在使用过Retrofit之后，我的工作变得更加简单了。在开始阅读教程之前，建议先阅读一下下面的几个话题。这是一个入门项目，可以让你了解如何使用Retrofit从API获取数据。\n\n这个项目也加到了[我的Github](https://github.com/basil2style/Retrofit-Android-Basic)中。\n\n[AsyncHttp ,Volley和Retrofit的对比](https://instructure.github.io/blog/2013/12/09/volley-vs-retrofit/)\n\n![volley-vs-retrofit](http://img.my.csdn.net/uploads/201504/13/1428929622_5444.png)\n\n与Retrofit相比，Volley是一个小型的、缺乏正式文档说明库。Retrofit是[Square](https://github.com/square)开发的，后者还开发过okhttp，picasso...等一些著名的库(你可以在[这里](https://square.github.io/#android)找到其他的库)。如果你需要Volley的指引，你可以在[Google Training](https://developer.android.com/training/volley/index.html)或者[Volley Plus from DWork](https://github.com/DWorkS/VolleyPlus)找到相关文档。\n\n## 简介\n\n[Retrofit](http://square.github.io/retrofit/)是[Square](http://square.github.io/)开发的一个Android和Java的REST客户端库。这个库非常简单并且具有很多特性，相比其他的网络库，更容易让初学者快速掌握。它可以处理GET、POST、PUT、DELETE...等请求，还可以使用picasso加载图片。在使用Picasso或Volley之前，可以先来读读[这个](https://www.bignerdranch.com/blog/solving-the-android-image-loading-problem-volley-vs-picasso/)。\n\n别纠结简介了，开始编码吧!!!\n\nDemo里使用是Github的API : https://api.github.com/users/basil2style\n\n你可以使用这个Demo App来搜索github的用户详细信息\n\n[GITHUB](https://github.com/basil2style/Retrofit-Android-Basic)\n\n![Download-Code](http://img.my.csdn.net/uploads/201504/13/1428929608_5673.png)\n\n[Download APK ](https://github.com/basil2style/Retrofit-Android-Basic/blob/master/APK/Retrofit%20Example.apk)\n\n<a href=\"https://github.com/basil2style/Retrofit-Android-Basic/blob/master/APK/Retrofit%20Example.apk\"><img class=\"\" src=\"http://img.my.csdn.net/uploads/201504/13/1428929607_5991.png\" alt=\"qr code\" width=\"196\" height=\"196\"></a>\n\n## 1) 概述\n\n![Retrofit-3-classes](http://img.my.csdn.net/uploads/201504/13/1428929609_1240.png)\n\n1) **POJO或模型实体类** : 从服务器获取的JSON数据将被填充到这种类的实例中。\n\n2) **接口** : 我们需要创建一个接口来管理像GET,POST...等请求的URL，这是一个服务类。\n\n3) **RestAdapter类** : 这是一个REST客户端(RestClient)类，retrofit中默认用的是Gson来解析JSON数据，你也可以设置自己的JSON解析器，比如jackson，我们将在下面的教程中详细解说明。\n\n## 2) 添加Retrofit库到项目中\n\n_**Gradle**_ :\n\n```groovy\ncompile 'com.squareup.retrofit:retrofit:1.9.0'\n```\n目前，1.9.0是最新的版本. 你可以在[这里](https://github.com/square/retrofit)获取更新的版本。\n\n_**JAR**_ :\n\n [下载Jar包](https://search.maven.org/remote_content?g=com.squareup.retrofit&a=retrofit&v=LATEST)\n\n## 3) 创建项目\n\n1) 在Android Studio中创建新项目: **File  =>  New Project** ，填写描述信息并点击**Next**.\n\n2) 填写**minimum SDK**，我用的是**4.0+**(Retrofit支持Android **2.3+**或Java **6**以上)\n\n3) 选择**Blank Activity**然后填写**Activity Name**和**Layout Name**，最后点击 **Finish**.\n\n4) **Gradle** : 你可以在**app =>build.gradle**中添加Retrofit的库文件。\n\n![gradle-dependencies](http://img.my.csdn.net/uploads/201504/13/1428929609_9928.png)\n\nJar包的添加方式 :  将jar包添加到libs文件夹下，右键点击Add as Library.\n\n5) 创建两个包：**API**和**model**。\n\n6) 在**API**包下右键点击**New** => **Java Class** , 填写**Name**为<font style=\"color: #ff0000;\">gitapi</font>并设置为<font style=\"color: #ff0000;\">Interface</font>。\n\n6) 在**API**包下创建名<font style=\"color: #ff0000;\">gitapi</font>的<font style=\"color: #ff0000;\">接口</font>。\n\n7) 在**model**包下右键点击**New** => **Java Class**, 填写**Name**为<font style=\"color: #ff0000;\">gitmodel</font>并设置为<font style=\"color: #ff0000;\">Class</font>。\n\n![proj-structure](http://img.my.csdn.net/uploads/201504/13/1428929622_5732.png)\n\n## 4) Android Manifest\n\n1) 添加<font style=\"color: #ff0000;\">INTERNET PERMISSION</font>权限\n\n```xml\n<uses-permission android:name=\"android.permission.INTERNET\"/>\n```\n\n你的`Manifest`文件看起来应该是这样的 :\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"  \n          package=\"com.makeinfo.flowerpi\" >\n   <uses-permission android:name=\"android.permission.INTERNET\"/>\n   <application android:allowBackup=\"true\"\n   \t   android:icon=\"@mipmap/ic_launcher\"\n       android:label=\"@string/app_name\"\n       android:theme=\"@style/AppTheme\" >\n       <activity\n           android:name=\".MainActivity\"\n           android:label=\"@string/app_name\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                 <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n </manifest>\n```\n\n## 5) 模型类\n\n首先，我们需要创建一个POJO或模型类。服务器返回的JSON数据不能在Java里直接使用，所以我们需要用模型类来做转换。\n\nURL的结构是这样的：https://api.github.com/users/ + “search term”\n\n举个栗子：https://api.github.com/users/basil2style\n\n我们的JSON返回数据是这样的：\n\n![json object response](http://img.my.csdn.net/uploads/201504/13/1428929583_4905.jpg)\n\n这是一个**JSON Object**，如果你不了解**JSON Array**和**JSON Object**的区别，请看[这里](http://stackoverflow.com/questions/12289844/difference-between-jsonobject-and-jsonarray)。\n\n使用[jsonschema2pojo](http://www.jsonschema2pojo.org/)来创建POJO更加简单，不要每一个JSON数据的POJO转换都用它，有时候会报错。选择源代码类型为**Json**，注解类型是**Gson**,然后点击**preview**。\n\n点击[这里](http://pastebin.com/4xckerN1)(需翻墙)查看`gitmodel.java`源代码。\n\n## 6) gitapi.java\n\n1) 现在我们需要使用接口调用URL.\n\n**`@GET(\"/users/{user}\")`**, 添加这个注解会调用服务器，参数url基于BASE URL，服务调用的参数以'/'开头，其中<font style=\"color: #ff0000;\">{user}</font>是从EditText获取的字符串。\n\n**`@Path(\"user\") String user`** 就是我们从EditText获取的字符串。\n\n服务器端响应的数据则会被存储到POJO实例中去。\n\n```java\npublic interface gitapi {\n   \n    @GET(\"/users/{user}\")      // here is the other url part.best way is to start using /\n    public void getFeed(@Path(\"user\") String user, Callback<gitmodel> response);\n     // string user is for passing values from edittext for eg: user=basil2style,google\n   \t // response is the response from the server which is now in the POJO\n}\n```\n\n## 7) RestAdapter\n\n现在该主要部分了，你需要设置`Rest Adapter`和`service`类.\n\n1) <font style=\"color: #ff0000;\">API</font>就是`Base URL`.\n\n2) 我们需要设置**`Endpoint(API)`**并调用**`buid()`**方法来创建一个**`RestAdapter`**对象。\n\n3) 使用我们的**`gitapi`**来创建一个服务适配器(service for adapter)。\n\n4) 调用函数并获得响应数据，回调接口是用来异步的获取模型实例的，我们的回调接口需要实现成功回调方法(success request)和错误处理方法(error handling)。\n\n5) 我们解析好的json数据的现在就存在于POJO实例中了，你可以每次调用一条。\n\n```java\nString API = \"https://api.github.com\";\n\nRestAdapter restAdapter = new RestAdapter.Builder().setLogLevel(RestAdapter.LogLevel.FULL).setEndpoint(API).build(); \n\ngitapi git = restAdapter.create(gitapi.class);\n\ngit.getFeed(user, new Callback<gitmodel>() {\n\t@Override\n\tpublic void success(gitmodel gitmodel, Response response) {\n\t\ttv.setText(\"Github Name :\" + gitmodel.getName() + \n\t\t           \"\\nWebsite :\" + gitmodel.getBlog() + \n\t\t           \"\\nCompany Name :\" + gitmodel.getCompany());\n\n\t\tpbar.setVisibility(View.INVISIBLE); // disable progressbar\n \t}\n\n \t@Override\n\tpublic void failure(RetrofitError error) {\n \t\ttv.setText(error.getMessage());\n \t\tpbar.setVisibility(View.INVISIBLE); //disable progressbar\n \t}\n });\n```\n\n完整的`MainActivty.java`代码：\n\n```java\npackage com.makeinfo.flowerpi;\n \nimport android.support.v7.app.ActionBarActivity;\nimport android.os.Bundle;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\n \nimport com.makeinfo.flowerpi.API.gitapi;\nimport com.makeinfo.flowerpi.model.gitmodel;\n \nimport retrofit.Callback;\nimport retrofit.RestAdapter;\nimport retrofit.RetrofitError;\nimport retrofit.client.Response;\n \n \npublic class MainActivity extends ActionBarActivity {\n \n    Button click;\n    TextView tv;\n    EditText edit_user;\n    ProgressBar pbar;\n    String API = \"https://api.github.com\";\t// BASE URL\n   \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n \n        click = (Button) findViewById(R.id.button);\n        tv = (TextView) findViewById(R.id.tv);\n        edit_user = (EditText) findViewById(R.id.edit);\n        pbar = (ProgressBar) findViewById(R.id.pb);\n        pbar.setVisibility(View.INVISIBLE);\n       \n        click.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String user = edit_user.getText().toString();\n                pbar.setVisibility(View.VISIBLE);\n               \n                // Retrofit section start from here...\n                // create an adapter for retrofit with base url\n                RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(API).build(); \n                       \n                // creating a service for adapter with our GET class       \n                gitapi git = restAdapter.create(gitapi.class);\t\n \n                // Now ,we need to call for response\n                // Retrofit using gson for JSON-POJO conversion\n               \n                git.getFeed(user,new Callback<gitmodel>() {\n                    @Override\n                    public void success(gitmodel gitmodel, Response response) {\n                        // we get json object from github server to our POJO or model class\n                       \n                        tv.setText(\"Github Name :\" + gitmodel.getName() + \n                                    \"\\nWebsite :\"+gitmodel.getBlog() + \n                                    \"\\nCompany Name :\"+gitmodel.getCompany());\n                       \n                        pbar.setVisibility(View.INVISIBLE);\t// disable progressbar\n                    }\n \n                    @Override\n                    public void failure(RetrofitError error) {\n                     \ttv.setText(error.getMessage());\n                        pbar.setVisibility(View.INVISIBLE);\t// disable progressbar\n                    }\n                });\n            }\n        });\n    }\n \n \n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return true;\n    }\n \n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        int id = item.getItemId();\n \n        //noinspection SimplifiableIfStatement\n        if (id == R.id.action_settings) {\n            return true;\n        }\n \n        return super.onOptionsItemSelected(item);\n    }\n}\n```\n"
  },
  {
    "path": "issue-7/readme.md",
    "content": "# 开发技术前线 第七期 周报\n\n## 一、Android \n### 1.1 技术文章\n| 文章名称 |   译者  | \n|---------|--------|\n| [使用Robolectric和Android生成代码覆盖率报告](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/使用Robolectric和Android生成代码覆盖率报告)  | [normalme](https://github.com/normalme)      | \n| [Retrofit开发指南](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Retrofit开发指南)  | [yaoqinwei](https://github.com/yaoqinwei)      |   \n| [Android测试框架:Dagger2+Espresso2+Mockito](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Android测试框架：Dagger2+Espresso2+Mockito)  | [yaoqinwei](https://github.com/yaoqinwei)      |   \n| [在Activity中使用Thread导致的内存泄漏](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/在Activity中使用Thread导致的内存泄漏)  | [chaossss](https://github.com/chaossss)      |  \n| [深入浅出Android 新特性-Transition-Part-3a](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/深入浅出Android 新特性-Transition-Part-3a)  | [tiiime](https://github.com/tiiime)      |   \n| [深入浅出Android 新特性-Transition-Part-3b](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/深入浅出Android 新特性-Transition-Part-3b)  | [tiiime](https://github.com/tiiime)      |  \n| [Android Lollipop 5.1.1 更新](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Android Lollipop update.md)  | [Mr.Simple](https://github.com/bboyfeiyu) \n| [Android Support 库 22.1版](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Android Support 库 22.1版.md)  | [Rocko](https://github.com/zhengxiaopeng) |  \n\n### 1.2 开源库\n\n* [自定义RecyclerView实现了listview、gridview、 瀑布流等样式](https://github.com/lucasr/twoway-view)\n\n* [一个类似iOS的Segment Control的控件](https://github.com/7heaven/SHSegmentControl)\n\n* [一个ViewIndicator](https://github.com/eccyan/SpinningTabStrip) \n\n\n## 二、iOS \n### 2.1 技术文章\n|       文章标题        |         译者           | \n|----------------------|------------------------|\n|  [iOS编程101-如何生成圆形和圆角的图像](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/iOS编程101-如何生成圆形和圆角的图像.md) |  [7heaven](https://github.com/7heaven) |  \n|  [CocoaPods指南](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/CocoaPods指南.md) |  [Lollypo](https://github.com/Lollypo)  |  \n|  [iOS开发-可滑动的单元格](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/iOS开发-可滑动的单元格.md) |  [Harries Chen](https://github.com/mrchenhao) | \n|  [View Debugging in Xcode 6 ](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/View Debugging in Xcode 6.md) |  [Mr.Simple](https://github.com/bboyfeiyu) |  \n\n\n\n###  2.2 开源库\n\n* [一个 AutoLayout 下自动计算 UITableViewCell 高度的扩展](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell)\n\n* [下拉刷新组件](https://github.com/CoderMJLee/MJRefresh) \n\n\n\n## 三、每周推荐\n\n* [友盟微社区,一个可以嵌入App中的微博SDK](http://wsq.umeng.com/)\n* [FIR.Im —— 免费的App内测托管平台，支持IOS、Android应用](http://fir.im/)\n\n\n## 四、涨薪机会\n\n|  公司  |     岗位     |     公司福利    |    详细信息   | \n|----|-----------------|----------------|---------------|\n| 友盟 |  Android 架构师  |  氛围好，弹性工作时间，福利好，专业团队 | [详细说明](https://github.com/android-cn/android-jobs/blob/master/%E5%8C%97%E4%BA%AC--JD/%E5%8F%8B%E7%9B%9F%20Android.md)  |  \n\t   \n\n\n## 五、段子\n\n1. 初中的时候，一个晚自习，前桌的一个心仪男生突然转身对我说：“10年后我一定会回来娶你。”\n   我一听，脸爆红，跟他玩得比较好，但没想到他会这么说...然后，他接着说，“回来取你狗命! 哈哈。。。”这么多年了，想起就无语."
  },
  {
    "path": "issue-7/使用Robolectric和Android生成代码覆盖率报告/readme.md",
    "content": "使用 Robolectric 和 Android 生成代码覆盖率（测试）报告\n---\n\n> * 原文链接 : [Code coverage reports using Robolectric and Android](http://raptordigital.blogspot.com/2014/08/code-coverage-reports-using-robolectric.html)\n* 原文作者 : [Kris Vandermast](http://raptordigital.blogspot.com/)\n* 译文出自 :  [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [normalme](https://github.com/normalme) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 : 校对中 \n\n#介绍\n\n我写过许多测试驱动开发与陷阱方面的文章。我认为，其中对测试驱动开发中遇到的陷阱的描述让整个介绍更加完整。测试驱动开发或者通常的测试中，最重要的是你清楚代码中哪些部分经过了测试，而哪些部分需要继续测试。\n\n你可以使用[JaCoCo](http://www.eclemma.org/jacoco/)搞定上述的问题，它对Grandle和Robolectric有较好的集成。\n\n# 配置build.gradle\n\n第一步，配置build.gradle。主要代码如下所示。完整代码见[GitHub](https://github.com/kvandermast/my-robolectric-app)。\n\n*build.gradle*\n\n```\n...\n\nandroid {\n    ...\n    buildTypes {\n        debug {\n            runProguard false\n            proguardFile 'proguard-rules.txt'\n            debuggable true\n            testCoverageEnabled = true\n\n        }\n    }\n\n    ...\n}\n\n...\n\napply plugin: 'jacoco'\n\njacoco {\n    toolVersion = \"0.7.1.201405082137\"\n}\n\ndef coverageSourceDirs = [\n        '../app/src/main/java'\n]\n\ntask jacocoTestReport(type:JacocoReport, dependsOn: \"testDebug\") {\n    group = \"Reporting\"\n\n    description = \"Generate Jacoco coverage reports\"\n\n    classDirectories = fileTree(\n            dir: '../app/build/intermediates/classes/debug',\n            excludes: ['**/R.class',\n                       '**/R$*.class',\n                       '**/*$ViewInjector*.*',\n                       '**/BuildConfig.*',\n                       '**/Manifest*.*']\n    )\n\n    additionalSourceDirs = files(coverageSourceDirs)\n    sourceDirectories = files(coverageSourceDirs)\n    executionData = files('../app/build/jacoco/testDebug.exec')\n\n    reports {\n        xml.enabled = true\n        html.enabled = true\n    }\n\n}\n```\n\n（现在）我们来看看其中最重要的配置。\n\n* buildType 声明，开启代码覆盖测试。\n\n```\n\nandroid {\n    ...\n    buildTypes {\n        debug {\n            runProguard false\n            proguardFile 'proguard-rules.txt'\n            debuggable true\n            testCoverageEnabled = true\n\n        }\n    }\n\n    ...\n}\n```\n\n* 加入一个 JaCoCo 插件，同时，指定使用最新版 : \n\n```\n\napply plugin: 'jacoco'\n\njacoco {\n    toolVersion = \"0.7.1.201405082137\"\n}\n\ndef coverageSourceDirs = [\n        '../app/src/main/java'\n]\n```\n\n* 配置 converageSourceDirs，指定一个文件夹，JaCoCo 将对文件夹中的目标进行反射。\n\n* 配置 JaCoCo 插件，指定你需要测试的类（它们已经经过编译）和不需要测试的类（比如 ButterKnife 注入的 *ViewInjector*）。\n\n```\ntask jacocoTestReport(type:JacocoReport, dependsOn: \"testDebug\") {\n    group = \"Reporting\"\n\n    description = \"Generate Jacoco coverage reports\"\n\n    classDirectories = fileTree(\n            dir: '../app/build/intermediates/classes/debug',\n            excludes: ['**/R.class',\n                       '**/R$*.class',\n                       '**/*$ViewInjector*.*',\n                       '**/BuildConfig.*',\n                       '**/Manifest*.*']\n    )\n\n    additionalSourceDirs = files(coverageSourceDirs)\n    sourceDirectories = files(coverageSourceDirs)\n    executionData = files('../app/build/jacoco/testDebug.exec')\n\n    reports {\n        xml.enabled = true\n        html.enabled = true\n    }\n\n}\n```\n\n#执行 gradle 任务\n\n修改 gradle.build 文件后，你必须执行与开发环境同步，以检查加入新插件后 gradle 也工作正常。\n\n在使用 JaCoCo 生成测试报告前，还需要提供 testDebug.exec 文件。（提供文件）最简单的方法是打开命令行，对你的项目上执行如下命令 ： \n`$ ./gradlew clean assemble`\n\n这条命令会清空之前编译生成的class文件，并重新构建。\n\n现在，你就可以使用 JaCoCo 生成测试报告啦，只要执行这条命令：\n`$ ./gradlew jacocoTestReport`\n\n（你会看到）终端将开始执行 grable 构建脚本，其中，最后一项任务是使用 JaCoCo 生成测试报告：\n\n>\n`$ ./gradlew jacocoTestReport\n:app:preBuild                \n:app:preDebugBuild                \n:app:checkDebugManifest                \n:app:preReleaseBuild                 \n:app:prepareComAndroidSupportSupportV42000Library UP-TO-DATE      \n:app:prepareDeKeyboardsurferAndroidWidgetCrouton184Library UP-TO-DATE      \n:app:prepareDebugDependencies                 \n:app:compileDebugAidl UP-TO-DATE      \n:app:compileDebugRenderscript UP-TO-DATE      \n:app:generateDebugBuildConfig UP-TO-DATE      \n:app:generateDebugAssets UP-TO-DATE      \n:app:mergeDebugAssets UP-TO-DATE      \n:app:generateDebugResValues UP-TO-DATE      \n:app:generateDebugResources UP-TO-DATE      \n:app:mergeDebugResources UP-TO-DATE      \n:app:processDebugManifest UP-TO-DATE      \n:app:processDebugResources UP-TO-DATE      \n:app:generateDebugSources UP-TO-DATE      \n:app:compileDebugJava UP-TO-DATE      \n:app:compileTestDebugJava                                                                    \n:app:processTestDebugResources UP-TO-DATE      \n:app:testDebugClasses                 \n:app:testDebug                                                             \n:app:jacocoTestReport                                                           \n               \nBUILD SUCCESSFUL\n               \nTotal time: 29.482 secs\n\n生成的代码覆盖率测试报告保存在 ./build/reports/jacoco/jacocoTestReport 中，结果类似下图：\n![p](http://img.blog.csdn.net/20150421201014450)    \n\n#注意事项\n\n* 你的**应用名**\n\n在我的例子中，Android module名是\"app\"。因此包含 `'../app/src/main/java'` 中的代码。如果你的Android module 名和例子中的不同，就请修改gradle文件中路径（所有涉及到 Android module 相关的路径）。比如，如果你的Android module名是FooBar，配置文件中就应修改为 `\"..app/src/main/java\"`。\n\n* **产品类別**\n\n例子中没用指明产品类别，所以构建任务使用`\"testDebug\"`和`\"../app/build/intermediates/classes/debug\"`中的class文件。但是，如果你在应用中指定产品类别（比如. Local），Gradle就找不到\"testDebug\"任务。所以，需要正确的命名，比如，这里你可以用 testLocalDebug 并包含正确的class文件：`'../app/build/intermediated/classes/debug'`。\n\n如果你有任何问题，别犹豫，直接来问我。代码在 [Github](https://github.com/kvandermast/my-robolectric-app) 上已经更新，请 Check Out。\n\n术语：    \n\n1. code coverage 代码覆盖率：软件测试中用来表示被测软件中被测试代码占整个软件的比例或程度。"
  },
  {
    "path": "issue-7/在Activity中使用Thread导致的内存泄漏/readme.md",
    "content": "在Activity中使用Thread导致的内存泄漏\n---\n\n> * 原文链接 : [Activitys, Threads, & Memory Leaks](http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html)\n* 原文作者 : [AlexLockwood](https://google.com/+AlexLockwood)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [yinna317](https://github.com/yinna317)  \n* 状态 :  完成\n\n\n\n\n> 注：这篇博文涉及的源码可以在 [GitHub](https://github.com/alexjlockwood/leaky-threads) 上面下载哦\n\n做 Android 开发最常遇到的问题就是在 Activity 的生命周期中协调耗时任务，避免执行任务导致不易察觉的内存泄漏。不妨先读一读下面的代码，代码写了一个简单的 Activity，Activity 在启动后就会开启一个线程，并循环执行该线程中的任务\n\n```java\n\n\t/** \n\t *  示例向我们展示了在 Activity 的配置改变时（配置改变会导致其下的 Activity 实例被销\n     *  毁）存活。此外，Activity 的 context 也是内存泄漏的一部分，因为每一个线程都被初始\n     *  化为匿名内部类，使得每一个线程都持有一个外部 Activity 实例的隐式引用，使得\n     *  Activity 不会被 Java 的垃圾回收机制回收。\n     */  \n\tpublic class MainActivity extends Activity {\n\t\n\t  @Override\n\t  protected void onCreate(Bundle savedInstanceState) {\n\t    super.onCreate(savedInstanceState);\n\t    exampleOne();\n\t  }\n\t\n\t  private void exampleOne() {\n\t    new Thread() {\n\t      @Override\n\t      public void run() {\n\t        while (true) {\n\t          SystemClock.sleep(1000);\n\t        }\n\t      }\n\t    }.start();\n\t  }\n\t}\n```\n\nActivity 配置发生改变会使 Activity 被销毁，并新建一个 Activity，我们总会觉得 Android 系统会将与被销毁的 Activity 相关的一切清理干净，例如回收与 Activity 关联的内存，Activity 执行的线程等等……然而，现实总是很残酷的，刚刚提到的这些东西都不会被回收，并导致内存泄漏，从而显著地影响应用的性能表现。\n\n## Activity 内存泄漏的根源 ##\n\n如果你读过我以前写的一篇有关 Handler 和 内部类的博文，那我接下来要讲的知识你肯定知道。在 Java 中，非静态匿名内部类会持有其外部类的隐式引用，如果你没有考虑过这一点，那么存储该引用会导致 Activity 被保留，而不是被垃圾回收机制回收。Activity 对象持有其 View 层以及相关联的所有资源文件的引用，换句话说，如果你的内存泄漏发生在 Activity 中，那么你将损失大量的内存空间。\n\n而这样的问题在 Activity 配置改变时会更加严重，因为 Activity 的配置改变表示 Android 系统将要销毁当前 Activity 并新建一个 Activity。举例来说吧，在使用应用的时候，你执行了10次横屏/竖屏操作，每一次方向的改变都会执行下面的代码，那么我们会发现（使用[ Eclipse 的内存分析工具](http://www.eclipse.org/mat/)可以看到）每一个 Activity 对象都会因为留有一个隐式引用而被保留在内存中。\n\n![](http://www.androiddesignpatterns.com/assets/images/posts/2013/04/15/activity-leak.png)\n\n每一次配置的改变都会使 Android 系统新建一个 Activity 并把改变前的 Activity 交给垃圾回收机制回收。但因为线程持有旧 Activity 的隐式引用，使该 Activity 没有被垃圾回收机制回收。这样的问题会导致每一个新建的 Activity 都将发生内存泄漏，与 Activity 相关的所有资源文件也不会被回收，其中的内存泄漏有多严重可想而知。\n\n看到这里可能你会很害怕，很惶恐，很无助，那我们该怎么办……莫慌，解决办法非常简单，既然我们已经确定了问题的根源，那么对症下药就可以了：我们把该线程类声明为私有的静态内部类就可以解决这个问题：\n\n```java\n\n\t /**\n      * 示例通过将线程类声明为私有的静态内部类避免了 Activity context 的内存泄漏问题，但\n      * 在配置发生改变后，线程仍然会执行。原因在于，DVM 虚拟机持有所有运行线程的引用，无论\n      * 这些线程是否被回收，都与 Activity 的生命周期无关。运行中的线程只会继续运行，直到\n      * Android 系统将整个应用进程杀死\n      */ \n\tpublic class MainActivity extends Activity {\n\t\n\t  @Override\n\t  protected void onCreate(Bundle savedInstanceState) {\n\t    super.onCreate(savedInstanceState);\n\t    exampleTwo();\n\t  }\n\t\n\t  private void exampleTwo() {\n\t    new MyThread().start();\n\t  }\n\t\n\t  private static class MyThread extends Thread {\n\t    @Override\n\t    public void run() {\n\t      while (true) {\n\t        SystemClock.sleep(1000);\n\t      }\n\t    }\n\t  }\n\t}\n```\n\n通过上面的代码，新线程再也不会持有一个外部 Activity 的隐式引用，而且该 Activity 也会在配置改变后被回收。\n\n## 线程内存泄漏的根源 ##\n\n第二个问题是：对于每个新建 Activity,如果 Activity 中的线程发生发生内存泄漏。在Java中线程是垃圾回收机制的根源，也就是说，在运行系统中DVM虚拟机总会使硬件持有所有运行状态的进程的引用，结果导致处于运行状态的线程将永远不会被回收。因此，你必须为你的后台线程实现销毁逻辑！下面是一种解决办法：\n\n```java\n\n\t/**\n     * 除了我们需要实现销毁逻辑以保证线程不会发生内存泄漏，其他代码和示例2相同。在退出当前\n     * Activity 前使用 onDestroy() 方法结束你的运行中线程是个不错的选择\n\t */\n\tpublic class MainActivity extends Activity {\n\t  private MyThread mThread;\n\t\n\t  @Override\n\t  protected void onCreate(Bundle savedInstanceState) {\n\t    super.onCreate(savedInstanceState);\n\t    exampleThree();\n\t  }\n\t\n\t  private void exampleThree() {\n\t    mThread = new MyThread();\n\t    mThread.start();\n\t  }\n\t\n      /**\n\t   * 私有的静态内部类不会持有其外部类的引用，使得 Activity 实例不会在配置改变时发生内\n\t   * 存泄漏\n       */\n\t  private static class MyThread extends Thread {\n\t    private boolean mRunning = false;\n\t\n\t    @Override\n\t    public void run() {\n\t      mRunning = true;\n\t      while (mRunning) {\n\t        SystemClock.sleep(1000);\n\t      }\n\t    }\n\t\n\t    public void close() {\n\t      mRunning = false;\n\t    }\n\t  }\n\t\n\t  @Override\n\t  protected void onDestroy() {\n\t    super.onDestroy();\n\t    mThread.close();\n\t  }\n\t}\n```\n\n通过上面的代码，我们在 onDestroy() 方法中结束了线程，确保不会发生意外的线程的内存泄漏问题。如果你想要在配置改变后保留该线程（而不是每一次在关闭 Activity 后都要新建一个线程），那我建议你使用 Fragment 去完成该耗时任务。你可以翻我以前的博文，一名叫作**“Handling Configuration Changes with Fragments”**应该能满足你的需求，在API demo中也提供了很好理解的例子来为你阐述相关概念。\n\n## 结论 ##\n\nAndroid 开发过程中，在 Activity 的生命周期里协调耗时任务可能会很困难，你一不小心就会导致内存泄漏问题。下面是一些小提示，能帮助你预防内存泄漏问题的发生：\n\n- **尽可能使用静态内部类而不是非静态内部类。**每一个非静态内部类实例都会持有一个外部类的引用，若该引用是 Activity 的引用，那么该 Activity 在被销毁时将无法被回收。如果你的静态内部类需要一个相关 Activity 的引用以确保功能能够正常运行，那么你得确保你在对象中使用的是一个 Activity 的弱引用，否则你的 Activity 将会发生意外的内存泄漏。\n\n- **不要总想着 Java 的垃圾回收机制会帮你解决所有内存回收问题。**就像上面的示例，我们以为垃圾回收机制会帮我们将不需要使用的内存回收，例如：我们需要结束一个 Activity，那么它的实例和相关的线程都该被回收。但现实并不会像我们剧本那样走。Java 线程会一直存活，直到他们都被显式关闭，抑或是其进程被 Android 系统杀死。所以，为你的后台线程实现销毁逻辑是你在使用线程时必须时刻铭记的细节，此外，你在设计销毁逻辑时要根据 Activity 的生命周期去设计，避免出现 Bug。\n\n- **考虑你是否真的需要使用线程。**Android 应用的框架层为我们提供了很多便于开发者执行后台操作的类。例如：我们可以使用 Loader 代替在 Activity 的生命周期中用线程通过注入执行短暂的异步后台查询操作，考虑用 Service 将结构通知给 UI 的 BroadcastReceiver。最后，记住，这篇博文中对线程进行的讨论同样适用于 AsyncTask（因为 AsyncTask 使用 ExecutorService 执行它的任务）。然而，虽说 ExecutorService 只能在短暂操作（文档说最多几秒）中被使用，那么这些方法导致的 Activity 内存泄漏应该永远不会发生。\n\n这篇博文的源码可以在 [GitHub](https://github.com/alexjlockwood/leaky-threads) 中下载，你也可以在 [Google Play](https://play.google.com/store/apps/details?id=com.adp.leaky.threads) 下载 APK 使用。\n\n![](http://www.androiddesignpatterns.com/assets/images/posts/2013/04/15/leaky-threads-screenshot.png)"
  },
  {
    "path": "issue-7/深入浅出Android新特性-Transition-Part-3a/readme.md",
    "content": "`深入理解 Shared Element Transition (part 3a)`\n---\n\n>\n* 原文链接 :  [Shared Element Transitions In-Depth (part 3a)][source-url]\n* 作者 : [Alex Lockwood](https://plus.google.com/+AlexLockwood)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime)\n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n\n\n##深入理解共享元素 Transition\n\n这篇文章会深度分析共享元素 transitions 和它在 Activity & Fragment Transitions API 中的作用。这篇文章是下面这个系列中的第三篇：\n\n- Part 1: [在 Activity 和 Fragment 中使用 Transition ][part-1]\n- Part 2: [深入理解 Content Transition][part-2]\n- Part 3a: [深入理解共享元素 Transition][part3a]\n- Part 3b:  [延迟共享元素的 Transition][part-3b]\n- Part 3c: 共享元素回调实践 (coming soon!)\n- Part 4:  Activity & Fragment 过渡动画示例(coming soon!)\n\nPart 3 会分成三个部分: part3a 介绍 共享元素 transitions 的底层操作，part3b 和 part3c \n主要关注 API 的具体实现细节，例如推迟某些 共享元素 transition 的重要性和如何实现\n**SharedElementCallbacks**。\n\n我们首先会总结下在 [part 1][part-1] 中提到的关于 共享元素 transition 的知识点，然后说一说在 Android Lollipop 中是怎样使用它来构建合适的过渡动画。\n\n##什么是 共享元素 Transition ?\n\n共享元素 transition 决定了 共享元素 视图(也叫做主角视图)在\nActivity/Fragment 场景过渡时的动画效果。共享元素 的动画\n在被调用 Activity/Fragment  的 **进入/返回**  共享元素 transition\n<a id=\"1\" href=\"#b1\">(1)</a> 中执行，\n可以通过下面的[Window][window]/[Fragment][fragment] 方法设置：\n\n- **setSharedElementEnterTransition()** -  **B** 的 **进入** 共享元素 transition ，执行将 \n\t共享元素视图 从 **A** 中的起始位置移动到它在 **B** 中的最终位置的动画。\n- **setSharedElementReturnTransition()**  - **B** 的 **返回** 共享元素 transition ，执行将 \n\t共享元素视图 从 **B** 中起始位置移动到它在 **A** 中的最终位置的动画。\n\n[**Video 3.1**][video-3.1] 展示了在 Google Play Music 中是怎样使用共享元素 transition \n的。这个 transition 包含两个元素：一个 **ImageView**和它的父视图 **CardView**。\nTransition 期间，**CardView** 会扩展到全屏或收缩回原状， \n**ImageView** 能在这两个 Activity 里无缝的衔接。\n\n在 [part1 ][part-1] 里只是简单的介绍了下这个话题，这篇文章将会对 共享元素 transition\n做更深度的分析。例如 共享元素 Transition 在底层是如何实现的？都有哪些类型的 Transition 对象可以使用? Transition 期间 共享元素视图 是在哪里怎样绘制的？接下来的几章里\n我们会逐个解答这些问题。\n\n##深入共享元素 Transitions 底层\n\n之前的文章已经介绍过 Transition 的两个主要任务分别是获取目标视图的 开始&结束 状态和创建这两个状态间视图的过渡动画(Animator)。共享元素 Transition 也一样：\n在创建动画前需要捕获每一个\n共享元素视图的起始和结束状态(就是共享元素在 **调用/被调用** Activity/Fragment\n里的位置，大小和外观)。有了这些信息 共享元素 Transition 就可以确定每一个 共享元素\n视图应该执行的动画。\n\n和 [Content Transitions 的底层][content-transition]相似，框架通过在运行时明确的\n更改每个 共享元素视图 的属性将这个状态信息提供给 共享元素 Transition 。\n更准确地说 Activity **A** 启动 Activity **B** 时将会出现以下事件：<a id=\"2\" href=\"#b2\">(2)</a>\n\n---\n\n1. Activity **A** 调用 **startActivity()** 构造，测量，布局了一个\n\t最初背景色为透明的半透明窗口 Activity **B** 。\n2. 框架将 **B** 中每一个共享元素视图复位到对应的原来在 **A** 中时的位置，接着 **B** 的进入 transition 捕获 **B** 中所有共享元素视图的起始状态。\n3. 框架将 **B** 中每一个共享元素视图复位到对应的在 **B** 的最终位置，接着 **B** 的进入 transition 捕获 B 中所有共享元素视图的结束状态。\n4. **B** 的进入 transition 比较所有共享元素视图的起始和结束状态，根据它们的不同\n\t创建一个 **Animator**。\n5. 框架命令 **A** 隐藏共享元素视图，并运行返回的 **Animator**。**B** 中的\n\t共享元素视图到位之后，**B** 的窗口背景在 **A **上逐渐显示，直到 **B** \n\t完全的显示出来，transition 运行完毕。\n---\n\nContent transitions 是根据每个过渡视图的可见性变化来调节的，**共享元素 transition \n是根据每个共享元素视图的位置，大小和外观的变化来调节的**。从 API 21 开始，框架提供了\n几个自定义共享元素场景切换动画的 **Transition** 实现。\n\n- [ChangeBounds][ChangeBounds] - 捕获共享元素布局边界根据不同构造动画。\n\t**ChangeBounds** 在共享元素 Transition 中经常使用，大多数共享元素在两个\n\tActivity/Fragment 间会有大小 或/和 位置不同。 \n- [ChangeTransform][ChangeTransform ]  - 捕获共享元素缩放和角度，\n\t根据不同构建动画<a id=\"3\" href=\"#b3\">(3)</a>。\n- [ChangeClipBounds][ChangeClipBounds] - 捕获共享元素的 [clip bounds][clip-bounds] \n\t(剪辑边界) ，根据不同构建动画。\n- [ChangeImageTransform][ChangeImageTransform] - 捕获共享元素 ImageView 的\n\t变换矩阵( transform matrices) ，根据不同构建动画。结合 **ChangeBounds**，\n\t可以让 ImageView\n\t 无缝的改变大小，形状和 [ ImageView.ScaleType ][ImageView.ScaleType]。 \n- [@android:transition/move][move] - 一个 **TransitionSet** ，同时执行上面四种\n\ttransition 。在 [part 1][part-1] 里提到过，如果没有明确的声明 进入/返回 共享元素 \n\ttransition ，框架会默认运行这个 transition。\n\t\n---\n\n在上面的例子中，我们还可以发现 **共享元素视图实例并没有在 Activities/Fragments 间\n“共享”**。事实上，进入/返回 共享元素 transitions期间，用户看到的绝大多数东西都是在\n**B** 的 content view 中绘制的。框架并没有从 **A** 向 **B** \n传递 共享元素视图实例，\n而是采用了不同的方法实现相同的视觉效果。当 **A** 启动 **B** ，框架收集 **A**\n中共享元素的所有相关信息，并传递给 **B**。接下来 **B** 使用这些信息初始化\n共享元素视图的起始状态(它们在 **A** 中时对应的大小，位置和外观)。Transition \n开始时，B 中除了共享元素视图外的所有东西都被初始化为对用户不可见。Tansition\n的执行过程中，框架将 B 的 Activity 窗口逐渐显示，直到 B \n中共享元素结束动画窗口变为不透明。\n\n---\n\n##使用共享元素 Overlay <a id=\"4\" href=\"#b4\">(4)</a>\n最后，如果想要完全理解共享元素 transition 的运作，我们必须先说说共享元素 overlay。\n可能不是很明显，**共享元素默认是在整个窗口视图层的顶层  [ViewOverlay][ViewOverlay] \n上绘制**。简单介绍下 ，\n**ViewOverlay** 这个类是在 API 18 中为了方便在视图层顶层\n绘制引入的。添加到视图 **ViewOverlay** 之中的Drawable \n和 view (甚至是一个 **ViewGroup** 的子类) ，\n将会被绘制在视图的最上层。这就解释了框架为什么\n默认选择在窗口视图层的  **ViewOverlay** 中绘制共享元素。\n共享元素视图应该是贯穿整个 transition 的焦点；\n如果 transitioning views 意外的绘制在共享元素之上就会\n破坏这个效果<a id=\"5\" href=\"#b5\">(5)</a>。\n\n---\n\n虽然共享元素默认绘制在共享元素的 ViewOverlay 之中，但是\n框架也提供了关闭 overlay 的方法，只要调用\n[Window#setSharedElementsUseOverlay(false) ][setsharedelementuseoverlay] \n就可以了。如果你关闭了 overlay\n，要留意这样做可能会引起的副作用。例如，[Video 3.2][video-3.2]\n 执行了一个简单的共享元素 transition 两次，\n一次开启和一次关闭 共享元素 overlay 。第一次达到了预期想要的结果，\n第二次关闭 overlay 后运行的效果不理想。Transition view 从底部向上移入\n调用 Activity 的 content view 时挡住了部分 共享元素 **ImageView** 。虽然\n可以改变在 View 上绘制视图的顺序或者通过在共享元素 parent 里调用\n **setClipChildren(false)** 这些旁门左道来修复问题，但是与可能带来的维护问题\n 相比真是得不偿失。总之，除非你感觉必须要关掉共享元素 overlay 才能达到你想要的效果，\n 其他情况尽量不要关闭它，这样会保持代码简洁，并且共享元素 transition 效果更引人注目。\n \n##结语\n\n综上所诉，这篇文章讲了三个重点:\n\n1. 共享元素 transition 确定 共享元素视图(主角视图) 从一个 Activity/Fragment 移动到\n\t另一个其间场景过渡的动画。\n2. 共享元素 transition 是根据每一个 共享元素视图 的位置，大小，和外观的变化调节的。\n3. 共享元素默认是绘制在窗口视图的顶层 **ViewOverlay** 上面的。\n\n希望这篇文章对你有所帮助 ～\n\n---\n\n1 Note that the Activity Transition API gives you the ability to also specify exit and reenter shared element transitions using the setSharedElementExitTransition() and setSharedElementReenterTransition() methods, although doing so is usually not necessary. For an example illustrating one possible use case, check out this blog post. For an explanation why exit and reenter shared element transitions are not available for Fragment Transitions, see George Mount's answer and comments in this StackOverflow post. ↩\n\n2 A similar sequence of events occurs during the exit/return/reenter transitions for both Activities and Fragments. ↩\n\n3 One other subtle feature of ChangeTransform is that it can detect and handle changes made to a shared element view's parent during a transition. This comes in handy when, for example, the shared element's parent has an opaque background and is by default selected to be a transitioning view during the scene change. In this case, the ChangeTransform will detect that the shared element's ｀parent is being actively modified by the content transition, pull out the shared element from its parent, and animate the shared element separately. See George Mount's StackOverflow answer for more information. ↩\n\n4 Note that this section only pertains to Activity Transitions. Unlike Activity Transitions, shared elements are not drawn in a ViewOverlay by default during Fragment Transitions. That said, you can achieve a similar effect by applying a ChangeTransform transition, which will have the shared element drawn on top of the hierarchy in a ViewOverlay if it detects that its parent has changed. See this StackOverflow post for more information. ↩\n\n5 Note that one negative side-effect of having shared elements drawn on top of the entire view hierarchy is that this means it will become possible for shared elements to draw on top of the System UI (such as the status bar, navigation bar, and action bar). For more information on how you can prevent this from happening, see this Google+ post. ↩\n\n\n1.  Activity Transition API 也提供了\n\t**setSharedElementExitTransition()** 和 **setSharedElementReenterTransition()**\n\t这两个方法来设置 退出/重入 共享元素过渡，虽然通常来说是不必要的。\n\t[这篇文章][thispost]介绍了一个可能会遇到的用例。在这个 [stackoverflow][stackoverflow1]\n\t提问中 George Mount 的回答解释了为什么 退出/重入 共享元素 transition 在\n\tFragment Transitions 中不可用。 <a id=\"b1\" href=\"#1\">↩</a>\n\n2. Activities 和 Fragments 的 退出/返回/重入 transition 过程中出现事件序列相似  <a id=\"b2\" href=\"#2\">↩</a>\n3. **ChangeTransform** 还有一个超赞的特性，它可以检测并处理共享元素父视图过渡期间\n\t的改变。当共享元素父视图有一个不透明背景，在场景变换过程中默认被选为\n\ttransitioning view 时，**ChangeTransform** 就有了用武之地。如果它检测出\n\t共享元素父视图已被 content transition 更改，就会将共享元素提取出来，单独执行\n\t共享元素的动画。[StackOverflow answer][stackoverflow2] 这里有 George Mount \n\t的详细说明。\n\t<a id=\"b3\" href=\"#3\">↩</a>\n4. 注意，这部分只与 Activity Transition 有关。和Activity Transition 不同，Fragment Transition\n\t期间共享元素默认不在 **ViewOverlay** 中绘制。尽管如此，你仍可以使用 ChangeTransform \n\ttransition 来达到相似的效果，如果它检测到父视图改变了，就会把共享元素绘制在 \n\t**ViewOverlay** 的顶层。[StackOverflow][stackoverflow3] 这里有更多信息。\n\t <a id=\"b4\" href=\"#4\">↩</a>\n5. 注意，将共享元素绘制在整个图层最顶层也有一些负面效果。有可能\n\t会将共享元素绘制在 System UI 之上(比如 status bar, navigation bar还有 action bar)。\n\t解决方法看这里 [Google+ post][gplus]。\n\t<a id=\"b5\" href=\"#5\">↩</a>\n\n---\n\n[source-url]:http://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html\n\n[part-1]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html\n[part-2]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html\n[part3a]:http://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html\n[part-3b]:http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html\n[window]:http://developer.android.com/reference/android/view/Window.html\n[fragment]:http://developer.android.com/reference/android/app/Fragment.html\n[content-transition]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html\n[video-3.1]:http://www.androiddesignpatterns.com/assets/videos/posts/2015/01/12/music-opt.mp4\n[Video-3.2]:http://www.androiddesignpatterns.com/assets/videos/posts/2015/01/12/overlay-opt.mp4\n[ChangeBounds]:https://developer.android.com/reference/android/transition/ChangeBounds.html\n[ChangeTransform ]:https://developer.android.com/reference/android/transition/ChangeTransform.html\n[ChangeClipBounds]:https://developer.android.com/reference/android/transition/ChangeClipBounds.html\n[ChangeImageTransform]:https://developer.android.com/reference/android/transition/ChangeImageTransform.html\n[move]:https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/res/res/transition/move.xml\n[clip-bounds]:https://developer.android.com/reference/android/view/View.html#getClipBounds()\n[ImageView.ScaleType]:https://developer.android.com/reference/android/widget/ImageView.ScaleType.html\n[ViewOverlay]:https://developer.android.com/reference/android/view/ViewOverlay.html\n[setsharedelementuseoverlay]:https://developer.android.com/reference/android/view/Window.html#setSharedElementsUseOverlay(boolean)\n[thispost]:https://halfthought.wordpress.com/2014/12/08/what-are-all-these-dang-transitions/\n[stackoverflow1]:http://stackoverflow.com/questions/27346020/understanding-exit-reenter-shared-element-transitions\n[stackoverflow2]:http://stackoverflow.com/questions/26899779/enter-transition-on-a-fragment-with-a-shared-element-targets-the-shared-element\n[stackoverflow3]:http://stackoverflow.com/questions/27892033/is-there-a-setsharedelementsuseoverlay-method-for-fragment-transitions\n[gplus]:https://plus.google.com/+AlexLockwood/posts/RPtwZ5nNebb\n"
  },
  {
    "path": "issue-7/深入浅出Android新特性-Transition-Part-3b/readme.md",
    "content": "延迟共享元素的过渡动画 (part 3b)\n---\n\n>\n* 原文链接 : [Postponed Shared Element Transitions (part 3b)][source-url]\n* 作者 : [Alex Lockwood](https://plus.google.com/+AlexLockwood)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime)\n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  完成\n\n通过讨论 Lollipop Transition API 的一个重要的特性：延迟共享元素的过渡动画，这篇博文将继续我们关于共享元素 Transition 的深度解析。这也是我关于 Transition 这个专栏的第四篇文章。\n\n- Part 1: [在 Activity 和 Fragment 中使用 Transition ][part-1]\n- Part 2: [深入理解 Transition][part-2]\n- Part 3a: [深入理解共享元素的 Transition][part3a]\n- Part 3b:  [延迟共享元素的 Transition][part-3b]\n- Part 3c: 共享元素回调实践 (coming soon!)\n- Part 4:  Activity & Fragment 过渡动画示例(coming soon!)\n\n我们通过一个常见的问题来解释为什么需要推迟某些共享元素的过渡动画。\n\n##理解问题\n\n通常问题的根源是框架在 Activity 生命周期非常早的时候启动共享元素 Transition 。回想我们的第一篇文章，Transitions 必须捕获目标 View 的起始和结束状态来构建合适的动画。因此，如果框架在共享元素获得它在调用它的 Activity 中所给定的大小和位置前启动共享元素的过渡动画，这个 Transition 将不能正确捕获到共享元素的结束状态值,生成动画也会失败(一个过渡失败的例子[Video 3.3](http://www.androiddesignpatterns.com/assets/videos/posts/2015/03/09/postpone-bug-opt.mp4)).\n\nTransition 开始前，能否计算出正确的共享元素的结束值主要依靠两个因素:\n\n(1) 调用共享元素的 Activity 的布局复杂度以及布局层次结构的深度 \n(2)调用共享元素Activity载入数据消耗的时间\n\n布局越复杂，在屏幕上确定共享元素的大小位置耗时越长。同样，如果调用共享元素的 Activity 依赖一个异步的数据载入，框架仍有可能会在数据载入完成前自动开始共享元素 Transition。下面列出的是你可能遇到的常见问题:\n\n- **存在于 Activity 托管的 Fragment 中的共享元素**。[FragmentTransactions 在 commit 后并不会被立即执行][FragmentTransactions]，它们会被安排到主线程中等待执行。因此，如果共享元素存在的 Fragment 的视图层和FragmentTransaction没有被及时执行，框架有可能在共享元素被正确测量大小和布局到屏幕前启动共享元素 Transition。<a id=\"b1\" href=\"#1\">(1)</a>\n\n- **共享元素是一个高分辨率的图片**。给 **ImageView** 设置一个超过其初始化边界的高分辨率图片，最终可能会导致在这个视图层里出现[额外的布局传递][add-layout-pass]，由此增加在共享元素准备好前就启动 Transition 的几率。流行的异步图片处理库比如 [`Volley`][volley] 和 [`Picasso`][picasso] ，也不能可靠的解决这个问题：框架不能预先了解图片是要被下载，缩放还是在后台线程中从磁盘读取，也不管图片是否处理完毕就启动共享元素 Transition。\n\n- **共享元素依赖于异步的数据加载**如果共享元素所需的数据是通过**AsyncTask**，**AsyncQueryHandler**,**Loader**或者其他类似的东西加载，在它们获得在调用它们的 Activity 的最终数据（大小、位置）前，框架就有可能在主线程中启动 Transition。\n\n现在你可能会想：如果有办法能让暂时延迟 Transition 的使用，直到我们确定了共享元素的确切大小和位置才使用它就好了。幸好 Activity Transitions API<a id=\"b2\" href=\"#2\">(2)</a> 为我们提供了解决方案。\n\n在 Activity 的`onCreate()`中调用[`postponeEnterTransition()`][postponeEnterTransition] 方法来暂时阻止启动共享元素 Transition。之后，你需要在共享元素准备好后调用 [`startPostponedEnterTransition`][startPostponedEnterTransition] 来恢复过渡效果。常见的模式是在一个[`OnPreDrawListener`][onPreDrawListener]中启动延时 Transition，它会在共享元素测量和布局完毕后被调用<a id=\"b3\" href=\"#3\">(3)</a>。\n\n```java\n\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    // Postpone the shared element enter transition.\n    postponeEnterTransition();\n\n    // TODO: Call the \"scheduleStartPostponedTransition()\" method\n    // below when you know for certain that the shared element is\n    // ready for the transition to begin.\n}\n\n/**\n * Schedules the shared element transition to be started immediately\n * after the shared element has been measured and laid out within the\n * activity's view hierarchy. Some common places where it might make\n * sense to call this method are:\n *\n * (1) Inside a Fragment's onCreateView() method (if the shared element\n *     lives inside a Fragment hosted by the called Activity).\n *\n * (2) Inside a Picasso Callback object (if you need to wait for Picasso to\n *     asynchronously load/scale a bitmap before the transition can begin).\n *\n * (3) Inside a LoaderCallback's onLoadFinished() method (if the shared\n *     element depends on data queried by a Loader).\n */\nprivate void scheduleStartPostponedTransition(final View sharedElement) {\n    sharedElement.getViewTreeObserver().addOnPreDrawListener(\n        new ViewTreeObserver.OnPreDrawListener() {\n            @Override\n            public boolean onPreDraw() {\n                sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);\n                startPostponedEnterTransition();\n                return true;\n            }\n        });\n}\n```\n\n忽略方法名，这里还有第二种方法可以延迟共享元素的返回 Transition，在调用Activity的[`onActivityReenter()`][onActivityReenter] 方法中延缓返回 Transition<a id=\"b4\" href=\"#4\">(4)</a>\n\n```java\n/**\n * Don't forget to call setResult(Activity.RESULT_OK) in the returning\n * activity or else this method won't be called!\n */\n@Override\npublic void onActivityReenter(int resultCode, Intent data) {\n    super.onActivityReenter(resultCode, data);\n\n    // Postpone the shared element return transition.\n    postponeEnterTransition();\n\n    // TODO: Call the \"scheduleStartPostponedTransition()\" method\n    // above when you know for certain that the shared element is\n    // ready for the transition to begin.\n}\n\n```\n\n尽管添加延时可以让共享元素 Transition 更加流畅准确，但是你也要知道在应用中引入共享元素 Transition 的延迟可能会产生一些负面影响：\n\n- **调用`postponeEnterTransition`后不要忘记调用`startPostponedEnterTransition`**。\n忘记调用`startPostponedEnterTransition`会让你的应用处于死锁状态，用户无法进入下个Activity。\n\n\n- **不要将共享元素 Transition 延迟设置到1s以上**。延迟时间过长会在应用中产生不必要的卡顿，影响用户体验。\n\n\n感谢阅读！希望这篇文章对你有所帮助。\n\n<a id=\"1\" href=\"#b1\">**1**</a>: 当然，许多应用通过调用 [`FragmentManager#executePendingTransactions()`](https://developer.android.com/reference/android/app/FragmentManager.html#executePendingTransactions()) 来避开这个问题，这样会强制立即执行FragmentTransactions而不是异步。\n\n<a id=\"2\" href=\"#b2\">**2**</a>: 注意!`postponeEnterTransition()`和`startPostponedEnterTransition()`只对 Activity Transition起作用，对Fragment无效。详细信息可以在这里找到 [StackOverflow](http://stackoverflow.com/questions/26977303/how-to-postpone-a-fragments-enter-transition-in-android-lollipop) & [Google+](https://plus.google.com/+AlexLockwood/posts/3DxHT42rmmY)\n\n<a id=\"3\" href=\"#b3\">**3**</a>: 小贴士:你可以先调用 [`View#isLayoutRequested()`](http://developer.android.com/reference/android/view/View.html#isLayoutRequested()) 来确认是否需要调用 [`OnPreDrawListener`][OnPreDrawListener]，有必要的话 [`View#isLaidOut()`](http://developer.android.com/reference/android/view/View.html#isLaidOut()) 在一些情况下也能派上用场\n\n\n<a id=\"4\" href=\"#b4\">**4**</a>: 在开发者选项中启用不保留 Activity 选项可以方便调试共享元素返回/重新进入时对应过渡动画的行为，这也可以帮助测试在返回的过渡效果开始之前可能发生最糟糕的情况( Activity 需要重新构造布局加载数据...)\n\n\n[source-url]:http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html\n[FragmentTransactions]:https://developer.android.com/reference/android/app/FragmentTransaction.html#commit()\n[postponeEnterTransition]:https://developer.android.com/reference/android/app/Activity.html#postponeEnterTransition()\n[startPostponedEnterTransition]:https://developer.android.com/reference/android/app/Activity.html#startPostponedEnterTransition()\n[add-layout-pass]:https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/widget/ImageView.java#L453-L455\n[video]:http://www.androiddesignpatterns.com/assets/videos/posts/2015/03/09/postpone-bug-opt.mp4\n[volley]:https://android.googlesource.com/platform/frameworks/volley\n[picasso]:http://square.github.io/picasso/\n[onPreDrawListener]:http://developer.android.com/reference/android/view/ViewTreeObserver.OnPreDrawListener.html\n[onActivityReenter]:https://developer.android.com/reference/android/app/Activity.html#onActivityReenter(int,%20android.content.Intent)\n\n[part-1]:https://github.com/bboyfeiyu/android-tech-frontier/tree/master/others/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAAndroid%20%E6%96%B0%E7%89%B9%E6%80%A7-Transition-Part-1\n[part-2]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html\n[part3a]:http://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html\n[part-3b]:http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html\n"
  },
  {
    "path": "issue-8/Android 进行单元测试难在哪-序.md",
    "content": "Android 进行单元测试难在哪-序\n---\n\n> * 原文链接 : [Against Android Unit Tests](http://philosophicalhacker.com/2015/04/10/against-android-unit-tests/)\n* 原文作者 : [Matthew Dupree](http://philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaosssss) \n* 校对者: [Rocko](https://github.com/zhaoxiaopeng)  \n* 状态 :  完成 \n\n其实不仅仅只有普通 Android 开发工程师觉得测试 Android 应用很恼火，大牛们也受此困扰已久。例如 Jake Wharton 曾经明确地表示：Android 平台自诞生之初就与应用测试势如水火。Don Felker 和 Kaushik Gopal 也在他们的[博文](http://fragmentedpodcast.com/episodes/1/)里也提出了相同的观点。当然了，他们还提到 Google 的 [IOSched 应用](https://github.com/google/iosched)，根本就没有进行过测试，据说 IOSched 还是 Android 开发环境中应用开发的最优集合体呢。IOSched 没有进行测试让我们这些开发者很困扰：1、Google 所谓的“测试是高效地进行 Android 开发中的关键一环”真的不是来唬小孩的吗；2、还是 Google 官方的工程师觉得测试 Android 应用简直就是浪费时间？不管怎样，如果这个世界上最优秀的 Android 开发工程师都觉得在 Android 中进行测试很麻烦，那我们这些小菜鸡玩不好测试也是理所当然的了。\n\n多年以来，Android 开发者们为克服在 Android 中难于进行测试的问题绞尽脑汁。Roboletric 就是这些工程师的智慧结晶，它能让开发者们在 JVM 虚拟机上进行 Android 测试。而最近又有博文开始声讨 Fragment，[个中翘楚 Square 就表示](https://corner.squareup.com/2014/10/advocating-against-android-fragments.html)：老子再也不用 Fragment 这种垃圾玩意了，我们要把业务逻辑都转换到新开发的 Mortar & Flow （MVP 开发框架）框架里，用纯 Java 对象进行编程，完全不依赖 Android 平台的 API。毫无疑问，这些 Java 对象在标准的 Java 测试工具中进行测试是非常简单的。\n\n我坚信那些和 Square 站在统一战线上的开发团队肯定也在想办法将 UI 从实际的业务逻辑中剥离为纯 Java对象，为提高应用的可测试性不懈努力。换句话说，我觉得我们可以不在 Android 中进行单元测试，也不用实现依赖于 Android SDK 的测试单元。我们应该做的是重构应用，让我们能够为应用中的代码实现纯 Java 的测试单元，无论最终能不能真正地提高 Android 的可测试性和健壮性，我觉得这都值得一试。\n\n我感觉到这个思路会是治本良方，所以我们要做的，就是将下图这样的 Android 的应用架构\n\n![](http://img.my.csdn.net/uploads/201504/26/1430014189_2164.png)\n\n变成下图这样：\n\n![](http://img.my.csdn.net/uploads/201504/26/1430014189_8490.png)\n\n虽然这个方法可能能从根本上解决问题，但它也有很大的风险，尽管如此，我还是坚持认为这个方法值得一试，因为它能拯救万千挣扎在实现 Android 测试单元的开发者们于水火之中，而且不用强迫他们使用第三方的库，毕竟第三库总会让他们滞后于最新的 Android 系统特性。此外，Kent Beck 认为：可测试性好的代码就是架构优秀的代码，如果他的观点是对的，或许我们还能找到架构应用更好的办法。\n\n在接下来的博文里，我将探索“重构 Android 应用以使它们能轻易地通过标准的 Java 工具进行测试”这个方案的可操作性。\n\n在第一、第二篇博文中，我会侧重阐述在 Android 里进行单元测试为什么会带来如此痛苦的体验。我觉得阻碍 Android 测试方法发展的根本原因就在于：Android 系统本身就难于进行测试。缺乏对 Activity 和 Fragment 的合理注入就是让应用难以测试的根本原因，而且认识到这一点是设计可测试强的应用架构的关键。\n\n在第三篇博文中，我会在细节上探讨一个常见的解耦应用代码和 Android SDK的策略。简单来说，这个策略就是：将所有应用的具体行为交给一个 POJO 对象（Plain Ordinary Java Object）完成，这些 POJO 对象都是 Android 无关的接口的 Android 特定实现。\n\n在第四篇博文中，我会指出实现第二篇博文中提出的策略存在的技术难点，并尝试去挖掘可以解决这些难点的方法。在这些难点中，最大的问题在于内存泄漏和繁复的重用代码。\n\n在最后一篇博文中，我会通过展示我提出的架构为 Android 测试性带来的提高让大家觉得进行这样的技术探索是值得花费时间、精力，并且能获得相应回报的。\n"
  },
  {
    "path": "issue-8/Custom-Drawables.md",
    "content": "自定义Drawables\n---\n\n>\n* 原文链接 : [Custom Drawables](http://www.ryanharter.com/blog/2015/04/03/custom-drawables/)\n* 原文作者 : [Ryan Harter](http://www.ryanharter.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [SwinZh](https://github.com/SwinZh) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成\n\n\n我们都看过关于为什么你应该适当的使[自定义Views](http://www.ryanharter.com/blog/2014/05/14/using-custom-compound-views-in-android/)和如何能帮助你正确的封装你的应用程序代码的帖子。但非视图相关的部分如何转化为我们apps的其他部分的这种思考方式，我们对此并不非常了解。\n\n在我的应用[Fragment](https://play.google.com/store/apps/details?id=com.pixite.fragment&referrer=utm_source%3Dryanharter.com%26utm_medium%3Dpost%26utm_content%3Dcustom_drawables)中,有些地方我使用自定义Drawables来封装我的逻辑，就像你在customView中做的一样。\n\n##用例##\n\n在Fragment中,有一些使用水平滚动条作为一个选择视图的地方。这意味着该中心图标就是“选中”的图标,整个条目就该平滑的平移进去或平移出。为此，一个好的显示转换将非常棒。\n\n![](http://www.ryanharter.com/images/posts/custom-drawables/example.gif)\n\n虽然这并非完全必要，但我觉得它是一个能让这个滑动更加流畅并增加一个触摸的类在app上的效果。我本可以设置多个imageviews并让他们每个独立出来，但这真是使用自定义drawables的好地方~\n\n##自定义Drawables##\n\n在Android里，Drawables和Views实际上非常的相似。他们有相似的方法,例如padding和bounds(layout),并且都有一个可以被重写的draw方法。就我而言，我需要能够在一个选中的图片和一个未选中的图片之间进行转换基础上的一个值。\n\n在我们的例子中，我们简单地创建一个包含其他Drawables(和方向)的Drawable子类.\n\n\n\n```java\npublic class RevealDrawable extends Drawable {\n  public RevealDrawable(Drawable unselected, Drawable selected, int orientation) {\n    this(null, null);\n\n    mUnselectedDrawable = unselected;\n    mSelectedDrawable = selected;\n    mOrientation = orientation;\n  }\n}\n```\n\n接下来，我们需要设定能够与图片选择过程中相关联的值。幸运的是Drawable内置了这种类型的事件，setLevel(int).\n\n一个Drawable的level是介于0和10000的整数，它只是允许Drawable基于一个值去自定义它的view.在我们的例子中，我们可以定义5000作为图片被选择时的状态值，其他没被选中状态值在5000左右两侧。\nAll we need to do now is to override the draw(Canvas canvas) method to draw the appropriate drawable by clipping the canvas based on the current level.\n现在我们要做的就是重写draw(Canvas canvas)方法，通过基于当前的level裁剪画布去绘制相应的图片。\n\n```java\n@Override\npublic void draw(Canvas canvas) {\n\n  // If level == 10000 || level == 0, just draw the unselected image\n  int level = getLevel();\n  if (level == 10000 || level == 0) {\n    mRevealState.mUnselectedDrawable.draw(canvas);\n  }\n\n  // If level == 5000 just draw the selected image\n  else if (level == 5000) {\n    mRevealState.mSelectedDrawable.draw(canvas);\n  }\n\n  // Else, draw the transitional version\n  else {\n    final Rect r = mTmpRect;\n    final Rect bounds = getBounds();\n\n    { // Draw the unselected portion\n      float value = (level / 5000f) - 1f;\n      int w = bounds.width();\n      if ((mRevealState.mOrientation & HORIZONTAL) != 0) {\n        w = (int) (w * Math.abs(value));\n      }\n      int h = bounds.height();\n      if ((mRevealState.mOrientation & VERTICAL) != 0) {\n        h = (int) (h * Math.abs(value));\n      }\n      int gravity = value < 0 ? Gravity.LEFT : Gravity.RIGHT;\n      Gravity.apply(gravity, w, h, bounds, r);\n\n      if (w > 0 && h > 0) {\n        canvas.save();\n        canvas.clipRect(r);\n        mRevealState.mUnselectedDrawable.draw(canvas);\n        canvas.restore();\n      }\n    }\n\n    { // Draw the selected portion\n      float value = (level / 5000f) - 1f;\n      int w = bounds.width();\n      if ((mRevealState.mOrientation & HORIZONTAL) != 0) {\n        w -= (int) (w * Math.abs(value));\n      }\n      int h = bounds.height();\n      if ((mRevealState.mOrientation & VERTICAL) != 0) {\n        h -= (int) (h * Math.abs(value));\n      }\n      int gravity = value < 0 ? Gravity.RIGHT : Gravity.LEFT;\n      Gravity.apply(gravity, w, h, bounds, r);\n\n      if (w > 0 && h > 0) {\n        canvas.save();\n        canvas.clipRect(r);\n        mRevealState.mSelectedDrawable.draw(canvas);\n        canvas.restore();\n      }\n    }\n  }\n}\n```\n\n就这样，我们可基于滑动的位置以简单地设置icon的level,结束了~\n\n```java\nfloat offset = getOffestForPosition(recyclerView, position);\nif (Math.abs(offset) <= 1f) {\n  holder.image.setImageLevel((int) (offset * 5000) + 5000);\n} else {\n  holder.image.setImageLevel(0);\n}\n```\n\n想要看自定义Drawable源码，请在Github上搜索Gist[here](https://gist.github.com/rharter/34051da57f8a6a0991ff)\n\n"
  },
  {
    "path": "issue-8/Support Libraries v22.1.0.md",
    "content": "Support Libraries v22.1.0\n---\n\n> * 原文链接 : [Support Libraries v22.1.0](https://chris.banes.me/2015/04/22/support-libraries-v22-1-0/)\n* 原文作者 : [Chris Banes](https://chris.banes.me/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime)\n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成\n\n\n\n#Support Libraries v22.1.0\n22 Apr 2015\n\n好久不见了啊大家～ 你可能听说了我们已经发布 22.1.0 support libraries 的这个消息，\n这可能是目前为止我们对 support library 改动最大的一次更新。 \n\n在我们开讲前，建议先读一下  Ian Lake 的这篇官方 [blog][official-blog]，里面列出了\n这次更新中所有的新特性。\n\n这篇文章我将重点讲解内部运行的方式和原因，尤其是我完成的部分\n(因为我非常清楚了解这些)。\n\n---\n\n## AppCompat\n\n先从 AppCompat 说起吧，我们在这个版本中对它做了很大的更新。首先，它的重构...\n\n---\n\n\n###Refactoring\n\n在之前的版本中进入 AppCompat 唯一的入口 ActionBarActivity 已经被我们抛弃了。\n也就是说你以后只能使用一组 Activity 视图层，再也不能像 PreferenceActivity 那样使用它了。\n\n我们现在提取出了所有内部内容，并将它们暴露给一个单一委托 API ，\n[AppCompatDelegate][AppCompatDelegate] 。AppCompatDelegate 可以从\n任何提供了 [Window.Callback][Window.Callback] 接口的 Android 对象中构造，\n例如 Activity 或 Dialog 的子类。你可以使用它的静态方法\n [create](https://developer.android.com/reference/android/support/v7/app/AppCompatDelegate.html#create(android.app.Activity, android.support.v7.app.AppCompatCallback)) 创建它。\n\n如果你创建了一个委托，你需要在每次调用它提供的接口时回调到它。\n(例如 `onCreate()`)，不过这很简单，可以提取到一个基类中。\n\n最终，你可以给所有 Activity 的子类附加任何你想要的 AppCompat 的功能。\n\n如果你打算使用 AppCompatDelegate ，我强烈建议你有空看一眼 AppCompatActivity \n的源码。这是一个(极端的)例子介绍了怎样整合 AppCompatDelegate。\n\n大部分人不需要这层自定义，可以像原来那样使用 ActionBarActivity \n那样使用 [AppCompatActivity][AppCompatActivity] 这个类就好。\n\n---\n\n###Dialogs\n\n上面刚刚提到了 Dialog ，你应该也想到了我们还加入了什么。在完成重构工作后，\nDialog 很自然就是我们下一步工作对象。实际上从 decor-setup 角度来看，\n Activity 和 Dialog 这里有一些小的不同。\n \n 这意味着我们终于解决了自 v21 以来 AppCompat 最大的需求：\n material styled dialogs  (balalalala~)。\n\n我们现在有了新的 [AppCompatDialog][AppCompatDialog] 类，\n你应该在在引用(or 关联) `Theme.AppCompat.Dialog` 时使用它。\n\n最后，AppCompat 现在也有了它自己的 AlertDialog 实现，来方便构建\nmaterial 样式的 AlertDialog。只要使用\n [android.support.v7.app.AlertDialog][AlertDialog] 就好了，它会自己处理细节部分。\n \n有一点要注意，AppCompat 的 AlertDialog 没有实现框架版本所做的一切。\n它只暴露了在这个 ‘material 世界’ 中有价值的部分。( (╯°□°）╯︵ ┻━┻ )\n\n---\n\n###android:theme\n\n在进入这部分前，请先阅读这篇文章 [Theme vs Style][theme-vs-style]，\n了解下将要讲解内容的基础。\n\n在 AppCompat v21 里，我们提供了一个快速方便的方法设置设置 [Toolbar][toolbar]\n的主题，使用 `app:theme`。\n\n在 v22.1.0 里，我们扩充了它的功能，现在你可以给你的 layout 里的任意视图\n设置主题了。只要使用 `android:theme` 这个属性就好了，可以在 \ncompat 和 framework 之间无缝地切换功能。\n\n最好的一点要数它会自动继承父视图的 theme ，并且兼容所有 API v11 以上的设备。\n一个例子:\n\n```xml\n<Toolbar\n    android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\">\n\n    <!-- This TextView inherits its theme from the parent Toolbar -->\n    <TextView android:text=\"I'm light!\" />\n\n</Toolbar>\n```\n对于运行 API v10 甚至更老的设备来说，你也可以使用 `android:theme` 属性，\n不过它不会继承父视图 theme 。这就意味着你要么重新考虑你的布局，要么\n为每一个子视图都设置上 `android:theme` 属性。(这样做效率真的很低)\n\n如果你感兴趣的话，开启继承父类 theme 的方法在这里\n [LayoutInflater.Factory2][LayoutInflater.Factory2] 。\n \n ---\n \n###Widgets\n\n如果读过了 Ian 的文章，你可能看到了和 控件着色 (tinting widgets)相关的内容\n(这里还有一些新的内容)。\n\n很好，不过关于这点还有一个变化：我们不会再修改 该平台主题的默认\nwidget 样式了。也就是说如果你使用这个 widget 的  AppCompat \n实现(无论显式还是隐式的)，那么你在 v21 版本之前的设备上只能获取到\n material 样式。在实践中你应该不会看到有什么不同，\n 因为我们会自动插入适当的 AppCompat 的实现。\n\n这样我们就解决了已经使用 material 样式但是没有着色的问题。它出现在 \nwidget 的平台实现使用了我们的样式，并且出现在不同的位置之中时，比如\n Preferences 。\n\n反之，你将会看到当前平台默认的样式 (Holo，etc)。\n虽然这样看起来可能有点奇怪，不过这可比看一个空白未着色的 drawable 好多了。\n\n---\n\n###Theme window features\n现在 AppCompat 预测 窗口主题 flag 时会更严格 ，配合框架更密切。\n\n背后的原因是为我们早些时候提到的 dialogs 提供支持。它们大量使用了\nAppCompat 之前并没有重视的 `windowNoTitle ` 标志。\n\n升级到 v22.1.0 以后，你可能已经遇到过下面的异常：\n\n\tIllegalArgumentException: AppCompat does not support the current theme features\n我在这里回答了解决办法：\n[http://stackoverflow.com/q/29790070/474997][stackoverflow]\n\n---\n\n\n## v4\nsupport libraries 的老祖宗 support-v4 还在继续增长，添加了一些新内容。\n\n### ColorUtils\n[ColorUtils][ColorUtils] 已经从 Palette 中移入到 support-v4里。\n它包含了一些非常好的操作颜色的方法。比如，你可以计算在某个背景中，\n最小的文本颜色 alpha 值：\n\n```java\nint backgroundColor = ...;\nint textColor = Color.WHITE;\nfloat minContrastRatio = 4.5f; // We want a minimum contrast ration of 1:4.5\n\nint minAlpha = ColorUtils.calculateMinimumAlpha(\n        textColor, backgroundColor, minContrastRatio);\n\nif (minAlpha != -1) {\n    // There is an alpha value which has enough contrast, use it!\n    return ColorUtils.setAlphaComponent(textColor, minAlpha);\n}\n```\n还有很多方便的方法，比如颜色合成，计算亮度工具等等。更多信息可以\n去看文档。\n\n---\n\n\n### Drawable 着色\nLollipop 中加入的 Drawable 着色的方法非常好用，可以让你动态的处理着色资源。\n在 v21 support library 中 AppCompat 有它自己的实现，现在我们将它移入到\nsupport-v4 的 [DrawableCompat][DrawableCompat] 之中让大家都可以使用它。\n知道它的工作方式很重要。\n\n```java\nDrawable drawable = ...;\n\n// Wrap the drawable so that future tinting calls work\n// on pre-v21 devices. Always use the returned drawable.\ndrawable = DrawableCompat.wrap(drawable);\n\n// We can now set a tint\nDrawableCompat.setTint(drawable, Color.RED);\n// ...or a tint list\nDrawableCompat.setTintList(drawable, myColorStateList);\n// ...and a different tint mode\nDrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_OVER);\n```\n需要注意下在调用完 [DrawableCompat.wrap()][DrawableCompat.wrap] 之后，\n它的返回值和你赋值给它的那个并不是同一个东西。你应该使用\n[DrawableCompat.unwrap( )][DrawableCompat.unwrap] 取出原始 Drawable。\n\n在内部，我们将你的 Drawable 包裹在一个特殊的  ‘tint drawable’ (着色 drawable)\n之中，它会根据指定的色彩自动更新 Drawable 的滤色器。 允许我们处理\n[ColorStateList][ColorStateList] 实例。\n\n---\n\n##Palette\nPalette 在这次发布中也获得了一些更新。首先，我们给它加入了新的 \n[Builder][Palette.Builder] 类来帮助获取 Palette 实例。我们发现\nPalette 与日俱增的  ‘把手’ 和 设置 正在将它的 API 变得复杂难懂。\n使用 Builders 可以显著缓解这个问题。\n\n更加重要的是第二个改变，我们在很大程度上提升了 Palettes 的生成速度。\n在生成 Palette 过程中最耗时的要数 色彩量化这一步，它会读取一张图片中的\n所有像素点，并降低颜色深度到一个很小的色彩数 (通常是 16)。\n\n在这次更新中，我们使用了传统的方式优化色彩量化的性能，比如更少的对象分配，\n更合适的数据结构还有降低算法复杂度。成果很显著～\n\n下面是一些测试数据，在一个使用 ART 的设备上性能大概提升 5 到 6 倍，\n如果是使用 Dalvik 的设备，效果还会更加明显。\n\n Device          | 22.0\t\t | 22.1.0  \t| Speedup\n ----------|--------| ------|------\nNexus 6\t\t| 55ms \t| 8ms    \t |~6x\nNexus 5 \t\t| 55ms \t| 11ms\t |~5x\nNexus One\t| 1200ms\t| 120ms \t |~10x\n\n> 测试结果并不是很科学，只是给出一个近似的值，不过你懂这个意思的啦。\n\nCover photo:[Scaffolding][cover] by Brett Weinstein\n\n---\n\n[official-blog]:http://android-developers.blogspot.com/2015/04/android-support-library-221.html\n[AppCompatDelegate]:https://developer.android.com/reference/android/support/v7/app/AppCompatDelegate.html\n[Window.Callback]:https://developer.android.com/reference/android/view/Window.Callback.html\n[AppCompatDialog]:https://developer.android.com/reference/android/support/v7/app/AppCompatDialog.html\n[AlertDialog]:https://developer.android.com/reference/android/support/v7/app/AlertDialog.html\n[theme-vs-style]:https://chris.banes.me/2014/11/12/theme-vs-style/\n[toolbar]:https://developer.android.com/reference/android/support/v7/widget/Toolbar.html\n[LayoutInflater.Factory2]:https://developer.android.com/reference/android/view/LayoutInflater.Factory2.html\n[stackoverflow]:http://stackoverflow.com/q/29790070/474997\n[ColorUtils]:https://developer.android.com/reference/android/support/v4/graphics/ColorUtils.html\n[DrawableCompat]:https://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html\n[DrawableCompat.wrap]:https://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html#wrap(android.graphics.drawable.Drawable)\n[DrawableCompat.unwrap]:https://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html#unwrap(android.graphics.drawable.Drawable)\n[ColorStateList]:https://developer.android.com/reference/android/content/res/ColorStateList.html\n[cover]:https://flic.kr/p/GbAqX\n[Palette.Builder]:http://developer.android.com/reference/android/support/v7/graphics/Palette.Builder.html\n[AppCompatActivity]:https://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html"
  },
  {
    "path": "issue-8/如何在Android上响应各种信息通知.md",
    "content": "如何在Android上响应各种信息通知\n---\n\n> * 原文链接 : [How to respond to any* messaging notification on Android](https://medium.com/@polidea/how-to-respond-to-any-messaging-notification-on-android-7befa483e2d7)\n* 原文作者 : [Michał Tajchert](https://github.com/tajchert/NotificationResponse)\n*  [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [MrLoong](https://github.com/MrLoong) \n* 校对者: [bboyfeiyu ](https://github.com/bboyfeiyu)  \n* 状态 :  完成 \n\n\n\n有没有想过使用你的代码去回应未知的发送者是多么酷的事情？作为一家痴迷于最新技术的公司，我想和大家一起分享我们的一些研究\n\n如果你是Android用户，你可能会喜欢 [Pushbullet](https://www.pushbullet.com/)这个令人敬畏的App，它是一个越过设备同步你所有的消息的App，包括Pc和Mac，自从二月的更新，它允许回应消息给应用程序，例如Facebook Messenger，Hangouts，Telegram 等等 -难道不让人敬畏？可以确定！并没有易于实施的API接口去实现这些，基本上每一个平台都需要我们实现所有客户的连接，但这不是一个好的解决方案。\n\n作为一个喜欢此功能的用户，我们的工程师Michał Tajchert开始专研这个主题后他的好奇心被Reddit上的一个“它是怎么运行的”问题而引发，这个功能一定已经与Android Wear API发生了连接，作者是这样说的。小提示下，Android Wear是提供给智能手表的平台。更具体的讲，我们并不是谈论关于智能手表全新的数据API或消息API，但除了一些已经存在的小通知。由于Android Wear的介绍，我们可以增添一个被叫做\"WearableExtender\"的小物件，增添一些针对智能手表的功能，像活动页，背景，语音输入等\n\n```java\n\tRemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY).build();\n\t \n\tNotificationCompat.Action action =\n\t        new NotificationCompat.Action.Builder(...)\n\t                .addRemoteInput(remoteInput)\n\t                .build();\n\t \n\tNotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(action);\n\t \n\tNotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(MainActivity.this).extend(wearableExtender);\n```\n\n在上面的代码我们可以看到，我们已经新建RemoteInput(一个收集语音应答的对象),RemoteInput被作为一个Action添加到我们的WearableExtender对象，最终被添加带Notification他自己本身。如果你有一个显示通知并等待用户我文本响应的App(任何类似的App)，这是一个完美的解决方案对你来说。大多流行App都已经实现了。仅有非常少数的App仍然缺乏这点，像Skype 正在计划添加这个功能在接下来的几周。\n\n![我们使用这个技巧的事例应用程序，当用户点击程序按钮，他将回应Messenger conversation](https://d262ilb51hltx0.cloudfront.net/max/800/1*IlV2iOqJ5L9fgK-6_4l-bg.gif)\n\n我们使用这个技巧的事例应用程序，当用户点击程序按钮，他将回应Messenger conversation\n\n##怎样捕获通知?\n\n在Android上还是比较容易实现的，只要实现你自己的service，继承NotificationListenerService并且实现onNotificationPosted() and onNotificationRemoved()方法，这样任何通知的出现或被取消都会被感知。安全访问通知的完成是通过提示屏幕列表中能访问你的通知和能够选中或取消任何的应用程序通知。对于UX来说这并不是一个好的应用，因为它是全屏幕（所以这样的互动应该离开我们的App），一个更好的模式是有一个列表应程序替代询问我们特殊权限的应用程序窗口。\n\n##让我们看看如何可以访问WearableExtender\n\n第一种方式，这样做实际上是由一些用户在网上发布，从pushbulle源提取，随着一些代码的完善使它工作，这是什么样子的：\n\n```java\n\tBundle bundle = statusBarNotification.getNotification().extras;\n\tfor (String key : bundle.keySet()) {\n\t    Object value = bundle.get(key);\n\t \n\t    if(\"android.wearable.EXTENSIONS\".equals(key)){\n\t        Bundle wearBundle = ((Bundle) value);\n\t        for (String keyInner : wearBundle.keySet()) {\n\t            Object valueInner = wearBundle.get(keyInner);\n\t \n\t            if(keyInner != null && valueInner != null){\n\t                if(\"actions\".equals(keyInner) && valueInner instanceof ArrayList){\n\t                    ArrayList<Notification.Action> actions = new ArrayList<>();\n\t                    actions.addAll((ArrayList) valueInner);\n\t                    for(Notification.Action act : actions) {\n\t                        if (act.getRemoteInputs() != null) {//API > 20 needed\n\t                            android.app.RemoteInput[] remoteInputs = act.getRemoteInputs();\n\t                        }\n\t                    }\n\t                }\n\t            }\n\t        }\n\t    }\n\t}\n```\n\n[WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/)\n\n在这里刚刚发生了什么？首先，我们从WearableExtender中的参数分布提取出了Bundle通知，然后我们搜索一个能让我们访问所有Android Wear特殊的功能关键值“android.wearable.EXTENSIONS”，例如页面.....我们正在寻找RemoteInput！为了找到它，我们需要遍历所有的功能。这就是实际的工作，但还是代码看起来很丑并且“hackish”。\n\n![](https://d262ilb51hltx0.cloudfront.net/max/1375/1*BieupTbIh3rfs9hxruOvkQ.png)\n\n##我们能比这做的更好吗?\n\n当然！一行代码获得WearableExtender，会是多么的酷？\n\n```java\n\tWearableExtender wearableExtender = new WearableExtender(statusBarNotification.getNotification());\n```\n[WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/)\n\n好的，让我们现在直接提取Actions从RemoteInput中。\n\n```java\n\tList< Action> actions = wearableExtender.getActions();\n\tfor(Action act : actions) {\n\t\tif(act != null && act.getRemoteInputs() != null) {\n\t                RemoteInput[] remoteInputs = act.getRemoteInputs();\n\t        }\n\t}\n```\n[WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/)\n\n好的，但是我们还需要做些什么呢？我们需要PendingIntent发送我们的返回结果给应用程序，使它触发通知并且他也将很好的保持Bundle，因为他可能包含一些额外的价值，例如conversationId，让我们去实现它\n\n```java\n\tnotificationWear.pendingIntent = statusBarNotification.getNotification().contentIntent;\n\tnotificationWear.bundle = statusBarNotification.getNotification().extras;\n```\n\n[WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/)\n\n现在我们准备返回它！探究一下我们的事例应用程序，加速POC的开发过程，通过的数据以EventBus为媒介转到Activity被保存在Stack，当用户点击“Respond to last”直接就突出最后题目并且伪造响应文本给它\n\n##填充RemoteInput\n\n大概最有趣的部分是怎么能够填补一个对象，通常需要语音输入我们虚假的文字，它并不是很难。\n\n```java\n\tRemoteInput[] remoteInputs = new RemoteInput[notificationWear.remoteInputs.size()];\n\t \n\tIntent localIntent = new Intent();\n\tlocalIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\tBundle localBundle = notificationWear.bundle;\n\tint i = 0;\n\tfor(RemoteInput remoteIn : notificationWear.remoteInputs){\n\t    getDetailsOfNotification(remoteIn);\n\t    remoteInputs[i] = remoteIn;\n\t    localBundle.putCharSequence(remoteInputs[i].getResultKey(), \"Our answer\");//This work, apart from Hangouts as probably they need additional parameter (notification_tag?)\n\t    i++;\n\t}\n\tRemoteInput.addResultsToIntent(remoteInputs, localIntent, localBundle);\n\ttry {\n\t    notificationWear.pendingIntent.send(MainActivity.this, 0, localIntent);\n\t} catch (PendingIntent.CanceledException e) {\n\t    Log.e(TAG, \"replyToLastNotification error: \" + e.getLocalizedMessage());\n\t}\n```\n\n我们的notificationWear对象是一个暂时存放所有需要数据来响应特殊通知的容器，然后我们把每一个RemoteInput从我们保存的通知中填充到我们所期望的文本，通过使用putCharSequence()方法在Bundle返回。这里的关键是使用getResultKey()在每一个RemoteInput，因为被调用的程序将在返回Bundle答复文本。最后一步我们需要使Intent移植于RemoteInputs对于那些虚假的回应，让我们使用addResultsToIntent(inputs, intent, bundle)这样做。现在我们准备好所有的数据去触发PendingIntent，触发叫做pendingIntent.send(context, code,intent)的方法。这是全部，我们只是回应在Messenger，Telegraph，Line 或是任何其他使用WearableExtender与RemoteInput的应用程序！这种方法仍然缺少Hangouts，可能需要一些额外的参数，我最好的猜测是在其他Tag中的StatusBarNotification如果其他的（工作）应用程序也是空的。\n\n##帅不？\n\n是的，是这样。仔细看一看Pushbullet是他被允许做的非常好的事情。但是在我们的办公室，值得被的关注的是，用户被提示选择应用程序读取通知和使用这个技巧，我们可以应对任何通知。还有，我们不但可以响应也可以缓存需要被触发的数据，例如，一些用在Messenger中以后使用的对话，同时，潜在的，它允许你发送短信在没有正确权限，因为你“just responded to a notification”和偶然有短信应用程序。现在在Android默认文本消息的应用程序不包括WearableExtender，但也许这只是时间问题，而且Hangouts允许你发送短信。\n\n##时刻保持安全\n\n什么是单向的，可以做其他工作的，这就意味着如果你正在开发一个Wear-enabled用用程序，其他的开发人员可能会回复你的通知，后来出现RemoteInput黑客，让我们想想如何保持安全。最简单的方法是生成一些ID对于每个通知并且允许他们只能使用一次，这样任何应用程序使用这个技巧不会发送过多的消息到你的应用程序。另一个方法是在Bundle中不包括一些参数，但是作为一个例子Tag中的Notification比较后接受。 这可能是我们为什么不能让黑客工作在Hangouts。从另一方面来讲，如果你是使用者你应该更加关注那些曾获取“reading notifications”应用程序，因为他现在也允许应用程序与他们互动。如果你想要一个测验或者帮助我们对于Hangouts的工作，这个样品在[Github](https://github.com/tajchert/NotificationResponse)上\n\n\n"
  },
  {
    "path": "issue-8/开始学习Material Design.md",
    "content": "开始学习Material Design\n---\n\n> * 原文链接 : [Android Getting Started with Material Design](http://www.androidhive.info/2015/04/android-getting-started-with-material-design/)\n* 原文作者 : [Ravi Tamada](http://www.androidhive.info/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [xu6148152](https://github.com/xu6148152) \n* 校对者: [chaossss](https://github.com/chaossss)   \n* 状态 :  完成\n\n\n\n\n你可能已经听说过Android Lollipop中引入的Material Design.Material Design引入了很多新的东西，如Material 主题, 新的控件, 自定义阴影, 矢量绘制 and 自定义动画.如果你还没用过Material Design，那么本文很适合你。\n\n我们将在本文中学习Material Design开发的基本步骤:编写自定义主题和用RecyclerView实现navigation drawer.\n\n在下面的链接中，你能够学到更多有关Material Design的知识  \n\n[Material Design Specifications](http://www.google.com/design/spec/material-design/introduction.html#)  \n\n[Creating Apps with Material Design](http://developer.android.com/training/material/index.html)  \n \n[Code](http://download.androidhive.info/download?code=WPSkdrdZprHT0KLCZS3ClafgXBikGqM4r7FnNYdsdUTmlAkK6%2F2mkT0heOlNOq4U82rzqbod%2F14yU2uk5TWY4Zp%2FAYx6oiD7SKI%2FEgtUapzQUqkqcWEXX1bmw%3D%3DvqARiMEKqkqsXGbVf3vVUoffTqQcD2qfqZo)\n\n[Video](http://www.youtube.com/embed/jDXX_wDvarM)\n\n###1.下载AndroidStudio\n\n再往下走之前，先下载Android Studio，并配置好本文之前提到的东西，如果你是首次使用Android Studio，先去看看文档.\n\n###2.Material Design 颜色自定义\n\nMaterial Deisgin提供以下系列自定义Material Deisign 颜色主题的属性，但本文只使用5种基本属性来自定义整体的主题\n\ncolorPrimaryDark - 这是app中最黑的基本色，主要用来做notification bar的背景.\n\ncolorPrimary - 这是app的基本颜色,将用作toolbar的背景色\n\ntextColorPrimary - 文本颜色,用于toolbar的标题\n\nwindowBackground - app默认的背景颜色\n\nnavigationBarColor - 这个颜色定义了navigation bar页脚的背景色.  \n\n![](http://cdn2.androidhive.info/wp-content/uploads/2015/04/android-material-design-color-schema.png)\n\n你能选择适合你APP风格的颜色\n\n###3.创建Material Design 主题\n\n1.在as中，创建新项目,选择BlankActivity.\n\n2.打开res->values->strings.xml 添加以下字符值\n\n  \n```xml  \nstrings.xml\n<resources>  \n\n    <string name=\"app_name\">Material Design</string>\n    <string name=\"action_settings\">Settings</string>\n    <string name=\"action_search\">Search</string>\n    <string name=\"drawer_open\">Open</string>\n    <string name=\"drawer_close\">Close</string>\n \n    <string name=\"nav_item_home\">Home</string>\n    <string name=\"nav_item_friends\">Friends</string>\n    <string name=\"nav_item_notifications\">Messages</string>\n \n    <!-- navigation drawer item labels  -->\n    <string-array name=\"nav_drawer_labels\">\n        <item>@string/nav_item_home</item>\n        <item>@string/nav_item_friends</item>\n        <item>@string/nav_item_notifications</item>\n    </string-array>\n \n    <string name=\"title_messages\">Messages</string>\n    <string name=\"title_friends\">Friends</string>\n    <string name=\"title_home\">Home</string>\n</resources>  \n```  \n\n3.打开res->values->colors 添加以下颜色,如果你没有找到colors.xml文件，那么新建一个.\n   \n```xml \ncolors.xml  \n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>  \n\n    <color name=\"colorPrimary\">#F50057</color>\n    <color name=\"colorPrimaryDark\">#C51162</color>\n    <color name=\"textColorPrimary\">#FFFFFF</color>\n    <color name=\"windowBackground\">#FFFFFF</color>\n    <color name=\"navigationBarColor\">#000000</color>\n    <color name=\"colorAccent\">#FF80AB</color>\n    \n</resources>  \n```  \n4. 打开 dimens.xml文件，加入以下代码\n \n  \n```xml  \ndimens.xml\n<resources>  \n\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"nav_drawer_width\">260dp</dimen> \n    \n</resources>  \n```\n\n5.打开styles.xml文件加入以下代码，在这里定义的style对于所有的androidbanben都是通用的。  \n  \n```xml  \nstyles.xml\n<resources>\n \n    <style name=\"MyMaterialTheme\" parent=\"MyMaterialTheme.Base\">\n \n    </style>\n \n    <style name=\"MyMaterialTheme.Base\" \t\t\t\tparent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n     \n</resources>  \n```\n\n6.在res目录下，创建一个values-v21目录,并styles.xml\n文件夹,这些style仅仅用于Android Lollipop\n\nstyles.xml  \n```xml\n<resources>\n \n    <style name=\"MyMaterialTheme\" parent=\"MyMaterialTheme.Base\">\n        <item name=\"android:windowContentTransitions\">true</item>\n        <item name=\"android:windowAllowEnterTransitionOverlap\">true</item>\n        <item   name=\"android:windowAllowReturnTransitionOverlap\">true</item>\n        <item name=\"android:windowSharedElementEnterTransition\">@android:transition/move</item>\n        <item name=\"android:windowSharedElementExitTransition\">@android:transition/move</item>\n    </style>\n \n</resources>  \n```\n\n7.现在我们已经准备好Material Design style了， 为了使用这主题, 在AndroidManifest.xml文件中修改application theme属性如下：\n  \n```xml  \nAndroidManifest.xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"androidhive.info.materialdesign\" >\n \n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/MyMaterialTheme\" >\n        <activity\n            android:name=\".activity.MainActivity\"\n            android:label=\"@string/app_name\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n \n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n \n</manifest>  \n```\n\n现在如果你运行app，你能看到notification的颜色已经变成我们在style中设置的颜色.\n\n![](http://cdn4.androidhive.info/wp-content/uploads/2015/04/android-material-design-notification-bar.png)\n\n###3.1 添加工具栏 (Action Bar)\n\n添加toolbar很简单,你要做的只是为toolbar另外创建一个layout,然后你想在哪里显示它，就在那个页面布局中include它\n\n8.在layout目录下创建toolbar.xml, 在里面添加android.support.v7.widget.Toolbar，并设置宽高.代码如下：  \n\n```xml\t\ntoolbar.xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v7.widget.Toolbar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:local=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/toolbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"?attr/actionBarSize\"\n    android:background=\"?attr/colorPrimary\"\n    local:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n    local:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n```\n\n9.打开你的main activiy的布局,添加如下代码: \n\n  \n```xml\t\nactivity_main.xml\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n \n    <LinearLayout\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentTop=\"true\"\n        android:orientation=\"vertical\">\n \n        <include\n            android:id=\"@+id/toolbar\"\n            layout=\"@layout/toolbar\" />\n    </LinearLayout>\n \n \n</RelativeLayout>  \n```\n![](http://cdn4.androidhive.info/wp-content/uploads/2015/04/android-material-design-toolbar1.png) \n\n现在让我尝试添加toolbard额标题，并启用它的行为。  \n\n10.下载相关图片资源，然后导入到AS当中作为Image Asset.\n\n11.导入Image asset步骤,右键res目录->new->Image Asset 之后显示弹窗，然后找到你已经下载好的图片,选择Action Bar 和 Tab Icons， 给资源命名为ic_search_action. 如下图 \n\n![](http://cdn3.androidhive.info/wp-content/uploads/2015/04/android-studio-importing-image-asset.png) \n\n12.一旦图片导入了，打开menu_main.xml 添加seach menu item,代码如下：   \n\n\n```xml\t\nmenu_main.xml  \n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\".MainActivity\">\n \n    <item\n        android:id=\"@+id/action_search\"\n        android:title=\"@string/action_search\"\n        android:orderInCategory=\"100\"\n        android:icon=\"@drawable/ic_action_search\"\n        app:showAsAction=\"ifRoom\" />\n \n    <item\n        android:id=\"@+id/action_settings\"\n        android:title=\"@string/action_settings\"\n        android:orderInCategory=\"100\"\n        app:showAsAction=\"never\" />\n</menu>   \n```\n\n13.打开MainActivity.java做以下修改.\n\nMainActivity从ACtionBarAcitiv继承\n\n调用setSupportActionBar()来启用toolbar\n\n复写onCreateOptionsMenu() 和 onOptionsItemSelected()来启动toolbar菜单子目录的行为。  \n  \n```java  \nMainActivity.java\n \n    private Toolbar mToolbar;\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n \n        mToolbar = (Toolbar) findViewById(R.id.toolbar);\n \n        setSupportActionBar(mToolbar);\n        getSupportActionBar().setDisplayShowHomeEnabled(true);\n    }\n \n \n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return true;\n    }\n \n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        int id = item.getItemId();\n \n        //noinspection SimplifiableIfStatement\n        if (id == R.id.action_settings) {\n            return true;\n        }\n \n        return super.onOptionsItemSelected(item);\n    }\n  \n```\n  \n添加以上代码后，运行APP，你应该能够看到如下图所示的效果  \n\n![](http://cdn1.androidhive.info/wp-content/uploads/2015/04/android-material-design-toolbar-action-items.png?)\n\n###3.2添加Navigation Drawer  \n\n添加Natigation Drawer的方式跟lollipop以前一样,但我们使用RecyclerView来实现Menu Items.  \n\n14.在java文件下创建三个包，分别命名为activity,adapter,model,把MainActivity移到activity包种，这样整个项目看起来就更有组织性。 \n\n15.在build.gradle中添加以下依赖库  \n\n  \n```gradle  \nbuild.gradle\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile 'com.android.support:appcompat-v7:22.0.0'\n    compile 'com.android.support:recyclerview-v7:21.0.+'\n}  \n```  \n\n16.在model包下添加NavDrawerItem.java文件作为menuItem,代码如下：   \n  \n```java  \nNavDrawerItem.java\npublic class NavDrawerItem {\n    private boolean showNotify;\n    private String title;\n \n \n    public NavDrawerItem() {\n \n    }\n \n    public NavDrawerItem(boolean showNotify, String title) {\n        this.showNotify = showNotify;\n        this.title = title;\n    }\n \n    public boolean isShowNotify() {\n        return showNotify;\n    }\n \n    public void setShowNotify(boolean showNotify) {\n        this.showNotify = showNotify;\n    }\n \n    public String getTitle() {\n        return title;\n    }\n \n    public void setTitle(String title) {\n        this.title = title;\n    }\n}  \n```\n\n17.添加nav_drawer_row.xml布局文件,代码如下：  \n\n```xml  \n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clickable=\"true\">\n \n    <TextView\n        android:id=\"@+id/title\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"30dp\"\n        android:paddingTop=\"10dp\"\n        android:paddingBottom=\"10dp\"\n        android:textSize=\"15dp\"\n        android:textStyle=\"bold\" />\n \n</RelativeLayout>  \n```\n\n18.下载这个图片,这图片用在导航栏的头部  \n\n![](http://api.androidhive.info/images/ic_profile.pn)\n  \n19.创建另一个布局文件fragment_navigation_drawer.xml,如下代码:  \n  \n```xml  \nfragment_navigation_drawer.xml\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\">\n \n \n    <RelativeLayout\n        android:id=\"@+id/nav_header_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"140dp\"\n        android:layout_alignParentTop=\"true\"\n        android:background=\"@color/colorPrimary\">\n \n        <ImageView\n            android:layout_width=\"70dp\"\n            android:layout_height=\"70dp\"\n            android:src=\"@drawable/ic_profile\"\n            android:scaleType=\"fitCenter\"\n            android:layout_centerInParent=\"true\" />\n \n    </RelativeLayout>\n \n \n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/drawerList\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/nav_header_container\"\n        android:layout_marginTop=\"15dp\" />\n \n \n</RelativeLayout>\n```\n\n20.由于RecyclerView是自定义的，我们需要创建一个Adapter来渲染自定的布局, 因此创建NavigationDrawerAdapter.java文件  \n\n```java \npublic class NavigationDrawerAdapter extends RecyclerView.Adapter<NavigationDrawerAdapter.MyViewHolder> {\n    List<NavDrawerItem> data = Collections.emptyList();\n    private LayoutInflater inflater;\n    private Context context;\n \n    public NavigationDrawerAdapter(Context context, List<NavDrawerItem> data) {\n        this.context = context;\n        inflater = LayoutInflater.from(context);\n        this.data = data;\n    }\n \n    public void delete(int position) {\n        data.remove(position);\n        notifyItemRemoved(position);\n    }\n \n    @Override\n    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        View view = inflater.inflate(R.layout.nav_drawer_row, parent, false);\n        MyViewHolder holder = new MyViewHolder(view);\n        return holder;\n    }\n \n    @Override\n    public void onBindViewHolder(MyViewHolder holder, int position) {\n        NavDrawerItem current = data.get(position);\n        holder.title.setText(current.getTitle());\n    }\n \n    @Override\n    public int getItemCount() {\n        return data.size();\n    }\n \n    class MyViewHolder extends RecyclerView.ViewHolder {\n        TextView title;\n \n        public MyViewHolder(View itemView) {\n            super(itemView);\n            title = (TextView) itemView.findViewById(R.id.title);\n        }\n    }\n\n```\n\n21.在adapter包下创建FragmentDrawer.java，代码如下： \n\n```java  \nFragmentDrawer.java  \npublic class FragmentDrawer extends Fragment {\n \n    private static String TAG = FragmentDrawer.class.getSimpleName();\n \n    private RecyclerView recyclerView;\n    private ActionBarDrawerToggle mDrawerToggle;\n    private DrawerLayout mDrawerLayout;\n    private NavigationDrawerAdapter adapter;\n    private View containerView;\n    private static String[] titles = null;\n    private FragmentDrawerListener drawerListener;\n \n    public FragmentDrawer() {\n \n    }\n \n    public void setDrawerListener(FragmentDrawerListener listener) {\n        this.drawerListener = listener;\n    }\n \n    public static List<NavDrawerItem> getData() {\n        List<NavDrawerItem> data = new ArrayList<>();\n \n \n        // preparing navigation drawer items\n        for (int i = 0; i < titles.length; i++) {\n            NavDrawerItem navItem = new NavDrawerItem();\n            navItem.setTitle(titles[i]);\n            data.add(navItem);\n        }\n        return data;\n    }\n \n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n \n        // drawer labels\n        titles = getActivity().getResources().getStringArray(R.array.nav_drawer_labels);\n    }\n \n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\n                             Bundle savedInstanceState) {\n        // Inflating view layout\n        View layout = inflater.inflate(R.layout.fragment_navigation_drawer, container, false);\n        recyclerView = (RecyclerView) layout.findViewById(R.id.drawerList);\n \n        adapter = new NavigationDrawerAdapter(getActivity(), getData());\n        recyclerView.setAdapter(adapter);\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n        recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new ClickListener() {\n            @Override\n            public void onClick(View view, int position) {\n                drawerListener.onDrawerItemSelected(view, position);\n                mDrawerLayout.closeDrawer(containerView);\n            }\n \n            @Override\n            public void onLongClick(View view, int position) {\n \n            }\n        }));\n \n        return layout;\n    }\n \n \n    public void setUp(int fragmentId, DrawerLayout drawerLayout, final Toolbar toolbar) {\n        containerView = getActivity().findViewById(fragmentId);\n        mDrawerLayout = drawerLayout;\n        mDrawerToggle = new ActionBarDrawerToggle(getActivity(), drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close) {\n            @Override\n            public void onDrawerOpened(View drawerView) {\n                super.onDrawerOpened(drawerView);\n                getActivity().invalidateOptionsMenu();\n            }\n \n            @Override\n            public void onDrawerClosed(View drawerView) {\n                super.onDrawerClosed(drawerView);\n                getActivity().invalidateOptionsMenu();\n            }\n \n            @Override\n            public void onDrawerSlide(View drawerView, float slideOffset) {\n                super.onDrawerSlide(drawerView, slideOffset);\n                toolbar.setAlpha(1 - slideOffset / 2);\n            }\n        };\n \n        mDrawerLayout.setDrawerListener(mDrawerToggle);\n        mDrawerLayout.post(new Runnable() {\n            @Override\n            public void run() {\n                mDrawerToggle.syncState();\n            }\n        });\n \n    }\n \n    public static interface ClickListener {\n        public void onClick(View view, int position);\n \n        public void onLongClick(View view, int position);\n    }\n \n    static class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {\n \n        private GestureDetector gestureDetector;\n        private ClickListener clickListener;\n \n        public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {\n            this.clickListener = clickListener;\n            gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {\n                @Override\n                public boolean onSingleTapUp(MotionEvent e) {\n                    return true;\n                }\n \n                @Override\n                public void onLongPress(MotionEvent e) {\n                    View child = recyclerView.findChildViewUnder(e.getX(), e.getY());\n                    if (child != null && clickListener != null) {\n                        clickListener.onLongClick(child, recyclerView.getChildPosition(child));\n                    }\n                }\n            });\n        }\n \n        @Override\n        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {\n \n            View child = rv.findChildViewUnder(e.getX(), e.getY());\n            if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {\n                clickListener.onClick(child, rv.getChildPosition(child));\n            }\n            return false;\n        }\n \n        @Override\n        public void onTouchEvent(RecyclerView rv, MotionEvent e) {\n        }\n    }\n \n    public interface FragmentDrawerListener {\n        public void onDrawerItemSelected(View view, int position);\n    }\n}\n```\n\n22.最后在activity_main.xml中添加DrawerLayout，如下代码:  \n\n```xml  \n <android.support.v4.widget.DrawerLayout\n android:id=\"@+id/drawer_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n \n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n \n        <LinearLayout\n            android:id=\"@+id/container_toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n \n            <include\n                android:id=\"@+id/toolbar\"\n                layout=\"@layout/toolbar\" />\n        </LinearLayout>\n \n        <FrameLayout\n            android:id=\"@+id/container_body\"\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n \n \n    </LinearLayout>\n \n \n    <fragment\n        android:id=\"@+id/fragment_navigation_drawer\"\n        android:name=\"androidhive.info.materialdesign.activity.FragmentDrawer\"\n        android:layout_width=\"@dimen/nav_drawer_width\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        app:layout=\"@layout/fragment_navigation_drawer\"\n        tools:layout=\"@layout/fragment_navigation_drawer\" />\n        \n    <android.support.v4.widget.DrawerLayout>  \n```\n \n现在我们已经完成了所有的的布局和jav文件,接下来我们要实现navigation draw而得功能.  \n   \n23.打开MainActivity,作如下修改:\n\nMainActivity实现FragmentDrawer.FragmentDrawerListener接口，复写onDrawerItemSelected()\n  \n创建FragmentDrawer实例，并设置其选择监听事件,如下代码:  \n\n```java  \npublic class MainActivity extends ActionBarActivity implements FragmentDrawer.FragmentDrawerListener {\n \n    private Toolbar mToolbar;\n    private FragmentDrawer drawerFragment;\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n \n        mToolbar = (Toolbar) findViewById(R.id.toolbar);\n \n        setSupportActionBar(mToolbar);\n        getSupportActionBar().setDisplayShowHomeEnabled(true);\n \n        drawerFragment = (FragmentDrawer)\n                getSupportFragmentManager().findFragmentById(R.id.fragment_navigation_drawer);\n        drawerFragment.setUp(R.id.fragment_navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout), mToolbar);\n        drawerFragment.setDrawerListener(this);\n    }\n \n \n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return true;\n    }\n \n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        int id = item.getItemId();\n \n        //noinspection SimplifiableIfStatement\n        if (id == R.id.action_settings) {\n            return true;\n        }\n \n        return super.onOptionsItemSelected(item);\n    }\n \n    @Override\n    public void onDrawerItemSelected(View view, int position) {\n \n    }\n}\n\n```\n    \n    \n现在运行APP能看到以下效果.  \n\n![](http://cdn1.androidhive.info/wp-content/uploads/2015/04/androd-material-design-navigation-drawer.png)\n\n###3.3实现Navigation Drawer的选择  \n\n虽然navigation drawer已经能够工作了，但是MENU的子选项无法工作，这是因为我们还没有处理RecyclerView item是的点击监听。   \n  \n由于我们有三子菜单(Home,Friends&Mesages), 因此我们需要创建三个独立的Fragment  \n\n24.创建fragment_home布局文件,代码如下：  \n\n```xml  \nfragment_home.xml\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"androidhive.info.materialdesign.activity.HomeFragment\">\n \n \n    <TextView\n        android:id=\"@+id/label\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_marginTop=\"100dp\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_horizontal\"\n        android:textSize=\"45dp\"\n        android:text=\"HOME\"\n        android:textStyle=\"bold\"/>\n \n    <TextView\n        android:layout_below=\"@id/label\"\n        android:layout_centerInParent=\"true\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"12dp\"\n        android:layout_marginTop=\"10dp\"\n        android:gravity=\"center_horizontal\"\n        android:text=\"Edit fragment_home.xml to change the appearance\" />\n \n</RelativeLayout>\n```  \n\n25.在activity包下，创建HomeFragment.java文件，代码如下: \n  \n```java  \npublic class HomeFragment extends Fragment {\n \n    public HomeFragment() {\n        // Required empty public constructor\n    }\n \n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n \n    }\n \n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\n                             Bundle savedInstanceState) {\n        View rootView = inflater.inflate(R.layout.fragment_home, container, false);\n \n \n        // Inflate the layout for this fragment\n        return rootView;\n    }\n \n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n    }\n \n    @Override\n    public void onDetach() {\n        super.onDetach();\n    }\n}\n```    \n26.创建另外两个FRAGMENT，跟上面一样  \n\n27.做如下修改.代码如下：\n\ndisplayView方法用来显示menu item选中的页面.这个方法被onDrawerItemSelected()调用.  \n\n```java  \npublic class MainActivity extends ActionBarActivity implements FragmentDrawer.FragmentDrawerListener {\n \n    private static String TAG = MainActivity.class.getSimpleName();\n \n    private Toolbar mToolbar;\n    private FragmentDrawer drawerFragment;\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n \n        mToolbar = (Toolbar) findViewById(R.id.toolbar);\n \n        setSupportActionBar(mToolbar);\n        getSupportActionBar().setDisplayShowHomeEnabled(true);\n \n        drawerFragment = (FragmentDrawer)\n                getSupportFragmentManager().findFragmentById(R.id.fragment_navigation_drawer);\n        drawerFragment.setUp(R.id.fragment_navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout), mToolbar);\n        drawerFragment.setDrawerListener(this);\n \n        // display the first navigation drawer view on app launch\n        displayView(0);\n    }\n \n \n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return true;\n    }\n \n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        int id = item.getItemId();\n \n        //noinspection SimplifiableIfStatement\n        if (id == R.id.action_settings) {\n            return true;\n        }\n \n        if(id == R.id.action_search){\n            Toast.makeText(getApplicationContext(), \"Search action is selected!\", Toast.LENGTH_SHORT).show();\n            return true;\n        }\n \n        return super.onOptionsItemSelected(item);\n    }\n \n    @Override\n    public void onDrawerItemSelected(View view, int position) {\n            displayView(position);\n    }\n \n    private void displayView(int position) {\n        Fragment fragment = null;\n        String title = getString(R.string.app_name);\n        switch (position) {\n            case 0:\n                fragment = new HomeFragment();\n                title = getString(R.string.title_home);\n                break;\n            case 1:\n                fragment = new FriendsFragment();\n                title = getString(R.string.title_friends);\n                break;\n            case 2:\n                fragment = new MessagesFragment();\n                title = getString(R.string.title_messages);\n                break;\n            default:\n                break;\n        }\n \n        if (fragment != null) {\n            FragmentManager fragmentManager = getSupportFragmentManager();\n            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();\n            fragmentTransaction.replace(R.id.container_body, fragment);\n            fragmentTransaction.commit();\n \n            // set the toolbar title\n            getSupportActionBar().setTitle(title);\n        }\n    }\n}\n```    \n\n现在运行app,你能够选择menu的子菜单了。如下效果。\n\n![](http://cdn1.androidhive.info/wp-content/uploads/2015/04/android-material-design-navigation-drawer-1.png)\n\n![](http://cdn1.androidhive.info/wp-content/uploads/2015/04/android-material-design-navigation-drawer-2.png)\n\n![](http://cdn1.androidhive.info/wp-content/uploads/2015/04/android-material-design-navigation-drawer-3.png)\n"
  },
  {
    "path": "issue-8/检测Android应用的启动与关闭.md",
    "content": "检测Android应用的启动与关闭\n---\n\n> * 原文链接 : [Determine when App is Opened or Closed](http://engineering.meetme.com/2015/04/android-determine-when-app-is-opened-or-closed/)\n* 原文作者 : [Bill Donahue](http://engineering.meetme.com/author/bdonahue/)\n* [译文出自 :  开发技术前线 www.devtf.cn](www.devtf.cn)\n* 译者 : [xianjiajun](https://github.com/xianjiajun) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成\n\n\n##问题\n当开发安卓程序的时候，我们不免需要去检测应用什么时候在前台运行，用户什么时候离开。不幸的是，没有一个简单的方法可以做到这点。当用户第一次启动的时候去检测还是不难，但如果是重新打开或关闭就不简单了。\n\n这篇文章将会展示一个用来解决上述问题的技巧。\n\n## 入门指南\n应用的activity是否显示在界面是决定应用是打开还是关闭的核心因素。我们先来看一个简单的例子，一个应用只有一个activity并且不支持横屏，这个activity的onstart和onstop方法就决定了这个应用是打开的还是关闭的。\n\n```java\n@Override\nprotected void onStart() {\n    super.onStart();\n    // The Application has been opened!\n}\n\n@Override\nprotected void onStop() {\n    super.onStop();\n    // The Application has been closed!\n}\n```\n\n\n但有个问题，一旦我们支持横屏，上面这个方法就失效了。如果我们旋转设备，这个activity会重新创建，onstart方法会第二次执行，导致程序错误的认为应用第二次被打开。\n\n为了处理设备旋转，我们需要添加一个验证步骤。这个验证需要启动一个计时器，用来检测当activity停止后，我们是否能很快看到该程序另一个activity启动。如果不能，则说明用户推出了程序，否则说明用户还在使用程序。\n\n这样的验证同样支持有多个activity的应用。因为从一个activity跳转到另外一个也可以用这个验证方式处理。\n\n所以利用这个技巧，我创建了一个管理activity的类，当activity的可见性发生变化的时候都要报告给这个管理类。这个类为每个activity处理验证步骤，避免意外的验证。我们同样利用了“发布-订阅”（观察者）模式，使得其他相关的类能够收到程序打开或关闭的通知。\n\n##使用这个管理类的三个步骤\n\n###1) 将下面的代码添加到你的代码库\n\n```java\nimport android.app.Activity;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.support.annotation.NonNull;\nimport android.text.format.DateUtils;\n\nimport java.lang.ref.Reference;\nimport java.lang.ref.WeakReference;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * 这个类用于追踪当前所有启动的Activity，使得我们能判断应用是否在后台运行。\n */\npublic class AppForegroundStateManager {\n    private static final String TAG = AppForegroundStateManager.class.getSimpleName();\n    private static final int MESSAGE_NOTIFY_LISTENERS = 1;\n    public static final long APP_CLOSED_VALIDATION_TIME_IN_MS = 30 * DateUtils.SECOND_IN_MILLIS; // 30 Seconds\n    private Reference<Activity> mForegroundActivity;\n    private Set<OnAppForegroundStateChangeListener> mListeners = new HashSet<>();\n    private AppForegroundState mAppForegroundState = AppForegroundState.NOT_IN_FOREGROUND;\n    private NotifyListenersHandler mHandler;\n\n    // 获得一个线程安全的类实例\n    private static class SingletonHolder {\n        public static final AppForegroundStateManager INSTANCE = new AppForegroundStateManager();\n    }\n\n    public static AppForegroundStateManager getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    private AppForegroundStateManager() {\n        // 在主线程创建一个 handler\n        mHandler = new NotifyListenersHandler(Looper.getMainLooper());\n    }\n\n    public enum AppForegroundState {\n        IN_FOREGROUND,\n        NOT_IN_FOREGROUND\n    }\n\n    public interface OnAppForegroundStateChangeListener {\n        /** 当应用状态发生改变时这个方法被调用（隐藏到后台或显示到前台） */\n        public void onAppForegroundStateChange(AppForegroundState newState);\n    }\n\n    /** 当 Activity 可见时应该调用这个方法 */\n    public void onActivityVisible(Activity activity) {\n        if (mForegroundActivity != null) mForegroundActivity.clear();\n        mForegroundActivity = new WeakReference<>(activity);\n        determineAppForegroundState();\n    }\n\n    /** 当 Activity 不再可见时应该调用这个方法 */\n    public void onActivityNotVisible(Activity activity) {\n        /*\n         * 前台 Activity 可能会被一个新的 Activity 替换。\n         * 如果新 Activity 与前台 Activity 匹配，仅仅清除前台 Activity\n         */\n        if (mForegroundActivity != null) {\n            Activity ref = mForegroundActivity.get();\n\n            if (activity == ref) {\n                // This is the activity that is going away, clear the reference\n                mForegroundActivity.clear();\n                mForegroundActivity = null;\n            }\n        }\n\n        determineAppForegroundState();\n    }\n\n    /** 用于判断应用是否处于前台 */\n    public Boolean isAppInForeground() {\n        return mAppForegroundState == AppForegroundState.IN_FOREGROUND;\n    }\n\n    /**\n     * 用于判断当前状态，更新追踪的目标，并通知所有观察者状态是否发生了改变\n     */\n    private void determineAppForegroundState() {\n        /* 获取当前状态 */\n        AppForegroundState oldState = mAppForegroundState;\n\n        /* 决定当前状态 */\n        final boolean isInForeground = mForegroundActivity != null && mForegroundActivity.get() != null;\n        mAppForegroundState = isInForeground ? AppForegroundState.IN_FOREGROUND : AppForegroundState.NOT_IN_FOREGROUND;\n\n        /* 如果新的状态与之前的状态不一样，则之前的状态需要通知所有观察者状态发生了改变 */\n        if (mAppForegroundState != oldState) {\n            validateThenNotifyListeners();\n        }\n    }\n\n    /**\n     * 添加一个用于监听前台应用状态的监听器\n     *\n     * @param listener\n     */\n    public void addListener(@NonNull OnAppForegroundStateChangeListener listener) {\n        mListeners.add(listener);\n    }\n\n    /**\n     * 移除用于监听前台应用状态的监听器\n     *\n     * @param listener\n     */\n    public void removeListener(OnAppForegroundStateChangeListener listener) {\n        mListeners.remove(listener);\n    }\n\n    /** 通知所有监听器前台应用状态发生了改变 */\n    private void notifyListeners(AppForegroundState newState) {\n        android.util.Log.i(TAG, \"Notifying subscribers that app just entered state: \" + newState);\n\n        for (OnAppForegroundStateChangeListener listener : mListeners) {\n            listener.onAppForegroundStateChange(newState);\n        }\n    }\n\n    /**\n     * 这个方法会通知所有观察者：前台应用的状态发生了改变\n     * <br><br>\n     * 我们只在应用进入/离开前台时立刻监听器。当打开/关闭/方向切换这些操作频繁发生时，我们\n     * 简要的传递一个一定会被无视的 NOT_IN_FOREGROUND 值。为了实现它，当我们注意到状态发\n     * 生改变，一个延迟的消息会被发出。在这个消息被接收之前，我们不会注意前台应用的状态是否\n     * 发生了改变。如果在消息被延迟的那段时间内应用的状态发生了改变，那么该通知将会被取消。\n     */\n    private void validateThenNotifyListeners() {\n        // If the app has any pending notifications then throw out the event as the state change has failed validation\n        if (mHandler.hasMessages(MESSAGE_NOTIFY_LISTENERS)) {\n            android.util.Log.v(TAG, \"Validation Failed: Throwing out app foreground state change notification\");\n            mHandler.removeMessages(MESSAGE_NOTIFY_LISTENERS);\n        } else {\n            if (mAppForegroundState == AppForegroundState.IN_FOREGROUND) {\n                // If the app entered the foreground then notify listeners right away; there is no validation time for this\n                mHandler.sendEmptyMessage(MESSAGE_NOTIFY_LISTENERS);\n            } else {\n                // We need to validate that the app entered the background. A delay is used to allow for time when the application went into the\n                // background but we do not want to consider the app being backgrounded such as for in app purchasing flow and full screen ads.\n                mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY_LISTENERS, APP_CLOSED_VALIDATION_TIME_IN_MS);\n            }\n        }\n    }\n\n    private class NotifyListenersHandler extends Handler {\n        private NotifyListenersHandler(Looper looper) {\n            super(looper);\n        }\n\n        @Override\n        public void handleMessage(Message inputMessage) {\n            switch (inputMessage.what) {\n                // 解码完成\n                case MESSAGE_NOTIFY_LISTENERS:\n                    /* 通知所有观察者状态发生了改变 */\n                    android.util.Log.v(TAG, \"App just changed foreground state to: \" + mAppForegroundState);\n                    notifyListeners(mAppForegroundState);\n                    break;\n                default:\n                    super.handleMessage(inputMessage);\n            }\n        }\n    }\n}\n```\n\n###2) activity必须通知可见性的改变\n所有的activity都要实现下面的方法来通知管理者其可见性的改变，最好添加到你的base activity中。\n\n```java\n@Override\nprotected void onStart() {\n    super.onStart();\n    AppForegroundStateManager.getInstance().onActivityVisible(this);\n}\n\n@Override\nprotected void onStop() {\n    AppForegroundStateManager.getInstance().onActivityNotVisible(this);\n    super.onStop();\n}\n```\n\n###3) 订阅前台的变化\n\n订阅你感兴趣的前台的状态变化。application类的onCreate方法是首先需要订阅的，这样才能保证每次应用进入或退出前台的时候能收到通知。\n\n```java\npublic class MyApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        AppForegroundStateManager.getInstance().addListener(this);\n    }\n\n    @Override\n    public void onAppForegroundStateChange(AppForegroundStateManager.AppForegroundState newState) {\n        if (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND == newState) {\n            // App just entered the foreground. Do something here!\n        } else {\n            // App just entered the background. Do something here!\n        }\n    }\n}\n```\n\n##深入思考\n有一些细节还需要再讨论。你需要做一些改变来适配你的应用。\n\n##验证时间\n计时器应该隔多久检测一次应用是否真正进入后台。在上面的代码中设置为30秒。\n\n在应用运行的时候，第三方程序的activity可能会出现占满屏幕，比如说google的支付应用或者facebook的登录。这些程序必然会导致你的程序进入后台，因为你应用的activity都没有在前台显示。这种情况并不能当作用户离开了程序，因为他们并没有真正地离开。30秒的超时刚好可以解决这个问题。比如说绝大部分的用户都会在30秒之内完成支付操作，这样他们就不会被当作离开应用。\n\n如果这种情况不适合你，那么我建议你将验证时间设置为4秒。对于那些缓慢的设备来说，这段时间已经足够用来在旋转的时候创建一个activity。\n\n##CPU休眠\n还有一个潜在问题，如果用户在退出应用之后马上就锁屏（或者在应用还在运行的时候锁屏），不能保证CPU有足够长的运行时间来完成后台检测任务。为了确保像预期的一样工作，你需要持有唤醒锁防止CPU休眠，直到应用退出事件得到验证。实际上使用唤醒锁使这个看起来并不是什么大问题。\n\n##论应用如何启动\n到目前为止，我们知道了如何检测应用是什么时候被打开或者关闭的，但是我们还不知道应用是如何被打开的。是用户点击了通知，还是他们点击一个链接，又或者是他们只是从应用图标或最近任务点进来的？\n\n##记录启动方式\n首先我们要在某个地方记录应用打开的方式。在这段代码中，我在application类中添加了一个枚举型变量用来记录应用是如何被打开的。这个建立在上一个例子的基础之上，所以我们打印一下日志，来看看应用是什么时候被打开的和如何被打开的。\n\n```java\npublic class MyApplication extends Application {\n    public final String TAG = MyApplication.class.getSimpleName();\n\n    public enum LaunchMechanism {\n        DIRECT,\n        NOTIFICATION,\n        URL;\n    }\n\n    private LaunchMechanism mLaunchMechanism = LaunchMechanism.DIRECT;\n\n    public void setLaunchMechanism(LaunchMechanism launchMechanism) {\n        mLaunchMechanism = launchMechanism;\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        AppForegroundStateManager.getInstance().addListener(this);\n    }\n\n    @Override\n    public void onAppForegroundStateChange(AppForegroundStateManager.AppForegroundState newState) {\n        if (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND.equals(newState)) {\n            // 应用刚进入前台\n            Log.i(TAG, \"App Just Entered the Foreground with launch mechanism of: \" + mLaunchMechanism);\n        } else {\n            // 应用刚进入前台，并设置我们的登录模式为当前的默认状态\n            mLaunchMechanism = LaunchMechanism.DIRECT;\n        }\n    }\n}\n```\n\n##设置启动方式\n现在当用户打开应用时，我们就可以打印出启动的方式，但实际上我们还没有设置它的值。所以下一步就是要在用户通过链接或通知打开应用的时候设置启动方式。如果不是上述两个方式，则说明用户是直接打开应用。\n\n##记录链接点击\n为了记录用户通过点击链接打开应用，需要在某个地方拦截这个链接，加入下面这行代码。确保这行代码在activity的onStart()之前调用的。根据你的代码结构，可能需要把代码添加到很多地方或者一个公用的链接拦截器。\n\n```java\ngetApplication().setLaunchMechanism(LaunchMechanism.URL);\n```\n\n##记录通知事件\n记录从通知进入是有诀窍的。手机显示通知，用户点击它，打开一个被绑定了的PendingIntent。这个诀窍就是在给所有的PendingIntent加一个标志，用来说明这个Intent是来自通知的。换句话说，当intent最终打开activity的时候，我们需要能够检测到这个intent来自于通知的。\n\n下面就是一个创建来自通知的PendingIntent，把下面的代码添加到每一个intent。\n\n```java\n\tpublic static final String EXTRA_HANDLING_NOTIFICATION = \"Notification.EXTRA_HANDLING_NOTIFICATION\";\n\t\n\t// 通过 Extra 可以知道 Activity 是否通过推送启动\n\tintent.putExtra(EXTRA_HANDLING_NOTIFICATION, true);\n```\n\n最后我们还需要做的就是检查每个activity的标志（添加到你的base activity）。如果我们检测到这个标志量，那么就知道这个activity是通过通知产生的，我们可以设置启动方式为通知启动。这个步骤必须在onCreat里面完成，这样它才可以在应用显示到前台(打印启动方式)之前设置值。\n\n```java\n@Override\npublic void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    Intent intent = getIntent();\n    if (intent != null && intent.getExtras() != null) {\n        // 判断 Activity 是否由用户点击推送启动\n        if (intent.getExtras().getBoolean(EXTRA_HANDLING_NOTIFICATION, false)) {\n            // 发出“应用通过用户点击推送启动”的通知\n            getApplication().setLaunchMechanism(LaunchMechanism.NOTIFICATION);\n        }\n    }\n}\n```\n\n终于完成了。现在你不仅可以检测应用什么时候启动或关闭的，还可以检测出它是如何启动的。\n\n\n\n"
  },
  {
    "path": "issue-9/Android 10ms问题：关于Android音频路径延迟的解释.md",
    "content": "#Android 10ms问题：关于Android音频路径延迟的解释\n---\n\n> * 原文链接 : [Android’s 10 Millisecond Problem: The Android Audio Path Latency Explainer](http://superpowered.com/androidaudiopathlatency/#axzz3XksxvKDY)\n* 作者 : [Gabor (@szantog) and Patrick (@Pv), founders of Superpowered](http://superpowered.com/)\n* 译者 : [objectlife](https://github.com/objectlife) \n* 校对者:    \n* 状态 : \n\n\n   许多手机应用都是非常依赖低音频功能的，比如游戏类，合成软件，DAWs(数字音频工作站)，音频交互应用和模拟乐器应用，以及即将到来的模拟现实应用，所有的这些都因为Apple平台(AppStore+IOS devices)都在得以快速发展，在App Store 和 IOS开发者身上产生的巨大收入在Android上**是不存在的**\n   \n   \n\n   **Android 10ms 问题**了解的不多,但如果在android这个关键点上可以**以一种可接受的方式阻止这类应用产生**甚至发布那将是巨大的技术挑战并且将产生巨大的影响。\n   \n   \n初创公司和开发者不原意开发那些已成功的IOS app(需要10ms音频延迟)的android版本，他们担心音频的处理结果会给他们的口碑带来负面影响以及给品牌和名声带来重重的一击。\n\n\n这样会失去一部分消费者。因为他们还是非常愿意购买一些这样的android应用，有如目前在ios上的收入数据显示一样不能如愿。当有人考虑解释“下一个十亿”消费者将会成为‘mobile-only’人们会感激问题/机会的规模。\n\n\n\n我们要解决这个问题。这篇文章以一种非常容易理解方式说明了谷歌Nexus 9Android 10毫秒问题。\n\n\n\n###Android's 10ms问题和Android 音频路径延迟是如何影响App开发者和Android厂商的\n\n\n\n尽管音乐类应用占IOS App Store总下裁量的3%，但是在收入方面却是位居游戏和社交类之后第三名。它表明那些在App Store/IOS devices提供低延性能的音乐应用收入不成比例。\n\n\n在安卓方面。那是一个悲伤的故事。在play store音乐类应用的收入从没有挤进前5名。\n\n\n绝大多数的android设备都被高声音延迟蹂躏过。阻止开发者开发应用以满足消费者的强烈需求。\n\n\n同样的由于Android 10ms问题，Google和Android的开发者正在无缘于与Apple和IOS开发者共享的数十亿美元。\n\n\n同款移动设备音频的输入和输出都需要某种处理，延迟是不同的。正如音乐家说的一样。我们人类对于这种10ms的延迟是非常舒适的。只要偏高就会影响我们的舒适度。\n\n\n大多数的Android应用有超过100ms的音频输入延迟和超过200ms的来回(音频的输入和输出)延迟。\n为你提供一个快速理解的说法，就像奥斯卡电影Whiplash中鼓手慢了半拍一样。\n\n下面是一些与音频有关系并且正在遭受10ms延迟的蹂躏的应用。\n\n- 音乐乐器，音效类应用：音乐家不能同时一起使用，就像用Android设备的表演家会慢半拍一样。甚至都不能用于练习。\n- DJs不能表演beat-matching。作为他们耳机中的预听信号会和观众听到的信息有差距。应用起来的效果就像来回滚动并且回声也是非常困难的。\n- 游戏类：声音效果，比如爆炸和枪声都会落后几帧。游戏的声音和画面产生分离感，用户体验非常不佳。不能让玩家获得身临其境的体验。\n- 通讯类：比如Skype。如果两个人同时用一部高延迟的Android手机，综合来说声音延迟比网络延迟更严重。换句话说处理音频流比处理网络数据更浪费时间\n- 模拟现实类：当用户转动他的头部时，声音\"跟随\"的慢了，破坏了这种3D音频体验，你可以下载\"Paul McCartney Google Cardboard app\"这个应用试一下。Google正在丧失与Apple共享数十亿美元VR机会。\n\n\n为了教育和通知科技行业的leaders,应用程序开发人员、技术人员、产品经理、高管、记者、企业家、音乐家、玩家和投资者关于 Android 10ms的问题～还没有从中获取益的那些人，我们在Superpowered发表了你正在阅读的这篇问题概述提供了更简单的消化整个安卓音频链和潜在的瓶颈。\n\n我们的目标是在Android 10ms音频延迟的挑战上我们要团结一起。此外，将其转化为促进创新，更好的用户体验，消费者从Google Play受益，Android开发者，Android的OEM厂商和整个Android生态系统的机会。\n\n###音频延迟说明\n\n\n数字音频延迟的计量有两种有用的计量单位\n\n- Millisecond (ms)，大多数的延迟指的是这种。发生在时间上的单位\n- Sample (or frame)：表示在音频流中的一个离散的数字值（数字）。Sampling是软件如何把像声波一样的连续信号转换成samples的序列。Samples是独立的音频通道的数目。对于一个信道信号，一个sample是指一个号码。对于双信道信号的一个sample装置是指两个数字等。\n\n\n我们在最好的情况下计算音频信号流综合延迟\n\n- 音频位于Android的原生层(Android NDK),并且使用Google推荐的低延迟配置。不幸得是大多的应用都没有遵循Google的低延迟推荐\n- Android设备上使用适当的配置并且可以利用\"Fast Mixer\"路径用于输入和输出音频，除了最近的新款Nexus设备，大多数的其它厂家没有配置Android支持Fast Mixer，所以这些设备的延迟明显高于其它的设备，详情请看[ Superpowered’s Mobile Audio Census and Latency Test App](http://superpowered.com/latency)延迟在更多设备上的测试数据\n\n###Android 5.0 Lollipop 音频路径延迟的说明\n\n\n####模拟音频输入\n\n可能有许多不同的模拟元件，比如前置放大器的内置麦克风。这些模拟​​元件可以被认为是“零延迟”，因为在这种情况下，它们的真实延迟通常是幅度低于1毫秒。\n\n延迟:0\n\n---\n\n####模拟到数字转换(ADC)\n\n音频芯片测量在预定的时间间隔输入音频流和每一次测量变换为号码。这个预定义的时间间隔被称为采样率，以Hz测量。对于大多数Android和iOS设备音频芯片而言我们的[Mobile Audio Census and Latency Test App](http://superpowered.com/latency)表明48000赫兹是本地采样率，这意味着该声频数据流进行采样48000次每秒。\n\n因为ADC实现方式通常包含内过采样滤波器，ADC操作中通常是归为1ms延迟。\n\n现在，该音频流已经被数字化，从这点转向现在的数字音频。数字音频几乎从未行进一个接一个，但相反，在组块中被称为“缓冲器”或“周期”。\n\n延迟：1ms\n\n---\n\n####从音频芯片总线传输至音频驱动\n音频芯片有几个任务。它处理ADC和DAC，互相切换或混合多个输入和输出，适用于音量调节等，它也“群体”离散数字音频样本到缓冲区并处理这些缓冲器传送给操作系统。\n\n音频芯片被USB，PCI，火线等这样的总线连接到CPU，每个总线具有其自己的传输延迟，这取决于其内部缓冲器的大小和缓冲计数。在这里延迟为典型的1毫秒（在内部系统总线的音频芯片）至6毫秒（使用传统USB总线设置的USB声卡）。\n\n延迟：1-6 ms\n\n---\n\n####音频驱动（ALSA，OSS等）\n在大多数情况下，音频驱动程序使用音频芯片接收本地采样率48000赫兹的音频传入“总线缓冲区大小”的环形缓冲器。\n\n该环形缓冲器是平滑总线传输的重要组成部分，并且将总线传输缓冲区大小与操作系统音频堆栈的缓冲区大小相“连接”。环形缓冲器在操作系统音频堆栈的缓冲中处理数据，它自然增加了一些延迟时间。\n\nAndr​​oid是基于Linux的，并且大多数Android设备使用最流行的Linux音频驱动系统-ALSA（高级Linux声音架构）。ALSA处理环形缓冲器步骤如下：\n\n- 音频是从“period size”的环形缓冲器消耗。\n- 环形缓冲器的大小是“period size”的倍数。\n\n例如：\n\n- period size= 480samples。\n- Period count= 2。\n- 环形缓冲区的大小为480×2 = 960samples。\n- 音频输入被接收到的一个period（480samples），而音频堆读取/处理的其他period（480samples）。\n- 延迟为1period，480samples。相当于48000赫兹下的10毫秒。\n\n|\n|\n|环形缓冲区（960samples）|\n|period（480samples）period（480samples）|\n\n\n一个常见的​​时期数为2，但有些系统可能偏高...\n\n延迟：one or more periods\n\n---\n\n####Android的音频硬件抽象层（HAL）\n该HAL作为Android的媒体服务器以及Linux音频驱动程序之间的中间人。HAL的实现是由“移植”Android到设备移动设备的制造商提供的。\n\n实现是开放的，厂商可以自由地创建任何类型的HAL代码。使用预定义的结构与媒体服务器通信。媒体服务器装载HAL和要求用于创建具有可选优选参数，如采样速率，缓冲区大小或音频效果的输入输出流。\n\n注意：HAL能否可以根据所述参数执行取决于媒体服务器必须“适应”HAL。\n\n典型的HAL的实现是tinyALSA，它是用来与ALSA音频驱动程序进行通信。一些厂商认为把这块的音频实现代码进行闭源是很重要的。\n\n在分析Android源代码库开源HAL实现的代码后，我们发现一些音频路径的配置问题以及代码的质量严重的的增加了延迟以及不必要的CPU负载           \n\n一个好的HAL实现应该不添加任何的延迟。\n\n延迟：0 or more samples\n\n---\n\n####Audio Flinger\n\n\nAndroid的媒体服务器包括两个服务：\n\n- AudioPolicy服务处理音频会话和权限处理，如使用或者关闭麦克风。这和iOS的“音频会话处理非常相似。\n- AudioFlinger服务处理数字音频流。\n\nAudio Flinger创建RecordThread，其作为一个应用程序和音频驱动器之间的中间人。它的基本任务是：\n\n- 使用Android HAL从环形缓冲区驱动中获取下一个音频输入缓冲区。\n- 如果应用程序请求与原生不同的采样率重新采样。\n- 如果应用要求与原生不同的缓冲区大小执行额外的缓冲。\n\nAudio Flinger有一个“fast mixer”的路径，如果Android配置了这种方式。如果用户应用程序是使用本地（Android NDK）代码，并建立与本地硬件sample rate and period size设置了音频缓冲队列，没有重采样，额外的缓冲或混合（“MixerThread”)会发生在此步骤。\n\nRecordThread是一种“推”的工作方式，没有任何严格的同步到音频驱动程序。它试图使一个“猜测”何时唤醒和运行，但“推”的方法对放弃更加敏感。低延迟系统始终用“拉”的方式，音频驱动程序通过整个音频链“规定”音频I / O。很明显，当Android操作系统在最初构思，设计和开发时，低延迟的音频不是一个优先事项。\n\n延迟：1 period (best case scenario)\n\n---\n\n####Binder\nAndroid主要进程间通信系统的共享内存是用来传输Audio Flinger和用户应用程序之间的音频缓冲区。这是Android系统的心脏，用于Andr​​oid内部的每个地方。\n\n延迟：0\n\n---\n\n####AudioRecord\n\n现在我们处于用户应用程序的进程之中。AudioRecord实现了应用程序音频输入，这是通过OpenSL ES访问客户端库特性的示例\n\n潜伏期：0+samples\n\n---\n\n####用户应用程序\n最后，音频输入到达了目的地那就是用户的应用程序。\n\n因为输入和输出线程是不一样的，一个用户应用程序必须实现线程之间的环形缓冲区。它的大小为2个periods以上（1 for 音频输入 and 1 for 音频输出），但写得不好的应用程序通常比较暴力，用更多的时间来解决CPU瓶颈。\n\n从这一点来说，我们是我们开始回忆一下音频输出。\n\n延迟：超过1period，近2通常（最理想的情况）\n\n---\n\n####AudioTrack\nAudioTrack实现音频输出的用户应用程序的一面。这是通过OpenSL ES访问客户端库的特性。运行一个定期发送下一个音频缓冲区到Audio Flinger的线程。Android 4.4.4之后，AudioTrack不添加延迟的音频路径，因为它可以被设置为只使用一个缓冲区。\n\n延迟：0+ samples\n\n---\n\n####Binder\n\n同音频输入\n\n延迟：0\n\n---\n\n####Audio Flinger\n创建一个PlaybackThread，其工作方式为RecordThread的音频输入反向。\n\n延迟：1周期（最理想的情况）\n\n---\n\n####Android audio HAL \n同音频输入\n\n延迟:0 or more samples\n\n---\n\n####音频驱动（ALSA，OSS等）\n音频输入与输出的工作方式一样并且使用同一个缓冲区\n\n延迟:one or more periods\n\n---\n\n####从音频驱动总线传输到音频芯片\n与音频输入的总线传输类似，延迟范围是典型的从1 ms到6毫秒。\n\n延迟：1-6毫秒\n\n---\n\n####数字到模拟转换器（DAC）\nADC的反向，数字音频被“转换”成模拟这一点上，对于同种原因的ADC，DAC操作中通常是归为1ms延迟。。\n\n延迟：1ms\n\n---\n\n####模拟音频输出\n\nDAC的输出信号是模拟音频，但它需要额外的组件来驱动连接的设备，比如耳机。类似于模拟音频输入，模拟组件可以被认为是“零延迟”。\n\n延迟：0\n\n---\n\n###Android Audio Path Latency Animation\n![Alt text](http://bit.ly/1I9MKxo)\n\n###Android的音频路径延迟案例分析：Google Nexus 9\n\n到目前为止Nexus9在音频延迟测试中表现得最好\n\n最好的结果是35毫秒是使用USB声卡或直接连接耳机连接器的麦克风输入和输出，一个特殊音频适配器禁用内置麦克风阵列降噪/回馈破坏功能，增加了约〜13毫秒的额外延迟。\n\n因此，使用上述相同的模型，我们来分解一下Nexus 9的35毫秒音频延迟：\n\nGoogle Nexus 9的35ms延迟是如何产生的\n\n|COMPONENT |SAMPLES|MS|\n|:--------|--------:|:--:|\n|ADC||1|\n|Bus||1|\n|ALSA audio driver|256|5.3|\n|Audio Flinger|256|5.3|\n|User Application's Ring Buffer|512|10.6|\n|Audio Flinger|256|5.3|\n|ALSA audio driver|256|5.3|\n|Bus||1|\n|DAC||1|\n|||Result: \t35.8|\n\n\n###About Superpowered\n我们的使命是扩大制造商的创造力和生产力的能力-使其更加的真实-深刻塑造他们，生产者生产出来的东西没有超音频技术是不可能的。\n\n为此，我们正在建设的技术，解决音频瓶颈，这将解决Android的10毫秒问题。\n\nSuperpowered Audio SDK for Android and iOS is\n\n- 跨平台：开发人员可以使用或复用相同的代码在Android，iOS和OSX。\n- 速度快：它在移动设备上，为所有应用提供桌面级的加工和专业音频质量的最高性能的音频DSP。减少CPU负载来提高电池寿命和使应用更顺畅。\n- “push” and “pull” audio stacks处理更好即使是离线。\n- 0延迟：Superpowered的特性和处理不添加任何的延迟。使用Superpowered Audio SDK开发的应用程序在Android和IOS的低音频延迟设备上更容易的运行。\n\n我们很乐意听取您的意见。请将你的建议、意见和问题以邮件方式发给我们。\n\n感谢您的阅读。\n\n-Gabor (@szantog) and Patrick (@Pv), founders of Superpowered\n\nPS 请加入有关Android的10毫秒问题伟大的对话和[黑客新闻](https://news.ycombinator.com/item?id=9386994)。\n\n原文链接:[http://superpowered.com/androidaudiopathlatency/#axzz3XksxvKDY](http://superpowered.com/androidaudiopathlatency/#axzz3XksxvKDY)\n"
  },
  {
    "path": "issue-9/Android进行单元测试难在哪-part1.md",
    "content": "Android 进行单元测试难在哪-part1\n---\n\n> * 原文链接 : [WHY ANDROID UNIT TESTING IS SO HARD (PT 1)](http://philosophicalhacker.com/2015/04/17/why-android-unit-testing-is-so-hard-pt-1/)\n* 原文作者 : [Matthew Dupree](http://philosophicalhacker.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [tiiime](https://github.com/tiiime)  \n* 状态 :  完成\n\n\n\n正如我在[序](issue-8/Android 进行单元测试难在哪-序.md)中所说，在 Android 中难于进行测试是众多 Android 开发者的共识。上一篇博文发出后，有许多同行回复了我，并对我的观点表示支持：\n\n> — Andy Dyer (@dammitandy) April 13, 2015\n\n> @philosohacker大大发话了！他写了一篇有关如何能更好地测试 Android 应用的博文，[地址戳我](http://t.co/gQCJKIOrSN)\n> — Andy Dyer (@dammitandy) April 13, 2015\n\n> 卧槽，我终于知道在 Android 上测试应用的正确姿势了！感谢：[地址戳我](http://t.co/8gIqfoFVnL)\n\n> 非常赞同“Android SDK 排斥 Android 测试单元”的看法。你应该让你的代码和 SDK 的的联系尽可能地小。\n\n> — Pascal Hartig (@passy) April 12, 2015\n\n所以在 Android 中进行单元测试困难的问题，我提到的这些已经说得足够清楚了。但事实上，问题的根源比这更显而易见。难于进行单元测试的一部分原因是：为了启动 Roboletric 使你的测试单元能够以一种合理的速度运作，你往往需要做许多繁复且没有意义的事。不过在我看来，这并不是关键因素，关键在于：Google 设计的 Android SDK 和 Google 官方提倡的应用架构模式让测试变得困难，在某些情况下，进行测试甚至是不可能的。\n\n这样说可能让人感觉是泛泛而谈，为了证明我的结论，我今天将会用一整个系列的博文来论证我的观点。在接下来的博文里，我会尽可能解释为什么官方鼓励的应用开发方式会使进行单元测试困难，甚至不可能。除此以外，我还会在这些博文里通过重构应用架构探索增强 Android 可测试性的办法，使得应用的代码不需要直接依赖于 Android SDK。通过介绍 Android 怎么让测试变得困难，我会延伸出我想到的重构应用的几个建议，我会在系列博文的第四篇里把它们列出来。\n\n## 一个（看似）简单的测试示例\n\n要分析这个问题，我们不妨先从一个简单的测试范例开始研究。示例里面用的是 Google IOsched 应用的源码，因为我在第一篇博文里就说道：IOsched 就是 Android 开发者的参考模板。\n\nIOsched 应用里有一个有关 IO 大会细节的页面：\n\n![](https://philosophicalhacker.files.wordpress.com/2015/04/screenshot-0646am-apr-17-2015.png?w=169&h=300)\n\n页面里有一个“+”按钮，我们能通过这个按钮将 IO 大会添加日历的对应日期里，如果 IO 大会已经被添加到日历中，“+”按钮则会变成“check”按钮；如果用户再次点击“check”按钮，IO 大会则会从日历中被移除。打开相应的源码我们会发现：这个按钮的状态储存在一个叫作 mStarred 的实例变量里，Activity 会根据按钮的状态启动一个 Service，Service 将在它的 onStop() 方法中将对应的 IO 大会添加到用户的日历中/将对应的 IO 大会从用户的日历中移除。\n\n```java\n@Override\npublic void onStop() {\n    super.onStop();\n    if (mInitStarred != mStarred) {\n        if (UIUtils.getCurrentTime(this) < mSessionStart) {\n            // Update Calendar event through the Calendar API on Android 4.0 or new versions.\n            Intent intent = null;\n            if (mStarred) {\n                // Set up intent to add session to Calendar, if it doesn't exist already.\n                intent = new Intent(SessionCalendarService.ACTION_ADD_SESSION_CALENDAR,\n                        mSessionUri);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n                        mSessionStart);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n                        mSessionEnd);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_ROOM, mRoomName);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n            } else {\n                // Set up intent to remove session from Calendar, if exists.\n                intent = new Intent(SessionCalendarService.ACTION_REMOVE_SESSION_CALENDAR,\n                        mSessionUri);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n                        mSessionStart);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n                        mSessionEnd);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n            }\n            intent.setClass(this, SessionCalendarService.class);\n            startService(intent);\n \n            if (mStarred) {\n                setupNotification();\n            }\n        }\n    }\n}\n```\n\n在这样的代码里写一个测试单元看起来是个明智的选择，但问题就来了：你无法为这样的代码写一个测试单元。你可能就不懂了，为什么不能写啊？别急，为了找到问题所在，我们不妨回到 Android 系统里，并想一想通常情况下，是什么让代码可以被测试。\n\n我们都知道，一个测试单元包含了三个步骤：准备，测试，断言。为了顺利地展示我们测试中的断言步骤，我们需要获得程序在执行测试步骤中的代码后的状态。我们不妨将这个状态称为“测试后状态”。对一个测试单元来说，只有三种办法能获得测试后状态。\n\n第一种办法就是检查测试步骤中执行的方法的返回值，写下这样的测试代码倒不难：\n\n```java\npublic void testSquare() {\n    \n    MathNerd mathNerd = new MathNerd();\n    \n    int result = mathNerd.square(2);\n    \n    assertEquals(result, 4);\n}\n```\n\n第二种办法就是获得被测试对象中那些可访问的属性的引用，并断言你取得的这些属性就是你想要的：\n\n```java\npublic void testSquare() {\n    \n    MathNerd mathNerd = new MathNerd();\n    \n    int result = mathNerd.square(2);\n    \n    assertTrue(mathNerd.didDoMath());\n}\n```\n\n第三种办法是检查对象中被注入的依赖的状态。这些依赖或许是不可访问的，但因为你在测试函数中对它们进行注入，那么你肯定会持有它们的引用\n\n```java\npublic void testSquare() {\n    \n    Calculator calculator = new Calculator();\n    \n    TerribleMathStudent terribleMathStudent = new TerribleMathStudent(calculator);\n    \n    //terribleMathStudent uses calculator to do this\n    int result = terribleMathStudent.square(2);\n    The IOsched app has a IO session detail screen that looks like this:\n    assertTrue(calculator.didDoMath());\n}\n```\n\n那么我们现在已经把能够在一个测试单元中完成断言步骤的方法都列出来了，接下来就该解释为什么刚刚的 Android 代码无法被测试了：答案很简单，我们根本没有办法在 onStop() 方法里完成测试单元的断言步骤——因为 onStop() 方法根本就没有返回值。SessionDetailActivity 里也不存在能帮助我们验证 SessionCalendarService 是否通过正确的命令启动的可访问属性。最后，启动 SessionCalendarService 的代码也不属于注入到 SessionDetailActivity 中的依赖。\n\n你以为这样就完了吗？很不幸，还有比这更糟糕的。在 onStop() 里完成测试单元的准备步骤也是不可能的。为了在测试单元中完成准备步骤，你必须能够改变影响程序测试后状态的所有因素，不妨把它们叫作“预测试状态”。而我们没有任何办法能够在 onStop() 方法里改变预测试状态，所以我们不能对应用进行单元测试。\n\nonStop() 方法里，预测试状态是一个叫作 mInitStarred 的布尔值，你看看写在 onStop() 里的代码估计就懂我的意思了：\n\n```java\n@Override\npublic void onStop() {\n    super.onStop();\n    if (mInitStarred != mStarred) {\n        if (UIUtils.getCurrentTime(this) < mSessionStart) {\n            // Update Calendar event through the Calendar API on Android 4.0 or new versions.\n            Intent intent = null;\n            if (mStarred) {\n                // Set up intent to add session to Calendar, if it doesn't exist already.\n                intent = new Intent(SessionCalendarService.ACTION_ADD_SESSION_CALENDAR,\n                        mSessionUri);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n                        mSessionStart);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n                        mSessionEnd);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_ROOM, mRoomName);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n            } else {\n                // Set up intent to remove session from Calendar, if exists.\n                intent = new Intent(SessionCalendarService.ACTION_REMOVE_SESSION_CALENDAR,\n                        mSessionUri);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_START,\n                        mSessionStart);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_END,\n                        mSessionEnd);\n                intent.putExtra(SessionCalendarService.EXTRA_SESSION_TITLE, mTitleString);\n            }\n            intent.setClass(this, SessionCalendarService.class);\n            startService(intent);\n \n            if (mStarred) {\n                setupNotification();\n            }\n        }\n    }\n}\n```\n\n这个布尔值会在 ContentProvider 的查询操作完成后通过回调方法被设置：\n\n```java\nprivate void onSessionQueryComplete(Cursor cursor) {\n \n    final boolean inMySchedule = cursor.getInt(SessionsQuery.IN_MY_SCHEDULE) != 0;\n    \n    //...\n    \n    if (!mIsKeynote) {\n        showStarredDeferred(mInitStarred = inMySchedule, false);\n    }\n    \n    //...\n}\n```\n\n让 mInitStarred 在 Loader 回调方法里被初始化的原因是：如果用户向他们的日历添加了在 SessionDetailActivity 中选择的 IO 大会事件，那么用于标识该 IO 大会是否被添加到用户日历中的变量将会被存储到数据库中包含所有 IO 大会的表中。我们都知道，Google 希望我们使用 Loader 去获得数据库中的数据，所以 mInitStarred 在 Loader 回调方法里被初始化。\n\n> 注：但是 Google 自己用 Loader 取数据都非常困难，详见视频：[Click me!](https://www.youtube.com/watch?v=qrPoIF6A9gM)\n\nmInitStarred 用于判断应用是否必须启动 SessinCalendarService 更新用户的日历，添加/移除某个 IO 大会的信息。如果从数据库中查询获得的信息表示用户的日历已经添加了该大会，而且添加按钮已经转换为“check”，我们就不需要进行任何操作。\n\n所以，如果我们要在 onStop() 方法里进行测试，我们想要确定 onStop() 方法是否真正启动了一个根据“+”按钮的状态去更新用户日历的 Service。但很不幸，mInitStarred 是一个在 Loader 回调中被初始化的私有实例变量，如果要在测试单元里改变它的值，一定会非常麻烦。那我们现在不妨再来想想改变测试单元预测试状态的办法。\n\n第一个办法是，改变影响对象预测试状态的公有属性：\n\n> 注：改变测试状态的公有属性显然有很多办法。但如果，我举个例子，预测试状态由被测试对象的依赖决定，那我们当然也可以通过改变对象依赖的公有属性改变被测试对象的预测试状态。\n\n```java\npublic void testSquare() {\n    \n    TerribleMathStudent terribleMathStudent = new TerribleMathStudent();\n    terribleMathStudent.setCalculator(new BrokenCalculator());\n    \n    //terribleMathStudent uses broken calculator to do this, so this student really sucks\n    int result = terribleMathStudent.square(2);\n    \n    assertNotEquals(result, 4);\n    \n    //we pity the terrible math student, so we give him a working calculator\n    terribleMathStudent.setCalculator(new Calculator());\n    \n    result = terribleMathStudent.square(2);\n    \n    assertEquals(result, 4);\n}\n```\n\n第二个办法是改变传入被测试方法的参数：\n\n```java\npublic void testSquare() {\n    \n    MathNerd mathNerd = new MathNerd();\n    \n    //Math nerds only use calculators when they need them\n    int result = mathNerd.squareBigNumber(new BrokenCalculator(), 54321);\n    \n    assertNotEquals(result, 2950771041);\n    \n    result = mathNerd.squareBigNumber(new Calculator(), 54321);\n    \n    assertEquals(result, 2950771041);\n}\n```\n\n就像改变测试后状态一样，这两个办法都不能解决我们的问题，所以我们根本不可能在 onStop() 方法里面完成测试单元的准备步骤，问题的根源在于：SessionDetailActivity 里根本不存在能用于改变 mInitStarred 的公有属性。除此以外，onStop() 方法也不会把 mInitStarred 当作一个参数，所以我们无法通过改变 onStop() 的参数值来改变预测试状态。\n\n## 结论\n\n经过上面一系列源码的分析，每一个希冀进行单元测试的开发者都站在了悬崖的边缘上：即便是 Google 官方所谓的开发模板应用 - IOsched 中一个看起来非常简单的模块，要进行单元测试都困难重重。如果你曾经为了在 Activity 里为你的业务逻辑添加相应的测试单元绞尽脑汁，我现在可以告诉你为什么会这么蛋疼：大部分情况下，要在 Activity 里添加相应的测试单元只是不能实现而已。某些情况下，因为你没有办法改变被测试对象的预测试状态，使得你无法完成单元测试的准备步骤。在另一些情况下，因为你无法访问相关的测试后状态，使得你无法完成对象的断言步骤。而 SessionDetailActivity 的 onStop() 方法就是完美的示例，因为上面的两种情况在它身上体现地淋漓尽致。\n\nAndroid 里还有许多类似的代码也是无法测试的，这让我不禁怀疑：让我们写下无法测试的代码的罪魁祸首，会不会就是带有 Android 平台特性的应用架构方式。所以在下一篇博文里，我将会深入探索这个假设。\n"
  },
  {
    "path": "issue-9/NotRxJava懒人专用指南.md",
    "content": "NotRxJava懒人专用指南\n---\n\n> * 原文链接 : [NotRxJava guide for lazy folks](http://yarikx.github.io/NotRxJava/)\n* 原文作者 : [Yaroslav Heriatovych](http://yarikx.github.io/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [Rocko](https://github.com/Rocko) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  完成校对\n\n\n\n如果你是一位 Android 开发者，那么这些天你可能已经听到或看到一些关于 RxJava 满天飞的宣传了。RxJava 是一个能让你摆脱编写一些复杂繁琐的代码去处理异步事件的库。一旦开始在你的项目中使用，你会对它爱不释手的。\n\n然而，RxJava 有个缺陷，它需要一个陡峭的学习过程。对于一个从未接触使用过 RxJava 的人来说，是很难一次就领会到它的精髓所在的，对于它的一些使用方法你也可能会很迷惑。在项目中使用它意味着你需要稍微地改变一下你的代码编写思路，另外，这样的学习曲线会使得在项目中因为大规模的使用RxJava而引发一些问题。\n\n当然，关于如何去使用 RxJava 已经有许多的教程和代码范例了。感兴趣的开发者可以访问 RxJava 的官方 [Wiki](https://github.com/ReactiveX/RxJava/wiki)，里面有关于什么是 Observable 以及它和 Iterable、Future 之间关系的很好的解释。Wiki 里有一篇很有用的文章：[How To Use RxJava](https://github.com/ReactiveX/RxJava/wiki/How-To-Use-RxJava)，这篇文章包含怎么去发送事件流并且打印出它们的介绍以及它的样例代码。\n\n但我们要明确的是在还没有学习什么是 Observable 的前提下了解 RxJava 用来解决什么问题以及它是怎么帮助我们组织起异步代码的。\n\n我这篇文章的定位就是 RxJava 官方文档的“前篇”，读完这篇文章能更好地去理解 RxJava 所解决的问题。文章中也有一个小 Demo，就是自己怎么去整理那些凌乱的代码，然后看看我们在没有使用 RxJava 的情况下是怎么去遵循 RxJava 基本原则的。\n\n所以，如果你仍有足够的好奇的话就让我们开始吧！\n\n\n## Cat 应用程序\n\n让我们来创建一个真实世界的例子。我们都知道猫是我们技术发展的引擎，所以就让我们也来创建这么一个用来下载猫图片的典型应用吧。\n\n**任务描述**\n\n> 我们有个 Web API，能根据给定的查询请求搜索到整个互联网上猫的图片。每个图片包含可爱指数的参数（描述图片可爱度的整型值）。我们的任务将会下载到一个猫列表的集合，选择最可爱的那个，然后把它保存到本地。\n\n我们只关心下载、处理和保存猫的数据。\n\n我们开始吧~\n\n\n## 模型和 API\n\n下面是描述“猫”的简单数据结构：\n\n``` Java\npublic class Cat implements Comparable<Cat>{\n    Bitmap image;\n    int cuteness;\n\n    @Override\n    public int compareTo(Cat another) {\n        return Integer.compare(cuteness, another.cuteness);\n    }\n}\n```\n\n还有我们传统阻塞式风格的 API，它被打包进 cat-sdk.jar 中了：\n\n``` Java\npublic interface Api {\n    List<Cat> queryCats(String query);\n    Uri store(Cat cat);\n}\n```\n\n这足够清楚了吗？当然！那就让我们开始编写业务逻辑吧：\n\n``` Java\npublic class CatsHelper {\n\n    Api api;\n\n    public Uri saveTheCutestCat(String query){\n        List<Cat> cats = api.queryCats(query);\n        Cat cutest = findCutest(cats);\n        Uri savedUri = api.store(cutest);\n        return savedUri;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n唉，这样清晰简单的代码帅到让我窒息啊。来理清一下代码的炫酷之处吧。主方法 saveTheCutestCat 只包含了 3 个其它方法，然后花个几分钟来看看代码和思考这些方法。你给方法提供了输入参数然后就能得到结果返回了，在这个方法工作的时候我们需要等待它的完成。\n\n简洁而有用，让我们再看看组合方法的其它优势：\n\n**组合**\n\n正如我们看到的，根据其它 3 个方法而新创建了一个方法（` saveTheCutestCat `），因此我们组合了它们。像乐高积木那样，我们把方法之间连接起来组成了乐高积木（实际上可以在之后组合起来）。组合方法是很简单的，从一个方法得到返回结果然后再把它传递给另外的方法做为输入参数，这不简单吗？\n\n**错误的传递**\n\n另外一个好处就是我们处理错误的方式了。任何一个方法都可能因执行时发生错误而被终止，这个错误能在任何层次上被处理掉，Java 中我们叫它抛出了异常，然后这个错误在 try/catch 代码块中做处理。这里的关键点是我们不需要为组合方法里的每个方法都做异常处理，仅需要对这些组合起来的方法做统一处理，像下面这样：\n\n``` Java\ntry{\n    List<Cat> cats = api.queryCats(query);\n    Cat cutest = findCutest(cats);\n    Uri savedUri = api.store(cutest);\n    return savedUri;\n} catch (Exception e) {\n    e.printStackTrace()\n    return someDefaultValue;\n}\n```\n\n这个情况下，我们处理了所有执行时的错误，或者说如果我们没有使用 try/catch 代码块我们能够把错误传递到下一个层次上。\n\n\n## 走向异步\n\n要知道我们身在一个对等待很敏感的世界里，我们也知道不可能只有阻塞式的调用。在 Android 中我们也总需要处理异步代码。\n\n拿 Android 的 ` OnClickListener ` 举个例子，当你需要处理一个控件的点击事件时，你必须提供一个监听器（回调）以供在用户点击控件时被调用。这没有理由使用阻塞的方式去接受点击事件的回调，所以对点击来说总是异步的。现在，让我们也使用异步编程吧。\n\n\n**异步的网络调用**\n\n开始想象下使用没有阻塞的 HTTP client（例如[Ion](https://github.com/koush/ion)），还有就是我们的` cats-sdk.jar `已经更新。它的 API 也换成了异步的方式调用。\n\n新 API 的接口：\n\n``` Java\npublic interface Api {\n    interface CatsQueryCallback {\n        void onCatListReceived(List<Cat> cats);\n        void onError(Exception e);\n    }\n\n\n    void queryCats(String query, CatsQueryCallback catsQueryCallback);\n\n    Uri store(Cat cat);\n}\n```\n\n所以现在我们能异步的获取猫的信息集合列表了，返回正确或错误的结果时都会通过 ` CatsQueryCallback `回调接口。\n\n因为 API 改变了所以我们也不得不改变我们的` CatsHelper `:\n\n``` Java\npublic class CatsHelper {\n\n    public interface CutestCatCallback {\n        void onCutestCatSaved(Uri uri);\n        void onQueryFailed(Exception e);\n    }\n\n    Api api;\n\n    public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){\n        api.queryCats(query, new Api.CatsQueryCallback() {\n            @Override\n            public void onCatListReceived(List<Cat> cats) {\n                Cat cutest = findCutest(cats);\n                Uri savedUri = api.store(cutest);\n                cutestCatCallback.onCutestCatSaved(savedUri);\n            }\n\n            @Override\n            public void onQueryFailed(Exception e) {\n                cutestCatCallback.onError(e);\n            }\n        });\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n现在我们已经不能使用阻塞的 API 了，我们也不能把我们的客户端上写成阻塞式的调用（实际上是可以的，但需要明确的在线程中使用` synchronized  `、` CountdownLatch `、等等，也需要在下层中使用异步处理）。所以我们不能在 ` saveTheCutestCat `方法中直接返回一个值，我们需要对它进行异步回调处理。\n\n让我们再深入一点，如果我们 API 的两个方法调用都是异步的，举个例子，我们正在使用非阻塞IO去写进一个文件。\n\n``` Java\npublic interface Api {\n    interface CatsQueryCallback {\n        void onCatListReceived(List<Cat> cats);\n        void onQueryFailed(Exception e);\n    }\n\n    interface StoreCallback{\n        void onCatStored(Uri uri);\n        void onStoreFailed(Exception e);\n    }\n\n\n    void queryCats(String query, CatsQueryCallback catsQueryCallback);\n\n    void store(Cat cat, StoreCallback storeCallback);\n}\n```\n\n还有我们的 helper：\n\n``` Java\npublic class CatsHelper {\n\n    public interface CutestCatCallback {\n        void onCutestCatSaved(Uri uri);\n        void onError(Exception e);\n    }\n\n    Api api;\n\n    public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){\n        api.queryCats(query, new Api.CatsQueryCallback() {\n            @Override\n            public void onCatListReceived(List<Cat> cats) {\n                Cat cutest = findCutest(cats);\n                api.store(cutest, new Api.StoreCallback() {\n                    @Override\n                    public void onCatStored(Uri uri) {\n                        cutestCatCallback.onCutestCatSaved(uri);\n                    }\n\n                    @Override\n                    public void onStoreFailed(Exception e) {\n                        cutestCatCallback.onError(e);\n                    }\n                });\n            }\n\n            @Override\n            public void onQueryFailed(Exception e) {\n                cutestCatCallback.onError(e);\n            }\n        });\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n现在再来看看代码，跟之前一样优雅吗？明显不是了，这很糟糕！现在它有了更多无关代码和花括号，但是逻辑是一样的。\n\n那么组合在哪呢？他已经不见了！现在你不能像之前那样组合操作了。对于每一个异步操作你都必须创建出回调接口并在代码中插入它们，每一次都需要手动地加入！\n\n错误传递又在哪？又是一个否定！在这样的代码中错误不会自动地传递，我们需要在更深一层上通过自己手动地再让它传递下去（请看` onStoreFailed  `和`onQueryFailed `方法）。\n\n我们很难对这样的代码进行阅读和找出潜在的 bugs。\n\n\n**结束了？**\n\n结束了又怎样？我们能拿它来干嘛？我们被困在这个没有组合回调的地狱里了吗？前方高能，请抓紧你的安全带哦，我们将努力的去把这些干掉！\n  \n\n## 更美好的世界！\n\n### 泛型回调\n\n可以从我们的回调接口中找到共同的模式：\n\n- 都有一个分发结果的方法（` onCutestCatSaved `,` onCatListReceived `,` onCatStored `）\n\n- 它们中大多数（在我们的例子中是全部）有一个用于错误处理的方法（` onError `,` onQueryFailed `,` onStoreFailed `）\n\n所以我们可以创建一个泛型回调接口去替代原来所有的接口。但是我们不能去改变 API 的调用方法的签名，我们必须创建包装类来间接调用。\n\n所以我们的泛型回调接口看起来是这样的：\n\n``` Java\npublic interface Callback<T> {\n    void onResult(T result);\n    void onError(Exception e);\n}\n```\n\n然后我们来创建` ApiWrapper  `来改变一下调用方法的签名：\n\n``` Java\npublic class ApiWrapper {\n    Api api;\n\n    public void queryCats(String query, Callback<List<Cat>> catsCallback){\n        api.queryCats(query, new Api.CatsQueryCallback() {\n            @Override\n            public void onCatListReceived(List<Cat> cats) {\n                catsCallback.onResult(cats);\n            }\n\n            @Override\n            public void onQueryFailed(Exception e) {\n                catsCallback.onError(e);\n            }\n        });\n    }\n\n    public void store(Cat cat, Callback<Uri> uriCallback){\n        api.store(cat, new Api.StoreCallback() {\n            @Override\n            public void onCatStored(Uri uri) {\n                uriCallback.onResult(uri);\n            }\n\n            @Override\n            public void onStoreFailed(Exception e) {\n                uriCallback.onError(e);\n            }\n        });\n    }\n}\n```\n\n所以这仅仅是对于` Callback `的一些传递 resuts/errors 的调用转发逻辑。\n\n最后我们的` CatsHelper `：\n\n``` Java\npublic class CatsHelper{\n\n    ApiWrapper apiWrapper;\n\n    public void saveTheCutestCat(String query, Callback<Uri> cutestCatCallback){\n        apiWrapper.queryCats(query, new Callback<List<Cat>>() {\n            @Override\n            public void onResult(List<Cat> cats) {\n                Cat cutest = findCutest(cats);\n                apiWrapper.store(cutest, cutestCatCallback);\n            }\n\n            @Override\n            public void onError(Exception e) {\n                cutestCatCallback.onError(e);\n            }\n        });\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n可以看到比之前的简明了一些。我们可以通过直接传递一个顶级的` cutestCatCallback `回调接口给` apiWrapper.store `来减少回调间的层级调用，此外作为回调方法的签名是一样的。\n\n但是我们可以做的更好！\n\n\n### 你必须把它分开\n\n让我们来看看我们的异步操作（` queryCats `，` queryCats `，还有`saveTheCutestCat`），它们都遵循了相同的模式。调用它们的方法有一些参数（` query `、` cat `）也包括一个回调对象。再次说明：任何异步操作需要携带所需的常规参数和一个回调实例对象。如果我们试图去分开这几个阶段，每个异步操作仅仅将会携带一个参数对象，然后返回一些携带着回调（信息）的临时对象。\n\n我们来应用下这样的模式，看看是否对我们有所帮助。\n\n如果在异步操作中返回一些临时对象，我们需要定义一个出来。这样的一个对象需要包括常见的行为（以回调为单一参数），我们将定义这样的类给所有的异步操作使用，这个类就叫它` AsyncJob `。\n\n> P.S. 称之为` AsyncTask `更合适一点，但是我不希望你混淆了异步操作跟另外一个存在的抽象概念之间的关系（这是不好的一点）。\n\n所以：\n``` Java\npublic abstract class AsyncJob<T> {\n    public abstract void start(Callback<T> callback);\n}\n```\n\n非常的简单，` Callback `传进方法后就会开始它的工作任务。\n\n然后更改包装 API 的调用：\n\n``` Java\npublic class ApiWrapper {\n    Api api;\n\n    public AsyncJob<List<Cat>> queryCats(String query) {\n        return new AsyncJob<List<Cat>>() {\n            @Override\n            public void start(Callback<List<Cat>> catsCallback) {\n                api.queryCats(query, new Api.CatsQueryCallback() {\n                    @Override\n                    public void onCatListReceived(List<Cat> cats) {\n                        catsCallback.onResult(cats);\n                    }\n\n                    @Override\n                    public void onQueryFailed(Exception e) {\n                        catsCallback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n\n    public AsyncJob<Uri> store(Cat cat) {\n        return new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> uriCallback) {\n                api.store(cat, new Api.StoreCallback() {\n                    @Override\n                    public void onCatStored(Uri uri) {\n                        uriCallback.onResult(uri);\n                    }\n\n                    @Override\n                    public void onStoreFailed(Exception e) {\n                        uriCallback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n}\n```\n\n目前一切顺利，我们只是部分运用我们的包装过的 API 调用在程序之中。现在我们可以开始使用` AsyncJob `去做我们想要的了，当然也到更改` CatsHelper `的时间了。\n\n``` Java\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        return new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> cutestCatCallback) {\n                apiWrapper.queryCats(query)\n                        .start(new Callback<List<Cat>>() {\n                            @Override\n                            public void onResult(List<Cat> cats) {\n                                Cat cutest = findCutest(cats);\n                                apiWrapper.store(cutest)\n                                        .start(new Callback<Uri>() {\n                                            @Override\n                                            public void onResult(Uri result) {\n                                                cutestCatCallback.onResult(result);\n                                            }\n\n                                            @Override\n                                            public void onError(Exception e) {\n                                                cutestCatCallback.onError(e);\n                                            }\n                                        });\n                            }\n\n                            @Override\n                            public void onError(Exception e) {\n                                cutestCatCallback.onError(e);\n                            }\n                        });\n            }\n        };\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n哇，之前的版本更简单些啊，我们现在的优势是什么？答案就是现在我们可以给客户端返回“组合”操作的` AsyncJob<Uri> `。所以一个客户端（在 activity 或者 fragment 处）可以用组合起来的工作来操作。\n\n### Breaking things\n\n这是我们的逻辑数据流：\n``` bash\n         (async)                 (sync)           (async)\nquery ===========> List<Cat> -------------> Cat ==========> Uri\n       queryCats              findCutest           store\n```\n\n为了让我们的代码拥有之前的可读性，我们从这个事件流中强行进入到里面的操作里。但有件事需要注意，如果某些操作（方法）是异步的，然后调用它的操作（方法）又是异步的，就比如，查询猫的操作是异步的，然后寻找出最可爱的猫（即使有一个阻塞调用）也是一个异步操作（客户端希望接收的结果）。\n\n所以我们可以使用 AsyncJobs 把我们的方法分解成更小的操作：\n\n``` Java\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() {\n            @Override\n            public void start(Callback<Cat> callback) {\n                catsListAsyncJob.start(new Callback<List<Cat>>() {\n                    @Override\n                    public void onResult(List<Cat> result) {\n                        callback.onResult(findCutest(result));\n                    }\n\n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n\n        AsyncJob<Uri> storedUriAsyncJob = new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> cutestCatCallback) {\n                cutestCatAsyncJob.start(new Callback<Cat>() {\n                    @Override\n                    public void onResult(Cat cutest) {\n                        apiWrapper.store(cutest)\n                                .start(new Callback<Uri>() {\n                                    @Override\n                                    public void onResult(Uri result) {\n                                        cutestCatCallback.onResult(result);\n                                    }\n\n                                    @Override\n                                    public void onError(Exception e) {\n                                        cutestCatCallback.onError(e);\n                                    }\n                                });\n                    }\n\n                    @Override\n                    public void onError(Exception e) {\n                        cutestCatCallback.onError(e);\n                    }\n                });\n            }\n        };\n        return storedUriAsyncJob;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n代码量多了许多，但是更加清晰了。低层次嵌套的回调，利于理解的变量名（` catsListAsyncJob `、` cutestCatAsyncJob `、` storedUriAsyncJob `）。\n\n看起来好了许多，但我们要再做一些事情：\n\n### 简单映射\n\n现在看看 ` AsyncJob<Cat> cutestCatAsyncJob `的部分：\n\n``` Java\nAsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() {\n            @Override\n            public void start(Callback<Cat> callback) {\n                catsListAsyncJob.start(new Callback<List<Cat>>() {\n                    @Override\n                    public void onResult(List<Cat> result) {\n                        callback.onResult(findCutest(result));\n                    }\n\n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n```\n\n这 16 行代码只有一行是对我们有用（对于逻辑来说）的操作：\n\n``` Java\nfindCutest(result)\n```\n\n剩下的仅仅是开启另外一个` AsyncJob `和传递结果与错误的样板代码。此外，这些代码并不用于特定的任务，我们可以把其移动到其它地方而不影响编写我们真正需要的业务代码。\n\n我们该怎么写呢？我们必须做下面的两件事情：\n\n- ` AsyncJob `是我们转换的结果\n\n- 转换方法\n\n这又有另外一个问题，因为在 Java 中不能直接传递方法（函数）所以我们需要通过类（和接口）来间接实现这样的功能，然后我们就来定义这个 “方法”：\n\n``` Java\npublic interface Func<T, R> {\n    R call(T t);\n}\n```\n\n相当简单，` Func `接口有两个类型成员，` T `对应于参数类型而` R `对应于返回类型。\n\n当我们从一个` AsyncJob `中装换处结果后我们就需要做一些值之间的映射，这样的方法我们就叫它` map `。定义这个方法实例（Func 类型）最好的地方就在` AsyncJob `类中，所以` AsyncJob `代码里看起来就是这样了：\n\n``` Java\npublic abstract class AsyncJob<T> {\n    public abstract void start(Callback<T> callback);\n\n    public <R> AsyncJob<R> map(Func<T, R> func){\n        final AsyncJob<T> source = this;\n        return new AsyncJob<R>() {\n            @Override\n            public void start(Callback<R> callback) {\n                source.start(new Callback<T>() {\n                    @Override\n                    public void onResult(T result) {\n                        R mapped = func.call(result);\n                        callback.onResult(mapped);\n                    }\n\n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n}\n```\n\n赞，这时` CatsHelper `就是下面这样了：\n\n``` Java\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n\n        AsyncJob<Uri> storedUriAsyncJob = new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> cutestCatCallback) {\n                cutestCatAsyncJob.start(new Callback<Cat>() {\n                    @Override\n                    public void onResult(Cat cutest) {\n                        apiWrapper.store(cutest)\n                                .start(new Callback<Uri>() {\n                                    @Override\n                                    public void onResult(Uri result) {\n                                        cutestCatCallback.onResult(result);\n                                    }\n\n                                    @Override\n                                    public void onError(Exception e) {\n                                        cutestCatCallback.onError(e);\n                                    }\n                                });\n                    }\n\n                    @Override\n                    public void onError(Exception e) {\n                        cutestCatCallback.onError(e);\n                    }\n                });\n            }\n        };\n        return storedUriAsyncJob;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n现在好多了，创建` AsyncJob<Cat> cutestCatAsyncJob `只需要 6 行代码而回调也只有一个层级了。\n\n\n### 高级映射\n\n前面的那些已经很赞了，但是创建` AsyncJob<Uri> storedUriAsyncJob `的部分还有些不忍直视。能在这里创建映射吗？我们来试试吧：\n\n``` Java\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n\n        AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.map(new Func<Cat, Uri>() {\n            @Override\n            public Uri call(Cat cat) {\n                return apiWrapper.store(cat);\n        //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ will not compile\n        //      Incompatible types:\n        //      Required: Uri\n        //      Found: AsyncJob<Uri>                \n            }\n        });\n        return storedUriAsyncJob;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n呃呃。。。并不容易哦，我们来修改下结果的类型变量，试下其它方法：\n\n``` Java\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n\n        AsyncJob<AsyncJob<Uri>> storedUriAsyncJob = cutestCatAsyncJob.map(new Func<Cat, AsyncJob<Uri>>() {\n            @Override\n            public AsyncJob<Uri> call(Cat cat) {\n                return apiWrapper.store(cat);\n            }\n        });\n        return storedUriAsyncJob;\n        //^^^^^^^^^^^^^^^^^^^^^^^ will not compile\n        //      Incompatible types:\n        //      Required: AsyncJob<Uri>\n        //      Found: AsyncJob<AsyncJob<Uri>>\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n在目前这点上我们只能有` AsyncJob<AsyncJob<Uri>> `。我们需要往更深处挖吗？我们希望的是，去把`AsyncJob `在一个级别上的两个异步操作扁平化成一个单一的异步操作。\n\n现在我们需要的是得到能使方法返回映射成` R `类型也是` AsyncJob<R> `类型的操作。这个操作应该像` map `，但在最后应该` flatten `我们嵌套的` AsyncJob `。我们叫它为` flatMap `吧，然后就是来实现它：\n\n``` Java\npublic abstract class AsyncJob<T> {\n    public abstract void start(Callback<T> callback);\n\n    public <R> AsyncJob<R> map(Func<T, R> func){\n        final AsyncJob<T> source = this;\n        return new AsyncJob<R>() {\n            @Override\n            public void start(Callback<R> callback) {\n                source.start(new Callback<T>() {\n                    @Override\n                    public void onResult(T result) {\n                        R mapped = func.call(result);\n                        callback.onResult(mapped);\n                    }\n\n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n\n    public <R> AsyncJob<R> flatMap(Func<T, AsyncJob<R>> func){\n        final AsyncJob<T> source = this;\n        return new AsyncJob<R>() {\n            @Override\n            public void start(Callback<R> callback) {\n                source.start(new Callback<T>() {\n                    @Override\n                    public void onResult(T result) {\n                        AsyncJob<R> mapped = func.call(result);\n                        mapped.start(new Callback<R>() {\n                            @Override\n                            public void onResult(R result) {\n                                callback.onResult(result);\n                            }\n\n                            @Override\n                            public void onError(Exception e) {\n                                callback.onError(e);\n                            }\n                        });\n                    }\n\n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n}\n```\n\nFlatMap 的粗略实现，但这些东西的实现都在一个地方了，在客户端的业务代码中不会再见到它。接下来我们修复下` CatsHelper `:\n\n``` Java\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n\n        AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.flatMap(new Func<Cat, AsyncJob<Uri>>() {\n            @Override\n            public AsyncJob<Uri> call(Cat cat) {\n                return apiWrapper.store(cat);\n            }\n        });\n        return storedUriAsyncJob;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n哈哈！它能用了，读和写也简单了不少。\n\n\n### 最后的要点\n\n再来看看我们编写的代码，眼熟吗？如果我们使用 Java 8 的 lambdas（逻辑是一样的但是看起来更爽一些） 代码会更加地简洁。\n\n``` Java\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(cats -> findCutest(cats));\n        AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.flatMap(cat -> apiWrapper.store(cat));\n        return storedUriAsyncJob;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n它看起来会更好吗？我认为这样的代码跟我们第一次阻塞的版本差不多：\n\n``` Java\npublic class CatsHelper {\n\n    Api api;\n\n    public Uri saveTheCutestCat(String query){\n        List<Cat> cats = api.queryCats(query);\n        Cat cutest = findCutest(cats);\n        Uri savedUri = api.store(cutest);\n        return savedUri;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n是的，就是这样，逻辑是相似的！也有可能会复杂些（语义是一样的）。\n我们这样的代码有组合性吗？请大声的说有！我们组合了所有的异步操作然后作为返回结果我们仅需一个组合后的结果对象而已。\n错误传递呢？当然也有！所有的错误都会传递到最后的回调中。\n接下来的最后呢。。。\n\n\n## [RxJava](https://github.com/ReactiveX/RxJava)\n\n嘿，你不需要把那些代码拷到你的项目中，因为我们还是实现地不够完全的，仅仅算是非线程安全的 RxJava 的一小部分而已。\n\n它们之间只有一些差异：\n\n- ` AsyncJob<T> ` 就是实际上的 [Observable<T>](http://reactivex.io/documentation/observable.html)，它不仅可以只分发一个单一的结果也可以是一个序列（可以为空）。\n\n- ` Callback<T> ` 就是 [Observer<T>](http://reactivex.io/documentation/operators/subscribe.html)，除了 Callback 少了` onNext(T t) `方法。Observer<T> 中在` onError(Throwable t) `方法被调用后，会继而调用` onCompleted() `，然后 Observer 会包装好并发送出事件流（因为它能发送一个序列）。\n\n- ` abstract void start(Callback<T> callback) `对应 [Subscription subscribe(final Observer<? super T> observer)](http://reactivex.io/RxJava/javadoc/rx/Observable.html#subscribe(rx.Observer))，这个方法也返回 [Subscription](http://reactivex.io/RxJava/javadoc/rx/Subscription.html) ，在不需要它时你可以决定取消接收事件流。\n\n- 除了` map `和` flatMap `方法，` Observable `在 Observalbes 之上也有一些[其它](http://reactivex.io/documentation/operators.html)有用的操作。\n\n下面的代码是使用 RxJava 来完成我们前面自己写的代码的功能：\n\n``` Java\npublic class ApiWrapper {\n    Api api;\n\n    public Observable<List<Cat>> queryCats(final String query) {\n        return Observable.create(new Observable.OnSubscribe<List<Cat>>() {\n            @Override\n            public void call(final Subscriber<? super List<Cat>> subscriber) {\n                api.queryCats(query, new Api.CatsQueryCallback() {\n                    @Override\n                    public void onCatListReceived(List<Cat> cats) {\n                        subscriber.onNext(cats);\n                    }\n\n                    @Override\n                    public void onQueryFailed(Exception e) {\n                        subscriber.onError(e);\n                    }\n                });\n            }\n        });\n    }\n\n    public Observable<Uri> store(final Cat cat) {\n        return Observable.create(new Observable.OnSubscribe<Uri>() {\n            @Override\n            public void call(final Subscriber<? super Uri> subscriber) {\n                api.store(cat, new Api.StoreCallback() {\n                    @Override\n                    public void onCatStored(Uri uri) {\n                        subscriber.onNext(uri);\n                    }\n\n                    @Override\n                    public void onStoreFailed(Exception e) {\n                        subscriber.onError(e);\n                    }\n                });\n            }\n        });\n    }\n}\n\npublic class CatsHelper {\n\n    ApiWrapper apiWrapper;\n\n    public Observable<Uri> saveTheCutestCat(String query) {\n        Observable<List<Cat>> catsListObservable = apiWrapper.queryCats(query);\n        Observable<Cat> cutestCatObservable = catsListObservable.map(new Func1<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return CatsHelper.this.findCutest(cats);\n            }\n        });\n        Observable<Uri> storedUriObservable = cutestCatObservable.flatMap(new Func1<Cat, Observable<? extends Uri>>() {\n            @Override\n            public Observable<? extends Uri> call(Cat cat) {\n                return apiWrapper.store(cat);\n            }\n        });\n        return storedUriObservable;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n你可以看到代码是相同的，除了使用` Observable `来替代` AsyncJob `。\n\n\n### 总结\n\n我们看到，通过简单的转化我们可以把异步操作给抽象出来。这个抽象出来的东西可以被用来操作和组合异步操作就像简单的方法那样。通过这种方法我们可以摆脱嵌套的回调，在处理异步结果时也能手动处理错误的传递。\n\n如果你看到了这里的话建议你放松下，思考下 sync/async 之间的二元关系，然后看看这个很棒的来自[Erik Meijer](http://en.wikipedia.org/wiki/Erik_Meijer_(computer_scientist))的[视频](https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote-Duality)。\n\n## 一些有用的链接\n- [http://reactivex.io](http://reactivex.io)\n- [https://github.com/ReactiveX/RxJava](https://github.com/ReactiveX/RxJava)\n- [https://github.com/ReactiveX/RxJava/wiki](https://github.com/ReactiveX/RxJava/wiki)\n- [http://queue.acm.org/detail.cfm?id=2169076](http://queue.acm.org/detail.cfm?id=2169076)\n- [https://www.coursera.org/course/reactive](https://www.coursera.org/course/reactive)\n- [http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)\n\n\n## 感谢\n\n感谢我的朋友 Alexander Yakushev 帮忙翻译。\n"
  },
  {
    "path": "issue-9/readme.md",
    "content": "# 开发技术前线 第九期\n\n| 文章名称 |   译者  | \n|---------|--------|\n| [Android 10ms问题：关于Android音频路径延迟的解释](Android 10ms问题：关于Android音频路径延迟的解释.md)  | [objectlife](https://github.com/objectlife)      |\n| [Android进行单元测试难在哪-part1](Android 进行单元测试难在哪-part1.md)  | [chaossss](https://github.com/chaossss)|\n| [NotRxJava懒人专用指南](NotRxJava懒人专用指南.md)  | [Rocko](https://github.com/Rocko)      |\n| [使用Android Studio进行单元测试](使用Android-Studio进行单元测试.md)  | [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang)|\n| [通过Jenkins并行完成UI的自动化测试](通过Jenkins并行完成UI的自动化测试.md)  | [chaossss](https://github.com/chaossss)     |\n| [创建-RecyclerView-LayoutManager-Part-1](创建-RecyclerView-LayoutManager-Part-1.md)  | [tiiime](https://github.com/tiiime)     |\n"
  },
  {
    "path": "issue-9/使用Android-Studio进行单元测试.md",
    "content": "使用Android Studio进行单元测试\n---\n\n> * 原文链接 : [Unit Testing With Android Studio](http://rexstjohn.com/unit-testing-with-android-studio/)\n* 原文作者 : [Rex St John](http://rexstjohn.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang) \n* 校对者: [zhengxiaopeng](https://github.com/zhengxiaopeng)  \n* 状态 :  校对完\n\n这篇文章介绍了在Android Studio中进行单元测试的基础部分。\n\n#在Android Studio中可以进行单元测试\n\n\n很多教程都指导你应该在“build.gradlew”中添加几行代码来开启“instrument tests” 功能，而且还需要添加 Android 测试库的项目依赖。\n\n其实你并不需要按照这种错误的方式去做，因为这是完全没有必要的。\n\nAndroid Studio本身就支持Android单元测试，你只需要在你的项目中简单配置一下就可以了。\n\n注意：还有好几种流行的Android单元测试框架，比如[Robolectric](http://robolectric.org/)，这些框架涉及到的配置和设置比我在这里提到的更多，我希望在未来可以以这个主题写一些指导教程。\n\n#创建你的单元测试文件夹\n\n我喜欢把单元测试放在我的主项目里面，比如“com.mypath.tests.” ，你可以把测试目录放到你想放的任何地方。在开始之前，像下面这样，先创建你的\"test\"文件夹。(译者注：这一步不是必须的，你也可以把单元测试类创建在与Android Studio默认的ApplicationTest类相同的路径下面)\n\n![](http://i2.tietuku.com/8ea1f7ff89634a0f.png)\n\n接下来，创建一个叫做 “ExampleTest”的类，要继承自InstrumentationTestCase\n\n![](http://i2.tietuku.com/164d47e438f78f37.png)\n\n然后可以添加一段简单的测试代码，我们知道这段代码肯定会运行失败\n\n```\npublic class ExampleTest extends InstrumentationTestCase {\n    public void test() throws Exception {\n        final int expected = 1;\n        final int reality = 5;\n        assertEquals(expected, reality);\n    }\n}\n```\n\n\n注意：所有的测试方法必须以\"test\"开头，否则Android Studio不能找到要进行单元测试的方法，你将会得到各种各样的错误，并且无法正常执行。\n\n#为你的项目配置单元测试\n\n现在我们已经有了一个必然会运行失败的测试用例，我们必须把它run起来。\n\n首先点击\"Run-> Edit Configurations\"\n\n![](http://i2.tietuku.com/e91b3515dff21267.png)\n\n然后点击“+”，从左上角选择添加一个 Android Tests，然后你可以将这个测试配置重新命名为\"test\"或与之相关的名字\n\n![](http://i2.tietuku.com/6f5c952065151e07.png)\n\n然后就会创建如下的测试项目配置\n\n![](http://i2.tietuku.com/2183cac60bbed220.png)\n\n从下拉菜单中选择你当前的module\n\n![](http://i2.tietuku.com/854bbdd0299ddb1c.png)\n\n\n接下来，选择\"All in Package\"选项，然后把你的刚才创建的测试文件夹选中。你也可以选择“All in Module”选项，这样Android Studio会自动的找到你整个Module中的所有测试单元，你也可以通过更具体的类或者是方法选项，进一步缩小测试范围。\n\n做完这一切之后，看起来应该像下面这样\n\n![](http://i2.tietuku.com/15039870f925e4e9.png)\n\n我也喜欢选中下面的“Show chooser dialog”,这样当每次运行的时候，我可以指定如何去运行\n\n![](http://i2.tietuku.com/66d3e1d4a120df74.png)\n\n现在点击\"Apply\"然后关闭，你现在应该可以看到你的测试案例已经作为一个可以运行的项目配置在Android Studio上面的工具栏上了\n\n![](http://i2.tietuku.com/6acd89afcb22309d.png)\n\n#运行我们的单元测试\n\n我使用Genymotion来完成所有的事情，所以开启你的Genymotion然后运行test\n\n在assertion这一行添加一个断点，然后点击 “run debug mode”，目的是为了证明Android Studio确实执行了我们的单元测试。\n\n![](http://i2.tietuku.com/e91aefd47fe27e05.png)\n\n当你开始你的测试工程之后，你会看到一个叫做“Running Tests…”的显示窗口\n\n![](http://i2.tietuku.com/2202b414f006234a.png)\n\n\n当你的测试没有通过，点击“Logcat”然后查看综合的输出结果，看下我们测试失败的原因\n\n![](http://i2.tietuku.com/82cf59170999b096.png)\n\n通过控制台，可以看到打印出的错误原因：\n\n```\n“junit.framework.AssertionFailedError: expected:<1> but was:<5>”\n```\n\n恭喜你，你已经成功测试出错误啦~\n\n下面的这些资料在完成本文时，给了很大的帮助\n\n- http://mobilengineering.blogspot.com/2012/05/tdd-testing-asynctasks-on-android.html\n- http://www.vogella.com/tutorials/AndroidTesting/article.html\n- http://nikolaj.hesselholtskov.dk/2013/10/how-to-add-unit-tests-to-android-studio.html"
  },
  {
    "path": "issue-9/创建-RecyclerView-LayoutManager-Part-1.md",
    "content": "创建一个 RecyclerView LayoutManager – Part 1\n---\n\n> * 原文链接 : [Building a RecyclerView LayoutManager – Part 1][source]\n> * 原文作者 : [Dave Smith][author]\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [tiiime](https://github.com/tiiime) \n* 校对者: [chaossss](https://github.com/chaossss) \n* 状态 :   完成 \n\n\n# Building a RecyclerView LayoutManager – Part 1\n\n>本文是这个系列中的 Part 1，这里是 [Part 2][part2] 和 [Part 3][part3] 的链接。\n\n现如今，如果你是 Android 开发者，你肯定听说过 RecyclerView 这个控件；\n它是一个即将加入 support library 之中的新组件，\n通过方便的视图复用轻松实现自定义高效的视图集。\n已经有很多优秀的文章介绍了 RecyclerView 基础，讲解如何使用\nRecyclerView 提供的内建部分，包括 item animations。所以，\n我们就不再重复前人的工作了，下面是一些帮你入门的资料：\n\n- [A First Glance at Android’s RecyclerView][res-1]\n- [The new TwoWayView][res-2]\n- [RecyclerViewItemAnimators][res-3]\n\n我们的这个系列文章会关注 RecyclerView 底层细节，涉及到创建你自己的 \nLayoutManager，做些比单一的 垂直/水平 滚动列表稍稍复杂的东西。\n\n>在我们开始前，你需要知道  LayoutManager API  之所以能够让我们实现\n>强大复杂的布局是因为它只替你做了很少的工作；这就意味着\n>你不得不自己完成数量可观的代码。如果要在一个项目中使用自定义视图，\n>不要陷入过度优化或过度泛化代码之中。你只需要关心\n>在你的用例中需要实现的特性就可以了。\n\n# RecyclerView Playground\n\n这个系列中所有的代码段都取自 [RecyclerView Playground sample][demo] 这个项目，\n示例应用里包含了各个方面使用 RecyclerView 的实例，从创建简单的 list\n到自定义 LayoutManagers。\n\n本文的代码来自 `FixedGridLayoutManager` ，一个可以垂直和水平\n滚动的二维的网格布局。\n\n>support library 里也有一个自定义的 LayoutManager；本质上\n>是一个自定义 vertical linear list  的实现：\n>[SDK_PATH]/extras/android/compatibility/samples/Support7Demos/src/com/example/android/supportv7/widget/RecyclerViewActivity.java\n>\n>同时，Android L 和 新的 support libraries 可能还没加入 AOSP\n>之中，不过 RecyclerView 提供了 **JAR** 资源，可以在这里找到：\n>[SDK_PATH]/extras/android/m2repository/com/android/support/recyclerview-v7/21.0.0-rc1/recyclerview-v7-21.0.0-rc1-sources.jar\n\n# The Recycler\n\n首先，了解下 API 的结构。当你需要从一个可能再生的前子视图中\n回收旧的 view 或者 获取新的 view 时，\n你的 LayoutManager 可以访问一个 `Recycler` 实例。\n\nRecycler 也免掉了直接访问 view 当前适配器方法的麻烦。当你的\nLayoutManager 需要一个新的子视图时，只要调用 `getViewForPosition() `\n这个方法，Recycler 会决定到底是从头创建一个新的视图\n还是重用一个已存在的废弃视图。\n你的 LayoutManager 需要及时将不再显示的视图传递给 Recycler，\n避免 Recycler 创建不必要的 view 对象。\n\n## Detach vs. Remove\n\n布局更新时有两个方法处理已存在的子视图：detach 和\nremove (分离和移除)。Detach 是一个轻量的记录 view 操作。\n被 detach 的视图在你的代码返回前能够重新连接。可以通过 Recycler\n在不 重新绑定/重新构建 子视图的情况下修改已连接子视图的索引。\n\nRemove 意味着这个 view 已经不需要了。任何被永久移除的 view 都应该\n放到 Recycler 中，方便以后重用，不过 API 并没有强制要求。\n被 remove 的视图是否被回收取决于你。\n\n## Scrap vs. Recycle\n\nRecycler 有两级视图缓存系统： scrap heap 和 recycle pool (垃圾堆和回收池)，\nScrap heap 是一个轻量的集合，视图可以不经过适配器直接返回给\nLayoutManager 。通常被 detach \n但会在同一布局重新使用的视图会临时储存在这里。Recycle pool 存放的\n是那些假定并没有得到正确数据(相应位置的数据)的视图，\n因此它们都要经过适配器重新绑定后才能返回给 LayoutManager。\n\n当要给 LayoutManager 提供一个新 view 时，Recycler 首先会\n检查 scrap heap 有没有对应的 position/id；如果有对应的内容，\n就直接返回数据不需要通过适配器重新绑定。如果没有的话，\nRecycler 就会从 recycle pool 里弄一个合适的视图出来，\n然后用 adapter 给它绑定必要的数据\n(就是调用 `RecyclerView.Adapter.bindViewHolder()`) 再返回。\n如果 recycle pool 中也不存在有效 view ，就会在绑定数据前\n创建新的 view (就是 `RecyclerView.Adapter.createViewHolder()`)，\n最后返回数据。\n\n## 经验法则\n\n只要你原意，LayoutManager 的 API 允许你独立完成所有这些任务，\n所以可能的组合有点多。通常来说，\n如果你想要临时整理并且希望稍后在同一布局中重新使用某个 view 的话，\n 可以对它调用 `detachAndScrapView()` 。如果基于当前布局\n 你不再需要某个 view 的话，对其调用 `removeAndRecycleView()`。\n\n# Building The Core\n\nLayoutManager 需要实时添加，测量和布局所有它需要的子视图。\n当用户滚动屏幕时，布局管理器将来决定什么时候添加新的子视图，\n什么时候可以 detach/scrap (分离/废弃)视图。\n\n你需要实现下面这些方法创建一个可行的 LayoutManager 最小系统。\n\n#### generateDefaultLayoutParams()\n\n事实上你只要重写这个方法你的 LayoutManager 就能编译通过了。\n实现也很简单，返回一个你想要默认应用给所有从 \nRecycler 中获得的子视图做参数的 `RecyclerView.LayoutParams` 实例。\n这些参数会在对应的 `getViewForPosition()` 返回前赋值给相应的子视图。\n\n```java\n@Override\npublic RecyclerView.LayoutParams generateDefaultLayoutParams() {\n    return new RecyclerView.LayoutParams(\n        RecyclerView.LayoutParams.WRAP_CONTENT,\n        RecyclerView.LayoutParams.WRAP_CONTENT);\n}\n```\n\n#### onLayoutChildren()\n\n`onLayoutChildren()` 是 LayoutManager 的主入口。\n它会在 view 需要初始化布局时调用，\n当适配器的数据改变时(或者整个适配器被换掉时)会再次调用。\n**注意！这个方法不是在每次你对布局作出改变时调用的。**\n它是 初始化布局 或者 在数据改变时重置子视图布局的好位置。\n\n在接下来的部分，我们会分析在适配器更新时\n是怎样使用它基于当前可见元素刷新布局的。\n现在，我们将简单地解决这个问题当做子视图布局第一关。\n下面是 `FixedGridLayoutManager` 示例的精简版：\n\n```java\n@Override\npublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n    //Scrap measure one child\n    View scrap = recycler.getViewForPosition(0);\n    addView(scrap);\n    measureChildWithMargins(scrap, 0, 0);\n\n    /*\n     * We make some assumptions in this code based on every child\n     * view being the same size (i.e. a uniform grid). This allows\n     * us to compute the following values up front because they\n     * won't change.\n     */\n    mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);\n    mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);\n    detachAndScrapView(scrap, recycler);\n    \n    updateWindowSizing();\n    int childLeft;\n    int childTop;\n\n    /*\n     * Reset the visible and scroll positions\n     */\n    mFirstVisiblePosition = 0;\n    childLeft = childTop = 0;\n    \n    //Clear all attached views into the recycle bin\n    detachAndScrapAttachedViews(recycler);\n    //Fill the grid for the initial layout of views\n    fillGrid(DIRECTION_NONE, childLeft, childTop, recycler);\n}\n```\n\n我们会对子视图做一些记录和安排\n(为了简便，假设来自适配器的所有子视图都是一样大的)，\n确保所有已存在的视图在 scrap heap 之中。我将\n大部分工作抽象到 `fillGrid()` 这个辅助方法中以便重用。\n我们很快就会看到这个方法在更新可见视图和滚动屏幕中被大量调用。\n\n\n\n\n>就像是自定义实现一个 ViewGroup，你负责触发测量和布局每一个\n>从 Recycler 获取到的子视图。API 没有直接完成这项工作 。\n\n通常来说，在这类方法之中你需要完成的主要步骤如下：\n\n1. 在滚动事件结束后检查所有附加视图当前的偏移位置。\n2. 判断是否需要添加新视图填充由滚动屏幕产生的空白部分。并从 \n\tRecycler 中获取视图。\n3. 判断当前视图是否不再显示。移除它们并放置到 Recycler 中。\n4. 判断剩余视图是否需要整理。发生上述变化后可能\n\t需要你修改视图的子索引来更好地和它们的适配器位置校准。\n\n\n\n注意我们放进 `FixedGridLayoutManager.fillGrid()` 里填充 RecyclerView 的主要步骤。\n当到达最大行数时，这个 manager 将位置从右到左排序，封装。\n\n1. 清点目前我们所有的视图。将他们 Detach 以便稍后重新连接。\n\t\n\t```java\n\tSparseArray<View> viewCache = new SparseArray<View>(getChildCount());\n\t//...\n\tif (getChildCount() != 0) {\n\t\t//...\n\t\t//Cache all views by their existing position, before updating counts\n\t\tfor (int i=0; i < getChildCount(); i++) {\n\t\t\tint position = positionOfIndex(i);\n\t\t\tfinal View child = getChildAt(i);\n\t\t\tviewCache.put(position, child);\n\t\t}\n\t\t//Temporarily detach all views.\n\t\t// Views we still need will be added back at the proper index.\n\t\tfor (int i=0; i < viewCache.size(); i++) {\n\t\t\tdetachView(viewCache.valueAt(i));\n\t\t}\n\t}\n\t```\n\t\n2. 测量/布局每一个当前可见的子视图。重新连接已有的视图很简单；\n\t新的视图是从 Recycler 之中获取的。\n\t\n\t```java\n\tfor (int i = 0; i < getVisibleChildCount(); i++) {\n\t    //...\n\n\t    //Layout this position\n\t    View view = viewCache.get(nextPosition);\n\t    if (view == null) {\n\t        /*\n\t         * The Recycler will give us either a newly constructed view,\n\t         * or a recycled view it has on-hand. In either case, the\n\t         * view will already be fully bound to the data by the\n\t         * adapter for us.\n\t         */\n\t        view = recycler.getViewForPosition(nextPosition);\n\t        addView(view);\n\n\t        /*\n\t         * It is prudent to measure/layout each new view we\n\t         * receive from the Recycler. We don't have to do\n\t         * this for views we are just re-arranging.\n\t         */\n\t        measureChildWithMargins(view, 0, 0);\n\t        layoutDecorated(view, leftOffset, topOffset,\n\t                leftOffset + mDecoratedChildWidth,\n\t                topOffset + mDecoratedChildHeight);\n\t    } else {\n\t        //Re-attach the cached view at its new index\n\t        attachView(view);\n\t        viewCache.remove(nextPosition);\n\t    }\n\n\t    //...\n\t}\n\n\t```\n\t\n3. 最终，所有在第一步中 detach 并且没有被重新连接的视图都不可见。\n\t将它们移入 Recycler 中，以备后用。\n\t\n\t```java\n\tfor (int i=0; i < viewCache.size(); i++) {\n\t    recycler.recycleView(viewCache.valueAt(i));\n\t}\n\t```\n\n说明一下，先将所有视图 detach 之后再将需要的视图重新连接是为了\n保持每一个视图子索引的顺序 (就是  `getChildAt()` 的索引)。我们希望\n可见视图从左上到右下的索引从 `0` 开始，到 `getChildCount()-1` 结束。\n当我们上下滑动视图，新的子视图被添加，它的索引顺序会变得不可靠。\n我们需要保留正确的索引来在任意点上定位每一个视图。在一个简单地\nLayoutManager (比如 LinearLayoutManager)中，子视图可以轻松地插入\nlist 的两端， 记录层就没有存在的必要了。\n\n\n# 添加用户交互\n\n目前，我们已经有一个非常好的初始布局，但是它并不能动起来。\nRecyclerView 的关键就在于当用户浏览一组数据时动态提供视图。\n覆盖一些方法就能实现我们的目的。\n\n#### canScrollHorizontally() & canScrollVertically()\n\n这些方法很简单，在你想要滚动方向对应的方法里返回 true ，\n不想要滚动方向对应的方法里返回 false。\n\n```java\n@Override\npublic boolean canScrollVertically() {\n    //We do allow scrolling\n    return true;\n}\n```\n\n#### scrollHorizontallyBy() & scrollVerticallyBy()\n\n在这里你应该实现 content 移动的逻辑。RecyclerView 已经处理了 scrolling\n和 [flinging][flinging] (注：Fling: Gross gesture, no on-screen target)\n触摸操作，不需要处理 MotionEvents 或者 GestureDetectors 这些麻烦事。\n你只需要完成下面这三个任务：\n\n1. 将所有的子视图移动适当的位置 (对的，你得自己做这个)。\n2. 决定移动视图后 添加/移除 视图。\n3. 返回滚动的实际距离。框架会根据它判断你是否触碰到边界。\n\n在 FixedGridLayoutManager 里，这两个方法很像。这里是精简后的垂直滚动实现：\n\n```java\n@Override\npublic int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,\n        RecyclerView.State state) {\n\n    if (getChildCount() == 0) {\n        return 0;\n    }\n\n    //Take top measurements from the top-left child\n    final View topView = getChildAt(0);\n    //Take bottom measurements from the bottom-right child.\n    final View bottomView = getChildAt(getChildCount()-1);\n\n    //Optimize the case where the entire data set is too small to scroll\n    int viewSpan = getDecoratedBottom(bottomView) - getDecoratedTop(topView);\n    if (viewSpan <= getVerticalSpace()) {\n        //We cannot scroll in either direction\n        return 0;\n    }\n\n    int delta;\n    int maxRowCount = getTotalRowCount();\n    boolean topBoundReached = getFirstVisibleRow() == 0;\n    boolean bottomBoundReached = getLastVisibleRow() >= maxRowCount;\n\n    if (dy > 0) { // Contents are scrolling up\n        //Check against bottom bound\n        if (bottomBoundReached) {\n            //If we've reached the last row, enforce limits\n            int bottomOffset;\n            if (rowOfIndex(getChildCount() - 1) >= (maxRowCount - 1)) {\n                //We are truly at the bottom, determine how far\n                bottomOffset = getVerticalSpace() - getDecoratedBottom(bottomView)\n                        + getPaddingBottom();\n            } else {\n                /*\n                 * Extra space added to account for allowing bottom space in the grid.\n                 * This occurs when the overlap in the last row is not large enough to\n                 * ensure that at least one element in that row isn't fully recycled.\n                 */\n                bottomOffset = getVerticalSpace() - (getDecoratedBottom(bottomView)\n                        + mDecoratedChildHeight) + getPaddingBottom();\n            }\n            delta = Math.max(-dy, bottomOffset);\n        } else {\n            //No limits while the last row isn't visible\n            delta = -dy;\n        }\n    } else { // Contents are scrolling down\n        //Check against top bound\n        if (topBoundReached) {\n            int topOffset = -getDecoratedTop(topView) + getPaddingTop();\n            delta = Math.min(-dy, topOffset);\n        } else {\n            delta = -dy;\n        }\n    }\n\n    offsetChildrenVertical(delta);\n\n    if (dy > 0) {\n        if (getDecoratedBottom(topView) < 0 && !bottomBoundReached) {\n            fillGrid(DIRECTION_DOWN, recycler);\n         } else if (!bottomBoundReached) {\n            fillGrid(DIRECTION_NONE, recycler);\n         }\n    } else {\n        if (getDecoratedTop(topView) > 0 && !topBoundReached) {\n            fillGrid(DIRECTION_UP, recycler);\n        } else if (!topBoundReached) {\n            fillGrid(DIRECTION_NONE, recycler);\n        }\n    }\n\n    /*\n     * Return value determines if a boundary has been reached\n     * (for edge effects and flings). If returned value does not\n     * match original delta (passed in), RecyclerView will draw\n     * an edge effect.\n     */\n    return -delta;\n}\n```\n\n我们获得了滚动距离(dx/dy)的增量来验证。方法的第一部分判断\n按照所给的距离(标志给了滚动方向)滚动会不会超过边界。如果会，\n我们需要计算出视图实际滚动的距离。\n\n在这个方法里，我们需要自己手工移动这些视图。\n`offsetChildrenVertical() ` 和 `offsetChildrenHorizontal() ` \n这两个方法 可以帮助我们处理匀速移动。\n**如果你不实现它，你的视图就不会滚动**。\n移动视图操作完成后，我们触发另一个填充操作，\n根据滚动的距离替换视图。\n\n最后，将实际位移距离应用给子视图。RecyclerView 根据这个值判断是否\n绘制到达边界的效果。一般意义上，如果返回值不等于传入的值就意味着\n需要绘制边缘的发光效果了。\n**如果你返回了一个带有错误方向的值，框架的函数会把这个当做一个大的变化\n你将不能获得正确的边缘发光特效。**\n\n除了用来判断绘制边界特效外，返回值还被用来决定什么时候取消 flings。\n返回错误的值会让你失去对 content  fling 的控制。框架会认为你已经提前\n触碰到边缘并取消了 fling。\n\n#热身结束~\n\n目前，我们已经实现了基本的功能。它少了很多的细节部分，\n不过滚动和适当的视图回收已经完成了。\n关于自定义 LayoutManager 还有很多要说的东西。\n[接下来][part2]，我们会细致的介绍 decorations, data set changes\n还有实现滚动到特定位置。\n\n---\n\n\n[source]:http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/\n[author]:http://wiresareobsolete.com/\n[part2]:../issue-13/创建-RecyclerView-LayoutManager-Part-2.md\n[part3]:../issue-13/创建-RecyclerView-LayoutManager-Part-3.md\n[res-1]:http://www.grokkingandroid.com/first-glance-androids-recyclerview/\n[res-2]:http://lucasr.org/2014/07/31/the-new-twowayview/\n[res-3]:https://github.com/gabrielemariotti/RecyclerViewItemAnimators\n[demo]:https://github.com/devunwired/recyclerview-playground\n[flinging]:http://www.google.com/design/spec/patterns/gestures.html#gestures-touch-mechanics\n"
  },
  {
    "path": "issue-9/通过Jenkins并行完成UI的自动化测试.md",
    "content": "通过Jenkins并行完成UI的自动化测试\n---\n\n> * 原文链接 : [Concurrent Android UI automation with Jenkins](http://www.hidroh.com/2015/04/14/concurrent-android-ui-automation-jenkins/)\n* 原文作者 : [Ha Duy Trung](http://www.hidroh.com/)\n* [译文出自 :  开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [tiiime](https://github.com/tiiime)\n* 状态 :  完成\n\n\n\n\n![](http://www.hidroh.com/assets/img/parallel-cover.jpg)\n\n现在的 IT 公司会为了进入不同的市场开发相应的 App，来自同一家公司的 App 总会具有相似的 UI 逻辑，但 UI 的细节、风格又各有区分。随着产品的发展，抑或是在产品应用于新市场的过程中产生了旧 UI 逻辑的变种，用于测试所有 UI 逻辑的时间会与新 UI 的数量呈正比例关系增长。即使有 UI 的自动化框架可以用来减少测试所需的时间（例如 Calabash Android），使得每天只需要2个小时就能完成对 UI 的测试，但在这套 UI 衍生出四套“变种 UI”后，却要花费8个小时才能完成测试。\n\n这篇博文将利用 Continuous Integration (CI) 服务，同时在不同的设备中执行 UI 测试，并介绍一种从根源上减少这种实际场景下自动化测试所需时间的方法。此外，我们的测试都是在真机上运行，而不是虚拟机。\n\n更贴近现实的自动化测试：有很多东西只能通过真机来反映，像内存消耗，CPU 消耗：虚拟机通常都是消耗电脑的资源，而真机基本不会消耗电脑资源。\n\n而且用真机进行测试性价比较高！Android 设备都很便宜，所以你可以在需要设备进行测试的时候毫不犹豫地买买买，如果用虚拟机测试的话，由于虚拟机会消耗很多电脑资源而且拓展电脑硬件很烧钱，这无疑会限制测试设备的数量。\n\n虽说博文里同时使用了 Calabash Android 自动化框架 和 [Jenkins CI](https://jenkins-ci.org/)，但我提到的大部分方法可以通过运行在任意 CI 服务上的 [Android Debug Bridge (ADB)](https://wiki.jenkins-ci.org/display/JENKINS/Distributed+builds#Distributedbuilds-RunningMultipleSlavesontheSameMachine) 应用于其他的自动化测试框架里。\n\n## 创建 Jenkins slaves 和 slave group\n\n完成[分布式构建](https://wiki.jenkins-ci.org/display/JENKINS/Distributed+builds)的第一步就是创建 Jenkins slave，授权它管理多个“奴隶”。我们可以通过 Jenkins -> Manage Jenkins -> Manage Nodes 导航到 Jenkins slaves/nodes 的配置界面。\n\n根据定义，Jenkins slave 用于完成 Jenkins 主机分发下来的任务。在我们的使用场景中，需要完成的任务则是运行在 Android 设备上的自动化测试。为了完成这项任务，所有奴隶都可以[运行在相同的机器上](https://wiki.jenkins-ci.org/display/JENKINS/Distributed+builds#Distributedbuilds-RunningMultipleSlavesontheSameMachine)，也就是通过 USB 连接在电脑上的，具有执行 ADB 命令的 Android 设备。\n\n下面是一个与 Samsung S3 关联的奴隶的安装细节：\n\n![](http://www.hidroh.com/assets/img/parallel-slave-1.png)\n\n当所有奴隶都运行在同一台机器上时，我建议为不同的奴隶结点提供不同的系统 roots 文件，因为它们很可能会在测试结果处被存储，如果它们指向同一个 root 文件，可能会让测试结果被不同的结点重写。我们可以通过 Remote FS root 进行这样的设置。\n\n> **Remote FS root**\n\n> 每个奴隶都需要拥有 Jenkins 中的一个专用目录，我们在奴隶中为其指定绝对路径，如：'/var/jenkins' 或 'c:\\jenkins'，而且该路径应该是奴隶设备中的本地路径。但是该路径不需要对 Jenkins 可见，怎么正常怎么来就行。\n\n> 奴隶不会持有重要数据（不同于最后创建于其上的项目的活跃工作区），所以你可以将奴隶的工作区设置到一个临时目录。这样做的唯一缺点在于：你可能会在奴隶设备关机后失去最新的工作区。\n\n当 separate FS roots 和多处理器允许 Calabash 的并行操作时，ADB_DEVICE_ARG 环境变量需要用于通知 Calabash，因为 Calabash 运行的设备应该向其发送 ADB 命令，防止设备的多重连接。底层中，Calabash 通过 ADB 设备命令自动化 UI，当一个设备匹配于另一个奴隶结点，ADB_DEVICE_ARG 应该通过环境变量的配置应用到 node 结点层中，并在其后在任务层变为可用。\n\n> **环境变量**\n\n> 这些键值对将会应用于结点上的每一个构建，并重写任意全局值，所以他们能在 Jenkins 的配置中被使用（如 $key 或 ${key}），而且将会被添加到构建上启动的进程。\n \n在 Nexus 4 中设置奴隶的方式如上\n\n![](http://www.hidroh.com/assets/img/parallel-slave-2.png)\n\n如果你留心注意一些细节你会发现，所有奴隶结点都被配置了相同的 android-group 标签。这样做的目的主要是将奴隶分组，使得当我们需要使用奴隶时，方便我们调用特定分组里的所有奴隶。\n\n> 标签\n\n> 标签（AKA 标签）用于将多个奴隶分到一个逻辑组中，而每一个标签都会消耗空间。例如 'regression java6' 将为为结点分配 'regression' 和 'java6' 标签。\n\n> 举例来说吧，如果你有多个窗口奴隶，而且你要完成的任务需要窗口，那么你可以让你的所有窗口奴隶持有 'windows' 标签，然后将需要执行的任务与 'windows' 标签绑定。这使得你的任务在所有窗口奴隶中被执行，而不是被随意执行。\n\n![](http://www.hidroh.com/assets/img/parallel-slave-group.png)\n\n现在如果我们检查 android-group，我们会得到一个所有结点都带着 android-group 标签的列表，把其他设备添加到这个组里就像拷贝一个已存在的结点和更新 ADB_DEVICE_ARG 环境变量一样简单。\n\n## 使用奴隶群建立并行任务\n\n在进行了上面的配置以后，我们现在可以创建一个 Jenkins 任务使用连接到电脑的设备，通过各自的奴隶和具有 android-group 的奴隶群执行 Calabash Android。\n\n![](http://www.hidroh.com/assets/img/parallel-downstream-1.png)\n\n通过选中“如果必要的话，执行并行构建”和“限制项目运行位置”两个配置选项，使得任务可以被并行执行，主题对结点可用，仅使用标有“android-group”标签的结点。但其中的限制是：需要确保只有与已连接的设备关联的结点才能用于自动化测试，因为我们有用于完成其他任务的其他与未连接设备关联的结点。\n\n> **如果必要的话，执行并行构建**\n\n> 如果需要并行执行构建，Jenkins 会安排并并行执行多个构建（假设你有足够的执行器和传入的构建请求），这对于耗时长的构建和测试任务来说非常有用……此外，参数化构建也是非常实用的，因为每一个执行器的执行任务与其他执行器的执行任务相互独立。\n\n现在假设我们有4个自动化测试的请求和三个已连接的设备，处理完所有请求将需要两轮操作（第一轮三个设备将会被使用，剩下一个请求在队列中等待处理，当某个设备变为空闲状态则会处理该请求）。这就意味着我们可以使4个产品风格自动化测试的时间从8小时减少为4小时，如果我们再买一台或者多台设备的话，时间甚至会更少。\n\n##通过上游任务触发并行的下游任务\n\n写到这里，我们已经能够随心所欲地对我们想要的设备（连接在 USB 端口处可用的设备！）手动地触发并行的自动化测试。但是，我们为什么要手动地完成这些工作呢？我们可以用 Jenkins Parameterized Trigger Plugin 设置上游任务，通过相应的参数触发多个下游任务。\n\n例如，如果我们想要使用所有可用设备并行地测试多个应用 UI，那么我们可以设置一个这样的上游任务，并执行它：\n\n![](http://www.hidroh.com/assets/img/parallel-upstream-2.png)\n\n![](http://www.hidroh.com/assets/img/parallel-upstream-3.png)\n\n上面的配置完成后，我们可以触发三个并行的 MOBILE_TEST 下游任务（必须进行了“如果必要的话，执行并行构建”的配置），每一个下游任务将会测试一个指定的 App 风格“cherry”，“tomato”，“rasberry”，并通过一个叫作风格的参数发送到下游任务。上游任务将会被阻塞，等待所有下游任务完成任务并因此设置构建状态。\n\n我建议大家把上游任务运行在不同的奴隶结点/奴隶组中，而不是运行在和下游任务相同的奴隶结点/奴隶组中，否则它将占着设备不处理，浪费了资源\n\n![](http://www.hidroh.com/assets/img/parallel-upstream-1.png)\n\n所以从今天开始，让我们一起回收这些被淘汰的设备，让它为我们贡献最后一丝价值吧！\n"
  },
  {
    "path": "markdown简单教程.md",
    "content": "# 标题1，文章的第一句就是标题1\n\n## 这是标题2，其他小标题按照层级划分\n### 标题3\n### 标题4\n\n## 文字加粗\n**hello**\n\n## 引用\n引用的格式为: >加上一个空格，然后加上文字内容。      \n在一些注意点时，我们可以使用引用来引起读着的注意。例如 : \n\n> 注意 : 不能在子线程中更新UI。\n\n\n## 换行\n\n句尾有四个以上的空格代表换行。示例 :\n\n**四个空格换行（注意句尾的空格）**       \n这里是一行，重新换行。       \n新的一行。\n\n\n**回车换行**    \n这里是一行，重新换行。\n\n新的一行。\n\n## 超链接\n**格式1 :**      \n[链接名](地址)\n\n示例 :     \n[www.baidu.com](www.baidu.com)\n\n**格式2**      \n[链接名][唯一标识]\n[唯一标识]: url地址 \n\n这种格式一般用在某段话中链接比较多的情况，为了保持原文简洁，将链接单独提出来。   \n\n示例 :    \n[开发技术前线][devtf]\n\n[devtf]: http://www.devtf.cn \n\n## 代码\n\n```java\npublic class Name {\n\t\n}\n```\n\n适用于Android代码。需要注意的是开始处的```上面必须要有一个空行。\n\n使用于iOS方面的代码 :    \n\n```\nlet customPresentAnimationController = CustomPresentAnimationController()\n\noverride func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {\n        \n    if segue.identifier == \"showAction\" {\n        let toViewController = segue.destinationViewController as UIViewController\n        toViewController.transitioningDelegate = self\n    }\n}\n```\n\n## 显示图片\n格式 :    \n![图片名字](图片链接)\n\n示例 :    \n![p1](http://img.blog.csdn.net/20150416165133627)\n\n\n## 列表 \n列表前面也是需要一个空行，并且在标识符后面需要一个空格,比如: 1. 和* 。     \n\n这是有序列表 : \n\n1. 第一\n2. 第二\n3. 第三\n\n无序列表 : \n\n* 翻译正确\n* 中文流畅\n* 高质量\n\n另一种列表 :     \n\n- 翻译项目\n- 认真校对撒\n- 发布文章\n\n## 表格 \n格式为 :     \n\n|  \t  这是表头1\t\t| \t\t表头2 \t\t|\n|:-------------|:-------------:|\n| 第一行第一列，左对齐| 第一行第二列居中  |\n| 第二行第一列，左对齐| 第二行第二列，居中  |\n\n\nMarkdown更详细的教程请查看[Markdown简明教程](http://wowubuntu.com/markdown/)。"
  },
  {
    "path": "markdown转换教程.md",
    "content": "# markdown转换教程 #\n\nAuthor：[tmc9031](https://github.com/tmc9031)\n\n\n## HTML转markdown ##\n\n\n### WHY ###\n\n为提高翻译效率，推荐把原文的html格式先转换为markdown格式，可以简化手工整理原文的过程\n\n### INSTALL ###\n\n这里介绍一款格式转换神器 [pandoc.org](http://pandoc.org)\n\nLinux系统下的的安装\n\n    $ sudo apt-get install pandoc\n\n其它系统的详见官方 [installing](http://pandoc.org/installing.html)\n\n### HOWTO ###\n\n以下以该文章为例 [Automating Android development](https://medium.com/google-developer-experts/automating-android-development-6daca3a98396)\n\n1. 用浏览器打开另保存为aad.html，注意保存类型选择 `网页，仅 HTML`\n2. 使用pandoc工具，Linux系统下使用shell终端命令\n\n    $ pandoc aad.html -o aad.md --from=html --to=markdown\n\n3. 接下来打开aad.md去除一些头尾的多余部分即可开始翻译\n\n> 注意：\n> 建议在翻译过程中善用git保存转换出来的原文markdown\n> 另外保留中英双语方便校对，完成后再删除英文原文\n"
  },
  {
    "path": "others/Android-M中Intent的解析/readme.md",
    "content": "Android 6.0中Intent的解析\n---\n\n> * 原文链接 : [Intent Resolving in Android M](https://medium.com/google-developer-experts/intent-resolving-in-android-m-c17d39d27048#.n23z2g14e)\n* 原文作者 : [Said Tahsin Dane](https://medium.com/@tasomaniac)\n* 译文出自 : [开发技术前线 www.devtf.cn。未经允许，不得转载!](http://www.devtf.cn)\n* 译者 : [liuling07](https://github.com/liuling07) \n* 校对者: [这里校对者的github用户名](github链接)\n* 状态 :  未完成 \n\n注意了！在Android 6.0中，“隐式Intent”的解析不能像之前版本那样正常工作了。这很有可能导致你的app不能正常使用。\n\n现在让我解释一下这个意料之中的问题以及为什么它不能正常使用：\n最近，我正在开发一个小的开源项目，叫做“Open Link With”。希望不久后它能够在应用市场上架。\n\n我的这个app能够让你在其他app之间随意切换。当你给我分享一个链接的时候，我基本上可以根据这个链接查询出所有可以处理这个链接的Activity。然后我会模拟一个系统对话框让你切换app。\n\n![从已经打开的youtube的web页面切换到youtube应用](https://cdn-images-1.medium.com/max/1600/1*rW8I8aCpJ2q8fnfKH_51_g.gif)\n\n我一直都是使用下面的方法：\n\n```\nList<ResolveInfo> infos = packageManager\n        .queryIntentActivities(intent, MATCH_DEFAULT_ONLY);\n```\n\n这段代码几乎所有Android开发者都比较熟悉，并且我也相信大部分app都有用到这段代码。\n\n我的手机里有两个浏览器。“一个URL是Google+ 的Intent”期望得到一个具有3个ResolveInfo对象的列表（Google+应用以及两个浏览器）。\n\n好吧，并不是这样！\n\n欢迎来到Android 6.0！\n\nAndroid 6.0引进了应用关联。系统主要通过你的web页面来认证，并且自动使用你的app来打开这些URL，而不会向你做任何请求。或者你可以到系统设置，选择“应用程序”，然后点击一个应用，再点击“默认打开方式”，然后设置“用这个应用打开”，就可以每次都使用这个应用打开。\n\n![Android 6.0的应用默认设置页面](https://cdn-images-1.medium.com/max/800/1*MVZbYKhwu-7qnyGAFWuNsw.png)\n\n在这种情况下，queryIntentActivities方法只会给开发者返回一个只有一个Activity的列表（此例子返回的是Google+）。\n\n虽然这是在意料之中的，但是应该在文档中注明，因为它与公共API相矛盾了。\n\n我研究了一下，发现了一个MATCH_ALL标志，文档表示，它将禁用所有的系统级过滤器。\n\n```\n/**\n * Querying flag: if set and if the platform is doing any filtering of the results, then\n * the filtering will not happen. This is a synonym for saying that all results should\n * be returned.\n */\npublic static final int MATCH_ALL = 0x00020000;\n```\n\n这对我来说没什么用。我打开源码（至少我有源码）并开始研究这个方法。\n\n它似乎优先考虑验证应用程序的域，不仅在它的内部系统，在公共API中也是如此。\n\n如果有一个验证应用程序的域，它不会返回任何其他东西。MATCH_ALL标志会移除一些系统过滤器，但是仅仅是在没有验证程序的情况下。\n\n对于这个问题，我找不到任何可变通的措施。它只是排除浏览器应用，即使他们的IntentFilters匹配。\n\n之所以没有可变通的措施，是因为他是一个内部组件（我们无法访问），Android SDK通过IPC使用AIDL与它进行通信。\n\n大部分开发者使用这个方法来判断是否至少有一个Activity来处理隐式的Intent。在大多数情况下，列表中第一项就是你想要的。\n\n在花了几个小时搞明白到底发生了什么之后，我尝试寻找一个我认为每个人都应该知道的解决方案。\n\n在Android 6.0中，改动的地方很多。实际上谷歌提供了一些改变清单，在清单中你能看到到底有哪些改变。我认为还有很多类似上面的一些没有在清单中列出的改变，而这些改动很有可能导致你的应用无法正常运行。\n\n所以如果你使用PackageManager的方法，你一定得小心，并且认真检查。\n\n感谢此文的校对者：[Yağmur Dalman](https://twitter.com/yagmurdalman)、[ Sebastiano Poggi](https://medium.com/u/9706138c9bfb)、[Salim KAYABAŞI](https://medium.com/u/73761c65c602)、[Hasan Keklik](https://medium.com/u/24a0490cd588)\n\n\n"
  },
  {
    "path": "others/FaceBook推出的Android图片加载库-Fresco/readme.md",
    "content": "FaceBook推出的Android图片加载库-Fresco\n---\n\n>\n* 原文链接:[Introducing Fresco: A new image library for Android](https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/)\n* 作者 :  [tyrone Nicholas ](https://www.facebook.com/tyrone.nicholas)\n* 译者 :  [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang) \n* 校对者: [Chaossss](https://github.com/chaossss)\n* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)\n* 校对者: [BillionWang](https://github.com/BillionWang)\n* 状态 :  完成\n\n在Android设备上面，快速高效的显示图片是极为重要的。过去的几年里，我们在如何高效的存储图像这方面遇到了很多问题。图片太大，但是手机的内存却很小。每一个像素的R、G、B和alpha通道总共要占用4byte的空间。如果手机的屏幕是480*800,那么一张屏幕大小的图片就要占用1.5M的内存。手机的内存通常很小，特别是Android设备还要给各个应用分配内存。在某些设备上，分给Facebook App的内存仅仅有16MB。一张图片就要占据其内存的十分之一。\n\n当你的App内存溢出会发生什么呢？它当然会崩溃！我们开发了一个库来解决这个问题，我们叫它Fresco。它可以管理使用到的图片和内存，从此App不再崩溃。\n\n##内存区\n为了理解Facebook到底做了什么工作，在此之前我们需要了解在Android可以使用的堆内存之间的区别。Android中每个App的Java堆内存大小都是被严格的限制的。每个对象都是使用Java的new在堆内存实例化，这是内存中相对安全的一块区域。内存有垃圾回收机制，所以当App不在使用内存的时候，系统就会自动把这块内存回收。\n\n不幸的是，内存进行垃圾回收的过程正是问题所在。当内存进行垃圾回收时，内存不仅仅进行了垃圾回收，还把 Android 应用完全终止了。这也是用户在使用 App 时最常见的卡顿或短暂假死的原因之一。这会让正在使用 App 的用户非常郁闷，然后他们可能会焦躁地滑动屏幕或者点击按钮，但 App 唯一的响应就是：在 App 恢复正常之前，请求用户耐心等待\n\n相比之下，Native堆是由C++程序的new进行分配的。在Native堆里面有更多可用内存，App只被设备的物理可用内存限制，而且没有垃圾回收机制或其他东西拖后腿。但是c++程序员必须自己回收所分配的每一块内存，否则就会造成内存泄露，最终导致程序崩溃。\n\nAndroid有另外一种内存区域，叫做Ashmem。它操作起来更像Native堆，但是也有额外的系统调用。Android 在操作 Ashmem 堆时，会把该堆中存有数据的内存区域从 Ashmem 堆中抽取出来，而不是把它释放掉，这是一种弱内存释放模式；被抽取出来的这部分内存只有当系统真正需要更多的内存时（系统内存不够用）才会被释放。当 Android 把被抽取出来的这部分内存放回 Ashmem 堆，只要被抽取的内存空间没有被释放，之前的数据就会恢复到相应的位置。\n\n##可消除的Bitmap\nAshmem不能被Java应用直接处理，但是也有一些例外，图片就是其中之一。当你创建一张没有经过压缩的Bitmap的时候，Android的API允许你指定是否是可清除的。\n\n```\nBitmapFactory.Options = new BitmapFactory.Options();\noptions.inPurgeable = true;\nBitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);\n```\n经过上面的代码处理后，可清除的Bitmap会驻留在 Ashmem 堆中。不管发生什么，垃圾回收器都不会自动回收这些 Bitmap。当 Android 绘制系统在渲染这些图片，Android 的系统库就会把这些 Bitmap 从 Ashmem 堆中抽取出来，而当渲染结束后，这些 Bitmap 又会被放回到原来的位置。如果一个被抽取的图片需要再绘制一次，系统仅仅需要把它再解码一次，这个操作非常迅速。\n\n这听起来像一个完美的解决方案，但是问题是Bitmap解码的操作是运行在UI线程的。Bitmap解码是非常消耗CPU资源的，当消耗过大时会引起UI阻塞。因为这个原因，所以Google不推荐使用这个[特性](http://developer.android.com/intl/zh-cn/reference/android/graphics/BitmapFactory.Options.html#inPurgeable)。现在它们推荐使用另外一个特性——inBitmap。但是这个特性直到Android3.0之后才被支持。即使是这样，这个特性也不是非常有用，除非 App 里的所有图片大小都相同，这对Fackbook来说显然是不适用的。一直到4.4版本，这个限制才被移除了。但我们需要的是能够运行在 Android 2.3 - 最新版本中的通用解决方案。\n\n##自力更生\n对于上面提到的“解码操作致使 UI 假死”的问题，我们找到了一种同时使 UI 显示和内存管理都表现良好的解决方法。如果我们在 UI 线程进行渲染之前把被抽取的内存区域放回到原来的位置，并确保它再也不会被抽取，那我们就可以把这些图片放在 Ashmem 里，同时不会出现 UI 假死的问题。幸运的是，Android 的 NDK 中有一个函数可以完美地实现这个需求，名字叫做 AndroidBitmap_lockPixels。这个函数最初的目的就是：在调用 unlockPixels 再次抽取内存区域后被执行。\n\n当我们意识到我们没有必要这样做的时候，我们取得了突破。如果我们只调用lockPixels而不调用对应的unlockPixels，那么我们就可以在Java的堆内存里面创建一个内存安全的图像，并且不会导致UI线程加载缓慢。只需要几行c++代码，我们就完美的解决了这个问题。\n\n##用C++的思想写Java代码\n就像《蜘蛛侠》里面说的：“能力越强，责任越大。”可清除的 Bitmap 既不会被垃圾回收器回收，也不会被 Ashmem 内置的清除机制处理，这使得使用它们可能会造成内存泄露。所以我们只能靠自己啦。\n\n在c++中,通常的解决方案是建立智能指针类,实现引用计数。这些需要利用到c++的语言特性——拷贝构造函数、赋值操作符和确定的析构函数。这种语法在Java之中不存在，因为垃圾回收器能够处理这一切。所以我们必须以某种方式在Java中实现C++的这些保证机制。\n\n我们创建了两个类去完成这件事。其中一个叫做“SharedReference”，它有addReference和deleteReference两个方法，调用者调用时必须采取基类对象或让它在范围之外。一旦引用计数器归零，资源处理(Bitmap.recycle)就会发生。\n\n然而，很显然，让Java开发者去调用这些方法是很容易出错的。Java语言就是为了避免做这样的事情的！所以SharedReference之上,我们构建了CloseableReference类。它不仅实现了Java的Closeable接口,而且也实现了Cloneable接口。它的构造器和clone()方法会调用addReference()，而close()方法会调用deleteReference()。所以Java开发者需要遵守下面两条简单的的规则：\n\n1. 在分配CloseableReference新对象的时候,调用.clone()。\n2. 在超出作用域范围的时候，调用.close()，这通常是在finally代码块中。\n\n这些规则可以有效地防止内存泄漏,并让我们在像Fackbook的Android客户端这种大型的Java程序中享受Native内存管理和通信。\n\n##不仅仅是加载程序，它是一个管道\n在移动设备上显示图片需要很多的步骤：\n![](http://i2.tietuku.com/4480c88a0d8004bf.png)\n几个优秀的开源库都是按照这个顺序执行的，比如 Picasso,Universal Image Loader,Glide和 Volley等等。上面这些开源库为Android的发展做出了非常重要的贡献。我们相信Fresco在几个重要方面会表现的更好。\n\n我们的不同之处在于把上面的这些步骤看作是管道，而不仅仅是加载器。每一个步骤和其他方面应该是尽可能独立的，把数据和参数传递进去，然后产生一个输出，就这么简单。它应该可以做一些操作，不管是并行还是串行。一些操作只能在特性条件下才能执行。一些有特殊要求的在线程上执行。除此之外，当我们考虑改进图像的时候，所有的图片就会变得非常复杂。很多人在低网速情况下使用Facebook，我们想要这些人能够尽快的看到图片，甚至经常是在图片没有完全下载完之前。\n\n##不要烦恼，拥抱stream\n在Java中，异步代码历来都是通过Future机制来执行的。在另外的线程里面代码被提交执行，然后一个类似Future的对象可以检查执行的结果是不是已经完成了。但是，这只在假设只有一种结果的情况下行得通。在处理渐进的图像的时候，我们希望可以完整而且连续的显示结果。\n\n我们的解决方式是定义一个更广义的Future版本，叫做DataSource。它提供了一个订阅方法，调用者必须传入一个DataSubscriber和Executor。DataSubscriber可以从DataSource获取到处理中和处理完毕的结果，并且提供了很简单的方法来区分。因为我们需要非常频繁的处理这些对象，所以必须有一个明确的close调用，幸运的是，DataSource本身就是Closeable。\n\n在后台，每一个箱子上面都实现了一个叫做“生产者/消费者”的新框架。在这个问题是，我们是从[ReactiveX](http://reactivex.io/)获取的灵感。我们的系统拥有和[RxJava](https://github.com/ReactiveX/RxJava)相似的接口，但是更加适合移动设备，并且有内置的对Closeables的支持。\n\n保持简单的接口。Producer只有一个叫做produceResults的方法，这个方法需要一个Consumer对象。反过来，Consumer有一个onNewResult方法。\n\n我们使用像这样的系统把Producer联系起来。假设我们有一个producer的工作是把类型I转化为类型O，那么它看起来应该是这个样子：\n\n```\npublic class OutputProducer<I, O> implements Producer<O> {\n\n  private final Producer<I> mInputProducer;\n\n  public OutputProducer(Producer<I> inputProducer) {\n    this.mInputProducer = inputProducer;\n  }\n\n  public void produceResults(Consumer<O> outputConsumer, ProducerContext context) {\n    Consumer<I> inputConsumer = new InputConsumer(outputConsumer);\n    mInputProducer.produceResults(inputConsumer, context);\n  }\n\n  private static class InputConsumer implements Consumer<I> {\n    private final Consumer<O> mOutputConsumer;\n\n    public InputConsumer(Consumer<O> outputConsumer) {\n      mOutputConsumer = outputConsumer;\n    }\n\n    public void onNewResult(I newResult, boolean isLast) {\n      O output = doActualWork(newResult);\n      mOutputConsumer.onNewResult(output, isLast);      \n    }\n  }\n}\n```\n\n这可以使我们把非常复杂的步骤串起来，同时也可以保持他们逻辑的独立性。\n\n##动画全覆盖\n使用Facebook的人都非常喜欢Stickers，因为它可以以动画形式存储GIF和Web格式。如果支持这些格式，就需要面临新的挑战。因为每一个动画都是由不止一张图片组成的，你需要解码每一张图片，存储在内存里，然后显示出来。对于大一点的动画，把每一帧图片放在内存是不可行的。\n\n我们建立了AnimatedDrawable,一个强大的可以呈现动画的Drawable,同时支持GIF和WebP格式。AnimatedDrawable实现标准的Android Animatable接口,所以调用者可以随意的启动或者停止动画。为了优化内存使用，如果图片足够小的时候，我们就在内存里面缓存这些图片，但是如果太大，我们可以迅速的解码这些图片。这些行为调用者是完全可控的。\n\n所有的后台都用c++代码实现。我们保持一份解码数据和元数据解析,如宽度和高度。我们引用技术数据，它允许多个Java端的Drawables同时访问一个WebP图像。\n\n##如何去爱你？我来告诉你...\n当一张图片从网络上下载下来之后，我们想显示一张占位图。如果下载失败了，我们就会显示一个错误标志。当图片加载完之后，我们有一个渐变动画。通过使用硬件加速，我们可以按比例放缩，或者是矩阵变换成我们想要的大小然后渲染。我们不总是按照图片的中心进行放缩，那么我们可以自己定义放缩的聚焦点。有些时候，我们想显示圆角甚至是圆形的图片。所有的这些操作都应该是迅速而平滑的。\n\n我们之前的实现是使用Android的View对象——时机到了，可以使用ImageView替换出占位的View。这个操作是非常慢的。改变View会让Android强制刷新整个布局，当用户滑动的时候，这绝对不是你想看到的效果。比较明智的做法是使用Android的Drawables,它可以迅速的被替换。\n\n所以我们创建了Drawee。这是一个像MVC架构的图片显示框架。该模型被称为DraweeHierarchy。它被实现为Drawables的一个层，对于底层的图像而言，每一个曾都有特定的功能——成像、层叠、渐变或者是放缩。\n\nDraweeControllers通过管道的方式连接到图像上——或者是其他的图片加载库——并且处理后台的图片操作。他们从管道接收事件并决定如何处理他们。他们控制DraweeHierarchy实际上的操作——无论是占位图片,错误条件或是完成的图片。\n\nDraweeViews 的功能不多,但都是至关重要的。他们监听Android的View不再显示在屏幕上的系统事件。当图片离开屏幕的时候,DraweeView可以告诉DraweeController关闭使用的图像资源。这可以避免内存泄露。此外,如果它已经不在屏幕范围内的话，控制器会告诉图片管道取消网络请求。因此，像Fackbook那样滚动一长串的图片的时候，不会频繁的网络请求。\n\n通过这些努力，显示图片的辛苦操作一去不复返了。调用代码只需要实例化一个DraweeView,然后指定一个URI和其他可选的参数就可以了。剩下的一切都会自动完成。开发人员不需要担心管理图像内存，或更新图像流。Fresco为他们把一切都做了。\n\n##Fresco\n完成这个图像显示和操作复杂的工具库之后,我们想要把它分享到Android开发者社区。我们很高兴的宣布，从今天起，这个项目已经作为[开源代码](http://github.com/facebook/fresco)了！\n\n壁画是绘画技术,几个世纪以来一直受到世界各地人们的欢迎。我们许多伟大的艺术家使用这种名字,从意大利文艺复兴时期的大师拉斐尔到壁画艺术家斯里兰卡。我们并不是假装达到这个伟大的水平，我们真的希望Android开发者能像我们当初享受创建这个开源库的过程一样，非常享受的使用它。\n\n##更多\n[Fresco中文文档](http://fresco-cn.org/)\n"
  },
  {
    "path": "others/Google推荐的图片加载库Glide介绍/readme.md",
    "content": "Google推荐的图片加载库Glide介绍\n---\n\n>\n* 原文链接:[Google推荐的图片加载库Glide介绍](http://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en)\n* 作者 : [nuuneoi](http://inthecheesefactory.com/)\n* 译者 :  [jianghejie](https://github.com/jianghejie) \n* 校对者 : [chaossss](https://github.com/chaossss)\n* 状态 : 完成\n \n\n在泰国举行的谷歌开发者论坛上，谷歌为我们介绍了一个名叫 [Glide](https://github.com/bumptech/glide) 的图片加载库，作者是bumptech。这个库被广泛的运用在google的开源项目中，包括2014年google I/O大会上发布的官方app。\n\n毫无疑问，这个库引起了我的兴趣。于是我花了一个晚上研究和把玩它，将它的实现原理分析清楚以后，我决定写一篇博文分享一些自己的经验。在开始之前我想说，Glide和Picasso有90%的相似度，准确的说，我觉得它就像 Picasso 的克隆体。\n\n不管怎样，Glide 和 Picasso在细节上还是有不少区别的，接下来我会让你们了解到其中的差异。\n\n##导入库\n\nPicasso和Glide都在jcenter上。在项目中添加依赖非常简单：\nPicasso\n```gradle\ndependencies {\n    compile 'com.squareup.picasso:picasso:2.5.1'\n}\n```\nGlide\n```gradle\ndependencies {\n    compile 'com.github.bumptech.glide:glide:3.5.2'\n    compile 'com.android.support:support-v4:22.0.0'\n}\n```\n不管怎样，Glide 需要 Android Support Library v4 包，千万不要忘了像上面的代码做的那样添加 Android Support Library v4 包的依赖。不过这都不是什么大问题，因为现在 Android Support Library v4 基本是每一个新 Android 项目的标配了。\n\n##基础\n\n就如我所说的Glide和Picasso非常相似，Glide加载图片的方法和Picasso如出一辙。\n\nPicasso\n```java\nPicasso.with(context)\n    .load(\"http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg\")\n    .into(ivImg);\n```\nGlide\n\n```java\nGlide.with(context)\n    .load(\"http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg\")\n    .into(ivImg);\n```\n虽然两者看起来非常相似，但是 Glide 的代码无疑设计得更好，因为 Glide 的 with() 方法不光接受 Context，还接受 Activity 和 Fragment。此外，with() 方法还能自动地从你放入的各种东西里面提取出 Context，供它自己使用。\n\n![](http://jcodecraeer.com/uploads/20150327/1427445293711143.png)\n\n同 时将Activity/Fragment作为with()参数的好处是：图片加载会和Activity/Fragment的生命周期保持一致，比如 Paused状态在暂停加载，在Resumed的时候又自动重新加载。所以我建议传参的时候传递Activity 和 Fragment给Glide，而不是Context。\n\n##默认Bitmap格式是RGB_565\n\n下面是加载图片时和Picasso的比较（1920x1080 像素的图片加载到768x432的ImageView中）\n\n![](http://jcodecraeer.com/uploads/20150327/1427445293137409.jpg)\n\n可以看到Glide加载的图片质量要差于Picasso（ps：我看不出来哈），为什么？这是因为Glide默认的Bitmap格式是RGB_565 ，比ARGB_8888格式的内存开销要小一半。下面是Picasso在ARGB8888下与Glide在RGB565下的内存开销图（应用自身占用了8m，因此以8为基准线比较）：\n\n![](http://jcodecraeer.com/uploads/20150327/1427445293965030.png)\n\n如果你觉得 Glide 在默认的 RGB_565 格式下加载的图片质量可以接受的话，可以什么都不做。但如果你觉得难以接受，或者是你的实际需求对图片的质量有更高的要求的话，你可以像下面的代码那样创建一个 GlideModule 子类，把 Bitmap 的格式转换到 ARGB_8888：\n\n```java\n public class GlideConfiguration implements GlideModule {\n  \n    @Override\n    public void applyOptions(Context context, GlideBuilder builder) {\n        // Apply options to the builder here.\n        builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);\n    }\n  \n    @Override\n    public void registerComponents(Context context, Glide glide) {\n        // register ModelLoaders here.\n    }\n}\n```\n然后在AndroidManifest.xml中将GlideModule定义为meta-data\n\n```java\n<meta-data android:name=\"com.inthecheesefactory.lab.glidepicasso.GlideConfiguration\"\n            android:value=\"GlideModule\"/>\n```\n![](http://jcodecraeer.com/uploads/20150327/1427445294447874.jpg)\n\n这样看起来就会好很多。\n\n我们再来看看内存开销图，虽然看起来这次 Glide 的内存开销接近于上次的两倍，但是Picasso的内存开销仍然远大于Glide。\n\n![](http://jcodecraeer.com/uploads/20150327/1427445294918728.png)\n\n但是上面那样做的问题在于你需要手动计算 ImageView 的尺寸，又或者是你对 ImageView 设置了具体的尺寸大小，为了解决这样的麻烦，你可以在 Picasso 中通过这样做简化你的代码：\n\n```java\nPicasso.with(this)\n    .load(\"http://nuuneoi.com/uploads/source/playstore/cover.jpg\")\n    .resize(768, 432)\n    .into(ivImgPicasso);\n```\n但是问题在于你需要主动计算ImageView的大小，或者说你的ImageView大小是具体的值（而不是wrap_content），你也可以这样：\n\n```java\nPicasso.with(this)\n    .load(\"http://nuuneoi.com/uploads/source/playstore/cover.jpg\")\n    .fit()\n    .centerCrop()\n    .into(ivImgPicasso);\n```\n现在Picasso的内存开销就和Glide差不多了。\n\n![](http://jcodecraeer.com/uploads/20150327/1427445294433243.png)\n\n虽然内存开销差距不大，但是在这个问题上Glide完胜Picasso。因为Glide可以自动计算出任意情况下的ImageView大小。\n##Image质量的细节\n\n这是将ImageView还原到真实大小时的比较。\n![](http://jcodecraeer.com/uploads/20150327/1427445294704430.png)\n\n很显然，Glide 加载的图片有些像素点变得很模糊，看起来也没有 Picasso 那么平滑。而且直到现在，我也没有找到一个可以直观改变图片大小调整算法的方法。\n\n但是这并不算什么坏事，因为很难察觉。\n\n\n##磁盘缓存\n\nPicasso和Glide在磁盘缓存策略上有很大的不同。我们刚刚做了一个使用 Glide 和 Picasso 加载同一张高清图片的实验，我在实验后检查缓存目录时发现： Glide 缓存的图片和 ImageView 的尺寸相同，而 Picasso 缓存的图片和原始图片的尺寸相同。\n\n![](http://jcodecraeer.com/uploads/20150327/1427445294110987.jpg)\n\n上面提到的平滑度的问题依然存在，而且如果加载的是RGB565图片，那么缓存中的图片也是RGB565。\n\n我 尝试将ImageView调整成不同大小，但不管大小如何Picasso只缓存一个全尺寸的。Glide则不同，它会为每种大小的ImageView缓存 一次。尽管一张图片已经缓存了一次，但是假如你要在另外一个地方再次以不同尺寸显示，需要重新下载，调整成新尺寸的大小，然后将这个尺寸的也缓存起来。\n\n具体说来就是：假如在第一个页面有一个200x200的ImageView，在第二个页面有一个100x100的ImageView，这两个ImageView本来是要显示同一张图片，却需要下载两次。\n\n不过，你可以改变这种行为，让Glide既缓存全尺寸又缓存其他尺寸：\n\n```java\nGlide.with(this)\n     .load(\"http://nuuneoi.com/uploads/source/playstore/cover.jpg\")\n     .diskCacheStrategy(DiskCacheStrategy.ALL)\n     .into(ivImgGlide);\n```\n下次在任何ImageView中加载图片的时候，全尺寸的图片将从缓存中取出，重新调整大小，然后缓存。\n\nGlide的这种方式优点是加载显示非常快。而Picasso的方式则因为需要在显示之前重新调整大小而导致一些延迟，即便你添加了这段代码来让其立即显示：\n```java\n//Picasso\n.noFade();\n```\n![](http://jcodecraeer.com/uploads/allimg/150327/163Aa632-0.gif)\n\nPicasso 和 Glide 在磁盘缓存策略上各有所长，你应该根据自己的需求选择最合适的。\n\n对我而言，我更喜欢Glide，因为它远比Picasso快，虽然需要更大的空间来缓存。\n##特性\n\n你可以做到几乎和Picasso一样多的事情，代码也几乎一样。\n\nImage Resizing\n```java\n// Picasso\n.resize(300, 200);\n  \n// Glide\n.override(300, 200);\n```\n\nCenter Cropping\n```java\n// Picasso\n.centerCrop();\n  \n// Glide\n.centerCrop();\n```\n\nTransforming\n```java\n// Picasso\n.transform(new CircleTransform())\n  \n// Glide\n.transform(new CircleTransform(context))\n```\n设置占位图或者加载错误图：\n```java\n// Picasso\n.placeholder(R.drawable.placeholder)\n.error(R.drawable.imagenotfound)\n  \n// Glide\n.placeholder(R.drawable.placeholder)\n.error(R.drawable.imagenotfound)\n```\n\n正如我在博文开头所说，如果你已经熟悉如何使用 Picasso，那从Picasso转换到Glide对你来说就是小菜一碟。\n\n \n##有什么Glide可以做而Picasso 做不到\n\nGlide可以加载GIF动态图，而Picasso不能。\n\n![](http://jcodecraeer.com/uploads/20150327/1427445366503084.gif)\n\n\n同时因为Glide和Activity/Fragment的生命周期是一致的，因此gif的动画也会自动的随着Activity/Fragment的状态暂停、重放。Glide 的缓存在gif这里也是一样，调整大小然后缓存。\n\n但是从我的一次测试结果来看，用 Glide 显示动画会消耗很多内存，因此谨慎使用。\n\n除了gif动画之外，Glide还可以将任意本地视频解码成一张静态图片。\n\n还有一个特性是你可以配置图片显示的动画，而Picasso只有一种动画：fading in。\n\n最后一个是可以使用thumbnail()产生一个你所加载图片的thumbnail。\n\n其实还有一些特性，不过不是非常重要，比如将图像转换成字节数组等。\n配置\n\n有许多可以配置的选项，比如大小，缓存的磁盘位置，最大缓存空间，位图格式等等。可以在这个页面查看这些配置 Configuration 。\n\n##库的大小\n\nPicasso (v2.5.1)的大小约118kb，而Glide (v3.5.2)的大小约430kb。\n\n![](http://jcodecraeer.com/uploads/20150327/1427453389115686.png)\n\n不过312kb的差距并不是很重要。\n\nPicasso和Glide的方法个数分别是840和2678个。\n\n![](http://jcodecraeer.com/uploads/20150327/1427453390188737.png)\n\n必须指出，对于DEX文件65535个方法的限制来说，2678是一个相当大的数字了。建议在使用Glide的时候开启ProGuard。\n\n\n##总结\n\nGlide和Picasso都是非常完美的库。Glide加载图像以及磁盘缓存的方式都要优于Picasso，速度更快，并且Glide更有利于减少OutOfMemoryError的发生，GIF动画是Glide的杀手锏。不过Picasso的图片质量更高。你更喜欢哪个呢？\n\n虽然我使用了很长时间的Picasso，但是我得承认现在我更喜欢Glide。我的建议是使用Glide，但是将Bitmap格式换成 ARGB_8888、让Glide缓存同时缓存全尺寸和改变尺寸两种。\n\n \n##相关资源\n\n- [Glide 3.0: a media management library for Android](http://google-opensource.blogspot.com/2014/09/glide-30-media-management-library-for.html) \n- [Glide Wiki](https://github.com/bumptech/glide/wiki) \n- [Android Picasso vs Glide](http://pluu.github.io/android%20study/2015/01/15/android-glide-picasso/) \n- [Android: Image loading libraries Picasso vs Glide](http://vardhan-justlikethat.blogspot.com/2014/09/android-image-loading-libraries-picasso.html) \n\n"
  },
  {
    "path": "others/InstaMaterial概念设计系列/实现Instagram的Material Design概念设计/readme.md",
    "content": "实现Instagram的Material Design概念设计\n---\n\n>\n* 原文链接:[Instagram with Material Design concept is getting real ](http://frogermcs.github.io/Instagram-with-Material-Design-concept-is-getting-real/)\n* 译者 :  [jianghejie](https://github.com/jianghejie) \n* 译者博文链接 :  [jcodecraeer.com](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0204/2415.html)\n \n几个月前（这篇文章的日期是2014 年11月10日），google发布了app和web应用的Material Design设计准则之后，设计师Emmanuel Pacamalan在youtube上发布了一则概念视频，演示了Instagram如果做成Material风格会是什么样子：\n\n视频地址在这里 [http://v.youku.com/v_show/id_XODg2NDQ1NDQ4.html](http://v.youku.com/v_show/id_XODg2NDQ1NDQ4.html) ps:markdown不支持播放优酷视频\n\n这 仅仅是停留在图像上的设计，是美好的愿景，估计很多人都会问，能否使用相对简单的办法将它实现出来呢？答案是：yes，不仅仅能实现，而且无须要求在 Lillipop版本，实际上几年前4.0发布之后我们就可以实现这些效果了。ps 读到这里我们应该反思这几年开发者是不是都吃屎去了。\n\n鉴于这个原因，我决定开始撰写一个新的课题-如何将[INSTAGRAM with Material Design](https://www.youtube.com/watch?v=ojwdmgmdR_Q) 视频中的效果转变成现实。当然，我们并不是真的要做一个Instagram应用，只是将界面做出来而已，并且尽量减少一些不必要的细节。\n##开始\n\n本文将要实现的是视频中前7秒钟的效果。我觉得对于第一次尝试来说已经足够了。我想要提醒诸位的是，里面的实现方法不仅仅是能实现，也是我个人最喜欢的实现方式。还有，我不是一个美工，因此项目中的所有图片是直接从网上公开的渠道获取的。（主要是从   [resources page](http://www.google.com/design/spec/resources/sticker-sheets-icons.html) ）。\n\n好了，下面是最终效果的两组截图和视频（很短的视频，就是那7秒钟的效果，可以在上面的视频中看到，这里因为没法直接引用youtube的视频就略了）（分别从Android 4 和5上获得的）：\n\n![](http://jcodecraeer.com/uploads/20150204/1423058817102389.png)\n\n![](http://jcodecraeer.com/uploads/20150204/1423059082492283.png)\n\n\n##准备\n在我们的项目中，将使用一些热门的android开发工具和库。并不是所有这些东西本篇文章都会用到，我只是将它们准备好以备不时之需。\n初始化项目\n\n首先我们需要创建一个新的android项目。我使用的是Android Studio和gradle的build方式。最低版本的sdk是15（即Android 4.0.4）。然后我们将添加一些依赖。没什么好讲的，下面是build.gradle以及app/build.gradle文件的代码：\n\nbuild.gradle\n```gradle\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:0.14.0'\n        classpath 'com.jakewharton.hugo:hugo-plugin:1.1.+'\n    }\n}\n  \nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n```\napp/build.gradle\n```gradle\napply plugin: 'com.android.application'\napply plugin: 'hugo'\n  \nandroid {\n    compileSdkVersion 21\n    buildToolsVersion \"21.1\"\n      \n    defaultConfig {\n        applicationId \"io.github.froger.instamaterial\"\n        minSdkVersion 15\n        targetSdkVersion 21\n        versionCode 1\n        versionName \"1.0\"\n    }\n}\n  \ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n      \n    compile \"com.android.support:appcompat-v7:21.0.0\"\n    compile 'com.android.support:support-v13:21.+'\n    compile 'com.android.support:support-v4:21.+'\n    compile 'com.android.support:palette-v7:+'\n    compile 'com.android.support:recyclerview-v7:+'\n    compile 'com.android.support:cardview-v7:21.0.+'\n    compile 'com.jakewharton:butterknife:5.1.2'\n    compile 'com.jakewharton.timber:timber:2.5.0'\n    compile 'com.facebook.rebound:rebound:0.3.6'\n}\n```\n\n简而言之，我们有如下工具：\n\n一些兼容包（CardView, RecyclerView, Palette, AppCompat）-我喜欢使用最新的控件。当然你完全可以使用ListView Actionbar甚至View/FrameView来替代，但是为什么要这么折腾？😉\n\n[ButterKnife](http://jakewharton.github.io/butterknife/) - view注入工具简化我们的代码。（比方说不再需要写findViewById()来引用view，以及一些更强大的功能）。\n\n[Rebound](http://facebook.github.io/rebound/)  - 我们目前还没有用到，但是我以后肯定会用它。这个facebook开发的动画库可以让你的动画效果看起来更自然。\n\n[Timber](https://github.com/JakeWharton/timber)  和 [Hugo](https://github.com/JakeWharton/hugo)   - 对这个项目而言并不是必须，我仅仅是用他们打印log。\n\n##图片资源\n\n本项目中将使用到一些Material Design的图标资源。App 图标来自于 [NSTAGRAM with Material Design](https://www.youtube.com/watch?v=ojwdmgmdR_Q) 视频，[这里](https://github.com/frogermcs/frogermcs.github.io/raw/master/files/2/resources.zip)  是项目的全套资源。\n##样式\n\n我们从定义app的默认样式开始。同时为Android 4和5定义Material Desing样式的最简单的方式是直接继承Theme.AppCompat.NoActionBar 或者 Theme.AppCompat.Light.NoActionBar主题。为什么是NoActionBar？因为新的sdk中为我们提供了实现Actionbar功能的新模式。本例中我们将使用Toolbar控件，基于这个原因-Toolbar是ActionBar更好更灵活的解决方案。我们不会深入讲解这个问题，但你可以去阅读android开发者博客[AppCompat v21](http://android-developers.blogspot.com/2014/10/appcompat-v21-material-design-for-pre.html)  。\n\n\n根据概念视频中的效果，我们在AppTheme中定义了三个基本颜色（基色调）：\n\nstyles.xml\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- styles.xml-->\n<resources>\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"colorPrimary\">@color/style_color_primary</item>\n        <item name=\"colorPrimaryDark\">@color/style_color_primary_dark</item>\n        <item name=\"colorAccent\">@color/style_color_accent</item>\n    </style>\n</resources>\n```\ncolors1.xml\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--colors.xml-->\n<resources>\n    <color name=\"style_color_primary\">#2d5d82</color>\n    <color name=\"style_color_primary_dark\">#21425d</color>\n    <color name=\"style_color_accent\">#01bcd5</color>\n</resources>\n```\n关于这三个颜色的意义，你可以在这里找到Material [Theme Color Palette documentation](http://developer.android.com/training/material/theme.html#ColorPalette) 。 \n##布局\n项目目前主要使用了3个主要的布局元素\n\n    *Toolbar* - 包含导航图标和applogo的顶部bar\n\n    *TRecyclerView*T - 用于显示feed\n\n    *TFloating Action Button* - 一个实现了Material Design中[action button pattern](http://www.google.com/design/spec/components/buttons.html#buttons-flat-raised-buttons)的ImageButton。\n\n在开始实现布局之前，我们先在res/values/dimens.xml文件中定义一些默认值：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--dimens.xml-->\n<resources>\n    <dimen name=\"btn_fab_size\">56dp</dimen>\n    <dimen name=\"btn_fab_margins\">16dp</dimen>\n    <dimen name=\"default_elevation\">8dp</dimen>\n</resources>\n```\n这些值的大小是基于Material Design设计准则中的介绍。\n\n现在我们来实现MainActivity中的layout：\n\nactivity_main.xml\n```xml\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n  \n    <android.support.v7.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:background=\"?attr/colorPrimary\">\n      \n        <ImageView\n            android:id=\"@+id/ivLogo\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"center\"\n            android:scaleType=\"center\"\n            android:src=\"@drawable/img_toolbar_logo\" />\n    </android.support.v7.widget.Toolbar>\n      \n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/rvFeed\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/toolbar\"\n        android:scrollbars=\"none\" />\n      \n    <ImageButton\n        android:id=\"@+id/btnCreate\"\n        android:layout_width=\"@dimen/btn_fab_size\"\n        android:layout_height=\"@dimen/btn_fab_size\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginBottom=\"@dimen/btn_fab_margins\"\n        android:layout_marginRight=\"@dimen/btn_fab_margins\"\n        android:background=\"@drawable/btn_fab_default\"\n        android:elevation=\"@dimen/default_elevation\"\n        android:src=\"@drawable/ic_instagram_white\"\n        android:textSize=\"28sp\" />\n  \n</RelativeLayout>\n```\n以上代码的解释:\n\n    关于Toolbar最重要的特征是他现在是activity layout的一部分，而且继承自ViewGroup，因此我们可以在里面放一些UI元素（它们将利用剩余空间）。本例中，它被用来放置logo图片。同时，因为Toolbar是比Actionbar更灵活的控件，我们可以自定义更多的东西，比如设置背景颜色为colorPrimary（否则Toolbar将是透明的）。\n\n    RecyclerView虽然在xml中用起来非常简单，但是如果java代码中没有设置正确，app是不能启动的，会报java.lang.NullPointerException。\n\n\n    Elevation（ImageButton中）属性不兼容api21以前的版本。所以如果我们想做到Floating Action Button的效果需要在Lollipop和Lollipop之前的设备上使用不同的background。\n\n*Floating Action Button*\n\n为了简化FAB的使用，我们将用对Lollipop以及Lollipop之前的设备使用不同的样式：\n\n*FAB for Android v21:*\n\n![](http://jcodecraeer.com/uploads/20150204/1423059083822681.png)\n\n*FAB for Android pre-21:*\n\n![](http://jcodecraeer.com/uploads/20150204/1423059084110006.png)\n\n我们需要创建两个不同的xml文件来设置button的background：/res/drawable-v21/btn_fab_default.xml（Lollipop设备） ，/res/drawable/btn_fab_default.xml（Lollipop之前的设备）：\n\nbtn_fab_default2.xml\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--drawable-v21/btn_fab_default.xml-->\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"@color/fab_color_shadow\">\n    <item>\n    <shape android:shape=\"oval\">\n    <solid android:color=\"@color/style_color_accent\" />\n    </shape>\n    </item>\n</ripple>\n```\nbtn_fab_default1.xml\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--drawable/btn_fab_default.xml-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"false\">\n        <layer-list>\n            <item android:bottom=\"0dp\" android:left=\"2dp\" android:right=\"2dp\" android:top=\"2dp\">\n                <shape android:shape=\"oval\">\n                    <solid android:color=\"@color/fab_color_shadow\" />\n                </shape>\n            </item>\n              \n            <item android:bottom=\"2dp\" android:left=\"2dp\" android:right=\"2dp\" android:top=\"2dp\">\n                <shape android:shape=\"oval\">\n                    <solid android:color=\"@color/style_color_accent\" />\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n    <item android:state_pressed=\"true\">\n        <shape android:bottom=\"2dp\" android:left=\"2dp\" android:right=\"2dp\" android:shape=\"oval\" android:top=\"2dp\">\n        <solid android:color=\"@color/fab_color_pressed\" />\n        </shape>\n    </item>\n</selector>\n```\n上面的代码涉及到两个颜色的定义,在res/values/colors.xml中添加：\n```xml\n<color name=\"btn_default_light_normal\">#00000000</color>\n<color name=\"btn_default_light_pressed\">#40ffffff</color>\n```\n可以看到在 21之前的设备商制造阴影比较复杂。不幸的是在xml中达到真实的阴影效果没有渐变方法。其他的办法是使用图片的方式，或者通过java代码实现（参见[creating fab shadow](http://stackoverflow.com/questions/24480425/android-l-fab-button-shadow)）。\n##Toolbar\n\n现在我们来完成Toolbar。我们已经有了background和应用的logo，现在还剩下navigation以及menu菜单图标了。关于navigation，非常不幸的是，在xml中app:navigationIcon=\"\"是不起作用的，而android:navigationIcon=\"\"又只能在Lollipop上有用，所以只能使用代码的方式了：\n```xml\ntoolbar.setNavigationIcon(R.drawable.ic_menu_white);\n```\n注：app:navigationIcon=\"\"的意思是使用兼容包appcompat的属性，而android:navigationIcon=\"\"是标准的sdk属性。\n\n至于menu图标我们使用标准的定义方式就好了：\n\n在res/menu/menu_main.xml中\n```xml\n<!--menu_main.xml-->\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\".MainActivity\">\n        <item\n            android:id=\"@+id/action_inbox\"\n            android:icon=\"@drawable/ic_inbox_white\"\n            android:title=\"Inbox\"\n            app:showAsAction=\"always\" />\n</menu>\n```\n在activity中inflated这个menu：\n```java\n@Override\npublic boolean onCreateOptionsMenu(Menu menu) {\n    getMenuInflater().inflate(R.menu.menu_main, menu);\n    return true;\n}\n```\n本应运行的很好，但是正如我在twitter上提到的，Toolbar onClick  selectors有不协调的情况：\n\n\n为了解决这个问题，需要做更多的工作，首先为menu item创建一个自定义的view\n\nres/layout/menu_item_view.xml：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--menu_item_view.xml-->\n<ImageButton xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"?attr/actionBarSize\"\n    android:layout_height=\"?attr/actionBarSize\"\n    android:background=\"@drawable/btn_default_light\"\n    android:src=\"@drawable/ic_inbox_white\" />\n```\n然后为Lollipop和Lollipop之前的设备分别创建onClick的selector，在Lollipop上有ripple效果：\nbtn_default_light2.xml\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--drawable-v21/btn_default_light.xml-->\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"@color/btn_default_light_pressed\" />\n```\nbtn_default_light1.xml\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--drawable/btn_default_light.xml-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@color/btn_default_light_normal\" android:state_focused=\"false\" android:state_pressed=\"false\" />\n    <item android:drawable=\"@color/btn_default_light_pressed\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@color/btn_default_light_pressed\" android:state_focused=\"true\" />\n</selector>\n```\n现在，工程中的所有的color应该是这样子了：\n\ncolors.xml   \n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--colors.xml-->\n<resources>\n<color name=\"style_color_primary\">#2d5d82</color>\n<color name=\"style_color_primary_dark\">#21425d</color>\n<color name=\"style_color_accent\">#01bcd5</color>\n  \n<color name=\"fab_color_pressed\">#007787</color>\n<color name=\"fab_color_shadow\">#44000000</color>\n  \n<color name=\"btn_default_light_normal\">#00000000</color>\n<color name=\"btn_default_light_pressed\">#40ffffff</color>\n</resources>\n```\n 最后我们应该将custom view放到menu item中，在onCreateOptionsMenu()中：\n```java\n @Override\npublic boolean onCreateOptionsMenu(Menu menu) {\n    getMenuInflater().inflate(R.menu.menu_main, menu);\n    inboxMenuItem = menu.findItem(R.id.action_inbox);\n    inboxMenuItem.setActionView(R.layout.menu_item_view);\n    return true;\n}\n```\n以上就是toolbar的所有东西。并且onClick的按下效果也达到了预期的效果：\n\n![](http://jcodecraeer.com/uploads/20150204/1423059085109860.png)\n\n#Feed\n最后需要实现的是feed，基于RecyclerView实现。我们需要设置两个东西：layout manager和adapter，因为这里其实就是想实现ListView的效果，所以直接用LinearLayoutManager就行了，而adapter我们首先从item的布局开始(res/layout/item_feed.xml)：\n\nitem_feed.xml\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- item_feed.xml -->\n<android.support.v7.widget.CardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:card_view=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/card_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:layout_margin=\"8dp\"\n    card_view:cardCornerRadius=\"4dp\">\n  \n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n      \n        <ImageView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:src=\"@drawable/ic_feed_top\" />\n          \n        <io.github.froger.instamaterial.SquaredImageView\n            android:id=\"@+id/ivFeedCenter\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n          \n        <ImageView\n            android:id=\"@+id/ivFeedBottom\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n      \n    </LinearLayout>\n</android.support.v7.widget.CardView>\n```\nFeedAdapter也非常简单：\n\nFeedAdapter.java\n```java\npublic class FeedAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {\n    private static final int ANIMATED_ITEMS_COUNT = 2;\n      \n    private Context context;\n    private int lastAnimatedPosition = -1;\n    private int itemsCount = 0;\n      \n    public FeedAdapter(Context context) {\n        this.context = context;\n    }\n      \n    @Override\n    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        final View view = LayoutInflater.from(context).inflate(R.layout.item_feed, parent, false);\n        return new CellFeedViewHolder(view);\n    }\n      \n    private void runEnterAnimation(View view, int position) {\n        if (position >= ANIMATED_ITEMS_COUNT - 1) {\n            return;\n        }\n          \n        if (position > lastAnimatedPosition) {\n            lastAnimatedPosition = position;\n            view.setTranslationY(Utils.getScreenHeight(context));\n            view.animate()\n            .translationY(0)\n            .setInterpolator(new DecelerateInterpolator(3.f))\n            .setDuration(700)\n            .start();\n        }\n    }\n      \n    @Override\n    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {\n        runEnterAnimation(viewHolder.itemView, position);\n        CellFeedViewHolder holder = (CellFeedViewHolder) viewHolder;\n        if (position % 2 == 0) {\n            holder.ivFeedCenter.setImageResource(R.drawable.img_feed_center_1);\n            holder.ivFeedBottom.setImageResource(R.drawable.img_feed_bottom_1);\n        } else {\n            holder.ivFeedCenter.setImageResource(R.drawable.img_feed_center_2);\n            holder.ivFeedBottom.setImageResource(R.drawable.img_feed_bottom_2);\n        }\n    }\n      \n    @Override\n    public int getItemCount() {\n        return itemsCount;\n    }\n      \n    public static class CellFeedViewHolder extends RecyclerView.ViewHolder {\n        @InjectView(R.id.ivFeedCenter)\n        SquaredImageView ivFeedCenter;\n        @InjectView(R.id.ivFeedBottom)\n        ImageView ivFeedBottom;\n          \n        public CellFeedViewHolder(View view) {\n        super(view);\n            ButterKnife.inject(this, view);\n        }\n    }\n      \n    public void updateItems() {\n        itemsCount = 10;\n        notifyDataSetChanged();\n    }\n}\n```\n没什么特别之处需要说明。\n\n通过以下方法将他们放在一起：\n```java\nprivate void setupFeed() {\n    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);\n    rvFeed.setLayoutManager(linearLayoutManager);\n    feedAdapter = new FeedAdapter(this);\n    rvFeed.setAdapter(feedAdapter);\n}\n```\n下面是整个MainActivity class的源码：\n//MainActivity.java\n```java\npublic class MainActivity extends ActionBarActivity {\n    @InjectView(R.id.toolbar)\n    Toolbar toolbar;\n    @InjectView(R.id.rvFeed)\n    RecyclerView rvFeed;\n      \n    private MenuItem inboxMenuItem;\n    private FeedAdapter feedAdapter;\n      \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        ButterKnife.inject(this);\n          \n        setupToolbar();\n        setupFeed();\n    }\n      \n    private void setupToolbar() {\n        setSupportActionBar(toolbar);\n        toolbar.setNavigationIcon(R.drawable.ic_menu_white);\n    }\n      \n    private void setupFeed() {\n        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);\n        rvFeed.setLayoutManager(linearLayoutManager);\n        feedAdapter = new FeedAdapter(this);\n        rvFeed.setAdapter(feedAdapter);\n    }\n      \n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        inboxMenuItem = menu.findItem(R.id.action_inbox);\n        inboxMenuItem.setActionView(R.layout.menu_item_view);\n        return true;\n    }\n}\n\n```\n运行结果：\n\nAndroid Lollipop\n\n![](http://jcodecraeer.com/uploads/20150204/1423059086107717.png)\n\n\nAndroid pre-21\n\n![](http://jcodecraeer.com/uploads/20150204/1423059082492283.png)\n\n##动画\n\n最后一件也是最重要的事情就是进入时的动画效果，再浏览一遍概念视频，可以发现在main Activity启动的时候有如下动画，分成两步：\n\n\n显示Toolbar以及其里面的元素\n\n在Toolbar动画完成之后显示feed和floating action button。\n\nToolbar中元素的动画表现为在较短的时间内一个接一个的进入。实现这个效果的主要问题在于navigation icon的动画，navigation icon是唯一一个不能使用动画的，其他的都好办。\n*Toolbar animation*\n\n首先我们只是需要在activity启动的时候才播放动画（在旋转屏幕的时候不播放），还要知道menu的动画过程是不能在onCreate()中去实现的（我们在onCreateOptionsMenu()中实现），创建一个布尔类型的变量pendingIntroAnimation ，在onCreate()方法中初始化：\n```java\n//...\nif (savedInstanceState == null) {\n    pendingIntroAnimation = true;\n}\n```\nonCreateOptionsMenu():\n```java\n@Override\npublic boolean onCreateOptionsMenu(Menu menu) {\n    getMenuInflater().inflate(R.menu.menu_main, menu);\n    inboxMenuItem = menu.findItem(R.id.action_inbox);\n    inboxMenuItem.setActionView(R.layout.menu_item_view);\n    if (pendingIntroAnimation) {\n        pendingIntroAnimation = false;\n        startIntroAnimation();\n    }\n    return true;\n}\n```\n这样startIntroAnimation()将只被调用一次。\n\n现在该来准备Toolbar中元素的动画了，也非常简单\n\nToolbarAnimation\n```java\n//...\nprivate static final int ANIM_DURATION_TOOLBAR = 300;\n  \nprivate void startIntroAnimation() {\n    btnCreate.setTranslationY(2 * getResources().getDimensionPixelOffset(R.dimen.btn_fab_size));\n    int actionbarSize = Utils.dpToPx(56);\n    toolbar.setTranslationY(-actionbarSize);\n    ivLogo.setTranslationY(-actionbarSize);\n    inboxMenuItem.getActionView().setTranslationY(-actionbarSize);\n      \n    toolbar.animate()\n        .translationY(0)\n        .setDuration(ANIM_DURATION_TOOLBAR)\n        .setStartDelay(300);\n    ivLogo.animate()\n        .translationY(0)\n        .setDuration(ANIM_DURATION_TOOLBAR)\n        .setStartDelay(400);\n    inboxMenuItem.getActionView().animate()\n        .translationY(0)\n        .setDuration(ANIM_DURATION_TOOLBAR)\n        .setStartDelay(500)\n        .setListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n            startContentAnimation();\n            }\n        })\n        .start();\n}\n//...\n```\n在上面的代码中：\n\n    首先我们将所有的元素都通过移动到屏幕之外隐藏起来（这一步我们将FAB也隐藏了）。\n\n    让Toolbar元素一个接一个的开始动画\n\n    当动画完成，调用了startContentAnimation()开始content的动画（FAB和feed卡片的动画）\n\n 简单，是吧？\n*Content 动画*\n\n在这一步中我们将让FAB和feed卡片动起来。FAB的动画很简单，跟上面的方法类似，但是feed卡片稍微复杂些。\n\nstartContentAnimation方法\n```java\n//...\n//FAB animation\nprivate static final int ANIM_DURATION_FAB = 400;\n  \nprivate void startContentAnimation() {\n    btnCreate.animate()\n        .translationY(0)\n        .setInterpolator(new OvershootInterpolator(1.f))\n        .setStartDelay(300)\n        .setDuration(ANIM_DURATION_FAB)\n        .start();\n    feedAdapter.updateItems();\n}\n//...\n```\nFeedAdapter的代码在上面已经贴出来了。结合着就知道动画是如何实现的了。\n\n\n本篇文章就结束了，避免遗漏，这里是这篇文章是提交的代码 [commit for our project with implemented animations](https://github.com/frogermcs/InstaMaterial/commit/4e838861c5f858711b1072777cae2325ce12ee21) .\n\n \n##源代码\n\n完整的代码在[Github repository](https://github.com/frogermcs/InstaMaterial).\n\n作者: Miroslaw Stanek\n"
  },
  {
    "path": "others/VectorDrawable系列/VectorDrawable – 第一章/readme.md",
    "content": "VectorDrawable-第一章\n---\n\n>\n* 原文链接:[VectorDrawables – Part 1](https://blog.stylingandroid.com/vectordrawables-part-1/)\n* 译者 :  [jianghejie](https://github.com/jianghejie) \n* 译者博文链接 :  [jcodecraeer.com](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0201/2396.html)\n \n\nLollipop中有一个非常好的新特性是VectorDrawable以及相关的一些类，他们为我们提供了添加复杂矢量图形的强大功能，同时也提供了动画显示这些图形的方法。矢量图形的好处是放大不会失真，可以适应不同分辨率的屏幕。这个文章系列我们将了解这些类以及它们的优点，以及如何用相对较少的代码实现吸引人的效果。\n\n \n\n 简单的来说，矢量图形就是使用几何形状的方式来描述一个图像元素。矢量图形非常适合于与设备无关的简单或者合成的制图或者不需要实现真实感的场合。Adobe Illustrator和Inkscape 常用来制作矢量图形。而位图（Bitmap ），则完全相反，位图是定义每一个像素点的颜色来显示一张图片，它适合显示一张真实的照片。矢量图形的一大好处是它的渲染是在运行时开始的，因此它可以自适应不同的屏幕。由于矢量图其实保存的只是描述几何图形的文本，因此它只占用非常少的空间。当然因为需要在运行时将这些字符串转换成图像，花费多一点点的cpu是肯定的。\n\n \n\n矢量图形在安卓的Lollipop中已经实现了，相关的类就是VectorDrawable 。（虽然第三方的MrVector 已经实现了VectorDrawable 兼容Lollipop 之前的设备，但是它并没有实现后面会讲到的AnimatedVectorDrawable ）。VectorDrawable 的出现意味着以前我们放在mdpi, hdpi, xhdpi, xxhdpi中的部分图片资源（适合用矢量图描述的，比如图标）只用一个VectorDrawable 替代就可以了。\n\n为了说明，我找了下面这张svg文件（这个文件可以从library for displaying SVG graphics in Android 中获取到），图片是一个机器人 ：\n\n![](https://blog.stylingandroid.com/wp-content/uploads/2014/12/android.svg)\n这个文件的svg格式只有2265字节，但是如果我们将它转换成500 x 500的bitmap文件，保存成png格式，则有13272 字节。并且它可以自动伸缩为任意大小的图片，而位图就需要使用多张才能达到不同分辨率的效果。不过，这里以svg作为例子还是有点问题，因为svg并非VectorDrawable，所以我们不能直接使用svg图片。但是VectorDrawable 支持svg的一部分规则，我们可以将svg中的某些数据用在VectorDrawable中。主要其实就是svg中定义path的那部分数据。Svg的path类似于android.graphics.Path api，只不过是用字符串定义的，我们看看svg的源码就知道了：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948)  -->\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n     width=\"500px\" height=\"500px\" viewBox=\"0 0 500 500\" enable-background=\"new 0 0 500 500\" xml:space=\"preserve\">\n<g id=\"max_width__x2F__height\" display=\"none\">\n    <path display=\"inline\" d=\"M499.001,1v498H1V1H499.001 M500.001,0H0v500h500.001V0L500.001,0z\"/>\n</g>\n<g id=\"androd\">\n    <path fill=\"#9FBF3B\" d=\"M301.314,83.298l20.159-29.272c1.197-1.74,0.899-4.024-0.666-5.104c-1.563-1.074-3.805-0.543-4.993,1.199\n        L294.863,80.53c-13.807-5.439-29.139-8.47-45.299-8.47c-16.16,0-31.496,3.028-45.302,8.47l-20.948-30.41\n        c-1.201-1.74-3.439-2.273-5.003-1.199c-1.564,1.077-1.861,3.362-0.664,5.104l20.166,29.272\n        c-32.063,14.916-54.548,43.26-57.413,76.34h218.316C355.861,126.557,333.375,98.214,301.314,83.298\"/>\n    <path fill=\"#FFFFFF\" d=\"M203.956,129.438c-6.673,0-12.08-5.407-12.08-12.079c0-6.671,5.404-12.08,12.08-12.08\n        c6.668,0,12.073,5.407,12.073,12.08C216.03,124.03,210.624,129.438,203.956,129.438\"/>\n    <path fill=\"#FFFFFF\" d=\"M295.161,129.438c-6.668,0-12.074-5.407-12.074-12.079c0-6.673,5.406-12.08,12.074-12.08\n        c6.675,0,12.079,5.409,12.079,12.08C307.24,124.03,301.834,129.438,295.161,129.438\"/>\n    <path fill=\"#9FBF3B\" d=\"M126.383,297.598c0,13.45-10.904,24.354-24.355,24.354l0,0c-13.45,0-24.354-10.904-24.354-24.354V199.09\n        c0-13.45,10.904-24.354,24.354-24.354l0,0c13.451,0,24.355,10.904,24.355,24.354V297.598z\"/>\n    <path fill=\"#9FBF3B\" d=\"M140.396,175.489v177.915c0,10.566,8.566,19.133,19.135,19.133h22.633v54.744\n        c0,13.451,10.903,24.354,24.354,24.354c13.451,0,24.355-10.903,24.355-24.354v-54.744h37.371v54.744\n        c0,13.451,10.902,24.354,24.354,24.354s24.354-10.903,24.354-24.354v-54.744h22.633c10.569,0,19.137-8.562,19.137-19.133V175.489\n        H140.396z\"/>\n    <path fill=\"#9FBF3B\" d=\"M372.734,297.598c0,13.45,10.903,24.354,24.354,24.354l0,0c13.45,0,24.354-10.904,24.354-24.354V199.09\n        c0-13.45-10.904-24.354-24.354-24.354l0,0c-13.451,0-24.354,10.904-24.354,24.354V297.598z\"/>\n</g>\n</svg>\n```\n看起来很乱，我们不需要知道其内部的细节，只把我们用得着的东西筛选出来就行了。<svg> 标签中定义了一些属性：画布以及视图区域大小为500 x 500 px，然后有一个<g>标签，里面定义了一个描边，忽略掉就是了。再后又是一个<g>标签，这个标签的id为“android”，这部分里面的东西才是机器人logo自身的数据，也是我们需要的数据。它包含了6个<path> 元素，分别定义head、左眼、右眼、左手、身体和脚、右手。每一个path中的fill 属性定义填充色（可以看到，除了眼睛为白色之外，所有的填充色都是绿色），fill属性之后是包含path数据的属性d。想了解d属性中数据定义的可以参考SVG Path Specification ，但是这个不重要，因为我们只需简单的把这里面的数据直接用在VectorDrawable中就可以了。\n\n好了，我们来创建一个VectorDrawable：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:viewportWidth=\"500\"\n    android:viewportHeight=\"500\"\n    android:width=\"500px\"\n    android:height=\"500px\">\n    <group android:name=\"android\">\n        <path\n            android:name=\"head\"\n            android:fillColor=\"#9FBF3B\"\n            android:pathData=\"M301.314,83.298l20.159-29.272c1.197-1.74,0.899-4.024-0.666-5.104c-1.563-1.074-3.805-0.543-4.993,1.199L294.863,80.53c-13.807-5.439-29.139-8.47-45.299-8.47c-16.16,0-31.496,3.028-45.302,8.47l-20.948-30.41c-1.201-1.74-3.439-2.273-5.003-1.199c-1.564,1.077-1.861,3.362-0.664,5.104l20.166,29.272c-32.063,14.916-54.548,43.26-57.413,76.34h218.316C355.861,126.557,333.375,98.214,301.314,83.298\" />\n        <path\n            android:name=\"left_eye\"\n            android:fillColor=\"#FFFFFF\"\n            android:pathData=\"M203.956,129.438c-6.673,0-12.08-5.407-12.08-12.079c0-6.671,5.404-12.08,12.08-12.08c6.668,0,12.073,5.407,12.073,12.08C216.03,124.03,210.624,129.438,203.956,129.438\" />\n        <path\n            android:name=\"right_eye\"\n            android:fillColor=\"#FFFFFF\"\n            android:pathData=\"M295.161,129.438c-6.668,0-12.074-5.407-12.074-12.079c0-6.673,5.406-12.08,12.074-12.08c6.675,0,12.079,5.409,12.079,12.08C307.24,124.03,301.834,129.438,295.161,129.438\" />\n        <path\n            android:name=\"left_arm\"\n            android:fillColor=\"#9FBF3B\"\n            android:pathData=\"M126.383,297.598c0,13.45-10.904,24.354-24.355,24.354l0,0c-13.45,0-24.354-10.904-24.354-24.354V199.09c0-13.45,10.904-24.354,24.354-24.354l0,0c13.451,0,24.355,10.904,24.355,24.354V297.598z\" />\n        <path\n            android:name=\"body\"\n            android:fillColor=\"#9FBF3B\"\n            android:pathData=\"M140.396,175.489v177.915c0,10.566,8.566,19.133,19.135,19.133h22.633v54.744c0,13.451,10.903,24.354,24.354,24.354c13.451,0,24.355-10.903,24.355-24.354v-54.744h37.371v54.744c0,13.451,10.902,24.354,24.354,24.354s24.354-10.903,24.354-24.354v-54.744h22.633c10.569,0,19.137-8.562,19.137-19.133V175.489H140.396z\" />\n        <path\n            android:name=\"right_arm\"\n            android:fillColor=\"#9FBF3B\"\n            android:pathData=\"M372.734,297.598c0,13.45,10.903,24.354,24.354,24.354l0,0c13.45,0,24.354-10.904,24.354-24.354V199.09c0-13.45-10.904-24.354-24.354-24.354l0,0c-13.451,0-24.354,10.904-24.354,24.354V297.598z\" />\n    </group>\n</vector>\n```\n我们创建一个<vector> 元素，指定了宽和高，然后创建包含了6个<path>元素的<group>元素。这6个<path>元素的定义非常类似svg中的定义，只有非常小的差别，大部分内容都是直接从svg中copy的。我们还定义了name 属性来描述每个path的作用。这个文件保存下来仍然只有2412字节。\n\n下面我们可以就像一般drawable一样去使用上面定义的VectorDrawable了。\n```xml\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    tools:context=\".VectorDrawablesActivity\">\n \n    <ImageView\n        android:id=\"@+id/android\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/android\"\n        android:contentDescription=\"@null\" />\n \n</RelativeLayout>\n```\n运行结果如下：\n\n ![](http://jcodecraeer.com/uploads/20150201/1422796393485613.png)\n\n有一个小问题：这个VectorDrawable渲染的非常好，但是我遇到过从其他的svg文件中复制的VectorDrawable 在Android Studio中无法预览的情况（在真实设备中运行却是正常的），因此不管Android Studio是否能正常预览，还是以真实设备为准吧，Android Studio是有bug的。我用的是V1.0.2 版本，关于这个bug在这里有描述：[点击这里](https://code.google.com/p/android/issues/detail?id=91383)  。\n\n现在我们可以使用VectorDrawable大幅度的减小我们的apk了，而且这还让我们在维护app的时候容易的多，至少在使用VectorDrawable的资源上我们不需要因为要兼容新的设备而修改。这还只是VectorDrawable的一个用处，在后面的文章中我们将看到VectorDrawable是如何动画的。\n\n这篇文章的代码可以在这里得到：[这里](https://bitbucket.org/StylingAndroid/vectordrawables/src/b4767fe0e0a7b41323adbfd77845ed1ec24822c3/?at=Part1)。\n\n \n\n英文原文：https://blog.stylingandroid.com/vectordrawables-part-1/  "
  },
  {
    "path": "others/VectorDrawable系列/VectorDrawable – 第二章/readme.md",
    "content": "VectorDrawable-第二章\n---\n\n>\n* 原文链接:[VectorDrawables – Part 2](https://blog.stylingandroid.com/vectordrawables-part-2/)\n* 译者 :  [jianghejie](https://github.com/jianghejie) \n* 译者博文链接 :  [jcodecraeer.com](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0301/2514.html)\n \n[上篇文章](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0201/2396.html)    中，我们探讨了如何将svg图片转换成VectorDrawable，以在适应不同分辨率的同时减少资源文件的个数，同时也更易于维护。但是这并不是VectorDrawable的唯一好处-还可以用来制作动画。这篇文章就是关于如何用VectorDrawable来实现android机器人耸肩的效果！\n\n我们将要实现的动画很简单，在保持身体不动的同时，让头部和手臂在Y方向上上下移动。表面上看实现起来很复杂，因为我们这里只有一个Drawable（要是一般的Drawable估计的通过绘制来实现了）。但是有一个控件却可以使事情变得非常简单，那就是在Lollipop中和VectorDrawable一起被引入的AnimatedVectorDrawable。在上篇文章中，我们讲到了path元素的name属性是为了描述path的用处，但它还可以用在为指定path指定一个动画。本例中我们需要动画的path元素是头部，左眼，右眼，左臂，右臂。问题是单个的<path>是没有translateX和translateY属性的，因此无法使用属性动画来控制<path>translateY，而<group>元素是有的，所以我们需要先将相关的<path>元素包裹在一个个的<group>元素中：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:viewportWidth=\"500\"\n    android:viewportHeight=\"500\"\n    android:width=\"500px\"\n    android:height=\"500px\">\n    <group android:name=\"android\">\n        <group android:name=\"head_eyes\">\n            <path\n                android:name=\"head\"\n                android:fillColor=\"#9FBF3B\"\n                android:pathData=\"M301.314,83.298l20.159-29.272c1.197-1.74,0.899-4.024-0.666-5.104c-1.563-1.074-3.805-0.543-4.993,1.199L294.863,80.53c-13.807-5.439-29.139-8.47-45.299-8.47c-16.16,0-31.496,3.028-45.302,8.47l-20.948-30.41c-1.201-1.74-3.439-2.273-5.003-1.199c-1.564,1.077-1.861,3.362-0.664,5.104l20.166,29.272c-32.063,14.916-54.548,43.26-57.413,76.34h218.316C355.861,126.557,333.375,98.214,301.314,83.298\" />\n            <path\n                android:name=\"left_eye\"\n                android:fillColor=\"#FFFFFF\"\n                android:pathData=\"M203.956,129.438c-6.673,0-12.08-5.407-12.08-12.079c0-6.671,5.404-12.08,12.08-12.08c6.668,0,12.073,5.407,12.073,12.08C216.03,124.03,210.624,129.438,203.956,129.438\" />\n            <path\n                android:name=\"right_eye\"\n                android:fillColor=\"#FFFFFF\"\n                android:pathData=\"M295.161,129.438c-6.668,0-12.074-5.407-12.074-12.079c0-6.673,5.406-12.08,12.074-12.08c6.675,0,12.079,5.409,12.079,12.08C307.24,124.03,301.834,129.438,295.161,129.438\" />\n        </group>\n        <group android:name=\"arms\">\n            <path\n                android:name=\"left_arm\"\n                android:fillColor=\"#9FBF3B\"\n                android:pathData=\"M126.383,297.598c0,13.45-10.904,24.354-24.355,24.354l0,0c-13.45,0-24.354-10.904-24.354-24.354V199.09c0-13.45,10.904-24.354,24.354-24.354l0,0c13.451,0,24.355,10.904,24.355,24.354V297.598z\" />\n            <path\n                android:name=\"right_arm\"\n                android:fillColor=\"#9FBF3B\"\n                android:pathData=\"M372.734,297.598c0,13.45,10.903,24.354,24.354,24.354l0,0c13.45,0,24.354-10.904,24.354-24.354V199.09c0-13.45-10.904-24.354-24.354-24.354l0,0c-13.451,0-24.354,10.904-24.354,24.354V297.598z\" />\n        </group>\n        <path\n            android:name=\"body\"\n            android:fillColor=\"#9FBF3B\"\n            android:pathData=\"M140.396,175.489v177.915c0,10.566,8.566,19.133,19.135,19.133h22.633v54.744c0,13.451,10.903,24.354,24.354,24.354c13.451,0,24.355-10.903,24.355-24.354v-54.744h37.371v54.744c0,13.451,10.902,24.354,24.354,24.354s24.354-10.903,24.354-24.354v-54.744h22.633c10.569,0,19.137-8.562,19.137-19.133V175.489H140.396z\" />\n    </group>\n</vector> \n```\n现在再我们定义一个包含<animated-vector>的drawable文件，这个文件的作用是将动画应用在指定的group中，使得某些部分的path动起来：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:drawable=\"@drawable/android\">\n \n    <target\n        android:animation=\"@animator/shrug\"\n        android:name=\"head_eyes\" />\n \n    <target\n        android:animation=\"@animator/shrug\"\n        android:name=\"arms\" />\n</animated-vector>\n```\n\n虽然我知道完全可以将头、眼、和手臂放在一个group中，但是为了演示一个<animated-vector>是如何控制多个group的动画的，我有意把他们分成两部分，实际运用中，不同的group目标动画肯定是不一样的。\n\n<animated-vector>的最外层元素指定了我们要动画的VectorDrawable资源-在本例中这个资源是android.xml，里面的<target>元素根据group的name属性指定group的动画效果。\n\n<vector>, <group>, <clip-path>, 和<path> 元素都有各自可以播放动画的属性，查阅VectorDrawable JavaDocs你会找到每种元素到底有那些属性，以便针对这些属性播放特定的动画。比如：要使用tint效果需要作用于<vector>元素上，而修改填充颜色则需要作用于<path>元素。\n\n耸肩的效果很简单，只是个重复移动Y轴的animator：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <objectAnimator\n        android:propertyName=\"translateY\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"-10\"\n        android:repeatMode=\"reverse\"\n        android:repeatCount=\"infinite\"\n        android:duration=\"250\" />\n</set>\n```\n为了运行这个动画我们需要做几件事情。首先，要将ImageView的src改为动画效果的drawable。\n```xml\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    tools:context=\".VectorDrawablesActivity\">\n \n    <ImageView\n        android:id=\"@+id/android\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/animated_android\"\n        android:contentDescription=\"@null\" />\n \n</RelativeLayout>\n```\n如果运行现在的代码，我们只能看到静态的图片。这是因为我们需要手动调用播放动画。我们将在Activity中去调用，如果Drawable是Animatable（AnimatedVectorDrawable实现了Animatable）的实例，将开始动画。\n```xml\npublic class VectorDrawablesActivity extends Activity {\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_vector_drawables);\n        ImageView androidImageView = (ImageView) findViewById(R.id.android);\n        Drawable drawable = androidImageView.getDrawable();\n        if (drawable instanceof Animatable) {\n            ((Animatable) drawable).start();\n        }\n    }\n}\n```\n运行代码就会发现指定了animation的path出现动画效果：\n\n![](http://jcodecraeer.com/uploads/20150306/1425623349523137.gif)\n\n在接下来的文章中，我们将更深入的去了解AnimatedVectorDrawable，实现更酷的效果。\n\n本篇文章的源代码在 [这里](http://code.stylingandroid.com/vectordrawables/src/f4c31878fdfa3b9205bb58016c20c789e4dc426a/?at=Part2).\n\n\n\n\n"
  },
  {
    "path": "others/上传拍下的照片、视频到服务器/readme.md",
    "content": "上传拍下的照片、视频到服务器\n---\n\n>\n* 原文链接 : [Android Uploading Camera Image, Video to Server with Progress Bar](http://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/)\n* 作者 :  [Ravi Tamada](http://www.androidhive.info/)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)  \n* 状态 :  校对完成\n\n\n我在上一篇教程中给大家讲解了怎么通过进度条下载文件，今天，我将在这篇文章中给大家讲解如果在弹出进度条的同时上传一个文件到服务器。通过阅读这篇文章，并学习其中的知识，你能做出一个类似 Instagram 的App，在你做出的 App 里，你能够像在 Instagram 里那样用摄像头拍照/视频，然后把它们上传到服务器。在服务器端，我使用 PHP 读取上传的文件，并把文件移动到一个特殊的位置。\n\n这篇文章最精华的部分在于：即使是上传大文件，它也能很好地运行，而不是产生许多内存不足的错误。我能有这样的自信，是因为我进行了许多的测试。在测试过程中，即便我上传了一个 50MB 的文件，这个功能仍然运行得很完美，不会出现错误。\n\n[源码下载](http://download.androidhive.info/)\n\n## 准备工作 ##\n\n由于这篇文章需要上传摄像头拍下来的照片/视频，因此你需要先了解有关 Android 摄像头模块的知识。所以我建议你最好先去阅读我之前的一篇有关 Android 是如何操作摄像头的讲解文章，通过阅读这篇文章你能大概了解怎么在你的 Android App 中使用摄像头。\n\n## 创建 Android 工程 ##\n\n1、 在 Eclipse 中创建一个新的 Android 工程，具体流程如下：New ⇒ Android Application Project，然后在里面填好一些必要的信息。\n\n2、 打开 res 文件夹中的 strings.xml,然后把下面的内容添加进去：\n\n**strings.xml**\n\n```xml\n     <?xml version=\"1.0\" encoding=\"utf-8\"?>\n     <resources>\n      \n    \t\t <string name=\"app_name\">Camera File Upload</string>\n     \t <string name=\"btnTakePicture\">Capture Image</string>\n     \t <string name=\"btnRecordVideo\">Record Video</string>\n     \t <string name=\"or\">(or)</string>\n     \t <string name=\"btnUploadToServer\">Upload to Server</string>\n      \n     </resources>\n```\n \n3、 向 res 文件夹添加 colors.xml，并把下面的内容添加进去：\n\n**colors.xml**\n\n```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<resources>\n\t \n\t\t<color name=\"view_background\">#e8ecfa</color>\n\t\t<color name=\"btn_bg\">#277bec</color>\n\t\t<color name=\"white\">#ffffff</color>\n\t\t<color name=\"txt_font\">#4e5572</color>\n\t\t<color name=\"action_bar\">#1f2649</color>\n\t \n\t</resources>\n```\n\n4、 在 src 目录下创建一个叫做 Config 的类。这个类用于保存文件上传所送到的 URL 地址，还有在移动设备中存储图片/视频的目录名称。你可能需要在测试 App 时使用你自己的 URL 地址进行上传操作。\n\n5、 创建一个叫做 AndroidMultiPartEntity 的类，并把下面的代码复制进去。这个类是一个自定义的 MultipartEntity 类，主要用于提供这个工程中需要用到的关键功能，例如进度条的进度增量。\n\n**AndroidMultiPartEntity.java**\n\n```java\n    package info.androidhive.camerafileupload;\n     \n    import java.io.FilterOutputStream;\n    import java.io.IOException;\n    import java.io.OutputStream;\n    import java.nio.charset.Charset;\n     \n    import org.apache.http.entity.mime.HttpMultipartMode;\n    import org.apache.http.entity.mime.MultipartEntity;\n     \n    @SuppressWarnings(\"deprecation\")\n    public class AndroidMultiPartEntity extends MultipartEntity\n     \n    {\n     \n    \tprivate final ProgressListener listener;\n     \n    \tpublic AndroidMultiPartEntity(final ProgressListener listener) {\n    \t\tsuper();\n    \t\tthis.listener = listener;\n    \t}\n     \n    \tpublic AndroidMultiPartEntity(final HttpMultipartMode mode,\n    \t\tfinal ProgressListener listener) {\n    \t\tsuper(mode);\n    \t\tthis.listener = listener;\n    \t}\n     \n    \tpublic AndroidMultiPartEntity(HttpMultipartMode mode, final String boundary,\n    \t\tfinal Charset charset, final ProgressListener listener) {\n    \t\tsuper(mode, boundary, charset);\n    \t\tthis.listener = listener;\n    \t}\n     \n    \t@Override\n    \tpublic void writeTo(final OutputStream outstream) throws IOException {\n    \t\tsuper.writeTo(new CountingOutputStream(outstream, this.listener));\n    \t}\n     \n    \tpublic static interface ProgressListener {\n    \t\tvoid transferred(long num);\n    \t}\n     \n    \tpublic static class CountingOutputStream extends FilterOutputStream {\n     \n    \t\tprivate final ProgressListener listener;\n    \t\tprivate long transferred;\n     \n    \t\tpublic CountingOutputStream(final OutputStream out,\n    \t\t\tfinal ProgressListener listener) {\n    \t\t\t\tsuper(out);\n    \t\t\t\tthis.listener = listener;\n    \t\t\t\tthis.transferred = 0;\n    \t\t}\n     \n\t    public void write(byte[] b, int off, int len) throws IOException {\n\t    \tout.write(b, off, len);\n\t    \tthis.transferred += len;\n\t    \tthis.listener.transferred(this.transferred);\n\t    }\n     \n\t    public void write(int b) throws IOException {\n\t    \tout.write(b);\n\t    \tthis.transferred++;\n\t    \tthis.listener.transferred(this.transferred);\n\t    }\n   \t  }\n\t}\n```\n\n现在我们需要通过在 App 中添加一个简单的页面来添加摄像头功能，在这里我们将使用两个 Button 来调用我们的摄像头为我们的 App 拍照/视频。\n\n6、 在你的 AndroidManifest.xml 中添加相应的权限。你会注意到 UploadActivity 也需要被添加到 AndroidManifest.xml 文件中，我们会在后面提到这个。\n\n**INTERNET** – 联网权限\n\n**WRITE_EXTERNAL_STORAGE** – 存储照片/视频到本地的权限\n\n**RECORD_AUDIO** – 拍摄权限\n\n**AndroidManifest.xml**\n\n```xml\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t    package=\"info.androidhive.camerafileupload\"\n\t    android:versionCode=\"1\"\n\t    android:versionName=\"1.0\" >\n\t \n\t    <uses-sdk\n\t        android:minSdkVersion=\"11\"\n\t        android:targetSdkVersion=\"21\" />\n\t \n\t    <uses-permission android:name=\"android.permission.INTERNET\" />\n\t    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n\t    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n\t \n\t    <application\n\t        android:allowBackup=\"true\"\n\t        android:icon=\"@drawable/ic_launcher\"\n\t        android:label=\"@string/app_name\"\n\t        android:theme=\"@style/AppTheme\" >\n\t        <activity\n\t            android:name=\"info.androidhive.camerafileupload.MainActivity\"\n\t            android:label=\"@string/app_name\"\n\t            android:screenOrientation=\"portrait\" >\n\t            <intent-filter>\n\t                <action android:name=\"android.intent.action.MAIN\" />\n\t \n\t                <category android:name=\"android.intent.category.LAUNCHER\" />\n\t            </intent-filter>\n\t        </activity>\n\t        <activity\n\t            android:name=\"info.androidhive.camerafileupload.UploadActivity\"\n\t            android:screenOrientation=\"portrait\" >\n\t        </activity>\n\t    </application>\n\n    </manifest>\n```\n\n7、打开你 MainActivity 的布局文件（activity_main.xml)，并添加下面的代码，这样做能让你的布局拥有两个 Button。\n\n**activity_main.xml**\n\n```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t    xmlns:tools=\"http://schemas.android.com/tools\"\n\t    android:layout_width=\"fill_parent\"\n\t    android:layout_height=\"fill_parent\"\n\t    android:background=\"@color/view_background\"\n\t    android:baselineAligned=\"false\"\n\t    android:orientation=\"vertical\" >\n\t \n\t    <LinearLayout\n\t        android:layout_width=\"fill_parent\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:layout_centerInParent=\"true\"\n\t        android:gravity=\"center\"\n\t        android:orientation=\"vertical\" >\n\t \n\t        <!-- Capture picture button -->\n\t \n\t        <Button\n\t            android:id=\"@+id/btnCapturePicture\"\n\t            android:layout_width=\"wrap_content\"\n\t            android:layout_height=\"wrap_content\"\n\t            android:layout_marginBottom=\"20dp\"\n\t            android:background=\"@color/btn_bg\"\n\t            android:paddingLeft=\"20dp\"\n\t            android:paddingRight=\"20dp\"\n\t            android:text=\"@string/btnTakePicture\"\n\t            android:textColor=\"@color/white\" />\n\t \n\t        <TextView\n\t            android:layout_width=\"wrap_content\"\n\t            android:layout_height=\"wrap_content\"\n\t            android:layout_marginBottom=\"20dp\"\n\t            android:gravity=\"center_horizontal\"\n\t            android:text=\"@string/or\"\n\t            android:textColor=\"@color/txt_font\" />\n\t \n\t        <!-- Record video button -->\n\t \n\t        <Button\n\t            android:id=\"@+id/btnRecordVideo\"\n\t            android:layout_width=\"wrap_content\"\n\t            android:layout_height=\"wrap_content\"\n\t            android:background=\"@color/btn_bg\"\n\t            android:paddingLeft=\"20dp\"\n\t            android:paddingRight=\"20dp\"\n\t            android:text=\"@string/btnRecordVideo\"\n\t            android:textColor=\"@color/white\" />\n\t    </LinearLayout>\n\n\t</RelativeLayout>\n```\n\n8、在你的 MainActivity 中添加使用摄像头的相关代码，这些代码和这个系列的教程中使用的代码是一样的。简单来说，这个 Activity 主要会完成下面的工作：\n\n- 这个 App 在安装并打开后，能够通过点击 Button 拍照/视频。\n\n- 只要照片/视频被拍下来，就会保存在移动设备的 SD 卡上\n\n- 最后， 通过传递 SD 卡中储存媒体文件的对应路径，UploadActivity 会被打开，然后执行上传的流程。\n\n**MainActivity.java**\n\n```java\n\tpackage info.androidhive.camerafileupload;\n\t \n\timport java.io.File;\n\timport java.text.SimpleDateFormat;\n\timport java.util.Date;\n\timport java.util.Locale;\n\t \n\timport android.app.Activity;\n\timport android.content.Intent;\n\timport android.content.pm.PackageManager;\n\timport android.graphics.Color;\n\timport android.graphics.drawable.ColorDrawable;\n\timport android.net.Uri;\n\timport android.os.Bundle;\n\timport android.os.Environment;\n\timport android.provider.MediaStore;\n\timport android.util.Log;\n\timport android.view.View;\n\timport android.widget.Button;\n\timport android.widget.Toast;\n\t \n\tpublic class MainActivity extends Activity {\n\t     \n\t    // LogCat tag\n\t    private static final String TAG = MainActivity.class.getSimpleName();\n\t     \n\t  \n\t    // Camera activity request codes\n\t    private static final int CAMERA_CAPTURE_IMAGE_REQUEST_CODE = 100;\n\t    private static final int CAMERA_CAPTURE_VIDEO_REQUEST_CODE = 200;\n\t     \n\t    public static final int MEDIA_TYPE_IMAGE = 1;\n\t    public static final int MEDIA_TYPE_VIDEO = 2;\n\t  \n\t    private Uri fileUri; // file url to store image/video\n\t     \n\t    private Button btnCapturePicture, btnRecordVideo;\n\t  \n\t    @Override\n\t    protected void onCreate(Bundle savedInstanceState) {\n\t        super.onCreate(savedInstanceState);\n\t        setContentView(R.layout.activity_main);\n\t         \n\t        // Changing action bar background color\n\t        // These two lines are not needed\n\t        getActionBar().setBackgroundDrawable(new ColorDrawable(Color.parseColor(getResources().getString(R.color.action_bar))));\n\t  \n\t        btnCapturePicture = (Button) findViewById(R.id.btnCapturePicture);\n\t        btnRecordVideo = (Button) findViewById(R.id.btnRecordVideo);\n\t  \n\t        /**\n\t         * Capture image button click event\n\t         */\n\t        btnCapturePicture.setOnClickListener(new View.OnClickListener() {\n\t  \n\t            @Override\n\t            public void onClick(View v) {\n\t                // capture picture\n\t                captureImage();\n\t            }\n\t        });\n\t  \n\t        /**\n\t         * Record video button click event\n\t         */\n\t        btnRecordVideo.setOnClickListener(new View.OnClickListener() {\n\t  \n\t            @Override\n\t            public void onClick(View v) {\n\t                // record video\n\t                recordVideo();\n\t            }\n\t        });\n\t  \n\t        // Checking camera availability\n\t        if (!isDeviceSupportCamera()) {\n\t            Toast.makeText(getApplicationContext(),\n\t                    \"Sorry! Your device doesn't support camera\",\n\t                    Toast.LENGTH_LONG).show();\n\t            // will close the app if the device does't have camera\n\t            finish();\n\t        }\n\t    }\n\t  \n\t    /**\n\t     * Checking device has camera hardware or not\n\t     * */\n\t    private boolean isDeviceSupportCamera() {\n\t        if (getApplicationContext().getPackageManager().hasSystemFeature(\n\t                PackageManager.FEATURE_CAMERA)) {\n\t            // this device has a camera\n\t            return true;\n\t        } else {\n\t            // no camera on this device\n\t            return false;\n\t        }\n\t    }\n\t  \n\t    /**\n\t     * Launching camera app to capture image\n\t     */\n\t    private void captureImage() {\n\t        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n\t  \n\t        fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);\n\t  \n\t        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);\n\t  \n\t        // start the image capture Intent\n\t        startActivityForResult(intent, CAMERA_CAPTURE_IMAGE_REQUEST_CODE);\n\t    }\n\t     \n\t    /**\n\t     * Launching camera app to record video\n\t     */\n\t    private void recordVideo() {\n\t        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);\n\t  \n\t        fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);\n\t  \n\t        // set video quality\n\t        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);\n\t  \n\t        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file\n\t                                                            // name\n\t  \n\t        // start the video capture Intent\n\t        startActivityForResult(intent, CAMERA_CAPTURE_VIDEO_REQUEST_CODE);\n\t    }\n\t  \n\t    /**\n\t     * Here we store the file url as it will be null after returning from camera\n\t     * app\n\t     */\n\t    @Override\n\t    protected void onSaveInstanceState(Bundle outState) {\n\t        super.onSaveInstanceState(outState);\n\t  \n\t        // save file url in bundle as it will be null on screen orientation\n\t        // changes\n\t        outState.putParcelable(\"file_uri\", fileUri);\n\t    }\n\t  \n\t    @Override\n\t    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n\t        super.onRestoreInstanceState(savedInstanceState);\n\t  \n\t        // get the file url\n\t        fileUri = savedInstanceState.getParcelable(\"file_uri\");\n\t    }\n\t  \n\t     \n\t  \n\t    /**\n\t     * Receiving activity result method will be called after closing the camera\n\t     * */\n\t    @Override\n\t    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n\t        // if the result is capturing Image\n\t        if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE) {\n\t            if (resultCode == RESULT_OK) {\n\t                 \n\t                // successfully captured the image\n\t                // launching upload activity\n\t                launchUploadActivity(true);\n\t                 \n\t                 \n\t            } else if (resultCode == RESULT_CANCELED) {\n\t                 \n\t                // user cancelled Image capture\n\t                Toast.makeText(getApplicationContext(),\n\t                        \"User cancelled image capture\", Toast.LENGTH_SHORT)\n\t                        .show();\n\t             \n\t            } else {\n\t                // failed to capture image\n\t                Toast.makeText(getApplicationContext(),\n\t                        \"Sorry! Failed to capture image\", Toast.LENGTH_SHORT)\n\t                        .show();\n\t            }\n\t         \n\t        } else if (requestCode == CAMERA_CAPTURE_VIDEO_REQUEST_CODE) {\n\t            if (resultCode == RESULT_OK) {\n\t                 \n\t                // video successfully recorded\n\t                // launching upload activity\n\t                launchUploadActivity(false);\n\t             \n\t            } else if (resultCode == RESULT_CANCELED) {\n\t                 \n\t                // user cancelled recording\n\t                Toast.makeText(getApplicationContext(),\n\t                        \"User cancelled video recording\", Toast.LENGTH_SHORT)\n\t                        .show();\n\t             \n\t            } else {\n\t                // failed to record video\n\t                Toast.makeText(getApplicationContext(),\n\t                        \"Sorry! Failed to record video\", Toast.LENGTH_SHORT)\n\t                        .show();\n\t            }\n\t        }\n\t    }\n\t     \n\t    private void launchUploadActivity(boolean isImage){\n\t        Intent i = new Intent(MainActivity.this, UploadActivity.class);\n\t        i.putExtra(\"filePath\", fileUri.getPath());\n\t        i.putExtra(\"isImage\", isImage);\n\t        startActivity(i);\n\t    }\n\t      \n\t    /**\n\t     * ------------ Helper Methods ---------------------- \n\t     * */\n\t  \n\t    /**\n\t     * Creating file uri to store image/video\n\t     */\n\t    public Uri getOutputMediaFileUri(int type) {\n\t        return Uri.fromFile(getOutputMediaFile(type));\n\t    }\n\t  \n\t    /**\n\t     * returning image / video\n\t     */\n\t    private static File getOutputMediaFile(int type) {\n\t  \n\t        // External sdcard location\n\t        File mediaStorageDir = new File(\n\t                Environment\n\t                        .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),\n\t                Config.IMAGE_DIRECTORY_NAME);\n\t  \n\t        // Create the storage directory if it does not exist\n\t        if (!mediaStorageDir.exists()) {\n\t            if (!mediaStorageDir.mkdirs()) {\n\t                Log.d(TAG, \"Oops! Failed create \"\n\t                        + Config.IMAGE_DIRECTORY_NAME + \" directory\");\n\t                return null;\n\t            }\n\t        }\n\t  \n\t        // Create a media file name\n\t        String timeStamp = new SimpleDateFormat(\"yyyyMMdd_HHmmss\",\n\t                Locale.getDefault()).format(new Date());\n\t        File mediaFile;\n\t        if (type == MEDIA_TYPE_IMAGE) {\n\t            mediaFile = new File(mediaStorageDir.getPath() + File.separator\n\t                    + \"IMG_\" + timeStamp + \".jpg\");\n\t        } else if (type == MEDIA_TYPE_VIDEO) {\n\t            mediaFile = new File(mediaStorageDir.getPath() + File.separator\n\t                    + \"VID_\" + timeStamp + \".mp4\");\n\t        } else {\n\t            return null;\n\t        }\n\t  \n\t        return mediaFile;\n\t    }\n\t}\n```\n\n如果你现在运行你的 App，你会看到类似下面这样的输出结果。\n\n![](http://cdn4.androidhive.info/wp-content/uploads/2014/12/android-file-upload-camera-screen.jpg?6141f6)\n\n![](http://cdn2.androidhive.info/wp-content/uploads/2014/12/android-file-upload-camera-taking-camera-picture.jpg?6141f6)\n\n只要你能够打开摄像头并拍着照片/视频，我们就能继续今天的学习，也就是接下来要讲的——创建实现上传功能的 Activity。\n\n9、在 res 文件夹中创建一个 activity_upload.xml 文件，这个布局提供了 ImageView、VideoView 用以预览拍下来的照片/视频，此外，还有一个 ProgressBar 被用于展现上传进度。\n\n**activity_upload.xml**\n\n```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t    android:layout_width=\"fill_parent\"\n\t    android:layout_height=\"fill_parent\"\n\t    android:background=\"@color/view_background\"\n\t    android:orientation=\"vertical\"\n\t    android:padding=\"10dp\" >\n\t \n\t     \n\t \n\t    <!-- To display picture taken -->\n\t \n\t    <ImageView\n\t        android:id=\"@+id/imgPreview\"\n\t        android:layout_width=\"fill_parent\"\n\t        android:layout_height=\"200dp\"\n\t        android:visibility=\"gone\"\n\t        android:layout_marginTop=\"15dp\"/>\n\t \n\t    <!-- Videoview to preview recorded video -->\n\t \n\t    <VideoView\n\t        android:id=\"@+id/videoPreview\"\n\t        android:layout_width=\"fill_parent\"\n\t        android:layout_height=\"400dp\"\n\t        android:visibility=\"gone\"\n\t        android:layout_marginTop=\"15dp\"/>\n\t \n\t    <TextView\n\t        android:id=\"@+id/txtPercentage\"\n\t        android:layout_width=\"wrap_content\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:layout_gravity=\"center_horizontal\"\n\t        android:layout_marginBottom=\"15dp\"\n\t        android:layout_marginTop=\"15dp\"\n\t        android:textColor=\"@color/txt_font\"\n\t        android:textSize=\"30dp\" />\n\t \n\t    <ProgressBar\n\t        android:id=\"@+id/progressBar\"\n\t        style=\"?android:attr/progressBarStyleHorizontal\"\n\t        android:layout_width=\"fill_parent\"\n\t        android:layout_height=\"20dp\"\n\t        android:layout_marginBottom=\"35dp\"\n\t        android:visibility=\"gone\"/>\n\t \n\t    <Button\n\t        android:id=\"@+id/btnUpload\"\n\t        android:layout_width=\"wrap_content\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:layout_gravity=\"center_horizontal\"\n\t        android:background=\"@color/btn_bg\"\n\t        android:paddingLeft=\"20dp\"\n\t        android:paddingRight=\"20dp\"\n\t        android:text=\"@string/btnUploadToServer\"\n\t        android:textColor=\"@color/white\"\n\t        android:layout_marginBottom=\"20dp\"/>\n\t \n\t</LinearLayout>\n```\n\n10、创建一个叫做 UploadActivity 的类，并把下面的代码复制进去。在这个类里，我们主要完成下面两项工作：\n\n- 接收 MainActivity 传过来的媒体文件路径，并把媒体文件显示在我们的视图中供以预览。\n\n- 通过 UploadFileToServer()方法我们异步处理了上传文件到服务器和更新进度条进度两项工作。\n\n**UploadActivity.java**\n\n```java\n\tpackage info.androidhive.camerafileupload;\n\t \n\timport info.androidhive.camerafileupload.AndroidMultiPartEntity.ProgressListener;\n\t \n\timport java.io.File;\n\timport java.io.IOException;\n\t \n\timport org.apache.http.HttpEntity;\n\timport org.apache.http.HttpResponse;\n\timport org.apache.http.client.ClientProtocolException;\n\timport org.apache.http.client.HttpClient;\n\timport org.apache.http.client.methods.HttpPost;\n\timport org.apache.http.entity.mime.content.FileBody;\n\timport org.apache.http.entity.mime.content.StringBody;\n\timport org.apache.http.impl.client.DefaultHttpClient;\n\timport org.apache.http.util.EntityUtils;\n\t \n\timport android.app.Activity;\n\timport android.app.AlertDialog;\n\timport android.content.DialogInterface;\n\timport android.content.Intent;\n\timport android.graphics.Bitmap;\n\timport android.graphics.BitmapFactory;\n\timport android.graphics.Color;\n\timport android.graphics.drawable.ColorDrawable;\n\timport android.os.AsyncTask;\n\timport android.os.Bundle;\n\timport android.util.Log;\n\timport android.view.View;\n\timport android.widget.Button;\n\timport android.widget.ImageView;\n\timport android.widget.ProgressBar;\n\timport android.widget.TextView;\n\timport android.widget.Toast;\n\timport android.widget.VideoView;\n\t \n\tpublic class UploadActivity extends Activity {\n\t    // LogCat tag\n\t    private static final String TAG = MainActivity.class.getSimpleName();\n\t \n\t    private ProgressBar progressBar;\n\t    private String filePath = null;\n\t    private TextView txtPercentage;\n\t    private ImageView imgPreview;\n\t    private VideoView vidPreview;\n\t    private Button btnUpload;\n\t    long totalSize = 0;\n\t \n\t    @Override\n\t    protected void onCreate(Bundle savedInstanceState) {\n\t        super.onCreate(savedInstanceState);\n\t        setContentView(R.layout.activity_upload);\n\t        txtPercentage = (TextView) findViewById(R.id.txtPercentage);\n\t        btnUpload = (Button) findViewById(R.id.btnUpload);\n\t        progressBar = (ProgressBar) findViewById(R.id.progressBar);\n\t        imgPreview = (ImageView) findViewById(R.id.imgPreview);\n\t        vidPreview = (VideoView) findViewById(R.id.videoPreview);\n\t \n\t        // Changing action bar background color\n\t        getActionBar().setBackgroundDrawable(\n\t                new ColorDrawable(Color.parseColor(getResources().getString(\n\t                        R.color.action_bar))));\n\t \n\t        // Receiving the data from previous activity\n\t        Intent i = getIntent();\n\t \n\t        // image or video path that is captured in previous activity\n\t        filePath = i.getStringExtra(\"filePath\");\n\t \n\t        // boolean flag to identify the media type, image or video\n\t        boolean isImage = i.getBooleanExtra(\"isImage\", true);\n\t \n\t        if (filePath != null) {\n\t            // Displaying the image or video on the screen\n\t            previewMedia(isImage);\n\t        } else {\n\t            Toast.makeText(getApplicationContext(),\n\t                    \"Sorry, file path is missing!\", Toast.LENGTH_LONG).show();\n\t        }\n\t \n\t        btnUpload.setOnClickListener(new View.OnClickListener() {\n\t \n\t            @Override\n\t            public void onClick(View v) {\n\t                // uploading the file to server\n\t                new UploadFileToServer().execute();\n\t            }\n\t        });\n\t \n\t    }\n\t \n\t    /**\n\t     * Displaying captured image/video on the screen\n\t     * */\n\t    private void previewMedia(boolean isImage) {\n\t        // Checking whether captured media is image or video\n\t        if (isImage) {\n\t            imgPreview.setVisibility(View.VISIBLE);\n\t            vidPreview.setVisibility(View.GONE);\n\t            // bimatp factory\n\t            BitmapFactory.Options options = new BitmapFactory.Options();\n\t \n\t            // down sizing image as it throws OutOfMemory Exception for larger\n\t            // images\n\t            options.inSampleSize = 8;\n\t \n\t            final Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);\n\t \n\t            imgPreview.setImageBitmap(bitmap);\n\t        } else {\n\t            imgPreview.setVisibility(View.GONE);\n\t            vidPreview.setVisibility(View.VISIBLE);\n\t            vidPreview.setVideoPath(filePath);\n\t            // start playing\n\t            vidPreview.start();\n\t        }\n\t    }\n\t \n\t    /**\n\t     * Uploading the file to server\n\t     * */\n\t    private class UploadFileToServer extends AsyncTask<Void, Integer, String> {\n\t        @Override\n\t        protected void onPreExecute() {\n\t            // setting progress bar to zero\n\t            progressBar.setProgress(0);\n\t            super.onPreExecute();\n\t        }\n\t \n\t        @Override\n\t        protected void onProgressUpdate(Integer... progress) {\n\t            // Making progress bar visible\n\t            progressBar.setVisibility(View.VISIBLE);\n\t \n\t            // updating progress bar value\n\t            progressBar.setProgress(progress[0]);\n\t \n\t            // updating percentage value\n\t            txtPercentage.setText(String.valueOf(progress[0]) + \"%\");\n\t        }\n\t \n\t        @Override\n\t        protected String doInBackground(Void... params) {\n\t            return uploadFile();\n\t        }\n\t \n\t        @SuppressWarnings(\"deprecation\")\n\t        private String uploadFile() {\n\t            String responseString = null;\n\t \n\t            HttpClient httpclient = new DefaultHttpClient();\n\t            HttpPost httppost = new HttpPost(Config.FILE_UPLOAD_URL);\n\t \n\t            try {\n\t                AndroidMultiPartEntity entity = new AndroidMultiPartEntity(\n\t                        new ProgressListener() {\n\t \n\t                            @Override\n\t                            public void transferred(long num) {\n\t                                publishProgress((int) ((num / (float) totalSize) * 100));\n\t                            }\n\t                        });\n\t \n\t                File sourceFile = new File(filePath);\n\t \n\t                // Adding file data to http body\n\t                entity.addPart(\"image\", new FileBody(sourceFile));\n\t \n\t                // Extra parameters if you want to pass to server\n\t                entity.addPart(\"website\",\n\t                        new StringBody(\"www.androidhive.info\"));\n\t                entity.addPart(\"email\", new StringBody(\"abc@gmail.com\"));\n\t \n\t                totalSize = entity.getContentLength();\n\t                httppost.setEntity(entity);\n\t \n\t                // Making server call\n\t                HttpResponse response = httpclient.execute(httppost);\n\t                HttpEntity r_entity = response.getEntity();\n\t \n\t                int statusCode = response.getStatusLine().getStatusCode();\n\t                if (statusCode == 200) {\n\t                    // Server response\n\t                    responseString = EntityUtils.toString(r_entity);\n\t                } else {\n\t                    responseString = \"Error occurred! Http Status Code: \"\n\t                            + statusCode;\n\t                }\n\t \n\t            } catch (ClientProtocolException e) {\n\t                responseString = e.toString();\n\t            } catch (IOException e) {\n\t                responseString = e.toString();\n\t            }\n\t \n\t            return responseString;\n\t \n\t        }\n\t \n\t        @Override\n\t        protected void onPostExecute(String result) {\n\t            Log.e(TAG, \"Response from server: \" + result);\n\t \n\t            // showing the server response in an alert dialog\n\t            showAlert(result);\n\t \n\t            super.onPostExecute(result);\n\t        }\n\t \n\t    }\n\t \n\t    /**\n\t     * Method to show alert dialog\n\t     * */\n\t    private void showAlert(String message) {\n\t        AlertDialog.Builder builder = new AlertDialog.Builder(this);\n\t        builder.setMessage(message).setTitle(\"Response from Servers\")\n\t                .setCancelable(false)\n\t                .setPositiveButton(\"OK\", new DialogInterface.OnClickListener() {\n\t                    public void onClick(DialogInterface dialog, int id) {\n\t                        // do nothing\n\t                    }\n\t                });\n\t        AlertDialog alert = builder.create();\n\t        alert.show();\n\t    }\n\t \n\t} \n```\n\n到这里，我们已经把 Android 项目构建好了，现在让我们来快速地创建一个 PHP工程以用于接收我们 App 发送过来的文件，就能看到这个项目的实际想过啦。但在那之前，我们可能需要为 WAMP Server做一点点简单的配置。\n\n## 2.安装与配置 WAMP Server ##\n\n1、下载并安装 WAMP，在 Windows 系统中，WAMP 将会被安装在 C:\\wamp location。\n\n2、打开 php,ini 文件，并修改下列值。在 WAMP Server 中，上传文件的最大值默认为 2MB，通过修改下列值，我们能够让上传文件的最大值达到 50MB。\n\n![](http://cdn3.androidhive.info/wp-content/uploads/2014/12/wamp-server-editing-php.ini-file1.png?6141f6)\n\n> upload_max_filesize = 50M\n> post_max_size = 50M\n> max_input_time = 300\n> max_execution_time = 300\n\n3、配置完成后重启 WAMP Server 即可。\n\n## 3.创建一个 PHP 项目 ##\n \n1、到 C:\\wamp\\www 目录中创建一个名为 AndroidFileUpload 的文件夹，这将会是我们 PHP 项目的根目录\n\n2、进入这个文件夹中创建一个叫作 uploads 的文件夹，用以储存所有上传到服务器的文件\n\n3、创建一个叫做 fileUpload.php 的文件，并把下面的内容复制进去。这些 PHP 代码的作用是：接收我们 Android App 上传的文件，并把它们储存在 uploads 文件夹中。一旦上传完成，服务器就会返回一个 JSON。\n\n**fileUpload.php**\n\n```php\n\t<?php\n\t \n\t// Path to move uploaded files\n\t$target_path = \"uploads/\";\n\t \n\t// array for final json respone\n\t$response = array();\n\t \n\t// getting server ip address\n\t$server_ip = gethostbyname(gethostname());\n\t \n\t// final file url that is being uploaded\n\t$file_upload_url = 'http://' . $server_ip . '/' . 'AndroidFileUpload' . '/' . $target_path;\n\t \n\t \n\tif (isset($_FILES['image']['name'])) {\n\t    $target_path = $target_path . basename($_FILES['image']['name']);\n\t \n\t    // reading other post parameters\n\t    $email = isset($_POST['email']) ? $_POST['email'] : '';\n\t    $website = isset($_POST['website']) ? $_POST['website'] : '';\n\t \n\t    $response['file_name'] = basename($_FILES['image']['name']);\n\t    $response['email'] = $email;\n\t    $response['website'] = $website;\n\t \n\t    try {\n\t        // Throws exception incase file is not being moved\n\t        if (!move_uploaded_file($_FILES['image']['tmp_name'], $target_path)) {\n\t            // make error flag true\n\t            $response['error'] = true;\n\t            $response['message'] = 'Could not move the file!';\n\t        }\n\t \n\t        // File successfully uploaded\n\t        $response['message'] = 'File uploaded successfully!';\n\t        $response['error'] = false;\n\t        $response['file_path'] = $file_upload_url . basename($_FILES['image']['name']);\n\t    } catch (Exception $e) {\n\t        // Exception occurred. Make error flag true\n\t        $response['error'] = true;\n\t        $response['message'] = $e->getMessage();\n\t    }\n\t} else {\n\t    // File parameter is missing\n\t    $response['error'] = true;\n\t    $response['message'] = 'Not received any file!F';\n\t}\n\t \n\t// Echo final json response to client\n\techo json_encode($response);\n\t?>\n```\n\nBelow is the sample JSON response if the file is uploaded successfully. You can use error value to verify the upload on android side.\n\n如果文件成功被上传，就会得到类似下面这个简单的 JSON 返回值范例。你可以使用错误代码来确认 Android 端的上传结果。\n\n```php\n\t{\n\t    \"file_name\": \"DSC_0021.JPG\",\n\t    \"email\": \"admin@androidhive.info\",\n\t    \"website\": \"www.androidhive.info\",\n\t    \"message\": \"File uploaded successfully!\",\n\t    \"error\": false,\n\t    \"file_path\": \"http://192.168.0.104/AndroidFileUpload/uploads/DSC_0021.JPG\"\n\t}\n```\n\n## 4.测试上传文件功能（本地） ##\n\n你可以通过完成下面的步骤在本地测试 App 的功能\n\n1. 运行必要的设备（这里指的是运行 WAMP server 和 Android 手机），并将它们连在同一个 WIFI 网络中\n\n2. 开启 WAMP server\n\n3. 在电脑上运行 PHP 工程，并获取本机的 IP 地址。你可以通过在控制台中输入 ipconfig 命令获得你的 IP 地址。（在 Mac Os 系统中，你需要使用 ifconfig 获得 IP 地址）\n\n4. 把 Config 类中的 IP 地址换成你的 IP 地址（创建 Android 项目的第四步）\n\n5. 把我们的 Android App 安装到手机上，并运行它\n\n![](http://cdn2.androidhive.info/wp-content/uploads/2014/12/android-uploading-camera-picture-to-server.jpg?6141f6)\n\n![](http://cdn2.androidhive.info/wp-content/uploads/2014/12/android-uploading-camera-picture-to-server1.jpg?6141f6)\n\n![](http://cdn2.androidhive.info/wp-content/uploads/2014/12/android-uploading-camera-picture-to-server2.jpg?6141f6)\n\n## 引用 ##\n\n1. [Stackoverflow](http://stackoverflow.com/questions/22874735/upload-large-file-with-progress-bar-and-without-outofmemory-error-in-android) 上有一个相关的使用进度条上传文件的问题\n\n2. 我在 App 中使用的[图标](https://www.iconfinder.com/icons/66782/file_upload_icon#size=128)"
  },
  {
    "path": "others/如何在本地搭建一个Android应用crashing跟踪系统－ACRA/readme.md",
    "content": "如何在本地搭建一个Android应用crashing跟踪系统－ACRA\n---\n>\n* 原文链接 : [How to setup ACRA, an Android Application Crash Tracking system, on your own host](http://inthecheesefactory.com/blog/how-to-install-and-use-acra-android/en)\n* 作者 : [nuunei](http://inthecheesefactory.com/blog)\n* 译者 : [sundroid](https://github.com/sundroid) \n* 校对者: [sundroid](https://github.com/sundroid) \n* 状态 :  校对完成\n\n\n在开发一款移动app时的一个事实是会有很多约束，比如硬件（CPU、RAM、Battery 等等）。如果你的代码设计不是很好，你会遇到一个非常让人头疼的问题：“Crash”,研究表明：\n\n>\n*应用崩溃时绝大多数应用使用者抱怨的问题。\n\n此外\n\n>\n* 如果应用程序联系崩溃三次，大约一半的用户将会卸载这款应用。\n\n\n崩溃跟踪系统，帮助开发者能够直接的葱用户的设备收集每一个崩溃原因，是不是发现这个功能很特殊。目前最受欢迎的崩溃跟踪系统是 [Crashlytics](http://fabric.io/)和[Parse Crash Reporting](http://blog.parse.com/2014/12/09/introducing-parse-crash-reporting-2/)，这两个系统都是完全免费的。开发者可以免费的集成他们在自己的应用中。不论什么时候app崩溃了，整个bug信息将会发送到后台，允许开发人员用最简单的方式去解决这些bug。通过这个方法，你可以在短时间内迭代一款不会影响正常使用的应用。\n\n\n然而，提供崩溃信息收集的厂商收集这些崩溃信息同时也收集了用户信息，这可能让引起大公司担心用户隐私。\n\n\n所以，这儿有没有崩溃信息跟踪系统可以让我们搭建在自己的服务器上？那么就不存在泄漏用户隐私的担忧了。当然有了，并且这个系统提供了非常简单的搭建方法。在这里我们来介绍下[Application Crash Reporting on Android (ACRA)](https://www.acra.gov.sg/home/),一个库允许Android应用自动地发送崩溃信息到自己的服务器。\n\n下面将会介绍如何去搭建。\n\n## 搭建一个服务器 ##\n\n服务器端是一个先决条件，让我们先从搭建服务器端开始。\n\n由于ACRA设计的很好并且很受欢迎。它允许开发者开发自己的服务器系统，并且现在我们可以看到很多这样的系统。即便如此我觉得最好的是Acralyzer，这个也是由ACRA团队研发。Acralyzer工作在Apache CouchDB，所以\n这里没有必要安装除了CouchDB以外的软件。\n\nAcralyzer是一个功能相当齐全的后端崩溃跟踪系统。来自不同原因的相同堆栈轨迹将会被分组成一个单一的问题。如果你解决了所有问题，你可以非常便捷的关闭Acralyzer服务，并且这种关闭服务的操作时实时的，我发现系统唯一的缺点是它的ui让人感到不舒服，但是谁会在乎这个？它是为开发者开发的。\n\n安装起来也很简单，下面将介绍如何在Ubuntu安装Acralyzer。\n\n打开命令窗口，开始安装couchdb\n>\n*apt-get install couchdb\n\nTest the installation with this command:\n\n测试是否安装成功。\n>\n*curl http://127.0.0.1:5984\n\n如果正确安装，会显示如下：\n>\n*{\"couchdb\":\"Welcome\",\"version\":\"1.2.0\"}\n\n编辑etc/couchdb/local.ini允许我们通过外部IP(默认的访问会通过127.0.0.1)去访问CouchDB。仅仅改变两行实现这个功能：\n>\n*;port = 5984\n*;bind_address = 127.0.0.1\n\n\n改变为\n>\n*port = 5984\n*bind_address = 0.0.0.0\n\n在同一个文件夹下，你需要添加username/password作为管理员账户。找到这一行（应该会在文件末尾）\n>\n*[admins]\n\n下一行添加username/password 形式为username = password，比如：\n>\n*[nuuneoi = 12345]\n\n\n请不要对在这里书写明文密码感到担心，一旦CouchDB重启后，你的密码将会自动地散列，并且将会是不可读的，\n\n保存你刚刚编辑的文件同时通过命令行重启hashed：\n>\n*curl -X POST http://localhost:5984/_restart -H\"Content-Type: application/json\"\n\n从现在起，你将可以通过浏览器访问CouchDB。这个web服务我们称之为Futon，一个CouchDB UI管理后台。在你的浏览器中打开这个地址。\n\n>\n*http://<YOUR_SERVER_IP>:5984/_utils\n\n让我们开始吧，Futon。\n![](http://inthecheesefactory.com/uploads/source/acra/futon.png)\n\n\n首先，通过你之前设置的管理员账号登陆这个系统。\n\n现在我们开始安装一个acro-storage (Acralyzer's Storage Endpoing).在左边的菜单，点击Replicator，然后填写远程存储改为本地存储的表单。\n>\n*from Remote Database: http://get.acralyzer.com/distrib-acra-storage to Local Database: acra-myapp\n\n点击Replicate然后等待，知道这个过程结束。\n\n下一步安装Acralyzer通过同样的方法，但是参数是不同的。\n>\n*from Remote Database: http://get.acralyzer.com/distrib-acralyzer to Local Database: acralyzer\n\n点击Replicate安装。\n\n如果你操作正确，系统将会有两个数据库，acra-myapp 和 acralyzer。\n\n![](http://inthecheesefactory.com/uploads/source/acra/acra3.png)\n\n我门就快大功告成了，下一步，我们需要为这个客户端创建一个用户，打开浏览器，然后打开这个网址：\n>\n*http://<YOUR_SERVER_IP>:5984/acralyzer/_design/acralyzer/index.html\n![](http://inthecheesefactory.com/uploads/source/acra/admincreateuser.png)\n\n填写你想要的Username/Password，然后点击Create User，这些信息将会出现。\n![](http://inthecheesefactory.com/uploads/source/acra/users2.png)\n\n复制这些信息，然后粘贴到你的文本编辑器，我们可能会用这个在客户端设置。\n\n最后一件事是限制访问权限来保护在acra-myapp里面的数据，进入acra-myapp然后点击Securities，填写用户角色分配；\n>\n*[\"reader\"]\n![](http://inthecheesefactory.com/uploads/source/acra/reader.png)\n\n完工！\n在这些结束后，你可以通过同一个网址访问这个控制台，去Admin选项卡，并选择Users。\n\n>\n*[http://<YOUR_SERVER_IP>:5984/acralyzer/_design/acralyzer/index.html\n\n请注意acro-myapp只能够为一款应用服务。以防你想为另外一款应用创建一个后台，请通过同样的过程复制另外一个acro-storage，但是改变本地数据库名为acra-<your_app_name>。请注意，有必要去通过acra- 去开启服务，或者它不能够在仪表盘中罗列为选择项供我们去选择。\n\n如果在系统中有不止一款应用，在Acralyzer的仪表盘中将会有一个下拉列表，让我们去选择看哪一个的问题。你可以试一试。\n\n在客户端设置ACRA。\n\n在客户端中设置ACRA很简单，首先，在你的 build.gradle里添加ACRA的依赖配置信息。\n>\n*compile 'ch.acra:acra:4.6.1'\n\n同步你的gradle文件，然后创建一个自定义Application类，但是不要忘记在AndroidManifest.xml中定义这个Application类。（我假设每一个Android开发者不会忘记这么做）。\n\n在你创建的自定义的Application类中添加 @ReportCrashes注解。\n\n```java\nimport android.app.Application;\nimport org.acra.ACRA;\nimport org.acra.annotation.ReportsCrashes;\nimport org.acra.sender.HttpSender;\n \n/**\n * Created by nuuneoi on 2/19/2015.\n */\n \n@ReportsCrashes(\n)\npublic class MainApplication extends Application {\n \n    @Override\n    public void onCreate() {\n        super.onCreate();\n \n        ACRA.init(this);\n    }\n \n}\n```\n\n现在我们复制服务器端生成的信息，并且像下面那样粘贴到@ReportsCrashes中。\n\n```java\n\t@ReportsCrashes(\n    httpMethod = HttpSender.Method.PUT,\n    reportType = HttpSender.Type.JSON,\n    formUri = \"http://YOUR_SERVER_IP:5984/acra-myapp/_design/acra-storage/_update/report\",\n    formUriBasicAuthLogin = \"tester\",\n    formUriBasicAuthPassword = \"12345\"\n)\n```\n\n最后一步，不要忘记添加在AndroidManifest.xml网络访问权限，否则ACRA可能无法发送这些日志信息到你的服务器上。\n>\n*<uses-permission android:name=\"android.permission.INTERNET\"/>\n\n\n恭喜，现在所有的配置都已经完成，ACRA可以正常的工作，帮助你收集崩溃日志信息，从而你可以快速解决应用出现的问题。\n\n##测试##\n\n现在我们通过在Activity中强制一些崩溃来做一些测试，例子如下： \n\n```java\nextView tvHello;\n \n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n \n    tvHello.setText(\"Test Crash\");\n}\n```\n\n运行你的应用，然后改变崩溃的原因，再运行一次。查看你的仪表盘，你将会看到这些发送到后台的bug。\n\n![](http://inthecheesefactory.com/uploads/source/acra/acra.png)\n\n每一个bug来自不同用户不同时间，并且这些报告被分组了。\n![](http://inthecheesefactory.com/uploads/source/acra/reportslist.png)\n\n仔细看看这些报告信息，你将会发现他们都是完整的崩溃信息。\n![](http://inthecheesefactory.com/uploads/source/acra/stacktrace.png)\n\n并且非常多的信息，足足有7页。\n\n如果你修复这些bug后，你可以关闭这个问题，通过简单的点击在页面中高亮显示的\"bug\"图标，\n![](http://inthecheesefactory.com/uploads/source/acra/closeissue.png)\n\n希望这篇文章对你们有用，特别是对于一些需要应用崩溃信息收集但是却担心隐私信息的大公司可以来使用这个系统。\n\n事实上ACRA还有许多其他特性，比如：当一个月崩溃时显示Toast 或者 popup 来报告这些信息。你可以在ACRA网站上发现这些选项。\n\nAcralytics也一样，这里有许多其他特性可以使用，比如，你可以设置一个服务器来发送邮件给我们。\n\n下一篇博客再见。\n\n\n\t\n\n\n\n\n\n\n"
  },
  {
    "path": "others/深入浅出Android 新特性-Transition-Part-1/readme.md",
    "content": "开始使用 Transitions（过渡动画） (part 1)\n---\n\n>\n* 原文链接 : [Getting Started with Activity & Fragment Transitions (part 1)][source-url]\n* 作者 : [Alex Lockwood](https://plus.google.com/+AlexLockwood)\n* 译者 : [tiiime](https://github.com/tiiime)\n* 校对者: [chaossss](https://github.com/chaossss)  \n* 状态 :  校对完成\n\n\n\n\n##首先\n这篇文章主要介绍 Android 5.0 新加入的 Transition (过渡动画) API，这是这个系列的第一篇文章。主要介绍下面几个话题:\n\n\n- Part 1: [在 Activity 和 Fragment 中使用 Transition ][part-1]\n- Part 2: [深入理解 Transition][part-2]\n- Part 3a: [深入理解共享元素的 Transition][part3a]\n- Part 3b:  [延迟共享元素的 Transition][part-3b]\n- Part 3c: 共享元素回调实践 (coming soon!)\n- Part 4:  Activity & Fragment 过渡动画示例(coming soon!)\n\n今天这篇文章是 Transition 的概述，同时也象征着这个专栏的开始，希望大家喜欢啦。\n\n#先说下什么是 Transition(过渡动画).\nLollipop 中 Activity 和 Fragment 的过渡动画是基于 Android 一个叫作 Transition 的新特性实现的。\n初次引入这个特性是在 KitKat 中，Transition 框架提供了一个方便的 API 来构建应用中不同 UI 状态切换时的动画。\n这个框架始终围绕两个关键概念:场景和过渡。\n**场景** 描述应用中 UI 的状态，[**过渡**][transition] 确定两个场景转换之间的过渡动画。\n\n当场景转换，Transition 的主要职责是：\n\n1. 捕获每一个 View 的起始和结束状态\n2. 根据这些数据来创建从一个场景到另一个场景间的过渡动画。\n\n下面是一个简单示例，当用户点击，我们需要 Activity 的 View 视图产生消失和出现的效果。使用 Transition ，实现这个需求只要几行代码，代码如下：<a id=\"1\" href=\"#b1\">(1)</a>\n\n```java\npublic class ExampleActivity extends Activity implements View.OnClickListener {\n    private ViewGroup mRootView;\n    private View mRedBox, mGreenBox, mBlueBox, mBlackBox;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        mRootView = (ViewGroup) findViewById(R.id.layout_root_view);\n        mRootView.setOnClickListener(this);\n\n        mRedBox = findViewById(R.id.red_box);\n        mGreenBox = findViewById(R.id.green_box);\n        mBlueBox = findViewById(R.id.blue_box);\n        mBlackBox = findViewById(R.id.black_box);\n    }\n\n    @Override\n    public void onClick(View v) {\n        TransitionManager.beginDelayedTransition(mRootView, new Fade());\n        toggleVisibility(mRedBox, mGreenBox, mBlueBox, mBlackBox);\n    }\n\n    private static void toggleVisibility(View... views) {\n        for (View view : views) {\n            boolean isVisible = view.getVisibility() == View.VISIBLE;\n            view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE);\n        }\n    }\n}\n```\n\n为了更好地理解底层中发生了什么，我们一步一步地分析下这段代码，首先假设屏幕上的所有的 View 都是**可见**的:\n\n1. 首先，点击按钮后调用了 [beginDelayedTransition()][beginDelayedTransition]，\n将根场景和[Fade][fade] Transition对象（淡入/淡出过渡效果）作为参数传递出去。框架立即对场景中所有 View 调用 Transitions 的 [captureStartValues()][captureStartValues] 方法，同时， Transitions 将记录每个 View 的可见性。\n\n2. 调用结束后，开发者将场景中所有 View 设置为**不可见**的。\n\n3. 在下一个画面，框架对场景中所有 View(近期更新的) 调用 Transitions 的[captureEndValues()][captureEndValues]\n方法， Transitions 记录可见性。\n\n4. 框架调用 Transitions 的 [createAnimator()][createAnimator] 方法。Transition 分析每一个 View 的起始/结束状态，注意到 View 的可见性发生了变化。之后 **Fade** 对象利用这些信息创建了一个**AnimatorSet** 对象，并将其返回到框架中，进而将每个 View 的 **alpha** 值渐变到 **0f**。\n\n5. 框架运行返回的**动画**,让所有 View 从屏幕中淡出。\n\n这个例子强调了 Transition 框架的两个优点：第一，**Transition** 将开发人员所需要的**动画**概念抽象，减少了 Activity 和 Fragment 内的代码复用，使得我们只要设置好 View 的 起始 和 结束 时的状态，就能通过 Transition 自动创建动画。第二，只要更换 **Transition** 对象就可以修改两个场景间的动画。\n\n[ 示例 **Video 1.1**](http://www.androiddesignpatterns.com/assets/videos/posts/2014/12/04/trivial-opt.mp4),只要少量代码就可以创建复杂的动画效果。\n后续文章会介绍如何做到。\n\n# Lollipop 中的 Activity & Fragment Transitions\n在 Android 5.0 中， 切换 **Activitys** 或者 **Fragments** 时可以使用 **Transitions** 来构建精致的过场动画。虽然在之前的版本中已经引入 Activity 和 Fragment 的切换动画(通过 [Activity#overridePendingTransition()][overridePendingTransition] 和 [FragmentTransaction#setCustomAnimation()][setCustomAnimations] 方法实现)，但是动画的对象只能是**Activity/Fragment**整体。而新的 API 将这个特性延伸，使我们可以为每个 View 单独设置动画，甚至可以在两个独立的 Activity/Fragment 容器内共享某些 View的动画。\n\n接下来介绍些术语。注意，虽然下面是以 Activity 为例，但是在 Fragment 中这些术语也同样有效:\n\n>假设 **A** 和 **B** 是两个 Activity，通过 **A** 来启动 **B**。\n>**A** 叫做 \"调用Activity\"(调用 `startActivity()` 的那个)\n>**B** 就是 \"被调用Activity\"\n\nActivity transition API 是围绕退出，进入，返回还有重入过渡动画效果构建的。根据之前的定义我们可以这样描述它们:\n\n>Activity **A** 的 退出 Transition 确定 **A** 启动 **B** 时 **A** 中 View 的动画\n\n>Activity **B** 的 进入 Transition 确定 **A** 启动 **B** 时 **B** 中 View 的动画\n\n>Activity **B** 的 返回 Transition 确定 **B** 返回 **A** 时 **B** 中 View 的动画\n\n>Activity **A** 的 重入 Transition 确定 **B** 返回 **A** 时 **A** 中 View 的动画\n\n最后，Transition 框架提供了 **Content(内容)**和**共享元素(Shared Element)** 两种类型的Activity过渡动画，每个都可以让我们以独特的方式自定义 Activity 切换间的动画\n\n>**Content(内容) Transition** 确定了非共享元素如何 进入/退出 Activity 场景\n\n>**共享元素(Shared Element) Transition** 确定了两个Activity 共享 View (也被叫做主角视图)的动画效果。\n\n[Video 1.2](http://www.androiddesignpatterns.com/assets/videos/posts/2014/12/04/news-opt.mp4)这段视频很好的解释了 Content Transition 和 共享元素 Transition，我猜想它使用了下面的过渡动画。\n\n- **A**(调用Activity) 的**退出**和**重新进入** Content Transition 都是 **null**。因为用户退出和重新进入时 Activity A中的非共享视图没有动画效果。<a id=\"2\" href=\"#b2\">(2)</a>\n\n\n- **B**(被调用Activity) 的**进入** Content Transition 使用了一个自定义的 Slide Transition 将list item从底部移至屏幕中。\n\n- Activity **B** 的**返回** Content Transition是一个 **TransitionSet**，同时进行两个子 Transition:一个Slide (Gravity.TOP) Transition\n针对Activity上半部分的View，一个Slide (Gravity.BOTTOM) Transition 针对Activity 下半部分View。当用户点击按钮返回Activity A，Activity呈现一种断成两半的感觉。\n\n- 共享元素的进入和退出 Transition 都是 **ChangeImageTransform**，使ImageView过渡动画可以在两个Activity间无缝衔接。\n\n你可能也注意到了在共享元素 Transition 下还有一个圆形的过渡动画(circular reveal)，我们会在将来的章节中介绍它是如何实现的。现在，我们来继续了解 Activity 和 Fragment transition APIs\n\n#介绍Activity Transition API\n\n使用 Lollipop 的 APIs 创建一个 Activity 过渡动画 非常简单，下面的总结是实现一个过渡动画的必要步骤。在接下来的文章中我们还会介绍很多提升水平的用例，不过现在先让我们来入个门:\n\n- 在你的A(调用Activity)和B(被调用Activity)的 `.java` 文件或者\n`xml`<a id=\"3\" href=\"#b3\">(3)</a>布局中请求启用\n[`Window.FEATURE_ACTIVITY_TRANSITIONS`][FEATURE_ACTIVITY_TRANSITIONS] 窗口特性，\n使用Material主题的应用默认已开启。\n\n- 为A和B单独设置 [**exit**][exit] 和 [**enter**][enter] Content Transition 。\nMaterial主题的 [**exit**][exit] 和 [**enter**][enter] Content Transition 默认分别是\n`null`和`Fade`。如果没有明确定义 [**reenter**][reenter] 或 [**return**][return]\nContent Transition 将会使用 Activity 的 [**exit**][exit] 和 [**enter**][enter]\n Transition 来代替。\n\n- 为 A 和 B 设置 [**exit**][exit] 和 [**enter**][enter] 共享元素 Transition。\nMaterial主题中共享元素默认设置 [`@android:transition/move`][move] 作为\n[**exit**][exit] 和 [**enter**][enter] 过渡动画。如果没有明确定义\n[**reenter**][reenter] 和 [**return**][return] 的过渡动画将会使用 Activity 的\n[**exit**][exit] 和 [**enter**][enter] 过渡动画作为替代。\n- 启动一个包含 Content Transition 和 共享元素 Transition 的 Activity 时要调用\n`startActivity(Context, Bundle) `方法，其中第二参数 Bundle 通过下面这段代码获得：\n \n\t```java\n\tActivityOptions.makeSceneTransitionAnimation(activity, pairs).toBundle();\n\t``` \n\n**pairs** 是一个 **Pair< View, String >** 数组，记录Activity间<a id=\"4\" href=\"#b4\">(4)</a> 共享元素的View 和 相对应的特征字符串。别忘了在[程序][setTransitionName]中或 [xml][xml] 文件里给共享元素设置不重复的名称，否则过渡动画不会正常运行。\n\n- 通过启动程序返回一个 Transition，调用 **finishAfterTransition()** 代替 **finish()**。\n\n- Material主题应用默认会在他们的**退出/重入** Transition 完成前一点点启动**进入/返回** Content Transition，这样会在两个动画间产生一些重叠，让过渡动画更好看。如果你想关闭这个特性可以调用 [ setWindowAllowEnterTransitionOverlap()][setAllowEnterTransitionOverlap] 和 [setWindowAllowReturnTransitionOverlap()][setAllowReturnTransitionOverlap] 方法或者在xml文件里给定适当的属性\n\n##Fragment 的 Transition API\n\n如果你使用 Fragment 的 transition API，大部分 API 相似，但是会有一些小的不同:\n\n- Content 的[退出][exit]，[进入][enter]，[重入][reenter]和[返回][return] 过渡动画应该在 Fragment 的`.java`文件中调用对应的方法或者在 xml 属性声明里设置。\n\n- 共享元素 的[进入][enter]和 [返回][return] 过渡动画应该在 Fragment 的`.java`文件中调用对应的方法或者在 xml 属性声明里设置。\n\n- 鉴于Activity的 Transition 是通过调用 **startActivity()** 和 **finishAfterTransition()** 直接启动的,Fragment 的过渡是在 Fragment\n被add, remove, attach, detach, show,或 hidden 后由 FragmentTransaction 自动启动。\n\n- 共享元素应该在transaction(事务)提交前调用[`addSharedElement(View, String)`][addSharedElement]声明为 **FragmentTransaction** 的一部分。\n\n##结语\n\n这篇文章里我们只是简单的介绍了 Activitiy 和 Fragment transition API，但是在接下来的文章你会发现扎实的基础给你带来的好处，尤其是在讲到**自定义过渡动画**时。后面我们会非常深入的讲解 Content Transition 和 共享元素 Transition，让你更加了解 Activity 和 Fragment 背后的工作。\n\n希望你喜欢我的文章，感谢观看～\n\n1. 如果你想尝试这个例子，这里有[xml代码][xmlcode] <a id=\"b1\" href=\"#1\">↩</a>\n\n2. 第一眼看上去可能感觉是Activity A fade in/out 屏幕, 事实上是Activity B 在 Activity A 的上面渐变. A 中的 View 事实上是没有动画的. 你可以在被调用 Activity 的 Window 中使用 [setTransitionBackgroundFadeDuration()][setTransitionBackgroundFadeDuration] 方法调节背景渐变持续时间。 <a id=\"b2\" href=\"#2\">↩</a>\n\n3. 了解更多关于 **FEATURE_ACTIVITY_TRANSITIONS** 和 **FEATURE_CONTENT_TRANSITIONS** 窗口特性的不同可以看[这里StackOverflow Post][window-feature]<a id=\"b3\" href=\"#3\">↩</a>\n\n4. 启动一个包含Content Transition 而不是共享元素 Transition 的Activity,可以这样创建**Bundle**\n\n\t```java\n\tActivityOptions.makeSceneTransitionAnimation(activity).toBundle()\n\t```\n如果想完全禁用Content Transition 和 共享元素 Transition 可以将 Bundle 设为 **null**. <a id=\"b4\" href=\"#4\">↩</a>\n\n\n[source-url]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html\n\n[part-1]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html\n[part-2]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html\n[part3a]:http://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html\n[part-3b]:http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html\n\n[beginDelayedTransition]:https://developer.android.com/reference/android/transition/TransitionManager.html#beginDelayedTransition(android.view.ViewGroup,%20android.transition.Transition)\n[captureStartValues]:https://developer.android.com/reference/android/transition/Transition.html#captureStartValues(android.transition.TransitionValues)\n[captureEndValues]:https://developer.android.com/reference/android/transition/Transition.html#captureEndValues(android.transition.TransitionValues)\n\n[createAnimator]:https://developer.android.com/reference/android/transition/Transition.html#createAnimator(android.view.ViewGroup,%20android.transition.TransitionValues,%20android.transition.TransitionValues)\n[video1.1]:http://www.androiddesignpatterns.com/assets/videos/posts/2014/12/04/trivial-opt.mp4\n[video1.2]:http://www.androiddesignpatterns.com/assets/videos/posts/2014/12/04/news-opt.mp4\n[FEATURE_ACTIVITY_TRANSITIONS]:http://developer.android.com/reference/android/view/Window.html#FEATURE_ACTIVITY_TRANSITIONS\n[move]:https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/res/res/transition/move.xml\n[fade]:https://developer.android.com/reference/android/transition/Fade.html\n[transition]:https://developer.android.com/reference/android/transition/Transition.html\n\n[overridePendingTransition]:http://developer.android.com/reference/android/app/Activity.html#overridePendingTransition(int,%20int)\n[setCustomAnimations]:http://developer.android.com/reference/android/app/FragmentTransaction.html#setCustomAnimations(int,%20int,%20int,%20int)\n\n\n[exit]:https://developer.android.com/reference/android/view/Window.html#setExitTransition(android.transition.Transition)\n[enter]:https://developer.android.com/reference/android/view/Window.html#setEnterTransition(android.transition.Transition)\n[reenter]:https://developer.android.com/reference/android/view/Window.html#setSharedElementReenterTransition(android.transition.Transition)\n[return]:https://developer.android.com/reference/android/view/Window.html#setReturnTransition(android.transition.Transition)\n[addSharedElement]:https://developer.android.com/reference/android/app/FragmentTransaction.html#addSharedElement(android.view.View,%20java.lang.String)\n[setTransitionName]:https://developer.android.com/reference/android/view/View.html#setTransitionName(java.lang.String)\n[xml]:https://developer.android.com/reference/android/view/View.html#attr_android:transitionName\n[setAllowEnterTransitionOverlap]:http://developer.android.com/reference/android/view/Window.html#setAllowEnterTransitionOverlap(boolean)\n[setAllowReturnTransitionOverlap]:http://developer.android.com/reference/android/view/Window.html#setAllowReturnTransitionOverlap(boolean)\n[xmlcode]:https://gist.github.com/alexjlockwood/a96781b876138c37e88e\n[window-feature]:http://stackoverflow.com/questions/28975840/feature-activity-transitions-vs-feature-content-transitions\n[setTransitionBackgroundFadeDuration]:http://developer.android.com/reference/android/view/Window.html#setTransitionBackgroundFadeDuration(long)\n"
  },
  {
    "path": "others/深入浅出Android 新特性-Transition-Part-2/readme.md",
    "content": "`深入理解Content Transition  (part 2)`\n---\n\n>\n* 原文链接 :  [Content Transitions In-Depth (part 2)][source-url]\n* 译者 : [tiiime](https://github.com/tiiime)\n* 校对者: [chaossss](https://github.com/chaossss)\n* 状态 :  完成\n\n\n\n#深入理解Content Transition\n\n这篇文章会深度分析 Content Transitions 和它在 Activity & Fragment Transitions API 中的作用。这篇文章是下面这个系列中的第二篇：\n\n- Part 1: [在 Activity 和 Fragment 中使用 Transition ][part-1]\n- Part 2: [深入理解 Content Transition][part-2]\n- Part 3a: [深入理解共享元素 Transition][part3a]\n- Part 3b:  [延迟共享元素的 Transition][part-3b]\n- Part 3c: 共享元素回调实践 (coming soon!)\n- Part 4:  Activity & Fragment 过渡动画示例(coming soon!)\n\n我们先来总结下在 [part 1][part-1] 中提到的关于 Content Transitions 的知识点，然后\n说一说在 Android Lollipop 中是怎样使用它来构建合适的过渡动画。\n\n---\n\n##Content Transition 是什么？\n\nContent transition 决定了非共享元素 View (也称为 transitioning view) 在\nActivity & Fragment 过渡期间是如何进入或退出场景的。出于 Google 新的设计语言\n[Material Design][md-design] , content transitions 允许我们协调 Activity/Fragment 中每一个\n view 的进入和退出 transition，轻松搞定流畅的屏幕切换动作。\n开始使用 Android Lollipop，调用下面这些[ Window ][window] 和\n[ Fragment ][fragment] 方法可以通过程序设置 content transitions :\n\n\n- **setExitTransition()** - 当 A **启动** B 时， **A** 中 views **离开**场景的退出过渡动画\n\n- **setEnterTransition()** - 当 A **启动** B 时， **B** 中 views **进入**场景的进入过渡动画\n\n- **setReturnTransition()** - 当 B **返回** A 时， **B** 中 views **离开**场景的返回过渡动画\n\n- **setReenterTransition()** - 当 B **返回** A 时， **A** 中 views **进入**场景的重入过渡动画\n\n(注:A 和 B 是Activity 见 [part 1][part-1])。\n\n[**Video 2.1**][video2.1] - Content transitions in the Google Play Games app (as of v2.2). Click to play.\n\n---\n\n\n[**Video 2.1**][video2.1]作为示例阐明了在 Google Play Games app 中是如何使\n用 content transitions 实现流畅的 activitiy 切换动画。当第二个 activity 启动，\n它的**进入** content transition 轻轻地从屏幕底部边缘将头像推入场景。按下返回按钮后，\n第二个 activity 的**返回** content transition 将图层分成上下两块，分别推出屏幕。\n\n\n---\n\n目前为止我们对 content transitions 的分析仅仅停留在表面，一些重要的问题仍然没有涉及。\n例如 Content Transition 在底层是如何实现的？都有哪些类型的 **Transition** 对象可以使用?框架是如何确定哪些 view\n是 transitioning view? 在 content transitions 中一个 **ViewGroup** 和它的子视图能不能当成一个整体\n执行动画?下面我们就来一个一个解答这些问题。\n\n---\n\n##Content Transitions 底层深入\n\n上篇文章我们说过 Transition 的两个主要职责分别是获取目标视图的开始结束状态和创建这两个状态间的过渡动画。同样，框架必须先改变每个 transitioning view 的可见性并将状态信息交给 content\ntransition ，它才能创建过渡动画。更准确的说，当 Activity **A** 启动 Activity **B** 将会出现以下事件：<a id=\"1\" href=\"#b1\">(1)</a>\n\n1. Activity **A** 调用 **startActivity()**\n\n\t-  框架首先会遍历 **A** 的 view 层次结构，确定当 **A** 的退出 transition 运行后有哪些\n\ttransitioning views 会退出场景。\n\t- **A** 的退出 transition 捕获 A 中 transitioning views 的起始状态。\n\t- 框架将 **A** 中所有 transitioning views 设置为**不可见**。\n\t- 在下一个画面中，**A** 的退出 transition 捕获 **A** 中所有 transitioning views 的结束状态。\n\t- **A** 的退出 transition 比较每一个 transitioning view 开始和结束状态的不同，\n\t并基于这些信息创建一个 **Animator**，最后运行 **Animator** 将所有 transitioning views\n\t移出场景。\n\n2. Activity **B** 被启动\n\t- 框架遍历 **B** 的 view 层次结构， 确定当 B 的进入 transition 运行后有哪些\n\t   transitioning views 会进入场景。\n\t-  **B** 的进入 transition 捕获 B 中 transitioning views 的起始状态。\n\t-  框架将 **B** 中所有  transitioning views  设置为**可见**\n\t-  在下一个画面中，**B** 的进入 transition 捕获 **B** 中所有 transitioning views 的结束状态。\n\t-  **B** 的进入 transition 比较每一个 transitioning view 开始和结束状态的不同，\n\t并基于这些信息创建一个 **Animator**，最后运行 **Animator** 将所有 transitioning views\n\t移入场景。\n\n---\n\n框架通过在**可见**和**不可见**之间切换每个 transitioning view 的可见性来保证\ncontent transition 能够获得用来构建目标动画所需要的状态信息。\n显然所有的 content **Transition** 对象至少要能够获取和记录每个 transitioning view\n起始和结束状态的可见性。还好 [Visibility][Visibility] 这个抽象类已经提供了这个功能 :\n需要创建并返回 (让 view 进入/退出场景的) **Animator** 的 **Visibility**\n子类只需要实现 [**onAppear()**][onAppear]  和 [**onDisappear()**][onDisappear] 这两个工厂方法。\n从 API 21 开始，有三个已经写好的 **Visibility** 实现( [**Fade**][Fade], [**Slide**][slide] 和 [**Explode**][explode])，可以使用它们来构建  Activity 和 Fragment 的 content transitions。\n有必要的话也可以自己实现 Visibility 类达到想实现的效果。后边的文章会有具体介绍。\n\n---\n\n##Transitioning Views 和 Transition Groups\n直到现在，我们已经假设 content transitions 操作一组叫做 transitioning views 的非共享 view 。\n在这节中，我们将探讨 Transition 框架如何确定哪些 View 是非共享 View，以及如何使用 transition group 深度定制框架\n\nTransition 开始前，框架会在 Activity 窗口(或 Fragment) 的视图层上执行一个递归的搜索，用来\n构建 transitioning views 的集合。这个搜索通过对图层的根视图递归调用重写的**ViewGroup#captureTransitioningViews** 方法启动，部分[源码][source-code]如下:\n\n```java\n/** @hide */\n@Override\npublic void captureTransitioningViews(List<View> transitioningViews) {\n    if (getVisibility() != View.VISIBLE) {\n        return;\n    }\n    if (isTransitionGroup()) {\n        transitioningViews.add(this);\n    } else {\n        int count = getChildCount();\n        for (int i = 0; i < count; i++) {\n            View child = getChildAt(i);\n            child.captureTransitioningViews(transitioningViews);\n        }\n    }\n}\n```\n\n---\n\n\n[**Video 2.2**][video2.2] - A simple Radiohead app that illustrates a potential bug involving transition groups and WebViews. Click to play.\n\n---\n\n这个递归调用很简单: 框架遍历树的每一层，直到找到一个**可见的**[ leaf view ][leafview] (子视图)或者一个 transition group。Transition groups 本质上允许我们在 Activity/Fragment 的 transition\n期间将全部 **ViewGroups** 当作一个整体执行过渡动画。如果一个 **ViewGroup** 的\n[`isTransitionGroup ()`][isTransitionGroup] <a id=\"2\" href=\"#b2\">(2)</a>方法返回值为 **true**，它和它的子视图会被当作一\n个整体来执行过渡动画。否则，这个递归搜索会继续执行下去， 这个 ViewGroup 的子视图在动画期间\n会执行自己的独立的过渡动画。搜索最终会返回一个全部由 content transition\n 执行动画的 transitioning views 集合 。<a id=\"3\" href=\"#b3\">(3)</a>\n\n---\n\n上面的 [**Video 2.1**][video2.1] 展示了 transition groups  的效果。在 enter transition ，\n用户头像是作为一个单独的 View 进入屏幕，return transition 时却是和包含它的 parent\n**ViewGroup** 一起消失。在  Google Play Games 里可能用了一个 transition group\n来实现在返回前一个 activity 时，让当前场景拦腰斩断的效果。\n\n---\n\n有时 transition groups 还被用来修复 Activity/Fragment transitions 中诡异的 bugs。\n例如，Video 2.2中: **调用 Activity** 显示了一个电台司令专辑图的网格布局，\n**被调用 Activity** 展示了一个背景标题图，一个共享元素专辑封面图还有一个 **WebView**。\n这个 App 使用了一个和 Google Play Games app 类似的 return transition，将背景图和底部的\n**WebView** 分别推出屏幕。然而这里有个小故障导致 **WebView** 不能流畅的退出屏幕。\n\n好吧，错误在哪呢？原来 **WebView** 是一个 **ViewGroup**，因此在默认情况下 WebView 不会被当作 transitioning view\n的。当 return transition 被执行时，**WebView** 会被完全忽略，直到过渡动画结束才会被移除屏幕。\n找到问题就好解决了，只要在 return transition 开始前调用 `webView.setTransitionGroup(true)`\n就能修复这个bug。\n\n---\n\n##结语\n\n总之，这篇文章讲了三个重点:\n\n-  Content transition 决定了 Activity/Fragment 中非共享元素视图(被称为 transitioning views)\n\t在 Activity/Fragment transition 期间如何进入或退出场景。\n- Content transitions 被触发是因为它的 transitioning views 可见性改变  ，并且应该总是继承\n\t**Visibility** 这个抽象类。\n- Transition groups 可以让我们在 content transition 期间将 **ViewGroups**\n\t当作一个整体执行过渡动画。\n\n希望这篇文章能够帮到你，欢迎留下评论～\n\n---\n\n1. Activities 和 Fragments 在  return/reenter transitions 期间出现的一系列事件相似。\n\t<a id=\"b1\" href=\"#1\">↩</a>\n2. 如果 ViewGroup 有一个非空的  background drawable 或者非空的默认 transition name 那么\n\t`isTransitionGroup()` 将返回 **true** (所述方法[文档][isTransitionGroup])\n\t<a id=\"b2\" href=\"#2\">↩</a>\n3. 当 transition 运行时，任何在 content **Transition** 对象中被明确地 [ added ][addTarget]或\n\t[ excluded ][excludeTarget] 的 view 也会被考虑。<a id=\"b3\" href=\"#3\">↩</a>\n\n\n[source-url]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html\n\n[part-1]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html\n[part-2]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html\n[part3a]:http://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html\n[part-3b]:http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html\n[md-design]:http://www.google.com/design/spec/animation/meaningful-transitions.html\n[window]:http://developer.android.com/reference/android/view/Window.html\n[fragment]:http://developer.android.com/reference/android/app/Fragment.html\n[video2.1]:http://www.androiddesignpatterns.com/assets/videos/posts/2014/12/15/games-opt.mp4\n[jump1]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html#footnote1\n[Visibility]:https://developer.android.com/reference/android/transition/Visibility.html\n[onAppear]:https://developer.android.com/reference/android/transition/Visibility.html#onAppear(android.view.ViewGroup,%20android.transition.TransitionValues,%20int,%20android.transition.TransitionValues,%20int)\n[onDisappear]:https://developer.android.com/reference/android/transition/Visibility.html#onDisappear(android.view.ViewGroup,%20android.transition.TransitionValues,%20int,%20android.transition.TransitionValues,%20int)\n[Fade]:https://developer.android.com/reference/android/transition/Fade.html\n[slide]:https://developer.android.com/reference/android/transition/Slide.html\n[explode]:https://developer.android.com/reference/android/transition/Explode.html\n[source-code]:https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/view/ViewGroup.java#L6243-L6258\n[leafview]:https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/view/View.java#L19351-L19362\n[isTransitionGroup]:https://developer.android.com/reference/android/view/ViewGroup.html#isTransitionGroup()\n[video2.2]:http://www.androiddesignpatterns.com/assets/videos/posts/2014/12/15/webview-opt.mp4\n[addTarget]:https://developer.android.com/reference/android/transition/Transition.html#addTarget(android.view.View)\n[excludeTarget]:https://developer.android.com/reference/android/transition/Transition.html#excludeTarget(android.view.View,%20boolean)\n"
  },
  {
    "path": "others/清晰的软件架构/readme.md",
    "content": "The Clean Architecture\n---\n\n\n>* 原文链接 : [The Clean Architecture](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)\n> * [Robert Martin](http://blog.8thlight.com/) \n> * 译者:[zimoguo](https://github.com/zimoguo)\n> * 校对者:[Mr.Simple](https://github.com/bboyfeiyu)\n\n![](http://blog.8thlight.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg)      \n\n在过去的几年中,我们已经看到了关于系统框架的一些想法 : \n\n* [Hexagonal Architecture(六角架构)](http://alistair.cockburn.us/Hexagonal+architecture)(a.k.a. Ports and Adapters) 这种架构是由Alistair Cockburn提出的，并由Steve Freeman和Nat Pryce在他们的书[Growing Object Oriented Software](http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627)中提出。 \n* [Onion Architecture(洋葱架构)](http://jeffreypalermo.com/blog/the-onion-architecture-part-1/) 提出者是Jeffrey Palermo。\n* [尖叫架构(Screaming Architecture)](http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html) 提出者是Uncle Bob（就是这篇文章的作者）。\n* [DCI架构](http://www.amazon.com/Lean-Architecture-Agile-Software-Development/dp/0470684208/) 提出者是James Coplien和Trygve Reenskaug。\n* [BCE架构](http://www.amazon.com/Object-Oriented-Software-Engineering-Approach/dp/0201544350) 提出者是Ivar Jacobson。在他的书《Object Oriented Software Engineering: A Use-Case Driven Approach》中有大量提及对这种架构的说明。\n\n虽然这些文章在细节上有所不同,总体来说是非常相似的.它们关注点分离,通过将软件划分成层达到分离效果.每层最少包含一个业务规划或者接口。    \n\n这些架构有以下特点:\n\n1. 独立框架     \n这些架构不依赖某些特定库的加载,允许你使用框架作为工具,不会限制你的系统；\n2. 可测试     \n业务规划在没有UI界面,数据库,网页服务器或者其他外部元素的情况下进行测试；\n3. 独立于UI      \nUI很容易改变,而不用改变系统的其他部分,网页界面可以被控制台界面替换,同时还不用改变业务规划；\n4.独立于数据库      \n你可以切换到Oracle,SQL Server,Mongo,BigTable,CouchDB或者其他类型数据库,业务规则不与数据库绑定；\n5.独立于任何外部代理     \n事实上你的业务规划根本不需要知道外面的世界\n\n在这篇文章顶部的图表是将这些架构的理念集成在一起。\n\n\n## 依赖规则\n同心圆代表软件的不同区域,一般情况下,越接近中心位置软件的级别会变得越来越高,外圆圈是机制,内圆圈是策略。\n\n覆盖规则使得架构遵循依赖规则,这条规则表明,源代码只能向内依赖,内侧圆环不了解关于外侧圆环的一起,原则上,外圈圆环声明的名称不需要在内侧圆环中提到,其中包括函数,类,变量或者其他软件实体的名字\n\n出于同样的原因,在外圆圈中使用的数据格式不应该使用在内圆圈,特别是格式是由外圆圈的框架产生的情况,我们不希望外圆圈影响内圆圈的内容\n\n##实体\n实体封装项目范围内的业务规则,一个实体可以是一个对象的方法,或者是一组数据结构和功能.只要在项目中实体被不同的应用所使用即可。\n\n如果你没有项目,只是单纯的写一个应用程序,那么这些实体就是应用程序的业务对象.它们封装的最普通,最高级的规则.当外部变化时,它们最有可能改变,例如,你不希望这些对象被一个更改的页面导航或者安全影响,改变特定应用程序的操作不应该影响实体层。\n\n##用例\n在此层应用的业务规则包含应用特定的业务规则,他封装并实现了所有的系统用例,这些用例编排数据的流入和流出的实体,指示这些实体用在它们项目范围内的业务规则到达用力的目标。\n\n我们不希望改变层时影响实体,也不希望层被外部的改变所影响,例如数据库,用户界面或者任何的共同框架的改变,此层与这部分是隔离开的。\n\n我们这样做,不过是希望改变应用程序的操作时影响软件层的用例,用例详细信息改变时,这一层的代码也会受到影响。\n\n## 接口适配器\n这层软件是一个转化数字的适配器,从格式最方便的用例和实体转化为格式最方便的一些外部机构,如数据库或者网页,这一层完全包含GUI的MVC架构,代理者,视图,控制器都属于这一层,该模型可是只是从控制器传回到用例,然后从用例到代理和视图的数据结构。   \n\n同样的,在这一层数据被转换,从形式最方便的实体和用例转化成形式最方便的使用持久框架,即数据库.内圈里的任何代码不应该知道关于数据库的任何事情.如果这个数据库是SQL数据库,所有的SQL应该被限制在这一层,尤其是在这层对数据库的操作。\n\n另外,在这一层其他适配器需要将数据从外部形势(如外部服务)转化成内部形式的用例和实体。\n\n## 框架和驱动程序\n最外层一般由框架和工具组成,如数据库,web框架等.一般来说,你不会在这一层写太多代码,而是贴代码传达到内层圆圈内。\n\n这一层有很多细节,网页是一个细节,数据库是一个细节,我们保持外层的这些细节收到更少的破坏。    \n\n## 只有四个圆环?\n不,圆圈只是传达意思,你可能会发现你使用到的不仅仅是这四个,没有规定你必须使用这四个圆环,然后,依赖规则始终适用,源代码始终向内依赖.越往圆圈内侧抽象水平越高,最外面的圆圈为低一级的具体细节.越往圆圈内侧抽象和封装的层次更高,最内侧圆层次最高,最普通.   \n\n## 跨越边界\n该图的右下方是一个我们如何穿越边界的例子,它展示了控制器和代理者与下一层用例进行通信.注意控制流,开始于控制器,通过用例移动,结束于代理者的执行.还要注意源代码的依赖关系,指向内侧用例。    \n\n我们通常使用[依赖倒置原则](http://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99)解释这个明显的矛盾,像java语言,例如,我们会编排接口和继承的关系，使源代码在跨越边界的右侧点依赖反对控制流.\n\n例如,考虑用例需要调用的代理.然而不需要直接调用,因为这将违反相关性规则:在外圈中的名称不需要在内圈中提及.因此,我们在内侧圆环中调用接口(这里显示的用例输出端口),在外侧圆环实现它.\n\n相同的技术应用在跨越边界的系统结构中,无论控制流会在什么方向,我们以动态多态性的优势创建源代码依赖性,反对控制流,使得我们能够符合依赖规则。  \n\n## 什么是数据跨越边界\n通常跨越边界的数据是一种单纯的数据结构,可以使用基本的结构或者简单的数据进行传输.或者数据可以单纯的在函数中调用,或者你可以打包成一个hashMap,或构造成一个对象,重要的是分离,操作简单,通过数据结构跨越边界.我们不想通过实体和数据作弊,不希望数据结构有任何一种依赖违反依赖规则.\n\n例如,很多数据库框架响应查询返回一个方便的数据格式,我们称之为行结构.我们不希望向内跨越行结构,这将违反依赖规则,因为这会迫使内侧圆环了解外侧圆环的一些东西。\n\n因此,当我们跨越边界传输数据时,它总是使用最方便内圆环的格式。\n\n## 结论\n符合这些简单的规则并不难,并会为你省掉很多前进过程中头疼的问题,通过软件分层,顺应依赖规则,创建一个系统在本质上是可以检测的,意味着拥有其本身的好处.当任何一个系统的外部部件过时时,如数据库或者web框架,你可以使用最少的忧虑替换掉那些过时的元素。\n"
  },
  {
    "path": "others/简化Android的UI开发/readme.md",
    "content": "简化Android的UI开发\n---\n\n>\n* 原文链接 : [android ui development made easy](http://zserge.com/blog/android-mvx.html)\n* 作者 : [Zaitsev Serge](http://zserge.com/blog.html)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang)  \n* 状态 :  校对完成\n\n\n\n\n如果你觉得这篇文章太长，而且还没有往下阅读的话，我可以给你简要的介绍文章要讲的内容：我使用纯 Java 通过数据绑定的方式提供了一种\n\nAndroid UI 开发的代码往往是支离破碎的，写出来的代码通常都是大量的模板化代码，而且没有结构可言。下面是一些问题（纯属个人见解）：\n\n- Android UI 开发很少符合 MVC 模式（或者是 M-V-其他任何东西）\n\n- XML文件通常包含了很多重复的代码，在代码复用方面比较糟糕\n\n- XMLS 非常脆弱，这使得你在写 XML 文件时，即使输入了 TextVeiw ，在编译过程中编译器也不会警告你，但在 App 运行时又会抛出 InflateException 异常\n\n- 缺少对 styles 的支持，缺少对变量的支持，不支持宏和计算结果（例如 10dp + 2px)\n\n- 没有数据绑定，这使得你必须自己把所有的 findViewById 和 setOn...Listener 写好\n\n- 你可以通过 Java 实现你的布局，但是写出来的代码有如天书\n\n## 使用 mithril.js 建立用户接口 ##\n\n在 Web 开发中，开发者们很快就意识到在没有 MVx 的情况下开发复杂的应用会很吃力，这使得他们意识到 jQuery 中存在的问题，并开发了 Backbone，Knockout，Angular，Ember...等等，来提高他们的开发效率\n\n但在 Android 中，我们还在通过那一点点函数毫无章法可言地设置 View 的属性，就像在 jQuery 里一样：\n\n```java\n\t$('.myview').text('Hello');\n\t$('.myview').on('click', function() {\n\t\n\t});\n\t\n\tmyView.setText(\"Hello\");\n\tmyView.setOnClickListener(new View.OnClickListener() { ...});\n```\n\n我们在一个目录下定义了我们的 Layout ，又在另一个目录中使用它们，然后在 UI 开发的代码里改变\n，这样并不好。\n\nReact.js 对 Web 开发有一点点影响：他们以树状关系的自定义对象创建了一个虚拟的 DOM 概念，并以此展示实际的 HTML 布局。虚拟树创建和切换的时间都很短，所以当实际的 DOM 需要被渲染，两棵虚拟树（前一棵和新的那棵）将进行对比，只有不匹配的部分才会被渲染。\n\nMithril.js 是一个精悍、短小的框架，使用它能使 React.js 的实现更整洁。在 Mithril 中，除了纯 JavaScript，你几乎能摆脱一切，同时，它还能让你在写布局的时候感受到图灵完备的语言所具备的力量。\n\n```java\n\treturn m('div',\n\t         m('p', someText),\n\t         m('ul',\n\t           items.map((item) => m('li', item))),\n\t         m('button', {onclick: myClickHandler}));\n```\n\n因此，你能用循环生成许多 View，你能用判断语句改变布局中的某个部分，最后你能绑定数据和设置事件监听器。\n\n那这个方法能在 Android 中被使用吗？\n\n## 虚拟布局 ##\n\n虚拟布局（使用类似 Web 中虚拟 DOM 的概念）是树状的自定义Java对象集合，被用于展示实际的 Android 布局。虽然 App 的数据改变多少次，树就会被构建多少次，但布局改变的内容应该仅仅是前后不一致的部分（当前的布局和改变前布局）。\n\n我们的框架只导入一个静态类，所以所有类中的静态方法都不需要类名前缀就能被使用（例如我们只需要使用 v()，而不是 Render.v()），这是语言特性带来的好处。下面是我们如何创建布局的例子：\n\n```java\n\tv(LinearLayout.class,\n\t    orientation(LinearLayout.VERTICAL),\n\t    v(TextView.class,\n\t        text(someText)),\n\t    v(Button.class,\n\t        text(\"Click me\"),\n\t        onClick(someClickHandler)));\n```\n\n第一个 v() 方法返回了一个虚拟布局，每一次调用后它会返回当前应用状态的实际展示（不是实际的 View！）\n\n当一些文字变量被改变 - 虚拟树会获得一个被用于下次渲染的发生了改变的结点值，然后调用 setText()改变相应的 TextView 实例。但是其余的布局不会发生任何变化。\n\n一棵虚拟布局树在理想情况下应该只是一个类，我们就把它叫作结点吧。但是结点主要有两种类型：View 结点（TextView.class等等）和属性设置结点，例如text（someText)\n\n那这就意味着结点应该任意包含一个 View 类和一个方法去改变 View 的属性。\n\n```java\n\tinterface AttributeSetter {\n\t    public void set(View v);\n\t}\n\n\tpublic static class Node {\n\t    List<Node> attrs = new ArrayList<Node>();\n\t    Class<? extends View> viewClass; // for view nodes\n\t    AttributeSetter setter;          // for attribute setter nodes\n\t\n\t    public Node(Class<? extends View> c) {\n\t        this.viewClass = c;\n\t    }\n\t\n\t    public Node(AttributeSetter setter) {\n\t        this.setter = setter;\n\t    }\n\t}\n```\n\n现在我们需要定义类在产生虚拟布局的时候实际能干的事情了，那就让我们来调用可渲染类吧。一个可渲染类可以是一个 Activity，或者一个自定义的 ViewGroup，或者 Fragment 也凑合。每一个可渲染类都应该有一个用于返回虚拟布局的方法，此外，如果这个方法指定了它将要作用于实际布局中的哪个 View 会更好。\n\n```java\n\tpublic interface Renderable {\n\t    Node view();\n\t    ViewGroup getRootView();\n\t}\n```\n\n由于 v() 方法的第一个参数是 View 子类的泛型，所以你不用担心类型安全问题。剩下的参数都是结点类型，所以我们只需要把它们添加到 list 中，无视掉空结点的话效果会更好一些。\n\n```java\n\tpublic static Node v(final Class<? extends View> cls, final Node ...nodes) {\n\t    return new Node(cls) ;\n\t}\n```\n\nHere's an example of the text() attribute setter (the real code is a bit different, but it could have been implemented like this):\n\n下面是一个 text() 属性的设置方法（实际代码会有点不一样，但是也能像下面这样实现）：\n\n```java\n\tpublic static Node text(final String s) {\n\t    return new Node(new AttributeSetter() {\n\t        public void set(View v) {\n\t            ((TextView) v).setText(s);\n\t        }\n\t    });\n\t}\n```\n\n其他类似的工具方法也能用于改变线性布局的方向，View 的大小、页边距、间距，总之所有 View 的参数都能被改变。\n\n## 那么，我们要怎么去渲染呢？ ##\n\n现在我们需要一个“渲染者”。这是一个能够根据类名创建 View ，使用 AttributeSetters修改对应的参数并且递归地添加子 View的方法。（同样的，下面的代码也是被简化的，实际的代码会有些不一样，主要差别在于当结点没有被改变的时候，我们应该如何避免视图的渲染）\n\t\n```java\n\tpublic static View inflateNode(Context c, Node node, ViewGroup parent) {\n\t    if (node.viewClass == null) {\n\t        throw new RuntimeException(\"Root is not a view!\");\n\t    }\n\t    // Exception handling skipped here to make the code look shorter\n\t    View v = (View) node.viewClass.getConstructor(Context.class).newInstance(c);\n\t    parent.addView(v);\n\t    for (Node subnode: node.attrs) {\n\t        if (subnode.setter != null) {\n\t            subnode.setter.set(v);\n\t        } else {\n\t            View subview = inflateNode(c, subnode, (ViewGroup) v);\n\t        }\n\t    }\n\t    return v;\n\t}\n```\n\n现在我们真的可以摆脱 XMLS，并以一种简洁的方式通过 Java 进行布局了。\n\n布局结点不应该直接地被使用，而应该是通过 render(Renderer r) 和 render()被使用。前者用于重渲染某一个 View，后者用于重渲染所有被展示的 View。Renderer 通过一个弱哈希表存储，使得在 View 被移除或者 Activity 被销毁的同时 - 他们的渲染者也不会再被使用。\n\n## 什么时候去渲染呢？ ##\n\n这个框架的核心在于 自动进行重渲染，使得 UI 总能展示当前的虚拟布局状态。这就意味着 render() 应该在某个特定的节点被调用。\n\n我参考 Mithril 的方法，把每一个 On...Listener 和 调用 render 的方法捆绑在每一次 UI 的交互中。\n\n```java\n\tpublic static Node onClick(final View.OnClickListener listener) {\n\t    return new Node(new AttributeSetter() {\n\t        public void set(View v) {\n\t            v.setOnClickListener(new View.OnClickListener() {\n\t                public void onClick(View v) {\n\t                    listener.onClick(v);\n\t                    // After the click was processed - some data may have been changed\n\t                    // so we try to re-render the UI\n\t                    render();\n\t                }\n\t            });\n\t        }\n\t    });\n\t}\n```\n\n我觉得这样做是有道理的，因为大多数 Android 应用的数据都是在发生用户交互的时候被改变的。如果你的数据是因为其他因素被改变的 - 那就只能手动通过 render()渲染了。\n\n## 总的来说 ##\n\n这个方法虽然简单，却非常有用：\n\n- 你能用类似 XML 的方式定义你的布局结构（通过嵌套调用 v() 方法）\n\n- 你能用一种清晰易懂的方式绑定数据和监听器\n\n- 布局都是类型安全的，并且你的编译器会自动完成相应的工作\n\n- 没有运行时产生的开销，没有使用反射机制，没有自动生成代码\n\n- 你能在任何地方使用 Java（变量，语句，宏）生成布局\n\n- 你能用自定义 View 和自定义的属性设置方法\n\n- 因为你的所有 UI 数据都被保存在属性中，因此你能轻易的保存它们\n\n- 使用纯 Java 实现这些逻辑需要的代码还不到 250 行！\n\n以上证明了这个方法是可行的。现在我在想，如果有人想要用这个方法开发一个功能齐全的库呢？\n\n设计一个好的“区分”算法会是其中的关键。基本地，它应该能判断一个结点是否被添加/移除/修改，而文件就在于属性节点。简单的数据类型我们只要调用 equals() 去比较两个值就可以了，但是监听器呢？\n\n```java\n\tv(SomeView.java,\n\t    onClick(v => ...));\n```\n\n这样做的话每一次虚拟树被创建，都会创建一个对应的监听器对象。那怎么去比较它们？还是永远都不更新监听器，只更新发生了改变的监听器类？或者使用某种事件分发机制分发事件，而不是使用监听器？\n\n另一件需要被注意的是：我不想自己把所有属性设置方法写好。这里有一个更好的方法，也就是 Kotlin 他们在 koan 库中做的那样。\n\n我现在在研究怎么从 android.jar 的类中自动生成设置器，以使得这个项目更有用。\n\n不管怎样，现在的代码我都放在 [Github](https://github.com/zserge/anvil) 上了，有 MIT 的许可。欢迎大家来评论和 PR！"
  },
  {
    "path": "others/自动化截图－应用分发时的自动截图方案/readme.md",
    "content": "自动化截图－应用分发时的自动截图方案\n---\n\n>\n* 原文链接 : [Screenshots Through Automation](http://flavienlaurent.com/blog/2014/12/05/screenshot_automation/)\n* 作者 : [Flavien Laurent](http://flavienlaurent.com/)\n* 译者 : [chaossss](https://github.com/chaossss) \n* 校对者: [sundroid](https://github.com/sundroid)  \n* 状态 :  校对完成\n\n在发布 App 到应用商店时有一件的事情不得不做，就是上传最新的高清无码截图到应用商店上。可是如果你的 App 有许多页面，那你每次发布更新都可能是一场梦魇，因为你需要一页一页地去截图。为了解决众多 App 开发者的这个痛点，我将在这篇博文中介绍一个实现自动化截图的方法：\n\n刚到 [Capitaine Train](https://www.capitainetrain.com/) 公司里，就有人让我造个能自动截图的轮子，因为我们公司的 App 每次版本更新都让人很头疼：问题在于我们的 App 对应有3种设备，4种语言，也就是有 12 种版本。此外，我们有6个需要截图的页面，也就是说，我们每次版本更新都需要72张截图。我们无法忍受这种低效并浪费时间的工作，于是我们经过不懈的努力，找到了一个自动化截图的方案，在这个方案中，要实现自动化截图有三个关键点：uiautomator 自动化测试, accessibility 和 bash脚本。\n\n## 修改 uiautomator ##\n\nuiautomator 是一个用部分封装代码将 UI 处理成一个 JUnit 测试用例的框架。这里需要注意的是：被测试的 App 里没有包含这些测试用例，因为他们在一个独立的进程中运行。换句话说，你可以把 uiautomator 框架看作一个独立的机器人，它能帮你在设备上完成诸如：点击，滚动，截图等简单动作。\n\n\n**预备知识**\n\n在继续讲解之前，我建议你花些时间阅读官方文档，这能帮助你更好地理解接下来的内容。\n\nuiautomator 框架的 API 非常简单，里面有三个类分别代表了不同类型的 UI 界面元素：\n\n- UiObject: 基本界面元素，例如：TextView\n\n- UiCollection: 包含多个 UiObject 的界面元素，例如：LinearLayout\n\n- UiScrollable: 包含多个 UiObject ，并能滚动的界面元素，例如：ListView\n\n![](http://flavienlaurent.com/media/2014-12-05-screenshot_automation/uml.png)\n\n框架里这两个类你也需要了解：\n\n- UiDevice：用于执行设备常见的动作，例如：点击按钮，截图等等\n\n- UiSelector：通过 id, 类型等获得屏幕上的 UI 界面元素\n\n最后，UiAutomatorTestCase 是框架里你绝对不能忽略的类，因为我们必须通过继承它来获得一个 uiautomator 测试用例。\n\n当然了，我刚刚提到的这些类在官方文档里面都有详细的解释，此外，文档还提供了一些示例来帮助我们熟悉 uiautomator 。\n\n**安装，创建和运行**\n\n接下来我们要做的就是创建 uiautomator ，但很不幸，uiautomator 并没有一个官方的 Gradle 整合模块，所以我们必须自己去完成这项工作。把这些工作都完成后，才能在我们的 App 上使用 uiautomator。uiautomator 测试用例的最终输出应该是一个独立的 JAR 包。具体步骤如下：\n\n在你的项目里新建一个 Gradle 模块，并在其中添加与 local.properties 相同的 android.jar 依赖包：\n\n.build.gradle\n\n```java\n\tapply plugin: 'java'\n\t\n\tProperties props = new Properties()\n\tprops.load(new FileInputStream(file(\"../local.properties\")))\n\t\n\tdependencies {\n\t    compile fileTree(dir: props['sdk.dir'] + '/platforms/' + androidSdkTarget, include: '*.jar')\n\t}\n```\n\n通过使用 local.properties 和 gradle.properties 新建一个 ant 文件，使其获得与项目相同的配置信息(target, sdk path)：\n\nbuild.xml\n\n```xml\n\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t<project name=\"uiautomator\" default=\"help\">\n\t    <loadproperties srcFile=\"../local.properties\" />\n\t    <loadproperties srcFile=\"gradle.properties\" />\n\t    <property name=\"target\" value=\"${androidSdkTarget}\" />\n\t    <import file=\"${sdk.dir}/tools/ant/uibuild.xml\" />\n\t</project>\n```\n\n使用ant 构建JAR（不要使用Gradle构建），并把它加到你的设备中，然后运行你的测试用例。\n\n```java\n$ ant build\n$ adb push uiautomator.jar data/local/tmp\n$ adb shell uiautomator runtest uiautomator.jar -c com.your.TestCase\n```\n\n**自动切换设置信息**\n\n现在我准备讲解怎么在设置中自动切换设置项和设置信息（特别是从一个语言切换到另一个语言）。首先，这是一个练习使用 uiautomator 的机会。同时，这也是自动化截图的关键步骤。但你要记住，我接下来介绍的只是一个能在 Android 5.0 系统上正常使用的办法，如果你有更好的建议或者想法，也可以通过留言和我交流，一起优化这个步骤。\n\n- 打开快捷设置\n\n```java\n\tmUiDevice.openQuickSettings();\n```\n\n- 点击设置按钮以打开设置界面\n\n```java\n\tnew UiObject(new UiSelector().resourceId(\"com.android.systemui:id/settings_button\")).click();\n```\n\n- 因为在设置界面里我们没有可用的 View 的 id 值，所以我们必须根据设置栏的文字改变相对应的语言设置。所以我们滚动到某一项（FrameLayout）并点击它\n\n![](http://flavienlaurent.com/media/2014-12-05-screenshot_automation/settings_app.png)\n\n```java\n\tUiScrollable scrollable = new UiScrollable(new UiSelector().resourceId(\"com.android.settings:id/dashboard\"));\n\t\n\tscrollable.getChildByText(new UiSelector().className(FrameLayout.class), \"Language & input\", true).click();\n```\n\n- 通过上面的代码处理，整个“寻找并点击”的自动化逻辑已经能在语言设置栏里被使用了。\n![](http://flavienlaurent.com/media/2014-12-05-screenshot_automation/settings_language.png)\n\n```java\n\tUiScrollable scrollable = new UiScrollable(new UiSelector().className(ListView.class));\n\tscrollable.getChildByText(new UiSelector().className(LinearLayout.class), \"Language\", true).click();\n```\n\n- 添加这样的代码后，就能使得目标语言被选中了。\n\n![](http://flavienlaurent.com/media/2014-12-05-screenshot_automation/settings_language_selection.png)\n\n```java\n\tUiScrollable scrollable = new UiScrollable(new UiSelector().className(ListView.class));\n\tscrollable.getChildByText(new UiSelector().className(LinearLayout.class), \"Français (France)\", true).click();\n\tLocale.setDefault(new Locale(\"fr\"));\n```\n\n完成了上面的操作后，你还需要强制设置新的语言环境以避免 uiautomator 操作过程中保存了翻译缓存。\n\n**小提示**\n\n- 为了保证 uiautomator 的稳定性，当你在使用 uiautomator 时，必须关掉设备上的所有动画效果（你可以通过下面的设置完成：Settings > Developer options > Window animation|Transition animation|Animator duration scale）\n\n- 如果你想打 Log 方便你的调试，你可以使用 android.util.Log。为了更好地区分 Log 信息，你可以使用特定的标记来筛选它们。\n\n- 每一次你需要在 View 的不同层级间切换都要使用 uiautomatorviewer。因为它能为你提供一个精确的选择器，使你能够获得目标 UI 界面元素（uiautomatorviewer 在 sdk/tools/uiautomatorviewer 里）。\n\n- 记住，uiautomator 测试用例不是 Android 的测试用例，所以你不需要使用任何形式的 Context。\n\n- 你不能通过 uiautomator 进入你的 App 类，你只能引用 Android 框架中的类。\n\n- 你可以在命令行中使用 -e 命令把 uiautomator 命令行的参数传递到测试用例类中，又或者是使用测试用例类中的 UiAutomatorTestCase.html#getParams()。\n\n这样处理下来，你会发现自动完成语言的切换很简单对吧？uiautomator 虽然是个很好的工具，但如果你的 App 不是可访问的，它就没什么用了。特别是你的 App 需要创建完全自定义的 View 时，就可能会出现各种问题，所以接下来我们要解决的问题就是让 App 可以被访问，特别是自定义 View。\n\n## 让自定义 View 可访问 ##\n\n可访问性对一个 App 来说非常重要，其作用主要体现在两个方面：有些用户/开发者需要它（但总有开发者会忽略这个需求），此外，uiautomator 都以可访问性为基础，也就是说，如果一个应用不能提供可访问的入口，我们将无法在其中使用 uiautomator 自动化测试工具。\n\n大部分情况下，你都没有必要让你的 App 可以被其他应用访问。但事实上，大部分 View 都是可访问的，例如 TextView，ListView 等等。不过在你使用自定义 View 时，获得访问性可能会麻烦点，因为这需要你花费一些功夫去改变其中的代码。\n\n在 Capitaine Train App 里，为了满足对日历视图的特殊需求，我们创建了一个自定义 View。这个 View 是基于 ListView 设计的，ListView 中的每一项都有好几个自定义 View，并且每一个自定义 View 都代表一个月（我们称为 MonthView）。MonthView 是一个纯粹的 View，它继承于 View，并没有子类。这样使得 MonthView 中的一切都需要通过 onDraw() 方法进行绘制。因此，MonthView 在默认情况下不能被访问。\n\n首先要做的事情很简单：使用 View#setContentDescription 方法为每一个 MonthView 设置内容描述，这样我们能够把 ListView 滚动到一个特殊的月份上。\n\n然后，一旦 ListView 停留在某一个给定的月份上，我们希望我们能够选择一个确定的日期。为了实现这个需求，我们需要使 MonthView 的内容是可访问的。幸运的是，Android 的支持库在类似的处理上提供了一个很有用的 Helper类：ExploreByTouchHelper。由于 MonthView 不是以树形结构结合展示其中的 View 集合，所以创建伪树状结构的 View 集合需要基于触摸反馈实现。\n\n\n**为自定义 View 实现 ExploreByTouchHelper**\n\n我们有四个方法可以实现：\n\n- getVirtualViewAt(float x, float y)    \n返回参数 x,y处所对应的虚拟 View 的 id。如果对应位置上没有虚拟 View，则返回 ExploreByTouchHelper.INVALID_ID\n\n- getVisibleVirtualViews(List<Integer> virtualViewIds)      \n将自定义 View 中所有虚拟 View 的 id 添加到 virtualViewIds 数组中。\n\n- onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event)      \n让虚拟 View 的相关信息可以被访问，例如：文字，内容描述\n\n- onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node)      \n让给定结点能够访问虚拟 View 的相关信息，例如文字，内容描述，类名，与父类的关系。如果两者之间产生了交互，你必须在给定结点中说明。\n\n- onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments)    \n在虚拟 View 中实现某种动作（在前面的方法中被指定）\n\n怎么让 ExploreByTouchHelper 的接口变得更简单：\n\n- 创建一个 VirtualView 类去持有虚拟 View 的各种信息，例如：id，文字，内容描述，与父类的关系。\n- 在你的自定义 View 中使用一系列的 VirtualView。尽快初始化它们，并在绘制后更新它们\n\nYourAccessibilityTouchHelper.java\n\n```java\n\tprivate class YourAccessibilityTouchHelper extends ExploreByTouchHelper {\n\t\n\t    public YourAccessibilityTouchHelper(View forView) {\n\t        super(forView);\n\t    }\n\t\n\t    @Override\n\t    protected int getVirtualViewAt(float x, float y) {\n\t        final VirtualView vw = findVirtualViewByPosition(x, y);\n\t        if (vw == null) {\n\t            return ExploreByTouchHelper.INVALID_ID;\n\t        }\n\t        return vw.id;\n\t    }\n\t\n\t    @Override\n\t    protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {\n\t        for (int i = 0; i < mVirtualViews.size(); i++) {\n\t            mVirtualViews.add(mVirtualViews.get(i).id);\n\t        }\n\t    }\n\t\n\t    @Override\n\t    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {\n\t        final VirtualDayView vw = findVirtualViewById(virtualViewId);\n\t        if (vw == null) {\n\t            return;\n\t        }\n\t        event.getText().add(vw.description);\n\t    }\n\t\n\t    @Override\n\t    protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node) {\n\t        final VirtualDayView vw = findVirtualViewById(virtualViewId);\n\t        if (vw == null) {\n\t            return;\n\t        }\n\t\n\t        node.setText(Integer.toString(vw.text));\n\t        node.setContentDescription(vw.description);\n\t        node.setClassName(vw.className);\n\t        node.setBoundsInParent(vw.boundsInParent);\n\t    }\n\t}\n```\n\n**在你的自定义 View 中使用 Helper 类**\n\n我们需要在 ListView.getView 方法被执行后通过 setAccessibilityDelegate() 方法重设代理，因为我们需要实现 dispatchHoverEvent() 方法来激活对触摸事件的探索。（如果你的自定义 View 没有在 ListView 中被使用的话，只需要在构造器中设置代理）。\n\nYourCustomView.java\n\n```java\n\tpublic class YourCustomView extends View {\n\t\n\t    private final YourAccessibilityTouchHelper mTouchHelper;\n\t\n\t  public YourCustomView(Context context, AttributeSet attrs, int defStyle) {\n\t      super(context, attrs, defStyle);\n\t      mTouchHelper = new YourAccessibilityTouchHelper(this);\n\t  }\n\t  \n\t  private void setAccessibilityDelegate() {\n\t      setAccessibilityDelegate(mTouchHelper);\n\t    }\n\t\n\t  [...]\n\t\n\t  public boolean dispatchHoverEvent(MotionEvent event) {\n\t      if (mTouchHelper.dispatchHoverEvent(event)) {\n\t          return true;\n\t      }\n\t      return super.dispatchHoverEvent(event);\n\t  }\n```\n\n**用 uiautmatorviewer 检查你的接口能否正常运行**\n\n如果一切都正常运行，在你用 uiautmatorviewer 截图后，你应该能在虚拟 View 图层看到在可访问结点中预设置的所有信息。\n\n![](http://flavienlaurent.com/media/2014-12-05-screenshot_automation/accessibility_calendar_uiautomator.png)\n\n另一方面，在我写这篇博文的时候我发现 Capitaine Train App里的一个问题：每一个虚拟 View 的类名都是 com.capitainetrain.x，因为我们忘了用 Proguard。\n\n现在 App 中的一切都是可访问的，我们总算可以在 App 中顺利使用 uiautomator 进行自动化截图了。打铁趁热，我们不妨对我们的代码稍作修改，让它能够“优雅地截图”。\n\n## 优雅地截取图片 ##\n\n这篇博文要讲解的最后一个问题就是怎么改进 uiautomator ，使得它能在多种语言中优雅地自动截图。实现这个功能需要两个步骤：第一，使用 bash 脚本运行 uiautomator 测试用例，并按照你需要的图片数量进行自动化截图，之后用 imagemagick 处理你获得的照片。\n\n首先要做的就是创建 uiautomator JAR包，然后运行测试用例。因为你已经在前面的讲解中学习了怎么在测试用例中转换语言，所以你只需要传递两个参数到测试用例中：当前设置中使用的语言和你将要切换的语言。\n\nscreenshot.sh\n\n```gradle\n\t# Build and push the uiautomator JAR\n\tant build\n\tadb push bin/uiautomator.jar data/local/tmp\n\t\n\tadb shell uiautomator runtest uiautomator.jar\n\t  -e current_language ${currentLanguage}\n\t  -e new_language ${newLanguage}\n\t  -c com.your.TestCase\n```\n\n接下来我们只要再创建一个能够切换语言，打开 App并截图的简单测试用例就可以啦：\n\nTestCase.java\n\n```java\n\tpublic class TestCase extends UiAutomatorTestCase {\n\t  [...]\n\t  @Override\n\t    protected void setUp() throws Exception {\n\t        super.setUp();\n\t        final Bundle params = getParams();\n\t        mCurrentLanguage = params.getString(\"current_language\");\n\t        mNewLanguage = params.getString(\"new_language\");\n\t    }\n\t\n\t    public void test() throws Exception {\n\t        switchLanguage(mCurrentLanguage, mNewLanguage);\n\t        openApp();\n\t        takeScreenshot(\"data/local/tmp/screenshots\");\n\t    }\n\t}\n```\n\n\n- switchLanguage(String,String)只需要使用我在\"修改 uiautomator\"中讲解的方法就能轻松地实现 \n- openApp() 在 [这里](https://developer.android.com/tools/testing/testing_ui.html#sample)有详细的解释\n- takeScreenshot() 使用了 UiDevice#takeScreenshot 方法。在这里只有一个小提示：如果一个 App 使用了可滚动的 View，在滚动条消失之前我们必须安静地等一会儿，不然的话我们会在最后的截图里看到它。\n\n现在截图都被储存在设备里了，我们只需要把它们取出来就大功告成了：\n\nscreenshot.sh\n\n```\n\tmkdir screenshots\n\tadb pull data/local/tmp/screenshots screenshots\n```\n\n在多语言环境中运行测试用例。它会从设备当前使用的语言开始运行，因为我找不到一个合适的方式去表示它，然后会在不同的语言环境下（我们需要截图的那些语言）运行测试用例。\n\nscreenshot.sh\n\n```\n\tscreenshot() {\n\t    currentLanguage=$1\n\t  newLanguage=$2\n\t  adb shell uiautomator runtest uiautomator.jar\n\t      -e current_language ${currentLanguage}\n\t      -e new_language ${newLanguage}\n\t      -c com.your.TestCase\n\t}\n\n\tscreenshot $deviceLanguage fr\n\tscreenshot fr en\n\tscreenshot en de\n```\n\nApp 每次卸载/安装后在相同的环境下运行测试用例都能正常地实现自动化截图的功能：\n\nscreenshot.sh\n\n```\n\tscreenshot() {\n\t    currentLanguage=$1\n\t  newLanguage=$2\n\t\n\t  # Uninstall/Install the app\n\t  adb uninstall com.your.app\n\t  adb install ../app/build/outputs/apk/yourapp-release.apk\n\t  \n\t  adb shell uiautomator runtest uiautomator.jar\n\t      -e current_language ${currentLanguage}\n\t      -e new_language ${newLanguage}\n\t      -c com.your.TestCase\n\t}\n```\n\n最后把所有模块糅合在一起：\n\nscreenshot.sh\n\n```\n\tscreenshot() {\n\t  currentLanguage=$1\n\t  newLanguage=$2\n\t\n\t  # Uninstall/Install the app\n\t  adb uninstall com.your.app\n\t  adb install ../app/build/outputs/apk/yourapp-release.apk\n\t\n\t  # Run the test case\n\t  adb shell uiautomator runtest uiautomator.jar\n\t      -e current_language ${currentLanguage}\n\t      -e new_language ${newLanguage}\n\t      -c com.your.TestCase\n\t      \n\t  mkdir screenshots\n\t  adb pull data/local/tmp/screenshots screenshots\n\t}\n\n\t# Build and push the uiautomator JAR\n\tant build\n\tadb push bin/uiautomator.jar data/local/tmp\n\t\n\t# Build the APK\n\tcd .. && ./gradlew assembleRelease && cd uiautomator\n\t\n\t# Screenshot everything\n\tscreenshot $currentLanguage fr\n\tscreenshot fr en\n\tscreenshot en de\n```\n\n**美化截图**\n分享一篇好文：[Creating professional looking screenshots](http://cyrilmottier.com/2012/07/11/creating-professional-looking-screenshots/)。\n\n每一个 App 的运营者都应该尽其所能美化 App 的截图，因为这是用户在应用商店中对 App 的第一印象。大多数情况下，用户都不会阅读应用的描述，而是直接打开应用的截图，因为阅读文字比看图片更费劲。虽然不能说经过下面的处理能获得完美无瑕的图片，但也在水平线以上了。那么什么样的 App 截图是优雅的截图呢？\n\n- 始终保持状态栏的整洁\n\n- 移除导航栏\n\n- 适配多种屏幕的尺寸\n\n第二点可以用一个超神奇的工具—imagemagick 实现，虽然它的官方文档非常大，但我们用不到那么多的特性，所以我们只需要关注两个特性：组合和转换。\n\n**用组合图覆盖状态栏**\n\n组合图是用来把一个图片覆盖到另一个上面的，这是获得简洁状态栏的完美办法。\n\n```java\ncomposite -quality 100 -compose atop clean_status_bar.png screenshot.png clean_screenshot.png\n```\n\n**通过转换裁剪导航栏**\n\n转换特性被用于转换图片的格式，使其格式与裁剪后的图片相同，这是从截图中移除导航栏的完美办法。\n\n```java\nconvert -quality 100 screenshot.png -gravity South -chop 0x144 clean_screenshot.png\n```\n\n144是在Nexu5上导航栏的高度像素值。\n\n**结论**\n\n因为有了这篇博文，通常要花费半天，甚至一天的截图工作现在能通过 Capitaine Train 上用的这个自动化截图工具缩短到 20～30 分钟完成（我相信没有人想手动地做这些工作，或者因为嫌弃这样的工作，从不更新 App 的截图）。这个工具能高效地节省时间，如果能够更多的人和资源投入到这个工具的开发之中，我相信这个工具还能变得更好，也不会那么容易出错和崩溃。\n\n接下来可能做的：\n\n使用 Google Play 发布的 API 简化上传这些自动生成的截图的流程，并把这个工具整合到 Jenkins 里，让 App 每一次版本更新都能自动地获取最新的截图，并将其显示在应用商店中。\n\n"
  },
  {
    "path": "rxjava/chap1.md",
    "content": "# RxJava开发精要1-从.NET到RxJava\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n## RX - 从.NET到RxJava\n\n响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河：它可以被观测，被过滤，被操作，或者为新的消费者与另外一条流合并为一条新的流。\n\n响应式编程的一个关键概念是事件。事件可以被等待，可以触发过程，也可以触发其它事件。事件是唯一的以合适的方式将我们的现实世界映射到我们的软件中：如果屋里太热了我们就打开一扇窗户。同样的，当我们更改电子表（变化的传播）中的一些数值时，我们需要更新整个表格或者我们的机器人碰到墙时会转弯（响应事件）。\n\n今天，响应式编程最通用的一个场景是UI：我们的移动App必须做出对网络调用、用户触摸输入和系统弹框的响应。在这个世界上，软件之所以是事件驱动并响应的是因为现实生活也是如此。\n\n## 微软响应式扩展\n\n函数响应式编程是一个来自90年代后期受微软的一名计算机科学家Erik Meijer启发的思想，用来设计和开发微软的Rx库。\n\nRx 是微软.NET的一个响应式扩展。Rx借助可观测的序列提供一种简单的方式来创建异步的，基于事件驱动的程序。开发者可以使用Observables模拟异步数据流，使用LINQ语法查询Observables，并且很容易管理调度器的并发。\n\nRx让众所周知的概念变得易于实现和消费，例如**push方法**。在响应式的世界里，我们不能假装作用户不关注或者是不抱怨它而一味的等待函数的返回结果，网络调用，或者数据库查询的返回结果。我们时刻都在等待某些东西，这就让我们失去了并行处理其他事情的机会，提供更好的用户体验，让我们的软件免受顺序链的影响，而阻塞编程。\n\n下表列出的与.NET 枚举相关的.NET Observable\n\n| .NET Observable| 一个返回值| 多个返回值  |\n| ------------- |:-------------:| -----:|\n| Pull/Synchronous/Interactive|`T`| `IEnumerable<T>` |\n| Push/Asynchronous/Reactive| `Task<T>`|`IObservable<T>`|\n\npush方法把这个问题逆转了：取而代之的是不再等待结果，开发者只是简单的请求结果，而当它返回时得到一个通知即可。开发者对即将发生的事件提供一个清晰的响应链。对于每一个事件，开发者都作出相应的响应；例如，用户被要求登录的时候，提交一个携带他的用户名和密码的表单。应用程序执行登录的网络请求，接下来将要发生的情况有：\n\n* 显示一个成功的信息，并保存用户的个人信息。\n* 显示一个错误的信息\n\n正如你用push方法所看到的，开发者不需要等待结果。而是在结果返回时通知他。在这期间，他可以做他想做的任何事情：\n\n* 显示一个进度对话框\n* 为下次登录保存用户名和密码\n* 预加载一些他认为登录成功后需要耗时处理的事情\n\n## 来到Java世界 - Netflix RxJava\n\nNetflix在2012年开始意识到他们的架构要满足他们庞大的用户群体已经变得步履维艰。因此他们决定重新设计架构来减少REST调用的次数。取代几十次的REST调用，而是让客户端自己处理需要的数据，他们决定基于客户端需求创建一个专门优化过的REST调用。\n\n为了实现这一目标，他们决定尝试响应式，开始将.NET Rx迁移到JVM上面。他们不想只基于Java语言；而是整个JVM，从而有可能为市场上的每一种基于JVM的语言：如Java、Closure、Groovy、Scala等等提供一种新的工具。\n\n2013年二月份,Ben Christensen 和 Jafar Husain发在Netflix技术博客的一篇文章第一次向世界展示了RxJava。\n\n主要特点有：\n\n* 易于并发从而更好的利用服务器的能力。\n* 易于有条件的异步执行。\n* 一种更好的方式来避免回调地狱。\n* 一种响应式方法。\n\n\n正如.NET,RxJava Observable 是push 迭代的等价体，即pull。pull方法是阻塞并等待的方法：消费者从源头pull值，并阻塞线程直到生产者提供新的值。\n\npush方法作用于订阅和响应：消费者订阅新值的发射，当它们可用时生产者push这些新值并通知消费者。在这一点上，消费者消费了它们。push方法很明显更灵活，因为从逻辑和实践的观点来看，开发者只需忽略他需要的数据是来自同步还是异步；他的代码将仍然起作用。\n\n\n## RxJava的与众不同之处\n\n从纯Java的观点看，RxJava Observable类源自于经典的Gang Of Four的观察者模式。\n\n它添加了三个缺少的功能：\n\n* 生产者在没有更多数据可用时能够发出信号通知：onCompleted()事件。\n* 生产者在发生错误时能够发出信号通知：onError()事件。\n* RxJava Observables 能够组合而不是嵌套，从而避免开发者陷入回调地狱。\n\n\nObservables和Iterables共用一个相似的API：我们在Iterable可以执行的许多操作也都同样可以在Observables上执行。当然，由于Observables流的本质，没有如Iterable.remove()这样相应的方法。\n\n| Pattern| 一个返回值| 多个返回值  |\n| ------------- |:-------------:| -----:|\n| Synchronous|`T getData()`| `Iterable<T>` |\n| Asynchronous| `Future<T> getData()`|`Observable<T> getData()`|\n\n从语义的角度来看，RxJava就是.NET Rx。从语法的角度来看，Netflix考虑到了对应每个Rx方法,保留了Java代码规范和基本的模式。\n\n## 总结\n\n本章中，我们初步探索了响应式的世界。从微软的.NET到Netflix的RxJava，我们了解了Rx是如何诞生的，我们也了解到传统的方法与响应式方法相比之间的相似和不同。\n\n下一章，我们将学习到Observables是什么，以及如何创建它并把响应式编程应用到我们的日常编码中去。\n"
  },
  {
    "path": "rxjava/chap2.md",
    "content": "# RxJava开发精要2-为什么是Observables?\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n## 为什么是Observables?\n\n在面向对象的架构中，开发者致力于创建一组解耦的实体。这样的话，实体就可以在不用妨碍整个系统的情况下可以被测试、复用和维护。设计这种系统就带来一个棘手的负面影响：维护相关对象之间的统一。\n\n在Smalltalk MVC架构中，创建模式的第一个例子就是用来解决这个问题的。用户界面框架提供一种途径使UI元素与包含数据的实体对象相分离，并且同时，它提供一种灵活的方法来保持它们之间的同步。\n\n在这本畅销的四人组编写的《设计模式——可复用面向对象软件的基础》一书中，观察者模式是最有名的设计模式之一。它是一种行为模式并提供一种以一对多的依赖来绑定对象的方法：即当一个对象发生变化时，依赖它的所有对象都会被通知并且会自动更新。\n\n在本章中，我们将会对观察者模式有一个概述，它是如何实现的以及如何用RxJava来扩展，Observable是什么，以及Observables如何与Iterables相关联。\n\n\n## 观察者模式\n\n在今天，观察者模式是出现的最常用的软件设计模式之一。它基于subject这个概念。subject是一种特殊对象，当它改变时，那些由它保存的一系列对象将会得到通知。而这一系列对象被称作Observers,它们会对外暴漏了一个通知方法,当subject状态发生变化时会调用的这个方法。\n\n在上一章中，我们看到了电子表单的例子。现在我们可以展开这个例子讲，展示一个更复杂的场景。让我们考虑这样一个填着账户数据的电子表单。我们可以把这些数据比作一张表，或者是3D柱状图，或者是饼状图。它们中每一个代表的意义都取决于同一组要展示的数据。每一个都是一个观察者，都依赖于那一个subject，维护着全部信息。\n\n3D柱状图这个类、饼状图类、表这个类以及维护这些数据的类是完全解耦的：它们彼此相互独立复用，但也能协同工作。这些表示类彼此不清楚对方，但是正如它们所做的：它们知道在哪能找到它们需要展示的信息，它们也知道一旦数据发生变化就通知需要更新数据表示的那个类。\n\n这有一张图描述了Subject/Observer的关系是怎样的一对多的关系：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter2_1.png)\n\n上面这张图展示了一个Subject为3个Observers提供服务。很明显，没有理由去限制Observers的数量：如果有需要，一个Subject可以有无限多个Observers,当subject状态发生变化时，这些Observers中的每一个都会收到通知。\n\n## 你什么时候使用观察者模式？\n\n观察者模式很适合下面这些场景中的任何一个：\n\n* 当你的架构有两个实体类，一个依赖另一个，你想让它们互不影响或者是独立复用它们时。\n* 当一个变化的对象通知那些与它自身变化相关联的未知数量的对象时。\n* 当一个变化的对象通知那些无需推断具体类型的对象时。\n\n\n## RxJava观察者模式工具包\n\n在RxJava的世界里，我们有四种角色：\n* Observable\n* Observer\n* Subscriber\n* Subjects\n\nObservables和Subjects是两个“生产”实体，Observers和Subscribers是两个“消费”实体。\n\n## Observable\n\n当我们异步执行一些复杂的事情，Java提供了传统的类，例如Thread、Future、FutureTask、CompletableFuture来处理这些问题。当复杂度提升，这些方案就会变得麻烦和难以维护。最糟糕的是，它们都不支持链式调用。\n\nRxJava Observables被设计用来解决这些问题。它们灵活，且易于使用，也可以链式调用，并且可以作用于单个结果程序上，更有甚者，也可以作用于序列上。无论何时你想发射单个标量值，或者一连串值，甚至是无穷个数值流，你都可以使用Observable。\n\nObservable的生命周期包含了三种可能的易于与Iterable生命周期事件相比较的事件，下表展示了如何将Observable async/push 与 Iterable sync/pull相关联起来。\n\n| Event| Iterable(pull)|Observable(push)|\n| ------------- |:-------------:| -----:|\n| 检索数据|`T next()`| `onNext(T)` |\n| 发现错误| `throws Exception`|`onError(Throwable)`|\n| 完成    |`!hasNext()`|`onCompleted()`|\n\n使用Iterable时，消费者从生产者那里以同步的方式得到值，在这些值得到之前线程处于阻塞状态。相反，使用Observable时，生产者以异步的方式把值推给观察者，无论何时，这些值都是可用的。这种方法之所以更灵活是因为即便值是同步或异步方式到达，消费者在这两种场景都可以根据自己的需要来处理。\n\n为了更好地复用Iterable接口，RxJava Observable类扩展了GOF观察者模式的语义。引入了两个新的接口：\n* onCompleted() 即通知观察者Observable没有更多的数据。\n* onError() 即观察者有错误出现了。\n\n\n### 热Observables和冷Observables\n\n从发射物的角度来看，有两种不同的Observables:热的和冷的。一个\"热\"的Observable典型的只要一创建完就开始发射数据，因此所有后续订阅它的观察者可能从序列中间的某个位置开始接受数据（有一些数据错过了）。一个\"冷\"的Observable会一直等待，直到有观察者订阅它才开始发射数据，因此这个观察者可以确保会收到整个数据序列。\n\n### 创建一个Observable\n\n在接下来的小节中将讨论Observables提供的两种创建Observable的方法。\n\n#### Observable.create()\n\ncreate()方法使开发者有能力从头开始创建一个Observable。它需要一个OnSubscribe对象,这个对象继承Action1,当观察者订阅我们的Observable时，它作为一个参数传入并执行call()函数。\n```java\nObservable.create(new Observable.OnSubscribe<Object>(){\n        @Override\n        public void call(Subscriber<? super Object> subscriber) {\n            \n        }\n});\n```\nObservable通过使用subscriber变量并根据条件调用它的方法来和观察者通信。让我们看一个“现实世界”的例子：\n```java\nObservable<Integer> observableString = Observable.create(new Observable.OnSubscribe<Integer>() {\n        @Override\n        public void call(Subscriber<? super Integer> observer) {\n            for (int i = 0; i < 5; i++) {\n                observer.onNext(i);\n            }\n            observer.onCompleted();\n        }\n});\n\nSubscription subscriptionPrint = observableString.subscribe(new Observer<Integer>() {\n    @Override\n    public void onCompleted() {\n        System.out.println(\"Observable completed\");\n    }\n\n    @Override\n    public void onError(Throwable e) {\n        System.out.println(\"Oh,no! Something wrong happened！\");\n    }\n\n    @Override\n    public void onNext(Integer item) {\n        System.out.println(\"Item is \" + item);\n    }\n});\n```\n例子故意写的简单，是因为即便是你第一次见到RxJava的操作，我想让你明白接下来要发生什么。\n\n我们创建一个新的`Observable<Integer>`,它执行了5个元素的for循环，一个接一个的发射他们，最后完成。\n\n另一方面，我们订阅了Observable，返回一个Subscription\n。一旦我们订阅了，我们就开始接受整数，并一个接一个的打印出它们。我们并不知道要接受多少整数。事实上，我们也无需知道是因为我们为每种场景都提供对应的处理操作：\n* 如果我们接收到了整数，那么就打印它。\n* 如果序列结束，我们就打印一个关闭的序列信息。\n* 如果错误发生了，我们就打印一个错误信息。\n\n#### Observable.from()\n\n在上一个例子中，我们创建了一个整数序列并一个一个的发射它们。假如我们已经有一个列表呢？我们是不是可以不用for循环而也可以一个接一个的发射它们呢？\n\n在下面的例子代码中，我们从一个已有的列表中创建一个Observable序列：\n```java\nList<Integer> items = new ArrayList<Integer>();\nitems.add(1);\nitems.add(10);\nitems.add(100);\nitems.add(200);\n\nObservable<Integer> observableString = Observable.from(items);\nSubscription subscriptionPrint = observableString.subscribe(new Observer<Integer>() {\n    @Override\n    public void onCompleted() {\n        System.out.println(\"Observable completed\");\n    }\n\n    @Override\n    public void onError(Throwable e) {\n        System.out.println(\"Oh,no! Something wrong happened！\");\n    }\n\n    @Override\n    public void onNext(Integer item) {\n        System.out.println(\"Item is \" + item);\n    }\n});\n```\n\n输出的结果和上面的例子绝对是一样的。\n\n`from()`创建符可以从一个列表/数组来创建Observable,并一个接一个的从列表/数组中发射出来每一个对象，或者也可以从Java `Future`类来创建Observable，并发射Future对象的`.get()`方法返回的结果值。传入`Future`作为参数时，我们可以指定一个超时的值。Observable将等待来自`Future`的结果；如果在超时之前仍然没有结果返回，Observable将会触发`onError()`方法通知观察者有错误发生了。\n\n#### Observable.just()\n\n如果我们已经有了一个传统的Java函数，我们想把它转变为一个Observable又改怎么办呢？我们可以用`create()`方法，正如我们先前看到的，或者我们也可以像下面那样使用以此来省去许多模板代码：\n```java\nObservable<String> observableString = Observable.just(helloWorld());\n\nSubscription subscriptionPrint = observableString.subscribe(new Observer<String>() {\n    @Override\n    public void onCompleted() {\n        System.out.println(\"Observable completed\");\n    }\n\n    @Override\n    public void onError(Throwable e) {\n        System.out.println(\"Oh,no! Something wrong happened!\");\n    }\n\n    @Override\n    public void onNext(String message) {\n        System.out.println(message);\n    }\n});\n```\n\n`helloWorld()`方法比较简单，像这样：\n```java\nprivate String helloWorld(){\n    return \"Hello World\";\n}\n```\n\n不管怎样，它可以是我们想要的任何函数。在刚才的例子中，我们一旦创建了Observable，`just()`执行函数，当我们订阅Observable时，它就会发射出返回的值。\n\n`just()`方法可以传入一到九个参数，它们会按照传入的参数的顺序来发射它们。`just()`方法也可以接受列表或数组，就像`from()`方法，但是它不会迭代列表发射每个值,它将会发射整个列表。通常，当我们想发射一组已经定义好的值时会用到它。但是如果我们的函数不是时变性的，我们可以用just来创建一个更有组织性和可测性的代码库。\n\n最后注意`just()`创建符，它发射出值后，Observable正常结束，在上面那个例子中，我们会在控制台打印出两条信息：“Hello World”和“Observable completed”。\n\n#### Observable.empty(),Observable.never(),和Observable.throw()\n\n当我们需要一个Observable毫无理由的不再发射数据正常结束时，我们可以使用`empty()`。我们可以使用`never()`创建一个不发射数据并且也永远不会结束的Observable。我们也可以使用`throw()`创建一个不发射数据并且以错误结束的Observable。\n\n\n## Subject = Observable + Observer\n\n`subject`是一个神奇的对象，它可以是一个Observable同时也可以是一个Observer：它作为连接这两个世界的一座桥梁。一个Subject可以订阅一个Observable，就像一个观察者，并且它可以发射新的数据，或者传递它接受到的数据，就像一个Observable。很明显，作为一个Observable，观察者们或者其它Subject都可以订阅它。\n\n一旦Subject订阅了Observable，它将会触发Observable开始发射。如果原始的Observable是“冷”的，这将会对订阅一个“热”的Observable变量产生影响。\n\nRxJava提供四种不同的Subject：\n* PublishSubject\n* BehaviorSubject\n* ReplaySubject.\n* AsyncSubject\n\n### PublishSubject\n\nPublish是Subject的一个基础子类。让我们看看用PublishSubject实现传统的Observable `Hello World`:\n```java\nPublishSubject<String> stringPublishSubject = PublishSubject.create();\nSubscription subscriptionPrint = stringPublishSubject.subscribe(new Observer<String>() {\n    @Override\n    public void onCompleted() {\n        System.out.println(\"Observable completed\");\n    }\n\n    @Override\n    public void onError(Throwable e) {\n        System.out.println(\"Oh,no!Something wrong happened!\");                \n    }\n\n    @Override\n    public void onNext(String message) {\n        System.out.println(message);\n    }\n});\nstringPublishSubject.onNext(\"Hello World\");\n```\n\n在刚才的例子中，我们创建了一个`PublishSubject`，用`create()`方法发射一个`String`值，然后我们订阅了PublishSubject。此时，没有数据要发送，因此我们的观察者只能等待，没有阻塞线程，也没有消耗资源。就在这随时准备从subject接收值，如果subject没有发射值那么我们的观察者就会一直在等待。再次声明的是，无需担心：观察者知道在每个场景中该做什么，我们不用担心什么时候是因为它是响应式的：系统会响应。我们并不关心它什么时候响应。我们只关心它响应时该做什么。\n\n最后一行代码展示了手动发射字符串“Hello World”,它触发了观察者的`onNext()`方法，让我们在控制台打印出“Hello World”信息。\n\n让我们看一个更复杂的例子。话说我们有一个`private`声明的Observable，外部不能访问。Observable在它生命周期内发射值，我们不用关心这些值，我们只关心他们的结束。\n\n首先，我们创建一个新的PublishSubject来响应它的`onNext()`方法，并且外部也可以访问它。\n\n```java\nfinal PublishSubject<Boolean> subject = PublishSubject.create();\n        \nsubject.subscribe(new Observer<Boolean>() {\n    @Override\n    public void onCompleted() {\n        \n    }\n\n    @Override\n    public void onError(Throwable e) {\n\n    }\n\n    @Override\n    public void onNext(Boolean aBoolean) {\n        System.out.println(\"Observable Completed\");\n    }\n});\n```\n然后，我们创建“私有”的Observable，只有subject才可以访问的到。\n```java\nObservable.create(new Observable.OnSubscribe<Integer>() {\n    @Override\n    public void call(Subscriber<? super Integer> subscriber) {\n        for (int i = 0; i < 5; i++) {\n            subscriber.onNext(i);\n        }\n        subscriber.onCompleted();\n    }\n}).doOnCompleted(new Action0() {\n    @Override\n    public void call() {\n        subject.onNext(true);\n    }\n}).subscribe();\n```\n`Observable.create()`方法包含了我们熟悉的for循环，发射数字。`doOnCompleted()`方法指定当Observable结束时要做什么事情：在subject上发射true。最后，我们订阅了Observable。很明显，空的`subscribe()`调用仅仅是为了开启Observable，而不用管已发出的任何值，也不用管完成事件或者错误事件。为了这个例子我们需要它像这样。\n\n在这个例子中，我们创建了一个可以连接Observables并且同时可被观测的实体。当我们想为公共资源创建独立、抽象或更易观测的点时，这是极其有用的。\n\n### BehaviorSubject\n\n简单的说，BehaviorSubject会首先向他的订阅者发送截至订阅前最新的一个数据对象（或初始值）,然后正常发送订阅后的数据流。\n\n```java\nBehaviorSubject<Integer> behaviorSubject = BehaviorSubject.create(1);\n```\n在这个短例子中，我们创建了一个能发射整形(Integer)的BehaviorSubject。由于每当Observes订阅它时就会发射最新的数据，所以它需要一个初始值。\n### ReplaySubject\n\nReplaySubject会缓存它所订阅的所有数据,向任意一个订阅它的观察者重发:\n```java\nReplaySubject<Integer> replaySubject = ReplaySubject.create();\n```\n\n### AsyncSubject\n\n当Observable完成时AsyncSubject只会发布最后一个数据给已经订阅的每一个观察者。\n\n```java\nAsyncSubject<Integer> asyncSubject = AsyncSubject.create();\n```\n\n## 总结\n\n本章中，我们了解到了什么是观察者模式，为什么Observables在今天的编程场景中如此重要，以及如何创建Observables和subjects。\n\n下一章中，我们将创建第一个基于RxJava的Android应用程序，学习如何检索数据来填充listview，以及探索如何创建一个基于RxJava的响应式UI。"
  },
  {
    "path": "rxjava/chap3.md",
    "content": "\n# RxJava开发精要3-向响应式世界问好\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n## 向响应式世界问好\n\n在上一章中，我们对观察者模式有个理论上的快速概述。我们也看了从头开始、从列表、或者从已经存在的函数来创建Observables。在本章中，我们将用我们学到的来创建我们第一个响应式Android应用程序。首先，我们需要搭建环境，导入需要的库和有用的库。然后我们将创建一个简单的应用程序，在不同的flavors中包含几个用RxJava填充的RecycleView items。\n\n\n## 启动引擎\n\n我们将使用IntelliJ IDEA/Android Studio来创建这个工程，因此你会对截图看起来比较熟悉。\n\n让我们开始创建一个新的Android工程。你可以创建你自己的工程或者用本书中提供的导入。选择你自己喜欢的创建方式这取决于你。\n\n如果你想用Android Studio创建一个新的工程，通常你可以参考官方文档：http://developer.android.com/intl/zh-cn/training/basics/firstapp/creating-project.html\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter3_1.png)\n\n### 依赖\n\n很明显，我们将使用**Gradle**来管理我们的依赖列表。我们的build.gradble文件看起来像这样：\n ![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter3_2.png)\n ![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter3_3.png)\n正如你看到的我们引入了RxAndroid。RxAndroid是RxJava的增强版，尤其是针对Android设计的。\n### RxAndroid\n\nRxAndroid是RxJava家族的一部分。它基于RxJava1.0.x,在普通的RxJava基础上添加了几个有用的类。大多数情况下，它为Android添加了特殊的调度器。我们将在第七章Schedulers-Defeating the Android MainThread Issue再讨论它。\n\n## 工具\n\n出于实用，我们引入了Lombok 和 Butter Knife。这两个可以帮助我们在Android应用程序中少写许多模板类代码。\n\n### Lombok\n\nLombok使用注解的方式为你生成许多代码。我们将使用它老生成`getter/setter`、`toString()`、`equals()`、`hashCode()`。它借助于Gradle依赖和一个Android Studio插件。\n\n### Butter Knife\n\nButter Knife使用注解的方式来帮助我们免去写`findViewById()`和设置点击监听的痛苦。至于Lombok,我们可以通过导入依赖和安装Android Studio插件来获得更好的体验。\n\n### Retrolambda\n\n最后，我们导入Retrolambda，是因为我们开发的Android是基于Java 1.6，然后我们可以借助它来实现Java 8 Lambda函数从而减少许多模板代码。\n\n\n## 我们的第一个Observable\n\n在我们的第一个列子里，我们将检索安装的应用列表并填充RecycleView的item来展示它们。我们也设想一个下拉刷新的功能和一个进度条来告知用户当前任务正在执行。\n\n首先，我们创建Observable。我们需要一个函数来检索安装的应用程序列表并把它提供给我们的观察者。我们一个接一个的发射这些应用程序数据，将它们分组到一个单独的列表中，以此来展示响应式方法的灵活性。\n\n```java\nprivate Observable<AppInfo> getApps(){\n    return Observable.create(subscriber -> {\n        List<AppInfoRich> apps = new ArrayList<AppInfoRich>();\n\n        final Intent mainIntent = new Intent(Intent.ACTION_MAIN,null);\n        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);\n\n        List<ResolveInfo> infos = getActivity().getPackageManager().queryIntentActivities(mainIntent, 0);\n\n        for(ResolveInfo info : infos){\n            apps.add(new AppInfoRich(getActivity(),info));\n        }\n\n        for (AppInfoRich appInfo:apps) {\n            Bitmap icon = Utils.drawableToBitmap(appInfo.getIcon());\n            String name = appInfo.getName();\n            String iconPath = mFilesDir + \"/\" + name;\n            Utils.storeBitmap(App.instance, icon,name);\n            \n            if (subscriber.isUnsubscribed()){\n                return;\n            }\n            subscriber.onNext(new AppInfo(name,iconPath,appInfo.getLastUpdateTime()));                \n        }\n        if (!subscriber.isUnsubscribed()){\n            subscriber.onCompleted();\n        }\n    });\n}\n```\nAppInfo对象如下：\n\n```java\n@Data\n@Accessors(prefix = \"m\")\npublic class AppInfo implements Comparable<Object> {\n\n    long mLastUpdateTime;\n    String mName;\n    String mIcon;\n\n    public AppInfo(String nName, long lastUpdateTime, String icon) {\n        mName = nName;\n        mIcon = icon;\n        mLastUpdateTime = lastUpdateTime;\n    }\n\n    @Override\n    public int compareTo(Object another) {\n        AppInfo f = (AppInfo)another;\n        return getName().compareTo(f.getName());\n    }\n}\n```\n需要重点注意的是在发射新的数据或者完成序列之前要检测观察者的订阅情况。这样的话代码会更高效，因为如果没有观察者等待时我们就不生成没有必要的数据项。\n\n此时，我们可以订阅Observable并观察它。订阅一个Observable意味着当我们需要的数据进来时我们必须提供对应的操作来执行它。\n\n当前的场景是什么？我们展示一个进度条来等待数据。当数据到来时，我们需要隐藏掉进度条,填充list,最终展示列表。现在，我们知道当一切都准备好了该做什么。那么错误的场景呢？对于错误这种情况，我们仅仅是用Toast展示一个错误的信息。\n\n使用Butter Knife,我们得到list和下拉刷新组件的引用：\n\n```java\n@InjetcView(R.id.fragment_first_example_list)\nRecyclerView mRecycleView;\n    \n@InjectView(R.id.fragment_first_example_swipe_container)\nSwipeRefreshLayout mSwipeRefreshLayout;\n```\n\n我们使用Android 5的标准组件：RecyclerView和SwipeRefreshLayout。截屏展示了我们这个简单App的list Fragment的layout文件：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter3_4.png)\n\n我们使用一个下拉刷新方法，因此列表数据可以来自初始化加载，或由用户触发的一个刷新动作。针对这两个场景，我们用同样的行为，因此我们把我们的观察者放在一个易被复用的函数里面。下面是我们的观察者，定义了成功、失败、完成要做的事情：\n\n```java\nprivate void refreshTheList() {\n    getApps().toSortedList()\n            .subscribe(new Observable<List<AppInfo>>() {\n\n                @Override\n                public void onCompleted() {\n                    Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(List<AppInfo> appInfos) {\n                    mRecyclerView.setVisibility(View.VISIBLE);\n                    mAdapter.addApplications(appInfos);\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n            });\n}\n```\n\n定义一个函数使我们能够用同样一个block来处理两种场景成为了可能。当fragment加载时我们只需调用`refreshTheList()`方法并设置`refreshTheList()`方法作为用户下拉这一行为所触发的方法。\n\n```java\nmSwipeRefreshLayout.setOnRefreshListener(this::refreshTheList);\n```\n\n我们第一个例子现在完成了，运行跑一下。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter3_5.png)\n\n\n## 从列表创建一个Observable\n\n在这个例子中，我们将引入`from()`函数。使用这个特殊的“创建”函数，我们可以从一个列表中创建一个Observable。Observable将发射出列表中的每一个元素，我们可以通过订阅它们来对这些发出的元素做出响应。\n\n为了实现和第一个例子同样的结果，我们在每一个`onNext()`函数更新我们的适配器，添加元素并通知插入。\n\n我们将复用和第一个例子同样的结构。主要的不同的是我们不再检索已安装的应用列表。列表由外部实体提供：\n\n```java\nmApps = ApplicationsList.getInstance().getList();\n```\n获得列表后，我们仅需将它响应化并填充RecyclerView的item:\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable.from(apps)\n            .subscribe(new Observable<AppInfo>() {\n\n                @Override\n                public void onCompleted() {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                    Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(AppInfo appInfo) {\n                    mAddedApps.add(appInfo); \n                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n                }\n            });\n}\n```\n正如你看到的，我们将已安装的应用程序列表作为参数传进`from()`函数，然后我们订阅生成的Observable。观察者和我们第一个例子中的观察者十分相像。一个主要的不同是我们在`onCompleted()`函数中停掉进度条是因为我们一个一个的发射元素；第一个例子中的Observable发射的是整个list,因此在`onNext()`函数中停掉进度条的做法是安全的。\n\n\n\n## 再多几个例子\n\n在这一节中，我们将基于RxJava的`just()`,`repeat()`,`defer()`,`range()`,`interval()`,和`timer()`方法展示一些例子。\n\n### just()\n\n假如我们只有3个独立的AppInfo对象并且我们想把他们转化为Observable并填充到RecyclerView的item中：\n```java\nList<AppInfo> apps = ApplicationsList.getInstance().getList();\n\nAppInfo appOne = apps.get(0);\n\nAppInfo appTwo = apps.get(10);\n\nAppInfo appThree = apps.get(24);\n\nloadApps(appOne,appTwo,appThree);\n\n```\n\n我们可以像我们之前的例子那样检索列表并提取出这三个元素。然后我们将他们传到这个`loadApps()`函数里面：\n```java\nprivate void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable.just(appOne,appTwo,appThree)\n            .subscribe(new Observable<AppInfo>() {\n\n                @Override\n                public void onCompleted() {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                    Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(AppInfo appInfo) {\n                    mAddedApps.add(appInfo); \n                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n                }\n            });\n}\n```\n\n正如你看到的，代码和之前的例子很像。这种方法让我们有机会来考虑一下代码的复用。\n\n你可以将一个函数作为参数传给`just()`方法，你将会得到一个已存在代码的原始Observable版本。在一个新的响应式架构的基础上迁移已存在的代码，这个方法可能是一个有用的开始点。\n\n\n### repeat()\n\n假如你想对一个Observable重复发射三次数据。例如，我们用`just()`例子中的Observable：\n\n```java\nprivate void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable.just(appOne,appTwo,appThree)\n            .repeat(3)\n            .subscribe(new Observable<AppInfo>() {\n\n                @Override\n                public void onCompleted() {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                    Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(AppInfo appInfo) {\n                    mAddedApps.add(appInfo); \n                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n                }\n            });\n}\n```\n正如你看到的，我们在`just()`创建Observable后追加了`repeat(3)`，它将会创建9个元素的序列，每一个都单独发射。\n\n### defer()\n\n有这样一个场景，你想在这声明一个Observable但是你又想推迟这个Observable的创建直到观察者订阅时。看下面的`getInt()`函数：\n```java\nprivate Observable<Integer> getInt(){\n    return Observable.create(subscriber -> {\n        if(subscriber.isUnsubscribed()){\n            return;\n        }\n        App.L.debug(\"GETINT\");\n        subscriber.onNext(42);\n        subscriber.onCompleted();\n    });\n}\n```\n\n这比较简单，并且它没有做太多事情，但是它正好为我们服务。现在，我们可以创建一个新的Observable并且应用`defer()`:\n\n```java\nObservable<Integer> deferred = Observable.defer(this::getInt);\n```\n这次，`deferred`存在，但是`getInt()` `create()`方法还没有调用:logcat日志也没有“GETINT”打印出来:\n\n```java\ndeferred.subscribe(number -> {\n    App.L.debug(String.valueOf(number));\n});\n```\n但是一旦我们订阅了，`create()`方法就会被调用并且我们也可以在logcat日志中得到下卖弄两个：GETINT和42。\n\n### range()\n\n你需要从一个指定的数字X开始发射N个数字吗？你可以用`range`:\n```java\nObservable.range(10,3)\n    .subscribe(new Observable<Integer>() {\n\n        @Override\n        public void onCompleted() {\n            Toast.makeText(getActivity(), \"Yeaaah!\", Toast.LENGTH_LONG).show();\n        }\n\n        @Override\n        public void onError(Throwable e) {\n            Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n        }\n\n        @Override\n        public void onNext(Integer number) {\n            Toast.makeText(getActivity(), \"I say \" + number, Toast.LENGTH_SHORT).show();\n        }\n    });\n```\n\n`range()`函数用两个数字作为参数：第一个是起始点，第二个是我们想发射数字的个数。\n\n\n### interval()\n\n`interval()`函数在你需要创建一个轮询程序时非常好用。\n```java\nSubscription stopMePlease = Observable.interval(3,TimeUnit.SECONDS)\n    .subscribe(new Observable<Integer>() {\n\n        @Override\n        public void onCompleted() {\n            Toast.makeText(getActivity(), \"Yeaaah!\", Toast.LENGTH_LONG).show();\n        }\n\n        @Override\n        public void onError(Throwable e) {\n            Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n        }\n\n        @Override\n        public void onNext(Integer number) {\n            Toast.makeText(getActivity(), \"I say \" + number, Toast.LENGTH_SHORT).show();\n        }\n    });\n```\n`interval()`函数的两个参数：一个指定两次发射的时间间隔，另一个是用到的时间单位。\n\n\n### timer()\n\n如果你需要一个一段时间之后才发射的Observable，你可以像下面的例子使用`timer()`：\n\n```java\nObservable.timer(3,TimeUnit.SECONDS)\n    .subscribe(new Observable<Long>() {\n\n        @Override\n        public void onCompleted() {\n            \n        }\n\n        @Override\n        public void onError(Throwable e) {\n            \n        }\n\n        @Override\n        public void onNext(Long number) {\n            Log.d(\"RXJAVA\", \"I say \" + number);\n        }\n    });\n```\n它将3秒后发射0,然后就完成了。让我们使用`timer()`的第三个参数，就像下面的例子：\n```java\nObservable.timer(3,3,TimeUnit.SECONDS)\n    .subscribe(new Observable<Long>() {\n\n        @Override\n        public void onCompleted() {\n            \n        }\n\n        @Override\n        public void onError(Throwable e) {\n            \n        }\n\n        @Override\n        public void onNext(Long number) {\n            Log.d(\"RXJAVA\", \"I say \" + number);\n        }\n    });\n```\n用这个代码，你可以创建一个以初始值来延迟（上一个例子是3秒）执行的`interval()`版本，然后每隔N秒就发射一个新的数字（前面的例子是3秒）。\n\n\n## 总结\n\n在本章中，我们创建了第一个由RxJava强化的Android应用程序。我们从头、从已有的列表、从已有的函数来创建Observable。我们也学习了如何创建重复发射的Observables，间隔发射的Observables以及延迟发射的Observables。\n\n在下一章中，我们将掌握过滤操作，能够从我们接收到的序列中创建我们需要的序列。\n"
  },
  {
    "path": "rxjava/chap4.md",
    "content": "# RxJava开发精要4 - Observables过滤\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n## 过滤Observables\n\n在上一章中，我们学习了使用RxJava创建一个Android工程以及如何创建一个可观测的列表来填充RecyclerView。我们现在知道了如何从头、从列表、从一个已存在的传统Java函数来创建Observable。\n\n这一章中，我们将研究可观测序列的本质：过滤。我们将学到如何从发射的Observable中选取我们想要的值，如何获取有限个数的值，如何处理溢出的场景，以及更多的有用的技巧。\n\n## 过滤序列\n\nRxJava让我们使用`filter()`方法来过滤我们观测序列中不想要的值，在上一章中，我们在几个例子中使用了已安装的应用列表，但是我们只想展示以字母`C`开头的已安装的应用该怎么办呢？在这个新的例子中，我们将使用同样的列表，但是我们会过滤它，通过把合适的谓词传给`filter()`函数来得到我们想要的值。\n\n上一章中`loadList()`函数可以改成这样：\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable.from(apps)\n            .filter((appInfo) ->\n            appInfo.getName().startsWith(\"C\"))\n            .subscribe(new Observable<AppInfo>() {\n\n                @Override\n                public void onCompleted() {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(AppInfo appInfo) {\n                    mAddedApps.add(appInfo); \n                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n                }\n            });\n}\n```\n我们从上一章中的`loadList()`函数中添加下面一行：\n```java\n.fliter((appInfo -> appInfo.getName().startsWith(\"C\"))\n```\n创建Observable完以后，我们从发出的每个元素中过滤掉开头字母不是C的。为了让这里更清楚一些，我们用Java 7的语法来实现：\n```java\n.filter(new Func1<AppInfo,Boolean>(){\n    @Override\n    public Boolean call(AppInfo appInfo){\n        return appInfo.getName().startsWith(\"C\");\n    }\n})\n```\n\n我们传一个新的`Func1`对象给`filter()`函数，即只有一个参数的函数。`Func1`有一个`AppInfo`对象来作为它的参数类型并且返回`Boolean`对象。只要条件符合`filter()`函数就会返回`true`。此时，值会发射出去并且所有的观察者都会接收到。\n\n正如你想的那样，从一个我们得到的可观测序列中创建一个我们需要的序列`filter()`是很好用的。我们不需要知道可观测序列的源或者为什么发射这么多不同的数据。我们只是想要这些元素的子集来创建一个可以在应用中使用的新序列。这种思想促进了我们编码中的分离性与抽象性。\n\n`filter()`函数最常用的用法之一时过滤`null`对象：\n```java\n.filter(new Func1<AppInfo,Boolean>(){\n    @Override\n    public Boolean call(AppInfo appInfo){\n        return appInfo != null;\n    }\n})\n```\n这看起来简单，对于简单的事情有许多模板代码，但是它帮我们免去了在`onNext()`函数调用中再去检测`null`值，让我们把注意力集中在应用业务逻辑上。\n\n下图展示了过滤出的C字母开头的已安装的应用列表。\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_1.png\" width=\"240\" />\n\n\n\n## 获取我们需要的数据\n\n当我们不需要整个序列时，而是只想取开头或结尾的几个元素，我们可以用`take()`或`takeLast()`。\n\n## Take\n\n如果我们只想要一个可观测序列中的前三个元素那将会怎么样，发射它们，然后让Observable完成吗？`take()`函数用整数N来作为一个参数，从原始的序列中发射前N个元素，然后完成：\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable.from(apps)\n            .take(3)\n            .subscribe(new Observable<AppInfo>() {\n\n                @Override\n                public void onCompleted() {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(AppInfo appInfo) {\n                    mAddedApps.add(appInfo); \n                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n                }\n            });\n}\n```\n下图中展示了发射数字的一个可观测序列。我们对这个可观测序列应用`take(2)`函数，然后我们创建一个只发射可观测源的第一个和第二个数据的新序列。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_2.png)\n\n## TakeLast\n\n如果我们想要最后N个元素，我们只需使用`takeLast()`函数：\n```java\nObservable.from(apps)\n        .takeLast(3)\n        .subscribe(https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.);\n```\n正如听起来那样不值一提，重点注意`takeLast()`函数由于用一组有限的发射数的本质使得它仅可用于完成的序列。\n\n下图中展示了如何从可观测源中发射最后一个元素来创建一个新的序列：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_3.png)\n\n下图中展示了我们在已安装的应用列表使用`take()`和`takeLast()`函数后发生的结果：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_4.png)\n\n\n## 有且仅有一次\n\n一个可观测序列会在出错时重复发射或者被设计成重复发射。`distinct()`和`distinctUntilChanged()`函数可以方便的让我们处理这种重复问题。\n\n## Distinct\n\n如果我们想对一个指定的值仅处理一次该怎么办？我们可以对我们的序列使用`distinct()`函数去掉重复的。就像`takeLast()`一样，`distinct()`作用于一个完整的序列，然后得到重复的过滤项，它需要记录每一个发射的值。如果你在处理一大堆序列或者大的数据记得关注内存使用情况。\n\n下图展示了如何在一个发射1和2两次的可观测源上创建一个无重的序列：\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_5.png)\n\n为了创建我们例子中序列，我们将使用我们至今已经学到的几个方法：\n* `take()`：它有一小组的可识别的数据项。\n* `repeat()`：创建一个有重复的大的序列。\n\n然后，我们将应用`distinct()`函数来去除重复。\n\n## 注意\n\n我们用程序实现一个重复的序列，然后过滤出它们。这听起来时不可思议的，但是为了实现这个例子来使用我们至今为止已学习到的东西则是个不错的练习。\n\n```java\nObservable<AppInfo> fullOfDuplicates = Observable.from(apps)\n    .take(3)\n    .repeat(3);\n```\n`fullOfDuplicates`变量里把我们已安装应用的前三个重复了3次：有9个并且许多重复的。然后，我们使用`distinct()`:\n\n```java\nfullOfDuplicates.distinct()\n            .subscribe(new Observable<AppInfo>() {\n\n                @Override\n                public void onCompleted() {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(AppInfo appInfo) {\n                    mAddedApps.add(appInfo); \n                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n                }\n            });\n}\n```\n\n结果，很明显，我们得到：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_6.png\" width=\"240\" />\n\n## DistinctUntilsChanged\n\n如果在一个可观测序列发射一个不同于之前的一个新值时让我们得到通知这时候该怎么做？我们猜想一下我们观测的温度传感器，每秒发射的室内温度：\n```\n21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.22°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.\n```\n每次我们获得一个新值，我们都会更新当前正在显示的温度。我们出于系统资源保护并不想在每次值一样时更新数据。我们想忽略掉重复的值并且在温度确实改变时才想得到通知。`ditinctUntilChanged()`过滤函数能做到这一点。它能轻易的忽略掉所有的重复并且只发射出新的值。\n\n下图用图形化的方式展示了我们如何将`distinctUntilChanged()`函数应用在一个存在的序列上来创建一个新的不重复发射元素的序列。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_7.png)\n\n## First and last\n\n下图展示了如何从一个从可观测源序列中创建只发射第一个元素的序列。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_8.png)\n\n`first()`方法和`last()`方法很容易弄明白。它们从Observable中只发射第一个元素或者最后一个元素。这两个都可以传`Func1`作为参数，：一个可以确定我们感兴趣的第一个或者最后一个的谓词：\n\n下图展示了`last()`应用在一个完成的序列上来创建一个仅仅发射最后一个元素的新的Observable。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_9.png)\n\n与`first()`和`last()`相似的变量有：`firstOrDefault()`和`lastOrDefault()`.这两个函数当可观测序列完成时不再发射任何值时用得上。在这种场景下，如果Observable不再发射任何值时我们可以指定发射一个默认的值\n\n## Skip and SkipLast\n\n下图中展示了如何使用`skip(2)`来创建一个不发射前两个元素而是发射它后面的那些数据的序列。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_10.png)\n\n`skip()`和`skipLast()`函数与`take()`和`takeLast()`相对应。它们用整数N作参数，从本质上来说，它们不让Observable发射前N个或者后N个值。如果我们知道一个序列以没有太多用的“可控”元素开头或结尾时我们可以使用它。\n\n下图与前一个场景相对应：我们创建一个新的序列，它会跳过后面两个元素从源序列中发射剩下的其他元素。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_11.png)\n\n\n## ElementAt\n\n如果我们只想要可观测序列发射的第五个元素该怎么办？`elementAt()`函数仅从一个序列中发射第n个元素然后就完成了。\n\n如果我们想查找第五个元素但是可观测序列只有三个元素可供发射时该怎么办？我们可以使用`elementAtOrDefault()`。下图展示了如何通过使用`elementAt(2)`从一个序列中选择第三个元素以及如何创建一个只发射指定元素的新的Observable。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_12.png)\n\n## Sampling\n\n让我们再回到那个温度传感器。它每秒都会发射当前室内的温度。说实话，我们并不认为温度会变化这么快，我们可以使用一个小的发射间隔。在Observable后面加一个`sample()`，我们将创建一个新的可观测序列，它将在一个指定的时间间隔里由Observable发射最近一次的数值：\n\n```java\nObservable<Integer> sensor = [...]\n\nsensor.sample(30,TimeUnit.SECONDS)\n    .subscribe(new Observable<Integer>() {\n\n        @Override\n        public void onCompleted() {\n           \n        }\n\n        @Override\n        public void onError(Throwable e) {\n            \n        }\n\n        @Override\n        public void onNext(Integer currentTemperature) {\n            updateDisplay(currentTemperature)\n        }\n    });\n```\n例子中Observable将会观测温度Observable然后每隔30秒就会发射最后一个温度值。很明显，`sample()`支持全部的时间单位：秒，毫秒，天，分等等。\n\n下图中展示了一个间隔发射字母的Observable如何采样一个发射数字的Observable。Observable的结果将会发射每个已发射字母的最后一组数据：1，4，5.\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_13.png)\n\n如果我们想让它定时发射第一个元素而不是最近的一个元素，我们可以使用`throttleFirst()`。\n\n## Timeout\n\n假设我们工作的是一个时效性的环境，我们温度传感器每秒都在发射一个温度值。我们想让它每隔两秒至少发射一个，我们可以使用`timeout()`函数来监听源可观测序列,就是在我们设定的时间间隔内如果没有得到一个值则发射一个错误。我们可以认为`timeout()`为一个Observable的限时的副本。如果在指定的时间间隔内Observable不发射值的话，它监听的原始的Observable时就会触发`onError()`函数。\n\n```java\nSubscription subscription = getCurrentTemperature()\n    .timeout(2,TimeUnit.SECONDS)\n    .subscribe(new Observable<Integer>() {\n\n        @Override\n        public void onCompleted() {\n           \n        }\n\n        @Override\n        public void onError(Throwable e) {\n            Log.d(\"RXJAVA\",\"You should go check the sensor, dude\");\n        }\n\n        @Override\n        public void onNext(Integer currentTemperature) {\n            updateDisplay(currentTemperature)\n        }\n    });\n```\n\n和`sample()`一样，`timeout()`使用`TimeUnit`对象来指定时间间隔。\n\n下图中展示了一旦Observable超过了限时就会触发`onError()`函数：因为超时后它才到达，所以最后一个元素将不会发射出去。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_14.png)\n\n\n## Debounce\n\n`debounce()`函数过滤掉由Observable发射的速率过快的数据；如果在一个指定的时间间隔过去了仍旧没有发射一个，那么它将发射最后的那个。\n\n就像`sample()`和`timeout()`函数一样，`debounce()`使用`TimeUnit`对象指定时间间隔。\n\n下图展示了多久从Observable发射一次新的数据，`debounce()`函数开启一个内部定时器，如果在这个时间间隔内没有新的数据发射，则新的Observable发射出最后一个数据：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter4_15.png)\n\n## 总结\n\n这一章中，我们学习了如何过滤一个可观测序列。我们现在可以使用`filter()`，`skip()`，和`sample()`来创建我们想要的Observable。\n\n下一章中，我们将学习如何转换一个序列，将函数应用到每个元素，给它们分组和扫描来创建我们所需要的能完成目标的特定Observable。"
  },
  {
    "path": "rxjava/chap5.md",
    "content": "\n\n# RxJava开发精要5 - Observables变换\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n在上一章中，我们探索了RxJava通用过滤方法。我们学习了如何使用`filter()`方法过滤我们不需要的值，如何使用`take()`得到发射元素的子集，如何使用`distinct()`函数来去除重复的。我们学习了如何使用`timeout()`，`sample()`，以及`debounce()`来利用时间。\n\n这一章中，我们将学习如何变换可观测序列来创建一个更好满足我们需求的序列。\n\n## *map家族\n\nRxJava提供了几个mapping函数：`map()`,`flatMap()`,`concatMap()`,`flatMapIterable()`以及`switchMap()`.所有这些函数都作用于一个可观测序列，然后变换它发射的值，最后用一种新的形式返回它们。让我们用“真实世界”合适的例子一个个的学习下。\n\n## Map\n\nRxJava的`map`函数接收一个指定的`Func`对象然后将它应用到每一个由Observable发射的值上。下图展示了如何将一个乘法函数应用到每个发出的值上以此创建一个新的Observable来发射转换的数据。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_1.png)\n\n考虑我们已安装的应用列表。我们怎么才能够显示同样的列表，但是所有的名字都是小写。\n\n我们的`loadList()`函数可以改成这样：\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable.from(apps)\n        .map(new Func1<AppInfo,AppInfo>(){\n            @Override\n            public Appinfo call(AppInfo appInfo){\n                String currentName = appInfo.getName();\n                String lowerCaseName = currentName.toLowerCase();\n                appInfo.setName(lowerCaseName);\n                return appInfo;\n            }\n        })\n        .subscribe(new Observable<AppInfo>() {\n\n            @Override\n            public void onCompleted() {\n                mSwipeRefreshLayout.setRefreshing(false);\n            }\n\n            @Override\n            public void onError(Throwable e) {\n                Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                mSwipeRefreshLayout.setRefreshing(false);\n            }\n\n            @Override\n            public void onNext(AppInfo appInfo) {\n                mAddedApps.add(appInfo); \n                mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n            }\n        });\n}\n```\n\n正如你看到的，像往常一样创建我们发射的Observable，我们加一个`map`调用，我们可以创建一个简单的函数来更新`AppInfo`对象并提供一个名字小写的新版本给观察者。\n\n## FlatMap\n\n在复杂的场景中，我们有一个这样的Observable：它发射一个数据序列，这些数据本身也可以发射Observable。RxJava的`flatMap()`函数提供一种铺平序列的方式，然后合并这些Observables发射的数据，最后将合并后的结果作为最终的Observable。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_2.png)\n\n当我们在处理可能有大量的Observables时，重要是记住任何一个Observables发生错误的情况，`flatMap()`函数将会触发它自己的`onError()`函数并放弃整个链。\n\n重要的一点是关于合并部分：它允许交叉。正如上图所示，这意味着`flatMap()`函数在最后的Observable中不能够保证源Observables确切的发射顺序。\n\n## ConcatMap\n\nRxJava的`concatMap()`函数解决了`flatMap()`的交叉问题，提供了一种能够把发射的值连续在一起的铺平函数，而不是合并它们，如下图所示：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_3.png)\n\n## FlatMapIterable\n\n作为*map家族的一员，`flatMapInterable()`和`flatMap()`很像。仅有的本质不同是它将源数据两两结成对，然后生成Iterable而不是原始数据和生成的Observables。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_4.png)\n\n## SwitchMap\n\n如下图所示，`switchMap()`和`flatMap()`很像，除了一点：当原始Observable发射一个新的数据（Observable）时，它将取消订阅并停止监视之前那个数据的Observable产生的Observable，并开始监视当前这一个。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_5.png)\n\n## Scan\n\nRxJava的`scan()`函数可以看做是一个累加器函数。`scan()`函数对原始Observable发射的每一项数据都应用一个函数，它将函数的结果填充回可观测序列，等待和下一次发射的数据一起使用。\n\n作为一个通用的例子，给出一个累加器：\n```java\nObservable.just(1,2,3,4,5)\n        .scan((sum,item) -> sum + item)\n        .subscribe(new Subscriber<Integer>() {\n            @Override\n            public void onCompleted() {\n                Log.d(\"RXJAVA\", \"Sequence completed.\");\n            }\n\n            @Override\n            public void onError(Throwable e) {\n                Log.e(\"RXJAVA\", \"Something went south!\");\n            }\n\n            @Override\n            public void onNext(Integer item) {\n                Log.d(\"RXJAVA\", \"item is: \" + item);\n            }\n        });\n```\n我们得到的结果是：\n```java\nRXJAVA: item is: 1\nRXJAVA: item is: 3\nRXJAVA: item is: 6\nRXJAVA: item is: 10\nRXJAVA: item is: 15\nRXJAVA: Sequence completed.\n```\n我们也可以创建一个新版本的`loadList()`函数用来比较每个安装应用的名字从而创建一个名字长度递增的列表。\n\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable.from(apps)\n            .scan((appInfo,appInfo2) -> {\n                if(appInfo.getName().length > appInfo2.getName().length()){\n                    return appInfo;\n                } else {\n                    return appInfo2;\n                }\n            })\n            .distinct()\n            .subscribe(new Observable<AppInfo>() {\n\n                @Override\n                public void onCompleted() {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onError(Throwable e) {\n                    Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                    mSwipeRefreshLayout.setRefreshing(false);\n                }\n\n                @Override\n                public void onNext(AppInfo appInfo) {\n                    mAddedApps.add(appInfo); \n                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n                }\n            });\n}\n```\n\n结果如下：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_6.png\" width=\"240\"/>\n\n\n有一个`scan()`函数的变体，它用初始值作为第一个发射的值，方法特征就像：`scan(R,Func2)`，就像下图中的例子这样：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_7.png\" />\n\n\n## GroupBy\n\n拿第一个例子开始，我们安装的应用程序列表按照字母表的顺序排序。然而，如果现在我们想按照最近更新日期来排序我们的App时该怎么办？RxJava提供了一个有用的函数从列表中按照指定的规则：`groupBy()`来分组元素。下图中的例子展示了`groupBy()`如何将发射的值根据他们的形状来进行分组。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_8.png)\n\n这个函数将源Observable变换成一个发射Observables的新的Observable。它们中的每一个新的Observable都发射一组指定的数据。\n\n为了创建一个分组了的已安装应用列表，我们在`loadList()`函数中引入了一个新的元素：\n```java\nObservable<GroupedObservable<String,AppInfo>> groupedItems = Observable.from(apps)\n    .groupBy(new Func1<AppInfo,String>(){\n        @Override\n        public String call(AppInfo appInfo){\n            SimpleDateFormat formatter = new SimpleDateFormat(\"MM/yyyy\");\n            return formatter.format(new Date(appInfo.getLastUpdateTime()));\n        }\n    });\n```\n现在我们创建了一个新的Observable，`groupedItems`，将会发射一个带有`GroupedObservable`的序列。`GroupedObservable`是一个特殊的Observable，它源自一个分组的key。在这个例子中，key就是`String`，代表的意思是`Month/Year`格式化的最近更新日期。\n\n这一点，我们已经创建了几个发射`AppInfo`数据的Observable，用来填充我们的列表。我们想保留字母排序和分组排序。我们将创建一个新的Observable将所有的联系起来，像通常一样然后订阅它：\n\n```java\nObservable.concat(groupedItems)\n    .subscribe(new Observable<AppInfo>() {\n\n        @Override\n        public void onCompleted() {\n            mSwipeRefreshLayout.setRefreshing(false);\n        }\n\n        @Override\n        public void onError(Throwable e) {\n            Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n            mSwipeRefreshLayout.setRefreshing(false);\n        }\n\n        @Override\n        public void onNext(AppInfo appInfo) {\n            mAddedApps.add(appInfo); \n            mAdapter.addApplication(mAddedApps.size() - 1,appInfo);\n        }\n    });\n```\n\n我们的`loadList()`函数完成了，结果是：\n\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_9.png\" width=\"240\"/>\n\n\n## Buffer\n\nRxJava中的`buffer()`函数将源Observable变换一个新的Observable，这个新的Observable每次发射一组列表值而不是一个一个发射。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_10.png)\n\n上图中展示了`buffer()`如何将`count`作为一个参数来指定有多少数据项被包在发射的列表中。实际上，`buffer()`函数有几种变体。其中有一个时允许你指定一个`skip`值：此后每当收到skip项数据，用count项数据就填充缓存。如下图所示：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_11.png)\n\n`buffer()`带一个`timespan`的参数，会创建一个每隔timespan时间段就会发射一个列表的Observable。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_12.png)\n\n\n## Window\n\nRxJava的`window()`函数和`buffer()`很像，但是它发射的时Observable而不是列表。下图展示了`window()`如何缓存3个数据项并把它们作为一个新的Observable发射出去。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_13.png)\n\n这些Observables中的每一个都发射原始Observable数据的一个子集，数量由`count`指定,最后发射一个`onCompleted()`结束。正如`buffer()`一样,`window()`也有一个`skip`变体,如下图所示：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_14.png)\n\n## Cast\n\nRxJava的`cast()`函数是本章中最后一个操作符。它是`map()`操作符的特殊版本。它将源Observable中的每一项数据都转换为新的类型，把它变成了不同的`Class`。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter5_15.png)\n\n\n## 总结\n\n这一章中，我们学习了RxJava时如何控制和转换可观测序列。用我们现在所学的知识，我们可以创建、过滤、转换我们所想要的任何种类的可观测序列。\n\n下一章，我们将学习如何组合Observable，合并它们，连接它们，再或者打包它们。\n"
  },
  {
    "path": "rxjava/chap6.md",
    "content": "\n# RxJava开发精要6 - 组合Observables\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n上一章中，我们学到如何转换可观测序列。我们也看到了`map()`,`scan()`,`groupBY()`,以及更多有用的函数的实际例子，它们帮助我们操作Observable来创建我们想要的Observable。\n\n本章中，我们将研究组合函数并学习如何同时处理多个Observables来创建我们想要的Observable。\n\n\n## Merge\n\n在异步的世界经常会创建这样的场景，我们有多个来源但是只想有一个结果：多输入，单输出。RxJava的`merge()`方法将帮助你把两个甚至更多的Observables合并到他们发射的数据里。下图给出了把两个序列合并在一个最终发射的Observable。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_1.png)\n\n正如你看到的那样，发射的数据被交叉合并到一个Observable里面。注意如果你同步的合并Observable，它们将连接在一起并且不会交叉。\n\n像通常一样，我们用我们的App和已安装的App列表来创建了一个“真实世界”的例子。我们还需要第二个Observable。我们可以创建一个单独的应用列表然后逆序。当然没有实际的意义，只是为了这个例子。第二个列表，我们的`loadList()`函数像下面这样：\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    List reversedApps = Lists.reverse(apps);\n    Observable<AppInfo> observableApps =Observable.from(apps);\n    Observable<AppInfo> observableReversedApps =Observable.from(reversedApps);\n    Observable<AppInfo> mergedObserbable = Observable.merge(observableApps,observableReversedApps);\n    \n    mergedObserbable.subscribe(new Observer<AppInfo>(){\n        @Override\n        public void onCompleted() {\n            mSwipeRefreshLayout.setRefreshing(false);\n            Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n        }\n        \n        @Override\n        public void onError(Throwable e) {\n            Toast.makeText(getActivity(), \"One of the two Observable threw an error!\", Toast.LENGTH_SHORT).show();\n            mSwipeRefreshLayout.setRefreshing(false);\n        }\n        \n        @Override\n        public void onNext(AppInfoappInfo) {\n            mAddedApps.add(appInfo);\n            mAdapter.addApplication(mAddedApps.size() - 1, appInfo);\n        } \n    });\n}\n```\n\n我们创建了Observable和observableApps数据以及新的observableReversedApps逆序列表。使用`Observable.merge()`，我们可以创建新的`ObservableMergedObservable`在单个可观测序列中发射源Observables发出的所有数据。\n\n正如你能看到的,每个方法签名都是一样的，因此我们的观察者无需在意任何不同就可以复用代码。结果如下：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_2.png\" width=\"240\"/>\n\n注意错误时的toast消息，你可以认为每个Observable抛出的错误将会打断合并。如果你需要避免这种情况，RxJava提供了`mergeDelayError()`，它能从一个Observable中继续发射数据即便是其中有一个抛出了错误。当所有的Observables都完成时，`mergeDelayError()`将会发射`onError()`，如下图所示：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_3.png)\n\n\n## ZIP\n\n我们在处理多源时可能会带来这样一种场景：多从个Observables接收数据，处理它们，然后将它们合并成一个新的可观测序列来使用。RxJava有一个特殊的方法可以完成：`zip()`合并两个或者多个Observables发射出的数据项，根据指定的函数`Func*`变换它们，并发射一个新值。下图展示了`zip()`方法如何处理发射的“numbers”和“letters”然后将它们合并一个新的数据项：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_4.png)\n\n对于“真实世界”的例子来说，我们将使用已安装的应用列表和一个新的动态的Observable来让例子变得有点有趣味。\n\n```java\nObservable<Long> tictoc = Observable.interval(1, TimeUnit.SECONDS);\n```\n`tictoc`Observable变量使用`interval()`函数每秒生成一个Long类型的数据：简单且高效，正如之前所说的，我们需要一个`Func`对象。因为它需要传两个参数，所以是`Func2`:\n\n```java\nprivate AppInfo updateTitle(AppInfoappInfo, Long time) {\n    appInfo.setName(time + \" \" + appInfo.getName());\n    return appInfo;\n}\n```\n现在我们的`loadList()`函数变成这样：\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable<AppInfo> observableApp = Observable.from(apps);\n    \n    Observable<Long> tictoc = Observable.interval(1, TimeUnit.SECONDS);\n    \n    Observable.zip(observableApp, tictoc,\n    (AppInfo appInfo, Long time) -> updateTitle(appInfo, time))\n    .observeOn(AndroidSchedulers.mainThread())\n    .subscribe(new Observer<AppInfo>() {\n        @Override\n        public void onCompleted() {\n            Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n        }\n        \n        @Override\n        public void onError(Throwable e) {\n            mSwipeRefreshLayout.setRefreshing(false);\n            Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n        }\n        \n        @Override\n        public void onNext(AppInfoappInfo) {\n            if (mSwipeRefreshLayout.isRefreshing()) {\n                mSwipeRefreshLayout.setRefreshing(false);\n            } \n            mAddedApps.add(appInfo);\n            int position = mAddedApps.size() - 1;\n            mAdapter.addApplication(position, appInfo);\n            mRecyclerView.smoothScrollToPosition(position);\n        }\n    });\n}\n```\n正如你看到的那样，`zip()`函数有三个参数：两个Observables和一个`Func2`。\n\n仔细一看会发现`observeOn()`函数。它将在下一章中讲解：现在我们可以小试一下。\n\n结果如下：\n\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_5.png\" width=\"320\"/>\n\n## Join\n\n前面两个方法，`zip()`和`merge()`方法作用在发射数据的范畴内，在决定如何操作值之前有些场景我们需要考虑时间的。RxJava的`join()`函数基于时间窗口将两个Observables发射的数据结合在一起。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_6.png)\n\n为了正确的理解上一张图，我们解释下`join()`需要的参数：\n\n* 第二个Observable和源Observable结合。\n* `Func1`参数：在指定的由时间窗口定义时间间隔内，源Observable发射的数据和从第二个Observable发射的数据相互配合返回的Observable。\n* `Func1`参数：在指定的由时间窗口定义时间间隔内，第二个Observable发射的数据和从源Observable发射的数据相互配合返回的Observable。\n* `Func2`参数：定义已发射的数据如何与新发射的数据项相结合。\n* \n如下练习的例子，我们可以修改`loadList()`函数像下面这样：\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    \n    Observable<AppInfo> appsSequence =\n    Observable.interval(1000, TimeUnit.MILLISECONDS)\n                .map(position -> {\n                    return apps.get(position.intValue());\n                });\n                \n    Observable<Long> tictoc = Observable.interval(1000,TimeUnit.MILLISECONDS);\n    \n    appsSequence.join(\n        tictoc, \n        appInfo -> Observable.timer(2,TimeUnit.SECONDS),\n        time -> Observable.timer(0, TimeUnit.SECONDS),\n        this::updateTitle)\n        .observeOn(AndroidSchedulers.mainThread())\n        .take(10)\n        .subscribe(new Observer<AppInfo>() {\n            @Override\n            public void onCompleted() {\n                Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n            }\n            \n            @Override\n            public void onError(Throwable e) {\n                mSwipeRefreshLayout.setRefreshing(false); \n                Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n            }\n            \n            @Override\n            public void onNext(AppInfoappInfo) {\n                if (mSwipeRefreshLayout.isRefreshing()) {\n                    mSwipeRefreshLayout.setRefreshing(false);\n                } \n                mAddedApps.add(appInfo);\n                int position = mAddedApps.size() - 1;\n                mAdapter.addApplication(position, appInfo);\n                mRecyclerView.smoothScrollToPosition(position);\n            } \n        });\n}\n```\n\n我们有一个新的对象`appsSequence`，它是一个每秒从我们已安装的app列表发射app数据的可观测序列。`tictoc`这个Observable数据每秒只发射一个新的`Long`型整数。为了合并它们，我们需要指定两个`Func1`变量：\n\n```java\nappInfo -> Observable.timer(2, TimeUnit.SECONDS)\n\ntime -> Observable.timer(0, TimeUnit.SECONDS)\n```\n上面描述了两个时间窗口。下面一行描述我们如何使用`Func2`将两个发射的数据结合在一起。\n```java\nthis::updateTitle\n```\n\n结果如下：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_7.png\" width=\"240\"/>\n\n它看起来有点乱，但是注意app的名字和我们指定的时间窗口，我们可以看到：一旦第二个数据发射了我们就会将它与源数据结合，但我们用同一个源数据有2秒钟。这就是为什么标题重复数字累加的原因。\n\n值得一提的是，为了简单起见，也有一个`join()`操作符作用于字符串然后简单的和发射的字符串连接成最终的字符串。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_8.png)\n\n## combineLatest\n\nRxJava的`combineLatest()`函数有点像`zip()`函数的特殊形式。正如我们已经学习的，`zip()`作用于最近未打包的两个Observables。相反，`combineLatest()`作用于最近发射的数据项：如果`Observable1`发射了A并且`Observable2`发射了B和C，`combineLatest()`将会分组处理AB和AC，如下图所示：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_9.png)\n\n`combineLatest()`函数接受二到九个Observable作为参数，如果有需要的话或者单个Observables列表作为参数。\n\n从之前的例子中把`loadList()`函数借用过来，我们可以修改一下来用于`combineLatest()`实现“真实世界”这个例子：\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    Observable<AppInfo> appsSequence = Observable.interval(1000, TimeUnit.MILLISECONDS)\n              .map(position ->apps.get(position.intValue()));\n    Observable<Long> tictoc = Observable.interval(1500, TimeUnit.MILLISECONDS);\n    Observable.combineLatest(appsSequence, tictoc,\n               this::updateTitle)\n       .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(new Observer<AppInfo>() {\n        \n        @Override\n        public void onCompleted() {\n            Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n        }\n        \n        @Override\n        public void onError(Throwable e) {\n            mSwipeRefreshLayout.setRefreshing(false);\n            Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n        }\n        \n        @Override\n        public void onNext(AppInfoappInfo) {\n            if (mSwipeRefreshLayout.isRefreshing()) {\n                mSwipeRefreshLayout.setRefreshing(false);\n            } \n            mAddedApps.add(appInfo);\n            int position = mAddedApps.size() - 1;\n            mAdapter.addApplication(position, appInfo);\n            mRecyclerView.smoothScrollToPosition(position);\n        } \n    });\n}\n```\n这我们使用了两个Observables：一个是每秒钟从我们已安装的应用列表发射一个App数据，第二个是每隔1.5秒发射一个`Long`型整数。我们将他们结合起来并执行`updateTitle()`函数，结果如下：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_10.png\" width=\"240\"/>\n\n正如你看到的，由于不同的时间间隔，`AppInfo`对象如我们所预料的那样有时候会重复。\n\n## And,Then和When\n\n在将来还有一些`zip()`满足不了的场景。如复杂的架构，或者是仅仅为了个人爱好，你可以使用And/Then/When解决方案。它们在RxJava的joins包下，使用Pattern和Plan作为中介，将发射的数据集合并到一起。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_11.png)\n\n我们的`loadList()`函数将会被修改从这样：\n```java\nprivate void loadList(List<AppInfo> apps) {\n\n    mRecyclerView.setVisibility(View.VISIBLE);\n\n    Observable<AppInfo> observableApp = Observable.from(apps);\n    \n    Observable<Long> tictoc = Observable.interval(1, TimeUnit.SECONDS);\n    \n    Pattern2<AppInfo, Long> pattern = JoinObservable.from(observableApp).and(tictoc); \n    \n    Plan0<AppInfo> plan = pattern.then(this::updateTitle);\n    \n    JoinObservable\n        .when(plan)\n        .toObservable()\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(new Observer<AppInfo>() {\n        \n            @Override\n            public void onCompleted() {\n                Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n            }\n            \n            @Override\n            public void onError(Throwable e) {\n                mSwipeRefreshLayout.setRefreshing(false); \n                Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n            }\n            \n            @Override\n            public void onNext(AppInfoappInfo) {\n                if (mSwipeRefreshLayout.isRefreshing()) { \n                mSwipeRefreshLayout.setRefreshing(false);\n                } \n                mAddedApps.add(appInfo);\n                int position = mAddedApps.size() - 1;\n                mAdapter.addApplication(position, appInfo); mRecyclerView.smoothScrollToPosition(position);\n            } \n        });\n}\n```\n和通常一样，我们有两个发射的序列，`observableApp`，发射我们安装的应用列表数据，`tictoc`每秒发射一个`Long`型整数。现在我们用`and()`连接源Observable和第二个Observable。\n\n```java\nJoinObservable.from(observableApp).and(tictoc);\n```\n这里创建一个`pattern`对象，使用这个对象我们可以创建一个`Plan`对象:\"我们有两个发射数据的Observables,`then()`是做什么的？\"\n```java\npattern.then(this::updateTitle);\n```\n现在我们有了一个`Plan`对象并且当plan发生时我们可以决定接下来发生的事情。\n```java\n.when(plan).toObservable()\n```\n这时候，我们可以订阅新的Observable，正如我们总是做的那样。\n\n\n\n## Switch\n\n有这样一个复杂的场景就是在一个`subscribe-unsubscribe`的序列里我们能够从一个Observable自动取消订阅来订阅一个新的Observable。\n\nRxJava的`switch()`，正如定义的，将一个发射多个Observables的Observable转换成另一个单独的Observable，后者发射那些Observables最近发射的数据项。\n\n给出一个发射多个Observables序列的源Observable，`switch()`订阅到源Observable然后开始发射由第一个发射的Observable发射的一样的数据。当源Observable发射一个新的Observable时，`switch()`立即取消订阅前一个发射数据的Observable（因此打断了从它那里发射的数据流）然后订阅一个新的Observable，并开始发射它的数据。\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_12.png)\n\n\n## StartWith\n\n我们已经学到如何连接多个Observables并追加指定的值到一个发射序列里。RxJava的`startWith()`是`concat()`的对应部分。正如`concat()`向发射数据的Observable追加数据那样，在Observable开始发射他们的数据之前， `startWith()`通过传递一个参数来先发射一个数据序列。  \n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter6_13.png)\n\n\n\n## 总结\n\n这章中，我们学习了如何将两个或者更多个Observable结合来创建一个新的可观测序列。我们将能够`merge` Observable，`join` Observables ，`zip` Observables 并在几种情况下把他们结合在一起。\n\n下一章，我们将介绍调度器，它将很容易的帮助我们创建主线程以及提高我们应用程序的性能。我们也将学习如何正确的执行长任务或者I/O任务来获得更好的性能。\n"
  },
  {
    "path": "rxjava/chap7.md",
    "content": "# RxJava开发精要7 - Schedulers-解决Android主线程问题\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n前面一章是最后一章关于RxJava的Observable的创建和操作的章节。我们学习到了如何将两个或更多的Observables合并在一起，`join`它们，`zip`它们，`merge`它们以及如何创建一个新的Observable来满足我们特殊的需求。\n\n本章中，我们提升标准看看如何使用RxJava的调度器来处理多线程和并发编程的问题。我们将学习到如何以响应式的方式创建网络操作，内存访问，以及耗时任务。\n\n\n## StrictMode\n\n为了获得更多出现在代码中的关于公共问题的信息，我们激活了`StrictMode`模式。\n\n`StrictMode`帮助我们侦测敏感的活动，如我们无意的在主线程执行磁盘访问或者网络调用。正如你所知道的，在主线程执行繁重的或者长时的任务是不可取的。因为Android应用的主线程时UI线程，它被用来处理和UI相关的操作：这也是获得更平滑的动画体验和响应式App的唯一方法。\n\n为了在我们的App中激活`StrictMode`，我们只需要在`MainActivity`中添加几行代码，即`onCreate()`方法中这样：\n```java\n@Override\npublic void onCreate() { \n    super.onCreate();\n    if (BuildConfig.DEBUG) {\n        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); \n        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());\n    } \n}\n```\n我们并不想它总是激活着，因此我们只在debug构建时使用。这种配置将报告每一种关于主线程用法的违规做法，并且这些做法都可能与内存泄露有关：`Activities`、`BroadcastReceivers`、`Sqlite`等对象。\n\n选择了`penaltyLog()`，当违规做法发生时，`StrictMode`将会在logcat打印一条信息。\n\n\n## 避免阻塞I/O的操作\n\n阻塞I/O的操作将使App能够进行下一步操作前会强制使其等待结果的返回。在UI线程上执行一个阻塞操作将强制使UI卡住，这将直接产生不好的用户体验。\n\n我们激活`StrictMode`后，我们开始收到了关于我们的App错误操作磁盘I/O的不友好信息。\n\n```java\nD/StrictMode  StrictMode policy violation; ~duration=998 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=31 violation=2\nat android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk (StrictMode.java:1135)\nat libcore.io.BlockGuardOs.open(BlockGuardOs.java:106) at libcore.io.IoBridge.open(IoBridge.java:393)\nat java.io.FileOutputStream.<init>(FileOutputStream.java:88) \nat android.app.ContextImpl.openFileOutput(ContextImpl.java:918) \nat android.content.ContextWrapper.openFileOutput(ContextWrapper. java:185)\nat com.packtpub.apps.rxjava_essentials.Utils.storeBitmap (Utils.java:30)\n```\n上一条信息告诉我们`Utils.storeBitmap()`函数执行完耗时998ms：在UI线程上近1秒的不必要的工作和App上近1秒不必要的迟钝。这是因为我们以阻塞的方式访问磁盘。我们的`storeBitmap()`函数包含了：\n```java\nFileOutputStream fOut = context.openFileOutput(filename, Context.MODE_PRIVATE);\n```\n它直接访问智能手机的固态存储然后就慢了。我们该如何提高访问速度呢？`storeBitmap()`函数保存了已安装App的图标。他返回了`void`，因此在执行下一个操作前我们毫无理由去等待直到它完成。我们可以启动它并让它执行在不同的线程。Android中这些年线程管理的变化产生了App诡异的行为。我们可以使用`AsyncTask`，但是我们要避免掉入前几章里的`onPrehttps://github.com/yuxingxin/RxJava-Essentials-CN/raw/master. onPosthttps://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.doInBackGround`地狱。我们将使用RxJava的方式;万岁的调度器！\n\n\n## Schedulers\n\n调度器以一种最简单的方式将多线程用在你的Apps的中。它们时RxJava重要的一部分并能很好地与Observables协同工作。它们无需处理实现、同步、线程、平台限制、平台变化而可以提供一种灵活的方式来创建并发程序。\n\nRxJava提供了5种调度器：\n\n* `.io()`\n* `.computation()`\n* `.immediate()`\n* `.newThread()`\n* `.trampoline()`\n\n让我们一个一个的来看下它们：\n\n## Schedulers.io()\n\n这个调度器时用于I/O操作。它基于根据需要，增长或缩减来自适应的线程池。我们将使用它来修复我们之前看到的`StrictMode`违规做法。由于它专用于I/O操作，所以并不是RxJava的默认方法；正确的使用它是由开发者决定的。\n\n重点需要注意的是线程池是无限制的，大量的I/O调度操作将创建许多个线程并占用内存。一如既往的是，我们需要在性能和简捷两者之间找到一个有效的平衡点。\n\n## Schedulers.computation()\n\n这个是计算工作默认的调度器，它与I/O操作无关。它也是许多RxJava方法的默认调度器：`buffer()`,`debounce()`,`delay()`,`interval()`,`sample()`,`skip()`。\n\n## Schedulers.immediate()\n\n这个调度器允许你立即在当前线程执行你指定的工作。它是`timeout()`,`timeInterval()`,以及`timestamp()`方法默认的调度器。\n\n## Schedulers.newThread()\n\n这个调度器正如它所看起来的那样：它为指定任务启动一个新的线程。\n\n## Schedulers.trampoline()\n\n当我们想在当前线程执行一个任务时，并不是立即，我们可以用`.trampoline()`将它入队。这个调度器将会处理它的队列并且按序运行队列中每一个任务。它是`repeat()`和`retry()`方法默认的调度器。\n\n\n## 非阻塞I/O操作\n\n现在我们知道如何在一个指定I/O调度器上来调度一个任务，我们可以修改`storeBitmap()`函数并再次检查`StrictMode`的不合规做法。为了这个例子，我们可以在新的`blockingStoreBitmap()`函数中重排代码。\n\n```java\nprivate static void blockingStoreBitmap(Context context, Bitmap bitmap, String filename) {\n    FileOutputStream fOut = null; \n    try {\n        fOut = context.openFileOutput(filename, Context.MODE_PRIVATE);\n        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut); \n        fOut.flush();\n        fOut.close();\n    } catch (Exception e) {\n        throw new RuntimeException(e);\n    } finally { \n        try {\n            if (fOut != null) {\n                fOut.close();\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e); \n        }\n    } \n}\n```\n现在我们可以使用`Schedulers.io()`创建非阻塞的版本：\n\n```java\npublic static void storeBitmap(Context context, Bitmap bitmap, String filename) {\n    Schedulers.io().createWorker().schedule(() -> {\n        blockingStoreBitmap(context, bitmap, filename);\n    }); \n}\n```\n\n每次我们调用`storeBitmap()`，RxJava处理创建所有它需要从I / O线程池一个特定的I/ O线程执行我们的任务。所有要执行的操作都避免在UI线程执行并且我们的App比之前要快上1秒：logcat上也不再有`StrictMode`的不合规做法。\n\n下图展示了我们在`storeBitmap()`场景看到的两种方法的不同：\n\n![](https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter7_1.png)\n\n\n## SubscribeOn and ObserveOn\n\n我们学到了如何在一个调度器上运行一个任务。但是我们如何利用它来和Observables一起工作呢？RxJava提供了`subscribeOn()`方法来用于每个Observable对象。`subscribeOn()`方法用`Scheduler`来作为参数并在这个Scheduler上执行Observable调用。\n\n在“真实世界”这个例子中，我们调整`loadList()`函数。首先，我们需要一个新的`getApps()`方法来检索已安装的应用列表：\n\n```java\nprivate Observable<AppInfo> getApps() { \n    return Observable.create(subscriber -> {\n        List<AppInfo> apps = new ArrayList<>();\n        SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);\n        Type appInfoType = new TypeToken<List<AppInfo>>(){}.getType();\n        String serializedApps = sharedPref.getString(\"APPS\", \"\");\n        if (!\"\".equals(serializedApps)) {\n            apps = new Gson().fromJson(serializedApps,appInfoType); \n        }\n        for (AppInfo app : apps) {\n            subscriber.onNext(app);\n        }\n        subscriber.onCompleted(); \n    });\n}\n```\n`getApps()`方法返回一个`AppInfo`的Observable。它先从Android的SharePreferences读取到已安装的应用程序列表。反序列化，并一个接一个的发射AppInfo数据。使用新的方法来检索列表，`loadList()`函数改成下面这样：\n\n```java\nprivate void loadList() {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    getApps().subscribe(new Observer<AppInfo>() {\n        @Override\n        public void onCompleted() {\n            mSwipeRefreshLayout.setRefreshing(false);\n            Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n        }\n        \n        @Override\n        public void onError(Throwable e) {\n            Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n            mSwipeRefreshLayout.setRefreshing(false);\n        }\n        \n        @Override\n        public void onNext(AppInfo appInfo) {\n            mAddedApps.add(appInfo);\n                mAdapter.addApplication(mAddedApps.size() - 1, appInfo);\n        } \n    });\n}\n```\n如果我们运行代码，`StrictMode`将会报告一个不合规操作，这是因为`SharePreferences`会减慢I/O操作。我们所需要做的是指定`getApps()`需要在调度器上执行：\n\n```java\n\ngetApps().subscribeOn(Schedulers.io())\n        .subscribe(new Observer<AppInfo>() { [https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.]\n```\n`Schedulers.io()`将会去掉`StrictMode`的不合规操作，但是我们的App现在崩溃了是因为：\n```java\nat rx.internal.schedulers.ScheduledAction.run(ScheduledAction.jav a:58)\nat java.util.concurrent.Executors$RunnableAdapter.call(Executors. java:422)\nat java.util.concurrent.FutureTask.run(FutureTask.java:237) \nat java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutu reTask.access$201(ScheduledThreadPoolExecutor.java:152)\nat java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutu reTask.run(ScheduledThreadPoolExecutor.java:265)\nat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolEx ecutor.java:1112)\nat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolE xecutor.java:587)\nat java.lang.Thread.run(Thread.java:841) Caused by:\n    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.\n\n```\nOnly the original thread that created a view hierarchy can touch its views.\n\n我们再次回到Android的世界。这条信息简单的告诉我们我们试图在一个非UI线程来修改UI操作。意思是我们需要在I/O调度器上执行我们的代码。因此我们需要和I/O调度器一起执行代码，但是当结果返回时我们需要在UI线程上操作。RxJava让你能够订阅一个指定的调度器并观察它。我们只需在`loadList()`函数添加几行代码，那么每一项就都准备好了：\n\n```java\ngetApps()\n.onBackpressureBuffer()\n.subscribeOn(Schedulers.io())\n.observeOn(AndroidSchedulers.mainThread())\n.subscribe(new Observer<AppInfo>() { [https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.]\n```\n`observeOn()`方法将会在指定的调度器上返回结果：如例子中的UI线程。`onBackpressureBuffer()`方法将告诉Observable发射的数据如果比观察者消费的数据要更快的话，它必须把它们存储在缓存中并提供一个合适的时间给它们。做完这些工作之后，如果我们运行App，就会出现已安装的程序列表：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter7_2.png\" width=\"240\"/>\n\n## 处理耗时的任务\n\n我们已经知道如何处理缓慢的I/O操作。让我们看一个与I/O无关的耗时的任务。例如，我们修改`loadList()`函数并创建一个新的`slow`函数发射我们已安装的app数据。\n\n```java\nprivate Observable<AppInfo> getObservableApps(List<AppInfo> apps) {\n    return Observable .create(subscriber -> {\n        for (double i = 0; i < 1000000000; i++) {\n            double y = i * i;\n        }\n        for (AppInfo app : apps) {\n            subscriber.onNext(app);\n        }\n        subscriber.onCompleted(); \n    });\n}\n```\n\n正如你看到的，这个函数执行了一些毫无意义的计算，只是针对这个例子消耗时间，然后从`List<AppInfo>`对象中发射我们的`AppInfo`数据，现在，我们重排`loadList()`函数如下：\n\n```java\nprivate void loadList(List<AppInfo> apps) {\n    mRecyclerView.setVisibility(View.VISIBLE);\n    getObservableApps(apps)\n        .subscribe(new Observer<AppInfo>() {\n            @Override\n            public void onCompleted() {\n                mSwipeRefreshLayout.setRefreshing(false);\n                Toast.makeText(getActivity(), \"Here is the list!\", Toast.LENGTH_LONG).show();\n            }\n            \n            @Override\n            public void onError(Throwable e) {\n                Toast.makeText(getActivity(), \"Something went wrong!\", Toast.LENGTH_SHORT).show();\n                mSwipeRefreshLayout.setRefreshing(false);\n            }\n            \n            @Override\n            public void onNext(AppInfo appInfo) { \n                mAddedApps.add(appInfo);  \n                mAdapter.addApplication(mAddedApps.size() - 1, appInfo);\n            } \n        });\n}\n```\n\n如果我们运行这段代码，当我们点击`Navigation Drawer`菜单项时App将会卡住一会，然后你能看到下图中半关闭的菜单:\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter7_3.png\" width=\"240\"/>\n\n\n如果我们不够走运的话，我们可以看到下图中经典的ANR信息框：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter7_4.png\" width=\"240\"/>\n\n可以确定的是，我们将会看到下面在logcat中不愉快的信息：\n\n```java\nI/Choreographer  Skipped 598 frames! The application may be doing too much work on its main thread.\n```\n\n这条信息比较清楚，Android在告诉我们用户体验非常差的原因是我们用不必要的工作量阻塞了UI线程。但是我们已经知道了如何处理它：我们有调度器！我们只须添加几行代码到我们的Observable链中就能去掉加载慢和`Choreographer`信息：\n\n```java\ngetObservableApps(apps)\n    .onBackpressureBuffer()\n    .subscribeOn(Schedulers.computation())\n    .observeOn(AndroidSchedulers.mainThread())\n    .subscribe(new Observer<AppInfo>() { [https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.]\n```\n用这几行代码，我们将可以快速关掉`Navigation Drawer`,一个漂亮的进度条，一个工作在独立的线程缓慢执行的计算任务，并在主线程返回结果让我们更新已安装的应用列表。\n\n## 执行网络任务\n\n网络在今天是99%的移动应用的一部分：我们总是连接远端服务器来检索我们App需要的信息。\n\n作为网络访问的第一个方法，我们将创建下面这样一个场景:\n\n* 加载一个进度条。\n* 用一个按钮开始文件下载。\n* 下载过程中更新进度条。\n* 下载完后开始视频播放。\n\n我们的用户界面非常简单，我们只需要一个有趣的进度条和一个下载按钮。\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter7_5.png\" width=\"240\"/>\n\n\n首先，我们创建`mDownloadProgress`\n\n```java\nprivate PublishSubject<Integer>mDownloadProgress = PublishSubject.create();\n```\n这个主题我们用来管理进度的更新，它和`download`函数协同工作。\n```java\nprivate boolean downloadFile(String source, String destination) {\n    boolean result = false;\n    InputStream input = null; \n    OutputStream output = null; \n    HttpURLConnection connection = null;\n    try {\n        URL url = new URL(source);\n        connection = (HttpURLConnection) url.openConnection(); \n        connection.connect();\n        if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {\n            return false;\n        }\n        int fileLength = connection.getContentLength();\n        input = connection.getInputStream();\n        output = new FileOutputStream(destination);\n        byte data[] = new byte[4096];\n        long total = 0;\n        int count;\n        while ((count = input.read(data)) != -1) {\n            total += count;\n            if (fileLength >0) {\n                int percentage = (int) (total * 100 / fileLength);\n                mDownloadProgress.onNext(percentage);\n            }\n            output.write(data, 0, count); \n        }\n        mDownloadProgress.onCompleted(); \n        result = true;\n    } catch (Exception e) { \n        mDownloadProgress.onError(e);\n    } finally { \n        try {\n            if (output != null) { \n                output.close();\n            }\n            if (input != null) {\n                input.close(); \n            }\n        } catch (IOException e) {    \n            mDownloadProgress.onError(e);\n        }\n        if (connection != null) {\n            connection.disconnect();\n            mDownloadProgress.onCompleted();\n        }\n    }\n    return result;\n}\n```\n上面的这段代码将会触发`NetworkOnMainThreadException`异常。我们可以创建RxJava版本的函数进入我们挚爱的响应式世界来解决这个问题：\n\n```java\nprivate Observable<Boolean> obserbableDownload(String source, String destination) {\n    return Observable.create(subscriber -> {\n        try {\n            boolean result = downloadFile(source, destination); \n            if (result) {\n                subscriber.onNext(true);\n                subscriber.onCompleted(); \n            } else {\n                subscriber.onError(new Throwable(\"Download failed.\"));\n            }\n        } catch (Exception e) { \n            subscriber.onError(e);\n        } \n    });\n}\n```\n现在我们需要触发下载操作，点击下载按钮:\n\n```java\n@OnClick(R.id.button_download)\nvoid download() {\n    mButton.setText(getString(R.string.downloading));\n    mButton.setClickable(false);\n    mDownloadProgress.distinct()\n    .observeOn(AndroidSchedulers.mainThread())\n    .subscribe(new Observer<Integer>() {\n    \n        @Override\n        public void onCompleted() {  \n            App.L.debug(\"Completed\");\n        }\n    \n        @Override\n        public void onError(Throwable e) {\n            App.L.error(e.toString()); \n        }\n        \n        @Override\n        public void onNext(Integer progress) {\n            mArcProgress.setProgress(progress);\n        } \n    });\n    \n    String destination = \"sdcardsoftboy.avi\";\n    obserbableDownload(\"http://archive.blender.org/fileadmin/movies/softboy.avi\", destination)\n        .subscribeOn(Schedulers.io())\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(success -> {\n            resetDownloadButton();\n            Intent intent = new Intent(android.content.Intent.ACTION_VIEW);\n            File file = new File(destination);\n            intent.setDataAndType(Uri.fromFile(file),\"video/avi\");\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); \n            startActivity(intent);\n        }, error -> {\n            Toast.makeText(getActivity(), \"Something went south\", Toast.LENGTH_SHORT).show();\n            resetDownloadButton();\n        });\n}\n```\n我们使用Butter Knife的注解`@OnClick`来绑定按钮的方法并更新按钮信息和点击状态：我们不想让用户点击多次从而触发多次下载事件。\n\n然后，我们创建一个subscription来观察下载进度并相应的更新进度条。很明显，我们我们观测主线程是因为进度条是UI元素。\n\n```java\nobserbableDownload(\"http://archive.blender.org/fileadmin/movies/softboy.avi\", \"sdcardsoftboy.avi\";)\n```\n这是一个下载Observable。网络调用是一个I/O任务和我们预料的那样使用I/O调度器。当下载完成时，我们在`onNext()`启动视频播放器，并且播放器将会在目的URL找到下载的文件.。\n\n下图展示了下载进度和视频播放器对话框：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter7_6.png\" width=\"240\"/>\n\n\n## 总结\n\n这一章中，我们学习了如何简单的将多线程应用在我们的App中。RxJava为此提供了极其有用的工具：调度器。调度器来自不同的指定优化场景并且我们也不避免了`StrictMode`不合法操作以及阻塞I/O函数。我们现在可以用简单的，响应式的并在整个App中保持一致的方式来访问内存和网络。\n\n下一章中，我们将会提高风险并创建一个`真实世界`App，并使用Square公司开源的REST API库Retrofit从不同的远程资源获取数据来创建一个复杂的material design UI。\n\n"
  },
  {
    "path": "rxjava/chap8.md",
    "content": "# RxJava开发精要8 - 与REST无缝结合-RxJava和Retrofit\n\n> \n> * 原文出自《RxJava Essentials》\n* 原文作者 : [Ivan Morgillo](https://www.packtpub.com/books/info/authors/ivan-morgillo)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [yuxingxin](https://github.com/yuxingxin) \n* 项目地址 : [RxJava-Essentials-CN](https://github.com/yuxingxin/RxJava-Essentials-CN)\n\n在上一章中，我们学习了如何使用调度器在不同于UI线程的线程上操作。我们学习了如何高效的运行I/O任务而不用阻塞UI以及如何运行耗时的计算任务而不耗损应用性能。在最后一章中，我们将创建一个最终版的`真实世界`的例子，用Retrofit映射到远程的API,异步的查询数据，从而不费力的创造一个丰富的UI。\n\n\n## 项目目标\n\n我们将在已存在的例子中创建一个新的`Activity`。这个`Activity`将会使用StackExchange API从stackoverflow检索最活跃的10位用户。使用这个信息，App将会展示一个用户的头像，姓名，名望数以及住址列表。对于每一位用户，app将会使用其居住城市和OpenWeatherMap API 来检索当地的天气预报，并展示一个小的天气图标。基于从StackOverflow检索的信息，app对列表的每一位用户将会提供一个`onClick`事件，它将会打开他们在个人信息中指定的个人网站或者打开Stack Overflow的个人主页。\n\n## Retrofit\n\nRetrofit是Square公司专为Android和Java设计的一个类型安全的REST客户端。它帮助你很容易的和任何REST API交互，它完美的集成R小Java：所有的JSON响应对象都被映射成原始的Java对象，并且所有的网络调用都基于Rxjava Observable这些对象。\n\n使用API文档，我们可以定义我们从服务器接收的JSON响应数据。为了很容易的将JSON响应数据映射为我们的Java代码，我们将使用jsonschema2pojo(http://www.jsonschema2pojo.org)，这个灵活的服务将会生成我们所需要映射JSON响应数据的所有Java类。\n\n当我们把所有的Java model准备好后，我们就可以开始建立Retrofit。Retrofi使用标准的Java接口来映射API路由。例如例子中，我们将使用来自API的一个路由，下面是我们Retrofit的接口：\n```java\npublic interface StackExchangeService {\n    @GET(\"2.2users?order=desc&sort=reputation&site=stackoverflow\")\n    Observable<User sResponse> getMostPopularSOusers(@Query(\"pagesize\") int howmany);\n}\n```\n`interface`接口只包含一个方法，即`getMostPopularSOusers`。这个方法用整型`howmany`作为一个参数并返回`UserResponse`的Observable。\n\n当我们有了`interface`，我们可以创建`RestAdapter`类，为了更清楚的组织我们的代码，我们创建一个`SeApiManager`函数提供一种更适当的方式来和StackExchange API交互。\n```java\npublic class SeApiManager {\n    private final StackExchangeService mStackExchangeService;\n    \n    public SeApiManager() {\n        RestAdapter restAdapter = new RestAdapter.Builder()\n            .setEndpoint(\"https://api.stackexchange.com\")\n            .setLogLevel(RestAdapter.LogLevel.BASIC)\n            .build();\n        mStackExchangeService = restAdapter.create(StackExchangeService.class);\n}\n\npublic Observable<List<User>> getMostPopularSOusers(int howmany) {\n    return mStackExchangeService\n    .getMostPopularSOusers(howmany)\n    .map(UsersResponse::getUsers)\n    .subscribeOn(Schedulers.io())\n    .observeOn(AndroidSchedulers.mainThread());\n}\n```\n为了简化例子，我们不再将这个类设计为它本应该设计为的单例。使用依赖注入解决方案，如Dagger2将会使代码质量更高。\n\n创建`RestAdapter`类，我们为API客户端建立了几个重要的点。这个例子中，我们设置`endpoint`和`log level`。由于这个例子URL只是硬编码，使用外部资源来像这样存储数据很重要。避免在代码中硬编码字符串是一个好的实践。\n\nRetrofit把`RestAdapter`类和我们的API接口绑定在一起后就创建结束。它返回给我们一个对象用来查询API。我们可以选择直接暴露这个对象，或者以某种方式封装依次来限制访问它。在这个例子中，我们封装它并只暴露`getMostPopularSOusers`方法。这个方法执行查询，让Retrofit解析JSON响应数据。获得用户列表，并返回给订阅者。正如你看到的，使用Retrofit、RxJava和Retrolambda，我们几乎没有模板代码：它非常紧凑并且可读性也高。\n\n现在，我们已经有一个API管理者来暴露一个响应式的方法，它从远程API获取到数据并给I/O调度器，解析映射最后提供给我们的消费者一个简洁的用户列表。\n\n\n## App架构\n\n我们不使用任何MVC，MVP，或者MVVM模式。因为那不是这本书的目的，因此我们的`Activity`类将包含我们需要创建和展示用户列表的所有逻辑。\n\n\n## 创建Activity类\n\n我们将在`onCreate()`方法里创建`SwipeRefreshLayout`和`RecyclerView`；我们有一个`refreshList()`方法来处理用户列表的获取和展示，`showRefreshing()`方法来管理进度条和`RecyclerView`的显示。\n\n我们的`refreshList()`函数看起来如下：\n```java\nprivate void refreshList() { \n    showRefresh(true);\n    mSeApiManager.getMostPopularSOusers(10)\n        .subscribe(users -> { \n            showRefresh(false);\n            mAdapter.updateUsers(users);\n        }, error -> { \n            App.L.error(error.toString());\n            showRefresh(false);\n        });\n}\n```\n我们显示了进度条，从StackExchange API 管理器观测用户列表。一旦获取到列表数据，我们开始展示它并更新`Adapter`的内容并让`RecyclerView`显示为可见。\n\n## 创建RecyclerView Adapter\n\n我们从REST API获取到数据后，我们需要把它绑定View上，并用一个适配器填充列表。我们的RecyclerView适配器是标准的。它继承于`RecyclerView.Adapter`并指定它自己的`ViewHolder`：\n```java\npublic static class ViewHolder extends RecyclerView.ViewHolder {\n    @InjectView(R.id.name) TextView name;\n    @InjectView(R.id.city) TextView city;\n    @InjectView(R.id.reputation) TextView reputation;\n    @InjectView(R.id.user_image) ImageView user_image;\n    public ViewHolder(View view) { \n        super(view);\n        ButterKnife.inject(this, view); \n    }\n}\n```\n我们一旦收到来自API管理器的数据，我们可以设置界面上所有的标签：`name`,`city`和`reputation`。\n\n为了展示用户的头像，我们将使用Sergey Tarasevich (https://github.com/nostra13/Android-Universal- ImageLoader)写的`Universal Image Loader`。UIL是非常有名的并且被测试出很好用的图片管理库。我们也可以使用Square公司的Picasso，Glide或者Facebook公司的Fresco。这只是根据你自己的爱好。重要的是无须重复造轮子：库能够方便开发者的生活并让他们更快速实现目标。\n\n在我们的适配器中，我们可以这样：\n```java\n@Override\npublic void onBindViewHolder(SoAdapter.ViewHolder holder, int position) {\n    User user = mUsers.get(position);\n    holder.setUser(user); \n}\n```\n在`ViewHolder`，我们可以这样：\n```java\npublic void setUser(User user) { \n    name.setText(user.getDisplayName());\n    city.setText(user.getLocation());\n    reputation.setText(String.valueOf(user.getReputation()));\n    \n    ImageLoader.getInstance().displayImage(user.getProfileImage(), user_image);\n}\n```\n\n此时，我们可以允许代码获得一个用户列表，正如下图所示：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter8_1.png\" width=\"240\"/>\n\n### 检索天气预报\n\n我们加大难度，将当地城市的天气加入列表中。**OpenWeatherMap**是一个灵活的web service公共API，我们可以查询检索许多有用的预报信息。\n\n和往常一样，我们将使用Retrofit映射到API然后通过RxJava来访问它。至于StackExchange API，我们将创建`interface`，`RestAdapter`和一个灵活的管理器：\n\n```java\npublic interface OpenWeatherMapService {\n    @GET(\"data2.5/weather\")\n    Observable<WeatherResponse> getForecastByCity(@Query(\"q\") String city);\n}\n```\n\n这个方法用城市名字作为参数提供当地的预报信息。我们像下面这样将接口和`RestAdapter`类绑定在一起：\n\n```java\nRestAdapter restAdapter = new RestAdapter.Builder()\n        .setEndpoint(\"http://api.openweathermap.org\")\n        .setLogLevel(RestAdapter.LogLevel.BASIC)\n        .build();\nmOpenWeatherMapService = restAdapter.create(OpenWeatherMapService.class);\n```\n像以前一样，我们只需设置API端口和log级别：我们只需要立马做的两件事情。\n\n`OpenWeatherMapApiManager`类将提供下面的方法：\n```java\npublic Observable<WeatherResponse> getForecastByCity(String city) {\n    return mOpenWeatherMapService.getForecastByCity(city)\n    .subscribeOn(Schedulers.io())\n    .observeOn(AndroidSchedulers.mainThread());\n}\n```\n现在，我们有了用户列表，我们可以根据城市名来查询OpenWeatherMap来接收天气预报信息。下一步是修改我们的`ViewHolder`类来为每位用户检索和使用天气预报信息从而根据状态来展示天气图标。\n\n我们使用这些工具方法先验证用户主页信息并获得一个合法的城市名字：\n```java\nprivate boolean isCityValid(String location) {\n    int separatorPosition = getSeparatorPosition(location);\n    return !\"\".equals(location) && separatorPosition > -1; \n}\n\nprivate int getSeparatorPosition(String location) { \n    int separatorPosition = -1;\n    if (location != null) {\n        separatorPosition = location.indexOf(\",\"); \n    }\n    return separatorPosition; \n}\n\nprivate String getCity(String location, int position) {\n    if (location != null) {\n        return location.substring(0, position); \n    } else {\n        return \"\"; \n    }\n}\n```\n借助一个有效的城市名，我们可以用下面命令来获得我们所需要天气的所有数据：\n\n```java\nOpenWeatherMapApiManager.getInstance().getForecastByCity(city)\n```\n用天气响应的结果，我们可以获得天气图标的URL：\n\n```java\ngetWeatherIconUrl(weatherResponse);\n```\n用图标URL，我们可以检索到图标本身：\n\n```java\nprivate Observable<Bitmap> loadBitmap(String url) {\n    return Observable.create(subscriber -> {\n        ImageLoader.getInstance().displayImage(url,city_image, new ImageLoadingListener() { \n            @Override\n            public void onLoadingStarted(String imageUri, View view) {\n            }\n            \n            @Override\n            public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                subscriber.onError(failReason.getCause()); \n            }\n            \n            @Override\n            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                subscriber.onNext(loadedImage);\n                subscriber.onCompleted(); \n            }\n            \n            @Override\n            public void onLoadingCancelled(String imageUri, View view) {\n                subscriber.onError(new Throwable(\"Image loading cancelled\"));\n            }\n         });\n    });\n}\n```\n这个`loadBitmap()`返回的Observable可以链接前面一个，并且最后我们可以为这个任务返回一个单独的Observable：\n\n```java\nif (isCityValid(location)) {\n    String city = getCity(location, separatorPosition);\n    OpenWeatherMapApiManager.getInstance().getForecastByCity(city)\n        .filter(response -> response != null)\n        .filter(response -> response.getWeather().size() > 0)\n        .flatMap(response -> {\n            String url = getWeatherIconUrl(response);\n            return loadBitmap(url);\n        })\n    .subscribeOn(Schedulers.io())\n    .observeOn(AndroidSchedulers.mainThread())\n    .subscribe(new Observer<Bitmap>() {\n    \n        @Override\n        public void onCompleted() {\n        }\n        \n        @Override\n        public void onError(Throwable e) {\n            App.L.error(e.toString()); \n        }\n        \n        @Override\n        public void onNext(Bitmap icon) {\n            city_image.setImageBitmap(icon); \n        }\n    });\n}\n```\n运行代码，我们可以在下面列表中为每个用户获得新的天气图标：\n\n<img src=\"https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master/images/chapter8_2.png\" width=\"240\"/>\n\n\n### 打开网站\n\n使用用户主页包含的信息，我们将会创建一个`onClick`监听器来导航到用户web页面，如果有，或者是Stack Overflow个人主页。\n\n为了实现它，我们简单实现`Activity`类的接口，用来在适配器触发Android的`onClick`事件。\n\n我们的`Adapter ViewHolder`指定这个接口：\n\n```java\npublic interface OpenProfileListener {\n    public void open(String url); \n}\n```\n\n`Activity`实现它：\n\n```java\n[https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.] implements SoAdapter.ViewHolder.OpenProfileListener { [https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.]\n    mAdapter.setOpenProfileListener(this); \n[https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.]\n\n@Override\npublic void open(String url) {\n    Intent i = new Intent(Intent.ACTION_VIEW);\n    i.setData(Uri.parse(url)); \n    startActivity(i);\n}\n```\n\n`Activity`收到URL并用外部Android浏览器打开它。我们的`ViewHolder`负责在用户列表的每个卡片上创建`OnClickListener`并检查我们是打开Stack Overflow用户主页还是外部个人站：\n\n```java\nmView.setOnClickListener(view -> { \n    if (mProfileListener != null) {\n        String url = user.getWebsiteUrl();\n        if (url != null && !url.equals(\"\") && !url.contains(\"search\")) {\n            mProfileListener.open(url); \n        } else {\n            mProfileListener.open(user.getLink()); \n        }\n    }\n)}；\n```\n一旦我们点击了，我们将直接重定向到预期的网站。在Android上，我们可以用RxAndroid的一种特殊形式（ViewObservable）以更加响应式的方式实现同样的结果。\n```java\nViewObservable.clicks(mView)\n    .subscribe(onClickEvent -> {\n    if (mProfileListener != null) {\n        String url = user.getWebsiteUrl();\n        if (url != null && !url.equals(\"\") && !url.contains(\"search\")) { \n            mProfileListener.open(url);\n        } else {\n            mProfileListener.open(user.getLink());\n        } \n    }\n});\n```\n上面两块代码片段是等价的，你可以选择你最喜欢的那一种方式。\n\n\n## 总结\n\n我们的旅程结束了。你已经准备好将你的Java应用带到一个新的代码质量水平。你可以享受一个新的编码模式并用更流畅的思维方式接触你的日常编码生活。RxJava提供这样一种机会来以面向时间的方式考虑数据：所有事情都是持续可变的，数据在更新，事件在触发，然后你可以创建基于这些事件响应的，灵活的，运行流畅的App。\n\n刚开始切换到RxJava看起来难并且耗时，但是我们经历了如何用响应式的方式有效地处理日常问题。现在你可以把你的旧代码迁移到RxJava上:给这些同步`getters`一种新的响应式生活。\n\nRxJava是一个正在不断发展和扩大的世界。还有许多方法我们还没有去探索。有些方法甚至还没有，因为RxJava，你可以创建你自己的操作符并把他们推得更远。\n\nAndroid是一个好玩的地方，但是它也有局限性。作为一个Android开发者，你可以用RxJava和RxAndroid克服它们中许多部分。我们用AndroidScheduler只简单提了下RxAndroid,除了在最后一章，你了解了`ViewObservable`。RxAndroid给了你许多：例如，`WidgetObservable`，`LifecycleObservable`。现在要更多的推动它取决于你了。\n\n记得可观测序列就像一条河：它们是流动的。你可以“过滤”一条河，你可以“转换”一条河，你可以将两条河合并成一个，然后它仍旧时流动的。最后，它将成为你想要的那条河。\n\n"
  },
  {
    "path": "software-architecture-patterns/chap-5.md",
    "content": "# 第五章 基于空间的架构\n\n大多数基于网站的商务应用都遵循相同的请求流程：一个请求从浏览器发到web服务器，然后到应用服务器，然后到数据库服务器。虽然这个模式在用户数不大的时候工作良好，但随着用户负载的增加,瓶颈开始出现，首先出现在web服务器层，然后应用服务器层，最后数据库服务器层。通常的解决办法就是**向外扩展**，也就是增加服务器数量。这个方法相对来说简单和廉价，并能够解决问题。然而，对于大多数高访问量的情况，它只不过是把web服务器的问题移到了应用服务器。而扩展应用服务器会更复杂，而且成本更高，并且又只是把问题移动到了数据库服务器，那会更复杂，更贵。就算你能扩展数据库服务器，你最终会陷入一个金字塔式的情形，在金字塔最下面是web服务器，它会出现最多的问题，但也最好伸缩。金字塔顶部是数据库服务器，问题不多，但最难伸缩。\n\n在一个高并发大容量的应用中，数据库通常是决定应用能够支持多少用户同时在线的关键因素。虽然各种缓存技术和数据库伸缩产品都在帮助解决这个问题，但数据库难以伸缩的现实并没有改变。\n\n基于空间的架构模型是专门为了**解决伸缩性和并发问题**而设计的。它对于用户数量不可预测且数量级经常变化的情况同样适用。在架构级别来解决这个伸缩性问题通常是比增加服务器数量或者提高缓存技术更好的解决办法。\n\n## 模型介绍\n\n基于空间的模型（有时也称为云架构模型）旨在减少限制应用伸缩的因素。模型的名字来源于分布式共享内存中的 tuple space（数组空间）概念。高伸缩性是通过去除中心数据库的限制，并使用从内存中复制的数据框架来获得的。保存在内存的应用数据被复制给所有运行的进程。进程可以动态的随着用户数量增减而启动或结束，以此来解决伸缩性问题。这样因为没有了中心数据库，数据库瓶颈就此解决，此后可以近乎无限制的扩展了。\n\n大多数使用这个模型的应用都是标准的网站，它们接受来自浏览器的请求并进行相关操作。竞价拍卖网站是一个很好的例子 ( 12306更是一个典型的示例 )。网站不停的接受来自浏览器的报价。应用收到对某一商品的报价，记录下报价和时间，并且更新对该商品的报价，将信息返回给浏览器。\n\n这个架构中有两个主要的模块：**处理单元** 和 **虚拟化中间件**。下图展示了这个架构和里面的主要模块。\n\n![](images/5-1.png)\n\n处理单元包含了应用模块（或者部分的应用模块）。具体来说就是包含了web组件以及后台业务逻辑。处理单元的内容根据应用的类型而异——小型的web应用可能会部署到单一的处理单元，而大型一些的应用会将应用的不同功能模块部署到不同的处理单元中。典型的处理单元包括应用模块，以及保存在内存的数据框架和为应用失败时准备的异步数据持久化模块。它还包括复制引擎，使得虚拟化中间件可以将处理单元修改的数据复制到其他活动的处理单元。\n\n虚拟化中间件负责保护自身以及通信。它包含用于数据同步和处理请求的模块，以及通信框架，数据框架，处理框架和部署管理器。这些在下文中即将介绍的部分，可以自定义编写或者购买第三方产品来实现。\n\n## 模型间合作\n\n基于空间的架构的魔力就在虚拟化中间件，以及各个处理单元中的内存中数据框架。下图展示了包含着应用模块、内存中数据框架、处理异步数据恢复的组件和复制引擎的处理单元架构。\n\n虚拟化中间件本质上是架构的控制器，它管理请求，会话，数据复制，分布式的请求处理和处理单元的部署。虚拟化中间件有四个架构组件：通信框架，数据框架，处理框架和部署管理器。\n\n![](images/5-2.png)\n\n### 通信框架\n\n通信框架管理输入请求和会话信息。当有请求进入虚拟化中间件，通信框架就决定有哪个处理单元可用，并将请求传递给这个处理单元。通信框架的复杂程度可以从简单的round robin算法到更复杂的用于监控哪个请求正在被哪个处理单元处理的next-available算法。\n\n![](images/5-3.png)\n\n### 数据框架\n\n数据框架可能是这个架构中最重要和关键的组件。它与各个处理单元的数据复制引擎交互，在数据更新时来管理数据复制功能。由于通信框架可以将请求传递给任何可用的处理单元，所以每个处理单元包含完全一样的内存中数据就很关键。下图展示处理单元间如何同步数据复制，实际中是通过非常迅速的并行的异步复制来完成的，通常在微秒级。\n\n![](images/5-4.png)\n\n### 处理框架\n\n处理框架，就像下图所示，是虚拟化中间件中一个可选组件，负责管理在有多个处理单元时的分布式请求处理，每个处理单元可能只负责应用中的某个特定功能。如果请求需要处理单元间合作（比如，一个订单处理单元和顾客处理单元），此时处理框架就充当处理单元见数据传递的媒介。\n\n![](images/5-5.png)\n\n### 部署管理器\n\n部署管理器根据负载情况管理处理单元的动态启动和关闭。它持续监控响应时间和用户负载，在负载增加时启动新的处理单元，在负载下降时关闭处理单元。它是实现可变伸缩性需求的关键。\n\n## 其他考虑\n\n基于空间的架构是一个复杂和实现起来相对昂贵的框架。对于拥有可变负载的小型web应用是很好的选择，然而，对于拥有大量草错的传统大规模关系型数据库应用，并不那么适用。\n\n虽然基于空间的架构模型不需要集中式的数据储存，但通常还是需要这样一个，来进行初始化内存中数据框架，和异步的更新各处理单元的数据。通常也会创建一个单独的分区，来从隔离常用的断电就消失的数据和不常用的数据，这样减少处理单元之间对对方内存数据的依赖。\n\n值得注意的是，虽然这个架构的另一个名字是云架构，处理单元（以及虚拟化中间件）都没有放在云端服务或者PaaS上。他们同样可以简单的放在本地服务器，这也是为什么我更倾向叫它“基于空间的架构”。\n\n从产品实现的角度讲，这个架构中的很多组件都可以从第三方获得，比如GemFire, JavaSpaces, GigaSpaces，IBM Object Grid，nCache，和 Oracle Coherence。由于架构的实现根据工程的预算和需求而异，所以作为架构师，你应该在实现或选购第三方产品前首先明确你的目标和需求。\n\n## 架构分析\n\n下面的表格是这个架构的特征分析和评分。每个特征的评分是基于一个典型的架构实现来给出的。要知道这个模式相对别的模式的对比，请参见最后的附录A。\n\n#### 综合能力\n\n评分：高\n\n分析：综合能力是对环境变化做出快速反应的能力。因为处理单元（应用的部署实例）可以快速的启动和关闭，整个应用可以根据用户量和负载做出反应。使用这个架构通常在应对代码变化上，由于较小的应用规模和组件间相互依赖，也会反映良好。\n\n#### 部署难易程度\n\n评分：高\n\n分析：虽然基于空间的架构通常没有解耦合并且功能分布，但他们是动态的，也是成熟的基于云的工具，允许应用轻松的部署到服务器。\n\n#### 可测试性\n\n评分：低\n\n分析：测试高用户负载既昂贵又耗时，所以在测试架构的可伸缩性方面很困难\n\n#### 性能\n\n评分：高\n\n分析：通过内存中数据存取和架构中的缓存机制可获得高性能\n\n#### 伸缩性\n\n评分：高\n\n分析：高伸缩性是源于几乎不依赖集中式的数据库，从而去除了这个限制伸缩性的瓶颈。\n\n#### 开发难度\n\n评分：低\n\n分析：成熟的缓存机制和内存中数据框架使这个架构开发起来相对复杂，主要是因为难以熟悉这个架构开发所需要的工具和第三方产品。而且，开发过程中还需要特别注意源码不要对性能和可伸缩性造成不良影响。\n"
  },
  {
    "path": "software-architecture-patterns/chapter01-BillonWang.md",
    "content": "# 第一章 分层架构 \n\n￼The most common architecture pattern is the layered architecture pattern, otherwise known as the n-tier architecture pattern. This pattern is the de facto standard for most Java EE applications and therefore is widely known by most architects, designers, and devel‐ opers. The layered architecture pattern closely matches the tradi‐ tional IT communication and organizational structures found in most companies, making it a natural choice for most business appli‐ cation development efforts.\n\n分层架构是一种很常见的架构模式，它也叫N层架构。这种架构是大多数Jave EE应用的实际标准，因此很多的架构师，设计师，还有程序员都知道它。许多传统IT公司的组织架构和分层模式十分的相似。所以它很自然的成为大多数应用的架构模式。\n\n## Pattern Description\nComponents within the layered architecture pattern are organized into horizontal layers, each layer performing a specific role within the application (e.g., presentation logic or business logic). Although the layered architecture pattern does not specify the number and types of layers that must exist in the pattern, most layered architec‐ tures consist of four standard layers: presentation, business, persis‐ tence, and database (Figure 1-1). In some cases, the business layer and persistence layer are combined into a single business layer, par‐ ticularly when the persistence logic (e.g., SQL or HSQL) is embed‐ ded within the business layer components. Thus, smaller applications may have only three layers, whereas larger and more complex business applications may contain five or more layers.\n\n分层架构模式里的组件被分成几个平行的层次，每一层都代表了应用的一个功能(展示逻辑或者业务逻辑)。尽管分层架构没有规定自身要分成几层几种，大多数的结构都分成四个层次:展示层，业务层，持久层，和数据库层。如表1-1，有时候，业务层和持久层会合并成单独的一个业务层，尤其是持久层的逻辑绑定在业务层的组件当中。因此，有一些小的应用可能只有3层，一些有着更复杂的业务的大应用可能有5层或者更多的分层。\n\nEach layer of the layered architecture pattern has a specific role and responsibility within the application. For example, a presentation layer would be responsible for handling all user interface and browser communication logic, whereas a business layer would be responsible for executing specific business rules associated with the request. Each layer in the architecture forms an abstraction around the work that needs to be done to satisfy a particular business request. For example, the presentation layer doesn’t need to know or worry about how to get customer data; it only needs to display that information on a screen in particular format. Similarly, the business layer doesn’t need to be concerned about how to format customer data for display on a screen or even where the customer data is coming from; it only needs to get the data from the persis‐ tence layer, perform business logic against the data (e.g., calculate values or aggregate data), and pass that information up to the pre‐ sentation layer.\n\n分层架构中的每一层都着特定的角色和职能。举个例子，展示层负责处理所有的界面展示以及交互逻辑，业务层负责处理请求对应的业务。架构里的层次是具体工作的高度抽象，它们都是为了实现某种特定的业务请求。比如说展示层并不需要关心怎样得到用户数据，它只需在屏幕上以特定的格式展示信息。业务层并不关心要展示在屏幕上的用户数据格式，也不关心这些用户数据从哪里来。它只需要从持久层得到数据，执行与数据有关的相应业务逻辑，然后把这些信息传递给展示层。\n\n![1-1](images/1-1.png)    \nFigure 1-1. Layered architecture pattern\n\nOne of the powerful features of the layered architecture pattern is the separation of concerns among components. Components within a specific layer deal only with logic that pertains to that layer. For example, components in the presentation layer deal only with pre‐ sentation logic, whereas components residing in the business layer deal only with business logic. This type of component classification makes it easy to build effective roles and responsibility models into your architecture, and also makes it easy to develop, test, govern, and maintain applications using this architecture pattern due to well-defined component interfaces and limited component scope.\n\n分层架构的一个突出特性是组件间关注点分离 (separation of concerns)。一个层中的组件只会处理本层的逻辑。比如说，展示层的组件只会处理展示逻辑，业务层中的组件只会去处理业务逻辑。多亏了组件分离，让我们更容易构造有效的角色和强力的模型。这样应用变的更好开发，测试，管理和维护。\n\n## Key Concepts\nNotice in Figure 1-2 that each of the layers in the architecture is marked as being closed. This is a very important concept in the lay‐ ered architecture pattern. A closed layer means that as a request moves from layer to layer, it must go through the layer right below it to get to the next layer below that one. For example, a request origi‐ nating from the presentation layer must first go through the busi‐ ness layer and then to the persistence layer before finally hitting the database layer.\n\n注意表1-2中每一层都是封闭的。这是分层架构中非常重要的特点。这意味request必须一层一层的传递。举个例子，从展示层传递来的请求首先会传递到业务层，然后传递到持久层，最后才传递到数据层。\n\n![1-2](images/1-2.png)     \nFigure 1-2. Closed layers and request access\n\nSo why not allow the presentation layer direct access to either the persistence layer or database layer? After all, direct database access from the presentation layer is much faster than going through a bunch of unnecessary layers just to retrieve or save database infor‐ mation. The answer to this question lies in a key concept known as layers of isolation.\n\n那么为什么不允许展示层直接访问数据层呢。如果只是获得以及读取数据，展示层直接访问数据层，比穿过一层一层来得到数据来的快多了。这涉及到一个概念:层隔离。\n\nThe layers of isolation concept means that changes made in one layer of the architecture generally don’t impact or affect components in other layers: the change is isolated to the components within that layer, and possibly another associated layer (such as a persistence layer containing SQL). If you allow the presentation layer direct access to the persistence layer, then changes made to SQL within the persistence layer would impact both the business layer and the pre‐ sentation layer, thereby producing a very tightly coupled application with lots of interdependencies between components. This type of architecture then becomes very hard and expensive to change.\n\n层隔离就是说架构中的某一层的改变不会影响到其他层:这些变化的影响范围限于当前层次。如果展示层能够直接访问持久层了，假如持久层中的SQL变化了，这对业务层和展示层都有一定的影响。这只会让应用变得紧耦合，组件之间互相依赖。这种架构会非常的难以维护。\n\n\nThe layers of isolation concept also means that each layer is inde‐ pendent of the other layers, thereby having little or no knowledge of the inner workings of other layers in the architecture. To understand the power and importance of this concept, consider a large refactor‐ ing effort to convert the presentation framework from JSP (Java Server Pages) to JSF (Java Server Faces). Assuming that the contracts (e.g., model) used between the presentation layer and the business layer remain the same, the business layer is not affected by the refac‐ toring and remains completely independent of the type of user- interface framework used by the presentation layer.\n\n从另外一个方面来说，分层隔离使得层与层之间都是相互独立的，架构中的每一层的互相了解都很少。为了说明这个概念的牛逼之处，想象一个超级重构，把展示层从JSP换成JSF。假设展示层和业务层的之间的联系保持一致，业务层不会受到重构的影响，它和展示层所使用的界面架构完全独立。\n\nWhile closed layers facilitate layers of isolation and therefore help isolate change within the architecture, there are times when it makes sense for certain layers to be open. For example, suppose you want to add a shared-services layer to an architecture containing com‐ mon service components accessed by components within the busi‐ ness layer (e.g., data and string utility classes or auditing and logging classes). Creating a services layer is usually a good idea in this case because architecturally it restricts access to the shared services to the business layer (and not the presentation layer). Without a separate layer, there is nothing architecturally that restricts the presentation layer from accessing these common services, making it difficult to govern this access restriction.\n\n然而封闭的架构层次也有不便之处，有时候也应该开放某一层。如果想往包含了一些由业务层的组件调用的普通服务组件的架构中添加一个分享服务层。在这个例子里，新建一个服务层通常是一个好主意，因为从架构上来说，它限制了分享服务访问业务层(也不允许访问展示层)。如果没有隔离层，就没有任何架构来限制展示层访问普通服务，难以进行权限管理。\n\nIn this example, the new services layer would likely reside below the business layer to indicate that components in this services layer are not accessible from the presentation layer. However, this presents a problem in that the business layer is now required to go through the services layer to get to the persistence layer, which makes no sense at all. This is an age-old problem with the layered architecture, and is solved by creating open layers within the architecture.\nAs illustrated in Figure 1-3, the services layer in this case is marked as open, meaning requests are allowed to bypass this open layer and go directly to the layer below it. In the following example, since the services layer is open, the business layer is now allowed to bypass it and go directly to the persistence layer, which makes perfect sense.\n\n在这个例子中，新的服务层是处于业务层之下的，展示层不能直接访问这个服务层中的组件。但是现在业务层还要通过服务层才能访问到持久层，这一点也不合理。这是分层架构中的老问题了，解决的办法是开放某些层。如表1-3所示，服务层现在是开放的了。请求可以绕过这一层，直接访问这一层下面的层。既然服务层是开放的，业务层可以绕过服务层，直接访问数据持久层。这样就非常合理。\n\n![1-3](images/1-3.png)       \n￼￼￼￼￼Figure 1-3. Open layers and request flow\n\nLeveraging the concept of open and closed layers helps define the relationship between architecture layers and request flows and also provides designers and developers with the necessary information to understand the various layer access restrictions within the architec‐ ture. Failure to document or properly communicate which layers in the architecture are open and closed (and why) usually results in tightly coupled and brittle architectures that are very difficult to test, maintain, and deploy.\n\n开放和封闭层的概念确定了架构层和请求流之间的关系，并且给设计师和开发人员提供了必要的信息理解架构里各种层之间的访问限制。如果随意的开放或者封闭架构里的层，整个项目可能都是紧耦合，一团糟的。以后也难以测试，维护和部署。\n\n\n## Pattern Example\n\nTo illustrate how the layered architecture works, consider a request from a business user to retrieve customer information for a particu‐ lar individual as illustrated in Figure 1-4. The black arrows show the request flowing down to the database to retrieve the customer data, and the red arrows show the response flowing back up to the screen to display the data. In this example, the customer informa‐ tion consists of both customer data and order data (orders placed by the customer).\n\n为了演示分层架构是如何工作的，想象一个场景，如表1-4，用户发出了一个请求要获得客户的信息。黑色的箭头是从数据库中获得用户数据的请求流，红色箭头显示用户数据的返回流的方向。在这个例子中，用户信息由客户数据和订单数组组成(客户下的订单)。\n\nThe customer screen is responsible for accepting the request and dis‐ playing the customer information. It does not know where the data is, how it is retrieved, or how many database tables must be queries to get the data. Once the customer screen receives a request to get customer information for a particular individual, it then forwards that request onto the customer delegate module. This module is responsible for knowing which modules in the business layer can process that request and also how to get to that module and what data it needs (the contract). The customer object in the business layer is responsible for aggregating all of the information needed by the business request (in this case to get customer information). This module calls out to the customer dao (data access object) module in the persistence layer to get customer data, and also the order dao module to get order information. These modules in turn execute SQL statements to retrieve the corresponding data and pass it back up to the customer object in the business layer. Once the customer object receives the data, it aggregates the data and passes that infor‐ mation back up to the customer delegate, which then passes that data to the customer screen to be presented to the user.\n\n用户界面只管接受请求以及显示客户信息。它不管怎么得到数据的，或者说得到这些数据要用到哪些数据表。如果用户界面接到了一个查询客户信息的请求，它就会转发这个请求给用户委托(Customer Delegate)模块。这个模块能找到业务层里对应的模块处理对应数据(约束关系)。业务层里的customer object聚合了业务请求需要的所有信息(在这个例子里获取客户信息)。这个模块调用持久层中的 customer dao 来得到客户信息，调用order dao来得到订单信息。这些模块会执行SQL语句，然后返回相应的数据给业务层。当 customer object收到数据以后，它就会聚合这些数据然后传递给 customer delegate,然后传递这些数据到customer screen 展示在用户面前。\n![1-4](images/1-4.png)     \nFigure 1-4. Layered architecture example\n\nFrom a technology perspective, there are literally dozens of ways these modules can be implemented. For example, in the Java plat‐ form, the customer screen can be a (JSF) Java Server Faces screen coupled with the customer delegate as the managed bean compo‐ nent. The customer object in the business layer can be a local Spring bean or a remote EJB3 bean. The data access objects illustrated in the previous example can be implemented as simple POJO’s (Plain Old Java Objects), MyBatis XML Mapper files, or even objects encapsulating raw JDBC calls or Hibernate queries. From a Micro‐ soft platform perspective, the customer screen can be an ASP (active server pages) module using the .NET framework to access C# mod‐ ules in the business layer, with the customer and order data access modules implemented as ADO (ActiveX Data Objects).\n\n从技术的角度来说，有很多的方式能够实现这些模块。比如说在Java平台中，customer screen 对应的是 (JSF) Java Server Faces ,用 bean 组件来实现 customer delegate。用本地的Spring bean或者远程的EJB3 bean 来实现业务层中的customer object。上例中的数据访问可以用简单的POJP's(Plain Old Java Objects)，或者可以用MyBatis，还可以用JDBC或者Hibernate 查询。Microsoft平台上，customer screen能用 .NET 库的ASP模块来访问业务层中的C#模块，用ADO来实现用户和订单数据的访问模块。\n\n## Considerations\nThe layered architecture pattern is a solid general-purpose pattern, making it a good starting point for most applications, particularly when you are not sure what architecture pattern is best suited for your application. However, there are a couple of things to consider from an architecture standpoint when choosing this pattern.\n\n分层架构是一个很可靠的架构模式。它适合大多数的应用。如果你不确定在项目中使用什么架构，分层架构是再好不过的了。然后，从架构的角度上来说，选择这个模式还要考虑很多的东西。\n\nThe first thing to watch out for is what is known as the architecture sinkhole anti-pattern. This anti-pattern describes the situation where requests flow through multiple layers of the architecture as simple pass-through processing with little or no logic performed within each layer. For example, assume the presentation layer responds to a request from the user to retrieve customer data. The presentation layer passes the request to the business layer, which simply passes the request to the persistence layer, which then makes a simple SQL call to the database layer to retrieve the customer data. The data is then passed all the way back up the stack with no additional pro‐ cessing or logic to aggregate, calculate, or transform the data.\n\n第一个要注意的就是 污水池反模式(architecture sinkhole anti-pattern)。\n在这个模式中，请求流只是简单的穿过层次，不留一点云彩，或者说只留下一阵青烟。比如说界面层响应了一个获得数据的请求。响应层把这个请求传递给了业务层，业务层也只是传递了这个请求到持久层，持久层对数据库做简单的SQL查询获得用户的数据。这个数据按照原理返回，不会有任何的二次处理，返回到界面上。\n\nEvery layered architecture will have at least some scenarios that fall into the architecture sinkhole anti-pattern. The key, however, is to analyze the percentage of requests that fall into this category. The 80-20 rule is usually a good practice to follow to determine whether or not you are experiencing the architecture sinkhole anti-pattern. It is typical to have around 20 percent of the requests as simple pass- through processing and 80 percent of the requests having some business logic associated with the request. However, if you find that this ratio is reversed and a majority of your requests are simple pass- through processing, you might want to consider making some of the architecture layers open, keeping in mind that it will be more diffi‐ cult to control change due to the lack of layer isolation.\n\n每个分层架构或多或少都可能遇到这种场景。关键在于这样的请求有多少。80-20原则可以帮助你确定架构是否处于反污水模式。大概有百分之二十的请求仅仅是做简单的穿越，百分之八十的请求会做一些业务逻辑操作。然而，如果这个比例反过来，大部分的请求都是仅仅穿过层，不做逻辑操作。那么开放一些架构层会比较好。不过由于缺少了层次隔离，项目会变得难以控制。\n\nAnother consideration with the layered architecture pattern is that it tends to lend itself toward monolithic applications, even if you split the presentation layer and business layers into separate deployable units. While this may not be a concern for some applications, it does pose some potential issues in terms of deployment, general robust‐ ness and reliability, performance, and scalability.\n另外一个要考虑的是分层模式会导致项目变得巨大无比，即使你的展示层和业务层可以单独发布。某些小应用可能会无视这个问题，不过他的确会导致一些潜在的问题，比如说分布模式复杂，总体的健壮性和可靠性，性能和规模等。\n\n## Pattern Analysis\nThe following table contains a rating and analysis of the common architecture characteristics for the layered architecture pattern. The rating for each characteristic is based on the natural tendency for that characteristic as a capability based on a typical implementa‐ tion of the pattern, as well as what the pattern is generally known for. For a side-by-side comparison of how this pattern relates to other patterns in this report, please refer to Appendix A at the end of this report.\n\n下面的的表里分析了分层架构的各个方面。\n\n### Overall agility 整体灵活性\nRating: Low    \nAnalysis: Overall agility is the ability to respond quickly to a constantly changing environment. While change can be isolated through the layers of isolation feature of this pattern, it is still cumbersome and time-consuming to make changes in this architecture pattern because of the monolithic nature of most implementations as well as the tight coupling of components usually found with this pattern.\n评级:低\n分析:总体灵活性是响应环境变化的能力。尽管分层模式中的变化可以隔绝起来，想在这种架构中做一些也改变也是并且费时费力的。分层模式的笨重以及经常出现的组件之间的紧耦合是导致灵活性降低的原因。\n\n### Ease of deployment 部署难易度\nRating: Low    \nAnalysis: Depending on how you implement this pattern, deployment can become an issue, particularly for larger applica‐ tions. One small change to a component can require a redeployment of the entire application (or a large portion of the application), resulting in deployments that need to be planned, scheduled, and executed during off-hours or on weekends. As such, this pattern does not easily lend itself toward a contin‐ uous delivery pipeline, further reducing the overall rating for deployment.\n评级:低\n分析:这取决于你怎么发布这种模式，发布程序可能比较麻烦，尤其是很大的项目。一个组件的小小改动可能会影响到整个程序的发布(或者程序的大部分)。发布必须是按照计划，在非工作时间或者周末进行发布。因此。分层模式导致应用发布一点也不流畅，在发布上降低了灵活性。\n\n### Testability 可测试性\nRating: High     \nAnalysis: Because components belong to specific layers in the architecture, other layers can be mocked or stubbed, making this pattern is relatively easy to test. A developer can mock a presentation component or screen to isolate testing within a business component, as well as mock the business layer to test certain screen functionality.\n评级:高\n分析:因为组件都处于各自的层次中，可以模拟其他的层，或者说直接去掉层，所以分层模式很容易测试。开发者可以单独模拟一个展示组件，对业务组件进行隔绝测试。还可以年模拟业务层来测试某个展示功能。\n\n### Performance 性能\nRating: Low     \nAnalysis: While it is true some layered architectures can per‐ form well, the pattern does not lend itself to high-performance applications due to the inefficiencies of having to go through multiple layers of the architecture to fulfill a business request.\n评级:低\n分析:尽管某些分层架构的性能表现的确不错，但是这个模式的特点导致它无法带来高性能。因为一次业务请求要穿越所有的架构层，做了很多不必要的工作。\n\n### Scalability 伸缩性\nRating: Low     \nAnalysis: Because of the trend toward tightly coupled and mon‐ olithic implementations of this pattern, applications build using this architecture pattern are generally difficult to scale. You can scale a layered architecture by splitting the layers into separate physical deployments or replicating the entire application into multiple nodes, but overall the granularity is too broad, making it expensive to scale.\n评级:低\n分析:由于这种模式以紧密耦合的趋势在发展，规模也比较大，用分层架构构建的程序都比较难以扩展。你可以把各个层分成单独的物理模块或者干脆把整个程序分成多个节点来扩展分层架构，但是总体的关系过于紧密，这样很难扩展。\n\n### Ease of development 易开发性\nRating: High     \nAnalysis: Ease of development gets a relatively high score, mostly because this pattern is so well known and is not overly complex to implement. Because most companies develop appli‐ cations by separating skill sets by layers (presentation, business, database), this pattern becomes a natural choice for most business-application development. The connection between a company’s communication and organization structure and the way it develops software is outlined is what is called Conway’s law. You can Google “Conway’s law\" to get more information about this fascinating correlation.\n评级:容易\n分析:在开发难度上面，分层架构得到了比较高的分数。因为这种架构对大家来说很熟悉，不难实现。大部分公司在开发项目的都是通过层来区分技术的，这种模式对于大多数的商业项目开发来说都很合适。公司的组织架构和他们软件架构之间的联系被戏称为\"Conway's law\"。你可以Google一下查查这个有趣的联系。\n"
  },
  {
    "path": "software-architecture-patterns/chapter02-chaossss.md",
    "content": "# 第二章 事件驱动架构\n\n> 译者注：文章中 mediator 及 broker 的概念很容易混淆，在文章的结尾处译者对两者的区别（还有 proxy）进行了一定的阐述\n\nThe event-driven architecture pattern is a popular distributed asynchronous architecture pattern used to produce highly scalable applications. It is also highly adaptable and can be used for small applications and as well as large, complex ones. The event-driven architecture is made up of highly decoupled, single-purpose event processing components that asynchronously receive and process events.   \n\n事件驱动架构模式是一种主流的异步分发事件架构模式，常用于设计高度可拓展的应用。当然了，它有很高的适应性，使得它在小型应用、大型应用、复杂应用中都能表现得很好。事件驱动架构模式由高度解耦、单一目的的事件处理组件构成，这些组件负责异步接收和处理事件。\n\nThe event-driven architecture pattern consists of two main topolo‐ gies, the mediator and the broker. The mediator topology is com‐ monly used when you need to orchestrate multiple steps within an event through a central mediator, whereas the broker topology is used when you want to chain events together without the use of a central mediator. Because the architecture characteristics and imple‐ mentation strategies differ between these two topologies, it is impor‐ tant to understand each one to know which is best suited for your particular situation.\n\n事件驱动架构模式包含了两种主要的拓扑结构：**中介(mediator)**拓扑结构和**代理(broker)**拓扑结构。 mediator 拓扑结构通常在你需要在一个事件内使用一个核心中介分配、协调多个步骤间的关系、执行顺序时被使用；而代理拓扑结构则在你想要不通过一个核心中介将多个事件串联在一起时被使用。由于这两种结构在结构特征和实现策略上有很大的差别，所以如果你想要在你的应用中使用它们的话，一定要深入理解两者的技术实现细节，从而为你的实际使用场景选择最合理的结构。\n\n## Mediator Topology\n## 中介拓扑结构\n\nThe mediator topology is useful for events that have multiple steps and require some level of orchestration to process the event. For example, a single event to place a stock trade might require you to first validate the trade, then check the compliance of that stock trade against various compliance rules, assign the trade to a broker, calcu‐ late the commission, and finally place the trade with that broker. All of these steps would require some level of orchestration to deter‐\nmine the order of the steps and which ones can be done serially and in parallel.\n\n中介拓扑结构适合用于拥有多个步骤，并需要在处理事件时能通过某种程度的协调将事件分层的场景，举例来说吧：假设你现在需要进行股票交易，那你首先需要证券所批准你进行交易，然后检查进行这次交易是否违反了股票交易的某种规定，检查完成后将它交给一个经纪人，计算佣金，最后与经纪人确认交易。以上所有步骤都需要通过中介进行某种程度的分配和协调，以决定各个步骤的执行顺序，判断哪些步骤可以并行，哪些步骤可以串行。\n\nThere are four main types of architecture components within the mediator topology: event queues, an event mediator, event channels, and event processors. The event flow starts with a client sending an event to an event queue, which is used to transport the event to the event mediator. The event mediator receives the initial event and orchestrates that event by sending additional asynchronous events to event channels to execute each step of the process. Event process‐ ors, which listen on the event channels, receive the event from the event mediator and execute specific business logic to process the event. Figure 2-1 illustrates the general mediator topology of the event-driven architecture pattern.\n\n在中介拓扑结构中主要有四种组件：事件队列（event queue）, 事件中介, 事件通道（event channel）, 和 事件处理器（event processor）。当事件流需要被处理，客户端将一个事件发送到某个事件队列中，由消息队列将其运输给事件中介进行处理和分发。事件中介接收到该消息后，并通过将额外的异步事件发送给事件通道，让事件通道执行该异步事件中的每一个步骤，使得事件中介能够对事件进行分配、协调。同时，又因为事件处理器是事件通道的监听器，所以事件通道对异步事件的处理会触发事件处理器的监听事件，使事件处理器能够接收来自事件中介的事件，执行事件中具体的业务逻辑，从而完成对传入事件的处理。事件驱动架构模式中的中介拓扑模式结构大体如下图：\n\n![2-1](images/2-1.png)\n\nIt is common to have anywhere from a dozen to several hundred event queues in an event-driven architecture. The pattern does not specify the implementation of the event queue component; it can be a message queue, a web service endpoint, or any combination thereof.\n\n在事件驱动架构中拥有十几个，甚至几百个事件队列是很常见的情况，该模式并没有对事件队列的实现有明确的要求，这就意味着事件队列可以是消息队列，Web 服务端，或者其它类似的东西。\n\nThere are two types of events within this pattern: an initial event and a processing event. The initial event is the original event received by the mediator, whereas the processing events are ones that are generated by the mediator and received by the event-processing components.\n\n在事件驱动架构模式中主要有两种事件：初始事件和待处理事件。初始事件是中介所接收到的最原始的事件，没有经过其他组件的处理；而待处理事件是由事件中介生成，由事件处理器接收的组件，不能把待处理事件看作初始事件经过处理后得到的事件，两者是完全不同的概念。\n\nThe event-mediator component is responsible for orchestrating the steps contained within the initial event. For each step in the ini‐ tial event, the event mediator sends out a specific processing event to an event channel, which is then received and processed by the event processor. It is important to note that the event mediator doesn’t actually perform the business logic necessary to process the initial event; rather, it knows of the steps required to process the ini‐ tial event.\n\n事件中介负责分配、协调初始事件中的各个待执行步骤，事件中介需要为每一个初始事件中的步骤发送一个特定的待处理事件到事件通道中，触发事件处理器接收和处理该待处理事件。这里需要注意的是：事件 中介没有真正参与到对初始事件必须处理的业务逻辑的实现之中；相反，事件中介只是知道初始事件中有哪些步骤需要被处理。\n\nEvent channels are used by the event mediator to asynchronously pass specific processing events related to each step in the initial event to the event processors. The event channels can be either mes‐ sage queues or message topics, although message topics are most widely used with the mediator topology so that processing events can be processed by multiple event processors (each performing a different task based on the processing event received).\n\n事件中介通过事件通道将与初始事件每一个执行步骤相关联的特定待处理事件传递给事件处理器。尽管我们通常在待处理事件能被多个事件处理器处理时才会在中介拓扑结构中使用 message topics，但事件通道仍可以是消息队列或 message topics（不知道译作啥……）。（但需要注意的是，尽管在使用 message topics 时待处理事件能被多个事件处理器处理，但由于接收到的待处理事件各异，所以对其处理的操作也各不相同）\n\nThe event processor components contain the application business logic necessary to process the processing event. Event processors are self-contained, independent, highly decoupled architecture compo‐ nents that perform a specific task in the application or system. While the granularity of the event-processor component can vary from fine-grained (e.g., calculate sales tax on an order) to coarse- grained (e.g., process an insurance claim), it is important to keep in mind that in general, each event-processor component should per‐ form a single business task and not rely on other event processors to complete its specific task.\n\n为了能顺利处理待处理事件，事件处理器组件中包含了应用的业务逻辑。此外，事件处理器作为事件驱动架构中的组件，不依赖于其他组件，独立运作，高度解耦，在应用或系统中完成特定的任务。当事件处理器需要处理的事件从细粒度（例如：计算订单的营业税）变为粗粒度（例如：处理一项保险索赔事务），必须要注意的是：一般来说，每一个事件处理器组件都只完成一项唯一的业务工作，并且事件处理器在完成其特定的业务工作时不能依赖其他事件处理器。\n\nThe event mediator can be implemented in a variety of ways. As an architect, you should understand each of these implementation options to ensure that the solution you choose for the event media‐ tor matches your needs and requirements.\n\n虽然事件中介有许多方法可以实现，但作为一名架构工程师，你应该了解所有实现方式，以确保你能为你的实际需求选择了最合适的事件中介。\n\nThe simplest and most common implementation of the event medi‐ ator is through open source integration hubs such as Spring Integra‐ tion, Apache Camel, or Mule ESB. Event flows in these open source integration hubs are typically implemented through Java code or a DSL (domain-specific language). For more sophisticated mediation and orchestration, you can use BPEL (business process execution language) coupled with a BPEL engine such as the open source Apache ODE. BPEL is a standard XML-like language that describes the data and steps required for processing an initial event. For very large applications requiring much more sophisticated orchestration (including steps involving human interactions), you can implement the event mediator using a business process manager (BPM) such as jBPM.\n\n事件中介最简单、常见的实现就是使用开源框架，例如：Spring Integration，Apache Camel，或 Mule ESB。事件流在这些开源框架中通常用 Java 或 域特定语言（domain-specific language）。在调节过程和业务流程都很复杂的使用场景下，你可以使用业务流程执行语言（BPEL - business process execution language）结合类似开源框架 Apache ODE 的 BPEL 引擎进行开发。BPEL 是一种基于 XML 的服务编制编程语言，它为处理初始事件时需要描述的数据和步骤提供了描述。对每一个拥有复杂业务流程（包括与用户交互的执行步骤）的大型应用来说，你可以使用类似 jBPM 的业务处理管理系统（business process manager）实现事件中介。\n\nUnderstanding your needs and matching them to the correct event mediator implementation is critical to the success of any event- driven architecture using this topology. Using an open source inte‐ gration hub to do very complex business process management orchestration is a recipe for failure, just as is implementing a BPM solution to perform simple routing logic.\n\n如果你需要使用中介拓扑结构，那么理解你的需求，并为其匹配恰当的事件中介实现是构建事件驱动架构过程中至关重要的一环。使用开源框架去解决非常复杂的业务处理、管理、调节事件，注定会失败，因为开源框架只是用 BPM 的方式解决了一些简单的事件分发逻辑，比起你的业务逻辑，其中的事件分发逻辑简直是九牛一毛。\n\nTo illustrate how the mediator topology works, suppose you are insured through an insurance company and you decide to move. In this case, the initial event might be called something like relocation event. The steps involved in processing a relocation event are con‐ tained within the event mediator as shown in Figure 2-2. For each initial event step, the event mediator creates a processing event (e.g., change address, recalc quote, etc.), sends that processing event to the event channel and waits for the processing event to be processed by the corresponding event processor (e.g., customer process, quote process, etc.). This process continues until all of the steps in the ini‐ tial event have been processed. The single bar over the recalc quote and update claims steps in the event mediator indicates that these steps can be run at the same time.\n\n为了解释清楚中介拓扑结构是怎么运作的，我假设你在某家保险公司买了保险，成为了受保人，然后你打算搬家。在这种情况下，初始事件就是重定位事件，或者其他类似的事件。与重定位事件相关的处理步骤就像下图展示的那样，处于事件中介之中。对每一个初始事件的传入，事件中介都会创建一个待处理事件（例如：改变地址，重新计算保险报价，等等……），并将它发送给事件通道，等到待处理事件被发出响应的事件处理器处理（例如：客户改变地址的操作流程、报价计算流程，等等……）。直到初始事件中的每一个需要处理的步骤完成了，这项处理才会继续（例如：把所有手续都完成之后，保险公司才会帮你改变地址）。事件中介中，位于重新计算保险报价和更新请求前的小竖杠表示表示这些步骤可以并行处理。\n\n![2-1](images/2-2.png)\n\n## Broker Topology\n## 代理拓扑结构\n\nThe broker topology differs from the mediator topology in that there is no central event mediator; rather, the message flow is dis‐ tributed across the event processor components in a chain-like fashion through a lightweight message broker (e.g., ActiveMQ, HornetQ, etc.). This topology is useful when you have a relatively simple event processing flow and you do not want (or need) central event orchestration.\n\n代理拓扑结构与中介拓扑结构不同之处在于：代理拓扑结构中没有核心的事件中介；相反，事件流在代理拓扑结构中通过一个轻量的消息代理（例如：ActiveMQ, HornetQ，等等……）将消息串联成链状，分发至事件处理器组件中进行处理。代理扑结构适用的使用场景大致上具有以下特征：你的事件处理流相对来说比较简单，而且你不想（不需要）使用核心的事件分配、调节机制以提高你处理事件的效率。\n\nThere are two main types of architecture components within the broker topology: a broker component and an event processor compo‐ nent. The broker component can be centralized or federated and contains all of the event channels that are used within the event flow.\n\n在代理拓扑结构中主要包括两种组件：代理和事件处理器。代理可被集中或相互关联在一起使用，此外，代理中还可以包含所有事件流中使用的事件通道。\n\nThe event channels contained within the broker component can be message queues, message topics, or a combination of both.\n\n存在于代理组件中的事件通道可以是消息队列，消息主题,或者是两者的组合。\n\nThis topology is illustrated in Figure 2-3. As you can see from the diagram, there is no central event-mediator component controlling and orchestrating the initial event; rather, each event-processor component is responsible for processing an event and publishing a new event indicating the action it just performed. For example, an event processor that balances a portfolio of stocks may receive an initial event called stock split. Based on that initial event, the event processor may do some portfolio rebalancing, and then publish a new event to the broker called rebalance portfolio, which would then be picked up by a different event processor. Note that there may be times when an event is published by an event processor but not picked up by any another event processor. This is common when you are evolving an application or providing for future functionality and extensions.\n\n代理拓扑结构大致如下图，如你所见，在这其中没有一个核心的事件中介组件控制和分发初始事件；相反，每一个事件处理器只负责处理一个事件，并向外发送一个事件，以标明其刚刚执行的动作。例如，假设存在一个事件处理器用于平衡证券交易，那么事件处理器可能会接受一个拆分股票的初始事件，为了处理这项初始事件，事件处理器则需要重新平衡股票的投资金额，而这个重新平衡的事件将由另一个事件处理器接收、处理。在这其中有一个细节需要注意：处理初始事件后，由事件处理器发出的事件不被其他事件处理器接收、处理的情况时常会发生，尤其是你在为应用添加功能和进行功能拓展时，这种情况更为常见。\n\n![2-3](images/2-3.png)\n\nTo illustrate how the broker topology works, we’ll use the same example as in the mediator topology (an insured person moves). Since there is no central event mediator to receive the initial event in the broker topology, the customer-process component receives the event directly, changes the customer address, and sends out an event saying it changed a customer’s address (e.g., change address event). In this example, there are two event processors that are interested in the change address event: the quote process and the claims process. The quote processor component recalculates the new auto- insurance rates based on the address change and publishes an event to the rest of the system indicating what it did (e.g., recalc quote event). The claims processing component, on the other hand, receives the same change address event, but in this case, it updates an outstanding insurance claim and publishes an event to the system as an update claim event. These new events are then picked up by other event processor components, and the event chain continues through the system until there are no more events are published for that par‐ ticular initiating event.\n\n为了阐明代理拓扑结构的运行机制，我会用一个与讲解中介拓扑结构时类似的例子（受保人旅行的例子）进行解释。因为在代理拓扑结构中没有核心事件中介接收初始事件，那么事件将由客户自处理组件直接接收，改变客户的地址，并发出一个事件告知系统客户的地址被其进行了改变（例如：改变地址的事件）。在这个例子中：有两个事件处理器会与改变地址的事件产生关联：报价处理和索赔处理。报价事件处理器将根据受保人的心地址重新计算保险的金额，并发出事件告知系统该受保人的保险金额被其改变。而索赔事件处理器将接受到相同的改变地址事件，不同的是，它将更新保险的赔偿金额，并发出一个更新索赔金额事件告知系统该受保人的赔偿金额被其改变。当这些新的事件被其他事件处理器接收、处理，使事件链一环扣一环地交由系统处理，直到事件链上的所有事件都被处理完，初始事件的处理才算完成。\n\n![2-4](images/2-4.png)    \n\nAs you can see from Figure 2-4, the broker topology is all about the chaining of events to perform a business function. The best way to understand the broker topology is to think about it as a relay race. In a relay race, runners hold a baton and run for a certain distance, then hand off the baton to the next runner, and so on down the chain until the last runner crosses the finish line. In relay races, once a runner hands off the baton, she is done with the race. This is also true with the broker topology: once an event processor hands off the event, it is no longer involved with the processing of that spe‐ cific event.\n\n如上图所示，代理拓扑结构的设计思想就是将对事件流的处理转换为对事件链的业务功能处理，把代理拓扑结构看作是接力比赛是最好的理解方式：在一场4*100的接力比赛中，每一位运动员都需要拿着一根接力棒跑100米，运动员跑完自己的100米后需要将接力棒传递给下一位运动员，直到最后一位运动员拿着接力棒跑过终点线，整场接力比赛才算结束。根据这样的逻辑我们还可以知道：在代理拓扑结构中，一旦某个事件处理器将事件传递给另一个事件处理器，那么这个事件处理器不会与该事件的后续处理产生任何联系。\n\n## Considerations\n## 使用事件驱动架构模式的顾虑\n\nThe event-driven architecture pattern is a relatively complex pattern to implement, primarily due to its asynchronous distributed nature. When implementing this pattern, you must address various dis‐ tributed architecture issues, such as remote process availability, lack of responsiveness, and broker reconnection logic in the event of a broker or mediator failure.\n\n实现事件驱动架构模式相对于实现其他架构模式会更困难一些，因为它通过异步处理进行事件分发。当你需要在你的应用中使用这种架构模式，你必须处理各种由事件分发处理带来的问题，例如：远程操作功能的可用性，缺少权限，以及在代理或中介中处理事件失败时，用于处理这种情况的重连逻辑。如果你不能很好地解决这些问题，那你的应用一定会出现各种 Bug，让开发团队痛苦不已。\n\nOne consideration to take into account when choosing this architec‐ ture pattern is the lack of atomic transactions for a single business process. Because event processor components are highly decoupled and distributed, it is very difficult to maintain a transactional unit of work across them. For this reason, when designing your application using this pattern, you must continuously think about which events can and can’t run independently and plan the granu‐ larity of your event processors accordingly. If you find that you need to split a single unit of work across event processors—that is, if you are using separate processors for something that should be an undivided transaction—this is probably not the right pattern for your application.\n\n在选择事件驱动架构时还有一点需要注意：在处理单个业务逻辑时，这种架构模式不能处理细粒度的事务。因为事件处理器都高度解耦、并且广泛分布，这使得在这些事件处理器中维持一个业务单元变得非常困难。因此，当你使用这种架构模式架构你的应用时，你必须不断地考虑哪些事件能单独被处理，哪些不能，并为此设计相应事件处理器的处理粒度。如果你发现你需要将一个业务单元切割成许多子单元，并一一匹配相应的事件处理器，那你就要为此进行代码设计；如果你发现你用多个不同的事件处理器处理的哪些业务其实是可以合并到一个业务事件之中的，那么这种模式可能并不适合你的应用，又或者是你的设计出了问题。\n\nPerhaps one of the most difficult aspects of the event-driven archi‐ tecture pattern is the creation, maintenance, and governance of the event-processor component contracts. Each event usually has a spe‐ cific contract associated with it (e.g., the data values and data format being passed to the event processor). It is vitally important when using this pattern to settle on a standard data format (e.g., XML, JSON, Java Object, etc.) and establish a contract versioning policy right from the start.\n\n使用事件驱动架构模式最困难的地方就在于架构的创建、维护、以及对事件处理器的管理。通常每一个事件都拥有其指定的事件处理协议（例如：传递给事件处理器的数据类型、数据格式），这就使得设下标准的数据格式成为使用事件驱动架构模式中至关重要的一环（例如：XML，JSON，Java 对象，等等……），并在架构创建之初就为这些数据格式授权，以便处理。\n\n## Pattern Analysis\n## 事件驱动架构模式分析\n\nThe following table contains a rating and analysis of the common architecture characteristics for the event-driven architecture pattern. The rating for each characteristic is based on the natural tendency for that characteristic as a capability based on a typical implementa‐ tion of the pattern, as well as what the pattern is generally known for. For a side-by-side comparison of how this pattern relates to other patterns in this report, please refer to Appendix A at the end of this report.\n\n下面是基于对常见的架构模式特征进行评价的标准，对事件驱动架构模式所作的实际分析，评价是以常见的架构模式的相似实现作为标准进行的，如果你想知道进行对比的其他架构模式对应的特征，可以结尾处查看 Appendix A 的汇总表。\n\n### Overall agility\nRating: High    \n\n### 整体灵活性\n评价：高\n\nAnalysis: Overall agility is the ability to respond quickly to a constantly changing environment. Since event-processor com‐ ponents are single-purpose and completely decoupled from other event processor components, changes are generally iso‐ lated to one or a few event processors and can be made quickly without impacting other components.\n\n分析：整体灵活性用于评价架构能否在不断改变的使用场景下快速响应，因为事件处理器组件使用目的单一、高度解耦、与其他事件处理器组件相互独立，不相关联，那么发生的改变对一个或多个事件处理器来说普遍都是独立的，使得对改变的反馈非常迅速，不需要依赖其他事件处理器的响应作出处理。\n\n### Ease of deployment\nRating: High    \n\n## 部署灵活性\n评价：高\n\nAnalysis: Overall this pattern is relatively easy to deploy due to the decoupled nature of the event-processor components. The broker topology tends to be easier to deploy than the mediator topology, primarily because the event mediator com‐ ponent is somewhat tightly coupled to the event processors: a change in an event processor component might also require a change in the event mediator, requiring both to be deployed for any given change.\n\n分析：总的来看，事件驱动架构模式由于其高度解耦的事件处理器组件的存在，对事件的部署相对来说比较容易，而使用代理拓扑结构比使用中介拓扑结构进行事件调度会更容易一些，主要是因为在 中介拓扑结构中事件处理器与事件中介紧密地耦合在一起：事件处理器中产生的告别会引起事件中介的改变，如果我们需要让某个被处理的事件被改变，那么我们需要同时调度事件处理器和事件中介。\n\n### Testability\nRating: Low     \n\n### 可测试性\n评价：低\n\nAnalysis: While individual unit testing is not overly difficult, it does require some sort of specialized testing client or testing tool to generate events. Testing is also complicated by the asyn‐ chronous nature of this pattern.\n\n分析：虽然在事件驱动架构模式中进行单元测试并不困难，但如果我们要进行单元测试，我们就需要某种特定的测试客户端或者是测试工具产生事件，为单元测试提供初始值。此外，由于事件驱动架构模式是异步进行事件分发的，其异步处理的特性也为单元测试带来了一定的困难。\n\n### Performance\nRating: High     \n\n### 性能表现\n评价：高\n\nAnalysis: While it is certainly possible to implement an event- driven architecture that does not perform well due to all the messaging infrastructure involved, in general, the pattern ach‐ ieves high performance through its asynchronous capabili‐ ties; in other words, the ability to perform decoupled, parallel asynchronous operations outweighs the cost of queuing and dequeuing messages.\n\n分析：对消息传递的架构可能会让设计出来的事件驱动架构的表现不如我们的期望，但通常来说，该模式都能通过其异步处理的特性展示优秀的性能表现；换句话来说，高度解耦，异步并行操作大大减少了传递消息过程中带来的时间开销。\n\n### Scalability\nRating: High     \n\n### 可拓展性\n评价：高\n\nAnalysis: Scalability is naturally achieved in this pattern through highly independent and decoupled event processors. Each event processor can be scaled separately, allowing for fine-grained scalability.\n\n分析：事件驱动架构中的高度解耦、相互独立的事件处理器组件的存在，使得可拓展性成为该架构与生俱来的优点。架构的这些特定使得事件处理器能够进行细粒度的拓展，使得每一个事件处理器都能单独被拓展，而不影响其他事件处理器。\n\n### Ease of development\nRating: Low     \n\n### 开发的简易性\n评价：低\n\nAnalysis: Development can be somewhat complicated due to the asynchronous nature of the pattern as well as contract cre‐ ation and the need for more advanced error handling condi‐ tions within the code for unresponsive event processors and failed brokers.\n\n分析：由于使用事件驱动架构进行开发需要考虑其异步处理机制、协议创建流程，并且开发者需要用代码为事件处理器和操作失败的代理提供优秀的错误控制环境，无疑使得用事件驱动架构进行开发会比使用其他架构进行开发要困难一些。\n\n## 译者注\n\n读完整篇文章，我相信大家对 mediator 与 broker 这两个概念有一个大致的印象，但就两者的译文来看，中介和代理似乎没什么区别，特别是了解 proxy 的读者会更加困惑，这三者之间到底是什么关系？它们的概念是互通的吗？为了解决这种混淆，译者将在此阐述三者间的区别：\n\n假如现在我有一个事件/事件流需要被处理，那么使用 mediator、broker、proxy 处理事件的区别在哪里呢？\n\n- 如果我们使用 mediator，那就意味着我将把事件流交给 mediator，mediator 会帮我把事件分解为多个步骤，并分析其中的执行逻辑，调整和分发事件（例如判断哪些事件可以并行，哪些事件可以串行），然后根据 mediator 分解、调节的结果去执行事件中的每一个步骤，把所有步骤完成后，就能把需要处理的事件处理好。\n\n- 如果我们使用 broker，那就意味着我将把事件交给 broker，broker 获得事件后会把事件发出去（在本文中为：通知架构中所有可用的事件处理器），事件处理器们接收到事件以后，判断处理这个事件是否为自己的职责之一，如果不是则无视，与自己有关则把需要完成的工作完成，完成后如果事件还有后续需要处理的事件，则通过 broker 再次发布，再由相关的事件处理器接收、处理。以这样的方式将事件不断分解，沿着事件链一级一级地向下处理子事件，直到事件链中的所有事件被完成，我的事件也就处理好了。\n\n- 如果我们使用 proxy，那就意味着我自己对需要处理的事件进行了分解，然后把不同的子事件一一委托给不同的 proxy，由被委托的 proxy 帮我完成子事件，从而完成我要做的事件。"
  },
  {
    "path": "software-architecture-patterns/chapter03-Mr.Simple.md",
    "content": "# 第三章 微内核架构\n\r￼The microkernel architecture pattern (sometimes referred to as the plug-in architecture pattern) is a natural pattern for implementing product-based applications. A product-based application is one that is packaged and made available for download in versions as a typical third-party product. However, many companies also develop and release their internal business applications like software products, complete with versions, release notes, and pluggable features. These are also a natural fit for this pattern. The microkernel architecture pattern allows you to add additional application features as plug-ins to the core application, providing extensibility as well as feature separation and isolation.\r\r微内核架构模式(也称为插件化应用架构)对于基于产品的应用程序来说是一个很自然的选择。基于产品的应用是指一个经过打包的、可以通过版本下载的一个典型的第三方产品。然而，很多公司也会开发和发布他们的内部商业软件，完整的版本号、发布日志和可插拔的新特性，这些就非常符合微内核架构的思想。微内核架构模式可以通过插件的形式添加额外的特性到核心系统中，这提供了很好的扩展性，也使得新特性与核心系统隔离开来。( 译者注: 比如，著名的Eclipse IDE就是基于插件化开发的，eclipse核心更像是一个微内核，或者我们可把它叫做开放平台，其他的功能通过安装插件的形式添加到eclipse中。 )\r\r## Pattern Description 模式描述\rThe microkernel architecture pattern consists of two types of archi‐ tecture components: a core system and plug-in modules. Application logic is divided between independent plug-in modules and the basic core system, providing extensibility, flexibility, and isolation of application features and custom processing logic. Figure 3-1 illustrates the basic microkernel architecture pattern.    \n\n微内核架构主要包含两种架构组件: 核心系统和插件模块。应用逻辑被划分为独立的插件模块和核心系统，这样就提供良好的可扩展性、灵活性，应用的新特性和自定义处理逻辑也会被隔离。图3-1演示了基本的微内核架构。\n\rThe core system of the microkernel architecture pattern tradition‐ ally contains only the minimal functionality required to make the system operational. Many operating systems implement the micro‐ kernel architecture pattern, hence the origin of this pattern’s name. From a business-application perspective, the core system is often defined as the general business logic sans custom code for special cases, special rules, or complex conditional processing.\n\n微内核架构的核心系统一般情况下只包含一个能够使系统运作起来的最小化模块。很多操作系统的实现就是使用微内核架构，因此这也是该架构名字的由来。从商业应用的角度看，核心系统通常是为特定的使用场景、规则、或者复杂条件处理定义了通用的业务逻辑，而插件模块根据这些规则实现了具体的业务逻辑。\n\n![3-1](images/3-1.png)\r图 3-1. 微内核架构\r\rThe plug-in modules are stand-alone, independent components that contain specialized processing, additional features, and custom code that is meant to enhance or extend the core system to produce additional business capabilities. Generally, plug-in modules should be independent of other plug-in modules, but you can certainly design plug-ins that require other plug-ins to be present. Either way, it is important to keep the communication between plug-ins to a minimum to avoid dependency issues.    \n\n插件模块是一个包含专业处理、额外特性的独立组件，自定义代码意味着增加或者扩展核心系统以达到产生附加的业务逻辑的能力。通常，插件模块之间应该是没有任何依赖性的，但是你也可以设计一个需要依赖另一个插件的插件。但无论如何，重要的是，插件之间的通信要保持在最低限度，以避免因依赖导致的问题出现。\n\rThe core system needs to know about which plug-in modules are available and how to get to them. One common way of implementing this is through some sort of plug-in registry. This registry contains information about each plug-in module, including things like its name, data contract, and remote access protocol details (depending on how the plug-in is connected to the core system). For example, a plug-in for tax software that flags high-risk tax audit items might have a registry entry that contains the name of the service (AuditChecker), the data contract (input data and output data), and the contract format (XML). It might also contain a WSDL (Web Services Definition Language) if the plug-in is accessed through SOAP.  \n\n核心系统需要了解插件模块的可用性以及如何获取到它们。一个通用的实现方法是通过一组插件注册表。这个插件注册表含有每个插件模块的信息，包括它的名字、数据规约和远程访问协议(取决于插件如何与核心系统建立连接)。例如，一个税务软件的用于标识高风险的税务审计插件可能会有一个含有插件名(比如AuditChecker)的注册入口，数据规约(输入数据、输出数据)和规约格式( 比如xml )。如果这个插件是通过SOAP协议访问，那么它可能还要包含一个WSDL(Web Services Definition Language).\n\rPlug-in modules can be connected to the core system through a variety of ways, including OSGi (open service gateway initiative), messaging, web services, or even direct point-to-point binding (i.e., object instantiation). The type of connection you use depends on the type of application you are building (small product or large business application) and your specific needs (e.g., single deploy or dis￼￼tributed deployment). The architecture pattern itself does not specify any of these implementation details, only that the plug-in modules must remain independent from one another.   \n\n插件模块可以通过多种方式连接到核心系统，包括OSGi ( open service gateway initiative )、消息机制、web服务或者直接点对点的绑定 ( 比如对象实例化，即依赖注入 )。你使用的连接类型取决于你构建的应用类型和你的特殊需求（比如单机部署还是分布式部署）。微内核架构本身没有指定任何的实现方式，唯一的规定就是插件模块之间不要产生依赖。\n\rThe contracts between the plug-in modules and the core system can range anywhere from standard contracts to custom ones. Custom contracts are typically found in situations where plug-in components are developed by a third party where you have no control over the contract used by the plug-in. In such cases, it is common to create an adapter between the plug-in contact and your standard con‐ tract so that the core system doesn’t need specialized code for each plug-in. When creating standard contracts (usually implemented through XML or a Java Map), it is important to remember to create a versioning strategy right from the start.\n\n插件和核心系统的通信规范包含标准规范和自定义规范。自定义规范典型的使用场景是插件组件是被第三方构建的，并且你无法控制插件使用的规范。在这种情况下，通常做法是在第三方插件规约和你的标准规范之间创建一个Adapter（适配器），这样，核心系统就无需为每个插件提供专门的代码。当创建标准规范 ( 通常是通过XML或者Java Map )时，从一开始就创建一个版本策略是非常重要的。 \r\r## Pattern Examples 架构示例\rPerhaps the best example of the microkernel architecture is the Eclipse IDE. Downloading the basic Eclipse product provides you little more than a fancy editor. However, once you start adding plug-ins, it becomes a highly customizable and useful product. Internet browsers are another common product example using the microkernel architecture: viewers and other plug-ins add additional capabilities that are not otherwise found in the basic browser (i.e., core system).    \n\n也许微内核架构的最好示例就是大家熟知的Eclipse IDE了。下载最基本的Eclipse后，它只能提供一个编辑器。然后，一旦你开始添加插件，它就变成一个高度可定制化和非常有用的产品（译者注 : 更多内容大家可以参考 [开源软件架构 卷1：第6章 Eclipse之一](http://www.ituring.com.cn/article/6817) ）。浏览器是另一个使用微内核架构的产品示例，它由一个查看器和其他扩展的插件组成。\n\n\rThe examples are endless for product-based software, but what about large business applications? The microkernel architecture applies to these situations as well. To illustrate this point, let’s use another insurance company example, but this time one involving insurance claims processing. \n\n基于微内核架构的示例数不胜数，但是大型的商业应用呢？微内核应用架构也适用于这些情形。为了阐述这个观点，让我们来看看另一个保险公司的示例，但是这次的示例会涉及保险理赔处理。  \n\rClaims processing is a very complicated process. Each state has different rules and regulations for what is and isn’t allowed in an insurance claim. For example, some states allow free windshield replacement if your windshield is damaged by a rock, whereas other states do not. This creates an almost infinite set of conditions for a standard claims process.    \n\n理赔处理是一个非常复杂的过程。每个州都有不同的关于保险理赔的规则和条文。例如，你的挡风玻璃被石头砸碎了，在有些州可以免费更换，但在其他州则不可以。因为大家的标准都不一样，因此理赔标准几乎可以是无限的。\n\rNot surprisingly, most insurance claims applications leverage large and complex rules engines to handle much of this complexity. How‐ ever, these rules engines can grow into a complex big ball of mud where changing one rule impacts other rules, or making a simple rule change requires an army of analysts, developers, and testers. Using the microkernel architecture pattern can solve many of these issues.   \n\n有很多保险理赔应用运用大型和复杂的规则处理引擎来处理不同规则带来的复杂性。然而，可能会因为某条规则的改变而引起其他规则的改变而使得这些规则处理引擎变成一个大泥球，或者一个简单的规则变更也会需要很多的需求分析师、开发工程师、测试工程师来参与进行处理。使用微内核架构能够很好的解决这个问题，核心系统只知道根据理赔规则处理，但这个理赔规则是抽象的，系统将理赔规则作为一个插件规范，具体的规则有对应的实现，然后注入到系统中即可。\n\rThe stack of folders you see in Figure 3-2 represents the core system for claims processing. It contains the basic business logic required by the insurance company to process a claim, except without any custom processing. Each plug-in module contains the specific rules for that state. In this example, the plug-in modules can be implemented using custom source code or separate rules engine instances. Regardless of the implementation, the key point is that state-specific rules and processing is separate from the core claims system and can be added, removed, and changed with little or no effect on the rest of the core system or other plug-in modules.    \n\n图3-2中的一堆文件夹代表了理赔处理核心系统。它包含一些处理保险理赔的基本业务逻辑。每一个插件模块包含每个州的具体理赔规则。在这个例子中，插件模块可以通过自定义源代码或分离规则引擎实例来实现。不管具体实现如何，关键就在于理赔规则和处理都从核心系统中分离，而这些规则和处理过程都可以被动态地添加、移除，而这些改变对于核心系统和其他插件只有很小的影响或者根本不产生影响。\n\n![3-2](images/3-2.png)\r图 3-2. 微内核架构案例\r\r## Considerations 注意事项\rOne great thing about the microkernel architecture pattern is that it can be embedded or used as part of another architecture pattern. For example, if this pattern solves a particular problem you have with a specific volatile area of the application, you might find that you can’t implement the entire architecture using this pattern. In this case, you can embed the microservices architecture pattern in another pattern you are using (e.g., layered architecture). Similarly, the event-processor components described in the previous section on event-driven architecture could be implemented using the microservices architecture pattern.    \n\n微内核架构模式的妙处之一是，它可以嵌入或用作另一种架构模式的一部分。例如，如果这个架构解决的是一个你应用中易变领域的特定的问题 ( 译者注 : 即插件化能够解决你应用中的某个特定模块的架构问题 )，你可能会发现你不能在整个应用中使用这种架构。在这种情况下，你可以将微内核架构嵌入到另一个架构模式中 ( 比如分层架构 )。同样的，在上一章节中描述的事件驱动架构中的事件处理器组件也可以使用微内核架构。\n\rThe microservices architecture pattern provides great support for evolutionary design and incremental development. You can first produce a solid core system, and as the application evolves incrementally, add features and functionality without having to make significant changes to the core system.     \n\n微内核架构对渐进式设计和增量开发提供了非常好的支持。你可以先构建一个单纯的核心系统，随着应用的演进，系统会逐渐添加越来越多的特性和功能，而这并不会引起核心系统的重大变化。\n\rFor product-based applications, the microkernel architecture pattern should always be your first choice as a starting architecture, particularly for those products where you will be releasing additional features over time and want control over which users get which features. If you find over time that the pattern doesn’t satisfy all of your requirements, you can always refactor your application to another architecture pattern better suited for your specific requirements.    \n\n对基于产品的应用来说，微内核架构应该是你的第一选择。特别是那些你会在后续开发中发布附加特性和控制哪些用户能够获取哪些特性的应用。如果你在后续开发中发现这个架构不能满足你的需求了，你能够根据你的特殊需求将你的应用重构为另一个更好的架构。\n\n\r## Pattern Analysis 模式分析\rThe following table contains a rating and analysis of the common architecture characteristics for the microkernel architecture pattern. \n\nThe rating for each characteristic is based on the natural tendency for that characteristic as a capability based on a typical implementation of the pattern, as well as what the pattern is generally known for.\n\n For a side-by-side comparison of how this pattern relates to other patterns in this report, please refer to Appendix A at the end of this report.     \n\n下面的表格中包含了微内核架构每个特性的评级和分析。以微内核架构的最经典的实现方式的自然趋势为依据对每个特性进行评级。关于微内核架构与其他模式的相关性比较请参考附录A。\n\r### Overall agility 整体灵活性\rRating: High       \rAnalysis: Overall agility is the ability to respond quickly to a constantly changing environment. Changes can largely be isolated and implemented quickly through loosely coupled plug-in modules. In general, the core system of most microkernel archi‐ tectures tends to become stable quickly, and as such is fairly robust and requires few changes over time.     \n\n评级 : 高     \n分析 : 整体灵活性是指能够快速适应不断变化的环境的能力。通过插件模块的松耦合实现，可以将变化隔离起来，并且快速满足需求。通常，微内核架构的核心系统很快趋于稳定，这样系统就变得很健壮，随着时间的推移它也不会发生多大改变。\n\r### Ease of deployment  易于部署\rRating: High      \rAnalysis: Depending on how the pattern is implemented, the plug-in modules can be dynamically added to the core system at runtime (e.g., hot-deployed), minimizing downtime dur‐ ing deployment.\n\n评级 : 高     \n分析 : 根据实现方式，插件模块能够在运行时被动态地添加到核心系统中 （ 比如，热部署 ）,把停机时间减到最小。\r\n\r\n### Testability 可测试性\rRating: High\rAnalysis: Plug-in modules can be tested in isolation and can be easily mocked by the core system to demonstrate or prototype a particular feature with little or no change to the core system.\n\n评级 : 高     \n分析 : 插件模块能够被独立的测试，能够非常简单地被核心系统模拟出来进行演示，或者在对核心系统很小影响甚至没有影响的情况下对一个特定的特性进行原型展示。\r\r### Performance 性能\rRating: High     \rAnalysis: While the microkernel pattern does not naturally lend itself to high-performance applications, in general, most appli‐ cations built using the microkernel architecture pattern perform well because you can customize and streamline applications to only include those features you need. The JBoss Application Server is a good example of this: with its plug-in architecture, you can trim down the application server to only those features you need, removing expensive non-used features such as remote access, messaging, and caching that consume memory, CPU, and threads and slow down the app server.\n\n评级 : 高     \n分析 : 使用微内核架构不会自然而然地使你的应用变得高性能。通常，很多使用微内核架构的应用运行得很好，因为你能定制和简化应用程序，使它只包含那些你需要的功能模块。JBoss应用服务器就是这方面的优秀示例: 依赖于它的插件化架构，你可以只加载你需要的功能模块，移除那些消耗资源但没有使用的功能特性，比如远程访问，消息传递，消耗内存、CPU的缓存，以及线程，从而减小应用服务器的资源消耗。\r\r### Scalability 伸缩性\rRating: Low      \rAnalysis: Because most microkernel architecture implementa‐ tions are product based and are generally smaller in size, they are implemented as single units and hence not highly scalable. Depending on how you implement the plug-in modules, you can sometimes provide scalability at the plug-in feature level, but overall this pattern is not known for producing highly scala‐ ble applications.   \n\n评级 : 低    \n分析 : 因为微内核架构的实现是基于产品的，它通常都比较小。它们以独立单元的形式实现，因此没有太高的伸缩性。此时，伸缩性就取决于你的插件模块，有时你可以在插件级别上提供可伸缩性，但是总的来说这个架构并不是以构建高度伸缩性的应用而著称的。\n\r### Ease of development 易于开发\rRating: Low      \rAnalysis: The microkernel architecture requires thoughtful design and contract governance, making it rather complex to implement. Contract versioning, internal plug-in registries, plug-in granularity, and the wide choices available for plug-in connectivity all contribute to the complexity involved with implementing this pattern.\n\n评级 : 低\n\n分析 : 微内核架构需要详尽周全的设计和规约管理，这使得它实现起来相当复杂。规约版本控制，内部插件注册，插件粒度，广泛的插件连接选择，所有这些都是导致该架构的实现变得复杂的重要因素。"
  },
  {
    "path": "software-architecture-patterns/chapter04-dupengwei.md",
    "content": "# 第四章 微服务架构 \nThe microservices architecture pattern is quickly gaining ground in the industry as a viable alternative to monolithic applications and service-oriented architectures. Because this architecture pattern is still evolving, there’s a lot of confusion in the industry about what this pattern is all about and how it is implemented. This section of the report will provide you with the key concepts and foundational knowledge necessary to understand the benefits (and trade-offs) of this important architecture pattern and whether it is the right pat‐ tern for your application.\n\n微服务架构模式作为替代monolithic应用和面向服务架构的一个可行的选择，在业内迅速取得进展。由于这个架构模式仍然在不断的发展中，在业界存在很多困惑——这种模式是关于什么的？它是如何实现的？本报告的这部分将为你提供关键概念和必要的基础知识来理解这一重要架构模式的好处(和取舍)，以此来判断这种架构是否适合你的应用。\n\n## Pattern Description 模式描述\nRegardless of the topology or implementation style you chose, there are several common core concepts that apply to the general architecture pattern. The first of these concepts is the notion of separately deployed units. As illustrated in Figure 4-1, each component of the microservices architecture is deployed as a separate unit, allowing for easier deployment through an effective and streamlined delivery pipeline, increased scalability, and a high degree of application and component decoupling within your application.\n\n不管你选择哪种拓扑或实现风格,有几种常见的核心概念适用于一般架构模式。第一个概念是*单独部署单元*。如图4-1所示，微服务架构的每个组件都作为一个独立单元进行部署，让每个单元可以通过有效、简化的传输管道进行通信，同时它还有很强的扩展性，应用和组件之间高度解耦，使得部署更为简单。\n\nPerhaps the most important concept to understand with this pattern is the notion of a service component. Rather than think about services within a microservices architecture, it is better to think about service components, which can vary in granularity from a single module to a large portion of the application. Service components contain one or more modules (e.g., Java classes) that represent either a single-purpose function (e.g., providing the weather for a specific city or town) or an independent portion of a large business application (e.g., stock trade placement or determining auto-insurance rates). Designing the right level of service component granularity is one of the biggest challenges within a microservices architecture. This challenge is discussed in more detail in the following service component orchestration subsection.\n\n也许要理解这种模式，最重要的概念就是服务组件（service component）。不要考虑微服务架构内部的服务，而最好是考虑服务组件，从粒度上讲它可以小到单一的模块，或者大至一个应用程序。服务组件包含一个或多个模块（如Java类），这些模块可以提供一个单一功能（如，为特定的城市或城镇提供天气情况），或也可以作为一个大型商业应用的一个独立部分（如，股票交易布局或测定汽车保险的费率）。在微服务架构中，正确设计服务组件的粒度是一个很大的挑战。在接下来的服务组件部分对这一挑战进行了详细的讨论。\n\n![4-1](images/4-1.png)     \nFigure 4-1. Basic Microservices architecture pattern  图 4-1. 基本微服务架构模式\n\nAnother key concept within the microservices architecture pattern is that it is a distributed architecture, meaning that all the components within the architecture are fully decoupled from one other and accessed through some sort of remote access protocol (e.g., JMS, AMQP, REST, SOAP, RMI, etc.). The distributed nature of this architecture pattern is how it achieves some of its superior scalability and deployment characteristics.\n\n微服务架构模式的另一个关键概念是它是一个*分布式*的架构，这意味着架构内部的所有组件之间是完全解耦的，并通过某种远程访问协议（如， JMS, AMQP, REST, SOAP, RMI等）进行访问。这种架构的分布式特性是它实现一些优越的可扩展性和部署特性的关键所在。\n\nOne of the exciting things about the microservices architecture is that it evolved from issues associated with other common architecture patterns, rather than being created as a solution waiting for a problem to occur. The microservices architecture style naturally evolved from two main sources: monolithic applications developed using the layered architecture pattern and distributed applications developed through the service-oriented architecture pattern.\n\n微服务架构另一个令人兴奋的特性是它是由其他常见架构模式存在的问题演化来的，而不是作为一个解决方案被创造出来等待问题出现。微服务架构的演化有两个主要来源：使用分层架构模式的monolithic应用和使用面向服务架构的分布式应用。\n\nThe evolutionary path from monolithic applications to a microservices architecture style was prompted primarily through the development of continuous delivery, the notion of a continuous deployment pipeline from development to production which streamlines the deployment of applications. Monolithic applications typically consist of tightly coupled components that are part of a single deployable unit, making it cumbersome and difficult to change, test, and deploy the application (hence the rise of the common “monthly deployment” cycles typically found in most large IT shops). These factors commonly lead to brittle applications that break every time something new is deployed. The microservices architecture pattern addresses these issues by separating the application into multiple deployable units (service components) that can be individually developed, tested, and deployed independent of other service components.\n\n由单体应用( 一个应用就是一个整体 )到微服务的发展过程主要是由持续交付开发促成的。从开发到生产的持续部署管道概念,简化了应用程序的部署。单体应用通常是由紧耦合的组件组成，这些组件同时又是另一个单一可部署单元的一部分，这使得它繁琐，难以改变、测试和部署应用（因此常见的“月度部署”周期出现并通常发生在大型IT商店项目）。这些因素通常会导致应用变得脆弱以至于每次有一点新功能部署后应用就不能运行。微服务架构模式通过将应用分隔成多个可部署的单元（服务组件）的方法来解决这一问题，这些服务组件可以独立于其他服务组件进行单独开发、测试和部署。\n\nThe other evolutionary path that lead to the microservices architecture pattern is from issues found with applications implementing the service-oriented architecture pattern (SOA). While the SOA pattern is very powerful and offers unparalleled levels of abstraction, heterogeneous connectivity, service orchestration, and the promise of aligning business goals with IT capabilities, it is nevertheless complex, expensive, ubiquitous, difficult to understand and implement, and is usually overkill for most applications. The microservices architecture style addresses this complexity by simplifying the notion of a service, eliminating orchestration needs, and simplifying connectivity and access to service components.\n\n另一个导致微服务架构模式产生的演化过程是由面向服务架构模式（SOA）应用程序存在的问题引起的。虽然SOA模式非常强大，提供了无与伦比的抽象级别、异构连接、服务编排，并保证通过IT能力调整业务目标，但它仍然是复杂的,昂贵的,普遍存在，它很难理解和实现，对大多数应用程序来说过犹不及。微服务架构通过简化服务概念，消除编排需求、简化服务组件连接和访问来解决复杂度问题。\n\n## Pattern Topologies 模式拓扑\nWhile there are literally dozens of ways to implement a microservices architecture pattern, three main topologies stand out as the most common and popular: the API REST-based topology, 基于REST的应用 topology, and the centralized messaging topology.\n\n虽然有很多方法来实现微服务架构模式,但三个主要的拓扑结构脱颖而出，最常见和流行的有:API REST-based 拓扑结构,基于REST的应用拓扑结构和集中式消息拓扑结构。\n\nThe API REST-based topology is useful for websites that expose small, self-contained individual services through some sort of API (application programming interface). This topology, which is illustrated in Figure 4-2, consists of very fine-grained service components (hence the name microservices) that contain one or two modules that perform specific business functions independent from the rest of the services. In this topology, these fine-grained service components are typically accessed using a REST-based interface implemented through a separately deployed web-based API layer. Examples of this topology include some of the common single-purpose cloud-based RESTful web services found by Yahoo, Google, and Amazon.\n\n基于REST的API拓扑适用于网站，通过某些API（application programming interface）对外提供小型的、自包含的服务。这种拓扑结构,如图4 - 2所示,由粒度非常细的服务组件（因此得名微服务）组成，这些服务组件包含一个或两个模块并独立于其他服务来执行特定业务功能。在这种拓结构扑中,这些细粒度的服务组件通常被REST-based的接口访问，而这个接口是通过一个单独部署的web API层实现的。此种拓扑的例子包含一些常见的专用的、基于云的RESTful web service，大型网站像Yahoo, Google, and Amazon都在使用。\n\n![4-2](images/4-2.png)     \nFigure 4-2. API REST-based topology  图 4-2. API REST-based拓扑结构\n\nThe application REST-based topology differs from the API REST- based approach in that client requests are received through traditional web-based or fat-client business application screens rather than through a simple API layer. As illustrated in Figure 4-3, the user-interface layer of the application is deployed as a separate web application that remotely accesses separately deployed service components (business functionality) through simple REST-based interfaces. The service components in this topology differ from those in the API-REST-based topology in that these service components tend to be larger, more coarse-grained, and represent a small portion of the overall business application rather than fine-grained, single-action services. This topology is common for small to medium-sized business applications that have a relatively low degree of complexity.\n\n基于REST的应用拓扑结构与API REST- based不同，它通过传统的基于web的或胖客户端业务应用来接收客户端请求，而不是通过一个简单的API层。如图4-3所示，应用的用户接口层（user interface layer）是一个web应用，可以通过简单的REST-based接口访问单独部署的服务组件（业务功能）。该拓扑结构中的服务组件与API-REST-based拓扑结构中的不同，这些服务组件往往会更大、粒度更粗、代表整个业务应用程序的一小部分，而不是细粒度的、单一操作的服务。这种拓扑结构常见于中小型企业等复程度相对较低的应用程序。\n\n![4-3](images/4-3.png)     \nFigure 4-3. 基于REST的应用 topology 图 4-3. 基于REST的应用 拓扑结构\n\nAnother common approach within the microservices architecture pattern is the centralized messaging topology. This topology (illustrated in Figure 4-4) is similar to the previous application REST- based topology except that instead of using REST for remote access, this topology uses a lightweight centralized message broker (e.g., ActiveMQ, HornetQ, etc.). It is vitally important when looking at this topology not to confuse it with the service-oriented architecture pattern or consider it “SOA-Lite.\" The lightweight message broker found in this topology does not perform any orchestration, transformation, or complex routing; rather, it is just a lightweight transport to access remote service components.\n\n微服务架构模式中另一个常见的方法是集中式消息拓扑。该拓扑（如图4-4所示）与前面提到的基于REST的应用拓扑类似，不同的是，application REST- based拓扑结构使用REST进行远程访问，而该拓扑结构则使用一个轻量级的集中式消息代理（如，ActiveMQ, HornetQ等等）。不要将该拓扑与面向服务架构模式混淆或将其当做SOA简化版（“SOA-Lite”），这点是极其重要的。该拓扑中的轻量级消息代理（Lightweight Message Broker）不执行任何编排,转换,或复杂的路由;相反,它只是一个轻量级访问远程服务组件的传输工具。\n\nThe centralized messaging topology is typically found in larger business applications or applications requiring more sophisticated control over the transport layer between the user interface and the service components. The benefits of this topology over the simple REST-based topology discussed previously are advanced queuing mechanisms, asynchronous messaging, monitoring, error handling, and better overall load balancing and scalability. The single point of failure and architectural bottleneck issues usually associated with a centralized broker are addressed through broker clustering and broker federation (splitting a single broker instance into multiple broker instances to divide the message throughput load based on functional areas of the system).\n\n集中式消息拓扑结构通常应用在较大的业务应用程序中，或对于某些对传输层到用户接口层或者到服务组件层有较复杂的控制逻辑的应用程序中。该拓扑较之先前讨论的简单基于REST的拓扑结构，其好处是有先进的排队机制、异步消息传递、监控、错误处理和更好的负载均衡和可扩展性。与集中式代理相关的单点故障和架构瓶颈问题已通过代理集群和代理联盟（将一个代理实例为分多个代理实例，把基于系统功能区域的吞吐量负载划分开处理）解决。\n\n![4-4](images/4-4.png)   \n\nFigure 4-4. Centralized messaging topology  图 4-4. 集中式消息拓扑结构\n\n## Avoid Dependencies and Orchestration 避免依赖和编排\nOne of the main challenges of the microservices architecture pattern is determining the correct level of granularity for the service components. If service components are too coarse-grained you may not realize the benefits that come with this architecture pattern (deployment, scalability, testability, and loose coupling). However, service components that are too fine-grained will lead to service orchestration requirements, which will quickly turn your lean microservices architecture into a heavyweight service-oriented architecture, complete with all the complexity, confusion, expense, and fluff typically found with SOA-based applications.\n\n微服务架构模式的主要挑战之一就是决定服务组件的粒度级别。如果服务组件粒度过粗，那你可能不会意识到这个架构模式带来的好处（部署、可扩展性、可测试性和松耦合），然而,服务组件粒度过细将导致服务编制要求,这会很快导致将微服务架构模式变成一个复杂、容易混淆、代价昂贵并易于出错的重量级面向服务架构。\n\nIf you find you need to orchestrate your service components from within the user interface or API layer of the application, then chances are your service components are too fine-grained. Similarly, if you find you need to perform inter-service communication between service components to process a single request, chances are your service components are either too fine-grained or they are not partitioned correctly from a business functionality standpoint.\n\n如果你发现需要从应用内部的用户接口或API层编排服务组件，那么很有可能你服务组件的粒度太细了。如果你发现你需要在服务组件之间执行服务间通信来处理单个请求,那么很有可能要么是你服务组件的粒度太细了，要么是没有从业务功能角度正确划分服务组件。\n\nInter-service communication, which could force undesired couplings between components, can be handled instead through a shared database. For example, if a service component handing Internet orders needs customer information, it can go to the database to retrieve the necessary data as opposed to invoking functionality within the customer-service component.\n\n服务间通信，可能导致组件之间产生耦合，但可以通过共享数据库进行处理。例如，若一个服务组件处理网络订单而需要用户信息时，它可以去数据库检索必要的数据，而不是调用客户服务组件的功能。\n\nThe shared database can handle information needs, but what about shared functionality? If a service component needs functionality contained within another service component or common to all service components, you can sometimes copy the shared functionality across service components (thereby violating the DRY principle: don’t repeat yourself). This is a fairly common practice in most business applications implementing the microservices architecture pattern, trading off the redundancy of repeating small portions of business logic for the sake of keeping service components independent and separating their deployment. Small utility classes might fall into this category of repeated code.\n\n共享数据库可以处理信息需求，但是共享功能呢？如果一个服务组件需要的功能包含在另一个服务组件内，或是一个公共的功能,那么有时你可以将服务组件的共享功能复制一份（因此违反了DRY规则：don’t repeat yourself）。为了保持服务组件独立和部署分离，微服务架构模式实现中会存在一小部分由重复的业务逻辑而造成的冗余，这在大多数业务应用程序中是一个相当常见的问题。小工具类可能属于这一类重复的代码。\n\nIf you find that regardless of the level of service component granularity you still cannot avoid service-component orchestration, then it’s a good sign that this might not be the right architecture pattern for your application. Because of the distributed nature of this pattern, it is very difficult to maintain a single transactional unit of work across (and between) service components. Such a practice would require some sort of transaction compensation framework for rolling back transactions, which adds significant complexity to this relatively simple and elegant architecture pattern.\n\n如果你发现就算不考虑服务组件粒度的级别，你仍不能避免服务组件编排,这是一个好迹象,可能此架构模式不适用于你的应用。由于这种模式的分布式特性，很难维护服务组件之间的单一工作事务单元。这种做法需要某种事务补偿框架回滚事务,这对此相对简单而优雅的架构模式来说，显著增加了复杂性。\n\n## Considerations 架构考量\nThe microservices architecture pattern solves many of the common issues found in both monolithic applications as well as service-oriented architectures. Since major application components are split up into smaller, separately deployed units, applications built using the microservices architecture pattern are generally more robust, provide better scalability, and can more easily support continuous delivery.\n\n\n微服务架构模式解决了很多单体应用和面向服务架构应用存在的问题。由于主要应用组件被分成更小的,单独部署单元,使用微服务架构模式构建的应用程序通常更健壮,并提供更好的可扩展性,支持持续交付也更容易。\n\nAnother advantage of this pattern is that it provides the capability to do real-time production deployments, thereby significantly reducing the need for the traditional monthly or weekend “big bang” production deployments. Since change is generally isolated to specific service components, only the service components that change need to be deployed. If you only have a single instance of a service component, you can write specialized code in the user interface application to detect an active hot-deployment and redirect users to an error page or waiting page. Alternatively, you can swap multiple instances of a service component in and out during a real-time deployment, allowing for continuous availability during deployment cycles (something that is very difficult to do with the layered architecture pattern).\n\n该模式的另一个优点是,它提供了实时生产部署能力，从而大大减少了传统的月度或周末“大爆炸”生产部署的需求。因为变化通常被隔离成特定的服务组件，只有变化的服务组件才需要部署。如果你的服务组件只有一个实例，你可以在用户界面程序编写专门的代码用于检测一个活跃的热部署,一旦检测到就将用户重定向到一个错误页面或等待页面。你也可以在实时部署期间，将服务组件的多个实例进行交换，允许应用程序在部署期间保持持续可用性（分层架构模式很难做到这点）。\n\nOne final consideration to take into account is that since the microservices architecture pattern is a distributed architecture, it shares some of the same complex issues found in the event-driven architecture pattern, including contract creation, maintenance, and government, remote system availability, and remote access authentication and authorization.\n\n最后一个要重视的考虑是，由于微服务架构模式是分布式的架构，他与事件驱动架构模式具有一些共同的复杂的问题，包括约定的创建、维护，和管理，远程系统的可用性，远程访问身份验证和授权。\n\n## Pattern Analysis 模式分析\nThe following table contains a rating and analysis of the common architecture characteristics for the microservices architecture pattern. The rating for each characteristic is based on the natural tendency for that characteristic as a capability based on a typical implementation of the pattern, as well as what the pattern is generally known for. For a side-by-side comparison of how this pattern relates to other patterns in this report, please refer to Appendix A at the end of this report.\n\n下面这个表中包含了微服务架构模式的特点分析和评级，每个特性的评级是基于自然趋势，基于典型模式实现的能力特性,以及该模式是以什么闻名的。本报告中该模式与其他模式的并排比较，请参考报告最后的附件A。\n\n### Overall agility 整体灵活性\nRating: High     \nAnalysis: Overall agility is the ability to respond quickly to a constantly changing environment. Due to the notion of separately deployed units, change is generally isolated to individual service components, which allows for fast and easy deployment. Also, applications build using this pattern tend to be very loosely coupled, which also helps facilitate change.\n\n评级：高\n\n分析：整体的灵活性是能够快速响应不断变化的环境。由于单独部署单元的概念,变化通常被隔离成单独的服务组件,使得部署变得快而简单。同时，使用这种模式构建的应用往往是松耦合的，也有助于促进改变。\n\n### Ease of deployment 易部署性\nRating: High     \nAnalysis: Overall this pattern is relatively easy to deploy due to the decoupled nature of the event-processor components. The broker topology tends to be easier to deploy than the mediator topology, primarily because the event-mediator component is somewhat tightly coupled to the event processors: a change in an event processor component might also require a change in the event mediator, requiring both to be deployed for any given change.\n\n评级：高\n\n分析：整体来讲，由于该模式的解耦特性和事件处理组件使得部署变得相对简单。broker拓扑往往比mediator拓扑更易于部署，主要是因为event-mediator组件与事件处理器是紧耦合的，事件处理器组件有一个变化可能导致event mediator跟着变化，有任何变化两者都需要部署。\n\n### Testability 可测试性\nRating: High     \nAnalysis: Due to the separation and isolation of business functionality into independent applications, testing can be scoped, allowing for more targeted testing efforts. Regression testing for a particular service component is much easier and more feasible than regression testing for an entire monolithic application. Also, since the service components in this pattern are loosely coupled, there is much less of a chance from a development perspective of making a change that breaks another part of the application, easing the testing burden of having to test the entire application for one small change.\n\n评级：高\n\n分析：由于业务功能被分离成独立的应用模块,可以在局部范围内进行测试，这样测试工作就更有针对性。对一个特定的服务组件进行回归测试比对整个monolithic应用程序进行回归测试更简单、更可行。而且,由于这种模式的服务组件是松散耦合的，从开发角度来看，由一个变化导致应用其他部分也跟着变化的几率很小，并能减小由于一个微小的变化而不得不对整个应用程序进行测试的负担。\n\n### Performance 性能\nRating: Low     \nAnalysis: While you can create applications implemented from this pattern that perform very well, overall this pattern does not naturally lend itself to high-performance applications due to the distributed nature of the microservices architecture pattern.\n\n评级：低\n\n分析：虽然你可以从实现该模式来创建应用程序并可以很好的运行，整体来说，由于微服务架构模式的分布式特性，并不适用于高性能的应用程序。\n\n### Scalability 可扩展性\nRating: High     \nAnalysis: Because the application is split into separately deployed units, each service component can be individually scaled, allowing for fine-tuned scaling of the application. For example, the admin area of a stock-trading application may not need to scale due to the low user volumes for that functionality, but the trade-placement service component may need to scale due to the high throughput needed by most trading applications for this functionality.\n\n评级：高\n\n分析：由于应用程序被分为单独的部署单元,每个服务组件可以单独扩展，并允许对应用程序进行扩展调整。例如，股票交易的管理员功能区域可能不需要扩展，因为使用该功能的用户很少，但是交易布局服务组件可能需要扩展，因为大多数交易应用程序需要具备处理高吞吐量的功能。\n\n### Ease of development 开发容易度\nRating: High     \nAnalysis: Because functionality is isolated into separate and distinct service components, development becomes easier due to the smaller and isolated scope. There is much less chance a developer will make a change in one service component that would affect other service components, thereby reducing the coordination needed among developers or development teams.\n\n评级：高\n\n分析：由于功能被分隔成不同的服务组件，由于开发范围更小且被隔离，开发变得更简单。程序员在一个服务组件做出一个变化影响其他服务组件的几率是很小的，从而减少开发人员或开发团队之间的协调。\n"
  },
  {
    "path": "software-architecture-patterns/readme.md",
    "content": "软件架构模式\n-------\n\n原书名[《Software Architecture Patterns》](http://www.oreilly.com/programming/free/software-architecture-patterns.csp) .\n\n* [中文版百度云盘下载](http://pan.baidu.com/s/1sjAz23r)\n* [中文版github下载地址](https://raw.githubusercontent.com/hehonghui/android-tech-frontier/master/software-architecture-patterns/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F.pdf)\n\n## 打赏 - JUST FOR FUN\n\n> * 一杯咖啡钱, 打赏金额随意，感谢大家~ :)\n\n|   支付宝   |   微信    |\n|------------|-----------|\n|<img src=\"https://img-blog.csdnimg.cn/20200412132734488.JPG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Jib3lmZWl5dQ==,size_16,color_FFFFFF,t_70\" width=\"200\"/>| <img src=\"https://img-blog.csdnimg.cn/20200911174255577.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Jib3lmZWl5dQ==,size_16,color_FFFFFF,t_70\" width=\"200\"/>  |\n\n\n## 译员列表\n该书总共五章，译员可以领其中一章或者多个章节，截止日期 2015/4/19，感谢大家的参与。可参考资料 : [软件架构模式读书笔记](http://blog.csdn.net/bboyfeiyu/article/details/44977219) 。\n\n|  \t\t\t翻译章节\t\t\t |   \t\t译员信息 \t |\n|--------------------------|-------------------|\n|  [第一章 分层架构](chapter01-BillonWang.md) | [BillionWang](https://github.com/BillionWang)  |\n|  [第二章 事件驱动架构](chapter02-chaossss.md) | [chaossss](https://github.com/chaossss)  |\n|  [第三章 微内核架构](chapter03-Mr.Simple.md) | [Mr.Simple](https://github.com/bboyfeiyu)  |\n|  [第四章 微服务架构](chapter04-dupengwei.md) | [dupengwei](https://github.com/dupengwei)  |\n|  [第五章 基于空间的架构](chap-5.md) | [allenlsy](https://allenlsy.com)  |\n\n\n"
  },
  {
    "path": "software-architecture-patterns/软件架构模式.md",
    "content": "![cover](images/cover.png)\n\n\n软件架构模式  Mark Richards 著      \n版权归 © 2015 O’Reilly Media, Inc. 所有.      \n\n原书发布链接为[Software Architecture Patterns](http://www.oreilly.com/programming/free/software-architecture-patterns.csp).\n\n**译员信息**\n\n本书的译员均来自 开发技术前线 [www.devtf.cn](http://www.devtf.cn)。   \n\n|  译者  |  个人简介  | \n|--------|----------|\n|  [Mr.Simple](https://github.com/bboyfeiyu)   | 乐于分享，热爱开源的工程师，[个人博客](http://blog.csdn.net/bboyfeiyu) |  \n|  [chaossss](https://github.com/chaossss)   | 追风筝的吃货，汪～。[个人博客](http://blog.csdn.net/u012403246/) | \n|  [Allenlsy](https://github.com/allenlsy)   | 计算机科学爱好者，Rails程序员。[个人博客](allenlsy.com) | \n|  [BillonWang](https://github.com/BillonWang)   | 做好玩的事情，交好玩的朋友。[个人博客](www.woaichirou.com) | \n|  [dupengwei](https://github.com/dupengwei)   | 乐于分享的移动互联网开发工程师 | \n|  [Charli Hu](https://github.com/ht1348092)   | 喜欢英语，不放弃编程的菇凉。| \n\n\n# 目录\n* [简介](#introdution) \n* [第一章 分层架构](#chapter1)\n* [第二章 事件驱动架构](#chapter2)\n* [第三章 微内核架构](#chapter3)\n* [第四章 微服务架构](#chapter4)\n* [第五章 基于空间的架构](#chapter5)\n* [附录A](#appendix)\n* [关于作者](#author)\n\n\n<b id=\"introdution\"></b>\n## 简介\n对程序员来说很常见一种情况是在没有合理的程序架构时就开始编程，没有一个清晰的和定义好的架构的时候，大多数开发者和架构师通常会使用标准式的传统分层架构模式（也被称为多层架构）——通过将源码模块分割为几个不同的层到不同的包中。不幸的是，这种编码方式会导致一系列没有组织性的代码模块，这些模块缺乏明确的规则、职责和同其他模块之间的关联。这通常被称为架构大泥球。  \n\n应用程序缺乏合理的架构一般会导致程序过度耦合、容易被破坏、难以应对变化，同时很难有一个清晰的版本或者方向性。这样的结果是，如果你没有充分理解程序系统里每个组件和模块，就很难定义这个程序的结构特征。有关于程序的部署和维护的基本问题都难以回答，比如：程序架构是什么规模?应用程序有什么性能特点?应用程序有多容易应对变化?应用程序的部署特点是什么?架构是如何反应的?\n\n架构模式帮助你定义应用程序的基本特征和行为。例如，一些架构模式会让程序自己自然而然地朝着具有良好伸缩性的方向发展，而其他架构模式会让程序朝着高度灵活的方向发展。知道了这些特点，了解架构模式的优点和缺点是非常必要的，它帮助我们选择一个适合自己特定的业务需求和目标的的程序。\n    \n作为一个架构师,你必须证明你的架构模式的决策是正确的,特别是当需要选择一个特定的体系结构模式或方法的时候。这本迷你书的目的就是给你足够的信息让你去做出正确的架构决策。\n\n<b id=\"chapter1\"></b>\n## 第一章 分层架构\n\n分层架构是一种很常见的架构模式，它也叫N层架构。这种架构是大多数Jave EE应用的实际标准，因此很多的架构师，设计师，还有程序员都知道它。许多传统IT公司的组织架构和分层模式十分的相似。所以它很自然的成为大多数应用的架构模式。\n\n### 模式分析\n\n分层架构模式里的组件被分成几个平行的层次，每一层都代表了应用的一个功能(展示逻辑或者业务逻辑)。尽管分层架构没有规定自身要分成几层几种，大多数的结构都分成四个层次:展示层，业务层，持久层，和数据库层。如表1-1，有时候，业务层和持久层会合并成单独的一个业务层，尤其是持久层的逻辑绑定在业务层的组件当中。因此，有一些小的应用可能只有3层，一些有着更复杂的业务的大应用可能有5层或者更多的分层。\n\n分层架构中的每一层都着特定的角色和职能。举个例子，展示层负责处理所有的界面展示以及交互逻辑，业务层负责处理请求对应的业务。架构里的层次是具体工作的高度抽象，它们都是为了实现某种特定的业务请求。比如说展示层并不需要关心怎样得到用户数据，它只需在屏幕上以特定的格式展示信息。业务层并不关心要展示在屏幕上的用户数据格式，也不关心这些用户数据从哪里来。它只需要从持久层得到数据，执行与数据有关的相应业务逻辑，然后把这些信息传递给展示层。\n\n![1-1](images/1-1.png)    \n\n分层架构的一个突出特性是组件间关注点分离 (separation of concerns)。一个层中的组件只会处理本层的逻辑。比如说，展示层的组件只会处理展示逻辑，业务层中的组件只会去处理业务逻辑。多亏了组件分离，让我们更容易构造有效的角色和强力的模型。这样应用变的更好开发，测试，管理和维护。\n\n### 关键概念\n\n注意表1-2中每一层都是封闭的。这是分层架构中非常重要的特点。这意味request必须一层一层的传递。举个例子，从展示层传递来的请求首先会传递到业务层，然后传递到持久层，最后才传递到数据层。\n\n![1-2](images/1-2.png)     \n\n那么为什么不允许展示层直接访问数据层呢。如果只是获得以及读取数据，展示层直接访问数据层，比穿过一层一层来得到数据来的快多了。这涉及到一个概念:层隔离。\n\n层隔离就是说架构中的某一层的改变不会影响到其他层:这些变化的影响范围限于当前层次。如果展示层能够直接访问持久层了，假如持久层中的SQL变化了，这对业务层和展示层都有一定的影响。这只会让应用变得紧耦合，组件之间互相依赖。这种架构会非常的难以维护。\n\n从另外一个方面来说，分层隔离使得层与层之间都是相互独立的，架构中的每一层的互相了解都很少。为了说明这个概念的牛逼之处，想象一个超级重构，把展示层从JSP换成JSF。假设展示层和业务层的之间的联系保持一致，业务层不会受到重构的影响，它和展示层所使用的界面架构完全独立。\n\n然而封闭的架构层次也有不便之处，有时候也应该开放某一层。如果想往包含了一些由业务层的组件调用的普通服务组件的架构中添加一个分享服务层。在这个例子里，新建一个服务层通常是一个好主意，因为从架构上来说，它限制了分享服务访问业务层(也不允许访问展示层)。如果没有隔离层，就没有任何架构来限制展示层访问普通服务，难以进行权限管理。\n\n在这个例子中，新的服务层是处于业务层之下的，展示层不能直接访问这个服务层中的组件。但是现在业务层还要通过服务层才能访问到持久层，这一点也不合理。这是分层架构中的老问题了，解决的办法是开放某些层。如表1-3所示，服务层现在是开放的了。请求可以绕过这一层，直接访问这一层下面的层。既然服务层是开放的，业务层可以绕过服务层，直接访问数据持久层。这样就非常合理。\n\n![1-3](images/1-3.png)     \n\n开放和封闭层的概念确定了架构层和请求流之间的关系，并且给设计师和开发人员提供了必要的信息理解架构里各种层之间的访问限制。如果随意的开放或者封闭架构里的层，整个项目可能都是紧耦合，一团糟的。以后也难以测试，维护和部署。\n\n### 示例\n\n为了演示分层架构是如何工作的，想象一个场景，如表1-4，用户发出了一个请求要获得客户的信息。黑色的箭头是从数据库中获得用户数据的请求流，红色箭头显示用户数据的返回流的方向。在这个例子中，用户信息由客户数据和订单数组组成(客户下的订单)。\n\n用户界面只管接受请求以及显示客户信息。它不管怎么得到数据的，或者说得到这些数据要用到哪些数据表。如果用户界面接到了一个查询客户信息的请求，它就会转发这个请求给用户委托(Customer Delegate)模块。这个模块能找到业务层里对应的模块处理对应数据(约束关系)。业务层里的customer object聚合了业务请求需要的所有信息(在这个例子里获取客户信息)。这个模块调用持久层中的 customer dao 来得到客户信息，调用order dao来得到订单信息。这些模块会执行SQL语句，然后返回相应的数据给业务层。当 customer object收到数据以后，它就会聚合这些数据然后传递给 customer delegate,然后传递这些数据到customer screen 展示在用户面前。\n\n![1-4](images/1-4.png)     \n\n从技术的角度来说，有很多的方式能够实现这些模块。比如说在Java平台中，customer screen 对应的是 (JSF) Java Server Faces ,用 bean 组件来实现 customer delegate。用本地的Spring bean或者远程的EJB3 bean 来实现业务层中的customer object。上例中的数据访问可以用简单的POJP's(Plain Old Java Objects)，或者可以用MyBatis，还可以用JDBC或者Hibernate 查询。Microsoft平台上，customer screen能用 .NET 库的ASP模块来访问业务层中的C#模块，用ADO来实现用户和订单数据的访问模块。\n\n### 注意事项\n\n分层架构是一个很可靠的架构模式。它适合大多数的应用。如果你不确定在项目中使用什么架构，分层架构是再好不过的了。然后，从架构的角度上来说，选择这个模式还要考虑很多的东西。\n\n第一个要注意的就是 污水池反模式(architecture sinkhole anti-pattern)。\n在这个模式中，请求流只是简单的穿过层次，不留一点云彩，或者说只留下一阵青烟。比如说界面层响应了一个获得数据的请求。响应层把这个请求传递给了业务层，业务层也只是传递了这个请求到持久层，持久层对数据库做简单的SQL查询获得用户的数据。这个数据按照原理返回，不会有任何的二次处理，返回到界面上。\n\n每个分层架构或多或少都可能遇到这种场景。关键在于这样的请求有多少。80-20原则可以帮助你确定架构是否处于反污水模式。大概有百分之二十的请求仅仅是做简单的穿越，百分之八十的请求会做一些业务逻辑操作。然而，如果这个比例反过来，大部分的请求都是仅仅穿过层，不做逻辑操作。那么开放一些架构层会比较好。不过由于缺少了层次隔离，项目会变得难以控制。\n\n### 模式分析\n\n下面的的表里分析了分层架构的各个方面。\n\n##### 整体灵活性\n评级:低      \n分析:总体灵活性是响应环境变化的能力。尽管分层模式中的变化可以隔绝起来，想在这种架构中做一些也改变也是并且费时费力的。分层模式的笨重以及经常出现的组件之间的紧耦合是导致灵活性降低的原因。\n\n#### 易于部署\n评级:低     \n分析:这取决于你怎么发布这种模式，发布程序可能比较麻烦，尤其是很大的项目。一个组件的小小改动可能会影响到整个程序的发布(或者程序的大部分)。发布必须是按照计划，在非工作时间或者周末进行发布。因此。分层模式导致应用发布一点也不流畅，在发布上降低了灵活性。\n\n#### 可测试性\n评级:高      \n分析:因为组件都处于各自的层次中，可以模拟其他的层，或者说直接去掉层，所以分层模式很容易测试。开发者可以单独模拟一个展示组件，对业务组件进行隔绝测试。还可以模拟业务层来测试某个展示功能。\n\n#### 性能\n评级:低       \n分析:尽管某些分层架构的性能表现的确不错，但是这个模式的特点导致它无法带来高性能。因为一次业务请求要穿越所有的架构层，做了很多不必要的工作。\n\n#### 伸缩性\n评级:低     \n分析:由于这种模式以紧密耦合的趋势在发展，规模也比较大，用分层架构构建的程序都比较难以扩展。你可以把各个层分成单独的物理模块或者干脆把整个程序分成多个节点来扩展分层架构，但是总体的关系过于紧密，这样很难扩展。\n\n#### 易开发性\n评级:容易     \n分析:在开发难度上面，分层架构得到了比较高的分数。因为这种架构对大家来说很熟悉，不难实现。大部分公司在开发项目的都是通过层来区分技术的，这种模式对于大多数的商业项目开发来说都很合适。公司的组织架构和他们软件架构之间的联系被戏称为\"Conway's law\"。你可以Google一下查查这个有趣的联系。\n\n\n\n<b id=\"chapter2\"></b>\n## 第二章 事件驱动架构\n\n> 译者注：文章中 mediator 及 broker 的概念很容易混淆，在文章的结尾处译者对两者的区别（还有 proxy）进行了一定的阐述\n\n事件驱动架构模式是一种主流的异步分发事件架构模式，常用于设计高度可拓展的应用。当然了，它有很高的适应性，使得它在小型应用、大型应用、复杂应用中都能表现得很好。事件驱动架构模式由高度解耦、单一目的的事件处理组件构成，这些组件负责异步接收和处理事件。\n\n事件驱动架构模式包含了两种主要的拓扑结构：**中介(mediator)**拓扑结构和**代理(broker)**拓扑结构。 mediator 拓扑结构通常在你需要在事件内使用一个核心中介分配、协调多个步骤间的关系、执行顺序时使用；而代理拓扑结构则在你想要不通过一个核心中介将多个事件串联在一起时使用。由于这两种结构在结构特征和实现策略上有很大的差别，所以如果你想要在你的应用中使用它们的话，一定要深入理解两者的技术实现细节，从而为你的实际使用场景选择最合理的结构。\n\n### 中介 ( Mediator )拓扑结构\n\n中介拓扑结构适合用于拥有多个步骤，并需要在处理事件时能通过某种程度的协调将事件分层的场景，举例来说吧：假设你现在需要进行股票交易，那你首先需要证券所批准你进行交易，然后检查进行这次交易是否违反了股票交易的某种规定，检查完成后将它交给一个经纪人，计算佣金，最后与经纪人确认交易。以上所有步骤都需要通过中介进行某种程度的分配和协调，以决定各个步骤的执行顺序，判断哪些步骤可以并行，哪些步骤可以串行。\n\n在中介拓扑结构中主要有四种组件：事件队列（event queue）, 事件中介, 事件通道（event channel）, 和 事件处理器（event processor）。当事件流需要被处理，客户端将一个事件发送到某个事件队列中，由消息队列将其运输给事件中介进行处理和分发。事件中介接收到该消息后，并通过将额外的异步事件发送给事件通道，让事件通道执行该异步事件中的每一个步骤，使得事件中介能够对事件进行分配、协调。同时，又因为事件处理器是事件通道的监听器，所以事件通道对异步事件的处理会触发事件处理器的监听事件，使事件处理器能够接收来自事件中介的事件，执行事件中具体的业务逻辑，从而完成对传入事件的处理。事件驱动架构模式中的中介拓扑模式结构大体如下图：\n\n![2-1](images/2-1.png)\n\n在事件驱动架构中拥有十几个，甚至几百个事件队列是很常见的情况，该模式并没有对事件队列的实现有明确的要求，这就意味着事件队列可以是消息队列，Web 服务端，或者其它类似的东西。\n\n在事件驱动架构模式中主要有两种事件：初始事件和待处理事件。初始事件是中介所接收到的最原始的事件，没有经过其他组件的处理；而待处理事件是由事件中介生成，由事件处理器接收的组件，不能把待处理事件看作初始事件经过处理后得到的事件，两者是完全不同的概念。\n\n事件中介负责分配、协调初始事件中的各个待执行步骤，事件中介需要为每一个初始事件中的步骤发送一个特定的待处理事件到事件通道中，触发事件处理器接收和处理该待处理事件。这里需要注意的是：事件 中介没有真正参与到对初始事件必须处理的业务逻辑的实现之中；相反，事件中介只是知道初始事件中有哪些步骤需要被处理。\n\n事件中介通过事件通道将与初始事件每一个执行步骤相关联的特定待处理事件传递给事件处理器。尽管我们通常在待处理事件能被多个事件处理器处理时才会在中介拓扑结构中使用 消息主题，但事件通道仍可以是消息队列或 消息主题。（但需要注意的是，尽管在使用 消息主题 时待处理事件能被多个事件处理器处理，但由于接收到的待处理事件各异，所以对其处理的操作也各不相同）\n\n为了能顺利处理待处理事件，事件处理器组件中包含了应用的业务逻辑。此外，事件处理器作为事件驱动架构中的组件，不依赖于其他组件，独立运作，高度解耦，在应用或系统中完成特定的任务。当事件处理器需要处理的事件从细粒度（例如：计算订单的营业税）变为粗粒度（例如：处理一项保险索赔事务），必须要注意的是：一般来说，每一个事件处理器组件都只完成一项唯一的业务工作，并且事件处理器在完成其特定的业务工作时不能依赖其他事件处理器。\n\n虽然事件中介有许多方法可以实现，但作为一名架构工程师，你应该了解所有实现方式，以确保你能为你的实际需求选择了最合适的事件中介。\n\n事件中介最简单、常见的实现就是使用开源框架，例如：Spring Integration，Apache Camel，或 Mule ESB。事件流在这些开源框架中通常用 Java 或 域特定语言（domain-specific language）。在调节过程和业务流程都很复杂的使用场景下，你可以使用业务流程执行语言（BPEL - business process execution language）结合类似开源框架 Apache ODE 的 BPEL 引擎进行开发。BPEL 是一种基于 XML 的服务编制编程语言，它为处理初始事件时需要描述的数据和步骤提供了描述。对每一个拥有复杂业务流程（包括与用户交互的执行步骤）的大型应用来说，你可以使用类似 jBPM 的业务处理管理系统（business process manager）实现事件中介。\n\n如果你需要使用中介拓扑结构，那么理解你的需求，并为其匹配恰当的事件中介实现是构建事件驱动架构过程中至关重要的一环。使用开源框架去解决非常复杂的业务处理、管理、调节事件，注定会失败，因为开源框架只是用 BPM 的方式解决了一些简单的事件分发逻辑，比起你的业务逻辑，其中的事件分发逻辑简直是九牛一毛。\n\n为了解释清楚中介拓扑结构是怎么运作的，我假设你在某家保险公司买了保险，成为了受保人，然后你打算搬家。在这种情况下，初始事件就是重定位事件，或者其他类似的事件。与重定位事件相关的处理步骤就像下图展示的那样，处于事件中介之中。对每一个初始事件的传入，事件中介都会创建一个待处理事件（例如：改变地址，重新计算保险报价，等等……），并将它发送给事件通道，等待发出响应的事件处理器处理待处理事件（例如：客户改变地址的操作流程、报价计算流程，等等……）。直到初始事件中的每一个需要处理的步骤完成了，这项处理才会继续（例如：把所有手续都完成之后，保险公司才会帮你改变地址）。事件中介中，重新报价和更新理赔步骤上面的直线表示这些步骤可以并行处理。\n\n![2-1](images/2-2.png)\n\n### 代理 (Broker) 拓扑结构\n\n代理拓扑结构与中介拓扑结构不同之处在于：代理拓扑结构中没有核心的事件中介；相反，事件流在代理拓扑结构中通过一个轻量的消息代理（例如：ActiveMQ, HornetQ，等等……）将消息串联成链状，分发至事件处理器组件中进行处理。代理扑结构适用的使用场景大致上具有以下特征：你的事件处理流相对来说比较简单，而且你不想（不需要）使用核心的事件分配、调节机制以提高你处理事件的效率。\n\n在代理拓扑结构中主要包括两种组件：代理和事件处理器。代理可被集中或相互关联在一起使用，此外，代理中还可以包含所有事件流中使用的事件通道。\n\n存在于代理组件中的事件通道可以是消息队列，消息主题,或者是两者的组合。\n\n代理拓扑结构大致如下图，如你所见，在这其中没有一个核心的事件中介组件控制和分发初始事件；相反，每一个事件处理器只负责处理一个事件，并向外发送一个事件，以标明其刚刚执行的动作。例如，假设存在一个事件处理器用于平衡证券交易，那么事件处理器可能会接受一个拆分股票的初始事件，为了处理这项初始事件，事件处理器则需要重新平衡股票的投资金额，而这个重新平衡的事件将由另一个事件处理器接收、处理。在这其中有一个细节需要注意：处理初始事件后，由事件处理器发出的事件不被其他事件处理器接收、处理的情况时常会发生，尤其是你在为应用添加功能和进行功能拓展时，这种情况更为常见。\n\n![2-3](images/2-3.png)\n\n为了阐明代理拓扑结构的运行机制，我会用一个与讲解中介拓扑结构时类似的例子（受保人旅行的例子）进行解释。因为在代理拓扑结构中没有核心事件中介接收初始事件，那么事件将由客户处理组件直接接收，改变客户的地址，并发出一个事件告知系统客户的地址被其进行了改变（例如：改变地址的事件）。在这个例子中：有两个事件处理器会与改变地址的事件产生关联：报价处理和索赔处理。报价事件处理器将根据受保人的新地址重新计算保险的金额，并发出事件告知系统该受保人的保险金额被其改变。而索赔事件处理器将接受到相同的改变地址事件，不同的是，它将更新保险的赔偿金额，并发出一个更新索赔金额事件告知系统该受保人的赔偿金额被其改变。当这些新的事件被其他事件处理器接收、处理，使事件链一环扣一环地交由系统处理，直到事件链上的所有事件都被处理完，初始事件的处理才算完成。\n\n![2-4](images/2-4.png)    \n\n如上图所示，代理拓扑结构的设计思想就是将对事件流的处理转换为对事件链的业务功能处理，把代理拓扑结构看作是接力比赛是最好的理解方式：在一场4*100的接力比赛中，每一位运动员都需要拿着一根接力棒跑100米，运动员跑完自己的100米后需要将接力棒传递给下一位运动员，直到最后一位运动员拿着接力棒跑过终点线，整场接力比赛才算结束。根据这样的逻辑我们还可以知道：在代理拓扑结构中，一旦某个事件处理器将事件传递给另一个事件处理器，那么这个事件处理器不会与该事件的后续处理产生任何联系。\n\n### 顾虑\n\n实现事件驱动架构模式相对于实现其他架构模式会更困难一些，因为它通过异步处理进行事件分发。当你需要在你的应用中使用这种架构模式，你必须处理各种由事件分发处理带来的问题，例如：远程操作功能的可用性，缺少权限，以及在代理或中介中处理事件失败时，用于处理这种情况的重连逻辑。如果你不能很好地解决这些问题，那你的应用一定会出现各种 Bug，让开发团队痛苦不已。\n\n在选择事件驱动架构时还有一点需要注意：在处理单个业务逻辑时，这种架构模式不能处理细粒度的事务。因为事件处理器都高度解耦、并且广泛分布，这使得在这些事件处理器中维持一个业务单元变得非常困难。因此，当你使用这种架构模式架构你的应用时，你必须不断地考虑哪些事件能单独被处理，哪些不能，并为此设计相应事件处理器的处理粒度。如果你发现你需要将一个业务单元切割成许多子单元，并一一匹配相应的事件处理器，那你就要为此进行代码设计；如果你发现你用多个不同的事件处理器处理的哪些业务其实是可以合并到一个业务事件之中的，那么这种模式可能并不适合你的应用，又或者是你的设计出了问题。\n\n使用事件驱动架构模式最困难的地方就在于架构的创建、维护、以及对事件处理器的管理。通常每一个事件都拥有其指定的事件处理协议（例如：传递给事件处理器的数据类型、数据格式），这就使得设下标准的数据格式成为使用事件驱动架构模式中至关重要的一环（例如：XML，JSON，Java 对象，等等……），并在架构创建之初就为这些数据格式授权，以便处理。\n\n### 模式分析\n\n下面是基于对常见的架构模式特征进行评价的标准，对事件驱动架构模式所作的实际分析，评价是以常见的架构模式的相似实现作为标准进行的，如果你想知道进行对比的其他架构模式对应的特征，可以结尾处查看  [附录A](#appendix) 的汇总表。 \n\n#### 整体灵活性\n评价：高        \n分析：整体灵活性用于评价架构能否在不断改变的使用场景下快速响应，因为事件处理器组件使用目的单一、高度解耦、与其他事件处理器组件相互独立，不相关联，那么发生的改变对一个或多个事件处理器来说普遍都是独立的，使得对改变的反馈非常迅速，不需要依赖其他事件处理器的响应作出处理。\n\n#### 易于部署\n评价：高       \n分析：总的来看，事件驱动架构模式由于其高度解耦的事件处理器组件的存在，对事件的部署相对来说比较容易，而使用代理拓扑结构比使用中介拓扑结构进行事件调度会更容易一些，主要是因为在 中介拓扑结构中事件处理器与事件中介紧密地耦合在一起：事件处理器中发生改变后，事件中介也随之改变，如果我们需要改变某个被处理的事件，那么我们需要同时调度事件处理器和事件中介。\n\n#### 可测试性\n评价：低       \n分析：虽然在事件驱动架构模式中进行单元测试并不困难，但如果我们要进行单元测试，我们就需要某种特定的测试客户端或者是测试工具产生事件，为单元测试提供初始值。此外，由于事件驱动架构模式是异步进行事件分发的，其异步处理的特性也为单元测试带来了一定的困难。\n\n#### Performance 性能\n评价：高       \n分析：对消息传递的架构可能会让设计出来的事件驱动架构的表现不如我们的期望，但通常来说，该模式都能通过其异步处理的特性展示优秀的性能表现；换句话来说，高度解耦，异步并行操作大大减少了传递消息过程中带来的时间开销。\n\n#### 伸缩性\n评价：高       \n分析：事件驱动架构中的高度解耦、相互独立的事件处理器组件的存在，使得可拓展性成为该架构与生俱来的优点。架构的这些特定使得事件处理器能够进行细粒度的拓展，使得每一个事件处理器都能单独被拓展，而不影响其他事件处理器。\n\n#### 易于开发\n评价：低     \n分析：由于使用事件驱动架构进行开发需要考虑其异步处理机制、协议创建流程，并且开发者需要用代码为事件处理器和操作失败的代理提供优秀的错误控制环境，无疑使得用事件驱动架构进行开发会比使用其他架构进行开发要困难一些。\n\n### 译者注\n\n读完整篇文章，我相信大家对 mediator 与 broker 这两个概念有一个大致的印象，但就两者的译文来看，中介和代理似乎没什么区别，尤其是了解 proxy 的读者会更加困惑，这三者之间到底是什么关系？它们的概念是互通的吗？为了解决这种混淆，译者将在此阐述三者间的区别：\n\n假如现在我有一个事件/事件流需要被处理，那么使用 mediator、broker、proxy 处理事件的区别在哪里呢？\n\n- 如果我们使用 mediator，那就意味着我将把事件流交给 mediator，mediator 会帮我把事件分解为多个步骤，并分析其中的执行逻辑，调整和分发事件（例如判断哪些事件可以并行，哪些事件可以串行），然后根据 mediator 分解、调节的结果去执行事件中的每一个步骤，把所有步骤完成后，就能把需要处理的事件处理好。\n\n- 如果我们使用 broker，那就意味着我将把事件交给 broker，broker 获得事件后会把事件发出去（在本文中为：通知架构中所有可用的事件处理器），事件处理器们接收到事件以后，判断处理这个事件是否为自己的职责之一，如果不是则无视，与自己有关则把需要完成的工作完成，完成后如果事件还有后续需要处理的事件，则通过 broker 再次发布，再由相关的事件处理器接收、处理。以这样的方式将事件不断分解，沿着事件链一级一级地向下处理子事件，直到事件链中的所有事件被完成，我的事件也就处理好了。\n\n- 如果我们使用 proxy，那就意味着我自己对需要处理的事件进行了分解，然后把不同的子事件一一委托给不同的 proxy，由被委托的 proxy 帮我完成子事件，从而完成我要做的事件。\n\n\n<b id=\"chapter3\"></b>\n## 第三章 微内核架构\n\n微内核架构模式(也称为插件化应用架构)对于基于产品的应用程序来说是一个很自然的选择。基于产品的应用是指一个经过打包的、可以通过版本下载的一个典型的第三方产品。然而，很多公司也会开发和发布他们的内部商业软件，完整的版本号、发布日志和可插拔的新特性，这些就非常符合微内核架构的思想。微内核架构模式可以通过插件的形式添加额外的特性到核心系统中，这提供了很好的扩展性，也使得新特性与核心系统隔离开来。( 译者注: 比如，著名的Eclipse IDE就是基于插件化开发的，eclipse核心更像是一个微内核，或者我们可把它叫做开放平台，其他的功能通过安装插件的形式添加到eclipse中。 )\n\n### 模式描述\n\n微内核架构主要需要考虑两个方面: 核心系统和插件模块。应用逻辑被划分为独立的插件模块和核心系统，这样就提供良好的可扩展性、灵活性，应用的新特性和自定义处理逻辑也会被隔离。图3-1演示了基本的微内核架构。\n\n微内核架构的核心系统一般情况下只包含一个能够使系统运作起来的最小化模块。很多操作系统的实现就是使用微内核架构，因此这也是该架构名字的由来。从商业应用的角度看，核心系统通常是为特定的使用场景、规则、或者复杂条件处理定义了通用的业务逻辑，而插件模块根据这些规则实现了具体的业务逻辑。\n\n![3-1](images/3-1.png)\n\n插件模块是一个包含专业处理、额外特性的独立组件，自定义代码意味着增加或者扩展核心系统以达到产生附加的业务逻辑的能力。通常，插件模块之间应该是没有任何依赖性的，但是你也可以设计一个需要依赖另一个插件的插件。但无论如何，使得插件之间可以通信的同时避免插件之间产生依赖又是一个特别重要的问题。\n\n核心系统需要了解插件模块的可用性以及如何获取到它们。一个通用的实现方法是通过一组插件注册表。这个插件注册表含有每个插件模块的信息，包括它的名字、数据规约和远程访问协议(取决于插件如何与核心系统建立连接)。例如，一个税务软件的用于标识高风险的税务审计插件可能会有一个含有插件名(比如AuditChecker)的注册入口，数据规约(输入数据、输出数据)和规约格式( 比如xml )。如果这个插件是通过SOAP服务访问，那么它可能会包含一个WSDL (Web Services Definition Language).\n\n插件模块可以通过多种方式连接到核心系统，包括OSGi ( open service gateway initiative )、消息机制、web服务或者直接点对点的绑定 ( 比如对象实例化，即依赖注入 )。你使用的连接类型取决于你构建的应用类型和你的特殊需求（比如单机部署还是分布式部署）。微内核架构本身没有指定任何的实现方式，唯一的规定就是插件模块之间不要产生依赖。\n\n插件和核心系统的通信规范包含标准规范和自定义规范。自定义规范典型的使用场景是插件组件是被第三方构建的。在这种情况下，通常是在第三方插件规约和你的标准规范创建一个Adapter来使核心系统根本不需要知道每个插件的具体细节。当创建标准规范 ( 通常是通过XML或者Java Map )时，从一开始就创建一个版本策略是非常重要的。 \n\n### 架构示例\n\n也许微内核架构的最好示例就是大家熟知的Eclipse IDE了。下载最基本的Eclipse后，它只能提供一个编辑器。然后，一旦你开始添加插件，它就变成一个高度可定制化和非常有用的产品（译者注 : 更多内容大家可以参考 [开源软件架构 卷1：第6章 Eclipse之一](http://www.ituring.com.cn/article/6817) ）。浏览器是另一个使用微内核架构的产品示例，它由一个查看器和其他扩展的插件组成。\n\n基于微内核架构的示例数不胜数，但是大型的商业应用呢？微内核应用架构也适用于这些情形。为了阐述这个观点，让我们来看看另一个保险公司的示例，但是这次的示例会涉及保险赔偿处理。  \n\n赔偿处理是一个非常复杂的过程。每个州都有不同的关于保险赔偿的规则和条文。例如一些州允许在你的挡风玻璃被石头砸碎时免费进行替换，但是一些州则不是这样。因为大家的标准都不一样，因此赔偿标准几乎可以是无限的。\n\n有很多保险赔偿应用运用大型和复杂的规则处理引擎来处理不同规则带来的复杂性。然而，可能会因为某条规则的改变而引起其他规则的改变而使得这些规则处理引擎变成一个大泥球，或者使简单需求变更会需要一个很大的分析师、工程师、测试工程师来进行处理。使用微内核架构能够很好的解决这个问题，核心系统只知道根据赔偿规则处理，但这个赔偿规则是抽象的，系统将赔偿规则作为一个插件规范，具体的规则有对应的实现，然后注入到系统中即可。\n\n图3-2中的一堆文件夹代表了赔偿处理核心系统。它包含一些处理保险赔偿的基本业务逻辑。每一个插件模块包含每个州的具体赔偿规则。在这个例子中，插件模块通过自定义源代码实现或者分离规则引起实例。不管具体实现如何，关键就在于赔偿规则和处理都从核心系统中分离，而这些规则和处理过程都可以被动态地添加、移除，而这些改变对于核心系统和其他插件只有很小的影响或者根本不产生影响。\n\n![3-2](images/3-2.png)\n\n### 注意事项\n\n对于微内核架构来说一个很重要的一点就是它能够被嵌入或者说作为另一种架构的一部分。例如，如果这个架构解决的是一个你应用中易变领域的特定的问题 ( 译者注 : 即插件化能够解决你应用中的某个特定模块的架构问题 )，你可能会发现你不能在整个应用中使用这种架构。在这种情况下，你可以将微内核架构嵌入到另一个架构模式中 ( 比如分层架构 )。同样的，在上一章节中描述的事件驱动架构中的事件处理器组件也可以使用微内核架构。\n\n微内核架构对渐进式设计和增量开发提供了非常好的支持。你可以先构建一个单纯的核心系统，随着应用的演进，系统会逐渐添加越来越多的特性和功能，而这并不会引起核心系统的重大变化。\n\n对基于产品的应用来说，微内核架构应该是你的第一选择。特别是那些你会在后续开发中发布附加特性和控制哪些用户能够获取哪些特性的应用。如果你在后续开发中发现这个架构不能满足你的需求了，你能够根据你的特殊需求将你的应用重构为另一个更好的架构。\n\n### 模式分析\n\n下面的表格中包含了微内核架构每个特性的评级和分析。以微内核架构的最经典的实现方式的自然趋势为依据对每个特性进行评级。关于微内核架构与其他模式的相关性比较请参考附录A。\n\n#### 整体灵活性\n评级 : 高     \n分析 : 整体灵活性是指能够快速适应不断变化的环境的能力。通过插件模块的松耦合实现，可以将变化隔离起来，并且快速满足需求。通常，微内核架构的核心系统很快趋于稳定，这样系统就变得很健壮，随着时间的推移它也不会发生多大改变。\n\n#### 易于部署\n评级 : 高       \n分析 : 根据实现方式，插件模块能够在运行时被动态地添加到核心系统中 （ 比如，热部署 ）,把停机时间减到最小。\n\n#### 可测试性\n评级 : 高           \n分析 : 插件模块能够被独立的测试，能够非常简单地被核心系统模拟出来进行演示，或者在对核心系统很小影响甚至没有影响的情况下对一个特定的特性进行原型展示。\n\n#### 性能\n评级 : 高          \n分析 : 使用微内核架构不会自然而然地使你的应用变得高性能。通常，很多使用微内核架构的应用运行得很好，因为你能定制和简化应用程序，使它只包含那些你需要的功能模块。JBoss应用服务器就是这方面的优秀示例: 依赖于它的插件化架构，你可以只加载你需要的功能模块，移除那些消耗资源但没有使用的功能特性，比如远程访问，消息传递，消耗内存、CPU的缓存，以及线程，从而减小应用服务器的资源消耗。\n\n#### 伸缩性\n评级 : 低          \n分析 : 因为微内核架构的实现是基于产品的，它通常都比较小。它们以独立单元的形式实现，因此没有太高的伸缩性。此时，伸缩性就取决于你的插件模块，有时你可以在插件级别上提供可伸缩性，但是总的来说这个架构并不是以构建高度伸缩性的应用而著称的。\n\n#### 易于开发\n评级 : 低      \n分析 : 微内核架构需要考虑设计和规约管理，使它不会很难实现。规约的版本控制，内部的插件注册，插件粒度，丰富的插件连接的方式等是涉及到这个架构模式实现复杂度的重要因素。\n\n\n\n\n<b id=\"chapter4\"></b>\n## 第四章 微服务架构\n\n微服务架构模式作为替代单体应用和面向服务架构的一个可行的选择，在业内迅速取得进展。由于这个架构模式仍然在不断的发展中，在业界存在很多困惑——这种模式是关于什么的？它是如何实现的？本报告的这部分将为你提供关键概念和必要的基础知识来理解这一重要架构模式的好处(和取舍)，以此来判断这种架构是否适合你的应用。\n\n### 模式描述\n\n不管你选择哪种拓扑或实现风格,有几种常见的核心概念适用于一般架构模式。第一个概念是*单独部署单元*。如图4-1所示，微服务架构的每个组件都作为一个独立单元进行部署，让每个单元可以通过有效、简化的传输管道进行通信，同时它还有很强的扩展性，应用和组件之间高度解耦，使得部署更为简单。\n\n也许要理解这种模式，最重要的概念就是服务组件（service component）。不要考虑微服务架构内部的服务，而最好是考虑服务组件，从粒度上讲它可以小到单一的模块，或者大至一个应用程序。服务组件包含一个或多个模块（如Java类），这些模块可以提供一个单一功能（如，为特定的城市或城镇提供天气情况），或也可以作为一个大型商业应用的一个独立部分（如，股票交易布局或测定汽车保险的费率）。在微服务架构中，正确设计服务组件的粒度是一个很大的挑战。在接下来的服务组件部分对这一挑战进行了详细的讨论。\n\n![4-1](images/4-1.png)     \n\n微服务架构模式的另一个关键概念是它是一个*分布式*的架构，这意味着架构内部的所有组件之间是完全解耦的，并通过某种远程访问协议（如， JMS, AMQP, REST, SOAP, RMI等）进行访问。这种架构的分布式特性是它实现一些优越的可扩展性和部署特性的关键所在。\n\n微服务架构另一个令人兴奋的特性是它是由其他常见架构模式存在的问题演化来的，而不是作为一个解决方案被创造出来等待问题出现。微服务架构的演化有两个主要来源：使用分层架构模式的单体应用和使用面向服务架构的分布式应用。\n\n由单体应用( 一个应用就是一个整体 )到微服务的发展过程主要是由持续交付开发促成的。从开发到生产的持续部署管道概念,简化了应用程序的部署。单体应用通常是由紧耦合的组件组成，这些组件同时又是另一个单一可部署单元的一部分，这使得它繁琐，难以改变、测试和部署应用（因此常见的“月度部署”周期出现并通常发生在大型IT商店项目）。这些因素通常会导致应用变得脆弱以至于每次有一点新功能部署后应用就不能运行。微服务架构模式通过将应用分隔成多个可部署的单元（服务组件）的方法来解决这一问题，这些服务组件可以独立于其他服务组件进行单独开发、测试和部署。\n\n另一个导致微服务架构模式产生的演化过程是由面向服务架构模式（SOA）应用程序存在的问题引起的。虽然SOA模式非常强大，提供了无与伦比的抽象级别、异构连接、服务编排，并保证通过IT能力调整业务目标，但它仍然是复杂的,昂贵的,普遍存在，它很难理解和实现，对大多数应用程序来说过犹不及。微服务架构通过简化服务概念，消除编排需求、简化服务组件连接和访问来解决复杂度问题。\n\n### 模式拓扑\n\n虽然有很多方法来实现微服务架构模式,但三个主要的拓扑结构脱颖而出，最常见和流行的有:基于REST API的拓扑结构,基于REST的应用拓扑结构和集中式消息拓扑结构。\n\n基于REST的API拓扑适用于网站，通过某些API对外提供小型的、自包含的服务。这种拓扑结构,如图4 - 2所示,由粒度非常细的服务组件（因此得名微服务）组成，这些服务组件包含一个或两个模块并独立于其他服务来执行特定业务功能。在这种拓结构扑中,这些细粒度的服务组件通常被REST-based的接口访问，而这个接口是通过一个单独部署的web API层实现的。此种拓扑的例子包含一些常见的专用的、基于云的RESTful web service，大型网站像Yahoo, Google, and Amazon都在使用。\n\n![4-2](images/4-2.png)    \n\n基于REST的应用拓扑结构与基于REST API的不同，它通过传统的基于web的或胖客户端业务应用来接收客户端请求，而不是通过一个简单的API层。如图4-3所示，应用的用户接口层（user interface layer）是一个web应用，可以通过简单的REST-based接口访问单独部署的服务组件（业务功能）。该拓扑结构中的服务组件与API-REST-based拓扑结构中的不同，这些服务组件往往会更大、粒度更粗、代表整个业务应用程序的一小部分，而不是细粒度的、单一操作的服务。这种拓扑结构常见于中小型企业等复程度相对较低的应用程序。\n\n![4-3](images/4-3.png)    \n\n微服务架构模式中另一个常见的方法是集中式消息拓扑。该拓扑（如图4-4所示）与前面提到的基于REST的应用拓扑类似，不同的是，application REST- based拓扑结构使用REST进行远程访问，而该拓扑结构则使用一个轻量级的集中式消息代理（如，ActiveMQ, HornetQ等等）。不要将该拓扑与面向服务架构模式混淆或将其当做SOA简化版（“SOA-Lite”），这点是极其重要的。该拓扑中的轻量级消息代理（Lightweight Message Broker）不执行任何编排,转换,或复杂的路由;相反,它只是一个轻量级访问远程服务组件的传输工具。\n\n集中式消息拓扑结构通常应用在较大的业务应用程序中，或对于某些对传输层到用户接口层或者到服务组件层有较复杂的控制逻辑的应用程序中。该拓扑较之先前讨论的简单基于REST的拓扑结构，其好处是有先进的排队机制、异步消息传递、监控、错误处理和更好的负载均衡和可扩展性。与集中式代理相关的单点故障和架构瓶颈问题已通过代理集群和代理联盟（将一个代理实例为分多个代理实例，把基于系统功能区域的吞吐量负载划分开处理）解决。\n\n![4-4](images/4-4.png)   \n\n### 避免依赖和编排\n\n微服务架构模式的主要挑战之一就是决定服务组件的粒度级别。如果服务组件粒度过粗，那你可能不会意识到这个架构模式带来的好处（部署、可扩展性、可测试性和松耦合），然而,服务组件粒度过细将导致服务编制要求,这会很快导致将微服务架构模式变成一个复杂、容易混淆、代价昂贵并易于出错的重量级面向服务架构。\n\n如果你发现需要从应用内部的用户接口或API层编排服务组件，那么很有可能你服务组件的粒度太细了。如果你发现你需要在服务组件之间执行服务间通信来处理单个请求,那么很有可能要么是你服务组件的粒度太细了，要么是没有从业务功能角度正确划分服务组件。\n\n服务间通信，可能导致组件之间产生耦合，但可以通过共享数据库进行处理。例如，若一个服务组件处理网络订单而需要用户信息时，它可以去数据库检索必要的数据，而不是调用客户服务组件的功能。\n\n共享数据库可以处理信息需求，但是共享功能呢？如果一个服务组件需要的功能包含在另一个服务组件内，或是一个公共的功能,那么有时你可以将服务组件的共享功能复制一份（因此违反了DRY规则：don’t repeat yourself）。为了保持服务组件独立和部署分离，微服务架构模式实现中会存在一小部分由重复的业务逻辑而造成的冗余，这在大多数业务应用程序中是一个相当常见的问题。小工具类可能属于这一类重复的代码。\n\n如果你发现就算不考虑服务组件粒度的级别，你仍不能避免服务组件编排,这是一个好迹象,可能此架构模式不适用于你的应用。由于这种模式的分布式特性，很难维护服务组件之间的单一工作事务单元。这种做法需要某种事务补偿框架回滚事务,这对此相对简单而优雅的架构模式来说，显著增加了复杂性。\n\n### 注意事项\n\n微服务架构模式解决了很多单体应用和面向服务架构应用存在的问题。由于主要应用组件被分成更小的,单独部署单元,使用微服务架构模式构建的应用程序通常更健壮,并提供更好的可扩展性,支持持续交付也更容易。\n\n该模式的另一个优点是,它提供了实时生产部署能力，从而大大减少了传统的月度或周末“大爆炸”生产部署的需求。因为变化通常被隔离成特定的服务组件，只有变化的服务组件才需要部署。如果你的服务组件只有一个实例，你可以在用户界面程序编写专门的代码用于检测一个活跃的热部署,一旦检测到就将用户重定向到一个错误页面或等待页面。你也可以在实时部署期间，将服务组件的多个实例进行交换，允许应用程序在部署期间保持持续可用性（分层架构模式很难做到这点）。\n\n最后一个要重视的考虑是，由于微服务架构模式是分布式的架构，他与事件驱动架构模式具有一些共同的复杂的问题，包括约定的创建、维护，和管理，远程系统的可用性，远程访问身份验证和授权。\n\n### 模式分析\n\n下面这个表中包含了微服务架构模式的特点分析和评级，每个特性的评级是基于自然趋势，基于典型模式实现的能力特性,以及该模式是以什么闻名的。本报告中该模式与其他模式的并排比较，请参考报告最后的附件A。\n\n#### 整体灵活性\n评级：高      \n分析：整体的灵活性是能够快速响应不断变化的环境。由于单独部署单元的概念,变化通常被隔离成单独的服务组件,使得部署变得快而简单。同时，使用这种模式构建的应用往往是松耦合的，也有助于促进改变。\n\n#### 易于部署\n评级：高      \n分析：整体来讲，由于该模式的解耦特性和事件处理组件使得部署变得相对简单。broker拓扑往往比mediator拓扑更易于部署，主要是因为event-mediator组件与事件处理器是紧耦合的，事件处理器组件有一个变化可能导致event mediator跟着变化，有任何变化两者都需要部署。\n\n#### 可测试性\n评级：高      \n分析：由于业务功能被分离成独立的应用模块,可以在局部范围内进行测试，这样测试工作就更有针对性。对一个特定的服务组件进行回归测试比对整个单体应用程序进行回归测试更简单、更可行。而且,由于这种模式的服务组件是松散耦合的，从开发角度来看，由一个变化导致应用其他部分也跟着变化的几率很小，并能减小由于一个微小的变化而不得不对整个应用程序进行测试的负担。\n\n#### 性能\n评级：低      \n分析：虽然你可以从实现该模式来创建应用程序并可以很好的运行，整体来说，由于微服务架构模式的分布式特性，并不适用于高性能的应用程序。\n\n#### 伸缩性\n评级：高      \n分析：由于应用程序被分为单独的部署单元,每个服务组件可以单独扩展，并允许对应用程序进行扩展调整。例如，股票交易的管理员功能区域可能不需要扩展，因为使用该功能的用户很少，但是交易布局服务组件可能需要扩展，因为大多数交易应用程序需要具备处理高吞吐量的功能。\n\n#### 易于开发\n评级：高      \n分析：由于功能被分隔成不同的服务组件，由于开发范围更小且被隔离，开发变得更简单。程序员在一个服务组件做出一个变化影响其他服务组件的几率是很小的，从而减少开发人员或开发团队之间的协调。\n\n\n\n<b id=\"chapter5\"></b>\n## 第五章 基于空间的架构\n\n大多数基于网站的商务应用都遵循相同的请求流程：一个请求从浏览器发到web服务器，然后到应用服务器，然后到数据库服务器。虽然这个模式在用户数不大的时候工作良好，但随着用户负载的增加,瓶颈会开始出现，首先出现在web服务器层，然后应用服务器层，最后数据库服务器层。通常的解决办法就是**向外扩展**，也就是增加服务器数量。这个方法相对来说简单和廉价，并能够解决问题。然而，对于大多数高访问量的情况，它只不过是把web服务器的问题移到了应用服务器。而扩展应用服务器会更复杂，而且成本更高，并且又只是把问题移动到了数据库服务器，那会更复杂，更贵。就算你能扩展数据库服务器，你最终会陷入一个金字塔式的情形，在金字塔最下面是web服务器，它会出现最多的问题，但也最好伸缩。金字塔顶部是数据库服务器，问题不多，但最难伸缩。\n\n在一个高并发大容量的应用中，数据库通常是决定应用能够支持多少用户同时在线的关键因素。虽然各种缓存技术和数据库伸缩产品都在帮助解决这个问题，但数据库难以伸缩的现实并没有改变。\n\n基于空间的架构模型是专门为了**解决伸缩性和并发问题**而设计的。它对于用户数量不可预测且数量级经常变化的情况同样适用。在架构级别来解决这个伸缩性问题通常是比增加服务器数量或者提高缓存技术更好的解决办法。\n\n### 模型介绍\n\n基于空间的模型（有时也称为云架构模型）旨在减少限制应用伸缩的因素。模型的名字来源于分布式共享内存中的 tuple space（数组空间）概念。高伸缩性是通过去除中心数据库的限制，并使用从内存中复制的数据框架来获得的。保存在内存的应用数据被复制给所有运行的进程。进程可以动态的随着用户数量增减而启动或结束，以此来解决伸缩性问题。这样因为没有了中心数据库，数据库瓶颈就此解决，此后可以近乎无限制的扩展了。\n\n大多数使用这个模型的应用都是标准的网站，它们接受来自浏览器的请求并进行相关操作。竞价拍卖网站是一个很好的例子 ( 12306更是一个典型的示例 )。网站不停的接受来自浏览器的报价。应用收到对某一商品的报价，记录下报价和时间，并且更新对该商品的报价，将信息返回给浏览器。\n\n这个架构中有两个主要的模块：**处理单元** 和 **虚拟化中间件**。下图展示了这个架构和里面的主要模块。\n\n![](images/5-1.png)\n\n处理单元包含了应用模块（或者部分的应用模块）。具体来说就是包含了web组件以及后台业务逻辑。处理单元的内容根据应用的类型而异——小型的web应用可能会部署到单一的处理单元，而大型一些的应用会将应用的不同功能模块部署到不同的处理单元中。典型的处理单元包括应用模块，以及保存在内存的数据框架和为应用失败时准备的异步数据持久化模块。它还包括复制引擎，使得虚拟化中间件可以将处理单元修改的数据复制到其他活动的处理单元。\n\n虚拟化中间件负责保护自身以及通信。它包含用于数据同步和处理请求的模块，以及通信框架，数据框架，处理框架和部署管理器。这些在下文中即将介绍的部分，可以自定义编写或者购买第三方产品来实现。\n\n### 组件间合作\n\n基于空间的架构的魔力就在虚拟化中间件，以及各个处理单元中的内存中数据框架。下图展示了包含着应用模块、内存中数据框架、处理异步数据恢复的组件和复制引擎的处理单元架构。\n\n虚拟化中间件本质上是架构的控制器，它管理请求，会话，数据复制，分布式的请求处理和处理单元的部署。虚拟化中间件有四个架构组件：通信框架，数据框架，处理框架和部署管理器。\n\n![](images/5-2.png)\n\n#### 通信框架\n\n通信框架管理输入请求和会话信息。当有请求进入虚拟化中间件，通信框架就决定有哪个处理单元可用，并将请求传递给这个处理单元。通信框架的复杂程度可以从简单的round robin算法到更复杂的用于监控哪个请求正在被哪个处理单元处理的next-available算法。\n\n![](images/5-3.png)\n\n#### 数据框架\n\n数据框架可能是这个架构中最重要和关键的组件。它与各个处理单元的数据复制引擎交互，在数据更新时来管理数据复制功能。由于通信框架可以将请求传递给任何可用的处理单元，所以每个处理单元包含完全一样的内存中数据就很关键。下图展示处理单元间如何同步数据复制，实际中是通过非常迅速的并行的异步复制来完成的，通常在微秒级。\n\n![](images/5-4.png)\n\n#### 处理框架\n\n处理框架，就像下图所示，是虚拟化中间件中一个可选组件，负责管理在有多个处理单元时的分布式请求处理，每个处理单元可能只负责应用中的某个特定功能。如果请求需要处理单元间合作（比如，一个订单处理单元和顾客处理单元），此时处理框架就充当处理单元见数据传递的媒介。\n\n![](images/5-5.png)\n\n#### 部署管理器\n\n部署管理器根据负载情况管理处理单元的动态启动和关闭。它持续检测请求所需时间和在线用户量，在负载增加时启动新的处理单元，在负载下降时关闭处理单元。它是实现可变伸缩性需求的关键。\n\n### 其他考虑\n\n基于空间的架构是一个复杂和实现起来相对昂贵的框架。对于有可变伸缩性需求的小型web应用是很好的选择，然而，对于拥有大量数据操作的传统大规模关系型数据库应用，并不那么适用。\n\n虽然基于空间的架构模型不需要集中式的数据储存，但通常还是需要这样一个，来进行初始化内存中数据框架，和异步的更新各处理单元的数据。通常也会创建一个单独的分区，来从隔离常用的断电就消失的数据和不常用的数据，这样减少处理单元之间对对方内存数据的依赖。\n\n值得注意的是，虽然这个架构的另一个名字是云架构，处理单元（以及虚拟化中间件）都没有放在云端服务或者PaaS上。他们同样可以简单的放在本地服务器，这也是为什么我更倾向叫它“基于空间的架构”。\n\n从产品实现的角度讲，这个架构中的很多组件都可以从第三方获得，比如GemFire, JavaSpaces, GigaSpaces，IBM Object Grid，nCache，和 Oracle Coherence。由于架构的实现根据工程的预算和需求而异，所以作为架构师，你应该在实现或选购第三方产品前首先明确你的目标和需求。\n\n### 架构分析\n\n下面的表格是这个架构的特征分析和评分。每个特征的评分是基于一个典型的架构实现来给出的。要知道这个模式相对别的模式的对比，请参见最后的附录A。\n\n#### 综合能力\n评分：高       \n分析：综合能力是对环境变化做出快速反应的能力。因为处理单元（应用的部署实例）可以快速的启动和关闭，整个应用可以根据用户量和负载做出反应。使用这个架构通常在应对代码变化上，由于较小的应用规模和组件间相互依赖，也会反映良好。\n\n#### 易于部署\n评分：高      \n分析：虽然基于空间的架构通常没有解耦合并且功能分布，但他们是动态的，也是成熟的基于云的工具，允许应用轻松的部署到服务器。\n\n#### 可测试性\n评分：低      \n分析：测试高用户负载既昂贵又耗时，所以在测试架构的可伸缩性方面很困难\n\n#### 性能\n评分：高      \n分析：通过内存中数据存取和架构中的缓存机制可获得高性能\n\n#### 伸缩性\n评分：高     \n分析：高伸缩性是源于几乎不依赖集中式的数据库，从而去除了这个限制伸缩性的瓶颈。\n\n#### 易于开发\n评分：低      \n分析：主要是因为难以熟悉这个架构开发所需得工具和第三方产品，因此使用该架构需要较大的学习成本。而且，开发过程中还需要特别注意不要影响到性能和可伸缩性。\n\n<b id=\"appendix\" ></b>\n### 附录A \n#### 模式分析总结\n图A-1 总结了在这个报告中，对于架构模式的每部分进行的模式分析所产生的影响。这个总结帮助你确定哪些模式可能是最适合你的情况。例如,如果你的架构模式重点是可伸缩性，你可以在这个图表看看事件驱动模式,microservices模式,和基于空间模式，这些对于你来说可能是很好的架构模式的选择。同样的,如果你的程序注重的是分层架构模式,你可以参考图看到部署、性能和可伸缩性的在你的架构中所存在的风险。\n\n![a-1](images/a-1.png)     \n\n同时这个图表将指导你选择正确的模式,因为在选择一种架构模式的时候，有更多的因素需要考虑。你必须分析你的环境的各个方面,包括基础设施的支持,开发人员技能,项目预算,项目最后期限,和应用程序大小等等。选择正确的架构模式是至关重要的,因为一旦一个架构被确定就很难改变。\n\n<b id=\"author\" ></b>\n### 关于作者 \nMark•Richards是一位有丰富经验的软件架构师，他参与架构、设计和实施microservices体系结构、面向服务的体系结构和在J2EE中的分布式系统和其他技术。自1983年以来，他一直从事软件行业,在应用、继承和企业架构方面有大量的经验和专业知识。\n\nMark在1999到2003年间担任新英格兰Java用户组的主席。他是许多技术书籍和视频的作者,包括软件架构基础(O‘Reilly视频)、企业消息传递(O'Reilly视频),《Java消息服务，第二版》(O'Reilly)和《软件架构师应该知道的97件事》(O'Reilly)的特约作者。Mark拥有一个计算机科学硕士学位并且多次获得IBM、Sun、开放集团和BEA等颁发的架构师和开发人员认证。 \n\n他是Fluff Just Stuff(NFJS)研讨会系列（一个不定期会议）议长,并且有过上百次的在世界各地公益会议和用户组上围绕技术主题的演讲经验)。Mark不工作的时候经常会到白色山脉或阿帕拉契山径徒步旅行。\n\n\n"
  },
  {
    "path": "software-architecture-patterns/附录2015-4-11-charli.md",
    "content": "# APPENDIX A #\n## Pattern Analysis Summary ##\nFigure A-1 summarizes the pattern-analysis scoring for each of the\narchitecture patterns described in this report. This summary will\nhelp you determine which pattern might be best for your situation.\nFor example, if your primary architectural concern is scalability, you\ncan look across this chart and see that the event-driven pattern,\nmicroservices pattern, and space-based pattern are probably good\narchitecture pattern choices. Similarly, if you choose the layered\narchitecture pattern for your application, you can refer to the chart\nto see that deployment, performance, and scalability might be risk\nareas in your architecture.\n45\n\n# 附录A #\n## 模式分析总结 ##\n图A-1 总结了在这个报告中，对于架构模式的每部分进行的模式分析所产生的影响。这个总结帮助你确定哪些模式可能是最适合你的情况。例如,如果你的架构模式重点是可伸缩性，你可以在这个图表看看事件驱动模式,microservices模式,和基于空间模式，这些对于你来说可能是很好的架构模式的选择。同样的,如果你的程序注重的是分层架构模式,你可以参考图看到部署、性能和可伸缩性的在你的架构中所存在的风险。\n\nWhile this chart will help guide you in choosing the right pattern,\nthere is much more to consider when choosing an architecture pattern.\nYou must analyze all aspects of your environment, including\ninfrastructure support, developer skill set, project budget, project\ndeadlines, and application size (to name a few). Choosing the right\narchitecture pattern is critical, because once an architecture is in\nplace, it is very hard (and expensive) to change\n\n同时这个图表将指导你选择正确的模式,\n因为在选择一种架构模式的时候，有更多的需要考虑。\n你必须分析你的环境的各个方面,包括\n基础设施的支持,开发人员技能,项目预算,项目\n最后期限,和应用程序大小(等等)。选择正确的\n架构模式是至关重要的,因为一旦一个架构\n被确定了就很难改变。\n\n\n# About the Author #\nMark Richards is an experienced, hands-on software architect\ninvolved in the architecture, design, and implementation of microservices\narchitectures, service-oriented architectures, and distributed\nsystems in J2EE and other technologies.\n He has been in the\nsoftware industry since 1983 and has significant experience and\nexpertise in application, integration, and enterprise architecture.\nMark served as the president of the New England Java Users Group\nfrom 1999 through 2003.\n He is the author of numerous technical books and videos, including Software Architecture Fundamentals\n(O’Reilly video), Enterprise Messaging (O’Reilly video), Java\nMessage Service, 2nd Edition (O’Reilly), and a contributing author\nto 97 Things Every Software Architect Should Know (O’Reilly).\nMark has a master’s degree in computer science and numerous\narchitect and developer certifications from IBM, Sun, The Open\nGroup, and BEA. \nHe is a regular conference speaker at the No\nFluff Just Stuff (NFJS) Symposium Series and has spoken at more\nthan 100 conferences and user groups around the world on a variety\nof enterprise-related technical topics. \nWhen he is not working,\nMark can usually be found hiking in the White Mountains or\nalong the Appalachian Trail.\n# 有关作者 #\nMark•Richards是一位有实际经验的软件架构师，他参与架构、设计和实施microservices\n体系结构、面向服务的体系结构和在J2EE中的分布式\n系统和其他技术。自1983年以来，他一直从事软件行业,有大量的经验和专业知识在应用、集成和企业架构方面。\nMark从1999年到2003担任新英格兰Java用户组的主席。\n他是许多技术书籍和视频的作者,包括软件架构基础(O‘Reilly视频)、企业消息传递(O'Reilly视频),《Java\n消息服务，第二版》(O'Reilly)和《软件架构师应该知道的97件事》(O'Reilly)的特约作者。\nMark有一个计算机科学硕士学位和很多从IBM、Sun、开放\n集团和BEA获得的架构师和开发人员认证。\n他是Fluff Just Stuff(NFJS)研讨会系列的一个不定期会议议长\n,并且超过100多次在世界各地的公益的会议和用户组上围绕技术主题\n发言。当他不工作时,Mark通常会在白色山脉或阿帕拉契山径徒步旅行。\n\n\n"
  },
  {
    "path": "template.md",
    "content": "这里写中文标题\n---\n\n> * 原文链接 : [原文标题](原文url)\n* 原文作者 : [作者](作者的博客或者其他社交页面)\n* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)\n* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权，未经允许，不得转载!\n* 译者 : [这里写你的github用户名](github链接) \n* 校对者: [这里校对者的github用户名](github链接)  \n* 状态 :  未完成 / 校对中 / 完成 \n\n**注意 : 翻译完之后请认真的审核一遍有没有错字、语句通不通顺，谢谢~**\n\n\n`这里是翻译原文，注意翻译时英文和译文都要留在该文档中，并且是一段英文原文下面直接跟着写译文，便于校对。如下示例 : `\n\nOver the last months and after having friendly discussions at Tuenti with colleagues like @pedro_g_s and @flipper83 (by the way 2 badass of android development), I have decided that was a good time to write an article about architecting android applications.\nThe purpose of it is to show you a little approach I had in mind in the last few months plus all the stuff I have learnt from investigating and implementing it.\n\n过去几个月以来，通过在Tuenti网站上与@pedro_g_s和@flipper83（安卓开发两位大牛）进行友好讨论之后，我决定写这篇关于架构安卓应用的文章。     \n\n我写这篇文章的目的是想把我在过去几个月体悟到的小方法以及在调查和应用中学到的有用的东西分享给大家。\n\nGetting Started\nWe know that writing quality software is hard and complex: It is not only about satisfying requirements, also should be robust, maintainable, testable, and flexible enough to adapt to growth and change. This is where “the clean architecture” comes up and could be a good approach for using when developing any software application.\nThe idea is simple: clean architecture stands for a group of practices that produce systems that are:\n\n## 入门指南\n大家都知道要写一款精品软件是有难度且很复杂的：不仅要满足特定要求，而且软件还必须具有稳健性，可维护、可测试性强，并且能够灵活适应各种发展与变化。这时候，“清晰架构”就应运而生了，这一架构在开发任何软件应用的时候用起来非常顺手。\n\n这个思路很简单：简洁架构 意味着产品系统中遵循一系列的习惯原则：\n\n\n\n"
  },
  {
    "path": "the-bad-guys/readme.md",
    "content": "# 国内优秀Android学习资源\n\n## 技术博客\n\n### 应用开发\n\n|   博主  |         博客       |    备注   |\n|-------- |------------------|----------|\n|   任玉刚  |   [CSDN博客](http://blog.csdn.net/singwhatiwanna/)  |  深入Android应用开发，深度与广度兼顾   |\n|   郭霖  |   [CSDN博客](http://blog.csdn.net/guolin_blog/)  | 内容实用，行文流畅，高人气博主   |\n|   夏安明  |   [CSDN博客](http://blog.csdn.net/xiaanming/)  |      \n|   张鸿洋  |   [CSDN博客](http://blog.csdn.net/lmj623565791/)  | 自定义View系列非常有价值，质量与产量都很高 | \n|   爱哥  |   [CSDN博客](http://blog.csdn.net/aigestudio/)  | 自定义View系列非常有价值，内容详细逼格高 | \n|   傲慢的上校  |   [CSDN博客](http://blog.csdn.net/lilu_leo/)  | 自定义View系列非常有价值，内容详细逼格高 | \n|   Trinea  |   [个人博客](http://www.trinea.cn/)  | 性能优化,开源项目等 | \n|   胡凯  |   [个人博客](http://hukai.me/)  | 性能优化等 |\n|   谦虚的天下 | [博客园](http://www.cnblogs.com/qianxudetianxia/)  | 性能优化等 |\n|  兰亭风雨  | [CSDN博客](http://blog.csdn.net/ns_code)  | Java源码分析等 | \n|  Mr.Simple  | [CSDN博客](http://blog.csdn.net/bboyfeiyu)  | 开源框架系列、OOP等 | \n\n\n### 源码分析\n|   博主  |         博客       |    备注   |\n|-------- |------------------|----------|\n|   罗升阳  |   [CSDN博客](http://blog.csdn.net/luoshengyang/)  |    源码分析   |\n|   邓凡平  |   [博客园](http://www.cnblogs.com/innost/)  |    源码分析   |\n\n\n## 开源达人\n|   作者  |       备注   |\n|-------- |----------|\n|   [square项目组](https://github.com/square)  |  OkHttp, Retrofit, Otto等很多优秀的开源项目，业界良心！  |\n|   [facebook项目组](https://github.com/facebook)  | fresco,react native等很多优秀的开源项目，业界良心之二！  |\n|   [Jake Wharton](https://github.com/jakewharton)  | NineOldAnimatoins, ButterKnife,ViewIndicator等优秀作品，Square员工 |\n|   [Trinea](https://github.com/trinea)  | 优秀开源项目集锦,优秀开源库架构分析等  |\n|   [singwhatiwanna](https://github.com/singwhatiwanna)  | DL (插件化),PinnedHeaderExpandableListView等  |\n|   [daimajia](https://github.com/daimajia)  | 自定义View  |\n|   [greenrobot](https://github.com/greenrobot)  | EventBus, greenDAO |\n|   [wyouflf](https://github.com/wyouflf)  |  xUtils作者 |\n|   [litesuits](https://github.com/litesuits)  |  http,orm等库 |\n|   [Mr.Simple](https://github.com/bboyfeiyu)  |  AndroidEventBus,开发技术前线,Android源码设计模式分析等项目 |\n\n## 优秀学习网站\n|   地址  |       备注   |\n|-------- |----------|\n|   [开发技术前线](http://devtf.cn) |  国内外各个开发领域优质技术文章的聚合网站  |\n|   [Android Dev Blog](http://android-developers.blogspot.com/) |  Android官方博客 |\n|   [Android Weekly](http://androidweekly.net) |  国外优秀技术文章的收集网站，每周发布  |\n|   [Android开发技术周报](http://androidweekly.cn) |  AndroidWeekly中国版  |\n|   [码农周刊](http://weekly.manong.io/) |  文章、咨询周报  |\n|   [好东西传送门](http://memect.com/) |  各领域技术文章的日报  |\n\n"
  },
  {
    "path": "翻译项目协作流程.md",
    "content": "Github协作流程\n---\n\n\n## fork一份到你的账户下\n在[android-tech-frontier](https://github.com/bboyfeiyu/android-tech-frontier)项目中选择右边的\"fork\"，然后选择你的头像，将该项目fork一份到你的账户下。\n\n![fork](http://img.blog.csdn.net/20150323230517092)\n\n点击“fork”后能在个人主页中看到项目就表示fork成功了：\n\n![](http://img.my.csdn.net/uploads/201504/25/1429948243_6050.png)\n\n## clone你fork的仓库到本地\n输入如下命令clone一份你fork的仓库 (相当于拷贝了一份到你的账户下)，例如 : \n![clone](http://img.blog.csdn.net/20150323230449588)\n\n> git clone git@github.com:bboyfeiyu/android-tech-frontier.git\n\n等待clone完毕。此时，在你的当前目录下会有android-tech-frontier目录。进入该android-tech-frontier，在相应的目录下按照[readme.md](readme.md)中的命名规则创建你的翻译项目，然后完成翻译。    \n\n操作完成后就能在相应的目录中看到项目文件了\n\n## 发送 pull request\n\n打开项目首页，点击图中的小绿框\n\n![](http://img.my.csdn.net/uploads/201504/25/1429948415_2193.png)\n\n再点击红框内的文字\n\n![](http://img.my.csdn.net/uploads/201504/25/1429948487_1166.jpg)\n\n就会出现这个页面\n\n![](http://img.my.csdn.net/uploads/201504/25/1429948573_7445.jpg)\n\n将左边选成你的项目，右边选成项目作者的项目，就表示你要将你的项目更新为作者的状态，与作者保持一致\n\n例如项目更新了10篇文章，你翻译好了一篇文章，那么在你提交你的文章之前，你就需要先将项目更新为作者最新的状态，再提交，否则会出现冲突\n\n![](http://img.my.csdn.net/uploads/201504/25/1429948699_7368.jpg)\n\n将左边选成作者的项目，右边选成你的项目，则表示要将你对作者项目作出的改变提交给作者，请求作者同步\n\n![](http://img.my.csdn.net/uploads/201504/25/1429948882_7915.jpg)\n\n选择 Create Pull Request 就可以完成啦\n\n## 进行校对\n\n打开项目作者处的 pull request，当有其他人的 pull request 时页面会如下：\n\n![](http://img.my.csdn.net/uploads/201504/25/1429948986_4450.jpg)\n\n选择某一个 pull request ，将进入相应的页面\n\n![](http://img.my.csdn.net/uploads/201504/25/1429949102_5644.jpg)\n\n点击 commit 能看见提交者的所有 commit，点击相应的 commit 能看到提交者进行了什么操作，修改了什么东西。\n\n点击 FileChange 则能看见提交者提交上来的内容（最终版本）\n\n![](http://img.my.csdn.net/uploads/201504/25/1429949236_2350.jpg)\n\n在每一行的左侧，会出现一个蓝色的 + 号，点击它，就能进行 comment，通过 comment 可以对提交者存在问题的译文作出评论，和译者交流\n\n![](http://img.my.csdn.net/uploads/201504/25/1429949329_3391.jpg)\n\n## 认领翻译任务\n你可以在主仓库下看到一些翻译任务的状态: \n\n* 待认领 : 表示还没有人接这个翻译任务;\n* 翻译中 : 表明已经有人翻译了；\n* 翻译完成 : 表明已经翻译完成；\n* 寻找校对中 : 表明此时文章翻译完成，正在等待校对；\n* 校对中 : 表明正在校对；\n* 校对完成 : 表明校对完成，等待发布。\n \n当你看到某个状态为\"待认领\"的issue时，你可以撸起袖子在该issue下面添加评论，将你的github账户评论到下面，然后管理员会将该issue设置为\"翻译中\"。当你看到“寻找校对中”的任务，则说明该任务已经翻译完，但是需要校对，你也可以在该issue中将你的github账户添加到评论中，然后将该任务设置为“校对中”。    \n校对完毕，管理员确认没啥问题之后会发布该文章，发布后会将该issue关闭。   \n\n![is](http://img.blog.csdn.net/20150323231540412)\n\n\n## 推荐文章\n如果你看到好的文章，也可以在仓库的issue中添加一个，将推荐理由、原文链接填上。如图 : \n\n![submit](http://img.blog.csdn.net/20150323231923706)     \n\n## 提交翻译内容到你的仓库中\n在android-tech-frontier下依次输入如下三条命令提交你修改的内容到你的仓库。  \n\n```\ngit add .\ngit commit -m \"我翻译了xxx项目\"\ngit push origin master\n```\n\n## 将你的更新提交到主仓库\n经过上面push命令之后，你的内容已经更新到你自己fork的仓库，此时你需要把你的更新提交到主仓库。因此你需要发一个pull request。在你fork的项目的右边点击\"pull request\",然后选择\"new pull request\"。注意是从你的仓库master分支(左边)到我的仓库的master分支(右边)。然后选择\"create new pull request\"，最后填写相关的内容后确认即可。\n\n![pr](http://img.blog.csdn.net/20150323230526312)\n \n提交了pull request之后，管理员会找相关人员进行校对。校对完成之后管理员会将你的文章发布。       \n\n"
  }
]