VirtualApk 插件化原理分析

插件化需要解決的幾個(gè)問題:

  • 插件的類是如何加載的
  • 插件的資源文件如何獲取
  • 插件中的四大組件如何啟動(dòng)(Activity丙躏、Service、BroadCastReceiver矿辽、ContentProvider)

本問以VirtualApk為例 分析插件化實(shí)現(xiàn)的幾個(gè)關(guān)鍵點(diǎn):

一拍霜、VirualApk中的幾個(gè)重要的類

1筋蓖、LoadedPlugin 代表一個(gè)被加載的插件

LoadedPlugin 中持有插件Apk的保存位置,插件自己獨(dú)有的ClassLoader、Resources澜搅、PluginContext(上下文)和PluginPakageManager(插件包管理器)

public class LoadedPlugin {

    //PluginManager實(shí)例
    protected PluginManager mPluginManager;
     //宿主的Context
     protected Context mHostContext;

     //保存被加載的apk的位置
     protected final String mLocation;
     //插件自己的Context
     protected Context mPluginContext;
     //插件自己的Resource
     protected Resources mResources;
     //插件自己的ClassLoader
     protected ClassLoader mClassLoader;
     //插件自己的PackageManager
     protected PluginPackageManager mPackageManager;
     //插件的Application
     protected Application mApplication;

    //插件中注冊(cè)的Activity伍俘、Service、Receiver勉躺、ContentProvider
    protected Map<ComponentName, ActivityInfo> mActivityInfos;
    protected Map<ComponentName, ServiceInfo> mServiceInfos;
    protected Map<ComponentName, ActivityInfo> mReceiverInfos;
    protected Map<ComponentName, ProviderInfo> mProviderInfos;
    protected Map<String, ProviderInfo> mProviders; // key is authorities of provider
}

2癌瘾、PluginContext

PluginContext 插件中使用的上下文,通過復(fù)寫 getApplication()、getClassLoader()饵溅、getResources()妨退、getAssets()、getContentResolver()蜕企、getPackageManager() 等方法咬荷,返回插件的Application、ClassLoader轻掩、Resoures幸乒、AssetManager、ContentProvider放典、PackageManager逝变。

class PluginContext extends ContextWrapper {

    private final LoadedPlugin mPlugin;

    public PluginContext(LoadedPlugin plugin) {
        super(plugin.getPluginManager().getHostContext());
        this.mPlugin = plugin;
    }
    
    public PluginContext(LoadedPlugin plugin, Context base) {
        super(base);
        this.mPlugin = plugin;
    }

    @Override
    public Context getApplicationContext() {
        return this.mPlugin.getApplication();
    }



    private Context getHostContext() {
        return getBaseContext();
    }

    @Override
    public ContentResolver getContentResolver() {
        return new PluginContentResolver(getHostContext());
    }

    @Override
    public ClassLoader getClassLoader() {
        return this.mPlugin.getClassLoader();
    }


    @Override
    public PackageManager getPackageManager() {
        return this.mPlugin.getPackageManager();
    }

  
    @Override
    public Resources getResources() {
        return this.mPlugin.getResources();
    }

    @Override
    public AssetManager getAssets() {
        return this.mPlugin.getAssets();
    }

    @Override
    public Resources.Theme getTheme() {
        return this.mPlugin.getTheme();
    }

   
 }


3、PluginManager

PlugInManager是VirtualApk的大管家奋构,持有宿主的Content壳影、application,維護(hù)著所有加載過的插件LoadedApk,hook的系統(tǒng)的Instrumentation弥臼、ActivityManager宴咧、ContentProvider

public class PluginManager {
    
    //HostApp的Content和Application
    protected final Context mContext;
    protected final Application mApplication;

    //維護(hù)所有加載過的LoadedPlugin插件
    protected final Map<String, LoadedPlugin> mPlugins = new ConcurrentHashMap<>();

    //hook的系統(tǒng)的Instumentation
    protected VAInstrumentation mInstrumentation; // Hooked instrumentation
    //hook系統(tǒng)的ActivityManager
    protected IActivityManager mActivityManager; // Hooked IActivityManager binder
    //hook系統(tǒng)的ContentProvider
    protected IContentProvider mIContentProvider; // Hooked IContentProvider binder

}

二、插件中ClassLoader径缅、Resources 是如何創(chuàng)建的掺栅,so是如何處理的。

