Android外掛化原理分析(基於Neptune框架)
Android外掛化不算是一門新技術,發展了有一些年頭了。不同公司的外掛化方案大體原理上很相似。本文通過閱讀愛奇藝的 Neptune
框架來介紹外掛化的整體思路和流程。
外掛化基礎知識點
外掛應用安裝
所謂的外掛其實本質上也是一個apk。在原生的Android應用中,apk在執行時會被對映成一個 LoadedApk
物件。外掛在安裝之後也會被對映成類似的 PluginLoadedApk
物件,統一管理外掛的相關資訊。
public class PluginLoadedApk { public static final ConcurrentMap<String, Vector<Method>> sMethods = new ConcurrentHashMap<String, Vector<Method>>(1); private static final String TAG = "PluginLoadedApk"; /* 儲存注入到宿主ClassLoader的外掛 */ private static Set<String> sInjectedPlugins = Collections.synchronizedSet(new HashSet<String>()); /* 儲存所有的外掛ClassLoader */ private static Map<String, DexClassLoader> sAllPluginClassLoader = new ConcurrentHashMap<>(); /* 宿主的Context */ private final Context mHostContext; /* 宿主的ClassLoader */ private final ClassLoader mHostClassLoader; /* 宿主的Resource物件 */ private final Resources mHostResource; /* 宿主的包名 */ private final String mHostPackageName; /* 外掛的路徑 */ private final String mPluginPath; /* 外掛執行的程序名 */ private final String mProcessName; /* 外掛ClassLoader的parent */ private ClassLoader mParent; /* 外掛的類載入器 */ private DexClassLoader mPluginClassLoader; /* 外掛的Resource物件 */ private Resources mPluginResource; /* 外掛的AssetManager物件 */ private AssetManager mPluginAssetManager; /* 外掛的全域性預設主題 */ private Resources.Theme mPluginTheme; /* 外掛的詳細資訊,主要通過解析AndroidManifest.xml獲得 */ private PluginPackageInfo mPluginPackageInfo; /* 外掛工程的包名 */ private String mPluginPackageName; /* 外掛的Application */ private Application mPluginApplication; /* 自定義外掛Context,主要用來改寫其中的一些方法從而改變外掛行為 */ private PluginContextWrapper mPluginAppContext; /* 自定義Instrumentation,對Activity跳轉進行攔截 */ private PluginInstrument mPluginInstrument; ... } 複製程式碼
外掛的安裝分為內建外掛(asset目錄,sdcard)和線上外掛兩部分。
- 內建外掛:
- 約定存放在assets/pluginapp/<plugin_pkg_name>.apk形式,安裝時解壓到/data/data/<host_pkg_name>/app_pluginapp目錄
- sdcard外掛,允許除錯模式下安裝,以<plugin_pkg_name>.apk命名
- 線上外掛:直接將外掛下載到sdcard目錄上,然後拷貝到/data/data/<host_pkg_name>/app_pluginapp目錄下;為了減少拷貝操作,可以直接下載到/data/data/<hots_pkg_name>/app_pluginapp目錄;
外掛的安裝通過執行在獨立程序的Service完成,主要防止部分機型dexopt hang住主程序。
dexopt
Android根據系統版本不同會採用兩種虛擬機器。Dalvik虛擬機器是JIT方式解釋執行dex位元組碼;ART虛擬機器是AOT方式將dex位元組碼轉化為oat機器碼。
- Dalvik是執行時解釋dex檔案,安裝比較快,開啟應用比較慢,應用佔用空間小
- ART是安裝的時候位元組碼預編譯成機器碼儲存在本地,執行的時候直接就可以執行的,安裝慢,開啟應用快,佔用空間大;
如果當前執行在Dalvik虛擬機器下,Dalvik會對classes.dex進行一次“翻譯”,“翻譯”的過程也就是守護程序installd的函式dexopt來對dex位元組碼進行優化,實際上也就是由dex檔案生成odex檔案,最終odex檔案被儲存在手機的VM快取目錄data/dalvik-cache下(注意!這裡所生成的odex檔案依舊是以dex為字尾名,格式如:system@priv-app@[email protected]@classes.dex)。如果當前運行於ART模式下, ART同樣會在首次進入系統的時候呼叫/system/bin/dexopt(此處應該是dex2oat工具吧)工具來將dex位元組碼翻譯成本地機器碼,儲存在data/dalvik-cache下。 那麼這裡需要注意的是,無論是對dex位元組碼進行優化,還是將dex位元組碼翻譯成本地機器碼,最終得到的結果都是儲存在相同名稱的一個odex檔案裡面的,但是前者對應的是一個.dex檔案(表示這是一個優化過的dex),後者對應的是一個.oat檔案。通過這種方式,原來任何通過絕對路徑引用了該odex檔案的程式碼就都不需要修改了。 由於在系統首次啟動時會對應用進行安裝,那麼在預置APK比較多的情況下,將會大大增加系統首次啟動的時間。
對於外掛安裝來說,外掛的安裝通過執行在獨立程序的Service完成,主要防止部分機型dexopt hang住主程序。
外掛安裝過程主要執行以下幾步:
- 拷貝apk到內建儲存區,重新命名為<plugin_pkg_name>.apk
- 解壓apk中的so庫到app_pluginapp/<plugin_pkg_name>/lib目錄
- dexopt優化外掛dex,Android 7.0以上第一次會使用解釋模式執行dex,優化載入速度
類載入

