滴滴插件化解析(一)

我們這一節(jié)來分析一下滴滴插件化是如何啟動(dòng)插件的 Activity 的伺通。

一、使用

  • 1.配置宿主工程的 Module#build.gradle
image
  • 2.配置插件工程的 Module#build.gradle
image
  • 3.將插件工程使用 assemblePlugin 任務(wù)打包出一個(gè) apk 文件逢享,放進(jìn)宿主工程的 assets 目錄中

  • 4.在宿主的 Application 中進(jìn)行滴滴插件化工具的初始化

image
  • 5.在需要用到插件 apk 的資源的地方進(jìn)行插件加載
image
  • 6.正常調(diào)用插件 apk 的 Activity罐监、Service 等資源
image

二、插件化加載 Activity 的過程解析

  • 1.插件初始化

PluginManager.getInstance(context).init();

我們仔細(xì)看看這里做了什么工作:

public static PluginManager getInstance(Context base) {
        //宿主的 context
        if (sInstance == null) {
            synchronized (PluginManager.class) {
                if (sInstance == null)
                    sInstance = new PluginManager(base);
            }
        }

        return sInstance;
    }

    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();
        //關(guān)鍵操作
        this.hookInstrumentationAndHandler();
        //關(guān)鍵操作
        this.hookSystemServices();
    }

    public void init() {
        mComponentsHandler = new ComponentsHandler(this);
        RunUtil.getThreadPool().execute(new Runnable() {
            @Override
            public void run() {
                doInWorkThread();
            }
        });
    }
    
    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);
            }

            //hook Instrumentation瞒爬,set 進(jìn) ActivityThread 中
            final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
            Object activityThread = ReflectUtil.getActivityThread(this.mContext);
            ReflectUtil.setInstrumentation(activityThread, instrumentation);
            ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
            this.mInstrumentation = instrumentation;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

可以看到弓柱,PluginManager 初始化的時(shí)候回保存宿主的 Application context,之后會(huì)反射獲取 ActivityThread 的 Instrumentation 對(duì)象,并且生成一個(gè) VAInstrumentation 對(duì)象侧但,再反射設(shè)置進(jìn) ActivityThread 中矢空。

這一步很重要,因?yàn)?Instrumentation 涉及到加載 Activity 的過程禀横,插件化能對(duì) Android 系統(tǒng)進(jìn)行欺上瞞下以及調(diào)用宿主的 Activity 就是因?yàn)?hook 了 Instrumentation屁药。

  • 2.加載插件 apk
PluginManager.getInstance(mContext).loadPlugin(apk);
/**
     * load a plugin into memory, then invoke it's Application.
     * @param apk the file of plugin, should end with .apk
     * @throws Exception
     */
    public void loadPlugin(File apk) throws Exception {
        if (null == apk) {
            throw new IllegalArgumentException("error : apk is null.");
        }

        if (!apk.exists()) {
            throw new FileNotFoundException(apk.getAbsolutePath());
        }

        //調(diào)用 PackageParser.parsePackage 解析 apk,得到的信息封裝進(jìn) loadedPlugin 對(duì)象
        LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);
        if (null != plugin) {
            //加載過的插件緩存起來
            this.mPlugins.put(plugin.getPackageName(), plugin);
            synchronized (mCallbacks) {
                for (int i = 0; i < mCallbacks.size(); i++) {
                    //hookDataBindUtil 的時(shí)候會(huì)用到
                    mCallbacks.get(i).onAddedLoadedPlugin(plugin);
                }
            }
            //構(gòu)造插件的 Application,并調(diào)用插件 Application 的 onCreate()
            plugin.invokeApplication();
        } else {
            throw  new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
        }
    }

可以看到,這里創(chuàng)建了一個(gè) LoadedPlugin 對(duì)象柏锄,這個(gè) LoadedPlugin 對(duì)象很重要酿箭,用來解析插件 apk 的資源复亏。

/**
 * 資源加載
 * Created by renyugang on 16/8/9.
 */
public final class LoadedPlugin {

    public static LoadedPlugin create(PluginManager pluginManager, Context host, File apk) throws Exception {
        //需要注意 context 是宿主的 Context
        //apk 指的是插件的路徑
        return new LoadedPlugin(pluginManager, host, apk);
    }
    
