android 插件化框架VitualAPK


推薦閱讀:

滴滴Booster移動App質量優化框架-學習之旅 一

Android 模塊Api化演練

不一樣視角的Glide剖析(一)

LeakCanary 與 鵝場Matrix ResourceCanary對比分析

 

Android插件化已經出來好幾年了,各大廠都出了各自方案,引用Wiki中VirtualAPK和其他開源框架的對比如下:

 

 

VirtualAPK

VirtualAPK是滴滴出行自研的一款優秀的插件化框架,主要有如下幾個特性。

功能完備

  • 支持幾乎所有的Android特性;
  • 四大組件方面

四大組件均不需要在宿主manifest中預注冊,每個組件都有完整的生命周期。

  1. Activity:支持顯示和隱式調用,支持Activity的themeLaunchMode,支持透明主題;
  2. Service:支持顯示和隱式調用,支持Service的startstopbindunbind,並支持跨進程bind插件中的Service;
  3. Receiver:支持靜態注冊和動態注冊的Receiver;
  4. ContentProvider:支持provider的所有操作,包括CRUDcall方法等,支持跨進程訪問插件中的Provider。
  • 自定義View:支持自定義View,支持自定義屬性和style,支持動畫;
  • PendingIntent:支持PendingIntent以及和其相關的AlarmNotificationAppWidget
  • 支持插件Application以及插件manifest中的meta-data
  • 支持插件中的so

 VirtualAPK對插件沒有額外的約束,原生的apk即可作為插件。插件工程編譯生成apk后,即可通過宿主App加載,每個插件apk被加載后,都會在宿主中創建一個單獨的LoadedPlugin對象。如下圖所示,通過這些LoadedPlugin對象,VirtualAPK就可以管理插件並賦予插件新的意義,使其可以像手機中安裝過的App一樣運行。 VirtualAPK

 

一、Application支持

通常情況,我們可能在Applicaton中做的事情,如下:

1.onCreate中做三方庫的初始化

2.registerActivityLifeCycleCallbacks監控

3.ComponentCallbacks2支持,做些資源清理動作

4.attachBaseContext() 中 multiDex  install(插件不需要考慮)

 

從前3方面,看看VitualAPK支持哪些?

在PluginManager加載Plugin,構建LoadedPlugin事,就構建了插件Application,代碼如下:

protected Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) throws Exception {
     if (null != this.mApplication) {
            return this.mApplication;
     }

     String appClass = this.mPackage.applicationInfo.className;
     if (forceDefaultAppClass || null == appClass) {
          appClass = "android.app.Application";
     }
    
     this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());
     // inject activityLifecycleCallbacks of the host application
//宿主Application 監控插件Activity的生命周期 mApplication.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksProxy());
//插件Application.onCreate()回調 instrumentation.callApplicationOnCreate(
this.mApplication); return this.mApplication; }

 

從源碼可以看到,在構建插件Appliation后就回調了其onCreate方法,ActivityLifecycleCallbacksProxy反射獲取了宿主Application的mActivityLifecycleCallbacks,在插件Activity生命周期監控回調也派發到宿主Activity生命周期監控中。

 

先看看ActivityLifecycleCallbacks機制,Activity生命周期關鍵代碼如下:

public class Activity {
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         ...
//派發ActivityLifeCallbacks.onActivityCreated()方法 getApplication().dispatchActivityCreated(
this, savedInstanceState); ... } protected void onStart() { ...
//派發ActivityLifeCallbacks.onActivityStarted()方法 getApplication().dispatchActivityStarted(
this); ... } ... }

 

顯而易見,Activity的生命周期監控都是其Activity生命周期函數中派發。從后文Activity支持分析中,顯然插件Application可以監控到插件的Activity生命周期。

 

Application,Activity,Service,ContentProvider都實現了ComponentCallbacks2

//ComponentCallbacks2的接口函數
void onTrimMemory()
void onConfigurationChanged(Configuration newConfig);
void onLowMemory();

 

而這些函數都由ActivtyThread.mH發送異步消息,調用相應對應函數,而ActvityThread維護Application,Service,Activity,ContentProvider的相關記錄,能夠回調ComponentCallbacks2接口相應方法,但是插件Application並沒有在ActivityThread記錄過,而在LoadedPlugin中,並沒有對ComponentCallbacks2進行相應的處理,所以VitualAPK並不支持插件Application的ComponentCallbacks2,而插件Activity,Service,ContentProvider是否支持ComponentCallbacks2,見后文

 