2.1纳猪、插件的ClassLoader

 public class LoadedPlugin {
    protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
        File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
        String dexOutputPath = dexOutputDir.getAbsolutePath();
        DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);

        if (Constants.COMBINE_CLASSLOADER) {
            DexUtil.insertDex(loader, parent, libsDir);
        }

        return loader;
    }
}

LoadedPlugin中createClassLoader()方法創(chuàng)建自己的ClassLoader();

  • 以宿主的classLoader為父classLoader,創(chuàng)建了一個(gè)DexClassLoader作為插件的classLoader,并指定了dexPath為插件apk的路徑氧卧。
  • 如何指定了Constants.COMBINE_CLASSLOADER,則會(huì)修改宿主classLoader,將插件classLoader的dexElements和宿主ClassLoader的dexElements的合并(宿主在前氏堤、插件在后)沙绝,使宿主也可以加載插件中的類。

2.2、插件的Resources是如何創(chuàng)建的闪檬。

  protected Resources createResources(Context context, String packageName, File apk) throws Exception {
        if (Constants.COMBINE_RESOURCES) {
            //(3)COMBINE_RESOURCES模式下 resource處理
            return
            ResourcesManager.createResources(context, packageName, apk);
        } else {
            //(1)利用插件的assetManager 創(chuàng)建一個(gè)Resources對(duì)象
            Resources hostResources = context.getResources();
            AssetManager assetManager = createAssetManager(context, apk);
            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
        }
    }
    
   
  • 插件中創(chuàng)建Resources核心操作:創(chuàng)建一個(gè)屬于插件的AssetManager,利用assetManager和宿主的DisplayMetric和configuration 創(chuàng)建一個(gè)獨(dú)立的Resources實(shí)例作為插件的Resources
  • 創(chuàng)建中創(chuàng)建AssetManager實(shí)例:創(chuàng)建一個(gè)空的AssetManager星著,利用反射調(diào)用addAssetPath方法,加載插件中的資源(包括res和assets)
 //創(chuàng)建插件的AssetManager實(shí)例
     protected AssetManager createAssetManager(Context context, File apk) throws Exception {
        AssetManager am = AssetManager.class.newInstance();
        Reflector.with(am).method("addAssetPath", String.class).call(apk.getAbsolutePath());
        return am;
    }
  • 如果開啟了COMBINE_RESOURCES合并資源模式,處理會(huì)稍微復(fù)雜一些。

首先在創(chuàng)建插件AssetManager時(shí),會(huì)通過addAssetPath,將宿主的資源目錄,正在被加載的apk,已經(jīng)加載過的所有插件的apk資源 都添加到新創(chuàng)建的AssetManager

其次利用符合的AssetManager創(chuàng)建的Resources作為插件的Resources,可以訪問訪問宿主和所有插件的Resource資源

再次 還有hook 替換掉宿主Context中的Resrouces

這樣粗悯,宿主和插件使用同一個(gè)Resources對(duì)象虚循,可以訪問宿主和插件中的資源

2.3、插件的so是如何處理的

VirtualApk 會(huì)在宿主的內(nèi)部路徑創(chuàng)建一個(gè)“valibs”文件夾,來保存插件的so文件样傍。

安裝插件時(shí),會(huì)將插件apk中的so,選擇一個(gè)合適的arm-abi拷貝到valibs下横缔。

 protected void tryToCopyNativeLib(File apk) throws Exception {
        PluginUtil.copyNativeLib(apk, mHostContext, mPackageInfo, mNativeLibDir);
    }
    
     