    ......
    
    LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
        this.mPluginManager = pluginManager;
        this.mHostContext = context;
        this.mLocation = apk.getAbsolutePath();
        this.mPackage = PackageParserCompat.parsePackage(context, apk, /*PackageParser.PARSE_MUST_BE_APK*/0);
        this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
        this.mPackageInfo = new PackageInfo();
        this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
        this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();

        if (Build.VERSION.SDK_INT >= 28
                || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview
            try {
                this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;
            } catch (Throwable e) {
                PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
                this.mPackageInfo.signatures = info.signatures;
            }
        } else {
            this.mPackageInfo.signatures = this.mPackage.mSignatures;
        }
        this.mPackageInfo.packageName = this.mPackage.packageName;
        if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {
            throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);
        }
        this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
        this.mPackageInfo.versionName = this.mPackage.mVersionName;
        this.mPackageInfo.permissions = new PermissionInfo[0];
        this.mPackageManager = new PluginPackageManager();
        this.mPluginContext = new PluginContext(this);
        this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);

        //通過 COMBINE_RESOURCES 決定是否將插件資源加載到宿主中
        this.mResources = createResources(context, apk);
        //創(chuàng)建 ClassLoader
        this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());

        tryToCopyNativeLib(apk);

        // Cache instrumentations
        Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();
        for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) {
            instrumentations.put(instrumentation.getComponentName(), instrumentation.info);
        }
        this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);
        this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]);

        // Cache activities
        Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
        for (PackageParser.Activity activity : this.mPackage.activities) {
            activityInfos.put(activity.getComponentName(), activity.info);
        }
        this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
        this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);

        // Cache services
        Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();
        for (PackageParser.Service service : this.mPackage.services) {
            serviceInfos.put(service.getComponentName(), service.info);
        }
        this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);
        this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);

        // Cache providers
        Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();
        Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();
        for (PackageParser.Provider provider : this.mPackage.providers) {
            providers.put(provider.info.authority, provider.info);
            providerInfos.put(provider.getComponentName(), provider.info);
        }
        this.mProviders = Collections.unmodifiableMap(providers);
        this.mProviderInfos = Collections.unmodifiableMap(providerInfos);
        this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);

        // 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) {
                //靜態(tài)廣播轉(zhuǎn)動(dòng)態(tài)注冊(cè)
                this.mHostContext.registerReceiver(br, aii);
            }
        }
        this.mReceiverInfos = Collections.unmodifiableMap(receivers);
        this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);
    }

    private static ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) {
        File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE);
        String dexOutputPath = dexOutputDir.getAbsolutePath();
        //雙親委托機(jī)制加載,parent 為宿主的 ClassLoader缭嫡,這樣可以讓插件模塊調(diào)起宿主工程的 Activity
        //DexClassLoader: 可以從包含classes.dex的jar或者apk中缔御,加載類,一般用于執(zhí)行動(dòng)態(tài)加載
        DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);

        if (Constants.COMBINE_CLASSLOADER) {
            try {
                DexUtil.insertDex(loader);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return loader;
    }

    private static AssetManager createAssetManager(Context context, File apk) {
        try {
            AssetManager am = AssetManager.class.newInstance();
            ReflectUtil.invoke(AssetManager.class, am, "addAssetPath", apk.getAbsolutePath());
            return am;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static Resources createResources(Context context, File apk) {
        if (Constants.COMBINE_RESOURCES) {
            //將插件 Assetmanager 的路徑傳進(jìn)去妇蛀,適用于插件資源合并到宿主里面去的情況耕突,
            // 最后插件通過宿主的 Resources 對(duì)象去訪問宿主的資源
            Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
            //這里是宿主的 context
            ResourcesManager.hookResources(context, resources);
            return resources;
        } else {
            Resources hostResources = context.getResources();
            //獲取插件 apk 的 AssetManager
            AssetManager assetManager = createAssetManager(context, apk);
            //適用于資源獨(dú)立,返回插件獨(dú)立的 Resources 對(duì)象评架,不與宿主有關(guān)系有勾,無法訪問到宿主的資源
            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
            //return context.getResources();
        }
    }

    private static ResolveInfo chooseBestActivity(Intent intent, String s, int flags, List<ResolveInfo> query) {
        return query.get(0);
    }
    

可以看到,LoadedPlugin 負(fù)責(zé)生成 ClassLoader古程、Resources 對(duì)象來加載插件 apk 的Activity 和資源蔼卡。

  • 3.正常調(diào)用插件的 Activity

這個(gè)是真正難的地方,這里需要結(jié)合 Android 啟動(dòng)一個(gè) Activity 的過程來看挣磨。

Intent intent = new Intent();
intent.setClassName("com.example.moduleone", "com.example.moduleone.ModuleOneMainActivity");
startActivity(intent);

當(dāng)調(diào)用 startActivity(intent); 時(shí)雇逞,其實(shí)是調(diào)用 startActivityForResult(intent, -1);然后就走到了:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            
            //轉(zhuǎn)到了 Instrumentation 啟動(dòng) Activity
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
            
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
        
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

可以看到我們之前的重點(diǎn)關(guān)注對(duì)象 Instrumentation 出現(xiàn)了!W氯埂塘砸!

一起看看 Instrument 如何啟動(dòng)一個(gè) Activity。

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        //當(dāng) intent.getComponent() 為空時(shí)晤锥,根據(jù) intent 的 action掉蔬,data,category 等
        // 去已加載的 plugin 中匹配到確定的 Activity
        mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
        // null component is an implicitly intent
        if (intent.getComponent() != null) {
            //這句 Log 信息可以判斷 Loadplugin 對(duì)象是否已經(jīng)加載成功
            Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
                    intent.getComponent().getClassName()));
            //欺上瞞下,根據(jù)需要是否替換矾瘾,替換的話就根據(jù) launchode 替換插件的 Activity 為插件 Manifest.xml 占坑的 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};
            //調(diào)用宿主的 Instrument 對(duì)象使用 ActivityManagerProxy 和 AMS 進(jìn)行跨進(jìn)程通信
            result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
                    "execStartActivity", parameterTypes,
                    who, contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            if (e.getCause() instanceof ActivityNotFoundException) {
                throw (ActivityNotFoundException) e.getCause();
            }
            e.printStackTrace();
        }

        return result;
    }

