相關閱讀
插件化知識梳理(1) - Small 框架之如何引入應用插件
插件化知識梳理(2) - Small 框架之如何引入公共庫插件
插件化知識梳理(3) - Small 框架之宿主分身
插件化知識梳理(4) - Small 框架之如何實現插件更新
插件化知識梳理(5) - Small 框架之如何不將插件打包到宿主中
插件化知識梳理(6) - Small 源碼分析之 Hook 原理
插件化知識梳理(7) - 類的動態加載入門
插件化知識梳理(8) - 類的動態加載源碼分析
插件化知識梳理(9) - 資源的動態加載示例及源碼分析
插件化知識梳理(10) - Service 插件化實現及原理
一、Service 插件化思路
很可惜,Small
不支持Service
的插件化,但是在項目中我們確實有這樣的需求,那么就需要研究一下如何自己來實現Service
的插件化。在討論如何實現Service
的插件化之前,必須有三點準備:
- 掌握
Service
的基本知識,包括Service
的生命周期、如何啟動和結束Service
,bindService
和startService
之間的區別和聯系,這一部分就不過多介紹了,大家可以看一下 Carson_Ho 大神這篇文章,介紹的很全面 Android四大組件:Service服務史上最全面解析。 - 了解
Service
啟動的內部原理,對源碼進行一次簡單的走讀,主要是對Service
調用者、所有者以及AMS
之間的三方通信有一個清晰的認識,大家可以看一下我之前寫的這篇文章 Framework 源碼解析知識梳理(5) - startService 源碼分析。 - 掌握動態代理的知識,這里在前面分析
Retrofit
的時候也有介紹過,Retrofit 知識梳理(2) - Retrofit 動態代理內部實現。
在 插件化知識梳理(6) - Small 源碼分析之 Hook 原理 這篇文章中,我們一起學習了如何實現Activity
的插件化,簡單地來說,實現原理就是:
- 在調用
startActivity
啟動插件Activity
后,通過替換mInstrumentation
成員變量,攔截這一啟動過程,在后臺偷偷地把startActivity
時傳入的intent
中的component
替換成為在AndroidManifest.xml
中預先注冊的占坑Activity
,再通知ActivityManagerService
。 - 當
ActivityManagerService
完成調度后,有替換客戶端中的ActivityThread
中的mH
中的mCallback
,將占坑的Activity
重新恢復成插件的Activity
。
而Service
的插件化也可以采用類似的方式,大體的思路如下:
- 在調用
startService
啟動插件Service
時,通過攔截ActivityManagerProxy
的對應方法,將Intent
中的插件Service
類型替換成預先在AndroidManifest.xml
中預先注冊好的占坑Service
。 - 當
AMS
通過ApplicationThreadProxy
回調占坑Service
對應的生命周期時,我們再在占坑Service
中的onStartCommand
中,去創建插件Service
的實例,如果是第一次創建,那么先調用它的onCreate
方法,再調用它的onStartCommand
方法,否則,就只調用onStartCommand
方法就可以了。 - 在調用
stopService
停止插件Service
時,同樣通過攔截ActivityManagerProxy
的對應方法,去調用插件Service
的onDestroy
,如果此時發現沒有任何一個與占坑Service
關聯的插件Service
運行時,那么就可以停止插件Service
了。
二、具體實現
傳了一個簡單的例子到倉庫,大家可以簡單地對照著看一下,下面,我們開始分析具體的實現。
2.1 準備工作
初始化的過程如下所示:
public void setup(Context context) {
try {
//1.通過反射獲取到ActivityManagerNative類。
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(activityManagerNativeClass);
//2.獲取mInstance變量。
Class<?> singleton = Class.forName("android.util.Singleton");
Field instanceField = singleton.getDeclaredField("mInstance");
instanceField.setAccessible(true);
//3.獲取原始的對象。
Object original = instanceField.get(gDefault);
//4.動態代理,用于攔截Intent。
Class<?> iActivityManager = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{ iActivityManager }, new IActivityManagerInvocationHandler(original));
instanceField.set(gDefault, proxy);
//5.讀取插件當中的Service。
loadService();
//6.占坑的Component。
mStubComponentName = new ComponentName(ServiceManagerApp.getAppContext().getPackageName(), StubService.class.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
這里最主要的就是做了兩件事:
2.1.1 對 ActivityManagerNative.getDefault() 調用的攔截
這里應用到了動態代理的知識,我們用Proxy.newProxyInstance
所創建的proxy
對象,替代了ActivityManagerNative
中的gDefault
靜態變量。在 Framework 源碼解析知識梳理(1) - 應用程序與 AMS 的通信實現 中,我們分析過,它其實是一個AMS
在應用程序進程中的代理類。通過這一替換過程,那么當調用ActivityManagerNative.getDefault()
方法時,就會先經過IActivityManagerInvocationHandler
類,我們就可以根據invoke
所傳入的方法名,在方法真正被調用之前插入一些自己的邏輯(也就是前文所說的,將啟動插件Service
的Intent
替換成啟動占坑Service
的Intent
),最后才會通過Binder
調用到達AMS
端,以此達到了“欺騙系統”的目的。
下面是InvocationHandler
的具體實現,構造函數中傳入的是原始的ActivityManagerProxy
對象。
private class IActivityManagerInvocationHandler implements InvocationHandler {
private Object mOriginal;
public IActivityManagerInvocationHandler(Object original) {
mOriginal = original;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
switch (methodName) {
case "startService":
Intent matchIntent = null;
int matchIndex = 0;
for (Object object : args) {
if (object instanceof Intent) {
matchIntent = (Intent) object;
break;
}
matchIndex++;
}
if (matchIntent != null && ServiceManager.getInstance().isPlugService(matchIntent.getComponent())) {
Intent stubIntent = new Intent(matchIntent);
stubIntent.setComponent(getStubComponentName());
stubIntent.putExtra(KEY_ORIGINAL_INTENT, matchIntent);
//將插件的Service替換成占坑的Service。
args[matchIndex] = stubIntent;
}
break;
case "stopService":
Intent stubIntent = null;
int stubIndex = 0;
for (Object object : args) {
if (object instanceof Intent) {
stubIntent = (Intent) object;
break;
}
stubIndex++;
}
if (stubIntent != null) {
boolean destroy = onStopService(stubIntent);
if (destroy) {
//如果需要銷毀占坑的Service,那么就替換掉Intent進行處理。
Intent destroyIntent = new Intent(stubIntent);
destroyIntent.setComponent(getStubComponentName());
args[stubIndex] = destroyIntent;
} else {
//由于在onStopService中已經手動調用了onDestroy,因此這里什么也不需要做,直接返回就可以。
return null;
}
}
break;
default:
break;
}
Log.d("ServiceManager", "call invoke, methodName=" + method.getName());
return method.invoke(mOriginal, args);
}
}
先看startService
,args
參數中保存了startService
所傳入的實參,這里面就包含了啟動插件Service
的Intent
,我們將目標Intent
的Component
替換成為占坑的Component
,然后將原始的Intent
保存在KEY_ORIGINAL_INTENT
字段當中,最后,通過原始的對象調用到ActivityManagerService
端。
2.1.2 加載插件 Service 類
這里其實就是用到了前面介紹的DexClassLoader
的知識,詳細的可以看一下前面的這兩篇文章 插件化知識梳理(7) - 類的動態加載入門,插件化知識梳理(8) - 類的動態加載源碼分析。
最終,我們會將插件Service
的Class
對象保存在一個mLoadServices
的Map
當中,它的Key
就是插件Service
的包名和類名。
private void loadService() {
try {
//從插件中加載Service類。
File dexOutputDir = ServiceManagerApp.getAppContext().getDir("dex2", 0);
String dexPath = Environment.getExternalStorageDirectory().toString() + PLUG_SERVICE_PATH;
DexClassLoader loader = new DexClassLoader(dexPath, dexOutputDir.getAbsolutePath(), null, ServiceManagerApp.getAppContext().getClassLoader());
try {
Class clz = loader.loadClass(PLUG_SERVICE_NAME);
mLoadedServices.put(new ComponentName(PLUG_SERVICE_PKG, PLUG_SERVICE_NAME), clz);
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
2.2 啟動插件 Service
接下來,在宿主中通過下面的方式啟動插件Service
類:
public void startService(View view) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(ServiceManager.PLUG_SERVICE_PKG, ServiceManager.PLUG_SERVICE_NAME));
startService(intent);
}
按照前面的分析,首先會走到我們預設的“陷阱”當中,可以看到,這里面的Intent
還是插件Service
的Component
名字:
然而,經過替換,最終調用時的
Intent
就變成了占坑的Service
。如果一切正常,接下來占坑
Service
就會啟動,依次調用它的onCreate
和onStartCommand
方法,我們在onStartCommand
中,再去回調插件Service
對應的生命周期:
public class StubService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.d("StubService", "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("StubService", "onStartCommand");
ServiceManager.getInstance().onStartCommand(intent, flags, startId);
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
ServiceManager.getInstance().onDestroy();
Log.d("StubService", "onDestroy");
}
}
這里,我們取出之前保存在KEY_ORIGINAL_INTENT
中原始的Intent
,通過它找到對應插件Service
的包名和類名,以此為key
,在mLoadedServices
中找到前面從插件apk
中加載的Service
類,并通過反射實例化該對象,如果是第一次創建,那么先執行它的onCreate
方法,并將它保存在mAliveServices
中,之后再執行它的onStartCommand
方法。
這一過程的打印如下圖所示:
2.3 停止插件 Service
當我們通過stopService
方法,停止插件Service
時,也會和前面類似,先走到攔截的邏輯當中:
而在
onStop
方法中,我們判斷它是否是需要停止插件Service
,如果是那么就調用插件Service
的onDestory()
方法,并且判斷與占坑Service
相關聯的插件Service
是否都已經結束了,如果是,那么就返回true
,讓占坑Service
也銷毀。銷毀的時候,就是將插件
Service
的Intent
替換成占坑Service
:這時的打印為:
三、總結
以上,就是實現插件化Service
的核心思路,實現起來并不簡單,需要涉及到很多的知識,這已經是插件化學習的第十篇文章了。如果大家能一路看下來,可以發現,其實插件化并沒有什么神秘的地方,如果我們希望實現任意一個組件的插件化,無非就是以下幾點:
- 組件的生命周期。
- 組件的啟動過程,最主要就是和
ActivityManagerService
的交互過程,這也是最難的地方,要花很多的時間去看源碼,而且各個版本的API
也可能有所差異。 - 插件化常用技巧,也就是
Hook
,動態代理之類的知識。 - 類動態加載的知識。
掌握了以上幾點,對于市面上大廠的插件框架基本能夠看懂個六七成,但是對于大多數人而言,并沒有這么多的時間和條件,去分析一些細節問題。我寫的這些文章,也只能算是入門水平,和大家一起學習基本的思想。真正核心的東西,還是需要有機會能應用到生產環境中才能真正掌握。
很可惜,我也沒有這樣的機會,感覺每天工作的時間都是在調UI
、解Bug
、浪費時間,只能靠著晚上的時間,一點點摸索,寫Demo
,哎,說出來都是淚,還有半年,繼續加油吧!
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.lxweimin.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結目錄:http://lizejun.cn/categories/