public static void copyNativeLib(File apk, Context context, PackageInfo packageInfo, File nativeLibDir) throws Exception {
    long startTime = System.currentTimeMillis();
    ZipFile zipfile = new ZipFile(apk.getAbsolutePath());

    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            for (String cpuArch : Build.SUPPORTED_ABIS) {
                if (findAndCopyNativeLib(zipfile, context, cpuArch, packageInfo, nativeLibDir)) {
                    return;
                }
            }
            
        } else {
            if (findAndCopyNativeLib(zipfile, context, Build.CPU_ABI, packageInfo, nativeLibDir)) {
                return;
            }
        }
        
        findAndCopyNativeLib(zipfile, context, "armeabi", packageInfo, nativeLibDir);

    } finally {
        zipfile.close();
        Log.d(TAG, "Done! +" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

三、 四大組件是如何啟動(dòng)的衫哥。

3.1剪廉、Activity的啟動(dòng)

VirtualApk也是采用占坑位的方式,在宿主的Manifest中預(yù)先注冊(cè)了多個(gè)Activity炕檩。
利用VmInstrumentation類Hook了系統(tǒng)的Instrumenation類斗蒋。
為什么要hookInstrumentation類呢?因?yàn)锳ctivity的創(chuàng)建、啟動(dòng)笛质、以及各個(gè)聲明周期的回調(diào) 都會(huì)經(jīng)過Instrumentation泉沾,所以在Instrumentation中對(duì)Activity進(jìn)行替換是最佳的一個(gè)hook點(diǎn)。

public class VAInstrumentation extends Instrumentation implements Handler.Callback {
     @Override
    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
        injectIntent(intent);
        return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);
    }

   protected void injectIntent(Intent intent) {

        //(1)將隱式的Intent 轉(zhuǎn)換為顯式的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()));
            // resolve intent with Stub Activity if needed
            //(2)將Intent中啟動(dòng)的activity 替換成宿主中占位的Activity,并將真正要啟動(dòng)的Activity信息 以參數(shù)的形式 保存在原Intent中
            this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
        }
    }
}

Activity啟動(dòng)前 會(huì)調(diào)用execStartActivity()方法妇押,
在此方法中將待啟動(dòng)的插件中的Activity 替換為宿主中占位的Activity
-(1)將隱式的Intent 轉(zhuǎn)換為顯式的Intent

  • (2)將Intent中啟動(dòng)的activity 替換成宿主中占位的Activity,并將真正要啟動(dòng)的Activity信息 以參數(shù)的形式 保存在原Intent中

   public void markIntentIfNeeded(Intent intent) {
        if (intent.getComponent() == null) {
            return;
        }

        String targetPackageName = intent.getComponent().getPackageName();
        String targetClassName = intent.getComponent().getClassName();
        // search map and return specific launchmode stub 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);
        }
    }

執(zhí)行execStartActivity()后跷究,啟動(dòng)Activity的信息會(huì)通過IPC 傳遞給ActivityManagerService,ActivityManagerService 對(duì)Activity進(jìn)行校驗(yàn)通過之后,會(huì)通知原app進(jìn)程來創(chuàng)建Activity實(shí)例,最終會(huì)調(diào)用Instrumentation.newActivity()

public class VAInstrumentation extends Instrumentation implements Handler.Callback {
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try {
            cl.loadClass(className);
            Log.i(TAG, String.format("newActivity[%s]", className));
            
            //(1) 創(chuàng)建占位Activity時(shí),因?yàn)樗拗髦?只對(duì)StubActivity進(jìn)行了聲明,而并沒有對(duì)應(yīng)的Class,所以此時(shí)會(huì)異常,異常時(shí) 嘗試創(chuàng)建插件的Activity
        } catch (ClassNotFoundException e) {
            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.
             
                Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class);
                return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent));
            }

            //(2) 創(chuàng)建 利用插件的ClassLoader 創(chuàng)建插件的Activity實(shí)例
            
            Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
            activity.setIntent(intent);

            //(3)替換插件Activity中的Resources
            // for 4.1+
            Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());
    
            return newActivity(activity);
        }

        return newActivity(mBase.newActivity(cl, className, intent));
    }
    
 }

在Instrumentation的newActivity方法中,找出要啟動(dòng)的真正的Activity類 創(chuàng)建實(shí)例敲霍,并返回給ActivityManagerService.

  • (1) 創(chuàng)建占位Activity時(shí),因?yàn)樗拗髦?只對(duì)StubActivity進(jìn)行了聲明,而并沒有對(duì)應(yīng)的Class俊马,所以此時(shí)會(huì)異常,異常時(shí) 嘗試創(chuàng)建插件的Activity
  • (2) 創(chuàng)建 利用插件的ClassLoader 創(chuàng)建插件的Activity實(shí)例
  • (3)替換插件Activity中的Resources

3.2、Service的啟動(dòng)

VirtualApk中 Mainifest中預(yù)留了兩個(gè)Service(LocalService和RemoteService)肩杈,作為占位的本進(jìn)程Service和獨(dú)立進(jìn)程Service柴我。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.didi.virtualapk.core">
    <application>
        <!-- Local Service running in main process -->
        <service android:exported="false" android:name="com.didi.virtualapk.delegate.LocalService" />

        <!-- Daemon Service running in child process -->
        <service android:exported="false" android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon">
            <intent-filter>
                <action android:name="${applicationId}.intent.ACTION_DAEMON_SERVICE" />
            </intent-filter>
        </service>
    </application>
