From 69fb9a9f8f29f1f5445c80870d995a94d7a5bed1 Mon Sep 17 00:00:00 2001 From: Chaos <437700255@qq.com> Date: Tue, 19 Jan 2016 01:10:17 +0800 Subject: [PATCH] update --- ...droid-Activity\346\265\213\350\257\225.md" | 170 ++ ...347\232\204AOP\347\274\226\347\250\213.md" | 367 +++ ...06\346\236\266\350\247\243\346\236\220.md" | 2153 +++++++++++++++++ ...\274\200\345\217\221\345\272\223-part1.md" | 390 +++ ...\236\204\346\250\241\345\274\217-Part1.md" | 213 ++ .../Android-MVVM\346\250\241\345\274\217.md" | 213 ++ "issue-23/TextView\347\232\204TextLayout.md" | 68 + ...\350\241\214UI\346\265\213\350\257\225.md" | 101 + ...ws\345\256\242\346\210\267\347\253\257.md" | 211 ++ ...rawable \345\222\214 Drawable.Callback.md" | 115 + ...60\346\215\256\347\273\221\345\256\232.md" | 414 ++++ ...21\345\267\245\344\275\234\346\265\201.md" | 146 ++ ...60\345\274\217\346\223\215\344\275\234.md" | 233 ++ ...47\232\204Flux\346\236\266\346\236\204.md" | 346 +++ ...70\345\277\203\345\212\237\350\203\275.md" | 140 ++ ...5\215ViewPager\344\275\277\347\224\250.md" | 220 ++ ...205\215Palette\344\275\277\347\224\250.md" | 132 + ...232\204\350\241\200\346\241\210-Square.md" | 437 ++++ ...\217\226Bitmap\351\242\234\350\211\262.md" | 96 + ...6\234\211Maven\344\273\223\345\272\223.md" | 205 ++ ...24\345\214\226\344\271\213\350\267\257.md" | 351 +++ issue-26/Tinting drawables.md | 85 + ...\346\236\220UI\346\200\247\350\203\275.md" | 152 ++ ...02\344\275\225\345\244\204\347\220\206.md" | 20 + ...4\275\277\347\224\250Observable.create.md" | 49 + ...30\345\214\226\346\214\207\345\215\227.md" | 399 +++ ...0\203\345\210\260Bitbucket\344\270\212.md" | 144 ++ ...03\347\256\241\347\220\206\345\231\250.md" | 342 +++ ...73\221\345\256\232(Data Binding)-Part1.md" | 210 ++ ...73\221\345\256\232(Data Binding)-Part2.md" | 107 + ...73\221\345\256\232(Data Binding)-Part3.md" | 143 ++ ...73\221\345\256\232(Data Binding)-Part4.md" | 259 ++ ...73\221\345\256\232(Data Binding)-Part5.md" | 206 ++ ...72\346\233\264\345\271\263\351\241\272.md" | 33 + ...232\204Android\345\272\224\347\224\250.md" | 230 ++ ...id5.0)\344\270\212\347\232\204EditText.md" | 55 + ...7\237\245\350\257\206\347\202\271Part1.md" | 60 + ...7\237\245\350\257\206\347\202\271Part2.md" | 64 + issue-30/Flux and Android.md | 140 ++ ...73\347\232\204\346\200\247\350\203\275.md" | 99 + ...50\345\205\245\346\241\206\346\236\266.md" | 107 + ...\270\255\350\260\203\350\257\225RxJava.md" | 275 +++ ...24\347\224\250\346\236\266\346\236\204.md" | 173 ++ ...7\237\245\350\257\206\347\202\271Part3.md" | 75 + ...42\345\244\215\347\212\266\346\200\201.md" | 46 + ...32\345\212\250\350\241\214\344\270\272.md" | 116 + ...07\350\243\201\345\211\252\345\272\223.md" | 68 + ...e \357\274\211\345\212\237\350\203\275.md" | 175 ++ ...347\232\204MVP\346\250\241\345\274\217.md" | 419 ++++ ...3\230\346\200\247\350\203\275ListViews.md" | 72 + ...35\350\265\226\347\256\241\347\220\206.md" | 78 + ...13\351\233\252\345\212\250\347\224\273.md" | 214 ++ ...\222\214\344\271\220\350\266\243-part1.md" | 83 + ...44\271\211Lint\350\247\204\345\210\231.md" | 246 ++ ...\234\260\351\205\215\347\275\256okhttp.md" | 166 ++ ...245\347\250\213101 \342\200\223 Part 1.md" | 146 ++ ...245\347\250\213101 \342\200\223 Part 2.md" | 102 + ...62\347\232\204Handlers & Inner Classes.md" | 110 + ...\270\255\344\275\277\347\224\250RxJava.md" | 309 +++ ...0\246\201\347\224\250fitsSystemWindows.md" | 62 + ...id\347\232\204\350\247\206\345\233\276.md" | 190 ++ ...40\347\232\204\345\210\207\346\215\242.md" | 124 + ...10\345\217\257\347\274\226\350\276\221.md" | 39 + ...\347\224\250\347\232\204VectorDrawable.md" | 117 + ...e\345\260\217\347\237\245\350\257\2061.md" | 115 + ...e\345\260\217\347\237\245\350\257\2062.md" | 247 ++ ...e\345\260\217\347\237\245\350\257\2063.md" | 275 +++ ...e\345\260\217\347\237\245\350\257\2064.md" | 50 + ...77\347\224\250\346\212\200\345\267\247.md" | 198 ++ ...11\346\213\251\345\212\250\344\275\234.md" | 123 + ...45\255\230Rest\350\257\267\346\261\202.md" | 167 ++ ...46\345\222\214\346\200\247\350\203\275.md" | 210 ++ ...4\344\270\200\351\203\250\345\210\206).md" | 146 ++ ...274\272\345\244\247\347\232\204Dagger2.md" | 274 +++ ...06\345\220\221\345\267\245\347\250\213.md" | 133 + ...60\346\215\256\350\247\243\346\236\220.md" | 77 + ...25\345\267\245\344\275\234\347\232\204.md" | 94 + ...50\357\274\215\345\271\225\345\220\216.md" | 79 + ...\232\204\345\210\206\344\272\253Intent.md" | 161 ++ ...01\344\270\242\345\244\261\357\274\237.md" | 108 + 80 files changed, 15487 insertions(+) create mode 100644 "issue-22/Android-Activity\346\265\213\350\257\225.md" create mode 100644 "issue-22/Android\344\270\255\347\232\204AOP\347\274\226\347\250\213.md" create mode 100644 "issue-22/Binder\346\241\206\346\236\266\350\247\243\346\236\220.md" create mode 100644 "issue-22/\346\267\261\345\205\245\345\211\226\346\236\220Android\347\275\221\347\273\234\345\274\200\345\217\221\345\272\223-part1.md" create mode 100644 "issue-23/Android-MVPR-\346\236\266\346\236\204\346\250\241\345\274\217-Part1.md" create mode 100644 "issue-23/Android-MVVM\346\250\241\345\274\217.md" create mode 100644 "issue-23/TextView\347\232\204TextLayout.md" create mode 100644 "issue-23/\344\275\277\347\224\250Espresso\350\277\233\350\241\214UI\346\265\213\350\257\225.md" create mode 100644 "issue-23/\344\275\277\347\224\250TDD\347\232\204\346\226\271\345\274\217\345\274\200\345\217\221\344\270\200\344\270\252Hackernews\345\256\242\346\210\267\347\253\257.md" create mode 100644 "issue-24/Android LayerDrawable \345\222\214 Drawable.Callback.md" create mode 100644 "issue-24/Android\345\217\214\345\220\221\346\225\260\346\215\256\347\273\221\345\256\232.md" create mode 100644 "issue-24/Android\350\256\276\350\256\241\344\270\216\345\274\200\345\217\221\345\267\245\344\275\234\346\265\201.md" create mode 100644 "issue-24/\344\275\277\347\224\250Kotlin\345\257\271ViewGroup\344\270\255\347\232\204View\350\277\233\350\241\214\345\207\275\346\225\260\345\274\217\346\223\215\344\275\234.md" create mode 100644 "issue-24/\351\200\202\347\224\250\344\272\216Android\347\232\204Flux\346\236\266\346\236\204.md" create mode 100644 "issue-25/Android\344\270\273\351\242\230\345\212\250\346\200\201\345\210\207\346\215\242\345\274\200\346\272\220\345\272\223Prism\345\237\272\346\234\254\345\216\237\347\220\2061-\346\240\270\345\277\203\345\212\237\350\203\275.md" create mode 100644 "issue-25/Android\344\270\273\351\242\230\345\212\250\346\200\201\345\210\207\346\215\242\345\274\200\346\272\220\345\272\223Prism\345\237\272\346\234\254\345\216\237\347\220\2062-\346\220\255\351\205\215ViewPager\344\275\277\347\224\250.md" create mode 100644 "issue-25/Android\344\270\273\351\242\230\345\212\250\346\200\201\345\210\207\346\215\242\345\274\200\346\272\220\345\272\223Prism\345\237\272\346\234\254\345\216\237\347\220\2063-\346\220\255\351\205\215Palette\344\275\277\347\224\250.md" create mode 100644 "issue-25/\344\270\200\344\270\252\345\206\205\345\255\230\346\263\204\346\274\217\345\274\225\345\217\221\347\232\204\350\241\200\346\241\210-Square.md" create mode 100644 "issue-25/\345\234\250Android Lollipop\344\270\255\344\275\277\347\224\250Palette\346\212\275\345\217\226Bitmap\351\242\234\350\211\262.md" create mode 100644 "issue-26/30\345\210\206\351\222\237\346\220\255\345\273\272\344\270\200\344\270\252android\347\232\204\347\247\201\346\234\211Maven\344\273\223\345\272\223.md" create mode 100644 "issue-26/Android\346\236\266\346\236\204\346\274\224\345\214\226\344\271\213\350\267\257.md" create mode 100644 issue-26/Tinting drawables.md create mode 100644 "issue-26/\344\275\277\347\224\250Systrace\345\210\206\346\236\220UI\346\200\247\350\203\275.md" create mode 100644 "issue-26/\345\234\250Android M\344\270\255\346\235\203\351\231\220\350\242\253\346\213\222\347\273\235\346\227\266\350\257\245\345\246\202\344\275\225\345\244\204\347\220\206.md" create mode 100644 "issue-27/\344\270\272\344\273\200\344\271\210\344\270\215\344\273\205\347\273\247\346\211\277Observale\350\200\214\344\270\224\344\275\277\347\224\250Observable.create.md" create mode 100644 "issue-27/\344\270\272\344\275\240\347\232\204\345\272\224\347\224\250\345\212\240\351\200\237 - \345\256\211\345\215\223\344\274\230\345\214\226\346\214\207\345\215\227.md" create mode 100644 "issue-27/\344\275\277\347\224\250Gradle\345\260\206\351\241\271\347\233\256\345\217\221\345\270\203\345\210\260Bitbucket\344\270\212.md" create mode 100644 "issue-27/\345\274\200\345\217\221\344\275\240\350\207\252\345\267\261\347\232\204Android\346\216\210\346\235\203\347\256\241\347\220\206\345\231\250.md" create mode 100644 "issue-28/\346\225\260\346\215\256\347\273\221\345\256\232(Data Binding)-Part1.md" create mode 100644 "issue-28/\346\225\260\346\215\256\347\273\221\345\256\232(Data Binding)-Part2.md" create mode 100644 "issue-28/\346\225\260\346\215\256\347\273\221\345\256\232(Data Binding)-Part3.md" create mode 100644 "issue-28/\346\225\260\346\215\256\347\273\221\345\256\232(Data Binding)-Part4.md" create mode 100644 "issue-28/\346\225\260\346\215\256\347\273\221\345\256\232(Data Binding)-Part5.md" create mode 100644 "issue-29/Chrome\350\207\252\345\256\232\344\271\211Tabs-\350\256\251App\345\222\214Web\344\271\213\351\227\264\347\232\204\350\275\254\345\234\272\346\233\264\345\271\263\351\241\272.md" create mode 100644 "issue-29/\345\274\200\345\217\221\345\256\211\345\205\250\347\232\204Android\345\272\224\347\224\250.md" create mode 100644 "issue-29/\346\263\250\346\204\217API21(Android5.0)\344\270\212\347\232\204EditText.md" create mode 100644 "issue-30/Android\345\274\200\345\217\221\347\224\237\345\203\273\345\215\264\345\256\236\347\224\250\347\232\204\347\237\245\350\257\206\347\202\271Part1.md" create mode 100644 "issue-30/Android\345\274\200\345\217\221\347\224\237\345\203\273\345\215\264\345\256\236\347\224\250\347\232\204\347\237\245\350\257\206\347\202\271Part2.md" create mode 100644 issue-30/Flux and Android.md create mode 100644 "issue-30/\351\200\232\350\277\207\347\241\254\344\273\266\345\261\202\346\217\220\351\253\230Android\345\212\250\347\224\273\347\232\204\346\200\247\350\203\275.md" create mode 100644 "issue-31/Android \344\270\255\347\232\204\344\276\235\350\265\226\346\263\250\345\205\245\346\241\206\346\236\266.md" create mode 100644 "issue-31/Android\344\270\255\350\260\203\350\257\225RxJava.md" create mode 100644 "issue-31/Android\345\272\224\347\224\250\346\236\266\346\236\204.md" create mode 100644 "issue-31/Android\345\274\200\345\217\221\347\224\237\345\203\273\345\215\264\345\256\236\347\224\250\347\232\204\347\237\245\350\257\206\347\202\271Part3.md" create mode 100644 "issue-31/Espresso\344\277\235\345\255\230\345\222\214\346\201\242\345\244\215\347\212\266\346\200\201.md" create mode 100644 "issue-32/AppBarLayout\347\232\204\350\266\212\347\225\214\346\273\232\345\212\250\350\241\214\344\270\272.md" create mode 100644 "issue-32/\345\211\252\345\210\200\346\211\213\342\200\224Android\345\271\263\345\217\260\344\270\212\347\232\204\345\233\276\347\211\207\350\243\201\345\211\252\345\272\223.md" create mode 100644 "issue-32/\345\256\236\347\216\260\345\256\211\345\215\2236.0\347\232\204\347\233\264\346\216\245\345\210\206\344\272\253\357\274\210Direct Share \357\274\211\345\212\237\350\203\275.md" create mode 100644 "issue-32/\351\207\215\346\236\204Plaid-\345\223\215\345\272\224\345\274\217\347\232\204MVP\346\250\241\345\274\217.md" create mode 100644 "issue-32/\351\253\230\346\200\247\350\203\275ListViews.md" create mode 100644 "issue-33/Android Libraries\347\232\204\344\276\235\350\265\226\347\256\241\347\220\206.md" create mode 100644 "issue-33/\344\270\213\351\233\252\345\212\250\347\224\273.md" create mode 100644 "issue-33/\344\275\277\347\224\250ADB-Shell\347\232\204\346\225\210\347\216\207\345\222\214\344\271\220\350\266\243-part1.md" create mode 100644 "issue-33/\345\246\202\344\275\225\350\207\252\345\256\232\344\271\211Lint\350\247\204\345\210\231.md" create mode 100644 "issue-33/\351\253\230\346\225\210\345\234\260\351\205\215\347\275\256okhttp.md" create mode 100644 "issue-34/Android\351\200\206\345\220\221\345\267\245\347\250\213101 \342\200\223 Part 1.md" create mode 100644 "issue-34/Android\351\200\206\345\220\221\345\267\245\347\250\213101 \342\200\223 Part 2.md" create mode 100644 "issue-34/Context\346\230\257\346\200\216\344\271\210\346\263\204\351\234\262\347\232\204Handlers & Inner Classes.md" create mode 100644 "issue-34/\345\234\250Android\345\274\200\345\217\221\344\270\255\344\275\277\347\224\250RxJava.md" create mode 100644 "issue-35/\344\270\272\344\273\200\344\271\210\346\210\221\344\273\254\350\246\201\347\224\250fitsSystemWindows.md" create mode 100644 "issue-35/\346\230\237\347\220\203\345\244\247\346\210\230\357\274\232\345\216\237\345\212\233\350\247\211\351\206\222\346\210\226\350\200\205\347\224\250\345\216\237\345\212\233\347\262\211\347\242\216Android\347\232\204\350\247\206\345\233\276.md" create mode 100644 "issue-35/\347\224\250Transition\345\256\214\346\210\220Fragment\345\205\261\344\272\253\345\205\203\347\264\240\347\232\204\345\210\207\346\215\242.md" create mode 100644 "issue-35/\350\256\251EditText\344\270\255\347\232\204\351\223\276\346\216\245\345\215\263\345\217\257\347\202\271\345\207\273\345\217\210\345\217\257\347\274\226\350\276\221.md" create mode 100644 "issue-35/\350\277\221\344\271\216\351\200\232\347\224\250\347\232\204VectorDrawable.md" create mode 100644 "issue-36/Gradle\345\260\217\347\237\245\350\257\2061.md" create mode 100644 "issue-36/Gradle\345\260\217\347\237\245\350\257\2062.md" create mode 100644 "issue-36/Gradle\345\260\217\347\237\245\350\257\2063.md" create mode 100644 "issue-36/Gradle\345\260\217\347\237\245\350\257\2064.md" create mode 100644 "issue-36/Gradle\346\217\220\347\244\272\345\222\214\344\275\277\347\224\250\346\212\200\345\267\247.md" create mode 100644 "issue-37/\344\275\277\347\224\250ACTION_PROCESS_TEXT\345\210\233\345\273\272\350\207\252\345\256\232\344\271\211\346\226\207\346\234\254\351\200\211\346\213\251\345\212\250\344\275\234.md" create mode 100644 "issue-37/\344\275\277\347\224\250RxJava\347\274\223\345\255\230Rest\350\257\267\346\261\202.md" create mode 100644 "issue-37/\345\234\250Android\344\270\255\344\275\277\347\224\250\345\271\266\345\217\221\346\235\245\346\217\220\351\253\230\351\200\237\345\272\246\345\222\214\346\200\247\350\203\275.md" create mode 100644 "issue-37/\345\246\202\344\275\225\347\220\206\350\247\243RxJava\344\270\255\347\232\204Subjects(\347\254\254\344\270\200\351\203\250\345\210\206).md" create mode 100644 "issue-37/\346\233\264\345\212\240\345\274\272\345\244\247\347\232\204Dagger2.md" create mode 100644 "issue-38/Android\351\200\206\345\220\221\345\267\245\347\250\213.md" create mode 100644 "issue-38/Android\351\253\230\346\200\247\350\203\275JSON\346\225\260\346\215\256\350\247\243\346\236\220.md" create mode 100644 "issue-38/RecyclerView\345\212\250\347\224\273 \347\254\254\344\270\200\351\203\250\357\274\215\345\212\250\347\224\273\346\225\210\346\236\234\346\230\257\345\246\202\344\275\225\345\267\245\344\275\234\347\232\204.md" create mode 100644 "issue-38/RecyclerView\345\212\250\347\224\273 \347\254\254\344\272\214\351\203\250\357\274\215\345\271\225\345\220\216.md" create mode 100644 "issue-38/\344\273\216\347\275\221\351\241\265\344\270\255\350\247\246\345\217\221Android\345\216\237\347\224\237\347\232\204\345\210\206\344\272\253Intent.md" create mode 100644 "issue-39/FragmentTransaction \344\270\216 Activity \347\212\266\346\200\201\344\270\242\345\244\261\357\274\237.md" diff --git "a/issue-22/Android-Activity\346\265\213\350\257\225.md" "b/issue-22/Android-Activity\346\265\213\350\257\225.md" new file mode 100644 index 00000000..854fcb69 --- /dev/null +++ "b/issue-22/Android-Activity\346\265\213\350\257\225.md" @@ -0,0 +1,170 @@ +#Activity测试 +--- + +> * 原文链接 : [Activity Testing](http://developer.android.com/intl/zh-cn/tools/testing/activity_testing.html) +* 原文作者 : [Android Developers](http://developer.android.com/index.html) +* 译者 : [desmond1121](https://github.com/desmond1121) +* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu) + + +Activity测试依赖于Android Instrumentation测试框架。有其他组件不同的是Activity有更复杂的生命周期,这些生命周期函数不能直接地被调用,而只能通过Instrumentation发送事件来触发它们。 + +这篇文档描述了怎么样使用Instrumentation和其他测试工具来进行自动化测试。在阅读它之前,你应该先阅读[Android测试基础](http://developer.android.com/tools/testing/testing_android.html),它介绍了何为Android自动化测试与Instrumentation框架。 + + +## Activity测试API介绍 + + +Activity测试的基类是[InstrumentationTestCase](http://developer.android.com/reference/android/test/InstrumentationTestCase.html), 它为Activity测试所用的几个类提供Instrumentation支持。 + + +在Activity测试中,这个基类能够提供以下三个功能: + +- 生命周期控制:你可以使用Instrumentation来调用Activity的onResume、onPause、onDestroy等方法, 从而控制它的生命周期。 + +- 依赖注入:你可以使用Instrumentation制造出伪Context或伪Application,这样能够帮助你更好地控制测试环境,通过自定义Intent启动Activity。 + +- 用户界面交互:你可以使用Instrumentation直接发送按键信息和触屏事件。 + +Activity测试类通过继承[TestCase](http://developer.android.com/reference/junit/framework/TestCase.html)和[Assert](http://developer.android.com/reference/junit/framework/Assert.html)实现了JUnit测试框架,你可以在测试的过程中使用。 + +两个主要用的测试子类是[ActivityInstrumentationTestCase2](http://developer.android.com/reference/android/test/ActivityInstrumentationTestCase2.html)和[ActivityUnitTestCase](http://developer.android.com/reference/android/test/ActivityUnitTestCase.html),不过当Activity的`launchMode`设置为非`standard`属性时,你需要用[SingleLaunchActivityTestCase](http://developer.android.com/reference/android/test/SingleLaunchActivityTestCase.html); + +### ActivityInstrumentationTestCase2 + +这个类可以用来测试同一个应用的多个Activity,它拥有正常的应用实例环境和Context,可以接触到正常的系统结构(文件、数据库等)。你可以发送伪装的Intent给Activity,测试你的程序是否对接收到的各种Intent进行了正确处理。 **注意:这个类中不能伪Context和伪Application,所以你无法将测试从系统环境中独立出来。** + +### ActivityUnitTestCase + +这个类是用来测试单个Activity的。它可以运行在独立的测试环境中,你可以通过在启动Activity前设置Context或Application(当然都设置也可以)来实现模拟环境。你可以在自己的虚拟环境中测试程序而不会对系统、文件等造成实际的影响。在这个测试类下你无法向正在测试的Activity发送Intent,不过你可以在启动Activity的时候调用`Activity.startActivity(Intent)`来查看接收到的参数。 + +### SingleLaunchActivityTestCase + +这个类可以很方便地用来测试单个Activity。由于它只会调用一次`setUp()`和`tearDown()`(而不是每次方法都中都会调用),所以在这个实例中的所有测试方法共享一个测试环境。在这个类中不允许你加入任何的伪Object。 + +这个类在测试Activity的`launchMode`属性不是`standard`的时候非常有用,它保证了测试环境的不变,所以你可以用它来测试Activity是否对多次重复调用做出了正确应对。 + +### Activity中的伪Object使用 + +这一段内容是要告诉你怎么在Android测试中使用`android.test.mock`包中所定义的一系列伪Object。 + +[MockApplication](http://developer.android.com/reference/android/test/mock/MockApplication.html)只可以在`ActivityUnitTestCase`中使用,你可以通过`setApplication()`来指定它(必须在startActivity之前调用)。若不指定的话,ActivityUnitTestCase会自己生成一个伪Application。(MockContext可以通过`setActivityContext()`指定) + +### Activity测试中的判断语句 + +[ViewAsserts](http://developer.android.com/reference/android/test/ViewAsserts.html)定义了供View使用的一些判断语句,它可以检查View内容的位置、对其情况和状态。 + +## 我们要测试什么? + +- 输入合法性:你可以测试Activity是否对EditText的各类输入做出了正常反应。通过模拟输入一串信息发送给Activity,使用`findViewById(int)`来检查View的状态。你可以通过设置一个`Button`当输入异常时disable,输入正常时enable来,然后通过`assertEquals(button.isEnabled(), true)`检测。你还可以通过设置错误输入检查Activity是否进行了合适的处理。 + +- 生命周期控制:你可以测试程序中的Activity是否正常处理了生命周期的各个轮转情况。一个合格的Activity应该在pause或destroy时保存一些下次运行时需要的状态。 **记住若屏幕布局方向改变(如竖屏变为横屏)会引起Activity的destroy过程**,你应该保证设备外部移动不会引起应用数据丢失。 + +- Intent测试:你可以测试每个Activity是否正常处理了它在manifest文件下属性中的Intent.你可以在`ActivityInstrumentationTestCase2`测试中发送伪Intent给Activity。 + +- 运行时设置改动: 你可以模拟设备的设置改变(如屏幕方向改变、语言改变等)来测试正在运行中的Activity的应对是否正确。在[Handling Runtime Changes](http://developer.android.com/guide/topics/resources/runtime-changes.html)一文中讲述了Activity如何应对运行时设备设置改变。 + +- 设备尺寸以及分辨率的适配: 在你的应用要发布之前,你应该保证它在各式各样的屏幕上运行正常,可以通过`ViewAsserts`来自动化检测布局情况。你可以在各式各样的安卓虚拟机上运行测试,或在直接目标机型上运行。你可以在[Support Multiple Screens](http://developer.android.com/guide/practices/screens_support.html)上查看应用如何支持多样化屏幕。 + +## 接下来该做的事 + +在[Testing from Eclipse with ADT](http://developer.android.com/tools/testing/testing_eclipse.html)一文中讲述了怎么使用Eclipse来进行Android自动化测试,[Testring from Other IDEs](http://developer.android.com/tools/testing/testing_otheride.html)一文中讲述了其他开发工具的Android测试使用。 + +你可以通过[Activity Testing Tutorial](http://developer.android.com/training/testing.htm)来循序渐进地学习如何做Activity自动化测试,这篇文章为你提供了测试以Activity为主的应用的方案。 + +## 附录:关于UI测试中需要注意的地方 + +以下这部分内容是对于一些UI测试的tips,特别是当你要测试主线程中的动作(触屏、输入和锁屏等事件)时,你应该仔细看一下这部分内容。 + +### 在主线程上做测试 + +应用的Activities是运行在主线程上的。一旦UI界面实例化(举个简单的例子:当Activity的onCreate方法经调用后UI即会实例化),那么所有和UI界面交互的事件都必须在主线程里面发生。当你正常启动应用时,必须按照这个规则程序才能够正常运行。但是在以Instrumentation为基础的测试中(其他类测试不可以),你可以在测试程序中对UI进行操作。 + +如果要让整个测试函数都在主线程中运行,你可以给函数加上注解`@UiThreadTest`,但是这种情况下会让整个函数中的所有语句都运行在主线程中,这种情况下不和UI组件交互的语句是不允许的(如`Instrumentation.waitForIdleSync()`)。 + +让一个测试函数中的一部分在主线程中运行,你可以调用的`appActivity.runOnUiThread()`办法(`appActivity`是你在测试中拥有的Activity实例),将你想要运行在主线程中的东西放入一个匿名内部类`Runnable`中然后传给它。 + +举个例子,这段代码测试了一个Activity实力,控制Spinner获取焦点,并发送给它一个触屏事件。注意`waitForIdleSync`和`sendKeys`是不允许运行在主线程中的。 + + private MyActivity mActivity; // MyActivity is the class name of the app under test + private Spinner mSpinner; + + ... + + protected void setUp() throws Exception { + super.setUp(); + mInstrumentation = getInstrumentation(); + + mActivity = getActivity(); // get a references to the app under test + + /* + * Get a reference to the main widget of the app under test, a Spinner + */ + mSpinner = (Spinner) mActivity.findViewById(com.android.demo.myactivity.R.id.Spinner01); + + ... + + public void aTest() { + /* + * request focus for the Spinner, so that the test can send key events to it + * This request must be run on the UI thread. To do this, use the runOnUiThread method + * and pass it a Runnable that contains a call to requestFocus on the Spinner. + */ + mActivity.runOnUiThread(new Runnable() { + public void run() { + mSpinner.requestFocus(); + } + }); + + mInstrumentation.waitForIdleSync(); + + this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER); + +###屏蔽掉物理按键和触屏 + +为了让模拟器或者设备接收到测试程序的按键,你需要屏蔽掉设备的物理的按键和触屏事件。如果不这么做的话,测试程序发来的此类事件是不会起作用的。 + +你可以通过调用`ActivityInstrumentationTestCase2.setActivityTouchMode(false)`来做到这点(在`getActivity()`之间调用这条语句才会起到作用)。 **注意,你不能在运行在主线程的语句中调用它**,所以它不可以出现在含有`@UiThreadTest`注解的函数中,实际上最好的办法就是在`setUp()`方法中做这件事。 + +###先将设备解锁再进行测试 + +你可能发现UI测试在屏幕锁屏(或其他加密手段)时是无法使用的,因为在这种情况下应用无法接收到`sendKeys()`发送的事件。最好的解决办法就是先解锁设备再进行测试。 + +当然你也可以通过在onCreate中加上以下这段代码来显式地禁用锁屏,不过你需要为应用加上权限``。 + + mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + mLock = mKeyGuardManager.newKeyguardLock("activity_classname"); + mLock.disableKeyguard(); + +###疑难解答 + +这部分列出了一些在UI测试中比较多发生的错误。 + +**`WrongThreadException`:** + +**问题:** + +遇到错误消息:`android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.`而导致测试崩溃。 + +**可能的原因:** + +如果从非UI线程中和UI进行交互就会发生这个问题。当在测试中UI控件交互而你不加`@UiThreadTest`注解或者不在`runOnUiThread()`方法中进行的话,它会在非主进程中执行这些命令,这个错误就会发生。 + +**建议:** + +在主进程中和UI交互,并且使用支持Instrumentation的测试类。你可以在[Testing on UI Thread](http://developer.android.com/tools/testing/activity_testing.html#RunOnUIThread)找到更多消息。 + +**`java.lang.RuntimeException`:** + +**问题:** + +由错误消息`java.lang.RuntimeException: This method can not be called from the main application thread`所导致的测试崩溃。 + +**可能的原因:** + +这个错误经常发生在你在`@UiThreadTest`注解的函数中调用了`runOnUiThread()`或其他不在UI进程中运行的语句。 + +**建议:** + +你可以通过尝试去掉`@UiThreadTest`注解,或去掉`runOnUiThread()`,或重写测试程序来尝试解决。 + diff --git "a/issue-22/Android\344\270\255\347\232\204AOP\347\274\226\347\250\213.md" "b/issue-22/Android\344\270\255\347\232\204AOP\347\274\226\347\250\213.md" new file mode 100644 index 00000000..b4c02698 --- /dev/null +++ "b/issue-22/Android\344\270\255\347\232\204AOP\347\274\226\347\250\213.md" @@ -0,0 +1,367 @@ +Android 中的 AOP 编程 +--- + +> * 原文链接 : [Aspect Oriented Programming in Android](http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android) +* 原文作者 : [Fernando Cejas](http://fernandocejas.com) +* [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 译者 : [byronwind](https://github.com/byronwind) +* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu) +* 状态 : 校对完成 + + +**面向切面编程(AOP,Aspect-oriented programming)**需要把程序逻辑分解成『**关注点**』(concerns,功能的内聚区域)。这意味着,在 AOP 中,我们不需要显式的修改就可以向代码中添加可执行的代码块。这种编程范式假定『横切关注点』(cross-cutting concerns,多处代码中需要的逻辑,但没有一个单独的类来实现)应该只被实现一次,且能够多次注入到需要该逻辑的地方。 + +**代码注入是 AOP 中的重要部分:**它在处理上述提及的横切整个应用的**『关注点』**时很有用,例如日志或者性能监控。这种方式,并不如你所想的应用甚少,相反的,每个程序员都可以有使用这种注入代码能力的场景,这样可以避免很多痛苦和无奈。 + +**AOP** 是一种已经存在了很多年的编程范式。我发现把它应用到 Android 开发中也很有用。经过一番调研后,我认为我们用它可以获得很多好处和有用的东西。 + +## 术语(迷你术语表) +在开始之前,我们先看看需要了解的词汇: + +* **Cross-cutting concerns(横切关注点):** 尽管面向对象模型中大多数类会实现单一特定的功能,但通常也会开放一些通用的附属功能给其他类。例如,我们希望在数据访问层中的类中添加日志,同时也希望当UI层中一个线程进入或者退出调用一个方法时添加日志。尽管每个类都有一个区别于其他类的主要功能,但在代码里,仍然经常需要添加一些相同的附属功能。 + +* **Advice(通知):** 注入到class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 除了在方法中注入代码,也可能会对代码做其他修改,比如在一个class中增加字段或者接口。 + +* **Joint point(连接点):** 程序中可能作为代码注入目标的特定的点,例如一个方法调用或者方法入口。 + +* **Pointcut(切入点):** 告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,比如,标记了一个定义成@DebguTrace 的自定义注解的所有方法。 + +* **Aspect(切面):** Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。 + +* **Weaving(织入):** 注入代码(advices)到目标位置(joint points)的过程。 + + +下面这张图简要总结了一下上述这些概念。 + +![](http://upload-images.jianshu.io/upload_images/30689-55846998f4f5b4ce.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240) + +## 那么...我们何时何地应用AOP呢? +一些示例的 cross-cutting concerns 如下: + +* **日志** +* **持久化** +* **性能监控** +* **数据校验** +* **缓存** +* [其他更多](http://en.wikipedia.org/wiki/Cross-cutting_concern) + +取决于你所选的其中一种或其他方案 :)。 + +## 工具和库 +有一些工具和库帮助我们使用 AOP: + +* [AspectJ:](https://eclipse.org/aspectj/) 一个 JavaTM 语言的面向切面编程的无缝扩展(适用Android)。 + +* [Javassist for Android:](https://github.com/crimsonwoods/javassist-android) 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。 + +* [DexMaker:](https://code.google.com/p/dexmaker/) Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。 + +* [ASMDEX:](http://asm.ow2.org/asmdex-index.html) 一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。 + +## 为什么用 AspectJ? + +我们下面的例子选用 AspectJ,有以下原因: + +* **功能强大** +* **支持编译期和加载时代码注入** +* **易于使用** + +## 示例 + +比方说,我们要测量一个方法的性能(执行这个方法需要多长时间)。为此我们用一个 **@DebugTrace** 的注解标记我们的这个方法,并且无需在每个注解过的方法中编写代码,就可以通过 logcat 输出结果。我们的方法是使用 AspectJ 达到这个目的。 + +我们看下在底层到底发生了什么: + +* **我们在编译过程中增加一个新的步骤处理注解。** +* **注解的方法内会生成和注入必要的样板代码。** + +在此,我必须要提到当我研究这些时,发现了[Jake Wharton’s Hugo Library](https://github.com/JakeWharton/hugo) 这个项目,支持做同样的事情。因此,我重构了我的代码,看上去和它类似。尽管,我的代码是一个更加原始和简化的版本(顺便提一下,通过看这个项目的代码,我学到了很多)。 + +![](http://upload-images.jianshu.io/upload_images/30689-77fa4ba34c4afe60.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240) + +### 工程结构 +我们会把一个简单的示例应用拆分成两个 modules,第一个包含我们的 Android App 代码,第二个是一个 Android Library 工程,使用 AspectJ 织入代码(代码注入)。 + +你可能会想知道为什么我们用一个 Android Library 工程,而不是用一个纯的 Java Library:原因是为了使 AspectJ 能在 Android 上运行,我们必须在编译时做一些 hook。这只能使用 andorid-library gradle 插件完成。(先不要为此担心,后面我会给出更多细节。) + +### 创建注解 +首先我们创建我们的Java注解。这个注解周期声明在 class 文件上(RetentionPolicy.CLASS),可以注解构造函数和方法(ElementType.CONSTRUCTOR 和 ElementType.METHOD)。因此,我们的 DebugTrace.java 文件看上是这样的: + +```java +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) +public @interface DebugTrace {} +``` + +### 我们的性能监控计时类 +我已经创建了一个简单的计时类,包含 `start/stop` 方法。下面是 StopWatch.java 文件: + +```java +/** + * Class representing a StopWatch for measuring time. + */ +public class StopWatch { + private long startTime; + private long endTime; + private long elapsedTime; + + public StopWatch() { + //empty + } + + private void reset() { + startTime = 0; + endTime = 0; + elapsedTime = 0; + } + + public void start() { + reset(); + startTime = System.nanoTime(); + } + + public void stop() { + if (startTime != 0) { + endTime = System.nanoTime(); + elapsedTime = endTime - startTime; + } else { + reset(); + } + } + + public long getTotalTimeMillis() { + return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0; + } +} +``` + +### DebugLog 类 +我只是包装了一下 “android.util.Log”,因为我首先想到的是向 android log 中增加更多的实用功能。下面是代码: + +```java +/** + * Wrapper around {@link android.util.Log} + */ +public class DebugLog { + + private DebugLog() {} + + /** + * Send a debug log message + * + * @param tag Source of a log message. + * @param message The message you would like logged. + */ + public static void log(String tag, String message) { + Log.d(tag, message); + } +} +``` + +### Aspect 类 +现在是时候创建我们的 Aspect 类(TraceAspect.java)了。Aspect 类负责管理注解的处理和代码织入。 + +```java +/** + * Aspect representing the cross cutting-concern: Method and Constructor Tracing. + */ +@Aspect +public class TraceAspect { + + private static final String POINTCUT_METHOD = + "execution(@org.android10.gintonic.annotation.DebugTrace * *(..))"; + + private static final String POINTCUT_CONSTRUCTOR = + "execution(@org.android10.gintonic.annotation.DebugTrace *.new(..))"; + + @Pointcut(POINTCUT_METHOD) + public void methodAnnotatedWithDebugTrace() {} + + @Pointcut(POINTCUT_CONSTRUCTOR) + public void constructorAnnotatedDebugTrace() {} + + @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()") + public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + String className = methodSignature.getDeclaringType().getSimpleName(); + String methodName = methodSignature.getName(); + + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + Object result = joinPoint.proceed(); + stopWatch.stop(); + + DebugLog.log(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis())); + + return result; + } + + /** + * Create a log message. + * + * @param methodName A string with the method name. + * @param methodDuration Duration of the method in milliseconds. + * @return A string representing message. + */ + private static String buildLogMessage(String methodName, long methodDuration) { + StringBuilder message = new StringBuilder(); + message.append("Gintonic --> "); + message.append(methodName); + message.append(" --> "); + message.append("["); + message.append(methodDuration); + message.append("ms"); + message.append("]"); + + return message.toString(); + } +} +``` + +几个在此提到的重点: + +* 我们声明了两个作为 pointcuts 的 public 方法,筛选出所有通过 ```“org.android10.gintonic.annotation.DebugTrace”``` 注解的方法和构造函数。 +* 我们使用 `“@Around”` 注解定义了`“weaveJointPoint(ProceedingJoinPoint joinPoint)” `方法,使我们的代码注入在使用`"@DebugTrace"`注解的地方生效。 +* `“Object result = joinPoint.proceed();” `这行代码是被注解的方法执行的地方。因此,在此之前,我们启动我们的计时类计时,在这之后,停止计时。 +* 最后,我们构造日志信息,用 Android Log 输出。 + +###使 AspectJ 运行在 Anroid 上 +现在,所有代码都可以正常工作了,但是,如果我们编译我们的例子,我们并没有看到任何事情发生。原因是我们必须使用 AspectJ 的编译器(ajc,一个java编译器的扩展)对所有受 aspect 影响的类进行织入。这就是为什么,我之前提到的,我们需要在 gradle 的编译 task 中增加一些额外配置,使之能正确编译运行。 + +我们的 build.gradle 文件如下: + +```java +import com.android.build.gradle.LibraryPlugin +import org.aspectj.bridge.IMessage +import org.aspectj.bridge.MessageHandler +import org.aspectj.tools.ajc.Main + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.12.+' + classpath 'org.aspectj:aspectjtools:1.8.1' + } +} + +apply plugin: 'android-library' + +repositories { + mavenCentral() +} + +dependencies { + compile 'org.aspectj:aspectjrt:1.8.1' +} + +android { + compileSdkVersion 19 + buildToolsVersion '19.1.0' + + lintOptions { + abortOnError false + } +} + +android.libraryVariants.all { variant -> + LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin) + JavaCompile javaCompile = variant.javaCompile + javaCompile.doLast { + String[] args = ["-showWeaveInfo", + "-1.5", + "-inpath", javaCompile.destinationDir.toString(), + "-aspectpath", javaCompile.classpath.asPath, + "-d", javaCompile.destinationDir.toString(), + "-classpath", javaCompile.classpath.asPath, + "-bootclasspath", plugin.project.android.bootClasspath.join( + File.pathSeparator)] + + MessageHandler handler = new MessageHandler(true); + new Main().run(args, handler) + + def log = project.logger + for (IMessage message : handler.getMessages(null, true)) { + switch (message.getKind()) { + case IMessage.ABORT: + case IMessage.ERROR: + case IMessage.FAIL: + log.error message.message, message.thrown + break; + case IMessage.WARNING: + case IMessage.INFO: + log.info message.message, message.thrown + break; + case IMessage.DEBUG: + log.debug message.message, message.thrown + break; + } + } + } +} +``` + +### 我们的测试方法 +我们添加一个测试方法,来使用我们炫酷的 aspect 注解。我已经在主 Activity 类中增加了一个方法用来测试。看下代码: + +```java + @DebugTrace + private void testAnnotatedMethod() { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +``` + +### 运行我们的应用 +我们用 gradle 命令编译部署我们的 app 到 android 设备或者模拟器上: + +``` +gradlew clean build installDebug +``` + +If we open the logcat and execute our sample, we will see a debug log with: +如果我们打开 logcat,执行我们的例子,会看到一条 debug 日志: + +``` +Gintonic --> testAnnotatedMethod --> [10ms] +``` + +**我们的第一个使用 AOP 的 Androd 应用可以工作了!** +你可以用 Dex Dump 或者任何其他的逆向工具反编译 apk 文件,看一下生成和注入的代码。 + +## 回顾 +回顾总结如下: + +* 我们已经对面向切面编程(AOP)这一范式有了初步体验。 +* 代码注入是 AOP 中的重要部分。 +* AspectJ 是在 Android 应用中进行代码织入的强大且易用的工具。 +* 我们已经使用 AOP 能力创建了一个可以工作的示例。 + + +## 结论 +面向切面编程很强大。通过正确使用,你可以在开发你的 Android 应用时,避免在『cross-cutting concerns』处复制大量代码,比如我们在示例中看到的性能监控部分。我非常鼓励你尝试一下,你会发现它非常有用。 + +我希望你能喜欢这篇文章,文章的目的是分享我学到的东西,所以,欢迎评论和反馈,如果能 fork 代码玩一下就更好了。 + +我确信我们能在示例 app 的 AOP 模块里增加些有趣的东西,欢迎提出你的想法;)。 + +## 源码 +你可以在 https://github.com/android10/Android-AOPExample 下载示例 app 代码。另外我还有一个使用动态代理的 Java AOP 示例(也可以用在Android上):https://github.com/android10/Android-AOPExample + +## 资源 +* [Aspect-oriented programming.](http://en.wikipedia.org/wiki/Aspect-oriented_programming) +* [Aspect-oriented software development.](http://en.wikipedia.org/wiki/Aspect-oriented_software_development) +* [Practical Introduction into Code Injection with AspectJ, Javassist, and Java Proxy.](http://www.javacodegeeks.com/2011/09/practical-introduction-into-code.html) +* [Implementing Build-time Bytecode Instrumentation With Javassist.](http://java.dzone.com/articles/implementing-build-time) +* [Frequently Asked Questions about AspectJ.](http://www.eclipse.org/aspectj/doc/released/faq.php) +* [AspectJ Cheat Sheet.](http://blog.espenberntsen.net/2010/03/20/aspectj-cheat-sheet/) + + +----- +译注 +> * AOP 中的术语并没有统一的中文翻译,翻译过程中,术语一节我选取了用的比较多的中文名称注释在括号中帮助理解,正文中其他部分出现的术语,使用原始英文命名。 +* 这篇文章是2014年发布的,2015年7月,阿里巴巴刚刚开源了一个强大的 Android 平台 AOP 框架 [Dexposed](https://github.com/alibaba/dexposed),该项目基于著名的[ Xposed 项目](https://github.com/rovo89/Xposed)。 \ No newline at end of file diff --git "a/issue-22/Binder\346\241\206\346\236\266\350\247\243\346\236\220.md" "b/issue-22/Binder\346\241\206\346\236\266\350\247\243\346\236\220.md" new file mode 100644 index 00000000..08341b41 --- /dev/null +++ "b/issue-22/Binder\346\241\206\346\236\266\350\247\243\346\236\220.md" @@ -0,0 +1,2153 @@ +#Why are you here? + +* 你想要更好的理解Android是怎样工作的 + + 。Intent、ContextProvider、Messenger + 。访问系统的服务 + 。生命周期中的回调 + 。安全 + +* 你想要通过高效率和低延时的IPC框架打破应用程序模块化的业务逻辑 +* 你想要添加新的系统服务,可以更好地暴露给开发人员 +* 你只是感觉IPC和Binder是不可缺少的,有趣的 +* 你没有其它的事要做啦 + +#Objectives + +* Binder Overview +* IPC +* Advantages of Binder +* Binder vs Intent/ContentProvider/Messenger-based IPC Binder Terminology +* Binder Communication and Discovery AIDL +* Binder Object Reference Mapping Binder by Example +* Async Binder Memory Sharing Binder Limitations Security + +>Slides and screencast from this class will be posted to: http://mrkn.co/bgnhg + + + +#Who am I? +亚历山大 加尔根塔 + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/author.png) + +Developer and instructor of Android Internals and Security training at +Marakana + +* 马里亚纳(位于西太平洋)Android Internals and Security training的开发者和指导师 +* San Francisco Android User Group (sfandroid.org)的创始人和组织者 +* San Francisco Java User Group (sfjava.org) 的创始人和组织者 +* San Francisco HTML5 User Group (sfhtml5.org) 的合作发起人和组织者 +* AnDevCon, AndroidOpen, Android Builders Summit 等等的演讲者 +* Server-side Java and Linux, since 1997 +* Android/embedded Java and Linux, since 2009 +* 曾就职于 SMS, WAP Push, MMS, OTA provisioning +* Follow + + 。@agargenta + 。+Aleksandar Gargenta + 。http://marakana.com/s/author/1/aleksandar_gargenta + + +#What is Binder? + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/what_is_Binder_01.png) + + +* 一个IPC组件,开发面向对象的操作系统服务 + + 。没有另外一个面向对象内核 + 。代替运行在传统内核上,面向对象的操作系统环境,如:Linux + +* Android的关键 +* 来自OpenBinder + + * 发源于 Be,Inc ,作为 "next generation BeOS"(~ 2001) 的关键部分 + * 被 PalmSource 收购 + * 最初使用在 Palm Cobalt (micro-kernel based OS) + * Palm 切换到 Linux,所以,Binder被移植到Linux,开源(~ 2005) + * 谷歌雇用了OpenBinder的核心工程师Dianne Hackborn,加入Android团队(~ 2008) + * OpenBinder不再维护 -- Binder长存 + +* 专注于可伸缩性、稳定性、灵活性、低延迟和开销、简单的编程模型 + + +#Why Binder? + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/why_binder_01.png) + + +* 出于安全性、稳定性和内存管理的考虑,Android的应用和系统服务运行在分离的进程中,但是它们之间需要通信和共享数据 + + 。安全性:每一个进程就是一个沙盒,运行在一个不同的系统标识中 + 。稳定性:如果一个进程失常(例如:崩溃),它不影响其它的进程 + 。内存管理:“不需要”的进程会被移除,为新的释放资源(主要是内存) + 。事实上,一个单独的Android应用可以让它的组件运行在不同的进程中 + + +* IPC来拯救 + + 。如果我们需要避免传统IPC开销和服务拒绝的问题 + +* Android的libc(a.k.a bionic)库不支持System V IPCs + + 。没有SysV信号量,共享内存、消息队列等等 + 。当一个进程终止时,“忘记”释放IPC共享资源,System V IPC会报内存资源泄露的错误 + 。bug,恶意代码或者一个正常的应用在低内存的情况下都会无条件终止 + + +* Binder来拯救 + + + 。其内置的“对象”引用的引用计数器,加上消亡提醒机制,让它适用于“敌对的”环境(低内存强杀) + 。当一个Binder服务没有任何终端引用时,它的所有者可以自动提醒它去处理自己 + +* 很多其它的特性: + + 。"线程迁移" - 如,编程模式: + + * 远程对象可以像本地的一样调用自动管理线程池方法 + * “跳转”到其它的进程中 + * 同步和异步(单向)的调用模式 + + + 。分辨发送者和接受者(通过UID/PID)- 对于安全很重要 + 。独特的跨进程边界对象映射 + 。一个远程对象的引用可以传递到的另外的进程中,并且可以用作一个标志令牌 + 。各个进程之间发送文件描述符的能力 + 。简单的Android接口定义语言(AIDL) + 。内置支持很多编组的常见数据类型 + 。通过自动生成的代理和存根简化事务调用模型(只有Java) + 。跨进程递归 - 例如:当调用本地对象上的方法时就跟递归的语义一样 + 。如果客户端和服务器运行在同样的进程中,就会是本地执行模式(不是IPC数据信号编集) + + + + +* 但是: + + 。不支持RPC(只有本地) + 。客户端与服务之间是基于消息的通信 - 不适合流 + 。没有被POSIX或任何其他标准定义 + +* 大多数应用程序和核心系统服务依赖于Binder + + 。应用程序大多数生命周期的回调(例如:onResume(), onDestory(), etc.)会通过Binder被ActivityManagerService调用 + 。关闭binder,然后整个系统慢慢停止(无显示,无音频,无输入,无传感器,...) + 。有些情况下使用Unix域中的socket(例如:RILD) + + +#IPC with Intents and ContentProviders? + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/ipc_with_intents_01.png) + + +* 通过Intent和content provider Android支持一个简单形式的IPC +* 意图的消息传递是Android组件之间异步通信的一个框架 + + 。这些组件可能运行在相同的或不同的应用中(例如:多进程) + 。支持点对点和发布-订阅消息传递域 + 。意图本身代表一个包含操作的描述和传递给接受者的数据 + 。隐式意图能够给APIs解耦合 + + +* ContentResolver通过稳定的(CRUB)API 与 ContentProviders (典型的运行在不同的应用中的)同步通信 + +* 所以的Android组件都可以是发送者,但是大多数是接受者 +* 所有通信发生在循环线程(又称主线程)中 +* 但是: + + 。不是真实的面向对象 + 。只有基于intent的异步通信 + 。不适合低延时 + 。因为,API定义地松散,所以容易发生运行时错误 + 。所有的底层通信都是基于Binder的 + 。事实上,Intent和ContentProvider只是Binder的高级抽象 + 。基于系统服务方便扩展:ActivityManagerService和PackageManagerService + +For example: +*src/com/marakana/shopping/UpcLookupActivity.java* + +```java + +public class ProductLookupActivity extends Activity { + + private static final int SCAN_REQ = 0; ... + + public void onClick(View view) { + Intent intent = new Intent("com.google.zxing.client.android.SCAN"); //1 + intent.setPackage("com.google.zxing.client.android"); //1 + intent.putExtra("SCAN_MODE", "PRODUCT_MODE"); //2 + super.startActivityForResult(intent, SCAN_REQ); //3 + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { //4 + if (requestCode == SCAN_REQ && resultCode == RESULT_OK) { //5 + String barcode = data.getStringExtra("SCAN_RESULT"); //6 + String format = data.getStringExtra("SCAN_RESULT_FORMAT"); //6 + ... + super.startActivity( + new Intent(Intent.ACTION_VIEW, + Uri.parse("http://www.upcdatabase.com/item/" + barcode))); //7 + } + ... + } +} + +``` + +*src/com/google/zxing/client/android/CaptureActivity.java:* +```java + +... + +public class CaptureActivity extends Activity { + + ... + + private void handleDecodeExternally(Result rawResult, ...) { + + Intent intent = new Intent(getIntent().getAction()); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + intent.putExtra(Intents.Scan.RESULT, rawResult.toString()); //8 + intent.putExtra(Intents.Scan.RESULT_FORMAT, + rawResult.getBarcodeFormat().toString()); + ... + super.setResult(Activity.RESULT_OK, intent); + super.finish(); //9 + } +} + +``` + + +* 1 指定我们要调用的是谁 +* 2 为我们的调用指定输入参数 +* 3 启动异步调用 +* 4 通过回调接收响应 +* 5 确认这是我们预期的响应 +* 6 得到响应 +* 7 启动另一个IPC请求,但不期望结果 +* 8 在服务方面,把结果放在一个新的Intent中 +* 9 异步发回结果 + +#Messenger IPC + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/messenger_ipc_01.png) + + +* Android的Messenger表示一个可以通过Intent发送到远程进程中的Handler引用 +* Messenger的引用可以通过Intent传递,使用前面提到的IPC机制来实现 +* 远程进程发送消息,通过Messenger,发送到本地的处理程序中 +* Messenger就像是Intent,它们可以指定“行为”(aMessge.what)和数据(aMessage.getData()) +* 仍然是异步,但却是更低的延迟/开销 +* 从服务端到客户端高效的回调 +* 消息默认会在循环器线程中处理 +* 所有底层的通信依然基于Binder +* 例如,如何定义客户端: + +*src/com/marakana/android/download/client/DownloadClientActivity.java:* + +```java +... +public class DownloadClientActivity extends Activity { + + private static final int CALLBACK_MSG = 0; + ... + + @Override + public void onClick(View view) { + + Intent intent = new Intent( "com.marakana.android.download.service.SERVICE"); //1 + ArrayList uris = ... + intent.putExtra("uris", uris); //2 + Messenger messenger = new Messenger(new ClientHandler(this)); //3 + intent.putExtra("callback-messenger", messenger); //4 + super.startService(intent); //5 + + } + + private static class ClientHandler extends Handler { + + private final WeakReference clientRef; //6 + public ClientHandler(DownloadClientActivity client) { + this.clientRef = new WeakReference(client); + + } + + @Override + public void handleMessage(Message msg) { //7 + + Bundle data = msg.getData(); + DownloadClientActivity client = clientRef.get(); + if (client != null && msg.what == CALLBACK_MSG && data != null) { + + Uri completedUri = data.getString("completed-uri"); //8 + // client now knows that completedUri is done + ... + } + } + } +} + +``` + + +* 1 指定我们想要调用的对象(返回使用Intent) +* 2 为我们的调用指定输入参数 +* 3 在我们的handler中创建一个messenger +* 4 传递的messenger也是输入的参数 +* 5 启动异步的调用 +* 6 我们的handler保存客户端的引用 +* 7 通过一个handler中的回调收接收响应 +* 8 获取响应数据 +* 和我们的服务端可以看下面: + +*src/com/marakana/android/download/service/DownloadService.java:* + +```java +... + +public class MessengerDemoService extends IntentService { + + private static final int CALLBACK_MSG = 0; + + ... + + @Override + protected void onHandleIntent(Intent intent) { //1 + + ArrayList uris = intent.getParcelableArrayListExtra("uris"); //2 + Messenger messenger = intent.getParcelableExtra("callback-messenger"); //3 + for (Uri uri : uris) { + // download the uri + ... + if (messenger != null) { + + Message message = Message.obtain(); //4 + message.what = CALLBACK_MSG; + Bundle data = new Bundle(1); + data.putParcelable("completed-uri", uri); //5 + message.setData(data); //4 + + try { + + messenger.send(message); //6 + + } catch (RemoteException e) { + ... + } finally { + message.recycle(); //4 } + } + } + } +} + +``` + + +* 1 处理客户端的请求(可能是本地的或远程的) +* 2 获取请求数据 +* 3 获取messenger的引用 +* 4 用Message作为数据的通用信封 +* 5 设置我们的应答 +* 6 发送我们的应答 + + +#Binder 术语 + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_terminology_01.png) + +* Binder (Framework) + + 所有的IPC架构 + +* Binder Driver + + 内核级别的驱动,处理各个进程之间的通信 + +* Binder Protocol + + 底层协议(基于ioctl),用于与Binder驱动通信 + +* IBinder Interface + + 定义良好的行为(例如:方法),Binder对象必须实现 + +* AIDL + + Android接口定义语言,用于描述IBinder接口的业务操作 + +* Binder (Object) + + 通用IBinder接口的实现 + +* Binder Token + + 一个抽象的32位数值,在系统的所有进程中唯一的标识一个Binder对象 + +* Binder Service + + 真正实现Binder(对象)的业务操作 + +* Binder Client + + 一个对象,使用Binder服务提供的行为 + +* Binder Transaction + + 远程Binder对象调用一个行为(例如:一个方法),基于Binder协议,可能涉及发送、接受的数据 + +* Parcel + + "可以在IBinder中发送消息的容器(数据和对象的引用)",事务处理的数据单元——一个用作流出请求,另一个用作流入响应 + +* Marshalling + + 将高级的应用程序数据结构(例如:请求、响应参数)转化成parcel对象的过程,目的是将它们嵌套进Binder的事务中 + +* Unmarshalling + + 将Binder事务中获取到的parcel对象重构成高级应用的数据结构的过程(例如:请求、响应参数) + +* Proxy + + 一个AIDL接口的实现,编组、解组数据,映射调用事务的方法,将一个封装的IBinder引用指向Binder对象 + +* Stub + + 一个AIDL接口局部的实现,当编组/解组数据时,映射事务到Binder Service调用 + +* Context Manager (a.k.a. servicemanager) + + 一个特殊的已知处理的Binder对象,被用作为其它Binder注册、查询 + + +Binder Communication and Discovery +#Binder通信挖掘 + + +* 对于关注的客户端,它只是想使用服务: + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_01.png) + +* 当进程不能直接操作其它的进程(或者读写数据),内核可以做到,因此他们使用Binder驱动: + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_02.png) + + +>警告:因为服务端可能从多个客户端得到并发的请求,所以,它需要保护(同步访问的)其变量的状态 + +* Binder驱动通过/dev/binder暴露出来,并提供了相关联的基于open、release、poll、mmap、flush、ioctl的操作 +* 事实上,大多数的通信都是通过ioctl(binderFd, BINDER_WRITE_READ, &bwd)进行的,bwd被定义为: + +```C + +struct binder_write_read { + + signed long write_size; /* bytes to write */ + signed long write_consumed; /* bytes consumed by driver */ + unsigned long write_buffer; + signed long read_size; /* bytes to read */ + signed long read_consumed; /* bytes consumed by driver */ + unsigned long read_buffer; + +}; + +``` + + +* write_buffer包含一系列操作驱动的命令 + + 。Book-keeping命令,例如:增加、减少binder对象的引用,请求、清除死亡通知 + 。请求响应的命令,例如:BC_TRANSACTION + +* read_buffer包含用户操作的命令 + + 。book-keeping命令 + 。操作响应的命令或执行嵌套(递归)请求处理 + +* 客户端通过事务与服务端通信,事务中包含一个binder token、方法体、原始缓存数据和PID/UID的发送者(驱动添加的) +* 客户端和服务端使用的大多数底层的操作和数据结构(例如:Parcel)是libbinder的抽象(在底层) +* 为了避免客户端和服务知道Binder协议和libbinder所有事情,它们使用代理和存根: + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_03.png) + + +>注意:基于java的代理和存根可以通过描述服务的aidl工具自动生成 + +* 事实上,大多数的客户端都不知道它们正在使用IPC,从来没有提过Binder或者代理,因为,它们依靠抽象来管理它们的复杂性: + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_04.png) + + +>注意:对系统服务来说,这是非常正确的,它们使用管理者只向客户端暴露了API的一个子集 + +* 但是,客户端是怎么获取到它想要会话的handle的呢?只需要问Servicemanager(Binder's CONTEXT_MGR),希望当前的服务已经注册了handle: + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_communication_05.png) + + +>注意:出于安全、健康的原因,Binder驱动一次只会接受一个CONTEXT_MGR注册,这就是为什么Android中的Servicemanager是第一批启动的服务 + +* 想使用servicemanager获取当前注册的服务清单,运行: + +```shell + +$ adb shell service list +Found 71 services: +0 sip: [android.net.sip.ISipService] +1 phone: [com.android.internal.telephony.ITelephony] +... +20 location: [android.location.ILocationManager] +... +55 activity: [android.app.IActivityManager] +56 package: [android.content.pm.IPackageManager] +... +67 SurfaceFlinger: [android.ui.ISurfaceComposer] +68 media.camera: [android.hardware.ICameraService] +69 media.player: [android.media.IMediaPlayerService] +70 media.audio_flinger: [android.media.IAudioFlinger] + +``` + + +* 另外一种查看方式: + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_commnuication_06.png) + +#Location Service: An Example + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/location_service_01.png) + + +#AIDL + + +* AIDL是Android的语言,定义了基于Binder的服务接口 +* AIDL遵循类似Java的接口语法,允许我们定义自己的“业务”方法 +* 每一个基于Binder的服务都被定义在.aidl文件中,经典的命名如:IFooService.aidl,并且保存在src/目录下 + + +*src/com/example/app/IFooService.aidl* + +```aidl + +package com.example.app; + +import com.example.app.Bar; + +interface IFooService { + + void save(inout Bar bar); + Bar getById(int id); + void delete(in Bar bar); + List getAll(); + +} + +``` + +* aidl的编译工具(Android SDK 部分)被用作从每个.aidl文件中提取真正的java接口(连同提供Android‘s android.os.IBinder的Stub)放到我们的/gen目录下 + +*gen/com/example/app/IFooService.java* + +```java + +package com.example.app; + +public interface IFooService extends android.os.IInterface { + + public static abstract class Stub extends android.os.Binder implements com.example.app.IFooService { + ... + public static com.example.app.IFooService asInterface( + android.os.IBinder obj) { + ... + return new com.example.app.IFooService.Stub.Proxy(obj); + + } + + ... + + public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { + + switch (code) + ... + case TRANSACTION_save: { + ... + com.example.app.Bar _arg0; + ... + _arg0 = com.example.app.Bar.CREATOR.createFromParcel(data); this.save(_arg0); + ... + } + ... + } + ... + } + + ... + + private static class Proxy implements com.example.app.IFooService { + + private android.os.IBinder mRemote; + ... + public void save(com.example.app.Bar bar) throws android.os.RemoteException { + + ... + android.os.Parcel _data = android.os.Parcel.obtain(); + ... + bar.writeToParcel(_data, 0); + ... + mRemote.transact(Stub.TRANSACTION_save, _data, _reply, 0); + ... + } + } + } + + void save(com.example.app.Bar bar) throws android.os.RemoteException; + com.example.app.Bar getById(int id) throws android.os.RemoteException; + void delete(com.example.app.Bar bar) throws android.os.RemoteException; + java.util.List getAll() throws android.os.RemoteException; + +} + +``` + + +>注意:每次Eclipse ADT在src/目录下发现.aidl文件,它就会自动调用 + + +* AIDL支持以下的类型: + + 。null + 。boolean,boolean[],byte,byte[],char[],int,int[],long,long[],float,float[], + double,double[] + 。java.lang.CharSequence,java.lang.String (sent as UTF-20) + 。java.io.FileDescriptor - transferred as a dup of the original file descriptor (points to the same underlying stream and position) + 。java.io.Serializable - not efficient (too verbose) + 。java.util.Map - of supported types (always reconstructed as + java.util.HashMap) + 。android.os.Bundle - a specialized Map-wrapper that only accepts AIDL-supported data types + 。java.util.List - of supported types (always reconstructed as java.util.ArrayList) + 。java.lang.Object[] - of supported types (including primitive wrappers) + 。android.util.SparseArray,android.util.SparseBooleanArray + 。android.os.IBinder,android.os.IInterface - transferred by (globally unique) reference (as a "strong binder",a.k.a. handle) that can be used to call-back into the sender + 。android.os.Parcelable - 自定义类型: + +*src/com/example/app/Bar.java* + +```java + +package com.example.app; +import android.os.Parcel; + +import android.os.Parcelable; +public class Bar implements Parcelable { + + private int id; + private String data; + public Bar(int id, String data) { this.id = id; + this.data = data; + +} + +// getters and setters omitted + ... +public int describeContents() { + return 0; +} + +public void writeToParcel(Parcel parcel, int flags) { + + parcel.writeInt(this.id); + parcel.writeString(this.data); + +} + +public void readFromParcel(Parcel parcel) { + + this.id = parcel.readInt(); + this.data = parcel.readString(); + +} + +public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + public Bar createFromParcel(Parcel parcel) { + return new Bar(parcel.readInt(), parcel.readString()); + } + + public Bar[] newArray(int size) { + + return new Bar[size]; + + } +}; + +} + +``` + + +>注意:public void readFromParcel(Parcel) 方法不是 Parcelable 接口定义的。这里是出于Bar的易变性考虑 -- 比如:我们期望远程的那端能够在 void save(inout Bar bar) 方法中修改它。 + +* 同理,public static final Parcelable.Creator CREATOR 也不是 Parcelable 接口定义的(很明显)。它从 save 的事务中获取 _data ,从 getById 操作中获取 _reply ,重构了 Bar。 + +* 这些自定义的类必须在它们自己的.aidl文件中申明 + +*src/com/example/app/Bar.aidl* + +```java + +package com.example.app; +parcelable Bar; + +``` + +>注意:AIDL接口必须导入parcelable自定义的类,即使它们在同一个包中。对于前面的示例,src/com/example/app/IFooService.aidl 必须导入com.example.app.Bar。 + +* AIDL定义的方法可以不传或传入多个参数,但是,必须返回一个值或者Void +* 所有的非基本类型的参数需要一个指定方向的标签,in、out、inout,这其中的一个 + + 。基本数据类型的指向一直是in(可以省略) + 。当编组数据的时候指向标签会传递给Binder,因此它会对性能有直接影响 + + +* 所有的.aidl注释都应该被拷贝到生成的Java接口中(除了import和package语句前的) + + +* 只有以下的异常会默认支持:SecurityException, BadParcelableException, IllegalArgumentException, NullPointerException, IllegalStateException +* .aidl文件中,不支持静态成员 + + +#跨进程映射Binder对象引用 + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/binder_object_reference_01.png) + + +* Binder对象的引用是以下的一种: + + 。在同一个进程中,Binder对象的一个真实的虚拟地址 + 。在另外一个进程中的,Binder对象的一个抽象的32位handle + + +* 在每个的事务中,Binder驱动都会自动的将远程的binder handle映射成本地的地址,将本地的地址映射成远程的Binder handle + +这种映射实现: + + 。Binder事务的目标 + + 。IBinder对象的引用作为参数或返回值跨进程共享(嵌套在事务数据中) + + +* 为了能够工作 + + 。binder驱动在进程之间维持本地地址和远程handle(每一个进程都作为一个二进制树),因此,binder可以传送 + 。嵌套在事务中的引用是基于客户端提供的偏移 + + +* Binder驱动不知道不与远程进程共享的Binder对象 + 。一旦一个新的Binder对象引用在事务中被发现,它就会记录在Binder驱动中 + 。任何时候,Binder引用与其它的进程共享,Binder驱动的引用计数器就会增长 + 。当进程消亡的时候,Binder驱动的计数器就会明确的减少或自动地减少 + 。当一个引用不再需要的时候,它的所有者就会提醒它可以释放掉,并且Binder会删除自己的映射 + +#构建基于Binder的服务端和客户端 + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/building_a_binder_01.png) + + +* 为了演示基于Binder的服务端和客户端(基于Fibonacci),我们将会创建三个独立的工程: + + 1.FibonacciCommon库工程 - 定义AIDL接口,自定义类型作为参数和返回值 + 2.FibonacciService工程 - 我们实现AIDL接口,并把它暴露给客户端 + 3.FibonacciClient工程 - 用它连接我们的AIDL服务 + + +* 以下的代码是可用的 + + 。一个ZIP归档文件 :https://github.com/marakana/FibonacciBinderDemo/zipball/master + 。通过Git:git clone https://github.com/marakana/FibonacciBinderDemo.git + + +* UI显示大概如下: + +![](http://git.oschina.net/sjyin/android-tech-translate/raw/master/pics//binder-pic/building_a_binder_02.png) + + +FibonacciCommon - Define AIDL Interface and Custom Types +#FibonacciCommon - 定义AIDL接口和自定义类型 + + +* 我们开始创建一个新的Android(库)工程,这是服务端和客户端可以共享的API文件(作为参数和返回值的一个AIDL接口和自定义类型) + + 。工程名:FibonacciCommon + 。构建目标:Android 2.2+(API 8+) + 。包名:com.marakana.android.fibonaccicommon + 。最低SDK版本:8+ + 。不需要指定应用名或activity + + +* 要转换成库工程,我们需要访问properties → Android → Library,然后,选中Library +* 我们也可以手动地添加 android.library=true 到 FibonacciCommon/default.properties 中,然后刷新该工程 +* 因为库工程从来不会进入到实际的应用(APKS),所以我们可以简化清单文件: + +*FibonacciCommon/AndroidManifest.xml* + +```xml + + + + + +``` + +* 并且我们可以移除 FibonacciCommon/res/ 目录(例如:rm -fr FibonacciCommon/res/* )下的任何文件 + +* 我们现在已经准备好创建AIDL接口了 + +*FibonacciCommon/src/com/marakana/android/fibonaccicommon/IFibonacciService.aidl* + +```java + +package com.marakana.android.fibonaccicommon; + +import com.marakana.android.fibonaccicommon.FibonacciRequest; +import com.marakana.android.fibonaccicommon.FibonacciResponse; + +interface IFibonacciService { + + long fibJR(in long n); + long fibJI(in long n); + long fibNR(in long n); + long fibNI(in long n); + FibonacciResponse fib(in FibonacciRequest request); + +} + +``` + + +* 很明显,我们的接口依赖两个自定义的Java类型,但是定义在它们自己的.aidl文件中 + +*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciRequest.java* + +```java + +package com.marakana.android.fibonaccicommon; + +import android.os.Parcel; import android.os.Parcelable; + +public class FibonacciRequest implements Parcelable { + + public static enum Type { + RECURSIVE_JAVA, ITERATIVE_JAVA, RECURSIVE_NATIVE, ITERATIVE_NATIVE + } + + private final long n; + private final Type type; + public FibonacciRequest(long n, Type type) { + + this.n = n; + + if (type == null){ + throw new NullPointerException("Type must not be null"); + } + this.type = type; + } + + public long getN() { + return n; + } + + public Type getType() { + return type; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + + parcel.writeLong(this.n); + parcel.writeInt(this.type.ordinal()); + + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + public FibonacciRequest createFromParcel(Parcel in) { + + long n = in.readLong(); + Type type = Type.values()[in.readInt()]; + return new FibonacciRequest(n, type); + + } + + public FibonacciRequest[] newArray(int size) { + return new FibonacciRequest[size]; + } + }; +} + + +``` + +*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciRequest.aidl* + +```java + +package com.marakana.android.fibonaccicommon; +parcelable FibonacciRequest; + +``` + +*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciResponse.java* + +```java + +package com.marakana.android.fibonaccicommon; + +import android.os.Parcel; import android.os.Parcelable; + +public class FibonacciResponse implements Parcelable { + + private final long result; + private final long timeInMillis; + public FibonacciResponse(long result, long timeInMillis) { + + this.result = result; + this.timeInMillis = timeInMillis; + + } + + public long getResult() { + return result; + } + + public long getTimeInMillis() { + return timeInMillis; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + + parcel.writeLong(this.result); parcel.writeLong(this.timeInMillis); + + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + public FibonacciResponse createFromParcel(Parcel in) { + + return new FibonacciResponse(in.readLong(), in.readLong()); + + } + + public FibonacciResponse[] newArray(int size) { + return new FibonacciResponse[size]; + } + }; +} + +``` + +*FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciResponse.aidl* + +```java + +package com.marakana.android.fibonaccicommon; +parcelable FibonacciResponse; + +``` + + +* 最后,我们可以准备查看自动生成的Java接口 + +*FibonacciCommon/gen/com/marakana/android/fibonaccicommon/IFibonacciService.java* + +```java + +package com.marakana.android.fibonaccicommon; +public interface IFibonacciService extends android.os.IInterface { + + public static abstract class Stub extends android.os.Binder + implements com.marakana.android.fibonacci.IFibonacciService { + + ... + + public static com.marakana.android.fibonacci.IFibonacciService asInterface(android.os.IBinder obj) { + ... + } + + public android.os.IBinder asBinder() { + return this; + } + ... + } + + public long fibJR(long n) throws android.os.RemoteException; + public long fibJI(long n) throws android.os.RemoteException; + public long fibNR(long n) throws android.os.RemoteException; + public long fibNI(long n) throws android.os.RemoteException; + public com.marakana.android.fibonaccicommon.FibonacciResponse fib( + com.marakana.android.fibonaccicommon.FibonacciRequest request) throws android.os.RemoteException; + +} + +``` + + +#FibonacciService - 实现AIDL接口,并暴露给我们的客户端 + +* 我们开始创建一个新的Android工程,工程中有AIDL服务的实现和访问服务实现的机制(例如:绑定) + + 。工程名:FibonacciService + 。编译目标:Android 2.2+(API 8+) + 。包名:com.marakana.android.fibonacciservice + 。应用名称:Fibonacci Service + 。最小SDK: 8+ + 。不需要指定一个Activity + +* 为了能够访问common APIS,我们需要将工程跟FibonacciCommon链接: +* 工程名称* → Android → Library → Add... → FibonacciCommon* + + 。最终, FibonacciService/default.properties 有了 android.library.reference.1=../FibonacciCommon ,并且 FibonacciService/.project 也链接到了 FibonacciCommon + +* 我们的服务端将会使用实现了斐波那契算法的 com.marakana.android.fibonaccinative.FibLib + +* 我们将FibonacciNative工程从Java类(和jni/implementation)中拷贝出(或移出) + + 。不要忘记在*FibonacciService/*下运行*ndk-build*去生成需要的本地库 + +* 我们现在已经准备好实现AIDL接口,通过扩展自动生成的*com.marakana.android.fibonaccicommon.IFibonacciService.Stub*(继承于*android.os.Binder*) + +* FibonacciService/src/com/marakana/android/fibonacciservice/IFibonacciServiceImpl.java* + +```java + +package com.marakana.android.fibonacciservice; + +import android.os.SystemClock; +import android.util.Log; +import com.marakana.android.fibonaccicommon.FibonacciRequest; +import com.marakana.android.fibonaccicommon.FibonacciResponse; +import com.marakana.android.fibonaccicommon.IFibonacciService; +import com.marakana.android.fibonaccinative.FibLib; + +public class IFibonacciServiceImpl extends IFibonacciService.Stub { + + private static final String TAG = "IFibonacciServiceImpl"; + + public long fibJI(long n) { + Log.d(TAG, String.format("fibJI(%d)", n)); return FibLib.fibJI(n); + } + + public long fibJR(long n) { + Log.d(TAG, String.format("fibJR(%d)", n)); return FibLib.fibJR(n); + } + + public long fibNI(long n) { + Log.d(TAG, String.format("fibNI(%d)", n)); return FibLib.fibNI(n); + } + + public long fibNR(long n) { + Log.d(TAG, String.format("fibNR(%d)", n)); return FibLib.fibNR(n); + } + + public FibonacciResponse fib(FibonacciRequest request) { + + Log.d(TAG,String.format("fib(%d, %s)", request.getN(), request.getType())); + long timeInMillis = SystemClock.uptimeMillis(); + long result; + + switch (request.getType()) { + case ITERATIVE_JAVA: + result = this.fibJI(request.getN()); break; + case RECURSIVE_JAVA: + result = this.fibJR(request.getN()); break; + case ITERATIVE_NATIVE: + result = this.fibNI(request.getN()); break; + case RECURSIVE_NATIVE: + result = this.fibNR(request.getN()); break; + default: return null; + } + + timeInMillis = SystemClock.uptimeMillis() - timeInMillis; + + return new FibonacciResponse(result, timeInMillis); + } +} + +``` + +#暴露我们定义的AIDL服务实现给客户端 + +* 为了让客户端(调用者)使用我们的服务,首先它们需要绑定服务 +* 但是,为了让它们绑定服务,首先我们需要暴露服务,通过*android.app.Service's +onBind(Intent)*实现 + +*FibonacciService/src/com/marakana/android/fibonacciservice/FibonacciService.java* + +```java + +package com.marakana.android.fibonacciservice; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; + +public class FibonacciService extends Service { //1 + + private static final String TAG = "FibonacciService"; + private IFibonacciServiceImpl service; //2 + + @Override + public void onCreate() { + super.onCreate(); + this.service = new IFibonacciServiceImpl(); //3 + Log.d(TAG, "onCreate()'ed"); //5 + } + + @Override + public IBinder onBind(Intent intent) { + Log.d(TAG, "onBind()'ed"); //5 + return this.service; //4 + } + + @Override + public boolean onUnbind(Intent intent) { + Log.d(TAG, "onUnbind()'ed"); //5 + return super.onUnbind(intent); + } + + @Override + public void onDestroy() { + Log.d(TAG, "onDestroy()'ed"); + this.service = null; super.onDestroy(); + } +} + +``` + + +1、我们通过继承*android.app.Service*创建另外一个“service”。*FibonacciService* 对象的目的是提供访问我们的基于Binder的*IFibonacciServiceImpl* 对象 + +2、我们在这里简单的申明了一个本地的*IFibonacciServiceImpl* 引用,该引用是一个单例(例如:所有的客户端将会共享一个实例)。因为,我们的*IFibonacciServiceImpl* 不需要初始化的位置,所以我们可以在这里初始化,但是,我们选择推迟到*onCreate()* 方法中 + +3、现在,我们初始化了提供给客户端(在onBind(Intent)方法中)的*IFibonacciServiceImpl* 。如果,我们的*IFibonacciServiceImpl* 需要访问*Context*,我们可以传递一个引用给它(例如:*android.app.Service* ,实现了*android.content.Context* )。很多基于Binder的服务端使用*context*去访问其它平台的方法 + +4、我们在这里提供客户端访问*IFibonacciServiceImpl* 对象的接口。我们选择,只拥有一个*IFibonacciServiceImpl* 实例对象(因此所有的客户端都可以共享它),但是,我们也可以给每一个客户端提供一个*IFibonacciServiceImpl* 实例对象 + +5、我们添加一些日志,这样跟踪服务的生命周期就会简单 + + +* 最后,我们在*AndroidManifest.xml* 中注册*FibonacciService* ,所以,客户端可以找到它 + +* FibonacciService/AndroidManifest.xml* + +```xml + + + + + + + + + + + + + +``` + + +1、名称可以随便定义,但是,我们通常使用派生的AIDL接口的全名 + + +#FibonacciClient - 使用基于Binder的AIDL服务 + +* 我们开始创建一个新的Android工程,这个工程是我们之前实现的AIDL Service的客户端 + + 。工程名:FibonacciClient + 。构建的目标:Android 2.2+(API 8+) + 。包名:com.marakana.android.fibonacciclient + 。应用名:Fibonacci Client + 。创建的Activity:FibonacciActivity + + * Activity中的大部分代码来自于 FibonacciNative + + 。最小SDK版本:8+ + +* 我们需要将工程链接到*FibonacciCommon*上,以便能够访问公有的APIs: +* project properties → Android → Library → Add... → FibonacciCommon + + 。结果,*FibonacciClient/default.properties* 中有*android.library.reference.1=../FibonacciCommon* 和*FibonacciClient/.classpath* ,并且*FibonacciClient/.project* 也链接到了*FibonacciCommon* + 。作为一种替代,我们可以避免首先创建*FibonacciCommon* + 。FibonacciService 和 FibonacciClient 都需要拷贝:IFibonacciService.aidl、 FibonacciRequest.aidl、 FibonacciResponse.aidl、 FibonacciResult.java、 FibonacciResponse.java++ + 。但是,我们不喜欢重复的代码(即使在运行时二进制文件会重复) + + +* 我们的客户端将会使用 FibonacciNative 应用中的字符串和布局资源 + +* FibonacciClient/res/values/strings.xml* + +```xml + + + + Get Your Fibonacci Here! + Fibonacci Client + Enter N + Numbers only! + Get Fib Result + Calculating... + Failed to get Fibonacci result + fibJR + fibJI + fibNR + fibNI + + +``` + +* FibonacciClient/res/layout/main.xml* + +```xml + + + + + + + + + + + + + + + + +