重點(diǎn)看一下 this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);

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

        String targetPackageName = intent.getComponent().getPackageName();
        String targetClassName = intent.getComponent().getClassName();
        // 判斷如果啟動(dòng)的是插件中類女轿,則將啟動(dòng)的包名和Activity類名存到了intent中,可以看到這里存儲(chǔ)明顯是為了后面恢復(fù)用的
        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);
        }
    }

    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替換啟動(dòng)的Activity為占坑Activity
        intent.setClassName(mContext, stubActivity);
    }

//Manifest.xml 寫好的占坑 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 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("LogUtils_StubActivity", "getStubActivity, is transparent theme ? " + windowIsTranslucent);
        }
        //這里只是為了騙過系統(tǒng)壕翩,Activity 已在 Manifest.xml 中注冊(cè)蛉迹,所以這里的包名以及類的全名必須
        //和 Manifest.xml 占坑的相同
        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;
    }
image

可以看到,本來啟動(dòng)未在 Manifest.xml 中注冊(cè)的插件 Activity 應(yīng)該會(huì)報(bào)錯(cuò)的放妈,但是經(jīng)過 VAInstrumentation 這么一個(gè)欺上瞞下北救,就做到了欺騙系統(tǒng)加載插件 Activity。

但是這里只是做到了一半芜抒,因?yàn)閾Q了要啟動(dòng)的 Activity珍策,欺騙過了 AMS 之后,最終要啟動(dòng)的不可能是占坑 Activity宅倒,還應(yīng)該是我們的啟動(dòng)的目標(biāo) Activity 呀攘宙。

接下來的就要看 Activity 的啟動(dòng)過程了。

Instrumentation.execStartActivity() 會(huì)調(diào)用ActivityManagerService.startActivity() 來啟動(dòng) Activity (IPC 過程)。這個(gè)過程 AMS 會(huì)對(duì)要啟動(dòng)的 Activity 進(jìn)行 Activity 棧還有其他的處理模聋。

