Showing preview only (2,160K chars total). Download the full file or copy to clipboard to get everything.
Repository: hehonghui/android-tech-frontier
Branch: master
Commit: 9538823e782c
Files: 292
Total size: 2.0 MB
Directory structure:
gitextract_zcp2h22p/
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── android-blog/
│ ├── Android性能优化系列/
│ │ └── readme.md
│ ├── Google+ 团队的 Android UI 测试/
│ │ └── readme.md
│ └── readme.md
├── androidweekly/
│ ├── Android中的人脸检测入门.md
│ ├── Android性能案例研究续集/
│ │ └── readme.md
│ ├── Kotlin for Android (II)创建一个工程/
│ │ └── readme.md
│ ├── Kotlin for Android (III)-扩展函数与默认值/
│ │ └── readme.md
│ ├── ListView或者RecycleView滚动时隐藏Toolbar-part-1/
│ │ └── readme.md
│ ├── ListView或者RecycleView滚动时隐藏Toolbar-part-2/
│ │ └── readme.md
│ ├── Square 开源库Flow和Mortar的介绍/
│ │ └── readme.md
│ ├── kotlin-for-android简介/
│ │ └── readme.md
│ ├── readme.md
│ ├── 一个支持多设备的Android参考应用/
│ │ └── readme.md
│ ├── 一种在android中实现MVP模式的新思路/
│ │ └── readme.md
│ ├── 一种更清晰的Android架构/
│ │ └── readme.md
│ ├── 使用Robolectric的参数化测试/
│ │ └── readme.md
│ ├── 使用RxJava.Observable取代AsyncTask和AsyncTaskLoader/
│ │ └── readme.md
│ ├── 使用buildSrc Gradle项目和Codemodel生成java代码/
│ │ └── readme.md
│ ├── 功能测试框架 espresso/
│ │ └── readme.md
│ ├── 在Android 5.0中使用JobScheduler/
│ │ └── readme.md
│ ├── 在Android调试模式中使用Stetho/
│ │ └── README.md
│ ├── 安卓字体渲染器/
│ │ └── readme.md
│ ├── 安卓的模糊视图/
│ │ └── readme.md
│ ├── 欢迎来到Android多进程时代/
│ │ └── readme.md
│ ├── 深入了解Android Graphics Pipeline-part-1/
│ │ └── readme.md
│ ├── 深入了解Android Graphics Pipeline-part-2/
│ │ └── readme.md
│ ├── 深入了解Bundle和Map/
│ │ └── readme.md
│ ├── 符合Material Design的抽屉导航效果/
│ │ └── readme.md
│ ├── 让你的Android应用能使用多种主题-Part-1/
│ │ └── readme.md
│ ├── 让你的Android应用能使用多种主题-Part-2/
│ │ └── readme.md
│ └── 那些年我们错过的响应式编程/
│ └── readme.md
├── authorization.md
├── git简单使用教程.md
├── issue-10/
│ ├── Android如何直播RTMP流.md
│ ├── Android进行单元测试难在哪-part2.md
│ ├── Kotlin-for-Android-(IV):自定义视图和Android的扩展.md
│ ├── readme.md
│ ├── 使用Facebook-SDK为安卓应用添加Like按钮.md
│ └── 将基于Dagger-1开发的项目迁移到Dagger-2中.md
├── issue-11/
│ ├── Android-Espresso测试框架介绍.md
│ ├── Android进行单元测试难在哪-part3.md
│ ├── Code Review最佳实践.md
│ ├── readme.md
│ ├── 听FaceBook工程师讲Custom-ViewGroups.md
│ └── 详解Dagger2.md
├── issue-12/
│ ├── Android上MVP的介绍.md
│ ├── Android自动截屏工具.md
│ ├── Android进行单元测试难在哪-part4.md
│ ├── MVP框架Mosby架构详解.md
│ ├── readme.md
│ ├── 当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合.md
│ └── 自动化Android开发.md
├── issue-13/
│ ├── Android进行单元测试难在哪-终.md
│ ├── Square:从今天开始抛弃Fragment吧!.md
│ ├── readme.md
│ ├── 优化android-studio编译效率的方法.md
│ ├── 创建-RecyclerView-LayoutManager-Part-2.md
│ ├── 创建-RecyclerView-LayoutManager-Part-3.md
│ └── 创建-RecyclerView-LayoutManager-Redux.md
├── issue-14/
│ ├── Android-Design-Support-Library.md
│ ├── Android之WebRTC介绍.md
│ ├── Design-Support-Library(I)-Navigation-View.md
│ ├── MVVM 模式简介.md
│ └── 新的测试注解.md
├── issue-15/
│ ├── 2015-Google-IO带来的新Android开发工具 .md
│ ├── Android-C++引用计数介绍.md
│ ├── Android-M的App-Links实现详解.md
│ ├── facebook代码分析工具-infer.md
│ ├── readme.md
│ ├── 你可能漏掉的知识点-onResumeFragments.md
│ └── 如何修复编译时的MultiDex崩溃.md
├── issue-16/
│ ├── Android一体机模式:规则限制.md
│ ├── readme.md
│ ├── 为什么你应该停止使用EventBus.md
│ ├── 手动实现布局Transitions动画-第一部分.md
│ ├── 手动实现布局Transitions动画-第三部分.md
│ ├── 手动实现布局Transitions动画-第二部分.md
│ └── 结合RxJava更简单地使用SQLite.md
├── issue-17/
│ ├── Android UI 自动化测试.md
│ ├── Android中的帧动画.md
│ ├── Android开发III-规范与性能.md
│ ├── readme.md
│ ├── 为什么需要在你的Crash报告中使用git-SHA.md
│ ├── 使用RxJava从几个数据源中加载数据.md
│ ├── 如何使用Android-Studio把自己的Android-library分发到jCenter和Maven-Central.md
│ └── 当钢铁侠变得反应更快-RxJava.md
├── issue-18/
│ ├── Service测试.md
│ ├── readme.md
│ ├── 如何提高你的代码质量.md
│ ├── 拖拽RecyclerView.md
│ ├── 用组合代替继承能为Activity带来什么.md
│ └── 还在用Toast?你Out啦,试试Snackbar吧!.md
├── issue-19/
│ ├── Android-UI自动化测试.md
│ ├── RxJava-Observables单元测试.md
│ ├── gradle技巧之语法浅谈.md
│ └── readme.md
├── issue-20/
│ ├── readme.md
│ ├── 在你开发应用前应知道的六件事情.md
│ └── 检测和解决Android应用的性能问题.md
├── issue-21/
│ ├── Android数据绑定-再见Presenter,你好ViewModel.md
│ ├── TextView预渲染研究.md
│ ├── Yelp是如何通过Glide优化图片加载的.md
│ └── 使用Mockito对异步方法进行单元测试.md
├── issue-22/
│ ├── Android-Activity测试.md
│ ├── Android中的AOP编程.md
│ ├── Binder框架解析.md
│ └── 深入剖析Android网络开发库-part1.md
├── issue-23/
│ ├── Android-MVPR-架构模式-Part1.md
│ ├── Android-MVVM模式.md
│ ├── TextView的TextLayout.md
│ ├── 使用Espresso进行UI测试.md
│ └── 使用TDD的方式开发一个Hackernews客户端.md
├── issue-24/
│ ├── Android LayerDrawable 和 Drawable.Callback.md
│ ├── Android双向数据绑定.md
│ ├── Android设计与开发工作流.md
│ ├── 使用Kotlin对ViewGroup中的View进行函数式操作.md
│ └── 适用于Android的Flux架构.md
├── issue-25/
│ ├── Android主题动态切换开源库Prism基本原理1-核心功能.md
│ ├── Android主题动态切换开源库Prism基本原理2-搭配ViewPager使用.md
│ ├── Android主题动态切换开源库Prism基本原理3-搭配Palette使用.md
│ ├── 一个内存泄漏引发的血案-Square.md
│ └── 在Android Lollipop中使用Palette抽取Bitmap颜色.md
├── issue-26/
│ ├── 30分钟搭建一个android的私有Maven仓库.md
│ ├── Android架构演化之路.md
│ ├── Tinting drawables.md
│ ├── 使用Systrace分析UI性能.md
│ └── 在Android M中权限被拒绝时该如何处理.md
├── issue-27/
│ ├── 为什么不仅继承Observale而且使用Observable.create.md
│ ├── 为你的应用加速 - 安卓优化指南.md
│ ├── 使用Gradle将项目发布到Bitbucket上.md
│ └── 开发你自己的Android授权管理器.md
├── issue-28/
│ ├── 数据绑定(Data Binding)-Part1.md
│ ├── 数据绑定(Data Binding)-Part2.md
│ ├── 数据绑定(Data Binding)-Part3.md
│ ├── 数据绑定(Data Binding)-Part4.md
│ └── 数据绑定(Data Binding)-Part5.md
├── issue-29/
│ ├── Chrome自定义Tabs-让App和Web之间的转场更平顺.md
│ ├── 开发安全的Android应用.md
│ └── 注意API21(Android5.0)上的EditText.md
├── issue-30/
│ ├── Android开发生僻却实用的知识点Part1.md
│ ├── Android开发生僻却实用的知识点Part2.md
│ ├── Flux and Android.md
│ └── 通过硬件层提高Android动画的性能.md
├── issue-31/
│ ├── Android 中的依赖注入框架.md
│ ├── Android中调试RxJava.md
│ ├── Android应用架构.md
│ ├── Android开发生僻却实用的知识点Part3.md
│ ├── Espresso保存和恢复状态.md
│ └── 使用ClassyShark压缩你的项目.md
├── issue-32/
│ ├── AppBarLayout的越界滚动行为.md
│ ├── 剪刀手—Android平台上的图片裁剪库.md
│ ├── 实现安卓6.0的直接分享(Direct Share )功能.md
│ ├── 重构Plaid-响应式的MVP模式.md
│ └── 高性能ListViews.md
├── issue-33/
│ ├── Android Libraries的依赖管理.md
│ ├── 下雪动画.md
│ ├── 使用ADB-Shell的效率和乐趣-part1.md
│ ├── 如何自定义Lint规则.md
│ └── 高效地配置okhttp.md
├── issue-34/
│ ├── Android逆向工程101 – Part 1.md
│ ├── Android逆向工程101 – Part 2.md
│ ├── Context是怎么泄露的Handlers & Inner Classes.md
│ └── 在Android开发中使用RxJava.md
├── issue-35/
│ ├── 为什么我们要用fitsSystemWindows.md
│ ├── 星球大战:原力觉醒或者用原力粉碎Android的视图.md
│ ├── 用Transition完成Fragment共享元素的切换.md
│ ├── 让EditText中的链接即可点击又可编辑.md
│ └── 近乎通用的VectorDrawable.md
├── issue-36/
│ ├── Gradle小知识1.md
│ ├── Gradle小知识2.md
│ ├── Gradle小知识3.md
│ ├── Gradle小知识4.md
│ └── Gradle提示和使用技巧.md
├── issue-37/
│ ├── 使用ACTION_PROCESS_TEXT创建自定义文本选择动作.md
│ ├── 使用RxJava缓存Rest请求.md
│ ├── 在Android中使用并发来提高速度和性能.md
│ ├── 如何理解RxJava中的Subjects(第一部分).md
│ └── 更加强大的Dagger2.md
├── issue-38/
│ ├── Android逆向工程.md
│ ├── Android高性能JSON数据解析.md
│ ├── RecyclerView动画 第一部-动画效果是如何工作的.md
│ ├── RecyclerView动画 第二部-幕后.md
│ └── 从网页中触发Android原生的分享Intent.md
├── issue-39/
│ ├── Android Studio提示和技巧.md
│ ├── Android dex分包导致的App启动速度下降.md
│ ├── FragmentTransaction 与 Activity 状态丢失?.md
│ ├── RxJava中repeatWhen 和 retryWhen 操作符的解释.md
│ └── 在滚动列表中播放视频.md
├── issue-40/
│ ├── 权限 - 第一篇.md
│ ├── 权限 - 第三篇.md
│ ├── 权限 - 第二篇.md
│ └── 权限 - 第四篇.md
├── issue-41/
│ ├── Service十件你不知道的事.md
│ ├── 自定义CoordinatorLayout的行为.md
│ └── 让你了解数据加载的生命周期.md
├── issue-42/
│ ├── IndeterminateProgressbar解析-Part 1.md
│ ├── 剖析okhttp缓存机制.md
│ ├── 在Activity配置改变时保存状态.md
│ └── 提高NYTimes的启动速度.md
├── issue-43/
│ ├── IndeterminateProgressbar解析-Part 2.md
│ ├── LayoutInflater.inflate() 方法剖析.md
│ ├── 结合motion和Transition实现共享元素的酷炫动画.md
│ ├── 通过CoordinatorLayout的Behavior拦截一切.md
│ └── 避免Android应用冷启动问题.md
├── issue-44/
│ ├── Android-Reverse-Engineering-101-Part-4.md
│ ├── Android-Support-Library-23.2.md
│ ├── 使用Clean Architecture模型开发Android应用详细指南.md
│ ├── 利用Retrofit和RxJava实现服务器轮询和出错重试.md
│ └── 用RxJava替代EventBus.md
├── issue-45/
│ ├── Android圆弧整容之谜.md
│ ├── IndeterminateProgressbar解析-Part 3.md
│ ├── 使用反射到底会对性能造成多大影响?.md
│ ├── 安卓Binder架构概述.md
│ └── 简化复杂的视图层次.md
├── issue-46/
│ ├── Android-Reverse-Engineering-101-Part-5.md
│ ├── IndeterminateProgressbar解析-Part 4.md
│ ├── IndeterminateProgressbar解析-Part 5.md
│ └── 尝试结合Mosby和Flow代替Fragment.md
├── issue-47/
│ ├── Building a Kotlin project.md
│ ├── Kotlin在Android上令人惊叹的技巧.md
│ ├── Layouts-Attributes-and-you.md
│ └── 在Java和Android中使用Optional.md
├── issue-48/
│ ├── Android中的MVP-Part2.md
│ ├── Parcelable vs Serializable对比.md
│ ├── 为什么在Android开发中我仍然不想使用Kotlin.md
│ └── 构建我的Presentation层.md
├── issue-49/
│ ├── Android上的网络响应日志技巧.md
│ ├── AutoValue简介.md
│ ├── Jack&Jill的缺点.md
│ ├── 为什么在Android使用ClassLoader.getResourceAsStream会如此影响性能.md
│ ├── 如何使用BottomSheet.md
│ └── 深入研究AutoValue.md
├── issue-7/
│ ├── Android-Lollipop-update.md
│ ├── Android-Support库22.1版.md
│ ├── Android测试框架:Dagger2+Espresso2+Mockito/
│ │ └── README.md
│ ├── Retrofit开发指南/
│ │ └── README.md
│ ├── readme.md
│ ├── 使用Robolectric和Android生成代码覆盖率报告/
│ │ └── readme.md
│ ├── 在Activity中使用Thread导致的内存泄漏/
│ │ └── readme.md
│ ├── 深入浅出Android新特性-Transition-Part-3a/
│ │ └── readme.md
│ └── 深入浅出Android新特性-Transition-Part-3b/
│ └── readme.md
├── issue-8/
│ ├── Android 进行单元测试难在哪-序.md
│ ├── Custom-Drawables.md
│ ├── Support Libraries v22.1.0.md
│ ├── 如何在Android上响应各种信息通知.md
│ ├── 开始学习Material Design.md
│ └── 检测Android应用的启动与关闭.md
├── issue-9/
│ ├── Android 10ms问题:关于Android音频路径延迟的解释.md
│ ├── Android进行单元测试难在哪-part1.md
│ ├── NotRxJava懒人专用指南.md
│ ├── readme.md
│ ├── 使用Android-Studio进行单元测试.md
│ ├── 创建-RecyclerView-LayoutManager-Part-1.md
│ └── 通过Jenkins并行完成UI的自动化测试.md
├── markdown简单教程.md
├── markdown转换教程.md
├── others/
│ ├── Android-M中Intent的解析/
│ │ └── readme.md
│ ├── FaceBook推出的Android图片加载库-Fresco/
│ │ └── readme.md
│ ├── Google推荐的图片加载库Glide介绍/
│ │ └── readme.md
│ ├── InstaMaterial概念设计系列/
│ │ └── 实现Instagram的Material Design概念设计/
│ │ └── readme.md
│ ├── VectorDrawable系列/
│ │ ├── VectorDrawable – 第一章/
│ │ │ └── readme.md
│ │ └── VectorDrawable – 第二章/
│ │ └── readme.md
│ ├── 上传拍下的照片、视频到服务器/
│ │ └── readme.md
│ ├── 如何在本地搭建一个Android应用crashing跟踪系统-ACRA/
│ │ └── readme.md
│ ├── 深入浅出Android 新特性-Transition-Part-1/
│ │ └── readme.md
│ ├── 深入浅出Android 新特性-Transition-Part-2/
│ │ └── readme.md
│ ├── 清晰的软件架构/
│ │ └── readme.md
│ ├── 简化Android的UI开发/
│ │ └── readme.md
│ └── 自动化截图-应用分发时的自动截图方案/
│ └── readme.md
├── rxjava/
│ ├── chap1.md
│ ├── chap2.md
│ ├── chap3.md
│ ├── chap4.md
│ ├── chap5.md
│ ├── chap6.md
│ ├── chap7.md
│ └── chap8.md
├── software-architecture-patterns/
│ ├── chap-5.md
│ ├── chapter01-BillonWang.md
│ ├── chapter02-chaossss.md
│ ├── chapter03-Mr.Simple.md
│ ├── chapter04-dupengwei.md
│ ├── readme.md
│ ├── 软件架构模式.md
│ └── 附录2015-4-11-charli.md
├── template.md
├── the-bad-guys/
│ └── readme.md
└── 翻译项目协作流程.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
*.project
*.classpath
bin/
gen/
*.DS_Store
*/bin/
*/gen/
.settings/
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Android开发技术前线 ( android-tech-frontier )
> 一个定期翻译、发布国内外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目,让我们的技术跟上国际步伐。
> 我们翻译的文章在能够联系到作者的情况下都会在获得作者授权后进行翻译,并且公开发布。发布的文章中都会保留原文链接、作者名,如有相关的版权协议我们也会一并附上。目前已经联系到的作者列表请参考[授权文档](authorization.md);
---------
[Android App内存泄漏自动分析工具 - MMAT发布](https://github.com/hehonghui/mmat)
## 可阅读文章列表
* [请移步到wiki](https://github.com/bboyfeiyu/android-tech-frontier/wiki)
## 其他学习资源
* [Android 源码设计模式分析 地址](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis)
* [Android优秀学习资料整理](the-bad-guys/)
* [软件架构模式](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)
## 版权信息
<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>进行许可。
================================================
FILE: android-blog/Android性能优化系列/readme.md
================================================
## Android 应用性能优化系列
>原文链接分别为 :
>
>* [https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE)
* [https://www.udacity.com/course/ud825](https://www.udacity.com/course/ud825)
* 译者 : [胡凯](http://hukai.me)
1. [Android性能优化典范](http://hukai.me/android-performance-patterns/)
2. [Android性能优化之渲染篇](http://hukai.me/android-performance-render/)
3. [Android性能优化之运算篇](http://hukai.me/android-performance-compute/)
4. [Android性能优化之内存篇](http://hukai.me/android-performance-memory/)
5. [Android性能优化之电量篇](http://hukai.me/android-performance-battery/)
================================================
FILE: android-blog/Google+ 团队的 Android UI 测试/readme.md
================================================
Google+ 团队的 Android UI 测试
---
>
* 原文链接:[How the Google+ Team Tests Mobile Apps](http://googletesting.blogspot.sg/2013/08/how-google-team-tests-mobile-apps.html)
* 译者:[allenlsy](http://allelsy.com)
* 译者博文地址:[http://allenlsy.com/android-ui-tests-in-google-plus-team/]()
* 校对者:
Android 测试主要分为3个类型:
#### 单元测试(Unit Test)
区分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)。
#### 封闭UI测试 (Hermetic UI Test)
这个测试方法使得测试不需要外部依赖和网络请求。这样做的主要目的是提高测试速度,减少测试时的外部影响,毕竟网络调用是相对很慢的。[Espresso](http://www.youtube.com/watch?v=T7ugmCuNxDU)可以用来模拟用户的UI操作。
#### Monkey Test
Monkey Test 就好像一只猴子在测试app一样,没有任何规律的在你的app上胡按。计算机运行monkey test的时候,每秒钟能做出几千个UI动作(可以配置这个频率),比如点击和拖拽。所以这个测试可以算是一个压力测试,用来检测[ANR](http://developer.android.com/training/articles/perf-anr.html)。
---
Google+ 团队总结了一些 UI 测试时的经验和策略。
#### 策略1: 不要使用 End-to-end 测试作为UI测试
先看一些定义:__UI 测试__ 是为了确保对于用户的UI动作,app能返回正确的UI输出。__End-to-end测试(E2E test)__ 是通过客户端和后台服务器的交互测试整个系统。下面这个图在展示了测试步骤:

通常做UI测试,你需要后台服务器,所以可能产生网络调用。所以UI测试和E2E测试很像。但是在E2E测试中会遇到很多困难:
* 测试速度缓慢
* 网络请求会失败
* 难以Debug
下面看看如何解决这些问题。
#### 策略2:使用伪服务器做封闭UI测试
这个策略中,你可以通过假的后台服务器来避免网络请求,以及其他外部依赖。技术上,你就需要在app本地提供返回数据了。有很多办法可以做到,比如手动做一次网络请求,把response保存下来,在测试的时候重复这个response。这样你就做了一个封闭在本地的伪服务器
当你有了这个伪服务器,你还需要给这个伪服务器写测试。于是这是,你的E2E测试就分为了服务器测试,客户端测试和集成测试。

现在这样的解决方案,你需要自己维护伪服务器,本地数据库和tests了。
下面这是E2E 测试的示例图:

这是使用了伪服务器的封闭UI测试

其区别在于:Frontend Server的几个数据源变了。由原来的真实后端,变成了封闭服务器,或者是mock服务器。这个在测试调用网络API的时候非常有用。
#### 策略3:使用Dependency Injection
Dependency Injection(依赖注入)可以帮助生成测试数据。我推荐选择使用[dagger](http://square.github.io/dagger/)作为依赖注入框架。
依赖注入在UI test和unit test都中都可以用于生成假数据。在instrumentation test框架中,测试用的apk文件和测试时运行的app,是在同一个进程下面,所以测试代码可以调用app代码。你还可以覆盖app的classpath,通过这种方式注入假数据。比如你可以用依赖注入来伪造一个网络连接的实现,调用这个网络连接的时候就可以提供假数据。

### 策略4:把app分为小的libraries
这个方法可以更好地模块化你的app。你的app被分为更小的类库之后,你可以为这些类库添加他们自己的UI依赖或gradle库依赖。
当你有了自己的库,并提供依赖注入的支持,那么你可以为各个库写测试app。最后,可以写__集成测试__来确保类库直接的合作正确。
比如我们有一个登陆功能的库,那么我可以写一个测试app只为这个登陆功能库:

#### 总结:
1. 不要用E2E测试来代替UI测试。更好的做法是用单元测试 + 集成测试 + UI测试。
2. 使用封闭测试策略
3. 使用依赖注入
4. 把app分为不同的小组件小类库,并分别写测试,然后再写集成测试来确保各组件之间的交互正确。
5. 模块化 UI 测试已经被证明了比E2E测试快,并且十分稳定。这样的测试又能极大的提高开发效率。
================================================
FILE: android-blog/readme.md
================================================
## Android官方博客
================================================
FILE: androidweekly/Android中的人脸检测入门.md
================================================
Android中的人脸检测入门
---
> * 原文链接 : [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)
* 原文作者 : [Paul Trebilcox-Ruiz](http://tutsplus.com/authors/paul-trebilcox-ruiz)
* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn)
* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载!
* 译者 : [LangleyChang](https://github.com/LangleyChang)
* 校对者: [desmond1121](https://github.com/desmond1121)
* 状态 : 已完成
随着Play服务8.1中引入了视觉库,作为一个开发者,Face Detection让你可以更容易的通过分析视频或图像来定位人脸(face)。一旦有了一个图像中人脸的列表,你就能获取到每个人脸的相关信息,比如方向,笑脸的概率,某人是睁眼还是闭眼,还有他们脸上特定的关键点(landmark)。
这些信息在很多应用中都有用,比如一个相机应用,它可以在每个人都在睁着眼笑的时刻拍一张照片,或者给图片加一些搞笑的效果,比如牛角什么的。有一点很重要,注意人脸检测(Face Detection)并不是人脸识别(facial recognition)。尽管都是在获取一张脸的信息,(Play服务中的)视觉库并不会用这些信息来分辨两张脸是不是来自同一个人。
这个教程会使用一张静止的图片来跑Face Detection API,然后获取有关照片中人物的信息。同时,还会在其上覆盖图形来展示获取到的信息。这个教程的所有代码都可以在[GitHub](https://github.com/tutsplus/Android-PlayServices-FaceDetection)上找到。

搞笑效果的例子,在人脸上添加牛角
## 1. 建立项目
为了把视觉库添加到你的项目中,你需要把Play服务8.1或更高版本导入到你的项目。这个教程仅仅导入了Play服务视觉库(Play Servcies Vision library)。打开你项目的**build.gradle**文件,把如下的编译行添加到`dependencies`结点中
```groovy
compile 'com.google.android.gms:play-services-vision:8.1.0'
```
一旦你在你的项目中包含了Play服务,你就可以关掉**build.gradle**文件,然后打开**AndroidManifest.xml**。你需要在`manifest`的`application`结点添加一个`meta-data`项来定义人脸检测的依赖。
```xml
<meta-data android:name="com.google.android.gms.vision.DEPENDENCIES" android:value="face"/>
```
完成了`AndroidManifest.xml`的建立,你就可以直接关掉它了。接着,你需要创建一个叫`FaceOverlayView.java`的新类。这个类继承了`View`,并且包含着项目中人脸检测的逻辑,其中包括显示分析的位图并且在图像上绘制用以示意的点。
现在,在类的开头添加一个成员变量并且定义构造函数。`Bitmap`对象用来存储被分析的位图,`Face`对象中的`SparseArray`用来存储位图中发现的每张人脸。
```Java
public class FaceOverlayView extends View {
private Bitmap mBitmap;
private SparseArray<Face> mFaces;
public FaceOverlayView(Context context) {
this(context, null);
}
public FaceOverlayView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FaceOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
```
接着,在`FaceOverlayView`中添加一个新的方法叫`setBitmap(Bitmap bitmap)`。这个方法当前只是简单把传给他的位图存起来,一会儿你要用这个方法来分析图像。
```Java
public void setBitmap( Bitmap bitmap ) {
mBitmap = bitmap;
}
```
下一步,你需要一个位图。我已经在[GitHub](https://github.com/tutsplus/Android-PlayServices-FaceDetection)上的示例项目中包含了一张,但是你也可以拿你喜欢任何图片和Face Detection玩玩,看看哪些好使,哪些不好使。这个教程假设你的图片名为`face.jpg`。
把你的图片放在`res/raw`目录下之后,打开`res/layout/activity_main.xml`。这个布局包含了一个`FaceOverlayView`引用,这样就可以把它显示在`MainActivity`中。
```xml
<?xml version="1.0" encoding="utf-8"?>
<com.tutsplus.facedetection.FaceOverlayView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/face_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
```
布局定义好了,现在打开`MainActivity`,在`onCreate()`中建立`FaceOverlayView`。怎么建立呢?你先获取一个这个view的引用,接着从raw目录的输入流中读取`face.jpg`文件,然后把它转换成一个位图。一旦你拿到了这个位图,你就可以调用`FaceOverlayView`的`setBitmap()`方法把这幅图像传给你自定义控件了。
```Java
public class MainActivity extends AppCompatActivity {
private FaceOverlayView mFaceOverlayView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFaceOverlayView = (FaceOverlayView) findViewById( R.id.face_overlay );
InputStream stream = getResources().openRawResource( R.raw.face );
Bitmap bitmap = BitmapFactory.decodeStream(stream);
mFaceOverlayView.setBitmap(bitmap);
}
}
```
## 2.检测人脸
既然你的项目已经建立完成了,现在就该开始检测人脸了。在`setBitmap(Bitmap bitmap)`中你需要创建一个`FaceDetector`。可以通过用一个`FaceDetector.Builder`来创建。它允许你定义多个参数,这些参数影响着你多快能检测出人脸和`FaceDetector`还能生成其他什么数据。
你选择什么设置取决于你想在应用中做什么。如果你开启了关键点(landmark)搜索,那么检测人脸的速度就会慢一些。这和程序设计的大多数时候一样,鱼和熊掌不可兼得。想了解更多关于`FaceDetector.Builder`中的可用选项,你可以查看Android开发网站的官方文档。
```Java
FaceDetector detector = new FaceDetector.Builder( getContext() )
.setTrackingEnabled(false)
.setLandmarkType(FaceDetector.ALL_LANDMARKS)
.setMode(FaceDetector.FAST_MODE)
.build();
```
同时,你也需要看看`FaceDetector`是否可用。当一个用户第一次在他的设备上使用人脸检测的时候,Play服务需要跳出来加载一系列小的原生库来处理你的请求。虽然大多数情况下这都能在你的应用结束前完成,你还是需要处理加载失败的意外情况。
如果`FaceDetector`是可用的,那你就可以把你的位图转成一个`Frame`对象,然后把它传给检测器来获取图像中关于人脸的数据。完成了以后,你需要释放检测器,防止内存泄漏。当人脸检测完成了以后,你就可以调用`invalidate()`来触发这个控件的重绘了。
```Java
if (!detector.isOperational()) {
//Handle contingency
} else {
Frame frame = new Frame.Builder().setBitmap(bitmap).build();
mFaces = detector.detect(frame);
detector.release();
}
invalidate();
```
既然你已经从图像中检测到了人脸,现在就该用它们了。在这个例子中,你简单的在每个脸周围画一个绿框。因为`invalidate`已经在检测完人脸后调用了,所以你可以在`onDraw(Canvas canvas)`中添加必要的逻辑。这个方法得确保位图和人脸都已经设置好了,然后就开始在画布上绘制位图,再在每个脸的周围绘制一个框。
因为不同的设备有不同的显示尺寸,你需要跟踪位图的缩放大小,让整个图片在不同的设备上都时刻可见,上面的覆盖物也要合适的绘制。
```Java
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if ((mBitmap != null) && (mFaces != null)) {
double scale = drawBitmap(canvas);
drawFaceBox(canvas, scale);
}
}
```
`drawBitmap(Canvas canvas)`将你的位图绘制在画布上,并且把它的大小调整合适。同时,它还会返回一个系数以正确调整其他尺寸的比例。
```Java
private double drawBitmap( Canvas canvas ) {
double viewWidth = canvas.getWidth();
double viewHeight = canvas.getHeight();
double imageWidth = mBitmap.getWidth();
double imageHeight = mBitmap.getHeight();
double scale = Math.min( viewWidth / imageWidth, viewHeight / imageHeight );
Rect destBounds = new Rect( 0, 0, (int) ( imageWidth * scale ), (int) ( imageHeight * scale ) );
canvas.drawBitmap( mBitmap, null, destBounds, null );
return scale;
}
```
每一个检测出的人脸都有一个其左上角的位置信息。`drawFaceBox(Canvas canvas, double scale)`这个方法比较有趣,它会用利用这个位置按照每张脸的宽度和高度画一个绿色的矩形把它们都圈出来。
你需要定义你的`Paint`对象,然后循环遍历`SparseArray`的每一个`Face`找到它的位置,宽度和高度,然后用这些信息在画布上画出矩形。
```Java
private void drawFaceBox(Canvas canvas, double scale) {
//paint should be defined as a member variable rather than
//being created on each onDraw request, but left here for
//emphasis.
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
float left = 0;
float top = 0;
float right = 0;
float bottom = 0;
for( int i = 0; i < mFaces.size(); i++ ) {
Face face = mFaces.valueAt(i);
left = (float) ( face.getPosition().x * scale );
top = (float) ( face.getPosition().y * scale );
right = (float) scale * ( face.getPosition().x + face.getWidth() );
bottom = (float) scale * ( face.getPosition().y + face.getHeight() );
canvas.drawRect( left, top, right, bottom, paint );
}
}
```
现在,你的应用应该能跑了,并且你能看到图像中每一个检测出来的脸上都用矩形框了起来。注意,现在Face Detection API仍然相当稚嫩,所以它可能不会检测出每一张脸。你可以折腾折腾`FaceDetector`中的其他设置,看看能不能获得一些数据,尽管我也不能保证。

人脸被检测了出来并用矩形框出
## 3.理解关键点
关键点是一个人脸中一些感兴趣的点。Face Detection API不用关键点来检测人脸,但是可以检测到脸的整体轮廓之后再寻找关键点。这就是为什么发现关键点在是一个可以用`FaceDetector.Builder`开启的可选选项。
你可以把这些关键点作为一个额外的信息源,比如对象的眼睛在哪里,这样你就可以在你的应用中恰当的交互。可被找到的关键点共有十二个:
* 左右眼
* 左右耳
* 左右耳廓尖
* 鼻子基部
* 左右脸颊
* 嘴的左右角
* 嘴基部
哪些关键点是可用的取决于检测到的脸的角度。举个例子,某人的侧脸中只能检测到一个可见的眼睛,这意味着另一个眼睛是检测不到的。下面的表格概括了基于人脸的欧拉Y角度(左右方向)哪些关键点是可以被检测到的
| 欧拉 Y 角度 | 可见的关键点 |
| ------------- |:-------------:|
| < -36° | 左眼,嘴的左半边,左耳,鼻子基部,左脸颊 |
| -36° to -12° | 嘴的左半边,鼻子基部,嘴的底部,右眼,左眼,左脸颊,左耳尖 |
| -12° to 12° | 右眼,左眼,鼻子基部,左脸颊,右脸颊,嘴的左半边,嘴的右半边,嘴的底部 |
| 12° to 36° | 嘴的右半边,鼻子基部,嘴的底部,左眼,右眼,右脸颊,右耳尖|
| > 36° | 右眼,嘴的右半边,右耳,鼻子基部,右脸颊 |
不管你信不信,在你的应用中使用关键点也是非常的容易,因为你已经在人脸检测的时候把它们包含到了你的项目中了。你只需要调用一个`Face`对象的`getLandMarks()`就能获取到一个`LandMark`对象的`List`,这样你就能使用它了。
你这个教程中,你将会在每个检测出的关键点上绘制一个小圆圈,只需调用一个新的方法`drawFaceLandmarks(Canvas canvas, double scale)`。这个方法是在`onDraw(Canvas canvas)`调用,而不是`drawFaceBox(Canvas canvas)`。它会获取每个关键点的位置,根据位图的比例调整它的大小,然后绘制出示意关键点的圆圈。
```Java
private void drawFaceLandmarks( Canvas canvas, double scale ) {
Paint paint = new Paint();
paint.setColor( Color.GREEN );
paint.setStyle( Paint.Style.STROKE );
paint.setStrokeWidth( 5 );
for( int i = 0; i < mFaces.size(); i++ ) {
Face face = mFaces.valueAt(i);
for ( Landmark landmark : face.getLandmarks() ) {
int cx = (int) ( landmark.getPosition().x * scale );
int cy = (int) ( landmark.getPosition().y * scale );
canvas.drawCircle( cx, cy, 10, paint );
}
}
}
```
在调用了这个方法之后,你应该能看到许多绿色的小圆圈覆盖在检测到的人脸上,如下面的例子所示。

在检测到的关键点之后放置圆圈
## 4.额外的人脸数据
人脸的位置和关键点是很有用,但是你还可以通过一些`Face`对象的内置方法在检测到的人脸中发现更多的信息。`getIsSmilingProbability()`,`getIsLeftEyeOpenProbability()`和`getIsRightEyeOpenProbability()`方法会返回一个0.0到1.0的浮点数,你可以用其确定眼睛是否睁开或者检测到的这个人是否在笑。返回值越接近1.0,这个人就越有可能在小或者他的左右眼越有可能是睁开的。
你也可以通过检查图像的欧拉值找出一张图像中人脸在Y轴和Z轴上的角度。Z欧拉值的一直都能收到。但是,要想接受到X值你必须使用精确模式。在下面的代码片段中你会看到如何获取这些值。
```Java
private void logFaceData() {
float smilingProbability;
float leftEyeOpenProbability;
float rightEyeOpenProbability;
float eulerY;
float eulerZ;
for( int i = 0; i < mFaces.size(); i++ ) {
Face face = mFaces.valueAt(i);
smilingProbability = face.getIsSmilingProbability();
leftEyeOpenProbability = face.getIsLeftEyeOpenProbability();
rightEyeOpenProbability = face.getIsRightEyeOpenProbability();
eulerY = face.getEulerY();
eulerZ = face.getEulerZ();
Log.e( "Tuts+ Face Detection", "Smiling: " + smilingProbability );
Log.e( "Tuts+ Face Detection", "Left eye open: " + leftEyeOpenProbability );
Log.e( "Tuts+ Face Detection", "Right eye open: " + rightEyeOpenProbability );
Log.e( "Tuts+ Face Detection", "Euler Y: " + eulerY );
Log.e( "Tuts+ Face Detection", "Euler Z: " + eulerZ );
}
}
```
## 结论
在这个教程中,你已经学到了Play服务视觉库的一个主要组件,**Face Detection**。你现在知道如何在一张静止的图像中检测人脸,如何获取其中的信息,然后从每张人脸中找到重要的关键点。
用你学到的东西,你应该可以在你自己的应用中添加一些很棒的特性,比如添加静态图像、在视频中跟踪人脸或者你能想到的任何创意。
================================================
FILE: androidweekly/Android性能案例研究续集/readme.md
================================================
# Android性能案例研究续集
---
> * 原文链接 : [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)
> * 译者 : [shenyansycn](https://github.com/shenyansycn)
> * 校对 : [Mr.Simple](https://github.com/bboyfeiyu)
两年前,我发表了名为Android Performance Case Study的文章来帮助Android开发者了解什么工具和技术能被应用到识别、追踪和解决性能问题上。
这篇文章的示例程序叫Falcon Pro,是由Joaquim Vergès设计和开发的一个Twitter客户端。感谢Joaquim让我以Flacon Pro为例来处理我做演示。一切都很顺利,直到Joaquim开始开发Falcon Pro 3,在发布他新的应用之前不久,Joaquim因为需要解决一个影响滚动的性能问题他联系到了我(这一次我依然没有他的源码可以参考)。
Joaquim使用了所有工具来检测问题所在,但是都发现与之前猜测的原因无关。比如,他发现并不是overdraw引发问题。于是他将范围缩小到ViewPager类,他发给我如图1-1:

图1-1
Joaquim使用系统的GPU图形分析工具发现了帧速的下降。左边的截图展示了没有Viewpage的滚动性能时间轴,右边的展示了有Viewpager的时间轴(他使用2014年的Moto X获得这些数据)。这个问题根源看起来非常明显。
我的第一反应是ViewPager是不是误用了硬件加速。我们观察到的这个性能问题可能在List滚动时每一帧硬件层都被更新了。系统的 hardware layers updates debugging tool 没有提供有价值的信息。我检查了两次HierarchyViewer, 但ViewPager表现令我感到满意。(相反,我认定它不太可能出现问题)
我转而使用了另一个强大的、不常用的工具: Tracer for OpenGL。我前一篇文章解释了这个工具的更多细节。这个工具收集了所有你想知道的UI工具发送给GPU的绘画命令。
Android4.3及以前:从Android4.3我们引进了reordering and merging of drawing commands后,Tracer变得难以使用。reordering and merging of drawing commands是一个令人惊讶的有用优化,但是他阻止了来自view的组绘画命令。使用如下命令(在启动你的应用前)你可以通过关闭显示list优化来恢复旧的行为。
分析OpenGl Traces:蓝色的命令显示屏幕上绘制像素的GL操作。其他所有被用于传输数据或者设置状态命令能很容易的被忽略掉。每次你点击蓝色的命令,Tracer会更新Detail选项卡,在你点击的被执行后立即显示当前渲染对象的内容。通过一个接一个的点击,你可以重建每一帧。这几乎就是我用Trace分析性能问题的流程。了解被渲染的一帧都提供了什么。
当仔细看收集到的traces时,我惊奇的发现了一些SaveLayer/ComposeLayer命令块,r如图1-2。

图 1-2
这些块表明创建和合成了临时性的硬件层。这些临时硬件层被不同的Canvas.saveLayer()创建。当下面的特殊条件被满足时,UI 系统调用 Canvas.saveLayer()函数来绘制视图,并且alpha值小于1:
* getAlpha() 返回一个小于1的值
* onSetAlpha() 返回 false
* getLayerType() 返回LAYER_TYPE_NONE
* hasOverlappingRendering() 返回 true
在一些演讲中,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版,因此多个临时硬件层的使用可能是引起这个性能问题的根源。
更重要的问题是:是什么创建了这些临时层?Tracer给了我们答案。如果你看到了Tracer的截屏,你能看到仅是一组OpenGL的SaveLayer命令在一个小的渲染对象循环调用了。现在让我们看下应用截图, 如图1-3:

图1-3
你看到顶部的几个小圆点了么?这是ViewPager指示器,用于显示使用者的页面位置。Joaquim使用了一个第三方库来画这些指示器。当前页面的指示器是一个白色的圆点,其他页面的指示器是一个灰色的圆。我说“什么导致了它显示为灰色”,因为这些圆实际上是半透明的白色圆点。这个库中对于每一个圆都用了一个View(这本身就是浪费)并调用setAlpha()改变他们的颜色。
这里有一些修复这个问题的几个解决方案:
* 使用一个可定制的颜色来代替在View上设置不透明度;
* 使hasOverlappingRendering()返回false,然后系统会为你设置一个适当的alpha值到画笔上;(译者注 : 对于有重叠内容的View,系统简单粗暴的使用 offscreen buffer来协助处理。当告知系统该View无重叠内容时,系统会分别使用合适的alpha值绘制每一层。)
* 使onSetAlpha()返回true,并设置Paint的alpha来绘画灰色的圆。
最合适的是第二种方法,但最低支持API level 16.如果你必须支持老版本,可以使用另外两个方法中的一个。我相信Joaquim会抛弃第三方库并使用他自己的指示器。
我希望这篇文章中市我们能够意识到性能问题很可能出现在看似无害的操作上。请记住:不要假设,实践出真理!
================================================
FILE: androidweekly/Kotlin for Android (II)创建一个工程/readme.md
================================================
Kotlin for Android (II)创建一个工程
---
>
* 原文链接 : [Kotlin for Android (II): Create a new project](http://antonioleiva.com/kotlin-android-create-project/)
* 译者 : [Lollypo](https://github.com/Lollypo)
* 校对者: [chaossss](https://github.com/chaossss)
* 状态 : 校对中
当我从[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)这篇文章 )
对于本系列文章, 我将创建一个我早些时候创建的[Bandhook](https://play.google.com/store/apps/details?id=com.limecreativelabs.bandhook)的简化版本, 它基本上就是连接到一个基于RESTful的音乐API然后接收一些乐队的信息. 链接到 [Bandhook Kotlin on Github](https://github.com/antoniolg/Bandhook-Kotlin) 查看源代码.
###创建一个新项目然后下载Kotlin插件###
就像你平常做的那样,我们只需要用Android Studio创建一个带Activity的基本Android项目。
一旦完成,我们需要做的第一件事就是去下载Kotlin插件. 去到Android Studio的系统设置中然后查找plugins.之后,再次使用搜索找到Kotlin插件,安装并重启IDE。

###添加Kotlin插件的依赖到的应用程序的build.gradle中###
该项目的build.gradle需要添加一个新的依赖,这个依赖将会被Kotlin插件要求以在主Module中使用:
```gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.3'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.0'
}
}
```
###配置Module的build.grade###
首先, 应用Kotlin插件:
```gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
```
接着, 添加Kotlin库到你的依赖:
```gradle
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.0'
}
```
最后, 你需要添加我们在下一个步骤创建的Kotlin文件夹:
```gradle
android {
compileSdkVersion 22
buildToolsVersion "22.0.0"
...
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
```
或者,你可以跳过这一步,当做完下一个步骤时,使用这个Android Studio的操作:

我更倾向于手动去做以保持我的Gradle文件有整洁有序, 但第二个选项可能较为容易些。
###创建Kotlin文件夹###
如果你将项目的视图从‘Android’转到‘Project’,那将会非常容易。依次选择‘app->src->main’ 然后创建一个名为 ‘kotlin'的文件夹:

###将Java activity转换成Kotlin文件###
Kotlin插件能将Java转换为Kotlin类. 我们可以轻松的通过‘Code’菜单中的‘Convert Java File to Kotlin File'选项转换当前的Activity到Kotlin类 :

IDE将建议你移动新文件到Kotlin文件夹,点击‘Move File’(或者手动完成,假如你没看到这个选项).
```java
public class MainActivity : ActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
val id = item.getItemId()
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true
}
return super.onOptionsItemSelected(item)
}
}
```
###主要区别###
看一看之前的代码, 我们可以看到一些明显的差异。 其中很大一部分我们将会在下一篇文章讲解到:
- 使用冒号,而不是'extends'。
- 显式使用‘override': 在Java中, 我们可以使用一个注释使我们的代码更清晰,但它不是必要条件. Kotlin将迫使我们使用它.
- 函数则使用‘fun’关键字: Kotlin是一个面向对象的函数式语言, 因此可能会与其他语言类似,例如Scala. Java方法被函数的形式表示。
- 函数参数命名规则不同: 类型和名称都写在相反的位置,并用冒号隔开。
- 分号可选: 我们不需要在行的结尾处加上分号。如果我们想要也可以加上, 但如果我们不这样做,它就可以节省大量的时间,并使我们的代码整洁。
- 其他小细节: 在简介一文中, 我已经说到了 ‘?’ 的意义. 这表明参数可以为空。NULL的处理方式不同于Java。
###总结###
也许我们会认为使用一门新语言将会非常困难, Kotlin被JetBrains团队开发出来的,要成为最容易和可交互的语言用来覆盖那些Java的不足之处。由于Android Studio也是基于JetBrains的产品,这将让集成到这个IDE中并且开始工作非常简单。
下一篇文章将介绍一些让我们在使用Kotlin开发Android应用程序时,能让开发过程更简单的奇巧淫技。
================================================
FILE: androidweekly/Kotlin for Android (III)-扩展函数与默认值/readme.md
================================================
Kotlin for Android (III)/ 扩展函数与默认值
---
>
* 原文链接 : [Kotlin for Android (III): Extension functions and default values](http://antonioleiva.com/kotlin-android-extension-functions/)
* 译者 : [Lollypo](https://github.com/Lollypo)
* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)
* 状态 : 校对完成
现在你已经了解[Kotlin基础](http://antonioleiva.com/kotlin-for-android-introduction/)与[如何配置你的项目](http://antonioleiva.com/kotlin-android-create-project/),是时候谈论Kotlin能为我们做哪些Java做不到的有趣的事情了.请记住,如果你对Kotlin语言有任何疑问,可以参考[官方文档](http://kotlinlang.org/docs/reference/).这份文档条理分明、简单易懂, 而且我这篇文章不会涉及到语言的基础部分.
### 扩展函数
Kotlin的扩展函数功能可以让我们添加新的函数到现有的类上而不必去修改它本身.例如,我们可以轻松的的通过扩展函数语法将一个显示Toast的函数添加到Activity类中:
```java
fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){
Toast.makeText(this, message, duration)
}
```
我们可以在任意的地方声明这个函数(例如一个工具类文件), 并在我们的Activities中当作一个普通方法使用:
```java
override fun onCreate(savedInstanceState: Bundle?) {
super<BaseActivity>.onCreate(savedInstanceState)
toast("This is onCreate!!")
}
```
声明一个扩展函数跟添加类名到函数名上一样简单.这个函数将被作为导入的类使用.
这可以帮助我们简化代码而且让封闭的类打破局限.但是我们必须小心和适度的使用.最后,这些函数通常会替代工具类.工具方法通常是静态的且不能被修改. 因此,过度使用通常是表示我们懒得创建一个委托类.
这里是另一个有趣的例子,让我解释另一个有趣的概念:具体类型(reified types).
```java
inline public fun <reified T : Activity> Activity.navigate(id: String) {
val intent = Intent(this, javaClass<T>())
intent.putExtra("id", id)
startActivity(intent)
}
```
内联函数可以使用具体类型,这意味着我们可以从内部函数取得类而不用通过将类类型作为参数.
> 内联函数的有点不同于一般的函数.内联函数将会在编译过程中替换代码而不是真的调用一个函数. 这会简化某些场景.例如,如果我们将一个函数作为参数,常规函数将在内部创建一个包含该函数de对象,.另一方面,内联函数会在函数被调用的地方替换掉,因此它真的不需要一个内部对象.
> ```java
navigate<DetailActivity>("2")
```
使用具体类型,我们可以在函数内部创建一个意图, 并使用扩展函数,我们可以直接调用startActivity().
### 可选参数与默认值
由于有参数默认值和构造函数,你永远不需要重载函数了.一个声明可以满足你所有的需求.回到Toast示例:
```java
fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){
Toast.makeText(this, message, duration)
}
```
第二个参数指的是Toast的持续时长.它是可选参数,在没有指定的情况下,将使用Toast.LENGTH_SHORT作为默认值.现在你有两种方法调用这个函数:
```java
toast("Short Toast!!")
toast("Long Toast!!", Toast.LENGTH_LONG)
```
对于第二个示例,我们也许想添加一些转换棒棒糖的参数:
```java
inline public fun <reified T : Activity> Activity.navigate(
id: String,
sharedView: View? = null,
transitionName: String? = null) {
...
}
```
我们现在有两种不同的方式调用相同的函数:
```java
navigate<DetailActivity>("2")
navigate<DetailActivity>("2", sharedView, TRANSITION_NAME)
```
甚至是第三种,虽然在大多数情况没什么意义,但是可以帮助我们理解另一种概念:我们可以使用参数名来决定我们想调用哪些参数:
```java
navigate<DetailActivity>(id = "2", transitionName = TRANSITION_NAME)
```
可选参数可以被用于默认构造函数,因此你可以通过一个声明实现许多的重载方法.自定义视图是一个特例, 因为在Java中它们需要多个构造函数才能够正常工作.我将会在下一篇文章讲解到.
## 总结
通过这两个优势,我们可以简化大量的代码甚至可以做那些Java中不可能的事情.Kotlin真的是简洁明了.下一篇文章将会讲解Kotlin的Android拓展,这可以让我们在Activities中自动注入视图,与如何在Kotlin中自定义View.
记得浏览一下[示例库](https://github.com/antoniolg/Bandhook-Kotlin)来看一下它的实际应用.
================================================
FILE: androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-1/readme.md
================================================
ListView或者RecycleView滚动时隐藏Toolbar (1)
---
>
* 原文链接 : [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/)
* 译者 : [chaossss](https://github.com/chaossss)
* 校对者: [这里校对者的github用户名](github链接)
* 状态 : 校对中
今天我打算写一篇博文给大家介绍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) 。
> 如果可以的话,我们希望当屏幕向下滚动时,App的Toolbar和FAB将离开屏幕,从而在垂直方向上为可显示区域腾出更大的空间来显示我们的内容。而当我们的屏幕向上滚动时,App的Toolbar和FAB会再次出现。
我们做出来的最终效果应该是下面这样的:

在这篇博文的讲解中,我们将会用RecyclerView作为我们的list,但这不代表其他可滚动的容器就不能实现这样的效果了,只是其他的可滚动容器(如:ListView)需要要多花一些功夫才能实现这个效果。那么我们要怎么去实现呢?我想到了两个办法:
- 对list容器加一个Padding
- 对list容器加一个Header
就我个人而言,我更想用第二种方法去实现,因为在设计代码的过程中我发现:为RecyclerView添加Header会产生几个问题,这给了我很好的机会去思考如何解决它,与此同时,在这个思考和解决问题的过程中我还能学习到更多的知识,何乐而不为呢?不过我还是会给大家简要地介绍如何使用第一种方法实现的啦!
## 那就让我们开始今天的讲解吧!
首先,我们需要创建一个工程和添加必要的工具库:
接着我们需要定义 styles.xml ,以确保我们的App没有添加Actionbar(因为我们将会使用Toolbar),同时我们App的设计风格要符合Google的 Material Design 规范。
最后我们就要创建我们activity中显示的布局:
事实上,我们只需要一个简单的布局,其中包含了:RecyclerView,Toolbar和ImageButton。但需要注意的是:我们需要把它们放在一个FrameLayout里,否则当我们隐藏Toolbar时list的上方将会出现一个空白区域,这显然不会是我们想要的效果。我们理想的效果应该是:当Toolbar被隐藏,list能在屏幕的可见区域中显示出一整个列表,而这就需要通过使Toolbar覆盖在RecycleView上面来实现。
接着来看看我们MainActivity代码吧:
就像你看到的那样,这是一个只重写了OnCreate()方法,非常非常非常简单的Activity。而且OnCreate()方法也只做了下面三件事情:
1. 初始化Toolbar
1. 获取mFabButton的引用(mFabButton是FAB的对象哦,也就是屏幕右下方的小按钮)
1. 初始化RecyclerView
为了在list中显示出内容,我们现在就要为RecyclerView创建一个Adapter啦。但在此之前,我们应该为我们list中的item添加相应的子布局以及对应的ViewHolder:
list的每一个item只需要一个text用来显示文字,非常简单!
那RecyclerAdapter该怎么实现呢:
就像你看到的这样,这是一个非常普通的RecycleView.Adapter,没有任何特别的地方,是不是感觉被骗了呐 23333~(如果你想要了解更多有关RecyclerView知识,我强烈建议你去看大牛 Mark Allison's 巨巨写的这些优秀文章 [series of posts](https://blog.stylingandroid.com/material-part-4/))
经过上面的努力,我们已经把车子的小零件组装的七七八八啦,是时候让小车子上路跑一跑,展现真正的技术了!

WTF,谁能告诉我这是什么鬼……?
我相信只要不是瞎子都会发现,App的Toolbar竟然把我们的item挡住了!!!或许部分机智的小伙伴已经发现了问题所在:出现这样的问题是因为我们在activity_main.xml里使用了FrameLayout,正是FrameLayout导致了这样的问题,而这就是我开头所提到的问题之一了。
第一个解决办法是为我们的RecyclerView添加一个PaddingTop,并将PaddingTop的值设置为Toolbar的高度。但有一个细节我们不能忽略,那就是RecyclerView会默认裁剪到子View的Padding区域,所以为了我们伟大的事业,我们必须把这个特性关掉。
经过这些修改之后,我们就能实现我们想要的效果,这就是我所说的第一种方法。但如我所说,我写这篇博文的目的,不仅仅只是教你实现这个效果,然后就完了。我想教给你实现同一个效果各种各样的方法,并且为你介绍其中的思想,让你接触到平常很难接触到的问题并教你如何解决它。有些方法固然会更加复杂(在本文中是为list添加一个Header),但你在实现过程中也能学到更多的知识,毕竟授人以鱼不如授人以渔嘛。
## 为RecycleView添加一个Header
要用第二种方法去实现这个效果,首先我们要做的就是稍微修改一下我们的Adapter:
下面是其实现原理:
我们需要定义一个常量来区分Recycler展现的item的类型。这里我需要为你介绍的是:RecyclerView是一个非常灵活的组件,RecyclerView 完全能实现你想要让list的item具有各种各样不同的布局的愿望,而此时,我们定义来区分item类型的常量就会被利用到。而这样的特性正是我们想要的——让Header成为RecyclerView的第一个item,显然这会与其余的item不一样。(第3-4行)
因而我们需要让Recycler知道它需要展示的子布局是什么类型的,在本文中我们用作类型区分的常量则是TYPE_ITEM和TYPE_HEADER。(第49-54行)
接着,我们需要修改onCreateViewHolder()和onBindViewHolder()方法,如果item的类型是TYPE_ITEM的话,使它们返回和绑定一个普通的item;如果item的类型是TYPE_HEADER的话,则返回Header。(第14-34行)
此外,由于我们的list并不仅仅只有普通的item,我们还在list中添加了Header,因此我们需要修改getItemCount()方法的返回值,让我们的返回值是普通item的总数量 + 1(第43-45行)
最后让我们来为Header布局创建一个layout和一个ViewHolder,但出乎意料的是,我们需要为Header创建的layout和ViewHolder都非常简单,唯一需要注意的是:Header的高度必须和Toolbar的高度一致。
那么这样我们就把布局弄好啦~不信你看图!

所以总的来说,我们为RecyclerView添加了一个和Toolbar有相同高度的Header,而现在我们的Toolbar把header隐藏起来了(因为header现在是一个空的view),同时,我们所有的普通item都是可见的。那么现在就让我们来实现滚动时改变Toolbar和FAB的出现和隐藏吧!
## 滚动时控制Toolbar和FAB的出现和隐藏
为了实现这个效果,我们为RecyclerView再创建一个——OnScrollListener类就够了你敢信?
我现在还要告诉你,在OnScrollListener类里,我们只需要重载onScrolled()方法就能让这个酷炫的效果成为App中秒杀用户的黑魔法!其中,onScrolled()方法的参数——dx,dy分别是水平和垂直方向上的滚动距离。但大家需要注意的是:这里dx,dy并不是代表屏幕上的物理距离,而是两个事件的相对距离。
具体的实现算法大体如下:
只有当list向上滚动且Toolbar和FAB被隐藏,抑或是当list向下滚动且Toolbar和FAB出现,我们才会计算总的滚动距离,因为这两种情况下的滚动距离才是我们实现这个效果所需要关心的。
总的滚动距离需要超过我们展现/隐藏Toolbar和FAB所在的方向的极限值才能将其展现/隐藏(你把极限值调整的越大,通过滚动展示/隐藏Toolbar和FAB需要的距离就越大)(dy>0意味着我们在向下滚动,dy<0意味着我们在向上滑动)
实际上我们并没有在我们的滚动监听类里面展现/隐藏Toolbar和FAB,我们是通过调用show()/hide()方法来展现/隐藏Toolbar和FAB的,所以调用者可以通过接口实现它。
现在我们需要为RecyclerView添加它的监听:
下面是我们通过动画改变我们的视图的方法:
当我们隐藏Toolbar和FAB的时候,我们必须把Padding也考虑进去,不然的话视图并不能够完全被隐藏。
是骡子是马,让我们拉出来溜一溜!

虽然现在看起来已经很nice了,但其实这里有一个小小的bug——如果你在list的顶部,此时临界值非常小,因而你能隐藏Toolbar,但你在list的顶部会看到有一个空白的区域。不过幸好这里有一个很简单的方法可以解决这个bug:我们可以通过检测当前list的第一个item是否为可见的,只有当它不可见,才使用我们设计的展示/隐藏逻辑。
在这样的修改后,即使第一个item是可见的并且有item被它挡住了,我们也在展示它们,除此以外的情况我们都像之前说的那样实现我们的效果。
各位观众,接下来,就是见证奇迹的时刻:

太棒了思密达!感觉之前的失败都如雨后甘霖温润我脆弱的心灵呐~
其实羞羞地说一句……这篇文章是我人生中的第一篇博文呐,如果你觉得很无聊或者我有哪里讲解错误的话千万不要喷我哦。我会在未来变得更棒哒,然后尽我所能为大家贡献更多的文章!
如果你看到这里还是通过添加Header来实现这个效果很恼火的话,用第一种方法结合HidingScrolllistener 也是可以实现这个效果的~
如果你有什么疑问的话,可以在评论区问我哦,我都会尽我所能为你解答的!
## 源码
整个项目的源码在GitHub上面都有,大家可以在这看 [repo](https://github.com/mzgreen/HideOnScrollExample)
感谢Mirek Stanek帮我校对文章,么么哒!爱你的好基友Michal Z~
如果你喜欢这篇博文的话,你可以[在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)!
================================================
FILE: androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-2/readme.md
================================================
ListView或者RecycleView滚动时隐藏Toolbar( Part 2 )
---
>
* 原文链接 : [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)/)
* 译者 : [chaossss](https://github.com/chaossss)
* 校对者: [这里校对者的github用户名](github链接)
* 状态 : 校对中
Hello,各位小伙伴,俺胡汉三又来了!!!今天我打算接着上一篇博文继续给大家讲解展现/隐藏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那样,废话不多说,我们现在进入正题吧:
在我们开始之前,我想先告诉大家的是我已经对这个项目进行了一些重构——我继承项目的 MainActivity 实现了两个新的子类:PartOneActivity 和 PartTwoActivity。源码在包 partone 和 parttwo里,你可以在这两个包里挑选你喜欢的那一个使用
下面是我们的最终效果图,我把它和 Google Play Store Toolbar 放在一起比较,大家可以感受一下:


## 准备工作 ##
在这里我不会再给大家展示 build.gradle 文件,因为这和第一部分的 build.gradle 文件是一样的,所以我们将会从这个步骤开始——为我们的Activity创建一个layout:
同样的,layout里面只有一个 RecyclerView 和一个 Toolbar (稍后再加上 Tabs)。值得注意的是,我这里的实现是使用之前说的第二种方法(为RecyclerView添加Padding)
同样的道理,由于我们的布局文件、list、RecyclerAdapter 都和之前是一样的,我在这里都不会再给大家讲解了。
那现在我们来看看 PartTwoAcitivty 的代码吧:
在 PartTwoActivty 里面仍然是简单地对 RecyclerView 和 Toolbar 进行初始化,但大家一定要记得设置 OnScrollListener 哦(第27行)
感觉大家看到这里也感觉昏昏欲睡了,因为前面提到的内容大体上都和上一篇相似。但是莫慌!俺接下来就要讲这篇博文中最有趣的部分 —— HidingScrollListener 了,请大家紧紧抱住我,跟上节奏!
如果你看过第一篇博文可能会觉得此情此景很熟悉(可能还会感觉简单一些)。我们在 HidingScrollListener 里耍了什么 tricks 呢?那就是存了与 Toolbar 的高度相关联的屏幕滚动偏移量 —— mToolbarOffset。为了简化其中的逻辑,只有当 mToolbarOffset 的取值在[0 , Toolbar的高度]之间时,我们才会实现我们的逻辑:当我们向上滚动时,mToolbarOffset的值会增加(但不会超过Toolbar的高度);当我们向下滚动时,mToolbarOffset 的值会减少(但不会低于0)。大家现在可能会有许多疑问:为什么要引用 mToolbarOffset 呀?为什么要让 mToolbarOffset 的取值范围介于那两者之间呐?别怕!你马上就会理解为什么我们要这样限制mToolbarOffset的取值了。我们必须知道的是,尽管我们极力去避免意外的发生,但现实总会出人意料,在这里也不例外,有时候 mToolbarOffset 的值就是会不可避免地在我们的取值范围之外,但由于我们的逻辑设计的限制,最终的显示效果会是闪烁一下。(例如:在屏幕上快速的挥动、滑动)这样的结果显然不是我们这些有格调的 Android 工程师想要的,因而我们需要对 mToolbarOffset 进行一定程度的裁剪,以规避这样的风险。基于这样的考虑,我们重载了 onMoved()方法 —— onMoved() 方法是一个当我们滚动视图时被调用的抽象方法。可能会吓到你,但是莫慌,继续抱住我!
接下来,我们就要回到我们 PartTwoActivity 设计之中,并且在我们的滚动监听器中实现 onMoved()方法。
是的,这就是所有内容啦。我们运行 App 后可以看到我们的最终效果:

是不是感觉自己棒棒哒~就像我们想象的那样,Toolbar 完美的随着list的滚动实现了展现/隐藏的效果。其中的功劳都得归功于我们对 mToolbarOffset 取值范围的限制。如果我们省略掉检查 mToolbarOffset 是否在[0 , Toolbar的高度]范围中取值的过程,带着完成控件的喜悦向上滚动我们的list,Toolbar 确实会如你所期望的那样离开屏幕,但与此同时,Toolbar 还会远远地,远远地,离开视图,再也不回来。然后当你满是期许地向下滚动时,你就会发现刚刚那一声再见,竟是永远。如果你想再次遇见它,你就必须想下滚动,直到 mToolbarOffset = 0。
再脑补第二种情况吧,现在你正好把 Toolbar 滚动到 mToolbarHeight = mToolbarOffset 的位置,不偏不倚。那么现在Toolbar就刚好“坐”在了list顶部,如果你向上滚动的话,无论你怎么滚,它都不会动,只会静静地坐在那儿笑看人世沧桑。而如果你向下滚动,它又成为许多年前那个明亮、可爱的小女孩了。
虽然最终的实现效果看起来非常赞,但这并不是我想要的。因为我们能在滚动过程中停止整个效果,使得 Toolbar 有一部分是可见的,另一部分又是不可见的。但悲伤的是,Google Play Games 就是这么干的,而我一直认为这是一个Bug……
## 在某一点停下 Toolbar ##
就我的认知来说,我认为滚动的Views是能够如丝般顺滑地对齐相应的位置的,就像 Chrome 应用里的 Logo/SearchBar 又或者是 Google Play Store应用里那样。我很确定我在 Material Design 的guidelines/checklist 或者是 以前听过的Google I/O 大会上听过类似的规范。
那我们现在再来看看 HidingScrollListener 的代码吧:
虽然为了实现上面提到的效果我们会让 HidingScrollListener 的代码变得更复杂一些,但是我再说一次,莫慌,抱紧我!我们现在只需要重载 RecyclerView.onScrollListener 类的 onScrollStateChanged()方法,然后按照下面那样干就行了:
首先,我们需要检查list是否处于 RecyclerView.SCROLL_STATE_IDLE 状态,以确保list没有在滚动或者挥动(因为如果list如果正在滚动或者挥动的时候,我们就需要像第一篇博文那样去考虑 Toolbar 的Y方向位置哦)
如果我们抬起了手指,而且list已经停止移动了()我们就要去检查Toolbar是不是可见的,如果是可见的,我们就需要考虑 mToolbarOffset 的值了。如果此时 mToolbarOffset 的值大于 HIDE_THRESHOLD 我们就必须把 Toolbar 隐藏起来;mToolbarOffset 的值小于 HIDE_THRESHOLD,我们则需要让 Toolbar 显示出来。
如果 Toolbar 不是可见的,我们就需要做相反的事情 —— 如果 此时mToolbarOffset(此时计算 mToolbarOffset要从顶部位置来考虑了,也就是 mToolbarHeight - mToolbarOffset) 大于 SHOW_THRESHOLD 我们就让 Toolbar显示;相反,如果 mToolbarOfffset 小于 SHOW_THRESHOLD ,我们就要再次将 Toolbar隐藏起来。
onScrolled()方法和第一篇博文的一样,我们并不需要作什么改变。我们现在最后需要做的就是在 PartTwoActivity 类里实现我们的两个抽象方法:
那么,你准备好看魔术了吗?

hey,派大星你看,是不是很酷!
现在你可能在脑补添加 Tabs 会有多麻烦了,可是兄弟啊,生活很多时候都是出人意料的呐,不信你继续往下看呀
## 添加 Tabs ##
为了添加 Tabs,首先要做的当然是为我们的 Activity 布局添加一个 tabs.xml啦
你可以从源码那发现,我并没有添加真正的 Tabs,只是在布局里面模拟了 Tabs。而以上的一切不会改变任何之前的实现,你能在里面放任何你想要放的View。下面是一些 GitHub 上符合 Material Design规范的 Tabs 实现,当然你也可以自己实现啦。
添加 Tabs 意味这他们会稍微覆盖我们的list,所以我们需要增加Padding。为了减少代码的操作复杂度,我们不会在 xml 里进行这个操作(注意把 RecyclerView 在 part_tuo_activity里的padding删掉哦),因为 Toolbar 可能会在不同的设备中切换方向时拥有不同的高度(例如在平板中),这样的话我们需要创建一大堆的 xml 去解决这些乱七八糟的烦人问题。所以我决定在代码中解决这个问题:这是非常简单的,我们只需要和 Tabs高度的总和。如果我们把 Padding设置为 Toolbar 的高度现在运行起来的话,就会发现这样的东西:

看起来很正常的样子……我们第一个item刚刚好是可见的,我们也能移动跟随着它。实际上我们在 HidingScrollListener 类里什么也没干,唯一需要的改变都是在 PartTwoActivity 里做的:
你能发现什么发生了改变吗?我们现在不妨创建一个 mToolbarContainer 的引用,但是大家要注意哦, mToolbarContainer 是 LinearLayout 对象而不是 Toolbar对象,而且在 onMove(),onHide(),和 onShow()方法中,我们都把 Toolbar 改成了 mToolbarContainer。这会使得包含了 Toolbar 和 Tabs 的Container被移动,这恰恰就是我们想要做的。
如果我们把修改后的代码运行起来会发现,实际的运行效果正好就是我们所期望的,但如果你看的认真一些你会发现,里面其实有一个小Bug。在 Tabs 和 Toolbar 之间有时候会有一条白线,虽然时间非常短,但还是很惹人讨厌呐。我个人觉得这大概是因为当Toolbar 和 Tabs被显示的时候,他们并没有像我们期望的那样同步在一起。不过万幸这不是什么无法解决的Bug~
解决办法非常简单,就是让 Toolbar 和 Tabs 的背景和父布局保持一致:
现在即使 mToolbarContainer 在显示过程中没有很好的同步在一起,白线也不会出现了。正当我打算吃根辣条庆祝我们伟大战役的胜利的时候,又出现了一个Bug………………这个Bug和我们在第一篇博文里遇到的Bug是一样的,如果我们在list的顶部,我们可以向上滚动一丢丢,如果此时 HIDE_THRESHOLD 足够小,Toolbar 就会藏起来,导致那里有一块空白区域(其实就是我们设置的Padding)在list的顶部,但是我相信你到了现在应该不会慌了,因为你已经知道所有Bug在我眼里都是非常容易解决的:
我们只需要再增加一个变量用于存储list的总滚动偏移量,当我们准备去检查我们是否应该展现/隐藏 Toolbar的时候,我们首先应该检查我们的滚动距离是否比 Toolbar 的高度要大(如果不是的话,我们再让 Toolbar 出现)
这就是今天博文要讲的一切了,让我们来看一看实际效果!

现在运行的效果简直完美啊大兄弟~即使用其他的 LayoutManagers 也不需要改变任何东西的哦:

评论区有好学的同学问了个有关存储滚动状态的问题,这确实是个小麻烦。如果我们list的item中的文字在垂直方向达到2行,在水平方向达到1行的话,我们的item高度就会变得很奇怪了……举个例子吧,如果我们滚动到垂直方向100的位置,然后旋转我们的设备,同时存储 mTotalScrolledDistance的值,在旋转之后,我们会滚动到list的顶部,然后我们会发现 mTotalscrolleddistance 的值不等于0。这个时候即使全能如我也想不到简单的办法来解决这个问题了,但是在我们的使用场景中,这样的小问题并不会有什么影响。如果你真的想要解决这个问题的话,我个人的做法是:在旋转之后把 mTotalScrolledDistance 的值重设为0 并且显示 Toolbar。
感觉今天写了好多内容啊,大家看到这里应该感觉很累了吧?不过今天这篇博文就是这个系列的最后一篇文章啦,大家能在第一篇博文中学习到知识我真的很高兴呢。大家鼓励和夸奖的话也让我很感动,我会继续写我的博客,为大家传授更多的知识,不过我也不知道下一篇博文会在什么时候写 2333。
除此以外我还想说的是,在这两篇博文中我提到的方法可能看起来运行的很好,但其实我并没有进行非常严谨的测试,所以我也不确定它们能不能被用于企业级应用中(你看我们不就遇到了好几个Bug了嘛)。这个系列的博文的初衷只是想告诉你,即使只使用标准API里面的一两个简单方法,也能够实现酷炫的效果。同时,我在写博文的过程中也发现了这些方法还有其他有趣的用法(例如:利用视差背景制作有粘性的 Tabs,就像在 Google+ 个人页面那样)。不管怎样,祝大家在写代码的过程中找到更多的快乐!
## 源码 ##
整个项目的源码都已经被上传到 [GitHub](https://github.com/mzgreen/HideOnScrollExample) ,大家可以去下载和使用哦,爱你们的 Michal Z。
如果你喜欢这篇博文的话,你可以 [在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) 。
================================================
FILE: androidweekly/Square 开源库Flow和Mortar的介绍/readme.md
================================================
Flow和Mortar的调查
---
>
* 原文链接 : [Architecting An Investigation into Flow and Mortar](http://www.bignerdranch.com/blog/an-investigation-into-flow-and-mortar/)
* 译者 : [sundroid](https://github.com/sundroid)( [chaossss](https://github.com/chaossss) 协同翻译)
* 校对者: [chaossss](https://github.com/chaossss)、[Mr.Simple](https://github.com/bboyfeiyu)
* 状态 : 完成
“在 App 开发过程中尽可能使用 Fragment 替代 Activity”,Google 官方的这个建议无疑让万千 Android 开发者开始关注、使用 Fragment。但随着使用 Fragment 的人数增多,Fragment 存在的各种问题也开始暴露,在各种 Android 社区中,已经开始有人质疑用 Fragment 替代 Activity 在应用开发中是否真的像 Google 说的那样有益。质疑 Fragment 的理由大体如下:
- 在使用 Fragment 时,我们只能选择使用默认的构造方法,而不能自由地构造我们想要的构造方法。
- 嵌套使用 Fragment 很容易出现各种奇奇怪怪的 Bug,抑或是受到种种让人郁闷的限制。
- Fragment 自身的生命周期非常复杂。
更让人哭笑不得的是,让这部分开发者坚定地站在“反 Fragment”队伍中的原因竟然是:在开发过程中使用 Fragment 完全不能让这部分 Android 开发者感受到使用 Fragment 能给他们带来的便利和愉悦;相反,使用 Fragment 给他们带来的是无尽的困然和烦恼。真不知道 Google 看到这些批评 Fragment 的帖子会想什么…………
但在我们的 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。
然而,虽然我们是 Fragment 的忠实粉丝,但本着不断学习和探索新知识的心态,我们还是对现有的 Android 库进行了相当多的研究和探索,以求能够找到 Fragment 的最佳替代物,帮助这些备受煎熬的 Android 开发者早日脱离苦海,走向 Android 开发的美丽新世界。
## 进入Flow和Mortar
奉行着想毁灭世界上所有 Fragment 的信条,Square 大概在一年前介绍了两个全新的库: Flow 和 Mortar。作为反 Fragment 教主,Square 还创造了许多很好的库:
* [Dagger](http://square.github.io/dagger/)
* [Retrofit](http://square.github.io/retrofit/)
* [Picasso](http://square.github.io/picasso/)
* [Otto](http://square.github.io/otto/)
* And so many [more](https://github.com/square)
我只想说,我相信他们,我认为任何来自Square的资源可能是有用的或者至少是有趣的,所以他们的项目都值得一看。
在我们深入了解这些库之前我想提醒大家的是,Square只在他们内部的一小部分项目中使用这些库,并且我在写本文章时这些库还在预发布阶段。也就是说,这两个库在最近几个月取得了积极的进展,这预示着一个值得尊敬的未来,虽然库就像流沙,随时可能改变,崩溃甚至停止发布,但库所依赖的核心架构原则是一成不变的。
##体系架构
首先,我们先来看下 Android 应用的体系架构,在 Android Honeycomb 被使用之前,甚至在Fragment 出现之前,开发 Android 应用的标准模式是创建许多 Activity。在那个时候最常见的现象是:大多数开发者都没有规范地遵循 MVC模式进行开发,不过这也很正常。因为模型(Model)依赖于数据,传统的一些数据库或者是以 JSON 的形式存储的 Web 请求,抑或是各种各样的java对象枚举。开发者们很高兴地通过 XML 布局去为 Activity 设置 View ,而且 View 的控制器就是每一个屏幕显示的 Actvitiy 自身。
虽然这只是一个简要的概述,但是你能从中了解到 Android 与 MVC 模式是如何自然契合的。

随着 Fragment 在 Honeycomb 中的出现,Android 团队也让处理不同形式的事件变得更简单。到了今天,标准的 Android 应用架构已经转变为由一小部分的 Activity 和 许多 Fragment 构成,使得我们的 App 能够在 手机、平板、智能手表甚至是太空船上跨平台使用。

在这样的愿景下,有关 Fragment 的一切都是美好的,Fragment 变得流行起来,将一个 Activity 分解为几个 Fragment 是被提倡的。除此以外,即使 Activity 常常被简化为 Fragment 的持有者,或者是 Fragment 和 系统之间的接口,Android 应用的架构仍然遵循着 MVC 模式。
但到底是 Activity 不能实现我们 App 跨平台使用的愿望,还是我们没有用正确的方式使用 Activity呢?也许,如果将 Activity 与 自定义 View结合在一起使用,说不定不需要 Fragment 就能让 Activity 实现跨平台使用的目标呢。使用 Flow 和 Mortar库背后的关键目的就是探索这个问题,并得到确切的答案。Flow 和 Mortar 的工作通过用自定义 View 代替 Fragment,并使用注入自定义 View 中的特定 Controller 对象,控制视图,以此允许我们通过 View 来代替 Fragment 完成工作。

我们将在我们的讨论中构建这个图的中间部分,弄清楚如何在不使用 Fragment 的情况下把不同的视图碎片拼凑到一个 Activity 里。我们会看着标准MVC架构演变成完全不同的东西,这将大量涉及到咱们的Flow和Mortar。
那么,Flow和Mortar到底是什么?它们又是如何起作用的呢?
##Flow
Flow 将一个应用分成一个逻辑上的 Screen组合,Screen不是任何形式的特殊的库对象,而是一个被创造来代表我们应用视图的普通java对象(POJO)。每一个Screen是这个app里面自包含的段,他们有自己的功能和意图。一个Screen的用处和传统Activity的用处没有什么不同,应用程序中的每一个Screen都对应于一个特定的位置,有点像一个Android中的URL网页或者是特定的隐式Intent。所以,Screen类可以被看作是应用中某个部分自带的可读定义。
我们应用中的每一个Activity将会成为一个 Flow 对象,Flow对象在返回栈中保存了 Screen 的记录,和 Activity 或者 FragmentManager 的返回栈有些类似,通过这样的设计允许我们在 Screen 之间通过简单地实例化就可以轻松的切换,而不需要在应用中包含很多Activity。这里有一小部分 Activity(最好是一个)来持有这些 Screen。他们之间的关系下图类似:

如果我们想切换到一个新的 Screen,我们只需简单地实例化这个 Screen,并且告诉我们 Flow 对象帮助我们切换为这个 Screen。除此以外,正如我们所期待的,Flow 被实例化后也会实现 goBack() 和 goUp() 方法。然而,许多开发者都把 Java 中的 goto 语句看作洪水猛兽,但事实上 Java 中的 goto 语句并没有它听起来那么恐怖。

从本质上看,Flow 的作用仅仅是在 App 中告诉我们将要切换到哪一个 Screen。而这样设计的好处在于,Flow 通过这样的设计让我们能够方便地在我们定义的各种不同的自定义 View 中切换,并使我们免受在 Activity 或 Fragment 需要考虑的种种麻烦,让我们把注意力都集中在处理 View上。Flow 为我们创造了一个简单,方便,以 View 为中心的应用架构。
##Mortar
Mortar是一个专注拖拽和依赖注入的库,Mortar 用以下几个不同的部分将一个应用分为可组合的模块:Blueprints, Presenters and a boatload of custom Views。
Mortar App里的每一个部分(在这里指的是每一个 Screen,因为我们在使用 Flow)都由 Blueprint 定义,并赋予他们一个私有的 Dagger 模块。它看起来有点像是下面这样的

Flow 和 Mortar 结合在一起使用的效果很好,我们只需要调节我们的 Screen 类实现去 Mortar 提供的 Blueprint 接口,然后它就会给我们一个可以自由使用的 Dagger 作用域。

Presenter 是一个拥有简单生命周期和伴随其生命周期的 Bundle 的 View 私有对象,主要被用作该 View 的控制器。每一个 View 都有存在于对应的 Screen (还有 Blueprint)中,与 View 自身相关联的 Presenter。因为 Presenter 只能作用于他所在的 Screen,所以当我们使用 Flow 进入一个新的 Screen,Presenter(在我们这个架构中非常重要的一环) 很可能会被 Java 的垃圾回收机制自动回收掉。此外,在 Mortar 作用域中的 Dagger 将与自动垃圾回收机制结合在一起,使得我们 App 能更好的管理、使用其内存——其中原因当然是:当前没有被使用的控制器对象都被我们回收掉了。而在传统的 Activity 开发中,Fragment 和 Activity 的切换过程中,不经意的垃圾回收并不能很好的被注意和提防。
由于自定义 View 在我们的架构中被频繁地使用,以至于我们只需要通过 Dagger 简单地注入所有重要的模型数据,然后使用与 View 关联的 Presenter 去控制 View 本身。即使配置被改变,Presenters 也不会消失,而且我们还非常了解与 Activity 生命周期相关的知识,使得 Presenters 在进程被杀死之后还能被恢复。事实上,Presenter 与 Activity onSavedInstanceState() 方法的 bundle 钩连在一起,使得它能够用与 Activity 相同的机制储存和读取配置改变后产生的数据。而 Presenter 的生命周期非常简单,只有四个回调方法:
- onEnterScope(MortarScope scope)
- onLoad(Bundle savedInstanceState)
- onSave(Bundle outState)
- onExitScope()
完全没有 Fragment 那样复杂的生命周期,这可不是我吹的!
文章写到这里,你会发现在 Flow 和 Mortar 中有许多发生改变的部分,新的术语和类,还有新的使用规范,这难免会让人一头雾水。所以为了方便大家的理解,总的来说,我们需要重视的是下面几个部分:
- Screen: 在应用导航层次结构中的一个特殊存在,用来代表我们视图的对象
- Blueprint: 应用中具有私有的 Dagger 模块的部分
- Presenter: 一个 View 控制器对象
- Custom Views: 通过 Java 代码定义的 View,当然,用 XML 定义也是很常见的
Here’s what our final Mortar and Flow architecture looks like:
我们 Mortar 和 Flow 整个体系架构将会如下所示:

抛弃了对 MVC 模式的执念,这个架构在完成之后变得更像 MVP 模式。这样巨大的转变使得新的架构需要关注如何处理应用在运行时配置信息改变的问题,例如:旋转。在 MVC 模式中,我们的控制器(Activity 和 Fragment)会随着我们的 View 一起被杀死。然而,在 MVP 模式中,我们只有 View
被杀死,又在需要它的时候重现它。挺有趣的对吧?

## 积极的反馈
为了摆脱 Fragment,Square 付出了无数的汗水去进行重新架构和设计,并完成了 Mortar 和 Flow库,他们当然会获得相应的回报,接下来我就给大家介绍这两个库给我们带来的好处吧。
使用 Mortar 和 Flow 库强迫我们创建了一个符合 MVP 模式设计的模块化 App 结构,通过这样做能有效地帮助我们保持代码的整洁。
通过对我们自定义 View 和 Presenters 的依赖注入,测试变得更简单了
动画能够在 View 层被处理,而不用像从前在 Activity 和 Fragment 中使用时那样担心动画会出现Bug
Mortar 在 View 和 Presenter 层中自动进行垃圾回收以处理其作用域,意味着应用能更有效地利用内存
## 可优化的空间
尽管 Flow 和 Mortar 给我们带来了许多好处,但是它们也还存在一些问题:
**想要熟练使用 Flow 和 Mortar,需要面对一条陡峭的学习曲线。**在你真正理解这两个库的设计思想和原理之前,它们的使用模式看起来非常复杂,如果你想要将他们用的得心应手,无疑需要大量的探索和实验,此外,这些库并不是为初学者提供的,我们更建议初学者先学习如何正确和有效地使用 Activity 和 Fragment,我可不是吓唬你们,这样跟你们说吧,就算是 Android 开发大神,在面对这些库时仍需要花费大量的精力和时间去学习有关设计模式的知识,才能真正理解这个库。
**如果你正准备使用 Mortar 和 Flow 库,你真的要全面了解它们的用法。**因为让它和标准的“少使用 Fragment”开发模式相互作用是很困难的。如果你想修改一个已经写好的项目,让它使用 Mortar 和 Flow,虽然不是不可能的,但是完成这个目标的过程会是非常漫长和艰难的。
**这里还存在无数的模板和配置信息需要被处理。**而这正是我最大的担忧,在使用这些新的类和接口时,我常常觉得被淹没在无趣的代码海洋里,因为这些代码都被设计成和其中的各个类、接口钩连在一起,而这也的设计让我觉得这两个库并没有像我期待的那样有趣。
## 接下来呢
不过现在 Mortar 和 Flow 库都处于预发布阶段,现在也没有官方发布的版本。这意味着 Square 还在处理这两个库存在的问题,改动和更新,但这同样也意味着它们还需要许多时间作改进,才能真正投入到使用中。
使用 Mortar 和 Flow 库是个有趣的体验,我非常享受使用各种新的库和寻找官方以 Fragment 为导向的应用结构的替代品,但我并不认为 Mortar 和 Flow 是 Android 寻找的替代 Fragment 的办法,毕竟 Fragment 可能在接下来的几个月或者几年中被修改。但我仍然希望这些项目能够引起更多人关注,并且继续优化,我肯定会继续关注他们的最新进展的,希望大家继续关注我的博客哦。
================================================
FILE: androidweekly/kotlin-for-android简介/readme.md
================================================
kotlin-for-android简介(1)
---
>
* 原文链接 : [Kotlin for Android (I): Introduction
](http://antonioleiva.com/kotlin-for-android-introduction/)
* 译者 : [canglangwenyue](https://github.com/canglangwenyue)
* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)
* 状态 : 完成
Kotlin是众多基于JVM的语言之一,它一开始是作为android 开发中java语言的可能的代替者出现的。java是世界上使用最多的语言之一,而伴随着其他语言的发展使得程序员编程越来越容易,但是java并没有尽快地向预期目标发展。
###Kotlin简介
Kotlin是JetBrains创造的基于JVM的语言,JetBrains是IntelliJ的缔造团队。
Kotlin是一个拥有很多函数编程特点的面向对象的编程语言。
###为什么要用Kotlin
我首先声明我并没有使用Kotlin很长时间,我几乎是在学习的同时写了这些文章的。我并没有尝试任何其它的替 代语言,例如Go和Scala,所以如果你是真的考虑换一种开发语言的话,我建议你去搜索一下其他人对这些 语言的评价。一个使用Scala开发android的例子可以在[47deg Github site](http:/
47deg.github.io/translate-bubble-android/)找到。
以下是我选择学习Kotlin的原因:
* **学习曲线相对较快**:以Scala作为例子进行比较,我们是向着更简单的方向前进。Kotlin有更多的限制,但是如果你没有学习过一门现代编程语言的话,Kotlin更容易学习。
* **轻量级**:与其他语言相比Kotlin的核心库更小。这很重要,因为android函数数量限制(函数数量不能大于64k)是一个问题,虽
然有一些选择来解决这个问题,例如proguard 或 multidexing,但是这些解决方案会加复杂度,并导
致调试时花费更多的时间。引入Kotlin核心库添加了不到7000个方法,大致和support-v4一样。
* **高交互性**:Kotlin和其它java库协调使用的特别好,并且交互操作很简单。这是Kotlin团队
在开发新语言是的主要理念之一。他们想在使用Kotlin开发时并不用重写之前所有用java写的代码,所以,Kotlin和java交互的能力必须非常高。
* **与AS和Gradle完美结合**:我们有一个IDE的插件和另一个属于Grade的插件,因此,用Kotlin进行
android编程并不困难。
在开始任何争论之前我建议你看一下Jake Wharton写的一个有趣的文档[the use of Kotlin for Android development](https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/edit?hl=es&forcehl=1&pli=1)。
###Kotlin的优点
####1. 可读性更高,更简洁
使用Kotlin,可以更容易的避免创建模版型代码,因为大多数经典的情景都默认包含在Kotlin中。
例如,在java中,我们想要创建一个典型的data class时需要这样做:
```java
public class Artist {
private long id;
private String name;
private String url;
private String mbid;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMbid() {
return mbid;
}
public void setMbid(String mbid) {
this.mbid = mbid;
}
@Override public String toString() {
return "Artist{" +
"id=" + id +
", name='" + name + '\'' +
", url='" + url + '\'' +
", mbid='" + mbid + '\'' +
'}';
}
}
```
那么在Kotlin需要多少代码呢?仅仅是下面这个简单的数据类:
```java
data class Artist(
var id: Long,
var name: String,
var url: String,
var mbid: String)
```
####2. 空指针安全
当我们用java开发时,我们的大多数代码是要进行类型检查的,如果我们不想出现**unexpected
NullPointerException**的话,我们就要在运行代码之前持续的检查是否有对象为null。Kotlin,和其它语
言一样,是空指针安全的,因为我们可以通过安全的调用操作来准确的声明一个object可以为null。
我们可以这样做:
```java
//This won´t compile. Artist can´t be null
var notNullArtist: Artist = null
//Artist can be null
var artist: Artist? = null
// Won´t compile, artist could be null and we need to deal with that
artist.print()
// Will print only if artist != null
artist?.print()
// Smart cast. We don´t need to use safe call operator if we previously checked nullity
if (artist != null) {
artist.print()
}
// Only use it when we are sure it´s not null. Will throw an exception otherwise.
artist!!.print()
// Use Elvis operator to give an alternative in case the object is null
val name = artist?.name ?: "empty"
```
####3. 扩展方法
我们可以给任何类添加新方法。这比我们在project中使用的工具类可读性更高。例如:我们可以给Fragment添加一个新方法来显示Toast。
```java
fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(getActivity(), message, duration).show()
}
```
我们可以这样使用:
```java
fragment.toast("Hello world!")
```
####4. 支持函数式编程
如果我们可以不用在我们需要的时候每一次都创建一个listener,就像创建一个click listener那样的操作,
而是仅仅定义我们想要做什么?这种想法的确可以实现,它的实现得益于**lambda**d的使用:
```java
view.setOnClickListener({ toast("Hello world!") })
```
###Kotlin目前存在的限制
Kotlin 依旧在发展,虽然它相对稳定,并且final release版本就很快发布,但是Kotlin在进行android相关开发的时候还是有些限制的。
* **交互性与自动代码生成**:一些有名的android Libraries,例如Dagger 或 Butterknife,他们依赖于自动
代码生成,这种情况下如果有名字冲突的话将无法进行代码生成。Kotlin正在解决这个问题,所以这个问题也许
会很快解决。无论如何,我将在接下来的文章里阐明,Kotlin语言的表达能力会让我们觉得不再需要那么多的
Libraries。
* **声明自定义View比较困难**:Kotlin类只能声明一个构造函数,然而自定义View通常需要三个。如果我
们使用代码来创建View的话可以避免这个问题,但对于使用XML文件来声明View的话就不能满足需求了。最容易的变通方式是用java来声明这些
自定义View的实现类,然后通过Kotlin来使用它们。Kotlin团队许诺将在M11 release解决这个问题。
**Update: Kotlin M11 is out and now includes [secondary constructors](http://kotlinlang.org/docs/reference/classes.html#constructors)**
* **android 下Junit测试**:AS 1.1中介绍的新特性并不适用与Kotlin。顺便说说,系统测试和Junit 测试对于纯Kotlin项目是完全可用。
###结论
对于android apps 开发,Kotlin是一个非常有趣的java替代者。下一篇文章将会描述如何用Kotlin新建一
个project,和如何更好的适用Kotlin来使得android开发更加简单。敬请关注!
================================================
FILE: androidweekly/readme.md
================================================
# 不要将文章放到这个文件夹下啦!请放到最新的issue文件夹中,谢谢大家。
================================================
FILE: androidweekly/一个支持多设备的Android参考应用/readme.md
================================================
一个支持多设备的Android参考应用
---
>
* 原文链接 : [a-new-reference-app-for-multi-device](http://android-developers.blogspot.com/2015/03/a-new-reference-app-for-multi-device.html)
* 译者 : [Mr.Simple & Sophie.Ping](https://www.github.com/bboyfeiyu)
现在你可以把你的app的好用之处分享给你的用户,不管他们身处何地,手上拿着何种设备。今儿我们会发布一个参考示例,展示一下如何把这种服务运用到一个在多个Android form-Factor运作的app上。这个示例叫做Universal Music Player,它是一个准系统但是参考功能齐全,在单个代码库里支持多种设备和形式因素。它能与Android Auto、Android Wear和Google Cast设备兼容。你可以试试把你的app适配到你的用户,无论他在哪里,无论他手上拿的是手机,手表,电视,汽车还是其他的设备。

锁屏时的播放控制和专辑封面
应用toolbar上的Google Cast 图标

使用Android Auto控制播放

通过Android Wear Watch控制播放
本示例运用了Android 5.0 Lollipop的几个新功能,比如MediaStyle通知,MediaSession和MediaBrowserService.这几个新功能使得在多个设备上使用单一app版本操作媒体浏览和回放变得更容易。
一起来看看源代码吧,让你的用户以他们的方式爱上你的app。
作者:Renato Mangini,高级开发工程师,Google开发者平台团队成员
================================================
FILE: androidweekly/一种在android中实现MVP模式的新思路/readme.md
================================================
一种在android中实现MVP模式的新思路
---
>
* 原文链接 : [android-mvp-an-alternate-approach](http://blog.cainwong.com/android-mvp-an-alternate-approach/)
* 译者 : [FTExplore](https://github.com/FTExplore)
* 校对 : [sundroid](https://github.com/sundroid)
今天我想分享我自己在Android上实现MVP模式的方法。如果你对MVP模式还不熟悉或者你不清楚为什么要在Android应用中使用MVP模式,我建议你先阅读以下[这篇维基百科的文章](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)和[这篇博客](http://antonioleiva.com/mvp-android/)。
## 使用Activity和Fragment作为视图层(View)真的合适么?
目前很多使用了MVP模式的android 项目,基本上都是将activity和fragment作为视图层来进行处理的.而presenters通常是通过继承自被视图层实例化或者注入的对象来得到的. 诚然,我同意说,这种方式可以节省掉很多让人厌烦的"import android.*.*"语句, 并且将presenters从activity的生命周期中分割出来以后, 项目后续的维护会变得简便很多.这种思路是正确的, 但是,从另一个角度来说, activity 有一个很复杂的生命周期(fragment的生命周期可能会更复杂), 而这些生命周期很有可能对你项目的业务逻辑有非常重大的影响. Activity 可以获取上下文环境和多种android系统服务. Activity中发送Intent,启动Service和执行FragmentTransisitons等。而这些特性在我看来绝不应该是视图层应该涉及的领域(视图的功能就是现实数据和从用户那里获取输入数据,在理想的情况下,视图应该避免业务逻辑).
基于上述的原因,我对目前的主流做法并不赞同,所以我在尝试使用Activity和Fragment作为Presenters。
## 使用Activity和Fragment作为presenters的步骤
### 1. 去除所有的view
将Activity和Fragment作为presenter最大的困难就是如何将关于UI的逻辑抽取出来.我的解决方案是: 让需要作为presenter的activity 或者 fragment来继承一个抽象的类(或者叫"基类"), 这样关于View 各种组件的初始化以及逻辑,都可以在继承了抽象类的方法中进行操作,而当继承了该抽象类的class需要对某些组件进行操作的时候,只需要调用继承自抽象类的方法,就可以了。
那么抽象类怎么获取到的view组件呢?在抽象类里面会有一个实例化的接口,这个接口里面的init()方法就会对view进行实例化,这个接口如下:
```java
public interface Vu {
void init(LayoutInflater inflater, ViewGroup container);
View getView();
}
```
正如你所见,Vu定义了一个通用的初始化例程,我可以通过它来实现一个容器视图,它也有一个方法来获得一个View的实例,每一个presenter将会和它自己的Vu关联,这个presenter将会继承这个接口(直接或者间接的去继承一个来自Vu的接口)
### 2. 创建一个presenter基类 (Activity)
有了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,译者本人就遇到过)。
```java
public abstract class BasePresenterActivity<V extends Vu> extends Activity {
protected V vu;
@Override
protected final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
vu = getVuClass().newInstance();
vu.init(getLayoutInflater(), null);
setContentView(vu.getView());
onBindVu();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
protected final void onDestroy() {
onDestroyVu();
vu = null;
super.onDestroy();
}
protected abstract Class<V> getVuClass();
protected void onBindVu(){};
protected void onDestroyVu() {};
}
```
### 3. 创建一个基本的presenter(Fragment)
```java
public abstract class BasePresenterFragment<V extends Vu> extends Fragment {
protected V vu;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = null;
try {
vu = getVuClass().newInstance();
vu.init(inflater, container);
onBindVu();
view = vu.getView();
} catch (java.lang.InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return view;
}
@Override
public final void onDestroyView() {
onDestroyVu();
vu = null;
super.onDestroyView();
}
protected void onDestroyVu() {};
protected void onBindVu(){};
protected abstract Class<V> getVuClass();
}
```
### 4. 一个简单的例子
如前文所述,我们已经确定了一个框架,现在就来写一个简单的例子来进一步的说明. 为了避免篇幅过长,我就写一个“hello world”的例子。首先要有一个实现Vu接口的类:
```java
public class HelloVu implements Vu {
View view;
TextView helloView;
@Override
public void init(LayoutInflater inflater, ViewGroup container) {
view = inflater.inflate(R.layout.hello, container, false);
helloView = (TextView) view.findViewById(R.id.hello);
}
@Override
public View getView() {
return view;
}
public void setHelloMessage(String msg){
helloView.setText(msg);
}
}
```
下一步,创建一个presenter来操作这个TextView
```
public class HelloActivity extends BasePresenterActivity {
@Override
protected void onBindVu() {
vu.setHelloMessage("Hello World!");
}
@Override
protected Class<HelloVu> getVuClass() {
return HelloVu.class;
}
}
```
OK,这样就大功告成了!!是不是很简便!
### 等等...耦合警告!
你可能注意到我的HelloVu类直接实现了Vu接口,我的Presenter的getVuClass方法直接引用了实现类。传统的MVP模式中,Presenter是要通过接口与他们的View解耦合的。因此,你也可以这么做。避免直接实现Vu接口,我们可以创建一个扩展了Vu的IHelloView接口,然后使用这个接口作为Presenter的泛型类型。这样Presenter看起来应该是如下这样的 :
```java
public class HelloActivity extends BasePresenterActivity<IHelloVu> {
@Override
protected void onBindVu() {
vu.setHelloMessage("Hello World!");
}
@Override
protected Class<HelloVuImpl> getVuClass() {
return HelloVuImpl.class;
}
}
```
在我使用强大的模拟工具过程中,我个人并没有看到在一个接口下面实现Vu所带来的好处。但是对于我来说一个好的方面是,有没有Vu接口它都能够工作,唯一的需求就是最终你会实现Vu。
## 5. 如何进行测试
通过以上几步,我们可以发现,去除了UI逻辑之后,Activity变得非常简洁。并且,相关的测试
也变的非常异常的简单。请看如下的代码:
```java
public class HelloActivityTest {
HelloActivity activity;
HelloVu vu;
@Before
public void setup() throws Exception {
activity = new HelloActivity();
vu = Mockito.mock(HelloVu.class);
activity.vu = vu;
}
@Test
public void testOnBindVu(){
activity.onBindVu();
verify(vu).setHelloMessage("Hello World!");
}
}
```
以上代码是一段标准的jnuit单元测试的代码,不需要在android设备中部署运行,只需要在编译环境中即可测试。大幅度的提高了测试效率。但是,在测试某些方法的时候,你必须要使用android设备,例如当你想测试activity生命周期中的resume()方法。在缺乏设备环境的时候,super.resume()会报错。为了解决这个问题,可以借鉴一些工具,例如Robolectric、还有android gradle 1.1 插件中内置的testOptions { unitTests.returnDefaultValues = true }。此外,你仍然可以将这些生命周期也抽离出来。例如如下:
```java
@Override
protected final void onResume() {
super.onResume();
afterResume();
}
protected void afterResume(){}
```
现在,你可以在没有android设备的情况下,快速的测试了!
## 意外收获:使用adapter作为presenter
将Activity作为presente已经足够狡猾了吧?使用adapter作为presenter,你想过没有?
好吧,请看如下的代码:
```java
public abstract class BasePresenterAdapter extends BaseAdapter {
protected V vu;
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
try {
vu = (V) getVuClass().newInstance();
vu.init(inflater, parent);
convertView = vu.getView();
convertView.setTag(vu);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
vu = (V) convertView.getTag();
}
if(convertView!=null) {
onBindListItemVu(position);
}
return convertView;
}
protected abstract void onBindListItemVu(int position);
protected abstract Class<V> getVuClass();
}
```
如上代码,使用adapter作为presenter其实和activity或者fragement几乎是一样的,只有一点明显的区别就是,我把onBingVu替换成了onBindListItemVu(接受int参数),其实我是借鉴了[ViewHolder模式](http://developer.android.com/training/improving-layouts/smooth-scrolling.html)。
## 总结和一个demo
我在这篇文章里介绍了我自己的一个实现MVP的方法。我非常期待其他android开发者对我的这套方法的反馈。我切实的想知道我的方法是否可行?我已经把这套思路用在一个框架的开发上,并且即将公布,与此同时,我在github上面有一个demo项目,感兴趣的人可以去看一下https://github.com/wongcain/MVP-Simple-Demo
================================================
FILE: androidweekly/一种更清晰的Android架构/readme.md
================================================
一种更清晰的Android架构
---
>
* 原文链接 : [Architecting Android…The clean way?](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)
* 译者 : [Mr.Simple & Sophie.Ping](https://www.github.com/bboyfeiyu)
过去几个月以来,通过在Tuenti网站上与@pedro_g_s和@flipper83(安卓开发两位大牛)进行友好讨论之后,我决定写这篇关于架构安卓应用的文章。
我写这篇文章的目的是想把我在过去几个月体悟到的小方法以及在调查和应用中学到的有用的东西分享给大家。
## 入门指南
大家都知道要写一款精品软件是有难度且很复杂的:不仅要满足特定要求,而且软件还必须具有稳健性,可维护、可测试性强,并且能够灵活适应各种发展与变化。这时候,“清晰架构”就应运而生了,这一架构在开发任何软件应用的时候用起来非常顺手。
这个思路很简单:简洁架构 意味着产品系统中遵循一系列的习惯原则:
* 框架独立性
* 可测试
* UI独立性
* 数据库独立性
* 任何外部代理模块的独立性

我们并不要求一定要用四环结构(如图所示),这只是一个示例图解,但是要考虑的是依赖项规则:源码依赖项只能向内指向,内环里的所有项不能了解外环所发生的东西。
以下是更好地理解和熟悉本方法的一些相关词汇:
* Entities:是指一款应用的业务对象
* Use cases:是指结合数据流和实体中的用例,也称为Interactor
* Interface Adapters: 这一组适配器,是负责以最合理的格式转换用例(use cases)和实体(entities)之间的数据,表现层(Presenters )和控制层(Controllers ),就属于这一块的。
* Frameworks and Drivers: 这里是所有具体的实现了:比如:UI,工具类,基础框架,等等。
想要更具体,更生动丰富的解释,可以参考[这篇文章](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)或者[这个视频](https://vimeo.com/43612849)。
## 场景
我会设置一个简单的场景来开始:创建一个简单的小app,app中显示从云端获取的一个朋友或用户列表。当点击其中任何一个时,会打开一个新的窗口,显示该用户的详细信息。这里我放了一段视频,大家看看[这个视频 (需翻墙)](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)大概就可以对我所描述的东西了解个大概了。
## Android应用架构
这一对象遵循关注分离原则,也就是通过业务规则让内环操作对外环事物一无所知,这样一来,在测试时它们就不会依赖任何的外部元素了。
要达到这个目的,我的建议就是把一个项目分成三个层次,每个层次拥有自己的目的并且各自独立于堆放运作。
值得一提的是,每一层次使用其自有的数据模型以达到独立性的目的(大家可以看到,在代码中需要一个数据映射器来完成数据转换。如果你不想把你的模型和整个应用交叉使用,这是你要付出的代价)。
以下是图解,大家感受下:

>
注:我并没有使用任何的外部库(除了用于json数据句法分析的gson和用于测试的junit, mockito, robolectric和espresso以外)。原因是它可以使这个示例更清晰。总之,在存储磁盘数据时,记得加上ORM、依赖注入框架或者你熟悉的任何工具或库,这些都会带来很大帮助。(记住:重复制造轮子可不是明智的选择)
## 表现层 (Presentation Layer)
表现层在此,表现的是与视图和动画相关的逻辑。这里仅用了一个Model View Presenter(下文简称MVP),但是大家也可以用MVC或MVVM等模式。这里我不再赘述细节,但是需要强调的是,这里的fragment和activity都是View,其内部除了UI逻辑以外没有其他逻辑,这也是所有渲染的东西发生的地方。
本层次的Presenter由多个interactor(用例)组成,Presenter在 android UI 线程以外的新线程里工作,并通过回调将要渲染到View上的数据传递回来。

如果你需要一个使用MVP和MVVM的[Effective Android UI](https://github.com/pedrovgs/EffectiveAndroidUI/)典型案例,可以参考我朋友Pedro Gómez的文章。
## 领域层 (Domain Layer)
这里的业务规则是指所有在本层发生的逻辑。对于Android项目来说,大家还可以看到所有的interactor(用例)实施。这一层是纯粹的java模块,没有任何的Android依赖性。当涉及到业务对象时,所有的外部组件都使用接口。

## 数据层 (Data Layer)
应用所需的所有数据都来自这一层中的UserRepository实现(接口在领域层)。这一实现采用了[Repository Pattern](http://martinfowler.com/eaaCatalog/repository.html),主要策略是通过一个工厂根据一定的条件选取不同的数据来源。
比如,通过ID获取一个用户时,如果这个用户在缓存中已经存在,则硬盘缓存数据源会被选中,否则会通过向云端发起请求获取数据,然后存储到硬盘缓存。
这一切背后的原理是由于原始数据对于客户端是透明的,客户端并不关心数据是来源于内存、硬盘还是云端,它需要关心的是数据可以正确地获取到。

>
注:在代码方面,出于学习目的,我通过文件系统和Android preference实现了一个简单、原始的硬盘缓存。请记住,如果已经存在了能够完成这些工作的库,就不要重复制造轮子。
## 错误处理
这是一个长期待解决的讨论话题,如果大家能够分享各自的解决方案,那真真是极好的。
我的策略是使用回调,这样的话,如果数据仓库发生了变化,回调有两个方法:onResponse()和onError(). onError方法将异常信息封装到一个ErrorBundle对象中: 这种方法的难点在于这其中会存在一环扣一环的回调链,错误会沿着这条回调链到达展示层。因此会牺牲一点代码的可读性。另外,如果出现错误,我本来可以通过事件总线系统抛出事件,但是这种实现方式类似于使用C语言的goto语法。在我看来,当你订阅多个事件时,如果不能很好的控制,你可能会被弄得晕头转向。
## 测试
关于测试方面,我根据不同的层来选择不同的方法:
* 展示层 ( Presentation Layer) : 使用android instrumentation和 espresso进行集成和功能测试
* 领域层 ( Domain Layer) : 使用JUnit和Mockito进行单元测试;
* 数据层 ( Data Layer) : 使用Robolectric ( 因为依赖于Android SDK中的类 )进行集成测试和单元测试。
## 代码展示
我猜你现在在想,扯了那么久的淡,代码究竟在哪里呢? 好吧,这就是你可以找到上述解决方案的[github链接](https://github.com/android10/Android-CleanArchitecture)。还要提一点,在文件夹结构方面,不同的层是通过以下不同的模块反应的:
* presentation: 展示层的Android模块
* domain: 一个没有android依赖的java模块
* data: 一个数据获取来源的android模块。
* data-test: 数据层测试,由于使用Robolectric 存在一些限制,所以我得再独立的java模块中使用。
## 结论
正如 Bob大叔 所说:“Architecture is About Intent, not Frameworks” ,我非常同意这个说法,当然了,有很多不同的方法做不同的事情(不同的实现方法),我很确定,你每天(像我一样)会面临很多挑战,但是遵循这些方法,可以确保你的应用会:
* 易维护 Easy to maintain
* 易测试 Easy to tes.
* 高内聚 Very cohesive.
* 低耦合 Decoupled.
最后,我强烈推荐你去实践一下,并且分享你的经验。也许你会找到更好的解决方案:我们都知道,不断提升自己是一件件非常好的事。我希望这篇文章对你有所帮助,欢迎拍砖。
## 参考资料
<ol>
<li>Source code: <a href="https://github.com/android10/Android-CleanArchitecture">https://github.com/android10/Android-CleanArchitecture</a></li>
<li><a href="http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html">The clean architecture by Uncle Bob</a></li>
<li><a href="http://www.infoq.com/news/2013/07/architecture_intent_frameworks">Architecture is about Intent, not Frameworks</a></li>
<li><a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter">Model View Presenter</a></li>
<li><a href="http://martinfowler.com/eaaCatalog/repository.html">Repository Pattern by Martin Fowler</a></li>
<li><a href="http://www.slideshare.net/PedroVicenteGmezSnch/">Android Design Patterns Presentation</a></li>
</ol>
================================================
FILE: androidweekly/使用Robolectric的参数化测试/readme.md
================================================
使用Robolectric的参数化测试
---
>
* 原文标题 : Parameterized testing with Robolectric
* 原文链接 : [Parameterized testing with Robolectric](http://www.jayway.com/2015/03/19/parameterized-testing-with-robolectric/)
* 译者 : [Lollypo](https://github.com/Lollypo)
* 校对者: [Chaos](https://github.com/chaossss)
* 状态 : 校对完成
在目前的项目中我们使用Robolectric为Android应用程序编写单元测试,它一直都干的不错。最近我需要编写一个测试用例,通过每次使用不同的测试数据,将同一个操作执行若干次,并由此断言:正确的动作能否发生是由数据决定的。
JUnit对于这个情况提供了一个易于使用的选项,它叫做参数化测试-先定义测试数据,,然后使用参数化测试运行器来执行测试。这将创建一个该测试类的实例把测试数据中的每个元素传递到构造函数的参数中。
事实证明,Robolectric有一个完全相同的`ParameterizedRobolectricTestRunner`(稍微调整以适应Robolectric),而且它对于我的测试:验证应用程序从外部服务提供者接收到不同的错误代码的行为,做的非常好
```java
@RunWith(ParameterizedRobolectricTestRunner.class)
public class ContactServiceTest {
@ParameterizedRobolectricTestRunner.Parameters(name = "ErrorCode = {0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{105, 105_ERROR_MSG},
{113, 113_ERROR_MSG},
{114, 114_ERROR_MSG},
{134, 134_ERROR_MSG},
{137, 137_ERROR_MSG},
{999, DEFAULT_ERROR_MSG} // Bogus错误代码
});
}
private int errorCode;
private String expectedErrorMsg;
public ContactServiceTest(int errorCode, String errorMsg) {
this.errorCode = errorCode;
this.expectedErrorMsg = errorMsg;
}
@Test
public void when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user() {
// HTTP响应从服务包含定义的错误代码
Robolectric.addPendingHttpResponse(HttpStatus.SC_OK, buildFakeServiceResponse(errorCode));
// 联系服务
mService.contactService();
// 使用awaitility等到错误消息显示给用户
// 然后断言该错误代码与期望一致
await().until(getDisplayedErrorMsg(), is(expectedErrorMsg));
}
```
该测试用例将被执行6次,一旦遍历测试数据的每个元素,就会将打印的错误消息与特定错误代码定义的错误消息相比较。当创建测试报告时,每一个测试运行都将视为其自身的测试用例。
添加了`name`参数到Parameters注解上将会整理测试结果。作为测试运行结果显示,测试用例的名称将会像下面这种情况下
```java
when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 105]
when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 113]
when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 114]
...
```
如需深入理解请查阅[JUnit Theories](https://github.com/junit-team/junit/wiki/Theories)以及[junit-quickcheck](https://github.com/pholser/junit-quickcheck),一个好的生成测试数据的方法是在JUnit中自动生成(Robolectric也差不多),而不是由你自己定义。
================================================
FILE: androidweekly/使用RxJava.Observable取代AsyncTask和AsyncTaskLoader/readme.md
================================================
使用RxJava.Observable取代AsyncTask和AsyncTaskLoader
---
>
* 原文链接 : [Replace AsyncTask and AsyncTaskLoader with rx.Observable – RxJava Android Patterns](http://stablekernel.com/blog/replace-asynctask-asynctaskloader-rx-observable-rxjava-android-patterns/)
* 译者 : [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang)
* 校对者: [chaossss](https://github.com/chaossss)
* 状态 : 校对完成
在网上有很多关于RxJava入门指南的帖子,其中一些是基于Android环境的。但是,我想到目前为止,很多人只是喜欢他们所看到的这些,当要解决在他们的Android项目中出现的具体问题时,他们并不知道如何或者是为什么要使用RxJava。在这一系列的文章中,我想要探索在我工作过的一些依赖于RxJava架构的Android项目中的模式。
在文章的开始,我想要处理一些Android开发者在使用RxJava的时候,很容易遇到的状况。从这个角度,我将提供更高级和更合适的解决方案。在这一系列的文章中,我希望可以听到其他开发者在使用RxJava的过程中解决类似的问题,或许他们和我发现的一样呢。
#问题一:后台任务
Android开发者首先遇到的挑战就是如何有效的在后台线程中工作,然后在UI线程中更新UI。这经常是因为需要从web service中获取数据。对于已经有相关经验的你可能会说:“这有什么挑战性?你只需要启动一个AsyncTask,然后所有的工作它就都给你做了。”如果你是这样想的,那么你有一个机会去改善这种状况。这是因为你已经习惯了这种复杂的方式并且忘记这本应该是很简洁的,或者是说你没有处理所有应该处理的边界情况。让我们来谈谈这个。
##默认的解决方案:AsyncTask
AsyncTask是在Android里面默认的处理工具,开发者可以做里面一些长时间的处理工作,而不会阻塞用户界面。(注意:最近,AsyncTaskLoader用来处理一些更加具体的数据加载任务,我们以后会再谈谈这个)
表面上,这似乎很简单,你定义一些代码在后台线程中运行,然后定义一些代码运行在UI线程中,在后台任务处理完之后,它在UI线程会处理从后台任务传递过来的执行结果。
```java
private class CallWebServiceTask extends AsyncTask<String, Result, Void> {
protected Result doInBackground(String... someData) {
Result result = webService.doSomething(someData);
return result;
}
protected void onPostExecute(Result result) {
if (result.isSuccess() {
resultText.setText("It worked!");
}
}
}
```
使用AsyncTask的最大的问题是在细节的处理上,让我们谈谈这个问题。
###错误处理
这种简单用法的第一个问题就是:“如果出现了错误怎么办?”不幸的是,暂时没有非常好的解决方案。所以很多的开发者最终要继承AsyncTask,然后在doInBackground()中包裹一个try/catch,返回一个<TResult, Exception>,然后根据发生的情况,分发到新定义的例如onSuccess()或者是onError()中。(我也曾经见过仅捕获异常的引用,然后在 onPostExcecute()中进行检查的写法)
这最终是有点帮助的,但是你必须为你的每个项目写上额外的代码,随着时间的推移,这些自定义的代码在开发者之间和项目之间,可能不会保持很好的一致性和可预见性。
###Activity和Fragment的生命周期
另外一个你必须面对的问题是:“当AsyncTask正在运行的时候,如果我退出Activity或者是旋转设备的话会发生什么?”嗯,如果你只是发送一些数据,之后就不再关心发送结果,那可能是没有问题的,但是如果你需要根据Task的返回结果更新UI呢?如果你不做一些事情阻止的话,那么当你试图去调用Activity或者是view的话,你将得到一个空指针异常导致程序崩溃,因为他们现在是不可见或者是null的。
同样,在这个问题上AsyncTask没有做很多工作去帮助你。作为一个开发者,你需要确保保持一个Task的引用,并且要么当Activity正在被销毁的时候取消Task,要么当你试图在onPostExecute()里面更新UI的时候,确保Activity是在一个可达状态。当你只想明确的做一些工作,并且让项目容易维护的时候,这将会继续提高维护项目的难度。
###旋转时的缓存(或是其他情况)
当你的用户还是待在当前Activity,仅仅是旋转屏幕会发生什么?在这种情况下,取消Task没有任何意义,因为在旋转之后,你最终还是需要重新启动Task。或者是你不想重启Task,因为状况在一些地方以非幕等的方式发生了突变(because it mutates some state somewhere in a non-idempotent way),但是你确实想要得到结果,因为这样你就可以更新UI来反映这种情况。
如果你专门的做一个只读的加载操作,你可以使用AsyncTaskLoader去解决这个问题。但是对于标准的Android方式来说,它还是很沉重,因为缺少错误处理,在Activity中没有缓存,还有很多独有的其他怪癖。
###组合的多个Web Server调用
现在,假如说我们已经想办法把上面的问题都解决了,但是我们现在需要做一些连续的网络请求,每一个请求都需要基于前一个请求的结果。或者是,我们想做一些并发的网络请求,然后把结果合并在一起发送到UI线程,但是,再次抱歉,AsyncTask在这里帮不到你。
一旦你开始做这样的事情,随着更多的复杂线程模型的增长,之前的问题会导致处理这样的事情非常的痛苦和苍白无力。如果想要这些线程一起运行,要么你就让它们单独运行,然后回调,要么让它们在一个后台线程中同步运行,最后复制组成不同。(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.)
如果要并行运行,你必须创建一个自定义的executor然后传递给AsyncTaskTask,因为默认的AsyncTask不支持并行。并且为了协调并行线程,你需要使用像是CountDownLatchs, Threads, Executors 和 Futures去降低更复杂的同步模式。
###可测试性
抛开这些不说,如果你喜欢测试你的代码,AsyncTask并不能给你带来什么帮助。如果我们不做额外的工作,测试AsyncTask非常困难,它很脆弱并且难以维持。这是一篇有关如何成功测试AsyncTask的[帖子](http://www.making-software.com/2012/10/31/testable-android-asynctask/)。
##更好的解决方案:RxJava’s Observable
幸运的是,一旦我们决定使用RxJava依赖库的时候,我们讨论的这些问题就都迎刃而解了。下面我们看看它能为我们做什么。
下面我们将会使用Observables写一个请求代码来替代上面的AsyncTask方式。(如果你使用Retrofit,那么你应该很容易使用,其次它还支持Observable 返回值,并且它工作在一个后台的线程池,无需你额外的工作)
```java
webService.doSomething(someData)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
result -> resultText.setText("It worked!")),
e -> handleError(e)
);
```
###错误处理
你可能会注意到,没有做额外的工作,我们已经处理了AsyncTask不会处理的成功和错误的情况,并且我们写了很少的代码。你看到的额外的组件是我们想要Observer 在UI主线程中处理的结果。这样可以让我们前进一点点。并且如果你的webService对象不想在后台线程中运行,你也可以在这里通过使用.subscribeOn(...) 声明。(注意:这些例子是使用Java 8的lambda语法,使用[Retrolambda](https://github.com/orfjackal/retrolambda)就可以在Android项目中进行使用了,但在我看来,这样做的回报是高于风险的,和写这篇文章相比,我们更喜欢在我们的项目中使用。)
###Activity和Fragment的生命周期
现在,我们想在这里利用RxAndroid解决上面提到的生命周期的问题,我们不需要指定mainThread() scheduler(顺便说一句,你只需要导入RxAndroid)。就像下面这样
```java
AppObservable.bindFragment(this, webService.doSomething(someData))
.subscribe(
result -> resultText.setText("It worked!")),
e -> handleError(e)
);
```
我通常的做法是在应用的Base Fragment里面创建一个帮助方法来简化这一点,你可以参考我维护的一个[Base RxFragment](https://github.com/rosshambrick/standardlib/blob/master/src/main/java/com/rosshambrick/standardlib/RxFragment.java) 获得一些指导。
```java
bind(webService.doSomething(someData))
.subscribe(
result -> resultText.setText("It worked!")),
e -> handleError(e)
);
```
如果我们的Activity或者是Fragment不再是可见状态,那么AppObservable.bindFragment()可以在调用链中插入一个垫片,来阻止onNext()运行。如果当Task试图运行的时候,Activity、Fragment是不可达状态,subscription 将会取消订阅并且停止运行,所以不会存在空指针的风险,程序也不会崩溃。一个需要注意的是,当我们离开Activity和Fragment时,我们会暂时或者是永久的泄露,这取决于问题中的Observable 的行为。所以在bind()方法中,我也会调用LifeCycleObservable机制,当Fragment销毁的时候自动取消。这样做的好处是一劳永逸。
所以,这解决了首要的两个问题,但是下面这一个才是RxJava大发神威的地方。
###组合的多个Web Server调用
在这里我不会详细的说明,因为这是一个[复杂的问题](http://reactivex.io/documentation/observable.html),但是通过使用Observables,你可以用非常简单和易于理解的方式完成复杂的事情。这里是一个链式Web Service调用的例子,这些请求互相依赖,在线程池中运行第二批并行调用,然后在将结果返回给Observer之前,对数据进行合并和排序。为了更好的测量,我甚至在里面放置了一个过滤器。所有的业务逻辑都在下面这短短五行代码里面...
```java
public Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {
return cityDirectory.getUsCapitals()
.flatMap(cityList -> Observable.from(cityList))
.filter(city -> city.getPopulation() > 500,000)
.flatMap(city -> weatherService.getCurrentWeather(city)) //each runs in parallel
.toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()));
}
```
###旋转时的缓存(或是其他情况)
既然这是一个加载的数据,那么我们可能需要将数据进行缓存,这样当我们旋转设备的时候,就不会触发再次调用全部web service的事件。一种实现的方式是保留Fragment,并且保存一个对Observable 的缓存的引用,就像下面这样:
```java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
weatherObservable = weatherManager.getWeatherForLargeUsCapitals().cache();
}
public void onViewCreated(...) {
super.onViewCreated(...)
bind(weatherObservable).subscribe(this);
}
```
在旋转之时,正在运行当中的Subscription(代表了事件源和订阅者之间的关系)会被缓存到一个实例,在旋转之后,这个实例就会立即发送一些和旋转之前已经发送过的事件相同的事件,从而避免了再去重复请求网络服务。
如果你想要避免缓存的Fragment(或者是由于它是一个子Fragment,你不能缓存它),此时我们可以通过putting the same cache instance one layer down inside a service singleton,或者使用一个无论何时被订阅,都会重新发出最后的事件AsyncSubject实现缓存,或者使用可以在整个应用中获得最后的值以及由改变引起的新值的BehaviorSubject这些来完成缓存(以上这些我将会在我不久后的一篇文章里面更详细的讲明,那篇文章我将使用一种更接近事件总线的observables)。
```java
WeatherListFragment.java
public void onViewCreated() {
super.onViewCreated()
bind(weatherManager.getWeatherForLargeUsCapitals()).subscribe(this);
}
WeatherManager.java
public Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {
if (weatherSubject == null) {
weatherSubject = AsyncSubject.create();
cityDirectory.getUsCapitals()
.flatMap(cityList -> Observable.from(cityList))
.filter(city -> city.getPopulation() > 500,000)
.flatMap(city -> weatherService.getCurrentWeather(city))
.toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()))
.subscribe(weatherSubject);
}
return weatherSubject;
}
```
因为“缓存”是由Manager单独管理的,它不会与Fragment/Activity的周期绑定,并且会保持与Activity/Fragment的解耦。如果你想以一种和处理保存的fragment的生命周期事件相同的手段来强制刷新这个缓存实例的话,你可以这样做:
```java
public void onCreate() {
super.onCreate()
if (savedInstanceState == null) {
weatherManager.invalidate(); //invalidate cache on fresh start
}
}
```
这件事情的伟大之处在于,它不像是Loaders,我们可以很灵活的缓存这些结果,他们来自我们选择的Activity和Services中。只需要去掉oncreate()中的invalidate()调用,并让你的Manager对象决定何时发出新的气象数据就可以了。可能是一个Timer,或者是用户定位改变,或者是其他任何时刻,这真的没关系。你现在可以控制什么时候如何去更新缓存和重新加载。并且当你的缓存策略发生改变的时候,Fragment和你的Manager对象之间的接口不需要进行改变。它只不过是一个 List<WeatherData>的Observer。
###可测试性
测试是我们想要实现干净、简单的最后一个挑战。(我们可以不用模拟真实的网络服务来进行测试。做法很简单,下面通过一个接口注入这些依赖,这个接口你可能已经在用了。)
幸运的是,Observables给我们一个简单的方式来将一个异步方法变成同步,你要做的就是使用toblocking()方法。我们看一个测试例子。
```java
List results = getWeatherForLargeUsCapitals().toBlocking().first();
assertEquals(12, results.size());
```
就像这样,我们没有必要去使用Futures或者是CountDownLatchs让做一些脆弱的操作,比如线程睡眠或者是让我们的测试变得很复杂,我们的测试现在是简单、干净、可维护的。
##结论
更新:我已经创建了一对简单的项目来演示[AsyncTask风格](https://github.com/rosshambrick/AsyncExamples)和[AsyncTaskLoader](https://github.com/rosshambrick/rain-or-shine)风格。
RxJava,你值得拥有。我们使用rx.Observable来替换AsyncTask和AsyncTaskLoader可实现更加强大和清晰的代码。使用RxJava Observables很快乐,而且我期待能够呈现更多的Android问题的解决方案。
================================================
FILE: androidweekly/使用buildSrc Gradle项目和Codemodel生成java代码/readme.md
================================================
#How to generate Java sources using buildSrc Gradle project and Codemodel
#使用buildSrc Gradle项目和Codemodel生成java代码
[Android](http://www.thedroidsonroids.com/category/blog/android/) • 1 September 2015
> * 原文链接 : [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/)
> * 原文作者 : [Karol](http://www.thedroidsonroids.com/blog/)
> * 译文出自 : [开发技术前线 www.devtf.cn。未经允许,不得转载!](http://www.devtf.cn/)
> * 译者 : [shenyansycn](https://github.com/shenyansycn)
> * 校对者 :
> * 状态 : 校对中
##Introduction
假设现在你现在需要在Android应用中嵌入和解析外部数据,你会怎么做?在本文中我们将在应用中获取[互联网顶级域名(TLD)的列表](https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains)。你可以在[ICANN TLD报告](http://stats.research.icann.org/dns/tld_report/)报告中看到一共有一千多种顶级域名,时不时有新域名的加入,也有旧的域名被废弃。
由于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**类一样。
##生成的代码长什么样?
例子中的数据是一个字符串的列表,所以它可以被表示为**List****<****String****>**。域是独一无二的,它永远不能被编辑并且[List](http://developer.android.com/reference/java/util/List.html)接口提供稍微多一点的内容,比如索引。我们用一个方法创建了一个实用性的类,就像下边这样:
```
public final class TldList {
private static final List TLD_LIST = Collections.unmodifiableList(Arrays.asList(<TLDs here>));
/**
* javadoc here
*/
public static List<String> getTldList() {
return TLD_LIST;
}
private TldList() {}
}
```
##How to generate Java code automatically?
##如何自动生成Java代码
生成Java代码需要下载源文件并重写到Java源文件中。后者可以打印java语法元素到文件中。但更好的方法是使用专用库。在此情况下你不必担心大括号,换行符和其他语法元素只需要关注逻辑。在这个例子中使用的生成java代码的库是[Codemodel](https://codemodel.java.net/)。使用Codemodel生成代码是简单的。我们用Groovy编写Gradle本身和大部分的插件。
这里有生成好的代码:
```
public class TldListGenerator {
private static final URL IANA_TLDS_URL =
new URL('https://data.iana.org/TLD/tlds-alpha-by-domain.txt')
/**
* Generates <code>pl.droidsonroids.domainnameutils.TldList</code>
* and places it into <code>outputDir</code>.
* @param outputDir directory where generated sources will be written to
* @param useSavedVersion if true then saved TLD data will be used instead of downloading it
* from IANA's website
*/
public static void generateTldListClass(final File outputDir, final boolean useSavedVersion) {
def javadocConfig = new ConfigSlurper()
.parse(TldListGenerator.class.getResource('javadoc.properties'))
def sourceUrl = useSavedVersion ?
TldListGenerator.class.getResource('tlds-alpha-by-domain.txt')
: IANA_TLDS_URL
def codeModel = new JCodeModel()
def fqcn = TldListGenerator.class.getPackage().getName() + '.TldList'
def tldListClass = codeModel._class(PUBLIC | FINAL, fqcn, ClassType.CLASS)
def classJavadoc = tldListClass.javadoc()
classJavadoc.append(javadocConfig.getProperty('classJavadoc'))
def listStringType = codeModel.ref(List.class).narrow(codeModel.ref(String.class))
def asListInvocation = codeModel.directClass(Arrays.class.getName()).staticInvoke('asList')
sourceUrl.eachLine {
if (!it.startsWith('#')) {
asListInvocation.arg(it.toLowerCase(Locale.ENGLISH))
} else {
classJavadoc.append('\n<br/>').append(it.replaceFirst('#\\s+', ''))
}
return
}
def constant = codeModel
.directClass(Collections.class.getName())
.staticInvoke('unmodifiableList')
.arg(asListInvocation)
def field = tldListClass.field(PRIVATE | STATIC | FINAL, listStringType, 'TLD_LIST', constant)
def method = tldListClass.method(PUBLIC | STATIC, listStringType, 'getTldList')
method.javadoc()
.append(javadocConfig.getProperty('methodJavadoc'))
.addReturn()
.append(javadocConfig.getProperty('methodReturnJavadoc'))
method.body()._return(field)
tldListClass.constructor(PRIVATE)
if (!outputDir.isDirectory() && !outputDir.mkdirs()) {
throw new IOException('Could not create directory: ' + outputDir)
}
codeModel.build(outputDir)
}
}
```
让我们解释下关键代码部分。在开始的部分我们使用[ConfigSlurper](http://docs.groovy-lang.org/latest/html/gapi/groovy/util/ConfigSlurper.html)从属性中加载java文档。属性是一对key-value,和代码位于不同文件中,并没有被混淆在一起,所有的java文档都在一个地方就像好Android的String资源。Codemodel API就像我们平时写代码一样调用方法即可。
读取输入使用[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声明。
小写TLDs的操作设置为英语环境也是很重要的。如果没有则使用host提供的默认设置。例如:如果设置为土耳其或者阿塞拜疆,非ASII字符小写i会被当作一个小写的ASCII字符I,我们生成的一些TLDs就会无效。更多信息可以查看[Internationalizing Turkish](http://www.i18nguy.com/unicode/turkish-i18n.html).最终,我们创建了输出的目录结构并保存了生成的代码。
怎么处理错误,如果没有网络连接或者数据没有被下载会发生什么?如,你看到的没有**cactch**声明也无**throws**处理。在Groovy里我们是不需要的,所有的异常都是未被检查的被处理过。如果我们的代码抛出了异常,它仅仅引起Gradle编译失败,会在Android Studio的消息窗口和控制台中显示。
##哪里存放生成的代码
Gradle给我们提供了几个方式保存生成的代码,例如:
1.直接嵌入到我们App项目的**build.gradle**文件中
2.分开的文件。例如:**generator.gradle**和在**build.gradle**文件中使用**apply from: 'generator.gradle'**
3.[buildSrc project](https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources)
4.[standalone project](https://docs.gradle.org/current/userguide/custom_plugins.html#N16FA7)
头两个选项没有一点灵活性。例如:我们生成的代码不能很简单的测试,(特别是第一个)生成的代码与编译配置选项容易混淆让人难以读懂。剩余两项的关键不同是在应用中如何配置。独立的项目在当很多项目被配置使用、需要一个仓库或至少拷贝一个JAR文件时是有用处的。在这个例子中我们将在一个单独的项目中使用我们生成好的代码并且我们将它放在**buildSrc**项目中
##**buildSrc** project是什么?
Gradle特别处理过的名字为**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执行。
##如何使用生成的代码?
我们需要调用我们的**generateTldListClass()**方法。我们能创建一个完整的Gradle插件,但对于这个简单的目的,我们在App项目中的**build.gradle**文件中添加一个自定义的任务。例子实现如下:
```
import pl.droidsonroids.domainnameutils.TldListGenerator
apply plugin: 'com.android.application'
def generatedSrcDir = new File(buildDir, "generated/tld/src/main/java/")
task generateTldList << {
TldListGenerator.generateTldListClass(generatedSrcDir, true)
}
preBuild.dependsOn generateTldList
android {
sourceSets {
main {
java {
srcDirs += generatedSrcDir
}
}
}
<rest of the android closure>
}
```
让我们分析这个编译脚本。Gradle编译时产生的文件默认会被放在项目根目录下的**build**目录。在**build.gradle**中会被作为**buildDir**被检索到。如此,我们构建了输出目录。
我们也可以创建自定义任务叫**generateTldList**.注意**<<**是一个定义为行为的快捷方式。关于任务的更多信息看[Gradle tasks documentation](https://docs.gradle.org/current/userguide/more_about_tasks.html)。在Android Gradle插件里我们继续添加我们的任务作为**preBuild**任务的一个依赖,当每一个项目编译刚开始时被执行。最终我们把输出目录加入到了main source中,在app源码中可以被引入。
##如何使用生成的代码
我们可以像使用其他类一样使用我们生成的类。下边的例子展示如何创建一个包含TLDS的简单Spinner:
```
final Spinner spinner = (Spinner) findViewById(R.id.spinner_tld);
spinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, TldList.getTldList()));
```
##Sample project
##例子
生成代码的例子和简单的Android项目可以在Github上看到:[koral–/buildsrc-sample](https://github.com/koral--/buildsrc-sample)。Android Studio 1.3中会提示**Class 'TldListGenerator' already exists in 'pl.droidsonroids.domainnameutils'**是但是它并不会影响项目的构建。运行App看起来像下边这样

##References
##引用
* [IANA — Root Zone Database](https://data.iana.org/TLD/tlds-alpha-by-domain.txt)
* [Codemodel](https://codemodel.java.net/)
* [Internationalizing Turkish](http://www.i18nguy.com/unicode/turkish-i18n.html)
* [Organizing Gradle Build Logic](https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources)
* [Writing Custom Gradle Task Classes](https://docs.gradle.org/current/userguide/custom_tasks.html)
* [More about Gradle tasks](https://docs.gradle.org/current/userguide/more_about_tasks.html)
* [Sample project](https://github.com/koral--/buildsrc-sample)
================================================
FILE: androidweekly/功能测试框架 espresso/readme.md
================================================
功能测试框架 espresso
---
>
* 原文链接 : [the-hitchhikers-guide-to-android-testing-part-2-espresso](http://wiebe-elsinga.com/blog/the-hitchhikers-guide-to-android-testing-part-2-espresso/)
* 译者 : [Lollypo](https://github.com/Lollypo)
* 校对者: [kang](https://github.com/tiiime)
* 状态 : 校对完成

正如[Ali Derbane](https://plus.google.com/+AliDerbane)和我写的第一篇关于Android的功能测试的文章中提到的,有许多的框架供你使用.
在这个旅程的第二部分,我将讲解[Espresso](https://code.google.com/p/android-test-kit/)这个功能测试框架.
### 简介
Espresso 是在2013年的 GTAC 上首次提出,目的是让开发人员能够快速地写出简洁,美观,可靠的 Android UI 测试。
Espresso有以下几个通用组件:
- “Espresso”类提供的“onView”和“onData”方法,仅可用于特定接口上测试最优数.
- `ViewMatchers` 包含一个实现了`Matcher <? super View>`接口的对象集合. 使用该类你可以收集或是检查View元素.例如,通过文本 “7” 获取一个View元素(Button).
- `ViewActions` 包含了一组`viewAction`对象,储存了将要在View上执行的动作. 这些动作被传递给`ViewInteraction.perform`方法,也许包含更多的动作. For 例如, 点击一下View元素(Button).
- `ViewAssertions` 包含`ViewAssertion`集合,用于对Views进行检查.
举个例子说明一下,这些测试组件看起来就像下面这样:
```java
Espresso.onView(ViewMatchers.withText("7")).perform(ViewActions.click());
Espresso.onView(withId(R.id.result)).check(ViewAssertions.matches(ViewMatchers.withText("42")));
```
好消息,去年谷歌推出了集成Espresso的[Testing Support Library](https://developer.android.com/tools/support-library/index.html).因此,让我们通过实现Espresso开始吧.
> 为了方便解释, 我们要编写一些测试用例来测试[Android calculator application](https://github.com/welsinga/sample_espresso/app)这个App. 先来实现一个测试“6”x“7”等于“42”是否正确的普通测试场景。
### 定义test runner
使用Espresso我们首先需要定义这些测试用例。Espresso使用新的名为AndroidJUnitRunner的测试用例。该测试用例基于“InstrumentationTestRunner”和“GoogleInstrumentationTestRunner”,运行JUnit3和JUnit4来测试你的Android应用程序。
首先将依赖项添加到你的`build.gradle`文件中, 这里假设你已经安装好了[Testing Support Library](https://developer.android.com/tools/support-library/index.html).
```gradle
dependencies {
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
}
```
然后添加测试用例到你的`build.gradleandroid.defaultConfig`配置中
```gradle
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
```
### 编写测试
你可能已经想到了,测试类必须在`src\androidTest\com.example.package.tests`中.包com.example.package是在AndroidManifest文件中指定的属性.
每一个测试类还必须继承抽象类`ActivityInstrumentationTestCase2`并且使用默认测试的 Activity 作为泛型.
它还需要通过`super()`方法传递给父类.要使被测试的Activity被测试框架调用,只需要在setup方法中同步调用`getActivity()`方法.
```java
public class FunctionalInstrumentationTest extends ActivityInstrumentationTestCase2<ActivityToTest> {
public FunctionalInstrumentationTest() {
super(ActivityToTest.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
getActivity();
}
}
```
正如前面提到的,我们想要检查“6”x“7”是否等于“42”.
```java
public void testAnswer_to_the_Ultimate_Question_of_Life_the_Universe_and_Everything() {
onView(withText("7")).perform(click());
onView(withText("×")).perform(click());
onView(withText("6")).perform(click());
onView(withText("=")).perform(click());
onView(withId(R.id.resText)).check(matches(withText("42")));
}
```
你可能已经注意到,这个示例是使用静态导入.这样做完全是为了使代码更易于阅读.
其他你可能会用到的操作:
- `pressBack()`; to simulate the use of the “back” button,
- `isDisplayed()`; to check if an element is being shown and
- `scrollTo()`; to scroll to an element.
- `pressBack()`; 模拟后退按钮
- `isDisplayed()`; jian检查某个元素是否显示
- `scrollTo()`; 滚动到另外一个元素
### 运行测试
现在我们做做有趣的,运行测试.这可以通过`gradle clean assembleDebug connectedAndroidTest`从命令行运行,或者使用Android Studio:
1. 打开Run菜单 | Edit Configurations
2. 添加一个新的Android Tests configuration
3. 选择你需要测试的Module
4. 定义我们的测试用例: `android.support.test.runner.AndroidJUnitRunner`

现在你对于Espresso有一些了解了。如果需要深入,可以浏览以下链接:
- [Espresso website](https://code.google.com/p/android-test-kit/)
- [Github repo corresponding to this article](https://github.com/welsinga/sample_espresso)
- [General Espresso Github samples by Google](https://github.com/googlesamples/android-testing)
================================================
FILE: androidweekly/在Android 5.0中使用JobScheduler/readme.md
================================================
在Android 5.0中使用JobScheduler
---
> * 原文链接 : [using-the-jobscheduler-api-on-android-lollipop](http://code.tutsplus.com/tutorials/using-the-jobscheduler-api-on-android-lollipop--cms-23562)
> * 译者 : [Mr.Simple](https://github.com/bboyfeiyu)
> * 校对者 : [Mr.Simple](https://github.com/bboyfeiyu)
在这篇文章中,你会学习到在Android 5.0中如何使用JobScheduler API。JobScheduler API允许开发者在符合某些条件时创建执行在后台的任务。
## 介绍
在Android开发中,会存在这么些场景 : 你需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务,例如当设备接通电源适配器或者连接到WIFI。幸运的是在API 21 ( Android 5.0,即Lollipop )中,google提供了一个叫做JobScheduler API的新组件来处理这样的场景。
当一系列预置的条件被满足时,JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外,JobScheduler API允许同时执行多个任务。这允许你的应用执行某些指定的任务时不需要考虑时机控制引起的电池消耗。
这篇文章中,你会学到关于JobScheduler API更多的东西以及在你的应用中用于运行一个简单的后台任务的JobService,这篇文章中所展示的代码你都可以在[github](https://github.com/tutsplus/Android-JobSchedulerAPI)中找到。
## 1. 创建Job Service
首先,你需要创建一个API最低为21的Android项目,因为JobScheduler是最近的版本才加入Android的,在写这篇文章的时候,它还没有兼容库支持。(译者注:截止目前已知一个兼容库 [JobSchedulerCompat](https://github.com/evant/JobSchedulerCompat))
假定你使用的是Android Studio,当你点击了创建项目的完成按钮之后,你会得到一个"hello world"的应用骨架。你要做的第一步就是创建一个新的java类。为了简单起见,让我们创建一个继承自JobService且名字为JobSchedulerService的类,这个类必须实现两个方法,分别是`onStartJob(JobParameters params) `和 `onStopJob(JobParameters params)`;
```java
public class JobSchedulerService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
```
当任务开始时会执行`onStartJob(JobParameters params)`方法,因为这是系统用来触发已经被执行的任务。正如你所看到的,这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了你的肩上。当任务执行完毕时你需要调用`jobFinished(JobParameters params, boolean needsRescheduled)`来通知系统。
当系统接收到一个取消请求时,系统会调用`onStopJob(JobParameters params)`方法取消正在等待执行的任务。很重要的一点是如果`onStartJob(JobParameters params)`返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。换句话说,`onStopJob(JobParameters params)`在这种情况下不会被调用。
需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。因为多线程技术已经超出了我们这篇文章的范围,让我们简单实现一个Handler来执行我们在JobSchedulerService定义的任务吧。
```java
private Handler mJobHandler = new Handler( new Handler.Callback() {
@Override
public boolean handleMessage( Message msg ) {
Toast.makeText( getApplicationContext(),
"JobService task running", Toast.LENGTH_SHORT )
.show();
jobFinished( (JobParameters) msg.obj, false );
return true;
}
} );
```
在Handler中,你需要实现`handleMessage(Message msg)`方法来处理你的任务逻辑。在这个例子中,我们尽量保证例子简单,因此我们只在`handleMessage(Message msg)`中显示了一个Toast,这里就是你要写你的任务逻辑( 耗时操作 )的地方,比如同步数据等。
当任务执行完毕之后,你需要调用`jobFinished(JobParameters params, boolean needsRescheduled)`来让系统知道这个任务已经结束,系统可以将下一个任务添加到队列中。如果你没有调用`jobFinished(JobParameters params, boolean needsRescheduled)`,你的任务只会执行一次,而应用中的其他任务就不会被执行。
`jobFinished(JobParameters params, boolean needsRescheduled) `的两个参数中的params参数是从JobService的`onStartJob(JobParameters params)`的params传递过来的,needsRescheduled参数是让系统知道这个任务是否应该在最初的条件下被重复执行。这个boolean值很有用,因为它指明了你如何处理由于其他原因导致任务执行失败的情况,例如一个失败的网络请求调用。
创建了Handler实例之后,你就可以实现`onStartJob(JobParameters params)` 和`onStopJob(JobParameters params)`方法来控制你的任务了。你可能已经注意到在下面的代码片段中`onStartJob(JobParameters params)`返回了true。这是因为你要通过Handler实例来控制你的操作,这意味着Handler的handleMessage方法的执行时间可能比`onStartJob(JobParameters params)`更长。返回true,你会让系统知道你会手动地调用`jobFinished(JobParameters params, boolean needsRescheduled)`方法。
```java
@Override
public boolean onStartJob(JobParameters params) {
mJobHandler.sendMessage( Message.obtain( mJobHandler, 1, params ) );
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
mJobHandler.removeMessages( 1 );
return false;
}
```
一旦你在Java部分做了上述工作之后,你需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。
```
<service android:name=".JobSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE" />
```
## 2. 创建一个JobScheduler对象
随着JobSchedulerService构建完毕,我们可以开始研究你的应用如何与JobScheduler API进行交互了。第一件要做的事就是你需要创建一个JobScheduler对象,在实例代码的MainActivity中我们通过`getSystemService( Context.JOB_SCHEDULER_SERVICE )`初始化了一个叫做mJobScheduler的JobScheduler对象。
```java
mJobScheduler = (JobScheduler)
getSystemService( Context.JOB_SCHEDULER_SERVICE );
```
当你想创建定时任务时,你可以使用`JobInfo.Builder`来构建一个JobInfo对象,然后传递给你的Service。JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,第二个是这个Service组件的类名。
```java
JobInfo.Builder builder = new JobInfo.Builder( 1,
new ComponentName( getPackageName(),
JobSchedulerService.class.getName() ) );
```
这个builder允许你设置很多不同的选项来控制任务的执行。下面的代码片段就是展示了如何设置以使得你的任务可以每隔三秒运行一次。
```java
builder.setPeriodic( 3000 );
```
其他设置方法 :
* setMinimumLatency(long minLatencyMillis): 这个函数能让你设置任务的延迟执行时间(单位是毫秒),这个函数与`setPeriodic(long time)`方法不兼容,如果这两个方法同时调用了就会引起异常;
* setOverrideDeadline(long maxExecutionDelayMillis):
这个方法让你可以设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。与`setMinimumLatency(long time)`一样,这个方法也会与`setPeriodic(long time)`不兼容,同时调用这两个方法会引发异常。
* setPersisted(boolean isPersisted):
这个方法告诉系统当你的设备重启之后你的任务是否还要继续执行。
* setRequiredNetworkType(int networkType):
这个方法让你这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行。
* setRequiresCharging(boolean requiresCharging):
这个方法告诉你的应用,只有当设备在充电时这个任务才会被执行。
* setRequiresDeviceIdle(boolean requiresDeviceIdle):
这个方法告诉你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务。
需要注意的是`setRequiredNetworkType(int networkType)`, `setRequiresCharging(boolean requireCharging)` 以及 `setRequiresDeviceIdle(boolean requireIdle)`这几个方法可能会使得你的任务无法执行,除非调用`setOverrideDeadline(long time)`设置了最大延迟时间,使得你的任务在未满足条件的情况下也会被执行。一旦你预置的条件被设置,你就可以构建一个JobInfo对象,然后通过如下所示的代码将它发送到你的JobScheduler中。
```java
if( mJobScheduler.schedule( builder.build() ) <= 0 ) {
//If something goes wrong
}
```
你可能注意到了,这个schedule方法会返回一个整型。如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。
如果你的应用想停止某个任务,你可以调用JobScheduler对象的`cancel(int jobId)`来实现;如果你想取消所有的任务,你可以调用JobScheduler对象的`cancelAll()`来实现。
```java
mJobScheduler.cancelAll();
```
到了这里,你现在应该已经知道如何在你的应用中使用JobScheduler API来执行批量任务和后台操作了。
## 结论
这篇文章中,你学会了怎么实现一个使用Handler对象来运行后台任务的JobService子类,你也学会了如何使用JobInfo.Builder来设置JobService。掌握了这些之后,你可以在减少资源消耗的同时提升应用的效率。
================================================
FILE: androidweekly/在Android调试模式中使用Stetho/README.md
================================================
#Stetho for Android debug builds only
# 在Android调试模式中使用Stetho
------
- 原文链接:[在Android调试模式中使用Stetho][11]
- 译者:[BillionWang](https://github.com/BillionWang)
- 校对者:[chaossss](https://github.com/chaossss)
- 状态:完成
最近FaceBook发布了一个叫做[Stetho][2]的工具.这个工具是一个谷歌浏览器的开发者工具扩展 ,它可以用来检测你的应用。我发现这东西挺好用的,因为它还提供了访问应用中SQLite数据库的接口。很明显,这种类型的工具只应该在应用的调试模式中使用。接下来我们来看看怎么用这个工具。
#添加依赖
为了保证只在调试模式中使用Stetho,你可以添加一个调试编译依赖,而不是平时常用的普通依赖类型。
depencencies {
// your other dependencies here...
debugCompile 'com.facebook.stetho:stetho:1.0.0'
}
#在调试模式中初始化Stetho
现在我们在调试中使用Stetho。你会怎么做?当然使用牛逼闪闪的Android Gradle构建系统啦。创建一个源文件夹,目录结构为 <font color="red">src/debug/java</font>。这个目录中的代码仅仅是用于调试模式。这个目录结构和<font color="red">src/main/java</font>很像,因为构建模式就是用于应用程序的调试的。(这句话再想想)。然后添加一个[Stetho][5]主页上描述的 [Application][6]。
import com.facebook.stetho.Stetho;
public class MyDebugApplication extends MyApplication {
@Override
public void onCreate() {
super.onCreate();
Stetho.initialize(
Stetho.newInitializerBuilder(this)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
.build());
}
}
看清楚这个类是怎样继承一个已经有的<font color="red">MyApplication.</font>类的。这样写的确很方便,因为你的应用里可能已经有一个application来进行其他的初始化了。如果你还没有一个application。你从<font color="red">android.app.Application.</font>继承一个就行了。
#激活我的调试应用
最后一步,我们要做的工具是确保当前的应用的调试版本使用的是MyDebugApplication类。在这里我们用Gradle来验证。在<font color="red">src/debug</font>文件夹中添加一个<font color="red">AndroidManifest.xml</font>
<manifest
package="com.mycompany"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
tools:replace="android:name"
android:name=".MyDebugApplication"/>
</manifest>
这个<font color="red">AndroidManifest.xml</font>会合并到<font color="red">src/main</font>中的主AndroidManifest.xml文件里,并且会替换标签中的<font color="red">android:name</font>属性。这是因为我们特别添加上了tools:replace="android:name"属性。真屌。
现在当你启动程序的调试模式,[Stetho][8]就会被激活。如果你切换到发布版本,Stetho绝对不会被激活,也看不到它的任何痕迹。如果想要不丢人,程序员要保证软件没有BUG。
#总结
用Android Gradle构建系统,可以很容易的给你的应用添加更多的调试功能。这门手艺不仅可以用在[Stetho][10]上,还可以用于那些你希望仅仅在调试模式中添加的类库或者工具。
[1]: https://github.com/facebook/stetho
[2]: https://github.com/facebook/stetho
[3]: http://developer.android.com/reference/android/app/Application.html
[4]: https://github.com/facebook/stetho
[5]: https://github.com/facebook/stetho
[6]: http://developer.android.com/reference/android/app/Application.html
[7]: https://github.com/facebook/stetho
[8]: https://github.com/facebook/stetho
[9]: https://github.com/facebook/stetho
[10]: https://github.com/facebook/stetho
[11]:http://littlerobots.nl/blog/stetho-for-android-debug-builds-only/
================================================
FILE: androidweekly/安卓字体渲染器/readme.md
================================================
安卓字体渲染器
---
* 原文:[android font renderer](https://medium.com/@romainguy/androids-font-renderer-c368bbde87d9)
* 译者:[7heaven](http://github.com/7heaven)
* 校对者:
* 状态:翻译完成
任何一个有几年的客户端应用开发经验的开发者都会知道文本渲染有多复杂。至少我在2010年开始写libhwui(基于OpenGL的安卓2D绘制API)之前是这么认为的。在开始写libhwui后,我意识到如果试图用GPU来渲染文本会使文本渲染变得更复杂。
##Text and Android
##文本与安卓
安卓的硬件加速字体渲染最开始是由Renderscript团队的一位同事编写的,后来经过了多位工程师的修改和优化,其中就包括我以及我的朋友Chet Haase。你可以很容易的找到很多关于如何用OpenGL渲染文本的教程。但是,大部分的这些文章都把重点放在游戏开发以及如何绕过一些复杂的问题上。
下面的内容并非如小说般的通俗易懂,但我认为它能很容易地给开发者一个如何实现完整的基于GPU的文字渲染系统的总览。文章中同时也描述了几个容易实现的文本渲染的优化。
通常用OpenGL渲染文本的方法是计算一张包含所有需要的字形的纹理集合。这通常是在离线状态下用一个非常复杂的打包算法来最大化的减小纹理集合的资源浪费。创建这样一个纹理集合需要预先知道哪些文本--包括字体、字号以及其他属性等--然后应用就可以在运行时使用这些字形。
在安卓上用预先渲染的纹理显然不是一个可行的解决方案。UI组件无法预先得知哪些文本需要被渲染;部分应用甚至会在运行时加载自定义字体。这是个主要的约束,但是这仅仅是其中一个:
* 必须在运行时建立字体缓存
* 必须能处理数量巨大的字体
* 必须能处理数量巨大的字形
* 必须最大化地减小纹理浪费
* 渲染速度必须够快
* 在高端和低端机型上必须效果一致
* 在任何驱动/GPU组合上都必须完美运行
##实现字体渲染
在我们研究底层OpenGL文字渲染是如何实现之前,我们先来看看应用中直接调用的上层接口。这些接口对理解libhwui如何工作是非常重要的。
###文本接口
应用中用来排版和绘制文本的主要API有4个:
* android.widget.TextView,一个处理排版和渲染的控件
* android.text.*,创建风格化文本和文本布局的类集合
* android.graphics.Paint,文本测量
* android.graphics.Canvas,文本渲染
TextView以及android.text都是以Paint和Canvas为基础的顶层实现。在安卓3.0之前Paint和Canvas都是直接由[Skia](https://code.google.com/p/skia/)(软件渲染库)实现的顶层API,Skia提供一个抽象库叫[Freetype](http://www.freetype.org/),一个流行的开源字体光栅化器。
>安卓软件文本渲染
>
安卓4.4以后整个过程变得有点复杂。Paint和Canvas用一个叫TextLayoutCache的内部JNI接口来实现复杂的文本排版布局(CTL)。这个接口依赖于[Harfbuzz](http://www.freedesktop.org/wiki/Software/HarfBuzz/),这是一个开源的文字shaping引擎。TextLayoutCache接受字体和UTF-16编码的字符串输入,并输出一个包含了x,y坐标的字形标示列表。
TextLayoutCache是处理非拉丁文字,包括阿拉伯文、希伯来文、泰文等的关键。这边我不详细解释关于TextLayoutCache和Harfbuzz是如何工作的。但是如果你想在你的应用中更好的支持非拉丁文字,我强烈建议你学习CTL(复杂文本排版布局)的相关知识。这个问题极少在讨论用OpenGL渲染文本的教程中提到。绘制文本会比单纯地从左到右一个接一个地摆放字形更复杂。部分语言,例如阿拉伯语,是从右到左排列的。泰文甚至需要把字形从上到下 或者从下到上排列。
> Android hardware accelerated text rendering
>安卓硬件加速文字渲染
>
所有这些意味着当你调用Canvas.drawText(),不管是直接还是间接调用。OpenGL渲染器都不会接收到你发送的参数,而只是接收到一个字形标示以及x/y坐标的数组。
###光栅化和缓存
所有字体渲染的调用都要有字体的配合。字体用来缓存多个独立的字形。字形储存在一个缓存纹理上(一个缓存纹理可以包含不同字体的字形)。缓存纹理是用来存放多个缓存的重要对象:一个空的块列表、一个像素缓存、OpenGL纹理和顶点缓存(the mesh)。
>缓存结构
>
用来储存所有这些对象的数据结构很简单:
* 字体储存在字体渲染器的一个LRU缓存中
* 字形存放在每一个字体的映射集中(the key is the glyph identifier)
* 缓存纹理用一个块链表来追踪剩余空间
* 像素缓存为uint8 或者 uint32_t的数组(alpha以及RGB缓存)
* mesh是一个包含x/y坐标和u/v坐标的顶点缓存
* 纹理是一个GLunit句柄
字体渲染器初始化的时候会创建两种类型的缓存纹理:alpha和 RGBA。Alpha纹理用来储存普通的字形;字体本身不包含颜色信息,所以我们只需要储存抗锯齿相关的信息。RGBA缓存用来储存emoji表情。
字体渲染器会为每种类型的纹理创建多个针对不同尺寸的CacheTexture实例。缓存的尺寸在不同设备上不一样,下面是几个默认的尺寸(缓存的数量是硬编码的):
* 1024x512 alpha缓存
* 2048x256 alpha缓存
* 2048x256 alpha缓存
* 2048x512 alpha缓存
* 1024x512 RGBA缓存
* 2048x256 RGBA缓存
CacheTexture实例创建后,它下面的缓存并不会自动分配。字体渲染器会根据需要来分配,1024x512alpha缓存作为一个例外每次都会分配。
字形会在纹理中被打包成多个列。当渲染器遇到一个没有缓存的字形时,它会要求上面列表中对应类型的CacheTexture缓存该字形。
这时候上面提到的块列表就登场了。这个列表包含了给定缓存纹理的已分配空间加上可用空间。如果一个已存在的列可以容纳下某个字形,那么这个字形就会被添加到这个列的已占用空间的底部。
如果所有的列都被占用,它便会在左边的剩余空间中创建一个新列。由于部分字体是等宽字体,渲染器会把每一个字形的宽度四舍五入到4的倍数(默认情况下)。打包并不是最优解,但是它提供了一个快速实现方法。
所有储存在纹理中的字形都由一个空的一像素的边包围。这是为了避免在双线性过滤时需要对字体纹理进行人工干预处理。
这边需要了解的一个重点是当文本在渲染的时候做了缩放变换,这些变换会被交给Skia/Freetype。这表示这些字形是以变换后的形态储存在缓存纹理中。这在提高渲染质量的同时造成性能损耗。幸运的是,文本很少做动画缩放,即使做了动画缩放也只影响到少部分的字形。我做了大量的测试也没有出现性能造成比较大影响的情况。
粗体、斜体、文本x轴缩放(这边不是用canvas的变换矩阵来处理)、样式和线宽等属性也会影响字形的光栅化和储存。
###光栅化代替方案
有另外一种用GPU处理文本的方法。字形可以直接用顶点向量的方式渲染,但是这样开销非常大。我也稍微研究了一下有向距离场,但是简单的实现方式会导致精确度的问题(curves tend to become "wobbly").
建议看一下[Glyphy](https://code.google.com/p/glyphy/),这是一个由Harfbuzz的作者写的开源库,扩展了有向距离场技术并解决了精度的问题。我有一段时间没关注这个项目了,上次看的时候着色器开销在安卓上还是禁止的。
###预缓存
缓存字形是理所当然的,但是预缓存会更好一点。由于libhwui是一个延迟渲染器(和Skia的即时模式相反),所有即将被绘制到屏幕上的字形在帧开始时都是预知的。在显示列表操作的排序过程中(批处理和合并),字体渲染器会被要求尽可能多的预先缓存字形。
这样做的主要优势是完全或者 最大化的避免纹理在两帧之前的上传数量。纹理上传是一个开销极大的操作,会导致CPU或者GPU的延迟。更严重的是,在部分GPU架构上帧间修改纹理会导致内存紧张。
ImaginationTech公司的PowerVR SGX 系列GPU用了一个很有意思的延迟tiling架构,但是会强制驱动保留一份帧间修改的纹理的备份。字体纹理是非常大的,如果不注意纹理上传问题很容易导致内存溢出。
Google Play上的一款应用就出现了这个问题。这款应用是一个简单的计算器,包含多个有数学符号和数字的按钮。字体渲染器在第一帧渲染的时候内存溢出了。因为按钮是按顺序绘制的。每一个按钮的绘制都会触发纹理上传。以及对整个字体缓存的拷贝。系统没有足够的内存来维持这么多的缓存拷贝。
###清理缓存
缓存字形的纹理非常大,它们在部分情况下会被系统回收来把空间让给其他应用。
当用户让应用进入后台的时候,系统会发送一条要求释放尽可能多内存的信息给应用。最显而易见的方式就是销毁最大得缓存纹理。在安卓系统上,所有除了第一个创建的缓存纹理(默认是1024x512)都被视为大型纹理。
当所有缓存都没有任何剩余空间得时候,纹理也会被清理掉。字体渲染器用LRU来追踪字体,但不对它做任何操作。如果需要的话,可以选择清理相对使用较少的纹理,这样更加智能化。现在还没有证据证明这是必须的,但是这是一个潜在的优化策略。
###批处理和合并
安卓4.3引入了[批处理和合并](https://www.youtube.com/watch?v=vQZFaec9NpA)绘制操作,彻底降低了OpenGL驱动的指令问题数量,是一个很重要的优化策略。
为了实现合并,字体渲染器在多个绘制请求上对文本几何结构进行缓存。每一个缓存纹理用于一个2048 quad的客户端数组(1 quad = 一个字形),他们共享一个索引缓存(在GPU中储存为一个VBO)。当libhwui内部发起一个绘制请求时,字体渲染器会为每一个字形获取一个mesh并把x/y坐标和u/v坐标写进去。mesh在批处理的最后或者在quad缓存满得时候被发送给GPU(由延迟显示列表系统中所描述)。有可能在渲染一个字符串的时候会有多个mesh,每个缓存纹理一个。
这个优化策略容易实现,并且对性能提升有很大帮助。由于字体渲染器使用多个缓存纹理,导致字符串中的大部分字形一部分在一个纹理中,一部分在另一个纹理中。如果没有批处理/合并优化策略,每次字体渲染器需要切换不同缓存纹理的时候都会发起一个绘制请求给GPU。
我用来测试字体渲染器的一个应用上就出现了这个问题。这个应用用不同的样式和尺寸渲染一个"Hello world"字符串。"o"字被储存在和其他字符不同的纹理中。这会导致字体渲染器先绘制"hell", 然后是"o","w","o", 最后是"rld"。一共五次绘制请求以及5次纹理绑定,但实际上只需要两个纹理。使用优化后,渲染器会先绘制"hell w rld"然后再同时绘制两个"o"。
###优化纹理上传
我之前提到字体渲染器在上传缓存纹理的时候会追踪每个纹理的dirty rectangle来尽可能地上传最少量的数据。但是这种方式有两个限制。
首先,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缓存来绕过这个问题,但是这就需要预先知道长方形的大小。
一个妥协的办法是上传包含这个长方形的最小带宽的像素(smallest band of pixels)。由于带宽总是和纹理本身一样宽所以我们还是会浪费掉部分带宽,但是这总好过上传整个纹理。
第二个问题是纹理上传是同步的。这会导致CPU长时间的停顿(多至一毫秒,取决于纹理大小、驱动和CPU)。这在预缓存正常工作的情况下并不是大问题,但是在使用大量文本的应用或者使用大量字形的语言(例如中文)的时候用户会感受到停顿。
OpenGL ES 3.0提供了这两个问题的解决方案。用一个叫GL_UNPACK_ROW_LENGTH的像素储存新属性可以上传一个长方形的一部分。这个属性指定了幅度或者主内存中的原始数据。但请注意:这个属性会对当前的OpenGL上下文的全局状态造成影响。
通过使用像素缓存对象或者PBO可以避免CPU停顿。类似于OpenGL中的其他缓存对象,PBO reside in the GPU but can be mapped in main memory.PBO有很多有趣的属性,但是其中最让我们关注的事它允许异步上传纹理。整个操作过程变成:
```
glMapBufferRange->把字形写入缓存->glUnmapBuffer->glPixlStorei(GL_UNPACK_ROW_LENGTH)->glTexSubImage2D
```
对glTexSubImage2D的调用现在会立刻返回而不会阻断渲染器。字体渲染器会同时把整个缓存映射到主内存中。虽然这不太可能导致性能问题,但是最好处理方式还是只映射更新缓存纹理所必须的那一部分。
这两个OpenGL ES 3.0的优化策略[已经在安卓4.4中实现](https://plus.google.com/+RomainGuy/posts/9QSTyVCSoz3)
###投影
文本通常在渲染的时候会带上阴影。这是一个开销较大的操作。由于相邻的字形的阴影模糊会互相影响,字体渲染器无法预先对字形进行模糊化。实现模糊的方法很多,但为了减小每帧间的混合操作和纹理取样,投影会以纹理的形式储存并延续到多个帧。
由于应用很容易让GPU超负荷,我们决定把模糊化交给CPU处理。最简单和高效的处理方法是使用RenderScript的C++ 接口。只需要几行代码and takes advantage of all the available cores.唯一需要注意的是初始化Renderscript的时候要指定RS_INIT_LOW_LATENCY标示来把操作交给CPU执行。
###未来的优化策略?
在我离开安卓团队前有一个优化策略我希望能够实现。文本预缓存,异步以及部分纹理更新都是相当重要的优化方式,但是字形的光栅化依然是一个开销极大的操作。在systrace里很容易看出来。(勾选gfx标签并找到precacheText事件)。
一个简单的优化方法是在后台使用worker线程来执行字形光栅化。这种技巧在不渲染成OpenGL几何体的复杂路径光栅化上已经得到应用。
文本渲染的批处理和合并也有潜在的提升空间。用来绘制文本的部分的颜色是以整体的形式发送给碎片着色器的。这降低了发送给GPU的顶点数据但是同时也导致了副作用,会产生不必要得批处理指令:一个批处理只能包含单色的文本。如果以顶点的属性方式储存会减少发送给GPU的批处理指令。
###源码
如果你需要深入研究字体渲染器的实现可以访问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)支持的像素缓存的抽象类。
你会发现源代码中有一些配置属性。这些属性在安卓的[性能调节](http://source.android.com/devices/tuning.html)文档中有描述。
###题外话
这篇文章仅仅是对安卓字体渲染器的简单介绍。还有很多实现的细节被我略过或者会出现在我其他的文章中。有问题请尽管提出。
================================================
FILE: androidweekly/安卓的模糊视图/readme.md
================================================
安卓的模糊视图
---
>
* 原文链接 : [A Blurring View for Android](http://developers.500px.com/2015/03/17/a-blurring-view-for-android.html)
* 作者 : [Jun Luo](https://500px.com/junluo)
* 译者 : [lvtea0105](https://github.com/lvtea0105)
* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)
* 校对者: [chaossss](https://github.com/chaossss)
* 状态 : 校对完成
模糊效果可以生动地表现出内容的层次感,当使用者关注重点内容,即便在模糊表面之下发生视差效果或者动态改变,也能够保持当前背景。
在IOS设备中,我们首先构造一个UIVisualEffectView,之后添加 visualEffectView 到view层,在view中可以进行动态的模糊。
```java
UIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
```
## 安卓中的表现形式
我们在雅虎天气APP中确实看到了很好的模糊效果实例,但是根据 [Nicholas Pomepuy 的博客帖子](http://nicolaspomepuy.fr/blur-effect-for-android-design/),然而,这个 App 是通过缓存一张预渲染的背景图片来实现图片虚化的。
虽然这种方法非常有效,但是确实不符合我们的需求,在 [500px](https://500px.com/) 的APP中,图像通常是获得焦点的内容,而不仅仅是提供背景,这说明图像的变化很大且迅速,即便他们是在模糊层之下。在我们的[安卓APP](https://play.google.com/store/apps/details?id=com.fivehundredpx.viewer) 中即是一个恰当的例子:当用户滑动至下一页时,整排图片会以相反方向淡出,为了组成所需的模糊效果,适当地管理多个预渲染图是困难的。

## 一种绘制模糊视图的方法 ##
我们需要的效果是,实时地模糊其下的视图,最终需要给界面的是模糊视图的一个 blurred view 引用。
```java
blurringView.setBlurredView(blurredView);
```
之后当blurred view 改变时,不管是因为内容改变(比如呈现新的图片)、view的变换还是处在动画过程,我们都要刷新 blurring view。
```java
blurringView.invalidate();
```
为了实现 blurring view, 我们需要继承 view类并重写 onDraw()方法来渲染模糊效果。
```java
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Use the blurred view’s draw() method to draw on a private canvas.
mBlurredView.draw(mBlurringCanvas);
// Blur the bitmap backing the private canvas into mBlurredBitmap
blur();
// Draw mBlurredBitmap with transformations on the blurring view’s main canvas.
canvas.save();
canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY());
canvas.scale(DOWNSAMPLE_FACTOR, DOWNSAMPLE_FACTOR);
canvas.drawBitmap(mBlurredBitmap, 0, 0, null);
canvas.restore();
}
```
这里的关键点在于,当模糊视图重绘时,它使用blurred view 的draw()方法,模糊视图保持blurred view的引用,它绘制一个私有的,以bitmap作为背景的画布。
```java
mBlurredView.draw(mBlurringCanvas);
```
这种使用另一个视图的 draw()方法,也适用于建立放大或者个性的UI界面,在其中,放大区域的内容是扩大的,而不是模糊的。
根据 [Nicholas Pomepuy 讨论](http://nicolaspomepuy.fr/blur-effect-for-android-design/) 的想法,我们使用子采样和 [渲染脚本](http://developer.android.com/guide/topics/renderscript/compute.html) 进行快速绘制,当初始化blurring view的私有画布mBlurringCanvas 后,子采样就已经完成了。
```java
int scaledWidth = mBlurredView.getWidth() / DOWNSAMPLE_FACTOR;
int scaledHeight = mBlurredView.getHeight() / DOWNSAMPLE_FACTOR;
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
mBlurringCanvas = new Canvas(mBitmapToBlur);
```
通过mBlurringCanvas的 建立与恰当的渲染脚本初始化,重绘时的blur()方法如下:
```java
mBlurInput.copyFrom(mBitmapToBlur);
mBlurScript.setInput(mBlurInput);
mBlurScript.forEach(mBlurOutput);
mBlurOutput.copyTo(mBlurredBitmap);
```
此时 mBlurredBitmap 已准备好,剩下的就是使用适当的变换和缩放,在blurring view自己的画布上重绘视图。
## 实现细节
完整实现blurring view 时,我们需要注意几个技术点:
一:我们发现缩放因子8,模糊半径15 很好地满足我们的目标,但满足你需求的参数可能是不同的。
二:在模糊的bitmap边缘会遇到一些渲染脚本效果,我们将缩放的宽度和高度进行了圆角化,直到最近的4的倍数。
```java
// The rounding-off here is for suppressing RenderScript artifacts at the edge.
scaledWidth = scaledWidth - (scaledWidth % 4) + 4;
scaledHeight = scaledHeight - (scaledHeight % 4) + 4;
```
三:为了保证更好的表现效果,我们新建两个bitmap对象——mBitmapToBlur 和 mBlurredBitmap ,
mBitmapToBlur 位于私有画布mBlurringCanvas 之下, mBlurredBitmap 仅当blurred view的大小变化时才重新建立他们;
同样地当blurred view的大小改变时,我们才创建渲染脚本对象mBlurInput 和 mBlurOutput。
四:我们以with PorterDuff.Mode.OVERLAY 模式绘制一个白色半透明的层,它处在被模糊的的图片上层。用来处理设计需求中的淡化。
最后,因为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) 告诉我这一点)
## 优点和缺点
我们喜欢这个view的绘制方法,因为它可以实时模糊并且容易使用,使得blurred view 的内容,也在blurring view 和blurred view中间保证了灵活性,最重要的是,它满足了我们的需求
这个方法确实使得blurring view 与适当协同变换的 blurred view 保持了私有联系,相关地,模糊视图必须不能是blurred view的子类,否则会因为互相调用造成堆栈溢出。简单有效处理此限制的方法是要保证模糊视图与blurred view 在同级,并且Z轴次序上 blurred view 在blurring view 之前。
另一点需要注意的限制是,由于与 矢量图形和文本 有关,我们默认的bitmap削减采样表现效果不是很好。
## 库文件和示例
你可以在我们的安卓 [APP](https://play.google.com/store/apps/details?id=com.fivehundredpx.viewer) 上看到解决方法,我们也把开源的库文件连同一个示例分享到了 [github](https://github.com/500px/500px-android-blur),它能够展示内容变换、动画和视图变换。

有关这个效果在 [HackNews](https://news.ycombinator.com/item?id=9219097) 中的讨论
================================================
FILE: androidweekly/欢迎来到Android多进程时代/readme.md
================================================
欢迎来到Android多进程时代
---
>
* 原文标题 : Going multiprocess on Android
* 原文链接 : [Going multiprocess on Android](https://medium.com/@rotxed/going-multiprocess-on-android-52975ed8863c)
* 译者 : [Lollypo](https://github.com/Lollypo)
* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)
* 状态 : 完成
###That moment when one Dalvik alone is no longer enough.
生活在内存限制中
---
有很多方面使得Android成为一个独特的移动平台操作系统,但有时候却让人觉得难以融入,特别是从开发人员的角度看。
例如,把内存限制。iOS应用程序提供几乎没有限制的内存预算(200 MB不是什么大不了的事),Android有严重的局限性,从最近设备的24/32/48 MB以及旧设备极小的16 MB便可以看出。
RAM预算就是一切你的应用运行时所能获得的全部了,这意味着,它必须满足加载类、线程、服务、资源和你的应用程序想要显示的内容。想象一个通过网格视图展示优美图片的照片浏览应用,或一个需要在后台播放的音乐播放器:这太恐怖了
> 那时候你的体会应该是这样的

要理解为什么Android提出了这些限制以及提供了什么解决方案来应对他们,我们需要知道一点点在这背后之后发生了些什么。
理解Android进程
---
你应该已经知道了,安卓系统是基于Linux的。因此,每个应用程序都运行在其本身的进程(拥有一个独一无二的PID)中:这允许应用运行在一个相互隔离的环境中,不能被其他应用程序/进程干扰。通常来说,当你想启动一个应用程序,Android创建一个进程(从Zygote中fork出来的),并创建一个主线程,然后开始运行Main Activity。
你可能不知道的是,你可以指定应用程序的一些组件运行在不同的进程中,而不是那个被用于启动应用程序的。先来看一下这个Nice的属性:
<center>
```xml
android:process
```
</center>
该进程属性可用于activities、services、content providers和broadcast receivers 和指定的进程中应该执行的特定组件。
在这个例子中,我指定MusicService必须执行在一个单独的“music”的进程:
```xml
<manifest ...>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Main" >
<activity
android:name=".MusicActivity"
/>
<service
android:name=".MusicService"
android:process=":music"
/>
</application>
</manifest>
```
它有什么意义呢?
在这个简短的介绍中,我提到了每一个Android应用程序在运行的时候都有一个不能超出的内存预算值。更精确的说,这限制了它只能在单个基础的进程上运行。换句话说,应用程序的每一个进程都将会有一个专门的内存预算(更不用说其中止时也有更酷的不同的规则)
让我们看看这种方法将是一件好事还是坏事。(剧透:两者都是)
使用多进程有啥好处
---
正如我刚才提到的,一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。
此外,操作系统对待运行在不同组件中的进程是不一样的。这意味着,当系统运行在低可用内存的条件时,并不是所有的进程都会被杀死。想象一下:你的音乐播放器正在后台运行,音乐突然播放,系统需要释放一些内存(因为facebook,这就是原因)。由于播放音乐的服务跑在另一个进程中,一种极为可能的情况就是操作系统将会先杀死你的主进程(那个运行着你的UI的),而留下那个在另一个进程播放音乐的。
最后一点对于用户来说看起来似乎很不错!因为你的程序的每一个进程都有自身的在应用程序管理器上的屏幕显示RAM用度。其中有一个或多个将出现在“缓存”部分(这意味着它们是不活跃的)。
> 正如你所看到的,Spotify在后台播放一些音乐。有一个活跃的带有服务的进程 [上图],而另一个进程(持有UI的)是缓存状态的,因为不再可见/不活动的[下图]。


使用多进程时的那些坑
---
不幸的是,坑有很多。事实上,你要学习拥有多个进程不是一下子就能完成的事
首先,进程是被设计成独立的(如安全特性),这意味着每一个进程将有自己的Dalvik VM实例。反过来,这意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。并且这延伸到应用程序每一个的状态。
这是否意味着两个独立的进程之间互相交流是不可能的吗?不,实际上是可能的,有几种方法可以做到。最值得注意的是,Intent可以跨进程“旅行”,Handlers和Messengers也可以。。你也可以依靠AIDL(Android接口定义语言)和Binder,和你通常声明一个bound service茶不错(但你可以做更多的事!)。
我需要使用多进程吗
---
当然,这取决于你需要查看到的迹象。如果你的用户正在经历越来越频繁OutOfMemory错误或者他们抱怨你的应用程序是极其消耗RAM,你可能需要考虑使用一个或多个独立的进程。
音乐播放器的例子是第二个进程能使你的App做的更好的其中最常见的一个场景,当然,还有更多。
例如,你的应用程序是一个客户端云存储服务:委托同步服务组件专用的进程似乎是完全合理的,所以即使UI会被系统杀死,服务仍然可以运行并且保持文件更新。
> 类似的情况会发生在你第一次真正意识到进程隔离的意思时

如果你认为你需要它,那么我建议你先玩一个小试验台应用:只有通过实际体验过使用多个进程的优势和其内在的复杂性,你才能够决定你是否真的需要它,如果是这样,什么是最好的处理它的方式而不至于把我们逼疯。
结语
---
我知道我仅仅触及到这个问题的表面,我只是想给你一些实用的建议,而不是告诉你在操作系统层调控进程的全部理论与工作机制。
还是那句话,如果你对此感兴趣并愿意深入其中,那就留言让我知道!同时,不要忘记文档是你最好的朋友[[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)
================================================
FILE: androidweekly/深入了解Android Graphics Pipeline-part-1/readme.md
================================================
深入了解Android Graphics Pipeline-part-1
---
* 原文链接 : [Android Graphics Pipeline: From Button to Framebuffer (Part 1)](https://blog.inovex.de/android-graphics-pipeline-from-button-to-framebuffer-part-1/)
* 作者 : [mgarbe](https://blog.inovex.de/author/mgarbe/)
* 译者 : [dupengwei](https://github.com/dupengwei)
* 校对者: [chaossss](https://github.com/chaossss)
* 状态 : 完成
在这个小型博文系列中我们想给有兴趣研究 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这个有趣的世界。
请注意,本小型系列博文中会涉及大量的源代码和序列图!它值得一读,就算你对Android Graphics Pipeline一点兴趣也没有,你也可以学到很多(或者你至少看看这些漂亮的图片)。所以给自己一杯咖啡,读吧!
## 引言
为了充分理解view显示到屏幕的过程,我们采用一个小Demo来描述Android Graphics Pipeline的每个主要阶段,由Android Java API (SDK)开始,然后是本地C++代码,最后看原始的OpenGL绘图操作。

这个 Demo 非常值得我们研究,因为这个不起眼的 App 的代码就能充分覆盖整个 Android Graphics 的内部结构,所以,它实际上是一个相当好的例子。
这个activity由一个简单 RelativeLayout,一个带应用图标和标题 ActionBar 和一个读作“Hello world!”的简单 Button 组成。

但从上图你也可以看到,我们这个简单的 Demo 的视图层实际上是相当复杂。
在Android 视图层内部,相对布局(RelativeLayout)由一个简单的颜色渐变背景组成。更复杂的, Actionbar 由一个渐变的背景结合一个bitmap,**One Button** 文本元素和应用图标(也是一个bitmap)组成。9-Patch用作按钮的背景,文本 **Hello World!** 画在它的最上层。屏幕顶部的导航栏和底部的状态栏不属于 App 的 Activity 部分,它们由系统服务`SystemUI`进行渲染。
## Pipeline概述
如果有看过Google I/O演讲**For Butter or Worse**,你肯定认识下面的幻灯片,因为它它显示了完整的 Android Graphics Pipeline 结构。

上图就是在 Google 在 Google I/O 2012 大会中提供的完整的 Android Graphics Pipeline 结构。
Surface Flinger负责创建图形缓冲区并将其合成到主显示器,虽然这对应安卓系统非常重要,但是在此不作赘述。
相反,我们将注意力放在其他组件上,因为这些组件的主要任务就是将view显示到屏幕上。

## Display Lists
可能你已经知道,Android 使用一个叫做 DisplayLists 的概念去渲染所有的view。对于不知道的人来说,display list是一个绘图命令序列集合,需要执行这些命令去渲染指定的view。display lists是Android Graphics Pipeline达到高性能的重要元素。

每个视图层的 view 都有其对应的 display list,每个开发者都知道 `onDraw()` 方法,这个 display list 就是由 view 的 `onDraw()` 方法生成的。为了将视图层画到屏幕上,只有 display lists 需要被评估并执行。假如某个单一 view 失效(因用户输入、动画或转换),受影响的 display lists 将会重建和重绘。这就避免Android在每个frame层调用相当耗费资源的 `onDraw()`。
Display 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.
Display lists 也可以被嵌套,这意味着一个Display list也可以发出一个命令绘制一个子display list。这对复制视图层的display lists来说非常重要。毕竟,即使是最简单的 App 也会具有多个嵌套视图。
这些命令的语句的可以直接映射到OpenGL命令,如翻译和设置剪辑矩阵,或更复杂的命令如`DrawText` 和 `DrawPatch`,但这些复杂命令需要相应的OpenGL命令集,不然我们也无法使用。
`An example of a display list for a button.`
`一个按钮的display list示例`
```java
Save 3
DrawPatch
Save 3
ClipRect 20.00, 4.00, 99.00, 44.00, 1
Translate 20.00, 12.00
DrawText 9, 18, 9, 0.00, 19.00, 0x17e898
Restore
RestoreToCount 0
```
在上面的示例中,你可以清楚地看到 display list 为绘制一个简单的按钮进行了什么操作。第一个操作是保存当前翻译矩阵到栈中,使得它随后可以被恢复。然后又画了9-Patch按钮,接下来是另外一个保存命令,这是必要的,因为对于要绘制的文本来说,只有绘制文本的区域才会创建裁剪矩阵。手机绘图处理器将此矩形区域当做一个线索以便在后续阶段对绘图调用进一步优化。然后将绘画圆点转换到文本位置,并绘制文本。最后,最初的转换矩阵和状态从堆中还原,裁剪矩阵也被重置。
在这篇文章的底部可以看到示例用的的display lists的完整日志。
## 深入代码
带着这些新获取的知识,我们准备深入研究代码。
### 根视图(Root View)
每个Android activity在视图层的最顶层都有一个隐式根视图(Root View),包含一个子view。这个子view是程序员在应用中定义的第一个真正的view。根视图负责调度和执行多种操作,如绘图,使view无效等等。
类似的,每个view都有一个parent的引用。视图层内部的第一个view将根视图作为parent。虽然View类作为每个可视化的元素或组件的基类,但是它并不支持任何子类。然而,其派生类ViewGroup支持多子类,并作为一个容器基类,它已经被标准布局(RelativeLayout 等等)所采用。
如果一个view局部重绘,那么该view会调用根视图的invalidateChildInParent()方法。根视图跟踪所有重绘的区域,并在choreographer调度一个新的遍历,choreographer会在下一个VSync事件执行。

ViewRoot: InvalidateChildInParent(…)
```java
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
// Add the new dirty rect to the current one
mDirty.union(dirty.left, dirty.top,
dirty.right, dirty.bottom);
// Already scheduled?
if (!mWillDrawSoon) {
scheduleTraversals();
}
return null;
}
```
## 创建Display Lists
As 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.
正如刚刚提到的,每个view负责生产自己的display list。当一个VSync事件被触发,choreographer调用根视图的`performTraversals`方法,根视图要求`HardwareRenderer`绘制view,`HardwareRenderer`反过来要求view生成自己的display list。

在Android framework层中,View是其中一个比较大的类,当前代码量将近20000行。这并不令人惊奇,因为它是每个小组件和应用的构建块。它处理键盘、轨迹球、触摸事件,以及滚动,滚动条,布局和测量,还有很多很多。

`View.getDisplayList(…)`方法被Hardware Renderer调用时将会创建一个内部的display list,这个内部display list将会在view生命周期的剩余部分被使用。然后这个内部display list被要求提供一个足够大的画布来容纳这个view。并提供一个GLES20RecordingCanvas,所有view和它的children将会在上面绘图,然后传递给`draw(…)`方法。这个画布有点特殊,因为它不执行绘图命令,而是保存命令到display list。这意味着小组件和每个view能使用正常的绘图API,甚至无须关注display list内部的命令
`View: getDisplayList(…)`
```java
private DisplayList getDisplayList(DisplayList displayList,
boolean isLayer) {
HardwareCanvas canvas = displayList.start(width, height);
if (!isLayer && layerType != LAYER_TYPE_NONE) {
// Layers don't get drawn via a display list
} else {
draw(canvas);
}
return displayList;
}
```
在`draw(…)`方法中,view将执行`onDraw()`方法的代码,渲染自己到画布上。如果这个view有任何children,children各自调用`draw()`方法。这些children可以是任何东西,可以是一个正常的按钮,也可以是一个布局或view group,这些children包含另外的children,都将被绘制。

## 寄语
伴随着display list的产生,part 1结束了。如果你对这系列博文有兴趣的话,请关注 part 2,届时我们会知道 display lists 将如何被呈现在屏幕上!
## 下载
The full Bachelor’s Thesis on which this article is based is available for download.
本文参考的所有学士论文可供[下载](http://mathias-garbe.de/files/introduction-android-graphics.pdf)
================================================
FILE: androidweekly/深入了解Android Graphics Pipeline-part-2/readme.md
================================================
深入了解Android Graphics Pipeline-part-2
---
>
* 原文链接 : [Android Graphics Pipeline: From Button to Framebuffer (Part 2)](https://blog.inovex.de/android-graphics-pipeline-from-button-to-framebuffer-part-2/)
* 作者 : [Mathias Garbe](https://blog.inovex.de/author/mgarbe/)
* 译者 : [chaossss](https://github.com/chaossss)
* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)
* 状态 : 完成
在上一篇博文中,我们图文结合介绍了 Android 怎么把 onDraw() 方法的 Java 代码转换为 Native 层的 C/C++ 代码。而今天,我们会承接上一篇博文讲的内容,继续探索 Android 在 Native 层是如何通过 C/C++ 代码绘制屏幕上的各种元素(控件、动画等)。在开始讲解之前,我需要提前提醒大家:要深入到 Native 层中研究 C/C++ 代码对 Java 代码的实现,意味着我们从此刻开始要忘掉 Java 安逸的垃圾回收机制,并忍受 C/C++ 代码带来的各种令人蛋疼的内存管理问题。不过大家不必惶恐,我会尽可能简单地讲解今天的内容,而且只会展示与我们今天的内容有关的有趣代码。
## 绘制元素 ##
在 Android 4.3 之前,UI 的绘制流程和将 UI 元素添加到相应 View 层级的顺序相同,并将它们添加到 display list 中。但这里存在一个很严重的问题,这样的渲染流程会不断进入 GPU 最糟糕的使用场景,原因在于:这样的绘制流程会导致在绘制不同的控件时,需要不断地切换状态。举例来说吧,我们现在要绘制两个 Button,那么 GPU 就需要先绘制第一个 Button 的背景和文字,绘制完成后再对第二个 Button 做同样的操作,渲染流程结束后,至少进行了三次的状态改变。
## 重新设计绘制流程 ##
所以为了最小化状态切换带来的 GPU 开销,Android 基于 UI 元素的类型和状态重新设计了相应的绘制流程。不过在接着讲解之前我们要暂时放下“界面中只有一个 Button ”的例子,并随便找一个 Activity 先进行相关知识的介绍。

> 示例 Activity 包含了许多重叠的元素,我们这样做的目的是:通过使用极端的例子模拟各种可能出现的情况,解释为什么绘制时会产生这些问题,以及对应的解决策略。
如你所见,简单地根据 UI 的元素类型重新设计绘制流程在大多数情况下都不能满足我们的需求,原因在于:无论是先绘制所有文字,再绘制图片;还是先绘制图片,再绘制文字,都不能获得我们我们真正想要的 UI,因为总有一些该显示的 UI 元素会因为绘制顺序被挡住,我相信任何一个有品位的 UI 设计师都不会接受这样的客户端。
为了能正确地渲染示例 Activity 的 UI,UI 中的文字元素 A 和 B 必须先被绘制,然后绘制图片元素 C,最后是文字元素 D。由于 A 和 B 是同类型的元素,所以我们可以同时进行 A 和 B的绘制,但 C 和 D 只能按照顺序绘制,不然又会产生 UI 元素的覆盖问题。
为了更好地优化 UI 每一个 View 层级的绘制流程,在重新设计了绘制顺序后,就要将类似的绘制操作合并。合并在 DeferredDisplayList 中执行,给这个函数取这个名字是因为绘制操作没有按序执行,而是在所有操作的分析,重排序,合并完成前,不断地延迟其绘制动作,直到分析,重排序,合并完成才按序绘制。
由于每一个 display list 的绘制操作只能用于绘制它自身,如果一个操作支持把相同类型的元素的多个操作合并,那这个操作必须能被用于绘制多个具有相同类型的不同页面。但你需要注意的是,这不意味每一个操作都能进行合并,在新的设计里还留有许多只能重排序的操作。

OpenGLRenderer 是 Skia 2D 图像绘制 API 的一个接口,与常见的接口不同,它不需要利用 CPU 进行硬件加速,而是用 OpenGL 完成了所有硬件加速的工作。虽然有很多办法能完成这样的工作,但是 OpenGLRenderer 是第一个用 C++ 实现的本地类。OpenGLRenderer 在 Android 3.0 中被提出,设计它主要是让它和 GLES20Canvas 协作,绘制我们想要的界面元素。有趣的是,在界面绘制的操作中,只有它们是以协作的形式进行的。
为了把多个操作合并到一个绘制操作中,每一个操作都通过调用 addDrawOp() 方法被添加到 deferred display list 中。同时,绘制操作还需要提供 batchId,因为绘制操作必须知道这个类型的操作能否被合并,此外,绘制操作还需要通过调用 DrawOp.onDefer(...) 方法提供 mergeId,以指明哪些操作已经被合并.
一般 batchId 包含了一个简单的枚举,主要是为 9-Patch 图片元素提供 OpBatch_Patch,并为普通的文字元素提供 OpBatch_Text。mergeId 的值由 DrawOpitself 决定,用于判断两个具有相同的 DrawOp 类型的操作能否被合并。对 9-Patch 图片元素来说,mergeId 用于指向图片资源文件,对文字元素来说,则是对应的文字颜色。来自同一个资源文件夹的资源文件可能会被合并到同一个 batch 中,帮助我们大量地节约绘制流程带来时间开销。
有关一个操作的所有信息都被归并到一个简单的结构中,代码如下:
```java
struct DeferInfo {
// Type of operation (TextView, Button, etc.)
// batchId 注明被操作的 UI 元素的类型(如 TextView,Button等……)
int batchId;
// State of operation (Text size, font, color, etc.)
// mergeId 注明被操作的 UI 元素的状态(如 文字大小,字体,文字颜色等……)
mergeid_t mergeId;
// Indicates if operation is mergable
// 标记操作是否可被合并
bool mergeable;
};
```
当一个绘制操作的 batchId 和 mergeId 被确定,如果它还没有被合并,就会被添加到 batch 队列的尾部。如果没有可用的 batch,我们就会创建一个新的 batch。不过一般情况下,这些绘制操作都是可以合并的。为了知道每一个最近合并的 batch 的去向,我们会通过一个简化的算法调用 MergeBatches 的实例 hashmap,用 batchId 构建键值对保存相应的 batch。对每一个 batch 使用 hashmap 能避免使用 mergeId 导致的冲突。
```java
vector<DrawBatch> batches;
Hashmap<MergeId, DrawBatch*> mergingBatches[BatchTypeCount];
void DeferredDisplayList::addDrawOp(DrawOp op):
DeferInfo info;
/* DrawOp fills DeferInfo with its mergeId and batchId */
/* DrawOp 方法用 mergeId 和 batchId 填充 DeferInfo */
op.onDefer(info);
if(/* op is not mergeable */):
/* Add Op to last added Batch with same batchId, if first
op then create a new Batch */
/* 将 Op 添加到最后被添加入元素的 Batch 中,但这个 Batch 必须与 Op 具有相同 batchId,此外,如果 op 是 Batch 中的第一个元素,那么需要新建一个 Batch */
return;
DrawBatch batch = NULL;
if(batches.isEmpty() == false):
batch = mergingBatches[info.batchId].get(info.mergeId);
if(batch != NULL && /* Op can merge with batch */):
batch.add(op);
mergingBatches[info.batchId].put(info.mergeId, batch);
return;
/* Op can not merge with batch due to different states,
flags or bounds */
/* 如果 Op 与 Batch 具有不同的状态,标记,和边界,那么 Op 将无法被合并到 Batch 中 */
int newBatchIndex = batches.size();
for(overBatch in batches.reverse()):
if (overBatch == batch):
/* No intersection as we found our own batch */
/* Batch 之间应该没有交集 */
break;
if(overBatch.batchId == info.batchId):
/* Save position of similar batches to insert
after (reordering) */
/* 在重排序后保存 batchId 相同的 batch 中对应的位置,便于后面插入元素 */
newBatchIndex == iterationIndex;
if(overBatch.intersects(localBounds)):
/* We can not merge due to intersection */
/* 如果 Batch 间产生了交集,我们不能进行合并 */
batch = NULL
break;
if(batch == NULL):
/* Create new Batch and add to mergingBatches */
/* 如果 batch 为空,则创建一个新的 batch,并将它添加到 mergingBatches 中 */
batch = new DrawBatch(...);
mergingBatches[deferInfo.batchId].put(info.mergeId, batch);
batches.insertAt(newBatchIndex, batch);
batch.add(op);
```
如果当前操作能够与其他具有相同 mergeId 和 batchId 的操作合并,那么这个操作和下一个可以合并的操作都会被添加到现有的 batch 中。但如果它因为状态不一致、绘制标记或边界限制无法被合并,算法就需要将它插入到一个新的 batch 中。为了实现这样的需求,我们则需要获得 batch 队列中所有 batch 对应的 postion。理想情况下,它能在当前绘制操作中找到一个和它状态相同的 batch。不过需要注意的是,在这个绘制操作中为它找到合适的位置的过程中,也必须保证它和其他 batch 没有交集。因此,batch 列表都以逆序寻找一个合适的位置,并确认对应的位置与其他元素没有交集。如果出现了交集,那么对应操作则不能被合并,并需要在这个位置创建一个新的 DrawBatch,并将其插入 Mergebatchedhaspmap。新的 batch 会被添加到 batch 队列的相应位置中。无论发生什么,改操作都会被加入到当前的 batch 中,区别在于:是在新的 batch 还是已存在的 batch 中。
具体的实现会比我们的简化讲解更复杂(虽然我们这里讲的也很难懂……)。但其中优化的方法值得我们学习:算法通过移除堵塞的绘制操作尽可能地避免重绘,同时通过对为合并的操作进行重排序,从而避免 GPU 状态改变带来的开销。
## 绘制界面 ##
在重排序和合并后,新的 deferred display list 终于可以被绘制到屏幕上了。

在 OpenGLRenderers::drawDisplayList(…) 方法里,deferred display list 其实就是一个填满了操作的新建普通显示列表,填充完成后延迟显示页面将绘制它自身。
**OpenGLRenderer: drawDisplayList(…)**
```java
status_t OpenGLRenderer::drawDisplayList(
DisplayList* displayList, Rect& dirty,
int32_t replayFlags) {
// All the usual checks and setup operations
// (quickReject, setupDraw, etc.)
// will be performed by the display list itself
// 所有的常规检查与创建操作(例如 quickReject, setupDraw 等等)都会由 display list 完成
if (displayList && displayList->isRenderable()) {
DeferredDisplayList deferredList(*(mSnapshot->clipRect));
DeferStateStruct deferStruct(
deferredList, *this, replayFlags);
displayList->defer(deferStruct, 0);
return deferredList.flush(*this, dirty);
}
return DrawGlInfo::kStatusDone;
}
```
multiDraw(…) 方法会在列表中的第一个操作中被调用,而其他的操作都被视作参数。被调用的操作负责立刻绘制所有被提供的操作,并调用 OpenGLRenderer 执行绘制其自身的操作。
## 显示列表中的操作 ##
每一个绘制操作都会在拥有对应显示操作列表的 Canvas 里被执行,所有显示操作列表必须实现重载了绘制操作的 replay() 方法。这些绘制操作调用 OpenGLRenderer 去绘制他们,当我们创建一个操作时需要提供一个 renderer 的引用。除此以外,我们还需要实现 onDefer() 方法,并返回操作的 drawId 和 mergeId。为合并的 batch 会设置相应的绘制 id 为 kOpBatch_None。可合并的操作必须实现用于立刻绘制所有已合并的操作的 multiDraw() 方法。
例如,绘制 9-Patch 的操作包含了下列 multiDraw(…) 实现:
**DrawPatchOp::multiDraw(…)**
```java
virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,
const Vector<OpStatePair>& ops, const Rect& bounds) {
// Merge all 9-Patche vertices and texture coordinates
// into one big vector
// 将所有 9-Patche 图片的顶点坐标和纹理坐标合并到一个矢量中
Vector<TextureVertex> vertices;
for (unsigned int i = 0; i < ops.size(); i++) {
DrawPatchOp* patchOp = (DrawPatchOp*) ops[i].op;
const Patch* opMesh = patchOp->getMesh(renderer);
TextureVertex* opVertices = opMesh->vertices;
for (uint32_t j = 0; j < opMesh->verticesCount;
j++, opVertices++) {
vertices.add(TextureVertex(opVertices->position[0],
opVertices->position[1],
opVertices->texture[0],
opVertices->texture[1]));
}
}
// Tell the renderer to draw multipe textured polygons
// 让渲染器绘制具有多种纹理的多边形
return renderer.drawPatches(mBitmap, getAtlasEntry(),
&vertices[0], getPaint(renderer));
}
```
9-Patch 图片的 batchId 总是常量 kOpBatch_Patch,而 mergeId 则是指向图片的指针,因此,所有使用了相同图片的 9-Patch 对象都能够被合并为一个。此外,这种特性对我们使用资源文件里的图片非常有帮助,因为现在 Android 框架层所有经常被使用的 9-Patch 图片都可以根据其相同的纹理合并到同一个地方存储、使用。
## 纹理贴图集 ##
Android 的起始进程 Zygote 总会预加载一些与所有进程共享的资源文件,这些资源文件夹包含了频繁被使用的那些 9-Batch 图片和 Android 控件使用的图片。但在 Android 4.4 之前,每一个进程在 GPU 内存中都拥有这些资源文件的独立拷贝。从 Android 4.4 开始,这些频繁被使用的资源文件则被打包到一个纹理贴图集,随后传输到 GPU 内存中,并共享于所有进程之中。在这些操作完成之后才有可能对标准 Android 框架中的 9-Patch 和 Drawable资源文件进行合并。

> 系统产生的纹理贴图集是为了减少切换纹理带来的 GPU 负荷。
刚刚那张图展示了 Nexus 7 在 Android 4.4 系统下生成的纹理贴图集,图集中包含了所有频繁被使用的 Android 框架层图像资源,如果你看得仔细,你会发现 9-Patch 文件没有突出布局和间距区域的边界,而原始的资源文件在系统启动之初则进行了解析,不过它们之后也不会被用于绘制,所以我们也不用在意。
在 Android 进行系统更新后,系统第一次进行引导时(或者每一次进行引导时), AssetAtlasService 都会重新生成纹理贴图集,并在之后的每一次重新启动过程中再次使用它,直到 Android 更新的内容被应用于系统中。
为了生成纹理贴图集,service 组件会强行搜查各种图集配置,想尽千方百计找到那个能穿上水晶鞋的贴图集配置,那么什么样的贴图集配置是最好的呢?答案是:纹理资源最丰富,且纹理尺寸最小。原因在于:我们所获得的配置信息,会被写入到 /data/system/framework_atlas.config 中,此外,无论元素是否允许旋转,是否添加了间距,配置信息中都包含了我们选择的算法和尺寸大小。完成上述操作后,配置信息就会在之后的每一次重新启动过程中被应用,生成纹理贴图集。之后,系统会分配一个 RGBA8888 的图形缓存区,通过使用 Skiabitmap 将 资源纹理贴图集和所有资源文件绘制到这个缓存区中。经过上述繁复的操作后,资源纹理贴图集将在 AssetAtlasService 组件整个生命周期中有效,只有在系统自身被关闭时才会被释放。
为了真正把所有资源文件打包到一个图集中,AssetAtlasService 会从打包空白纹理开始。在将第一个资源文件放入之后,剩下的空间将会被切分成两个矩形区域。然后利用我们在配置信息中选择的算法,可以将这两个区域分别作为水平方向和垂直方向的视图处理区域。而空白纹理之后的纹理资源文件将会被添加到足以存放它的第一个区域中。而这个区域会再次被切分成两个区域,把下一个资源文件添加到相应区域后再次切分,循环往复。可能有人会问了,这样难道不会因为迭代操作使得时间开销很大吗?不用担心,AssetAtlasService 同时使用多个线程并行操作,使得时间开销被大大减少。
当一个新的 App 被创建,它的硬件渲染器需要 AssetAtlasService 为它提供相应的纹理贴图,并且当渲染器每一次需要绘制 bitmap 或者 9-Patch 图片,它都会先检查它的贴图集。
## 字体的缓存与绘制 ##
为了合并包含文字的 View 的绘制操作,一个简单的方法就是缓存字体。但与纹理贴图集的操作方法相反,字体集是每一个 App 或字体独有的。但由于字体的颜色会被应用到 shader 中,因此 Android 不会将文字颜色添加到字体集中。

> 左边:字体集由字体渲染器生成;右边:用 CPU 生成的几何体渲染字符
就算你只是瞥一眼字体集,你都会注意到只有一部分字符被绘制,如果你看得认真一些,你会注意到只有被使用的字符才会被绘制出来(没有重复的字符)!如果你看到这里在想 Android 支持多少种语言,抑或是支持多少种字符,那只缓存被使用的字符确实是最优解。又因为 Actionbar 和 Button 使用着相同的字体,那么它们俩使用的所有字符都能被合并到一种纹理中。
为了将相应的文字绘制到视图中,渲染器需要在一块拥有边界的纹理区域中生成一个几何体。几何体由 CPU 生成后通过 OpenGL 的 glDrawElements() 命令绘制,如果设备支持 OpenGL ES 3.0,字体渲染器会异步更新字体的纹理缓存,并将其上传到框架的入口,即 GPU 仍接近处于闲置状态时,进行这样的操作能够为每一个框架的加载节省下宝贵的时间。为了实现异步上传,纹理缓存会被实现为 OpenGL 的像素缓存对象。
## OpenGL ##
在博文的前言阶段我曾许下承诺:我会结合部分原生的 OpenGL 绘制命令讲解博文中的知识。那么现在,就是我兑现诺言的时刻了。从此刻开始,我会用 OpenGL 的绘制 log 讲解之前的简单示例 Activity(只有一个 Button 的那个例子!)
```shell
glUniformMatrix4fv(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])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glGenBuffers(n = 1, buffers = [3])
glBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 3)
glBufferData(target = GL_ELEMENT_ARRAY_BUFFER, size = 24576, data = [ 24576 bytes ], usage = GL_STATIC_DRAW)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf18)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf20)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 72, type = GL_UNSIGNED_SHORT, indices = 0x0)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 768, size = 576, data = [ 576 bytes ])
glDisable(cap = GL_BLEND)
glUniformMatrix4fv(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])
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x300)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x308)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 54, type = GL_UNSIGNED_SHORT, indices = 0x0)
eglSwapBuffers()
```
你可以在博文的结尾看到完整的绘制 log
## 结论 ##
两篇超简单的博文看下来,Android 如何在 display list 中通过一系列的命令对 View 层的操作进行重排序和合并,如何在底层中执行这些命令,我相信没人会不懂了吧!(有人懂才怪呢 XD~)
结合两篇博文的知识储备,我们重新回去分析之前的示例 App:App 中 View 的整个渲染流程其实只有5步:

1. 布局绘制其背景图片,也就是我们的线性布局。
2. ActionBar 和 Button 的 9-Patch 图片文件均被绘制,这两个操作被合并到一个 batch 中,因为它俩的 9-Patch 被用在相同的纹理区域中。
3. 为 Actionbar 绘制一个线性布局。
4. 同时绘制 Button 和 ActionBar 中的文字,因为这两个 View 使用了相同的字体,使得字体渲染器能够使用相同的字体纹理,因此能合并两个文字绘制操作。
5. 应用图标绘制完成。
就像你看到的,我们深入解析了绘制 View 层的操作是如何在底层与 OpenGL 命令一一对应的,这个系列的博文也算是得到了一个完美的收尾了。
## Download ##
文章中引用的论文可以下载,[链接在此](http://mathias-garbe.de/files/introduction-android-graphics.pdf)
## 完整 Log ##
**Display List**
Shell
```shell
Start display list (0x5ea4f008, PhoneWindow.DecorView, render=1)
Save 3
ClipRect 0.00, 0.00, 720.00, 1184.00
SetupShader, shader 0x5ea5af08
Draw Rect 0.00 0.00 720.00 1184.00
ResetShader
Draw Display List 0x5ea64d30, flags 0x244053
Start display list (0x5ea64d30, ActionBarOverlayLayout, render=1)
Save 3
ClipRect 0.00, 0.00, 720.00, 1184.00
Draw Display List 0x5ea5ad78, flags 0x24053
Start display list (0x5ea5ad78, FrameLayout, render=1)
Save 3
Translate (left, top) 0, 146
ClipRect 0.00, 0.00, 720.00, 1038.00
Draw Display List 0x5ea59bf8, flags 0x224053
Start display list (0x5ea59bf8, RelativeLayout, render=1)
Save 3
ClipRect 0.00, 0.00, 720.00, 1038.00
Save flags 3
ClipRect 32.00 32.00 688.00 1006.00
Draw Display List 0x5cfee368, flags 0x224073
Start display list (0x5cfee368, Button, render=1)
Save 3
Translate (left, top) 32, 32
ClipRect 0.00, 0.00, 243.00, 96.00
Draw patch 0.00 0.00 243.00 96.00
Save flags 3
ClipRect 24.00 0.00 219.00 80.00
Translate by 24.000000 23.000000
Draw Text of count 12, bytes 24
Restore to count 1
Done (0x5cfee368, Button)
Restore to count 1
Done (0x5ea59bf8, RelativeLayout)
Done (0x5ea5ad78, FrameLayout)
Draw Display List 0x5ea64ac8, flags 0x24053
Start display list (0x5ea64ac8, ActionBarContainer, render=1)
Save 3
Translate (left, top) 0, 50
ClipRect 0.00, 0.00, 720.00, 96.00
Draw patch 0.00 0.00 720.00 96.00
Draw Display List 0x5ea64910, flags 0x224053
Start display list (0x5ea64910, ActionBarView, render=1)
Save 3
ClipRect 0.00, 0.00, 720.00, 96.00
Draw Display List 0x5ea63790, flags 0x224053
Start display list (0x5ea63790, LinearLayout, render=1)
Save 3
Translate (left, top) 17, 0
ClipRect 0.00, 0.00, 265.00, 96.00
Draw Display List 0x5ea5fe80, flags 0x224053
Start display list (0x5ea5fe80,
ActionBarView.HomeView, render=1)
Save 3
ClipRect 0.00, 0.00, 80.00, 96.00
Draw Display List 0x5ea5ed00, flags 0x224053
Start display list (0x5ea5ed00, ImageView, render=1)
Save 3
Translate (left, top) 8, 16
ClipRect 0.00, 0.00, 64.00, 64.00
Save flags 3
ConcatMatrix
[0.67 0.00 0.00] [0.00 0.67 0.00] [0.00 0.00 1.00]
Draw bitmap 0x5d33ae70 at 0.000000 0.000000
Restore to count 1
Done (0x5ea5ed00, ImageView)
Done (0x5ea5fe80, ActionBarView.HomeView)
Draw Display List 0x5ea63618, flags 0x224053
Start display list (0x5ea63618, LinearLayout, render=1)
Save 3
Translate (left, top) 80, 23
ClipRect 0.00, 0.00, 185.00, 49.00
Save flags 3
ClipRect 0.00 0.00 169.00 49.00
Draw Display List 0x5ea634a0, flags 0x224073
Start display list (0x5ea634a0, TextView, render=1)
Save 3
ClipRect 0.00, 0.00, 169.00, 49.00
Save flags 3
ClipRect 0.00 0.00 169.00 49.00
Draw Text of count 9, bytes 18
Restore to count 1
Done (0x5ea634a0, TextView)
Restore to count 1
Done (0x5ea63618, LinearLayout)
Done (0x5ea63790, LinearLayout)
Done (0x5ea64910, ActionBarView)
Done (0x5ea64ac8, ActionBarContainer)
Draw patch 0.00 146.00 720.00 178.00
Done (0x5ea64d30, ActionBarOverlayLayout)
Done (0x5ea4f008, PhoneWindow.DecorView)
```
**OpenGL**
```shell
eglCreateContext(version = 1, context = 0)
eglMakeCurrent(context = 0)
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGetString(name = GL_VERSION) = OpenGL ES 2.0 14.01003
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGenBuffers(n = 1, buffers = [1])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 1)
glBufferData(target = GL_ARRAY_BUFFER, size = 64, data = [64 bytes],
usage = GL_STATIC_DRAW)
glDisable(cap = GL_SCISSOR_TEST)
glActiveTexture(texture = GL_TEXTURE0)
glGenBuffers(n = 1, buffers = [2])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferData(target = GL_ARRAY_BUFFER, size = 131072, data = 0x0,
usage = GL_DYNAMIC_DRAW)
glGetIntegerv(pname = GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
params = [16])
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGenTextures(n = 1, textures = [1])
glBindTexture(target = GL_TEXTURE_2D, texture = 1)
glEGLImageTargetTexture2DOES(target = GL_TEXTURE_2D,
image = 2138532008)
glGetError(void) = (GLenum) GL_NO_ERROR
glDisable(cap = GL_DITHER)
glClearColor(red = 0,000000, green = 0,000000, blue = 0,000000,
alpha = 0,000000)
glEnableVertexAttribArray(index = 0)
glDisable(cap = GL_BLEND)
glGenTextures(n = 1, textures = [2])
glBindTexture(target = GL_TEXTURE_2D, texture = 2)
glPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 1)
glTexImage2D(target = GL_TEXTURE_2D, level = 0,
internalformat = GL_ALPHA, width = 1024, height = 512,
border = 0, format = GL_ALPHA, type = GL_UNSIGNED_BYTE,
pixels = [])
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER,
param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)
glViewport(x = 0, y = 0, width = 800, height = 1205)
glPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 1)
glTexSubImage2D(target = GL_TEXTURE_2D, level = 0, xoffset = 0, yoffset = 0, width = 1024, height = 80, format = GL_ALPHA, type = GL_UNSIGNED_BYTE, pixels = 0x697b7008)
glInsertEventMarkerEXT(length = 0, marker = Flush)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glBindTexture(target = GL_TEXTURE_2D, texture = 1)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, param = 9729)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9729)
glCreateShader(type = GL_VERTEX_SHADER) = (GLuint) 1
glShaderSource(shader = 1, count = 1, string = attribute vec4 position;
attribute vec2 texCoords;
uniform mat4 projection;
uniform mat4 transform;
varying vec2 outTexCoords;
void main(void) {
outTexCoords = texCoords;
gl_Position = projection * transform * position;
}
, length = [0])
glCompileShader(shader = 1)
glGetShaderiv(shader = 1, pname = GL_COMPILE_STATUS, params = [1])
glCreateShader(type = GL_FRAGMENT_SHADER) = (GLuint) 2
glShaderSource(shader = 2, count = 1, string = precision mediump float;
varying vec2 outTexCoords;
uniform sampler2D baseSampler;
void main(void) {
gl_FragColor = texture2D(baseSampler, outTexCoords);
}
, length = [0])
glCompileShader(shader = 2)
glGetShaderiv(shader = 2, pname = GL_COMPILE_STATUS, params = [1])
glCreateProgram(void) = (GLuint) 3
glAttachShader(program = 3, shader = 1)
glAttachShader(program = 3, shader = 2)
glBindAttribLocation(program = 3, index = 0, name = position)
glBindAttribLocation(program = 3, index = 1, name = texCoords)
glGetProgramiv(program = 3, pname = GL_ACTIVE_ATTRIBUTES, params = [2])
glGetProgramiv(program = 3, pname = GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, params = [10])
glGetActiveAttrib(program = 3, index = 0, bufsize = 10, length = [0], size = [1], type = [GL_FLOAT_VEC4], name = position)
glGetActiveAttrib(program = 3, index = 1, bufsize = 10, length = [0], size = [1], type = [GL_FLOAT_VEC2], name = texCoords)
glGetProgramiv(program = 3, pname = GL_ACTIVE_UNIFORMS, params = [3])
glGetProgramiv(program = 3, pname = GL_ACTIVE_UNIFORM_MAX_LENGTH, params = [12])
glGetActiveUniform(program = 3, index = 0, bufsize = 12, length = [0], size = [1], type = [GL_FLOAT_MAT4], name = projection)
glGetActiveUniform(program = 3, index = 1, bufsize = 12, length = [0], size = [1], type = [GL_FLOAT_MAT4], name = transform)
glGetActiveUniform(program = 3, index = 2, bufsize = 12, length = [0], size = [1], type = [GL_SAMPLER_2D], name = baseSampler)
glLinkProgram(program = 3)
glGetProgramiv(program = 3, pname = GL_LINK_STATUS, params = [1])
glGetUniformLocation(program = 3, name = transform) = (GLint) 2
glGetUniformLocation(program = 3, name = projection) = (GLint) 1
glUseProgram(program = 3)
glGetUniformLocation(program = 3, name = baseSampler) = (GLint) 0
glUniform1i(location = 0, x = 0)
glUniformMatrix4fv(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])
glUniformMatrix4fv(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])
glEnableVertexAttribArray(index = 1)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x681e7af4)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x681e7afc)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 4)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 4)
glDrawArrays(mode = GL_TRIANGLE_STRIP, first = 0, count = 4)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 0, size = 576, data = [ 576 bytes ])
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 576, size = 192, data = [ 192 bytes ])
glEnable(cap = GL_BLEND)
glBlendFunc(sfactor = GL_SYNC_FLUSH_COMMANDS_BIT, dfactor = GL_ONE_MINUS_SRC_ALPHA)
glUniformMatrix4fv(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])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glGenBuffers(n = 1, buffers = [3])
glBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 3)
glBufferData(target = GL_ELEMENT_ARRAY_BUFFER, size = 24576, data = [ 24576 bytes ], usage = GL_STATIC_DRAW)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf18)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf20)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 72, type = GL_UNSIGNED_SHORT, indices = 0x0)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 768, size = 576, data = [ 576 bytes ])
glDisable(cap = GL_BLEND)
glUniformMatrix4fv(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])
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x300)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x308)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 54, type = GL_UNSIGNED_SHORT, indices = 0x0)
glEnable(cap = GL_BLEND)
glUniformMatrix4fv(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])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glBindTexture(target = GL_TEXTURE_2D, texture = 2)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x696bd008)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x696bd010)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 80)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 80)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 120, type = GL_UNSIGNED_SHORT, indices = 0x0)
glGenTextures(n = 1, textures = [3])
glBindTexture(target = GL_TEXTURE_2D, texture = 3)
glPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 4)
glTexImage2D(target = GL_TEXTURE_2D, level = 0, internalformat = GL_RGBA, width = 64, height = 64, border = 0, format = GL_RGBA, type = GL_UNSIGNED_BYTE, pixels = 0x420cd930)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)
glUniformMatrix4fv(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])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 1)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x0)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x8)
glBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 0)
glDrawArrays(mode = GL_TRIANGLE_STRIP, first = 0, count = 4)
glGetError(void) = (GLenum) GL_NO_ERROR
eglSwapBuffers()
```
================================================
FILE: androidweekly/深入了解Bundle和Map/readme.md
================================================
深入了解Bundle和Map
---
>
* 原文链接 : [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)
* 译者 : [yinna317](https://github.com/yinna317)
* 校对者: [chaossss](https://github.com/chaossss)
* 状态 : 翻译完成
##前言
因为往Bundle对象中放入Map实际上没有表面上看起来那么容易。
这篇博客是在Eugenio @workingkills Marletti的帮助下完成的。
**警告**:这是一篇篇幅较长的博客
##案例:往Bundle对象放入特殊的Map
假设有这样一个案例:你需要将一个要传递的map附加到Intent对象。这个案例虽然不常见,但是,这种情况也是很有可能发生。
如果你在Intent对象中附加的是一个Map最常见的接口实现类HashMap,而不是包含附加信息的自定义类,你是幸运的,你可以用以下方法将map附加到Intent对象:
```java
intent.putExtra("map",myHashMap);
```
在你接收的Activity里,你可以用以下方法毫无问题地取出之前在Intent中附加的Map:
```java
HashMap map = (HashMap) getIntent().getSerializableExtra("map");
```
但是,如果你在Intent对象附加另一种类型的Map,比如:一个TreeMap(或者其他的自定义Map接口实现类),你在Intent中取出之前附加的TreeMap时,你用如下方法:
```java
TreeMap map = (TreeMap) getIntent().getSerializableExtra("map");`
```
然后就会出现一个类转换异常:
```java
java.lang.ClassCastException: java.util.HashMap cannot be cast to java.util.TreeMap`
```
因为编译器认为你的Map(TreeMap)正试图转换成一个HashMap
稍后我会详细地为大家讲解我为什么用 getSerializableExtra() 这个方法来取出附加到Intent中的Map。现在我可以先给大家一个通俗易懂的解释:因为所有默认的 Map 接口实现类都是Serializable,并且 putExtra()/getExtra() 方法接受的参数几乎都是“键-值”对,而其中值的类型非常广泛,Serializable就是其中之一,因此我们能够使用 getSerializableExtra() 来取得我们传递的Map。
在我们进行下一步之前,让我们去了解调用putExtra()/get*Extra()时涉及到的一些类或方法。
**提示**:文章很长,如果你只是想要一个解决方案的话,请跳到文章的最后面解决方案那里。
##Parcels:
大家都知道(也许少部分人不知道),在Android 系统中所有进程间通信是基于Binder机制。但是,希望大家明白的是允许数据在进程间传递是基于Parcel。
Parcel是Android进程间通信中, 高效的专用序列化机制。
与 Serializable 相反,Parcels 决不应该被用于储存任何类型的持久性数据,因为 Parcels 并不是为“操作可更新数据”(可更新数据指的是,具有持久性的数据会由于它的长留存时间会不断更新它的值)提供的,Parcels 更多的是传递 “短暂的一次性数据”,所以,不管什么时候使用Bundle,你在底层处理的都是Parcel。比如附加数据到Intent对象,在Fragment中设参数,等等。
Parcels 能处理很多类型,包括:本地类型,字符串类型,数组类型,Map类型,sparse arrays类型,以及parcelables和serializables对象。
除非你必须使用Serializable,一般情况下推荐使用Parcelables读写数据到Parcel.
相较于Serializable,Parcelable的优势更多地体现在性能上,因为Parcelable在内存开销方面更小,而这个理由足以让我们在大多数情况下毫不犹豫地选择Parcelable而不是Serializable。
##深入底层分析
让我们来了解下是什么原因使我们得到了ClassCastException异常。
从我们的代码中可以看到,我们对Intent中putExtras()的调用实际上是传入了一个String值和一个Serializable的对象,而不是传入一个Map值。因为Map接口实现类都是Serializable的,而不是Parcelable的。
###第一步:找到第一个突破口
让我们来看看在Intent.putExtra(String, Serializable)方法中做了什么。
>Intent.java
```java
public Intent putExtra(String name, Serializable value) {
// ...
mExtras.putSerializable(name, value);
return this;
}
}
```
在这里,mExtras是个Bundle,Intent指令所有附加信息到bundle,从而调用了Bundle的putSerializable()方法,让我们来看看在Bundle中的putSerializable()方法中做了什么:
>Bundle.java
```java
@Override
public void putSerializable(String key, Serializable value) {
super.putSerializable(key, value);
}
```
从上面代码我们可以看出,Bundle中的putSerializable()方法中只是对父类的实现,调用了父类BaseBundle中的putSerializable()方法。
>BaseBundle.java
```java
void putSerializable(String key, Serializable value) {
unparcel();
mMap.put(key, value);
}
```
首先,让我们忽略其中的unparcel()这个方法。我们注意到mMap是一个ArrayMap<String, Object>类型的。这告诉我们,到了这步,我们往mMap中设入的是一个Object类型的。也就是说,不管我们之前是什么类型,在父类BaseBundle这里都转成了Obeject类型。
####这里开始出现问题了

###第二步:分析写入 map
有趣的是当把Bundle中的值写入到一个Parcel中时,如果此时我们去检查我们附加值的类型,我们发现仍然能得到正确的类型。
```java
Intent intent = new Intent(this, ReceiverActivity.class);
intent.putExtra("map", treeMap);
Serializable map = intent.getSerializableExtra("map");
Log.i("MAP TYPE", map.getClass().getSimpleName());
```
如我们所料,这里打印出来的是TreeMap类型的。因此,在Bundle中写成一个Parcel,与再次读这期间一定发生了类型转换。
如果我们观察下是怎样写入Parcel的,我们看到,实际上是调BaseBundle中的writeToParcelInner()方法。
>BaseBundle.java
```java
void writeToParcelInner(Parcel parcel, int flags) {
if (mParcelledData != null) {
// ...
} else {
// ...
int startPos = parcel.dataPosition();
parcel.writeArrayMapInternal(mMap);
int endPos = parcel.dataPosition();
// ...
}
}
```
跳过所有不相干的代码,我们看到在Parcel的writeArrayMapInternal()方法中做了大量的事(mMap 是一个 ArrayMap类型)
>Parcel.java
```java
/* package */ void writeArrayMapInternal(
ArrayMap<String, Object> val) {
// ...
int startPos;
for (int i=0; i<N; i++) {
// ...
writeString(val.keyAt(i));
writeValue(val.valueAt(i));
// ...
}
}
```
####下一步让我们更深入地分析

###第三步:分析写入Map值
那么,在Parcel中writeValue() 方法又是怎样呢?主要是一些if...else语句。
>Parcel.java
```java
public final void writeValue(Object v) {
if (v == null) {
writeInt(VAL_NULL);
} else if (v instanceof String) {
writeInt(VAL_STRING);
writeString((String) v);
} else if (v instanceof Integer) {
writeInt(VAL_INTEGER);
writeInt((Integer) v);
} else if (v instanceof Map) {
writeInt(VAL_MAP);
writeMap((Map) v);
} else if (/* you get the idea, this goes on and on */) {
// ...
} else {
Class<?> clazz = v.getClass();
if (clazz.isArray() &&
clazz.getComponentType() == Object.class) {
// Only pure Object[] are written here, Other arrays of non-primitive types are
// handled by serialization as this does not record the component type.
writeInt(VAL_OBJECTARRAY);
writeArray((Object[]) v);
} else if (v instanceof Serializable) {
// Must be last
writeInt(VAL_SERIALIZABLE);
writeSerializable((Serializable) v);
} else {
throw new RuntimeException("Parcel: unable to marshal value "+ v);
}
}
}
```
虽然TreeMap是以Serializable的类型传入到 bundle,但是在Parcel中writeValue()方法执行的是map这个分支的代码---“v instanceof Map”,(“v instanceof Map”在“v instanceOf Serializable”之前)
####到了这里,问题更明显了。

现在我想,他们是不是对 Map 进行了一些非常规的处理,使得 Map 将无可避免地被转换为 HashMap 类型。
###第四步:分析将Map写入到Parcel中
Parcel中的writeMap()方法并没有做什么事,只是将我们传入的Map值强转成Map<String, Object>类型,调用writeMapInternal()方法。
>Parcel.java
```java
public final void writeMap(Map val) {
writeMapInternal((Map<String, Object>) val);
}
```
JavaDoc文档对这个方法的解释非常清楚:即Map必须是String类型的。
尽管我们可能传入一个key值不为String的Map,类型擦除也使我们不会获得运行时错误。(这是完全非法的)
事实上,看一下Parcel中的writeMapInternal()方法,这更打击我们。
>Parcel.java
```java
/* package */ void writeMapInternal(Map<String,Object> val) {
// ...
Set<Map.Entry<String,Object>> entries = val.entrySet();
writeInt(entries.size());
for (Map.Entry<String,Object> e : entries) {
writeValue(e.getKey());
writeValue(e.getValue());
}
}
```
类型擦除使所有的这些代码都不会出现运行时错误。
实际上,在我们遍历Map调用writeValue()方法时,依赖的是原先的类型检查。从我们之前分析writeValue() 这个方法能看出,writeValue()能处理非String类型的key值。
也许这里的文档和代码在某些地方有些不一致(还没有同步)。
但是,如果你在一个Bundle里对TreeMap<Integer, Object>进行设值和取值,将不会出现问题。
当然也还是会出现TreeMap转换成HashMap的异常。
####黑洞启示录:

在这里已经非常清楚了,当Map写入到一个Parcel时,Map丢失了它们的类型,所以当我们再次读时是没办法来复原原来的信息。
###第五步:分析读Map
让我们来看看Parcel中readValue()这个方法,这个方法和writeValue()相对应。
>Parcel.java
```java
public final Object readValue(ClassLoader loader) {
int type = readInt();
switch (type) {
case VAL_NULL:
return null;
case VAL_STRING:
return readString();
case VAL_INTEGER:
return readInt();
case VAL_MAP:
return readHashMap(loader);
// ...
}
}
```
>parcel处理写入数据的方式是:
1. 写入一个int来定义数据类型(一个VAL_*的常量)。
2. 存储数据本身(包括其他一些元数据,比如String这种没有固定大小的类型的数据长度)。
3. 递归调用非原始数据类型。
这里我们可以看到,readValue()方法中,首先读取一个int的数据,这个int数据是在writeValue()中将TreeMap设成的VAL_MAP的常量,然后去匹配后面的分支,调用readHashMap()方法来取回数据。
>Parcel.java
```java
public final HashMap readHashMap(ClassLoader loader)
{
int N = readInt();
if (N < 0) {
return null;
}
HashMap m = new HashMap(N);
readMapInternal(m, N, loader);
return m;
}
```
readMapInternal()这个方法只是将我们从Parcel中读取的map重新进行打包。
这就是为什么我们总是从Bundle中获得一个HashMap,同样的,如果你创建了一个实现了Parcelable自定义类型Map,得到的也是一个HashMap。
很难说本身设计如此,还是是一个疏忽。
这确实是一个极端例子,因为在一个Intent中传一个Map是比较少见的,你也只有很小的理由来传Serializable而不是Parcelable的。
但是文档上没有写,这让我觉得应该是个疏忽,而不是本身设计如此。
##解决方案:
好了,分析底层代码我们已经弄明白了我们的问题,现在我们定位到问题的关键位置。
我们需要明白的是在writeValue()方法中,我们的TreeMap不应该进入“v instanceOf Map”这个分支。
当我和 Eugenio谈话时,我想到的第一个想法是将map包裹成一个Serializable的容器,这个想法是丑陋的但是有效的。
Eugenio迅速写了个通用的wrapper类解决了这个问题。
>MapWrapper.java
```java
public class MapWrapper<T extends Map & Serializable> implements Serializable {
private final T map;
public MapWrapper(T map) {
this.map = map;
}
public T getMap() {
return map; public static <T extends Map & Serializable> Intent
putMapExtra(Intent intent, String name, T map) {
return intent.putExtra(name, new MapWrapper<>(map));
}
public static <T extends Map & Serializable> T
getMapExtra(Intent intent, String name)
throws ClassCastException {
Serializable s = intent.getSerializableExtra(name);
return s == null ? null : ((MapWrapper<T>)s).getMap();
}
}
}
```
##另一个可行的解决方案:
另一个解决方法是,在你将Map附加到Intent前,将Map转成byte array的。然后调用getByteArrayExtra()方法。但是这种方法你必须处理序列化与反序列化的问题。
如果你想要其他的解决方案,你可以依据Eugenio提供的代码要点来写一个。
##当你不能掌控Intent上面的代码时:
也许,有这样或那样的原因,对于Bundle中的代码你无法掌控,比如可能在第三方的library中。
这种情况,要想到,
Map接口实现类有一个构造器方法,可以将map作为参数传入,比如 new TreeMap(Map),你可以把从Bundle中取回的HashMap,用构造器的方式转成你想要的类型。
不过,要记得的是,这种用构造器的方式,map中的附加属性将会丢失,只有键值对被保存了下来。
##总结:
在Android开发中,你可能会被一些表面的事所欺骗,特别是一些小的,似乎是无关紧要的事。
当事情没有像我们期盼中那样发生时,不要死盯着JavaDoc文档,因为JavaDoc可能过时了,JavaDoc的作者也不知道你的特殊需求。这个时候去看看源码,答案可能在AOSP代码里。
AOSP代码是我们的巨大财富,这在移动开发领域几乎是独一无二的,因为我们能够准确的知道在底层都做了些什么。
当你知道了底层代码执行了什么,你也就能够成为一个更好的开发人员。
记住:凡事要嘛就是成功,要嘛就是失败

##最后:
感谢[chaossss](https://github.com/chaossss) 帮我校对文章
初次翻译,Android也是刚接触,翻译不好的地方,请大家多多指教。
================================================
FILE: androidweekly/符合Material Design的抽屉导航效果/readme.md
================================================
###符合Material Design的抽屉导航效果:
---
>
* 原文链接 : [Navigation Drawer styling according to Material Design](https://medium.com/@sotti/navigation-drawer-styling-according-material-design-5306190da08f)
* 译者 : [wly2014](https://github.com/wly2014)
* 校对者: [chaossss](https://github.com/chaossss)
* 状态 : 校对完成

####前言:
现在看来,抽屉式导航[已经成为主流导航模式之一](http://goo.gl/w4FVWS)。尽管广受[批评](https://medium.com/@villainschorus/your-problem-goes-beyond-hamburgers-e0aae6a1576),但我还是很喜欢该样式,因此我决定在我写的几个app上添加这个控件。这篇文章想通过介绍我觉得抽屉式导航有趣的地方,帮助阅读本文的 Android 开发者们学习到一些知识,同时从其他人的评论中学习到更多的东西。
这是三篇文章中的第二篇。欢迎查看第一篇和第三篇:
* [Material Design下的抽屉导航的大小](https://medium.com/@sotti/navigation-drawer-styling-under-material-design-f0767882e692)
* Material Design下的抽屉效果的行为(敬请期待)
你可以从下面查看Material Design指南上关于抽屉导航的部分:
* [Navigation Drawer pattern](http://www.google.com/design/spec/patterns/navigation-drawer.html)
* [Material Design metrics and keylines](http://www.google.com/design/spec/layout/metrics-keylines.html#)
* [Toolbar metrics](http://www.google.com/design/spec/layout/structure.html#structure-toolbars)
### 开始:
抽屉式导航一直以来都是被争论的热点话题。当 Material Design 规范刚发布的时候,抽屉式导航就在规范中处在一个尴尬的位置,使得开发者们很[困惑](http://goo.gl/q3dnCI)到底要不要使用抽屉式导航,甚至在 Material Design 规范下的[开发指南出来以后](https://plus.google.com/+SebastianoPoggi/posts/6MFgMeRLrrg),有关如何在 Android 开发中对待抽屉式导航都没有一个清晰的答案。
现在,虽然这儿已有一些漂亮的[库](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)。
在这篇文章里,我将会谈论如何使用抽屉的样式,但是并不会完全涉及 Material Design 指南上的所有样式,只是捡一些我认为需要强调的东西。
准备好了吗?
### 位置
在过去,抽屉式导航栏 和 ActionBar 处于同一个 View 层级

随着这种设计模式的发展,其中相互矛盾的地方也开始浮出水面……在 Material Design 中很清晰地指出:处于不同 View 层级的两个页面,是不能共存于同一个父布局中的同一个 View 层级的。有关这个问题引发了[许多讨论](https://plus.google.com/+RomanNurik/posts/3G8zYvN5oRC),但[更重要的是](http://goo.gl/SHrQmd), [Google 并没有给出好的解释](http://goo.gl/FghQhb),不过最后抽屉式导航还是得到了一个被认可的定义:
左抽屉式导航打开后,导航栏的高度应该和屏幕一致,但要低于状态栏。此外,其他任何在抽屉层以下的内容都应该被阴影覆盖,但这些内容又是可见的。
> 左侧的导航抽屉横跨屏幕的高度并覆盖下方的状态栏。抽屉下的东西将会变暗。但仍是可见的。

注意图中右边的抽屉略有不同。
注:我假设你使用的是[AppCompat ](https://chris.banes.me/2014/10/17/appcompat-v21/) [toolbar](https://developer.android.com/reference/android/widget/Toolbar.html)
既然toolbar是View 层级的另一个view,不妨就就将toolbar置于抽屉的layout之下,跟其他的view一样。
如果你在Google应用上看到如下的东西也不用担心:

Google+ Photos可能是最后一个使用抽屉,却没有覆盖ActionBar/Toolbar的Google应用,但是我想他应该很快就会被改过来。
### 旋转标题图标
你还记得当抽屉打开时那个ActionBar/Toolbar中的漂亮的图标动画吗?这个动画在Holo主题下并不是很好看,在 Matarial Design 下却很漂亮。

我觉得当它第一次出现在Google Play Store时,很多的开发者和设计师都会很喜欢它。
仅在那数周之后,抽屉式导航上就[开始出现该动画](https://lh6.googleusercontent.com/-9oPeSA7FUkI/VOs530mLbLI/AAAAAAABapc/ekQNTZPXyoE/w499-h281-no/tumblr_inline_nk2y80nOF91rllljr.gif)。那时它显得很特别,因为在Google Material Design videos 和 promo features上都出现了它的身影。

我记得很多人在第一次遵循 Matarial Design 规范开发时,就是使用这个汉堡/箭头图标动画。Google第一次实现这个抽屉式导航栏的时候,并不是把它放在 ToolBar 的上面,因此你可以看到这个精美的动画。但是 Material Design 指南发布后,出现了一个很奇怪的现象,很多的Google应用都在做与 Material Design 指南背道而驰的事情。即使是现在我写这篇博文的时候,大部分的应用还是在遵循着 Material Desgin 指南,但我还是希望抽屉式导航栏都能在 Toolbar 上面。
在我看来,Material Design发布的已经有点晚了。因为图标动画已出现在发布了的SDK中并且在默认情况下被使用了。
由于某些原因,即使要求把抽屉导航栏布局到其他的view之上,但大多数的google应用还是会有这样的动画(注:在写下这篇文章的时候,Gmail和Inbox已经停用了),即使你很难发现它(但如果你仔细看的话,在缓慢的移动抽屉式导航时,还是可以看到动画的)。让我很不爽的是:一旦你看到了,又会每次都忍不住去看,得不偿失。因此,我也决定关掉这种动画效果。
第一眼看来,DrawerArrowStyle的参数很容易懂:
[< item name="spineBars"> true < /item>](https://developer.android.com/reference/android/support/v7/mediarouter/R.attr.html#spinBars)
[Android Developers中定义如下:](https://developer.android.com/reference/android/support/v7/appcompat/R.attr.html#spinBars)
> 在移动抽屉导航栏的过程中,无论图标是否应该旋转,都要设定一个布尔值:要么是 true,要么是false.
但问题是,这并不能起到作用。如果你设置为false,bars就会以一种奇怪的方式旋转。
我发现的解决的方式是:覆写onDrawerSlide方法。见下面链接的Gist。
> 既然这个图标动画的可视性较差,那就没有必要再保留它了。如果你不注意看,你就看不到它,但当你注意看并看到的时候,又不知道是怎么回事。
### 资料图片
这张个人头像是圆形的,我们有很多方法能让图片变成圆形,但我每次需要实现这个需求想起的都是 [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),我还没去了解过它的具体实现,可能值得我们看一看吧。
在Google Paly Movies与Google Paly Books上,这个图片有一个白色的边框。而其他的Google app上却没有。Google+和Hangouts的资料图片在toolbar上,不过却有白边框。


注意:[查看资料图片大小](https://medium.com/@sotti/navigation-drawer-styling-under-material-design-f0767882e692)
> 资料图片是圆形的,通常没有边框。建议你通过Romain Guy推出的库来获得圆形。
### 封面图片
封面图片(不同于资料图片),是账号/头像部分的背景(就是抽屉式导航的上部,通常你可以在此切换账号,查看昵称,email和你的资料图片)。
这块的文字是白色的,并且要确保能看的见,你可以应用一个前景或者半透明的黑色来覆盖封面图片。我试了一下,发现40-50% 的黑色是最好的。要注意的是,不要既弄得图片不可见,又弄得文字没法读。
我是在FrameLayout中加一个前景。但我不知道这是不是最好的方法,欢迎大家交流。我并没有实现在账号切换的功能,而且这整个layout/section都是可点击的,有touch反馈,或者是Lollipop中的ripple,或者两者。当然你也可以使用[centerCrop ](https://developer.android.com/reference/android/widget/ImageView.ScaleType.html)scaleType 让它更漂亮。

仔细看一下这个图片,你会发现其实它在状态栏下也是可见的。当我写下这行字的时候,Google apps正在应用这种效果。Gmail,Inbox,Keep,Playe Story和Hangouts已经实现了,而其他的也准备实现它。当然,这只是Lollipop及以上的版本中才会有的效果。


即使是现在,在Play Store 上的Google IO 的有些应用的抽屉式导航也是完全错的,但他们在改良代码,并且准备下一个版本了(不过好像有段时间了... 可能会在今年几个月后的Google IO 大会之前更新)
比较神奇的是[Google ScrimInsets layout](http://goo.gl/07TJnm)。拷贝,粘贴,然后自己试着修修改改就OK了。我觉得Google的人员应该比我做的更好。深入的阅读一下gist上的代码,了解一下关于 themes/styles 的更多的详细内容,能让你有更好地表现。
让我有点疑惑就是ScrimInsets layout能不能应用到Lollipop以下的版本中。我知道在Kit Kat中是可行的,但是Google并没这样做。可以肯定的是“挤满”状态栏和/或导航栏在Lollipop下的版本中并不存在,这可能是背后的一个原因。
注意:[查看封面照片的大小](https://medium.com/@sotti/navigation-drawer-styling-under-material-design-f0767882e692)
> 只有在Lollipop中,抽屉导航会出现在状态栏之下。在Lollipop之下的版本中,挤满状态或导航栏并不是什么事情,这可能是其中的原因。
### 可选中行的背景,图标和文字

当要更改抽屉中的主要行的样式的时候,对每行的每个元素,我们都要处理其中的三个子元素(背景,图标,文本)和3个不同的状态(默认,选中,点击)。但每一个开发者都需要明白:开发 App 不需要完全遵循[规范](http://www.google.com/design/spec/patterns/navigation-drawer.html) ,但是了解规范又是必不可少的,大家可以看看 Google 的 App 和其他的一些好看的apps是怎样体现 Material Desgin 所包含的思想的,把你从这些 App 里总结出来的东西应用到你的 App 中。
Okay,现在来看一下Google apps 都有哪些特征。在下面的图片中,第一行是默认的状态,第二行是选中的状态,第三行是点击的状态。











虽然上面的图片看起来很相似,但其实它们是不一样的。总结一下就是:


> Google apps看起来十分的连贯,但是当你注意下细节时就会发现,此时抽屉式导航的选中行有超过10多种的样式。
* 拇指规则:
在自己不断地尝试并参考了设计指南与Google apps后,这是我提出的一些想法:


### 如何实现
尽管我想知道,但是没人告诉我他是怎么来实现的,所以我自己就这么做了。
首先,使用两个drawable作为背景。一个放在res/drawable-v21里,为Lollipop及以上的版本使用;另一个在res/drawable中,目标是更低的版本。因为ripple在5.0以下的版本中并不存在。
> 仅在Lollipop及以上的版本中使用ripple。Ripples并不支持5.0以下的版本。
每当你点击一行的时候,ripple就会出现,不管它选中与否。因此,你在res/drawable-v21中的是有一组包含ripple的items的selector。这是因为我们希望对于选中和未选中的行,在被点击时都能显示相同的ripple,但是未选中的背景是白色,而选中的item的背景是grey_200。
另外在res/drawable中,你需要的是一个不包含ripple的selector。
### 图标
在最近的几个月里,我尝试让同一图标呈现出不同的颜色来。 因此,我就把所有的图标都先弄成白色,然后在用想要的颜色进行[着色](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)。
我实现的方式是在一个[自定义的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)。
看一下下面链接中的gist中我是如何做的。如果你发现了更好的方式,或错误,请反馈给我。
### Header和footer的表现

头部(aka account section)中有些是不可滑动的,而有些是可滑动的。以我看来,如果可以的话,最好设计成不可滑动的。这样的话,抽屉式导航看起来更美观,连贯,易用。
底部(aka setting and support)可以是可滑动的,也可以不是。如果你看一下Google Apps,就会发现有些是不可滑动的,而有些是在可滑动的scroll的底部。如果你有需求是不能放在抽屉的底部的话,那就放在能滑动的列表的后面。


再次强调一下,在我看来,不可滑动是最好的方式,但是也可以有例外。比如,当头部不可滑动时,有些条目就被固定了,因此在抽屉里就有了可滚动的区域了。但是如果可滚动的空间太小,那么看起来就很糟糕了(只有一行或两行),要想得到更多的空间,那么一个好的办法就是将footer 解除固定。
> 头部和底部理应被固定住,除非抽屉需要更多的空间以表现更出色。
由于抽屉选项,路径,结构...的不同,因此这里其实并没有什么拇指法则。
### 资源
-[Google Official Material Design icons](https://github.com/google/material-design-icons)
-[Material Design Color Definitions](http://www.google.com/design/spec/style/color.html)
### 代码
-[Github项目地址](https://github.com/Sottti/MaterialDesignNavDrawer)
### 总结
这是关于如何更改抽屉式导航的样式,你仍需要花费很多时间来想清楚你想做成什么样,而这要比做到花费的时间要长的多。
如果你想要了解更改抽屉式导航的样式,请看看另两篇文章。
欢迎评论,反馈...
Hava fun!
================================================
FILE: androidweekly/让你的Android应用能使用多种主题-Part-1/readme.md
================================================
在你的Android App中支持多主题
---
>
* 原文链接 : [Supporting multiple themes in your Android app (Part 1)](http://www.hidroh.com/2015/02/16/support-multiple-themes-android-app/)
* 译者 : [chaossss](https://github.com/chaossss)
* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)
* 状态 : 完成


我最近一直在忙着整我的黑客资讯App——Materialistic,今天难得有空,就让我来给大家分享一下我在Materialistic里使用的一个有趣的功能吧。
纵观现在的主流阅读类App,用户最常见的需求就是能够基于自己的阅读习惯选择明亮/灰暗两种风格的主题。为了用户的使用体验,我当然要为Materialistic添加这样的功能啦,要不然没人用我会很伤心的!而且很幸运的是,在Android里支持多种主题的切换并不麻烦(如果你的代码没有问题的话),实现这个功能蛮顺利的。所以今天我打算通过这篇博客给大家介绍我在Materialistic里面为了支持多种主题切换所使用的方法。
准备工作:
1. 你最少要有两个由Android 基本的light/dark主题衍生而来的主题。如果你使用了最新的appcompat-v7包,所对应的就是Theme.AppCompat.Light 或 Theme.AppCompat.Light.DarkActionBar(明亮风格),和Theme.AppCompat(灰暗风格)主题
1. 你需要为你的主题设置颜色。你可以在 [Google design spec](http://www.google.com/design/spec/style/color.html#color-color-palette " Google design spec website") 里面看到有关颜色搭配的指导
1. (可选项)为每一个主题的选项菜单图标加上颜色。取决于你的实现方式,染色过程可以是自动的,也可以是手动的,不过自动化的过程不就意味着你可以把一套图标应用于一种主题嘛,其他的调整只要改改颜色就可以了;但就Materialistic的实际需求来考虑,我还是为一个主题预留了多套不同的图标来避免麻烦……
我今天就以明亮风格的主题来开始讲解吧:
## values/styles.xml ##
```xml
<style name="AppTheme" parent="Theme.AppCompat.Light">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColorPrimary">@color/textColorPrimary</item>
<item name="android:textColorSecondary">@color/textColorSecondary</item>
<item name="android:textColorPrimaryInverse">@color/textColorPrimaryInverse</item>
<item name="android:textColorSecondaryInverse">@color/textColorSecondaryInverse</item>
<!-- some other theme configurations for actionbar, overflow menu etc. -->
...
</style>
```
## values/colors.xml ##
```xml
<!-- brand color: orange -->
<color name="colorPrimary">#FF9800</color>
<color name="colorPrimaryDark">#F57C00</color>
<color name="colorPrimaryLight">#FFE0B2</color>
<!-- accent color: red -->
<color name="colorAccent">#FF5252</color>
<!-- text color: white -->
<color name="textColorPrimary">#FFFFFF</color>
<color name="textColorSecondary">#9E9E9E</color>
<!-- inverse text color: 87% black -->
<color name="textColorPrimaryInverse">#DE000000</color>
<color name="textColorSecondaryInverse">#9E9E9E</color>
```
## AndroidManifest.xml ##
```xml
<application android:name=".Application" android:theme="@style/AppTheme">
...
</application>
```
theme 中涉及的各种属性的含义可以在[Android Developers blog](http://android-developers.blogspot.sg/2014/10/appcompat-v21-material-design-for-pre.html "Android Developers blog") 里面找到解释
## 贴心小提示 ##
> 虽然Android里面style的属性/值非常全面,我们想要实现的效果style基本上都包含了有,但是Android文档有关这些主题属性的解释特别少,尤其是对appcompat的解释。所以我们还是建议你写一个小Demo去测试style里面的属性/值应该怎么使用、能实现什么样的效果,然后再根据我们的需求去考虑使用哪些属性/值来实现我们想要的效果。
根据Android的Material Design规范,选项菜单图标的颜色应该和action bar上面的文字颜色保持一致,在我这是通过 android:textColorPrimary 来实现的,也就是使用#FFFFFF,基于这样的规范,我们需要为action bar提供一套白色的选项菜单图标。
## 贴心小提示 ##
> Google 有在 [material-design-icons - Github](https://github.com/google/material-design-icons "Github") 上提供一些开源的Material Design图标哦。
## menu/my_menu.xml ##
```xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@id/menu_comment"
android:icon="@drawable/ic_mode_comment_white_24dp" />
<item android:id="@id/menu_story"
android:icon="@drawable/ic_subject_white_24dp" />
<item android:id="@id/menu_share"
app:actionProviderClass="android.support.v7.widget.ShareActionProvider" />
</menu>
```
为了使颜色一致,并且能让我们的Views和Texts能够在多个主题下被使用,最好的解决办法就是把颜色变成资源的引用,例如:android:textColor="@color/textColorPrimary;又或者是通过设置style来改变,例如:在textEmptyStyle.xml文件下,我们只使用被选中的颜色
## values/styles.xml ##
```xml
<style name="textEmptyStyle">
<item name="android:textColor">@color/textColorSecondary</item>
<item name="android:textSize">@dimen/abc_text_size_headline_material</item>
...
</style>
```
我相信通过今天在上面所介绍的这些内容已经足够让我们实现一个符合Material Design的明亮风格的主题了,下一篇博文我将会给大家介绍如何实现一个符合Material Design的灰暗风格的主题,以及如何在运行App的过程中切换主题。希望大家继续关注我的博客哦。
================================================
FILE: androidweekly/让你的Android应用能使用多种主题-Part-2/readme.md
================================================
让你的Android应用能使用多种主题 ( Part 2 )
---
>
* 原文链接 : [Supporting multiple themes in your Android app (Part 2)](http://www.hidroh.com/2015/02/25/support-multiple-themes-android-app-part-2/)
* 译者 : [chaossss](https://github.com/chaossss)
* 校对者: [Mr.Simple](https://github.com/bboyfeiyu)
* 状态 : 完成
在 [上一篇博文](http://www.hidroh.com/2015/02/16/support-multiple-themes-android-app/) 中,我们创建了一个明亮风格的主题,并且为实现使用多种主题作了一些前期的准备,而今天呢,我打算在这篇博文中接着上一篇博文继续为大家讲解,而我今天要讲的内容大概是以下三个部分:使 Android 应用能够使用多种主题,创建一个灰暗风格的主题,以及允许 Android 应用在运行时自由地切换不同的主题。
在理想的情况下,如果我们把主题的设置看作是一项配置,那么我们应该能够在类似 "theme-qualifier" 的目录下指定我们想要的特定主题,例如:values-dark 就是我们想要的灰暗风格主题;而values-light 则是明亮风格的主题。但很遗憾,在这篇博文所要讲述的实现方法里,这种方法并没有成为实现方式之一。
那么我们要怎么为不同的主题指定相应的资源文件呢?如果我们有了解过 appcompat 是怎么使用资源文件的话,对 Android 系统是如何管理和使用资源文件会有一个粗略的认识。毫无疑问,[Materialistic](https://play.google.com/store/apps/details?id=io.github.hidroh.materialistic) 中使用的方法就是类似于 Android 系统使用的方法。
## 主题设置 ##
**values/styles.xml**
```xml
<style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- original theme attributes -->
...
</style>
<style name="AppTheme.Dark" parent="Theme.AppCompat">
<item name="colorPrimary">@color/colorPrimaryInverse</item>
<item name="colorPrimaryDark">@color/colorPrimaryDarkInverse</item>
<item name="colorAccent">@color/colorAccentInverse</item>
<item name="android:textColorPrimary">@color/textColorPrimaryInverse</item>
<item name="android:textColorSecondary">@color/textColorSecondaryInverse</item>
<item name="android:textColorPrimaryInverse">@color/textColorPrimary</item>
<item name="android:textColorSecondaryInverse">@color/textColorSecondary</item>
...
</style>
```
**values/color.xml**
```xml
<!-- original color palette -->
...
<!-- alternative color palette -->
<color name="colorPrimaryInverse">...</color>
<color name="colorPrimaryDarkInverse">...</color>
<color name="colorAccentInverse">...</color>
```
在上面的操作中我们创建了一个名叫 AppTheme.Dark 的灰暗风格主题,此外,为了保持 style 和 color 的一致性,我们的 AppTheme.Dark 主题衍生于 appcompat 的 Theme.AppCompat 主题(一个 Android 自带的灰暗风格主题)。然而,由于我们的两个主题(明亮风格和灰暗风格)衍生于不同的基础主题,因此这两个主题之间并不能够进行属性的共享(在JAVA中,类只能进行单继承)。
这两个主题理应有一些恰当的属性值,能同时用于设置基本的 Android 和 appcompat的主题属性,例如:在灰暗风格中,android:textColorPrimary 应该被设置为明亮的,而在明亮风格中,android:textColorPrimary则应该是灰暗的。按照常用的命名习惯,我们在这里将用相反的后缀来区分可替代的主题颜色。
## 温馨小提示
> 在某些情况下,一种颜色能同时在明亮风格和灰暗风格的主题中被使用,这当然是喜闻乐见的情况,但是在大部分主题中这并不能够实现。所以我希望你在设计主题的过程中,通过在 AndroidManifest.xml 中短暂地切换你应用里正在使用的可替代主题,以此确定你的主题是否需要添加其他的 colors/style 文件来满足你的主题设计需求。
## 特定的主题资源文件
到了现在,我相信我们都能很轻松地为我们的 App 设计出美如画的灰暗风格主题,但这里还存在一些小麻烦,例如:用于美化 action bar 菜单选项的 drawables 资源文件。灰暗风格的 action bar 需要用明亮的颜色修饰它的菜单选项,反之亦然。为了让 Android 能够在不同的App主题下区分不同的 drawables 资源文件,我们创建了能够指定正确资源文件的 [自定义属性](http://developer.android.com/training/custom-views/create-view.html#customattr) 引用,并且在不同的主题下提供了不同的 drawable 引用,将其值赋给特定的自定义属性。(温婉如妻,appcompat 库贴心地为我们准备了类似 colorPrimary 的自定义属性值)
**values/attrs.xml**
```xml
<attr name="themedMenuStoryDrawable" format="reference" />
<attr name="themedMenuCommentDrawable" format="reference" />
...
```
**values/styles.xml**
```xml
<style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- original theme attributes -->
...
<item name="themedMenuStoryDrawable">@drawable/ic_subject_white_24dp</item>
<item name="themedMenuCommentDrawable">@drawable/ic_mode_comment_white_24dp</item>
</style>
<style name="AppTheme.Dark" parent="Theme.AppCompat">
<!-- alternative theme attributes -->
...
<item name="themedMenuStoryDrawable">@drawable/ic_subject_black_24dp</item>
<item name="themedMenuCommentDrawable">@drawable/ic_mode_comment_black_24dp</item>
</style>
```
**menu/my_menu.xml**
```xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@id/menu_comment"
android:icon="?attr/themedMenuCommentDrawable" />
<item android:id="@id/menu_story"
android:icon="?attr/themedMenuStoryDrawable" />
<item android:id="@id/menu_share"
app:actionProviderClass="android.support.v7.widget.ShareActionProvider" />
</menu>
```
根据你实际的主题设计需要,类似的实现也能被用于为大多数自定义属性指定相应的资源值。但这个方法存在一个问题:根据实际的需要从 drawable 资源文件中解析相应的属性值,并应用于主题的方法在API 21之前的版本似乎都不可行。举例来说明这个问题吧:如果你有一个 layer-list 中包含了各种你所需要的 color 的 drawable 资源文件,在API 21之前的版本中,这些 color 的值都应该是固定的,而不是能够在App运行过程中不断变化的。这个问题在 Google I/O 2014 大会上有被提出,并要求给出相应的解决办法。(详情参见 [Click Me!](https://github.com/google/iosched/commit/dd7ed72a7eb2d223203db079bd99d31c6ef3061e))。
此外,为了避免在不同的主题中重复使用相同的资源文件,我们可以利用 drawable 的 tint 属性解决这个需求。虽然这个属性可以在API 21之后的版本中使用。但 [Dan Lew](http://blog.danlew.net/2014/08/18/fast-android-asset-theming-with-colorfilter/) 在他的博客中为我们介绍了怎么在所有的 API 版本中使用 tint 属性。但就个人偏好来说,如果可以的话,我会更倾向于选择不受 View 逻辑影响的 Java 实现,所以我选择为每一个主题提供不同的 drawable 资源文件。
## 动态主题切换
现在我们已经有两个可以使用的主题了,接下来我们需要做的就是让用户能够在使用 App 时能够自如地根据他们的个人偏好切换不同的主题。要实现这个功能,我们可以通过使用 SharedPreferences 来实现,通过改变 pref_dark_theme 的值去存储当前被选择的主题并决定我们的 App 将要被切换成什么主题。但从实际情况来考虑,主题切换后,App 所有 Activity 的 View 在被创建之前都应该被改变,所以我们只需要在 onCreate()方法中实现我们的逻辑。
**BaseActivity.java**
```java
public abstract class BaseActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("pref_dark_theme"), false)) {
setTheme(R.style.AppTheme_Dark);
}
super.onCreate(savedInstanceState);
}
}
```
在这里,因为我们的 App 已经使用了默认的明亮风格主题,所以我们只需要检查默认的引用是否被重载,是否被用于重载灰暗风格的主题。为了默认的引用能够被所有 Activity共享,其中的逻辑已经在 "base" Activity中被写好了。
值得注意的是,这个方法只能被用于改变没有处在 [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。
## SettingsFragment.java ##
```java
public class SettingsFragment extends PreferenceFragment {
...
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (!key.equals("pref_dark_theme")) {
return;
}
getActivity().finish();
final Intent intent = getActivity().getIntent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
getActivity().startActivity(intent);
}
};
}
...
```
虽然结束得有些突然,但我们今天的讲解就到此结束啦。现在我们的 App 拥有了两个这么优雅的主题,就算是挑剔的文艺小清新也不会嫌弃我们的 App 很 low 了吧!如果你想要了解整个 Materialistic 的具体实现,或者是这个功能的源码,可以来我的 [GitHub](https://github.com/hidroh/materialistic) 上获取哦~
================================================
FILE: androidweekly/那些年我们错过的响应式编程/readme.md
================================================
## 那些年我们错过的响应式编程
---
>
* 原文链接 : [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)
* 作者 : [@andrestaltz](https://twitter.com/andrestaltz)
* 译者 : [yaoqinwei](https://github.com/yaoqinwei)
* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu)、[chaossss](https://github.com/chaossss)
* 状态 : 完成
相信你们在学习响应式编程这个新技术的时候都会充满了好奇,特别是它的一些变体,例如:Rx系列、Bacon.js、RAC等等……
在缺乏优秀资料的前提下,响应式编程的学习过程将满是荆棘。起初,我试图寻找一些教程,却只找到少量的实践指南,而且它们讲的都非常浅显,从来没人接受围绕响应式编程建立一个完整知识体系的挑战。此外,官方文档通常也不能很好地帮助你理解某些函数,因为它们通常看起来很绕,不信请看这里:
> **Rx.Observable.prototype.flatMapLatest(selector, [thisArg])**
> 根据元素下标,将可观察序列中每个元素一一映射到一个新的可观察序列当中,然后...%…………%&¥#@@……&**(晕了)
天呐,这简直太绕了!
我读过两本相关的书,一本只是在给你描绘响应式编程的伟大景象,而另一本却只是深入到如何使用响应式库而已。我在不断的构建项目过程中把响应式编程了解的透彻了一些,最后以这种艰难的方式学完了响应式编程。在我工作公司的一个实际项目中我会用到它,当我遇到问题时,还可以得到同事的支持。
学习过程中最难的部分是**如何以响应式的方式来思考**,更多的意味着要摒弃那些老旧的命令式和状态式的典型编程习惯,并且强迫自己的大脑以不同的范式来运作。我还没有在网络上找到任何一个教程是从这个层面来剖析的,我觉得这个世界非常值得拥有一个优秀的实践教程来教你**如何以响应式编程的方式来思考**,方便引导你开始学习响应式编程。然后看各种库文档才可以给你更多的指引。希望这篇文章能够帮助你快速地进入**响应式编程**的世界。
## "什是响应式编程?"
网络上有一大堆糟糕的解释和定义,如[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做出反应,我的改变当然也会传播,如果没有这些,我的界面根本就没有东西可渲染。
所以,不要再扯这些废话了。
#### 响应式编程就是与异步数据流交互的编程范式
一方面,这已经不是什么新事物了。事件总线(Event Buses)或一些典型的点击事件本质上就是一个异步事件流(asynchronous event stream),这样你就可以观察它的变化并使其做出一些反应(do some side effects)。响应式是这样的一个思路:除了点击和悬停(hover)的事件,你还可以给其他任何事物创建数据流。数据流无处不在,任何东西都可以成为一个数据流,例如变量、用户输入、属性、缓存、数据结构等等。举个栗子,你可以把你的微博订阅功能想象成跟点击事件一样的数据流,你可以监听这样的数据流,并做出相应的反应。
**最重要的是,你会拥有一些令人惊艳的函数去结合、创建和过滤任何一组数据流。** 这就是"函数式编程"的魔力所在。一个**数据流**可以作为另一个**数据流**的输入,甚至多个**数据流**也可以作为另一个**数据流**的输入。你可以_合并_两个**数据流**,也可以_过滤_一个数据流得到另一个只包含你感兴趣的事件的**数据流**,还可以_映射_一个**数据流**的值到一个新的**数据流**里。
**数据流**是整个响应式编程体系中的核心,要想学习响应式编程,当然要先走进数据流一探究竟了。那现在就让我们先从熟悉的"点击一个按钮"的**事件流**开始

一个**数据流**是一个**按时间排序的即将发生的事件(Ongoing events ordered in time)**的序列。如上图,它可以发出3种不同的事件(上一句已经把它们叫做事件):一个某种类型的**值事件**,一个**错误事件**和一个**完成事件**。当一个**完成事件**发生时,在某些情况下,我们可能会做这样的操作:关闭包含那个按钮的窗口或者视图组件。
我们只能**异步**捕捉被发出的事件,使得我们可以在发出一个**值事件**时执行一个函数,发出**错误事件**时执行一个函数,发出**完成事件**时执行另一个函数。有时候你可以忽略后两个事件,只需聚焦于如何定义和设计在发出**值事件**时要执行的函数,监听这个**事件流**的过程叫做**订阅**,我们定义的函数叫做**观察者**,而事件流就可以叫做被观察的**主题**(或者叫被观察者)。你应该察觉到了,对的,它就是[**观察者模式**](https://en.wikipedia.org/wiki/Observer_pattern)。
上面的示意图我们也可以用ASCII码的形式重新画一遍,请注意,下面的部分教程中我们会继续使用这幅图:
```
--a---b-c---d---X---|->
a, b, c, d 是值事件
X 是错误事件
| 是完成事件
---> 是时间线(轴)
```
现在你对响应式编程事件流应该非常熟悉了,为了不让你感到无聊,让我们来做一些新的尝试吧:我们将创建一个由原始点击事件流演变而来的一种新的点击事件流。
首先,让我们来创建一个记录按钮点击次数的事件流。在常用的响应式库中,每个事件流都会附有一些函数,例如 `map`, `filter`, `scan`等,当你调用这其中的一个方法时,比如`clickStream.map(f)`,它会返回基于点击事件流的一个**新事件流**。它不会对原来的点击事件流做任何的修改。这种特性叫做**不可变性(immutability)**,而且它可以和响应式事件流搭配在一起使用,就像豆浆和油条一样完美的搭配。这样我们可以用链式函数的方式来调用,例如:`clickStream.map(f).scan(g)`:
```
clickStream: ---c----c--c----c------c-->
vvvvv map(c becomes 1) vvvv
---1----1--1----1------1-->
vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->
```
`map(f)`函数会根据你提供的`f`函数把原事件流中每一个返回值分别映射到新的事件流中。在上图的例子中,我们把每一次点击事件都映射成数字1,`scan(g)`函数则把之前映射的值聚集起来,然后根据`x = g(accumulated, current)`算法来作相应的处理,而本例的`g`函数其实就是简单的加法函数。然后,当一个点击事件发生时,`counterStream`函数则上报当前点击事件总数。
为了展示响应式编程真正的魅力,我们假设你有一个"双击"事件流,为了让它更有趣,我们假设这个事件流同时处理"三次点击"或者"多次点击"事件,然后深吸一口气想想如何用传统的命令式和状态式的方式来处理,我敢打赌,这么做会相当的讨厌,其中还要涉及到一些变量来保存状态,并且还得做一些时间间隔的调整。
而用响应式编程的方式处理会非常的简洁,实际上,逻辑处理部分只需要[四行代码](http://jsfiddle.net/staltz/4gGgs/27/)。但是,当前阶段让我们现忽略代码的部分,无论你是新手还是专家,看着图表思考来理解和建立事件流将是一个非常棒的方法。

图中,灰色盒子表示将上面的事件流转换下面的事件流的**函数**过程,首先根据250毫秒的间隔时间(event silence, 译者注:无事件发生的时间段,上一个事件发生到下一个事件发生的间隔时间)把点击事件流一段一隔开,再将每一段的一个或多个点击事件添加到列表中(这就是这个函数:`buffer(stream.throttle(250ms))`所做的事情,当前我们先不要急着去理解细节,我们只需专注响应式的部分先)。现在我们得到的是多个含有事件流的列表,然后我们使用了`map()`中的函数来算出每一个列表长度的整数数值映射到下一个事件流当中。最后我们使用了过滤`filter(x >= 2)` 函数忽略掉了小于`1` 的整
gitextract_zcp2h22p/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── android-blog/ │ ├── Android性能优化系列/ │ │ └── readme.md │ ├── Google+ 团队的 Android UI 测试/ │ │ └── readme.md │ └── readme.md ├── androidweekly/ │ ├── Android中的人脸检测入门.md │ ├── Android性能案例研究续集/ │ │ └── readme.md │ ├── Kotlin for Android (II)创建一个工程/ │ │ └── readme.md │ ├── Kotlin for Android (III)-扩展函数与默认值/ │ │ └── readme.md │ ├── ListView或者RecycleView滚动时隐藏Toolbar-part-1/ │ │ └── readme.md │ ├── ListView或者RecycleView滚动时隐藏Toolbar-part-2/ │ │ └── readme.md │ ├── Square 开源库Flow和Mortar的介绍/ │ │ └── readme.md │ ├── kotlin-for-android简介/ │ │ └── readme.md │ ├── readme.md │ ├── 一个支持多设备的Android参考应用/ │ │ └── readme.md │ ├── 一种在android中实现MVP模式的新思路/ │ │ └── readme.md │ ├── 一种更清晰的Android架构/ │ │ └── readme.md │ ├── 使用Robolectric的参数化测试/ │ │ └── readme.md │ ├── 使用RxJava.Observable取代AsyncTask和AsyncTaskLoader/ │ │ └── readme.md │ ├── 使用buildSrc Gradle项目和Codemodel生成java代码/ │ │ └── readme.md │ ├── 功能测试框架 espresso/ │ │ └── readme.md │ ├── 在Android 5.0中使用JobScheduler/ │ │ └── readme.md │ ├── 在Android调试模式中使用Stetho/ │ │ └── README.md │ ├── 安卓字体渲染器/ │ │ └── readme.md │ ├── 安卓的模糊视图/ │ │ └── readme.md │ ├── 欢迎来到Android多进程时代/ │ │ └── readme.md │ ├── 深入了解Android Graphics Pipeline-part-1/ │ │ └── readme.md │ ├── 深入了解Android Graphics Pipeline-part-2/ │ │ └── readme.md │ ├── 深入了解Bundle和Map/ │ │ └── readme.md │ ├── 符合Material Design的抽屉导航效果/ │ │ └── readme.md │ ├── 让你的Android应用能使用多种主题-Part-1/ │ │ └── readme.md │ ├── 让你的Android应用能使用多种主题-Part-2/ │ │ └── readme.md │ └── 那些年我们错过的响应式编程/ │ └── readme.md ├── authorization.md ├── git简单使用教程.md ├── issue-10/ │ ├── Android如何直播RTMP流.md │ ├── Android进行单元测试难在哪-part2.md │ ├── Kotlin-for-Android-(IV):自定义视图和Android的扩展.md │ ├── readme.md │ ├── 使用Facebook-SDK为安卓应用添加Like按钮.md │ └── 将基于Dagger-1开发的项目迁移到Dagger-2中.md ├── issue-11/ │ ├── Android-Espresso测试框架介绍.md │ ├── Android进行单元测试难在哪-part3.md │ ├── Code Review最佳实践.md │ ├── readme.md │ ├── 听FaceBook工程师讲Custom-ViewGroups.md │ └── 详解Dagger2.md ├── issue-12/ │ ├── Android上MVP的介绍.md │ ├── Android自动截屏工具.md │ ├── Android进行单元测试难在哪-part4.md │ ├── MVP框架Mosby架构详解.md │ ├── readme.md │ ├── 当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合.md │ └── 自动化Android开发.md ├── issue-13/ │ ├── Android进行单元测试难在哪-终.md │ ├── Square:从今天开始抛弃Fragment吧!.md │ ├── readme.md │ ├── 优化android-studio编译效率的方法.md │ ├── 创建-RecyclerView-LayoutManager-Part-2.md │ ├── 创建-RecyclerView-LayoutManager-Part-3.md │ └── 创建-RecyclerView-LayoutManager-Redux.md ├── issue-14/ │ ├── Android-Design-Support-Library.md │ ├── Android之WebRTC介绍.md │ ├── Design-Support-Library(I)-Navigation-View.md │ ├── MVVM 模式简介.md │ └── 新的测试注解.md ├── issue-15/ │ ├── 2015-Google-IO带来的新Android开发工具 .md │ ├── Android-C++引用计数介绍.md │ ├── Android-M的App-Links实现详解.md │ ├── facebook代码分析工具-infer.md │ ├── readme.md │ ├── 你可能漏掉的知识点-onResumeFragments.md │ └── 如何修复编译时的MultiDex崩溃.md ├── issue-16/ │ ├── Android一体机模式:规则限制.md │ ├── readme.md │ ├── 为什么你应该停止使用EventBus.md │ ├── 手动实现布局Transitions动画-第一部分.md │ ├── 手动实现布局Transitions动画-第三部分.md │ ├── 手动实现布局Transitions动画-第二部分.md │ └── 结合RxJava更简单地使用SQLite.md ├── issue-17/ │ ├── Android UI 自动化测试.md │ ├── Android中的帧动画.md │ ├── Android开发III-规范与性能.md │ ├── readme.md │ ├── 为什么需要在你的Crash报告中使用git-SHA.md │ ├── 使用RxJava从几个数据源中加载数据.md │ ├── 如何使用Android-Studio把自己的Android-library分发到jCenter和Maven-Central.md │ └── 当钢铁侠变得反应更快-RxJava.md ├── issue-18/ │ ├── Service测试.md │ ├── readme.md │ ├── 如何提高你的代码质量.md │ ├── 拖拽RecyclerView.md │ ├── 用组合代替继承能为Activity带来什么.md │ └── 还在用Toast?你Out啦,试试Snackbar吧!.md ├── issue-19/ │ ├── Android-UI自动化测试.md │ ├── RxJava-Observables单元测试.md │ ├── gradle技巧之语法浅谈.md │ └── readme.md ├── issue-20/ │ ├── readme.md │ ├── 在你开发应用前应知道的六件事情.md │ └── 检测和解决Android应用的性能问题.md ├── issue-21/ │ ├── Android数据绑定-再见Presenter,你好ViewModel.md │ ├── TextView预渲染研究.md │ ├── Yelp是如何通过Glide优化图片加载的.md │ └── 使用Mockito对异步方法进行单元测试.md ├── issue-22/ │ ├── Android-Activity测试.md │ ├── Android中的AOP编程.md │ ├── Binder框架解析.md │ └── 深入剖析Android网络开发库-part1.md ├── issue-23/ │ ├── Android-MVPR-架构模式-Part1.md │ ├── Android-MVVM模式.md │ ├── TextView的TextLayout.md │ ├── 使用Espresso进行UI测试.md │ └── 使用TDD的方式开发一个Hackernews客户端.md ├── issue-24/ │ ├── Android LayerDrawable 和 Drawable.Callback.md │ ├── Android双向数据绑定.md │ ├── Android设计与开发工作流.md │ ├── 使用Kotlin对ViewGroup中的View进行函数式操作.md │ └── 适用于Android的Flux架构.md ├── issue-25/ │ ├── Android主题动态切换开源库Prism基本原理1-核心功能.md │ ├── Android主题动态切换开源库Prism基本原理2-搭配ViewPager使用.md │ ├── Android主题动态切换开源库Prism基本原理3-搭配Palette使用.md │ ├── 一个内存泄漏引发的血案-Square.md │ └── 在Android Lollipop中使用Palette抽取Bitmap颜色.md ├── issue-26/ │ ├── 30分钟搭建一个android的私有Maven仓库.md │ ├── Android架构演化之路.md │ ├── Tinting drawables.md │ ├── 使用Systrace分析UI性能.md │ └── 在Android M中权限被拒绝时该如何处理.md ├── issue-27/ │ ├── 为什么不仅继承Observale而且使用Observable.create.md │ ├── 为你的应用加速 - 安卓优化指南.md │ ├── 使用Gradle将项目发布到Bitbucket上.md │ └── 开发你自己的Android授权管理器.md ├── issue-28/ │ ├── 数据绑定(Data Binding)-Part1.md │ ├── 数据绑定(Data Binding)-Part2.md │ ├── 数据绑定(Data Binding)-Part3.md │ ├── 数据绑定(Data Binding)-Part4.md │ └── 数据绑定(Data Binding)-Part5.md ├── issue-29/ │ ├── Chrome自定义Tabs-让App和Web之间的转场更平顺.md │ ├── 开发安全的Android应用.md │ └── 注意API21(Android5.0)上的EditText.md ├── issue-30/ │ ├── Android开发生僻却实用的知识点Part1.md │ ├── Android开发生僻却实用的知识点Part2.md │ ├── Flux and Android.md │ └── 通过硬件层提高Android动画的性能.md ├── issue-31/ │ ├── Android 中的依赖注入框架.md │ ├── Android中调试RxJava.md │ ├── Android应用架构.md │ ├── Android开发生僻却实用的知识点Part3.md │ ├── Espresso保存和恢复状态.md │ └── 使用ClassyShark压缩你的项目.md ├── issue-32/ │ ├── AppBarLayout的越界滚动行为.md │ ├── 剪刀手—Android平台上的图片裁剪库.md │ ├── 实现安卓6.0的直接分享(Direct Share )功能.md │ ├── 重构Plaid-响应式的MVP模式.md │ └── 高性能ListViews.md ├── issue-33/ │ ├── Android Libraries的依赖管理.md │ ├── 下雪动画.md │ ├── 使用ADB-Shell的效率和乐趣-part1.md │ ├── 如何自定义Lint规则.md │ └── 高效地配置okhttp.md ├── issue-34/ │ ├── Android逆向工程101 – Part 1.md │ ├── Android逆向工程101 – Part 2.md │ ├── Context是怎么泄露的Handlers & Inner Classes.md │ └── 在Android开发中使用RxJava.md ├── issue-35/ │ ├── 为什么我们要用fitsSystemWindows.md │ ├── 星球大战:原力觉醒或者用原力粉碎Android的视图.md │ ├── 用Transition完成Fragment共享元素的切换.md │ ├── 让EditText中的链接即可点击又可编辑.md │ └── 近乎通用的VectorDrawable.md ├── issue-36/ │ ├── Gradle小知识1.md │ ├── Gradle小知识2.md │ ├── Gradle小知识3.md │ ├── Gradle小知识4.md │ └── Gradle提示和使用技巧.md ├── issue-37/ │ ├── 使用ACTION_PROCESS_TEXT创建自定义文本选择动作.md │ ├── 使用RxJava缓存Rest请求.md │ ├── 在Android中使用并发来提高速度和性能.md │ ├── 如何理解RxJava中的Subjects(第一部分).md │ └── 更加强大的Dagger2.md ├── issue-38/ │ ├── Android逆向工程.md │ ├── Android高性能JSON数据解析.md │ ├── RecyclerView动画 第一部-动画效果是如何工作的.md │ ├── RecyclerView动画 第二部-幕后.md │ └── 从网页中触发Android原生的分享Intent.md ├── issue-39/ │ ├── Android Studio提示和技巧.md │ ├── Android dex分包导致的App启动速度下降.md │ ├── FragmentTransaction 与 Activity 状态丢失?.md │ ├── RxJava中repeatWhen 和 retryWhen 操作符的解释.md │ └── 在滚动列表中播放视频.md ├── issue-40/ │ ├── 权限 - 第一篇.md │ ├── 权限 - 第三篇.md │ ├── 权限 - 第二篇.md │ └── 权限 - 第四篇.md ├── issue-41/ │ ├── Service十件你不知道的事.md │ ├── 自定义CoordinatorLayout的行为.md │ └── 让你了解数据加载的生命周期.md ├── issue-42/ │ ├── IndeterminateProgressbar解析-Part 1.md │ ├── 剖析okhttp缓存机制.md │ ├── 在Activity配置改变时保存状态.md │ └── 提高NYTimes的启动速度.md ├── issue-43/ │ ├── IndeterminateProgressbar解析-Part 2.md │ ├── LayoutInflater.inflate() 方法剖析.md │ ├── 结合motion和Transition实现共享元素的酷炫动画.md │ ├── 通过CoordinatorLayout的Behavior拦截一切.md │ └── 避免Android应用冷启动问题.md ├── issue-44/ │ ├── Android-Reverse-Engineering-101-Part-4.md │ ├── Android-Support-Library-23.2.md │ ├── 使用Clean Architecture模型开发Android应用详细指南.md │ ├── 利用Retrofit和RxJava实现服务器轮询和出错重试.md │ └── 用RxJava替代EventBus.md ├── issue-45/ │ ├── Android圆弧整容之谜.md │ ├── IndeterminateProgressbar解析-Part 3.md │ ├── 使用反射到底会对性能造成多大影响?.md │ ├── 安卓Binder架构概述.md │ └── 简化复杂的视图层次.md ├── issue-46/ │ ├── Android-Reverse-Engineering-101-Part-5.md │ ├── IndeterminateProgressbar解析-Part 4.md │ ├── IndeterminateProgressbar解析-Part 5.md │ └── 尝试结合Mosby和Flow代替Fragment.md ├── issue-47/ │ ├── Building a Kotlin project.md │ ├── Kotlin在Android上令人惊叹的技巧.md │ ├── Layouts-Attributes-and-you.md │ └── 在Java和Android中使用Optional.md ├── issue-48/ │ ├── Android中的MVP-Part2.md │ ├── Parcelable vs Serializable对比.md │ ├── 为什么在Android开发中我仍然不想使用Kotlin.md │ └── 构建我的Presentation层.md ├── issue-49/ │ ├── Android上的网络响应日志技巧.md │ ├── AutoValue简介.md │ ├── Jack&Jill的缺点.md │ ├── 为什么在Android使用ClassLoader.getResourceAsStream会如此影响性能.md │ ├── 如何使用BottomSheet.md │ └── 深入研究AutoValue.md ├── issue-7/ │ ├── Android-Lollipop-update.md │ ├── Android-Support库22.1版.md │ ├── Android测试框架:Dagger2+Espresso2+Mockito/ │ │ └── README.md │ ├── Retrofit开发指南/ │ │ └── README.md │ ├── readme.md │ ├── 使用Robolectric和Android生成代码覆盖率报告/ │ │ └── readme.md │ ├── 在Activity中使用Thread导致的内存泄漏/ │ │ └── readme.md │ ├── 深入浅出Android新特性-Transition-Part-3a/ │ │ └── readme.md │ └── 深入浅出Android新特性-Transition-Part-3b/ │ └── readme.md ├── issue-8/ │ ├── Android 进行单元测试难在哪-序.md │ ├── Custom-Drawables.md │ ├── Support Libraries v22.1.0.md │ ├── 如何在Android上响应各种信息通知.md │ ├── 开始学习Material Design.md │ └── 检测Android应用的启动与关闭.md ├── issue-9/ │ ├── Android 10ms问题:关于Android音频路径延迟的解释.md │ ├── Android进行单元测试难在哪-part1.md │ ├── NotRxJava懒人专用指南.md │ ├── readme.md │ ├── 使用Android-Studio进行单元测试.md │ ├── 创建-RecyclerView-LayoutManager-Part-1.md │ └── 通过Jenkins并行完成UI的自动化测试.md ├── markdown简单教程.md ├── markdown转换教程.md ├── others/ │ ├── Android-M中Intent的解析/ │ │ └── readme.md │ ├── FaceBook推出的Android图片加载库-Fresco/ │ │ └── readme.md │ ├── Google推荐的图片加载库Glide介绍/ │ │ └── readme.md │ ├── InstaMaterial概念设计系列/ │ │ └── 实现Instagram的Material Design概念设计/ │ │ └── readme.md │ ├── VectorDrawable系列/ │ │ ├── VectorDrawable – 第一章/ │ │ │ └── readme.md │ │ └── VectorDrawable – 第二章/ │ │ └── readme.md │ ├── 上传拍下的照片、视频到服务器/ │ │ └── readme.md │ ├── 如何在本地搭建一个Android应用crashing跟踪系统-ACRA/ │ │ └── readme.md │ ├── 深入浅出Android 新特性-Transition-Part-1/ │ │ └── readme.md │ ├── 深入浅出Android 新特性-Transition-Part-2/ │ │ └── readme.md │ ├── 清晰的软件架构/ │ │ └── readme.md │ ├── 简化Android的UI开发/ │ │ └── readme.md │ └── 自动化截图-应用分发时的自动截图方案/ │ └── readme.md ├── rxjava/ │ ├── chap1.md │ ├── chap2.md │ ├── chap3.md │ ├── chap4.md │ ├── chap5.md │ ├── chap6.md │ ├── chap7.md │ └── chap8.md ├── software-architecture-patterns/ │ ├── chap-5.md │ ├── chapter01-BillonWang.md │ ├── chapter02-chaossss.md │ ├── chapter03-Mr.Simple.md │ ├── chapter04-dupengwei.md │ ├── readme.md │ ├── 软件架构模式.md │ └── 附录2015-4-11-charli.md ├── template.md ├── the-bad-guys/ │ └── readme.md └── 翻译项目协作流程.md
Condensed preview — 292 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,195K chars).
[
{
"path": ".gitattributes",
"chars": 378,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs diff=csharp\n\n# St"
},
{
"path": ".gitignore",
"chars": 68,
"preview": "*.project\n*.classpath\nbin/\ngen/\n*.DS_Store\n*/bin/\n*/gen/\n.settings/\n"
},
{
"path": "LICENSE",
"chars": 11325,
"preview": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licens"
},
{
"path": "README.md",
"chars": 1239,
"preview": "# Android开发技术前线 ( android-tech-frontier )\n\n> 一个定期翻译、发布国内外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目,让我们的技术跟上国际步伐。 \n> 我们翻译"
},
{
"path": "android-blog/Android性能优化系列/readme.md",
"chars": 631,
"preview": "## Android 应用性能优化系列\n\n>原文链接分别为 : \n>\n>* [https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE](https"
},
{
"path": "android-blog/Google+ 团队的 Android UI 测试/readme.md",
"chars": 2772,
"preview": "Google+ 团队的 Android UI 测试\n---\n\n>\n* 原文链接:[How the Google+ Team Tests Mobile Apps](http://googletesting.blogspot.sg/2013/0"
},
{
"path": "android-blog/readme.md",
"chars": 14,
"preview": "## Android官方博客"
},
{
"path": "androidweekly/Android中的人脸检测入门.md",
"chars": 10267,
"preview": "Android中的人脸检测入门\n---\n\n> * 原文链接 : [An Introduction to Face Detection on Android](http://code.tutsplus.com/tutorials/an-int"
},
{
"path": "androidweekly/Android性能案例研究续集/readme.md",
"chars": 3257,
"preview": "# Android性能案例研究续集\n---\n> * 原文链接 : [Android Performance Case Study Follow-up](http://www.curious-creature.com/2015/03/25/a"
},
{
"path": "androidweekly/Kotlin for Android (II)创建一个工程/readme.md",
"chars": 4175,
"preview": "Kotlin for Android (II)创建一个工程\n---\n\n>\n* 原文链接 : [Kotlin for Android (II): Create a new project](http://antonioleiva.com/ko"
},
{
"path": "androidweekly/Kotlin for Android (III)-扩展函数与默认值/readme.md",
"chars": 2853,
"preview": "Kotlin for Android (III)/ 扩展函数与默认值\n---\n\n>\n* 原文链接 : [Kotlin for Android (III): Extension functions and default values](ht"
},
{
"path": "androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-1/readme.md",
"chars": 5382,
"preview": "ListView或者RecycleView滚动时隐藏Toolbar (1)\n---\n\n>\n* 原文链接 : [How to hide/show Toolbar when list is scroling (part 1)](http://m"
},
{
"path": "androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-2/readme.md",
"chars": 6981,
"preview": "ListView或者RecycleView滚动时隐藏Toolbar( Part 2 )\n---\n\n>\n* 原文链接 : [How to hide/show Toolbar when list is scrolling (part 2)](h"
},
{
"path": "androidweekly/Square 开源库Flow和Mortar的介绍/readme.md",
"chars": 7428,
"preview": "Flow和Mortar的调查\n---\n>\n* 原文链接 : [Architecting An Investigation into Flow and Mortar](http://www.bignerdranch.com/blog/an-i"
},
{
"path": "androidweekly/kotlin-for-android简介/readme.md",
"chars": 4709,
"preview": "kotlin-for-android简介(1)\n---\n\n>\n* 原文链接 : [Kotlin for Android (I): Introduction\n](http://antonioleiva.com/kotlin-for-andro"
},
{
"path": "androidweekly/readme.md",
"chars": 39,
"preview": "# 不要将文章放到这个文件夹下啦!请放到最新的issue文件夹中,谢谢大家。\n"
},
{
"path": "androidweekly/一个支持多设备的Android参考应用/readme.md",
"chars": 950,
"preview": "一个支持多设备的Android参考应用\n---\n>\n* 原文链接 : [a-new-reference-app-for-multi-device](http://android-developers.blogspot.com/2015/03"
},
{
"path": "androidweekly/一种在android中实现MVP模式的新思路/readme.md",
"chars": 7737,
"preview": "一种在android中实现MVP模式的新思路\n---\n\n>\n* 原文链接 : [android-mvp-an-alternate-approach](http://blog.cainwong.com/android-mvp-an-alter"
},
{
"path": "androidweekly/一种更清晰的Android架构/readme.md",
"chars": 5128,
"preview": "一种更清晰的Android架构\n---\n\n>\n* 原文链接 : [Architecting Android…The clean way?](http://fernandocejas.com/2014/09/03/architecting-a"
},
{
"path": "androidweekly/使用Robolectric的参数化测试/readme.md",
"chars": 2492,
"preview": "使用Robolectric的参数化测试\n---\n\n>\n* 原文标题 : Parameterized testing with Robolectric\n* 原文链接 : [Parameterized testing with Robolect"
},
{
"path": "androidweekly/使用RxJava.Observable取代AsyncTask和AsyncTaskLoader/readme.md",
"chars": 8708,
"preview": "使用RxJava.Observable取代AsyncTask和AsyncTaskLoader\n---\n\n>\n* 原文链接 : [Replace AsyncTask and AsyncTaskLoader with rx.Observable"
},
{
"path": "androidweekly/使用buildSrc Gradle项目和Codemodel生成java代码/readme.md",
"chars": 8875,
"preview": "#How to generate Java sources using buildSrc Gradle project and Codemodel\n#使用buildSrc Gradle项目和Codemodel生成java代码\n\r\n[Andr"
},
{
"path": "androidweekly/功能测试框架 espresso/readme.md",
"chars": 3988,
"preview": "功能测试框架 espresso\n---\n\n>\n* 原文链接 : [the-hitchhikers-guide-to-android-testing-part-2-espresso](http://wiebe-elsinga.com/blog"
},
{
"path": "androidweekly/在Android 5.0中使用JobScheduler/readme.md",
"chars": 6339,
"preview": "在Android 5.0中使用JobScheduler\n---\n\n> * 原文链接 : [using-the-jobscheduler-api-on-android-lollipop](http://code.tutsplus.com/tu"
},
{
"path": "androidweekly/在Android调试模式中使用Stetho/README.md",
"chars": 2926,
"preview": "#Stetho for Android debug builds only\n# 在Android调试模式中使用Stetho\n------\n\n - 原文链接:[在Android调试模式中使用Stetho][11]\n - 译者:[Billion"
},
{
"path": "androidweekly/安卓字体渲染器/readme.md",
"chars": 7470,
"preview": "安卓字体渲染器\n---\n\n* 原文:[android font renderer](https://medium.com/@romainguy/androids-font-renderer-c368bbde87d9)\n* 译者:[7heav"
},
{
"path": "androidweekly/安卓的模糊视图/readme.md",
"chars": 4821,
"preview": "安卓的模糊视图\n---\n\n>\n* 原文链接 : [A Blurring View for Android](http://developers.500px.com/2015/03/17/a-blurring-view-for-android"
},
{
"path": "androidweekly/欢迎来到Android多进程时代/readme.md",
"chars": 3449,
"preview": "欢迎来到Android多进程时代\n---\n\n>\n* 原文标题 : Going multiprocess on Android\n* 原文链接 : [Going multiprocess on Android](https://medium.c"
},
{
"path": "androidweekly/深入了解Android Graphics Pipeline-part-1/readme.md",
"chars": 6438,
"preview": "深入了解Android Graphics Pipeline-part-1\n---\n\n* 原文链接 : [Android Graphics Pipeline: From Button to Framebuffer (Part 1)](http"
},
{
"path": "androidweekly/深入了解Android Graphics Pipeline-part-2/readme.md",
"chars": 27928,
"preview": "深入了解Android Graphics Pipeline-part-2\n---\n\n>\n* 原文链接 : [Android Graphics Pipeline: From Button to Framebuffer (Part 2)](ht"
},
{
"path": "androidweekly/深入了解Bundle和Map/readme.md",
"chars": 9849,
"preview": "深入了解Bundle和Map\n---\n\n>\n* 原文链接 : [The mysterious case of the Bundle and the Map](https://medium.com/the-wtf-files/the-myst"
},
{
"path": "androidweekly/符合Material Design的抽屉导航效果/readme.md",
"chars": 10599,
"preview": "###符合Material Design的抽屉导航效果:\n---\n>\n* 原文链接 : [Navigation Drawer styling according to Material Design](https://medium.com/"
},
{
"path": "androidweekly/让你的Android应用能使用多种主题-Part-1/readme.md",
"chars": 4230,
"preview": "在你的Android App中支持多主题\n---\n\n>\n* 原文链接 : [Supporting multiple themes in your Android app (Part 1)](http://www.hidroh.com/201"
},
{
"path": "androidweekly/让你的Android应用能使用多种主题-Part-2/readme.md",
"chars": 7039,
"preview": "让你的Android应用能使用多种主题 ( Part 2 )\n---\n\n>\n* 原文链接 : [Supporting multiple themes in your Android app (Part 2)](http://www.hidr"
},
{
"path": "androidweekly/那些年我们错过的响应式编程/readme.md",
"chars": 23439,
"preview": "## 那些年我们错过的响应式编程\n---\n\n>\n* 原文链接 : [The introduction to Reactive Programming you've been missing](https://gist.github.com/"
},
{
"path": "authorization.md",
"chars": 1366,
"preview": "# 作者授权列表\nAndroid开发技术前线翻译的文章都尽可能的找到原作者的联系方式,然后获得文章作者的翻译授权,原文也将保留原文链接和作者名。下面是目前已获得授权的作者列表。\n\n| 作者 | 博客地址 | 授权状态 | 联系人 "
},
{
"path": "git简单使用教程.md",
"chars": 1066,
"preview": "#git简单教程 (适用于参与开发技术前线)\n\n## 1、git的安装与配置\n[Git详解之一:Git起步](http://blog.jobbole.com/25775/)\n\n## 2、参与开发技术前线的项目\n\n1. 首先到[iOS-tec"
},
{
"path": "issue-10/Android如何直播RTMP流.md",
"chars": 7015,
"preview": "Android 如何直播RTMP流\n===\n> * 原文链接 : [How To Stream RTMP live in Android](http://www.truiton.com/2015/03/stream-rtmp-live-an"
},
{
"path": "issue-10/Android进行单元测试难在哪-part2.md",
"chars": 10870,
"preview": "Android 进行单元测试难在哪-part2\n---\n\n> * 原文链接 : [Why Android Unit Testing is so hard](http://philosophicalhacker.com/2015/04/24/"
},
{
"path": "issue-10/Kotlin-for-Android-(IV):自定义视图和Android的扩展.md",
"chars": 3539,
"preview": "Kotlin for Android (IV):自定义视图和Android的扩展\n---\n\n> * 原文链接 : [Kotlin for Android (IV): Custom Views and Android Extensions]("
},
{
"path": "issue-10/readme.md",
"chars": 612,
"preview": "# 开发技术前线 第10期\n\n| 文章名称 | 译者 | \n|---------|--------|\n| [Android如何直播RTMP流](Android如何直播RTMP流.md) | [ayyb1988](https://gi"
},
{
"path": "issue-10/使用Facebook-SDK为安卓应用添加Like按钮.md",
"chars": 13862,
"preview": "使用Facebook SDK为安卓应用添加Like按钮\n\n> * 原文链接 : [How to add a Native Facebook Like Button to your Android app using Facebook SDK"
},
{
"path": "issue-10/将基于Dagger-1开发的项目迁移到Dagger-2中.md",
"chars": 9863,
"preview": "将基于Dagger-1开发的项目迁移到Dagger-2中\n---\n\n> * 原文链接 : [Dagger 1 to 2 migration process](http://frogermcs.github.io/dagger-1-to-2-"
},
{
"path": "issue-11/Android-Espresso测试框架介绍.md",
"chars": 6912,
"preview": "Android Espresso 测试框架介绍\n---\n\n> * 原文链接 : [Introduction to Android Espresso](https://androidresearch.wordpress.com/2015/04"
},
{
"path": "issue-11/Android进行单元测试难在哪-part3.md",
"chars": 12192,
"preview": "Android 进行单元测试难在哪-part3\n---\n\n> * 原文链接 : [HOW TO MAKE OUR ANDROID APPS UNIT TESTABLE (PT. 1)](http://philosophicalhacker."
},
{
"path": "issue-11/Code Review最佳实践.md",
"chars": 3790,
"preview": "Code Review最佳实践\n====\n> * 原文链接 : [Code Review Best Practices](http://kevinlondon.com/2015/05/05/code-review-best-practice"
},
{
"path": "issue-11/readme.md",
"chars": 556,
"preview": "# 开发技术前线 第10期\n\n| 文章名称 | 译者 | \n|---------|--------|\n| [Android-Espresso测试框架介绍](Android-Espresso测试框架介绍.md) | [zhengxia"
},
{
"path": "issue-11/听FaceBook工程师讲Custom-ViewGroups.md",
"chars": 9709,
"preview": "#听FackBook工程师讲*Custom ViewGroups*\n\n---\n\n> * 原文链接 : [Custom ViewGroups](https://sriramramani.wordpress.com/2015/05/06/cus"
},
{
"path": "issue-11/详解Dagger2.md",
"chars": 12737,
"preview": "Dagger2\n---\n\n> * ԭ : [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/)\n* ԭ"
},
{
"path": "issue-12/Android上MVP的介绍.md",
"chars": 17667,
"preview": "MVP在Android平台上的应用\n---\n\n> * 原文链接 : [Introduction to Model-View-Presenter on Android](http://konmik.github.io/introduction"
},
{
"path": "issue-12/Android自动截屏工具.md",
"chars": 2707,
"preview": "Android 自动截屏工具\n---\n\n> * 原文链接 : [Automating Android Screenshots](https://medium.com/@swanhtet1992/automating-android-scre"
},
{
"path": "issue-12/Android进行单元测试难在哪-part4.md",
"chars": 16016,
"preview": "Android 进行单元测试难在哪-part4\n---\n\n> * 原文链接 : [HOW TO MAKE OUR ANDROID APPS UNIT TESTABLE (PT. 2)](http://philosophicalhacker."
},
{
"path": "issue-12/MVP框架Mosby架构详解.md",
"chars": 24325,
"preview": "# Ted Mosby - 软件架构\n\n* 作者:Hannes Dorfmann\n* 原文链接 : [http://hannesdorfmann.com/android/mosby/]\n(http://hannesdorfmann.com/"
},
{
"path": "issue-12/readme.md",
"chars": 622,
"preview": "# 2015.5.31 ( 第十二期 )\n| 文章名称 | 译者 | \n|---------|--------|\n| [自动化Android开发](自动化Android开发.md) | [tmc9031](https://githu"
},
{
"path": "issue-12/当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合.md",
"chars": 9288,
"preview": "当复仇者联盟遇上Dragger2、RxJava和Retrofit的巧妙结合\n---\n\n> * 原文链接 : [When the Avengers meet Dagger2, RxJava and Retrofit in a clean wa"
},
{
"path": "issue-12/自动化Android开发.md",
"chars": 7936,
"preview": "自动化 Android 开发\n---\n\n> * 原文链接:[Automating Android development](https://medium.com/google-developer-experts/automating-and"
},
{
"path": "issue-13/Android进行单元测试难在哪-终.md",
"chars": 4852,
"preview": "Android 进行单元测试难在哪-终\n---\n\n> * 原文链接 : [WHAT I’VE LEARNED FROM TRYING TO MAKE AN ANDROID APP UNIT TESTABLE](http://www.phil"
},
{
"path": "issue-13/Square:从今天开始抛弃Fragment吧!.md",
"chars": 14453,
"preview": "Square:从今天开始抛弃Fragment吧!\n---\n\n> * 原文链接 : [Advocating Against Android Fragments](https://corner.squareup.com/2014/10/advo"
},
{
"path": "issue-13/readme.md",
"chars": 747,
"preview": "# 2015.6.5 ( 第十三期 )\n| 文章名称 | 译者 | \n|---------|--------|\n| [Square:从今天开始抛弃Fragment吧!](Square:从今天开始抛弃Fragment吧!.md) | "
},
{
"path": "issue-13/优化android-studio编译效率的方法.md",
"chars": 2913,
"preview": "优化android studio编译效率的方法\n---\n\n> * 原文链接 : [Boosting the performance for Gradle in your Android projects](https://medium.co"
},
{
"path": "issue-13/创建-RecyclerView-LayoutManager-Part-2.md",
"chars": 9443,
"preview": "创建 RecyclerView LayoutManager – Part 2\n---\n\n> * 原文链接 : [Building a RecyclerView LayoutManager – Part 2][part2]\n> * 原文作者 "
},
{
"path": "issue-13/创建-RecyclerView-LayoutManager-Part-3.md",
"chars": 11506,
"preview": "创建 RecyclerView LayoutManager – Part 3\n---\n\n> * 原文链接 : [Building a RecyclerView LayoutManager – Part 3][part3]\n> * 原文作者 "
},
{
"path": "issue-13/创建-RecyclerView-LayoutManager-Redux.md",
"chars": 5269,
"preview": "创建-RecyclerView-LayoutManager-Redux\n---\n\n> * 原文链接 : [Building A RecyclerView LayoutManager – Redux](http://wiresareobsol"
},
{
"path": "issue-14/Android-Design-Support-Library.md",
"chars": 13271,
"preview": "Android Design Support Library\n---\n\n> * 原文链接 : [Android Design Support Library](http://android-developers.blogspot.jp/20"
},
{
"path": "issue-14/Android之WebRTC介绍.md",
"chars": 13863,
"preview": "#Android之WebRTC介绍\n---\n\n> * 原文链接 : [Introduction to WebRTC on Android](https://tech.appear.in/2015/05/25/Introduction-to-"
},
{
"path": "issue-14/Design-Support-Library(I)-Navigation-View.md",
"chars": 5517,
"preview": "\nDesign Support Library (I): Navigation View\n\n---\n\n> * 原文链接 : [Design Support Library (I): Navigation View][source]\n* 原文"
},
{
"path": "issue-14/MVVM 模式简介.md",
"chars": 1523,
"preview": "MVVM 模式简介\n---\n\n> * 原文链接 : [MVVM on Android: What You Need to Know](http://www.willowtreeapps.com/blog/mvvm-on-android-wh"
},
{
"path": "issue-14/新的测试注解.md",
"chars": 751,
"preview": "# 新的测试注解\n--------\n\n> * 原文链接 : [New annotation for testing ](https://plus.google.com/+StephanLinzner/posts/GBdq6NsRy6S)\n*"
},
{
"path": "issue-15/2015-Google-IO带来的新Android开发工具 .md",
"chars": 8493,
"preview": "2015 Google IO带来的新 Android 开发工具\n---\n\n> * 原文链接 : [Google I/O Summary: What’s new in Android Development Tools](http://rob"
},
{
"path": "issue-15/Android-C++引用计数介绍.md",
"chars": 4103,
"preview": "Android C++ 引用计数介绍, part 1\n---\n\n> * 原文链接 : [Introduction to Android C++ reference counting, part 1](http://pierrchen.blo"
},
{
"path": "issue-15/Android-M的App-Links实现详解.md",
"chars": 7278,
"preview": "# Android M的App Links实现详解\n-----\n\n> * 原文链接 : [THINGS YOU MAY NOT KNOW: ONRESUMEFRAGMENTS](http://www.randomlytyping.com/b"
},
{
"path": "issue-15/facebook代码分析工具-infer.md",
"chars": 3200,
"preview": "# facebook开源项目Facebook Infer: 静态代码检查工具\n-----\n\n> * 原文链接 : [Open-sourcing Facebook Infer: Identify bugs before you ship](h"
},
{
"path": "issue-15/readme.md",
"chars": 0,
"preview": ""
},
{
"path": "issue-15/你可能漏掉的知识点-onResumeFragments.md",
"chars": 1701,
"preview": "你可能漏掉的知识点: onResumeFragments\n---\n\n> * 原文链接 : [THINGS YOU MAY NOT KNOW: ONRESUMEFRAGMENTS](http://www.randomlytyping.com/"
},
{
"path": "issue-15/如何修复编译时的MultiDex崩溃.md",
"chars": 2701,
"preview": "如何修复编译时的MultiDex崩溃\n---\n\n> * 原文链接 : [PSA: fix MultiDex build crashes](https://medium.com/sebs-top-tips/psa-fix-multidex-b"
},
{
"path": "issue-16/Android一体机模式:规则限制.md",
"chars": 5661,
"preview": "# Android 一体机模式:规则限制\n-----\n\n> * 原文链接 : [Android Kiosk Mode: Rules for Restrictions](http://cases.azoft.com/android-kiosk"
},
{
"path": "issue-16/readme.md",
"chars": 0,
"preview": ""
},
{
"path": "issue-16/为什么你应该停止使用EventBus.md",
"chars": 1777,
"preview": "为什么你应该停止使用EventBus\n---\n\n> * 原文链接 : [Why you should avoid using an event bus](http://endlesswhileloop.com/blog/2015/06/11"
},
{
"path": "issue-16/手动实现布局Transitions动画-第一部分.md",
"chars": 15173,
"preview": "# 手动实现布局Transitions动画-第一部分\n----\n\n> * 原文链接 : [Manual Layout Transitions – Part 1](https://blog.stylingandroid.com/manual-"
},
{
"path": "issue-16/手动实现布局Transitions动画-第三部分.md",
"chars": 8493,
"preview": "# 手动实现布局Transitions动画-第三部分\n\n> * 原文链接 : [Manual Layout Transitions – Part 3](https://blog.stylingandroid.com/manual-layou"
},
{
"path": "issue-16/手动实现布局Transitions动画-第二部分.md",
"chars": 9980,
"preview": "# 手动实现布局Transitions动画-第二部分\n----\n\n> * 原文链接 : [Manual Layout Transitions – Part 2](https://blog.stylingandroid.com/manual-"
},
{
"path": "issue-16/结合RxJava更简单地使用SQLite.md",
"chars": 3568,
"preview": "结合RxJava更简单地使用SQLite\n---\n\n> * 原文链接 : [Easy SQLite on Android with RxJava][source]\n* 原文作者 : [Cédric Beust](http://beust.c"
},
{
"path": "issue-17/Android UI 自动化测试.md",
"chars": 8857,
"preview": "#Android UI 自动化测试\n\n* 原文链接 : [Automating User Interface Testing on Android](http://code.tutsplus.com/tutorials/automating"
},
{
"path": "issue-17/Android中的帧动画.md",
"chars": 7510,
"preview": "Android中的帧动画(Frame Animation)\n---\n\n> * 原文链接 : [Frame Animations in Android](https://www.bignerdranch.com/blog/frame-anim"
},
{
"path": "issue-17/Android开发III-规范与性能.md",
"chars": 5310,
"preview": "Android开发, III: 规范: 性能\n---\n\n> * 原文链接 : [Developing for Android, III: The Rules: Performance](https://medium.com/google-d"
},
{
"path": "issue-17/readme.md",
"chars": 0,
"preview": ""
},
{
"path": "issue-17/为什么需要在你的Crash报告中使用git-SHA.md",
"chars": 2149,
"preview": "为什么需要用 GIT SHA 管理Crash\n---\n\n> * 原文链接 : [Why You Should Use a GIT SHA in Your Crash Reporting](http://www.donnfelker.com/"
},
{
"path": "issue-17/使用RxJava从几个数据源中加载数据.md",
"chars": 2642,
"preview": "# 使用RxJava从多个数据源中加载数据\n---\n\n> * 原文链接 : [Loading data from multiple sources with RxJava](http://blog.danlew.net/2015/06/22"
},
{
"path": "issue-17/如何使用Android-Studio把自己的Android-library分发到jCenter和Maven-Central.md",
"chars": 17284,
"preview": "# 如何使用Android Studio把自己的Android library分发到jCenter和Maven Central\n---\n\n> * 原文链接 : [How to distribute your own Android libr"
},
{
"path": "issue-17/当钢铁侠变得反应更快-RxJava.md",
"chars": 8982,
"preview": "当钢铁侠反应更灵敏-RxJava\n---\n\n> * 原文链接 : [When Iron Man becomes reactive, RxJava](http://saulmm.github.io/when-Iron-Man-becomes-"
},
{
"path": "issue-18/Service测试.md",
"chars": 3177,
"preview": "# Service测试\n---\n\n> * 原文链接 : [Service Testing](http://developer.android.com/tools/testing/service_testing.html)\n* 原文作者 : "
},
{
"path": "issue-18/readme.md",
"chars": 638,
"preview": "# 开发技术前线 第18期\n\n| 文章名称 | 译者 | \n|---------|--------|\n| [用组合代替继承能为Activity带来什么](用组合代替继承能为Activity带来什么.md) | [chaossss]("
},
{
"path": "issue-18/如何提高你的代码质量.md",
"chars": 7899,
"preview": "如何提高你的代码质量\n---\n> * 原文链接 : [How to improve quality and syntax of your Android code](http://vincentbrison.com/2014/07/19/h"
},
{
"path": "issue-18/拖拽RecyclerView.md",
"chars": 7778,
"preview": "#拖拽RecyclerView\n\n---\n\n> * 原文链接 : [Drag and Swipe with RecyclerView](https://medium.com/@ipaulpro/drag-and-swipe-with-rec"
},
{
"path": "issue-18/用组合代替继承能为Activity带来什么.md",
"chars": 2841,
"preview": "#用组合代替继承能为 Activity 带来什么\n\n---\n\n> * 原文链接 : [Composition over Inheritance,What it means for your Activities](https://plus."
},
{
"path": "issue-18/还在用Toast?你Out啦,试试Snackbar吧!.md",
"chars": 3472,
"preview": "#还在用Toast?你Out啦,试试Snackbar吧!\n\n---\n\n> * 原文链接 : [Welcome Snackbar, Goodbye Toast!](http://www.technotalkative.com/part-2-w"
},
{
"path": "issue-19/Android-UI自动化测试.md",
"chars": 8799,
"preview": "#Android UI 自动化测试\n\n* 原文链接 : [Automating User Interface Testing on Android](http://code.tutsplus.com/tutorials/automating"
},
{
"path": "issue-19/RxJava-Observables单元测试.md",
"chars": 2675,
"preview": "RxJava Observables单元测试\n---\n> * 原文链接 : [Unit Testing RxJava Observables](https://medium.com/ribot-labs/unit-testing-rxjav"
},
{
"path": "issue-19/gradle技巧之语法浅谈.md",
"chars": 5306,
"preview": "gradle技巧之语法浅谈\n---\n\n> * 原文链接 : [Gradle tip #2: understanding syntax](http://trickyandroid.com/gradle-tip-2-understanding-"
},
{
"path": "issue-19/readme.md",
"chars": 323,
"preview": "# 开发技术前线 第19期\n\n| 文章名称 | 译者 | \n|---------|--------|\n| [RxJava-Observables单元测试](RxJava-Observables单元测试.md) | [dengshiw"
},
{
"path": "issue-20/readme.md",
"chars": 0,
"preview": ""
},
{
"path": "issue-20/在你开发应用前应知道的六件事情.md",
"chars": 4822,
"preview": "开发第一个应用之前你需要知道的六件事\n---\n> * 原文链接 : [6 THINGS I WISH I KNEW BEFORE I WROTE MY FIRST ANDROID APP](http://www.philosophicalh"
},
{
"path": "issue-20/检测和解决Android应用的性能问题.md",
"chars": 8199,
"preview": "# 检测和解决Android应用的性能问题\n-------------\n\n> * 原文链接 : [Detect and Resolve Performance Problems on Android](http://code.tutsplu"
},
{
"path": "issue-21/Android数据绑定-再见Presenter,你好ViewModel.md",
"chars": 24155,
"preview": "# Android DataBinding:再见Presenter,你好ViewModel!\n\n> @author ASCE1885的 [Github](https://github.com/ASCE1885) [简书](http://w"
},
{
"path": "issue-21/TextView预渲染研究.md",
"chars": 4633,
"preview": "\n# TextView预渲染研究\n-------------------------------------\n\n> * [原文链接](http://ragnraok.github.io/textview-pre-render-researc"
},
{
"path": "issue-21/Yelp是如何通过Glide优化图片加载的.md",
"chars": 2415,
"preview": "# Yelp是如何通过Glide优化图片加载的\n-------\n> * 原文链接 : [Glide – How Yelp’s Android App Loads Images](http://engineeringblog.yelp.com"
},
{
"path": "issue-21/使用Mockito对异步方法进行单元测试.md",
"chars": 5772,
"preview": "# 使用Mockito对异步方法进行单元测试\n=====================================================\n\n> * 原文链接 : [Unit testing asynchronous meth"
},
{
"path": "issue-22/Android-Activity测试.md",
"chars": 7471,
"preview": "#Activity测试\n---\n\n> * 原文链接 : [Activity Testing](http://developer.android.com/intl/zh-cn/tools/testing/activity_testing.ht"
},
{
"path": "issue-22/Android中的AOP编程.md",
"chars": 10913,
"preview": "Android 中的 AOP 编程\n---\n\n> * 原文链接 : [Aspect Oriented Programming in Android](http://fernandocejas.com/2014/08/03/aspect-or"
},
{
"path": "issue-22/Binder框架解析.md",
"chars": 60051,
"preview": "#Why are you here?\n\n* 你想要更好的理解Android是怎样工作的\n\n 。Intent、ContextProvider、Messenger \n\t。访问系统的服务 \n\t。生命周期中的回调 \n\t。安全\n\n"
},
{
"path": "issue-22/深入剖析Android网络开发库-part1.md",
"chars": 11844,
"preview": "深入剖析Android网络开发库-part1: OkHttp, Volley and Gson\n---\n\n> * 原文链接 : [Android Networking I: OkHttp, Volley and Gson](https://"
},
{
"path": "issue-23/Android-MVPR-架构模式-Part1.md",
"chars": 8908,
"preview": "Android MVPR 架构模式-Part1\n---\n\n> * 原文链接 : [MVPR: A FLEXIBLE, TESTABLE ARCHITECTURE FOR ANDROID (PT. 1)](http://www.philoso"
},
{
"path": "issue-23/Android-MVVM模式.md",
"chars": 6838,
"preview": "#Android MVVM模式\n\n---\n> * 原文链接 : [MVVM on Android using the Data Binding Library](http://stablekernel.com/blog/mvvm-on-an"
},
{
"path": "issue-23/TextView的TextLayout.md",
"chars": 3223,
"preview": "多文本布局\n---\n> * 原文链接 : [Multiple Text Layout](https://sriramramani.wordpress.com/2014/08/14/multiple-text-layout/)\n* 原文作者 "
},
{
"path": "issue-23/使用Espresso进行UI测试.md",
"chars": 3069,
"preview": "#使用Espresso进行UI测试\n---\n\n> * 原文链接 : [Using Espresso for Easy UI Testing](http://www.michaelevans.org/blog/2015/08/03/using"
},
{
"path": "issue-23/使用TDD的方式开发一个Hackernews客户端.md",
"chars": 11140,
"preview": "使用TDD的方式开发一个Hackernews客户端\n---\n\n\n> * 原文链接 : [MAKING A TDD-BASED HACKERNEWS CLIENT FOR ANDROID](http://www.philosophicalha"
},
{
"path": "issue-24/Android LayerDrawable 和 Drawable.Callback.md",
"chars": 4631,
"preview": "#Android LayerDrawable 和 Drawable.Callback\n\n> * 原文链接 : [Android LayerDrawable and Drawable.Callback](http://www.roman10."
},
{
"path": "issue-24/Android双向数据绑定.md",
"chars": 12072,
"preview": "Android 双向 Data Binding\n---\n\n> * 原文链接 : [Two-way Android Data Binding](https://medium.com/@fabioCollini/android-data-bin"
},
{
"path": "issue-24/Android设计与开发工作流.md",
"chars": 5627,
"preview": "Android设计与开发工作流\n---\n> * 原文链接 : [Wutson: Exploring Design And Development Workflows](http://novoda.com/blog/londroid-wuts"
},
{
"path": "issue-24/使用Kotlin对ViewGroup中的View进行函数式操作.md",
"chars": 6566,
"preview": "使用 Kotlin 对 ViewGroup 中的 View 进行函数式操作\n---\n\n> * 原文链接 : [Functional operations over Views in ViewGroup using Kotlin](http:"
},
{
"path": "issue-24/适用于Android的Flux架构.md",
"chars": 12912,
"preview": "\n# Flux Architecture on Android \n# 适用于Android的Flux架构\n============================\n\n> * 原文链接 : [Flux Architecture on Andr"
},
{
"path": "issue-25/Android主题动态切换开源库Prism基本原理1-核心功能.md",
"chars": 6097,
"preview": "#Android主题动态切换开源库Prism基本原理1-核心功能\n\n> * 原文链接 : [Prism Fundamentals Part 1](https://blog.stylingandroid.com/prism-fundament"
},
{
"path": "issue-25/Android主题动态切换开源库Prism基本原理2-搭配ViewPager使用.md",
"chars": 7689,
"preview": "#Android主题动态切换开源库Prism基本原理2-搭配ViewPager使用\n\n> * 原文链接 : [Prism Fundamentals Part 2](https://blog.stylingandroid.com/prism-"
},
{
"path": "issue-25/Android主题动态切换开源库Prism基本原理3-搭配Palette使用.md",
"chars": 5194,
"preview": "#Android主题动态切换开源库Prism基本原理3-搭配Palette使用\n\n> * 原文链接 : [Prism Fundamentals Part 3](https://blog.stylingandroid.com/prism-fu"
},
{
"path": "issue-25/一个内存泄漏引发的血案-Square.md",
"chars": 12624,
"preview": "一个内存泄漏引发的血案-Square\n---\n\n> * 原文链接 : [A small leak will sink a great ship](https://corner.squareup.com/2015/08/a-small-lea"
},
{
"path": "issue-25/在Android Lollipop中使用Palette抽取Bitmap颜色.md",
"chars": 3782,
"preview": "#在Android Lollipop中使用Palette抽取Bitmap颜色\n\n> * 原文链接 : [Extracting Colors to a Palette with Android Lollipop](https://www.bi"
},
{
"path": "issue-26/30分钟搭建一个android的私有Maven仓库.md",
"chars": 5648,
"preview": "30分钟搭建一个android的私有Maven仓库\n---\n\n> * 原文链接 : [A PRIVATE MAVEN REPOSITORY FOR ANDROID IN 30 MIN](原文url)\n* 原文作者 : [JEROEN MOL"
},
{
"path": "issue-26/Android架构演化之路.md",
"chars": 11844,
"preview": "Android架构演化之路\n---\n\n> * 原文链接 : [Architecting Android…The evolution](http://fernandocejas.com/2015/07/18/architecting-andr"
},
{
"path": "issue-26/Tinting drawables.md",
"chars": 2618,
"preview": "\n#Tinting drawables\n\n---\n\n> * 原文链接 : [Tinting drawables](http://andraskindler.com/blog/2015/tinting_drawables/)\n* 原文作者 :"
},
{
"path": "issue-26/使用Systrace分析UI性能.md",
"chars": 5374,
"preview": "#使用Systrace分析UI性能\n---\n\n> * 原文链接 : [Analyzing UI Performance with Systrace](http://developer.android.com/intl/zh-cn/tools"
},
{
"path": "issue-26/在Android M中权限被拒绝时该如何处理.md",
"chars": 1021,
"preview": "#在Android M中权限被拒绝时该如何处理\n\n> * 原文链接 : [How to deal with permission denial on Android M](https://plus.google.com/+AndroidDe"
},
{
"path": "issue-27/为什么不仅继承Observale而且使用Observable.create.md",
"chars": 2026,
"preview": "#为什么不仅继承Observale而且使用Observale.create()?\n---\n\n> * 原文链接 : [Why use Observable.create() and not just inherit from Observab"
},
{
"path": "issue-27/为你的应用加速 - 安卓优化指南.md",
"chars": 19655,
"preview": "Speed up your app\n\n---\n\n原文链接 : [Speed up your app][1]\n原文作者 : [UDI COHEN][2]\n译文出自 : [开发技术前线 www.devtf.cn。未经允许,不得转载!][3]\n译"
},
{
"path": "issue-27/使用Gradle将项目发布到Bitbucket上.md",
"chars": 3597,
"preview": "使用Gradle将项目发布到Bitbucket上\n---\n\n> * 原文链接 : [Publish with Gradle on Bitbucket](https://medium.com/@Mul0w/publish-with-gradl"
},
{
"path": "issue-27/开发你自己的Android授权管理器.md",
"chars": 15665,
"preview": "开发你自己的Android 授权管理器\n---\n\n> * 原文链接 : [Write your own Android Authenticator](http://blog.udinic.com/2013/04/24/write-your-"
},
{
"path": "issue-28/数据绑定(Data Binding)-Part1.md",
"chars": 8171,
"preview": "#数据绑定(Data Binding)-Part1\n---\n\n> * 原文链接 : [Data Binding - Part 1](https://blog.stylingandroid.com/data-binding-part-1/)\n"
},
{
"path": "issue-28/数据绑定(Data Binding)-Part2.md",
"chars": 3872,
"preview": "#数据绑定(Data Binding)-Part2\n---\n\n> * 原文链接 : [Data Binding - Part 2](https://blog.stylingandroid.com/data-binding-part-2/)\n"
},
{
"path": "issue-28/数据绑定(Data Binding)-Part3.md",
"chars": 4968,
"preview": "#数据绑定(Data Binding)-Part3\n---\n\n> * 原文链接 : [Data Binding - Part 3](https://blog.stylingandroid.com/data-binding-part-3/)\n"
},
{
"path": "issue-28/数据绑定(Data Binding)-Part4.md",
"chars": 9636,
"preview": "#数据绑定(Data Binding)-Part4\n---\n\n> * 原文链接 : [Data Binding - Part 4](https://blog.stylingandroid.com/data-binding-part-4/)\n"
},
{
"path": "issue-28/数据绑定(Data Binding)-Part5.md",
"chars": 7779,
"preview": "#数据绑定(Data Binding)-Part5\n---\n\n> * 原文链接 : [Data Binding - Part 5](https://blog.stylingandroid.com/data-binding-part-5/)\n"
},
{
"path": "issue-29/Chrome自定义Tabs-让App和Web之间的转场更平顺.md",
"chars": 2819,
"preview": "Chrome自定义Tabs,让App和Web之间的转场更平顺\n---\n\n> * 原文链接 : [Chrome custom tabs smooth the transition between apps and the web](http:"
},
{
"path": "issue-29/开发安全的Android应用.md",
"chars": 9490,
"preview": "开发安全的Android应用\n---\n\n> * 原文链接 : [Develop a secured Android application](http://blog.octo.com/en/develop-secured-android-a"
},
{
"path": "issue-29/注意API21(Android5.0)上的EditText.md",
"chars": 1621,
"preview": "注意API 21 (Android 5.0) 上的EditText\n---\n\n> * 原文链接 : [Beware EditText on API 21](http://blog.danlew.net/2015/10/12/beware-e"
},
{
"path": "issue-30/Android开发生僻却实用的知识点Part1.md",
"chars": 2037,
"preview": "Android 开发生僻却实用的知识点 Part 1\n---\n\n> * 原文链接 : [Android Development Tidbits // No. 1](http://willowtreeapps.com/blog/android"
},
{
"path": "issue-30/Android开发生僻却实用的知识点Part2.md",
"chars": 2304,
"preview": "Android 开发生僻却实用的知识点 Part 2\n---\n\n> * 原文链接 : [Android Development Tidbits // No. 2](http://willowtreeapps.com/blog/android"
},
{
"path": "issue-30/Flux and Android.md",
"chars": 7025,
"preview": "Flux and Android\n---\n\n> * 原文链接 : [Flux and Android](http://armueller.github.io/android/2015/03/29/flux-and-android.html)"
},
{
"path": "issue-30/通过硬件层提高Android动画的性能.md",
"chars": 3647,
"preview": "通过硬件层提高Android动画的性能\n---\n\n> * 原文链接 : [Using hardware layers to improve Android animation performance](http://blog.danlew."
},
{
"path": "issue-31/Android 中的依赖注入框架.md",
"chars": 4547,
"preview": "Android 中的依赖注入框架\n\n---\n\n> * 原文链接 : [Dependency Injection on Android](http://tech.just-eat.com/2015/10/26/dependency-injec"
},
{
"path": "issue-31/Android中调试RxJava.md",
"chars": 10397,
"preview": "# Android中调试RxJava\n\n> * 原文链接 : [Debugging RxJava on Android](http://fernandocejas.com/2015/11/05/debugging-rxjava-on-and"
},
{
"path": "issue-31/Android应用架构.md",
"chars": 8303,
"preview": "> - 原文链接: [Android Application Architecture](https://medium.com/ribot-labs/android-application-architecture-8b6e34acda65"
},
{
"path": "issue-31/Android开发生僻却实用的知识点Part3.md",
"chars": 2957,
"preview": "Android 开发生僻却实用的知识点 Part 3\n---\n\n> * 原文链接 : [Android Development Tidbits // No. 3](http://willowtreeapps.com/blog/android"
},
{
"path": "issue-31/Espresso保存和恢复状态.md",
"chars": 1586,
"preview": "Espresso:保存和恢复状态\n---\n\n> * 原文链接 : [Espresso: Save and restore state](http://blog.sqisland.com/2015/10/espresso-save-and-r"
},
{
"path": "issue-31/使用ClassyShark压缩你的项目.md",
"chars": 1799,
"preview": "使用ClassyShark压缩你的项目\n---\n\n> * 原文链接 : [Shrinking Your Build With No Rules\nand do it with Class(yShark)](https://medium.co"
},
{
"path": "issue-32/AppBarLayout的越界滚动行为.md",
"chars": 4150,
"preview": "AppBarLayout的越界滚动行为\n---\n\n> * 原文链接 : [Overscroll AppBarLayout Behavior](https://medium.com/@nullthemall/overscroll-appbar"
},
{
"path": "issue-32/剪刀手—Android平台上的图片裁剪库.md",
"chars": 2360,
"preview": "剪刀手:Android平台上的图片裁剪库\n---\n\n> * 原文链接 : [Scissors: an image cropping library for Android](https://eng.lyft.com/scissors-an-"
},
{
"path": "issue-32/实现安卓6.0的直接分享(Direct Share )功能.md",
"chars": 5354,
"preview": "实现Android6.0的直接分享(Direct Share )功能\n---\n\n> * 原文链接 : [Implementing Android Marshmallow Direct Share](https://www.bignerdra"
},
{
"path": "issue-32/重构Plaid-响应式的MVP模式.md",
"chars": 16485,
"preview": "\n#重构Plaid-响应式的MVP模式(一)\n---\n\n> * 原文链接 : [REFACTORING PLAID APP - A REACTIVE MVP APPROACH (PART 1)](http://hannesdorfmann"
},
{
"path": "issue-32/高性能ListViews.md",
"chars": 3104,
"preview": "高性能ListViews\n---\n\n> * 原文链接 : [Performance ListViews](http://willowtreeapps.com/blog/performance-listviews/?utm_source=An"
},
{
"path": "issue-33/Android Libraries的依赖管理.md",
"chars": 3919,
"preview": "Android Libraries的依赖管理\n---\n\n> * 原文链接 : [Dependency Management for Android Libraries](http://johnpetitto.com/android-lib-"
},
{
"path": "issue-33/下雪动画.md",
"chars": 7058,
"preview": "\n# 下雪动画\n\n---\n\n> * 原文链接 : [Snowfall](https://blog.stylingandroid.com/snowfall/)\n* 原文作者 : [Styling Android](https://blog."
},
{
"path": "issue-33/使用ADB-Shell的效率和乐趣-part1.md",
"chars": 3121,
"preview": "# 使用ADB Shell的效率和乐趣-Part1\n> * 原文链接 : [Efficiency and fun from using ADB Shell, Part 1](https://ar-g.github.io/ADB-Shell-"
},
{
"path": "issue-33/如何自定义Lint规则.md",
"chars": 8989,
"preview": "#如何自定义Lint规则\n> * 原文链接 : [Help developers with custom Lint rules](http://jeremie-martinez.com/2015/12/15/custom-lint-rule"
},
{
"path": "issue-33/高效地配置okhttp.md",
"chars": 6833,
"preview": "高效地配置OkHttp\n---\n\n> * 原文链接 : [Effective OkHttp](http://omgitsmgp.com/2015/12/02/effective-okhttp/)\n* 原文作者 : [Michael Park"
},
{
"path": "issue-34/Android逆向工程101 – Part 1.md",
"chars": 4698,
"preview": "#Android逆向工程101 – Part 1\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 1](http://www.fasteque.com/android-reve"
},
{
"path": "issue-34/Android逆向工程101 – Part 2.md",
"chars": 3975,
"preview": "Android逆向工程101 – Part 2\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 2](http://www.fasteque.com/android-rever"
},
{
"path": "issue-34/Context是怎么泄露的Handlers & Inner Classes.md",
"chars": 3877,
"preview": "Context是怎么泄露的:Handlers & Inner Classes\n---\n\n> * 原文链接 : [How to Leak a Context: Handlers & Inner Classes](http://www.andr"
},
{
"path": "issue-34/在Android开发中使用RxJava.md",
"chars": 11716,
"preview": "#在Android开发中使用RxJava\n\n---\n\n- 原文链接 : [Getting Started with RxJava and Android](http://www.captechconsulting.com/blogs/get"
},
{
"path": "issue-35/为什么我们要用fitsSystemWindows.md",
"chars": 3339,
"preview": "我们为什么要用fitsSystemWindows?\n---\n\n> * 原文链接 : [Why would I want to fitsSystemWindows?](https://medium.com/google-developers/"
},
{
"path": "issue-35/星球大战:原力觉醒或者用原力粉碎Android的视图.md",
"chars": 5387,
"preview": "星球大战:原力觉醒或者用原力粉碎Android的视图\n---\n\n> * 原文链接 : [Star Wars: The Force Awakens Or How to Crumble View Into Tiny Pieces on Andr"
},
{
"path": "issue-35/用Transition完成Fragment共享元素的切换.md",
"chars": 4888,
"preview": "用 Transition 完成 Fragment 共享元素的切换\n---\n\n> * 原文链接 : [Fragment transitions with shared elements](https://medium.com/@bherbst"
},
{
"path": "issue-35/让EditText中的链接即可点击又可编辑.md",
"chars": 1597,
"preview": "让EditText中的链接即可点击又可编辑\n---\n\n> * 原文链接 : [Making EditTexts with links both clickable and editable](http://blog.danlew.net/2"
},
{
"path": "issue-35/近乎通用的VectorDrawable.md",
"chars": 5713,
"preview": "近乎通用的VectorDrawable\n---\n\n> * 原文链接 : [Vectors For All (almost)](https://blog.stylingandroid.com/vectors-for-all-almost/?u"
},
{
"path": "issue-36/Gradle小知识1.md",
"chars": 2780,
"preview": "Gradle小知识#1:tasks\n---\n\n> * 原文链接 : [Gradle tip #1: tasks](http://trickyandroid.com/gradle-tip-1-tasks/)\n* 原文作者 : [Tricky "
},
{
"path": "issue-36/Gradle小知识2.md",
"chars": 6963,
"preview": "Gradle小知识#2:学学语法\n---\n\n> * 原文链接 : [Gradle tip #2: understanding syntax](http://trickyandroid.com/gradle-tip-2-understandi"
},
{
"path": "issue-36/Gradle小知识3.md",
"chars": 5714,
"preview": "Gradle小知识#3:任务的顺序\n---\n\n> * 原文链接 : [Gradle tip #3: Tasks ordering](http://trickyandroid.com/gradle-tip-3-tasks-ordering/)"
},
{
"path": "issue-36/Gradle小知识4.md",
"chars": 1499,
"preview": "Gradle小知识#4:把单元测试的日志打印到控制台\n---\n\n> * 原文链接 : [Gradle tip #4: Log unit test execution events into console](http://trickyand"
},
{
"path": "issue-36/Gradle提示和使用技巧.md",
"chars": 4984,
"preview": "Gradle提示和使用技巧\n---\n\n> * 原文链接 : [Gradle tips & tricks to survive the zombie apocalypse](https://medium.com/@cesarmcferreir"
},
{
"path": "issue-37/使用ACTION_PROCESS_TEXT创建自定义文本选择动作.md",
"chars": 6803,
"preview": "使用ACTION_PROCESS_TEXT创建自定义文本选择动作\n---\n\n> * 原文链接 : [Creating custom Text Selection actions with ACTION_PROCESS_TEXT](https"
},
{
"path": "issue-37/使用RxJava缓存Rest请求.md",
"chars": 5811,
"preview": "使用RxJava缓存Rest请求\n---\n\n> * 原文链接 : [Subscribe It While It's Hot: Cached Rest Requests With RxJava](http://fedepaol.github."
},
{
"path": "issue-37/在Android中使用并发来提高速度和性能.md",
"chars": 6763,
"preview": "在Android中使用并发来提高速度和性能\n---\n\n> * 原文链接 : [Using concurrency to improve speed and performance in Android](https://medium.com"
},
{
"path": "issue-37/如何理解RxJava中的Subjects(第一部分).md",
"chars": 5328,
"preview": "如何理解RxJava中的Subjects(第一部分)\n----\n\n> * 原文链接 : [How to think about Subjects in RxJava (Part 1)](https://tech.instacart.com/"
},
{
"path": "issue-37/更加强大的Dagger2.md",
"chars": 8219,
"preview": "更加强大的Dagger2\n---\n\n> * 原文链接 : [Dagger 2: Even sharper, less square](https://blog.gouline.net/2015/05/04/dagger-2-even-sha"
},
{
"path": "issue-38/Android逆向工程.md",
"chars": 5478,
"preview": "Android逆向工程-第三部分\n---\n\n> * 原文链接 : [Android Reverse Engineering 101 – Part 3](http://www.fasteque.com/android-reverse-engi"
},
{
"path": "issue-38/Android高性能JSON数据解析.md",
"chars": 3387,
"preview": "Android高性能JSON数据解析\n---\n\n> * 原文链接 : [Hi Performance JSON Parsing in Android](http://www.donnfelker.com/hi-performance-jso"
},
{
"path": "issue-38/RecyclerView动画 第一部-动画效果是如何工作的.md",
"chars": 4851,
"preview": "#RecyclerView动画 第一篇-动画是如何工作的\n\n> * 原文链接 : [RecyclerView Animations Part 1 – How Animations Work](http://www.birbit.com/re"
},
{
"path": "issue-38/RecyclerView动画 第二部-幕后.md",
"chars": 4756,
"preview": "#RecyclerView Animations Part 2 – Behind The Scenes\n#RecyclerView动画 第二篇-幕后\n> * 原文链接 : [RecyclerView Animations Part 2 – "
},
{
"path": "issue-38/从网页中触发Android原生的分享Intent.md",
"chars": 9321,
"preview": "# Triggering a native Share intent on Android from the web\n# 从网页中触发Android原生的分享Intent\n\n> * 原文链接 : [Triggering a native S"
},
{
"path": "issue-39/Android Studio提示和技巧.md",
"chars": 2460,
"preview": "#Android Studio Tips and Tricks\n#Android Studio提示和技巧\n> - Jan 6th, 2016\n\n我最近参加了Goolge的[Android Dev Summit](https://androi"
},
{
"path": "issue-39/Android dex分包导致的App启动速度下降.md",
"chars": 9822,
"preview": "#Android Multidex导致的App启动缓慢\n---\n\n> * 原文链接 : [Android’s multidex slows down app startup](https://medium.com/groupon-eng/a"
},
{
"path": "issue-39/FragmentTransaction 与 Activity 状态丢失?.md",
"chars": 5682,
"preview": "FragmentTransaction 与 Activity 状态丢失?\n---\n\n> * 原文链接 : [Fragment Transactions & Activity State Loss](http://www.androiddes"
},
{
"path": "issue-39/RxJava中repeatWhen 和 retryWhen 操作符的解释.md",
"chars": 10380,
"preview": "RxJava中repeatWhen 和 retryWhen 操作符的解释\n---\n\n> * 原文链接 : [RxJava's repeatWhen and retryWhen, explained](http://blog.danlew.n"
},
{
"path": "issue-39/在滚动列表中播放视频.md",
"chars": 12335,
"preview": "在滚动列表中播放视频\n---\n\n> * 原文链接 : [Implementing video playback in a scrolled list (ListView & RecyclerView)](https://medium.com"
},
{
"path": "issue-40/权限 - 第一篇.md",
"chars": 4979,
"preview": "# 权限 - 第一篇\n\n> * 原文链接 : [Permissions – Part 1](https://blog.stylingandroid.com/permissions-part-1/)\n* 原文作者 : [Mark Alliso"
},
{
"path": "issue-40/权限 - 第三篇.md",
"chars": 6322,
"preview": "# Permissions – Part 3\n# 权限 - 第三篇\n\n> * 原文链接 : [Permissions – Part 3](https://blog.stylingandroid.com/permissions-part-3/"
},
{
"path": "issue-40/权限 - 第二篇.md",
"chars": 3189,
"preview": "# Permissions – Part 2\n# 权限 - 第二篇\n\n> * 原文链接 : [Permissions – Part 2](https://blog.stylingandroid.com/permissions-part-2/"
},
{
"path": "issue-40/权限 - 第四篇.md",
"chars": 2096,
"preview": "# Permissions – Part 4\n# 权限 - 第四篇\n\n> * 原文链接 : [Permissions – Part 4](https://blog.stylingandroid.com/permissions-part-4/"
},
{
"path": "issue-41/Service十件你不知道的事.md",
"chars": 2375,
"preview": "Service十件你不知道的事\n---\n\n> * 原文链接 : [10 things you didn’t know about Android’s Service component](https://medium.com/@workin"
},
{
"path": "issue-41/自定义CoordinatorLayout的行为.md",
"chars": 4412,
"preview": "自定义 CoordinatorLayout 的行为\n---\n\n> * 原文链接 : [Customizing CoordinatorLayout's Behavior](https://www.bignerdranch.com/blog/c"
},
{
"path": "issue-41/让你了解数据加载的生命周期.md",
"chars": 12267,
"preview": "让你了解数据加载的生命周期\n---\n\n> * 原文链接 : [Making loading data lifecycle aware](https://medium.com/google-developers/making-loading-"
},
{
"path": "issue-42/IndeterminateProgressbar解析-Part 1.md",
"chars": 6360,
"preview": "IndeterminateProgressbar解析 – Part 1\n---\n\n> * 原文链接 : [Indeterminate – Part 1](https://blog.stylingandroid.com/indetermina"
}
]
// ... and 92 more files (download for full content)
About this extraction
This page contains the full source code of the hehonghui/android-tech-frontier GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 292 files (2.0 MB), approximately 542.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.