Java中的類都是通過ClassLoader載入的,而Android中類的載入也離不開ClassLoadder。在Android系統中,主要的ClassLoader有三個:
- BootClassLoader:Android系統啟動時用來預載入常用的類
- PathClassLoader:用來載入系統和應用程式中的類,如果是非系統應用程式類,則會載入/data/app目錄下的dex、apk或jar檔案
- DexClassLoader:可以載入指定路徑的dex、apk或jar檔案,支援從SD卡進行載入,是外掛化的技術基礎
類載入的雙親委派機制
某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。
關於外掛中類的載入機制有兩種處理方式,一種是單類載入機制,另一種是多類載入機制;單類載入器機制,即所有外掛APP的類都通過宿主的ClassLoader(即PathClassLoader)進行載入,與MultiDex、Qzone熱修復技術類似,通過Dex前插後者後插的方式實現。採用單類載入器模型,隨著業務團隊和外掛的增加,很容易出現類重複問題,無法保證所有類都是獨一無二的。多類載入器機制是指每個外掛都由一個新的類載入器例項來載入,元件間的類是完全隔離,不能直接互相訪問。
利用ClassLoader的雙親委派機制,多類載入有兩種思路:
- 自定義代理的ClassLoader設定為PathClassLoader的父類載入器,那麼自定義的類載入器就能代理所有的類載入行為;在代理ClassLoader內部做類載入的邏輯分發,先嚐試從宿主的ClassLoader載入,再嘗試外掛的ClassLoader載入。(好處:只需要在啟動時hook ClassLoader,新增DelegateClassLoader,後續的類載入由DelegateClassLoader分發;對於未載入的外掛,可以通過包名匹配,先觸發外掛載入,再載入類)
- 每個
PluginLoadedApk
維護一個PluginClassLoader
例項,其父ClassLoader是PathClassLoader;在類載入時,先嚐試從宿主的ClassLoader載入,再嘗試本外掛的ClassLoader載入。(好處:每個外掛維護自己的PluginLoadedApk
,不存在分發,類隔離做的更好)
資源載入
Android APP執行除了類還有資源,執行時需要載入資源;對於Android來說,資源是通過AssetManager和Resources這兩個類管理。App在執行時查詢資源是通過當前Context的Resource例項中查詢,在Resource內部是通過AssetManager管理當前的資源,AssetManager維護了資源包路徑的陣列。外掛化的原理,就是將外掛的資源路徑新增到AssetManager的資源路徑陣列中,通過反射AssetManager的隱藏方法addAssetPath實現外掛資源的載入。
try{ AssetManager am = AssetManager.class.newInstance(); Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); addAssetPath.setAccessible(true); addAssetPath.invoke(am, pluginApkPath); Resources pluginResources = new Resources(am, hostResource.getDisplayMetrics(), hostResources.getConfiguration()); } catch (Exception e) { e.printStackTrace(); } 複製程式碼
各種外掛化方案的資源載入原理都是一樣,區別主要在於不同外掛的資源管理,是公用一套資源還是外掛獨立資源,外掛和宿主的資源訪問ID衝突問題。
- 公用一套資源需要採用固定資源id及ID分段機制避免衝突
- 獨立資源方案,不同外掛管理自己的資源
外掛化中資源使用限制
限制:外掛不能使用自己的轉場動畫,只能使用宿主、系統定義的轉場動畫。
轉場動畫最終會呼叫到 IActivityManager
,發起IPC請求,與AMS互動
public void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim) throws RemoteException; 複製程式碼
Apk打包流程
先附上兩張Android原生打包流程圖



在外掛編譯打包時,需要完成以下幾件事:
- 外掛的資源和宿主的資源通過不同的資源分段區分
- 在外掛化中,如果外掛需要引用宿主的資源,則需要將宿主的資源id進行固定
- 處理外掛aapt的編譯產物,不將宿主的資源打入apk中
- 處理Manifest檔案,將佔坑的四大元件寫入Manifest檔案中
- 在位元組碼層面對程式碼做修改
Hook點
- Hook MergeResources Task,將public.xml檔案拷貝至資源merge完成的目錄
- Hook ProcessAndroidResources Task,修改生成的arsc檔案。
- Hook ManifestProcessorTask, 在Manifest中插入特定資訊。
- Hook dexTask/Transform,最原始碼的修改
四大元件的外掛化
Activity的外掛化
Activity啟動可以分為兩個階段:往AMS發起啟動Activity的請求、AMS校驗後執行Activity啟動。
往AMS發起請求

在Android 8.0(api 26)以下,應用往AMS發起啟動Activity請求的流程如上。在Android 8.0及以上版本,AMN、AMP已經被棄用,而是使用 ActivityManager
類; 參考文章 。
Hook點:
- Hook Instrumentation類,代理execStartActivity方法
- Hook AMN(<26)/ActivityManager(>=26),動態代理IActivityManager介面的例項物件
AMS校驗後啟動Activity

Android P(api 28)對Activity的啟動過程做了修改;在Android P之前,是在H類的handleMessage方法的switch分支語句中,有專門處理啟動Activity的邏輯
public void handleMessage(Message msg) { switch (msg.what) { case LAUNCH_ACTIVITY: { final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); } break; //以下省略很多程式碼 } } 複製程式碼
在Android P中,啟動Activity的這部分邏輯,被轉移到了LaunchActivityItem類的execute方法中
public class LaunchActivityItem extends ClientTransactionItem { @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mIsForward, mProfilerInfo, client); client.handleLaunchActivity(r, pendingActions, null /* customIntent */); } } 複製程式碼
Android P把H類中的100-109這10個訊息都刪除了,取而代之的是159這個訊息,名為EXECUTE_TRANSACTION。收斂了Activity相關的message分發。
Hook點:
- Hook H類,將佔坑Activity替換成真實的Activity(需要做Android P的適配)
- Hook Instrumentation類,替換成自定義的Instrument,重寫newActivity、callActivityOnCreate等方法
Service的外掛化
Service啟動可以分為兩個階段:往AMS發起啟動Service的請求、AMS校驗後執行Service啟動。
往AMS發起啟動Service的請求

在Android 8.0(api 26)以下,應用往AMS發起啟動Service請求的流程如上。在Android 8.0及以上版本,AMN、AMP已經被棄用,而是使用ActivityManager類。
Hook點
- Hook ContextWrapper;替換成自定義的ContextWrapper,將Service替換成佔坑的Service
- Hook AMN(<26)/ActivityManager(>=26),動態代理IActivityManager介面的例項物件
AMS校驗後執行Service啟動