APP 進(jìn)程客戶端:ActivityManagerProxy =====> Binder驅(qū)動(dòng) =====> ActivityManagerService:AMS 服務(wù)端

在 AMS 處理完啟動(dòng) Activity 后,會(huì)調(diào)用:app.thread.scheduleLaunchActivity() (同樣是 IPC 過程)唠亚,這里的 thread 對(duì)應(yīng)為 server 端链方,其實(shí)就是我們 APP 進(jìn)程的 ActivityThread 中的 ApplicationThread 對(duì)象,而此時(shí)的 AMS 的 IApplicationThread 作為客戶端灶搜,所以是 AMS 客戶端調(diào)用 APP 進(jìn)程服務(wù)端的 ApplicationThread.scheduleLaunchActivity() 方法祟蚀,這樣啟動(dòng) Activity 的操作又回到了 APP 進(jìn)程。

APP 進(jìn)程服務(wù)端:ApplicationThread <===== Binder驅(qū)動(dòng) <===== ApplicationThreadProxy:AMS 客戶端

這時(shí)會(huì)在 ApplicationThread 內(nèi)部會(huì)調(diào)用 mH 類(類 H 是 ActivityThread 的內(nèi)部類割卖,并繼承了 Handler)的 sendMessage() 方法前酿,傳遞的標(biāo)識(shí)為 H.LAUNCH_ACTIVITY,進(jìn)入調(diào)用到 ActivityThread 的 handleLaunchActivity() 方法 :

ActivityThread.handleLaunchActivity() -> ActivityThread.performLaunchActivity() -> mInstrumentation.newActivity() (通過 ClassLoader 實(shí)例化 Activity 對(duì)象)-> Instrumentation.callActivityOnCreate() 調(diào)用 Activity 的 onCreate()鹏溯。

至此罢维,我們就知道了 Activity 的啟動(dòng)流程,那么應(yīng)用到滴滴的插件化技術(shù)丙挽,我們可以知道肺孵,在 AMS 處理完畢后,會(huì)回調(diào)到 VAInstrumentation.newActivity()颜阐。我們重點(diǎn)看看這個(gè)方法:

@Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try {
            //這個(gè)是宿主的 ClassLoader,而我們替換之后的Activity是插件 Manifest.xml 中聲明的 Activity平窘,
            // 實(shí)際上并沒有這樣的類,所以如果要加載的是插件的 占坑 Activity 一定會(huì)拋異常凳怨,加載宿主的就不會(huì)
            cl.loadClass(className);
        } catch (ClassNotFoundException e) {
            //取出目標(biāo)啟動(dòng) Activity 的包名和類名
            ComponentName component = PluginUtil.getComponent(intent);
            //根據(jù)包名獲取加載插件的 ClassLoader
            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
            //取出要啟動(dòng)的插件目標(biāo) Activity 類名
            String targetClassName = component.getClassName();

            //這里的 className 就是插件占坑的 Activity 名字
            Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));

            if (plugin != null) {
                //這里傳入的是構(gòu)造的插件的 ClassLoader瑰艘,所以能加載插件的 Activity
                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;
            }
        }

        //如果是啟動(dòng)宿主的 Activity,直接就跳到了這一步肤舞,中間的過程不會(huì)進(jìn)入紫新,intent 的內(nèi)容也不會(huì)被替換
        return mBase.newActivity(cl, className, intent);
    }

可以看到,滴滴插件化也是通過在 Instrumentation 里面進(jìn)行還原目標(biāo) Activity 的加載李剖。這個(gè)做法侵入性比較小弊琴,所以兼容性好一點(diǎn),感覺也比較優(yōu)雅杖爽。

接下來我們看最后一步:Instrumentation.callActivityOnCreate()敲董。這一步直接關(guān)聯(lián)到了傳說中的 Activity.onCreate()。

