当前位置: 首页 > 新闻 > 信息荟萃
编号:263
Android插件化开发指南最新版.pdf
http://www.100md.com 2019年12月20日
第1页
第8页
第20页
第28页
第36页
第140页

    参见附件(7335KB,679页)。

     Android插件化开发指南为大家提供,由作者包建强编写完成,全书分为三大部分,共22章,为开发用户深入讲解了移动开发的基础知识等内容。

    Android插件化开发指南介绍

    本书不仅详细介绍Android插件化技术如何实现,而且包含大量Android系统的底层知识,有助于App开发人员深入理解Android系统,从而写出更健壮的代码。

    Android插件化技术不仅适用于快速修复bug,还可以快速上线新功能,从而在时间上和竞争对手抢占用户。本书梳理了插件化技术千头万绪的思想和实现方案,并给出了应用场景和源代码。

    Android插件化开发指南作者简介

    包建强

    毕业于复旦大学数学系。先后在多家互联网公司担任无线部门技术总监,现在从事区块链技术领域的研究,在Android、iOS、ReactNative等多门无线技术中跋涉过,在App的项目管理上也有多年的实践经验。

    Android插件化开发指南章节

    第一部分 预备知识

    第1章 插件化技术的昨天、今天与明天

    第2章 Android底层知识

    第3章 反射

    第4章 代理模式

    第5章 对startActivity方法进行Hook

    第二部分 解决方案

    第6章 插件化技术基础知识

    第7章 资源初探

    第8章 最简单的插件化解决方案

    第9章 Activity的插件化解决方案

    第10章 Service的插件化解决方案

    第11章 BroadcastReceiver的插件化解决方案

    第13章 基于静态代理的插件化解决方案:that框架

    第14章 that框架对Service和BroadcastReceiver的支持

    第15章 再谈资源

    第16章 基于Fragment的插件化框架

    Android插件化开发指南截图

    移动开发

    Android插件化开发指南

    包建强 著

    ISBN:978-7-111-60336-8

    本书纸版由机械工业出版社于2018年出版,电子版

    由华章分社(北京华章图文信息有限公司,北京奥

    维博世图书发行有限公司)全球范围内制作与发

    行。

    版权所有,侵权必究

    客服热线:+ 86-10-68995265

    客服信箱:service@bbbvip.com

    官方网址:www.hzmedia.com.cn

    新浪微博 @华章数媒

    微信公众号 华章电子书(微信号:hzebook)目录 序一

    序二

    序三

    前言

    第一部分 预备知识

    第1章 插件化技术的昨天、今天与明天

    1.1 插件化技术是什么

    1.2 为什么需要插件化

    1.3 插件化技术的历史

    1.4 插件化技术的用途到底是什么

    1.5 更好的替代品:React Native

    1.6 只有中国这么玩吗

    1.7 四大组件都需要插件化技术吗

    1.8 双开和虚拟机

    1.9 从原生页面到HTML 5的过渡

    1.10 本章小结

    第2章 Android底层知识

    2.1 概述

    2.2 Binder原理

    2.3 AIDL原理

    2.4 AMS

    2.5 Activity工作原理

    2.6 App内部的页面跳转

    2.7 Context家族史2.8 Service工作原理

    2.9 BroadcastReceiver工作原理

    2.10 ContentProvider工作原理

    2.11 PMS及App安装过程

    2.12 ClassLoader家族史

    2.13 双亲委托

    2.14 MultiDex

    2.15 实现一个音乐播放器App

    2.16 本章小结

    第3章 反射

    3.1 基本反射技术

    3.2 jOOR

    3.3 对基本反射语法的封装

    3.4 对反射的进一步封装

    3.5 本章小结

    第4章 代理模式

    4.1 概述

    4.2 静态代理和动态代理

    4.3 对AMN的Hook

    4.4 对PMS的Hook

    4.5 本章小结

    第5章 对startActivity方法进行Hook

    5.1 startActivity方法的两种形式

    5.2 对Activity的startActivity方法进行Hook

    5.3 对Context的startActivity方法进行Hook

    5.4 启动没有在AndroidManifest中声明的Activity

    5.5 本章小结

    第二部分 解决方案

    第6章 插件化技术基础知识

    6.1 加载外部的dex

    6.2 面向接口编程

    6.3 插件的瘦身

    6.4 对插件进行代码调试

    6.5 Application的插件化解决方案

    6.6 本章小结

    第7章 资源初探

    7.1 资源加载机制

    7.2 资源的插件化解决方案

    7.3 换肤

    7.4 殊途同归:另一种换肤方式

    7.5 本章小结

    第8章 最简单的插件化解决方案

    8.1 在AndroidManifest中声明插件中的组件

    8.2 宿主App加载插件中的类

    8.3 启动插件Service

    8.4 加载插件中的资源

    8.5 本章小结

    第9章 Activity的插件化解决方案

    9.1 启动没有在AndroidManifest中声明的插件

    Activity

    9.2 基于动态替换的Activity插件化解决方案9.3 加载插件中类的方案2:合并多个dex

    9.4 为Activity解决资源问题

    9.5 对LaunchMode的支持

    9.6 加载插件中类的方案3:修改App原生的

    ClassLoader

    9.7 本章小结

    第10章 Service的插件化解决方案

    10.1 Android界的荀彧和荀攸:Service和

    Activity

    10.2 预先占位

    10.3 startService的解决方案

    10.4 bindService的解决方案

    10.5 本章小结

    第11章 BroadcastReceiver的插件化解决方案

    11.1 Receiver概述

    11.2 动态广播的插件化解决方案

    11.3 静态广播的插件化解决方案

    11.4 静态广播的插件化终极解决方案

    11.5 本章小结

    第12章 ContentProvider的插件化解决方案

    12.1 ContentProvider基本概念

    12.2 一个简单的ContentProvider例子

    12.3 ContentProvider插件化

    12.4 执行这段Hook代码的时机

    12.5 ContentProvider的转发机制

    12.6 本章小结第13章 基于静态代理的插件化解决方案:that

    框架

    13.1 静态代理的思想

    13.2 一个最简单的静态代理的例子

    13.3 插件内部的页面跳转

    13.4 从“肉体”上消灭that关键字

    13.5 插件向外跳转

    13.6 面向接口编程在静态代理中的应用

    13.7 对LaunchMode的支持

    13.8 本章小结

    第14章 that框架对Service和BroadcastReceiver的

    支持

    14.1 静态代理的思想在Service的应用

    14.2 对BindService的支持

    14.3 Service的预先占位思想

    14.4 Service插件化的终极解决方案:动静结

    合

    14.5 静态代理的思想在BroadcastReceiver的

    应用

    14.6 本章小结

    第15章 再谈资源

    15.1 Android App的打包流程

    15.2 修改AAPT

    15.3 public.xml固定资源id值

    15.4 插件使用宿主的资源

    15.5 本章小结第16章 基于Fragment的插件化框架

    16.1 AndroidDynamicLoader概述

    16.2 最简单的Fragment插件化例子

    16.3 插件内部的Fragment跳转

    16.4 从插件的Fragment跳转到插件外部的

    Fragment

    16.5 本章小结

    第三部分 相关技术

    第17章 降级

    17.1 从Activity到HTML 5

    17.2 从HTML 5到Activity

    17.3 对返回键的支持

    17.4 本章小结

    第18章 插件的混淆

    18.1 插件的基本混淆

    18.2 方案1:不混淆公共库MyPluginLibrary

    18.3 方案2:混淆公共库MyPluginLibrary

    18.4 本章小结

    第19章 增量更新

    19.1 如何使用增量更新

    19.2 制作插件的增量包

    19.3 App下载增量包并解压到本地

    19.4 App合并增量包

    19.5 本章小结

    第20章 so的插件化解决方案

    20.1 编写一个最简单的so20.2 使用so

    20.3 so的加载原理

    20.4 基于System.load的插件化解决方案

    20.5 基于System.loadLibrary的插件化解决方

    案

    20.6 本章小结

    第21章 对App的打包流程进行Hook

    21.1 自定义Gradle插件

    21.2 修改resources.arsc

    21.3 本章小结

    第22章 插件化技术总结

    22.1 插件的工程化

    22.2 插件中类的加载

    22.3 哪些地方可以Hook

    22.4 Activity插件化的解决方案

    22.5 资源的解决方案

    22.6 Fragment是哪门哪派

    22.7 Service、ContentProvider、BroadcastReceiver插件化的通用解决方案

    22.8 特定于Service的插件化解决方案

    22.9 特定于BroadcastReceiver的插件化解决

    方案

    22.10 特定于ContentProvider的插件化解决方

    案

    22.11 本章小结

    附录附录A 常用工具

    附录B 本书代码索引序一

    当接到包老师邀请写序时,我真是受宠若惊。

    大家都知道,作为一个程序员,写代码拿手那是自

    然的事情,文字工作实在不是我的强项,但是能给

    包老师的书写序,实在是荣幸之至,更何况盛情难

    却呢。

    我与包老师相识是在2015年的一个关于

    DroidPlugin的分享会上,那会儿我还在360公司做

    手机助手。2014年年底到2015年年初的时候,在公

    司比较空闲,所以写了一个插件化框架叫

    DroidPlugin,并在2015年7月份以公司的名义在

    GitHub上开源了出来,承蒙大家抬爱,在极短的时

    间内便收获了数千颗星。因为在项目的介绍中我写

    上了“免修改、免重打包、免安装运行”的宣传语,所以也被一些人冠以“360黑科技”,在知乎上甚至

    有人认为这是360的什么阴谋。不管是不是什么阴

    谋,但这个项目让我认识了很多行业中的大牛(包

    老师就是其中一位),确实是我意料之外的事情。

    后来跟包老师、任玉刚老师几位经常结成“饭醉团

    伙”,自然也少不了讨论插件化技术。既然与包老

    师是因为DroidPlugin开源项目相识,而包老师这本

    书的内容涉及的很多技术也跟其相关,所以我还是

    主要介绍一下DroidPlugin项目中的相关技术。如你所见,那会儿正是插件化技术大热的时

    候,各大公司也相继开源了自己的插件化框架,但

    是总结来看,所涉及的技术原理也大同小异。但是

    DroidPlugin在其中的确算是比较“奇葩”的一个,因

    为它实际不止是一个插件化框架,更多的算是一个

    用户态虚拟机,后来大多数的双开软件也都参考了

    它的源码或者原理。

    要在Android上实现免安装、免修改运行一个

    App,并不是一件容易的事情。因为Android系统设

    计和权限的限制,我们需要做很多的工作……这跟

    Docker不一样,虽然都是Linux系统,但在Android

    上我们不能要求root权限,而Docker则没有这些限

    制,因此Linux内核提供的namespace、cgroup、chroot等能力也自然可以应用在Docker中。同时因

    为国内各大厂商的定制系统,我们也做了大量的适

    配工作。虽然现在看起来这个框架还有不少缺陷,但在当时确实算是比较先进的一个框架。

    DroidPlugin使用了一些比较hack的技巧,但是

    总结起来也就是一句话“利用hook技术实现欺上瞒

    下,从而达到免安装运行的目的”。因为Android系

    统出于安全考虑,系统服务与App进程采用分进程

    设计,它们之间通讯使用binder技术,系统服务实

    际上是不知道App进程中运行的具体代码,这为我

    们实现“欺上瞒下”提供了可能。所谓“欺上瞒下”中的“欺上”是指我们可以通过某种技术手段,拦截所

    有插件发向系统服务的通信,让系统不知道插件在

    我们的宿主App中运行;“瞒下”是指通过拦截并模

    拟系统服务发向插件的通信,让插件“以为”自己已

    经被安装。这样我们就可以模拟一个环境,让插件

    运行在宿主的模拟进程中。

    要做到欺上瞒下,hook技术不可缺。hook这个

    词是我从Window安全技术中借用而来的,这实际

    上是一种函数拦截技术。在某个函数调用流程中插

    入我们自己的函数,实现对目标函数的参数、返回

    值的修改。比如某个函数调用流程为:a调用b、b

    调用c,那么我们可以动态拦截对b函数的调用,插

    入我们自己的函数h,修改后的调用流程为:a调用

    h,h调用b,b调用c,因为h是我们新插入的函数,由我们自己编写逻辑,那么在h函数中调用b的时

    候,我们就可以修改其参数、返回值,甚至可以中

    断调用流程,不调用b函数而伪造一个返回结果给

    a。在此过程中,a是不知道它调用的b函数已经被

    我们修改了。这就达到了我们“欺上瞒下”的目的。

    hook技术的思想非常类似于设计模式中的代理

    模式和Java Spring框架中的AOP(Aspect Oriented

    Programming,面向切面编程)。尽管它们的实现

    原理完全不同,但目的却差不多。DroidPlugin中的

    Hook技术实际上是使用了Java中的动态代理技术,它只是在一些关键点通过动态代理生成的对象替换

    掉系统原来的对象,从而完成了对系统通信的

    hook。

    这其中最关键的是对AMS(Activity Manage

    Service)和PMS(Package Manage Service)以及

    Handler的hook。AMS负责管理Android中Activity、Service、Content Provider、Broadcast四大组件的生

    命周期以及各种调度工作,我们hook它可以实现对

    插件四大组件的管理及调度;PMS负责管理系统中

    安装的所有App,我们hook它是为了让插件以为自

    己已经被安装;Handler是系统向插件传递消息的一

    个桥梁,我们hook它则是为了把系统发向宿主的消

    息转发给插件。至于在DroidPlugin中看到的对其他

    系统服务的hook,在大多数情况下都是为了“欺

    上”——让系统感知不到插件的运行。DroidPlugin

    还实现了一个简单的AMS服务、一个PMS服务,将

    hook后的AMS和PMS系统调用转发到我们自己的

    AMS和PMS服务中去,由DroidPlugin自己管理。

    除此之外,DroidPlugin的另外一个特色是“占

    位”操作。众所周知,在Android四大组件中除了

    BroadcastReceiver之外,其他如Activity三大组件都

    需要在AndroidManifest.xml中注册,这是静态的,是Android框架要求我们预先写死的,我们没有办

    法动态地向系统注册一个Activity或者是Service。所以插件中的Activity、Service、Content Provider则是

    不可能向系统真正注册。所以我们使用了“占位”的

    技术,也就是说先在宿主中注册很多的“坑位”,比

    如对于Activity来说,就是Activity1、Activity2、Activity3等。在我们需要启动某插件Activity的时

    候,可以通过hook技术,将其替换为某个坑位,如

    Activity1,让系统服务去启动坑位Activity;而在真

    正的系统服务AMS回调插件进程要求插件进程去启

    动坑位Activity的时候,我们再换回插件的

    Activity,这样我们就实现了插件Activity的免注册

    运行。当然,因为Activity的Launch Mode等各种参

    数问题,我们还需要做很多的细节工作,才能完

    美。

    为了某种完美,DroidPlugin中做了大量的适配

    工作,这让其初看起来复杂又臃肿,但是当了解到

    其中的原理和关键代码后,你会发现如果仅仅是满

    足“插件化”需求的话,那么其中很多适配并不必

    需。从现在来看,DroidPlugin实际上算是一个用户

    态虚拟机的雏形,从插件化的角度来说则有

    些“重”了。但是它对于大家深入研究Android技术本

    身,则或多或少会有些帮助。

    现在市面上也有各种各样的开源插件化框架,其中很多都已经在各大公司自己的产品中长期稳定

    使用,满足了各种现实的需求,它们的稳定性、可用性都还是不错的。包老师在本书中也对其中很多

    插件进行了介绍剖析。

    如果我们不满足于业务研发,希望可以了解一

    些Android底层知识,研读这些开源框架的源码则

    大有裨益。当然,包老师的这本书是国内第一本介

    绍插件化技术的书籍,作为我们学习插件化技术的

    入门书籍,则相当合适。

    张勇,2018年6月于北京序二

    很荣幸能为本书写下三言两语。

    我和插件化技术之间有着难解的情缘,到目前

    为止我已经工作好几年了,如果简化一下,就是下

    面这个样子:

    1)开源dynamic-load-apk插件化框架(业余时

    间)。

    2)开发百度手机卫士出版《Android开发艺

    术探索》(百度)。

    3)开源VirtualAPK插件化框架(滴滴出

    行)。

    这么一看,自从我工作以来,有一半时间都在

    从事插件化相关的开发工作。我喜欢Android,也

    喜欢插件化。最开始我对插件化只是兴趣使然,在

    工作之余我喜欢做一些研究,所以有了dynamic-

    load-apk。到后面我加入滴滴出行则是使命使然,在我的内心深处,我觉得dynamic-load-apk不够完

    美,我想开发一款完美的插件化框架,于是有了后

    面的VirtualAPK。回想起插件化的发展,也就仿佛看到了一路走来的自己。

    2014年3月30号,我在CSDN上发布了一篇文章

    《Android apk动态加载机制的研究》。这篇文章现

    在看起来很傻,技术也很落后,但是很多人都无法

    感受当时的情形。在2014年年初,别说插件化知识

    了,就连高质量的Android技术文章都比较匮乏。

    说起高质量的Android技术文章,大家可能会

    想到:可以看《第一行代码Android》和《Android

    开发艺术探索》呀!但是很遗憾,那个时候这两本

    书都还没出版,不止是这两本书,很多大家所熟知

    的书都没有出版。当时Android技术圈沉醉于下拉

    刷新、侧滑菜单等这种炫酷特效,对于AIDL和

    View原理不曾研究过,你要问插件化?90%的

    Android工程师都不知道这是个什么东西。除了技

    术文章和书籍比较匮乏以外,开源也比较匮乏。在

    2014年,插件化技术只是一个概念,虽然当时阿里

    已经有了Atlas,但是并没有开源,所以在这种情形

    下,我发的那篇文章就显得很专业了,当时引起了

    技术圈的广泛关注,获得了7万多的阅读量。

    在2014年下半年,我和田啸、宋思宇等同学发

    起了dynamic-load-apk这个开源项目,现在大家都知

    道了,dynamic-load-apk在插件化历史中有着浓厚的

    一笔。dynamic-load-apk支持动态加载代码和资源,资源访问可以直接通过R来进行,在四大组件方面

    支持Activity和Service。虽然说dynamic-load-apk谈

    不上多完善,但是业内却有不少公司基于dynamic-

    load-apk进行二次开发来定制自己的插件化框架,从这个角度来说dynamic-load-apk是很成功的一款开

    源框架。

    到2016年,插件化框架才真正迎来了大繁荣时

    代,可谓百家争鸣。不管是Atlas、Small、DroidPlugin还是携程的DynamicAPK,都极大地促

    进了Android插件化框架的发展。我也是在2016年

    年初离开百度,来到了现在的滴滴出行。如果说在

    百度的工作是做应用开发的话,那么在滴滴出行的

    工作就完全是热修复和插件化开发了。经过长时间

    的开发和验证,滴滴出行在2017年6月30日开源了

    一个更为完善的插件化框架VirtualAPK,而我则在

    这个框架的开源中发挥了至关重要的作用。

    如果给我这几年的职业生涯写一个总结,那就

    是:两款Android插件化框架+一个App+一本书,而

    我还将在Android的道路上继续耕耘。

    任玉刚,2018年6月于北京序三

    听说包猪猪的第二本书要出版了,很为他高

    兴,作为一个旁观者,眼见着本书由一个想法萌芽

    逐渐充实,颇有感触。我脑海里就像过电影一般,浮现这几个月中包猪猪的种种状态:为解决一个

    bug而连续工作十几个小时的苦闷,第二天一早起

    来灵感触发迎刃而解的喜悦,一边照顾父母一边因

    书的进度被某一难题阻滞而焦灼……身边的人会跟

    他说:“事情这么多,歇歇再写吧,别让自己太辛

    苦。”可是一个想快点跟业界分享自己想法和成果

    的程序员又怎会因琐事耽搁?曾是微软MVP的他,入行十几年,仍然秉承着刚入行时的激情与热忱,吭哧吭哧地调bug到凌晨,这本书或许是这个“内心

    有团火”的家伙希望送给读者最好的礼物——智慧

    的分享与教学相长的领悟。

    我是学中文和法律的,作为圈外人在本书前作

    序,多少有班门弄斧之嫌。如论代码,各位读者在

    自己的领域各有所长。不过我这种对技术一窍不通

    的人,竟在看一本技术书时捧腹大笑,足见本书的

    吸引力。比如说,书中调侃张勇和任玉刚是插件化

    领域的男乔峰女慕容,以至于我一直想亲眼见一下

    这两个人。比如说他在前言中拿娃娃开涮,连着感

    谢了霹雳娇娃、赵越和Dinosaur,殊不知那却是一个人,可能是为了看上去人多一些,以壮声势。

    程序员的世界我不太懂,尤其是包猪猪,他经

    常做出一些令人啼笑皆非的事情。比如说,情人节

    他第一次给我送花,却阴差阳错地寄来了两束。晚

    上的时候他跟我说,两束花别浪费了,另一束花就

    快递给邓凡平吧——据说那也是Android行业内的

    一位大神,他因为伺候老婆坐月子而忘记准备情人

    节礼物了。于是,作为情人节只收别人礼物的我,在这一天,第一次给别人送礼物。

    他做饭很好吃,有很多招牌菜。用他的话讲,炒菜是设计模式中的装饰器模式。比如说西红柿炒

    鸡蛋,放锅里炒了几分钟后,加点糖,就是味道甜

    甜的西红柿炒鸡蛋,再加些盐,就是酸甜可口的西

    红柿炒鸡蛋,也许还会再加些其他调料,但这道菜

    永远都是西红柿炒鸡蛋,只是味道不同罢了。技术

    做到这一步已经接近于完美,但他后面的奇葩行

    为,却颠覆了我对他的认知。

    他研究做鱼,第一天没做好,第二天再买条鱼

    继续做,直到他认为完美。把钻研计算机技术的执

    着用于烹饪,结果就是我一连吃了5天鱼汤,上

    火,满嘴都是泡。

    第一次见包猪猪是在酒吧,一边听着不知道是哪里的古老而嘈杂的乐队嘶吼,另一边是他给我讲

    关于五个海盗分赃的小故事。隐约记得故事是这样

    的:“5个海盗抢到100个金币,他们决定依次由A,B,C,D,E五个海盗来分,他们订立了如下规

    则:当由A提分配方案时,剩下的海盗表决,如果

    B,C,D,E四人中有一半以上反对就把A扔下

    海,再由B分……如是这般,那么A海盗如何分,才能既保住性命又能获得更多的金币。”这是个有

    意思的小故事,轻松而又暗藏思维逻辑,我们可能

    有很多种解决方案,但是最优方案最后一定是“博

    弈与制衡”的平衡。此前,我一直以为程序员的世

    界充斥着代码,那是一套拥有独立计算机语言的系

    统,难以接近跟理解。可是包猪猪有种能力将难以

    理解或者比较复杂的事情用一种有趣的方式表达出

    来,诙谐有趣、通俗易懂又能在嘻嘻哈哈的氛围中

    有所感悟。

    本书讲插件化,从插件化的历史讲起,说了不

    少这行的人跟事,还有八卦。接下来由基础知识开

    始讲起,后来又介绍了插件化解决方案及周边技

    术。文字所限,本书内容结构不赘述了,各位可以

    凭目录了解。代码方面我虽然读不懂,不过并不影

    响看书的心情,这是本书颇为神奇的地方。没有高

    深莫测的理论,没有艰深难懂的词汇,就像包猪猪

    站在我面前娓娓道来,举一些他觉得有意思的例

    子,让你觉得调皮又生动。对于对插件化不熟悉的读者,可能本书提及的有些词汇是新的,不太容易

    理解,感谢本书的编辑在成书过程中从旁指引,因

    而本书在前言部分增加了名词解释,并在各个章节

    多加了一些描述性的文字。

    关于本书前言所提及未展开描述的部分,其实

    作者在写作前已经预留了位置,但是在成书前两天

    犹豫再三还是有所删减,因为总觉得讲得不透、不

    彻底是不好意思呈现给读者看的。因此,我跟很多

    读者一样很期待包猪猪过段时间能将那些本书没说

    透的东西再写本书好好讲一讲。比如说Small,他

    半夜说梦话时经常念叨这个词。

    谨以此书献给奋斗在一线的程序员们,作为家

    属深刻了解程序员的辛酸,希望本书对读者能够有

    所启发,运用到工作中可以提高效率,多一些休息

    时间陪伴家人、朋友,少一些熬夜加班、拼命赶

    工。

    郭曼云,2018年6月于北京前言

    这是一本什么书

    如果只把本书当作纯粹介绍Android插件化技

    术的书那就错了。本书在研究Android插件化之

    余,还详细介绍了Android系统的底层知识,包括

    Binder和AIDL的原理、四大组件的原理、App的安

    装和启动流程、Context和ClassLoader的家族史。没

    有罗列大量的Android系统中的源码,而是以一张

    张UML图把这些知识串起来。

    本书详细介绍了Android中的资源机制,包括

    aapt命令的原理、resource文件的组成以及

    public.xml的使用方式,顺带还提及了如何自定义

    一个Gradle插件化。

    此外,本书还介绍了so的加载原理,尤其是动

    态加载so的技术,可以帮助App进行瘦身;探讨了

    HTML5降级技术,可以实现任何一个原生页面和

    HTML5页面的互换;介绍了反射技术,以及jOOR

    这个有趣的开源框架;介绍了Android中的动态代

    理技术Proxy.newProxyInstance方法。

    如果读者能坚持把这本书从头到尾读完,那么不仅掌握了插件化技术,而且也把上述所有这些知

    识点全都系统地学习了一遍。也许Android插件化

    会随着Google的限制而有所变化甚至消亡,但我在

    本书中介绍的其他知识,仍然是大有用武之处的。

    如何面对Android P的限制

    写作这本书的时候,Google推出了Android P

    preview的操作系统,会限制对@hide api的反射调

    用。目前会通过log发出警告,用户代码仍然能够获

    取到正确的Method或Field,在后续版本中获取到的

    Method或Field极有可能为空。

    但是道高一尺,魔高一丈。Google对这次限

    制,很快就被技术极客们绕过去了 [1]

    ,有两种解

    决方法:

    1)把通过反射调用的系统内部类改为直接调

    用。具体操作办法是,在Android项目中新建一个

    库,把要反射的类的方法和字段复制一份到这个库

    中,App对这个库的引用关系设置为provided。那么

    我们就可以在App中直接调用这个类和方法,同

    时,在编译的时候,又不会把这些类包含到apk

    中。

    其实早在2015年,hoxkx就在他的插件化框架中实现了这种技术 [2]。但是这种解决方案,仅限

    于Android系统中标记为public的方法和字段,对于

    protected和private就无能为力了。比如

    AssetsManager的addAssetPath方法,ActivityThread

    的currentActivityThread方法。

    2)类的每个方法和字段都有一个标记,表明

    它是不是hide类型的。我们只要在jni层,把这个标

    记改为不是hide的,就可以绕过检查了。

    然而,魔高一丈,道高一丈二。Google在

    Android P的正式版中势必会推出更严厉的限制方

    案,到时候,又会有新的解决方案面世,让我们拭

    目以待。

    其实,开发者是无意和Google进行技术对抗

    的,这是毫无意义的。泛滥成灾的修改导致了App

    大量的崩溃,Google实在看不下去了,所以才搞出

    这套限制方案;另一方面,插件化技术是刚需,尤

    其在中国的互联网行业,App崩溃会直接影响使

    用,很可能导致经济损失,所以开发者才会不惜一

    切代价走插件化这条路。

    再回到限制方案来,Google也不是清一色不要

    开发者使用系统底层的标记为hide的API,而是推

    出了一组黑灰名单,如下所示:所以,另一种应对策略是,在插件化中使用浅

    灰名单中的API,比如说ActivityThread的

    currentActivityThread方法。

    Google的这组清单还在持续调整中,据我所

    知,给各大手机厂商的清单与其在社区中发布的清

    单略有出入。在Android P的正式版本中,这份清单

    会最终确定下来。所以现在中国的各个插件化框架

    的开发人员,都在等Android P的正式版本发布后再

    制定相应的策略。留给中国队的时间不多了。

    这本书的来龙去脉

    这是一本酝酿了3年的书。

    早在2015年Android插件化技术百家争鸣时,我就看好这个技术,想写一本书介绍这个技术,但

    当时的积累还不够。那年,我在一场技术大会上发

    表了《Android插件化从入门到放弃》演讲,四十

    五分钟介绍了插件化技术的皮毛。后来这个演讲内

    容被整理成文章发布到网上,流传很广。2017年1月,有企业要我去讲2天Android插件

    化技术。为此,我花了一个月时间,准备了四十多

    个例子。这是我第一次系统地积累了素材。

    2017年6月,我在腾讯课堂做Android线上培

    训,为了宣传推广我的课程,我写了一系列文章

    《写给Android App开发人员看的Android底层知

    识》,共8篇,没列太多代码,完全以UML图的方

    式向读者普及Binder、AIDL、四大组件、AMS、PMS的知识。本书的第2章就是在这8篇文章的基础

    之上进行扩充的。

    2018年1月,我父亲住院一周。我当时在医院

    每天晚上值班。老爷子半夜打呼噜,吵得我睡不

    着,事后我才知道,我睡着了打呼噜声音比他还

    大。半夜睡不着时就开始了本书的写作,每晚坚持

    写到凌晨两三点。直到父亲出院,这本书写了将近

    五分之一。

    碰巧的是,这一年5月底我结婚,促使我想在5

    月初完成这本书的一稿,为此,我宅在家里整整写

    了3个月。仅以此书作为新婚礼物献给我亲爱的老

    婆,感谢你的理解,这本书才得以面世。

    这两年我在忙什么2016年5月写完《App研发录》后不久,我就从

    一线互联网公司出来,开始了长达两年的App技术

    培训工作。一改之前十几年在办公室闷头研究技术

    的工作方式,开始在全中国飞来飞去,给各大国

    企、传统公司、手机商的Android和iOS团队进行培

    训。这两年去过了近百家公司,有了很多与过去十

    几年不一样的感受。

    App开发人员的分布也呈金字塔型,在金字塔

    尖的自然是那些一线互联网的开发人员,他们掌握

    Android和iOS最先进的技术,比如组件化、插件化

    等技术,但这些人毕竟是少数。而位于金字塔底端

    的开发人员则是大多数,他们大都位于创业公司或

    者传统行业,相应的App侧重于业务的实现,对

    App的高端技术,用得不多,需要不断补充新知

    识。另外,我在腾讯课堂讲了几个月App开发课程

    的过程中,认识了很多学员,有几千粉丝,同样面

    临需要不断学习新知识。

    写作这本书的目的,是向广大Android开发人

    员普及插件化技术。

    这本书里讲些什么

    战战兢兢写下这本书,有十几万字,仍不能覆

    盖Android插件化的所有技术。因为插件化技术千头万绪,流派众多,我想从最基本的原理讲起,配

    合大量的例子,希望能帮助完全不懂Android插件

    化技术的小白,升级为一个精通这类技术的高手。

    面对业内各种成熟的插件化框架,我只选取了

    具有代表意义的DroidPlugin、DL、Small和Zeus进

    行介绍,这几个框架基本覆盖了插件化编程的所有

    思想,而且非常简单,像Zeus只有11个类,就支撑

    起掌阅App的插件化。而对于后期推出的

    VirtualApk、Atlas、Replugin等,在本书中并没有

    介绍,主要是因为这些框架都是大块头,代码量很

    多,我没有精力再去研究和学习了。但这些企业级

    插件化框架所用的技术,本书都有涉及。

    本书的结构及内容

    全书分为三大部分,共22章。第1部分“预备知

    识”包括第1~5章,是进行Android插件化编程的准

    备知识。第2部分“解决方案”包括第6~16章,详细

    介绍并分析了插件化编程的各种解决方案。第3部

    分“相关技术”包括第17~21章,介绍插件化编程的

    周边技术,并对纷繁复杂的插件化技术进行了总

    结。

    第1章介绍的是Android插件化的历史,可以当

    作小说来读,茶余饭后,地铁站中就可以读完。第2章介绍Android底层知识,涉及那些与

    Android插件化相关的知识,比如Binder和AIDL,Android App的安装流程和启动流程,ActivityThread,LoadedApk,Android四大组件的运

    行原理。这一章篇幅较多,需要仔细研读。其中,讲到一个音乐播放器的例子,帮助大家更加深刻地

    认识Android的四大组件。

    第3章讲反射,详细介绍了构造函数、方法、字段、泛型的反射语法。这章介绍了Java领域很火

    的一个开源库jOOR,可惜,它对Android的支持并

    不是很好,所以这章还介绍了我们自己封装的

    RefInvoke类,这个类将贯穿本书,基本上所有源码

    例子都会使用到它。

    第4章讲代理模式。这个模式在Android中最著

    名的实现就是Proxy.newProxyInstance方法。基于

    此,我们Hook了AMS和PMS中的一些方法。

    第5章是第4章的延续,仍然是基于

    Proxy.newProxyInstance方法,Hook了Activity的启

    动流程,从而可以启动一个没有在AndroidManifest

    中声明的Activity,这是插件化的核心技术之一。

    第6章介绍了如何加载插件App,以及如何对插

    件化项目的宿主App和插件App同时进行调试。说到插件化编程,离不开面向接口编程的思想,这章

    也花了很多笔墨介绍这个思想,以及具体的代码实

    现。

    第7章介绍了资源的加载机制,包括

    AssetManager和Resources,并给出了资源的插件化

    解决方案,从而为Activity的插件化铺平了道路。另

    外还介绍了换肤技术的插件化实现。

    第8章介绍了最简单的插件化解决方案,通过

    在宿主App的AndroidManifest中事先声明插件中的

    四大组件。为了能让宿主App随意加载插件的类,这章介绍了合并dex的技术方案。

    第9章到第12章介绍了Android四大组件的插件

    化解决方案。四大组件的生命周期各不相同,所以

    它们各自的插件化解决方案也都不同。

    第13章、第14章介绍了Android插件化的静态

    代理的解决方案。这是一种“牵线木偶”的思想,我

    们不用Hook太多Android系统底层的代码。

    第15章再次讲到资源,这次要解决的是宿主和

    多个插件的资源id值冲突的问题。这一章介绍了多

    种解决方案,包括思路分析、代码示例。

    第16章介绍一种古老的插件化解决方案,通过动态替换Fragment的方式。

    第17章介绍了App的降级解决方案。一旦插件

    化方案不可用,那么我们仍然可以使用H5,来替换

    任何一个App原生页面。

    第18章介绍了插件的混淆技术。有时候宿主

    App和插件App都会引用MyPluginLibrary这个类

    库,这个公用类库是否要混淆,相应有两种不同的

    混淆方案。

    第19章介绍了增量更新技术。这是插件化必备

    的技术,从而保证插件的升级,不需要从服务器下

    载太大的包。

    第20章介绍了so的插件化解决方案。这章详细

    介绍了so的加载原理,以及从服务器动态加载so的

    方案,基于此,有两种so的插件化解决方案。

    第21章介绍了gradle-small这个自定义Gradle插

    件。这章是对第15章的补充,是另一种解决插件资

    源id冲突的方案。

    第22章作为整本书的结尾,系统总结了

    Android插件化的各种解决方案。如果读者能坚持

    读到这最后一章,可以帮助读者巩固这些知识。关于本书名词解释

    本书有很多专业术语,刚接触Android插件化

    的读者可能不容易理解。有一些专业术语还有别称

    或者简称,我在这里罗列出最常见的一些术语:

    ·HostApp,本书中有时也写作“宿主App”。用

    于承载各种插件App,是最终发版的App。我们从

    Android市场上下载的都是HostApp。

    ·Plugin,本书中有时也写作“插件”、“插件

    App”。

    ·Receiver,是BroadcastReceiver的简称,Android四大组件之一。

    ·AndroidManifest,也就是

    AndroidManifest.xml。

    ·Hook,就是使用反射修改Android系统底层的

    方法和字段。

    ·AMS,是ActivityManagerService的简称,在

    App运行时和四大组件进行通信。

    ·PMS,是PackageManagerService的简称,用于

    App的安装和解析。关于本书的源码

    本书精心挑选了70多个例子,都可以直接下载

    使用,正文中都列出了代码名称,在相应网站可以

    找到。附录B还列出了所有源码对应的章节。

    致谢

    几乎所有的书都千篇一律地感谢很多人,却不

    写为什么要感谢他们。我在这里一定要把感谢的理

    由说清楚。

    首先,感谢我那古灵精怪的老婆郭曼云。谢谢

    她在我人生迷惘的时候及时出现,陪我玩王者荣

    耀,带我骑小黄车去散心,看电影时一起八卦剧情

    然后被坐在旁边的观众出声制止。她每天要我做不

    一样的饭菜给她吃,把我锻炼成厨房小能手,我现

    在已经习惯于每天傍晚五点半就放下手中所有的活

    儿,愉快地投入买菜做饭的工作。

    感谢张勇、任玉刚、罗迪、黄剑、林光亮、邓

    凡平、王尧波、田维术等Android领域的朋友们,我在写作这本书的时候,经常会遇到各种疑惑,每

    次问到他们,他们都会不厌其烦地给我详细解答。

    在这里,尤其感谢田维术,他的技术博客(weishu.me)“介绍插件化的一系列文章”对我的影

    响很大,可惜没写完,只讲了Binder原理和四大组

    件的插件化方案。本书的部分章节参考了他的博客

    文章,对他提供的一些代码例子进行了二次加工。

    经过他本人同意后,收入这本书中。代码中的很多

    类上都标注了作者是weishu,以表达对他的感谢。

    感谢任正浩、霹雳娇娃、赵越、Dinosaur等这

    群狐朋狗友的陪伴,我从互联网公司出来转型做

    App技术培训的过程中,整理了半年PPT教程后才

    开始陆陆续续接到单子,在这半年时间里,我就跟

    这帮学弟学妹厮混在一起,爬山、撕名牌、唱K、密室逃脱、狼人杀,还有一阵时间沉迷于你画我

    猜。那是我最惬意的一段时光。

    感谢我爸我妈以及咱爸咱妈,你们的女儿我一

    定照顾好。虽然北京与天津距离那么近,很抱歉还

    是不能常回家看看,我永远是那么忙,忙着去追求

    事业的成功,距离财务自由还很远,但是我一直在

    努力。

    最后,感谢曹洪伟等21位社区朋友的辛勤劳

    动,把这本书翻译为英文,限于篇幅,这里就不一

    一致谢了。接下来,这本书的英文版本会在国外网

    站社区逐篇发布,乃至出版成书,让全世界的

    Android开发人员看到中国工程师们的智慧结晶。包建强

    2018年6月于北京

    [1] 详细内容,请参见田维术的文章:

    http:weishu.me20180607free-reflection-above-

    android-p

    [2] 项目地址参见:https:github.comhoukxandroid-

    pluginmgr第一部分 预备知识

    第1章 插件化技术的昨天、今天与明天

    第2章 Android底层知识

    第3章 反射

    第4章 代理模式

    第5章 对startActivity方法进行Hook第1章 插件化技术的昨天、今天与明

    天

    这是最好的时代,国内各大应用市场对插件化

    技术的态度是开放的,因此,国内各大互联网App

    无一不有自己的插件化框架。有了开放的环境,才

    会有无数英雄大展身手,在Android插件化的领域

    中出现百家争鸣、百花齐放的局面。

    这是最坏的时代,随着插件化技术在中国的普

    及,你会发现,去中国的各大互联网公司面试,一

    般都会聊聊插件化的技术。这就使得开发人员要去

    了解Android底层的知识,这无形中增加了学习难

    度。

    本章将介绍插件化的概念、历史及应用,为后

    续学习插件化技术,提供基础。1.1 插件化技术是什么

    一个游戏平台,比如联众,支持上百种游戏,如象棋、桥牌、80分。一个包括所有游戏的游戏平

    台往往有上百兆的体积,需要下载很卡时间,但是

    用户往往只玩其中的1~2款游戏。让用户下载并不

    会去玩的上百款游戏,是不明智的做法。此外,任

    何一个游戏更新或者新上线一个游戏,都需要重新

    下载数百兆的安装包,也会让用户抓狂。

    所以,游戏平台必然采用插件化技术。

    通常的做法是,只让用户下载一个十几兆大小

    的安装包,其中只包括游戏大厅和一个全民类游

    戏,如“斗地主”。用户进入游戏大厅,可以看到游

    戏清单,点击“80分”就下载80分的游戏插件,点

    击“中国象棋”就下载中国象棋的游戏插件,这称

    为“按需下载”。这就需要插件化编程,不过这是基

    于电脑上的游戏平台,是一个个exe可执行文件。

    在Android领域,是没有exe这种文件的。所有

    的文件都会被压缩成一个apk文件,然后下载到本

    地。Android应用中所谓的安装App的过程,其实就

    是把apk放到本地的一个目录,然后使用PMS读取

    其中的权限信息和四大组件信息。所以Android领域的插件化编程,与电脑上的软件的插件化编程是

    不一样的。

    其实,在Android领域,对于游戏而言,用的

    还真不是插件化技术,而是从服务器动态下发脚

    本,根据脚本中的信息,修改人物属性,增加道具

    和地图。

    Android插件化技术,主要用在新闻、电商、阅读、出行、视频、音乐等领域,比如说旅游类

    App,因为涉及下单和支付,所以算是电商的一个

    分支,旅游类App可以拆分出酒店、机票、火车票

    等完全独立的插件。1.2 为什么需要插件化

    在那山的这边海的那边有一群程序员,他们老实又胹腆,他们聪明又有钱。

    他们一天到晚坐在那里熬夜写软件,饿了就咬一口方便面。

    哦苦命的程序员,哦苦命的程序员,只要一改需求他们就要重新搞一遍,但是期限只剩下两天。

    这首改编自《蓝精灵》主题曲的《程序员之

    歌》,道出了中国互联网行业的程序员现状。

    就在Android程序员疯狂编写新需求之际,自

    然会衍生出各种bug,甚至是崩溃。App有bug,会

    导致用户下不了单,而一旦崩溃,那就连下单页面都进不去,因此我们要在最短时间内修复这些问

    题,重新发版到Android各大市场已经来不及,每

    分每秒都在丢失生意,因此,Android插件化的意

    义就体现出来了,不需要用户重新下载App,分分

    钟就能享受到插件新的版本。

    另一方面,如果要和竞争对手抢占市场,那么

    谁发布新功能越快越多,对市场对用户的占有率就

    越高。如果隔三岔五就发布一个新版本到Android

    各大市场,那么用户会不胜其烦,发版周期固定为

    半个月,又会导致新功能长期积压,半个月后才能

    让用户见到,而竞争对手早就让用户在使用同样的

    新功能了。这时候,如果有插件化技术支持,那么

    新功能就可以在做完之后立刻让用户看到,这可是

    让竞争对手闻风丧胆的手段。1.3 插件化技术的历史

    2012年7月27日,是Android插件化技术的第一

    个里程碑。大众点评的屠毅敏(Github名为

    mmin18),发布了第一个Android插件化开源项目

    AndroidDynamicLoader

    [1]

    ,大众点评的App就是基

    于这个框架来实现的。这是基于Fragment来实现的

    一个插件化框架。通过动态加载插件中的

    Fragement,来实现页面的切换,而Activity作为

    Fragement的容器却只有一个。我们也是在这个开源

    项目中第一次看到了如何通过反射调用

    AssetManger的addAssetPath方法来处理插件中的资

    源。

    2013年,出现了23Code。23Code提供了一个

    壳,在这个壳里可以动态下载插件,然后动态运

    行。我们可以在壳外编写各种各样的控件,在这个

    框架下运行。这就是Android插件化技术。这个项

    目的作者和开源地址,我不是很清楚,如果作者恰

    巧读到我这本书,请联系我,咱们一起喝杯咖啡。

    2013年3月27日,第16期阿里技术沙龙,淘宝

    客户端的伯奎做了一个技术分享,专门讲淘宝的

    Atlas插件化框架,包括ActivityThread那几个类的

    Hook、增量更新、降级、兼容等技术。这个视频[2]

    ,只是从宏观来讲插件化,具体怎么实现的并没

    说,更没有开源项目可以参考。时隔5年再看这个

    视频,会觉得很简单,但在2013年,这个思想还是

    很先进的,毕竟那时的我还处在Android入门阶

    段。

    2014年3月30日8点20分,是Android插件化的

    第二个里程碑。任玉刚开源了一个Android插件化

    项目dynamic-load-apk [3]

    ,这与后续介绍的很多插

    件化项目都不太一样,它没有对Android系统的底

    层方法进行Hook,而是从上层,也就是App应用层

    解决问题——通过创建一个ProxyActivity类,由它

    来进行分发,启动相应的插件Activity。因为任玉刚

    在这个框架中发明了一个that关键字,所以我在本

    书中把它称为that框架。其实作者不喜欢我给他的

    最爱起的这个外号,他一直称之为DL。曾经和玉

    刚在一起吃饭聊天,他感慨当年如何举步维艰地开

    发这个框架,因为2014年之前没有太多的插件化技

    术资料可以参考。

    that框架一开始只有Activity的插件化实现,后

    来随着田啸和宋思宇的加入,实现了Service的插件

    化。2015年4月that框架趋于稳定。那时我在途牛做

    App技术总监,无意中看到这个框架,毅然决定在

    途牛的App中引入that框架。具体实施的是汪亮亮和

    魏正斌,他们当时一个初为人父另一个即将为人父,还是咬牙把这个that框架移植到了途牛App中。

    that框架经受住了千万级日活App的考验,这是它落

    地的第一个实际项目 [4]。

    与此同时,张涛也在研究插件化技术 [5]

    ,并

    于2014年5月发布了他的第一个插件化框架

    CJFrameForAndroid [6]。它的设计思想和that框架差

    不多,只是把ProxyActivity和ProxyService称为托管

    所。此外,CJFrameFor-Android框架还给出了

    Activity的LaunchMode的解决方案,这是对插件化

    框架一个很重要的贡献,可以直接移植到that框架

    中。

    2014年11月,houkx在GitHub上发布了插件化

    项目android-pluginmgr

    [7]

    ,这个框架最早提出在

    AndroidManifest文件中注册一个StubActivity来“欺

    骗AMS”,实际上却打开插件中的ActivityA。但是

    作者并没有使用对Instrumnetation和ActivityThread

    的技术进行Hook,而是通过dexmaker.jar这个工具

    动态生成StubActivity,StubActivity类继承自插件中

    的ActivityA。现在看来,这种动态生成类的思想并

    不适用于插件化,但在当时能走到这一步已经很不

    容易了。

    同时,houkx还发现,在插件中申请的权限无

    法生效,所以要事先在宿主App中申请所有的权限。android-pluginmgr有两个分支——dev分支和

    master分支。作者的插件化思想位于dev分支。后来

    高中生Lody参与了这个开源项目,把android-

    pluginmgr设计为对Instrumnetation的思想进行

    Hook,体现在master分支上,但这已是2015年11月

    的事情了。

    2014年12月8日有一个好消息,那就是Android

    Studio1.0版本出现了。Android开发人员开始逐步抛

    弃Eclipse,而投入Android Studio的怀抱。Android

    Studio借助于Gradle来编译和打包,这就使插件化

    框架的设计变得简单了许多,排除了之前Eclipse还

    要使用Ant来运行Android SDK的各种不便。

    时间到了2015年。高中生Lody此刻还是高二学

    生。他是从初中开始研究Android系统源码的。他

    的第一个著名的开源项目是TurboDex [8]

    ,能够以

    迅雷不及掩耳之势加载dex,这在插件化框架中尤

    其好用,因为首次加载所有的插件需要花很久的时

    间。

    2015年3月底,Lody发布插件化项目Direct-

    Load-apk [9]。这个框架结合了任玉刚的that框架的

    静态代理思想和Houkx的pluginmgr框架的“欺骗

    AMS”的思想,并Hook了Instrumnetation。可惜Lody

    当时还是个学生,没有花大力气宣传这个框架,以至于没有太多的人知道这个框架的存在。Lody的传

    奇还没结束,后来他投身于VirtualApp,这是一个

    App,相当于Android系统之上的虚机,这是一个更

    深入的技术领域,我们稍后再提及。

    2015年5月,limpoxe发布插件化框架Android-

    Plugin-Framework [10]。

    2015年7月,kaedea发布插件化框架android-

    dynamical-loading [11]。

    2015年8月27日,是Android插件化技术的第三

    个里程碑。张勇的DroidPlugin问世了。张勇当时在

    360的手机助手团队,DroidPlugin就是手机助手使

    用的插件化框架。这个框架的神奇在于,能把任意

    的App都加载到宿主里面去。你可以基于这个框架

    写一个宿主App,然后就可以把别人写的App都当

    作插件来加载。

    DroidPlugin的功能很强大,但强大的代价就是

    要Hook很多Android系统的底层代码,而且张勇没

    有给DroidPlugin项目加任何说明文档,导致这个框

    架不太容易理解。网上有很多人写文章研究

    DroidPlugin,但其中写得最好的是田维术 [12]。他

    当时就在360,刚刚毕业转正,写出一系列介绍

    DroidPlugin思想的文章,包括Binder和AIDL的原理、Hook机制、四大组件的插件化机制等。

    2015年是Android插件化蓬勃发展的一年,不

    光有that框架和DroidPlugin,很多插件化框架也在

    这个时候诞生。

    OpenAtlas这个项目是2015年5月发布在Github

    上的,后来改名为ACDD。里面提出了通过修改并

    重新生成aapt命令,使得插件apk的资源id不再是固

    定的0x7f,可以修改为0x71这样的值。这就解决了

    把插件资源合并到宿主HostApp资源后资源id冲突

    的问题。

    OpenAtlas也是基于Hook Android底层

    Instrumentation的execStartActivity方法,来实现

    Activity的插件化。此外,OpenAltas还Hook了

    ContextWrapper,在其中重写了getResource等方

    法,因为Activity是ContextWrapper的“孙子”,所以

    插件Activity就会继承这些getResource方法,从而取

    到插件中的资源——这种做法现在已经不用了,我

    们是通过为插件Activity创建一个基类

    BasePluginActivity并在其中重写getResource方法来

    实现插件资源加载的。

    携程于2015年10月开源了他们的插件化框架

    DynamicAPK [13]

    ,这是基于OpenAltas框架基础之上,融入了携程自己特殊的业务逻辑。

    2015年12月底,林光亮的Small框架发布,他

    当时在福建一家二手车交易平台,这个框架是为这

    个二手车平台的App量身打造的,主要特点如下:

    ·Small把插件的ClassLoader对应的dex文件,塞

    入到宿主HostApp的ClassLoader中,从而HostApp可

    以加载任意插件的任意类。

    ·Small框架通过Hook Instrumentation来启动插

    件的Activity,这一点和DroidPlugin相同,那么自然

    也会在AndroidManifest中声明一个StubActivity,来“欺骗AMS”。

    ·Small框架对其他三大组件的支持,需要提前

    在宿主HostApp的AndroidManifest中声明插件的

    Service、Receiver和ContentProvider。

    ·Small对资源的解决方案独树一帜。使用

    AssetManager的addAssetPath方法,把所有插件的资

    源都合并到宿主的资源中,这时候就会发生资源id

    冲突的问题。Small没有采用Atlas修改AAPT的思

    路,而是在生成插件R.java和resources.arsc这两个文

    件后,把插件R.java中所有资源的前缀从0x7f改为

    0x71这样的值,同时也把resources.arsc中出现0x7f的地方也改为0x71。

    随着2015年的落幕,插件化技术所涉及的各种

    技术难点都已经有了一种甚至多种解决方案。在这

    一年,插件化技术领域呈现了百家争鸣的繁荣态

    势。这一时期以个人主导发明的插件化框架为主,基本上分为两类——以张勇的DroidPlugin为代表的

    动态替换方案,以任玉刚的that框架为代表的静态

    代理方案。

    就在2015年,Android热修复技术和React

    Native开始进入开发者的视线,与Android插件化技

    术平分秋色。Android插件化技术不再是开发人员

    唯一的选择。

    从2016年起,国内各大互联网公司陆续开源了

    自己研发的插件化框架。这时候已经没有什么新技

    术出现了,因为插件化所有的解决方案都已经在

    2015年由个人开发者给出来了。互联网公司是验证

    这些插件化技术是否可行的最好的平台,因为他们

    的App拥有动辄千万用户的日活。

    按时间顺序列举插件化框架如下:

    2016年8月,掌阅推出Zeus [14]。2017年3月,阿里推出Atlas [15]。

    2017年6月26日,360手机卫士的RePlugin [16]。

    2017年6月29日,滴滴推出VisualApk [17]。

    仔细读这些框架的源码会发现,互联网公司开

    源的这些框架更多关注于:

    ·插件的兼容性,包括Android系统的升级对插

    件化框架的影响,各个手机ROM的不同而对插件化

    的影响。

    ·插件的稳定性,比如各种奇葩的崩溃。

    ·对插件的管理,包括安装和卸载。

    斗转星移,时光荏苒,虽然只有几年时间,但

    各个插件化框架已经渐趋稳定,现在做插件化技术

    的开发人员,只需要关注每年Android系统版本的

    升级对自身框架的影响,以及如果消除这种影响。

    随着插件化领域的技术基本成型,我身边很多

    做插件化的朋友都开始转行,有的人还在Android

    这个领域,比如张勇基于他的DroidPlugin框架,在做他的创业项目闪电盒子;有的人转入区块链,每

    天沉浸于用GO语言写智能合约。

    谨以此文献给那些在插件化领域中做出过贡献

    的朋友们,包括开源框架的作者,以及写文章传经

    布道的作者。我的见识有限,有些人、有些框架、有些文章可能会没有提及,欢迎广大读者多多指

    正。

    [1] 开源项目地址:

    https:github.commmin18AndroidDynamicLoader

    [2] 视频地址:

    http:v.youku.comv_showid_XNTMzMjYzMzM2.html

    [3] 开源项目地址:

    https:github.comsingwhatiwannadynamic-load-apk

    [4] 参考文章:

    https:blog.csdn.netlostinaiarticledetails50496976

    [5] 张涛的开源实验室:https:kymjs.com

    [6] 开源项目地址:

    https:github.comkymjsCJFrameForAndroid

    [7] 开源项目地址:https:github.comhoukxandroid-

    pluginmgr

    [8] 开源项目地址:

    https:github.comasLodyTurboDex

    [9] 开源项目地址:

    http:git.oschina.netoycoceanDirect-Load-apk[10] 开源项目地址:

    https:github.comlimpoxeAndroid-Plugin-

    Framework

    [11] 开源项目地址:

    https:github.comkaedeaandroid-dynamical-loading

    [12] 田维术的技术博客:http:weishu.me

    [13] 开源项目地址:

    https:github.comCtripMobileDynamicAPK

    [14] 开源项目地址:

    https:github.comiReaderAndroidZeusPlugin

    [15] 开源项目地址:https:github.comalibabaatlas

    [16] 开源项目地址:

    https:github.comQihoo360RePlugin

    [17] 开源项目地址:

    https:github.comdidiVirtualAPK1.4 插件化技术的用途到底是什么

    我们曾经天真地认为,Android插件化是为了

    增加新功能,或者增加一个完整的模块。费了不少

    时间和精力,等项目实施了插件化后,我们才发

    现,插件化80%的使用场景,是为了修复线上

    bug。在这一点上,插件化与Tinker、Robust这类热

    修复工具拥有相同的能力,甚至比热修复工具做得

    更好。

    App每半个月发一次版,新功能上线,一般都

    会等这个时间点。另一方面,很多公司的Android

    发版策略是受iPhone新版本影响的,新功能要等两

    个版本一起面世,那就只有等Apple Store审核通过

    iPhone的版本,Android才能发版。所以,真的不是

    那么着急。

    在没有插件化的年代,我们做开发都是战战兢

    兢的,生怕写出什么bug,非常严重的话就要重新

    发版本。有了插件化框架,开发人员没有了后顾之

    忧,于是App上线后,每个插件化,每天都会有一

    到两个新版本发布。Android插件化框架,已经沦

    落为bug修复的工具。这是我们不愿看到的场景。

    其实,插件化框架更适合于游戏领域。比如王者荣耀,经常都会有新皮肤,或者隔几天上线一个

    新英雄,调整一下英雄的数据,这些都不需要重新

    发版。

    插件化还有一种很好的使用场景,那就是

    ABTest,只是没有深入人心罢了。当产品经理为两

    种风格的设计举棋不定时,那么把这两种策略做成

    两个插件包,让50%的用户下载A策略,另外50%

    的用户下载B策略,一周后看数据,比如说页面转

    化率,就知道哪种策略更优了。这就是数据驱动产

    品。

    随着业务线的独立,Android和iOS团队拆分到

    各自的业务线,有各自的汇报关系,因此有必要把

    酒店机票火车票这些不同的业务拆分成不同的模

    块。在Android组件化中,模块之间还是以aar的方

    式进行依赖的,所以我们可以借助Maven来管理这

    些aar。

    Android的这种组件化模型,仅适用于开发阶

    段,一旦线上有了bug,或者要发布新功能,那就

    需要将所有模块重新打包一起发布新版本。

    Android组件化再往前走一步,就是插件化。

    此时,各个业务模块提供的就不再是aar了,而是一

    个打包好的apk文件,放在宿主App的assets目录下。这样,发版后,某个模块有更新,只需重新打

    包这个模块的代码,生成增量包,放到服务器上供

    用户下载就可以了。这才是Android插件化在企业

    级开发中的价值所在。一般的小公司只做了

    Android组件化,没有做插件化,所以体会不到这

    个好处,这是因为插件化开发成本很高,投入产出

    比很低。1.5 更好的替代品:React Native

    2015年,React Native(RN)横空出世,当时

    并没有多少人关注它,因为它还很不成熟,甚至连

    基本的控件都没几个。后来随着RN项目的迭代,功能日趋完善,虽然迄今为止还没有一个1.0的

    release版本,我们还是欣喜地发现,这个东西就是

    Android和iOS的插件化啊。

    外国人和中国人的思路不一样。就好像国际象

    棋与中国象棋不一样。当我们投入大量人力去钻研

    怎么Hook Android系统源码的时候,外国人走的是

    另一条路,那就是映射,让Android或iOS中的每个

    控件,在RN中都能找到相对应的控件。RN是基于

    JavaScript代码来编写的,打包后放到服务器,供

    Android和iOS的App下载并使用。

    RN比Android插件化更好的地方在于它还支持

    iOS,因此最大程度地实现了跨平台。于是当我们

    一厢情愿地以为Android插件化多么好用,而对iOS

    如何发布新功能一筹莫展时,便有了RN这个更好

    的选择。至于性能,二者差别不大,RN在iOS和

    Android上都很流畅,这一点不用担心。

    对于中小型公司和创业公司而言,缺少人力和财力自己研制一套插件化框架,一般就采用国内比

    较稳定的、开源的、持续更新的插件化框架。但

    iOS没有这方面的技术框架,尤其是在jsPatch热修

    复被AppStore明令禁止了之后,最好的选择就是

    RN。只要招聘做JavaScript前端的技术人员,就能

    快速迭代、快速上线了,完全不受发版的限制。我

    是从研发的岗位走出来,在国内做了两年培训,全

    国各地走了上百家公司,包括大型国企、二三线互

    联网公司、传统行业,我发现国内90%的公司都属

    于这种类型,国内对RN的需求远大于Android插件

    化。

    关于RN的话题,至少要一本书才能说清楚。

    本书主要介绍Android插件化,这里只指出Android

    插件化不如RN的地方。1.6 只有中国这么玩吗

    有读者会问,Android插件化在中国如火如

    荼,为什么在国外却悄无声息?打开硅谷那些独角

    兽的App,都没有发现插件化的影子。

    一方面原因是,国外人都使用Google Play,这

    个官方市场不允许插件化App的存在,审核会不通

    过,这就很像Apple Store了。

    另一方面原因是,国外没有这样的需求。所以

    当你发现国外某款App显示数据错误了,或者莫名

    其妙崩溃了,就算你反馈给他们,得到的也是一副

    坐看闲云、宠辱不惊的回复——下个版本再修复

    吧。下个版本什么时候?一个月后。

    这就和中国国内的App境遇不同了。在互联网

    公司,特别是有销售业务的公司,任何数据显示的

    错误或者崩溃,都会导致订单数量的下降,直接影

    响的是钱啊。所以,我经常半夜被叫醒去修bug,然后快速出新版本的插件包,避免更多订单的损

    失。

    国内的一二线互联网公司,会花很多钱雇佣一

    群做插件化框架的人,框架设计完,他们一般会比较闲。在Android每年发布新版本的时候,他们会

    很忙,去研究新版本改动了哪些Android系统源

    码,会对自家公司的插件化框架有什么影响。从长

    期来看,公司花的这些钱是划算的,基本等于没有

    插件化而损失的订单数量的价值。

    而国内的中小型公司以及创业公司,没有额外

    的财力来做自己的插件化框架,一般就采用国内比

    较稳定的、开源插件化框架。后来有了RN,就转

    投RN的怀抱了。

    就在中国的各路牛人纷纷推出自家的Android

    插件化框架之际,国外的技术人员在研究些什么

    呢?

    国外的技术人员比较关注用户体验,所以在国

    外Material Design大行其道,而在中国,基本是设

    计师只设计出iOS的样稿,Android保持做的一样就

    够了。国外的技术人员比较关注函数式编程,追求

    代码的优雅、实用、健壮、复用,而不像国内的

    App,为了赶进度超越竞争对手,纯靠人力堆砌代

    码,甚至带bug上线,以后有时间了再进行重构,而那时当初写代码的人也许已经离职了。

    所以,当硅谷那边层出不穷地推出

    ButterKnife、Dagger、OKHttp、Retrofit、RxJava的时候,国内能拿出来与之媲美的只有各种插件化框

    架和热修复框架,以及双开技术。1.7 四大组件都需要插件化技术吗

    在Android中,Activity、Service、ContentProvider和BroadcastReceiver并称为四大组

    件。四大组件都需要插件化吗?这些年,我是一直

    带着这个问题做插件化技术的。

    我所工作过的几家公司都属于OTA(在线旅

    游)行业。这类App类似于电商,包括完整的一套

    下单支付流程,用得最多的是Activity,达数百个;

    Service和Receiver用得很少,屈指可数;

    ContentProvider根本就没用过。

    国内大部分App都是如此。根据技术栈来划分

    App行业:

    ·游戏类App ,有一套自己的在线更新流程,很多用的是Lua之类的脚本。

    ·手机助手、手机卫士 ,这类App对Service、Receiver、ContentProvider的使用比较多。所以四大

    组件的插件化都必须实现。

    ·音乐类、视频类、直播类App ,除了使用比

    较多的Activity,对Service和Receiver的依赖很强。·电商类、社交类、新闻类、阅读类App ,基

    本是Activity,其他三大组件使用不是很多,可以只

    考虑对Activity插件化的支持。

    我们应该根据App对四大组件的依赖程度,来

    选择合适的插件化技术。四大组件全都实现插件化

    固然是最好的,但是如果App中主要是Activity,那

    么选择静态代理that框架就够了。1.8 双开和虚拟机

    既然插件化会慢慢被RN所取代,那么插件化

    的未来是什么?答案是,虚拟机技术。

    各位读者应该有过在PC机上安装虚拟机的经

    历。只要电脑的内存足够大,那么就可以同时打开

    多个虚拟机,在每个虚拟机上都安装QQ软件,使

    用不同的账号登录,然后自己跟自己聊天。

    在Android系统上,是否也能支持安装一个或

    多个虚拟机呢?国内已经有人在做了,我所知道

    的,一个是高中生Lody(当你阅读这本书的时候,他应该已经是大学生了吧),他有一个很著名的开

    源项目VirtualApp [1]

    ,这个项目现在已经商业化运

    作了。另一个是DroidPlugin的作者张勇,他现在创

    业专职做这个,在DroidPlugin的基础上研发了闪电

    盒子,可以极速加载各种apk。

    有了这样一个虚拟机系统,我们就可以在手机

    上打开两个不同账号的QQ,自己和自己聊天了。

    我们称同时打开一个App的多个分身的技术

    叫“双开”。现在国内有些手机系统已经支持双开技

    术了,可以在设置中看到这一选项。关于双开和虚拟机的技术,我们就介绍这么

    多,毕竟这已经不是本书所涉及的技术范畴了。

    [1] 开源项目地址:

    https:github.comasLodyVirtualApp1.9 从原生页面到HTML 5的过渡

    无线技术越来越成熟,已经从2012年时的初步

    开荒,发展到现在的蔚为壮观。对于国人来说,我

    们比较关注的是:热修复、性能、开发效率、App

    体积、数据驱动产品。这些点目前都已经有了很好

    的解决方案,也涌现出RxJava、LeakCanary这样优

    秀的框架。这个话题很大,本文就不展开说了。

    由App技术的无比繁荣,回想起我2004年刚工

    作的时候,IT行业正从CS(Service-Client)转型为

    BS(Browser-Server)。2004年之前大都是CS这样

    的软件,比如Windows上安装一个联众的客户端就

    可以和网友斗地主了,后来互联网的技术成熟起来

    了,就把原先的系统都搬到网站上,这就是BS。

    后来BS做多了,大家觉得BS太“单薄”,很多

    功能不支持,不如CS,于是就提出SmartClient的概

    念,也就是智能客户端,Outlook就是一个很好的例

    子,你可以脱机读和写邮件,没网络也可以,什么

    时候有网络了,再将写好的邮件发送出去。

    再后来Flash火起来了,这个本来是网页制作工

    具三剑客之一,却阴差阳错地成为了网页富客户端

    的鼻祖。在此基础上便有了Flex,现在还有些公司在使用。微软这时候提出了Silverlight,是搭载在网

    页上的。与此同时,JavaScript也在发力,并逐渐取

    代前者,成为富客户端的最后赢家,那时候

    《JavaScript设计模式》一书非常畅销。

    JavaScript在2004年仅是用来做网页特效的。时

    至今日,我们发现,JavaScript经历了Ajax、jQuery、ECMAScript从1到6、Webpack打包,以及

    Angular、React、Vue三大主流框架,已经变得无比

    强大,被封装成一门“面向对象”的语言了。

    前面铺垫了那么多,就是想说明App也在走同

    样的发展道路,先沉淀几年,把网站的很多技术都

    搬到App上,也就是目前的发展阶段,差不多该有

    的也都有了。下一个阶段就是从CS过渡到BS,Hybird技术就类似于BS,但是有很多缺陷,尤其是

    Web Browser性能很差,然后便出现了React

    Native,HTML 5很慢,但可以把HTML 5翻译成原

    生的代码啊。再往前发展是什么样,我不知道,但

    是这个发展方向是很清晰的。一方面,Android和

    iOS技术不会消亡;另一方面HTML 5将慢慢成为

    App开发的主流。1.10 本章小结

    本章回顾了Android插件化技术的发展历史,基本上分为两大流派:静态代理和动态替换,所有

    的插件化框架都基于此。看完这段历史你会发现,这门技术也不是一蹴而就的,期间也经历了从无到

    有,以及逐步完善的过程。

    插件化技术不仅仅用于修复bug和动态发布新

    功能,我们在研究插件化技术的过程中,顺带开发

    出了Android虚拟机和双开技术,这是一个新的技

    术领域,可以摆脱Android原生系统的束缚,更快

    地运行App。

    本章还谈到了ReactNative,它也能修复bug和

    动态发布新功能,和Android插件化有异曲同工之

    妙。具体该采用哪门技术,取决于研发团队以H5为

    主还是以Android为主,取决于是否要发布到Google

    Play。第2章 Android底层知识

    这一章,改编自2017年我的系列文章《写给

    Android App开发人员看的Android底层知识》 [1]。

    在此基础上,扩充了PMS、双亲委托、ClassLoader

    等内容。这些Android底层知识都是学习Android插

    件化技术所必需的。

    本章介绍的这些Android底层知识基于Android

    6.0.1版本。我把本章以及整本书涉及的Android系

    统底层的类或aidl都搜集在一起,放在我的GitHub

    上 [2]

    ,读者可以下载并研读这些代码。

    [1] 文章地址:

    http:www.cnblogs.comJaxp6864103.html

    [2] 项目地址:

    https:github.comBaoBaoJianqiangAndroidSourceCode2.1 概述

    在我还是Android菜鸟的时候,有很多技术我

    都不太明白,也都找不到答案,比如,apk是怎么

    安装的?资源是怎么加载的?再比如,每本书都会

    讲AIDL,但我却从来没用过。四大组件也是这个

    问题,我只用过Activity,其他三个组件不但没用

    过,甚至连它们是做什么的,都不是很清楚。

    之所以这样,是因为我一直从事的是电商类

    App开发的工作,这类App基本是由列表页和详情

    页组成的,所以我每天面对的是Activity,会写这两

    类页面,把网络底层封装得足够强大就够了。绝大

    多数App开发人员都是如此。但直到接触Android的

    插件化编程和热修复技术,我才发现只掌握上述这

    些技术是远远不够的。

    市场上有很多介绍Android底层的书籍,网上

    也有很多文章,但大都是给ROM开发人员看的——

    动辄贴出几页代码,这类书不适合App开发人员去

    阅读学习。

    于是,这几年来,我一直在寻找这样一类知

    识,App开发人员看了能有助于他们更好地编写

    App程序,而又不需要知道太多这门技术底层的代码实现。

    这类知识分为两种:

    ·知道概念即可,比如Zygote,其实App开发人

    员是不需要了解Zygote的,知道有这么个东西是“孕

    育天地”的就够了,类似的还有SurfaceFlinger、WMS这些概念。

    ·需要知道内部原理,比如Binder,关于Binder

    的介绍铺天盖地,但对于App开发者,需要了解的

    是它的架构模型,只要有Client、Server以及

    ServiceManager就足够了。

    四大组件的底层通信机制都是基于Binder的,我们需要知道每个组件中,分别是哪些类扮演了

    Binder Client,哪些类扮演了Binder Server。知道这

    些概念有助于App开发人员进行插件化编程。

    接下来的章节将介绍以下概念,掌握了这些底

    层知识,就算是迈进Android插件化的大门了:

    ·Binder;

    ·AIDL;

    ·AMS;·四大组件的工作原理;

    ·PMS;

    ·App安装过程;

    ·ClassLoader以及双亲委托。2.2 Binder原理

    Binder的目的是解决跨进程通信。关于Binder

    的文章实在是太多了,每篇文章都能从Java层讲到

    C++层,App开发人员其实是没必要了解这么多内

    容的。我们看看对App开发有用的几个知识点:

    1)Binder分为Client和Server两个进程。

    注意,Client和Server是相对的。谁发消息,谁

    就是Client,谁接收消息,谁就是Server。举个例

    子,进程A和进程B之间使用Binder通信,进程A发

    消息给进程B,那么这时候A是Binder Client,B是

    Binder Server;进程B发消息给进程A,那么这时候

    B是Binder Client,A是Binder Server。其实,这么

    说虽然简单,但是不太严谨,我们先这么理解。

    2)Binder的组成。

    Binder的架构如图2-1所示,图中的IPC即为进

    程间通信,ServiceManager负责把Binder Server注册

    到一个容器中。

    有人把ServiceManager恰当地比喻成电话局,存储着每个住宅的座机电话。张三给李四打电话,拨打电话号码,会先转接到电话局,电话局的接线

    员查到这个电话号码的地址,因为李四的电话号码

    之前在电话局注册过,所以就能拨通;如果没注

    册,就会提示该号码不存在。

    对照着Android Binder机制和图2-1,张三就是

    Binder Client,李四就是Binder Server,电话局就是

    ServiceManager,电话局的接线员在这个过程中做

    了很多事情,对应着图中的Binder驱动。

    图2-1 Binder的组成(摘自田维术的博客)

    3)Binder的通信过程。Binder通信流程如图2-2所示,图中的SM即为

    ServiceManager。我们看到,Client不可以直接调用

    Server的add方法,因为它们在不同的进程中,这时

    候就需要Binder来帮忙了。

    图2-2 Binder的通信流程(摘自田维术的博客)

    ·首先,Server在SM容器中注册。

    ·其次,Client若要调用Server的add方法,就需

    要先获取Server对象,但是SM不会把真正的Server

    对象返回给Client,而是把Server的一个代理对象,也就是Proxy,返回给Client。

    ·再次,Client调用Proxy的add方法,ServiceManager会帮它去调用Server的add方法,并

    把结果返回给Client。

    以上这3步,Binder驱动出了很多力,但我们不

    需要知道Binder驱动的底层实现,这涉及C或C++的

    代码。我们要把有限的时间用在更有意义的事情

    上。

    App开发人员对Binder的掌握,这些内容就足

    够了。

    综上所述:

    1)学习Binder是为了更好地理解AIDL,基于

    AIDL模型,进而了解四大组件的原理。

    2)理解了Binder再看AMS和四大组件的关

    系,就像是Binder的两个进程Server和Client通信。2.3 AIDL原理

    AIDL是Binder的延伸。一定要先了解前文介绍

    的Binder,再来看AIDL。要按顺序阅读。

    Android系统中很多系统服务都是AIDL,比如

    剪切板。举这个例子是为了让App开发人员知道

    AIDL和我们距离非常近,无处不在。

    学习AIDL需要知道下面几个类:

    ·IBinder

    ·IInterface

    ·Binder

    ·Proxy

    ·Stub

    当我们自定义一个aidl文件时(比如

    MyAidl.aidl,里面有一个sum方法),Android

    Studio会帮我们生成一个类文件MyAidl.java,如图

    2-3所示。图2-3 AIDL中涉及的类图

    我们把MyAidl.java中的三个类拆开,就一目了

    然了,如下所示:

    public interface MyAidl extends android.os.IInterface {

    public int sum(int a, int b) throws android.os.RemoteException;

    }

    public abstract class Stub extends android.os.Binder implements jianqiang.com.hostapp.MyAidl {

    private static final java.lang.String DESCRIPTOR = jianqiang.com.hostapp.MyAidl;

    static final int TRANSACTION_sum = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

    Construct the stub at attach it to the interface.

    public Stub {

    this.attachInterface(this, DESCRIPTOR); }

    Cast an IBinder object into an jianqiang.com.hostapp.MyAidl interface, generating a proxy if needed.

    public static jianqiang.com.hostapp.MyAidl asInterface(android.os.IBinder obj) {

    if ((obj == null)) {

    return null;

    }

    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

    if (((iin != null) (iin instanceof jianqiang.com.hostapp.MyAidl))) {

    return ((jianqiang.com.hostapp.MyAidl) iin);

    }

    return new jianqiang.com.hostapp.MyAidl.Stub.Proxy(obj);

    }

    @Override

    public android.os.IBinder asBinder {

    return this;

    }

    @Override

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {

    switch (code) {

    case INTERFACE_TRANSACTION: {

    reply.writeString(DESCRIPTOR);

    return true;

    }

    case TRANSACTION_sum: {

    data.enforceInterface(DESCRIPTOR);

    int _arg0;

    _arg0 = data.readInt;

    int _arg1;

    _arg1 = data.readInt;

    int _result = this.sum(_arg0, _arg1);

    reply.writeNoException;

    reply.writeInt(_result);

    return true;

    }

    }

    return super.onTransact(code, data, reply, flags);

    }

    }class Proxy implements jianqiang.com.hostapp.MyAidl {

    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {

    mRemote = remote;

    }

    @Override

    public android.os.IBinder asBinder {

    return mRemote;

    }

    public java.lang.String getInterfaceDescriptor {

    return DESCRIPTOR;

    }

    @Override

    public int sum(int a, int b) throws android.os.RemoteException {

    android.os.Parcel _data = android.os.Parcel.obtain;

    android.os.Parcel _reply = android.os.Parcel.obtain;

    int _result;

    try {

    _data.writeInterfaceToken(DESCRIPTOR);

    _data.writeInt(a);

    _data.writeInt(b);

    mRemote.transact(Stub.TRANSACTION_sum, _data, _reply, 0);

    _reply.readException;

    _result = _reply.readInt;

    } finally {

    _reply.recycle;

    _data.recycle;

    }

    return _result;

    }

    }

    我曾经很不理解,为什么不是生成3个文件

    ——一个接口,两个类,清晰明了。都放在一个文

    件中,这是导致很多人看不懂AIDL的一个门槛。其实,Android这样设计是有道理的。当有多个

    AIDL类的时候,Stub和Proxy类就会重名,把它们

    放在各自的AIDL接口中,就区分开了。

    对照图2-3,我们继续来分析,Stub的sum方法

    是怎么调用到Proxy的sum方法的,然后又是怎么调

    用另一个进程的sum方法的?

    起决定作用的是Stub的asInterface方法和

    onTransact方法。其实图2-3没有画全,把完整的

    Binder Server也加上,应该如图2-4所示。

    图2-4 完整的AIDL类图1)从Client看,对于AIDL的使用者,我们写

    程序:

    MyAidl.Stub.asInterface(某IBinder对象).sum(1, 2); 最好在执行sum方法前判空。

    asInterface方法的作用是判断参数,也就是

    IBinder对象,和自己是否在同一个进程,如果

    ·是,则直接转换、直接使用,接下来则与

    Binder跨进程通信无关;

    ·否,则把这个IBinder参数包装成一个Proxy对

    象,这时调用Stub的sum方法,间接调用Proxy的

    sum方法,代码如下:

    return new MyAidl.Stub.Proxy(obj);

    2)Proxy在自己的sum方法中,会使用

    Parcelable来准备数据,把函数名称、函数参数都写

    入_data,让_reply接收函数返回值。最后使用

    IBinder的transact方法,就可把数据传给Binder的

    Server端了。

    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); 这里的mRemote就是asInterface方法传过来的obj参数3)Server则是通过onTransact方法接收Client进

    程传过来的数据,包括函数名称、函数参数,找到

    对应的函数(这里是sum),把参数喂进去,得到

    结果,返回。所以onTransact函数经历了读数据→

    执行要调用的函数→把执行结果再写数据的过程。

    下面要介绍的四大组件的原理,我们都可以对

    照图2-4来理解,比如,四大组件的启动和后续流

    程,都是在与ActivityManagerService(AMS)来来

    回回地通信,四大组件给AMS发消息,四大组件就

    是Binder Client,而AMS就是Binder Server;AMS

    发消息通知四大组件,那么角色互换。

    在四大组件中,比如Activity,是哪个类扮演

    了Stub的角色,哪个类扮演了Proxy的角色呢?这也

    是本章下面要介绍的,包括AMS、四大组件各自的

    运行原理。

    好戏即将开始。2.4 AMS

    如果站在四大组件的角度来看,AMS就是

    Binder中的Server。

    AMS(ActivityManagerService)从字面意思上

    看是管理Activity的,但其实四大组件都归它管。

    由此而说到了插件化,有两个困惑我已久的问

    题:

    1)App的安装过程,为什么不把apk解压缩到

    本地,这样读取图片就不用每次从apk包中读取

    了。这个问题,我们放到2.12节再详细说。

    2)为什么Hook永远是在Binder Client端,也就

    是四大组件这边,而不是在AMS那一侧进行

    Hook。

    这里要说清楚第二个问题。就拿Android剪切

    板举例吧。前面说过,这也是个Binder服务。

    AMS要负责和所有App的四大组件进行通信。

    如果在一个App中,在AMS层面把剪切板功能进行

    了Hook,那会导致Android系统所有的剪切板功能被Hook——这就是病毒了,如果是这样的话,Android系统早就死翘翘了。所以Android系统不允

    许我们这么做。

    我们只能在AMS的另一侧,即Client端,也就

    是四大组件这边做Hook,这样即使我们把剪切板功

    能进行了Hook,也只影响Hook代码所在的App,在

    别的App中,剪切板功能还是正常的。

    关于AMS我们就说这么多,下面的小节在介绍

    四大组件时,会反复提到四大组件和AMS的跨进程

    通信。2.5 Activity工作原理

    对于App的开发人员而言,Activity是四大组件

    中用得最多的,也是最复杂的。这里只讲述Activity

    的启动和通信原理。

    2.5.1 App是怎么启动的

    在手机屏幕上点击某个App的图标,假设是斗

    鱼App,这个App的首页(或引导页)就出现在我

    们面前了。这个看似简单的操作,背后经历了

    Activity和AMS的反反复复的通信过程。

    首先要搞清楚,在手机屏幕上点击App的快捷

    图标,此时手机屏幕就是一个Activity,而这个

    Activity所在的App,业界称之为Launcher。

    Launcher是各手机系统厂商提供的,比拼的是谁的

    Launcher绚丽和人性化。

    Launcher这个App,其实和我们做的各种应用

    类App没有什么不同,我们大家用过华为、小米之

    类的手机,预装App以及我们下载的各种App,都

    显示在Launcher上,每个App表现为一个图标。图

    标多了可以分页,可以分组,此外,Launcher也会发起网络请求,调用天气的数据,显示在屏幕上,即人性化的界面。

    还记得我们在开发一款App时,在

    AndvoidManifest文件中是怎么定义默认启动

    Activity的吗?代码如下所示:

    

    

    

    

    而Launcher中为每个App的图标提供了启动这

    个App所需要的Intent信息,如下所示(以斗鱼App

    为例):

    action:android.intent.action.MAIN

    category: android.intent.category.LAUNCHER

    cmp: 斗鱼的包名+ 首页Activity名

    这些信息是App安装(或Android系统启动)的

    时候,PackageManager-Service从斗鱼的apk包的

    AndroidManifest文件中读取到的。所以点击图标就

    启动了斗鱼App的首页。

    2.5.2 启动App并非那么简单前文只是App启动的一个最简单的描述。

    仔细看,我们会发现,Launcher和斗鱼是两个

    不同的App,它们位于不同的进程中,它们之间的

    通信是通过Binder完成的——这时候AMS出场了。

    仍然以启动斗鱼App为例,整体流程分为以下7

    个阶段 [1]。

    1)Launcher通知AMS,要启动斗鱼App,而且

    指定要启动斗鱼App的哪个页面(也就是首页)。

    2)AMS通知Launcher,“好了我知道了,没你

    什么事了”。同时,把要启动的首页记下来。

    3)Launcher当前页面进入Paused状态,然后通

    知AMS,“我睡了,你可以去找斗鱼App了”。

    4)AMS检查斗鱼App是否已经启动了。是,则唤起斗鱼App即可。否,就要启动一个新的进

    程。AMS在新进程中创建一个ActivityThread对象,启动其中的main函数。

    5)斗鱼App启动后,通知AMS,“我启动好

    了”。

    6)AMS翻出之前在2)中存的值,告诉斗鱼App,启动哪个页面。

    7)斗鱼App启动首页,创建Context并与首页

    Activity关联。然后调用首页Activity的onCreate函

    数。

    至此启动流程完成,可分成两部分:第1~3阶

    段,Launcher和AMS相互通信;第4~7阶段,斗鱼

    App和AMS相互通信。

    这会涉及一堆类,列举如下,在接下来的分析

    中,我们会遇到这些类。

    ·Instrumentation;

    ·ActivityThread;

    ·H;

    ·LoadedApk;

    ·AMS;

    ·ActivityManagerNative和

    ActivityManagerProxy;

    ·ApplicationThread和ApplicationThreadProxy。第1阶段:Launcher通知AMS

    第1步和第2步——点击图标启动App。

    从图2-5中我们看到,点击Launcher上的斗鱼

    App的快捷图标,这时会调用Launcher的

    startActivitySafely方法,其实还是会调用Activity的

    startActivity方法,intent中带着要启动斗鱼App所需

    要的如下关键信息:

    action = android.intent.action.MAIN

    category = android.intent.category.LAUNCHER

    cmp = com.douyu.activity.MainActivity

    第3行代码是我推测的,就是斗鱼App在

    AndroidManifest文件中指定为首页的那个Activity。

    这样,我们终于明白,为什么在AndroidManifest

    中,给首页指定action和category了。在App的安装

    过程中,会把这个信息“记录”在Launcher的斗鱼启

    动快捷图标中。关于App的安装过程,我会在后面

    的文章详细介绍。图2-5 Launcher通知AMS的流程

    startActivity这个方法,如果我们看它的实现,会发现它调来调去,经过一系列startActivity的重载

    方法,最后会走到startActivityForResult方法。代码

    如下:

    public void startActivity(Intent intent, @Nullable Bundle options) {

    if (options != null) {

    startActivityForResult(intent, -1, options);

    } else {

    startActivityForResult(intent, -1);

    }

    }

    我们知道startActivityForResult需要两个参数,一个是intent,另一个是code,这里code是-1,表示

    Launcher才不关心斗鱼的App是否启动成功的返回

    结果。第3步——startActivityForResult。

    Activity内部会保持一个对Instrumentation的引

    用,但凡是做过App单元测试的读者,对这个类都

    很熟悉,习惯上称之为“仪表盘”。

    在startActivityForResult方法的实现中,会调用

    Instrumentation的execStartActivity方法。代码如

    下:

    public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {

    前后省略一些代码

    Instrumentation.ActivityResult ar =

    mInstrumentation.execStartActivity(

    this, mMainThread.getApplicationThread, mToken, this,intent, requestCode, options);

    }

    看到这里,我们发现有个mMainThread变量,这是一个ActivityThread类型的变量。这个家伙的来

    头可不小。

    ActivityThread,就是主线程,也就是UI线程,它是在App启动时创建的,它代表了App应用程

    序。读者不禁会问,ActivityThread代表了App应用

    程序,那Application类岂不是被架空了?其实,Application对我们App开发人员来说也许很重要,但是在Android系统中还真的没那么重要,它就是个上下文。Activity不是有一个Context上下文吗?

    Application就是整个ActivityThread的上下文。

    ActivityThread则没有那么简单。它里面有main

    函数。我们知道大部分程序都有main函数,比如

    Java和C,iPhone App用到的Objective-C也有main

    函数。那么,Android的main函数藏在哪里?就在

    ActivityThread中,如下所示,代码太多,此处只截

    取了一部分:

    public final class ActivityThread {

    public static void main(String[] args) {

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ActivityThreadMain);

    SamplingProfilerIntegration.start;

    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser;

    以下省略了很多代码

    }

    }

    又有人会问:不是说谁写的程序,谁就要提供

    main函数作为入口吗?但Android App却不是这样

    的。Android App的main函数在ActivityThread里

    面,而这个类是Android系统提供的底层类,不是

    我们提供的。

    所以这就是Android有趣的地方。Android App

    的入口是AndroidManifest中定义默认启动Activity。这是由Android AMS与四大组件的通信机制决定

    的。

    上面的代码中传递了两个很重要的参数:

    ·通过ActivityThread的getApplicationThread方法

    取到一个Binder对象,这个对象的类型为

    ApplicationThread,代表了Launcher所在的App进

    程。

    ·mToken也是一个Binder对象,代表Launcher这

    个Activity也通过Instrumentation传给AMS,AMS一

    查电话簿,就知道是谁向AMS发起了请求。

    这两个参数是伏笔,传递给AMS,以后AMS

    想反过来通知Launcher,就能通过这两个参数找到

    Launcher。

    第4步——Instrumentation的execStartActivity方

    法。

    Instrumentation绝对是Android测试团队的最

    爱,因为它可以帮助我们启动Activity。

    回到App的启动过程来,在Instrumentation的

    execStartActivity方法中:public class Instrumentation {

    public ActivityResult execStartActivity(

    Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {

    省略一些代码

    try {

    省略一些代码

    int result = ActivityManagerNative.getDefault

    .startActivity(whoThread, who.getBasePackageName, intent,intent.resolveTypeIfNeeded(who.getContentResolver),token, target != null ? target.mEmbeddedID : null,requestCode, 0, null, options);

    省略一些代码

    } catch (RemoteException e) {

    throw new RuntimeException(Failure from system, e);

    }

    return null;

    }

    }

    这就是一个透传,借助Instrumentation,Activity把数据传递给ActivityManagerNativ。

    第5步——AMN的getDefault方法。

    ActivityManagerNative(AMN),这个类后面

    会反复用到。

    ServiceManager是一个容器类。AMN通过

    getDefault方法,从ServiceManager中取得一个名为

    activity的对象,然后把它包装成一个ActivityManagerProxy对象(AMP),AMP就是

    AMS的代理对象。

    AMN的getDefault方法返回类型为

    IActivityManager,而不是AMP。IActivityManager

    是一个实现了IInterface的接口,里面定义了四大组

    件所有的生命周期。

    AMN和AMP都实现了IActivityManager接口,AMS继承自AMN,对照着前面AIDL的UML,就不

    难理解了。AMN和AMP如图2-6所示。

    第6步——AMP的startActivity方法。

    看到这里,你会发现AMP的startActivity方法和

    AIDL的Proxy方法,是一模一样的,写入数据到另

    一个进程,也就是AMS,然后等待AMS返回结

    果。

    至此,第1阶段的工作就做完了。图2-6 AMNAMP的类图

    第2阶段:AMS处理Launcher传过来的信息

    先来看一下AMP的startActivity方法的实现:

    class ActivityManagerProxy implements IActivityManager

    {

    public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,String resolvedType, IBinder resultTo, String resultWho, int requestCode,int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {

    Parcel data = Parcel.obtain;

    Parcel reply = Parcel.obtain;

    data.writeInterfaceToken(IActivityManager.descriptor);

    data.writeStrongBinder(caller != null ? caller.asBinder : null);

    data.writeString(callingPackage); mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);

    reply.readException;

    int result = reply.readInt;

    reply.recycle;

    data.recycle;

    return result;

    }

    }

    这个阶段主要是Binder的Server端在做事情。

    因为我们没有机会修改Binder的Server端逻辑,所

    以这个阶段看起来非常“枯燥”,主要过程如下。

    1)Binder(也就是AMNAMP)和AMS通信,肯定每次是做不同的事情,比如这次Launcher要启

    动斗鱼App,那么会发送类型为START_ACTIVITY

    的请求给AMS,同时会告诉AMS要启动哪个

    Activity。

    2)AMS说,“好,我知道了。”然后它会干一

    件很有趣的事情——检查斗鱼App中的

    AndroidManifest文件,是否存在要启动的Activity。

    如果不存在,就抛出Activity not found的错误,各

    位做App的读者对这个异常应该再熟悉不过了,经

    常写了个Activity而忘记在AndroidManifest中声明,就报这个错误。这是因为AMS在这里做检查。不管

    是新启动一个App的首页,还是在App内部跳转到

    另一个Activity,都会做这个检查。3)AMS通知Launcher,“没你什么事了,你洗

    洗睡吧。”那么AMS是通过什么途径告诉Launcher

    的呢?

    前面讲过,Binder的双方进行通信是平等的,谁发消息谁就是Client,接收的一方就是Server。

    Client这边会调用Server的代理对象。对于从

    Launcher发来的消息,通过AMS的代理对象AMP发

    送给AMS。

    那么当AMS想给Launcher发消息,又该怎么办

    呢?前面不是把Launcher以及它所在的进程给传过

    来了吗?它在AMS这边保存为一个ActivityRecord

    对象,这个对象里面有一个

    ApplicationThreadProxy,顾名思义,这就是一个

    Binder代理对象。它的Binder真身,也就是

    ApplicationThread。

    站在AIDL的角度,来画这张图,如图2-7所

    示。图2-7 IApplicationThread的类簇

    结论是,AMS通过ApplicationThreadProxy发送

    消息,而App端则通过ApplicationThread来接收这个

    消息。

    第3阶段:Launcher去休眠,然后通知

    AMS:“我真的已经睡了”

    此阶段的过程如图2-8所示。图2-8 Launcher再次通知AMS

    ApplicationThread(APT),它和

    ApplicationThreadProxy(ATP)的关系,我们在第

    2阶段已经介绍过了。

    APT接收到来自AMS的消息后,调用

    ActivityThread的sendMessage方法,向Launcher的主

    线程消息队列发送一个PAUSE_ACTIVITY消息。

    前面说过,ActivityThread就是主线程(UI线

    程)

    看到下面的代码是不是很亲切?

    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {

    if (DEBUG_MESSAGES) Slog.v(

    TAG, SCHEDULE + what + + mH.codeToString(what)

    + : + arg1 + + obj);

    Message msg = Message.obtain; msg.what = what;

    msg.obj = obj;

    msg.arg1 = arg1;

    msg.arg2 = arg2;

    if (async) {

    msg.setAsynchronous(true);

    }

    mH.sendMessage(msg);

    }

    发送消息是通过一个名为H的Handler类的完成

    的,这个H类的名字真有个性,很容易记住。

    做App的读者都知道,继承自Handler类的子

    类,就要实现handleMessage方法,这里是一个

    switch…case语句,处理各种各样的消息,PAUSE_ACTIVITY消息只是其中一种。由此也能

    预见,AMS给Activity发送的所有消息,以及给其

    他三大组件发送的所有消息,都从H这里经过。为

    什么要强调这一点呢?既然四大组件都走这条路,那么就可以从这里入手做插件化技术,这个我们以

    后介绍插件化技术的时候会讲到。

    代码如下:

    public final class ActivityThread {

    private class H extends Handler {

    省略一些代码

    public void handleMessage(Message msg) {

    if (DEBUG_MESSAGES) Slog.v(TAG, >>> handling: + codeToString(msg.what));

    switch (msg.what) { case PAUSE_ACTIVITY:

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, activityPause);

    handlePauseActivity((IBinder)msg.obj, false, (msg.arg11) != 0, msg.arg2, (msg.arg12) != 0);

    maybeSnapshot;

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

    break;

    };

    省略一些代码

    }

    }

    H对PAUSE_ACTIVITY消息的处理,如上面的

    代码,是调用ActivityThread的handlePauseActivity

    方法。这个方法做两件事:

    ·ActivityThread里面有一个mActivities集合,保

    存当前App也就是Launcher中所有打开的Activity,把它找出来,让它休眠。

    ·通过AMP通知AMS,“我真的休眠了。”

    你可能会找不到H和APT这两个类文件,那是

    因为它们都是ActivityThread的内嵌类。

    至此,Launcher的工作完成了。你可以看到在

    这个过程中,各个类都起到了什么作用。

    第4阶段:AMS启动新的进程

    接下来又轮到AMS做事了,你会发现我不太喜欢讲解AMS的流程,甚至都不画UML图,因为这

    部分逻辑和App开发人员关系不是很大,我尽量说

    得简单一些,把流程说清楚即可。

    AMS接下来要启动斗鱼App的首页,因为斗鱼

    App不在后台进程中,所以要启动一个新的进程。

    这里调用的是Process.start方法,并且指定了

    ActivityThread的main函数为入口函数。代码如下:

    int pid = Process.start(“android.app.ActivityThread”,mSimpleProcessManagement ? app.processName : gid, debugFlags, null);

    第5阶段:新的进程启动,以ActivityThread的

    main函数作为入口

    启动新进程,其实就是启动一个新的App,如

    图2-9所示。图2-9 ActivityThread启动

    在启动新进程的时候,为这个进程创建

    ActivityThread对象,这就是我们耳熟能详的主线程

    (UI线程)。

    创建好UI线程后,立刻进入ActivityThread的

    main函数,接下来要做两件具有重大意义的事情:

    1)创建一个主线程Looper,也就是

    MainLooper。注意,MainLooper就是在这里创建

    的。

    2)创建Application。注意,Application是在这

    里生成的。

    主线程在收到BIND_APPLICATION消息后,根据传递过来的ApplicationInfo创建一个对应的

    LoadedApk对象(标志当前APK信息),然后创建

    ContextImpl对象(标志当前进程的环境),紧接着

    通过反射创建目标Application,并调用其attach方

    法,将ContextImpl对象设置为目标Application的上

    下文环境,最后调用Application的onCreate函数,做一些初始工作。

    App开发人员对Application非常熟悉,因为我

    们可以在其中写代码,进行一些全局的控制,所以

    我们通常认为Application是掌控全局的,其实

    Application的地位在App中并没有那么重要,它就

    是一个Context上下文,仅此而已。

    App中的灵魂是ActivityThread,也就是主线

    程,只是这个类对于App开发人员是访问不到的,但使用反射是可以修改这个类的一些行为的。

    创建新App的最后就是告诉AMS“我启动好

    了”,同时把自己的ActivityThread对象发送给

    AMS。从此以后,AMS的电话簿中就多了这个新

    的App的登记信息,AMS以后就通过这个

    ActivityThread对象,向这个App发送消息。

    第6阶段:AMS告诉新App启动哪个ActivityAMS把传入的ActivityThread对象转为一个

    ApplicationThread对象,用于以后和这个App跨进程

    通信。还记得APT和ATP的关系吗?参见图2-7。

    在第1阶段,Launcher通知AMS,要启动斗鱼

    App的哪个Activity。在第2阶段,这个信息被AMS

    存下来。在第6阶段,AMS从过去的记录中翻出来

    要启动哪个Activity,然后通过ATP告诉App。

    第7阶段:启动斗鱼首页Activity

    毕其功于一役,尽在第7阶段。这是最后一

    步,过程如图2-10所示。

    图2-10 首页Activity最终被启动

    在Binder的另一端,App通过APT接收到AMS

    的消息,仍然在H的handleMessage方法的switch语句中处理,只不过,这次消息的类型是

    LAUNCH_ACTIVITY:

    public final class ActivityThread {

    private class H extends Handler {

    省略一些代码

    public void handleMessage(Message msg) {

    if (DEBUG_MESSAGES) Slog.v(TAG, >>> handling: + codeToString(msg.what));

    switch (msg.what) {

    case LAUNCH_ACTIVITY: {

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, activityStart);

    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

    r.packageInfo = getPackageInfoNoCheck(

    r.activityInfo.applicationInfo, r.compatInfo);

    handleLaunchActivity(r, null);

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

    } break;

    省略一些代码

    }

    }

    ActivityClientRecord是什么?这是AMS传递过

    来的要启动的Activity。

    我们仔细看那个getPackageInfoNoCheck方法,这个方法会提取apk中的所有资源,然后设置r的

    packageInfo属性。这个属性的类型很有名,叫做

    LoadedApk。注意,这个地方也是插件化技术渗入

    的一个点。

    在H的这个分支中,又反过来回调ActivityThread的handleLaunchActivity方法(图2-

    11),你一定会觉得很绕。其实我一直觉得,ActivityThread和H合并成一个类也没问题。

    图2-11 ActivityThread与H的交互

    重新看一下这个过程,每次都是APT执行

    ActivityThread的sendMessage方法,在这个方法

    中,把消息拼装一下,然后扔给H的swicth语句去

    分析,来决定要执行ActivityThread的那个方法。每

    次都是这样,习惯就好了。

    handleLaunchActivity方法都做哪些事呢?

    1)通过Instrumentation的newActivity方法,创建要启动的Activity实例。

    2)为这个Activity创建一个上下文Context对

    象,并与Activity进行关联。

    3)通过Instrumentation的callActivityOnCreate

    方法,执行Activity的onCreate方法,从而启动

    Activity。看到这里是不是很熟悉很亲切?

    至此,App启动完毕。这个流程经过了很多次

    握手,App和ASM频繁地向对方发送消息,而发送

    消息的机制,是建立在Binder的基础之上的。

    [1] 这个流程分析,基本上是基于Android 6.0的源码

    进行的。2.6 App内部的页面跳转

    在介绍完App的启动流程后,我们发现,其实

    就是启动一个App的首页。接下来我们看App内部

    页面的跳转。

    从ActivityA跳转到ActivityB,其实可以把

    ActivityA看作Launcher,那么这个跳转过程和App

    的启动过程就很像了。有了前面的分析基础,你会

    发现这个过程不需要重新启动一个新的进程,所以

    可以省略App启动过程中的一些步骤,流程简化

    为:

    1)ActivityA向AMS发送一个启动ActivityB的

    消息。

    2)AMS保存ActivityB的信息,然后通知

    App,“你洗洗睡吧”。

    3)ActivityA进入休眠,然后通知AMS,“我休

    眠了(onPaused)。”

    4)AMS发现ActivityB所在的进程就是

    ActivityA所在的进程,所以不需要重新启动新的进

    程,它就会通知App启动ActivityB。5)App启动ActivityB。

    为了更好地理解上述文字,可参考图2-12。

    图2-12 启动一个新的Activity

    整体流程此处不再赘述,和上一小节介绍的

    App启动流程是基本一致的。以上的分析,仅限于ActivityA和ActivityB在相

    同的进程中,如果在AndroidManifest中指定不在同

    一个进程中的两个Activity,那么就又是另一套流程

    了,但是整体流程大同小异。2.7 Context家族史

    Activity、Service、Application其实是亲戚关

    系,如图2-13所示。

    图2-13 Context家族

    Activity因为有了一层Theme,所以中间有个

    ContextThemeWrapper,相当于它是Service和

    Application的侄子。ContextWrapper只是一个包装类,没有任何具

    体的实现,真正的逻辑都在ContextImpl里面。

    一个应用包含的Context个数=Service个数

    +Activity个数+1(Application类本身对应一个

    Context对象)。

    应用程序中包含多个ContextImpl对象,而其内

    部变量mPackageInfo指向同一个PackageInfo对象。

    我们以Activity为例,看看Activity和Context的联系

    和区别。

    我们知道,跳转到一个新的Activity要写如下

    代码:

    btnNormal.setOnClickListener(new View.OnClickListener {

    @Override

    public void onClick(View view) {

    Intent intent = new Intent(Intent.Action.VIEW);

    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    intent.setData(Uri.parse(https:www.baidu.com));

    startActivity(intent);

    }

    });

    我们还知道,也可以在Activity中使用

    getApplicationContext方法获取Context上下文信息,然后使用Context的startActivity方法,启动一个新的

    Activity:btnNormal.setOnClickListener(new View.OnClickListener {

    @Override

    public void onClick(View view) {

    Intent intent = new Intent(Intent.Action.VIEW);

    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    intent.setData(Uri.parse(https:www.baidu.com));

    getApplicationContext.startActivity(intent);

    }

    });

    这二者的区别是什么?通过图2-14便可明了。

    图2-14 两种startActivity

    关于Context的startActivity方法,我看了在

    ContextImpl中的源码实现,仍然是从ActivityThread中取出Instrumentation,然后执行execStartActivity

    方法,这和使用Activity的startActivity方法的流程是

    一样的。

    还记得我们前面分析的App启动流程么?在第5

    阶段,创建App进程的时候,先创建的

    ActivityThread,再创建的Application。Application

    的生命周期是跟整个App相关联的。而

    getApplicationContext得到的Context,就是从

    ActivityThread中取出来的Application对象。代码如

    下:

    class ContextImpl extends Context {

    @Override

    public void startActivity(Intent intent, Bundle options) {

    warnIfCallingFromSystemProcess;

    if ((intent.getFlagsIntent.FLAG_ACTIVITY_NEW_TASK) == 0) {

    throw new AndroidRuntimeException(

    Calling startActivity from outside of an Activity

    + context requires the FLAG_ACTIVITY_NEW_TASK flag.

    + Is this really what you want?);

    }

    mMainThread.getInstrumentation.execStartActivity(

    getOuterContext, mMainThread.getApplicationThread, null,(Activity) null, intent, -1, options);

    }

    }2.8 Service工作原理

    众所周知,Service有两套流程,一套是启动流

    程,另一套是绑定流程,如图2-15所示。图2-15 Service的两套流程

    2.8.1 在新进程启动Service我们先看Service启动过程,假设要启动的

    Service是在一个新的进程中,启动过程可分为5个

    阶段:

    1)App向AMS发送一个启动Service的消息。

    2)AMS检查启动Service的进程是否存在,如

    果不存在,先把Service信息存下来,然后创建一个

    新的进程。

    3)新进程启动后,通知AMS,“我可以啦”。

    4)AMS把刚才保存的Service信息发送给新进

    程。

    5)新进程启动Service。

    我们详细分析这5个阶段。

    第1阶段:App向AMS发送一个启动Service的

    消息

    和Activity非常像,仍然是通过AMMAMP把要

    启动的Service信息发送给AMS,如图2-16所示。图2-16 App向AMS发送一个启动Service的消息

    第2阶段:AMS创建新的进程

    AMS检查Service是否在AndroidManifest中声明

    了,若没声明则会直接报错。AMS检查启动Service

    的进程是否存在,如果不存在,先把Service信息存

    下来,然后创建一个新的进程。在AMS中,每个

    Service,都使用ServiceRecord对象来保存。

    第3阶段:新进程启动后,通知AMS,“我可以

    啦。”

    Service所在的新进程启动的过程,与前面介绍App启动时的过程相似。

    新进程启动后,也会创建新的ActivityThread,然后把ActivityThread对象通过AMP传递给AMS,告诉AMS,“新进程启动成功了”。

    第4阶段:AMS把刚才保存的Service信息发送

    给新进程

    AMS把传进来的ActivityThread对象改造为

    ApplicationThreadProxy,也就是ATP,通过ATP把

    要启动的Service信息发送给新进程。

    第5阶段:新进程启动Service

    新进程启动Service过程如图2-17所示。图2-17 启动一个Service

    新进程通过ApplicationThread接收到AMS的信

    息,和前面介绍的启动Activity的最后一步相同,借

    助ActivityThread和H,执行Service的onCreate方

    法。在此期间,为Service创建了Context上下文对

    象,并与Service相关联。

    需要重点关注的是ActivityThread的

    handleCreateService方法,代码如下:

    private void handleCreateService(CreateServiceData data) {

    LoadedApk packageInfo = getPackageInfoNoCheck(

    data.info.applicationInfo, data.compatInfo);

    Service service = null;

    try {

    java.lang.ClassLoader cl = packageInfo.getClassLoader;

    service = (Service) cl.loadClass(data.info.name).newInstance;

    }

    省略一些代码

    }

    你会发现,这段代码和前面介绍的

    handleLaunchActivity差不多,都是从PMS中取出包

    的信息packageInfo,这是一个LoadedApk对象,然

    后获取它的classloader,反射出来一个类的对象,在这里反射的是Service。

    四大组件的逻辑都是如此,所以我们要做插件化,可以在这里做文章,换成插件的classloader,加载插件中的四大组件。

    至此,我们在一个新的进程中启动了一个

    Service。

    2.8.2 启动同一进程的Service

    如果是在当前进程启动这个Service,那么上面

    的步骤就简化为:

    1)App向AMS发送一个启动Service的消息。

    2)AMS例行检查,比如Service是否声明了,把Service在AMS这边注册。AMS发现要启动的

    Service就是App所在的Service,于是通知App启动

    这个Service。

    3)App启动Service。

    我们看到,没有了启动新进程的过程。

    2.8.3 在同一进程绑定Service

    如果要在当前进程绑定这个Service,可分为以

    下5个阶段:1)App向AMS发送一个绑定Service的消息。

    2)AMS例行检查,比如Service是否声明了,把Service在AMS这边注册。AMS发现要启动的

    Service就是App所在的Service,就先通知App启动

    这个Service,然后再通知App对Service进行绑定操

    作。

    3)App收到AMS第1个消息,启动Service。

    4)App收到AMS第2个消息,绑定Service,并

    把一个Binder对象传给AMS。

    5)AMS把接收到的Binder对象发送给App。

    你也许会问,都在一个进程,App内部直接使

    用Binder对象不就好了,其实,要考虑不在一个进

    程的场景,代码又不能写两份,两套逻辑,所以就

    都放在一起了,即使在同一个进程,也要绕着AMS

    走一圈。接下来,我们详细分析一下这5个阶段。

    第1阶段:App向AMS发送一个绑定Service的

    消息

    具体过程如图2-18所示。

    第2阶段:AMS创建新的进程AMS检查Service是否在AndroidManifest中声明

    了,没声明会直接报错。

    AMS检查启动Service的进程是否存在,如果不

    存在,先把Service信息存下来,然后创建一个新的

    进程。在AMS中,每个Service,都使用

    ServiceRecord对象来保存。

    第3阶段:新进程启动后,通知AMS,“我可以

    啦”

    Service所在的新进程启动的过程,就和前面介

    绍的App启动时的过程差不多。

    新进程启动后,也会创建新的ActivityThread,然后把ActivityThread对象通过AMP传递给AMS,告诉AMS,“新进程启动成功了”。图2-18 App向AMS发送一个绑定Service的消息

    第4阶段:处理第2个消息,绑定Service

    具体过程如图2-19所示。图2-19 处理第2个消息

    第5阶段:把Binder对象发送给App

    这一步是要仔细说的,因为AMS把Binder对象

    传给App,这里没用ATP和APT,而是利用AIDL来

    实现的,这个AIDL的名字是IServiceConnection,如图2-20所示。图2-20 AIDL原理

    ServiceDispatcher的connect方法最终会调用

    ServiceConnection的onServiceConnected方法,这个

    方法我们已经很熟悉了。App开发人员在这个方法

    中拿到connection,就可以做自己的事情了。

    好了,关于Service的底层知识,我们就全都介

    绍完了。当你再去编写一个Service时,一定能感到

    对这个组件理解得更透彻了。2.9 BroadcastReceiver工作原理

    BroadcastReceiver就是广播,简称Receiver。

    很多App开发人员表示,从来没用过

    Receiver。其实,对于音乐播放类App,Service和

    Receiver用的还是很多的,如果你用过QQ音乐,App退到后台,音乐照样播放不会停止,这就是

    Service在后台起的作用。

    在前台的Activity,点击“停止”按钮,就会给后

    台Service发送一个Receiver,通知它停止播放音

    乐;点击“播放”按钮,仍然发送这个Receiver,只

    是携带的值变了,所以Service收到请求后播放音

    乐。

    反过来,后台Service每播放完一首音乐,接下

    来准备播放下一首音乐的时候,就会给前台Activity

    发Receiver,让Activity显示下一首音乐的名称。

    所以音乐播放器的原理,就是一个前后台

    Activity和Service互相发送和接收Receiver的过程,如图2-21所示。图2-21 音乐播放器的两个Receiver

    Receiver分静态广播和动态广播两种。

    在AndroidManifest中声明的Receiver,是静态

    广播:

    

    

    

    在程序中手动写注册代码的是动态广播:

    activityReceiver = new ActivityReceiver;

    IntentFilter filter = new IntentFilter;

    Filter.addAction(UPDATE_ACTION);

    registerReceiver(activityReceiver, filter);Intent intent = new Intent(this, MyService.class);

    startService(intent);

    二者具有相同的功能,只是写法不同。既然如

    此,我们就可以把所有静态广播都改为动态广播,这就避免在AndroidManifest文件中声明了,也避免

    了AMS检查。你想到什么?对,Receiver的插件化

    解决方案就是这个思路。

    接下来我们看Receiver是怎么和AMS打交道

    的,分为两部分:一是注册,二是发送广播。

    你只有注册了这个广播,发送这个广播时,才

    能通知你执行onReceive方法。

    我们就以音乐播放器为例,在Activity注册

    Receiver,在Service发送广播。Service播放下一首

    音乐时,会通知Activity修改当前正在播放的音乐名

    称。

    2.9.1 注册过程

    注册过程如下:

    1)在Activity中,注册Receiver,并通知

    AMS,如图2-22所示。图2-22 注册Receiver的流程

    这里Activity使用了Context提供的

    registerReceiver方法,然后通过AMNAMP,把一

    个Receiver传给AMS。在创建这个Receiver对象的时

    候,需要为Receiver指定IntentFilter,这个filter就是

    Receiver的身份证,用来描述Receiver。

    在Context的registerReceiver方法中,它会使用

    PMS获取到包的信息,也就是LoadedApk对象。就

    是这个LoadedApk对象,它的getReceiverDispatcher

    方法,将Receiver封装成一个实现了IIntentReceiver

    接口的Binder对象。

    我们就是将这个Binder对象和filter传递给

    AMS。但只传递Receiver给AMS是不够的,当发送

    广播时,AMS不知道该发给谁,所以Activity所在的进程还要把自身对象也发送给AMS。

    2)AMS收到消息后,就会把上面这些信息,存在一个列表中,这个列表中保存了所有的

    Receiver。

    注意,这里都是在注册动态Receiver。静态

    Receiver什么时候注册到AMS呢?是在App安装的

    时候。PMS会解析AndroidManifest中的四大组件信

    息,把其中的Receiver存起来。

    动态Receiver和静态Receiver分别存在AMS不

    同的变量中,在发送广播的时候,会把两种

    Receiver合并到一起,然后依次发送。其中动态的

    排在静态的前面,所以动态Receiver永远优先于静

    态Receiver收到消息。

    此外,Android系统每次启动的时候,也会把

    静态广播接收者注册到AMS。因为Android系统每

    次启动时,都会重新安装所有的apk, ......

您现在查看是摘要介绍页, 详见PDF附件(7335KB,679页)