滴滴開源Android外掛化框架VirtualAPK原理分析
概述
滴滴出行公司的首個對外開源專案 - VirtualAPK。地址: https://github.com/didi/VirtualAPK
滴滴自行研發了這款外掛化框架,功能全面、相容性好,還能夠適用於有耦合的業務外掛,這就是VirtualAPK存在的意義。業內認為,在載入耦合外掛方面,VirtualAPK可以說是開源方案的首選。據說滴滴打車裡面已經用上了,所以還是有必要一探究竟的~~
VirtualAPK 的工作流程如圖所示:

image.png
LoadedPlugin
物件。如上圖所示,通過這些
LoadedPlugin
物件,VirtualAPK 就可以管理外掛並賦予外掛新的意義,使其可以像手機中安裝過的App一樣執行。
Activity 支援
Hook ActivityManagerService
外掛化支援首先要解決的一點就是外掛裡的Activity並未在宿主程式的 AndroidMainfest.xml
註冊,常規方法肯定無法直接啟動外掛的Activity,這個時候就需要去了解Activity的啟動流程。
從上文中可知, Activity
啟動實際上是呼叫了 Instrumentation.execStartActivity
這個方法。原始碼如下:
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); for (int i=0; i<N; i++) { //先查詢一遍看是否存在這個activity final ActivityMonitor am = mActivityMonitors.get(i); if (am.match(who, null, intent)) { am.mHits++; if (am.isBlocking()) { return requestCode >= 0 ? am.getResult() : null; } break; } } } } try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); //這裡才是真正開啟activity的地方,其核心功能在whoThread中完成。 int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()),token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); checkStartActivityResult(result, intent); // 處理各種異常,如ActivityNotFound } catch (RemoteException e) { } return null; }
可見, startActivity
最終通過 ActivityManagerNative.getDefault()
遠端呼叫了AMS的startActivity方法, ActivityManagerNative
實際上就是 ActivityManagerService
這個遠端物件的 Binder
代理物件,每次需要與AMS互動時,需要通過這個 Binder
物件完成遠端IPC呼叫。
// ActivityManagerNative.getDefault() static public IActivityManager getDefault() { return gDefault.get(); } private static final Singleton<iactivitymanager> gDefault = new Singleton<iactivitymanager>() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); if (false) { Log.v("ActivityManager", "default service binder = " + b); } IActivityManager am = asInterface(b); if (false) { Log.v("ActivityManager", "default service = " + am); } return am; } };
從這我們可以知道, ActivityManagerNative.getDefault()
實際上是返回了一個 IActivityManager 的單例物件。
那麼,VirtualApk 所要做的第一件事,就是把這個 AMS 代理物件儲存起來。首先,我們可以看一下 VirtualApk 核心庫裡面 com.didi.virtualapk.PluginManager
這個類的初始化:
// 構造方法 private PluginManager(Context context) { Context app = context.getApplicationContext(); if (app == null) { this.mContext = context; } else { this.mContext = ((Application)app).getBaseContext(); } prepare(); } // 初始化 private void prepare() { Systems.sHostContext = getHostContext(); this.hookInstrumentationAndHandler(); this.hookSystemServices(); } /** * Hook 出一個IActivityManager,也就是 AMS 的代理物件 */ private void hookSystemServices() { try { // 反射呼叫 ActivityManagerNative.getDefault(),實際上這在6.0中是公開的靜態方法,反射可能是考慮到版本相容性吧? Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault"); // 通過動態代理的方式去建立代理物件,之後所有ActivityManagerNative中的方法被呼叫的時候都會經過這個代理 IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get()); // Hook IActivityManager from ActivityManagerNative,實際上就是把 ActivityManagerNative 替換為剛建立的 activityManagerProxy ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy); if (defaultSingleton.get() == activityManagerProxy) { // 兩者一樣,儲存下來 this.mActivityManager = activityManagerProxy; } } catch (Exception e) { e.printStackTrace(); } }
實際上除了 startActivity
是呼叫 AMS 的方法以外, startService
, bindService
等方法,最終呼叫到AMS的裡的方法,這個我們在動態代理類 com.didi.virtualapk.delegate.ActivityManagerProxy
也可以找到:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("startService".equals(method.getName())) { try { // 執行自定義的 startService 過程,後面會提到 return startService(proxy, method, args); } catch (Throwable e) { Log.e(TAG, "Start service error", e); } } else if ("stopService".equals(method.getName())) { try { return stopService(proxy, method, args); } catch (Throwable e) { Log.e(TAG, "Stop Service error", e); } } else if ("stopServiceToken".equals(method.getName())) { try { return stopServiceToken(proxy, method, args); } catch (Throwable e) { Log.e(TAG, "Stop service token error", e); } } else if ("bindService".equals(method.getName())) { try { return bindService(proxy, method, args); } catch (Throwable e) { e.printStackTrace(); } } else if ("unbindService".equals(method.getName())) { try { return unbindService(proxy, method, args); } catch (Throwable e) { e.printStackTrace(); } } else if ("getIntentSender".equals(method.getName())) { try { getIntentSender(method, args); } catch (Exception e) { e.printStackTrace(); } } else if ("overridePendingTransition".equals(method.getName())){ try { overridePendingTransition(method, args); } catch (Exception e){ e.printStackTrace(); } } try { // sometimes system binder has problems. return method.invoke(this.mActivityManager, args); } catch (Throwable th) { Throwable c = th.getCause(); if (c != null && c instanceof DeadObjectException) { // retry connect to system binder IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE); if (ams != null) { IActivityManager am = ActivityManagerNative.asInterface(ams); mActivityManager = am; } } Throwable cause = th; do { if (cause instanceof RemoteException) { throw cause; } } while ((cause = cause.getCause()) != null); throw c != null ? c : th; } }
所以實際上就等同於我們重寫了一些 Activity
、 Service
的相關操作。具體做些什麼,後面會提到~
Hook Instrumentation
回過頭去看看 Instrumentation.execStartActivity
這個方法,在最後有這麼一句程式碼:
checkStartActivityResult(result, intent); // 處理各種異常,如ActivityNotFound
static void checkStartActivityResult(int res, Object intent) { if (res >= ActivityManager.START_SUCCESS) { return; } switch (res) { case ActivityManager.START_INTENT_NOT_RESOLVED: case ActivityManager.START_CLASS_NOT_FOUND: if (intent instanceof Intent && ((Intent)intent).getComponent() != null) throw new ActivityNotFoundException( "Unable to find explicit activity class " + ((Intent)intent).getComponent().toShortString() + "; have you declared this activity in your AndroidManifest.xml?"); throw new ActivityNotFoundException( "No Activity found to handle " + intent); case ActivityManager.START_PERMISSION_DENIED: throw new SecurityException("Not allowed to start activity " + intent); case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: throw new AndroidRuntimeException( "FORWARD_RESULT_FLAG used while also requesting a result"); case ActivityManager.START_NOT_ACTIVITY: throw new IllegalArgumentException( "PendingIntent is not an activity"); default: throw new AndroidRuntimeException("Unknown error code " + res + " when starting " + intent); } }
相信大家對上面的這些異常資訊不陌生吧,其中最熟悉的非 Unable to find explicit activity class
莫屬了,如果 Activity
沒有在 AndroidMainfest.xml
註冊,將會丟擲此異常。
那麼就得思考一個問題了,外掛的 Activity
並未在宿主程式的 AndroidMainfest.xml
註冊,要如何才能繞過這一層檢測?
前文中提到, com.didi.virtualapk.PluginManager
這個類的初始化的時候,除了 Hook 出一個 AMS 代理物件以外,還 Hook 出一個 Instrumentation
物件。程式碼如下:
private void hookInstrumentationAndHandler() { try { Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext); if (baseInstrumentation.getClass().getName().contains("lbe")) { // reject executing in paralell space, for example, lbe. System.exit(0); } // 建立自定義的 instrumentation,重寫了 newActivity() 等一些方法 // baseInstrumentation 後面還會用到,也儲存下來 final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation); // 獲取 ActivityThread 的例項 Object activityThread = ReflectUtil.getActivityThread(this.mContext); // 用自定義的 instrumentation 替換掉 ActivityThread 裡面的 instrumentation ReflectUtil.setInstrumentation(activityThread, instrumentation); ReflectUtil.setHandlerCallback(this.mContext, instrumentation); this.mInstrumentation = instrumentation; } catch (Exception e) { e.printStackTrace(); } }
既然 Activity 的啟動,中間走了 Instrumentation.execStartActivity
這個方法,那麼我們大概可以知道,Hook 出一個 Instrumentation
物件用來做什麼了,實際上就是用來幫助啟動外掛的 Activity
。
啟動外掛Activity
我們 Hook 了一個 VAInstrumentation
以替代系統的 Instrumentation
,這樣當系統通過 ActivityThread
呼叫 它的的成員變數 mInstrumentation 的 newActivity()
等方法的時候,實際是呼叫我們 VAInstrumentation
的 newActivity()
。
實際上對於外掛 Activity
啟動,採用的是宿主 manifest 中佔坑的方式來繞過系統校驗,然後再載入真正的activity。
什麼是佔坑?就是構造一系列假的 Activity
替身,在 AndroidMainfest.xml
裡面進行註冊,以繞過檢測,然後到了真正啟動 Activity 的時候,再把它變回,去啟動真正的目標 Activity
。那麼這一步是怎麼做的呢?
我們可以開啟核心庫裡面的 AndroidMainfest.xml
看看:
<application> <!-- Stub Activities --> <activity android:name=".A$1" android:launchMode="standard"/> <activity android:name=".A$2" android:launchMode="standard" android:theme="@android:style/Theme.Translucent" /> <!-- Stub Activities --> <activity android:name=".B$1" android:launchMode="singleTop"/> <activity android:name=".B$2" android:launchMode="singleTop"/> <activity android:name=".B$3" android:launchMode="singleTop"/> <activity android:name=".B$4" android:launchMode="singleTop"/> <activity android:name=".B$5" android:launchMode="singleTop"/> <activity android:name=".B$6" android:launchMode="singleTop"/> <activity android:name=".B$7" android:launchMode="singleTop"/> <activity android:name=".B$8" android:launchMode="singleTop"/> <!-- Stub Activities --> <activity android:name=".C$1" android:launchMode="singleTask"/> <activity android:name=".C$2" android:launchMode="singleTask"/> <activity android:name=".C$3" android:launchMode="singleTask"/> <activity android:name=".C$4" android:launchMode="singleTask"/> <activity android:name=".C$5" android:launchMode="singleTask"/> <activity android:name=".C$6" android:launchMode="singleTask"/> <activity android:name=".C$7" android:launchMode="singleTask"/> <activity android:name=".C$8" android:launchMode="singleTask"/> <!-- Stub Activities --> <activity android:name=".D$1" android:launchMode="singleInstance"/> <activity android:name=".D$2" android:launchMode="singleInstance"/> <activity android:name=".D$3" android:launchMode="singleInstance"/> <activity android:name=".D$4" android:launchMode="singleInstance"/> <activity android:name=".D$5" android:launchMode="singleInstance"/> <activity android:name=".D$6" android:launchMode="singleInstance"/> <activity android:name=".D$7" android:launchMode="singleInstance"/> <activity android:name=".D$8" android:launchMode="singleInstance"/> </application>
可以發現,在清單裡面註冊了一堆假的 StubActivity
。 ABCD分別對應不同的啟動模式,那麼,我們啟動外掛的 Activity 的時候,是如何把它改為清單裡面已註冊的這些假的 Activity
名呢?
在 VAInstrumentation
裡面,重寫了 startActivity
的必經之路,就是 execStartActivity()
方法:
public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { // 這裡面做了一系列操作,實際上就是查詢外掛裡面第一個符合隱式條件的第一個ResolveInfo,並設定進intent mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent); // null component is an implicitly intent if (intent.getComponent() != null) { Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(), intent.getComponent().getClassName())); // !!! 重頭戲在這裡,用那些註冊的假的StubActivity來替換真實的Activity,以繞過檢測 !!! this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); } ActivityResult result = realExecStartActivity(who, contextThread, token, target, intent, requestCode, options); return result; } private ActivityResult realExecStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ActivityResult result = null; try { Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class}; result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase, "execStartActivity", parameterTypes, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { e.printStackTrace(); } return result; }
那麼,是如何替換 StubActivity
的呢? 跟進程式碼:
public void markIntentIfNeeded(Intent intent) { if (intent.getComponent() == null) { return; } String targetPackageName = intent.getComponent().getPackageName(); String targetClassName = intent.getComponent().getClassName(); // 判斷是否是啟動外掛的Activity if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) { // 做標記 intent.putExtra(Constants.KEY_IS_PLUGIN, true); // 儲存真實的意圖 intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName); intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName); dispatchStubActivity(intent); } } /** * 真正的轉換就在這裡。根據啟動模式,轉換對應的 StubActivity */ private void dispatchStubActivity(Intent intent) { ComponentName component = intent.getComponent(); String targetClassName = intent.getComponent().getClassName(); LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent); ActivityInfo info = loadedPlugin.getActivityInfo(component); if (info == null) { throw new RuntimeException("can not find " + component); } int launchMode = info.launchMode; // 臨時替換主題 Resources.Theme themeObj = loadedPlugin.getResources().newTheme(); themeObj.applyStyle(info.theme, true); // 實際上就是這一句,完成轉換 String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj); Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity)); intent.setClassName(mContext, stubActivity); }
繼續跟進程式碼:
class StubActivityInfo { public static final int MAX_COUNT_STANDARD = 1; public static final int MAX_COUNT_SINGLETOP = 8; public static final int MAX_COUNT_SINGLETASK = 8; public static final int MAX_COUNT_SINGLEINSTANCE = 8; public static final String corePackage = "com.didi.virtualapk.core"; // 這個格式,就是那些假的Activity的名字 public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d"; public static final String STUB_ACTIVITY_SINGLETOP = "%s.B$%d"; public static final String STUB_ACTIVITY_SINGLETASK = "%s.C$%d"; public static final String STUB_ACTIVITY_SINGLEINSTANCE = "%s.D$%d"; public final int usedStandardStubActivity = 1; public int usedSingleTopStubActivity = 0; public int usedSingleTaskStubActivity = 0; public int usedSingleInstanceStubActivity = 0; private HashMap<String, String> mCachedStubActivity = new HashMap<>(); /** * 在這裡根據啟動模式及主題構造 StubActivity */ public String getStubActivity(String className, int launchMode, Theme theme) { String stubActivity= mCachedStubActivity.get(className); if (stubActivity != null) { return stubActivity; } TypedArray array = theme.obtainStyledAttributes(new int[]{ android.R.attr.windowIsTranslucent, android.R.attr.windowBackground }); boolean windowIsTranslucent = array.getBoolean(0, false); array.recycle(); if (Constants.DEBUG) { Log.d("StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent); } stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity); switch (launchMode) { case ActivityInfo.LAUNCH_MULTIPLE: { stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity); if (windowIsTranslucent) { stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2); } break; } case ActivityInfo.LAUNCH_SINGLE_TOP: { usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1; stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity); break; } case ActivityInfo.LAUNCH_SINGLE_TASK: { usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1; stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity); break; } case ActivityInfo.LAUNCH_SINGLE_INSTANCE: { usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1; stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity); break; } default:break; } mCachedStubActivity.put(className, stubActivity); return stubActivity; } }
到這一步,就基本清晰了。同樣的,既然變為了 StubActivity
,那麼真正啟動的時候還得變回來才行。來看一下重寫後的 newActivity()
方法:
@Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { cl.loadClass(className); } catch (ClassNotFoundException e) { // 根據 intent 型別,去獲取相應的外掛 LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); // 這裡就是從Intent中取出我們剛才儲存的真正的意圖 String targetClassName = PluginUtil.getTargetActivity(intent); Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName)); if (targetClassName != null) { // mBase 是未替換之前的 Instrumentation 物件,所以這個實際上是交給系統原先的 Instrumentation 物件去執行,所以這個模式其實也可以理解為與動態代理等同 // plugin.getClassLoader() 是自己構造的一個 DexClassLoader,專門用於載入對應的apk裡面的類 Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent); try { // for 4.1+ ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources()); } catch (Exception ignored) { // ignored. } return activity; } } return mBase.newActivity(cl, className, intent); }
到這裡,外掛的 Activity 啟動流程分析,就基本結束了。細節方面,沒法一步到位,還需要大家邊看原始碼邊理解,這樣才能看得更透徹。
Service 支援
對於 Service
的支援,採用動態代理AMS,攔截 Service
相關的請求,將其中轉給Service Runtime去處理,Service Runtime會接管系統的所有操作。
對於我們動態代理AMS,在上一節 Activity支援 中已經介紹過了,那麼,簡單的來看一下 ActivityManagerProxy
是如何啟動一個Service的。
在執行 startService
等方法的時候,AMS 代理物件會相應的來執行以下這些方法:
private Object startService(Object proxy, Method method, Object[] args) throws Throwable { IApplicationThread appThread = (IApplicationThread) args[0]; Intent target = (Intent) args[1]; ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); if (null == resolveInfo || null == resolveInfo.serviceInfo) { // is host service return method.invoke(this.mActivityManager, args); } return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE); } private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command); return mPluginManager.getHostContext().startService(wrapperIntent); } private Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { // fill in service with ComponentName target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name)); String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation(); // 這裡進行判斷,看是交給 LocalService,還是 RemoteService 處理 // LocalService 和 RemoteService 分別對應是否在新的程序中啟動Activity boolean local = PluginUtil.isLocalService(serviceInfo); Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class; Intent intent = new Intent(); intent.setClass(mPluginManager.getHostContext(), delegate); intent.putExtra(RemoteService.EXTRA_TARGET, target); // 儲存一下這個的Command,對應執行不同操作 intent.putExtra(RemoteService.EXTRA_COMMAND, command); intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation); if (extras != null) { intent.putExtras(extras); } return intent; }
實際上包括我們呼叫 stopService()
,AMS 代理物件最後變換後的意圖,同樣也是上面程式碼的最後兩個個方法 startDelegateServiceForTarget
和 wrapperTargetIntent()
,只不過 command 不一樣。
所以本質上 AMS 作為代理,不管你執行啟動或者關閉外掛裡面的 Service,他都是呼叫 LocalService 或者 RemoteService 的 startService 方法,在 LocalService 或者 RemoteService 的 onStartCommand()
下,根據 command 進行相應的操作。那麼我們來看一下 LocalService 的 onStartCommand()
方法:
@Override public int onStartCommand(Intent intent, int flags, int startId) { if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) { return START_STICKY; } Intent target = intent.getParcelableExtra(EXTRA_TARGET); int command = intent.getIntExtra(EXTRA_COMMAND, 0); if (null == target || command <= 0) { return START_STICKY; } ComponentName component = target.getComponent(); LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); switch (command) { case EXTRA_COMMAND_START_SERVICE: { ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext()); IApplicationThread appThread = mainThread.getApplicationThread(); Service service; if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) { service = this.mPluginManager.getComponentsHandler().getService(component); } else { try { service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance(); Application app = plugin.getApplication(); IBinder token = appThread.asBinder(); Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class); IActivityManager am = mPluginManager.getActivityManager(); attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am); service.onCreate(); this.mPluginManager.getComponentsHandler().rememberService(component, service); } catch (Throwable t) { return START_STICKY; } } service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement()); break; } case EXTRA_COMMAND_BIND_SERVICE: { ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext()); IApplicationThread appThread = mainThread.getApplicationThread(); Service service = null; if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) { service = this.mPluginManager.getComponentsHandler().getService(component); } else { try { service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance(); Application app = plugin.getApplication(); IBinder token = appThread.asBinder(); Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class); IActivityManager am = mPluginManager.getActivityManager(); attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am); service.onCreate(); this.mPluginManager.getComponentsHandler().rememberService(component, service); } catch (Throwable t) { t.printStackTrace(); } } try { IBinder binder = service.onBind(target); IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc"); IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection); iServiceConnection.connected(component, binder); } catch (Exception e) { e.printStackTrace(); } break; } case EXTRA_COMMAND_STOP_SERVICE: { Service service = this.mPluginManager.getComponentsHandler().forgetService(component); if (null != service) { try { service.onDestroy(); } catch (Exception e) { Log.e(TAG, "Unable to stop service " + service + ": " + e.toString()); } } else { Log.i(TAG, component + " not found"); } break; } case EXTRA_COMMAND_UNBIND_SERVICE: { Service service = this.mPluginManager.getComponentsHandler().forgetService(component); if (null != service) { try { service.onUnbind(target); service.onDestroy(); } catch (Exception e) { Log.e(TAG, "Unable to unbind service " + service + ": " + e.toString()); } } else { Log.i(TAG, component + " not found"); } break; } } return START_STICKY; }
很顯然,在這裡面才對應去控制了外掛Service的生命週期。具體程式碼就留給大家分析吧~~
ContentProvider 支援
動態代理 IContentProvider
,攔截provider相關的請求,將其中轉給Provider Runtime去處理,Provider Runtime會接管系統的所有操作。
我們來看一下 com.didi.virtualapk.internal.PluginContentResolver
這個類:
public class PluginContentResolver extends ContentResolver { private ContentResolver mBase; private PluginManager mPluginManager; private static Method sAcquireProvider; private static Method sAcquireExistingProvider; private static Method sAcquireUnstableProvider; static { try { sAcquireProvider = ContentResolver.class.getDeclaredMethod("acquireProvider", new Class[]{Context.class, String.class}); sAcquireProvider.setAccessible(true); sAcquireExistingProvider = ContentResolver.class.getDeclaredMethod("acquireExistingProvider", new Class[]{Context.class, String.class}); sAcquireExistingProvider.setAccessible(true); sAcquireUnstableProvider = ContentResolver.class.getDeclaredMethod("acquireUnstableProvider", new Class[]{Context.class, String.class}); sAcquireUnstableProvider.setAccessible(true); } catch (Exception e) { //ignored } } public PluginContentResolver(Context context) { super(context); mBase = context.getContentResolver(); mPluginManager = PluginManager.getInstance(context); } protected IContentProvider acquireProvider(Context context, String auth) { try { if (mPluginManager.resolveContentProvider(auth, 0) != null) { // 在這裡,去 hook 一個 IContentProvider 代理物件 return mPluginManager.getIContentProvider(); } return (IContentProvider) sAcquireProvider.invoke(mBase, context, auth); } catch (Exception e) { e.printStackTrace(); } return null; } // ... }
這個類是在構造 LoadedPlugin
的時候建立的 PluginContext
物件裡面的 getContentResolver()
裡面建立的。
class PluginContext extends ContextWrapper { private final LoadedPlugin mPlugin; public PluginContext(LoadedPlugin plugin) { super(plugin.getPluginManager().getHostContext()); this.mPlugin = plugin; } @Override public ContentResolver getContentResolver() { // 建立代理支援 return new PluginContentResolver(getHostContext()); } }
那麼,上面Hook 的 IContentProvider 代理物件,實際上是在 PluginManager 做的。
private void hookIContentProviderAsNeeded() { Uri uri = Uri.parse(PluginContentResolver.getUri(mContext)); mContext.getContentResolver().call(uri, "wakeup", null, null); try { Field authority = null; Field mProvider = null; ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext); Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap"); Iterator iter = mProviderMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue(); String auth; if (key instanceof String) { auth = (String) key; } else { if (authority == null) { authority = key.getClass().getDeclaredField("authority"); authority.setAccessible(true); } auth = (String) authority.get(key); } if (auth.equals(PluginContentResolver.getAuthority(mContext))) { if (mProvider == null) { mProvider = val.getClass().getDeclaredField("mProvider"); mProvider.setAccessible(true); } IContentProvider rawProvider = (IContentProvider) mProvider.get(val); IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider); mIContentProvider = proxy; Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider); break; } } } catch (Exception e) { e.printStackTrace(); } }
這一塊的內容,最好根據滴滴提供的Demo,再來看,比較容易理解。
Uri bookUri = Uri.parse("content://com.ryg.chapter_2.book.provider/book"); ContentValues values = new ContentValues(); values.put("_id", 6); values.put("name", "程式設計的藝術");

大綱與資料.png

高清視訊資料.jpg