@Override
    public void callActivityOnCreate(Activity activity, Bundle icicle) {
        final Intent intent = activity.getIntent();
        //在「欺上瞞下」的時(shí)候進(jìn)行標(biāo)記為 true慰安,否則就是啟動(dòng) 宿主的Activity腋寨,跳過資源替換的步驟
        if (PluginUtil.isIntentFromPlugin(intent)) {
            //這個(gè) base 是宿主的 Application
            Context base = activity.getBaseContext();
            try {
                LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
                //由于 ContextWrapper 和 Resources 資源加載相關(guān),所以必須替換為插件的 Context 以及插件的 Resources
                //詳細(xì)見 LoadedPlugin#createResources()
                ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources());
                //mBase 就是 pluginContext 對(duì)象化焕,里面包裝有宿主的 Application
                ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext());
                //之前在 LoadPlugin 對(duì)象的時(shí)候萄窜,就已經(jīng)創(chuàng)建了插件的 Application,進(jìn)行替換
                ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication());
                ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext());

                // set screenOrientation
                ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
                if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                    activity.setRequestedOrientation(activityInfo.screenOrientation);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent);
            if (theme != 0) {
                activity.setTheme(theme);
            }
        }

        mBase.callActivityOnCreate(activity, icicle);
    }

//LoadPlugin#createResources()
private static Resources createResources(Context context, File apk) {
        if (Constants.COMBINE_RESOURCES) {
            //將插件 Assetmanager 的路徑傳進(jìn)去,適用于插件資源合并到宿主里面去的情況查刻,
            // 最后插件通過宿主的 Resources 對(duì)象去訪問宿主的資源
            Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
            //這里是宿主的 context
            ResourcesManager.hookResources(context, resources);
            return resources;
        } else {
            Resources hostResources = context.getResources();
            //獲取插件 apk 的 AssetManager
            AssetManager assetManager = createAssetManager(context, apk);
            //適用于資源獨(dú)立键兜,返回插件獨(dú)立的 Resources 對(duì)象,不與宿主有關(guān)系穗泵,無法訪問到宿主的資源
            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
            //return context.getResources();
        }
    }

可以看到普气,對(duì)于要啟動(dòng)的插件 Activity,這里修改了插件的 mResources佃延、mBase(Context)现诀、mApplication 對(duì)象,以及設(shè)置一些可動(dòng)態(tài)設(shè)置的屬性履肃,這里僅設(shè)置了屏幕方向仔沿。

注意啦,這里將 mBase 替換為 PluginContext尺棋,可以修改 Resources封锉、AssetManager 以及攔截相當(dāng)多的操作。

原本 Activity 的部分 get 操作

# ContextWrapper
@Override
public AssetManager getAssets() {
    return mBase.getAssets();
}

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

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

@Override
public ContentResolver getContentResolver() {
    return mBase.getContentResolver();
}

替換之后:

# PluginContext

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

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

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

這樣可以很巧妙地對(duì)資源的加載進(jìn)行攔截膘螟,甚至攔截和替換啟動(dòng)的插件 Service 等烘浦。

到這里,我們的插件 Activity 就可以正常啟動(dòng)啦萍鲸。

三闷叉、優(yōu)秀的參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市脊阴,隨后出現(xiàn)的幾起案子握侧,更是在濱河造成了極大的恐慌,老刑警劉巖嘿期,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件品擎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡备徐,警方通過查閱死者的電腦和手機(jī)萄传,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜜猾,“玉大人秀菱,你說我怎么就攤上這事〔渌” “怎么了衍菱?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肩豁。 經(jīng)常有香客問我脊串,道長辫呻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任琼锋,我火速辦了婚禮放闺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缕坎。我一直安慰自己怖侦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布念赶。 她就那樣靜靜地躺著础钠,像睡著了一般恰力。 火紅的嫁衣襯著肌膚如雪叉谜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天踩萎,我揣著相機(jī)與錄音停局,去河邊找鬼。 笑死香府,一個(gè)胖子當(dāng)著我的面吹牛董栽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播企孩,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼锭碳,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了勿璃?” 一聲冷哼從身側(cè)響起擒抛,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎补疑,沒想到半個(gè)月后歧沪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莲组,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年诊胞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锹杈。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撵孤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竭望,到底是詐尸還是另有隱情早直,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布市框,位于F島的核電站霞扬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喻圃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一萤彩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斧拍,春花似錦雀扶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至昂勉,卻和暖如春浪册,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背岗照。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工村象, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人攒至。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓厚者,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迫吐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子库菲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353