【读书笔记】【Android 开发艺术探索】第 9 章 四大组件的工作过程

一、Activity 的工作过程

以应用的角度出发,Activity 分两类
根 Activity:
根 Activity 一快捷图标的形式显示在应用程序启动器中,它的启动过程代表了一个 Android 应用程序的启动过程。
子 Activity:
子 Activity 由根 Activity 或者其他子 Activity 启动,它们启动可能与启动他们的 Activity 运行在同一个进程中,也可能运行在不同的进程中,着取决于它们的配置和启动的参数。

1、根 Activity 组件的启动过程

可以通过命令行 adb shell dumpsys activity 查看
  根 Activity 组件是由 Laucher 组件来启动的,而Laucher 组件又是通过 Activity 管理服务 ActivityManagerService 来启动  根Activity 组件的。

  根 Activity ,Laucher、ActivityManagerService 分别运行在不同的进程中,因此,根 Activity 组件的启动过程涉及到三个  进程。这三个进程是通过 Binder 进程通信机制来完成根 Activity 的启动过程。

  Laucher 启动根 Activity 组件的过程:

 (1)Laucher 向 AMS(ActivityManagerService) 发送一个启动根 Activity 组件的进程间通信请求;

 (2) AMS 首先将要启动的根 Activity 组件的信息保存下来,然后再向 Laucher 发一个进入中止状态的进程间通信请求;

(3) Laucher 进入到中止状态之后,就会向 AMS 发送一个已进入中止状态的进程通信请求,以便 AMS 可以继续执行启  动根 Activity 组件的操作;

  (4)  AMS 发现用来运行根 Activity 组件的应用程序进程不存在,因此,它会先启动一个新的应用程序进程;

  (5) 新的应用程序进程启动完成后,就会向 AMS 发送一个启动完成的进程间通信请求,以便 AMS 可以继续执行启动根Activity 组件的操作;

 (6)AMS 将第 2 步保存下来的根 Activity 组件的信息发送给第 4 步创建的应用程序进程,以便它可以将根 Activity 组件启动起来。
            
ActivityManagerService 是一个系统关键服务,它运行在系统进程 System 中,负责启动和调度应用程序组件。

Activity 类的成员变量 mInstrumentation 的类型为 Instrumention ,它用来监控应用程序和系统之间的交互操作。


新的应用程序启动时,主要做了两件事:
I.在进程创建时会创建一个 ActivityThread 对象,并且调用它的成员函数 attach(...)  向 ActivityManagerService 发送一个启动完成通知。
II. 调用Looper 类的静态成员函数 prepareMainLooper 创建一个消息循环,并且在向 AMS 发送启动完成通知之后,使得当前进程进入到这个消息循环中。
   private void attach(boolean system) {
       ...
        if (!system) {
            ...
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
         ...
    }

    public static void main(String[] args) {
     ...
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
      ...
        Looper.loop();
        
    }

在main() 我们看到调用了 Looper.prepareMainLooper(),和 Looper.loop() 方法,所以,我们可以直接在 Activity 的 UI 线程中
直接使用 Handler.

最后通过一个类加载器,启动,调用了 Activity.attach() 和 Activity.onCreate() 方法。


2、子 Activity 组件在进程内启动

子 Activity 与 Client 同一个进程中
(1). Client 向 AMS 发送一个启动子 Activity 的进程间通信请求;

(2). AMS 首先将要启动的子 Activity 组件信息保存下来,然后向 Client 组件发送一个进入中止状态的进程间通信请求;

(3). Client 进入到中止状态之后,就会向 AMS 发送一个已进入中止状态的进程间通信请求,以便 AMS 可以继续执行启动子 Activity 组件的操作;

(4). AMS 发现用来运行子 Activity 组件的应用程序进程已经存在,因此将第 2 步 保存下来的子 Activity 组件信息发送到该应用程序进程,以便它可以将子 Activity 组件启动起来。


I Client Activity 进入中止状态
   # ActivityThread.java
    private void handlePauseActivity(IBinder token, boolean finished,
                                     boolean userLeaving, int configChanges, boolean dontReport) {
        ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r);
            // 如果离开,像点击 home 键,就会调用这里
            if (userLeaving) {
                performUserLeavingActivity(r);
            }

            r.activity.mConfigChangeFlags |= configChanges;
            // 这里最终会调用 Client Activity 的 onPause() 方法
            performPauseActivity(token, finished, r.isPreHoneycomb());

            // Make sure any pending writes are now committed.
            // 等待前面的工作完成
            if (r.isPreHoneycomb()) {
                QueuedWork.waitToFinish();
            }

            // Tell the activity manager we have paused.
            if (!dontReport) {
                try {
                    // 发送一个已经进入中止状态的进程间通信
                    ActivityManagerNative.getDefault().activityPaused(token);
                } catch (RemoteException ex) {
                }
            }
            mSomeActivitiesChanged = true;
        }
    }
    从上面的 handlePauseActivity 方法中可以看到会先把当前 Activity 进行 pause 完成,让后再发送一个进入中止状态的进程间通信,让子 Activity 启动。所以,不要在 onPause 中做一些耗时的操作,否则会使子 Activity 等待启动的时间过久.