二、Activity 支持

需要考慮如下問題:

1.怎樣啟動Activity

2.怎樣加載activity

3.加載資源

4.怎樣保證生命周期

5.是否支持多進程

6.ComponentCallbacks2支持,做些資源清理動作

 

1.啟動Activity

正常情況下,Activity啟動調用了 Instrumentation.execStartActivity方法,完成AMS遠程Activity,再由mh發送異步activity啟動消息,從binder線程池環境切換到ActivityThread主線程環境,開始正真的activity啟動。插件里activity沒有宿主AndroidManifest.xml注冊,常規方法是沒法啟動插件activity的。對於插件 Activity 啟動,VitualApk采用的是宿主 manifest 中占坑的方式來繞過系統校驗,然后再加載真正的activity。

什么是占坑?就是構造一系列假的 Activity 替身,在 AndroidMainfest.xml 里面進行注冊,以繞過檢測,然后到了真正啟動 Activity 的時候,再把它變回,去啟動真正的目標 Activity。那么這一步是怎么做的呢?

 

坑位Activity注冊如下:

    <application>
        <activity android:exported="false" android:name="com.didi.virtualapk.delegate.StubActivity" android:launchMode="standard"/>
        <!-- Stub Activities -->
        <activity android:exported="false" android:name=".A$1" android:launchMode="standard"/>
        <activity android:exported="false" android:name=".A$2" android:launchMode="standard"
            android:theme="@android:style/Theme.Translucent" />

        <!-- Stub Activities -->
        <activity android:exported="false" android:name=".B$1" android:launchMode="singleTop"/>
        <activity android:exported="false" android:name=".B$2" android:launchMode="singleTop"/>
        <activity android:exported="false" android:name=".B$3" android:launchMode="singleTop"/>
        <activity android:exported="false" android:name=".B$4" android:launchMode="singleTop"/>
        <activity android:exported="false" android:name=".B$5" android:launchMode="singleTop"/>
        <activity android:exported="false" android:name=".B$6" android:launchMode="singleTop"/>
        <activity android:exported="false" android:name=".B$7" android:launchMode="singleTop"/>
        <activity android:exported="false" android:name=".B$8" android:launchMode="singleTop"/>

        <!-- Stub Activities -->
        <activity android:exported="false" android:name=".C$1" android:launchMode="singleTask"/>
        <activity android:exported="false" android:name=".C$2" android:launchMode="singleTask"/>
        <activity android:exported="false" android:name=".C$3" android:launchMode="singleTask"/>
        <activity android:exported="false" android:name=".C$4" android:launchMode="singleTask"/>
        <activity android:exported="false" android:name=".C$5" android:launchMode="singleTask"/>
        <activity android:exported="false" android:name=".C$6" android:launchMode="singleTask"/>
        <activity android:exported="false" android:name=".C$7" android:launchMode="singleTask"/>
        <activity android:exported="false" android:name=".C$8" android:launchMode="singleTask"/>

        <!-- Stub Activities -->
        <activity android:exported="false" android:name=".D$1" android:launchMode="singleInstance"/>
        <activity android:exported="false" android:name=".D$2" android:launchMode="singleInstance"/>
        <activity android:exported="false" android:name=".D$3" android:launchMode="singleInstance"/>
        <activity android:exported="false" android:name=".D$4" android:launchMode="singleInstance"/>
        <activity android:exported="false" android:name=".D$5" android:launchMode="singleInstance"/>
        <activity android:exported="false" android:name=".D$6" android:launchMode="singleInstance"/>
        <activity android:exported="false" android:name=".D$7" android:launchMode="singleInstance"/>
        <activity android:exported="false" android:name=".D$8" android:launchMode="singleInstance"/>

    </application>

 

可以發現,在清單里面注冊了一堆假的 StubActivity。 ABCD分別對應不同的啟動模式,那么,我們啟動插件的 Activity 的時候,是如何把它改為清單里面已注冊的這些假的 Activity 名呢?

 

