├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── android-blog ├── Android性能优化系列 │ └── readme.md ├── Google+ 团队的 Android UI 测试 │ └── readme.md └── readme.md ├── androidweekly ├── .DS_Store ├── 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 │ ├── images │ │ └── playstore.gif │ └── 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 ├── 功能测试框架 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 │ ├── .DS_Store │ ├── images │ │ ├── multiple-theme-dark.png │ │ └── multiple-theme-light.png │ └── readme.md ├── 让你的Android应用能使用多种主题-Part-2 │ └── readme.md └── 那些年我们错过的响应式编程 │ └── readme.md ├── authorization.md ├── git简单使用教程.md ├── issue-10 ├── Android 进行单元测试难在哪-part2.md └── readme.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 ├── others ├── .DS_Store ├── 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 ├── software-architecture-patterns ├── chap-5.md ├── chapter01-BillonWang.md ├── chapter02-chaossss.md ├── chapter03-Mr.Simple.md ├── chapter04-dupengwei.md ├── images │ ├── 1-1.png │ ├── 1-2.png │ ├── 1-3.png │ ├── 1-4.png │ ├── 2-1.png │ ├── 2-2.png │ ├── 2-3.png │ ├── 2-4.png │ ├── 3-1.png │ ├── 3-2.png │ ├── 4-1.png │ ├── 4-2.png │ ├── 4-3.png │ ├── 4-4.png │ ├── 5-1.png │ ├── 5-2.png │ ├── 5-3.png │ ├── 5-4.png │ ├── 5-5.png │ ├── a-1.png │ └── cover.png ├── readme.md ├── software-architecture-patterns.pdf ├── 软件架构模式.md ├── 软件架构模式.pdf └── 附录2015-4-11-charli.md ├── template.md ├── the-bad-guys └── readme.md └── 翻译项目协作流程.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.project 2 | *.classpath 3 | bin/ 4 | gen/ 5 | *.DS_Store 6 | */bin/ 7 | */gen/ 8 | .settings/ 9 | -------------------------------------------------------------------------------- /android-blog/Android性能优化系列/readme.md: -------------------------------------------------------------------------------- 1 | ## Android 应用性能优化系列 2 | 3 | >原文链接分别为 : 4 | > 5 | >* [https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE) 6 | * [https://www.udacity.com/course/ud825](https://www.udacity.com/course/ud825) 7 | * 译者 : [胡凯](http://hukai.me) 8 | 9 | 10 | 1. [Android性能优化典范](http://hukai.me/android-performance-patterns/) 11 | 2. [Android性能优化之渲染篇](http://hukai.me/android-performance-render/) 12 | 3. [Android性能优化之运算篇](http://hukai.me/android-performance-compute/) 13 | 4. [Android性能优化之内存篇](http://hukai.me/android-performance-memory/) 14 | 5. [Android性能优化之电量篇](http://hukai.me/android-performance-battery/) -------------------------------------------------------------------------------- /android-blog/Google+ 团队的 Android UI 测试/readme.md: -------------------------------------------------------------------------------- 1 | Google+ 团队的 Android UI 测试 2 | --- 3 | 4 | > 5 | * 原文链接:[How the Google+ Team Tests Mobile Apps](http://googletesting.blogspot.sg/2013/08/how-google-team-tests-mobile-apps.html) 6 | * 译者:[allenlsy](http://allelsy.com) 7 | * 译者博文地址:[http://allenlsy.com/android-ui-tests-in-google-plus-team/]() 8 | * 校对者: 9 | 10 | Android 测试主要分为3个类型: 11 | 12 | #### 单元测试(Unit Test) 13 | 14 | 区分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)。 15 | 16 | #### 封闭UI测试 (Hermetic UI Test) 17 | 18 | 这个测试方法使得测试不需要外部依赖和网络请求。这样做的主要目的是提高测试速度,减少测试时的外部影响,毕竟网络调用是相对很慢的。[Espresso](http://www.youtube.com/watch?v=T7ugmCuNxDU)可以用来模拟用户的UI操作。 19 | 20 | #### Monkey Test 21 | 22 | Monkey Test 就好像一只猴子在测试app一样,没有任何规律的在你的app上胡按。计算机运行monkey test的时候,每秒钟能做出几千个UI动作(可以配置这个频率),比如点击和拖拽。所以这个测试可以算是一个压力测试,用来检测[ANR](http://developer.android.com/training/articles/perf-anr.html)。 23 | 24 | --- 25 | 26 | Google+ 团队总结了一些 UI 测试时的经验和策略。 27 | 28 | #### 策略1: 不要使用 End-to-end 测试作为UI测试 29 | 30 | 先看一些定义:__UI 测试__ 是为了确保对于用户的UI动作,app能返回正确的UI输出。__End-to-end测试(E2E test)__ 是通过客户端和后台服务器的交互测试整个系统。下面这个图在展示了测试步骤: 31 | 32 | ![](http://img.my.csdn.net/uploads/201503/28/1427507159_1836.png) 33 | 34 | 通常做UI测试,你需要后台服务器,所以可能产生网络调用。所以UI测试和E2E测试很像。但是在E2E测试中会遇到很多困难: 35 | 36 | * 测试速度缓慢 37 | * 网络请求会失败 38 | * 难以Debug 39 | 40 | 下面看看如何解决这些问题。 41 | 42 | #### 策略2:使用伪服务器做封闭UI测试 43 | 44 | 这个策略中,你可以通过假的后台服务器来避免网络请求,以及其他外部依赖。技术上,你就需要在app本地提供返回数据了。有很多办法可以做到,比如手动做一次网络请求,把response保存下来,在测试的时候重复这个response。这样你就做了一个封闭在本地的伪服务器 45 | 46 | 当你有了这个伪服务器,你还需要给这个伪服务器写测试。于是这是,你的E2E测试就分为了服务器测试,客户端测试和集成测试。 47 | 48 | ![](http://img.my.csdn.net/uploads/201503/28/1427507159_8354.png) 49 | 50 | 现在这样的解决方案,你需要自己维护伪服务器,本地数据库和tests了。 51 | 52 | 下面这是E2E 测试的示例图: 53 | 54 | ![](http://img.my.csdn.net/uploads/201503/28/1427507160_4776.jpg) 55 | 56 | 这是使用了伪服务器的封闭UI测试 57 | 58 | ![](http://img.my.csdn.net/uploads/201503/28/1427507167_8779.jpg) 59 | 60 | 其区别在于:Frontend Server的几个数据源变了。由原来的真实后端,变成了封闭服务器,或者是mock服务器。这个在测试调用网络API的时候非常有用。 61 | 62 | #### 策略3:使用Dependency Injection 63 | 64 | Dependency Injection(依赖注入)可以帮助生成测试数据。我推荐选择使用[dagger](http://square.github.io/dagger/)作为依赖注入框架。 65 | 66 | 依赖注入在UI test和unit test都中都可以用于生成假数据。在instrumentation test框架中,测试用的apk文件和测试时运行的app,是在同一个进程下面,所以测试代码可以调用app代码。你还可以覆盖app的classpath,通过这种方式注入假数据。比如你可以用依赖注入来伪造一个网络连接的实现,调用这个网络连接的时候就可以提供假数据。 67 | 68 | ![](http://img.my.csdn.net/uploads/201503/28/1427507159_6700.png) 69 | 70 | 71 | ### 策略4:把app分为小的libraries 72 | 73 | 这个方法可以更好地模块化你的app。你的app被分为更小的类库之后,你可以为这些类库添加他们自己的UI依赖或gradle库依赖。 74 | 75 | 当你有了自己的库,并提供依赖注入的支持,那么你可以为各个库写测试app。最后,可以写__集成测试__来确保类库直接的合作正确。 76 | 77 | 比如我们有一个登陆功能的库,那么我可以写一个测试app只为这个登陆功能库: 78 | 79 | ![](http://img.my.csdn.net/uploads/201503/28/1427507160_4803.png) 80 | 81 | #### 总结: 82 | 83 | 1. 不要用E2E测试来代替UI测试。更好的做法是用单元测试 + 集成测试 + UI测试。 84 | 2. 使用封闭测试策略 85 | 3. 使用依赖注入 86 | 4. 把app分为不同的小组件小类库,并分别写测试,然后再写集成测试来确保各组件之间的交互正确。 87 | 5. 模块化 UI 测试已经被证明了比E2E测试快,并且十分稳定。这样的测试又能极大的提高开发效率。 88 | -------------------------------------------------------------------------------- /android-blog/readme.md: -------------------------------------------------------------------------------- 1 | ## Android官方博客 -------------------------------------------------------------------------------- /androidweekly/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/androidweekly/.DS_Store -------------------------------------------------------------------------------- /androidweekly/Android性能案例研究续集/readme.md: -------------------------------------------------------------------------------- 1 | # Android性能案例研究续集 2 | --- 3 | > * 原文链接 : [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) 4 | > * 译者 : [shenyansycn](https://github.com/shenyansycn) 5 | > * 校对 : [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: ![image](http://androidperformance.com/images/android-performance-case-study-follow-up/falconpro3.png) 6 | 图1-1 7 | Joaquim使用系统的GPU图形分析工具发现了帧速的下降。左边的截图展示了没有Viewpage的滚动性能时间轴,右边的展示了有Viewpager的时间轴(他使用2014年的Moto X获得这些数据)。这个问题根源看起来非常明显。 8 | 我的第一反应是ViewPager是不是误用了硬件加速。我们观察到的这个性能问题可能在List滚动时每一帧硬件层都被更新了。系统的 hardware layers updates debugging tool 没有提供有价值的信息。我检查了两次HierarchyViewer, 但ViewPager表现令我感到满意。(相反,我认定它不太可能出现问题) 9 | 我转而使用了另一个强大的、不常用的工具: Tracer for OpenGL。我前一篇文章解释了这个工具的更多细节。这个工具收集了所有你想知道的UI工具发送给GPU的绘画命令。 10 | Android4.3及以前:从Android4.3我们引进了reordering and merging of drawing commands后,Tracer变得难以使用。reordering and merging of drawing commands是一个令人惊讶的有用优化,但是他阻止了来自view的组绘画命令。使用如下命令(在启动你的应用前)你可以通过关闭显示list优化来恢复旧的行为。 11 | 分析OpenGl Traces:蓝色的命令显示屏幕上绘制像素的GL操作。其他所有被用于传输数据或者设置状态命令能很容易的被忽略掉。每次你点击蓝色的命令,Tracer会更新Detail选项卡,在你点击的被执行后立即显示当前渲染对象的内容。通过一个接一个的点击,你可以重建每一帧。这几乎就是我用Trace分析性能问题的流程。了解被渲染的一帧都提供了什么。 当仔细看收集到的traces时,我惊奇的发现了一些SaveLayer/ComposeLayer命令块,r如图1-2。 ![](http://androidperformance.com/images/android-performance-case-study-follow-up/glTrace.png) 12 | 图 1-2 这些块表明创建和合成了临时性的硬件层。这些临时硬件层被不同的Canvas.saveLayer()创建。当下面的特殊条件被满足时,UI 系统调用 Canvas.saveLayer()函数来绘制视图,并且alpha值小于1: 13 | * getAlpha() 返回一个小于1的值 * onSetAlpha() 返回 false * getLayerType() 返回LAYER_TYPE_NONE * hasOverlappingRendering() 返回 true 14 | 在一些演讲中,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版,因此多个临时硬件层的使用可能是引起这个性能问题的根源。 15 | 更重要的问题是:是什么创建了这些临时层?Tracer给了我们答案。如果你看到了Tracer的截屏,你能看到仅是一组OpenGL的SaveLayer命令在一个小的渲染对象循环调用了。现在让我们看下应用截图, 如图1-3: ![](http://androidperformance.com/images/android-performance-case-study-follow-up/before.png) 16 | 图1-3 17 | 你看到顶部的几个小圆点了么?这是ViewPager指示器,用于显示使用者的页面位置。Joaquim使用了一个第三方库来画这些指示器。当前页面的指示器是一个白色的圆点,其他页面的指示器是一个灰色的圆。我说“什么导致了它显示为灰色”,因为这些圆实际上是半透明的白色圆点。这个库中对于每一个圆都用了一个View(这本身就是浪费)并调用setAlpha()改变他们的颜色。 这里有一些修复这个问题的几个解决方案: * 使用一个可定制的颜色来代替在View上设置不透明度; * 使hasOverlappingRendering()返回false,然后系统会为你设置一个适当的alpha值到画笔上;(译者注 : 对于有重叠内容的View,系统简单粗暴的使用 offscreen buffer来协助处理。当告知系统该View无重叠内容时,系统会分别使用合适的alpha值绘制每一层。) * 使onSetAlpha()返回true,并设置Paint的alpha来绘画灰色的圆。 18 | 最合适的是第二种方法,但最低支持API level 16.如果你必须支持老版本,可以使用另外两个方法中的一个。我相信Joaquim会抛弃第三方库并使用他自己的指示器。 我希望这篇文章中市我们能够意识到性能问题很可能出现在看似无害的操作上。请记住:不要假设,实践出真理! -------------------------------------------------------------------------------- /androidweekly/Kotlin for Android (II)创建一个工程/readme.md: -------------------------------------------------------------------------------- 1 | Kotlin for Android (II)创建一个工程 2 | --- 3 | 4 | > 5 | * 原文链接 : [Kotlin for Android (II): Create a new project](http://antonioleiva.com/kotlin-android-create-project/) 6 | * 译者 : [Lollypo](https://github.com/Lollypo) 7 | * 校对者: [chaossss](https://github.com/chaossss) 8 | * 状态 : 完成 9 | 10 | 11 | 当我从[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)这篇文章 ) 12 | 13 | 对于本系列文章, 我将创建一个我早些时候创建的[Bandhook](https://play.google.com/store/apps/details?id=com.limecreativelabs.bandhook)的简化版本, 它基本上就是连接到一个基于RESTful的音乐API然后接收一些乐队的信息. 链接到 [Bandhook Kotlin on Github](https://github.com/antoniolg/Bandhook-Kotlin) 查看源代码. 14 | 15 | 16 | ###创建一个新项目然后下载Kotlin插件### 17 | 18 | 就像你平常做的那样,我们只需要用Android Studio创建一个带Activity的基本Android项目。 19 | 20 | 一旦完成,我们需要做的第一件事就是去下载Kotlin插件. 去到Android Studio的系统设置中然后查找plugins.之后,再次使用搜索找到Kotlin插件,安装并重启IDE。 21 | 22 | ![kotlin-plugin](http://7xi8kj.com1.z0.glb.clouddn.com/kotlin-plugin-e1424632570741.png) 23 | 24 | ###添加Kotlin插件的依赖到的应用程序的build.gradle中### 25 | 26 | 该项目的build.gradle需要添加一个新的依赖,这个依赖将会被Kotlin插件要求以在主Module中使用: 27 | ```gradle 28 | buildscript { 29 | repositories { 30 | jcenter() 31 | } 32 | dependencies { 33 | classpath 'com.android.tools.build:gradle:1.1.3' 34 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.11.91' 35 | } 36 | } 37 | ``` 38 | 39 | 40 | 41 | ###配置Module的build.grade### 42 | 43 | 首先, 应用Kotlin插件: 44 | ```gradle 45 | apply plugin: 'com.android.application' 46 | apply plugin: 'kotlin-android' 47 | ``` 48 | 接着, 添加Kotlin库到你的依赖: 49 | ```gradle 50 | dependencies { 51 | compile fileTree(dir: 'libs', include: ['*.jar']) 52 | compile 'org.jetbrains.kotlin:kotlin-stdlib:0.11.91' 53 | } 54 | ``` 55 | 最后, 你需要添加我们在下一个步骤创建的Kotlin文件夹: 56 | ```gradle 57 | android { 58 | compileSdkVersion 22 59 | buildToolsVersion "22.0.0" 60 | 61 | ... 62 | 63 | sourceSets { 64 | main.java.srcDirs += 'src/main/kotlin' 65 | } 66 | } 67 | ``` 68 | 或者,你可以跳过这一步,当做完下一个步骤时,使用这个Android Studio的操作: 69 | 70 | ![configure-kotlin-project](http://7xi8kj.com1.z0.glb.clouddn.com/configure-kotlin-project.png) 71 | 72 | 我更倾向于手动去做以保持我的Gradle文件有整洁有序, 但第二个选项可能较为容易些。 73 | 74 | 75 | 76 | ###创建Kotlin文件夹### 77 | 78 | 如果你将项目的视图从‘Android’转到‘Project’,那将会非常容易。依次选择‘app->src->main’ 然后创建一个名为 ‘kotlin'的文件夹: 79 | 80 | ![kotlin-folder](http://7xi8kj.com1.z0.glb.clouddn.com/kotlin-folder.png) 81 | 82 | 83 | 84 | ###将Java activity转换成Kotlin文件### 85 | 86 | Kotlin插件能将Java转换为Kotlin类. 我们可以轻松的通过‘Code’菜单中的‘Convert Java File to Kotlin File'选项转换当前的Activity到Kotlin类 : 87 | 88 | ![convert-java-to-kotlin](http://7xi8kj.com1.z0.glb.clouddn.com/convert-java-to-kotlin-e1424633562637.png) 89 | 90 | IDE将建议你移动新文件到Kotlin文件夹,点击‘Move File’(或者手动完成,假如你没看到这个选项). 91 | ```java 92 | public class MainActivity : ActionBarActivity() { 93 | 94 | override fun onCreate(savedInstanceState: Bundle?) { 95 | super.onCreate(savedInstanceState) 96 | setContentView(R.layout.activity_main) 97 | } 98 | 99 | 100 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 101 | // Inflate the menu; this adds items to the action bar if it is present. 102 | getMenuInflater().inflate(R.menu.menu_main, menu) 103 | return true 104 | } 105 | 106 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 107 | // Handle action bar item clicks here. The action bar will 108 | // automatically handle clicks on the Home/Up button, so long 109 | // as you specify a parent activity in AndroidManifest.xml. 110 | val id = item.getItemId() 111 | 112 | //noinspection SimplifiableIfStatement 113 | if (id == R.id.action_settings) { 114 | return true 115 | } 116 | 117 | return super.onOptionsItemSelected(item) 118 | } 119 | } 120 | ``` 121 | 122 | 123 | 124 | ###主要区别### 125 | 126 | 看一看之前的代码, 我们可以看到一些明显的差异。 其中很大一部分我们将会在下一篇文章讲解到: 127 | 128 | - 使用冒号,而不是'extends'。 129 | - 显式使用‘override': 在Java中, 我们可以使用一个注释使我们的代码更清晰,但它不是必要条件. Kotlin将迫使我们使用它. 130 | - 函数则使用‘fun’关键字: Kotlin是一个面向对象的函数式语言, 因此可能会与其他语言类似,例如Scala. Java方法被函数的形式表示。 131 | - 函数参数命名规则不同: 类型和名称都写在相反的位置,并用冒号隔开。 132 | - 分号可选: 我们不需要在行的结尾处加上分号。如果我们想要也可以加上, 但如果我们不这样做,它就可以节省大量的时间,并使我们的代码整洁。 133 | - 其他小细节: 在简介一文中, 我已经说到了 ‘?’ 的意义. 这表明参数可以为空。NULL的处理方式不同于Java。 134 | 135 | 136 | 137 | ###总结### 138 | 139 | 也许我们会认为使用一门新语言将会非常困难, Kotlin被JetBrains团队开发出来的,要成为最容易和可交互的语言用来覆盖那些Java的不足之处。由于Android Studio也是基于JetBrains的产品,这将让集成到这个IDE中并且开始工作非常简单。 140 | 141 | 下一篇文章将介绍一些让我们在使用Kotlin开发Android应用程序时,能让开发过程更简单的奇巧淫技。 142 | -------------------------------------------------------------------------------- /androidweekly/Kotlin for Android (III)-扩展函数与默认值/readme.md: -------------------------------------------------------------------------------- 1 | Kotlin for Android (III)/ 扩展函数与默认值 2 | --- 3 | 4 | > 5 | * 原文链接 : [Kotlin for Android (III): Extension functions and default values](http://antonioleiva.com/kotlin-android-extension-functions/) 6 | * 译者 : [Lollypo](https://github.com/Lollypo) 7 | * 校对者: [Mr.Simple](https://github.com/bboyfeiyu) 8 | * 状态 : 校对完成 9 | 10 | 现在你已经了解[Kotlin基础](http://antonioleiva.com/kotlin-for-android-introduction/)与[如何配置你的项目](http://antonioleiva.com/kotlin-android-create-project/),是时候谈论Kotlin能为我们做哪些Java做不到的有趣的事情了.请记住,如果你对Kotlin语言有任何疑问,可以参考[官方文档](http://kotlinlang.org/docs/reference/).这份文档条理分明、简单易懂, 而且我这篇文章不会涉及到语言的基础部分. 11 | 12 | 13 | 14 | ### 扩展函数 15 | 16 | Kotlin的扩展函数功能可以让我们添加新的函数到现有的类上而不必去修改它本身.例如,我们可以轻松的的通过扩展函数语法将一个显示Toast的函数添加到Activity类中: 17 | 18 | ```java 19 | fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){
 20 | Toast.makeText(this, message, duration)
 21 | } 22 | ``` 23 | 24 | 我们可以在任意的地方声明这个函数(例如一个工具类文件), 并在我们的Activities中当作一个普通方法使用: 25 | 26 | ```java 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState)
 29 | toast("This is onCreate!!")
 30 | } 31 | ``` 32 | 33 | 声明一个扩展函数跟添加类名到函数名上一样简单.这个函数将被作为导入的类使用. 34 | 35 | 这可以帮助我们简化代码而且让封闭的类打破局限.但是我们必须小心和适度的使用.最后,这些函数通常会替代工具类.工具方法通常是静态的且不能被修改. 因此,过度使用通常是表示我们懒得创建一个委托类. 36 | 37 | 这里是另一个有趣的例子,让我解释另一个有趣的概念:具体类型(reified types). 38 | 39 | ```java 40 | inline public fun Activity.navigate(id: String) {
 41 | val intent = Intent(this, javaClass()) 42 | 
 intent.putExtra("id", id) 43 | 
 startActivity(intent)
 44 | } 45 | ``` 46 | 47 | 内联函数可以使用具体类型,这意味着我们可以从内部函数取得类而不用通过将类类型作为参数. 48 | 49 | > 内联函数的有点不同于一般的函数.内联函数将会在编译过程中替换代码而不是真的调用一个函数. 这会简化某些场景.例如,如果我们将一个函数作为参数,常规函数将在内部创建一个包含该函数de对象,.另一方面,内联函数会在函数被调用的地方替换掉,因此它真的不需要一个内部对象. 50 | 51 | > ```java 52 | navigate("2") 53 | ``` 54 | 55 | 使用具体类型,我们可以在函数内部创建一个意图, 并使用扩展函数,我们可以直接调用startActivity(). 56 | 57 | 58 | 59 | ### 可选参数与默认值 60 | 61 | 62 | 由于有参数默认值和构造函数,你永远不需要重载函数了.一个声明可以满足你所有的需求.回到Toast示例: 63 | 64 | ```java 65 | fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){
 66 | Toast.makeText(this, message, duration)
 67 | } 68 | ``` 69 | 70 | 第二个参数指的是Toast的持续时长.它是可选参数,在没有指定的情况下,将使用Toast.LENGTH_SHORT作为默认值.现在你有两种方法调用这个函数: 71 | 72 | ```java 73 | toast("Short Toast!!") 74 | toast("Long Toast!!", Toast.LENGTH_LONG) 75 | ``` 76 | 77 | 对于第二个示例,我们也许想添加一些转换棒棒糖的参数: 78 | 79 | ```java 80 | inline public fun Activity.navigate( 81 | 
 id: String,
 82 | sharedView: View? = null, 83 | 
 transitionName: String? = null) {
 
 84 | ... 85 | 
} 86 | ``` 87 | 88 | 我们现在有两种不同的方式调用相同的函数: 89 | 90 | ```java 91 | navigate("2") 92 | navigate("2", sharedView, TRANSITION_NAME) 93 | ``` 94 | 95 | 甚至是第三种,虽然在大多数情况没什么意义,但是可以帮助我们理解另一种概念:我们可以使用参数名来决定我们想调用哪些参数: 96 | 97 | ```java 98 | navigate(id = "2", transitionName = TRANSITION_NAME) 99 | ``` 100 | 101 | 可选参数可以被用于默认构造函数,因此你可以通过一个声明实现许多的重载方法.自定义视图是一个特例, 因为在Java中它们需要多个构造函数才能够正常工作.我将会在下一篇文章讲解到. 102 | 103 | 104 | 105 | ## 总结 106 | 107 | 通过这两个优势,我们可以简化大量的代码甚至可以做那些Java中不可能的事情.Kotlin真的是简洁明了.下一篇文章将会讲解Kotlin的Android拓展,这可以让我们在Activities中自动注入视图,与如何在Kotlin中自定义View. 108 | 109 | 记得浏览一下[示例库](https://github.com/antoniolg/Bandhook-Kotlin)来看一下它的实际应用. -------------------------------------------------------------------------------- /androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-1/readme.md: -------------------------------------------------------------------------------- 1 | ListView或者RecycleView滚动时隐藏Toolbar (1) 2 | --- 3 | 4 | > 5 | * 原文链接 : [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/) 6 | * 译者 : [chaossss](https://github.com/chaossss) 7 | * 校对者: [这里校对者的github用户名](github链接) 8 | * 状态 : 校对中 9 | 10 | 11 | 今天我打算写一篇博文给大家介绍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) 。 12 | 13 | 14 | > 如果可以的话,我们希望当屏幕向下滚动时,App的Toolbar和FAB将离开屏幕,从而在垂直方向上为可显示区域腾出更大的空间来显示我们的内容。而当我们的屏幕向上滚动时,App的Toolbar和FAB会再次出现。 15 | 16 | 我们做出来的最终效果应该是下面这样的: 17 | 18 | ![](http://img.my.csdn.net/uploads/201503/27/1427447324_1070.gif) 19 | 20 | 在这篇博文的讲解中,我们将会用RecyclerView作为我们的list,但这不代表其他可滚动的容器就不能实现这样的效果了,只是其他的可滚动容器(如:ListView)需要要多花一些功夫才能实现这个效果。那么我们要怎么去实现呢?我想到了两个办法: 21 | 22 | - 对list容器加一个Padding 23 | - 对list容器加一个Header 24 | 25 | 就我个人而言,我更想用第二种方法去实现,因为在设计代码的过程中我发现:为RecyclerView添加Header会产生几个问题,这给了我很好的机会去思考如何解决它,与此同时,在这个思考和解决问题的过程中我还能学习到更多的知识,何乐而不为呢?不过我还是会给大家简要地介绍如何使用第一种方法实现的啦! 26 | 27 | ## 那就让我们开始今天的讲解吧! 28 | 29 | 首先,我们需要创建一个工程和添加必要的工具库: 30 | 31 | 接着我们需要定义 styles.xml ,以确保我们的App没有添加Actionbar(因为我们将会使用Toolbar),同时我们App的设计风格要符合Google的 Material Design 规范。 32 | 33 | 最后我们就要创建我们activity中显示的布局: 34 | 35 | 事实上,我们只需要一个简单的布局,其中包含了:RecyclerView,Toolbar和ImageButton。但需要注意的是:我们需要把它们放在一个FrameLayout里,否则当我们隐藏Toolbar时list的上方将会出现一个空白区域,这显然不会是我们想要的效果。我们理想的效果应该是:当Toolbar被隐藏,list能在屏幕的可见区域中显示出一整个列表,而这就需要通过使Toolbar覆盖在RecycleView上面来实现。 36 | 37 | 接着来看看我们MainActivity代码吧: 38 | 39 | 就像你看到的那样,这是一个只重写了OnCreate()方法,非常非常非常简单的Activity。而且OnCreate()方法也只做了下面三件事情: 40 | 41 | 1. 初始化Toolbar 42 | 43 | 1. 获取mFabButton的引用(mFabButton是FAB的对象哦,也就是屏幕右下方的小按钮) 44 | 45 | 1. 初始化RecyclerView 46 | 47 | 为了在list中显示出内容,我们现在就要为RecyclerView创建一个Adapter啦。但在此之前,我们应该为我们list中的item添加相应的子布局以及对应的ViewHolder: 48 | 49 | list的每一个item只需要一个text用来显示文字,非常简单! 50 | 51 | 那RecyclerAdapter该怎么实现呢: 52 | 53 | 就像你看到的这样,这是一个非常普通的RecycleView.Adapter,没有任何特别的地方,是不是感觉被骗了呐 23333~(如果你想要了解更多有关RecyclerView知识,我强烈建议你去看大牛 Mark Allison's 巨巨写的这些优秀文章 [series of posts](https://blog.stylingandroid.com/material-part-4/)) 54 | 55 | 经过上面的努力,我们已经把车子的小零件组装的七七八八啦,是时候让小车子上路跑一跑,展现真正的技术了! 56 | 57 | ![](http://img.my.csdn.net/uploads/201503/27/1427447324_5421.png) 58 | 59 | WTF,谁能告诉我这是什么鬼……? 60 | 61 | 我相信只要不是瞎子都会发现,App的Toolbar竟然把我们的item挡住了!!!或许部分机智的小伙伴已经发现了问题所在:出现这样的问题是因为我们在activity_main.xml里使用了FrameLayout,正是FrameLayout导致了这样的问题,而这就是我开头所提到的问题之一了。 62 | 63 | 第一个解决办法是为我们的RecyclerView添加一个PaddingTop,并将PaddingTop的值设置为Toolbar的高度。但有一个细节我们不能忽略,那就是RecyclerView会默认裁剪到子View的Padding区域,所以为了我们伟大的事业,我们必须把这个特性关掉。 64 | 65 | 经过这些修改之后,我们就能实现我们想要的效果,这就是我所说的第一种方法。但如我所说,我写这篇博文的目的,不仅仅只是教你实现这个效果,然后就完了。我想教给你实现同一个效果各种各样的方法,并且为你介绍其中的思想,让你接触到平常很难接触到的问题并教你如何解决它。有些方法固然会更加复杂(在本文中是为list添加一个Header),但你在实现过程中也能学到更多的知识,毕竟授人以鱼不如授人以渔嘛。 66 | 67 | ## 为RecycleView添加一个Header 68 | 69 | 70 | 要用第二种方法去实现这个效果,首先我们要做的就是稍微修改一下我们的Adapter: 71 | 72 | 下面是其实现原理: 73 | 74 | 我们需要定义一个常量来区分Recycler展现的item的类型。这里我需要为你介绍的是:RecyclerView是一个非常灵活的组件,RecyclerView 完全能实现你想要让list的item具有各种各样不同的布局的愿望,而此时,我们定义来区分item类型的常量就会被利用到。而这样的特性正是我们想要的——让Header成为RecyclerView的第一个item,显然这会与其余的item不一样。(第3-4行) 75 | 76 | 因而我们需要让Recycler知道它需要展示的子布局是什么类型的,在本文中我们用作类型区分的常量则是TYPE_ITEM和TYPE_HEADER。(第49-54行) 77 | 78 | 接着,我们需要修改onCreateViewHolder()和onBindViewHolder()方法,如果item的类型是TYPE_ITEM的话,使它们返回和绑定一个普通的item;如果item的类型是TYPE_HEADER的话,则返回Header。(第14-34行) 79 | 80 | 此外,由于我们的list并不仅仅只有普通的item,我们还在list中添加了Header,因此我们需要修改getItemCount()方法的返回值,让我们的返回值是普通item的总数量 + 1(第43-45行) 81 | 82 | 最后让我们来为Header布局创建一个layout和一个ViewHolder,但出乎意料的是,我们需要为Header创建的layout和ViewHolder都非常简单,唯一需要注意的是:Header的高度必须和Toolbar的高度一致。 83 | 84 | 那么这样我们就把布局弄好啦~不信你看图! 85 | 86 | ![](http://img.my.csdn.net/uploads/201503/27/1427447325_8379.png) 87 | 88 | 所以总的来说,我们为RecyclerView添加了一个和Toolbar有相同高度的Header,而现在我们的Toolbar把header隐藏起来了(因为header现在是一个空的view),同时,我们所有的普通item都是可见的。那么现在就让我们来实现滚动时改变Toolbar和FAB的出现和隐藏吧! 89 | 90 | ## 滚动时控制Toolbar和FAB的出现和隐藏 91 | 92 | 93 | 为了实现这个效果,我们为RecyclerView再创建一个——OnScrollListener类就够了你敢信? 94 | 95 | 我现在还要告诉你,在OnScrollListener类里,我们只需要重载onScrolled()方法就能让这个酷炫的效果成为App中秒杀用户的黑魔法!其中,onScrolled()方法的参数——dx,dy分别是水平和垂直方向上的滚动距离。但大家需要注意的是:这里dx,dy并不是代表屏幕上的物理距离,而是两个事件的相对距离。 96 | 97 | 具体的实现算法大体如下: 98 | 99 | 只有当list向上滚动且Toolbar和FAB被隐藏,抑或是当list向下滚动且Toolbar和FAB出现,我们才会计算总的滚动距离,因为这两种情况下的滚动距离才是我们实现这个效果所需要关心的。 100 | 101 | 总的滚动距离需要超过我们展现/隐藏Toolbar和FAB所在的方向的极限值才能将其展现/隐藏(你把极限值调整的越大,通过滚动展示/隐藏Toolbar和FAB需要的距离就越大)(dy>0意味着我们在向下滚动,dy<0意味着我们在向上滑动) 102 | 103 | 实际上我们并没有在我们的滚动监听类里面展现/隐藏Toolbar和FAB,我们是通过调用show()/hide()方法来展现/隐藏Toolbar和FAB的,所以调用者可以通过接口实现它。 104 | 105 | 现在我们需要为RecyclerView添加它的监听: 106 | 107 | 下面是我们通过动画改变我们的视图的方法: 108 | 109 | 当我们隐藏Toolbar和FAB的时候,我们必须把Padding也考虑进去,不然的话视图并不能够完全被隐藏。 110 | 111 | 是骡子是马,让我们拉出来溜一溜! 112 | 113 | ![](http://img.my.csdn.net/uploads/201503/27/1427447325_8449.gif) 114 | 115 | 虽然现在看起来已经很nice了,但其实这里有一个小小的bug——如果你在list的顶部,此时临界值非常小,因而你能隐藏Toolbar,但你在list的顶部会看到有一个空白的区域。不过幸好这里有一个很简单的方法可以解决这个bug:我们可以通过检测当前list的第一个item是否为可见的,只有当它不可见,才使用我们设计的展示/隐藏逻辑。 116 | 117 | 在这样的修改后,即使第一个item是可见的并且有item被它挡住了,我们也在展示它们,除此以外的情况我们都像之前说的那样实现我们的效果。 118 | 119 | 各位观众,接下来,就是见证奇迹的时刻: 120 | 121 | ![](http://img.my.csdn.net/uploads/201503/27/1427447326_5458.gif) 122 | 123 | 太棒了思密达!感觉之前的失败都如雨后甘霖温润我脆弱的心灵呐~ 124 | 125 | 其实羞羞地说一句……这篇文章是我人生中的第一篇博文呐,如果你觉得很无聊或者我有哪里讲解错误的话千万不要喷我哦。我会在未来变得更棒哒,然后尽我所能为大家贡献更多的文章! 126 | 127 | 如果你看到这里还是通过添加Header来实现这个效果很恼火的话,用第一种方法结合HidingScrolllistener 也是可以实现这个效果的~ 128 | 129 | 如果你有什么疑问的话,可以在评论区问我哦,我都会尽我所能为你解答的! 130 | 131 | ## 源码 132 | 133 | 整个项目的源码在GitHub上面都有,大家可以在这看 [repo](https://github.com/mzgreen/HideOnScrollExample) 134 | 135 | 感谢Mirek Stanek帮我校对文章,么么哒!爱你的好基友Michal Z~ 136 | 137 | 如果你喜欢这篇博文的话,你可以[在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)! 138 | -------------------------------------------------------------------------------- /androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-2/images/playstore.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-2/images/playstore.gif -------------------------------------------------------------------------------- /androidweekly/ListView或者RecycleView滚动时隐藏Toolbar-part-2/readme.md: -------------------------------------------------------------------------------- 1 | ListView或者RecycleView滚动时隐藏Toolbar( Part 2 ) 2 | --- 3 | 4 | > 5 | * 原文链接 : [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)/) 6 | * 译者 : [chaossss](https://github.com/chaossss) 7 | * 校对者: [这里校对者的github用户名](github链接) 8 | * 状态 : 校对中 9 | 10 | 11 | 12 | 13 | 14 | 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那样,废话不多说,我们现在进入正题吧: 15 | 16 | 在我们开始之前,我想先告诉大家的是我已经对这个项目进行了一些重构——我继承项目的 MainActivity 实现了两个新的子类:PartOneActivity 和 PartTwoActivity。源码在包 partone 和 parttwo里,你可以在这两个包里挑选你喜欢的那一个使用 17 | 18 | 下面是我们的最终效果图,我把它和 Google Play Store Toolbar 放在一起比较,大家可以感受一下: 19 | 20 | ![](http://img.my.csdn.net/uploads/201503/27/1427447727_3335.gif) 21 | ![](images/playstore.gif) 22 | 23 | ## 准备工作 ## 24 | 25 | 在这里我不会再给大家展示 build.gradle 文件,因为这和第一部分的 build.gradle 文件是一样的,所以我们将会从这个步骤开始——为我们的Activity创建一个layout: 26 | 27 | 同样的,layout里面只有一个 RecyclerView 和一个 Toolbar (稍后再加上 Tabs)。值得注意的是,我这里的实现是使用之前说的第二种方法(为RecyclerView添加Padding) 28 | 29 | 同样的道理,由于我们的布局文件、list、RecyclerAdapter 都和之前是一样的,我在这里都不会再给大家讲解了。 30 | 31 | 那现在我们来看看 PartTwoAcitivty 的代码吧: 32 | 33 | 在 PartTwoActivty 里面仍然是简单地对 RecyclerView 和 Toolbar 进行初始化,但大家一定要记得设置 OnScrollListener 哦(第27行) 34 | 35 | 感觉大家看到这里也感觉昏昏欲睡了,因为前面提到的内容大体上都和上一篇相似。但是莫慌!俺接下来就要讲这篇博文中最有趣的部分 —— HidingScrollListener 了,请大家紧紧抱住我,跟上节奏! 36 | 37 | 如果你看过第一篇博文可能会觉得此情此景很熟悉(可能还会感觉简单一些)。我们在 HidingScrollListener 里耍了什么 tricks 呢?那就是存了与 Toolbar 的高度相关联的屏幕滚动偏移量 —— mToolbarOffset。为了简化其中的逻辑,只有当 mToolbarOffset 的取值在[0 , Toolbar的高度]之间时,我们才会实现我们的逻辑:当我们向上滚动时,mToolbarOffset的值会增加(但不会超过Toolbar的高度);当我们向下滚动时,mToolbarOffset 的值会减少(但不会低于0)。大家现在可能会有许多疑问:为什么要引用 mToolbarOffset 呀?为什么要让 mToolbarOffset 的取值范围介于那两者之间呐?别怕!你马上就会理解为什么我们要这样限制mToolbarOffset的取值了。我们必须知道的是,尽管我们极力去避免意外的发生,但现实总会出人意料,在这里也不例外,有时候 mToolbarOffset 的值就是会不可避免地在我们的取值范围之外,但由于我们的逻辑设计的限制,最终的显示效果会是闪烁一下。(例如:在屏幕上快速的挥动、滑动)这样的结果显然不是我们这些有格调的 Android 工程师想要的,因而我们需要对 mToolbarOffset 进行一定程度的裁剪,以规避这样的风险。基于这样的考虑,我们重载了 onMoved()方法 —— onMoved() 方法是一个当我们滚动视图时被调用的抽象方法。可能会吓到你,但是莫慌,继续抱住我! 38 | 39 | 接下来,我们就要回到我们 PartTwoActivity 设计之中,并且在我们的滚动监听器中实现 onMoved()方法。 40 | 41 | 是的,这就是所有内容啦。我们运行 App 后可以看到我们的最终效果: 42 | 43 | ![](http://img.my.csdn.net/uploads/201503/27/1427447725_3945.gif) 44 | 45 | 是不是感觉自己棒棒哒~就像我们想象的那样,Toolbar 完美的随着list的滚动实现了展现/隐藏的效果。其中的功劳都得归功于我们对 mToolbarOffset 取值范围的限制。如果我们省略掉检查 mToolbarOffset 是否在[0 , Toolbar的高度]范围中取值的过程,带着完成控件的喜悦向上滚动我们的list,Toolbar 确实会如你所期望的那样离开屏幕,但与此同时,Toolbar 还会远远地,远远地,离开视图,再也不回来。然后当你满是期许地向下滚动时,你就会发现刚刚那一声再见,竟是永远。如果你想再次遇见它,你就必须想下滚动,直到 mToolbarOffset = 0。 46 | 47 | 再脑补第二种情况吧,现在你正好把 Toolbar 滚动到 mToolbarHeight = mToolbarOffset 的位置,不偏不倚。那么现在Toolbar就刚好“坐”在了list顶部,如果你向上滚动的话,无论你怎么滚,它都不会动,只会静静地坐在那儿笑看人世沧桑。而如果你向下滚动,它又成为许多年前那个明亮、可爱的小女孩了。 48 | 49 | 虽然最终的实现效果看起来非常赞,但这并不是我想要的。因为我们能在滚动过程中停止整个效果,使得 Toolbar 有一部分是可见的,另一部分又是不可见的。但悲伤的是,Google Play Games 就是这么干的,而我一直认为这是一个Bug…… 50 | 51 | ## 在某一点停下 Toolbar ## 52 | 53 | 就我的认知来说,我认为滚动的Views是能够如丝般顺滑地对齐相应的位置的,就像 Chrome 应用里的 Logo/SearchBar 又或者是 Google Play Store应用里那样。我很确定我在 Material Design 的guidelines/checklist 或者是 以前听过的Google I/O 大会上听过类似的规范。 54 | 55 | 那我们现在再来看看 HidingScrollListener 的代码吧: 56 | 57 | 虽然为了实现上面提到的效果我们会让 HidingScrollListener 的代码变得更复杂一些,但是我再说一次,莫慌,抱紧我!我们现在只需要重载 RecyclerView.onScrollListener 类的 onScrollStateChanged()方法,然后按照下面那样干就行了: 58 | 59 | 首先,我们需要检查list是否处于 RecyclerView.SCROLL_STATE_IDLE 状态,以确保list没有在滚动或者挥动(因为如果list如果正在滚动或者挥动的时候,我们就需要像第一篇博文那样去考虑 Toolbar 的Y方向位置哦) 60 | 61 | 如果我们抬起了手指,而且list已经停止移动了()我们就要去检查Toolbar是不是可见的,如果是可见的,我们就需要考虑 mToolbarOffset 的值了。如果此时 mToolbarOffset 的值大于 HIDE_THRESHOLD 我们就必须把 Toolbar 隐藏起来;mToolbarOffset 的值小于 HIDE_THRESHOLD,我们则需要让 Toolbar 显示出来。 62 | 63 | 如果 Toolbar 不是可见的,我们就需要做相反的事情 —— 如果 此时mToolbarOffset(此时计算 mToolbarOffset要从顶部位置来考虑了,也就是 mToolbarHeight - mToolbarOffset) 大于 SHOW_THRESHOLD 我们就让 Toolbar显示;相反,如果 mToolbarOfffset 小于 SHOW_THRESHOLD ,我们就要再次将 Toolbar隐藏起来。 64 | 65 | onScrolled()方法和第一篇博文的一样,我们并不需要作什么改变。我们现在最后需要做的就是在 PartTwoActivity 类里实现我们的两个抽象方法: 66 | 67 | 那么,你准备好看魔术了吗? 68 | 69 | ![](http://img.my.csdn.net/uploads/201503/27/1427448161_2643.gif) 70 | 71 | hey,派大星你看,是不是很酷! 72 | 73 | 现在你可能在脑补添加 Tabs 会有多麻烦了,可是兄弟啊,生活很多时候都是出人意料的呐,不信你继续往下看呀 74 | 75 | ## 添加 Tabs ## 76 | 77 | 为了添加 Tabs,首先要做的当然是为我们的 Activity 布局添加一个 tabs.xml啦 78 | 79 | 你可以从源码那发现,我并没有添加真正的 Tabs,只是在布局里面模拟了 Tabs。而以上的一切不会改变任何之前的实现,你能在里面放任何你想要放的View。下面是一些 GitHub 上符合 Material Design规范的 Tabs 实现,当然你也可以自己实现啦。 80 | 81 | 添加 Tabs 意味这他们会稍微覆盖我们的list,所以我们需要增加Padding。为了减少代码的操作复杂度,我们不会在 xml 里进行这个操作(注意把 RecyclerView 在 part_tuo_activity里的padding删掉哦),因为 Toolbar 可能会在不同的设备中切换方向时拥有不同的高度(例如在平板中),这样的话我们需要创建一大堆的 xml 去解决这些乱七八糟的烦人问题。所以我决定在代码中解决这个问题:这是非常简单的,我们只需要和 Tabs高度的总和。如果我们把 Padding设置为 Toolbar 的高度现在运行起来的话,就会发现这样的东西: 82 | 83 | ![](http://img.my.csdn.net/uploads/201503/27/1427448162_3352.png) 84 | 85 | 看起来很正常的样子……我们第一个item刚刚好是可见的,我们也能移动跟随着它。实际上我们在 HidingScrollListener 类里什么也没干,唯一需要的改变都是在 PartTwoActivity 里做的: 86 | 87 | 你能发现什么发生了改变吗?我们现在不妨创建一个 mToolbarContainer 的引用,但是大家要注意哦, mToolbarContainer 是 LinearLayout 对象而不是 Toolbar对象,而且在 onMove(),onHide(),和 onShow()方法中,我们都把 Toolbar 改成了 mToolbarContainer。这会使得包含了 Toolbar 和 Tabs 的Container被移动,这恰恰就是我们想要做的。 88 | 89 | 如果我们把修改后的代码运行起来会发现,实际的运行效果正好就是我们所期望的,但如果你看的认真一些你会发现,里面其实有一个小Bug。在 Tabs 和 Toolbar 之间有时候会有一条白线,虽然时间非常短,但还是很惹人讨厌呐。我个人觉得这大概是因为当Toolbar 和 Tabs被显示的时候,他们并没有像我们期望的那样同步在一起。不过万幸这不是什么无法解决的Bug~ 90 | 91 | 解决办法非常简单,就是让 Toolbar 和 Tabs 的背景和父布局保持一致: 92 | 93 | 现在即使 mToolbarContainer 在显示过程中没有很好的同步在一起,白线也不会出现了。正当我打算吃根辣条庆祝我们伟大战役的胜利的时候,又出现了一个Bug………………这个Bug和我们在第一篇博文里遇到的Bug是一样的,如果我们在list的顶部,我们可以向上滚动一丢丢,如果此时 HIDE_THRESHOLD 足够小,Toolbar 就会藏起来,导致那里有一块空白区域(其实就是我们设置的Padding)在list的顶部,但是我相信你到了现在应该不会慌了,因为你已经知道所有Bug在我眼里都是非常容易解决的: 94 | 95 | 我们只需要再增加一个变量用于存储list的总滚动偏移量,当我们准备去检查我们是否应该展现/隐藏 Toolbar的时候,我们首先应该检查我们的滚动距离是否比 Toolbar 的高度要大(如果不是的话,我们再让 Toolbar 出现) 96 | 97 | 这就是今天博文要讲的一切了,让我们来看一看实际效果! 98 | 99 | ![](http://img.my.csdn.net/uploads/201503/27/1427447727_3335.gif) 100 | 101 | 现在运行的效果简直完美啊大兄弟~即使用其他的 LayoutManagers 也不需要改变任何东西的哦: 102 | 103 | ![](http://img.my.csdn.net/uploads/201503/27/1427448162_7185.png) 104 | 105 | 评论区有好学的同学问了个有关存储滚动状态的问题,这确实是个小麻烦。如果我们list的item中的文字在垂直方向达到2行,在水平方向达到1行的话,我们的item高度就会变得很奇怪了……举个例子吧,如果我们滚动到垂直方向100的位置,然后旋转我们的设备,同时存储 mTotalScrolledDistance的值,在旋转之后,我们会滚动到list的顶部,然后我们会发现 mTotalscrolleddistance 的值不等于0。这个时候即使全能如我也想不到简单的办法来解决这个问题了,但是在我们的使用场景中,这样的小问题并不会有什么影响。如果你真的想要解决这个问题的话,我个人的做法是:在旋转之后把 mTotalScrolledDistance 的值重设为0 并且显示 Toolbar。 106 | 107 | 感觉今天写了好多内容啊,大家看到这里应该感觉很累了吧?不过今天这篇博文就是这个系列的最后一篇文章啦,大家能在第一篇博文中学习到知识我真的很高兴呢。大家鼓励和夸奖的话也让我很感动,我会继续写我的博客,为大家传授更多的知识,不过我也不知道下一篇博文会在什么时候写 2333。 108 | 109 | 除此以外我还想说的是,在这两篇博文中我提到的方法可能看起来运行的很好,但其实我并没有进行非常严谨的测试,所以我也不确定它们能不能被用于企业级应用中(你看我们不就遇到了好几个Bug了嘛)。这个系列的博文的初衷只是想告诉你,即使只使用标准API里面的一两个简单方法,也能够实现酷炫的效果。同时,我在写博文的过程中也发现了这些方法还有其他有趣的用法(例如:利用视差背景制作有粘性的 Tabs,就像在 Google+ 个人页面那样)。不管怎样,祝大家在写代码的过程中找到更多的快乐! 110 | 111 | ## 源码 ## 112 | 113 | 整个项目的源码都已经被上传到 [GitHub](https://github.com/mzgreen/HideOnScrollExample) ,大家可以去下载和使用哦,爱你们的 Michal Z。 114 | 115 | 如果你喜欢这篇博文的话,你可以 [在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) 。 -------------------------------------------------------------------------------- /androidweekly/Square 开源库Flow和Mortar的介绍/readme.md: -------------------------------------------------------------------------------- 1 | Flow和Mortar的调查 2 | --- 3 | > 4 | * 原文链接 : [Architecting An Investigation into Flow and Mortar](http://www.bignerdranch.com/blog/an-investigation-into-flow-and-mortar/) 5 | * 译者 : [sundroid](https://github.com/sundroid)( [chaossss](https://github.com/chaossss) 协同翻译) 6 | * 校对者: [chaossss](https://github.com/chaossss)、[Mr.Simple](https://github.com/bboyfeiyu) 7 | * 状态 : 完成 8 | 9 | “在 App 开发过程中尽可能使用 Fragment 替代 Activity”,Google 官方的这个建议无疑让万千 Android 开发者开始关注、使用 Fragment。但随着使用 Fragment 的人数增多,Fragment 存在的各种问题也开始暴露,在各种 Android 社区中,已经开始有人质疑用 Fragment 替代 Activity 在应用开发中是否真的像 Google 说的那样有益。质疑 Fragment 的理由大体如下: 10 | 11 | - 在使用 Fragment 时,我们只能选择使用默认的构造方法,而不能自由地构造我们想要的构造方法。 12 | 13 | - 嵌套使用 Fragment 很容易出现各种奇奇怪怪的 Bug,抑或是受到种种让人郁闷的限制。 14 | 15 | - Fragment 自身的生命周期非常复杂。 16 | 17 | 更让人哭笑不得的是,让这部分开发者坚定地站在“反 Fragment”队伍中的原因竟然是:在开发过程中使用 Fragment 完全不能让这部分 Android 开发者感受到使用 Fragment 能给他们带来的便利和愉悦;相反,使用 Fragment 给他们带来的是无尽的困然和烦恼。真不知道 Google 看到这些批评 Fragment 的帖子会想什么………… 18 | 19 | 但在我们的 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。 20 | 21 | 然而,虽然我们是 Fragment 的忠实粉丝,但本着不断学习和探索新知识的心态,我们还是对现有的 Android 库进行了相当多的研究和探索,以求能够找到 Fragment 的最佳替代物,帮助这些备受煎熬的 Android 开发者早日脱离苦海,走向 Android 开发的美丽新世界。 22 | 23 | ## 进入Flow和Mortar 24 | 奉行着想毁灭世界上所有 Fragment 的信条,Square 大概在一年前介绍了两个全新的库: Flow 和 Mortar。作为反 Fragment 教主,Square 还创造了许多很好的库: 25 | 26 | * [Dagger](http://square.github.io/dagger/) 27 | * [Retrofit](http://square.github.io/retrofit/) 28 | * [Picasso](http://square.github.io/picasso/) 29 | * [Otto](http://square.github.io/otto/) 30 | * And so many [more](https://github.com/square) 31 | 32 | 我只想说,我相信他们,我认为任何来自Square的资源可能是有用的或者至少是有趣的,所以他们的项目都值得一看。 33 | 34 | 在我们深入了解这些库之前我想提醒大家的是,Square只在他们内部的一小部分项目中使用这些库,并且我在写本文章时这些库还在预发布阶段。也就是说,这两个库在最近几个月取得了积极的进展,这预示着一个值得尊敬的未来,虽然库就像流沙,随时可能改变,崩溃甚至停止发布,但库所依赖的核心架构原则是一成不变的。 35 | 36 | ##体系架构 37 | 首先,我们先来看下 Android 应用的体系架构,在 Android Honeycomb 被使用之前,甚至在Fragment 出现之前,开发 Android 应用的标准模式是创建许多 Activity。在那个时候最常见的现象是:大多数开发者都没有规范地遵循 MVC模式进行开发,不过这也很正常。因为模型(Model)依赖于数据,传统的一些数据库或者是以 JSON 的形式存储的 Web 请求,抑或是各种各样的java对象枚举。开发者们很高兴地通过 XML 布局去为 Activity 设置 View ,而且 View 的控制器就是每一个屏幕显示的 Actvitiy 自身。 38 | 39 | 虽然这只是一个简要的概述,但是你能从中了解到 Android 与 MVC 模式是如何自然契合的。 40 | ![mvc-pre](http://www.bignerdranch.com/img/blog/2015/02/mvc-pre.png) 41 | 42 | 随着 Fragment 在 Honeycomb 中的出现,Android 团队也让处理不同形式的事件变得更简单。到了今天,标准的 Android 应用架构已经转变为由一小部分的 Activity 和 许多 Fragment 构成,使得我们的 App 能够在 手机、平板、智能手表甚至是太空船上跨平台使用。 43 | ![mvc-post](http://www.bignerdranch.com/img/blog/2015/02/mvc-post.png) 44 | 45 | 在这样的愿景下,有关 Fragment 的一切都是美好的,Fragment 变得流行起来,将一个 Activity 分解为几个 Fragment 是被提倡的。除此以外,即使 Activity 常常被简化为 Fragment 的持有者,或者是 Fragment 和 系统之间的接口,Android 应用的架构仍然遵循着 MVC 模式。 46 | 47 | 但到底是 Activity 不能实现我们 App 跨平台使用的愿望,还是我们没有用正确的方式使用 Activity呢?也许,如果将 Activity 与 自定义 View结合在一起使用,说不定不需要 Fragment 就能让 Activity 实现跨平台使用的目标呢。使用 Flow 和 Mortar库背后的关键目的就是探索这个问题,并得到确切的答案。Flow 和 Mortar 的工作通过用自定义 View 代替 Fragment,并使用注入自定义 View 中的特定 Controller 对象,控制视图,以此允许我们通过 View 来代替 Fragment 完成工作。 48 | ![mvc-no-fragments](http://www.bignerdranch.com/img/blog/2015/02/mvc-no-fragments.png) 49 | 50 | 我们将在我们的讨论中构建这个图的中间部分,弄清楚如何在不使用 Fragment 的情况下把不同的视图碎片拼凑到一个 Activity 里。我们会看着标准MVC架构演变成完全不同的东西,这将大量涉及到咱们的Flow和Mortar。 51 | 52 | 那么,Flow和Mortar到底是什么?它们又是如何起作用的呢? 53 | 54 | ##Flow 55 | Flow 将一个应用分成一个逻辑上的 Screen组合,Screen不是任何形式的特殊的库对象,而是一个被创造来代表我们应用视图的普通java对象(POJO)。每一个Screen是这个app里面自包含的段,他们有自己的功能和意图。一个Screen的用处和传统Activity的用处没有什么不同,应用程序中的每一个Screen都对应于一个特定的位置,有点像一个Android中的URL网页或者是特定的隐式Intent。所以,Screen类可以被看作是应用中某个部分自带的可读定义。 56 | 57 | 我们应用中的每一个Activity将会成为一个 Flow 对象,Flow对象在返回栈中保存了 Screen 的记录,和 Activity 或者 FragmentManager 的返回栈有些类似,通过这样的设计允许我们在 Screen 之间通过简单地实例化就可以轻松的切换,而不需要在应用中包含很多Activity。这里有一小部分 Activity(最好是一个)来持有这些 Screen。他们之间的关系下图类似: 58 | ![screen](http://www.bignerdranch.com/img/blog/2015/02/screen.png) 59 | 60 | 如果我们想切换到一个新的 Screen,我们只需简单地实例化这个 Screen,并且告诉我们 Flow 对象帮助我们切换为这个 Screen。除此以外,正如我们所期待的,Flow 被实例化后也会实现 goBack() 和 goUp() 方法。然而,许多开发者都把 Java 中的 goto 语句看作洪水猛兽,但事实上 Java 中的 goto 语句并没有它听起来那么恐怖。 61 | ![flow](http://www.bignerdranch.com/img/blog/2015/02/flow.png) 62 | 63 | 从本质上看,Flow 的作用仅仅是在 App 中告诉我们将要切换到哪一个 Screen。而这样设计的好处在于,Flow 通过这样的设计让我们能够方便地在我们定义的各种不同的自定义 View 中切换,并使我们免受在 Activity 或 Fragment 需要考虑的种种麻烦,让我们把注意力都集中在处理 View上。Flow 为我们创造了一个简单,方便,以 View 为中心的应用架构。 64 | 65 | ##Mortar 66 | Mortar是一个专注拖拽和依赖注入的库,Mortar 用以下几个不同的部分将一个应用分为可组合的模块:Blueprints, Presenters and a boatload of custom Views。 67 | 68 | Mortar App里的每一个部分(在这里指的是每一个 Screen,因为我们在使用 Flow)都由 Blueprint 定义,并赋予他们一个私有的 Dagger 模块。它看起来有点像是下面这样的 69 | 70 | ![blueprint](http://www.bignerdranch.com/img/blog/2015/02/blueprint.png) 71 | 72 | Flow 和 Mortar 结合在一起使用的效果很好,我们只需要调节我们的 Screen 类实现去 Mortar 提供的 Blueprint 接口,然后它就会给我们一个可以自由使用的 Dagger 作用域。 73 | 74 | ![presenters](http://www.bignerdranch.com/img/blog/2015/02/presenters.png) 75 | 76 | Presenter 是一个拥有简单生命周期和伴随其生命周期的 Bundle 的 View 私有对象,主要被用作该 View 的控制器。每一个 View 都有存在于对应的 Screen (还有 Blueprint)中,与 View 自身相关联的 Presenter。因为 Presenter 只能作用于他所在的 Screen,所以当我们使用 Flow 进入一个新的 Screen,Presenter(在我们这个架构中非常重要的一环) 很可能会被 Java 的垃圾回收机制自动回收掉。此外,在 Mortar 作用域中的 Dagger 将与自动垃圾回收机制结合在一起,使得我们 App 能更好的管理、使用其内存——其中原因当然是:当前没有被使用的控制器对象都被我们回收掉了。而在传统的 Activity 开发中,Fragment 和 Activity 的切换过程中,不经意的垃圾回收并不能很好的被注意和提防。 77 | 78 | 由于自定义 View 在我们的架构中被频繁地使用,以至于我们只需要通过 Dagger 简单地注入所有重要的模型数据,然后使用与 View 关联的 Presenter 去控制 View 本身。即使配置被改变,Presenters 也不会消失,而且我们还非常了解与 Activity 生命周期相关的知识,使得 Presenters 在进程被杀死之后还能被恢复。事实上,Presenter 与 Activity onSavedInstanceState() 方法的 bundle 钩连在一起,使得它能够用与 Activity 相同的机制储存和读取配置改变后产生的数据。而 Presenter 的生命周期非常简单,只有四个回调方法: 79 | 80 | - onEnterScope(MortarScope scope) 81 | - onLoad(Bundle savedInstanceState) 82 | - onSave(Bundle outState) 83 | - onExitScope() 84 | 85 | 完全没有 Fragment 那样复杂的生命周期,这可不是我吹的! 86 | 87 | 文章写到这里,你会发现在 Flow 和 Mortar 中有许多发生改变的部分,新的术语和类,还有新的使用规范,这难免会让人一头雾水。所以为了方便大家的理解,总的来说,我们需要重视的是下面几个部分: 88 | 89 | - Screen: 在应用导航层次结构中的一个特殊存在,用来代表我们视图的对象 90 | - Blueprint: 应用中具有私有的 Dagger 模块的部分 91 | - Presenter: 一个 View 控制器对象 92 | - Custom Views: 通过 Java 代码定义的 View,当然,用 XML 定义也是很常见的 93 | 94 | Here’s what our final Mortar and Flow architecture looks like: 95 | 96 | 我们 Mortar 和 Flow 整个体系架构将会如下所示: 97 | 98 | ![](https://www.bignerdranch.com/img/blog/2015/02/mortar-and-flow.png) 99 | 100 | 抛弃了对 MVC 模式的执念,这个架构在完成之后变得更像 MVP 模式。这样巨大的转变使得新的架构需要关注如何处理应用在运行时配置信息改变的问题,例如:旋转。在 MVC 模式中,我们的控制器(Activity 和 Fragment)会随着我们的 View 一起被杀死。然而,在 MVP 模式中,我们只有 View 101 | 被杀死,又在需要它的时候重现它。挺有趣的对吧? 102 | 103 | ![](https://www.bignerdranch.com/img/blog/2015/02/mvp.png) 104 | 105 | ## 积极的反馈 106 | 107 | 为了摆脱 Fragment,Square 付出了无数的汗水去进行重新架构和设计,并完成了 Mortar 和 Flow库,他们当然会获得相应的回报,接下来我就给大家介绍这两个库给我们带来的好处吧。 108 | 109 | 使用 Mortar 和 Flow 库强迫我们创建了一个符合 MVP 模式设计的模块化 App 结构,通过这样做能有效地帮助我们保持代码的整洁。 110 | 111 | 通过对我们自定义 View 和 Presenters 的依赖注入,测试变得更简单了 112 | 113 | 动画能够在 View 层被处理,而不用像从前在 Activity 和 Fragment 中使用时那样担心动画会出现Bug 114 | 115 | Mortar 在 View 和 Presenter 层中自动进行垃圾回收以处理其作用域,意味着应用能更有效地利用内存 116 | 117 | ## 可优化的空间 118 | 尽管 Flow 和 Mortar 给我们带来了许多好处,但是它们也还存在一些问题: 119 | 120 | **想要熟练使用 Flow 和 Mortar,需要面对一条陡峭的学习曲线。**在你真正理解这两个库的设计思想和原理之前,它们的使用模式看起来非常复杂,如果你想要将他们用的得心应手,无疑需要大量的探索和实验,此外,这些库并不是为初学者提供的,我们更建议初学者先学习如何正确和有效地使用 Activity 和 Fragment,我可不是吓唬你们,这样跟你们说吧,就算是 Android 开发大神,在面对这些库时仍需要花费大量的精力和时间去学习有关设计模式的知识,才能真正理解这个库。 121 | 122 | **如果你正准备使用 Mortar 和 Flow 库,你真的要全面了解它们的用法。**因为让它和标准的“少使用 Fragment”开发模式相互作用是很困难的。如果你想修改一个已经写好的项目,让它使用 Mortar 和 Flow,虽然不是不可能的,但是完成这个目标的过程会是非常漫长和艰难的。 123 | 124 | **这里还存在无数的模板和配置信息需要被处理。**而这正是我最大的担忧,在使用这些新的类和接口时,我常常觉得被淹没在无趣的代码海洋里,因为这些代码都被设计成和其中的各个类、接口钩连在一起,而这也的设计让我觉得这两个库并没有像我期待的那样有趣。 125 | 126 | ## 接下来呢 127 | 不过现在 Mortar 和 Flow 库都处于预发布阶段,现在也没有官方发布的版本。这意味着 Square 还在处理这两个库存在的问题,改动和更新,但这同样也意味着它们还需要许多时间作改进,才能真正投入到使用中。 128 | 129 | 使用 Mortar 和 Flow 库是个有趣的体验,我非常享受使用各种新的库和寻找官方以 Fragment 为导向的应用结构的替代品,但我并不认为 Mortar 和 Flow 是 Android 寻找的替代 Fragment 的办法,毕竟 Fragment 可能在接下来的几个月或者几年中被修改。但我仍然希望这些项目能够引起更多人关注,并且继续优化,我肯定会继续关注他们的最新进展的,希望大家继续关注我的博客哦。 130 | -------------------------------------------------------------------------------- /androidweekly/kotlin-for-android简介/readme.md: -------------------------------------------------------------------------------- 1 | kotlin-for-android简介(1) 2 | --- 3 | 4 | > 5 | * 原文链接 : [Kotlin for Android (I): Introduction 6 | ](http://antonioleiva.com/kotlin-for-android-introduction/) 7 | * 译者 : [canglangwenyue](https://github.com/canglangwenyue) 8 | * 校对者: [Mr.Simple](https://github.com/bboyfeiyu) 9 | * 状态 : 完成 10 | 11 | Kotlin是众多基于JVM的语言之一,它一开始是作为android 开发中java语言的可能的代替者出现的。java是世界上使用最多的语言之一,而伴随着其他语言的发展使得程序员编程越来越容易,但是java并没有尽快地向预期目标发展。 12 | 13 | ###Kotlin简介 14 | Kotlin是JetBrains创造的基于JVM的语言,JetBrains是IntelliJ的缔造团队。 15 | Kotlin是一个拥有很多函数编程特点的面向对象的编程语言。 16 | 17 | ###为什么要用Kotlin 18 | 我首先声明我并没有使用Kotlin很长时间,我几乎是在学习的同时写了这些文章的。我并没有尝试任何其它的替 代语言,例如Go和Scala,所以如果你是真的考虑换一种开发语言的话,我建议你去搜索一下其他人对这些 语言的评价。一个使用Scala开发android的例子可以在[47deg Github site](http:/ 19 | 47deg.github.io/translate-bubble-android/)找到。 20 | 21 | 22 | 以下是我选择学习Kotlin的原因: 23 | * **学习曲线相对较快**:以Scala作为例子进行比较,我们是向着更简单的方向前进。Kotlin有更多的限制,但是如果你没有学习过一门现代编程语言的话,Kotlin更容易学习。 24 | * **轻量级**:与其他语言相比Kotlin的核心库更小。这很重要,因为android函数数量限制(函数数量不能大于64k)是一个问题,虽 25 | 然有一些选择来解决这个问题,例如proguard 或 multidexing,但是这些解决方案会加复杂度,并导 26 | 致调试时花费更多的时间。引入Kotlin核心库添加了不到7000个方法,大致和support-v4一样。 27 | * **高交互性**:Kotlin和其它java库协调使用的特别好,并且交互操作很简单。这是Kotlin团队 28 | 在开发新语言是的主要理念之一。他们想在使用Kotlin开发时并不用重写之前所有用java写的代码,所以,Kotlin和java交互的能力必须非常高。 29 | * **与AS和Gradle完美结合**:我们有一个IDE的插件和另一个属于Grade的插件,因此,用Kotlin进行 30 | android编程并不困难。 31 | 32 | 在开始任何争论之前我建议你看一下Jake Wharton写的一个有趣的文档[the use of Kotlin for Android development](https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/edit?hl=es&forcehl=1&pli=1)。 33 | 34 | ###Kotlin的优点 35 | 36 | ####1. 可读性更高,更简洁 37 | 使用Kotlin,可以更容易的避免创建模版型代码,因为大多数经典的情景都默认包含在Kotlin中。 38 | 例如,在java中,我们想要创建一个典型的data class时需要这样做: 39 | 40 | ```java 41 | public class Artist { 42 | private long id; 43 | private String name; 44 | private String url; 45 | private String mbid; 46 | 47 | public long getId() { 48 | return id; 49 | } 50 | 51 | public void setId(long id) { 52 | this.id = id; 53 | } 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | 59 | public void setName(String name) { 60 | this.name = name; 61 | } 62 | 63 | public String getUrl() { 64 | return url; 65 | } 66 | 67 | public void setUrl(String url) { 68 | this.url = url; 69 | } 70 | 71 | public String getMbid() { 72 | return mbid; 73 | } 74 | 75 | public void setMbid(String mbid) { 76 | this.mbid = mbid; 77 | } 78 | 79 | @Override public String toString() { 80 | return "Artist{" + 81 | "id=" + id + 82 | ", name='" + name + '\'' + 83 | ", url='" + url + '\'' + 84 | ", mbid='" + mbid + '\'' + 85 | '}'; 86 | } 87 | } 88 | ``` 89 | 90 | 那么在Kotlin需要多少代码呢?仅仅是下面这个简单的数据类: 91 | 92 | ```java 93 | 94 | data class Artist( 95 | var id: Long, 96 | var name: String, 97 | var url: String, 98 | var mbid: String) 99 | ``` 100 | 101 | ####2. 空指针安全 102 | 当我们用java开发时,我们的大多数代码是要进行类型检查的,如果我们不想出现**unexpected 103 | NullPointerException**的话,我们就要在运行代码之前持续的检查是否有对象为null。Kotlin,和其它语 104 | 言一样,是空指针安全的,因为我们可以通过安全的调用操作来准确的声明一个object可以为null。 105 | 106 | 我们可以这样做: 107 | 108 | ```java 109 | 110 | //This won´t compile. Artist can´t be null 111 | var notNullArtist: Artist = null 112 | 113 | //Artist can be null 114 | var artist: Artist? = null 115 | 116 | // Won´t compile, artist could be null and we need to deal with that 117 | artist.print() 118 | 119 | // Will print only if artist != null 120 | artist?.print() 121 | 122 | // Smart cast. We don´t need to use safe call operator if we previously checked nullity 123 | if (artist != null) { 124 | artist.print() 125 | } 126 | 127 | // Only use it when we are sure it´s not null. Will throw an exception otherwise. 128 | artist!!.print() 129 | 130 | // Use Elvis operator to give an alternative in case the object is null 131 | val name = artist?.name ?: "empty" 132 | ``` 133 | 134 | ####3. 扩展方法 135 | 我们可以给任何类添加新方法。这比我们在project中使用的工具类可读性更高。例如:我们可以给Fragment添加一个新方法来显示Toast。 136 | 137 | ```java 138 | fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) { 139 | Toast.makeText(getActivity(), message, duration).show() 140 | } 141 | ``` 142 | 我们可以这样使用: 143 | 144 | ```java 145 | fragment.toast("Hello world!") 146 | ``` 147 | 148 | ####4. 支持函数式编程 149 | 如果我们可以不用在我们需要的时候每一次都创建一个listener,就像创建一个click listener那样的操作, 150 | 而是仅仅定义我们想要做什么?这种想法的确可以实现,它的实现得益于**lambda**d的使用: 151 | 152 | ```java 153 | view.setOnClickListener({ toast("Hello world!") }) 154 | ``` 155 | 156 | ###Kotlin目前存在的限制 157 | Kotlin 依旧在发展,虽然它相对稳定,并且final release版本就很快发布,但是Kotlin在进行android相关开发的时候还是有些限制的。 158 | 159 | * **交互性与自动代码生成**:一些有名的android Libraries,例如Dagger 或 Butterknife,他们依赖于自动 160 | 代码生成,这种情况下如果有名字冲突的话将无法进行代码生成。Kotlin正在解决这个问题,所以这个问题也许 161 | 会很快解决。无论如何,我将在接下来的文章里阐明,Kotlin语言的表达能力会让我们觉得不再需要那么多的 162 | Libraries。 163 | 164 | * **声明自定义View比较困难**:Kotlin类只能声明一个构造函数,然而自定义View通常需要三个。如果我 165 | 们使用代码来创建View的话可以避免这个问题,但对于使用XML文件来声明View的话就不能满足需求了。最容易的变通方式是用java来声明这些 166 | 自定义View的实现类,然后通过Kotlin来使用它们。Kotlin团队许诺将在M11 release解决这个问题。 167 | **Update: Kotlin M11 is out and now includes [secondary constructors](http://kotlinlang.org/docs/reference/classes.html#constructors)** 168 | 169 | * **android 下Junit测试**:AS 1.1中介绍的新特性并不适用与Kotlin。顺便说说,系统测试和Junit 测试对于纯Kotlin项目是完全可用。 170 | 171 | ###结论 172 | 对于android apps 开发,Kotlin是一个非常有趣的java替代者。下一篇文章将会描述如何用Kotlin新建一 173 | 个project,和如何更好的适用Kotlin来使得android开发更加简单。敬请关注! 174 | 175 | -------------------------------------------------------------------------------- /androidweekly/readme.md: -------------------------------------------------------------------------------- 1 | AndroidWeekly.net 2 | --- 3 | 4 | -------------------------------------------------------------------------------- /androidweekly/一个支持多设备的Android参考应用/readme.md: -------------------------------------------------------------------------------- 1 | 一个支持多设备的Android参考应用 2 | --- 3 | > 4 | * 原文链接 : [a-new-reference-app-for-multi-device](http://android-developers.blogspot.com/2015/03/a-new-reference-app-for-multi-device.html) 5 | * 译者 : [Mr.Simple & Sophie.Ping](https://www.github.com/bboyfeiyu) 6 | 7 | 现在你可以把你的app的好用之处分享给你的用户,不管他们身处何地,手上拿着何种设备。今儿我们会发布一个参考示例,展示一下如何把这种服务运用到一个在多个Android form-Factor运作的app上。这个示例叫做Universal Music Player,它是一个准系统但是参考功能齐全,在单个代码库里支持多种设备和形式因素。它能与Android Auto、Android Wear和Google Cast设备兼容。你可以试试把你的app适配到你的用户,无论他在哪里,无论他手上拿的是手机,手表,电视,汽车还是其他的设备。 8 | ![android](http://img.blog.csdn.net/20150322112056022) 9 | 10 | 锁屏时的播放控制和专辑封面 11 | 应用toolbar上的Google Cast 图标 12 | 13 | ![auto](http://img.blog.csdn.net/20150322111453169) 14 | 使用Android Auto控制播放 15 | 16 | ![watch](http://img.blog.csdn.net/20150322111518047) 17 | 通过Android Wear Watch控制播放 18 | 19 | 20 | 本示例运用了Android 5.0 Lollipop的几个新功能,比如MediaStyle通知,MediaSession和MediaBrowserService.这几个新功能使得在多个设备上使用单一app版本操作媒体浏览和回放变得更容易。 21 | 一起来看看源代码吧,让你的用户以他们的方式爱上你的app。 22 | 作者:Renato Mangini,高级开发工程师,Google开发者平台团队成员 -------------------------------------------------------------------------------- /androidweekly/一种更清晰的Android架构/readme.md: -------------------------------------------------------------------------------- 1 | 一种更清晰的Android架构 2 | --- 3 | 4 | > 5 | * 原文链接 : [Architecting Android…The clean way?](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/) 6 | * 译者 : [Mr.Simple & Sophie.Ping](https://www.github.com/bboyfeiyu) 7 | 过去几个月以来,通过在Tuenti网站上与@pedro_g_s和@flipper83(安卓开发两位大牛)进行友好讨论之后,我决定写这篇关于架构安卓应用的文章。 8 | 我写这篇文章的目的是想把我在过去几个月体悟到的小方法以及在调查和应用中学到的有用的东西分享给大家。 ## 入门指南 大家都知道要写一款精品软件是有难度且很复杂的:不仅要满足特定要求,而且软件还必须具有稳健性,可维护、可测试性强,并且能够灵活适应各种发展与变化。这时候,“清晰架构”就应运而生了,这一架构在开发任何软件应用的时候用起来非常顺手。 这个思路很简单:简洁架构 意味着产品系统中遵循一系列的习惯原则: 9 | 10 | * 框架独立性 11 | * 可测试 12 | * UI独立性 13 | * 数据库独立性 14 | * 任何外部代理模块的独立性 15 | ![arch](https://camo.githubusercontent.com/dd69e725f30c30031dea279adc5a9d09ea3432f2/687474703a2f2f6665726e616e646f63656a61732e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30392f636c65616e5f617263686974656374757265312e706e67) 我们并不要求一定要用四环结构(如图所示),这只是一个示例图解,但是要考虑的是依赖项规则:源码依赖项只能向内指向,内环里的所有项不能了解外环所发生的东西。 16 | 以下是更好地理解和熟悉本方法的一些相关词汇: * Entities:是指一款应用的业务对象 * Use cases:是指结合数据流和实体中的用例,也称为Interactor * Interface Adapters: 这一组适配器,是负责以最合理的格式转换用例(use cases)和实体(entities)之间的数据,表现层(Presenters )和控制层(Controllers ),就属于这一块的。 17 | * Frameworks and Drivers: 这里是所有具体的实现了:比如:UI,工具类,基础框架,等等。 想要更具体,更生动丰富的解释,可以参考[这篇文章](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)或者[这个视频](https://vimeo.com/43612849)。 18 | ## 场景 我会设置一个简单的场景来开始:创建一个简单的小app,app中显示从云端获取的一个朋友或用户列表。当点击其中任何一个时,会打开一个新的窗口,显示该用户的详细信息。这里我放了一段视频,大家看看[这个视频 (需翻墙)](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/)大概就可以对我所描述的东西了解个大概了。 19 | ## Android应用架构 这一对象遵循关注分离原则,也就是通过业务规则让内环操作对外环事物一无所知,这样一来,在测试时它们就不会依赖任何的外部元素了。 要达到这个目的,我的建议就是把一个项目分成三个层次,每个层次拥有自己的目的并且各自独立于堆放运作。 值得一提的是,每一层次使用其自有的数据模型以达到独立性的目的(大家可以看到,在代码中需要一个数据映射器来完成数据转换。如果你不想把你的模型和整个应用交叉使用,这是你要付出的代价)。 20 | 以下是图解,大家感受下: 21 | ![schema](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_android.png) 22 | > 注:我并没有使用任何的外部库(除了用于json数据句法分析的gson和用于测试的junit, mockito, robolectric和espresso以外)。原因是它可以使这个示例更清晰。总之,在存储磁盘数据时,记得加上ORM、依赖注入框架或者你熟悉的任何工具或库,这些都会带来很大帮助。(记住:重复制造轮子可不是明智的选择) 23 | ## 表现层 (Presentation Layer) 24 | 表现层在此,表现的是与视图和动画相关的逻辑。这里仅用了一个Model View Presenter(下文简称MVP),但是大家也可以用MVC或MVVM等模式。这里我不再赘述细节,但是需要强调的是,这里的fragment和activity都是View,其内部除了UI逻辑以外没有其他逻辑,这也是所有渲染的东西发生的地方。 本层次的Presenter由多个interactor(用例)组成,Presenter在 android UI 线程以外的新线程里工作,并通过回调将要渲染到View上的数据传递回来。 ![mvp](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_mvp.png) 25 | 如果你需要一个使用MVP和MVVM的[Effective Android UI](https://github.com/pedrovgs/EffectiveAndroidUI/)典型案例,可以参考我朋友Pedro Gómez的文章。 ## 领域层 (Domain Layer) 26 | 这里的业务规则是指所有在本层发生的逻辑。对于Android项目来说,大家还可以看到所有的interactor(用例)实施。这一层是纯粹的java模块,没有任何的Android依赖性。当涉及到业务对象时,所有的外部组件都使用接口。 27 | ![domain](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_domain.png) 28 | ## 数据层 (Data Layer) 应用所需的所有数据都来自这一层中的UserRepository实现(接口在领域层)。这一实现采用了[Repository Pattern](http://martinfowler.com/eaaCatalog/repository.html),主要策略是通过一个工厂根据一定的条件选取不同的数据来源。 比如,通过ID获取一个用户时,如果这个用户在缓存中已经存在,则硬盘缓存数据源会被选中,否则会通过向云端发起请求获取数据,然后存储到硬盘缓存。 这一切背后的原理是由于原始数据对于客户端是透明的,客户端并不关心数据是来源于内存、硬盘还是云端,它需要关心的是数据可以正确地获取到。 ![data](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_data.png) 29 | > 注:在代码方面,出于学习目的,我通过文件系统和Android preference实现了一个简单、原始的硬盘缓存。请记住,如果已经存在了能够完成这些工作的库,就不要重复制造轮子。 ## 错误处理 这是一个长期待解决的讨论话题,如果大家能够分享各自的解决方案,那真真是极好的。 我的策略是使用回调,这样的话,如果数据仓库发生了变化,回调有两个方法:onResponse()和onError(). onError方法将异常信息封装到一个ErrorBundle对象中: 这种方法的难点在于这其中会存在一环扣一环的回调链,错误会沿着这条回调链到达展示层。因此会牺牲一点代码的可读性。另外,如果出现错误,我本来可以通过事件总线系统抛出事件,但是这种实现方式类似于使用C语言的goto语法。在我看来,当你订阅多个事件时,如果不能很好的控制,你可能会被弄得晕头转向。 ## 测试 关于测试方面,我根据不同的层来选择不同的方法: 30 | * 展示层 ( Presentation Layer) : 使用android instrumentation和 espresso进行集成和功能测试 * 领域层 ( Domain Layer) : 使用JUnit和Mockito进行单元测试; * 数据层 ( Data Layer) : 使用Robolectric ( 因为依赖于Android SDK中的类 )进行集成测试和单元测试。 ## 代码展示 我猜你现在在想,扯了那么久的淡,代码究竟在哪里呢? 好吧,这就是你可以找到上述解决方案的[github链接](https://github.com/android10/Android-CleanArchitecture)。还要提一点,在文件夹结构方面,不同的层是通过以下不同的模块反应的: 31 | * presentation: 展示层的Android模块 * domain: 一个没有android依赖的java模块 * data: 一个数据获取来源的android模块。 * data-test: 数据层测试,由于使用Robolectric 存在一些限制,所以我得再独立的java模块中使用。 ## 结论 正如 Bob大叔 所说:“Architecture is About Intent, not Frameworks” ,我非常同意这个说法,当然了,有很多不同的方法做不同的事情(不同的实现方法),我很确定,你每天(像我一样)会面临很多挑战,但是遵循这些方法,可以确保你的应用会: 32 | * 易维护 Easy to maintain * 易测试 Easy to tes. * 高内聚 Very cohesive. * 低耦合 Decoupled. 最后,我强烈推荐你去实践一下,并且分享你的经验。也许你会找到更好的解决方案:我们都知道,不断提升自己是一件件非常好的事。我希望这篇文章对你有所帮助,欢迎拍砖。 ## 参考资料
    33 |
  1. Source code: https://github.com/android10/Android-CleanArchitecture
  2. 34 |
  3. The clean architecture by Uncle Bob
  4. 35 |
  5. Architecture is about Intent, not Frameworks
  6. 36 |
  7. Model View Presenter
  8. 37 |
  9. Repository Pattern by Martin Fowler
  10. 38 |
  11. Android Design Patterns Presentation
  12. 39 |
-------------------------------------------------------------------------------- /androidweekly/使用Robolectric的参数化测试/readme.md: -------------------------------------------------------------------------------- 1 | 使用Robolectric的参数化测试 2 | --- 3 | 4 | > 5 | * 原文标题 : Parameterized testing with Robolectric 6 | * 原文链接 : [Parameterized testing with Robolectric](http://www.jayway.com/2015/03/19/parameterized-testing-with-robolectric/) 7 | * 译者 : [Lollypo](https://github.com/Lollypo) 8 | * 校对者: [Chaos](https://github.com/chaossss) 9 | * 状态 : 校对完成 10 | 11 | 在目前的项目中我们使用Robolectric为Android应用程序编写单元测试,它一直都干的不错。最近我需要编写一个测试用例,通过每次使用不同的测试数据,将同一个操作执行若干次,并由此断言:正确的动作能否发生是由数据决定的。 12 | 13 | JUnit对于这个情况提供了一个易于使用的选项,它叫做参数化测试-先定义测试数据,,然后使用参数化测试运行器来执行测试。这将创建一个该测试类的实例把测试数据中的每个元素传递到构造函数的参数中。 14 | 15 | 事实证明,Robolectric有一个完全相同的`ParameterizedRobolectricTestRunner`(稍微调整以适应Robolectric),而且它对于我的测试:验证应用程序从外部服务提供者接收到不同的错误代码的行为,做的非常好 16 | 17 | ```java 18 | @RunWith(ParameterizedRobolectricTestRunner.class) 19 | public class ContactServiceTest { 20 | 21 | @ParameterizedRobolectricTestRunner.Parameters(name = "ErrorCode = {0}") 22 | public static Collection data() { 23 | return Arrays.asList(new Object[][]{ 24 | {105, 105_ERROR_MSG}, 25 | {113, 113_ERROR_MSG}, 26 | {114, 114_ERROR_MSG}, 27 | {134, 134_ERROR_MSG}, 28 | {137, 137_ERROR_MSG}, 29 | {999, DEFAULT_ERROR_MSG} // Bogus错误代码 30 | }); 31 | } 32 | 33 | private int errorCode; 34 | private String expectedErrorMsg; 35 | 36 | public ContactServiceTest(int errorCode, String errorMsg) { 37 | this.errorCode = errorCode; 38 | this.expectedErrorMsg = errorMsg; 39 | } 40 | 41 | @Test 42 | public void when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user() { 43 | // HTTP响应从服务包含定义的错误代码 44 | Robolectric.addPendingHttpResponse(HttpStatus.SC_OK, buildFakeServiceResponse(errorCode)); 45 | // 联系服务 46 | mService.contactService(); 47 | // 使用awaitility等到错误消息显示给用户 48 | // 然后断言该错误代码与期望一致 49 | await().until(getDisplayedErrorMsg(), is(expectedErrorMsg)); 50 | } 51 | ``` 52 | 53 | 该测试用例将被执行6次,一旦遍历测试数据的每个元素,就会将打印的错误消息与特定错误代码定义的错误消息相比较。当创建测试报告时,每一个测试运行都将视为其自身的测试用例。 54 | 55 | 添加了`name`参数到Parameters注解上将会整理测试结果。作为测试运行结果显示,测试用例的名称将会像下面这种情况下 56 | 57 | ```java 58 | when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 105] 59 | when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 113] 60 | when_known_error_code_is_received_from_service_correct_error_msg_is_displayed_to_user[ErrorCode = 114] 61 | ... 62 | ``` 63 | 64 | 如需深入理解请查阅[JUnit Theories](https://github.com/junit-team/junit/wiki/Theories)以及[junit-quickcheck](https://github.com/pholser/junit-quickcheck),一个好的生成测试数据的方法是在JUnit中自动生成(Robolectric也差不多),而不是由你自己定义。 -------------------------------------------------------------------------------- /androidweekly/功能测试框架 espresso/readme.md: -------------------------------------------------------------------------------- 1 | 功能测试框架 espresso 2 | --- 3 | 4 | > 5 | * 原文链接 : [the-hitchhikers-guide-to-android-testing-part-2-espresso](http://wiebe-elsinga.com/blog/the-hitchhikers-guide-to-android-testing-part-2-espresso/) 6 | * 译者 : [Lollypo](https://github.com/Lollypo) 7 | * 校对者: [kang](https://github.com/tiiime) 8 | * 状态 : 校对完成 9 | 10 | ![VtFd68Pr19fYk.](http://7xi8kj.com1.z0.glb.clouddn.com/VtFd68Pr19fYk.gif) 11 | 12 | 正如[Ali Derbane](https://plus.google.com/+AliDerbane)和我写的第一篇关于Android的功能测试的文章中提到的,有许多的框架供你使用. 13 | 在这个旅程的第二部分,我将讲解[Espresso](https://code.google.com/p/android-test-kit/)这个功能测试框架. 14 | 15 | 16 | ### 简介 17 | 18 | Espresso 是在2013年的 GTAC 上首次提出,目的是让开发人员能够快速地写出简洁,美观,可靠的 Android UI 测试。 19 | 20 | Espresso有以下几个通用组件: 21 | 22 | - “Espresso”类提供的“onView”和“onData”方法,仅可用于特定接口上测试最优数. 23 | - `ViewMatchers` 包含一个实现了`Matcher `接口的对象集合. 使用该类你可以收集或是检查View元素.例如,通过文本 “7” 获取一个View元素(Button). 24 | - `ViewActions` 包含了一组`viewAction`对象,储存了将要在View上执行的动作. 这些动作被传递给`ViewInteraction.perform`方法,也许包含更多的动作. For 例如, 点击一下View元素(Button). 25 | - `ViewAssertions` 包含`ViewAssertion`集合,用于对Views进行检查. 26 | 27 | 举个例子说明一下,这些测试组件看起来就像下面这样: 28 | 29 | ```java 30 | Espresso.onView(ViewMatchers.withText("7")).perform(ViewActions.click()); 31 | Espresso.onView(withId(R.id.result)).check(ViewAssertions.matches(ViewMatchers.withText("42"))); 32 | ``` 33 | 34 | 好消息,去年谷歌推出了集成Espresso的[Testing Support Library](https://developer.android.com/tools/support-library/index.html).因此,让我们通过实现Espresso开始吧. 35 | 36 | > 为了方便解释, 我们要编写一些测试用例来测试[Android calculator application](https://github.com/welsinga/sample_espresso/app)这个App. 先来实现一个测试“6”x“7”等于“42”是否正确的普通测试场景。 37 | 38 | 39 | 40 | ### 定义test runner 41 | 42 | 使用Espresso我们首先需要定义这些测试用例。Espresso使用新的名为AndroidJUnitRunner的测试用例。该测试用例基于“InstrumentationTestRunner”和“GoogleInstrumentationTestRunner”,运行JUnit3和JUnit4来测试你的Android应用程序。 43 | 44 | 首先将依赖项添加到你的`build.gradle`文件中, 这里假设你已经安装好了[Testing Support Library](https://developer.android.com/tools/support-library/index.html). 45 | 46 | ```gradle 47 | dependencies { 48 | androidTestCompile 'com.android.support.test:testing-support-lib:0.1' 49 | } 50 | ``` 51 | 52 | 然后添加测试用例到你的`build.gradleandroid.defaultConfig`配置中 53 | 54 | ```gradle 55 | defaultConfig { 56 | ... 57 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 58 | } 59 | ``` 60 | 61 | 62 | 63 | ### 编写测试 64 | 65 | 你可能已经想到了,测试类必须在`src\androidTest\com.example.package.tests`中.包com.example.package是在AndroidManifest文件中指定的属性. 66 | 67 | 每一个测试类还必须继承抽象类`ActivityInstrumentationTestCase2`并且使用默认测试的 Activity 作为泛型. 68 | 69 | 它还需要通过`super()`方法传递给父类.要使被测试的Activity被测试框架调用,只需要在setup方法中同步调用`getActivity()`方法. 70 | 71 | ```java 72 | public class FunctionalInstrumentationTest extends ActivityInstrumentationTestCase2 { 73 | 74 | public FunctionalInstrumentationTest() { 75 | super(ActivityToTest.class); 76 | } 77 | 78 | @Override 79 | protected void setUp() throws Exception { 80 | super.setUp(); 81 | getActivity(); 82 | } 83 | } 84 | ``` 85 | 86 | 正如前面提到的,我们想要检查“6”x“7”是否等于“42”. 87 | 88 | ```java 89 | public void testAnswer_to_the_Ultimate_Question_of_Life_the_Universe_and_Everything() { 90 | onView(withText("7")).perform(click()); 91 | onView(withText("×")).perform(click()); 92 | onView(withText("6")).perform(click()); 93 | onView(withText("=")).perform(click()); 94 | 95 | onView(withId(R.id.resText)).check(matches(withText("42"))); 96 | } 97 | ``` 98 | 99 | 你可能已经注意到,这个示例是使用静态导入.这样做完全是为了使代码更易于阅读. 100 | 101 | 其他你可能会用到的操作: 102 | 103 | - `pressBack()`; to simulate the use of the “back” button, 104 | - `isDisplayed()`; to check if an element is being shown and 105 | - `scrollTo()`; to scroll to an element. 106 | - `pressBack()`; 模拟后退按钮 107 | - `isDisplayed()`; jian检查某个元素是否显示 108 | - `scrollTo()`; 滚动到另外一个元素 109 | 110 | 111 | ### 运行测试 112 | 113 | 现在我们做做有趣的,运行测试.这可以通过`gradle clean assembleDebug connectedAndroidTest`从命令行运行,或者使用Android Studio: 114 | 115 | 1. 打开Run菜单 | Edit Configurations 116 | 2. 添加一个新的Android Tests configuration 117 | 3. 选择你需要测试的Module 118 | 4. 定义我们的测试用例: `android.support.test.runner.AndroidJUnitRunner` 119 | 120 | ![10OjZwNlstPj9e](http://7xi8kj.com1.z0.glb.clouddn.com/10OjZwNlstPj9e.gif) 121 | 122 | 现在你对于Espresso有一些了解了。如果需要深入,可以浏览以下链接: 123 | 124 | - [Espresso website](https://code.google.com/p/android-test-kit/) 125 | - [Github repo corresponding to this article](https://github.com/welsinga/sample_espresso) 126 | - [General Espresso Github samples by Google](https://github.com/googlesamples/android-testing) 127 | -------------------------------------------------------------------------------- /androidweekly/在Android 5.0中使用JobScheduler/readme.md: -------------------------------------------------------------------------------- 1 | 在Android 5.0中使用JobScheduler 2 | --- 3 | 4 | > * 原文链接 : [using-the-jobscheduler-api-on-android-lollipop](http://code.tutsplus.com/tutorials/using-the-jobscheduler-api-on-android-lollipop--cms-23562) 5 | > * 译者 : [Mr.Simple](https://github.com/bboyfeiyu) 6 | > * 校对者 : [Mr.Simple](https://github.com/bboyfeiyu) 7 | 8 | 在这篇文章中,你会学习到在Android 5.0中如何使用JobScheduler API。JobScheduler API允许开发者在符合某些条件时创建执行在后台的任务。 9 | 10 | ## 介绍 11 | 在Android开发中,会存在这么些场景 : 你需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务,例如当设备接通电源适配器或者连接到WIFI。幸运的是在API 21 ( Android 5.0,即Lollipop )中,google提供了一个叫做JobScheduler API的新组件来处理这样的场景。 12 | 13 | 当一系列预置的条件被满足时,JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外,JobScheduler API允许同时执行多个任务。这允许你的应用执行某些指定的任务时不需要考虑时机控制引起的电池消耗。 14 | 15 | 这篇文章中,你会学到关于JobScheduler API更多的东西以及在你的应用中用于运行一个简单的后台任务的JobService,这篇文章中所展示的代码你都可以在[github](https://github.com/tutsplus/Android-JobSchedulerAPI)中找到。 16 | 17 | ## 1. 创建Job Service 18 | 19 | 首先,你需要创建一个API最低为21的Android项目,因为JobScheduler是最近的版本才加入Android的,在写这篇文章的时候,它还没有兼容库支持。(译者注:截止目前已知一个兼容库 [JobSchedulerCompat](https://github.com/evant/JobSchedulerCompat)) 20 | 21 | 假定你使用的是Android Studio,当你点击了创建项目的完成按钮之后,你会得到一个"hello world"的应用骨架。你要做的第一步就是创建一个新的java类。为了简单起见,让我们创建一个继承自JobService且名字为JobSchedulerService的类,这个类必须实现两个方法,分别是`onStartJob(JobParameters params) `和 `onStopJob(JobParameters params)`; 22 | 23 | ```java 24 | public class JobSchedulerService extends JobService { 25 | 26 | @Override 27 | public boolean onStartJob(JobParameters params) { 28 | 29 | return false; 30 | } 31 | 32 | @Override 33 | public boolean onStopJob(JobParameters params) { 34 | 35 | return false; 36 | } 37 | } 38 | ``` 39 | 40 | 当任务开始时会执行`onStartJob(JobParameters params)`方法,因为这是系统用来触发已经被执行的任务。正如你所看到的,这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了你的肩上。当任务执行完毕时你需要调用`jobFinished(JobParameters params, boolean needsRescheduled)`来通知系统。 41 | 42 | 当系统接收到一个取消请求时,系统会调用`onStopJob(JobParameters params)`方法取消正在等待执行的任务。很重要的一点是如果`onStartJob(JobParameters params)`返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。换句话说,`onStopJob(JobParameters params)`在这种情况下不会被调用。 43 | 44 | 需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。因为多线程技术已经超出了我们这篇文章的范围,让我们简单实现一个Handler来执行我们在JobSchedulerService定义的任务吧。 45 | 46 | ```java 47 | private Handler mJobHandler = new Handler( new Handler.Callback() { 48 | 49 | @Override 50 | public boolean handleMessage( Message msg ) { 51 | Toast.makeText( getApplicationContext(), 52 | "JobService task running", Toast.LENGTH_SHORT ) 53 | .show(); 54 | jobFinished( (JobParameters) msg.obj, false ); 55 | return true; 56 | } 57 | 58 | } ); 59 | ``` 60 | 61 | 在Handler中,你需要实现`handleMessage(Message msg)`方法来处理你的任务逻辑。在这个例子中,我们尽量保证例子简单,因此我们只在`handleMessage(Message msg)`中显示了一个Toast,这里就是你要写你的任务逻辑( 耗时操作 )的地方,比如同步数据等。 62 | 63 | 当任务执行完毕之后,你需要调用`jobFinished(JobParameters params, boolean needsRescheduled)`来让系统知道这个任务已经结束,系统可以将下一个任务添加到队列中。如果你没有调用`jobFinished(JobParameters params, boolean needsRescheduled)`,你的任务只会执行一次,而应用中的其他任务就不会被执行。 64 | 65 | `jobFinished(JobParameters params, boolean needsRescheduled) `的两个参数中的params参数是从JobService的`onStartJob(JobParameters params)`的params传递过来的,needsRescheduled参数是让系统知道这个任务是否应该在最初的条件下被重复执行。这个boolean值很有用,因为它指明了你如何处理由于其他原因导致任务执行失败的情况,例如一个失败的网络请求调用。 66 | 67 | 创建了Handler实例之后,你就可以实现`onStartJob(JobParameters params)` 和`onStopJob(JobParameters params)`方法来控制你的任务了。你可能已经注意到在下面的代码片段中`onStartJob(JobParameters params)`返回了true。这是因为你要通过Handler实例来控制你的操作,这意味着Handler的handleMessage方法的执行时间可能比`onStartJob(JobParameters params)`更长。返回true,你会让系统知道你会手动地调用`jobFinished(JobParameters params, boolean needsRescheduled)`方法。 68 | 69 | ```java 70 | @Override 71 | public boolean onStartJob(JobParameters params) { 72 | mJobHandler.sendMessage( Message.obtain( mJobHandler, 1, params ) ); 73 | return true; 74 | } 75 | 76 | @Override 77 | public boolean onStopJob(JobParameters params) { 78 | mJobHandler.removeMessages( 1 ); 79 | return false; 80 | } 81 | ``` 82 | 一旦你在Java部分做了上述工作之后,你需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。 83 | 84 | ``` 85 | 87 | ``` 88 | 89 | ## 2. 创建一个JobScheduler对象 90 | 随着JobSchedulerService构建完毕,我们可以开始研究你的应用如何与JobScheduler API进行交互了。第一件要做的事就是你需要创建一个JobScheduler对象,在实例代码的MainActivity中我们通过`getSystemService( Context.JOB_SCHEDULER_SERVICE )`初始化了一个叫做mJobScheduler的JobScheduler对象。 91 | 92 | ```java 93 | mJobScheduler = (JobScheduler) 94 | getSystemService( Context.JOB_SCHEDULER_SERVICE ); 95 | ``` 96 | 当你想创建定时任务时,你可以使用`JobInfo.Builder`来构建一个JobInfo对象,然后传递给你的Service。JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,第二个是这个Service组件的类名。 97 | 98 | ```java 99 | JobInfo.Builder builder = new JobInfo.Builder( 1, 100 | new ComponentName( getPackageName(), 101 | JobSchedulerService.class.getName() ) ); 102 | ``` 103 | 104 | 105 | 这个builder允许你设置很多不同的选项来控制任务的执行。下面的代码片段就是展示了如何设置以使得你的任务可以每隔三秒运行一次。 106 | 107 | ```java 108 | builder.setPeriodic( 3000 ); 109 | ``` 110 | 111 | 其他设置方法 : 112 | 113 | * setMinimumLatency(long minLatencyMillis): 这个函数能让你设置任务的延迟执行时间(单位是毫秒),这个函数与`setPeriodic(long time)`方法不兼容,如果这两个方法同时调用了就会引起异常; 114 | * setOverrideDeadline(long maxExecutionDelayMillis): 115 | 这个方法让你可以设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。与`setMinimumLatency(long time)`一样,这个方法也会与`setPeriodic(long time)`不兼容,同时调用这两个方法会引发异常。 116 | * setPersisted(boolean isPersisted): 117 | 这个方法告诉系统当你的设备重启之后你的任务是否还要继续执行。 118 | * setRequiredNetworkType(int networkType): 119 | 这个方法让你这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行。 120 | * setRequiresCharging(boolean requiresCharging): 121 | 这个方法告诉你的应用,只有当设备在充电时这个任务才会被执行。 122 | * setRequiresDeviceIdle(boolean requiresDeviceIdle): 123 | 这个方法告诉你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务。 124 | 125 | 需要注意的是`setRequiredNetworkType(int networkType)`, `setRequiresCharging(boolean requireCharging)` 以及 `setRequiresDeviceIdle(boolean requireIdle)`这几个方法可能会使得你的任务无法执行,除非调用`setOverrideDeadline(long time)`设置了最大延迟时间,使得你的任务在未满足条件的情况下也会被执行。一旦你预置的条件被设置,你就可以构建一个JobInfo对象,然后通过如下所示的代码将它发送到你的JobScheduler中。 126 | 127 | ```java 128 | if( mJobScheduler.schedule( builder.build() ) <= 0 ) { 129 | //If something goes wrong 130 | } 131 | ``` 132 | 133 | 你可能注意到了,这个schedule方法会返回一个整型。如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。 134 | 135 | 如果你的应用想停止某个任务,你可以调用JobScheduler对象的`cancel(int jobId)`来实现;如果你想取消所有的任务,你可以调用JobScheduler对象的`cancelAll()`来实现。 136 | 137 | ```java 138 | mJobScheduler.cancelAll(); 139 | ``` 140 | 到了这里,你现在应该已经知道如何在你的应用中使用JobScheduler API来执行批量任务和后台操作了。 141 | 142 | ## 结论 143 | 这篇文章中,你学会了怎么实现一个使用Handler对象来运行后台任务的JobService子类,你也学会了如何使用JobInfo.Builder来设置JobService。掌握了这些之后,你可以在减少资源消耗的同时提升应用的效率。 144 | -------------------------------------------------------------------------------- /androidweekly/在Android调试模式中使用Stetho/README.md: -------------------------------------------------------------------------------- 1 | #Stetho for Android debug builds only 2 | # 在Android调试模式中使用Stetho 3 | ------ 4 | 5 | - 原文链接:[在Android调试模式中使用Stetho][11] 6 | - 译者:[BillionWang](https://github.com/BillionWang) 7 | - 校对者:[chaossss](https://github.com/chaossss) 8 | - 状态:完成 9 | 10 | 最近FaceBook发布了一个叫做[Stetho][2]的工具.这个工具是一个谷歌浏览器的开发者工具扩展 ,它可以用来检测你的应用。我发现这东西挺好用的,因为它还提供了访问应用中SQLite数据库的接口。很明显,这种类型的工具只应该在应用的调试模式中使用。接下来我们来看看怎么用这个工具。 11 | 12 | 13 | #添加依赖 14 | 为了保证只在调试模式中使用Stetho,你可以添加一个调试编译依赖,而不是平时常用的普通依赖类型。 15 | 16 | depencencies { 17 | // your other dependencies here... 18 | debugCompile 'com.facebook.stetho:stetho:1.0.0' 19 | } 20 | 21 | #在调试模式中初始化Stetho 22 | 23 | 现在我们在调试中使用Stetho。你会怎么做?当然使用牛逼闪闪的Android Gradle构建系统啦。创建一个源文件夹,目录结构为 src/debug/java。这个目录中的代码仅仅是用于调试模式。这个目录结构和src/main/java很像,因为构建模式就是用于应用程序的调试的。(这句话再想想)。然后添加一个[Stetho][5]主页上描述的 [Application][6]。 24 | 25 | import com.facebook.stetho.Stetho; 26 | 27 | public class MyDebugApplication extends MyApplication { 28 | @Override 29 | public void onCreate() { 30 | super.onCreate(); 31 | Stetho.initialize( 32 | Stetho.newInitializerBuilder(this) 33 | .enableDumpapp(Stetho.defaultDumperPluginsProvider(this)) 34 | .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this)) 35 | .build()); 36 | } 37 | } 38 | 看清楚这个类是怎样继承一个已经有的MyApplication.类的。这样写的确很方便,因为你的应用里可能已经有一个application来进行其他的初始化了。如果你还没有一个application。你从android.app.Application.继承一个就行了。 39 | 40 | #激活我的调试应用 41 | 42 | 43 | 最后一步,我们要做的工具是确保当前的应用的调试版本使用的是MyDebugApplication类。在这里我们用Gradle来验证。在src/debug文件夹中添加一个AndroidManifest.xml 44 | 45 | 46 | 50 | 51 | 54 | 55 | 56 | 57 | 这个AndroidManifest.xml会合并到src/main中的主AndroidManifest.xml文件里,并且会替换标签中的android:name属性。这是因为我们特别添加上了tools:replace="android:name"属性。真屌。 58 | 59 | 60 | 现在当你启动程序的调试模式,[Stetho][8]就会被激活。如果你切换到发布版本,Stetho绝对不会被激活,也看不到它的任何痕迹。如果想要不丢人,程序员要保证软件没有BUG。 61 | 62 | 63 | #总结 64 | 用Android Gradle构建系统,可以很容易的给你的应用添加更多的调试功能。这门手艺不仅可以用在[Stetho][10]上,还可以用于那些你希望仅仅在调试模式中添加的类库或者工具。 65 | 66 | 67 | [1]: https://github.com/facebook/stetho 68 | [2]: https://github.com/facebook/stetho 69 | [3]: http://developer.android.com/reference/android/app/Application.html 70 | [4]: https://github.com/facebook/stetho 71 | [5]: https://github.com/facebook/stetho 72 | [6]: http://developer.android.com/reference/android/app/Application.html 73 | [7]: https://github.com/facebook/stetho 74 | [8]: https://github.com/facebook/stetho 75 | [9]: https://github.com/facebook/stetho 76 | [10]: https://github.com/facebook/stetho 77 | [11]:http://littlerobots.nl/blog/stetho-for-android-debug-builds-only/ 78 | -------------------------------------------------------------------------------- /androidweekly/安卓的模糊视图/readme.md: -------------------------------------------------------------------------------- 1 | 安卓的模糊视图 2 | --- 3 | 4 | > 5 | * 原文链接 : [A Blurring View for Android](http://developers.500px.com/2015/03/17/a-blurring-view-for-android.html) 6 | * 作者 : [Jun Luo](https://500px.com/junluo) 7 | * 译者 : [lvtea0105](https://github.com/lvtea0105) 8 | * 校对者: [bboyfeiyu](https://github.com/bboyfeiyu) 9 | * 校对者: [chaossss](https://github.com/chaossss) 10 | * 状态 : 校对完成 11 | 12 | 13 | 14 | 15 | 模糊效果可以生动地表现出内容的层次感,当使用者关注重点内容,即便在模糊表面之下发生视差效果或者动态改变,也能够保持当前背景。 16 | 17 | 在IOS设备中,我们首先构造一个UIVisualEffectView,之后添加 visualEffectView 到view层,在view中可以进行动态的模糊。 18 | 19 | ```java 20 | UIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 21 | 22 | UIVisualEffectView *visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; 23 | ``` 24 | 25 | ## 安卓中的表现形式 26 | 27 | 我们在雅虎天气APP中确实看到了很好的模糊效果实例,但是根据 [Nicholas Pomepuy 的博客帖子](http://nicolaspomepuy.fr/blur-effect-for-android-design/),然而,这个 App 是通过缓存一张预渲染的背景图片来实现图片虚化的。 28 | 29 | 虽然这种方法非常有效,但是确实不符合我们的需求,在 [500px](https://500px.com/) 的APP中,图像通常是获得焦点的内容,而不仅仅是提供背景,这说明图像的变化很大且迅速,即便他们是在模糊层之下。在我们的[安卓APP](https://play.google.com/store/apps/details?id=com.fivehundredpx.viewer) 中即是一个恰当的例子:当用户滑动至下一页时,整排图片会以相反方向淡出,为了组成所需的模糊效果,适当地管理多个预渲染图是困难的。 30 | 31 | ![](http://developers.500px.com/images/2015-03-17-500px-android-tour-blurring.png) 32 | 33 | ## 一种绘制模糊视图的方法 ## 34 | 35 | 我们需要的效果是,实时地模糊其下的视图,最终需要给界面的是模糊视图的一个 blurred view 引用。 36 | 37 | ```java 38 | blurringView.setBlurredView(blurredView); 39 | ``` 40 | 41 | 之后当blurred view 改变时,不管是因为内容改变(比如呈现新的图片)、view的变换还是处在动画过程,我们都要刷新 blurring view。 42 | 43 | ```java 44 | blurringView.invalidate(); 45 | ``` 46 | 47 | 为了实现 blurring view, 我们需要继承 view类并重写 onDraw()方法来渲染模糊效果。 48 | 49 | ```java 50 | protected void onDraw(Canvas canvas) { 51 | super.onDraw(canvas); 52 | 53 | // Use the blurred view’s draw() method to draw on a private canvas. 54 | mBlurredView.draw(mBlurringCanvas); 55 | 56 | // Blur the bitmap backing the private canvas into mBlurredBitmap 57 | blur(); 58 | 59 | // Draw mBlurredBitmap with transformations on the blurring view’s main canvas. 60 | canvas.save(); 61 | canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY()); 62 | canvas.scale(DOWNSAMPLE_FACTOR, DOWNSAMPLE_FACTOR); 63 | canvas.drawBitmap(mBlurredBitmap, 0, 0, null); 64 | canvas.restore(); 65 | } 66 | ``` 67 | 68 | 这里的关键点在于,当模糊视图重绘时,它使用blurred view 的draw()方法,模糊视图保持blurred view的引用,它绘制一个私有的,以bitmap作为背景的画布。 69 | 70 | ```java 71 | mBlurredView.draw(mBlurringCanvas); 72 | ``` 73 | 74 | 这种使用另一个视图的 draw()方法,也适用于建立放大或者个性的UI界面,在其中,放大区域的内容是扩大的,而不是模糊的。 75 | 76 | 根据 [Nicholas Pomepuy 讨论](http://nicolaspomepuy.fr/blur-effect-for-android-design/) 的想法,我们使用子采样和 [渲染脚本](http://developer.android.com/guide/topics/renderscript/compute.html) 进行快速绘制,当初始化blurring view的私有画布mBlurringCanvas 后,子采样就已经完成了。 77 | 78 | ```java 79 | int scaledWidth = mBlurredView.getWidth() / DOWNSAMPLE_FACTOR; 80 | int scaledHeight = mBlurredView.getHeight() / DOWNSAMPLE_FACTOR; 81 | 82 | mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888); 83 | mBlurringCanvas = new Canvas(mBitmapToBlur); 84 | ``` 85 | 86 | 通过mBlurringCanvas的 建立与恰当的渲染脚本初始化,重绘时的blur()方法如下: 87 | 88 | ```java 89 | mBlurInput.copyFrom(mBitmapToBlur); 90 | mBlurScript.setInput(mBlurInput); 91 | mBlurScript.forEach(mBlurOutput); 92 | mBlurOutput.copyTo(mBlurredBitmap); 93 | ``` 94 | 95 | 此时 mBlurredBitmap 已准备好,剩下的就是使用适当的变换和缩放,在blurring view自己的画布上重绘视图。 96 | 97 | ## 实现细节 98 | 99 | 完整实现blurring view 时,我们需要注意几个技术点: 100 | 101 | 一:我们发现缩放因子8,模糊半径15 很好地满足我们的目标,但满足你需求的参数可能是不同的。 102 | 103 | 二:在模糊的bitmap边缘会遇到一些渲染脚本效果,我们将缩放的宽度和高度进行了圆角化,直到最近的4的倍数。 104 | 105 | ```java 106 | // The rounding-off here is for suppressing RenderScript artifacts at the edge. 107 | scaledWidth = scaledWidth - (scaledWidth % 4) + 4; 108 | scaledHeight = scaledHeight - (scaledHeight % 4) + 4; 109 | ``` 110 | 111 | 三:为了保证更好的表现效果,我们新建两个bitmap对象——mBitmapToBlur 和 mBlurredBitmap , 112 | mBitmapToBlur 位于私有画布mBlurringCanvas 之下, mBlurredBitmap 仅当blurred view的大小变化时才重新建立他们; 113 | 同样地当blurred view的大小改变时,我们才创建渲染脚本对象mBlurInput 和 mBlurOutput。 114 | 115 | 四:我们以with PorterDuff.Mode.OVERLAY 模式绘制一个白色半透明的层,它处在被模糊的的图片上层。用来处理设计需求中的淡化。 116 | 117 | 最后,因为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) 告诉我这一点) 118 | 119 | ## 优点和缺点 120 | 121 | 我们喜欢这个view的绘制方法,因为它可以实时模糊并且容易使用,使得blurred view 的内容,也在blurring view 和blurred view中间保证了灵活性,最重要的是,它满足了我们的需求 122 | 123 | 这个方法确实使得blurring view 与适当协同变换的 blurred view 保持了私有联系,相关地,模糊视图必须不能是blurred view的子类,否则会因为互相调用造成堆栈溢出。简单有效处理此限制的方法是要保证模糊视图与blurred view 在同级,并且Z轴次序上 blurred view 在blurring view 之前。 124 | 125 | 另一点需要注意的限制是,由于与 矢量图形和文本 有关,我们默认的bitmap削减采样表现效果不是很好。 126 | 127 | ## 库文件和示例 128 | 129 | 你可以在我们的安卓 [APP](https://play.google.com/store/apps/details?id=com.fivehundredpx.viewer) 上看到解决方法,我们也把开源的库文件连同一个示例分享到了 [github](https://github.com/500px/500px-android-blur),它能够展示内容变换、动画和视图变换。 130 | 131 | ![](https://github.com/500px/500px-android-blur/raw/master/blurdemo.gif) 132 | 133 | 有关这个效果在 [HackNews](https://news.ycombinator.com/item?id=9219097) 中的讨论 -------------------------------------------------------------------------------- /androidweekly/欢迎来到Android多进程时代/readme.md: -------------------------------------------------------------------------------- 1 | 欢迎来到Android多进程时代 2 | --- 3 | 4 | > 5 | * 原文标题 : Going multiprocess on Android 6 | * 原文链接 : [Going multiprocess on Android](https://medium.com/@rotxed/going-multiprocess-on-android-52975ed8863c) 7 | * 译者 : [Lollypo](https://github.com/Lollypo) 8 | * 校对者: [Mr.Simple](https://github.com/bboyfeiyu) 9 | * 状态 : 完成 10 | 11 | ###That moment when one Dalvik alone is no longer enough. 12 | 13 | 14 | 生活在内存限制中 15 | --- 16 | 17 | 有很多方面使得Android成为一个独特的移动平台操作系统,但有时候却让人觉得难以融入,特别是从开发人员的角度看。 18 | 19 | 例如,把内存限制。iOS应用程序提供几乎没有限制的内存预算(200 MB不是什么大不了的事),Android有严重的局限性,从最近设备的24/32/48 MB以及旧设备极小的16 MB便可以看出。 20 | 21 | RAM预算就是一切你的应用运行时所能获得的全部了,这意味着,它必须满足加载类、线程、服务、资源和你的应用程序想要显示的内容。想象一个通过网格视图展示优美图片的照片浏览应用,或一个需要在后台播放的音乐播放器:这太恐怖了 22 | 23 | > 那时候你的体会应该是这样的 24 | 25 | ![Life’s a bitch, sometimes.](http://7xi8kj.com1.z0.glb.clouddn.com/img01.gif) 26 | 27 | 要理解为什么Android提出了这些限制以及提供了什么解决方案来应对他们,我们需要知道一点点在这背后之后发生了些什么。 28 | 29 | 理解Android进程 30 | --- 31 | 32 | 你应该已经知道了,安卓系统是基于Linux的。因此,每个应用程序都运行在其本身的进程(拥有一个独一无二的PID)中:这允许应用运行在一个相互隔离的环境中,不能被其他应用程序/进程干扰。通常来说,当你想启动一个应用程序,Android创建一个进程(从Zygote中fork出来的),并创建一个主线程,然后开始运行Main Activity。 33 | 34 | 你可能不知道的是,你可以指定应用程序的一些组件运行在不同的进程中,而不是那个被用于启动应用程序的。先来看一下这个Nice的属性: 35 |
36 | ```xml 37 | android:process 38 | ``` 39 |
40 | 41 | 该进程属性可用于activities、services、content providers和broadcast receivers 和指定的进程中应该执行的特定组件。 42 | 43 | 在这个例子中,我指定MusicService必须执行在一个单独的“music”的进程: 44 | ```xml 45 | 46 | 50 | 51 | 54 | 58 | 59 | 60 | ``` 61 | 62 | 它有什么意义呢? 63 | 64 | 在这个简短的介绍中,我提到了每一个Android应用程序在运行的时候都有一个不能超出的内存预算值。更精确的说,这限制了它只能在单个基础的进程上运行。换句话说,应用程序的每一个进程都将会有一个专门的内存预算(更不用说其中止时也有更酷的不同的规则) 65 | 66 | 让我们看看这种方法将是一件好事还是坏事。(剧透:两者都是) 67 | 68 | 使用多进程有啥好处 69 | --- 70 | 71 | 正如我刚才提到的,一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。 72 | 73 | 此外,操作系统对待运行在不同组件中的进程是不一样的。这意味着,当系统运行在低可用内存的条件时,并不是所有的进程都会被杀死。想象一下:你的音乐播放器正在后台运行,音乐突然播放,系统需要释放一些内存(因为facebook,这就是原因)。由于播放音乐的服务跑在另一个进程中,一种极为可能的情况就是操作系统将会先杀死你的主进程(那个运行着你的UI的),而留下那个在另一个进程播放音乐的。 74 | 75 | 最后一点对于用户来说看起来似乎很不错!因为你的程序的每一个进程都有自身的在应用程序管理器上的屏幕显示RAM用度。其中有一个或多个将出现在“缓存”部分(这意味着它们是不活跃的)。 76 | 77 | > 正如你所看到的,Spotify在后台播放一些音乐。有一个活跃的带有服务的进程 [上图],而另一个进程(持有UI的)是缓存状态的,因为不再可见/不活动的[下图]。 78 | 79 | ![](http://7xi8kj.com1.z0.glb.clouddn.com/img02.png) 80 | ![](http://7xi8kj.com1.z0.glb.clouddn.com/img03.png) 81 | 82 | 83 | 使用多进程时的那些坑 84 | --- 85 | 86 | 不幸的是,坑有很多。事实上,你要学习拥有多个进程不是一下子就能完成的事 87 | 88 | 首先,进程是被设计成独立的(如安全特性),这意味着每一个进程将有自己的Dalvik VM实例。反过来,这意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。并且这延伸到应用程序每一个的状态。 89 | 90 | 这是否意味着两个独立的进程之间互相交流是不可能的吗?不,实际上是可能的,有几种方法可以做到。最值得注意的是,Intent可以跨进程“旅行”,Handlers和Messengers也可以。。你也可以依靠AIDL(Android接口定义语言)和Binder,和你通常声明一个bound service茶不错(但你可以做更多的事!)。 91 | 92 | 我需要使用多进程吗 93 | --- 94 | 95 | 当然,这取决于你需要查看到的迹象。如果你的用户正在经历越来越频繁OutOfMemory错误或者他们抱怨你的应用程序是极其消耗RAM,你可能需要考虑使用一个或多个独立的进程。 96 | 97 | 音乐播放器的例子是第二个进程能使你的App做的更好的其中最常见的一个场景,当然,还有更多。 98 | 例如,你的应用程序是一个客户端云存储服务:委托同步服务组件专用的进程似乎是完全合理的,所以即使UI会被系统杀死,服务仍然可以运行并且保持文件更新。 99 | 100 | > 类似的情况会发生在你第一次真正意识到进程隔离的意思时 101 | 102 | ![This happens when you first realize what “isolation between processes” really means.](http://7xi8kj.com1.z0.glb.clouddn.com/img04.gif) 103 | 104 | 如果你认为你需要它,那么我建议你先玩一个小试验台应用:只有通过实际体验过使用多个进程的优势和其内在的复杂性,你才能够决定你是否真的需要它,如果是这样,什么是最好的处理它的方式而不至于把我们逼疯。 105 | 106 | 结语 107 | --- 108 | 109 | 我知道我仅仅触及到这个问题的表面,我只是想给你一些实用的建议,而不是告诉你在操作系统层调控进程的全部理论与工作机制。 110 | 111 | 还是那句话,如果你对此感兴趣并愿意深入其中,那就留言让我知道!同时,不要忘记文档是你最好的朋友[[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) -------------------------------------------------------------------------------- /androidweekly/深入了解Android Graphics Pipeline-part-1/readme.md: -------------------------------------------------------------------------------- 1 | 深入了解Android Graphics Pipeline-part-1 2 | --- 3 | 4 | * 原文链接 : [Android Graphics Pipeline: From Button to Framebuffer (Part 1)](https://blog.inovex.de/android-graphics-pipeline-from-button-to-framebuffer-part-1/) 5 | * 作者 : [mgarbe](https://blog.inovex.de/author/mgarbe/) 6 | * 译者 : [dupengwei](https://github.com/dupengwei) 7 | * 校对者: [chaossss](https://github.com/chaossss) 8 | * 状态 : 完成 9 | 10 | 11 | 12 | 在这个小型博文系列中我们想给有兴趣研究 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这个有趣的世界。 13 | 14 | 请注意,本小型系列博文中会涉及大量的源代码和序列图!它值得一读,就算你对Android Graphics Pipeline一点兴趣也没有,你也可以学到很多(或者你至少看看这些漂亮的图片)。所以给自己一杯咖啡,读吧! 15 | 16 | ## 引言 17 | 18 | 为了充分理解view显示到屏幕的过程,我们采用一个小Demo来描述Android Graphics Pipeline的每个主要阶段,由Android Java API (SDK)开始,然后是本地C++代码,最后看原始的OpenGL绘图操作。 19 | 20 | 21 | ![one-button-layout-cropped](http://img.my.csdn.net/uploads/201504/09/1428538541_6558.png) 22 | 23 | 这个 Demo 非常值得我们研究,因为这个不起眼的 App 的代码就能充分覆盖整个 Android Graphics 的内部结构,所以,它实际上是一个相当好的例子。 24 | 25 | 这个activity由一个简单 RelativeLayout,一个带应用图标和标题 ActionBar 和一个读作“Hello world!”的简单 Button 组成。 26 | 27 | ![one-button-viewhierarchy](http://img.my.csdn.net/uploads/201504/09/1428538541_8737.png) 28 | 29 | 但从上图你也可以看到,我们这个简单的 Demo 的视图层实际上是相当复杂。 30 | 31 | 在Android 视图层内部,相对布局(RelativeLayout)由一个简单的颜色渐变背景组成。更复杂的, Actionbar 由一个渐变的背景结合一个bitmap,**One Button** 文本元素和应用图标(也是一个bitmap)组成。9-Patch用作按钮的背景,文本 **Hello World!** 画在它的最上层。屏幕顶部的导航栏和底部的状态栏不属于 App 的 Activity 部分,它们由系统服务`SystemUI`进行渲染。 32 | 33 | ## Pipeline概述 34 | 35 | 如果有看过Google I/O演讲**For Butter or Worse**,你肯定认识下面的幻灯片,因为它它显示了完整的 Android Graphics Pipeline 结构。 36 | 37 | ![pipeline](http://img.my.csdn.net/uploads/201504/09/1428538607_4861.png) 38 | 39 | 上图就是在 Google 在 Google I/O 2012 大会中提供的完整的 Android Graphics Pipeline 结构。 40 | 41 | Surface Flinger负责创建图形缓冲区并将其合成到主显示器,虽然这对应安卓系统非常重要,但是在此不作赘述。 42 | 43 | 相反,我们将注意力放在其他组件上,因为这些组件的主要任务就是将view显示到屏幕上。 44 | 45 | ![interesting_bits](http://img.my.csdn.net/uploads/201504/09/1428538541_4202.png) 46 | 47 | ## Display Lists 48 | 49 | 可能你已经知道,Android 使用一个叫做 DisplayLists 的概念去渲染所有的view。对于不知道的人来说,display list是一个绘图命令序列集合,需要执行这些命令去渲染指定的view。display lists是Android Graphics Pipeline达到高性能的重要元素。 50 | 51 | 52 | ![display-lists](http://img.my.csdn.net/uploads/201504/09/1428538540_9146.png) 53 | 54 | 每个视图层的 view 都有其对应的 display list,每个开发者都知道 `onDraw()` 方法,这个 display list 就是由 view 的 `onDraw()` 方法生成的。为了将视图层画到屏幕上,只有 display lists 需要被评估并执行。假如某个单一 view 失效(因用户输入、动画或转换),受影响的 display lists 将会重建和重绘。这就避免Android在每个frame层调用相当耗费资源的 `onDraw()`。 55 | 56 | 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. 57 | 58 | Display lists 也可以被嵌套,这意味着一个Display list也可以发出一个命令绘制一个子display list。这对复制视图层的display lists来说非常重要。毕竟,即使是最简单的 App 也会具有多个嵌套视图。 59 | 60 | 这些命令的语句的可以直接映射到OpenGL命令,如翻译和设置剪辑矩阵,或更复杂的命令如`DrawText` 和 `DrawPatch`,但这些复杂命令需要相应的OpenGL命令集,不然我们也无法使用。 61 | 62 | `An example of a display list for a button.` 63 | 64 | `一个按钮的display list示例` 65 | 66 | ```java 67 | Save 3 68 | DrawPatch 69 | Save 3 70 | ClipRect 20.00, 4.00, 99.00, 44.00, 1 71 | Translate 20.00, 12.00 72 | DrawText 9, 18, 9, 0.00, 19.00, 0x17e898 73 | Restore 74 | RestoreToCount 0 75 | ``` 76 | 77 | 在上面的示例中,你可以清楚地看到 display list 为绘制一个简单的按钮进行了什么操作。第一个操作是保存当前翻译矩阵到栈中,使得它随后可以被恢复。然后又画了9-Patch按钮,接下来是另外一个保存命令,这是必要的,因为对于要绘制的文本来说,只有绘制文本的区域才会创建裁剪矩阵。手机绘图处理器将此矩形区域当做一个线索以便在后续阶段对绘图调用进一步优化。然后将绘画圆点转换到文本位置,并绘制文本。最后,最初的转换矩阵和状态从堆中还原,裁剪矩阵也被重置。 78 | 79 | 在这篇文章的底部可以看到示例用的的display lists的完整日志。 80 | 81 | ## 深入代码 82 | 83 | 带着这些新获取的知识,我们准备深入研究代码。 84 | 85 | ### 根视图(Root View) 86 | 87 | 每个Android activity在视图层的最顶层都有一个隐式根视图(Root View),包含一个子view。这个子view是程序员在应用中定义的第一个真正的view。根视图负责调度和执行多种操作,如绘图,使view无效等等。 88 | 89 | 类似的,每个view都有一个parent的引用。视图层内部的第一个view将根视图作为parent。虽然View类作为每个可视化的元素或组件的基类,但是它并不支持任何子类。然而,其派生类ViewGroup支持多子类,并作为一个容器基类,它已经被标准布局(RelativeLayout 等等)所采用。 90 | 91 | 如果一个view局部重绘,那么该view会调用根视图的invalidateChildInParent()方法。根视图跟踪所有重绘的区域,并在choreographer调度一个新的遍历,choreographer会在下一个VSync事件执行。 92 | 93 | ![view-invalidate](http://img.my.csdn.net/uploads/201504/09/1428538608_9350.png) 94 | 95 | ViewRoot: InvalidateChildInParent(…) 96 | 97 | ```java 98 | public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 99 | // Add the new dirty rect to the current one 100 | mDirty.union(dirty.left, dirty.top, 101 | dirty.right, dirty.bottom); 102 | // Already scheduled? 103 | if (!mWillDrawSoon) { 104 | scheduleTraversals(); 105 | } 106 | return null; 107 | } 108 | ``` 109 | 110 | ## 创建Display Lists 111 | 112 | 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. 113 | 114 | 正如刚刚提到的,每个view负责生产自己的display list。当一个VSync事件被触发,choreographer调用根视图的`performTraversals`方法,根视图要求`HardwareRenderer`绘制view,`HardwareRenderer`反过来要求view生成自己的display list。 115 | 116 | ![perform-traversals1](http://img.my.csdn.net/uploads/201504/09/1428538542_9748.png) 117 | 118 | 在Android framework层中,View是其中一个比较大的类,当前代码量将近20000行。这并不令人惊奇,因为它是每个小组件和应用的构建块。它处理键盘、轨迹球、触摸事件,以及滚动,滚动条,布局和测量,还有很多很多。 119 | 120 | ![view-getdisplaylist](http://img.my.csdn.net/uploads/201504/09/1428538608_4788.png) 121 | 122 | `View.getDisplayList(…)`方法被Hardware Renderer调用时将会创建一个内部的display list,这个内部display list将会在view生命周期的剩余部分被使用。然后这个内部display list被要求提供一个足够大的画布来容纳这个view。并提供一个GLES20RecordingCanvas,所有view和它的children将会在上面绘图,然后传递给`draw(…)`方法。这个画布有点特殊,因为它不执行绘图命令,而是保存命令到display list。这意味着小组件和每个view能使用正常的绘图API,甚至无须关注display list内部的命令 123 | 124 | `View: getDisplayList(…)` 125 | ```java 126 | private DisplayList getDisplayList(DisplayList displayList, 127 | boolean isLayer) { 128 | HardwareCanvas canvas = displayList.start(width, height); 129 | if (!isLayer && layerType != LAYER_TYPE_NONE) { 130 | // Layers don't get drawn via a display list 131 | } else { 132 | draw(canvas); 133 | } 134 | return displayList; 135 | } 136 | ``` 137 | 138 | 在`draw(…)`方法中,view将执行`onDraw()`方法的代码,渲染自己到画布上。如果这个view有任何children,children各自调用`draw()`方法。这些children可以是任何东西,可以是一个正常的按钮,也可以是一个布局或view group,这些children包含另外的children,都将被绘制。 139 | 140 | ![view-draw](http://img.my.csdn.net/uploads/201504/09/1428538608_8013.png) 141 | 142 | ## 寄语 143 | 144 | 伴随着display list的产生,part 1结束了。如果你对这系列博文有兴趣的话,请关注 part 2,届时我们会知道 display lists 将如何被呈现在屏幕上! 145 | 146 | ## 下载 147 | 148 | The full Bachelor’s Thesis on which this article is based is available for download. 149 | 150 | 本文参考的所有学士论文可供[下载](http://mathias-garbe.de/files/introduction-android-graphics.pdf) 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /androidweekly/让你的Android应用能使用多种主题-Part-1/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/androidweekly/让你的Android应用能使用多种主题-Part-1/.DS_Store -------------------------------------------------------------------------------- /androidweekly/让你的Android应用能使用多种主题-Part-1/images/multiple-theme-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/androidweekly/让你的Android应用能使用多种主题-Part-1/images/multiple-theme-dark.png -------------------------------------------------------------------------------- /androidweekly/让你的Android应用能使用多种主题-Part-1/images/multiple-theme-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/androidweekly/让你的Android应用能使用多种主题-Part-1/images/multiple-theme-light.png -------------------------------------------------------------------------------- /androidweekly/让你的Android应用能使用多种主题-Part-1/readme.md: -------------------------------------------------------------------------------- 1 | 在你的Android App中支持多主题 2 | --- 3 | 4 | > 5 | * 原文链接 : [Supporting multiple themes in your Android app (Part 1)](http://www.hidroh.com/2015/02/16/support-multiple-themes-android-app/) 6 | * 译者 : [chaossss](https://github.com/chaossss) 7 | * 校对者: [Mr.Simple](https://github.com/bboyfeiyu) 8 | * 状态 : 完成 9 | 10 | 11 | ![](images/multiple-theme-dark.png) 12 | ![](images/multiple-theme-light.png) 13 | 14 | 我最近一直在忙着整我的黑客资讯App——Materialistic,今天难得有空,就让我来给大家分享一下我在Materialistic里使用的一个有趣的功能吧。 15 | 16 | 纵观现在的主流阅读类App,用户最常见的需求就是能够基于自己的阅读习惯选择明亮/灰暗两种风格的主题。为了用户的使用体验,我当然要为Materialistic添加这样的功能啦,要不然没人用我会很伤心的!而且很幸运的是,在Android里支持多种主题的切换并不麻烦(如果你的代码没有问题的话),实现这个功能蛮顺利的。所以今天我打算通过这篇博客给大家介绍我在Materialistic里面为了支持多种主题切换所使用的方法。 17 | 18 | 准备工作: 19 | 20 | 1. 你最少要有两个由Android 基本的light/dark主题衍生而来的主题。如果你使用了最新的appcompat-v7包,所对应的就是Theme.AppCompat.Light 或 Theme.AppCompat.Light.DarkActionBar(明亮风格),和Theme.AppCompat(灰暗风格)主题 21 | 22 | 1. 你需要为你的主题设置颜色。你可以在 [Google design spec](http://www.google.com/design/spec/style/color.html#color-color-palette " Google design spec website") 里面看到有关颜色搭配的指导 23 | 24 | 1. (可选项)为每一个主题的选项菜单图标加上颜色。取决于你的实现方式,染色过程可以是自动的,也可以是手动的,不过自动化的过程不就意味着你可以把一套图标应用于一种主题嘛,其他的调整只要改改颜色就可以了;但就Materialistic的实际需求来考虑,我还是为一个主题预留了多套不同的图标来避免麻烦…… 25 | 26 | 我今天就以明亮风格的主题来开始讲解吧: 27 | 28 | ## values/styles.xml ## 29 | 30 | ```xml 31 | 42 | ``` 43 | 44 | ## values/colors.xml ## 45 | 46 | ```xml 47 | 48 | #FF9800 49 | #F57C00 50 | #FFE0B2 51 | 52 | #FF5252 53 | 54 | #FFFFFF 55 | #9E9E9E 56 | 57 | #DE000000 58 | #9E9E9E 59 | ``` 60 | 61 | ## AndroidManifest.xml ## 62 | 63 | ```xml 64 | 65 | ... 66 | 67 | ``` 68 | 69 | theme 中涉及的各种属性的含义可以在[Android Developers blog](http://android-developers.blogspot.sg/2014/10/appcompat-v21-material-design-for-pre.html "Android Developers blog") 里面找到解释 70 | 71 | ## 贴心小提示 ## 72 | 73 | > 虽然Android里面style的属性/值非常全面,我们想要实现的效果style基本上都包含了有,但是Android文档有关这些主题属性的解释特别少,尤其是对appcompat的解释。所以我们还是建议你写一个小Demo去测试style里面的属性/值应该怎么使用、能实现什么样的效果,然后再根据我们的需求去考虑使用哪些属性/值来实现我们想要的效果。 74 | 75 | 根据Android的Material Design规范,选项菜单图标的颜色应该和action bar上面的文字颜色保持一致,在我这是通过 android:textColorPrimary 来实现的,也就是使用#FFFFFF,基于这样的规范,我们需要为action bar提供一套白色的选项菜单图标。 76 | 77 | ## 贴心小提示 ## 78 | 79 | > Google 有在 [material-design-icons - Github](https://github.com/google/material-design-icons "Github") 上提供一些开源的Material Design图标哦。 80 | 81 | ## menu/my_menu.xml ## 82 | 83 | ```xml 84 | 85 | 87 | 89 | 91 | 92 | ``` 93 | 94 | 为了使颜色一致,并且能让我们的Views和Texts能够在多个主题下被使用,最好的解决办法就是把颜色变成资源的引用,例如:android:textColor="@color/textColorPrimary;又或者是通过设置style来改变,例如:在textEmptyStyle.xml文件下,我们只使用被选中的颜色 95 | 96 | ## values/styles.xml ## 97 | 98 | ```xml 99 | 104 | ``` 105 | 106 | 我相信通过今天在上面所介绍的这些内容已经足够让我们实现一个符合Material Design的明亮风格的主题了,下一篇博文我将会给大家介绍如何实现一个符合Material Design的灰暗风格的主题,以及如何在运行App的过程中切换主题。希望大家继续关注我的博客哦。 107 | -------------------------------------------------------------------------------- /androidweekly/让你的Android应用能使用多种主题-Part-2/readme.md: -------------------------------------------------------------------------------- 1 | 让你的Android应用能使用多种主题 ( Part 2 ) 2 | --- 3 | 4 | > 5 | * 原文链接 : [Supporting multiple themes in your Android app (Part 2)](http://www.hidroh.com/2015/02/25/support-multiple-themes-android-app-part-2/) 6 | * 译者 : [chaossss](https://github.com/chaossss) 7 | * 校对者: [Mr.Simple](https://github.com/bboyfeiyu) 8 | * 状态 : 完成 9 | 10 | 在 [上一篇博文](http://www.hidroh.com/2015/02/16/support-multiple-themes-android-app/) 中,我们创建了一个明亮风格的主题,并且为实现使用多种主题作了一些前期的准备,而今天呢,我打算在这篇博文中接着上一篇博文继续为大家讲解,而我今天要讲的内容大概是以下三个部分:使 Android 应用能够使用多种主题,创建一个灰暗风格的主题,以及允许 Android 应用在运行时自由地切换不同的主题。 11 | 12 | 在理想的情况下,如果我们把主题的设置看作是一项配置,那么我们应该能够在类似 "theme-qualifier" 的目录下指定我们想要的特定主题,例如:values-dark 就是我们想要的灰暗风格主题;而values-light 则是明亮风格的主题。但很遗憾,在这篇博文所要讲述的实现方法里,这种方法并没有成为实现方式之一。 13 | 14 | 那么我们要怎么为不同的主题指定相应的资源文件呢?如果我们有了解过 appcompat 是怎么使用资源文件的话,对 Android 系统是如何管理和使用资源文件会有一个粗略的认识。毫无疑问,[Materialistic](https://play.google.com/store/apps/details?id=io.github.hidroh.materialistic) 中使用的方法就是类似于 Android 系统使用的方法。 15 | 16 | 17 | ## 主题设置 ## 18 | 19 | **values/styles.xml** 20 | 21 | ```xml 22 | 26 | 27 | 37 | ``` 38 | 39 | **values/color.xml** 40 | 41 | ```xml 42 | 43 | ... 44 | 45 | ... 46 | ... 47 | ... 48 | 49 | ``` 50 | 51 | 52 | 在上面的操作中我们创建了一个名叫 AppTheme.Dark 的灰暗风格主题,此外,为了保持 style 和 color 的一致性,我们的 AppTheme.Dark 主题衍生于 appcompat 的 Theme.AppCompat 主题(一个 Android 自带的灰暗风格主题)。然而,由于我们的两个主题(明亮风格和灰暗风格)衍生于不同的基础主题,因此这两个主题之间并不能够进行属性的共享(在JAVA中,类只能进行单继承)。 53 | 54 | 这两个主题理应有一些恰当的属性值,能同时用于设置基本的 Android 和 appcompat的主题属性,例如:在灰暗风格中,android:textColorPrimary 应该被设置为明亮的,而在明亮风格中,android:textColorPrimary则应该是灰暗的。按照常用的命名习惯,我们在这里将用相反的后缀来区分可替代的主题颜色。 55 | 56 | ## 温馨小提示 57 | 58 | > 在某些情况下,一种颜色能同时在明亮风格和灰暗风格的主题中被使用,这当然是喜闻乐见的情况,但是在大部分主题中这并不能够实现。所以我希望你在设计主题的过程中,通过在 AndroidManifest.xml 中短暂地切换你应用里正在使用的可替代主题,以此确定你的主题是否需要添加其他的 colors/style 文件来满足你的主题设计需求。 59 | 60 | 61 | ## 特定的主题资源文件 62 | 63 | 到了现在,我相信我们都能很轻松地为我们的 App 设计出美如画的灰暗风格主题,但这里还存在一些小麻烦,例如:用于美化 action bar 菜单选项的 drawables 资源文件。灰暗风格的 action bar 需要用明亮的颜色修饰它的菜单选项,反之亦然。为了让 Android 能够在不同的App主题下区分不同的 drawables 资源文件,我们创建了能够指定正确资源文件的 [自定义属性](http://developer.android.com/training/custom-views/create-view.html#customattr) 引用,并且在不同的主题下提供了不同的 drawable 引用,将其值赋给特定的自定义属性。(温婉如妻,appcompat 库贴心地为我们准备了类似 colorPrimary 的自定义属性值) 64 | 65 | **values/attrs.xml** 66 | 67 | ```xml 68 | 69 | 70 | 71 | ... 72 | ``` 73 | 74 | **values/styles.xml** 75 | 76 | ```xml 77 | 78 | 84 | 85 | 91 | ``` 92 | 93 | **menu/my_menu.xml** 94 | 95 | ```xml 96 | 97 | 99 | 101 | 103 | 104 | ``` 105 | 106 | 根据你实际的主题设计需要,类似的实现也能被用于为大多数自定义属性指定相应的资源值。但这个方法存在一个问题:根据实际的需要从 drawable 资源文件中解析相应的属性值,并应用于主题的方法在API 21之前的版本似乎都不可行。举例来说明这个问题吧:如果你有一个 layer-list 中包含了各种你所需要的 color 的 drawable 资源文件,在API 21之前的版本中,这些 color 的值都应该是固定的,而不是能够在App运行过程中不断变化的。这个问题在 Google I/O 2014 大会上有被提出,并要求给出相应的解决办法。(详情参见 [Click Me!](https://github.com/google/iosched/commit/dd7ed72a7eb2d223203db079bd99d31c6ef3061e))。 107 | 108 | 此外,为了避免在不同的主题中重复使用相同的资源文件,我们可以利用 drawable 的 tint 属性解决这个需求。虽然这个属性可以在API 21之后的版本中使用。但 [Dan Lew](http://blog.danlew.net/2014/08/18/fast-android-asset-theming-with-colorfilter/) 在他的博客中为我们介绍了怎么在所有的 API 版本中使用 tint 属性。但就个人偏好来说,如果可以的话,我会更倾向于选择不受 View 逻辑影响的 Java 实现,所以我选择为每一个主题提供不同的 drawable 资源文件。 109 | 110 | ## 动态主题切换 111 | 112 | 现在我们已经有两个可以使用的主题了,接下来我们需要做的就是让用户能够在使用 App 时能够自如地根据他们的个人偏好切换不同的主题。要实现这个功能,我们可以通过使用 SharedPreferences 来实现,通过改变 pref_dark_theme 的值去存储当前被选择的主题并决定我们的 App 将要被切换成什么主题。但从实际情况来考虑,主题切换后,App 所有 Activity 的 View 在被创建之前都应该被改变,所以我们只需要在 onCreate()方法中实现我们的逻辑。 113 | 114 | **BaseActivity.java** 115 | 116 | ```java 117 | public abstract class BaseActivity extends ActionBarActivity { 118 | @Override 119 | protected void onCreate(Bundle savedInstanceState) { 120 | if (PreferenceManager.getDefaultSharedPreferences(this) 121 | .getBoolean("pref_dark_theme"), false)) { 122 | setTheme(R.style.AppTheme_Dark); 123 | } 124 | super.onCreate(savedInstanceState); 125 | } 126 | } 127 | ``` 128 | 129 | 在这里,因为我们的 App 已经使用了默认的明亮风格主题,所以我们只需要检查默认的引用是否被重载,是否被用于重载灰暗风格的主题。为了默认的引用能够被所有 Activity共享,其中的逻辑已经在 "base" Activity中被写好了。 130 | 131 | 值得注意的是,这个方法只能被用于改变没有处在 [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。 132 | 133 | ## SettingsFragment.java ## 134 | 135 | ```java 136 | public class SettingsFragment extends PreferenceFragment { 137 | ... 138 | 139 | @Override 140 | public void onActivityCreated(Bundle savedInstanceState) { 141 | super.onActivityCreated(savedInstanceState); 142 | 143 | mListener = new SharedPreferences.OnSharedPreferenceChangeListener() { 144 | @Override 145 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 146 | if (!key.equals("pref_dark_theme")) { 147 | return; 148 | } 149 | 150 | getActivity().finish(); 151 | 152 | final Intent intent = getActivity().getIntent(); 153 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK); 154 | getActivity().startActivity(intent); 155 | } 156 | }; 157 | } 158 | 159 | ... 160 | ``` 161 | 162 | 虽然结束得有些突然,但我们今天的讲解就到此结束啦。现在我们的 App 拥有了两个这么优雅的主题,就算是挑剔的文艺小清新也不会嫌弃我们的 App 很 low 了吧!如果你想要了解整个 Materialistic 的具体实现,或者是这个功能的源码,可以来我的 [GitHub](https://github.com/hidroh/materialistic) 上获取哦~ -------------------------------------------------------------------------------- /authorization.md: -------------------------------------------------------------------------------- 1 | # 作者授权列表 2 | Android开发技术前线翻译的文章都尽可能的找到原作者的联系方式,然后获得文章作者的翻译授权,原文也将保留原文链接和作者名。下面是目前已获得授权的作者列表。 3 | 4 | | 作者 | 博客地址 | 授权状态 | 联系人 |备注 | 5 | |-------|---------|-----------|-----------|--------| 6 | | Romain Guy | http://www.curious-creature.com/ | 已授权 | Mr.Simple | email | 7 | | Mark Allison | https://blog.stylingandroid.com/ | 已授权 | Mr.Simple| 需要遵循Creative Commons [Attribution-NonCommercial-ShareAlike 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/) 协议 | 8 | | Mark Richards | http://www.wmrichards.com/ | 沟通中 | Mr.Simple| 翻译的是《软件架构模式》免费电子书,需要跟O‘Reilly的编辑沟通 | 9 | | Hannes Dorfmann | http://hannesdorfmann.com/ | 沟通中 | Mr.Simple| g+沟通中 | 10 | | Ravi Tamada | http://www.androidhive.info/ | 沟通中 | Mr.Simple| g+沟通中 | 11 | | nuunei | http://inthecheesefactory.com/blog | 沟通中 | Mr.Simple| 博客留言沟通中 | 12 | | inovex | [http://blog.inovex.de](http://blog.inovex.de) | 沟通中 | Mr.Simple| facebook留言沟通中 | 13 | | Alex Lockwood | [http://www.androiddesignpatterns.com/](http://www.androiddesignpatterns.com/) | 已授权 | Mr.Simple| google+ | 14 | | Robert Martin (Bob大叔) | [Clean Coder](https://sites.google.com/site/unclebobconsultingllc/) 和 [http://blog.8thlight.com/](http://blog.8thlight.com/) | 沟通中 | Mr.Simple| 沟通中 | 15 | | Zaitsev Serge | [Zaitsev Serge](http://zserge.com/blog.html) | 已授权 | Mr.Simple| 沟通中 | 16 | | flavien Laurent | [flavien Laurent](http://flavienlaurent.com/) | 已授权 | Mr.Simple| 沟通中 | 17 | 18 | -------------------------------------------------------------------------------- /git简单使用教程.md: -------------------------------------------------------------------------------- 1 | #git简单教程 (适用于参与开发技术前线) 2 | 3 | ## 1、git的安装与配置 4 | [Git详解之一:Git起步](http://blog.jobbole.com/25775/) 5 | 6 | ## 2、参与开发技术前线的项目 7 | 8 | 1. 首先到[iOS-tech-frontier](https://github.com/bboyfeiyu/iOS-tech-frontier),将该仓库fork(右上角)到你的个人账户下,因此你首先需要注册一个github账户; 9 | 2. 用命令行进入到你的某个目录下,然后输入如下命令 : `git clone https://github.com/这里替换为你的用户名/iOS-tech-frontier`将你fork的那份克隆下来; 10 | 3. 进入到iOS-tech-frontier目录中,在对应期数的文件夹中(例如,issue-2代表第二期)创建你的文章文件,例如:swift编程指南.md,注意文件名不要有空格; 11 | 4. 按照规范翻译文章,并且保留原文,每段英文下面跟一段译文; 12 | 5. 完成翻译之后,通过如下命令提交你的工作。首先提交到本地,`git add .`,然后`git commit -m "我翻译了xxx"`,最好通过`git push origin master`将你的翻译内容提交到github上; 13 | 6. 等待提交结束之后,你的提交也只是提交到了你个人的项目中,此时你需要向主仓库发一个pull request (简称 pr ) 请求,可参考[Fork + Pull模式](http://www.worldhello.net/gotgithub/04-work-with-others/010-fork-and-pull.html); 14 | 7. 发布pr之后管理员会安排人员进行文章校对,有问题的地方校对人员会在pr下进行评论,翻译人员确认问题之后修改问题即可; 15 | 8. 校对并且修改完之后翻译人员更新pr,管理员确认没有问题之后会合并该pr,整个翻译流程就此结束啦! 16 | 17 | 如果在这个过程中有冲突,翻译人员需要先解决冲突,可以参考[Git下的冲突解决](http://www.cnblogs.com/sinojelly/archive/2011/08/07/2130172.html)。 18 | 19 | ## 3、更详细的资料 20 | 21 | [git - 简易指南](http://www.bootcss.com/p/git-guide/) 22 | [pro git中文版](http://pan.baidu.com/s/1o6Hsets) -------------------------------------------------------------------------------- /issue-10/readme.md: -------------------------------------------------------------------------------- 1 | # 开发技术前线 第10期 2 | 3 | | 文章名称 | 译者 | 4 | |---------|--------| 5 | -------------------------------------------------------------------------------- /issue-7/Android-Lollipop-update.md: -------------------------------------------------------------------------------- 1 | #Android 5.1 Lollipop 升级版:Android 5.1.1面世 2 | 3 | > * 原文连接 : [android-5-1-lollipop-update](http://www.androidpit.com/android-5-1-lollipop-update) 4 | > * 作者:Loie Favre 5 | > * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 6 | > * 译者 : [Mr.Simple](https://www.github.com/bboyfeiyu) 7 | 8 | Android Lollipop自2014年推出一来,仅应用在少数几款设备上。大多数人在兴奋迎接Android5.0的时候视野已经瞄准了Android 5.1。现在让我们来一睹Android 5.1.1的芳容。请点击[Android 5.1 Lollipop](http://www.androidpit.com/topic/android-5-1-lollipop)查看所有升级资料(以及即将来袭的Android 5.1.1)。继续往下阅读,来看看哪些设备会使用这个最新系统。 9 | 10 | * [Android 6.0](http://www.androidpit.com/android-6-release-date-news-rumors): 一起创造终极Android OS 11 | 12 | * [Android 5.0 Lollipop update](http://www.androidpit.com/android-5-0-lollipop-phone-update-news):我的设备什么时候才能使用最新系统? 13 | 14 | ![pic-1](http://fs01.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-teaser-picture-2.jpg) 15 | 16 | Android 5.1发布日期及新特性曝光。/@ANDROIDPIT 17 | 18 | ###Android 5.1.1升级版已经到来了吗? 19 | 20 | 可以这么说。Android 5.1.1 Lollipop升级版尚未应用到手机上,但是Nexus Player的factory image已经曝光--这也是目前第一款应用5.1.1系统的设备。一旦Android 5.1.1升级版可以应用其他Nexus设备或摩托罗拉设备,我们会第一时间与大家分享最新消息。 21 | 22 | ###Android 5.1.1 Lollipop升级版:最新系统什么时候能用? 23 | 24 | 第一批使用Android 5.1升级版的设备为[T-Mobile](http://www.androidpit.com/topic/t-mobile) Nexus 6 和 [Verizon](http://www.androidpit.com/topic/verizon) Nexus 6,其他Nexus设备火速待定中。 25 | 26 | ####Google Nexus设备 27 | 28 | * [Nexus 6 Android 升级版最新消息](http://www.androidpit.com/nexus-6-android-update) 29 | * [Nexus 5 Android 升级版最新消息](http://www.androidpit.com/nexus-5-android-update) 30 | * [Nexus 4 Android 升级版最新消息](http://www.androidpit.com/nexus-4-android-update) 31 | * [Nexus 7 (2013) Android 升级版最新消息](http://www.androidpit.com/nexus-7-2013-android-update) 32 | 33 | [Nexus 9](http://www.androidpit.com/device/google-nexus-9)尚未进入支持Android 5.1升级之列。Google Play Edition的设备很快会进行Android 5.1.1的升级。 34 | 35 | ####摩托罗拉设备 36 | 37 | 摩托罗拉最新的中高端设备已确认支持升级到Android 5.1,而且第一代Moto X 和 Moto G也即将步入升级行列(原本的Moto X目前正在巴西进行Android 5.1系统升级测试)。Moto E的一、二代均处在等待系统升级之列。 38 | 39 | * [Moto X (2014) Android 升级版最新消息](http://www.androidpit.com/moto-x-2014-android-update) 40 | * [Moto X (2013) Android 升级版最新消息](http://www.androidpit.com/moto-x-2013-android-update) 41 | * [Moto G (2014) Android 升级版最新消息](http://www.androidpit.com/moto-g-2014-android-update) 42 | * [Moto G (LTE, 2013) Android 升级版最新消息](http://www.androidpit.com/moto-g-android-update) 43 | * [Moto E Android 升级版最新消息](http://www.androidpit.com/moto-e-android-update) 44 | 45 | ####Android One设备 46 | 47 | 大家可能还记得,最初是Android One提醒了我们Android 5.1的存在。Google搜索页面把Android 5.1 Lollipop列为Evercoss One X、Mito Impact 和 Nexian Journey的Android即用版。 48 | 49 | ###Android Wear 5.1 50 | 51 | [Android Wear 升级版](http://www.androidpit.com/android-wear-update)不久也会上线,新版特性包括挥手感控Android Wear智能手表(通过手腕控制通知栏,无需双手同时操作)、Wi-Fi、手绘emoji、程序切换器以及快速联系人搜索等。 52 | 53 | Android Wear智能手表通常依靠蓝牙与联网智能手机连接使用,但是大部分智能手表带有预装Wi-Fi芯片,但到目前为止,这块芯片仍处于睡眠状态,毫无用处。Android Wear 5.1升级版推出之后,这一Wi-Fi组件有望被激活。 54 | 55 | ###Android 5.1 Lollipop 升级版有哪些新特性? 56 | 57 | ####中断、优先级和静音模式 58 | 59 | 新版系统的一个最小但最重要的改进在于其优先级设置。在新系统下我们可以将中断偏好设置为保持原样,直到下次警报为止。这在避免隔夜中断情况或“故障时间”情况下非常有用。 60 | 61 | ![pic-2](http://fs04.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-interruptions.jpg) 62 | 63 | Android 5.1完善了Lollipop的优先级模式,但仍然未包含通知灯闪烁的静音模式。/@ANDROIDPIT 64 | 65 | 66 | ####屏幕固定 67 | 68 | 如果大家之前没体验过屏幕固定功能,那么现在可以试试。当别人在使用你的手机时,这是保护手机中信息隐私最好的办法。 69 | 70 | 屏幕固定是指将某个应用或某个文件夹锁定,当别人使用你的手机时,在未获得相应PIN码的情况下他不能退出当前应用。PIN码很容易设置,这样一来,使用你的手机的朋友、小孩或者伴侣在未获得你的允许的情况下就无法浏览你手机里的其他内容了。新系统中的这个功能更容易找到,并且说明更详细。在设置>安全隐私项下可以找到。 71 | 72 | 说到安全隐私... 73 | 74 | ![pic-3](http://fs01.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-screen-pinning.jpg) 75 | 76 | 屏幕固定激活后,我们可以从“最近应用”列表中进入。 77 | 78 | ####安全隐私 79 | 80 | Android 5.1一个新特性是锁定功能,即使手机被偷后它也能保护手机内信息安全。激活“设备保护”后,除非使用者拥有你的Google账号登陆信息,否则设备将不可用,直到恢复出厂设置。 81 | 82 | 可惜的是,这个功能可不一定能帮你把手机找回来,但是能防止别人盗用你手机里的信息这一点是肯定的,这也算是一个小小的安慰吧。 83 | 84 | 只要你手上有一台可用的设备(但遗憾的是目前还没确定是哪些设备),屏幕安全锁定(通过pattern、PIN码或密码)并且登陆了Google账号,你就可以自动激活这个功能。 85 | 86 | 那如何才能确定这个功能在手机上被激活了呢?目前我们使用的唯一方法就是解除锁屏安全模式,如果跳出“设备保护功能将关闭”的通知,那么我们在锁屏的时候就可以知道这个功能是否激活了(经过一番周折,终于了解了!)。 87 | 88 | ![pic-4](http://fs04.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-device-protection.jpg) 89 | 90 | 兼容设备上的设备保护功能会自动激活。/@ANDROIDPIT 91 | 92 | ####完善快速设置菜单 93 | 94 | 为了增强可用性,快速设置菜单囊括了所有的的全新动画,Wi-Fi微调以及蓝牙菜单。 95 | 96 | 选择Wi-Fi或蓝牙网络比之前更容易操作,按钮旁的下拉箭头可以直接从快速设置菜单中找出连接列表。这真是极好的。 97 | 98 | * [如何将Nexus 5的系统升级到Android 5.1 Lollipop](http://www.androidpit.com/how-to-get-android-5-1-lollipop-on-nexus-5) 99 | 100 | ![pic-5](http://fs01.androidpit.info/userfiles/6473479/image/android-5-1/androidpit-android-5-1-wi-fi-settings.jpg) 101 | 102 | 快速设置中新添加的下拉式Wi-Fi连接功能的用户反馈很好。/@ANDROIDPIT 103 | 104 | ####通话音质更好,支持双卡双待 105 | 106 | 尽管运营商本身为设备提供了高清的语音质量,在这里高清音质正式被直接编入Android 5.1代码中,也就意味着大家能够享受更高质量的通话体验。 107 | 108 | 其他值得一提的特性有... 109 | 110 | * 更好的双卡双待支持 111 | * Android 5.1 SDK提供了新的API支持(为迎接更好的app铺平道路) 112 | * 完善的通知中心管理(通知栏现在可以半滑动,而不是像之前那样完全滑到上方) 113 | * 更好的WiFi连接,Android现在知道哪个接入点的WiFi信号更好。聪明。 114 | 115 | 目前还没有确切的关于什么设备能用、什么时间能用新版系统的消息,大家怀着美好的期待等待新系统的到来吧。赶紧去设置>关于手机>软件更新,燥起来吧!如果你玩的是摩托罗拉的手机,Nexus设备或Google Play Edition,很有可能你就是第一个体验新系统的人哟。 116 | 117 | * [Android 5.0 problems and solutions](http://www.androidpit.com/android-5-0-lollipop-problems-and-solutions) 118 | 119 | ![pic-6](http://fs04.androidpit.info/userfiles/2692059/image/Blog/AndroidPIT-Galaxy-S5-Lollipop-KitKat.JPG) 120 | 121 | Android平台越来越优秀。/@ANDROIDPIT 122 | 123 | 你的设备已经升级到Android 5.1了吗?觉得怎么样? 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /issue-7/Android测试框架:Dagger2+Espresso2+Mockito/README.md: -------------------------------------------------------------------------------- 1 | ## Android测试框架: Dagger 2 + Espresso 2 + Mockito 2 | --- 3 | 4 | >* 原文链接 : [Dagger 2 + Espresso 2 + Mockito](http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html) 5 | > * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 6 | * 译者 : [yaoqinwei](https://github.com/yaoqinwei) 7 | * 校对者: [chaossss](https://github.com/chaossss) 8 | * 状态 : 完成 9 | 10 | 我一直在用Dagger, Espresso和Mockito做Android测试,爱死这个组合了!为了庆祝[Dagger 2](http://google.github.io/dagger/)的推出,我分享了一个用Dagger 2, Espresso 2和Mockito做Android测试的[Demo](https://github.com/chiuki/android-test-demo) 11 | 12 | ### Dagger 组件(Components) 13 | 14 | [Dependency injection(依赖注入)](http://en.wikipedia.org/wiki/Dependency_injection) 允许我们在App开发和测试中可以获取到不同的模块,非常有利于创建可重用的测试用例,这个Demo App的功能是以`"yyyy-MM-dd"`格式显示今天的日期,我们需要测试一下来应对一些已知的日期,而非依赖于运行测试时的真实日期。 15 | 16 | 在`Dagger 2`中,一个组件(Component)接口可以给整个App提供模块,并且定义了在哪注入它们。 17 | 18 | ```java 19 | public interface DemoComponent { 20 | void inject(MainActivity mainActivity); 21 | } 22 | 23 | @Singleton 24 | @Component(modules = ClockModule.class) 25 | public interface ApplicationComponent extends DemoComponent { 26 | } 27 | 28 | @Singleton 29 | @Component(modules = MockClockModule.class) 30 | public interface TestComponent extends DemoComponent { 31 | void inject(MainActivityTest mainActivityTest); 32 | } 33 | ``` 34 | 35 | **`ApplicationComponent`**组件用于App的正常运行, 而**`TestComponent`**组件则用于测试,这两个组件都可以被注入到**`MainActivity`**中。 36 | 37 | **`MainActivity`**如何知道使用的哪个组件(component)? 答案是通过**`DemoApplication`**来注入, 它保存着该组件(component)的引用。 38 | 39 | ```java 40 | private DemoComponent component = null; 41 | 42 | @Override 43 | public void onCreate() { 44 | super.onCreate(); 45 | if (component == null) { 46 | component = DaggerDemoApplication_ApplicationComponent 47 | .builder() 48 | .clockModule(new ClockModule()) 49 | .build(); 50 | } 51 | } 52 | 53 | public void setComponent(DemoComponent component) { 54 | this.component = component; 55 | } 56 | 57 | public DemoComponent component() { 58 | return component; 59 | } 60 | ``` 61 | 62 | 测试时,我们需要在**`onCreate()`**方法执行之前调用**`setComponent()`**方法,将组件设置为**`TestComponent`**。而App正常运行时,组件在**`onCreate()`**方法中就被设置为**`ApplicationComponent`**了。 63 | 64 | ### Mockito 65 | 66 | App中有一个**`Clock`**类,其中有一个方法可以返回当前的时间: 67 | 68 | ```java 69 | public DateTime getNow() { 70 | return new DateTime(); 71 | } 72 | ``` 73 | 74 | **`TestComponent`**组件中包含**`MockClockModule`**模块,后者使用[Mockito](http://mockito.org/)提供了一个模拟的**`Clock`**。这样[**`MainActivityTest`**](https://github.com/chiuki/android-test-demo/blob/master/app/src/androidTest/java/com/sqisland/android/test_demo/MainActivityTest.java)就可以在测试期间提供一个预先设置的日期了。 75 | 76 | ```java 77 | Mockito.when(clock.getNow()).thenReturn(new DateTime(2008, 9, 23, 0, 0, 0)); 78 | ``` 79 | 80 | 因为我们使用了单例, 相同的模拟**`Clock`**将为整个App提供日期,这样就能被显示提供的日期,而非今天的日期了: 81 | 82 | ```java 83 | onView(withId(R.id.date)).check(matches(withText("2008-09-23"))); 84 | ``` 85 | 86 | ### 更多 87 | 88 | 这里还有很多示例, 包括使用intent启动的activity的测试和使用JUnit测试的单元测试,请点击下面链接查看: 89 | 90 | [Click Me](https://github.com/chiuki/android-test-demo) 91 | 92 | 相关阅读: 93 | 94 | >* [Instrumentation Testing with Dagger, Mockito, and Espresso](http://engineering.circle.com/instrumentation-testing-with-dagger-mockito-and-espresso/) 95 | * [A JUnit @Rule which launches an activity when your test starts](https://gist.github.com/JakeWharton/1c2f2cadab2ddd97f9fb) 96 | * [EspressoStartGuide](https://code.google.com/p/android-test-kit/wiki/EspressoStartGuide) 97 | * [What’s new in Android Testing](http://wiebe-elsinga.com/blog/whats-new-in-android-testing/) 98 | * [https://github.com/googlesamples/android-testing](https://github.com/googlesamples/android-testing) 99 | -------------------------------------------------------------------------------- /issue-7/readme.md: -------------------------------------------------------------------------------- 1 | # 开发技术前线 第七期 周报 2 | 3 | ## 一、Android 4 | ### 1.1 技术文章 5 | | 文章名称 | 译者 | 6 | |---------|--------| 7 | | [使用Robolectric和Android生成代码覆盖率报告](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/使用Robolectric和Android生成代码覆盖率报告) | [normalme](https://github.com/normalme) | 8 | | [Retrofit开发指南](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Retrofit开发指南) | [yaoqinwei](https://github.com/yaoqinwei) | 9 | | [Android测试框架:Dagger2+Espresso2+Mockito](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Android测试框架:Dagger2+Espresso2+Mockito) | [yaoqinwei](https://github.com/yaoqinwei) | 10 | | [在Activity中使用Thread导致的内存泄漏](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/在Activity中使用Thread导致的内存泄漏) | [chaossss](https://github.com/chaossss) | 11 | | [深入浅出Android 新特性-Transition-Part-3a](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/深入浅出Android 新特性-Transition-Part-3a) | [tiiime](https://github.com/tiiime) | 12 | | [深入浅出Android 新特性-Transition-Part-3b](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/深入浅出Android 新特性-Transition-Part-3b) | [tiiime](https://github.com/tiiime) | 13 | | [Android Lollipop 5.1.1 更新](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Android Lollipop update.md) | [Mr.Simple](https://github.com/bboyfeiyu) 14 | | [Android Support 库 22.1版](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/Android Support 库 22.1版.md) | [Rocko](https://github.com/zhengxiaopeng) | 15 | 16 | ### 1.2 开源库 17 | 18 | * [自定义RecyclerView实现了listview、gridview、 瀑布流等样式](https://github.com/lucasr/twoway-view) 19 | 20 | * [一个类似iOS的Segment Control的控件](https://github.com/7heaven/SHSegmentControl) 21 | 22 | * [一个ViewIndicator](https://github.com/eccyan/SpinningTabStrip) 23 | 24 | 25 | ## 二、iOS 26 | ### 2.1 技术文章 27 | | 文章标题 | 译者 | 28 | |----------------------|------------------------| 29 | | [iOS编程101-如何生成圆形和圆角的图像](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/iOS编程101-如何生成圆形和圆角的图像.md) | [7heaven](https://github.com/7heaven) | 30 | | [CocoaPods指南](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/CocoaPods指南.md) | [Lollypo](https://github.com/Lollypo) | 31 | | [iOS开发-可滑动的单元格](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/iOS开发-可滑动的单元格.md) | [Harries Chen](https://github.com/mrchenhao) | 32 | | [View Debugging in Xcode 6 ](https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-1/View Debugging in Xcode 6.md) | [Mr.Simple](https://github.com/bboyfeiyu) | 33 | 34 | 35 | 36 | ### 2.2 开源库 37 | 38 | * [一个 AutoLayout 下自动计算 UITableViewCell 高度的扩展](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell) 39 | 40 | * [下拉刷新组件](https://github.com/CoderMJLee/MJRefresh) 41 | 42 | 43 | 44 | ## 三、每周推荐 45 | 46 | * [友盟微社区,一个可以嵌入App中的微博SDK](http://wsq.umeng.com/) 47 | * [FIR.Im —— 免费的App内测托管平台,支持IOS、Android应用](http://fir.im/) 48 | 49 | 50 | ## 四、涨薪机会 51 | 52 | | 公司 | 岗位 | 公司福利 | 详细信息 | 53 | |----|-----------------|----------------|---------------| 54 | | 友盟 | Android 架构师 | 氛围好,弹性工作时间,福利好,专业团队 | [详细说明](https://github.com/android-cn/android-jobs/blob/master/%E5%8C%97%E4%BA%AC--JD/%E5%8F%8B%E7%9B%9F%20Android.md) | 55 | 56 | 57 | 58 | ## 五、段子 59 | 60 | 1. 初中的时候,一个晚自习,前桌的一个心仪男生突然转身对我说:“10年后我一定会回来娶你。” 61 | 我一听,脸爆红,跟他玩得比较好,但没想到他会这么说...然后,他接着说,“回来取你狗命! 哈哈。。。”这么多年了,想起就无语. -------------------------------------------------------------------------------- /issue-7/使用Robolectric和Android生成代码覆盖率报告/readme.md: -------------------------------------------------------------------------------- 1 | 使用 Robolectric 和 Android 生成代码覆盖率(测试)报告 2 | --- 3 | 4 | > * 原文链接 : [Code coverage reports using Robolectric and Android](http://raptordigital.blogspot.com/2014/08/code-coverage-reports-using-robolectric.html) 5 | * 原文作者 : [Kris Vandermast](http://raptordigital.blogspot.com/) 6 | * 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) 7 | * 译者 : [normalme](https://github.com/normalme) 8 | * 校对者: [Mr.Simple](https://github.com/bboyfeiyu) 9 | * 状态 : 校对中 10 | 11 | #介绍 12 | 13 | 我写过许多测试驱动开发与陷阱方面的文章。我认为,其中对测试驱动开发中遇到的陷阱的描述让整个介绍更加完整。测试驱动开发或者通常的测试中,最重要的是你清楚代码中哪些部分经过了测试,而哪些部分需要继续测试。 14 | 15 | 你可以使用[JaCoCo](http://www.eclemma.org/jacoco/)搞定上述的问题,它对Grandle和Robolectric有较好的集成。 16 | 17 | # 配置build.gradle 18 | 19 | 第一步,配置build.gradle。主要代码如下所示。完整代码见[GitHub](https://github.com/kvandermast/my-robolectric-app)。 20 | 21 | *build.gradle* 22 | 23 | ``` 24 | ... 25 | 26 | android { 27 | ... 28 | buildTypes { 29 | debug { 30 | runProguard false 31 | proguardFile 'proguard-rules.txt' 32 | debuggable true 33 | testCoverageEnabled = true 34 | 35 | } 36 | } 37 | 38 | ... 39 | } 40 | 41 | ... 42 | 43 | apply plugin: 'jacoco' 44 | 45 | jacoco { 46 | toolVersion = "0.7.1.201405082137" 47 | } 48 | 49 | def coverageSourceDirs = [ 50 | '../app/src/main/java' 51 | ] 52 | 53 | task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") { 54 | group = "Reporting" 55 | 56 | description = "Generate Jacoco coverage reports" 57 | 58 | classDirectories = fileTree( 59 | dir: '../app/build/intermediates/classes/debug', 60 | excludes: ['**/R.class', 61 | '**/R$*.class', 62 | '**/*$ViewInjector*.*', 63 | '**/BuildConfig.*', 64 | '**/Manifest*.*'] 65 | ) 66 | 67 | additionalSourceDirs = files(coverageSourceDirs) 68 | sourceDirectories = files(coverageSourceDirs) 69 | executionData = files('../app/build/jacoco/testDebug.exec') 70 | 71 | reports { 72 | xml.enabled = true 73 | html.enabled = true 74 | } 75 | 76 | } 77 | ``` 78 | 79 | (现在)我们来看看其中最重要的配置。 80 | 81 | * buildType 声明,开启代码覆盖测试。 82 | 83 | ``` 84 | 85 | android { 86 | ... 87 | buildTypes { 88 | debug { 89 | runProguard false 90 | proguardFile 'proguard-rules.txt' 91 | debuggable true 92 | testCoverageEnabled = true 93 | 94 | } 95 | } 96 | 97 | ... 98 | } 99 | ``` 100 | 101 | * 加入一个 JaCoCo 插件,同时,指定使用最新版 : 102 | 103 | ``` 104 | 105 | apply plugin: 'jacoco' 106 | 107 | jacoco { 108 | toolVersion = "0.7.1.201405082137" 109 | } 110 | 111 | def coverageSourceDirs = [ 112 | '../app/src/main/java' 113 | ] 114 | ``` 115 | 116 | * 配置 converageSourceDirs,指定一个文件夹,JaCoCo 将对文件夹中的目标进行反射。 117 | 118 | * 配置 JaCoCo 插件,指定你需要测试的类(它们已经经过编译)和不需要测试的类(比如 ButterKnife 注入的 *ViewInjector*)。 119 | 120 | ``` 121 | task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") { 122 | group = "Reporting" 123 | 124 | description = "Generate Jacoco coverage reports" 125 | 126 | classDirectories = fileTree( 127 | dir: '../app/build/intermediates/classes/debug', 128 | excludes: ['**/R.class', 129 | '**/R$*.class', 130 | '**/*$ViewInjector*.*', 131 | '**/BuildConfig.*', 132 | '**/Manifest*.*'] 133 | ) 134 | 135 | additionalSourceDirs = files(coverageSourceDirs) 136 | sourceDirectories = files(coverageSourceDirs) 137 | executionData = files('../app/build/jacoco/testDebug.exec') 138 | 139 | reports { 140 | xml.enabled = true 141 | html.enabled = true 142 | } 143 | 144 | } 145 | ``` 146 | 147 | #执行 gradle 任务 148 | 149 | 修改 gradle.build 文件后,你必须执行与开发环境同步,以检查加入新插件后 gradle 也工作正常。 150 | 151 | 在使用 JaCoCo 生成测试报告前,还需要提供 testDebug.exec 文件。(提供文件)最简单的方法是打开命令行,对你的项目上执行如下命令 : 152 | `$ ./gradlew clean assemble` 153 | 154 | 这条命令会清空之前编译生成的class文件,并重新构建。 155 | 156 | 现在,你就可以使用 JaCoCo 生成测试报告啦,只要执行这条命令: 157 | `$ ./gradlew jacocoTestReport` 158 | 159 | (你会看到)终端将开始执行 grable 构建脚本,其中,最后一项任务是使用 JaCoCo 生成测试报告: 160 | 161 | > 162 | `$ ./gradlew jacocoTestReport 163 | :app:preBuild 164 | :app:preDebugBuild 165 | :app:checkDebugManifest 166 | :app:preReleaseBuild 167 | :app:prepareComAndroidSupportSupportV42000Library UP-TO-DATE 168 | :app:prepareDeKeyboardsurferAndroidWidgetCrouton184Library UP-TO-DATE 169 | :app:prepareDebugDependencies 170 | :app:compileDebugAidl UP-TO-DATE 171 | :app:compileDebugRenderscript UP-TO-DATE 172 | :app:generateDebugBuildConfig UP-TO-DATE 173 | :app:generateDebugAssets UP-TO-DATE 174 | :app:mergeDebugAssets UP-TO-DATE 175 | :app:generateDebugResValues UP-TO-DATE 176 | :app:generateDebugResources UP-TO-DATE 177 | :app:mergeDebugResources UP-TO-DATE 178 | :app:processDebugManifest UP-TO-DATE 179 | :app:processDebugResources UP-TO-DATE 180 | :app:generateDebugSources UP-TO-DATE 181 | :app:compileDebugJava UP-TO-DATE 182 | :app:compileTestDebugJava 183 | :app:processTestDebugResources UP-TO-DATE 184 | :app:testDebugClasses 185 | :app:testDebug 186 | :app:jacocoTestReport 187 | 188 | BUILD SUCCESSFUL 189 | 190 | Total time: 29.482 secs 191 | 192 | 生成的代码覆盖率测试报告保存在 ./build/reports/jacoco/jacocoTestReport 中,结果类似下图: 193 | ![p](http://img.blog.csdn.net/20150421201014450) 194 | 195 | #注意事项 196 | 197 | * 你的**应用名** 198 | 199 | 在我的例子中,Android module名是"app"。因此包含 `'../app/src/main/java'` 中的代码。如果你的Android module 名和例子中的不同,就请修改gradle文件中路径(所有涉及到 Android module 相关的路径)。比如,如果你的Android module名是FooBar,配置文件中就应修改为 `"..app/src/main/java"`。 200 | 201 | * **产品类別** 202 | 203 | 例子中没用指明产品类别,所以构建任务使用`"testDebug"`和`"../app/build/intermediates/classes/debug"`中的class文件。但是,如果你在应用中指定产品类别(比如. Local),Gradle就找不到"testDebug"任务。所以,需要正确的命名,比如,这里你可以用 testLocalDebug 并包含正确的class文件:`'../app/build/intermediated/classes/debug'`。 204 | 205 | 如果你有任何问题,别犹豫,直接来问我。代码在 [Github](https://github.com/kvandermast/my-robolectric-app) 上已经更新,请 Check Out。 206 | 207 | 术语: 208 | 209 | 1. code coverage 代码覆盖率:软件测试中用来表示被测软件中被测试代码占整个软件的比例或程度。 -------------------------------------------------------------------------------- /issue-7/在Activity中使用Thread导致的内存泄漏/readme.md: -------------------------------------------------------------------------------- 1 | 在Activity中使用Thread导致的内存泄漏 2 | --- 3 | 4 | > * 原文链接 : [Activitys, Threads, & Memory Leaks](http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html) 5 | * 原文作者 : [AlexLockwood](https://google.com/+AlexLockwood) 6 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 7 | * 译者 : [chaossss](https://github.com/chaossss) 8 | * 校对者: [yinna317](https://github.com/yinna317) 9 | * 状态 : 完成 10 | 11 | 12 | 13 | 14 | > 注:这篇博文涉及的源码可以在 [GitHub](https://github.com/alexjlockwood/leaky-threads) 上面下载哦 15 | 16 | 做 Android 开发最常遇到的问题就是在 Activity 的生命周期中协调耗时任务,避免执行任务导致不易察觉的内存泄漏。不妨先读一读下面的代码,代码写了一个简单的 Activity,Activity 在启动后就会开启一个线程,并循环执行该线程中的任务 17 | 18 | ```java 19 | 20 | /** 21 | * 示例向我们展示了在 Activity 的配置改变时(配置改变会导致其下的 Activity 实例被销 22 | * 毁)存活。此外,Activity 的 context 也是内存泄漏的一部分,因为每一个线程都被初始 23 | * 化为匿名内部类,使得每一个线程都持有一个外部 Activity 实例的隐式引用,使得 24 | * Activity 不会被 Java 的垃圾回收机制回收。 25 | */ 26 | public class MainActivity extends Activity { 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | exampleOne(); 32 | } 33 | 34 | private void exampleOne() { 35 | new Thread() { 36 | @Override 37 | public void run() { 38 | while (true) { 39 | SystemClock.sleep(1000); 40 | } 41 | } 42 | }.start(); 43 | } 44 | } 45 | ``` 46 | 47 | Activity 配置发生改变会使 Activity 被销毁,并新建一个 Activity,我们总会觉得 Android 系统会将与被销毁的 Activity 相关的一切清理干净,例如回收与 Activity 关联的内存,Activity 执行的线程等等……然而,现实总是很残酷的,刚刚提到的这些东西都不会被回收,并导致内存泄漏,从而显著地影响应用的性能表现。 48 | 49 | ## Activity 内存泄漏的根源 ## 50 | 51 | 如果你读过我以前写的一篇有关 Handler 和 内部类的博文,那我接下来要讲的知识你肯定知道。在 Java 中,非静态匿名内部类会持有其外部类的隐式引用,如果你没有考虑过这一点,那么存储该引用会导致 Activity 被保留,而不是被垃圾回收机制回收。Activity 对象持有其 View 层以及相关联的所有资源文件的引用,换句话说,如果你的内存泄漏发生在 Activity 中,那么你将损失大量的内存空间。 52 | 53 | 而这样的问题在 Activity 配置改变时会更加严重,因为 Activity 的配置改变表示 Android 系统将要销毁当前 Activity 并新建一个 Activity。举例来说吧,在使用应用的时候,你执行了10次横屏/竖屏操作,每一次方向的改变都会执行下面的代码,那么我们会发现(使用[ Eclipse 的内存分析工具](http://www.eclipse.org/mat/)可以看到)每一个 Activity 对象都会因为留有一个隐式引用而被保留在内存中。 54 | 55 | ![](http://www.androiddesignpatterns.com/assets/images/posts/2013/04/15/activity-leak.png) 56 | 57 | 每一次配置的改变都会使 Android 系统新建一个 Activity 并把改变前的 Activity 交给垃圾回收机制回收。但因为线程持有旧 Activity 的隐式引用,使该 Activity 没有被垃圾回收机制回收。这样的问题会导致每一个新建的 Activity 都将发生内存泄漏,与 Activity 相关的所有资源文件也不会被回收,其中的内存泄漏有多严重可想而知。 58 | 59 | 看到这里可能你会很害怕,很惶恐,很无助,那我们该怎么办……莫慌,解决办法非常简单,既然我们已经确定了问题的根源,那么对症下药就可以了:我们把该线程类声明为私有的静态内部类就可以解决这个问题: 60 | 61 | ```java 62 | 63 | /** 64 | * 示例通过将线程类声明为私有的静态内部类避免了 Activity context 的内存泄漏问题,但 65 | * 在配置发生改变后,线程仍然会执行。原因在于,DVM 虚拟机持有所有运行线程的引用,无论 66 | * 这些线程是否被回收,都与 Activity 的生命周期无关。运行中的线程只会继续运行,直到 67 | * Android 系统将整个应用进程杀死 68 | */ 69 | public class MainActivity extends Activity { 70 | 71 | @Override 72 | protected void onCreate(Bundle savedInstanceState) { 73 | super.onCreate(savedInstanceState); 74 | exampleTwo(); 75 | } 76 | 77 | private void exampleTwo() { 78 | new MyThread().start(); 79 | } 80 | 81 | private static class MyThread extends Thread { 82 | @Override 83 | public void run() { 84 | while (true) { 85 | SystemClock.sleep(1000); 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | 通过上面的代码,新线程再也不会持有一个外部 Activity 的隐式引用,而且该 Activity 也会在配置改变后被回收。 93 | 94 | ## 线程内存泄漏的根源 ## 95 | 96 | 第二个问题是:对于每个新建 Activity,如果 Activity 中的线程发生发生内存泄漏。在Java中线程是垃圾回收机制的根源,也就是说,在运行系统中DVM虚拟机总会使硬件持有所有运行状态的进程的引用,结果导致处于运行状态的线程将永远不会被回收。因此,你必须为你的后台线程实现销毁逻辑!下面是一种解决办法: 97 | 98 | ```java 99 | 100 | /** 101 | * 除了我们需要实现销毁逻辑以保证线程不会发生内存泄漏,其他代码和示例2相同。在退出当前 102 | * Activity 前使用 onDestroy() 方法结束你的运行中线程是个不错的选择 103 | */ 104 | public class MainActivity extends Activity { 105 | private MyThread mThread; 106 | 107 | @Override 108 | protected void onCreate(Bundle savedInstanceState) { 109 | super.onCreate(savedInstanceState); 110 | exampleThree(); 111 | } 112 | 113 | private void exampleThree() { 114 | mThread = new MyThread(); 115 | mThread.start(); 116 | } 117 | 118 | /** 119 | * 私有的静态内部类不会持有其外部类的引用,使得 Activity 实例不会在配置改变时发生内 120 | * 存泄漏 121 | */ 122 | private static class MyThread extends Thread { 123 | private boolean mRunning = false; 124 | 125 | @Override 126 | public void run() { 127 | mRunning = true; 128 | while (mRunning) { 129 | SystemClock.sleep(1000); 130 | } 131 | } 132 | 133 | public void close() { 134 | mRunning = false; 135 | } 136 | } 137 | 138 | @Override 139 | protected void onDestroy() { 140 | super.onDestroy(); 141 | mThread.close(); 142 | } 143 | } 144 | ``` 145 | 146 | 通过上面的代码,我们在 onDestroy() 方法中结束了线程,确保不会发生意外的线程的内存泄漏问题。如果你想要在配置改变后保留该线程(而不是每一次在关闭 Activity 后都要新建一个线程),那我建议你使用 Fragment 去完成该耗时任务。你可以翻我以前的博文,一名叫作**“Handling Configuration Changes with Fragments”**应该能满足你的需求,在API demo中也提供了很好理解的例子来为你阐述相关概念。 147 | 148 | ## 结论 ## 149 | 150 | Android 开发过程中,在 Activity 的生命周期里协调耗时任务可能会很困难,你一不小心就会导致内存泄漏问题。下面是一些小提示,能帮助你预防内存泄漏问题的发生: 151 | 152 | - **尽可能使用静态内部类而不是非静态内部类。**每一个非静态内部类实例都会持有一个外部类的引用,若该引用是 Activity 的引用,那么该 Activity 在被销毁时将无法被回收。如果你的静态内部类需要一个相关 Activity 的引用以确保功能能够正常运行,那么你得确保你在对象中使用的是一个 Activity 的弱引用,否则你的 Activity 将会发生意外的内存泄漏。 153 | 154 | - **不要总想着 Java 的垃圾回收机制会帮你解决所有内存回收问题。**就像上面的示例,我们以为垃圾回收机制会帮我们将不需要使用的内存回收,例如:我们需要结束一个 Activity,那么它的实例和相关的线程都该被回收。但现实并不会像我们剧本那样走。Java 线程会一直存活,直到他们都被显式关闭,抑或是其进程被 Android 系统杀死。所以,为你的后台线程实现销毁逻辑是你在使用线程时必须时刻铭记的细节,此外,你在设计销毁逻辑时要根据 Activity 的生命周期去设计,避免出现 Bug。 155 | 156 | - **考虑你是否真的需要使用线程。**Android 应用的框架层为我们提供了很多便于开发者执行后台操作的类。例如:我们可以使用 Loader 代替在 Activity 的生命周期中用线程通过注入执行短暂的异步后台查询操作,考虑用 Service 将结构通知给 UI 的 BroadcastReceiver。最后,记住,这篇博文中对线程进行的讨论同样适用于 AsyncTask(因为 AsyncTask 使用 ExecutorService 执行它的任务)。然而,虽说 ExecutorService 只能在短暂操作(文档说最多几秒)中被使用,那么这些方法导致的 Activity 内存泄漏应该永远不会发生。 157 | 158 | 这篇博文的源码可以在 [GitHub](https://github.com/alexjlockwood/leaky-threads) 中下载,你也可以在 [Google Play](https://play.google.com/store/apps/details?id=com.adp.leaky.threads) 下载 APK 使用。 159 | 160 | ![](http://www.androiddesignpatterns.com/assets/images/posts/2013/04/15/leaky-threads-screenshot.png) -------------------------------------------------------------------------------- /issue-7/深入浅出Android新特性-Transition-Part-3b/readme.md: -------------------------------------------------------------------------------- 1 | 延迟共享元素的过渡动画 (part 3b) 2 | --- 3 | 4 | > 5 | * 原文链接 : [Postponed Shared Element Transitions (part 3b)][source-url] 6 | * 作者 : [Alex Lockwood](https://plus.google.com/+AlexLockwood) 7 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 8 | * 译者 : [tiiime](https://github.com/tiiime) 9 | * 校对者: [chaossss](https://github.com/chaossss) 10 | * 状态 : 完成 11 | 12 | 通过讨论 Lollipop Transition API 的一个重要的特性:延迟共享元素的过渡动画,这篇博文将继续我们关于共享元素 Transition 的深度解析。这也是我关于 Transition 这个专栏的第四篇文章。 13 | 14 | - Part 1: [在 Activity 和 Fragment 中使用 Transition ][part-1] 15 | - Part 2: [深入理解 Transition][part-2] 16 | - Part 3a: [深入理解共享元素的 Transition][part3a] 17 | - Part 3b: [延迟共享元素的 Transition][part-3b] 18 | - Part 3c: 共享元素回调实践 (coming soon!) 19 | - Part 4: Activity & Fragment 过渡动画示例(coming soon!) 20 | 21 | 我们通过一个常见的问题来解释为什么需要推迟某些共享元素的过渡动画。 22 | 23 | ##理解问题 24 | 25 | 通常问题的根源是框架在 Activity 生命周期非常早的时候启动共享元素 Transition 。回想我们的第一篇文章,Transitions 必须捕获目标 View 的起始和结束状态来构建合适的动画。因此,如果框架在共享元素获得它在调用它的 Activity 中所给定的大小和位置前启动共享元素的过渡动画,这个 Transition 将不能正确捕获到共享元素的结束状态值,生成动画也会失败(一个过渡失败的例子[Video 3.3](http://www.androiddesignpatterns.com/assets/videos/posts/2015/03/09/postpone-bug-opt.mp4)). 26 | 27 | Transition 开始前,能否计算出正确的共享元素的结束值主要依靠两个因素: 28 | 29 | (1) 调用共享元素的 Activity 的布局复杂度以及布局层次结构的深度 30 | (2)调用共享元素Activity载入数据消耗的时间 31 | 32 | 布局越复杂,在屏幕上确定共享元素的大小位置耗时越长。同样,如果调用共享元素的 Activity 依赖一个异步的数据载入,框架仍有可能会在数据载入完成前自动开始共享元素 Transition。下面列出的是你可能遇到的常见问题: 33 | 34 | - **存在于 Activity 托管的 Fragment 中的共享元素**。[FragmentTransactions 在 commit 后并不会被立即执行][FragmentTransactions],它们会被安排到主线程中等待执行。因此,如果共享元素存在的 Fragment 的视图层和FragmentTransaction没有被及时执行,框架有可能在共享元素被正确测量大小和布局到屏幕前启动共享元素 Transition。(1) 35 | 36 | - **共享元素是一个高分辨率的图片**。给 **ImageView** 设置一个超过其初始化边界的高分辨率图片,最终可能会导致在这个视图层里出现[额外的布局传递][add-layout-pass],由此增加在共享元素准备好前就启动 Transition 的几率。流行的异步图片处理库比如 [`Volley`][volley] 和 [`Picasso`][picasso] ,也不能可靠的解决这个问题:框架不能预先了解图片是要被下载,缩放还是在后台线程中从磁盘读取,也不管图片是否处理完毕就启动共享元素 Transition。 37 | 38 | - **共享元素依赖于异步的数据加载**如果共享元素所需的数据是通过**AsyncTask**,**AsyncQueryHandler**,**Loader**或者其他类似的东西加载,在它们获得在调用它们的 Activity 的最终数据(大小、位置)前,框架就有可能在主线程中启动 Transition。 39 | 40 | 现在你可能会想:如果有办法能让暂时延迟 Transition 的使用,直到我们确定了共享元素的确切大小和位置才使用它就好了。幸好 Activity Transitions API(2) 为我们提供了解决方案。 41 | 42 | 在 Activity 的`onCreate()`中调用[`postponeEnterTransition()`][postponeEnterTransition] 方法来暂时阻止启动共享元素 Transition。之后,你需要在共享元素准备好后调用 [`startPostponedEnterTransition`][startPostponedEnterTransition] 来恢复过渡效果。常见的模式是在一个[`OnPreDrawListener`][onPreDrawListener]中启动延时 Transition,它会在共享元素测量和布局完毕后被调用(3)。 43 | 44 | ```java 45 | 46 | 47 | @Override 48 | protected void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | setContentView(R.layout.activity_main); 51 | 52 | // Postpone the shared element enter transition. 53 | postponeEnterTransition(); 54 | 55 | // TODO: Call the "scheduleStartPostponedTransition()" method 56 | // below when you know for certain that the shared element is 57 | // ready for the transition to begin. 58 | } 59 | 60 | /** 61 | * Schedules the shared element transition to be started immediately 62 | * after the shared element has been measured and laid out within the 63 | * activity's view hierarchy. Some common places where it might make 64 | * sense to call this method are: 65 | * 66 | * (1) Inside a Fragment's onCreateView() method (if the shared element 67 | * lives inside a Fragment hosted by the called Activity). 68 | * 69 | * (2) Inside a Picasso Callback object (if you need to wait for Picasso to 70 | * asynchronously load/scale a bitmap before the transition can begin). 71 | * 72 | * (3) Inside a LoaderCallback's onLoadFinished() method (if the shared 73 | * element depends on data queried by a Loader). 74 | */ 75 | private void scheduleStartPostponedTransition(final View sharedElement) { 76 | sharedElement.getViewTreeObserver().addOnPreDrawListener( 77 | new ViewTreeObserver.OnPreDrawListener() { 78 | @Override 79 | public boolean onPreDraw() { 80 | sharedElement.getViewTreeObserver().removeOnPreDrawListener(this); 81 | startPostponedEnterTransition(); 82 | return true; 83 | } 84 | }); 85 | } 86 | ``` 87 | 88 | 忽略方法名,这里还有第二种方法可以延迟共享元素的返回 Transition,在调用Activity的[`onActivityReenter()`][onActivityReenter] 方法中延缓返回 Transition(4) 89 | 90 | ```java 91 | /** 92 | * Don't forget to call setResult(Activity.RESULT_OK) in the returning 93 | * activity or else this method won't be called! 94 | */ 95 | @Override 96 | public void onActivityReenter(int resultCode, Intent data) { 97 | super.onActivityReenter(resultCode, data); 98 | 99 | // Postpone the shared element return transition. 100 | postponeEnterTransition(); 101 | 102 | // TODO: Call the "scheduleStartPostponedTransition()" method 103 | // above when you know for certain that the shared element is 104 | // ready for the transition to begin. 105 | } 106 | 107 | ``` 108 | 109 | 尽管添加延时可以让共享元素 Transition 更加流畅准确,但是你也要知道在应用中引入共享元素 Transition 的延迟可能会产生一些负面影响: 110 | 111 | - **调用`postponeEnterTransition`后不要忘记调用`startPostponedEnterTransition`**。 112 | 忘记调用`startPostponedEnterTransition`会让你的应用处于死锁状态,用户无法进入下个Activity。 113 | 114 | 115 | - **不要将共享元素 Transition 延迟设置到1s以上**。延迟时间过长会在应用中产生不必要的卡顿,影响用户体验。 116 | 117 | 118 | 感谢阅读!希望这篇文章对你有所帮助。 119 | 120 | **1**: 当然,许多应用通过调用 [`FragmentManager#executePendingTransactions()`](https://developer.android.com/reference/android/app/FragmentManager.html#executePendingTransactions()) 来避开这个问题,这样会强制立即执行FragmentTransactions而不是异步。 121 | 122 | **2**: 注意!`postponeEnterTransition()`和`startPostponedEnterTransition()`只对 Activity Transition起作用,对Fragment无效。详细信息可以在这里找到 [StackOverflow](http://stackoverflow.com/questions/26977303/how-to-postpone-a-fragments-enter-transition-in-android-lollipop) & [Google+](https://plus.google.com/+AlexLockwood/posts/3DxHT42rmmY) 123 | 124 | **3**: 小贴士:你可以先调用 [`View#isLayoutRequested()`](http://developer.android.com/reference/android/view/View.html#isLayoutRequested()) 来确认是否需要调用 [`OnPreDrawListener`][OnPreDrawListener],有必要的话 [`View#isLaidOut()`](http://developer.android.com/reference/android/view/View.html#isLaidOut()) 在一些情况下也能派上用场 125 | 126 | 127 | **4**: 在开发者选项中启用不保留 Activity 选项可以方便调试共享元素返回/重新进入时对应过渡动画的行为,这也可以帮助测试在返回的过渡效果开始之前可能发生最糟糕的情况( Activity 需要重新构造布局加载数据...) 128 | 129 | 130 | [source-url]:http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html 131 | [FragmentTransactions]:https://developer.android.com/reference/android/app/FragmentTransaction.html#commit() 132 | [postponeEnterTransition]:https://developer.android.com/reference/android/app/Activity.html#postponeEnterTransition() 133 | [startPostponedEnterTransition]:https://developer.android.com/reference/android/app/Activity.html#startPostponedEnterTransition() 134 | [add-layout-pass]:https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/widget/ImageView.java#L453-L455 135 | [video]:http://www.androiddesignpatterns.com/assets/videos/posts/2015/03/09/postpone-bug-opt.mp4 136 | [volley]:https://android.googlesource.com/platform/frameworks/volley 137 | [picasso]:http://square.github.io/picasso/ 138 | [onPreDrawListener]:http://developer.android.com/reference/android/view/ViewTreeObserver.OnPreDrawListener.html 139 | [onActivityReenter]:https://developer.android.com/reference/android/app/Activity.html#onActivityReenter(int,%20android.content.Intent) 140 | 141 | [part-1]:https://github.com/bboyfeiyu/android-tech-frontier/tree/master/others/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAAndroid%20%E6%96%B0%E7%89%B9%E6%80%A7-Transition-Part-1 142 | [part-2]:http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html 143 | [part3a]:http://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html 144 | [part-3b]:http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html 145 | -------------------------------------------------------------------------------- /issue-8/Android 进行单元测试难在哪-序.md: -------------------------------------------------------------------------------- 1 | Android 进行单元测试难在哪-序 2 | --- 3 | 4 | > * 原文链接 : [Against Android Unit Tests](http://philosophicalhacker.com/2015/04/10/against-android-unit-tests/) 5 | * 原文作者 : [Matthew Dupree](http://philosophicalhacker.com/) 6 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 7 | * 译者 : [chaossss](https://github.com/chaosssss) 8 | * 校对者: [Rocko](https://github.com/zhaoxiaopeng) 9 | * 状态 : 完成 10 | 11 | 其实不仅仅只有普通 Android 开发工程师觉得测试 Android 应用很恼火,大牛们也受此困扰已久。例如 Jake Wharton 曾经明确地表示:Android 平台自诞生之初就与应用测试势如水火。Don Felker 和 Kaushik Gopal 也在他们的[博文](http://fragmentedpodcast.com/episodes/1/)里也提出了相同的观点。当然了,他们还提到 Google 的 [IOSched 应用](https://github.com/google/iosched),根本就没有进行过测试,据说 IOSched 还是 Android 开发环境中应用开发的最优集合体呢。IOSched 没有进行测试让我们这些开发者很困扰:1、Google 所谓的“测试是高效地进行 Android 开发中的关键一环”真的不是来唬小孩的吗;2、还是 Google 官方的工程师觉得测试 Android 应用简直就是浪费时间?不管怎样,如果这个世界上最优秀的 Android 开发工程师都觉得在 Android 中进行测试很麻烦,那我们这些小菜鸡玩不好测试也是理所当然的了。 12 | 13 | 多年以来,Android 开发者们为克服在 Android 中难于进行测试的问题绞尽脑汁。Roboletric 就是这些工程师的智慧结晶,它能让开发者们在 JVM 虚拟机上进行 Android 测试。而最近又有博文开始声讨 Fragment,[个中翘楚 Square 就表示](https://corner.squareup.com/2014/10/advocating-against-android-fragments.html):老子再也不用 Fragment 这种垃圾玩意了,我们要把业务逻辑都转换到新开发的 Mortar & Flow (MVP 开发框架)框架里,用纯 Java 对象进行编程,完全不依赖 Android 平台的 API。毫无疑问,这些 Java 对象在标准的 Java 测试工具中进行测试是非常简单的。 14 | 15 | 我坚信那些和 Square 站在统一战线上的开发团队肯定也在想办法将 UI 从实际的业务逻辑中剥离为纯 Java对象,为提高应用的可测试性不懈努力。换句话说,我觉得我们可以不在 Android 中进行单元测试,也不用实现依赖于 Android SDK 的测试单元。我们应该做的是重构应用,让我们能够为应用中的代码实现纯 Java 的测试单元,无论最终能不能真正地提高 Android 的可测试性和健壮性,我觉得这都值得一试。 16 | 17 | 我感觉到这个思路会是治本良方,所以我们要做的,就是将下图这样的 Android 的应用架构 18 | 19 | ![](http://img.my.csdn.net/uploads/201504/26/1430014189_2164.png) 20 | 21 | 变成下图这样: 22 | 23 | ![](http://img.my.csdn.net/uploads/201504/26/1430014189_8490.png) 24 | 25 | 虽然这个方法可能能从根本上解决问题,但它也有很大的风险,尽管如此,我还是坚持认为这个方法值得一试,因为它能拯救万千挣扎在实现 Android 测试单元的开发者们于水火之中,而且不用强迫他们使用第三方的库,毕竟第三库总会让他们滞后于最新的 Android 系统特性。此外,Kent Beck 认为:可测试性好的代码就是架构优秀的代码,如果他的观点是对的,或许我们还能找到架构应用更好的办法。 26 | 27 | 在接下来的博文里,我将探索“重构 Android 应用以使它们能轻易地通过标准的 Java 工具进行测试”这个方案的可操作性。 28 | 29 | 在第一、第二篇博文中,我会侧重阐述在 Android 里进行单元测试为什么会带来如此痛苦的体验。我觉得阻碍 Android 测试方法发展的根本原因就在于:Android 系统本身就难于进行测试。缺乏对 Activity 和 Fragment 的合理注入就是让应用难以测试的根本原因,而且认识到这一点是设计可测试强的应用架构的关键。 30 | 31 | 在第三篇博文中,我会在细节上探讨一个常见的解耦应用代码和 Android SDK的策略。简单来说,这个策略就是:将所有应用的具体行为交给一个 POJO 对象(Plain Ordinary Java Object)完成,这些 POJO 对象都是 Android 无关的接口的 Android 特定实现。 32 | 33 | 在第四篇博文中,我会指出实现第二篇博文中提出的策略存在的技术难点,并尝试去挖掘可以解决这些难点的方法。在这些难点中,最大的问题在于内存泄漏和繁复的重用代码。 34 | 35 | 在最后一篇博文中,我会通过展示我提出的架构为 Android 测试性带来的提高让大家觉得进行这样的技术探索是值得花费时间、精力,并且能获得相应回报的。 36 | -------------------------------------------------------------------------------- /issue-8/Custom-Drawables.md: -------------------------------------------------------------------------------- 1 | 自定义Drawables 2 | --- 3 | 4 | > 5 | * 原文链接 : [Custom Drawables](http://www.ryanharter.com/blog/2015/04/03/custom-drawables/) 6 | * 原文作者 : [Ryan Harter](http://www.ryanharter.com/) 7 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 8 | * 译者 : [SwinZh](https://github.com/SwinZh) 9 | * 校对者: [Mr.Simple](https://github.com/bboyfeiyu) 10 | * 状态 : 完成 11 | 12 | 13 | 我们都看过关于为什么你应该适当的使[自定义Views](http://www.ryanharter.com/blog/2014/05/14/using-custom-compound-views-in-android/)和如何能帮助你正确的封装你的应用程序代码的帖子。但非视图相关的部分如何转化为我们apps的其他部分的这种思考方式,我们对此并不非常了解。 14 | 15 | 在我的应用[Fragment](https://play.google.com/store/apps/details?id=com.pixite.fragment&referrer=utm_source%3Dryanharter.com%26utm_medium%3Dpost%26utm_content%3Dcustom_drawables)中,有些地方我使用自定义Drawables来封装我的逻辑,就像你在customView中做的一样。 16 | 17 | ##用例## 18 | 19 | 在Fragment中,有一些使用水平滚动条作为一个选择视图的地方。这意味着该中心图标就是“选中”的图标,整个条目就该平滑的平移进去或平移出。为此,一个好的显示转换将非常棒。 20 | 21 | ![](http://www.ryanharter.com/images/posts/custom-drawables/example.gif) 22 | 23 | 虽然这并非完全必要,但我觉得它是一个能让这个滑动更加流畅并增加一个触摸的类在app上的效果。我本可以设置多个imageviews并让他们每个独立出来,但这真是使用自定义drawables的好地方~ 24 | 25 | ##自定义Drawables## 26 | 27 | 在Android里,Drawables和Views实际上非常的相似。他们有相似的方法,例如padding和bounds(layout),并且都有一个可以被重写的draw方法。就我而言,我需要能够在一个选中的图片和一个未选中的图片之间进行转换基础上的一个值。 28 | 29 | 在我们的例子中,我们简单地创建一个包含其他Drawables(和方向)的Drawable子类. 30 | 31 | 32 | 33 | ```java 34 | public class RevealDrawable extends Drawable { 35 | public RevealDrawable(Drawable unselected, Drawable selected, int orientation) { 36 | this(null, null); 37 | 38 | mUnselectedDrawable = unselected; 39 | mSelectedDrawable = selected; 40 | mOrientation = orientation; 41 | } 42 | } 43 | ``` 44 | 45 | 接下来,我们需要设定能够与图片选择过程中相关联的值。幸运的是Drawable内置了这种类型的事件,setLevel(int). 46 | 47 | 一个Drawable的level是介于0和10000的整数,它只是允许Drawable基于一个值去自定义它的view.在我们的例子中,我们可以定义5000作为图片被选择时的状态值,其他没被选中状态值在5000左右两侧。 48 | All we need to do now is to override the draw(Canvas canvas) method to draw the appropriate drawable by clipping the canvas based on the current level. 49 | 现在我们要做的就是重写draw(Canvas canvas)方法,通过基于当前的level裁剪画布去绘制相应的图片。 50 | 51 | ```java 52 | @Override 53 | public void draw(Canvas canvas) { 54 | 55 | // If level == 10000 || level == 0, just draw the unselected image 56 | int level = getLevel(); 57 | if (level == 10000 || level == 0) { 58 | mRevealState.mUnselectedDrawable.draw(canvas); 59 | } 60 | 61 | // If level == 5000 just draw the selected image 62 | else if (level == 5000) { 63 | mRevealState.mSelectedDrawable.draw(canvas); 64 | } 65 | 66 | // Else, draw the transitional version 67 | else { 68 | final Rect r = mTmpRect; 69 | final Rect bounds = getBounds(); 70 | 71 | { // Draw the unselected portion 72 | float value = (level / 5000f) - 1f; 73 | int w = bounds.width(); 74 | if ((mRevealState.mOrientation & HORIZONTAL) != 0) { 75 | w = (int) (w * Math.abs(value)); 76 | } 77 | int h = bounds.height(); 78 | if ((mRevealState.mOrientation & VERTICAL) != 0) { 79 | h = (int) (h * Math.abs(value)); 80 | } 81 | int gravity = value < 0 ? Gravity.LEFT : Gravity.RIGHT; 82 | Gravity.apply(gravity, w, h, bounds, r); 83 | 84 | if (w > 0 && h > 0) { 85 | canvas.save(); 86 | canvas.clipRect(r); 87 | mRevealState.mUnselectedDrawable.draw(canvas); 88 | canvas.restore(); 89 | } 90 | } 91 | 92 | { // Draw the selected portion 93 | float value = (level / 5000f) - 1f; 94 | int w = bounds.width(); 95 | if ((mRevealState.mOrientation & HORIZONTAL) != 0) { 96 | w -= (int) (w * Math.abs(value)); 97 | } 98 | int h = bounds.height(); 99 | if ((mRevealState.mOrientation & VERTICAL) != 0) { 100 | h -= (int) (h * Math.abs(value)); 101 | } 102 | int gravity = value < 0 ? Gravity.RIGHT : Gravity.LEFT; 103 | Gravity.apply(gravity, w, h, bounds, r); 104 | 105 | if (w > 0 && h > 0) { 106 | canvas.save(); 107 | canvas.clipRect(r); 108 | mRevealState.mSelectedDrawable.draw(canvas); 109 | canvas.restore(); 110 | } 111 | } 112 | } 113 | } 114 | ``` 115 | 116 | 就这样,我们可基于滑动的位置以简单地设置icon的level,结束了~ 117 | 118 | ```java 119 | float offset = getOffestForPosition(recyclerView, position); 120 | if (Math.abs(offset) <= 1f) { 121 | holder.image.setImageLevel((int) (offset * 5000) + 5000); 122 | } else { 123 | holder.image.setImageLevel(0); 124 | } 125 | ``` 126 | 127 | 想要看自定义Drawable源码,请在Github上搜索Gist[here](https://gist.github.com/rharter/34051da57f8a6a0991ff) 128 | 129 | -------------------------------------------------------------------------------- /issue-8/如何在Android上响应各种信息通知.md: -------------------------------------------------------------------------------- 1 | 如何在Android上响应各种信息通知 2 | --- 3 | 4 | > * 原文链接 : [How to respond to any* messaging notification on Android](https://medium.com/@polidea/how-to-respond-to-any-messaging-notification-on-android-7befa483e2d7) 5 | * 原文作者 : [Michał Tajchert](https://github.com/tajchert/NotificationResponse) 6 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 7 | * 译者 : [MrLoong](https://github.com/MrLoong) 8 | * 校对者: [bboyfeiyu ](https://github.com/bboyfeiyu) 9 | * 状态 : 完成 10 | 11 | 12 | 13 | 有没有想过使用你的代码去回应未知的发送者是多么酷的事情?作为一家痴迷于最新技术的公司,我想和大家一起分享我们的一些研究 14 | 15 | 如果你是Android用户,你可能会喜欢 [Pushbullet](https://www.pushbullet.com/)这个令人敬畏的App,它是一个越过设备同步你所有的消息的App,包括Pc和Mac,自从二月的更新,它允许回应消息给应用程序,例如Facebook Messenger,Hangouts,Telegram 等等 -难道不让人敬畏?可以确定!并没有易于实施的API接口去实现这些,基本上每一个平台都需要我们实现所有客户的连接,但这不是一个好的解决方案。 16 | 17 | 作为一个喜欢此功能的用户,我们的工程师Michał Tajchert开始专研这个主题后他的好奇心被Reddit上的一个“它是怎么运行的”问题而引发,这个功能一定已经与Android Wear API发生了连接,作者是这样说的。小提示下,Android Wear是提供给智能手表的平台。更具体的讲,我们并不是谈论关于智能手表全新的数据API或消息API,但除了一些已经存在的小通知。由于Android Wear的介绍,我们可以增添一个被叫做"WearableExtender"的小物件,增添一些针对智能手表的功能,像活动页,背景,语音输入等 18 | 19 | ```java 20 | RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY).build(); 21 | 22 | NotificationCompat.Action action = 23 | new NotificationCompat.Action.Builder(...) 24 | .addRemoteInput(remoteInput) 25 | .build(); 26 | 27 | NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(action); 28 | 29 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(MainActivity.this).extend(wearableExtender); 30 | ``` 31 | 32 | 在上面的代码我们可以看到,我们已经新建RemoteInput(一个收集语音应答的对象),RemoteInput被作为一个Action添加到我们的WearableExtender对象,最终被添加带Notification他自己本身。如果你有一个显示通知并等待用户我文本响应的App(任何类似的App),这是一个完美的解决方案对你来说。大多流行App都已经实现了。仅有非常少数的App仍然缺乏这点,像Skype 正在计划添加这个功能在接下来的几周。 33 | 34 | ![我们使用这个技巧的事例应用程序,当用户点击程序按钮,他将回应Messenger conversation](https://d262ilb51hltx0.cloudfront.net/max/800/1*IlV2iOqJ5L9fgK-6_4l-bg.gif) 35 | 36 | 我们使用这个技巧的事例应用程序,当用户点击程序按钮,他将回应Messenger conversation 37 | 38 | ##怎样捕获通知? 39 | 40 | 在Android上还是比较容易实现的,只要实现你自己的service,继承NotificationListenerService并且实现onNotificationPosted() and onNotificationRemoved()方法,这样任何通知的出现或被取消都会被感知。安全访问通知的完成是通过提示屏幕列表中能访问你的通知和能够选中或取消任何的应用程序通知。对于UX来说这并不是一个好的应用,因为它是全屏幕(所以这样的互动应该离开我们的App),一个更好的模式是有一个列表应程序替代询问我们特殊权限的应用程序窗口。 41 | 42 | ##让我们看看如何可以访问WearableExtender 43 | 44 | 第一种方式,这样做实际上是由一些用户在网上发布,从pushbulle源提取,随着一些代码的完善使它工作,这是什么样子的: 45 | 46 | ```java 47 | Bundle bundle = statusBarNotification.getNotification().extras; 48 | for (String key : bundle.keySet()) { 49 | Object value = bundle.get(key); 50 | 51 | if("android.wearable.EXTENSIONS".equals(key)){ 52 | Bundle wearBundle = ((Bundle) value); 53 | for (String keyInner : wearBundle.keySet()) { 54 | Object valueInner = wearBundle.get(keyInner); 55 | 56 | if(keyInner != null && valueInner != null){ 57 | if("actions".equals(keyInner) && valueInner instanceof ArrayList){ 58 | ArrayList actions = new ArrayList<>(); 59 | actions.addAll((ArrayList) valueInner); 60 | for(Notification.Action act : actions) { 61 | if (act.getRemoteInputs() != null) {//API > 20 needed 62 | android.app.RemoteInput[] remoteInputs = act.getRemoteInputs(); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | [WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/) 73 | 74 | 在这里刚刚发生了什么?首先,我们从WearableExtender中的参数分布提取出了Bundle通知,然后我们搜索一个能让我们访问所有Android Wear特殊的功能关键值“android.wearable.EXTENSIONS”,例如页面.....我们正在寻找RemoteInput!为了找到它,我们需要遍历所有的功能。这就是实际的工作,但还是代码看起来很丑并且“hackish”。 75 | 76 | ![](https://d262ilb51hltx0.cloudfront.net/max/1375/1*BieupTbIh3rfs9hxruOvkQ.png) 77 | 78 | ##我们能比这做的更好吗? 79 | 80 | 当然!一行代码获得WearableExtender,会是多么的酷? 81 | 82 | ```java 83 | WearableExtender wearableExtender = new WearableExtender(statusBarNotification.getNotification()); 84 | ``` 85 | [WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/) 86 | 87 | 好的,让我们现在直接提取Actions从RemoteInput中。 88 | 89 | ```java 90 | List< Action> actions = wearableExtender.getActions(); 91 | for(Action act : actions) { 92 | if(act != null && act.getRemoteInputs() != null) { 93 | RemoteInput[] remoteInputs = act.getRemoteInputs(); 94 | } 95 | } 96 | ``` 97 | [WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/) 98 | 99 | 好的,但是我们还需要做些什么呢?我们需要PendingIntent发送我们的返回结果给应用程序,使它触发通知并且他也将很好的保持Bundle,因为他可能包含一些额外的价值,例如conversationId,让我们去实现它 100 | 101 | ```java 102 | notificationWear.pendingIntent = statusBarNotification.getNotification().contentIntent; 103 | notificationWear.bundle = statusBarNotification.getNotification().extras; 104 | ``` 105 | 106 | [WearableExtenderGetter](https://gist.github.com/tajchert/5a45deef2de9d667eb81#file-wearableextendergetter) hosted with ❤ by [GitHub](https://github.com/) 107 | 108 | 现在我们准备返回它!探究一下我们的事例应用程序,加速POC的开发过程,通过的数据以EventBus为媒介转到Activity被保存在Stack,当用户点击“Respond to last”直接就突出最后题目并且伪造响应文本给它 109 | 110 | ##填充RemoteInput 111 | 112 | 大概最有趣的部分是怎么能够填补一个对象,通常需要语音输入我们虚假的文字,它并不是很难。 113 | 114 | ```java 115 | RemoteInput[] remoteInputs = new RemoteInput[notificationWear.remoteInputs.size()]; 116 | 117 | Intent localIntent = new Intent(); 118 | localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 119 | Bundle localBundle = notificationWear.bundle; 120 | int i = 0; 121 | for(RemoteInput remoteIn : notificationWear.remoteInputs){ 122 | getDetailsOfNotification(remoteIn); 123 | remoteInputs[i] = remoteIn; 124 | localBundle.putCharSequence(remoteInputs[i].getResultKey(), "Our answer");//This work, apart from Hangouts as probably they need additional parameter (notification_tag?) 125 | i++; 126 | } 127 | RemoteInput.addResultsToIntent(remoteInputs, localIntent, localBundle); 128 | try { 129 | notificationWear.pendingIntent.send(MainActivity.this, 0, localIntent); 130 | } catch (PendingIntent.CanceledException e) { 131 | Log.e(TAG, "replyToLastNotification error: " + e.getLocalizedMessage()); 132 | } 133 | ``` 134 | 135 | 我们的notificationWear对象是一个暂时存放所有需要数据来响应特殊通知的容器,然后我们把每一个RemoteInput从我们保存的通知中填充到我们所期望的文本,通过使用putCharSequence()方法在Bundle返回。这里的关键是使用getResultKey()在每一个RemoteInput,因为被调用的程序将在返回Bundle答复文本。最后一步我们需要使Intent移植于RemoteInputs对于那些虚假的回应,让我们使用addResultsToIntent(inputs, intent, bundle)这样做。现在我们准备好所有的数据去触发PendingIntent,触发叫做pendingIntent.send(context, code,intent)的方法。这是全部,我们只是回应在Messenger,Telegraph,Line 或是任何其他使用WearableExtender与RemoteInput的应用程序!这种方法仍然缺少Hangouts,可能需要一些额外的参数,我最好的猜测是在其他Tag中的StatusBarNotification如果其他的(工作)应用程序也是空的。 136 | 137 | ##帅不? 138 | 139 | 是的,是这样。仔细看一看Pushbullet是他被允许做的非常好的事情。但是在我们的办公室,值得被的关注的是,用户被提示选择应用程序读取通知和使用这个技巧,我们可以应对任何通知。还有,我们不但可以响应也可以缓存需要被触发的数据,例如,一些用在Messenger中以后使用的对话,同时,潜在的,它允许你发送短信在没有正确权限,因为你“just responded to a notification”和偶然有短信应用程序。现在在Android默认文本消息的应用程序不包括WearableExtender,但也许这只是时间问题,而且Hangouts允许你发送短信。 140 | 141 | ##时刻保持安全 142 | 143 | 什么是单向的,可以做其他工作的,这就意味着如果你正在开发一个Wear-enabled用用程序,其他的开发人员可能会回复你的通知,后来出现RemoteInput黑客,让我们想想如何保持安全。最简单的方法是生成一些ID对于每个通知并且允许他们只能使用一次,这样任何应用程序使用这个技巧不会发送过多的消息到你的应用程序。另一个方法是在Bundle中不包括一些参数,但是作为一个例子Tag中的Notification比较后接受。 这可能是我们为什么不能让黑客工作在Hangouts。从另一方面来讲,如果你是使用者你应该更加关注那些曾获取“reading notifications”应用程序,因为他现在也允许应用程序与他们互动。如果你想要一个测验或者帮助我们对于Hangouts的工作,这个样品在[Github](https://github.com/tajchert/NotificationResponse)上 144 | 145 | 146 | -------------------------------------------------------------------------------- /issue-9/readme.md: -------------------------------------------------------------------------------- 1 | # 开发技术前线 第九期 2 | 3 | | 文章名称 | 译者 | 4 | |---------|--------| 5 | | [Android 10ms问题:关于Android音频路径延迟的解释](Android 10ms问题:关于Android音频路径延迟的解释.md) | [objectlife](https://github.com/objectlife) | 6 | | [Android 进行单元测试难在哪-part1](Android 进行单元测试难在哪-part1.md) | [chaossss](https://github.com/chaossss)| 7 | | [NotRxJava懒人专用指南](NotRxJava懒人专用指南.md) | [Rocko](https://github.com/Rocko) | 8 | | [使用Android Studio进行单元测试](使用Android-Studio进行单元测试.md) | [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang)| 9 | | [通过Jenkins并行完成UI的自动化测试](通过Jenkins并行完成UI的自动化测试.md) | [chaossss](https://github.com/chaossss) | 10 | | [创建-RecyclerView-LayoutManager-Part-1](创建-RecyclerView-LayoutManager-Part-1.md) | [tiiime](https://github.com/tiiime) | -------------------------------------------------------------------------------- /issue-9/使用Android-Studio进行单元测试.md: -------------------------------------------------------------------------------- 1 | 使用Android Studio进行单元测试 2 | --- 3 | 4 | > * 原文链接 : [Unit Testing With Android Studio](http://rexstjohn.com/unit-testing-with-android-studio/) 5 | * 原文作者 : [Rex St John](http://rexstjohn.com/) 6 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 7 | * 译者 : [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang) 8 | * 校对者: [zhengxiaopeng](https://github.com/zhengxiaopeng) 9 | * 状态 : 校对完 10 | 11 | 这篇文章介绍了在Android Studio中进行单元测试的基础部分。 12 | 13 | #在Android Studio中可以进行单元测试 14 | 15 | 16 | 很多教程都指导你应该在“build.gradlew”中添加几行代码来开启“instrument tests” 功能,而且还需要添加 Android 测试库的项目依赖。 17 | 18 | 其实你并不需要按照这种错误的方式去做,因为这是完全没有必要的。 19 | 20 | Android Studio本身就支持Android单元测试,你只需要在你的项目中简单配置一下就可以了。 21 | 22 | 注意:还有好几种流行的Android单元测试框架,比如[Robolectric](http://robolectric.org/),这些框架涉及到的配置和设置比我在这里提到的更多,我希望在未来可以以这个主题写一些指导教程。 23 | 24 | #创建你的单元测试文件夹 25 | 26 | 我喜欢把单元测试放在我的主项目里面,比如“com.mypath.tests.” ,你可以把测试目录放到你想放的任何地方。在开始之前,像下面这样,先创建你的"test"文件夹。(译者注:这一步不是必须的,你也可以把单元测试类创建在与Android Studio默认的ApplicationTest类相同的路径下面) 27 | 28 | ![](http://i2.tietuku.com/8ea1f7ff89634a0f.png) 29 | 30 | 接下来,创建一个叫做 “ExampleTest”的类,要继承自InstrumentationTestCase 31 | 32 | ![](http://i2.tietuku.com/164d47e438f78f37.png) 33 | 34 | 然后可以添加一段简单的测试代码,我们知道这段代码肯定会运行失败 35 | 36 | ``` 37 | public class ExampleTest extends InstrumentationTestCase { 38 | public void test() throws Exception { 39 | final int expected = 1; 40 | final int reality = 5; 41 | assertEquals(expected, reality); 42 | } 43 | } 44 | ``` 45 | 46 | 47 | 注意:所有的测试方法必须以"test"开头,否则Android Studio不能找到要进行单元测试的方法,你将会得到各种各样的错误,并且无法正常执行。 48 | 49 | #为你的项目配置单元测试 50 | 51 | 现在我们已经有了一个必然会运行失败的测试用例,我们必须把它run起来。 52 | 53 | 首先点击"Run-> Edit Configurations" 54 | 55 | ![](http://i2.tietuku.com/e91b3515dff21267.png) 56 | 57 | 然后点击“+”,从左上角选择添加一个 Android Tests,然后你可以将这个测试配置重新命名为"test"或与之相关的名字 58 | 59 | ![](http://i2.tietuku.com/6f5c952065151e07.png) 60 | 61 | 然后就会创建如下的测试项目配置 62 | 63 | ![](http://i2.tietuku.com/2183cac60bbed220.png) 64 | 65 | 从下拉菜单中选择你当前的module 66 | 67 | ![](http://i2.tietuku.com/854bbdd0299ddb1c.png) 68 | 69 | 70 | 接下来,选择"All in Package"选项,然后把你的刚才创建的测试文件夹选中。你也可以选择“All in Module”选项,这样Android Studio会自动的找到你整个Module中的所有测试单元,你也可以通过更具体的类或者是方法选项,进一步缩小测试范围。 71 | 72 | 做完这一切之后,看起来应该像下面这样 73 | 74 | ![](http://i2.tietuku.com/15039870f925e4e9.png) 75 | 76 | 我也喜欢选中下面的“Show chooser dialog”,这样当每次运行的时候,我可以指定如何去运行 77 | 78 | ![](http://i2.tietuku.com/66d3e1d4a120df74.png) 79 | 80 | 现在点击"Apply"然后关闭,你现在应该可以看到你的测试案例已经作为一个可以运行的项目配置在Android Studio上面的工具栏上了 81 | 82 | ![](http://i2.tietuku.com/6acd89afcb22309d.png) 83 | 84 | #运行我们的单元测试 85 | 86 | 我使用Genymotion来完成所有的事情,所以开启你的Genymotion然后运行test 87 | 88 | 在assertion这一行添加一个断点,然后点击 “run debug mode”,目的是为了证明Android Studio确实执行了我们的单元测试。 89 | 90 | ![](http://i2.tietuku.com/e91aefd47fe27e05.png) 91 | 92 | 当你开始你的测试工程之后,你会看到一个叫做“Running Tests…”的显示窗口 93 | 94 | ![](http://i2.tietuku.com/2202b414f006234a.png) 95 | 96 | 97 | 当你的测试没有通过,点击“Logcat”然后查看综合的输出结果,看下我们测试失败的原因 98 | 99 | ![](http://i2.tietuku.com/82cf59170999b096.png) 100 | 101 | 通过控制台,可以看到打印出的错误原因: 102 | 103 | ``` 104 | “junit.framework.AssertionFailedError: expected:<1> but was:<5>” 105 | ``` 106 | 107 | 恭喜你,你已经成功测试出错误啦~ 108 | 109 | 下面的这些资料在完成本文时,给了很大的帮助 110 | 111 | - http://mobilengineering.blogspot.com/2012/05/tdd-testing-asynctasks-on-android.html 112 | - http://www.vogella.com/tutorials/AndroidTesting/article.html 113 | - http://nikolaj.hesselholtskov.dk/2013/10/how-to-add-unit-tests-to-android-studio.html -------------------------------------------------------------------------------- /issue-9/通过Jenkins并行完成UI的自动化测试.md: -------------------------------------------------------------------------------- 1 | 通过Jenkins并行完成UI的自动化测试 2 | --- 3 | 4 | > * 原文链接 : [Concurrent Android UI automation with Jenkins](http://www.hidroh.com/2015/04/14/concurrent-android-ui-automation-jenkins/) 5 | * 原文作者 : [Ha Duy Trung](http://www.hidroh.com/) 6 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 7 | * 译者 : [chaossss](https://github.com/chaossss) 8 | * 校对者: [tiiime](https://github.com/tiiime) 9 | * 状态 : 完成 10 | 11 | 12 | 13 | 14 | ![](http://www.hidroh.com/assets/img/parallel-cover.jpg) 15 | 16 | 现在的 IT 公司会为了进入不同的市场开发相应的 App,来自同一家公司的 App 总会具有相似的 UI 逻辑,但 UI 的细节、风格又各有区分。随着产品的发展,抑或是在产品应用于新市场的过程中产生了旧 UI 逻辑的变种,用于测试所有 UI 逻辑的时间会与新 UI 的数量呈正比例关系增长。即使有 UI 的自动化框架可以用来减少测试所需的时间(例如 Calabash Android),使得每天只需要2个小时就能完成对 UI 的测试,但在这套 UI 衍生出四套“变种 UI”后,却要花费8个小时才能完成测试。 17 | 18 | 这篇博文将利用 Continuous Integration (CI) 服务,同时在不同的设备中执行 UI 测试,并介绍一种从根源上减少这种实际场景下自动化测试所需时间的方法。此外,我们的测试都是在真机上运行,而不是虚拟机。 19 | 20 | 更贴近现实的自动化测试:有很多东西只能通过真机来反映,像内存消耗,CPU 消耗:虚拟机通常都是消耗电脑的资源,而真机基本不会消耗电脑资源。 21 | 22 | 而且用真机进行测试性价比较高!Android 设备都很便宜,所以你可以在需要设备进行测试的时候毫不犹豫地买买买,如果用虚拟机测试的话,由于虚拟机会消耗很多电脑资源而且拓展电脑硬件很烧钱,这无疑会限制测试设备的数量。 23 | 24 | 虽说博文里同时使用了 Calabash Android 自动化框架 和 [Jenkins CI](https://jenkins-ci.org/),但我提到的大部分方法可以通过运行在任意 CI 服务上的 [Android Debug Bridge (ADB)](https://wiki.jenkins-ci.org/display/JENKINS/Distributed+builds#Distributedbuilds-RunningMultipleSlavesontheSameMachine) 应用于其他的自动化测试框架里。 25 | 26 | ## 创建 Jenkins slaves 和 slave group 27 | 28 | 完成[分布式构建](https://wiki.jenkins-ci.org/display/JENKINS/Distributed+builds)的第一步就是创建 Jenkins slave,授权它管理多个“奴隶”。我们可以通过 Jenkins -> Manage Jenkins -> Manage Nodes 导航到 Jenkins slaves/nodes 的配置界面。 29 | 30 | 根据定义,Jenkins slave 用于完成 Jenkins 主机分发下来的任务。在我们的使用场景中,需要完成的任务则是运行在 Android 设备上的自动化测试。为了完成这项任务,所有奴隶都可以[运行在相同的机器上](https://wiki.jenkins-ci.org/display/JENKINS/Distributed+builds#Distributedbuilds-RunningMultipleSlavesontheSameMachine),也就是通过 USB 连接在电脑上的,具有执行 ADB 命令的 Android 设备。 31 | 32 | 下面是一个与 Samsung S3 关联的奴隶的安装细节: 33 | 34 | ![](http://www.hidroh.com/assets/img/parallel-slave-1.png) 35 | 36 | 当所有奴隶都运行在同一台机器上时,我建议为不同的奴隶结点提供不同的系统 roots 文件,因为它们很可能会在测试结果处被存储,如果它们指向同一个 root 文件,可能会让测试结果被不同的结点重写。我们可以通过 Remote FS root 进行这样的设置。 37 | 38 | > **Remote FS root** 39 | 40 | > 每个奴隶都需要拥有 Jenkins 中的一个专用目录,我们在奴隶中为其指定绝对路径,如:'/var/jenkins' 或 'c:\jenkins',而且该路径应该是奴隶设备中的本地路径。但是该路径不需要对 Jenkins 可见,怎么正常怎么来就行。 41 | 42 | > 奴隶不会持有重要数据(不同于最后创建于其上的项目的活跃工作区),所以你可以将奴隶的工作区设置到一个临时目录。这样做的唯一缺点在于:你可能会在奴隶设备关机后失去最新的工作区。 43 | 44 | 当 separate FS roots 和多处理器允许 Calabash 的并行操作时,ADB_DEVICE_ARG 环境变量需要用于通知 Calabash,因为 Calabash 运行的设备应该向其发送 ADB 命令,防止设备的多重连接。底层中,Calabash 通过 ADB 设备命令自动化 UI,当一个设备匹配于另一个奴隶结点,ADB_DEVICE_ARG 应该通过环境变量的配置应用到 node 结点层中,并在其后在任务层变为可用。 45 | 46 | > **环境变量** 47 | 48 | > 这些键值对将会应用于结点上的每一个构建,并重写任意全局值,所以他们能在 Jenkins 的配置中被使用(如 $key 或 ${key}),而且将会被添加到构建上启动的进程。 49 | 50 | 在 Nexus 4 中设置奴隶的方式如上 51 | 52 | ![](http://www.hidroh.com/assets/img/parallel-slave-2.png) 53 | 54 | 如果你留心注意一些细节你会发现,所有奴隶结点都被配置了相同的 android-group 标签。这样做的目的主要是将奴隶分组,使得当我们需要使用奴隶时,方便我们调用特定分组里的所有奴隶。 55 | 56 | > 标签 57 | 58 | > 标签(AKA 标签)用于将多个奴隶分到一个逻辑组中,而每一个标签都会消耗空间。例如 'regression java6' 将为为结点分配 'regression' 和 'java6' 标签。 59 | 60 | > 举例来说吧,如果你有多个窗口奴隶,而且你要完成的任务需要窗口,那么你可以让你的所有窗口奴隶持有 'windows' 标签,然后将需要执行的任务与 'windows' 标签绑定。这使得你的任务在所有窗口奴隶中被执行,而不是被随意执行。 61 | 62 | ![](http://www.hidroh.com/assets/img/parallel-slave-group.png) 63 | 64 | 现在如果我们检查 android-group,我们会得到一个所有结点都带着 android-group 标签的列表,把其他设备添加到这个组里就像拷贝一个已存在的结点和更新 ADB_DEVICE_ARG 环境变量一样简单。 65 | 66 | ## 使用奴隶群建立并行任务 67 | 68 | 在进行了上面的配置以后,我们现在可以创建一个 Jenkins 任务使用连接到电脑的设备,通过各自的奴隶和具有 android-group 的奴隶群执行 Calabash Android。 69 | 70 | ![](http://www.hidroh.com/assets/img/parallel-downstream-1.png) 71 | 72 | 通过选中“如果必要的话,执行并行构建”和“限制项目运行位置”两个配置选项,使得任务可以被并行执行,主题对结点可用,仅使用标有“android-group”标签的结点。但其中的限制是:需要确保只有与已连接的设备关联的结点才能用于自动化测试,因为我们有用于完成其他任务的其他与未连接设备关联的结点。 73 | 74 | > **如果必要的话,执行并行构建** 75 | 76 | > 如果需要并行执行构建,Jenkins 会安排并并行执行多个构建(假设你有足够的执行器和传入的构建请求),这对于耗时长的构建和测试任务来说非常有用……此外,参数化构建也是非常实用的,因为每一个执行器的执行任务与其他执行器的执行任务相互独立。 77 | 78 | 现在假设我们有4个自动化测试的请求和三个已连接的设备,处理完所有请求将需要两轮操作(第一轮三个设备将会被使用,剩下一个请求在队列中等待处理,当某个设备变为空闲状态则会处理该请求)。这就意味着我们可以使4个产品风格自动化测试的时间从8小时减少为4小时,如果我们再买一台或者多台设备的话,时间甚至会更少。 79 | 80 | ##通过上游任务触发并行的下游任务 81 | 82 | 写到这里,我们已经能够随心所欲地对我们想要的设备(连接在 USB 端口处可用的设备!)手动地触发并行的自动化测试。但是,我们为什么要手动地完成这些工作呢?我们可以用 Jenkins Parameterized Trigger Plugin 设置上游任务,通过相应的参数触发多个下游任务。 83 | 84 | 例如,如果我们想要使用所有可用设备并行地测试多个应用 UI,那么我们可以设置一个这样的上游任务,并执行它: 85 | 86 | ![](http://www.hidroh.com/assets/img/parallel-upstream-2.png) 87 | 88 | ![](http://www.hidroh.com/assets/img/parallel-upstream-3.png) 89 | 90 | 上面的配置完成后,我们可以触发三个并行的 MOBILE_TEST 下游任务(必须进行了“如果必要的话,执行并行构建”的配置),每一个下游任务将会测试一个指定的 App 风格“cherry”,“tomato”,“rasberry”,并通过一个叫作风格的参数发送到下游任务。上游任务将会被阻塞,等待所有下游任务完成任务并因此设置构建状态。 91 | 92 | 我建议大家把上游任务运行在不同的奴隶结点/奴隶组中,而不是运行在和下游任务相同的奴隶结点/奴隶组中,否则它将占着设备不处理,浪费了资源 93 | 94 | ![](http://www.hidroh.com/assets/img/parallel-upstream-1.png) 95 | 96 | 97 | -------------------------------------------------------------------------------- /markdown简单教程.md: -------------------------------------------------------------------------------- 1 | # 标题1,文章的第一句就是标题1 2 | 3 | ## 这是标题2,其他小标题按照层级划分 4 | ### 标题3 5 | ### 标题4 6 | 7 | ## 文字加粗 8 | **hello** 9 | 10 | ## 引用 11 | 引用的格式为: >加上一个空格,然后加上文字内容。 12 | 在一些注意点时,我们可以使用引用来引起读着的注意。例如 : 13 | 14 | > 注意 : 不能在子线程中更新UI。 15 | 16 | 17 | ## 换行 18 | 19 | 句尾有四个以上的空格代表换行。示例 : 20 | 21 | **四个空格换行(注意句尾的空格)** 22 | 这里是一行,重新换行。 23 | 新的一行。 24 | 25 | 26 | **回车换行** 27 | 这里是一行,重新换行。 28 | 29 | 新的一行。 30 | 31 | ## 超链接 32 | **格式1 :** 33 | [链接名](地址) 34 | 35 | 示例 : 36 | [www.baidu.com](www.baidu.com) 37 | 38 | **格式2** 39 | [链接名][唯一标识] 40 | [唯一标识]: url地址 41 | 42 | 这种格式一般用在某段话中链接比较多的情况,为了保持原文简洁,将链接单独提出来。 43 | 44 | 示例 : 45 | [开发技术前线][devtf] 46 | 47 | [devtf]: http://www.devtf.cn 48 | 49 | ## 代码 50 | 51 | ```java 52 | public class Name { 53 | 54 | } 55 | ``` 56 | 57 | 适用于Android代码。需要注意的是开始处的```上面必须要有一个空行。 58 | 59 | 使用于iOS方面的代码 : 60 | 61 | ``` 62 | let customPresentAnimationController = CustomPresentAnimationController() 63 | 64 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 65 | 66 | if segue.identifier == "showAction" { 67 | let toViewController = segue.destinationViewController as UIViewController 68 | toViewController.transitioningDelegate = self 69 | } 70 | } 71 | ``` 72 | 73 | ## 显示图片 74 | 格式 : 75 | ![图片名字](图片链接) 76 | 77 | 示例 : 78 | ![p1](http://img.blog.csdn.net/20150416165133627) 79 | 80 | 81 | ## 列表 82 | 列表前面也是需要一个空行,并且在标识符后面需要一个空格,比如: 1. 和* 。 83 | 84 | 这是有序列表 : 85 | 86 | 1. 第一 87 | 2. 第二 88 | 3. 第三 89 | 90 | 无序列表 : 91 | 92 | * 翻译正确 93 | * 中文流畅 94 | * 高质量 95 | 96 | 另一种列表 : 97 | 98 | - 翻译项目 99 | - 认真校对撒 100 | - 发布文章 101 | 102 | ## 表格 103 | 格式为 : 104 | 105 | | 这是表头1 | 表头2 | 106 | |:-------------|:-------------:| 107 | | 第一行第一列,左对齐| 第一行第二列居中 | 108 | | 第二行第一列,左对齐| 第二行第二列,居中 | 109 | 110 | 111 | Markdown更详细的教程请查看[Markdown简明教程](http://wowubuntu.com/markdown/)。 -------------------------------------------------------------------------------- /others/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/others/.DS_Store -------------------------------------------------------------------------------- /others/FaceBook推出的Android图片加载库-Fresco/readme.md: -------------------------------------------------------------------------------- 1 | FaceBook推出的Android图片加载库-Fresco 2 | --- 3 | 4 | > 5 | * 原文链接:[Introducing Fresco: A new image library for Android](https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/) 6 | * 作者 : [tyrone Nicholas ](https://www.facebook.com/tyrone.nicholas) 7 | * 译者 : [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang) 8 | * 校对者: [Chaossss](https://github.com/chaossss) 9 | * 校对者: [bboyfeiyu](https://github.com/bboyfeiyu) 10 | * 校对者: [BillionWang](https://github.com/BillionWang) 11 | * 状态 : 完成 12 | 13 | 在Android设备上面,快速高效的显示图片是极为重要的。过去的几年里,我们在如何高效的存储图像这方面遇到了很多问题。图片太大,但是手机的内存却很小。每一个像素的R、G、B和alpha通道总共要占用4byte的空间。如果手机的屏幕是480*800,那么一张屏幕大小的图片就要占用1.5M的内存。手机的内存通常很小,特别是Android设备还要给各个应用分配内存。在某些设备上,分给Facebook App的内存仅仅有16MB。一张图片就要占据其内存的十分之一。 14 | 15 | 当你的App内存溢出会发生什么呢?它当然会崩溃!我们开发了一个库来解决这个问题,我们叫它Fresco。它可以管理使用到的图片和内存,从此App不再崩溃。 16 | 17 | ##内存区 18 | 为了理解Facebook到底做了什么工作,在此之前我们需要了解在Android可以使用的堆内存之间的区别。Android中每个App的Java堆内存大小都是被严格的限制的。每个对象都是使用Java的new在堆内存实例化,这是内存中相对安全的一块区域。内存有垃圾回收机制,所以当App不在使用内存的时候,系统就会自动把这块内存回收。 19 | 20 | 不幸的是,内存进行垃圾回收的过程正是问题所在。当内存进行垃圾回收时,内存不仅仅进行了垃圾回收,还把 Android 应用完全终止了。这也是用户在使用 App 时最常见的卡顿或短暂假死的原因之一。这会让正在使用 App 的用户非常郁闷,然后他们可能会焦躁地滑动屏幕或者点击按钮,但 App 唯一的响应就是:在 App 恢复正常之前,请求用户耐心等待 21 | 22 | 相比之下,Native堆是由C++程序的new进行分配的。在Native堆里面有更多可用内存,App只被设备的物理可用内存限制,而且没有垃圾回收机制或其他东西拖后腿。但是c++程序员必须自己回收所分配的每一块内存,否则就会造成内存泄露,最终导致程序崩溃。 23 | 24 | Android有另外一种内存区域,叫做Ashmem。它操作起来更像Native堆,但是也有额外的系统调用。Android 在操作 Ashmem 堆时,会把该堆中存有数据的内存区域从 Ashmem 堆中抽取出来,而不是把它释放掉,这是一种弱内存释放模式;被抽取出来的这部分内存只有当系统真正需要更多的内存时(系统内存不够用)才会被释放。当 Android 把被抽取出来的这部分内存放回 Ashmem 堆,只要被抽取的内存空间没有被释放,之前的数据就会恢复到相应的位置。 25 | 26 | ##可消除的Bitmap 27 | Ashmem不能被Java应用直接处理,但是也有一些例外,图片就是其中之一。当你创建一张没有经过压缩的Bitmap的时候,Android的API允许你指定是否是可清除的。 28 | 29 | ``` 30 | BitmapFactory.Options = new BitmapFactory.Options(); 31 | options.inPurgeable = true; 32 | Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options); 33 | ``` 34 | 经过上面的代码处理后,可清除的Bitmap会驻留在 Ashmem 堆中。不管发生什么,垃圾回收器都不会自动回收这些 Bitmap。当 Android 绘制系统在渲染这些图片,Android 的系统库就会把这些 Bitmap 从 Ashmem 堆中抽取出来,而当渲染结束后,这些 Bitmap 又会被放回到原来的位置。如果一个被抽取的图片需要再绘制一次,系统仅仅需要把它再解码一次,这个操作非常迅速。 35 | 36 | 这听起来像一个完美的解决方案,但是问题是Bitmap解码的操作是运行在UI线程的。Bitmap解码是非常消耗CPU资源的,当消耗过大时会引起UI阻塞。因为这个原因,所以Google不推荐使用这个[特性](http://developer.android.com/intl/zh-cn/reference/android/graphics/BitmapFactory.Options.html#inPurgeable)。现在它们推荐使用另外一个特性——inBitmap。但是这个特性直到Android3.0之后才被支持。即使是这样,这个特性也不是非常有用,除非 App 里的所有图片大小都相同,这对Fackbook来说显然是不适用的。一直到4.4版本,这个限制才被移除了。但我们需要的是能够运行在 Android 2.3 - 最新版本中的通用解决方案。 37 | 38 | ##自力更生 39 | 对于上面提到的“解码操作致使 UI 假死”的问题,我们找到了一种同时使 UI 显示和内存管理都表现良好的解决方法。如果我们在 UI 线程进行渲染之前把被抽取的内存区域放回到原来的位置,并确保它再也不会被抽取,那我们就可以把这些图片放在 Ashmem 里,同时不会出现 UI 假死的问题。幸运的是,Android 的 NDK 中有一个函数可以完美地实现这个需求,名字叫做 AndroidBitmap_lockPixels。这个函数最初的目的就是:在调用 unlockPixels 再次抽取内存区域后被执行。 40 | 41 | 当我们意识到我们没有必要这样做的时候,我们取得了突破。如果我们只调用lockPixels而不调用对应的unlockPixels,那么我们就可以在Java的堆内存里面创建一个内存安全的图像,并且不会导致UI线程加载缓慢。只需要几行c++代码,我们就完美的解决了这个问题。 42 | 43 | ##用C++的思想写Java代码 44 | 就像《蜘蛛侠》里面说的:“能力越强,责任越大。”可清除的 Bitmap 既不会被垃圾回收器回收,也不会被 Ashmem 内置的清除机制处理,这使得使用它们可能会造成内存泄露。所以我们只能靠自己啦。 45 | 46 | 在c++中,通常的解决方案是建立智能指针类,实现引用计数。这些需要利用到c++的语言特性——拷贝构造函数、赋值操作符和确定的析构函数。这种语法在Java之中不存在,因为垃圾回收器能够处理这一切。所以我们必须以某种方式在Java中实现C++的这些保证机制。 47 | 48 | 我们创建了两个类去完成这件事。其中一个叫做“SharedReference”,它有addReference和deleteReference两个方法,调用者调用时必须采取基类对象或让它在范围之外。一旦引用计数器归零,资源处理(Bitmap.recycle)就会发生。 49 | 50 | 然而,很显然,让Java开发者去调用这些方法是很容易出错的。Java语言就是为了避免做这样的事情的!所以SharedReference之上,我们构建了CloseableReference类。它不仅实现了Java的Closeable接口,而且也实现了Cloneable接口。它的构造器和clone()方法会调用addReference(),而close()方法会调用deleteReference()。所以Java开发者需要遵守下面两条简单的的规则: 51 | 52 | 1. 在分配CloseableReference新对象的时候,调用.clone()。 53 | 2. 在超出作用域范围的时候,调用.close(),这通常是在finally代码块中。 54 | 55 | 这些规则可以有效地防止内存泄漏,并让我们在像Fackbook的Android客户端这种大型的Java程序中享受Native内存管理和通信。 56 | 57 | ##不仅仅是加载程序,它是一个管道 58 | 在移动设备上显示图片需要很多的步骤: 59 | ![](http://i2.tietuku.com/4480c88a0d8004bf.png) 60 | 几个优秀的开源库都是按照这个顺序执行的,比如 Picasso,Universal Image Loader,Glide和 Volley等等。上面这些开源库为Android的发展做出了非常重要的贡献。我们相信Fresco在几个重要方面会表现的更好。 61 | 62 | 我们的不同之处在于把上面的这些步骤看作是管道,而不仅仅是加载器。每一个步骤和其他方面应该是尽可能独立的,把数据和参数传递进去,然后产生一个输出,就这么简单。它应该可以做一些操作,不管是并行还是串行。一些操作只能在特性条件下才能执行。一些有特殊要求的在线程上执行。除此之外,当我们考虑改进图像的时候,所有的图片就会变得非常复杂。很多人在低网速情况下使用Facebook,我们想要这些人能够尽快的看到图片,甚至经常是在图片没有完全下载完之前。 63 | 64 | ##不要烦恼,拥抱stream 65 | 在Java中,异步代码历来都是通过Future机制来执行的。在另外的线程里面代码被提交执行,然后一个类似Future的对象可以检查执行的结果是不是已经完成了。但是,这只在假设只有一种结果的情况下行得通。在处理渐进的图像的时候,我们希望可以完整而且连续的显示结果。 66 | 67 | 我们的解决方式是定义一个更广义的Future版本,叫做DataSource。它提供了一个订阅方法,调用者必须传入一个DataSubscriber和Executor。DataSubscriber可以从DataSource获取到处理中和处理完毕的结果,并且提供了很简单的方法来区分。因为我们需要非常频繁的处理这些对象,所以必须有一个明确的close调用,幸运的是,DataSource本身就是Closeable。 68 | 69 | 在后台,每一个箱子上面都实现了一个叫做“生产者/消费者”的新框架。在这个问题是,我们是从[ReactiveX](http://reactivex.io/)获取的灵感。我们的系统拥有和[RxJava](https://github.com/ReactiveX/RxJava)相似的接口,但是更加适合移动设备,并且有内置的对Closeables的支持。 70 | 71 | 保持简单的接口。Producer只有一个叫做produceResults的方法,这个方法需要一个Consumer对象。反过来,Consumer有一个onNewResult方法。 72 | 73 | 我们使用像这样的系统把Producer联系起来。假设我们有一个producer的工作是把类型I转化为类型O,那么它看起来应该是这个样子: 74 | 75 | ``` 76 | public class OutputProducer implements Producer { 77 | 78 | private final Producer mInputProducer; 79 | 80 | public OutputProducer(Producer inputProducer) { 81 | this.mInputProducer = inputProducer; 82 | } 83 | 84 | public void produceResults(Consumer outputConsumer, ProducerContext context) { 85 | Consumer inputConsumer = new InputConsumer(outputConsumer); 86 | mInputProducer.produceResults(inputConsumer, context); 87 | } 88 | 89 | private static class InputConsumer implements Consumer { 90 | private final Consumer mOutputConsumer; 91 | 92 | public InputConsumer(Consumer outputConsumer) { 93 | mOutputConsumer = outputConsumer; 94 | } 95 | 96 | public void onNewResult(I newResult, boolean isLast) { 97 | O output = doActualWork(newResult); 98 | mOutputConsumer.onNewResult(output, isLast); 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | 这可以使我们把非常复杂的步骤串起来,同时也可以保持他们逻辑的独立性。 105 | 106 | ##动画全覆盖 107 | 使用Facebook的人都非常喜欢Stickers,因为它可以以动画形式存储GIF和Web格式。如果支持这些格式,就需要面临新的挑战。因为每一个动画都是由不止一张图片组成的,你需要解码每一张图片,存储在内存里,然后显示出来。对于大一点的动画,把每一帧图片放在内存是不可行的。 108 | 109 | 我们建立了AnimatedDrawable,一个强大的可以呈现动画的Drawable,同时支持GIF和WebP格式。AnimatedDrawable实现标准的Android Animatable接口,所以调用者可以随意的启动或者停止动画。为了优化内存使用,如果图片足够小的时候,我们就在内存里面缓存这些图片,但是如果太大,我们可以迅速的解码这些图片。这些行为调用者是完全可控的。 110 | 111 | 所有的后台都用c++代码实现。我们保持一份解码数据和元数据解析,如宽度和高度。我们引用技术数据,它允许多个Java端的Drawables同时访问一个WebP图像。 112 | 113 | ##如何去爱你?我来告诉你... 114 | 当一张图片从网络上下载下来之后,我们想显示一张占位图。如果下载失败了,我们就会显示一个错误标志。当图片加载完之后,我们有一个渐变动画。通过使用硬件加速,我们可以按比例放缩,或者是矩阵变换成我们想要的大小然后渲染。我们不总是按照图片的中心进行放缩,那么我们可以自己定义放缩的聚焦点。有些时候,我们想显示圆角甚至是圆形的图片。所有的这些操作都应该是迅速而平滑的。 115 | 116 | 我们之前的实现是使用Android的View对象——时机到了,可以使用ImageView替换出占位的View。这个操作是非常慢的。改变View会让Android强制刷新整个布局,当用户滑动的时候,这绝对不是你想看到的效果。比较明智的做法是使用Android的Drawables,它可以迅速的被替换。 117 | 118 | 所以我们创建了Drawee。这是一个像MVC架构的图片显示框架。该模型被称为DraweeHierarchy。它被实现为Drawables的一个层,对于底层的图像而言,每一个曾都有特定的功能——成像、层叠、渐变或者是放缩。 119 | 120 | DraweeControllers通过管道的方式连接到图像上——或者是其他的图片加载库——并且处理后台的图片操作。他们从管道接收事件并决定如何处理他们。他们控制DraweeHierarchy实际上的操作——无论是占位图片,错误条件或是完成的图片。 121 | 122 | DraweeViews 的功能不多,但都是至关重要的。他们监听Android的View不再显示在屏幕上的系统事件。当图片离开屏幕的时候,DraweeView可以告诉DraweeController关闭使用的图像资源。这可以避免内存泄露。此外,如果它已经不在屏幕范围内的话,控制器会告诉图片管道取消网络请求。因此,像Fackbook那样滚动一长串的图片的时候,不会频繁的网络请求。 123 | 124 | 通过这些努力,显示图片的辛苦操作一去不复返了。调用代码只需要实例化一个DraweeView,然后指定一个URI和其他可选的参数就可以了。剩下的一切都会自动完成。开发人员不需要担心管理图像内存,或更新图像流。Fresco为他们把一切都做了。 125 | 126 | ##Fresco 127 | 完成这个图像显示和操作复杂的工具库之后,我们想要把它分享到Android开发者社区。我们很高兴的宣布,从今天起,这个项目已经作为[开源代码](http://github.com/facebook/fresco)了! 128 | 129 | 壁画是绘画技术,几个世纪以来一直受到世界各地人们的欢迎。我们许多伟大的艺术家使用这种名字,从意大利文艺复兴时期的大师拉斐尔到壁画艺术家斯里兰卡。我们并不是假装达到这个伟大的水平,我们真的希望Android开发者能像我们当初享受创建这个开源库的过程一样,非常享受的使用它。 130 | 131 | ##更多 132 | [Fresco中文文档](http://fresco-cn.org/) 133 | -------------------------------------------------------------------------------- /others/Google推荐的图片加载库Glide介绍/readme.md: -------------------------------------------------------------------------------- 1 | Google推荐的图片加载库Glide介绍 2 | --- 3 | 4 | > 5 | * 原文链接:[Google推荐的图片加载库Glide介绍](http://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en) 6 | * 作者 : [nuuneoi](http://inthecheesefactory.com/) 7 | * 译者 : [jianghejie](https://github.com/jianghejie) 8 | * 校对者 : [chaossss](https://github.com/chaossss) 9 | * 状态 : 完成 10 | 11 | 12 | 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 [Glide](https://github.com/bumptech/glide) 的图片加载库,作者是bumptech。这个库被广泛的运用在google的开源项目中,包括2014年google I/O大会上发布的官方app。 13 | 14 | 毫无疑问,这个库引起了我的兴趣。于是我花了一个晚上研究和把玩它,将它的实现原理分析清楚以后,我决定写一篇博文分享一些自己的经验。在开始之前我想说,Glide和Picasso有90%的相似度,准确的说,我觉得它就像 Picasso 的克隆体。 15 | 16 | 不管怎样,Glide 和 Picasso在细节上还是有不少区别的,接下来我会让你们了解到其中的差异。 17 | 18 | ##导入库 19 | 20 | Picasso和Glide都在jcenter上。在项目中添加依赖非常简单: 21 | Picasso 22 | ```gradle 23 | dependencies { 24 | compile 'com.squareup.picasso:picasso:2.5.1' 25 | } 26 | ``` 27 | Glide 28 | ```gradle 29 | dependencies { 30 | compile 'com.github.bumptech.glide:glide:3.5.2' 31 | compile 'com.android.support:support-v4:22.0.0' 32 | } 33 | ``` 34 | 不管怎样,Glide 需要 Android Support Library v4 包,千万不要忘了像上面的代码做的那样添加 Android Support Library v4 包的依赖。不过这都不是什么大问题,因为现在 Android Support Library v4 基本是每一个新 Android 项目的标配了。 35 | 36 | ##基础 37 | 38 | 就如我所说的Glide和Picasso非常相似,Glide加载图片的方法和Picasso如出一辙。 39 | 40 | Picasso 41 | ```java 42 | Picasso.with(context) 43 | .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg") 44 | .into(ivImg); 45 | ``` 46 | Glide 47 | 48 | ```java 49 | Glide.with(context) 50 | .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg") 51 | .into(ivImg); 52 | ``` 53 | 虽然两者看起来非常相似,但是 Glide 的代码无疑设计得更好,因为 Glide 的 with() 方法不光接受 Context,还接受 Activity 和 Fragment。此外,with() 方法还能自动地从你放入的各种东西里面提取出 Context,供它自己使用。 54 | 55 | ![](http://jcodecraeer.com/uploads/20150327/1427445293711143.png) 56 | 57 | 同 时将Activity/Fragment作为with()参数的好处是:图片加载会和Activity/Fragment的生命周期保持一致,比如 Paused状态在暂停加载,在Resumed的时候又自动重新加载。所以我建议传参的时候传递Activity 和 Fragment给Glide,而不是Context。 58 | 59 | ##默认Bitmap格式是RGB_565 60 | 61 | 下面是加载图片时和Picasso的比较(1920x1080 像素的图片加载到768x432的ImageView中) 62 | 63 | ![](http://jcodecraeer.com/uploads/20150327/1427445293137409.jpg) 64 | 65 | 可以看到Glide加载的图片质量要差于Picasso(ps:我看不出来哈),为什么?这是因为Glide默认的Bitmap格式是RGB_565 ,比ARGB_8888格式的内存开销要小一半。下面是Picasso在ARGB8888下与Glide在RGB565下的内存开销图(应用自身占用了8m,因此以8为基准线比较): 66 | 67 | ![](http://jcodecraeer.com/uploads/20150327/1427445293965030.png) 68 | 69 | 如果你觉得 Glide 在默认的 RGB_565 格式下加载的图片质量可以接受的话,可以什么都不做。但如果你觉得难以接受,或者是你的实际需求对图片的质量有更高的要求的话,你可以像下面的代码那样创建一个 GlideModule 子类,把 Bitmap 的格式转换到 ARGB_8888: 70 | 71 | ```java 72 | public class GlideConfiguration implements GlideModule { 73 | 74 | @Override 75 | public void applyOptions(Context context, GlideBuilder builder) { 76 | // Apply options to the builder here. 77 | builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); 78 | } 79 | 80 | @Override 81 | public void registerComponents(Context context, Glide glide) { 82 | // register ModelLoaders here. 83 | } 84 | } 85 | ``` 86 | 然后在AndroidManifest.xml中将GlideModule定义为meta-data 87 | 88 | ```java 89 | 91 | ``` 92 | ![](http://jcodecraeer.com/uploads/20150327/1427445294447874.jpg) 93 | 94 | 这样看起来就会好很多。 95 | 96 | 我们再来看看内存开销图,虽然看起来这次 Glide 的内存开销接近于上次的两倍,但是Picasso的内存开销仍然远大于Glide。 97 | 98 | ![](http://jcodecraeer.com/uploads/20150327/1427445294918728.png) 99 | 100 | 但是上面那样做的问题在于你需要手动计算 ImageView 的尺寸,又或者是你对 ImageView 设置了具体的尺寸大小,为了解决这样的麻烦,你可以在 Picasso 中通过这样做简化你的代码: 101 | 102 | ```java 103 | Picasso.with(this) 104 | .load("http://nuuneoi.com/uploads/source/playstore/cover.jpg") 105 | .resize(768, 432) 106 | .into(ivImgPicasso); 107 | ``` 108 | 但是问题在于你需要主动计算ImageView的大小,或者说你的ImageView大小是具体的值(而不是wrap_content),你也可以这样: 109 | 110 | ```java 111 | Picasso.with(this) 112 | .load("http://nuuneoi.com/uploads/source/playstore/cover.jpg") 113 | .fit() 114 | .centerCrop() 115 | .into(ivImgPicasso); 116 | ``` 117 | 现在Picasso的内存开销就和Glide差不多了。 118 | 119 | ![](http://jcodecraeer.com/uploads/20150327/1427445294433243.png) 120 | 121 | 虽然内存开销差距不大,但是在这个问题上Glide完胜Picasso。因为Glide可以自动计算出任意情况下的ImageView大小。 122 | ##Image质量的细节 123 | 124 | 这是将ImageView还原到真实大小时的比较。 125 | ![](http://jcodecraeer.com/uploads/20150327/1427445294704430.png) 126 | 127 | 很显然,Glide 加载的图片有些像素点变得很模糊,看起来也没有 Picasso 那么平滑。而且直到现在,我也没有找到一个可以直观改变图片大小调整算法的方法。 128 | 129 | 但是这并不算什么坏事,因为很难察觉。 130 | 131 | 132 | ##磁盘缓存 133 | 134 | Picasso和Glide在磁盘缓存策略上有很大的不同。我们刚刚做了一个使用 Glide 和 Picasso 加载同一张高清图片的实验,我在实验后检查缓存目录时发现: Glide 缓存的图片和 ImageView 的尺寸相同,而 Picasso 缓存的图片和原始图片的尺寸相同。 135 | 136 | ![](http://jcodecraeer.com/uploads/20150327/1427445294110987.jpg) 137 | 138 | 上面提到的平滑度的问题依然存在,而且如果加载的是RGB565图片,那么缓存中的图片也是RGB565。 139 | 140 | 我 尝试将ImageView调整成不同大小,但不管大小如何Picasso只缓存一个全尺寸的。Glide则不同,它会为每种大小的ImageView缓存 一次。尽管一张图片已经缓存了一次,但是假如你要在另外一个地方再次以不同尺寸显示,需要重新下载,调整成新尺寸的大小,然后将这个尺寸的也缓存起来。 141 | 142 | 具体说来就是:假如在第一个页面有一个200x200的ImageView,在第二个页面有一个100x100的ImageView,这两个ImageView本来是要显示同一张图片,却需要下载两次。 143 | 144 | 不过,你可以改变这种行为,让Glide既缓存全尺寸又缓存其他尺寸: 145 | 146 | ```java 147 | Glide.with(this) 148 | .load("http://nuuneoi.com/uploads/source/playstore/cover.jpg") 149 | .diskCacheStrategy(DiskCacheStrategy.ALL) 150 | .into(ivImgGlide); 151 | ``` 152 | 下次在任何ImageView中加载图片的时候,全尺寸的图片将从缓存中取出,重新调整大小,然后缓存。 153 | 154 | Glide的这种方式优点是加载显示非常快。而Picasso的方式则因为需要在显示之前重新调整大小而导致一些延迟,即便你添加了这段代码来让其立即显示: 155 | ```java 156 | //Picasso 157 | .noFade(); 158 | ``` 159 | ![](http://jcodecraeer.com/uploads/allimg/150327/163Aa632-0.gif) 160 | 161 | Picasso 和 Glide 在磁盘缓存策略上各有所长,你应该根据自己的需求选择最合适的。 162 | 163 | 对我而言,我更喜欢Glide,因为它远比Picasso快,虽然需要更大的空间来缓存。 164 | ##特性 165 | 166 | 你可以做到几乎和Picasso一样多的事情,代码也几乎一样。 167 | 168 | Image Resizing 169 | ```java 170 | // Picasso 171 | .resize(300, 200); 172 | 173 | // Glide 174 | .override(300, 200); 175 | ``` 176 | 177 | Center Cropping 178 | ```java 179 | // Picasso 180 | .centerCrop(); 181 | 182 | // Glide 183 | .centerCrop(); 184 | ``` 185 | 186 | Transforming 187 | ```java 188 | // Picasso 189 | .transform(new CircleTransform()) 190 | 191 | // Glide 192 | .transform(new CircleTransform(context)) 193 | ``` 194 | 设置占位图或者加载错误图: 195 | ```java 196 | // Picasso 197 | .placeholder(R.drawable.placeholder) 198 | .error(R.drawable.imagenotfound) 199 | 200 | // Glide 201 | .placeholder(R.drawable.placeholder) 202 | .error(R.drawable.imagenotfound) 203 | ``` 204 | 205 | 正如我在博文开头所说,如果你已经熟悉如何使用 Picasso,那从Picasso转换到Glide对你来说就是小菜一碟。 206 | 207 | 208 | ##有什么Glide可以做而Picasso 做不到 209 | 210 | Glide可以加载GIF动态图,而Picasso不能。 211 | 212 | ![](http://jcodecraeer.com/uploads/20150327/1427445366503084.gif) 213 | 214 | 215 | 同时因为Glide和Activity/Fragment的生命周期是一致的,因此gif的动画也会自动的随着Activity/Fragment的状态暂停、重放。Glide 的缓存在gif这里也是一样,调整大小然后缓存。 216 | 217 | 但是从我的一次测试结果来看,用 Glide 显示动画会消耗很多内存,因此谨慎使用。 218 | 219 | 除了gif动画之外,Glide还可以将任意本地视频解码成一张静态图片。 220 | 221 | 还有一个特性是你可以配置图片显示的动画,而Picasso只有一种动画:fading in。 222 | 223 | 最后一个是可以使用thumbnail()产生一个你所加载图片的thumbnail。 224 | 225 | 其实还有一些特性,不过不是非常重要,比如将图像转换成字节数组等。 226 | 配置 227 | 228 | 有许多可以配置的选项,比如大小,缓存的磁盘位置,最大缓存空间,位图格式等等。可以在这个页面查看这些配置 Configuration 。 229 | 230 | ##库的大小 231 | 232 | Picasso (v2.5.1)的大小约118kb,而Glide (v3.5.2)的大小约430kb。 233 | 234 | ![](http://jcodecraeer.com/uploads/20150327/1427453389115686.png) 235 | 236 | 不过312kb的差距并不是很重要。 237 | 238 | Picasso和Glide的方法个数分别是840和2678个。 239 | 240 | ![](http://jcodecraeer.com/uploads/20150327/1427453390188737.png) 241 | 242 | 必须指出,对于DEX文件65535个方法的限制来说,2678是一个相当大的数字了。建议在使用Glide的时候开启ProGuard。 243 | 244 | 245 | ##总结 246 | 247 | Glide和Picasso都是非常完美的库。Glide加载图像以及磁盘缓存的方式都要优于Picasso,速度更快,并且Glide更有利于减少OutOfMemoryError的发生,GIF动画是Glide的杀手锏。不过Picasso的图片质量更高。你更喜欢哪个呢? 248 | 249 | 虽然我使用了很长时间的Picasso,但是我得承认现在我更喜欢Glide。我的建议是使用Glide,但是将Bitmap格式换成 ARGB_8888、让Glide缓存同时缓存全尺寸和改变尺寸两种。 250 | 251 | 252 | ##相关资源 253 | 254 | - [Glide 3.0: a media management library for Android](http://google-opensource.blogspot.com/2014/09/glide-30-media-management-library-for.html) 255 | - [Glide Wiki](https://github.com/bumptech/glide/wiki) 256 | - [Android Picasso vs Glide](http://pluu.github.io/android%20study/2015/01/15/android-glide-picasso/) 257 | - [Android: Image loading libraries Picasso vs Glide](http://vardhan-justlikethat.blogspot.com/2014/09/android-image-loading-libraries-picasso.html) 258 | 259 | -------------------------------------------------------------------------------- /others/VectorDrawable系列/VectorDrawable – 第二章/readme.md: -------------------------------------------------------------------------------- 1 | VectorDrawable-第二章 2 | --- 3 | 4 | > 5 | * 原文链接:[VectorDrawables – Part 2](https://blog.stylingandroid.com/vectordrawables-part-2/) 6 | * 译者 : [jianghejie](https://github.com/jianghejie) 7 | * 译者博文链接 : [jcodecraeer.com](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0301/2514.html) 8 | 9 | [上篇文章](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0201/2396.html) 中,我们探讨了如何将svg图片转换成VectorDrawable,以在适应不同分辨率的同时减少资源文件的个数,同时也更易于维护。但是这并不是VectorDrawable的唯一好处-还可以用来制作动画。这篇文章就是关于如何用VectorDrawable来实现android机器人耸肩的效果! 10 | 11 | 我们将要实现的动画很简单,在保持身体不动的同时,让头部和手臂在Y方向上上下移动。表面上看实现起来很复杂,因为我们这里只有一个Drawable(要是一般的Drawable估计的通过绘制来实现了)。但是有一个控件却可以使事情变得非常简单,那就是在Lollipop中和VectorDrawable一起被引入的AnimatedVectorDrawable。在上篇文章中,我们讲到了path元素的name属性是为了描述path的用处,但它还可以用在为指定path指定一个动画。本例中我们需要动画的path元素是头部,左眼,右眼,左臂,右臂。问题是单个的是没有translateX和translateY属性的,因此无法使用属性动画来控制translateY,而元素是有的,所以我们需要先将相关的元素包裹在一个个的元素中: 12 | ```xml 13 | 14 | 19 | 20 | 21 | 25 | 29 | 33 | 34 | 35 | 39 | 43 | 44 | 48 | 49 | 50 | ``` 51 | 现在再我们定义一个包含的drawable文件,这个文件的作用是将动画应用在指定的group中,使得某些部分的path动起来: 52 | ```xml 53 | 54 | 56 | 57 | 60 | 61 | 64 | 65 | ``` 66 | 67 | 虽然我知道完全可以将头、眼、和手臂放在一个group中,但是为了演示一个是如何控制多个group的动画的,我有意把他们分成两部分,实际运用中,不同的group目标动画肯定是不一样的。 68 | 69 | 的最外层元素指定了我们要动画的VectorDrawable资源-在本例中这个资源是android.xml,里面的元素根据group的name属性指定group的动画效果。 70 | 71 | , , , 和 元素都有各自可以播放动画的属性,查阅VectorDrawable JavaDocs你会找到每种元素到底有那些属性,以便针对这些属性播放特定的动画。比如:要使用tint效果需要作用于元素上,而修改填充颜色则需要作用于元素。 72 | 73 | 耸肩的效果很简单,只是个重复移动Y轴的animator: 74 | ```xml 75 | 76 | 77 | 85 | 86 | ``` 87 | 为了运行这个动画我们需要做几件事情。首先,要将ImageView的src改为动画效果的drawable。 88 | ```xml 89 | 98 | 99 | 105 | 106 | 107 | ``` 108 | 如果运行现在的代码,我们只能看到静态的图片。这是因为我们需要手动调用播放动画。我们将在Activity中去调用,如果Drawable是Animatable(AnimatedVectorDrawable实现了Animatable)的实例,将开始动画。 109 | ```xml 110 | public class VectorDrawablesActivity extends Activity { 111 | 112 | @Override 113 | protected void onCreate(Bundle savedInstanceState) { 114 | super.onCreate(savedInstanceState); 115 | setContentView(R.layout.activity_vector_drawables); 116 | ImageView androidImageView = (ImageView) findViewById(R.id.android); 117 | Drawable drawable = androidImageView.getDrawable(); 118 | if (drawable instanceof Animatable) { 119 | ((Animatable) drawable).start(); 120 | } 121 | } 122 | } 123 | ``` 124 | 运行代码就会发现指定了animation的path出现动画效果: 125 | 126 | ![](http://jcodecraeer.com/uploads/20150306/1425623349523137.gif) 127 | 128 | 在接下来的文章中,我们将更深入的去了解AnimatedVectorDrawable,实现更酷的效果。 129 | 130 | 本篇文章的源代码在 [这里](http://code.stylingandroid.com/vectordrawables/src/f4c31878fdfa3b9205bb58016c20c789e4dc426a/?at=Part2). 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /others/如何在本地搭建一个Android应用crashing跟踪系统-ACRA/readme.md: -------------------------------------------------------------------------------- 1 | 如何在本地搭建一个Android应用crashing跟踪系统-ACRA 2 | --- 3 | > 4 | * 原文链接 : [How to setup ACRA, an Android Application Crash Tracking system, on your own host](http://inthecheesefactory.com/blog/how-to-install-and-use-acra-android/en) 5 | * 作者 : [nuunei](http://inthecheesefactory.com/blog) 6 | * 译者 : [sundroid](https://github.com/sundroid) 7 | * 校对者: [sundroid](https://github.com/sundroid) 8 | * 状态 : 校对完成 9 | 10 | 11 | 在开发一款移动app时的一个事实是会有很多约束,比如硬件(CPU、RAM、Battery 等等)。如果你的代码设计不是很好,你会遇到一个非常让人头疼的问题:“Crash”,研究表明: 12 | 13 | > 14 | *应用崩溃时绝大多数应用使用者抱怨的问题。 15 | 16 | 此外 17 | 18 | > 19 | * 如果应用程序联系崩溃三次,大约一半的用户将会卸载这款应用。 20 | 21 | 22 | 崩溃跟踪系统,帮助开发者能够直接的葱用户的设备收集每一个崩溃原因,是不是发现这个功能很特殊。目前最受欢迎的崩溃跟踪系统是 [Crashlytics](http://fabric.io/)和[Parse Crash Reporting](http://blog.parse.com/2014/12/09/introducing-parse-crash-reporting-2/),这两个系统都是完全免费的。开发者可以免费的集成他们在自己的应用中。不论什么时候app崩溃了,整个bug信息将会发送到后台,允许开发人员用最简单的方式去解决这些bug。通过这个方法,你可以在短时间内迭代一款不会影响正常使用的应用。 23 | 24 | 25 | 然而,提供崩溃信息收集的厂商收集这些崩溃信息同时也收集了用户信息,这可能让引起大公司担心用户隐私。 26 | 27 | 28 | 所以,这儿有没有崩溃信息跟踪系统可以让我们搭建在自己的服务器上?那么就不存在泄漏用户隐私的担忧了。当然有了,并且这个系统提供了非常简单的搭建方法。在这里我们来介绍下[Application Crash Reporting on Android (ACRA)](https://www.acra.gov.sg/home/),一个库允许Android应用自动地发送崩溃信息到自己的服务器。 29 | 30 | 下面将会介绍如何去搭建。 31 | 32 | ## 搭建一个服务器 ## 33 | 34 | 服务器端是一个先决条件,让我们先从搭建服务器端开始。 35 | 36 | 由于ACRA设计的很好并且很受欢迎。它允许开发者开发自己的服务器系统,并且现在我们可以看到很多这样的系统。即便如此我觉得最好的是Acralyzer,这个也是由ACRA团队研发。Acralyzer工作在Apache CouchDB,所以 37 | 这里没有必要安装除了CouchDB以外的软件。 38 | 39 | Acralyzer是一个功能相当齐全的后端崩溃跟踪系统。来自不同原因的相同堆栈轨迹将会被分组成一个单一的问题。如果你解决了所有问题,你可以非常便捷的关闭Acralyzer服务,并且这种关闭服务的操作时实时的,我发现系统唯一的缺点是它的ui让人感到不舒服,但是谁会在乎这个?它是为开发者开发的。 40 | 41 | 安装起来也很简单,下面将介绍如何在Ubuntu安装Acralyzer。 42 | 43 | 打开命令窗口,开始安装couchdb 44 | > 45 | *apt-get install couchdb 46 | 47 | Test the installation with this command: 48 | 49 | 测试是否安装成功。 50 | > 51 | *curl http://127.0.0.1:5984 52 | 53 | 如果正确安装,会显示如下: 54 | > 55 | *{"couchdb":"Welcome","version":"1.2.0"} 56 | 57 | 编辑etc/couchdb/local.ini允许我们通过外部IP(默认的访问会通过127.0.0.1)去访问CouchDB。仅仅改变两行实现这个功能: 58 | > 59 | *;port = 5984 60 | *;bind_address = 127.0.0.1 61 | 62 | 63 | 改变为 64 | > 65 | *port = 5984 66 | *bind_address = 0.0.0.0 67 | 68 | 在同一个文件夹下,你需要添加username/password作为管理员账户。找到这一行(应该会在文件末尾) 69 | > 70 | *[admins] 71 | 72 | 下一行添加username/password 形式为username = password,比如: 73 | > 74 | *[nuuneoi = 12345] 75 | 76 | 77 | 请不要对在这里书写明文密码感到担心,一旦CouchDB重启后,你的密码将会自动地散列,并且将会是不可读的, 78 | 79 | 保存你刚刚编辑的文件同时通过命令行重启hashed: 80 | > 81 | *curl -X POST http://localhost:5984/_restart -H"Content-Type: application/json" 82 | 83 | 从现在起,你将可以通过浏览器访问CouchDB。这个web服务我们称之为Futon,一个CouchDB UI管理后台。在你的浏览器中打开这个地址。 84 | 85 | > 86 | *http://:5984/_utils 87 | 88 | 让我们开始吧,Futon。 89 | ![](http://inthecheesefactory.com/uploads/source/acra/futon.png) 90 | 91 | 92 | 首先,通过你之前设置的管理员账号登陆这个系统。 93 | 94 | 现在我们开始安装一个acro-storage (Acralyzer's Storage Endpoing).在左边的菜单,点击Replicator,然后填写远程存储改为本地存储的表单。 95 | > 96 | *from Remote Database: http://get.acralyzer.com/distrib-acra-storage to Local Database: acra-myapp 97 | 98 | 点击Replicate然后等待,知道这个过程结束。 99 | 100 | 下一步安装Acralyzer通过同样的方法,但是参数是不同的。 101 | > 102 | *from Remote Database: http://get.acralyzer.com/distrib-acralyzer to Local Database: acralyzer 103 | 104 | 点击Replicate安装。 105 | 106 | 如果你操作正确,系统将会有两个数据库,acra-myapp 和 acralyzer。 107 | 108 | ![](http://inthecheesefactory.com/uploads/source/acra/acra3.png) 109 | 110 | 我门就快大功告成了,下一步,我们需要为这个客户端创建一个用户,打开浏览器,然后打开这个网址: 111 | > 112 | *http://:5984/acralyzer/_design/acralyzer/index.html 113 | ![](http://inthecheesefactory.com/uploads/source/acra/admincreateuser.png) 114 | 115 | 填写你想要的Username/Password,然后点击Create User,这些信息将会出现。 116 | ![](http://inthecheesefactory.com/uploads/source/acra/users2.png) 117 | 118 | 复制这些信息,然后粘贴到你的文本编辑器,我们可能会用这个在客户端设置。 119 | 120 | 最后一件事是限制访问权限来保护在acra-myapp里面的数据,进入acra-myapp然后点击Securities,填写用户角色分配; 121 | > 122 | *["reader"] 123 | ![](http://inthecheesefactory.com/uploads/source/acra/reader.png) 124 | 125 | 完工! 126 | 在这些结束后,你可以通过同一个网址访问这个控制台,去Admin选项卡,并选择Users。 127 | 128 | > 129 | *[http://:5984/acralyzer/_design/acralyzer/index.html 130 | 131 | 请注意acro-myapp只能够为一款应用服务。以防你想为另外一款应用创建一个后台,请通过同样的过程复制另外一个acro-storage,但是改变本地数据库名为acra-。请注意,有必要去通过acra- 去开启服务,或者它不能够在仪表盘中罗列为选择项供我们去选择。 132 | 133 | 如果在系统中有不止一款应用,在Acralyzer的仪表盘中将会有一个下拉列表,让我们去选择看哪一个的问题。你可以试一试。 134 | 135 | 在客户端设置ACRA。 136 | 137 | 在客户端中设置ACRA很简单,首先,在你的 build.gradle里添加ACRA的依赖配置信息。 138 | > 139 | *compile 'ch.acra:acra:4.6.1' 140 | 141 | 同步你的gradle文件,然后创建一个自定义Application类,但是不要忘记在AndroidManifest.xml中定义这个Application类。(我假设每一个Android开发者不会忘记这么做)。 142 | 143 | 在你创建的自定义的Application类中添加 @ReportCrashes注解。 144 | 145 | ```java 146 | import android.app.Application; 147 | import org.acra.ACRA; 148 | import org.acra.annotation.ReportsCrashes; 149 | import org.acra.sender.HttpSender; 150 | 151 | /** 152 | * Created by nuuneoi on 2/19/2015. 153 | */ 154 | 155 | @ReportsCrashes( 156 | ) 157 | public class MainApplication extends Application { 158 | 159 | @Override 160 | public void onCreate() { 161 | super.onCreate(); 162 | 163 | ACRA.init(this); 164 | } 165 | 166 | } 167 | ``` 168 | 169 | 现在我们复制服务器端生成的信息,并且像下面那样粘贴到@ReportsCrashes中。 170 | 171 | ```java 172 | @ReportsCrashes( 173 | httpMethod = HttpSender.Method.PUT, 174 | reportType = HttpSender.Type.JSON, 175 | formUri = "http://YOUR_SERVER_IP:5984/acra-myapp/_design/acra-storage/_update/report", 176 | formUriBasicAuthLogin = "tester", 177 | formUriBasicAuthPassword = "12345" 178 | ) 179 | ``` 180 | 181 | 最后一步,不要忘记添加在AndroidManifest.xml网络访问权限,否则ACRA可能无法发送这些日志信息到你的服务器上。 182 | > 183 | * 184 | 185 | 186 | 恭喜,现在所有的配置都已经完成,ACRA可以正常的工作,帮助你收集崩溃日志信息,从而你可以快速解决应用出现的问题。 187 | 188 | ##测试## 189 | 190 | 现在我们通过在Activity中强制一些崩溃来做一些测试,例子如下: 191 | 192 | ```java 193 | extView tvHello; 194 | 195 | @Override 196 | protected void onCreate(Bundle savedInstanceState) { 197 | super.onCreate(savedInstanceState); 198 | setContentView(R.layout.activity_main); 199 | 200 | tvHello.setText("Test Crash"); 201 | } 202 | ``` 203 | 204 | 运行你的应用,然后改变崩溃的原因,再运行一次。查看你的仪表盘,你将会看到这些发送到后台的bug。 205 | 206 | ![](http://inthecheesefactory.com/uploads/source/acra/acra.png) 207 | 208 | 每一个bug来自不同用户不同时间,并且这些报告被分组了。 209 | ![](http://inthecheesefactory.com/uploads/source/acra/reportslist.png) 210 | 211 | 仔细看看这些报告信息,你将会发现他们都是完整的崩溃信息。 212 | ![](http://inthecheesefactory.com/uploads/source/acra/stacktrace.png) 213 | 214 | 并且非常多的信息,足足有7页。 215 | 216 | 如果你修复这些bug后,你可以关闭这个问题,通过简单的点击在页面中高亮显示的"bug"图标, 217 | ![](http://inthecheesefactory.com/uploads/source/acra/closeissue.png) 218 | 219 | 希望这篇文章对你们有用,特别是对于一些需要应用崩溃信息收集但是却担心隐私信息的大公司可以来使用这个系统。 220 | 221 | 事实上ACRA还有许多其他特性,比如:当一个月崩溃时显示Toast 或者 popup 来报告这些信息。你可以在ACRA网站上发现这些选项。 222 | 223 | Acralytics也一样,这里有许多其他特性可以使用,比如,你可以设置一个服务器来发送邮件给我们。 224 | 225 | 下一篇博客再见。 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | -------------------------------------------------------------------------------- /others/清晰的软件架构/readme.md: -------------------------------------------------------------------------------- 1 | The Clean Architecture 2 | --- 3 | 4 | 5 | >* 原文链接 : [The Clean Architecture](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html) 6 | > * [Robert Martin](http://blog.8thlight.com/) 7 | > * 译者:[zimoguo](https://github.com/zimoguo) 8 | > * 校对者:[Mr.Simple](https://github.com/bboyfeiyu) 9 | 10 | ![](http://blog.8thlight.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg) 11 | 12 | 在过去的几年中,我们已经看到了关于系统框架的一些想法 : 13 | 14 | * [Hexagonal Architecture(六角架构)](http://alistair.cockburn.us/Hexagonal+architecture)(a.k.a. Ports and Adapters) 这种架构是由Alistair Cockburn提出的,并由Steve Freeman和Nat Pryce在他们的书[Growing Object Oriented Software](http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627)中提出。 15 | * [Onion Architecture(洋葱架构)](http://jeffreypalermo.com/blog/the-onion-architecture-part-1/) 提出者是Jeffrey Palermo。 16 | * [尖叫架构(Screaming Architecture)](http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html) 提出者是Uncle Bob(就是这篇文章的作者)。 17 | * [DCI架构](http://www.amazon.com/Lean-Architecture-Agile-Software-Development/dp/0470684208/) 提出者是James Coplien和Trygve Reenskaug。 18 | * [BCE架构](http://www.amazon.com/Object-Oriented-Software-Engineering-Approach/dp/0201544350) 提出者是Ivar Jacobson。在他的书《Object Oriented Software Engineering: A Use-Case Driven Approach》中有大量提及对这种架构的说明。 19 | 20 | 虽然这些文章在细节上有所不同,总体来说是非常相似的.它们关注点分离,通过将软件划分成层达到分离效果.每层最少包含一个业务规划或者接口。 21 | 22 | 这些架构有以下特点: 23 | 24 | 1. 独立框架 25 | 这些架构不依赖某些特定库的加载,允许你使用框架作为工具,不会限制你的系统; 26 | 2. 可测试 27 | 业务规划在没有UI界面,数据库,网页服务器或者其他外部元素的情况下进行测试; 28 | 3. 独立于UI 29 | UI很容易改变,而不用改变系统的其他部分,网页界面可以被控制台界面替换,同时还不用改变业务规划; 30 | 4.独立于数据库 31 | 你可以切换到Oracle,SQL Server,Mongo,BigTable,CouchDB或者其他类型数据库,业务规则不与数据库绑定; 32 | 5.独立于任何外部代理 33 | 事实上你的业务规划根本不需要知道外面的世界 34 | 35 | 在这篇文章顶部的图表是将这些架构的理念集成在一起。 36 | 37 | 38 | ## 依赖规则 39 | 同心圆代表软件的不同区域,一般情况下,越接近中心位置软件的级别会变得越来越高,外圆圈是机制,内圆圈是策略。 40 | 41 | 覆盖规则使得架构遵循依赖规则,这条规则表明,源代码只能向内依赖,内侧圆环不了解关于外侧圆环的一起,原则上,外圈圆环声明的名称不需要在内侧圆环中提到,其中包括函数,类,变量或者其他软件实体的名字 42 | 43 | 出于同样的原因,在外圆圈中使用的数据格式不应该使用在内圆圈,特别是格式是由外圆圈的框架产生的情况,我们不希望外圆圈影响内圆圈的内容 44 | 45 | ##实体 46 | 实体封装项目范围内的业务规则,一个实体可以是一个对象的方法,或者是一组数据结构和功能.只要在项目中实体被不同的应用所使用即可。 47 | 48 | 如果你没有项目,只是单纯的写一个应用程序,那么这些实体就是应用程序的业务对象.它们封装的最普通,最高级的规则.当外部变化时,它们最有可能改变,例如,你不希望这些对象被一个更改的页面导航或者安全影响,改变特定应用程序的操作不应该影响实体层。 49 | 50 | ##用例 51 | 在此层应用的业务规则包含应用特定的业务规则,他封装并实现了所有的系统用例,这些用例编排数据的流入和流出的实体,指示这些实体用在它们项目范围内的业务规则到达用力的目标。 52 | 53 | 我们不希望改变层时影响实体,也不希望层被外部的改变所影响,例如数据库,用户界面或者任何的共同框架的改变,此层与这部分是隔离开的。 54 | 55 | 我们这样做,不过是希望改变应用程序的操作时影响软件层的用例,用例详细信息改变时,这一层的代码也会受到影响。 56 | 57 | ## 接口适配器 58 | 这层软件是一个转化数字的适配器,从格式最方便的用例和实体转化为格式最方便的一些外部机构,如数据库或者网页,这一层完全包含GUI的MVC架构,代理者,视图,控制器都属于这一层,该模型可是只是从控制器传回到用例,然后从用例到代理和视图的数据结构。 59 | 60 | 同样的,在这一层数据被转换,从形式最方便的实体和用例转化成形式最方便的使用持久框架,即数据库.内圈里的任何代码不应该知道关于数据库的任何事情.如果这个数据库是SQL数据库,所有的SQL应该被限制在这一层,尤其是在这层对数据库的操作。 61 | 62 | 另外,在这一层其他适配器需要将数据从外部形势(如外部服务)转化成内部形式的用例和实体。 63 | 64 | ## 框架和驱动程序 65 | 最外层一般由框架和工具组成,如数据库,web框架等.一般来说,你不会在这一层写太多代码,而是贴代码传达到内层圆圈内。 66 | 67 | 这一层有很多细节,网页是一个细节,数据库是一个细节,我们保持外层的这些细节收到更少的破坏。 68 | 69 | ## 只有四个圆环? 70 | 不,圆圈只是传达意思,你可能会发现你使用到的不仅仅是这四个,没有规定你必须使用这四个圆环,然后,依赖规则始终适用,源代码始终向内依赖.越往圆圈内侧抽象水平越高,最外面的圆圈为低一级的具体细节.越往圆圈内侧抽象和封装的层次更高,最内侧圆层次最高,最普通. 71 | 72 | ## 跨越边界 73 | 该图的右下方是一个我们如何穿越边界的例子,它展示了控制器和代理者与下一层用例进行通信.注意控制流,开始于控制器,通过用例移动,结束于代理者的执行.还要注意源代码的依赖关系,指向内侧用例。 74 | 75 | 我们通常使用[依赖倒置原则](http://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99)解释这个明显的矛盾,像java语言,例如,我们会编排接口和继承的关系,使源代码在跨越边界的右侧点依赖反对控制流. 76 | 77 | 例如,考虑用例需要调用的代理.然而不需要直接调用,因为这将违反相关性规则:在外圈中的名称不需要在内圈中提及.因此,我们在内侧圆环中调用接口(这里显示的用例输出端口),在外侧圆环实现它. 78 | 79 | 相同的技术应用在跨越边界的系统结构中,无论控制流会在什么方向,我们以动态多态性的优势创建源代码依赖性,反对控制流,使得我们能够符合依赖规则。 80 | 81 | ## 什么是数据跨越边界 82 | 通常跨越边界的数据是一种单纯的数据结构,可以使用基本的结构或者简单的数据进行传输.或者数据可以单纯的在函数中调用,或者你可以打包成一个hashMap,或构造成一个对象,重要的是分离,操作简单,通过数据结构跨越边界.我们不想通过实体和数据作弊,不希望数据结构有任何一种依赖违反依赖规则. 83 | 84 | 例如,很多数据库框架响应查询返回一个方便的数据格式,我们称之为行结构.我们不希望向内跨越行结构,这将违反依赖规则,因为这会迫使内侧圆环了解外侧圆环的一些东西。 85 | 86 | 因此,当我们跨越边界传输数据时,它总是使用最方便内圆环的格式。 87 | 88 | ## 结论 89 | 符合这些简单的规则并不难,并会为你省掉很多前进过程中头疼的问题,通过软件分层,顺应依赖规则,创建一个系统在本质上是可以检测的,意味着拥有其本身的好处.当任何一个系统的外部部件过时时,如数据库或者web框架,你可以使用最少的忧虑替换掉那些过时的元素。 90 | -------------------------------------------------------------------------------- /others/简化Android的UI开发/readme.md: -------------------------------------------------------------------------------- 1 | 简化Android的UI开发 2 | --- 3 | 4 | > 5 | * 原文链接 : [android ui development made easy](http://zserge.com/blog/android-mvx.html) 6 | * 作者 : [Zaitsev Serge](http://zserge.com/blog.html) 7 | * 译者 : [chaossss](https://github.com/chaossss) 8 | * 校对者: [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang) 9 | * 状态 : 校对完成 10 | 11 | 12 | 13 | 14 | 如果你觉得这篇文章太长,而且还没有往下阅读的话,我可以给你简要的介绍文章要讲的内容:我使用纯 Java 通过数据绑定的方式提供了一种 15 | 16 | Android UI 开发的代码往往是支离破碎的,写出来的代码通常都是大量的模板化代码,而且没有结构可言。下面是一些问题(纯属个人见解): 17 | 18 | - Android UI 开发很少符合 MVC 模式(或者是 M-V-其他任何东西) 19 | 20 | - XML文件通常包含了很多重复的代码,在代码复用方面比较糟糕 21 | 22 | - XMLS 非常脆弱,这使得你在写 XML 文件时,即使输入了 TextVeiw ,在编译过程中编译器也不会警告你,但在 App 运行时又会抛出 InflateException 异常 23 | 24 | - 缺少对 styles 的支持,缺少对变量的支持,不支持宏和计算结果(例如 10dp + 2px) 25 | 26 | - 没有数据绑定,这使得你必须自己把所有的 findViewById 和 setOn...Listener 写好 27 | 28 | - 你可以通过 Java 实现你的布局,但是写出来的代码有如天书 29 | 30 | ## 使用 mithril.js 建立用户接口 ## 31 | 32 | 在 Web 开发中,开发者们很快就意识到在没有 MVx 的情况下开发复杂的应用会很吃力,这使得他们意识到 jQuery 中存在的问题,并开发了 Backbone,Knockout,Angular,Ember...等等,来提高他们的开发效率 33 | 34 | 但在 Android 中,我们还在通过那一点点函数毫无章法可言地设置 View 的属性,就像在 jQuery 里一样: 35 | 36 | ```java 37 | $('.myview').text('Hello'); 38 | $('.myview').on('click', function() { 39 | 40 | }); 41 | 42 | myView.setText("Hello"); 43 | myView.setOnClickListener(new View.OnClickListener() { ...}); 44 | ``` 45 | 46 | 我们在一个目录下定义了我们的 Layout ,又在另一个目录中使用它们,然后在 UI 开发的代码里改变 47 | ,这样并不好。 48 | 49 | React.js 对 Web 开发有一点点影响:他们以树状关系的自定义对象创建了一个虚拟的 DOM 概念,并以此展示实际的 HTML 布局。虚拟树创建和切换的时间都很短,所以当实际的 DOM 需要被渲染,两棵虚拟树(前一棵和新的那棵)将进行对比,只有不匹配的部分才会被渲染。 50 | 51 | Mithril.js 是一个精悍、短小的框架,使用它能使 React.js 的实现更整洁。在 Mithril 中,除了纯 JavaScript,你几乎能摆脱一切,同时,它还能让你在写布局的时候感受到图灵完备的语言所具备的力量。 52 | 53 | ```java 54 | return m('div', 55 | m('p', someText), 56 | m('ul', 57 | items.map((item) => m('li', item))), 58 | m('button', {onclick: myClickHandler})); 59 | ``` 60 | 61 | 因此,你能用循环生成许多 View,你能用判断语句改变布局中的某个部分,最后你能绑定数据和设置事件监听器。 62 | 63 | 那这个方法能在 Android 中被使用吗? 64 | 65 | ## 虚拟布局 ## 66 | 67 | 虚拟布局(使用类似 Web 中虚拟 DOM 的概念)是树状的自定义Java对象集合,被用于展示实际的 Android 布局。虽然 App 的数据改变多少次,树就会被构建多少次,但布局改变的内容应该仅仅是前后不一致的部分(当前的布局和改变前布局)。 68 | 69 | 我们的框架只导入一个静态类,所以所有类中的静态方法都不需要类名前缀就能被使用(例如我们只需要使用 v(),而不是 Render.v()),这是语言特性带来的好处。下面是我们如何创建布局的例子: 70 | 71 | ```java 72 | v(LinearLayout.class, 73 | orientation(LinearLayout.VERTICAL), 74 | v(TextView.class, 75 | text(someText)), 76 | v(Button.class, 77 | text("Click me"), 78 | onClick(someClickHandler))); 79 | ``` 80 | 81 | 第一个 v() 方法返回了一个虚拟布局,每一次调用后它会返回当前应用状态的实际展示(不是实际的 View!) 82 | 83 | 当一些文字变量被改变 - 虚拟树会获得一个被用于下次渲染的发生了改变的结点值,然后调用 setText()改变相应的 TextView 实例。但是其余的布局不会发生任何变化。 84 | 85 | 一棵虚拟布局树在理想情况下应该只是一个类,我们就把它叫作结点吧。但是结点主要有两种类型:View 结点(TextView.class等等)和属性设置结点,例如text(someText) 86 | 87 | 那这就意味着结点应该任意包含一个 View 类和一个方法去改变 View 的属性。 88 | 89 | ```java 90 | interface AttributeSetter { 91 | public void set(View v); 92 | } 93 | 94 | public static class Node { 95 | List attrs = new ArrayList(); 96 | Class viewClass; // for view nodes 97 | AttributeSetter setter; // for attribute setter nodes 98 | 99 | public Node(Class c) { 100 | this.viewClass = c; 101 | } 102 | 103 | public Node(AttributeSetter setter) { 104 | this.setter = setter; 105 | } 106 | } 107 | ``` 108 | 109 | 现在我们需要定义类在产生虚拟布局的时候实际能干的事情了,那就让我们来调用可渲染类吧。一个可渲染类可以是一个 Activity,或者一个自定义的 ViewGroup,或者 Fragment 也凑合。每一个可渲染类都应该有一个用于返回虚拟布局的方法,此外,如果这个方法指定了它将要作用于实际布局中的哪个 View 会更好。 110 | 111 | ```java 112 | public interface Renderable { 113 | Node view(); 114 | ViewGroup getRootView(); 115 | } 116 | ``` 117 | 118 | 由于 v() 方法的第一个参数是 View 子类的泛型,所以你不用担心类型安全问题。剩下的参数都是结点类型,所以我们只需要把它们添加到 list 中,无视掉空结点的话效果会更好一些。 119 | 120 | ```java 121 | public static Node v(final Class cls, final Node ...nodes) { 122 | return new Node(cls) ; 123 | } 124 | ``` 125 | 126 | Here's an example of the text() attribute setter (the real code is a bit different, but it could have been implemented like this): 127 | 128 | 下面是一个 text() 属性的设置方法(实际代码会有点不一样,但是也能像下面这样实现): 129 | 130 | ```java 131 | public static Node text(final String s) { 132 | return new Node(new AttributeSetter() { 133 | public void set(View v) { 134 | ((TextView) v).setText(s); 135 | } 136 | }); 137 | } 138 | ``` 139 | 140 | 其他类似的工具方法也能用于改变线性布局的方向,View 的大小、页边距、间距,总之所有 View 的参数都能被改变。 141 | 142 | ## 那么,我们要怎么去渲染呢? ## 143 | 144 | 现在我们需要一个“渲染者”。这是一个能够根据类名创建 View ,使用 AttributeSetters修改对应的参数并且递归地添加子 View的方法。(同样的,下面的代码也是被简化的,实际的代码会有些不一样,主要差别在于当结点没有被改变的时候,我们应该如何避免视图的渲染) 145 | 146 | ```java 147 | public static View inflateNode(Context c, Node node, ViewGroup parent) { 148 | if (node.viewClass == null) { 149 | throw new RuntimeException("Root is not a view!"); 150 | } 151 | // Exception handling skipped here to make the code look shorter 152 | View v = (View) node.viewClass.getConstructor(Context.class).newInstance(c); 153 | parent.addView(v); 154 | for (Node subnode: node.attrs) { 155 | if (subnode.setter != null) { 156 | subnode.setter.set(v); 157 | } else { 158 | View subview = inflateNode(c, subnode, (ViewGroup) v); 159 | } 160 | } 161 | return v; 162 | } 163 | ``` 164 | 165 | 现在我们真的可以摆脱 XMLS,并以一种简洁的方式通过 Java 进行布局了。 166 | 167 | 布局结点不应该直接地被使用,而应该是通过 render(Renderer r) 和 render()被使用。前者用于重渲染某一个 View,后者用于重渲染所有被展示的 View。Renderer 通过一个弱哈希表存储,使得在 View 被移除或者 Activity 被销毁的同时 - 他们的渲染者也不会再被使用。 168 | 169 | ## 什么时候去渲染呢? ## 170 | 171 | 这个框架的核心在于 自动进行重渲染,使得 UI 总能展示当前的虚拟布局状态。这就意味着 render() 应该在某个特定的节点被调用。 172 | 173 | 我参考 Mithril 的方法,把每一个 On...Listener 和 调用 render 的方法捆绑在每一次 UI 的交互中。 174 | 175 | ```java 176 | public static Node onClick(final View.OnClickListener listener) { 177 | return new Node(new AttributeSetter() { 178 | public void set(View v) { 179 | v.setOnClickListener(new View.OnClickListener() { 180 | public void onClick(View v) { 181 | listener.onClick(v); 182 | // After the click was processed - some data may have been changed 183 | // so we try to re-render the UI 184 | render(); 185 | } 186 | }); 187 | } 188 | }); 189 | } 190 | ``` 191 | 192 | 我觉得这样做是有道理的,因为大多数 Android 应用的数据都是在发生用户交互的时候被改变的。如果你的数据是因为其他因素被改变的 - 那就只能手动通过 render()渲染了。 193 | 194 | ## 总的来说 ## 195 | 196 | 这个方法虽然简单,却非常有用: 197 | 198 | - 你能用类似 XML 的方式定义你的布局结构(通过嵌套调用 v() 方法) 199 | 200 | - 你能用一种清晰易懂的方式绑定数据和监听器 201 | 202 | - 布局都是类型安全的,并且你的编译器会自动完成相应的工作 203 | 204 | - 没有运行时产生的开销,没有使用反射机制,没有自动生成代码 205 | 206 | - 你能在任何地方使用 Java(变量,语句,宏)生成布局 207 | 208 | - 你能用自定义 View 和自定义的属性设置方法 209 | 210 | - 因为你的所有 UI 数据都被保存在属性中,因此你能轻易的保存它们 211 | 212 | - 使用纯 Java 实现这些逻辑需要的代码还不到 250 行! 213 | 214 | 以上证明了这个方法是可行的。现在我在想,如果有人想要用这个方法开发一个功能齐全的库呢? 215 | 216 | 设计一个好的“区分”算法会是其中的关键。基本地,它应该能判断一个结点是否被添加/移除/修改,而文件就在于属性节点。简单的数据类型我们只要调用 equals() 去比较两个值就可以了,但是监听器呢? 217 | 218 | ```java 219 | v(SomeView.java, 220 | onClick(v => ...)); 221 | ``` 222 | 223 | 这样做的话每一次虚拟树被创建,都会创建一个对应的监听器对象。那怎么去比较它们?还是永远都不更新监听器,只更新发生了改变的监听器类?或者使用某种事件分发机制分发事件,而不是使用监听器? 224 | 225 | 另一件需要被注意的是:我不想自己把所有属性设置方法写好。这里有一个更好的方法,也就是 Kotlin 他们在 koan 库中做的那样。 226 | 227 | 我现在在研究怎么从 android.jar 的类中自动生成设置器,以使得这个项目更有用。 228 | 229 | 不管怎样,现在的代码我都放在 [Github](https://github.com/zserge/anvil) 上了,有 MIT 的许可。欢迎大家来评论和 PR! -------------------------------------------------------------------------------- /software-architecture-patterns/chap-5.md: -------------------------------------------------------------------------------- 1 | # 第五章 基于空间的架构 2 | 3 | 大多数基于网站的商务应用都遵循相同的请求流程:一个请求从浏览器发到web服务器,然后到应用服务器,然后到数据库服务器。虽然这个模式在用户数不大的时候工作良好,但随着用户负载的增加,瓶颈开始出现,首先出现在web服务器层,然后应用服务器层,最后数据库服务器层。通常的解决办法就是**向外扩展**,也就是增加服务器数量。这个方法相对来说简单和廉价,并能够解决问题。然而,对于大多数高访问量的情况,它只不过是把web服务器的问题移到了应用服务器。而扩展应用服务器会更复杂,而且成本更高,并且又只是把问题移动到了数据库服务器,那会更复杂,更贵。就算你能扩展数据库服务器,你最终会陷入一个金字塔式的情形,在金字塔最下面是web服务器,它会出现最多的问题,但也最好伸缩。金字塔顶部是数据库服务器,问题不多,但最难伸缩。 4 | 5 | 在一个高并发大容量的应用中,数据库通常是决定应用能够支持多少用户同时在线的关键因素。虽然各种缓存技术和数据库伸缩产品都在帮助解决这个问题,但数据库难以伸缩的现实并没有改变。 6 | 7 | 基于空间的架构模型是专门为了**解决伸缩性和并发问题**而设计的。它对于用户数量不可预测且数量级经常变化的情况同样适用。在架构级别来解决这个伸缩性问题通常是比增加服务器数量或者提高缓存技术更好的解决办法。 8 | 9 | ## 模型介绍 10 | 11 | 基于空间的模型(有时也称为云架构模型)旨在减少限制应用伸缩的因素。模型的名字来源于分布式共享内存中的 tuple space(数组空间)概念。高伸缩性是通过去除中心数据库的限制,并使用从内存中复制的数据框架来获得的。保存在内存的应用数据被复制给所有运行的进程。进程可以动态的随着用户数量增减而启动或结束,以此来解决伸缩性问题。这样因为没有了中心数据库,数据库瓶颈就此解决,此后可以近乎无限制的扩展了。 12 | 13 | 大多数使用这个模型的应用都是标准的网站,它们接受来自浏览器的请求并进行相关操作。竞价拍卖网站是一个很好的例子 ( 12306更是一个典型的示例 )。网站不停的接受来自浏览器的报价。应用收到对某一商品的报价,记录下报价和时间,并且更新对该商品的报价,将信息返回给浏览器。 14 | 15 | 这个架构中有两个主要的模块:**处理单元** 和 **虚拟化中间件**。下图展示了这个架构和里面的主要模块。 16 | 17 | ![](images/5-1.png) 18 | 19 | 处理单元包含了应用模块(或者部分的应用模块)。具体来说就是包含了web组件以及后台业务逻辑。处理单元的内容根据应用的类型而异——小型的web应用可能会部署到单一的处理单元,而大型一些的应用会将应用的不同功能模块部署到不同的处理单元中。典型的处理单元包括应用模块,以及保存在内存的数据框架和为应用失败时准备的异步数据持久化模块。它还包括复制引擎,使得虚拟化中间件可以将处理单元修改的数据复制到其他活动的处理单元。 20 | 21 | 虚拟化中间件负责保护自身以及通信。它包含用于数据同步和处理请求的模块,以及通信框架,数据框架,处理框架和部署管理器。这些在下文中即将介绍的部分,可以自定义编写或者购买第三方产品来实现。 22 | 23 | ## 模型间合作 24 | 25 | 基于空间的架构的魔力就在虚拟化中间件,以及各个处理单元中的内存中数据框架。下图展示了包含着应用模块、内存中数据框架、处理异步数据恢复的组件和复制引擎的处理单元架构。 26 | 27 | 虚拟化中间件本质上是架构的控制器,它管理请求,会话,数据复制,分布式的请求处理和处理单元的部署。虚拟化中间件有四个架构组件:通信框架,数据框架,处理框架和部署管理器。 28 | 29 | ![](images/5-2.png) 30 | 31 | ### 通信框架 32 | 33 | 通信框架管理输入请求和会话信息。当有请求进入虚拟化中间件,通信框架就决定有哪个处理单元可用,并将请求传递给这个处理单元。通信框架的复杂程度可以从简单的round robin算法到更复杂的用于监控哪个请求正在被哪个处理单元处理的next-available算法。 34 | 35 | ![](images/5-3.png) 36 | 37 | ### 数据框架 38 | 39 | 数据框架可能是这个架构中最重要和关键的组件。它与各个处理单元的数据复制引擎交互,在数据更新时来管理数据复制功能。由于通信框架可以将请求传递给任何可用的处理单元,所以每个处理单元包含完全一样的内存中数据就很关键。下图展示处理单元间如何同步数据复制,实际中是通过非常迅速的并行的异步复制来完成的,通常在微秒级。 40 | 41 | ![](images/5-4.png) 42 | 43 | ### 处理框架 44 | 45 | 处理框架,就像下图所示,是虚拟化中间件中一个可选组件,负责管理在有多个处理单元时的分布式请求处理,每个处理单元可能只负责应用中的某个特定功能。如果请求需要处理单元间合作(比如,一个订单处理单元和顾客处理单元),此时处理框架就充当处理单元见数据传递的媒介。 46 | 47 | ![](images/5-5.png) 48 | 49 | ### 部署管理器 50 | 51 | 部署管理器根据负载情况管理处理单元的动态启动和关闭。它持续监控响应时间和用户负载,在负载增加时启动新的处理单元,在负载下降时关闭处理单元。它是实现可变伸缩性需求的关键。 52 | 53 | ## 其他考虑 54 | 55 | 基于空间的架构是一个复杂和实现起来相对昂贵的框架。对于拥有可变负载的小型web应用是很好的选择,然而,对于拥有大量草错的传统大规模关系型数据库应用,并不那么适用。 56 | 57 | 虽然基于空间的架构模型不需要集中式的数据储存,但通常还是需要这样一个,来进行初始化内存中数据框架,和异步的更新各处理单元的数据。通常也会创建一个单独的分区,来从隔离常用的断电就消失的数据和不常用的数据,这样减少处理单元之间对对方内存数据的依赖。 58 | 59 | 值得注意的是,虽然这个架构的另一个名字是云架构,处理单元(以及虚拟化中间件)都没有放在云端服务或者PaaS上。他们同样可以简单的放在本地服务器,这也是为什么我更倾向叫它“基于空间的架构”。 60 | 61 | 从产品实现的角度讲,这个架构中的很多组件都可以从第三方获得,比如GemFire, JavaSpaces, GigaSpaces,IBM Object Grid,nCache,和 Oracle Coherence。由于架构的实现根据工程的预算和需求而异,所以作为架构师,你应该在实现或选购第三方产品前首先明确你的目标和需求。 62 | 63 | ## 架构分析 64 | 65 | 下面的表格是这个架构的特征分析和评分。每个特征的评分是基于一个典型的架构实现来给出的。要知道这个模式相对别的模式的对比,请参见最后的附录A。 66 | 67 | #### 综合能力 68 | 69 | 评分:高 70 | 71 | 分析:综合能力是对环境变化做出快速反应的能力。因为处理单元(应用的部署实例)可以快速的启动和关闭,整个应用可以根据用户量和负载做出反应。使用这个架构通常在应对代码变化上,由于较小的应用规模和组件间相互依赖,也会反映良好。 72 | 73 | #### 部署难易程度 74 | 75 | 评分:高 76 | 77 | 分析:虽然基于空间的架构通常没有解耦合并且功能分布,但他们是动态的,也是成熟的基于云的工具,允许应用轻松的部署到服务器。 78 | 79 | #### 可测试性 80 | 81 | 评分:低 82 | 83 | 分析:测试高用户负载既昂贵又耗时,所以在测试架构的可伸缩性方面很困难 84 | 85 | #### 性能 86 | 87 | 评分:高 88 | 89 | 分析:通过内存中数据存取和架构中的缓存机制可获得高性能 90 | 91 | #### 伸缩性 92 | 93 | 评分:高 94 | 95 | 分析:高伸缩性是源于几乎不依赖集中式的数据库,从而去除了这个限制伸缩性的瓶颈。 96 | 97 | #### 开发难度 98 | 99 | 评分:低 100 | 101 | 分析:成熟的缓存机制和内存中数据框架使这个架构开发起来相对复杂,主要是因为难以熟悉这个架构开发所需要的工具和第三方产品。而且,开发过程中还需要特别注意源码不要对性能和可伸缩性造成不良影响。 102 | -------------------------------------------------------------------------------- /software-architecture-patterns/images/1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/1-1.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/1-2.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/1-3.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/1-4.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/2-1.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/2-2.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/2-3.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/2-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/2-4.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/3-1.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/3-2.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/4-1.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/4-2.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/4-3.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/4-4.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/5-1.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/5-2.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/5-3.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/5-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/5-4.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/5-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/5-5.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/a-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/a-1.png -------------------------------------------------------------------------------- /software-architecture-patterns/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/images/cover.png -------------------------------------------------------------------------------- /software-architecture-patterns/readme.md: -------------------------------------------------------------------------------- 1 | 软件架构模式 2 | ----- 3 | 4 | 原书名[《Software Architecture Patterns》](http://www.oreilly.com/programming/free/software-architecture-patterns.csp) ,[软件架构模式pdf下载](http://pan.baidu.com/s/1sjAz23r)。 5 | 6 | 7 | ## 翻译规范 8 | 9 | 1. 在当前文件夹下创建一个markdown文件,文件命名为章节名-译者名.md,比如chapter01-Mr.Simple.md; 10 | 2. 翻译时需要保留原文,每一段英文原文后是译文, 比如 : 11 | Getting Started 12 | We know that writing quality software is hard and complex: It is not only about satisfying requirements, also should be robust, maintainable, testable, and flexible enough to adapt to growth and change. This is where “the clean architecture” comes up and could be a good approach for using when developing any software application. 13 | The idea is simple: clean architecture stands for a group of practices that produce systems that are: 14 | 入门指南 15 | 大家都知道要写一款精品软件是有难度且很复杂的:不仅要满足特定要求,而且软件还必须具有稳健性,可维护、可测试性强,并且能够灵活适应各种发展与变化。这时候,“清晰架构”就应运而生了,这一架构在开发任何软件应用的时候用起来非常顺手。 16 | 这个思路很简单:简洁架构 意味着产品系统中遵循一系列的习惯原则: 17 | 18 | 19 | ## 译员列表 20 | 该书总共五章,译员可以领其中一章或者多个章节,截止日期 2015/4/19,感谢大家的参与。可参考资料 : [软件架构模式读书笔记](http://blog.csdn.net/bboyfeiyu/article/details/44977219) 。 21 | 22 | | 翻译章节 | 译员信息 | 23 | |--------------------------|-------------------| 24 | | [第一章 分层架构](chapter01-BillionWang.md) | [BillionWang](https://github.com/BillionWang) | 25 | | [第二章 事件驱动架构](chapter02-chaossss.md) | [chaossss](https://github.com/chaossss) | 26 | | [第三章 微内核架构](chapter03-Mr.Simple.md) | [Mr.Simple](https://github.com/bboyfeiyu) | 27 | | [第四章 微服务架构](chapter04-dupengwei.md) | [dupengwei](https://github.com/dupengwei) | 28 | | [第五章 基于空间的架构](chap-5.md) | [allenlsy](https://allenlsy.com) | 29 | 30 | 31 | -------------------------------------------------------------------------------- /software-architecture-patterns/software-architecture-patterns.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/software-architecture-patterns.pdf -------------------------------------------------------------------------------- /software-architecture-patterns/软件架构模式.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windiest/android-tech-frontier/1740184bd07866a0002f0f68c3a1de0a5fb8f42f/software-architecture-patterns/软件架构模式.pdf -------------------------------------------------------------------------------- /software-architecture-patterns/附录2015-4-11-charli.md: -------------------------------------------------------------------------------- 1 | # APPENDIX A # 2 | ## Pattern Analysis Summary ## 3 | Figure A-1 summarizes the pattern-analysis scoring for each of the 4 | architecture patterns described in this report. This summary will 5 | help you determine which pattern might be best for your situation. 6 | For example, if your primary architectural concern is scalability, you 7 | can look across this chart and see that the event-driven pattern, 8 | microservices pattern, and space-based pattern are probably good 9 | architecture pattern choices. Similarly, if you choose the layered 10 | architecture pattern for your application, you can refer to the chart 11 | to see that deployment, performance, and scalability might be risk 12 | areas in your architecture. 13 | 45 14 | 15 | # 附录A # 16 | ## 模式分析总结 ## 17 | 图A-1 总结了在这个报告中,对于架构模式的每部分进行的模式分析所产生的影响。这个总结帮助你确定哪些模式可能是最适合你的情况。例如,如果你的架构模式重点是可伸缩性,你可以在这个图表看看事件驱动模式,microservices模式,和基于空间模式,这些对于你来说可能是很好的架构模式的选择。同样的,如果你的程序注重的是分层架构模式,你可以参考图看到部署、性能和可伸缩性的在你的架构中所存在的风险。 18 | 19 | While this chart will help guide you in choosing the right pattern, 20 | there is much more to consider when choosing an architecture pattern. 21 | You must analyze all aspects of your environment, including 22 | infrastructure support, developer skill set, project budget, project 23 | deadlines, and application size (to name a few). Choosing the right 24 | architecture pattern is critical, because once an architecture is in 25 | place, it is very hard (and expensive) to change 26 | 27 | 同时这个图表将指导你选择正确的模式, 28 | 因为在选择一种架构模式的时候,有更多的需要考虑。 29 | 你必须分析你的环境的各个方面,包括 30 | 基础设施的支持,开发人员技能,项目预算,项目 31 | 最后期限,和应用程序大小(等等)。选择正确的 32 | 架构模式是至关重要的,因为一旦一个架构 33 | 被确定了就很难改变。 34 | 35 | 36 | # About the Author # 37 | Mark Richards is an experienced, hands-on software architect 38 | involved in the architecture, design, and implementation of microservices 39 | architectures, service-oriented architectures, and distributed 40 | systems in J2EE and other technologies. 41 | He has been in the 42 | software industry since 1983 and has significant experience and 43 | expertise in application, integration, and enterprise architecture. 44 | Mark served as the president of the New England Java Users Group 45 | from 1999 through 2003. 46 | He is the author of numerous technical books and videos, including Software Architecture Fundamentals 47 | (O’Reilly video), Enterprise Messaging (O’Reilly video), Java 48 | Message Service, 2nd Edition (O’Reilly), and a contributing author 49 | to 97 Things Every Software Architect Should Know (O’Reilly). 50 | Mark has a master’s degree in computer science and numerous 51 | architect and developer certifications from IBM, Sun, The Open 52 | Group, and BEA. 53 | He is a regular conference speaker at the No 54 | Fluff Just Stuff (NFJS) Symposium Series and has spoken at more 55 | than 100 conferences and user groups around the world on a variety 56 | of enterprise-related technical topics. 57 | When he is not working, 58 | Mark can usually be found hiking in the White Mountains or 59 | along the Appalachian Trail. 60 | # 有关作者 # 61 | Mark•Richards是一位有实际经验的软件架构师,他参与架构、设计和实施microservices 62 | 体系结构、面向服务的体系结构和在J2EE中的分布式 63 | 系统和其他技术。自1983年以来,他一直从事软件行业,有大量的经验和专业知识在应用、集成和企业架构方面。 64 | Mark从1999年到2003担任新英格兰Java用户组的主席。 65 | 他是许多技术书籍和视频的作者,包括软件架构基础(O‘Reilly视频)、企业消息传递(O'Reilly视频),《Java 66 | 消息服务,第二版》(O'Reilly)和《软件架构师应该知道的97件事》(O'Reilly)的特约作者。 67 | Mark有一个计算机科学硕士学位和很多从IBM、Sun、开放 68 | 集团和BEA获得的架构师和开发人员认证。 69 | 他是Fluff Just Stuff(NFJS)研讨会系列的一个不定期会议议长 70 | ,并且超过100多次在世界各地的公益的会议和用户组上围绕技术主题 71 | 发言。当他不工作时,Mark通常会在白色山脉或阿帕拉契山径徒步旅行。 72 | 73 | 74 | -------------------------------------------------------------------------------- /template.md: -------------------------------------------------------------------------------- 1 | 这里写中文标题 2 | --- 3 | 4 | > * 原文链接 : [原文标题](原文url) 5 | * 原文作者 : [作者](作者的博客或者其他社交页面) 6 | * [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) 7 | * 译者 : [这里写你的github用户名](github链接) 8 | * 校对者: [这里校对者的github用户名](github链接) 9 | * 状态 : 未完成 / 校对中 / 完成 10 | 11 | **注意 : 翻译完之后请认真的审核一遍有没有错字、语句通不通顺,谢谢~** 12 | 13 | 14 | `这里是翻译原文,注意翻译时英文和译文都要留在该文档中,并且是一段英文原文下面直接跟着写译文,便于校对。如下示例 : ` 15 | 16 | Over the last months and after having friendly discussions at Tuenti with colleagues like @pedro_g_s and @flipper83 (by the way 2 badass of android development), I have decided that was a good time to write an article about architecting android applications. 17 | The purpose of it is to show you a little approach I had in mind in the last few months plus all the stuff I have learnt from investigating and implementing it. 18 | 19 | 过去几个月以来,通过在Tuenti网站上与@pedro_g_s和@flipper83(安卓开发两位大牛)进行友好讨论之后,我决定写这篇关于架构安卓应用的文章。 20 | 21 | 我写这篇文章的目的是想把我在过去几个月体悟到的小方法以及在调查和应用中学到的有用的东西分享给大家。 22 | 23 | Getting Started 24 | We know that writing quality software is hard and complex: It is not only about satisfying requirements, also should be robust, maintainable, testable, and flexible enough to adapt to growth and change. This is where “the clean architecture” comes up and could be a good approach for using when developing any software application. 25 | The idea is simple: clean architecture stands for a group of practices that produce systems that are: 26 | 27 | ## 入门指南 28 | 大家都知道要写一款精品软件是有难度且很复杂的:不仅要满足特定要求,而且软件还必须具有稳健性,可维护、可测试性强,并且能够灵活适应各种发展与变化。这时候,“清晰架构”就应运而生了,这一架构在开发任何软件应用的时候用起来非常顺手。 29 | 30 | 这个思路很简单:简洁架构 意味着产品系统中遵循一系列的习惯原则: 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /the-bad-guys/readme.md: -------------------------------------------------------------------------------- 1 | # 国内优秀Android学习资源 2 | 3 | ## 技术博客 4 | 5 | ### 应用开发 6 | 7 | | 博主 | 博客 | 备注 | 8 | |-------- |------------------|----------| 9 | | 任玉刚 | [CSDN博客](http://blog.csdn.net/singwhatiwanna/) | 深入Android应用开发,深度与广度兼顾 | 10 | | 郭霖 | [CSDN博客](http://blog.csdn.net/guolin_blog/) | 内容实用,行文流畅,高人气博主 | 11 | | 夏安明 | [CSDN博客](http://blog.csdn.net/xiaanming/) | 12 | | 张鸿洋 | [CSDN博客](http://blog.csdn.net/lmj623565791/) | 自定义View系列非常有价值,质量与产量都很高 | 13 | | 爱哥 | [CSDN博客](http://blog.csdn.net/aigestudio/) | 自定义View系列非常有价值,内容详细逼格高 | 14 | | 傲慢的上校 | [CSDN博客](http://blog.csdn.net/lilu_leo/) | 自定义View系列非常有价值,内容详细逼格高 | 15 | | Trinea | [个人博客](http://www.trinea.cn/) | 性能优化,开源项目等 | 16 | | 胡凯 | [个人博客](http://hukai.me/) | 性能优化等 | 17 | | 谦虚的天下 | [博客园](http://www.cnblogs.com/qianxudetianxia/) | 性能优化等 | 18 | | 兰亭风雨 | [CSDN博客](http://blog.csdn.net/ns_code) | Java源码分析等 | 19 | | Mr.Simple | [CSDN博客](http://blog.csdn.net/bboyfeiyu) | 开源框架系列、OOP等 | 20 | 21 | 22 | ### 源码分析 23 | | 博主 | 博客 | 备注 | 24 | |-------- |------------------|----------| 25 | | 罗升阳 | [CSDN博客](http://blog.csdn.net/luoshengyang/) | 源码分析 | 26 | | 邓凡平 | [博客园](http://www.cnblogs.com/innost/) | 源码分析 | 27 | 28 | 29 | ## 开源达人 30 | | 作者 | 备注 | 31 | |-------- |----------| 32 | | [square项目组](https://github.com/square) | OkHttp, Retrofit, Otto等很多优秀的开源项目,业界良心! | 33 | | [facebook项目组](https://github.com/facebook) | fresco,react native等很多优秀的开源项目,业界良心之二! | 34 | | [Jake Wharton](https://github.com/jakewharton) | NineOldAnimatoins, ButterKnife,ViewIndicator等优秀作品,Square员工 | 35 | | [Trinea](https://github.com/trinea) | 优秀开源项目集锦,优秀开源库架构分析等 | 36 | | [singwhatiwanna](https://github.com/singwhatiwanna) | DL (插件化),PinnedHeaderExpandableListView等 | 37 | | [daimajia](https://github.com/daimajia) | 自定义View | 38 | | [greenrobot](https://github.com/greenrobot) | EventBus, greenDAO | 39 | | [wyouflf](https://github.com/wyouflf) | xUtils作者 | 40 | | [litesuits](https://github.com/litesuits) | http,orm等库 | 41 | | [Mr.Simple](https://github.com/bboyfeiyu) | AndroidEventBus,开发技术前线,Android源码设计模式分析等项目 | 42 | 43 | ## 优秀学习网站 44 | | 地址 | 备注 | 45 | |-------- |----------| 46 | | [开发技术前线](devtf.cn) | 国内外各个开发领域优质技术文章的聚合网站 | 47 | | [Android Dev Blog](http://android-developers.blogspot.com/) | Android官方博客 | 48 | | [Android Weekly](androidweekly.net) | 国外优秀技术文章的收集网站,每周发布 | 49 | | [Android开发技术周报](androidweekly.cn) | AndroidWeekly中国版 | 50 | | [码农周刊](http://weekly.manong.io/) | 文章、咨询周报 | 51 | | [好东西传送门](http://memect.com/) | 各领域技术文章的日报 | 52 | 53 | -------------------------------------------------------------------------------- /翻译项目协作流程.md: -------------------------------------------------------------------------------- 1 | Github协作流程 2 | --- 3 | 4 | 5 | ## fork一份到你的账户下 6 | 在[android-tech-frontier](https://github.com/bboyfeiyu/android-tech-frontier)项目中选择右边的"fork",然后选择你的头像,将该项目fork一份到你的账户下。 7 | 8 | ![fork](http://img.blog.csdn.net/20150323230517092) 9 | 10 | 点击“fork”后能在个人主页中看到项目就表示fork成功了: 11 | 12 | ![](http://img.my.csdn.net/uploads/201504/25/1429948243_6050.png) 13 | 14 | ## clone你fork的仓库到本地 15 | 输入如下命令clone一份你fork的仓库 (相当于拷贝了一份到你的账户下),例如 : 16 | ![clone](http://img.blog.csdn.net/20150323230449588) 17 | 18 | > git clone git@github.com:bboyfeiyu/android-tech-frontier.git 19 | 20 | 等待clone完毕。此时,在你的当前目录下会有android-tech-frontier目录。进入该android-tech-frontier,在相应的目录下按照[readme.md](readme.md)中的命名规则创建你的翻译项目,然后完成翻译。 21 | 22 | 操作完成后就能在相应的目录中看到项目文件了 23 | 24 | ## 发送 pull request 25 | 26 | 打开项目首页,点击图中的小绿框 27 | 28 | ![](http://img.my.csdn.net/uploads/201504/25/1429948415_2193.png) 29 | 30 | 再点击红框内的文字 31 | 32 | ![](http://img.my.csdn.net/uploads/201504/25/1429948487_1166.jpg) 33 | 34 | 就会出现这个页面 35 | 36 | ![](http://img.my.csdn.net/uploads/201504/25/1429948573_7445.jpg) 37 | 38 | 将左边选成你的项目,右边选成项目作者的项目,就表示你要将你的项目更新为作者的状态,与作者保持一致 39 | 40 | 例如项目更新了10篇文章,你翻译好了一篇文章,那么在你提交你的文章之前,你就需要先将项目更新为作者最新的状态,再提交,否则会出现冲突 41 | 42 | ![](http://img.my.csdn.net/uploads/201504/25/1429948699_7368.jpg) 43 | 44 | 将左边选成作者的项目,右边选成你的项目,则表示要将你对作者项目作出的改变提交给作者,请求作者同步 45 | 46 | ![](http://img.my.csdn.net/uploads/201504/25/1429948882_7915.jpg) 47 | 48 | 选择 Create Pull Request 就可以完成啦 49 | 50 | ## 进行校对 51 | 52 | 打开项目作者处的 pull request,当有其他人的 pull request 时页面会如下: 53 | 54 | ![](http://img.my.csdn.net/uploads/201504/25/1429948986_4450.jpg) 55 | 56 | 选择某一个 pull request ,将进入相应的页面 57 | 58 | ![](http://img.my.csdn.net/uploads/201504/25/1429949102_5644.jpg) 59 | 60 | 点击 commit 能看见提交者的所有 commit,点击相应的 commit 能看到提交者进行了什么操作,修改了什么东西。 61 | 62 | 点击 FileChange 则能看见提交者提交上来的内容(最终版本) 63 | 64 | ![](http://img.my.csdn.net/uploads/201504/25/1429949236_2350.jpg) 65 | 66 | 在每一行的左侧,会出现一个蓝色的 + 号,点击它,就能进行 comment,通过 comment 可以对提交者存在问题的译文作出评论,和译者交流 67 | 68 | ![](http://img.my.csdn.net/uploads/201504/25/1429949329_3391.jpg) 69 | 70 | ## 认领翻译任务 71 | 你可以在主仓库下看到一些翻译任务的状态: 72 | 73 | * 待认领 : 表示还没有人接这个翻译任务; 74 | * 翻译中 : 表明已经有人翻译了; 75 | * 翻译完成 : 表明已经翻译完成; 76 | * 寻找校对中 : 表明此时文章翻译完成,正在等待校对; 77 | * 校对中 : 表明正在校对; 78 | * 校对完成 : 表明校对完成,等待发布。 79 | 80 | 当你看到某个状态为"待认领"的issue时,你可以撸起袖子在该issue下面添加评论,将你的github账户评论到下面,然后管理员会将该issue设置为"翻译中"。当你看到“寻找校对中”的任务,则说明该任务已经翻译完,但是需要校对,你也可以在该issue中将你的github账户添加到评论中,然后将该任务设置为“校对中”。 81 | 校对完毕,管理员确认没啥问题之后会发布该文章,发布后会将该issue关闭。 82 | 83 | ![is](http://img.blog.csdn.net/20150323231540412) 84 | 85 | 86 | ## 推荐文章 87 | 如果你看到好的文章,也可以在仓库的issue中添加一个,将推荐理由、原文链接填上。如图 : 88 | 89 | ![submit](http://img.blog.csdn.net/20150323231923706) 90 | 91 | ## 提交翻译内容到你的仓库中 92 | 在android-tech-frontier下依次输入如下三条命令提交你修改的内容到你的仓库。 93 | 94 | ``` 95 | git add . 96 | git commit -m "我翻译了xxx项目" 97 | git push origin master 98 | ``` 99 | 100 | ## 将你的更新提交到主仓库 101 | 经过上面push命令之后,你的内容已经更新到你自己fork的仓库,此时你需要把你的更新提交到主仓库。因此你需要发一个pull request。在你fork的项目的右边点击"pull request",然后选择"new pull request"。注意是从你的仓库master分支(左边)到我的仓库的master分支(右边)。然后选择"create new pull request",最后填写相关的内容后确认即可。 102 | 103 | ![pr](http://img.blog.csdn.net/20150323230526312) 104 | 105 | 提交了pull request之后,管理员会找相关人员进行校对。校对完成之后管理员会将你的文章发布。 106 | 107 | --------------------------------------------------------------------------------