相關閱讀
插件化知識梳理(1) - Small 框架之如何引入應用插件
插件化知識梳理(2) - Small 框架之如何引入公共庫插件
插件化知識梳理(3) - Small 框架之宿主分身
插件化知識梳理(4) - Small 框架之如何實現插件更新
插件化知識梳理(5) - Small 框架之如何不將插件打包到宿主中
插件化知識梳理(6) - Small 源碼分析之 Hook 原理
插件化知識梳理(7) - 類的動態加載入門
插件化知識梳理(8) - 類的動態加載源碼分析
插件化知識梳理(9) - 資源的動態加載示例及源碼分析
插件化知識梳理(10) - Service 插件化實現及原理
一、前言
至此,花了四天時間、五篇文章,學習了如何使用Small
框架來實現插件化。但是,對于我來說,一開始的目標就不是滿足于僅僅知道如何用,而是希望通過這一框架作為平臺,學習插件化中所用到的知識。
對于許多插件化的開源框架而言,一個比較核心的部分就是Hook
的實現,所謂Hook
,簡單地來說就是在應用側啟動A.Activity
,但是在AMS
看來卻是啟動的B.Activity
,之后AMS
通知應用側后,我們再重新替換成A.Activity
。
在閱讀這篇文章之前,大家可以先看一下之前的這篇文章 Framework 源碼解析知識梳理(1) - 應用進程與 AMS 的通信實現 ,Small
其實就是通過替換這一雙向通信過程中的關鍵類,對調用方法中傳遞的參數進行替換,來實現Hook
機制。
二、源碼分析
Hook
的過程是Small
預初始化的第一步,就是我們前面在自定義的Application
構造方法中所進行的操作:
public class SmallApp extends Application {
public SmallApp() {
Small.preSetUp(this);
}
}
在Small
的preSetUp(Application context)
函數中,做了下面的兩件事:
-
實例化三個
BundleLauncher
的實現類,添加到Bundle
類中的靜態變量sBundleLaunchers
中,這三個類的繼承關系為:
依次調用這個三個實現類的
onCreate()
方法。
public static void preSetUp(Application context) {
//1.添加關鍵的 BundleLauncher。
registerLauncher(new ActivityLauncher());
registerLauncher(new ApkBundleLauncher());
registerLauncher(new WebBundleLauncher());
//2.調用 BundleLauncher 的 onCreate() 方法。
Bundle.onCreateLaunchers(context);
}
對于之前添加進入的三個實現類,只有ApkBundleLauncher()
實現了onCreate()
方法,其它兩個都是空實現。
protected static void onCreateLaunchers(Application app) {
//調用之前添加進入的 BundleLauncher 的 onCreate() 方法。
for (BundleLauncher launcher : sBundleLaunchers) {
launcher.onCreate(app);
}
}
我們看一下ApkBundleLauncher
的內部實現,這里就是Hook
的實現代碼:
@Override
public void onCreate(Application app) {
super.onCreate(app);
Object/*ActivityThread*/ thread;
List<ProviderInfo> providers;
Instrumentation base;
ApkBundleLauncher.InstrumentationWrapper wrapper;
Field f;
// Get activity thread
thread = ReflectAccelerator.getActivityThread(app);
// Replace instrumentation
try {
f = thread.getClass().getDeclaredField("mInstrumentation");
f.setAccessible(true);
base = (Instrumentation) f.get(thread);
wrapper = new ApkBundleLauncher.InstrumentationWrapper(base);
f.set(thread, wrapper);
} catch (Exception e) {
throw new RuntimeException("Failed to replace instrumentation for thread: " + thread);
}
// Inject message handler
ensureInjectMessageHandler(thread);
// Get providers
try {
f = thread.getClass().getDeclaredField("mBoundApplication");
f.setAccessible(true);
Object/*AppBindData*/ data = f.get(thread);
f = data.getClass().getDeclaredField("providers");
f.setAccessible(true);
providers = (List<ProviderInfo>) f.get(data);
} catch (Exception e) {
throw new RuntimeException("Failed to get providers from thread: " + thread);
}
sActivityThread = thread;
sProviders = providers;
sHostInstrumentation = base;
sBundleInstrumentation = wrapper;
}
(1) 獲得當前應用進程的 ActivityThread 實例
首先,我們通過反射獲得當前應用進程的ActivityThread
實例
thread = ReflectAccelerator.getActivityThread(app)
具體的邏輯為:
public static Object getActivityThread(Context context) {
try {
//1.首先嘗試通過 ActivityThread 內部的靜態變量獲取。
Class activityThread = Class.forName("android.app.ActivityThread");
// ActivityThread.currentActivityThread()
Method m = activityThread.getMethod("currentActivityThread", new Class[0]);
m.setAccessible(true);
Object thread = m.invoke(null, new Object[0]);
if (thread != null) return thread;
//2.靜態變量獲取失敗,那么再通過 Application 的 mLoadedApk 中的 mActivityThread 獲取。
Field mLoadedApk = context.getClass().getField("mLoadedApk");
mLoadedApk.setAccessible(true);
Object apk = mLoadedApk.get(context);
Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
mActivityThreadField.setAccessible(true);
return mActivityThreadField.get(apk);
} catch (Throwable ignore) {
throw new RuntimeException("Failed to get mActivityThread from context: " + context);
}
}
這里面的邏輯為:
- 通過
ActivityThread
中的靜態方法currentActivityThread
來獲取:
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
sCurrentActivityThread
是在ActivityThread#attach(boolean)
方法中被賦值的,而attach
方法則是在入口函數main
中調用的:
public static void main(String[] args) {
//創建應用進程的 ActivityThread 實例。
ActivityThread thread = new ActivityThread();
thread.attach(false);
}
- 如果上面的方法獲取失敗,那么我們再嘗試獲取
Application
中的LoadedApk#mActivityThread
。
(2) 替換 ActivityThread 中的 mInstrumentation
通過(1)
拿到ActivityThread
實例之后,接下來就是替換其中mInstrumentation
成員變量為Small
自己的實現類ApkBundleLauncher.InstrumentationWrapper
,并將原始的mInstrumentation
傳入作為其成員變量。
正如 Framework 源碼解析知識梳理(1) - 應用進程與 AMS 的通信實現 中所介紹的,當我們調用startActivity
之后,那么會調用到它內部的mInstrumentation
的execStartActivity
方法,經過替換之后,就會調用ApkBundleLauncher.InstrumentationWrapper
的對應方法,下面截圖中的mBase
就是原始的mInstrumentation
:
ReflectAccelerator
又通過反射調用了mBase
的對應方法:由此可見,
hook
的目的就在于替代者的方法被調用,到調用原始對象的對應方法之間所進行的操作,也就是下面紅色框中的這兩句:首先看一下wrap(Intent intent)
方法,它的作用為:當我們在應用側啟動一個插件Activity
時,需要將它替換成為AndroidManifest.xml
預先注冊好的占坑Activity
。
private void wrapIntent(Intent intent) {
ComponentName component = intent.getComponent();
String realClazz;
//判斷是否顯示地設置了目標組件的類名。
if (component == null) {
//如果沒有顯示設置 Component,那么通過 resolveActivity 來解析出目標組件。
component = intent.resolveActivity(Small.getContext().getPackageManager());
if (component != null) {
return;
}
//獲得目標組件全路徑名。
realClazz = resolveActivity(intent);
if (realClazz == null) {
return;
}
} else {
//如果設置了類名,那么直接取出。
realClazz = component.getClassName();
if (realClazz.startsWith(STUB_ACTIVITY_PREFIX)) {
realClazz = unwrapIntent(intent);
}
}
if (sLoadedActivities == null) return;
//根據類名,確定它是否是插件當中的 Activity
ActivityInfo ai = sLoadedActivities.get(realClazz);
if (ai == null) return;
//將真實的 Activity 保存在 Category 中,并加上 > 標識符。
intent.addCategory(REDIRECT_FLAG + realClazz);
//選取占坑的 Activity
String stubClazz = dequeueStubActivity(ai, realClazz);
//重新設置 intent,用占坑的 Activity 來替代目標 Activity
intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
}
其中,dequeueStubActivity
就是取出占坑的Activity
,它是預先在AndroidManifest.xml
中注冊的一些占坑Activity
,同時,我們也會把真實的目標Activity
放在Category
字段當中。
接下來,再看一下ensureInjectMessageHandler(Object thread)
函數,代碼的邏輯很簡單,就是替換AcitivtyThread
的mH
中的mCallback
變量為sActivityThreadHandlerCallback
,它的類型為ActivityThreadHandlerCallback
,是我們自定的一個內部類。
private static void ensureInjectMessageHandler(Object thread) {
try {
Field f = thread.getClass().getDeclaredField("mH");
f.setAccessible(true);
Handler ah = (Handler) f.get(thread);
f = Handler.class.getDeclaredField("mCallback");
f.setAccessible(true);
boolean needsInject = false;
if (sActivityThreadHandlerCallback == null) {
needsInject = true;
} else {
Object callback = f.get(ah);
if (callback != sActivityThreadHandlerCallback) {
needsInject = true;
}
}
if (needsInject) {
// Inject message handler
sActivityThreadHandlerCallback = new ActivityThreadHandlerCallback();
f.set(ah, sActivityThreadHandlerCallback);
}
} catch (Exception e) {
throw new RuntimeException("Failed to replace message handler for thread: " + thread);
}
}
在 Framework 源碼解析知識梳理(1) - 應用進程與 AMS 的通信實現 我們分析過,Activity
的生命周期是由AMS
使用運行在系統進程的代理對象ApplicationThreadProxy
,通過Binder
通信發送消息,在應用進程中的ActivityThread#ApplicationThread
在onTransact()
收到消息后,再通過mH
(一個自定的Handler
,類型為H
),發送消息到主線程,H
在handleMessage
中處理消息,回調Activity
對應的生命周期方法。
而ensureInjectMessageHandler
所做就是讓H
的handleMessage
方法被調用之前,進行一些額外的操作,例如在占坑的Activity
啟動完成之后,將它在應用測的記錄替換成為Activity
,而這一過程是通過替換Handler
當中的mCallback
對象,因為在調用handleMessage
之前,會先去調用mCallback
的handleMessage
,并且在其不返回true
的情況下,會繼續調用Handler
本身的handleMessage
方法:
對于Small
來說,它會對以下四種類型的消息進行攔截:
我們以redirectActivity
為例,看一下將占坑的Activity
重新替換為真實的Activity
的過程。
private void redirectActivity(Message msg) {
Object/*ActivityClientRecord*/ r = msg.obj;
//通過反射獲得啟動該 Activity 的 intent。
Intent intent = ReflectAccelerator.getIntent(r);
//就是通過前面放在 Category 中的字段,來取得真實的 Activity 名字。
String targetClass = unwrapIntent(intent);
boolean hasSetUp = Small.hasSetUp();
if (targetClass == null) {
if (hasSetUp) return; // nothing to do
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
return;
}
Small.setUpOnDemand();
return;
}
if (!hasSetUp) {
//確保初始化了。
Small.setUp();
}
//重新替換為真實的 Activity
ActivityInfo targetInfo = sLoadedActivities.get(targetClass);
ReflectAccelerator.setActivityInfo(r, targetInfo);
}
由于handleMessage
的返回值為false
,按照前面的分析,mH
的handleMessage
方法也會得到執行。
以上就是整個Hook
的過程,簡單的總結下來就是在應用進程與AMS
進程的通信過程的某個節點,通過替換類的方式,插入一些邏輯,以繞過系統的檢查:
- 從應用進程到
AMS
所在進程的通信,是通過替換應用進程中的ActivityThread
的mInstrumentation
為自定義的ApkBundleLauncher.InstrumentationWrapper
,在其中將Intent
當中真實的Activity
替換成為占坑的Activity
,然后再調用原始的mInstrumentation
通知AMS
。 - 從
AMS
所在進行到應用進程的通信,是通過替換應用進程中的H
中的mCallback
,在其中將占坑Activity
替換成為真實的Activity
,再執行原本的操作。
(3) ActivityThread 內部的 mBoundApplication 變量
這一步沒有進行Hook
操作,而是先獲得ActivityThread
內部的mBoundApplication
實例,然后獲得該實例內部的providers
變量,它的類型為List<ProviderInfo>
。
(4) 備份
最后一步,就是備份一些關鍵變量,用于之后的操作:
//ActivityThread 實例
sActivityThread = thread;
//List<ProviderInfo> 實例
sProviders = providers;
//原始的 Instrumentation 實例
sHostInstrumentation = base;
//執行 Hook 操作的 Instrumentation 實例
sBundleInstrumentation = wrapper;
三、實例分析
以上就是源碼分析部分,下面,我們通過一個啟動插件Activity
的過程,來驗證一下前面的分析:
3.1 從應用進程到 AMS 進程
通過下面的方法啟動一個插件Activity
:
public void startStubActivity(View view) {
Small.openUri("upgrade", this);
}
按照前面的分析,此時應當會調用經過Hook
之后的Instrumentation
實例的execStartActivity
方法,可以看到在wrapIntent
方法調用之前,我們的目標Activity
仍然是真實的UpgradeActivity
:
讓斷點繼續往下走,經過
wrapIntent
之后,Intent
的目標對象替換成為了占坑的Activity
:3.2 從 AMS 進程到應用進程
而當AMS
需要通知應用進程時,它第一次回調的是占坑的Activity
,也就是如下所示:
通過反射,我們修改
ActivityClientRecord
中的內容,讓其還原成為真實的Activity
:四、小結
以上就是Small
預初始化所做的一些事情,也就是其Hook
實現的原理,很多第三方的插件化都是基于該原理來實現啟動不在AndroidManifest.xml
中注冊的組件的,開始的時候,理解起來可能會有點困難,關鍵是要弄清楚應用程序和AMS
進程的交互原理,歡迎閱讀 Framework 源碼解析知識梳理(1) - 應用進程與 AMS 的通信實現 。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.lxweimin.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結目錄:http://lizejun.cn/categories/