構建PluginManager時,Hook 了一個 VAInstrumentation 以替代系統的 Instrumentation,並ActivityThread.mH設置了callback,攔截處理Activity啟動請求。

 
         
    protected void hookInstrumentationAndHandler() {
try {
ActivityThread activityThread = ActivityThread.currentActivityThread();
Instrumentation baseInstrumentation = activityThread.getInstrumentation();
     
//Hook instrumentation,重設ActivityThread的mInstrmentation
final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);

//Hook Handler mh,設置其mCallback,攔截Activity啟動消息
Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();
Reflector.with(mainHandler).field("mCallback").set(instrumentation);
this.mInstrumentation = instrumentation;
Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation);
} catch (Exception e) {
Log.w(TAG, e);
}
}
 

 

VAInstrumentation 相關的代碼如下:

 public ActivityResult execStartActivity(...) {
        injectIntent(intent); return mBase.execStartActivity(...);
}

protected void injectIntent(Intent intent) {
//確定Intent Component targetActivity的packageName,從已經加載的插件中檢索 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())); // resolve intent with Stub Activity if needed 用那些注冊的假的StubActivity來替換真實的Activity,以繞過檢測 this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); } }

 

繞過了系統的檢測,通過mH發送LAUNCH_ACTIVITY的異步消息,由於Hook的時候設置了callback,攔截了LAUNCH_ACTIVITY,給Intent設置了插件的Theme,classloader,然后按照mH原有的邏輯走。

 

提個問題,通過adb shell dumpsys activity activities可以看到插件Activity? 顯然只能看到坑位Activity,因為在AMS登記的是坑位Activity,驗證如下:

 

2.加載activity

當ActivityThread使用Instrumentation.newActivity,構造activity,自然是調用VAInstrumentation的newActivity方法,代碼如下:

    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try {
//先使用host 的 classloader加載類 cl.loadClass(className); Log.i(TAG, String.format(
"newActivity[%s]", className)); } catch (ClassNotFoundException e) {
//根據intent 從加載的插件中檢索到 插件Activity ComponentName component
= PluginUtil.getComponent(intent); if (component == null) { return newActivity(mBase.newActivity(cl, className, intent)); }      
       String targetClassName
= component.getClassName(); Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName)); LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component); if (plugin == null) { // Not found then goto stub activity. boolean debuggable = false; try { Context context = this.mPluginManager.getHostContext(); debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } catch (Throwable ex) { } if (debuggable) { throw new ActivityNotFoundException("error intent: " + intent.toURI()); } Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class); return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent)); }
       //插件的Activity,使用插件自己的classloader,用插件Activity類,替換坑位Activity類名 Activity activity
= mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent); // for 4.1+ 把插件Activity的Resources設置為Resources Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources()); return newActivity(activity); } return newActivity(mBase.newActivity(cl, className, intent)); }

 

優先使用宿主host的classloader加載Activity,找不到,若是插件Activity,替換坑位Activity,使用插件的classloader加載。

那么插件的classloader是怎樣構建的,代碼如下:

protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
     File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
     String dexOutputPath = dexOutputDir.getAbsolutePath();
//插件classloder的 parent 為宿主host的classloader
//插件plugin可以加載宿主host的class,宿主host不能插件plugin的class DexClassLoader loader
= new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);    

//開啟加載器組合,插件的dex,so文件都設置host classloader的dexPathList(見BaseDexClassLoader)
//這樣host與plugin,plugin與plugin之間可以互相加載對方的class了
if (Constants.COMBINE_CLASSLOADER) { DexUtil.insertDex(loader, parent, libsDir); } return loader; }

 

VirtualApk始終開啟COMBINE_CLASSLOADER,也就是說host與plugin,plugin與plugin之間可以互相加載對方的class了。

 

3.資源加載

在VAInstrumentation.newActivity中,插件Activty的mResources 被設置為對應插件的Resources

Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());

 

那么看看插件的Resources是怎樣構建的,代碼如下:

 if (Constants.COMBINE_RESOURCES) {
//資源組合開啟,則把插件的apk路徑添加宿主Resources的相關的參數,並把插件resources替換為宿主的resrouces,
//這個過程很復雜,需要同步所有應用Resources地方,需要兼容 系統版本api和rom
return ResourcesManager.createResources(context, packageName, apk); } else {
//插件只能訪問自身的Resource資源 Resources hostResources
= context.getResources(); AssetManager assetManager = createAssetManager(context, apk); return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); }

 

