Android插件化开发指南
上QQ阅读APP看书,第一时间看更新

第2章 Android底层知识

这一章,改编自2017年我的系列文章《写给Android App开发人员看的Android底层知识》文章地址:http://www.cnblogs.com/Jax/p/6864103.html。在此基础上,扩充了PMS、双亲委托、ClassLoader等内容。这些Android底层知识都是学习Android插件化技术所必需的。

本章介绍的这些Android底层知识基于Android 6.0.1版本。我把本章以及整本书涉及的Android系统底层的类或aidl都搜集在一起,放在我的GitHub上项目地址:https://github.com/BaoBaoJianqiang/AndroidSourceCode,读者可以下载并研读这些代码。

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个阶段这个流程分析,基本上是基于Android 6.0的源码进行的。

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,要掌握手动拆包的办法,我们会在插件混淆的章节中用到这个技术。