performPauseActivity(...) 最终会调用当前 Client Activity 的 onPause() 方法. 如果因为类似旋转屏幕的行为,会在 performPauseActivity(...) 方法中回调 Activity.onSaveInstanceState(...) 方法,该方法在 onPause() 方法之前调用。

    如果是类似于点击 home 键离开当前 Activity 的行为,会调用 performUserLeavingActivity(...) 方法,改方法,最终会调用 
Activity.onUserInteraction() 和 Activity.onUserLeaveHint() 方法,它们回调的顺序在 onPause() 之前。

之后的过程于 Laucher 启动 Activity 过程类似。



二、Service 的启动过程

1、Client 组件在新进程中启动 Service 

(1). Client 向 AMS 发送一个启动 Service 的进程间通信请求;


(2). AMS 发现用来运行 Service 的应用程序进程不存在,因此,它会首先将 Service 的组件信息保存下来, 接着再创建一个新的应用程序进程;


(3).新的应用程序进程启动完成之后,就会向 AMS 发送一个启动完成的进程间通行请求,以便 AMS 可以继续执行启动 Service 的操作;


(4). AMS 将第 2 步保存下来的 Service 组件信息发送到第 2 步 创建的应用程序进程,以便它可以将 Service 启动。


I.   启动时,如果版本大于 LOLLIPOP 及其以上版本, Intent 必须是显示,否则会抛出异常

// ContextImpl.java 中

private void validateServiceIntent(Intent service) {
        if (service.getComponent() == null && service.getPackage() == null) {
            if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                IllegalArgumentException ex = new IllegalArgumentException(
                        "Service Intent must be explicit: " + service);
                throw ex;
            } else {
                Log.w(TAG, "Implicit intents with startService are not safe: " + service
                        + " " + Debug.getCallers(2, 3));
            }
        }
    }


II. 最终还是通过类加载器,加载到内存中,并创建实例。


2. Service 组件在进程内的绑定过程

(1). Activity 想 AMS 发送一个绑定 Service 组件的进程间通信请求;


(2). AMS 发现用来运行 Service 组件的应用程序进程,即 Activity 组件所运行在的应用程序进程已经存在。因此,它直接通知应用程序将 Service 启动;


(3).Service 启动后,AMS 就会请求它返回一个 Binder 本地对象,以便 Activity 可以通过这个 Binder 本地对象和 Service 建立连接;


(4). AMS 将前面从 Service 组件中获得一个 Binder 本地对象发个 Activity 组件;