</manifest>

VirtualApk hook了系統(tǒng)的ActivityManagerProxy類,利用動(dòng)態(tài)代理技術(shù),接管了Service的啟動(dòng)。

 /**
     * hookSystemServices, but need to compatible with Android O in future.
     */
    protected void hookSystemServices() {
        try {
            Singleton<IActivityManager> defaultSingleton;
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();
            } else {
                defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get();
            }
            IActivityManager origin = defaultSingleton.get();
            IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
                createActivityManagerProxy(origin));

            // Hook IActivityManager from ActivityManagerNative
            Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);

            if (defaultSingleton.get() == activityManagerProxy) {
                this.mActivityManager = activityManagerProxy;
                Log.d(TAG, "hookSystemServices succeed : " + mActivityManager);
            }
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }
  • 啟動(dòng)插件中的Service時(shí),會(huì)將intent中的Component替換為宿主中占位的LocalServie或者RemoteService
  • 真正的插件Service和Service的操作 都保存在Intent的參數(shù)中
  • 待LocalService或者RemoteService啟動(dòng)之后,onStartCommand()方法中取出插件Service相關(guān)的參數(shù),然后在LocalService或者RemoteService中 創(chuàng)建插件Service的實(shí)例扩然,并調(diào)用相應(yīng)的回調(diào)方法艘儒。
public class LocalService extends Service {
    private static final String TAG = Constants.TAG_PREFIX + "LocalService";

    /**
     * The target service, usually it's a plugin service intent
     */
    public static final String EXTRA_TARGET = "target";
    public static final String EXTRA_COMMAND = "command";
    public static final String EXTRA_PLUGIN_LOCATION = "plugin_location";

    public static final int EXTRA_COMMAND_START_SERVICE = 1;
    public static final int EXTRA_COMMAND_STOP_SERVICE = 2;
    public static final int EXTRA_COMMAND_BIND_SERVICE = 3;
    public static final int EXTRA_COMMAND_UNBIND_SERVICE = 4;


    private PluginManager mPluginManager;

    @Override
    public IBinder onBind(Intent intent) {
        return new Binder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mPluginManager = PluginManager.getInstance(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        

        ComponentName component = target.getComponent();
        LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
        

        switch (command) {
            case EXTRA_COMMAND_START_SERVICE: {
                ActivityThread mainThread = ActivityThread.currentActivityThread();
                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.currentActivityThread();
                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) {
                        Log.w(TAG, t);
                    }
                }
                try {
                    IBinder binder = service.onBind(target);
                    IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");
                    IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
                    if (Build.VERSION.SDK_INT >= 26) {
                        iServiceConnection.connected(component, binder, false);
                    } else {
                        Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder);
                    }
                } catch (Exception e) {
                    Log.w(TAG, e);
                }
                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;
    }

}

3.3、ContentProvider的啟動(dòng)

VirtualApk中預(yù)留了RemoteContentProvider的坑位ContentProivder

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.didi.virtualapk.core">
    
    <application>
        <provider
            android:exported="false"
            android:name="com.didi.virtualapk.delegate.RemoteContentProvider"
            android:authorities="${applicationId}.VirtualAPK.Provider"
            android:process=":daemon" />

    </application>

</manifest>

ContentProvider的支持依然是通過代理分發(fā)夫偶。

Cursor bookCursor = getContentResolvewr().query(bookUri,new String[]{"_id","name"},null,null,null)

這里用到了PluginContext界睁,在生成Activity、Service的時(shí)候兵拢,為其設(shè)置的Context都為PluginContext對(duì)象翻斟。

所以當(dāng)你調(diào)用getContentResolver時(shí),調(diào)用的為PluginContext的getContentResolver说铃。

  @Override
    public ContentResolver getContentResolver() {
        return new PluginContentResolver(getHostContext());
    }

返回的是一個(gè)PluginContentResolver對(duì)象访惜,當(dāng)我們調(diào)用query方法時(shí)敞斋,會(huì)輾轉(zhuǎn)調(diào)用到
ContentResolver.acquireUnstableProvider方法。該方法被PluginContentResolver中復(fù)寫:

  @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);
    }

PluginManager.getIContentProvider()返回的又是什么呢疾牲?

PluginManager.getIContentProvider() 返回的其實(shí)是RemoteContentProvider的代理對(duì)象IContentProviderProxy。