VirtualApk始終開啟COMBINE_RESOURCES,也就是說host與plugin,plugin與plugin之間可以互相加載對方的Reources資源了。

 

4.生命周期回調

 ActivtyThread維護這啟動的activity集合ArrayMap<IBinder, ActivityClientRecord> mActivities,自然而然生命周期也就同步了,需要注意的是:在調度插件的onCreate生命周期函數需要需要設置插件Activity的mBase,mResource,mApplication等,因為在Activity實例化后,進行attch動作,需要重置為插件對應的配置。

protected void injectActivity(Activity activity) {
    final Intent intent = activity.getIntent();
    if (PluginUtil.isIntentFromPlugin(intent)) {
         Context base = activity.getBaseContext();
         try {
             LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
             Reflector.with(base).field("mResources").set(plugin.getResources());
             Reflector reflector = Reflector.with(activity);
             reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));
             reflector.field("mApplication").set(plugin.getApplication());

             // set screenOrientation
             ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
             if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                 activity.setRequestedOrientation(activityInfo.screenOrientation);
             }
    
             // for native activity
             ComponentName component = PluginUtil.getComponent(intent);
             Intent wrapperIntent = new Intent(intent);
             wrapperIntent.setClassName(component.getPackageName(), component.getClassName());
             activity.setIntent(wrapperIntent);
                
       } catch (Exception e) {
                Log.w(TAG, e);
       }
   }
}

 

5.是否多進程

插件Activity在其他進程中啟動

<activity android:name=".OtherProcessActivity" android:process=":other"、>

 

打開該插件Activity,會創建該界面,adb查看相關進程,結果如下:

 

沒發現:other相關的進程,想想也是,坑位Activity都沒有設置android:Process,只能運行在主進程中。

 

6.支持ComponentCallbacks2,做些資源清理動作

 插件Activity在ActivityThread有登記,收集的ComponentCallbacks2接口,包括了插件Activity,所以能派發到插件Activity。

 

三、Service支持

需要考慮如下問題:

1.Service啟動

2.是否支持多進程

3.Service生命周期

4.是否支持ComponentCallbacks2,做些資源清理動作

 

1.Service啟動

動態代理AMS,攔截service相關的請求,將其中轉給Service Runtime去處理,Service Runtime會接管系統的所有操作

public class ActivityManagerProxy implements InvocationHandler {
     if ("startService".equals(method.getName())) {
            try {
                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) {
                Log.w(TAG, e);
            }
        } else if ("unbindService".equals(method.getName())) {
            try {
                return unbindService(proxy, method, args);
            } catch (Throwable e) {
                Log.w(TAG, e);
            }
        } else if ("getIntentSender".equals(method.getName())) {
            try {
                getIntentSender(method, args);
            } catch (Exception e) {
                Log.w(TAG, e);
            }
        } else if ("overridePendingTransition".equals(method.getName())){
            try {
                overridePendingTransition(method, args);
            } catch (Exception e){
                Log.w(TAG, e);
            }
        }

        try {
            // sometimes system binder has problems.
            return method.invoke(this.mActivityManager, args);
        } catch (Throwable th) {
            ...
        }

  
  protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
   //把插件Service相關操作的Intent 轉為為 坑位Service相關命令派發的Intent
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
  return mPluginManager.getHostContext().startService(wrapperIntent);
  }
}

 

Service runtime也注冊兩個坑位Service,由坑位Service分發插件Service的相關操作,一個LocalService,用來派發與宿主同進程的插件service操作,一個是RemoteService,用來派發與宿主不同進程的插件service操作,插件service並沒有在AMS、ActivityThread登記過,由Service runtime自身維護。

 

2.是否支持多進程

RemoteService用來派發與宿主不同進程的插件service操作,只不過插件Service運行在RemoteService所在的:daemon,沒有運行在插件Service指定的進程上。

 

3.Service生命周期

坑位Service在派發插件Service操作的時候,會回調相應的生命周期的周期函數。

需要注意以下兩點:

1)通常情況下bindService啟動Service,該Service的生命周期跟綁定着生命周期一致,但是插件Service有VitualAPK Service runtime維護,runtime沒有做插件Serice跟綁定者生命周期一致處理,需要手動調用unbindService,關閉該Service。

2)通常情況下startService和bindService或者使用,關閉Service需要調用stop和unbind方法,而插件Service混合啟動,只需要調用stop或者unbind方法就可以關閉Service|。

 