Hook點:
- 在佔坑Service的onStartCommand提取真實Service資訊,並分發執行真實Service邏輯
- Hook handleServiceCreate方法,發射獲取ServiceInfo,修改ServiceInfo的name欄位為真實Service的名字。載入真實的Service類。
BroadCastReceiver外掛化

廣播分為靜態廣播、動態廣播。動態廣播在執行時向AMS註冊相關資訊。
Hook點:
- 靜態廣播轉換為動態廣播
ContentProvider外掛化

Hook點:
- Hook AMN(<26)/ActivityManager(>=26),動態代理IActivityManager介面的例項物件;將目標ContentProvider的資訊放在query引數中
Neptune原始碼分析
Neptune類
Neptune
類是整個外掛系統的入口類,主要方法如下
- init()方法
/** * 初始化Neptune外掛環境 * * @param application宿主的Appliction * @param config配置資訊 */ public static void init(Application application, NeptuneConfig config) { sHostContext = application; sGlobalConfig = config != null ? config : new NeptuneConfig.NeptuneConfigBuilder().build(); PluginDebugLog.setIsDebug(sGlobalConfig.isDebug()); boolean hookInstr = VersionUtils.hasPie() || sGlobalConfig.getSdkMode() != NeptuneConfig.LEGACY_MODE; if (hookInstr) { //Hook Instrumentation hookInstrumentation(); } // 呼叫getInstance()方法會初始化bindService,管理外掛安裝的Service PluginPackageManagerNative.getInstance(sHostContext).setPackageInfoManager(sGlobalConfig.getPluginInfoProvider()); // 註冊外掛解除安裝監聽廣播 PluginManager.registerUninstallReceiver(sHostContext); } 複製程式碼
- launchPlugin()方法
/** * 啟動外掛 * * @param mHostContext主工程的上下文 * @param mIntent需要啟動的元件的Intent * @param mServiceConnection bindService時需要的ServiceConnection,如果不是bindService的方式啟動元件,傳入Null * @param mProcessName需要啟動的外掛執行的程序名稱,外掛方可以在Application的android:process指定 *如果沒有指定,則有外掛中心分配 */ public static void launchPlugin(final Context mHostContext, final Intent mIntent, final ServiceConnection mServiceConnection, final String mProcessName) { final String packageName = tryParsePkgName(mHostContext, mIntent); if (TextUtils.isEmpty(packageName)) { if (null != mHostContext) { deliver(mHostContext, false, mHostContext.getPackageName(), ErrorType.ERROR_PLUGIN_LOAD_NO_PKGNAME_INTENT); } PluginDebugLog.runtimeLog(TAG, "enterProxy packageName is null return! packageName: " + packageName); return; } // 處理不同程序跳轉 final String targetProcessName = TextUtils.isEmpty(mProcessName) ? ProcessManager.chooseDefaultProcess(mHostContext, packageName) : mProcessName; String currentProcess = FileUtils.getCurrentProcessName(mHostContext); if (!TextUtils.equals(currentProcess, targetProcessName)) { // 啟動程序和目標程序不一致,需要先啟動目標程序,初始化PluginLoadedApk Intent transIntent = new Intent(); transIntent.setAction(IntentConstant.ACTION_START_PLUGIN); //目標程序的Service中重新通過mIntent啟動外掛 transIntent.putExtra(IntentConstant.EXTRA_START_INTENT_KEY, mIntent); transIntent.putExtra(IntentConstant.EXTRA_TARGET_PROCESS, targetProcessName); try { String proxyServiceName = ComponentFinder.matchServiceProxyByFeature(targetProcessName); transIntent.setClass(mHostContext, Class.forName(proxyServiceName)); mHostContext.startService(transIntent); } catch (ClassNotFoundException e) { e.printStackTrace(); } return; } LinkedBlockingQueue<Intent> cacheIntents = PActivityStackSupervisor.getCachedIntent(packageName); //該外掛有其他任務排隊中,mIntent新增到隊尾 if (cacheIntents != null && cacheIntents.size() > 0) { cacheIntents.add(mIntent); PluginDebugLog.runtimeLog(TAG, "LoadingMap is not empty, Cache current intent, intent: " + mIntent + ", packageName: " + packageName); return; } boolean isLoadAndInit = isPluginLoadedAndInit(packageName); if (!isLoadAndInit) { if (null == cacheIntents) { cacheIntents = new LinkedBlockingQueue<Intent>(); PActivityStackSupervisor.addCachedIntent(packageName, cacheIntents); } // 快取這個intent,等待PluginLoadedApk載入到記憶體之後再啟動這個Intent PluginDebugLog.runtimeLog(TAG, "Environment is initializing and loading, cache current intent first, intent: " + mIntent); cacheIntents.add(mIntent); } else { PluginDebugLog.runtimeLog(TAG, "Environment is already ready, launch current intent directly: " + mIntent); //可以直接啟動元件 readyToStartSpecifyPlugin(mHostContext, mServiceConnection, mIntent, true); return; } // 處理外掛的依賴關係 final PluginLiteInfo info = PluginPackageManagerNative.getInstance(mHostContext.getApplicationContext()) .getPackageInfo(packageName); //獲取外掛的依賴關係 final List<String> mPluginRefs = PluginPackageManagerNative.getInstance(mHostContext) .getPluginRefs(packageName); if (info != null && mPluginRefs != null && mPluginRefs.size() > 0) { PluginDebugLog.runtimeLog(TAG, "start to check dependence installation size: " + mPluginRefs.size()); //依賴的總數量 final AtomicInteger count = new AtomicInteger(mPluginRefs.size()); for (String pkgName : mPluginRefs) { PluginDebugLog.runtimeLog(TAG, "start to check installation pkgName: " + pkgName); final PluginLiteInfo refInfo = PluginPackageManagerNative.getInstance(mHostContext.getApplicationContext()) .getPackageInfo(pkgName); PluginPackageManagerNative.getInstance(mHostContext.getApplicationContext()).packageAction(refInfo, new IInstallCallBack.Stub() { @Override public void onPackageInstalled(PluginLiteInfo packageInfo) { //未ready的依賴數量 count.getAndDecrement(); PluginDebugLog.runtimeLog(TAG, "check installation success pkgName: " + refInfo.packageName); if (count.get() == 0) { PluginDebugLog.runtimeLog(TAG, "start Check installation after check dependence packageName: " + packageName); //真正載入外掛 checkPkgInstallationAndLaunch(mHostContext, info, mServiceConnection, mIntent, targetProcessName); } } @Override public void onPackageInstallFail(PluginLiteInfo info, int failReason) throws RemoteException { PluginDebugLog.runtimeLog(TAG, "check installation failed pkgName: " + info.packageName + " failReason: " + failReason); count.set(-1); } }); } } else if (info != null) { PluginDebugLog.runtimeLog(TAG, "start Check installation without dependence packageName: " + packageName); //真正載入外掛 checkPkgInstallationAndLaunch(mHostContext, info, mServiceConnection, mIntent, targetProcessName); } else { //異常case PluginDebugLog.runtimeLog(TAG, "pluginLiteInfo is null packageName: " + packageName); PActivityStackSupervisor.clearLoadingIntent(packageName); if (PluginDebugLog.isDebug()) { throw new IllegalStateException("pluginLiteInfo is null when launchPlugin " + packageName); } } } 複製程式碼
/** * 真正啟動一個元件 * * @param mHostContext 主工程Context * @param mLoadedApk需要啟動的外掛的PluginLoadedApk * @param mIntent需要啟動元件的Intent * @param mConnectionbindService時需要的ServiceConnection,如果不是bindService的方式啟動元件,傳入Null */ private static void doRealLaunch(Context mHostContext, PluginLoadedApk mLoadedApk, Intent mIntent, ServiceConnection mConnection) { String targetClassName = ""; ComponentName mComponent = mIntent.getComponent(); if (mComponent != null) { //顯式啟動 targetClassName = mComponent.getClassName(); PluginDebugLog.runtimeLog(TAG, "launchIntent_targetClassName:" + targetClassName); if (TextUtils.isEmpty(targetClassName)) { targetClassName = mLoadedApk.getPluginPackageInfo().getDefaultActivityName(); } } String pkgName = mLoadedApk.getPluginPackageName(); Class<?> targetClass = null; if (!TextUtils.isEmpty(targetClassName) && !TextUtils.equals(targetClassName, IntentConstant.EXTRA_VALUE_LOADTARGET_STUB)) { try { //外掛ClassLoader載入類 targetClass = mLoadedApk.getPluginClassLoader().loadClass(targetClassName); } catch (Exception e) { deliver(mHostContext, false, pkgName, ErrorType.ERROR_PLUGIN_LOAD_COMP_CLASS); PluginDebugLog.runtimeLog(TAG, "launchIntent loadClass failed for targetClassName: " + targetClassName); executeNext(mLoadedApk, mConnection, mHostContext); return; } } String action = mIntent.getAction(); if (TextUtils.equals(action, IntentConstant.ACTION_PLUGIN_INIT) || TextUtils.equals(targetClassName, IntentConstant.EXTRA_VALUE_LOADTARGET_STUB)) { PluginDebugLog.runtimeLog(TAG, "launchIntent load target stub!"); //通知外掛初始化完畢 if (targetClass != null && BroadcastReceiver.class.isAssignableFrom(targetClass)) { Intent newIntent = new Intent(mIntent); newIntent.setComponent(null); newIntent.putExtra(IntentConstant.EXTRA_TARGET_PACKAGE_KEY, pkgName); newIntent.setPackage(mHostContext.getPackageName()); //通過廣播通知外掛載入完成 mHostContext.sendBroadcast(newIntent); } // 表示後臺載入,不需要處理該Intent executeNext(mLoadedApk, mConnection, mHostContext); return; } mLoadedApk.changeLaunchingIntentStatus(true); PluginDebugLog.runtimeLog(TAG, "launchIntent_targetClass: " + targetClass); if (targetClass != null && Service.class.isAssignableFrom(targetClass)) { //處理的是Service, 宿主啟動外掛Service只能通過顯式啟動 ComponentFinder.switchToServiceProxy(mLoadedApk, mIntent, targetClassName); if (mConnection == null) { mHostContext.startService(mIntent); } else { mHostContext.bindService(mIntent, mConnection, mIntent.getIntExtra(IntentConstant.BIND_SERVICE_FLAGS, Context.BIND_AUTO_CREATE)); } } else { //處理的是Activity ComponentFinder.switchToActivityProxy(pkgName, mIntent, -1, mHostContext); PActivityStackSupervisor.addLoadingIntent(pkgName, mIntent); Context lastActivity = null; PActivityStackSupervisor mActivityStackSupervisor = mLoadedApk.getActivityStackSupervisor(); lastActivity = mActivityStackSupervisor.getAvailableActivity(); if (mHostContext instanceof Activity) { mHostContext.startActivity(mIntent); } else if (lastActivity != null) { // Clear the Intent.FLAG_ACTIVITY_NEW_TASK int flag = mIntent.getFlags(); flag = flag ^ Intent.FLAG_ACTIVITY_NEW_TASK; mIntent.setFlags(flag); lastActivity.startActivity(mIntent); } else { // Add the Intent.FLAG_ACTIVITY_NEW_TASK mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mHostContext.startActivity(mIntent); } } // 執行下一個Intent executeNext(mLoadedApk, mConnection, mHostContext); } 複製程式碼
/** * 非同步初始化外掛,宿主靜默載入外掛 * * @deprecated 不建議使用 */ @Deprecated public static void initPluginAsync(final Context mHostContext, final String packageName, final String processName, final org.qiyi.pluginlibrary.listenter.IPluginStatusListener mListener) { // 外掛已經載入 if (PluginManager.isPluginLoadedAndInit(packageName)) { if (mListener != null) { mListener.onInitFinished(packageName); } return; } BroadcastReceiver recv = new BroadcastReceiver() { public void onReceive(Context ctx, Intent intent) { String curPkg = IntentUtils.getTargetPackage(intent); if (IntentConstant.ACTION_PLUGIN_INIT.equals(intent.getAction()) && TextUtils.equals(packageName, curPkg)) { PluginDebugLog.runtimeLog(TAG, "收到自定義的廣播org.qiyi.pluginapp.action.TARGET_LOADED"); //外掛初始化結束 if (mListener != null) { mListener.onInitFinished(packageName); } mHostContext.getApplicationContext().unregisterReceiver(this); } } }; PluginDebugLog.runtimeLog(TAG, "註冊自定義廣播org.qiyi.pluginapp.action.TARGET_LOADED"); IntentFilter filter = new IntentFilter(); filter.addAction(IntentConstant.ACTION_PLUGIN_INIT); //註冊廣播 mHostContext.getApplicationContext().registerReceiver(recv, filter); Intent intent = new Intent(); intent.setAction(IntentConstant.ACTION_PLUGIN_INIT); intent.setComponent(new ComponentName(packageName, recv.getClass().getName())); //傳送一個啟動外掛的intent launchPlugin(mHostContext, intent, processName); } 複製程式碼
NeptuneInstrument(PluginInstrument)類
Hook系統原生的Instrument,替換成NeptuneInstrument。 NeptuneInstrument
繼承 PluginInstrument
。 PluginInstrument
負責往AMS傳送的請求, NeptuneInstrument
負責AMS返回的結果處理。
/** * 負責轉移外掛的跳轉目標<br> * 用於Hook外掛Activity中Instrumentation * * @see android.app.Activity#startActivity(android.content.Intent) */ public class PluginInstrument extends Instrumentation { private static final String TAG = "PluginInstrument"; private static ConcurrentMap<String, Vector<Method>> sMethods = new ConcurrentHashMap<String, Vector<Method>>(5); Instrumentation mHostInstr; private String mPkgName; private ReflectionUtils mInstrumentRef; /** * 外掛的Instrumentation */ public PluginInstrument(Instrumentation hostInstr) { this(hostInstr, ""); } public PluginInstrument(Instrumentation hostInstr, String pkgName) { mHostInstr = hostInstr; mInstrumentRef = ReflectionUtils.on(hostInstr); mPkgName = pkgName; } /** * 如果是PluginInstrumentation,拆裝出原始的HostInstr * * @param instrumentation * @return */ public static Instrumentation unwrap(Instrumentation instrumentation) { if (instrumentation instanceof PluginInstrument) { return ((PluginInstrument) instrumentation).mHostInstr; } return instrumentation; } /** * @Override */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ... } /** * @Override */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { ... } /** * @Override For below android 6.0 */ public ActivityResult execStartActivityAsCaller( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options, int userId) { ... } /** * @Override For android 6.0 */ public ActivityResult execStartActivityAsCaller( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity, int userId) { ... } /** * @Override */ public void execStartActivitiesAsUser( Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId) { ... } /** * @Override For below android 6.0, start activity from Fragment */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) { ... } /** * @Override For android 6.0, start activity from Fragment */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { ... } } 複製程式碼
/** * 自定義的全域性的Instrumentation * 負責轉移外掛的跳轉目標和建立外掛的Activity例項 * 用於Hook ActivityThread中的全域性Instrumentation */ public class NeptuneInstrument extends PluginInstrument { private static final String TAG = "NeptuneInstrument"; private PluginActivityRecoveryHelper mRecoveryHelper = new PluginActivityRecoveryHelper(); public NeptuneInstrument(Instrumentation hostInstr) { super(hostInstr); } @Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { ... } @Override public void callActivityOnCreate(Activity activity, Bundle icicle) { ... } @Override public void callActivityOnDestroy(Activity activity) { ... } @Override public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) { ... } /** * 將Activity反射相關操作分發給外掛Activity的基類 */ private boolean dispatchToBaseActivity(Activity activity) { //這個模式已棄用 return Neptune.getConfig().getSdkMode() == NeptuneConfig.INSTRUMENTATION_BASEACT_MODE && activity instanceof IPluginBase; } } 複製程式碼
NeptuneInstrument
類中的幾個方法有一些特殊的邏輯處理,下面單獨分析:
@Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { if (className.startsWith(ComponentFinder.DEFAULT_ACTIVITY_PROXY_PREFIX)) { // 外掛代理Activity,替換回外掛真實的Activity String[] result = IntentUtils.parsePkgAndClsFromIntent(intent); String packageName = result[0]; String targetClass = result[1]; PluginDebugLog.runtimeLog(TAG, "newActivity: " + className + ", targetClass: " + targetClass); if (!TextUtils.isEmpty(packageName)) { //找到對應外掛 PluginLoadedApk loadedApk = PluginManager.getPluginLoadedApkByPkgName(packageName); if (loadedApk != null && targetClass != null) { Activity activity = mHostInstr.newActivity(loadedApk.getPluginClassLoader(), targetClass, intent); activity.setIntent(intent); if (!dispatchToBaseActivity(activity)) { // 這裡需要替換Resources,是因為ContextThemeWrapper會快取一個Resource物件,而在Activity#attach()和 // Activity#onCreate()之間,系統會呼叫Activity#setTheme()初始化主題,Android 4.1+ //替換成外掛的Resource資源 ReflectionUtils.on(activity).setNoException("mResources", loadedApk.getPluginResource()); } return activity; } else if (loadedApk == null) { // loadedApk 為空,可能是正在恢復程序,跳轉到 RecoveryActivity return mHostInstr.newActivity(cl, mRecoveryHelper.selectRecoveryActivity(className), intent); } } } return mHostInstr.newActivity(cl, className, intent); } 複製程式碼
@Override public void callActivityOnCreate(Activity activity, Bundle icicle) { boolean isRecovery = activity instanceof TransRecoveryActivity0; if (isRecovery) { //外掛載入中的Activity,使用宿主的Instrument mRecoveryHelper.saveIcicle(activity, icicle); mHostInstr.callActivityOnCreate(activity, null); return; } final Intent intent = activity.getIntent(); String[] result = IntentUtils.parsePkgAndClsFromIntent(intent); boolean isLaunchPlugin = false; if (IntentUtils.isIntentForPlugin(intent)) { String packageName = result[0]; String targetClass = result[1]; if (!TextUtils.isEmpty(packageName)) { PluginDebugLog.runtimeLog(TAG, "callActivityOnCreate: " + packageName); PluginLoadedApk loadedApk = PluginManager.getPluginLoadedApkByPkgName(packageName); if (loadedApk != null) { icicle = mRecoveryHelper.recoveryIcicle(activity, icicle); // 設定 extra 的 ClassLoader,不然可能會出現 BadParcelException, ClassNotFound if (icicle != null) { icicle.setClassLoader(loadedApk.getPluginClassLoader()); } if (!dispatchToBaseActivity(activity)) { // 如果分發給外掛Activity的基類了,就不需要在這裡反射hook替換相關成員變量了 try { ReflectionUtils activityRef = ReflectionUtils.on(activity); //設定為外掛資源 activityRef.setNoException("mResources", loadedApk.getPluginResource()); //設定外掛的Application activityRef.setNoException("mApplication", loadedApk.getPluginApplication()); Context pluginContext = new PluginContextWrapper(activity.getBaseContext(), packageName); //替換為PluginContextWrapper,處理inflate相關 ReflectionUtils.on(activity, ContextWrapper.class).set("mBase", pluginContext); // 5.0以下ContextThemeWrapper內會儲存一個mBase,也需要反射替換掉 ReflectionUtils.on(activity, ContextThemeWrapper.class).setNoException("mBase", pluginContext); //替換為外掛的Instrumentation ReflectionUtils.on(activity).setNoException("mInstrumentation", loadedApk.getPluginInstrument()); // 修改外掛Activity的ActivityInfo, theme, window等資訊 PluginActivityControl.changeActivityInfo(activity, targetClass, loadedApk); } catch (Exception e) { PluginDebugLog.runtimeLog(TAG, "callActivityOnCreate with exception: " + e.getMessage()); } } if (activity.getParent() == null) { //Activity棧的邏輯是怎麼處理的? loadedApk.getActivityStackSupervisor().pushActivityToStack(activity); } isLaunchPlugin = true; } } IntentUtils.resetAction(intent);//恢復Action } try { mHostInstr.callActivityOnCreate(activity, icicle); if (isLaunchPlugin) { NotifyCenter.notifyPluginStarted(activity, intent); NotifyCenter.notifyPluginActivityLoaded(activity); } //check是否需要hook callActivityOnRestoreInstanceState方法 mRecoveryHelper.mockActivityOnRestoreInstanceStateIfNeed(this, activity); } catch (Exception e) { ErrorUtil.throwErrorIfNeed(e); if (isLaunchPlugin) { NotifyCenter.notifyStartPluginError(activity); } activity.finish(); } } 複製程式碼
@Override public void callActivityOnDestroy(Activity activity) { mHostInstr.callActivityOnDestroy(activity); if (activity.getParent() != null) { return; } final Intent intent = activity.getIntent(); String pkgName = IntentUtils.parsePkgNameFromActivity(activity); if (IntentUtils.isIntentForPlugin(intent) || intent == null) { // intent為null時,如果能夠從Activity中解析出pkgName,也應該是外掛的頁面 if (!TextUtils.isEmpty(pkgName)) { PluginDebugLog.runtimeLog(TAG, "callActivityOnDestroy: " + pkgName); PluginLoadedApk loadedApk = PluginManager.getPluginLoadedApkByPkgName(pkgName); if (loadedApk != null) { //退出外掛的Activity棧 loadedApk.getActivityStackSupervisor().popActivityFromStack(activity); } } } } 複製程式碼
@Override public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) { if (activity instanceof TransRecoveryActivity0) { mRecoveryHelper.saveSavedInstanceState(activity, savedInstanceState); return; } if (IntentUtils.isIntentForPlugin(activity.getIntent())) { String pkgName = IntentUtils.parsePkgAndClsFromIntent(activity.getIntent())[0]; PluginLoadedApk loadedApk = PluginManager.getPluginLoadedApkByPkgName(pkgName); if (loadedApk != null && savedInstanceState != null) { //用外掛的ClassLoader恢復資料 savedInstanceState.setClassLoader(loadedApk.getPluginClassLoader()); } } mHostInstr.callActivityOnRestoreInstanceState(activity, savedInstanceState); } 複製程式碼
外掛Activity任務棧
/** * 外掛的Activity棧抽象, 和系統的{@link com.android.server.am.ActivityStack}類似 */ public class PActivityStack { private final LinkedList<Activity> mActivities; // taskAffinity private String taskName; PActivityStack(String taskName) { this.taskName = taskName; mActivities = new LinkedList<>(); } /** * 獲取當前任務棧的名稱 */ public String getTaskName() { return taskName; } public LinkedList<Activity> getActivities() { return mActivities; } public int size() { return mActivities.size(); } public synchronized boolean isEmpty() { return mActivities.isEmpty(); } // 放入連結串列的前面 public synchronized void push(Activity activity) { mActivities.addFirst(activity); } public synchronized void insertFirst(Activity activity) { mActivities.addLast(activity); } public synchronized boolean pop(Activity activity) { return mActivities.remove(activity); } public synchronized Activity getTop() { return mActivities.getFirst(); } /** * 清空當前任務棧裡的Activity */ public void clear(boolean needFinish) { Iterator<Activity> iterator = mActivities.iterator(); while (iterator.hasNext()) { Activity activity = iterator.next(); if (activity != null && needFinish && !FileUtils.isFinished(activity)) { activity.finish(); } iterator.remove(); } } } 複製程式碼
PActivityStackSupervisor
管理Activity任務棧
/** * 處理Activity的launchMode,給Intent新增相關的Flags */ public void dealLaunchMode(Intent intent) { if (null == intent) { return; } String targetActivity = IntentUtils.getTargetClass(intent); if (TextUtils.isEmpty(targetActivity)) { return; } PluginDebugLog.runtimeLog(TAG, "dealLaunchMode target activity: " + intent + " source: " + targetActivity); // 不支援LAUNCH_SINGLE_INSTANCE ActivityInfo info = mLoadedApk.getPluginPackageInfo().getActivityInfo(targetActivity); if (info == null || info.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { return; } boolean isSingleTop = info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP || (intent.getFlags() & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0; boolean isSingleTask = info.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK; boolean isClearTop = (intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0; PluginDebugLog.runtimeLog(TAG, "dealLaunchMode isSingleTop " + isSingleTop + " isSingleTask " + isSingleTask + " isClearTop " + isClearTop); int flag = intent.getFlags(); PluginDebugLog.runtimeLog(TAG, "before flag: " + Integer.toHexString(intent.getFlags())); if ((isSingleTop || isSingleTask) && (flag & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) { flag = flag ^ Intent.FLAG_ACTIVITY_SINGLE_TOP; } if ((isSingleTask || isClearTop) && (flag & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { flag = flag ^ Intent.FLAG_ACTIVITY_CLEAR_TOP; } intent.setFlags(flag); PluginDebugLog.runtimeLog(TAG, "after flag: " + Integer.toHexString(intent.getFlags())); if (isSingleTop && !isClearTop) { // 判斷棧頂是否為需要啟動的Activity, 只需要處理前臺棧 Activity activity = null; if (!mFocusedStack.isEmpty()) { activity = mFocusedStack.getTop(); } boolean hasSameActivity = false; String proxyClsName = ComponentFinder.findActivityProxy(mLoadedApk, info); if (activity != null) { // 棧內有例項, 可能是ProxyActivity,也可能是外掛真實的Activity //Fix: 新的實現中只有外掛真實的Activity if (TextUtils.equals(proxyClsName, activity.getClass().getName()) || TextUtils.equals(targetActivity, activity.getClass().getName())) { String key = getActivityStackKey(activity); if (!TextUtils.isEmpty(key) && TextUtils.equals(targetActivity, key)) { intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); hasSameActivity = true; } } } if (hasSameActivity) { handleOtherPluginActivityStack(activity, mFocusedStack); } } else if (isSingleTask || isClearTop) { PActivityStack targetStack; // 需要搜尋的任務棧 boolean fromBackStack = false; if (isClearTop) { targetStack = mFocusedStack; } else { // singleTask if (mLastFocusedStack != null && TextUtils.equals(mLastFocusedStack.getTaskName(), matchTaskName(info.taskAffinity))) { // 後臺棧和Activity的taskAffinity匹配 targetStack = mLastFocusedStack; fromBackStack = true; PluginDebugLog.runtimeLog(TAG, "dealLaunchMode search in background stack: " + info.taskAffinity); } else { // 前臺棧中搜索 targetStack = mFocusedStack; } } // 查詢棧中是否存在已有例項 Activity found = null; // 遍歷已經起過的activity for (Activity activity : targetStack.getActivities()) { String proxyClsName = ComponentFinder.findActivityProxy(mLoadedApk, info); if (activity != null) { if (TextUtils.equals(proxyClsName, activity.getClass().getName()) || TextUtils.equals(targetActivity, activity.getClass().getName())) { String key = getActivityStackKey(activity); if (!TextUtils.isEmpty(key) && TextUtils.equals(targetActivity, key)) { PluginDebugLog.runtimeLog(TAG, "dealLaunchMode found:" + IntentUtils.dump(activity)); found = activity; break; } } } } // 棧中已經有當前activity if (found != null) { // 處理其他外掛的邏輯 // 在以這兩種SingleTask, ClearTop flag啟動情況下,在同一個棧的情況下 handleOtherPluginActivityStack(found, targetStack); // 處理當前外掛的Activity List<Activity> popActivities = new ArrayList<Activity>(5); for (Activity activity : targetStack.getActivities()) { if (activity == found) { if (isSingleTask || isSingleTop) { PluginDebugLog.runtimeLog(TAG, "dealLaunchMode add single top flag!"); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } PluginDebugLog.runtimeLog(TAG, "dealLaunchMode add clear top flag!"); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); break; } popActivities.add(activity); } for (Activity act : popActivities) { PluginDebugLog.runtimeLog(TAG, "dealLaunchMode popActivities finish " + IntentUtils.dump(act)); popActivityFromStack(act); if (!FileUtils.isFinished(act)) { act.finish(); } } // 如果Activity是在後臺堆疊中找到的,需要合併前後臺棧 if (fromBackStack) { // https://developer.android.com/guide/components/activities/tasks-and-back-stack // 把返回棧中的Activity全部推到前臺 PActivityStack sysForeStack = findAssociatedStack(mFocusedStack); PActivityStack sysBackStack = findAssociatedStack(mLastFocusedStack); mergeActivityStack(sysBackStack, sysForeStack); // 處理外掛自身的棧 mergeActivityStack(mLastFocusedStack, mFocusedStack); // 切換前後臺堆疊 switchToBackStack(mFocusedStack, mLastFocusedStack); } mLoadedApk.quitApp(false); } else { // 堆疊裡沒有找到,遍歷還未啟動cache中的activity記錄 LinkedBlockingQueue<Intent> records = sIntentCacheMap .get(mLoadedApk.getPluginPackageName()); if (null != records) { Iterator<Intent> recordIterator = records.iterator(); String notLaunchTargetClassName = null; while (recordIterator.hasNext()) { Intent record = recordIterator.next(); if (null != record) { if (null != record.getComponent()) { notLaunchTargetClassName = record.getComponent().getClassName(); } if (TextUtils.equals(notLaunchTargetClassName, targetActivity)) { PluginDebugLog.runtimeLog(TAG, "sIntentCacheMap found: " + targetActivity); if (isSingleTask || isSingleTop) { intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); break; } } } } // 遍歷啟動過程中的activity記錄 List<Intent> loadingIntents = sIntentLoadingMap.get(mLoadedApk.getPluginPackageName()); if (null != loadingIntents) { Iterator<Intent> loadingRecordIterator = loadingIntents.iterator(); String notLaunchTargetClassName = null; while (loadingRecordIterator.hasNext()) { Intent record = loadingRecordIterator.next(); if (null != record) { notLaunchTargetClassName = IntentUtils.getTargetClass(record); if (TextUtils.equals(notLaunchTargetClassName, targetActivity)) { PluginDebugLog.runtimeLog(TAG, "sIntentLoadingMap found: " + targetActivity); if (isSingleTask || isSingleTop) { intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); break; } } } } if (isSingleTask) { // 是否需要放到單獨的任務棧 String taskName = matchTaskName(info.taskAffinity); if (!TextUtils.equals(mFocusedStack.getTaskName(), taskName)) { PluginDebugLog.runtimeLog(TAG, "dealLaunchMode push activity into separated stack: " + taskName); PActivityStack stack = mActivityStacks.get(taskName); if (stack == null) { // 建立一個新的任務棧 stack = new PActivityStack(taskName); mActivityStacks.put(taskName, stack); } // 切換前後臺棧 switchToBackStack(mFocusedStack, stack); } else { PluginDebugLog.runtimeLog(TAG, "dealLaunchMode push activity into current stack: " + taskName); } } } } PluginDebugLog.runtimeLog(TAG, "dealLaunchMode end: " + intent + " " + targetActivity); } 複製程式碼
處理外掛中的廣播
/** * 動態註冊外掛中的靜態Receiver */ private void installStaticReceiver() { if (mPluginPackageInfo == null || mHostContext == null) { return; } Map<String, PluginPackageInfo.ReceiverIntentInfo> mReceiverIntentInfos = mPluginPackageInfo.getReceiverIntentInfos(); if (mReceiverIntentInfos != null) { Set<Map.Entry<String, PluginPackageInfo.ReceiverIntentInfo>> mEntrys = mReceiverIntentInfos.entrySet(); Context mGlobalContext = mHostContext.getApplicationContext(); for (Map.Entry<String, PluginPackageInfo.ReceiverIntentInfo> mEntry : mEntrys) { PluginPackageInfo.ReceiverIntentInfo mReceiverInfo = mEntry.getValue(); if (mReceiverInfo != null) { try { BroadcastReceiver mReceiver = BroadcastReceiver.class.cast(mPluginClassLoader. loadClass(mReceiverInfo.mInfo.name).newInstance()); List<IntentFilter> mFilters = mReceiverInfo.mFilter; if (mFilters != null) { for (IntentFilter mItem : mFilters) { mGlobalContext.registerReceiver(mReceiver, mItem); } } } catch (Exception e) { e.printStackTrace(); } } } } } 複製程式碼
處理外掛中的Service
PluginContextWrapper
類中完成了startService等方法的代理
@Override public ComponentName startService(Intent service) { PluginDebugLog.log(TAG, "startService: " + service); PluginLoadedApk mLoadedApk = getPluginLoadedApk(); if (mLoadedApk != null) { ComponentFinder.switchToServiceProxy(mLoadedApk, service); } return super.startService(service); } @Override public boolean stopService(Intent name) { PluginDebugLog.log(TAG, "stopService: " + name); PluginLoadedApk mLoadedApk = getPluginLoadedApk(); if (mLoadedApk != null) { String actServiceClsName = ""; if (name.getComponent() != null) { actServiceClsName = name.getComponent().getClassName(); } else { ServiceInfo mServiceInfo = getPluginPackageInfo().resolveService(name); if (mServiceInfo != null) { actServiceClsName = mServiceInfo.name; } } PluginServiceWrapper plugin = PServiceSupervisor .getServiceByIdentifer(PluginServiceWrapper.getIdentify(getPluginPackageName(), actServiceClsName)); if (plugin != null) { plugin.updateServiceState(PluginServiceWrapper.PLUGIN_SERVICE_STOPED); plugin.tryToDestroyService(); return true; } } return super.stopService(name); } @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { PluginDebugLog.log(TAG, "bindService: " + service); PluginLoadedApk mLoadedApk = getPluginLoadedApk(); if (mLoadedApk != null) { ComponentFinder.switchToServiceProxy(mLoadedApk, service); } if (conn != null) { if (mLoadedApk != null && service != null) { String serviceClass = IntentUtils.getTargetClass(service); String packageName = mLoadedApk.getPluginPackageName(); if (!TextUtils.isEmpty(serviceClass) && !TextUtils.isEmpty(packageName)) { PServiceSupervisor.addServiceConnectionByIdentifer(packageName + "." + serviceClass, conn); } } } return super.bindService(service, conn, flags); } @Override public void unbindService(ServiceConnection conn) { super.unbindService(conn); PServiceSupervisor.removeServiceConnection(conn); PluginDebugLog.log(TAG, "unbindService: " + conn); } 複製程式碼
總結
Neptune
框架註釋比較清晰。但是由於 Neptune
框架程式碼存在兩套對Activity外掛化的方案實現(版本迭代,一套老的,一套新的),】,導致程式碼邏輯不是很統一。在閱讀程式碼的時候,把握住Hook思路,只看相關的程式碼,還是比較容易理解的。