VirtualApk hook了宿主的RemoteContentProvider,利用動(dòng)態(tài)代理接管了RemoteContentProvider的操作衙解。

protected void hookIContentProviderAsNeeded() {
        Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
        //(1)提前請(qǐng)求一些RemoteContentProvider 創(chuàng)建RemoteContentProvider的實(shí)例
        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 rawProvider = (IContentProvider) provider.get(val);
                    
                    //(2)利用動(dòng)態(tài)代理 接管RemoteContentProvider的操作
                    IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
                    mIContentProvider = proxy;
                    Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
                    break;
                }
            }
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }
  • (1)提前請(qǐng)求一些RemoteContentProvider 創(chuàng)建RemoteContentProvider的實(shí)例
  • (2)利用動(dòng)態(tài)代理 接管RemoteContentProvider的操作

有了IContentProviderProxy對(duì)象 就可以攔截query阳柔、insert、update蚓峦、delete操作舌剂,把用戶調(diào)用的uri,替換為占坑provider(RemouteContentPrpvider)的uri暑椰,再把原本的uri作為參數(shù)拼接在占坑provider的uri后面霍转。

RemouteContentProvider中收到請(qǐng)求后,
取出原本的uri,拿到auth一汽,在通過加載plugin得到providerInfo避消,反射生成provider對(duì)象,在調(diào)用其attachInfo方法召夹,即完成了ContentProvider的代理分發(fā)岩喷。

3.4、BroadCastReciever的注冊(cè)

BroadcastReceiver的處理比較簡(jiǎn)單,只需將插件中靜態(tài)注冊(cè)的BroadCastReceiver轉(zhuǎn)化成動(dòng)態(tài)注冊(cè)即可监憎。

 // 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 br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
            for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
                this.mHostContext.registerReceiver(br, aii);
            }
        }

3.5纱意、小結(jié):

  • Activity:在宿主apk中提前占幾個(gè)坑,然后通過“欺上瞞下”(這個(gè)詞好像是360之前的ppt中提到)的方式鲸阔,啟動(dòng)插件apk的Activity偷霉;因?yàn)橐С植煌膌aunchMode以及一些特殊的屬性,需要占多個(gè)坑褐筛。
  • Service:通過代理Service的方式去分發(fā)类少;主進(jìn)程和其他進(jìn)程,VirtualAPK使用了兩個(gè)代理Service渔扎。
  • BroadcastReceiver:靜態(tài)轉(zhuǎn)動(dòng)態(tài)瞒滴。
  • ContentProvider:通過一個(gè)代理Provider進(jìn)行分發(fā)。

四赞警、插件中的資源如何被加載

4.1妓忍、首先明確什么地方會(huì)用到Resouces

Resources是context的行為能力,所有Context的子類 才有可能取到Resources愧旦,即 Application世剖、Activity、Service笤虫。
另外 ContentProvider 會(huì)在attach()方法時(shí) 傳遞進(jìn)去一個(gè)Context對(duì)象旁瘫。

所以天生持有Resource(或者Context)的類 有四個(gè):Application祖凫、Activity、Service酬凳、ContentProvider

4.2 Application惠况、Activity、Service宁仔、ContentProvider中Resources的替換

PluginContext中保存了插件對(duì)應(yīng)的Resource

  • Application 是如何替換Resouces的
    創(chuàng)建Application對(duì)象時(shí),傳遞的是PluginContext
  • Activity 中 是如何替換Resouces資源的
    VAInstrumentation中 創(chuàng)建Activity 之后, 立即為Activity設(shè)置了mResources(PluginContext.Resouces)
  • Service 中是如何替換資源的
    Service.attach() 傳遞的是PluginContext
  • ContentProvider 是如何替換Resources資源的
    contentProvider.attachInfo() 傳遞的是PluginContenxt

五稠屠、 插件的ClassLoader是如何生效的?

5.1、四大組件創(chuàng)建時(shí)ClassLoader的選擇

  • 創(chuàng)建Application時(shí) 用的是Plugin.classLoader()
  • 創(chuàng)建Activity時(shí) classLoader 用的是Plugin.getClassLoader()
  • 創(chuàng)建Service時(shí) 用的Plugin.ClassLoader()
  • 創(chuàng)建BroadCastReceiver時(shí), 用的是Plugin.getClassLoader()
  • 創(chuàng)建ContentProvider時(shí), 用的是翎苫?权埠?? 沒有用插件的ClassLoader?

