第2章 Android底层知识
这一章,改编自2017年我的系列文章《写给Android App开发人员看的Android底层知识》。在此基础上,扩充了PMS、双亲委托、ClassLoader等内容。这些Android底层知识都是学习Android插件化技术所必需的。
本章介绍的这些Android底层知识基于Android 6.0.1版本。我把本章以及整本书涉及的Android系统底层的类或aidl都搜集在一起,放在我的GitHub上,读者可以下载并研读这些代码。
2.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注册到一个容器中。
图2-1 Binder的组成(摘自田维术的博客)
有人把ServiceManager恰当地比喻成电话局,存储着每个住宅的座机电话。张三给李四打电话,拨打电话号码,会先转接到电话局,电话局的接线员查到这个电话号码的地址,因为李四的电话号码之前在电话局注册过,所以就能拨通;如果没注册,就会提示该号码不存在。
对照着Android Binder机制和图2-1,张三就是Binder Client,李四就是Binder Server,电话局就是ServiceManager,电话局的接线员在这个过程中做了很多事情,对应着图中的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的吗?代码如下所示:
<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </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)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"
图2-5 Launcher通知AMS的流程
第3行代码是我推测的,就是斗鱼App在AndroidManifest文件中指定为首页的那个Activity。这样,我们终于明白,为什么在AndroidManifest中,给首页指定action和category了。在App的安装过程中,会把这个信息“记录”在Launcher的斗鱼启动快捷图标中。关于App的安装过程,我会在后面的文章详细介绍。
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所示。
图2-6 AMN/AMP的类图
第6步——AMP的startActivity方法。
看到这里,你会发现AMP的startActivity方法和AIDL的Proxy方法,是一模一样的,写入数据到另一个进程,也就是AMS,然后等待AMS返回结果。
至此,第1阶段的工作就做完了。
第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(也就是AMN/AMP)和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.arg1&1) ! = 0, msg. arg2, (msg.arg1&2) ! = 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启动哪个Activity
AMS把传入的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的基础之上的。
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.getFlags()&Intent.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非常像,仍然是通过AMM/AMP把要启动的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-18 App向AMS发送一个绑定Service的消息
第2阶段:AMS创建新的进程
AMS检查Service是否在AndroidManifest中声明了,没声明会直接报错。
AMS检查启动Service的进程是否存在,如果不存在,先把Service信息存下来,然后创建一个新的进程。在AMS中,每个Service,都使用ServiceRecord对象来保存。
第3阶段:新进程启动后,通知AMS, “我可以啦”
Service所在的新进程启动的过程,就和前面介绍的App启动时的过程差不多。
新进程启动后,也会创建新的ActivityThread,然后把ActivityThread对象通过AMP传递给AMS,告诉AMS, “新进程启动成功了”。
第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,是静态广播:
<receiver android:name=".MyReceiver"> <intent-filter> <action android:name="baobao" /> </intent-filter> </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方法,然后通过AMN/AMP,把一个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,详细流程我们会在后面PMS的相关章节看到。
2.9.2 发送广播的流程
发送广播的流程大致有三个步骤:
1)在Service中,通过AMM/AMP,发送广播给AMS,广播中携带着filter。
2)AMS收到这个广播后,在Receiver列表中,根据filter找到对应的Receiver,可能是多个,把它们都放到一个广播队列中。最后向AMS的消息队列发送一个消息。
当消息队列中的这个消息被处理时,AMS就从广播队列中找到合适的Receiver,向广播接收者所在的进程发送广播。
3)Receiver所在的进程收到广播,并没有把广播直接发给Receiver,而是将广播封装成一个消息,发送到主线程的消息队列中,当这个消息被处理时,才会把这个消息中的广播发送给Receiver。
下面通过图2-23,仔细看一下这三个步骤:
图2-23 Service发送广播给AMS
第1步:Service发送广播给AMS
发送广播,是通过Intent这个参数携带了filter,从而告诉AMS什么样的Receiver能接收这个广播。
第2步:AMS接收广播,发送广播
接收广播和发送广播是不同步的。AMS每接收到一个广播,就把它扔到广播发送队列中,至于发送是否成功,它就不管了。
因为Receiver分为无序Receiver和有序Receiver,所以广播发送队列也分为两个,分别发送这两种广播。
AMS发送广播给客户端,这又是一个跨进程通信,还是通过ATP把消息发给APT。因为要传递Receiver这个对象,所以它也是一个Binder对象才可以传过去。我们前面说过,在把Receiver注册到AMS的时候,会把Receiver封装为一个IIntentReceiver接口的Binder对象。那么接下来,AMS就是把这个IIntentReceiver接口对象传回来。
第3步:App处理广播
处理流程如图2-24所示:
图2-24 App处理广播
1)消息从AMS传到客户端,把AMS中的IIntentReceiver对象转为InnerReceiver对象,这就是Receiver,这是一个AIDL跨进程通信。
2)然后在ReceiverDispatcher中封装一个Args对象(这是一个Runnable对象,要实现run方法),包括广播接收者所需要的所有信息,交给ActivityThread来发送。
3)接下来要做的就是我们所熟悉的了,ActivityThread把Args消息扔到H这个Hanlder中,向主线程消息队列发送消息。等到执行Args消息的时候,自然是执行Args的run方法。
4)在Args的run方法中,实例化一个Receiver对象,调用它的onReceiver方法。
5)最后,在Args的run方法中,随着Receiver的onReceiver方法调用结束,会通过AMN/AMP发送一个消息给AMS,告诉AMS“广播发送成功了”。AMS得到通知后,就发送广播给下一个Receiver。
注意
InnerReceiver是IIntentReceiver的stub,是Binder对象的接收端。
2.9.3 广播的种类
Android广播按发送方式分为三种:无序广播、有序广播(OrderedBroadcast)和粘性广播(StickyBroadcast)。
1)无序广播是最普通的广播。
2)有序广播区别于无序广播,就在于它可以指定优先级。
这两种Receiver在AMS不同的变量中,可以认为是两个Receiver集合发送不同类别的广播。
3)粘性广播是无序广播的一种。我们平常见的不多,但我说一个场景你就明白了,那就是电池电量。当电量小于20%的时候,就会提示用户。而获取电池的电量信息,就是通过广播来实现的。但是一般的广播,发完就完了。我们需要有这样一种广播,发出后,还能一直存在,未来的注册者也能收到这个广播,这种广播就是粘性广播。
由于动态Receiver只有在Activity的onCreate()方法调用时才能注册再接收广播,所以当程序没有运行就不能收到广播;但是静态注册的则不依赖于程序是否处于运行状态。
至此,关于广播的所有概念就全都介绍完了,虽然本节列出的代码很少,但我希望上述文字能引导App开发人员进入一个神奇的世界。
2.10 ContentProvider工作原理
ContentProvider,简称CP。App开发人员,尤其是电商类App开发人员,对ContentProvider并不熟悉,对这个概念的最大程度的了解,也仅仅是建立在书本上,它是Android四大组件中的一个。开发系统管理类App,比如手机助手,则有机会频繁使用ContentProvider。
而对于应用类App,数据通常存在服务器端,当其他应用类App也想使用时,一般都是从服务器取数据,所以没机会使用到ContentProvider。
有时候我们会在自己的App中读取通讯录或者短信数据,这时候就需要用到ContentProvider了。通讯录或者短信数据,是以ContentProvider的形式提供的,我们在App这边,是使用方。
对于应用类App开发人员,很少有机会自定义ContentProvider供其他App使用。
我们快速回顾一下在App中怎么使用ContentProvider。
1)定义ContentProvider的App1。
在App1中定义一个ContentProvider的子类MyContentProvider,并在AndroidManifest中声明,为此要在MyContentProvider中实现ContentProvider的增删改查4个方法:
<provider android:name=".MyContentProvider" android:authorities="baobao" android:enabled="true" android:exported="true"></provider> public class MyContentProvider extends ContentProvider { public MyContentProvider() { } @Override public boolean onCreate() { //省略一些代码 } @Override public String getType(Uri uri) { //省略一些代码 } @Override public Uri insert(Uri uri, ContentValues values) { //省略一些代码 } @Override public Cursor query(Uri uri, String[] projection, String where, String[] whereArgs, String sortOrder){ //省略一些代码 } @Override public int delete(Uri uri, String where, String[] whereArgs) { //省略一些代码 } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs){ //省略一些代码 } }
2)使用ContentProvider的App2。
在App2访问App1中定义的ContentProvider,为此,要使用ContentResolver(如图2-25所示),它也提供了增删改查4个方法,用于访问App1中定义的ContentProvider:
public class MainActivity extends Activity { ContentResolver contentResolver; Uri uri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); uri = Uri.parse("content://baobao/"); contentResolver = getContentResolver(); } public void delete(View source) { int count = contentResolver.delete(uri, "delete_where", null); Toast.makeText(this, "delete uri:" + count, Toast.LENGTH_LONG).show(); } public void insert(View source) { ContentValues values = new ContentValues(); values.put("name", "jianqiang"); Uri newUri = contentResolver.insert(uri, values); Toast.makeText(this, "insert uri:" + newUri, Toast.LENGTH_LONG).show(); } public void update(View source) { ContentValues values = new ContentValues(); values.put("name", "jianqiang2"); int count = contentResolver.update(uri, values, "update_where", null); Toast.makeText(this, "update count:" + count, Toast.LENGTH_LONG).show(); } }
图2-25 App2访问App1提供的ContentProvider
首先,我们看一下ContentResolver的增删改查这4个方法的底层实现,其实都是和AMS通信,最终调用App1的ContentProvider的增删改查4个方法,后面我们会讲到这个流程是怎么样的。
其次,URI是ContentProvider的唯一标识。我们在App1中为ContentProvider声明URI,也就是authorities的值为baobao,那么在App2中想使用它,就在ContentResolver的增删改查4个方法中指定URI,格式为:
uri = Uri.parse("content://baobao/");
接下来把两个App都进入debug模式,就可以从App2调试进入App1了,比如,query操作。
2.10.1 ContentProvider的本质
ContentProvider的本质是把数据存储在SQLite数据库中。
不同的数据源具有不同的格式,比如短信、通讯录,它们在SQLite中就是不同的数据表,但是对外界的使用者而言,就需要封装成统一的访问方式,比如对于数据集合而言,必须提供增删改查4个方法,于是我们在SQLite之上封装了一层,也就是ContentProvider。
2.10.2 匿名共享内存(ASM)
ContentProvider读取数据使用了匿名共享内存(ASM),所以你看上面ContentProvider和AMS通信忙得不亦乐乎,其实下面别有一番风景。
ASM实质上也是个Binder通信,如图2-26所示。
图2-26 匿名共享内存的架构图
图2-27为ASM的类的交互关系图。
图2-27 匿名共享内存的类图
这里的CursorWindow就是匿名共享内存。这个流程简单来说包含3个步骤:
1)Client内部有一个CursorWindow对象,发送请求的时候,把这个CursorWindow类型的对象传过去,这个对象暂时为空。
2)Server收到请求,搜集数据,填充到这个CursorWindow对象中。
3)Client读取内部的这个CursorWindow对象,获取数据。
由此可见,这个CursorWindow对象就是匿名共享内存,这是同一块匿名内存。
举个生活中的例子,你定牛奶,在你家门口放个箱子,送牛奶的人每天早上往这个箱子里放一瓶牛奶,你睡醒了去箱子里取牛奶。这个牛奶箱就是匿名共享内存。
2.10.3 ContentProvider与AMS的通信流程
接下来我们看一下ContentProvider是怎么和AMS通信的。
还是以App2想访问App1中定义的ContentProvider为例。我们仅看ContentProvider的insert方法:
ContentResolver contentResolver = getContentResolver ();
Uri uri = Uri.parse(“content://baobao/”);
ContentValues values = new ContentValues();
values.put(“name”, “jianqiang”);
Uri newUri = contentResolver.insert(uri, values);
上面这5行代码,包括了启动ContentProvider和执行ContentProvider方法两部分,分水岭在insert方法,insert方法的实现,前半部分仍然是在启动ContentProvider,当ContentProvider启动后获取到ContentProvider的代理对象,后半部分便通过代理对象调用insert方法。
整体的流程如图2-28所示。
图2-28 ContentProvider与AMS的通信流程
1)App2发送消息给AMS,想要访问App1中的ContentProvider。
2)AMS检查发现,App1中的ContentProvider没启动过,为此新开一个进程,启动App1,然后获取App1启动的ContentProvider,把ContentProvider的代理对象返回给App2。
3)App2拿到ContentProvider的代理对象,也就是IContentProvider,就调用它的增删改查4个方法。接下来使用ASM传输数据或者修改数据了,也就是上面提到的CursorWindow这个类,取得数据或者操作结果即可,作为App的开发人员,可以不必知道太多底层的详细信息。
至此,关于ContentProvider的介绍就结束了。下一小节,我们讨论App的安装流程,也就PMS。
2.11 PMS及App安装过程
2.11.1 PMS简介
PackageManagerService(PMS)是用来获取apk包的信息的。
在前面分析四大组件与AMS通信的时候,我们介绍过,AMS总是会使用PMS加载包的信息,将其封装在LoadedApk这个类对象中,然后我们就可以从中取出在AndroidManifest声明的四大组件信息了。
在下载并安装App的过程中,会把apk存放在data/app目录下。
apk是一个zip压缩包,在文件头会记录压缩包的大小,所以就算后续在文件尾处追加一部小电影,也不会对解压造成影响——木马其实就是这个思路,在可执行文件exe尾巴上挂一个木马病毒,执行exe的同时也会执行这个木马,然后你就中招了。
我们可以把这一思想运用在Android多渠道打包上。在比较老的Android 4.4版本中,我们会在apk尾巴上追加几个字节,来标志apk的渠道。apk启动的时候,从apk中的尾巴上读取这个渠道值。
后来Google也发现这个安全漏洞了,在新版本的系统中,会在apk安装的时候,检查apk的实际大小,看这个值与apk的头部记录的压缩包大小是否相等,不相等就会报错说安装失败。
回答前面提及的一个问题:为什么App安装时,不把它解压呢?直接从解压文件中读取资源文件(比如图片)是不是更快呢?其实并不是这样的,这部分逻辑需要到底层C++的代码去寻找,我没有具体看过,只是道听途说问过Lody,他是这么给我解释的:
每次从apk中读取资源,并不是先解压再找图片资源,而是解析apk中的resources.arsc文件,这个文件中存储着资源的所有信息,包括资源在apk中的地址、大小等等,按图索骥,从这个文件中快速找到相应的资源文件。这是一种很高效的算法。
不解压apk的好处自然是节省空间。
2.11.2 App的安装流程
Android系统使用PMS解析这个apk中的AndroidManifest文件,包括:
□ 四大组件的信息,比如,前面讲过的静态Receiver,默认启动的Activity。
□ 分配用户Id和用户组Id。用户Id是唯一的,因为Android是一个Linux系统。用户组Id指的是各种权限,每个权限都在一个用户组中,比如读写SD卡或网络访问,分配了哪些用户组Id,就拥有了哪些权限。
□ 在Launcher生成一个icon, icon中保存着默认启动的Activity的信息。
□ 在App安装过程的最后,把上面这些信息记录在一个xml文件中,以备下次安装时再次使用。
在Android手机系统每次启动的时候,都会使用PMS,把Android系统中的所有apk都安装一遍,一共4个步骤,如图2-29所示。
图2-29 App的安装流程
第1步,因为结束安装的时候,都会把安装信息保存在xml文件中,所以Android系统再次启动时,会重新安装所有的apk,就可以直接读取之前保存的xml文件了。
第2步,从5个目录中读取并安装所有的apk。
第3步和第4步,与单独安装一个App的步骤是一样的,不再赘述。
2.11.3 PackageParser
Android系统重启后,会重新安装所有的App,这是由PMS这个类完成的。App首次安装到手机上,也是由PMS完成的。PMS是Android的系统进程,我们是不能Hook的。
PMS中有一个类,对于我们App开发人员来说很重要,那就是PackageParser,其类图如图2-30所示,它是专门用来解析AndroidManifest文件的,以获取四大组件的信息以及用户权限。
图2-30 PackageParser的类图
PackageParser类中,有一个parsePackage方法,接收一个apkFile的参数,既可以是当前apk文件,也可以是外部apk文件。我们可以使用这个类,来读取插件apk的Android Manifest文件中的信息,但是PackageParser是隐藏的不对App开发人员开放,但这并不是什么难题,可以通过反射来获取到这个类。
parsePackage方法返回的是Package类型的实体对象,里面存储着四大组件的信息,但Package类型我们一般用不到,取而代之的是PackageInfo对象,所以要使用PackageParser类的generatePackageInfo方法,把Package类型转换为PackageInfo类型。
在插件化编程中,我们反射PackageParser,一般是用来获取插件AndroidManifest中的四大组件信息。
2.11.4 ActivityThread与PackageManager
条条大路通罗马,对于App开发人员而言,也可以使用Context.getPackageManager()方法,来获取当前Apk的信息。
getPackageManager的具体实现自然是在ContextImpl类中:
class ContextImpl extends Context { private PackageManager mPackageManager; @Override public PackageManager getPackageManager() { if (mPackageManager ! = null) { return mPackageManager; } IPackageManager pm = ActivityThread.getPackageManager(); if (pm ! = null) { // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); } return null; } }
从上面代码看出,Context的getPackageManager方法返回的是ApplicationPackageManager类型的对象。ApplicationPackageManager是PackageManager的子类。
如果读者熟悉设计模式,那么能看出ApplicationPackageManager其实是一个装饰器模式。它就是一个壳,它装饰了ActivityThread.getPackageManager(),真正的逻辑也是在ActivityThread中完成的,如下所示:
public final class ActivityThread { private static ActivityThread sCurrentActivityThread; static IPackageManager sPackageManager; public static IPackageManager getPackageManager() { if (sPackageManager ! = null) { return sPackageManager; } IBinder b = ServiceManager.getService("package"); sPackageManager = IPackageManager.Stub.asInterface(b); return sPackageManager; } }
还记得我们前面介绍Binder时讲的ServiceManager吗?这就是一个字典容器,里面存放着各种系统服务,key为clipboard时,对应的是剪切板;key为package时,对应的是PMS。
IPackageManager是一个AIDL。根据前面章节对AIDL的介绍,我们发现以下语句是相同的对象:
□ Context的getPackageManager();
□ ActivityThread的getPackageManager();
□ ActivityThread的sPackageManager;
□ ApplicationPackageManager的mPM字段。
所以当你在程序中看到上述这些语句时,它们都是PMS在App进程的代理对象,都能获得当前Apk包的信息,尤其是我们感兴趣的四大组件信息。在插件化编程中,我们反射ActivityThread获取Apk包的信息,一般用于当前的宿主Apk,而不是插件Apk。
ApplicationPackageManager实现了IPackageManager.Stub。
2.12 ClassLoader家族史
Android插件化能从外部下载一个apk插件,就在于ClassLoader。ClassLoader是一个家族。ClassLoader是老祖先,它有很多儿孙,ClassLoader家族如图2-31所示。
图2-31 ClassLoader的家谱
其中最重要的是PathClassLoader和DexClassLoader,及其父类BaseDexClassLoader。
PathClassLoader和DexClassLoader这两个类都很简单,以至于看不出什么区别,但仔细看,你会发现,构造函数的第2个参数optimizedDirectory的值不一样,PathClassLoader把这个参数设置为null, DexClassLoader把这个参数设置为一个非空的值。
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); } } public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } }
我们不打算深究更底层的代码,只需要知道,optimizedDirectory是用来缓存我们需要加载的dex文件的,并创建一个DexFile对象,如果它为null,那么会直接使用dex文件原有的路径来创建DexFile对象。
DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部的dex,这些大都是存在于系统中已经安装过的Apk里面的。
2.13 双亲委托
说完了ClassLoader,就要讲双亲委托(Parent-Delegation Model)。对于ClassLoader家族,它们的老祖先是ClassLoader类,它的构造函数很有趣:
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { if (parentLoader == null && ! nullAllowed) { throw new NullPointerException("parentLoader == null && ! nullAllowed"); } parent = parentLoader; }
主要看第1个参数,也就是创建一个ClassLoader对象的时候,都要使用一个现有的ClassLoader作为参数parentLoader。这样在Android App中,会形成一棵由各种ClassLoader组成的树。
在CLassLoader加载类的时候,都会优先委派它的父亲parentLoader去加载类,沿着这棵数一路向上找,如果没有哪个ClassLoader能加载,那就只好自己加载这个类。
这样做的意义是为了性能,每次加载都会消耗时间,但如果父亲加载过,就可以直接拿来使用了。
对于App而言,Apk文件中有一个classes.dex文件,那么这个dex就是Apk的主dex,是通过PathClassLoader加载的。
在App的Activity中,通过getClassLoader获取到的是PathClassLoader,它的父类是BootClassLoader。
对于插件化而言,有一种方案是将App的ClassLoader替换为自定义的ClassLoader,这样就要求自定义ClassLoader模拟双亲委托机制。比较典型的代码就是Zeus插件化框架。
2.14 MultiDex
记得还是在Android 5之前的版本,在App的开发过程中,随着业务的扩展,App中的代码会急速增长,直到有一天,编译代码进行调试或者打包的时候,遇到下面的错误:
Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536
这就是著名的“65536问题”,业内戏称为“爆棚”。
其实我们的项目中,自己写的代码功能再多,一般也不会超过这个65536的上限。往往是引入一些第三方的SDK,它提供了很多的功能,而我们只用到其中几个功能或几个方法,那么其他数万个方法虽然用不到,但还是驻留在App中了,跟着一起打包。
在Apk打包的过程中,我们一般会开启proguard。这个工具不仅是做混淆的,它还会把App中用不到的方法全部删除,所以第三方SDK中那些无用的方法就这样被移除了,方法数量大幅减少,又降低到了65536之下。
但是代码调试期间,是不会开启proguard的,所以“65536问题”还是会出现。我当时的解决方案是,在开发其他功能时,把这个第三方SDK临时删掉,相应的功能注释掉——只要你不开发用到这个SDK的功能点,这种方案还是可行的,但毕竟不是长久之计。
后来Google官方解决了这个问题。一方面,Android 5.0上修复了这个爆棚的bug,我们拭目以待,看这个新的界限哪一天被突破,再次爆棚。
另一方面,在当时那个年代,还是要对Android 5.0之前的版本做兼容,类似于2.3和4.4版本的市场占用率还是很高的,于是Google推出了MultiDex这个工具。顾名思义,MultiDex就是把原先的一个dex文件,拆分成多个dex文件。每个dex的方法数量不超过65536个,如图2-32所示。
图2-32 把dex拆分为多个
classes.dex称为主dex,由App使用PathClassLoader进行加载。而classes2.dex和classes3.dex这些子dex,会在App启动后使用DexClassLoader进行加载。
时光荏苒,转眼已到2018年,市场上对Android系统的最低版本的支持已经到了Android 5.0,再也不会遇到65536的爆棚问题了。那么MultiDex就没有用武之地了吗?并不是这样。在Android 5.0下,虽然dex中已经可以容纳比65536还多的方法数量,但是dex的体积却变大了。所以我们还是会对dex进行拆分,classes.dex中只保留App启动时所需要的类以及首页的代码,从而确保App花最少时间启动并进入到首页;而把其他模块的代码转移到classes2.dex和classes3.dex。
如何手动指定classes2.dex中包含SecondActivity的代码,而classes3.dex中包含ThirdActivity的代码呢?这个技术称为“手动分包”,我们会在18.3节介绍这个技术。
这个技术再往前走一步,就是插件化。我们可以按照模块把原先的项目拆分成多个App,各自打包出Apk,把这些Apk放到主App的assets目录下,然后使用DexClass进行加载。
有人说插件化能减少App的体积,这是不正确的。在App打包的时候,就要把插件的1.0版本预先放在assets目录下一起打包,然后有更新版本时(比如版本1.1),才会下载新的插件或者增量包。
如果App打包时不包括插件,那么就会在App启动的时候才下载,这就慢了,用户体验很差。
所以App打包时一定要有1.0版本。用不用插件化,体积至少是相同的。
2.15 实现一个音乐播放器App
相信很多读者还没接触过Service和BroadcastReceiver的编程,这里我们举一个音乐播放器的例子,来带领大家熟悉这两个组件的工作机制。
2.15.1 基于两个Receiver的音乐播放器
音乐播放器有几个有趣的特点:
□ 即使你切换到另一个App,当前在播放的音乐,仍然播放而没有停止,这是利用了Service在后台播放音乐,其实直播也是基于这个思路来做的。音乐App的前台也就是Activity,负责展示当前哪个音乐在播放,有播放和停止两个按钮,无论点击哪个按钮,都是通知后台Service播放或停止音乐。这个通知就是通过BroadcastReceiver从Activity发送给Service的。
□ 每当后台Service播放完一首歌曲,就会通知前台Activity,于是在后台Service播放下一首歌的同时,前台Activity的展示内容将从当前播放的歌曲名称和作者,切换到下一首歌的名称和作者。这个通知则是通过另一个BroadcastReceiver从Service发送给Activity。
所以一个音乐播放App,至少需要一个Service和两个BroadcastReceiver。
1)在AndroidManifest.xml中,声明Activity和Service:
<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".MyService" />
2)在Activity这边,点击播放、停止按钮,都会发消息给Service:
public class MainActivity extends Activity { TextView tvTitle, tvAuthor; ImageButton btnPlay, btnStop; Receiver1 receiver1; //0x11: stoping; 0x12: playing; 0x13:pausing int status = 0x11; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvTitle = (TextView) findViewById(R.id.tvTitle); tvAuthor = (TextView) findViewById(R.id.tvAuthor); btnPlay = (ImageButton) this.findViewById(R.id.btnPlay); btnPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //send message to receiver in Service Intent intent = new Intent("UpdateService"); intent.putExtra("command", 1); sendBroadcast(intent); } }); btnStop = (ImageButton) this.findViewById(R.id.btnStop); btnStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //send message to receiver in Service Intent intent = new Intent("UpdateService"); intent.putExtra("command", 2); sendBroadcast(intent); } }); //register receiver in Activity receiver1 = new Receiver1(); IntentFilter filter = new IntentFilter(); filter.addAction("UpdateActivity"); registerReceiver(receiver1, filter); //start Service Intent intent = new Intent(this, MyService.class); startService(intent); } public class Receiver1 extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { status = intent.getIntExtra("status", -1); int current = intent.getIntExtra("current", -1); if (current >= 0) { tvTitle.setText(MyMusics.musics[current].title); tvAuthor.setText(MyMusics.musics[current].author); } switch (status) { case 0x11: btnPlay.setImageResource(R.drawable.play); break; case 0x12: btnPlay.setImageResource(R.drawable.pause); break; case 0x13: btnPlay.setImageResource(R.drawable.play); break; default: break; } } } }
也许有人看不懂Receiver1和Receiver2的对应关系,为了便于理解,请参见图2-33。
图2-33 音乐播放器中的两个Receiver
3)在Service这边,当一首音乐播放完,在播放下一首音乐的同时,会发消息给Activity:
public class MyService extends Service { Receiver2 receiver2; AssetManager am; MediaPlayer mPlayer; int status = 0x11; int current = 0; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { am = getAssets(); //register receiver in Service receiver2 = new Receiver2(); IntentFilter filter = new IntentFilter(); filter.addAction("UpdateService"); registerReceiver(receiver2, filter); mPlayer = new MediaPlayer(); mPlayer.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { current++; if (current >= 3) { current = 0; } prepareAndPlay(MyMusics.musics[current].name); //send message to receiver in Activity Intent sendIntent = new Intent("UpdateActivity"); sendIntent.putExtra("status", -1); sendIntent.putExtra("current", current); sendBroadcast(sendIntent); } }); super.onCreate(); } private void prepareAndPlay(String music) { try { AssetFileDescriptor afd = am.openFd(music); mPlayer.reset(); mPlayer.setDataSource(afd.getFileDescriptor() , afd.getStartOffset() , afd.getLength()); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { e.printStackTrace(); } } public class Receiver2 extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { int command = intent.getIntExtra("command", -1); switch (command) { case 1: if (status == 0x11) { prepareAndPlay(MyMusics.musics[current].name); status = 0x12; } else if (status == 0x12) { mPlayer.pause(); status = 0x13; } else if (status == 0x13) { mPlayer.start(); status = 0x12; } break; case 2: if (status == 0x12 || status == 0x13) { mPlayer.stop(); status = 0x11; } } //send message to receiver in Activity Intent sendIntent = new Intent("UpdateActivity"); sendIntent.putExtra("status", status); sendIntent.putExtra("current", current); sendBroadcast(sendIntent); } } }
也许有人看不懂0x11(stoping),0x12(playing)和0x13(pausing)三个状态之间的切换关系,为便于理解,请参见图2-34。
图2-34 音乐按钮的状态机
2.15.2 基于一个Receiver的音乐播放器
上一节介绍了音乐播放器App的第一种实现——使用两个Receiver的方式,在Activity和Service中各注册一个Receiver给对方使用。
其实,很多音乐类播放器中只有一个Receiver,也就是上一节中介绍的Receiver1,在Activity中注册,而后台音乐Service一旦播放完某首音乐,就发送广播给Receiver1,更新Activity界面
另一方面,我们使用Service的onBind方法,通过ServiceConnection获取到在Service中定义的Binder对象,在Activity点击播放或暂停音乐的按钮,会调用这个Binder对象的play和stop方法来操作后台Service,如图2-35所示。
图2-35 音乐播放器的流程图
为了解耦Activity和Service,不在Activity中直接使用Service中定义的MyBinder对象,我们创建IServiceInterface接口,让Activity和Service都面向IServiceInterface接口编程:
public interface IServiceInterface { public void play(); public void stop(); }
音乐播放器的Activity代码如下:
public class MainActivity extends Activity { TextView tvTitle, tvAuthor; ImageButton btnPlay, btnStop; //0x11: stoping; 0x12: playing; 0x13:pausing int status = 0x11; Receiver1 receiver1; IServiceInterface myService = null; ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder binder) { myService = (IServiceInterface) binder; Log.e("MainActivity", "onServiceConnected"); } public void onServiceDisconnected(ComponentName name) { Log.e("MainActivity", "onServiceDisconnected"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvTitle = (TextView) findViewById(R.id.tvTitle); tvAuthor = (TextView) findViewById(R.id.tvAuthor); btnPlay = (ImageButton) this.findViewById(R.id.btnPlay); btnPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myService.play(); } }); btnStop = (ImageButton) this.findViewById(R.id.btnStop); btnStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myService.stop(); } }); //register receiver in Activity receiver1 = new Receiver1(); IntentFilter filter = new IntentFilter(); filter.addAction("UpdateActivity"); registerReceiver(receiver1, filter); //bind Service Intent intent = new Intent(this, MyService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } public class Receiver1 extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { status = intent.getIntExtra("status", -1); int current = intent.getIntExtra("current", -1); if (current >= 0) { tvTitle.setText(MyMusics.musics[current].title); tvAuthor.setText(MyMusics.musics[current].author); } switch (status) { case 0x11: btnPlay.setImageResource(R.drawable.play); break; case 0x12: btnPlay.setImageResource(R.drawable.pause); break; case 0x13: btnPlay.setImageResource(R.drawable.play); break; default: break; } } } }
音乐播放器的Service代码如下:
public class MyService extends Service { AssetManager am; MediaPlayer mPlayer; int status = 0x11; int current = 0; private class MyBinder extends Binder implements IServiceInterface { @Override public void play() { if (status == 0x11) { prepareAndPlay(MyMusics.musics[current].name); status = 0x12; } else if (status == 0x12) { mPlayer.pause(); status = 0x13; } else if (status == 0x13) { mPlayer.start(); status = 0x12; } sendMessageToActivity(status, current); } @Override public void stop() { if (status == 0x12 || status == 0x13) { mPlayer.stop(); status = 0x11; } sendMessageToActivity(status, current); } } MyBinder myBinder = null; @Override public void onCreate() { myBinder = new MyBinder(); am = getAssets(); mPlayer = new MediaPlayer(); mPlayer.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { current++; if (current >= 3) { current = 0; } prepareAndPlay(MyMusics.musics[current].name); sendMessageToActivity(-1, current); } }); super.onCreate(); } @Override public IBinder onBind(Intent intent) { return myBinder; } @Override public boolean onUnbind(Intent intent) { myBinder = null; return super.onUnbind(intent); } private void sendMessageToActivity(int status1, int current1) { Intent sendIntent = new Intent("UpdateActivity"); sendIntent.putExtra("status", status1); sendIntent.putExtra("current", current1); sendBroadcast(sendIntent); } private void prepareAndPlay(String music) { try { AssetFileDescriptor afd = am.openFd(music); mPlayer.reset(); mPlayer.setDataSource(afd.getFileDescriptor() , afd.getStartOffset() , afd.getLength()); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { e.printStackTrace(); } } }
希望通过上面两个例子,能让各位读者更加深入了解Receiver和Service,毕竟这两个组件平常的出镜率并不是很高。
2.16 本章小结
本章详细介绍了Android底层的知识点。本章没有贴太多代码,而是画了几十张图,这是本章的特色。
对于Binder,掌握其原理即可,不需要深入源码,尤其是那些读起来让人头大的C++代码。Binder在App中的代表是AIDL,所以要牢记,在AIDL生成的Java代码中stub和proxy的作用。
要牢记App的启动流程和安装流程。
四大组件非常重要。四大组件要和AMS打交道,要牢记App端是怎么和AMS打交道的,而不需要了解太多AMS内部的逻辑。这就引出了App端参与交互的那些类,比如:
□ ActivityThread;
□ Instrumentation;
□ H;
□ AMN和AMP;
□ Context家族;
□ LoadedApk。
我们一般对Activity很熟悉,而对其他三大组件就很少了解了,这是我们需要去补充的知识。音乐播放器是一个很好的例子。
对于PMS,不需要深入PMS读取Apk的过程,而是要关注使用什么手段获取Apk的信息。重结果不重过程。
对于ClassLoader家族,要掌握DexClassLoader,这是插件化编程的关键。此外对双亲委托机制要理解。
对于MultiDex,要掌握手动拆包的办法,我们会在插件混淆的章节中用到这个技术。