滴滴开源Android插件化框架VirtualAPK原理分析
概述
滴滴出行公司的首個對外開源項目 - VirtualAPK。地址:github.com/didi/Virtua…
滴滴自行研發了這款插件化框架,功能全面、兼容性好,還能夠適用于有耦合的業務插件,這就是VirtualAPK存在的意義。業內認為,在加載耦合插件方面,VirtualAPK可以說是開源方案的首選。據說滴滴打車里面已經用上了,所以還是有必要一探究竟的~~
VirtualAPK 的工作流程如圖所示:
VirtualAPK 對于插件沒有額外的約束,原生的 apk 即可作為一個插件。插件工程編譯生成 apk 后,通過宿主 App 加載,每個插件 apk 被加載后,都會在宿主中創建一個單獨的 LoadedPlugin 對象。如上圖所示,通過這些 LoadedPlugin 對象,VirtualAPK 就可以管理插件并賦予插件新的意義,使其可以像手機中安裝過的App一樣運行。
Activity 支持
Hook ActivityManagerService
插件化支持首先要解決的一點就是插件里的Activity并未在宿主程序的 AndroidMainfest.xml 注冊,常規方法肯定無法直接啟動插件的Activity,這個時候就需要去了解Activity的啟動流程,關于啟動過程主要的幾個步驟請參考:淺析Android 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調用。
還不了解Binder的童鞋,可以看看老羅的Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
// 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 替換為剛創建的 activityManagerProxyReflectUtil.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 binderIBinder 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 里面的 instrumentationReflectUtil.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,并設置進intentmPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);// null component is an implicitly intentif (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();// 判斷是否是啟動插件的Activityif (!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 servicereturn 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 ComponentNametarget.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();// 這里進行判斷,看是交給 LocalService,還是 RemoteService 處理// LocalService 和 RemoteService 分別對應是否在新的進程中啟動Activityboolean 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;}@Overridepublic 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", "程序設計的藝術");哈哈,作者有點懶,用了任玉剛的《Android開發藝術探索》 改的,被發現了
Receiver 支持
官方解釋是將插件中靜態注冊的receiver重新注冊一遍。在代碼里貌似沒找到相應的支持,Demo 里也沒有,或許這部分還沒完成吧??
小結
本文重點在于分析插件的 Activity 啟動流程,其他包括主題、資源,并沒有詳細分析,因為說細了內容還是有點多了,主要是讓大伙兒在閱讀代碼時,有個大致的方向。有疑問歡迎一起探討喲~~
感謝閱讀!
原文鏈接:blog.csdn.net
總結
以上是生活随笔為你收集整理的滴滴开源Android插件化框架VirtualAPK原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Xposed简介以及小米去桌面广告的简单
- 下一篇: Android处理崩溃的一些实践