5.2煎谍、四大組件之外的類如何加載

對(duì)于宿主apk,插件是一個(gè)相對(duì)獨(dú)立的整體,宿主調(diào)用插件攘蔽,只能通過四大組件來調(diào)用(啟動(dòng)插件activity、service呐粘、broadcast满俗、contentProvider)。

我們知道作岖,通常情況下

在A類中加載B類漫雷,那么加載B類用到的ClassLoadert通常情況下就是A的classLoader

由此得出,我們只需要啟動(dòng)插件Activity時(shí) 選用插件的ClassLoader鳍咱,那么Activity內(nèi)用到的類降盹,默認(rèn)都是插件ClassLoader來加載的。

六谤辜、其他

6.1蓄坏、插件apk 是如何解析的?

利用系統(tǒng)自帶的PackageParser.parsePackage(),來解析一個(gè)APK

6.2丑念、坑位中的Activity是如何匹配的涡戳。

Manifest中預(yù)埋了 各種模式的Activity,分為ABCD四種:

  • A 代表 stand模式的Activity, 2個(gè)
  • B 代表 singleTop模式的Activity 脯倚,8個(gè)
  • C 代表 singleTask模式的Activity渔彰,8個(gè)
  • D 代表 singleInstance模式的Activity,8個(gè)

StubActivityInfo.getStubActivity() 根據(jù)launchMode 來選擇未使用的預(yù)留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>

6.3恍涂、virtualapk-gradle-plugin 做了什么事情

virtualapk-gradle-plugin 分為host和plugIn兩部分

  • 宿主-plugin
apply plugin: 'com.didi.virtualapk.host'
1、generateDependencies()記錄速錄依賴庫信息植榕,生成version.txt再沧、allVersions.txt兩個(gè)文件
2、backupHostR 備份R.txt文件 生成Host_R.txt
3尊残、backupProguardMapping 備份混淆文件炒瘸,生成mapping.txt
生成四個(gè)文件 version.txt淤堵、allVersions.txt、Host_R.txt顷扩、mapping.txt
  • 插件-plugin
插件工程引入
apply plugin: 'com.didi.virtualapk.plugin'
插件app moudle的build.gradle中配置
virtualApk {
    packageId = 0x6f             // The package id of Resources.
    targetHost='source/host/app' // The path of application module in host project.
    applyHostMapping = true      // [Optional] Default value is true. 
}

virtualapk.plugin 根據(jù)宿主生成的 version.txt拐邪、Host_R.txt、mapping.txt 對(duì)插件的依賴隘截、資源進(jìn)行了去重操作扎阶。重復(fù)的依賴或資源以宿主依賴和資源為主,去除插件的對(duì)應(yīng)項(xiàng)技俐。

七、參考資料

virtualAPK 四大組件啟動(dòng)

virualApk資源的替換
virtualApk-gradle-plugin

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末统台,一起剝皮案震驚了整個(gè)濱河市雕擂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贱勃,老刑警劉巖井赌,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贵扰,居然都是意外死亡仇穗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門戚绕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纹坐,“玉大人,你說我怎么就攤上這事舞丛≡抛樱” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵球切,是天一觀的道長(zhǎng)谷誓。 經(jīng)常有香客問我,道長(zhǎng)吨凑,這世上最難降的妖魔是什么捍歪? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮鸵钝,結(jié)果婚禮上糙臼,老公的妹妹穿的比我還像新娘。我一直安慰自己恩商,他們只是感情好弓摘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痕届,像睡著了一般韧献。 火紅的嫁衣襯著肌膚如雪末患。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天锤窑,我揣著相機(jī)與錄音璧针,去河邊找鬼。 笑死渊啰,一個(gè)胖子當(dāng)著我的面吹牛探橱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绘证,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼隧膏,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了嚷那?” 一聲冷哼從身側(cè)響起胞枕,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎魏宽,沒想到半個(gè)月后腐泻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队询,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年派桩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚌斩。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铆惑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出送膳,到底是詐尸還是另有隱情鸭津,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布肠缨,位于F島的核電站逆趋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晒奕。R本人自食惡果不足惜闻书,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脑慧。 院中可真熱鬧魄眉,春花似錦、人聲如沸闷袒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囊骤。三九已至晃择,卻和暖如春冀值,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宫屠。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工列疗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浪蹂。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓抵栈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親坤次。 傳聞我的和親對(duì)象是個(gè)殘疾皇子古劲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容