編輯:關於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()停掉服務
前言: 前面使用的退出程序用的是finish(),它只能退出當前Activity。如果Activity一多就不能一次性退出了。 1、退出應用程
說到工程模式,好多非技術流的玩家都很頭疼。手機工程模式給人的印象就是生硬的黑白屏,全屏的英文和代碼命令,就像視窗重新回到了DOS系統,雖然好奇但又怕手機變磚
一、debounce僅在過了一段指定的時間還沒發射數據時才發射一個數據,Debounce操作符會過濾掉發射速率過快的數據項。注:這個操作符會會接著最後一項數據發射原始Ob
引言Google I/O 2015 推出的 Android Design Support Library令人非常激動。Material Design的推出確實振奮了不少