4.不支持ComponentCallbacks2,做些資源清理動作

 插件Service沒有在ActivityThread登記,收集的ComponentCallbacks2沒有包括插件Service,所以插件不支持ComponentCallbacks2。

 

四、BroadcastReceiver支持

BroadcastRecevier注冊分為動態注冊和靜態注冊。

插件BroadcastRecevier的動態注冊,只需要能夠加載到插件BroadcastReceiver類即可,在前文分析中插件與宿主、插件與插件的classloader都可以加載對方的類,顯然VitaulAPK是支持插件的動態注冊。

插件BroadcastRecevier的靜態注冊,即在插件的AndroidManifest.xml中注冊,VituaAPK在加載插件時,通過PackageParserCompat的parsePackage方法解析了AndroidManifest.xml中Application和四大組件信息,

然后反射實例化插件BroadcastRecevier把插件的靜態注冊改為動態注冊,代碼如下:

 public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
       this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
       this.mPackageInfo.packageName = this.mPackage.packageName;
    if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {
        throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);
    }
   // Register broadcast receivers dynamically
    Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
    for (PackageParser.Activity receiver : this.mPackage.receivers) {
        receivers.put(receiver.getComponentName(), receiver.info);
        //反射實例化BroadcastReceiver
        BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
        for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
            this.mHostContext.registerReceiver(br, aii);//動態注冊
        }
     }
    this.mReceiverInfos = Collections.unmodifiableMap(receivers);
    this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);
    
}

 

當然插件的靜態注冊,在喚醒app的廣播接收中會失效。

 

 

五、ContentProvider支持

ContentProvider是數據共享型組件,進程間和進程內均可共享,由ContentResolver統一管理訪問ContentProvider,我們通過使用context.getContentResolver(),context的實例為contextImpl,ContentResolver通過IContentProvider遠程服務代理訪問ContentProvider服務接口增刪改查。IContentProvider服務代理從ActivityThread中獲取,如下:

public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
     //從本進程中獲取 final IContentProvider provider
= acquireExistingProvider(c, auth, userId, stable); if (provider != null) { return provider; } // There is a possible race here. Another thread may try to acquire // the same provider at the same time. When this happens, we want to ensure // that the first one wins. // Note that we cannot hold the lock while acquiring and installing the // provider since it might take a long time to run and it could also potentially // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; try {
//從ams中獲取 holder
= ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); return null; } // Install provider will increment the reference count for us, and break // any ties in the race. holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider; }

 

可以IContentProvider的來源,ActivityThread和AMS,優先從本進程中獲取。在應用的啟動時候,ContentProvider也跟着啟動,在AMS中有相應的記錄,

插件ContentProvider在AMS中沒有記錄,VitualApk使用代理轉發,宿主坑位ContentProvider攔截了ContentProvider操作,由坑位ContentProvider派發到具體的插件ContentProvider,代碼如下:

public class RemoteContentProvider extends ContentProvider {
    private static final String TAG = Constants.TAG_PREFIX + "RemoteContentProvider";

    public static final String KEY_PKG = "pkg";
    public static final String KEY_PLUGIN = "plugin";
    public static final String KEY_URI = "uri";

    public static final String KEY_WRAPPER_URI = "wrapper_uri";

    private static Map<String, ContentProvider> sCachedProviders = new HashMap<>();

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
//獲取插件的ContentProvider ContentProvider provider
= getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder); } return null; } @Override public Uri insert(Uri uri, ContentValues values) {
//獲取插件的ContentProvider ContentProvider provider
= getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.insert(pluginUri, values); } return uri; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) {
//獲取插件的ContentProvider ContentProvider provider
= getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.delete(pluginUri, selection, selectionArgs); } return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
//獲取插件的ContentProvider ContentProvider provider
= getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.update(pluginUri, values, selection, selectionArgs); } return 0; } @Override public int bulkInsert(Uri uri, ContentValues[] values) { ContentProvider provider = getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.bulkInsert(pluginUri, values); } return 0; } ... }

 

插件中ContentProvider沒有經過Android Framework層,由坑位ContentProvider維護。

插件有自己的ContentResolver:PluginContentResolver,插件訪問ContentProvider時,先從插件管理器中找對應ProviderInfo,找到了則用插件的IContentProvider,否則使用宿主的IContentProvider。

@Override
protected IContentProvider acquireProvider(Context context, String auth) {
   if (mPluginManager.resolveContentProvider(auth, 0) != null) {
       return mPluginManager.getIContentProvider();
    }
    return super.acquireProvider(context, auth);
}

@Override
protected IContentProvider acquireExistingProvider(Context context, String auth) {
    if (mPluginManager.resolveContentProvider(auth, 0) != null) {
        return mPluginManager.getIContentProvider();
    }
    return super.acquireExistingProvider(context, auth);
}
    
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected IContentProvider acquireUnstableProvider(Context context, String auth) {
   if (mPluginManager.resolveContentProvider(auth, 0) != null) {
     return mPluginManager.getIContentProvider();
   }
   return super.acquireUnstableProvider(context, auth);
}

 

那么插件中IContentProvider,怎來的了?宿主坑位ContentProvider,在ActivityThread中肯定存在對應IContentProvider,插件IContentProvider,對其進行動態代理構建了一個。

protected void hookIContentProviderAsNeeded() {
        Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
        mContext.getContentResolver().call(uri, "wakeup", null, null);
        try {
            Field authority = null;
            Field provider = null;
            ActivityThread activityThread = ActivityThread.currentActivityThread();
            Map providerMap = Reflector.with(activityThread).field("mProviderMap").get();
            Iterator iter = providerMap.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(RemoteContentProvider.getAuthority(mContext))) {
                    if (provider == null) {
                        provider = val.getClass().getDeclaredField("mProvider");
                        provider.setAccessible(true);
                    }
//插件IContentProvider IContentProvider rawProvider
= (IContentProvider) provider.get(val); IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider); mIContentProvider = proxy; Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider); break; } } } catch (Exception e) { Log.w(TAG, e); } }

 

 IContentProviderProxyi的nvoke函數首先將訪問插件的Uri轉到宿主占坑Uri

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   Log.v(TAG, method.toGenericString() + " : " + Arrays.toString(args));
   
//插件Uri轉為宿主占坑Uri
wrapperUri(method, args);
try { return method.invoke(mBase, args); } catch (InvocationTargetException e) { throw e.getTargetException(); }
}

 

 插件的Uri轉到宿主占坑Uri,代碼如下:

//
public
static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) { String pkg = loadedPlugin.getPackageName(); String pluginUriString = Uri.encode(pluginUri.toString()); StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext())); builder.append("/?plugin=" + loadedPlugin.getLocation()); builder.append("&pkg=" + pkg); builder.append("&uri=" + pluginUriString); Uri wrapperUri = Uri.parse(builder.toString()); return wrapperUri; }
 
Uri變成了content://host_authority/plugin_authority,其中host_authority表示宿主占坑ContentProvider對應的Auth,plugin_authority代表了實際要啟動的插件ContentProvider的Auth,由此可以插件的ContentProvider的數據保存占坑ContentProvider中。
 
插件ContentProvider onCreate回調
占坑ContentProvider維護這插件的ContentProvider,在構建插件的ConentProvider之后,調用了attch方法,會回調onCreate方法。
 
不支持插件ContentProvider ComponentCallbacks2
插件的ContentProvider沒有ActivityCurrent記錄過,占坑ContentProvider維護這插件的ContentProvider,占坑ContentProvider沒有重載相關ComponentCallbacks2接口方法,對插件ContentProvider進行派發。

總結

VitualAPK 四大組件實現原理如下:

1.Activity 采用宿主manifest中占坑的方式來繞過系統校驗,然后再加載真正的activity;

2.Service 動態代理AMS,攔截service相關的請求,將其中轉給Service Runtime去處理,Service Runtime會接管系統的所有操作;

3.Receiver 將插件中靜態注冊的receiver重新注冊一遍;

4.ContentProvider 動態代理IContentProvider,攔截provider相關的請求,將其中轉給Provider Runtime去處理,Provider Runtime會接管系統的所有操作。

 

插件組件需要注意的地方:

 

 

 

 

參考資料:

didi/VirtualAPK/wiki

VirtualApk源碼分析-BroadcastReceiver插件化

onLowMemory執行流程

 VirtualApk源碼分析-ContentProvider插件化

 

如果您對博主的更新內容持續感興趣,請關注公眾號!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM