Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> DroidPlugin代碼分析(四) 進程管理

DroidPlugin代碼分析(四) 進程管理

編輯:關於Android編程

之所以單列一篇寫進程管理,是因為看到注釋上寫“這是一個復雜的進程管理程序”,但是仔細看了一下好像也沒那麼“復雜”...

這一篇通過分析代碼試圖搞清楚以下3個問題:

? 插件進程是如何被hook住的?

? 插件進程die是如何被檢測到的?

? 插件進程是如何被管理的?

一、插件進程是如何被hook住的?

在寫宿主程序的時候,我們知道需要在Application的onCreate()和attachBaseContext()裡調用PluginHelper的API來安裝hook。但是,插件程序本身是不會調用這些API的,那麼被啟動的插件程序是如何被hook住的呢?

首先我們要再次回顧一下activity啟動的一些細節,圖比較大切成了兩張,縮進表示該方法是在上一級方法裡調用的子方法。先看左半邊圖:

 

\

 

比較簡單,宿主在一個新進程裡啟動插件的時候,AMS會向插件進程的ActivityThread發起兩個調用:bindApplication()和scheduleLaunchAcitivity()。再看右半邊圖:

 

\

 

這張圖主要描述了這兩個調用具體干了什麼,實際上它們只是向ActivityThread的mH裡發送了兩個消息,真正干活的是mH(看過第二篇的可能有印象,我們把mH裡面的mCallback替換成了我們的PluginCallback)。

看看bindApplication()具體做了哪些事情:

? 調用getPackageInfoNoCheck()創建一個LoadedApk對象,注意,由於AMS並不知道關於插件的事情,所以這裡加載的還是宿主apk!所以實際上插件是啟動不起來的,具體怎麼處理的後面會介紹。創建的LoadedApk會放到一個mPackages的map中。

? 創建Instrumentation,調用LoadedApk.makeApplication()創建Application,注意只是創建,並沒有調用Application的onCreate()。創建Application會放到一個mAllApplications的list中。

再看看scheduleLaunchActivity()具體做了哪些事情:

? 通過Instrumentation加載、創建activity對象,這裡會用到class loader。

? 從mPackages中取出之前創建的LoadedApk,再次調用它的makeApplication()方法。這次調用和上次不同,由於Application已經創建過了所以會直接拿出來用,另外傳入的第二個參數instrumentation不為空,因此會調用Application的onCreate()方法。

 

說了這麼多,都只是android默認的運行流程。那麼DroidPlugin是如何讓插件被加載和啟動的呢?先上一張圖描述一下概況,以免後面分析代碼的時候會暈。我們和上一張圖對比一下看主要區別在哪裡:

\

 

其實說穿了也很簡單,我們不是在PluginCallback裡hook了handleLaunchActivity()方法嗎?那就在這個hook裡手動加載一下插件apk,創建LoadedApk對象並調用其makeApplication()方法,創建Application並調用其onCreate()。在這一切都做完以後,繼續往下執行,調用宿主Application的onCreate()。

也就是說,其實創建了兩個Application對象,先調用插件Application的onCreate(),再調用宿主Application的onCreate()。這樣就回答了文章開頭提出的第一個問題了:在宿主Application的onCreate()裡,我們是安裝了所有hook的,這樣插件apk就也被hook住啦~~

思路已經清楚了,下面分析代碼,重點看一下PluginProcessManager的2個關鍵API:preLoadApk()和preMakeApplication()。現在明白為什麼這兩個方法要帶“pre”前綴了,因為新創建的Application確實是被先調用的呀。