(5). Activity 组件获得 AMS 给它发送的 Binder 本地对象之后,就可以通过它来获得 Service 组件的一个访问接口. Activity 组件以后可以通过这个访问接口来使用 Service 所提供的服务,这相当于将这个 Service 组件绑定在 Activity 内部。


ActivityThread.java

I.如果没有绑定过,则绑定,如果已经绑定过了,从新绑定

private void handleBindService(BindServiceData data) {
        Service s = mServices.get(data.token);
       ...
        if (s != null) {
            try {
                data.intent.setExtrasClassLoader(s.getClassLoader());
                data.intent.prepareToEnterProcess();
                try {
                    if (!data.rebind) {
                        IBinder binder = s.onBind(data.intent);
                        ActivityManagerNative.getDefault().publishService(
                                data.token, data.intent, binder);
                    } else {
                        s.onRebind(data.intent);
                        ActivityManagerNative.getDefault().serviceDoneExecuting(
                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                    }
                    ensureJitEnabled();
                } catch (RemoteException ex) {
                }
            } catch (Exception e) {
                ...
            }
        }
    }




三.BroadCastReceiver 的工作过程

广播机制是在 Binder 进程间通信机制的基础上实现的; 广播机制是一种基于消息发布和订阅的事件驱动模型,即广播发送者负责发布消息,而广播接收者需要先订阅消息,然后才能接收消息。


(1) 广播的注册

Activity 组件注册一个广播接受者是,并不是真的将这个广播接收者注册到 ActivityManagerService 中, 而是将与它关联的 InnerReceiver 对象注册到 AMS 中。


(2) 广播的发送过程

发送过程:
I.广播接收者,即一个 Activity 组件或者一个 Service 组件, 将一个特定类型的广播发送给 AMS;

II. AMS 接收到一个广播之后, 首先找到与这个广播对应的广播接收者,然后将它们添加到一个广播对调队列中,最后向 AMS 所运行在的线程消息队列发送一个类型 BROADCAST_INTENT_MSG 的消息。这时候对广播接收者来说,一个广播就发送完成了;

III. 当发送到 AMS 所运行在的线程的消息队列中的 BROADCAST_INTENT_MSG 消息被处理时, AMS 就会从广播调度队列中找到需要接收广播的广播接收者,并且将对应的广播发送给它们所运行在的应用程序进程;

IIII. 广播接收者所运行的应用程序进程接收到 AMS 发送过来的广播之后, 并不是直接将接收到的广播分发给广播接收者来处理,二是将接收到的广播封装成一个消息,并且发送到主线程的消息队列中。当这个消息被处理时,应用程序才会将它描述的广播发送给相应的广播接收者处理。


.在 AMS 的 broadcastIntentLocked 方法的开始添加了

 // By default broadcasts do not go to stopped apps.
  intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

这表示在 Android 5.0 中,默认情况下,广播不会发送给已经停止的应用


Intent 的标记位

FLAG_INCLUDE_STOPPED_PACKAGES

表示包含已经停止的应用,这个时候广播会发送给已经停止的应用;


FLAG_EXCLUDE_STOPPED_PACKAGES

表示不包含已经停止的应用,这个时候广播不会发送给已经停止的应用;


从 Android 3.1 开始,系统为所有广播默认条件了 FLAG_EXCLUDE_STOPPED_PACKAGES 标志。如果确实需要调去未启动的应用,添加

FLAG_INCLUDE_STOPPED_PACKAGES 标记位即可。 两者共存时,以 FLAG_INCLUDE_STOPPED_PACKAGES 为准。


这里应用处于停止状态分两种情况: 第一种是应用安装后未运行,第二种是应用被手动或者其他应用强停了。


从 Android 3.1 开始,处于停止状态的应用无法接收到开机广播,而 3.1 之前,处于停止状态的应用是可以收到开机广播的。
















发布了58 篇原创文章 · 获赞 20 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/yxhuang2008/article/details/51439344
今日推荐