【讀書筆記】【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 之前,處於停止狀態的應用是可以收到開機廣播的。
















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章