preLoadApk()大家可能還有印象,是在PluginCallback裡曾經露過臉,看一下具體代碼:

 

    public static void preLoadApk(Context hostContext, ComponentInfo pluginInfo) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, PackageManager.NameNotFoundException, ClassNotFoundException {
        boolean found = false;
        synchronized (sPluginLoadedApkCache) {
            Object object = ActivityThreadCompat.currentActivityThread();
            if (object != null) {
                Object mPackagesObj = FieldUtils.readField(object, "mPackages");
                Object containsKeyObj = MethodUtils.invokeMethod(mPackagesObj, "containsKey", pluginInfo.packageName)
                if (containsKeyObj instanceof Boolean && !(Boolean) containsKeyObj) {
                    final Object loadedApk;
                    ... ...
                    loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo, CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO());
        //-------------------------------------------------------------------------------------
            String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, pluginInfo.packageName);
            String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, pluginInfo.packageName);
            String apk = pluginInfo.applicationInfo.publicSourceDir;
            ... ...
            classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader());
            synchronized (loadedApk) {
                FieldUtils.writeDeclaredField(loadedApk, "mClassLoader", classloader);
            }
            ... ...
            found = true;
        //-------------------------------------------------------------------------------------
        if (found) {
            PluginProcessManager.preMakeApplication(hostContext, pluginInfo);
        }
    }

 

根據代碼的功能大致分為3段:

1.判斷ActivityThread的mPackages字段是否包含插件包,如果不包含,則調用getPackageInfoNoCheck()加載apk,獲取LoadedApk對象。

mPackages是ActivityThread裡的一個map:key是包名,value是對應的LoadedApk對象。getPackageInfoNoCheck()會創建一個新的LoadedApk對象,裡面包含了apk的所有信息,有了這些信息以後就可以啟動插件程序了。這個對象會被放進mPackages中待日後使用。

2.替換掉LoadedApk對象的mClassLoader字段

LoadedApk的class loader最終會被傳給Instrumentation,用來加載插件apk中的類。默認的class loader是一個PathClassLoader,這裡替換成了PluginClassLoader,目的和之前一樣是為了解決奇酷手機support V4庫加載的問題。

3.調用preMakeApplication(),下面節選了preMakeApplication()的代碼:

    private static void preMakeApplication(Context hostContext, ComponentInfo pluginInfo) {
        try {
            final Object loadedApk = sPluginLoadedApkCache.get(pluginInfo.packageName);
            if (loadedApk != null) {
                Object mApplication = FieldUtils.readField(loadedApk, "mApplication");
                if (mApplication != null) {
                    return;
                }
            ... ...
            MethodUtils.invokeMethod(loadedApk, "makeApplication", false, ActivityThreadCompat.getInstrumentation());
            ... ...
    }

首先判斷LoadedApk的mApplication字段是否為空,這段感覺有點多余,因為makeApplication()方法的開頭也會先判斷一下的。然後就是調用makeApplication()方法啦,這樣插件apk的Application就被創建出來了,onCreate()也會被執行。

二、插件進程die是如何被檢測到的?

上面分析過了,插件進程啟動的時候,也會創建宿主Application並調用其onCreate(),因此會調用到PluginHelper的applicationOnCreate()方法。

    public void applicationOnCreate(final Context baseContext) {
        mContext = baseContext;
        initPlugin(baseContext);
    }

在initPlugin()裡執行了下面兩個步驟:

? 添加一個ServiceConnection(PluginHelper實現了ServiceConnection接口)

? 調用PluginManager的init()方法去連接PluginManagerService

private void initPlugin(Context baseContext) {
        ... ...
    PluginManager.getInstance().addServiceConnection(PluginHelper.this);
    PluginManager.getInstance().init(baseContext);
        ... ...
}

// PluginManager.java
public void init(Context hostContext) {
    mHostContext = hostContext;
    connectToService();
}

看一下connectToService():

public void connectToService() {
    if (mPluginManager == null) {
        try {
            Intent intent = new Intent(mHostContext, PluginManagerService.class);
            intent.setPackage(mHostContext.getPackageName());
            mHostContext.startService(intent);
            mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
        } catch (Exception e) {
            Log.e(TAG, "connectToService", e);
        }
    }
}

首先startService(),然後再bindService(),這樣即使解綁了,服務還是可以繼續保持運行。連接上服務以後,會調用PluginManager的onServiceConnected(),這一步比較關鍵:

    public void onServiceConnected(final ComponentName componentName, final IBinder iBinder) {
        ... ...
        mPluginManager.waitForReady();
        mPluginManager.registerApplicationCallback(new IApplicationCallback.Stub() {
            @Override
            public Bundle onCallback(Bundle extra) throws RemoteException {
                return extra;
            }
        });
        ... ...
    }

看到沒,這裡注冊了一個ApplicationCallback,這是一個自定義的AIDL遠程調用接口,會調用到遠端的IPluginManagerImpl,進而調用進BaseActivityManagerService:

    public boolean registerApplicationCallback(int callingPid, int callingUid, IApplicationCallback callback) {
        return mRemoteCallbackList.register(callback, new ProcessCookie(callingPid, callingUid));
    }

注意,這可不是一個普通的list哦,這是一個RemoteCallbackList,在binder對端死掉的時候,會收到一個通知,這樣就能知道插件進程是死是活了,具體的處理放到onProcessDied()裡去實現。看一下這個類的實現:

    private class MyRemoteCallbackList extends RemoteCallbackList {
        @Override
        public void onCallbackDied(IApplicationCallback callback, Object cookie) {
            super.onCallbackDied(callback, cookie);
            if (cookie != null && cookie instanceof ProcessCookie) {
                ProcessCookie p = (ProcessCookie) cookie;
                onProcessDied(p.pid, p.uid);
            }
        }
    }

最後我們看一下RemoteCallbackList的register()方法,在注冊callback的時候會調用binder的linkToDeath,這樣當對端死掉的時候就能收到通知啦,就是這麼簡單:

    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }

至此,插件進程die如何被檢測到的問題就搞清楚了,如下圖:

\

三、插件進程是如何被管理的?

目前DroidPlugin的進程管理還是比較粗糙的,沒有考慮task affinity,策略也比較簡單粗暴。

主要邏輯實現在MyActivityManagerService裡,代碼就不貼了比較簡單,文字總結一下。

1. MyActivityManagerService裡維護了兩個進程列表:

? 一個叫StaticProcessList,包含了AndroidManifest.xml裡注冊的所有進程

? 一個叫RunningProcessList,包含了所有已經在運行的進程

2. 每次要啟動插件時,首先在RunningProcessList裡查找看是否有符合條件的進程:

? 如果該進程加載過這個包,並且進程名與插件包一致,返回直接使用

? 否則,遍歷該進程加載的所有包,如果與插件包簽名一致,返回直接使用

? 使用這種方式,相同簽名的多個插件包會運行在同一個進程中

3. 如果RunningProcessList裡找不到,就到StaticProcessList裡去查找看有沒有符合條件的進程:

?如果該進程在運行,但是是個空進程,也就是沒有啟動任何插件包,返回直接使用

? 如果該進程在運行但進程名為空,且該進程加載過這個包或者與插件包簽名一致,返回直接使用

? 如果該進程沒有運行,返回使用之

找到合適的進程以後,還要選出未被占用stub組件(activity/service/provider),然後把該組件的targetProcessName設置為目標進程。

 

另外還有個問題:如果進程不夠用了怎麼辦?需要設計一個進程回收策略。

每次selectStubXXX()、activity或者service調用onDestroy()、以及進程die的時候,都會調用runProcessGC()回收進程資源(好像少了個判斷?進程數量超過一個阈值的時候才需要回收吧)。如果是插件進程(非宿主進程),且不是持久進程:

? 按進程優先級排序,>=IMPORTANCE_SERVICE優先級的殺掉(數字越低優先級越高)

? 沒有任何activity、service、provider的空進程,殺掉

? 沒有activity,只有service的進程,獲取該進程的所有服務,調用stopSelf()停掉服務

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved