Android 插件化框架 DynamicLoadApk 源碼分析

DynamicLoadApk 應該算是 Android 插件化諸多框架中資歷比較老的一個了。它的項目地址在:dynamic-load-apk。該項目運行以後的效果是,使用 Gradle 編譯出插件包和宿主包,都是以 APK 的形式。安裝宿主包以後,經過 ADB 將插件包 push 到手機中。啓動宿主包時,它會自動進行掃描將插件加載到應用中。點擊插件以後,進入到插件的應用界面。java

印象中最初接觸的插件都是以單獨安裝的形式存在的,好比我能夠作一個基礎的應用,而後在該應用的基礎上開發插件。用戶能夠對插件進行選擇,而後下載並安裝,以讓本身的應用具備更豐富的功能。插件化也算是一種比較實用的技術,畢竟咱們使用 Chrome 和 AS 的時候不是同樣要加載插件。只是比較反感的是去修改底層的代碼,容易給系統帶來不穩定因素不說,技術到了一些人手裏,你知道他用來幹什麼。插件化挺好,但真的要去推廣這項技術,仍是看好 Google 官方去進行規範。git

技術要服務於產品,好的產品不必定要高超的技術,技術並非最重要的,重要的是你究竟想要表達什麼。這就像國內不少人只注重數理化,不注重人文學科。相比於國內的技術精英,我仍是比較贊同 Google 站在整個生態的角度去考慮技術演進。前些日子社區裏對插件化的討論:移動開發的羅曼蒂克消亡史。好吧,我本身的理解是,這歷來就不是什麼羅曼蒂克。github

DynamicLoadApk 插件化的實現方式仍是挺有意思的,它使用純 Java 實現,沒有涉及 Native 層的代碼,下面我理了下 DynamicLoadApk 的 Demo 程序的整個執行過程。後續的文章咱們就圍繞這張圖進行,安全

DynamicLoadApk 的插件化執行過程

首先是掃描文件路徑並加載 APK,這裏須要解析 APK 文件的信息,它是本質上是經過 PMS 實現的;框架

public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
        mFrom = DLConstants.FROM_EXTERNAL;

        // 經過 PMS 獲取包信息,這裏獲取了 Activity 和 Service 的信息
        PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
                PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
        if (packageInfo == null) {
            return null;
        }

        DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
        if (hasSoLib) {
            copySoLib(dexPath);
        }

        return pluginPackage;
    }
複製代碼

而後,它經過調用 preparePluginEnv() 方法來建立 AssetManager, DexClassLoader 和 Resource 等。咱們的插件類加載各類資源和類的時候使用的就是這哥仨:ui

private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {

        DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
        if (pluginPackage != null) {
            return pluginPackage;
        }
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        // create pluginPackage
        pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
        mPackagesHolder.put(packageInfo.packageName, pluginPackage);
        return pluginPackage;
    }

    private DexClassLoader createDexClassLoader(String dexPath) {
        File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
        dexOutputPath = dexOutputDir.getAbsolutePath();
        DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader());
        return loader;
    }

    private AssetManager createAssetManager(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            return assetManager;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private Resources createResources(AssetManager assetManager) {
        Resources superRes = mContext.getResources();
        Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        return resources;
    }
複製代碼

而後點擊插件的時候要啓動插件的 Activity,this

PluginItem item = mPluginItems.get(position);
    DLPluginManager pluginManager = DLPluginManager.getInstance(this);
    pluginManager.startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launcherActivityName));
複製代碼

這裏會把要啓動的包名和啓動類名包裝到 DLIntent 中。DLIntent 是 Intent 的子類。啓動插件的進一步的邏輯在 DLPluginManager 的 startPluginActivity() 方法中。按照上文的描述,這裏主要作了如下四件事情:spa

  1. 判斷要啓動的 Activity 是不是插件 Activity:由於要啓動的類也可能不是插件類,因此咱們須要分紅兩種狀況來進行處理,普通的 Activity 直接調用 Context.startActivity() 插件 Activity 須要調用代理 Activity 來執行。
  2. 判斷包名,獲取插件相關信息:這裏就算是一個安全的校驗吧,主要是從以前解析的 APK 信息中進行校驗。
  3. 使用插件的 DexClassLoader 加載啓動類:先要使用類加載器加載插件的 Activity 到內存中,插件 Activity 的信息會做爲 Intent 的參數一塊兒傳遞給代理 Activity。
  4. 使用 DLIntent.setClass() 啓動代理類:要啓動的代理類多是 DLProxyFragmentActivity 和 DLProxyActivity,因此這裏咱們先使用 getProxyActivityClass() 獲得代理類。該方法中使用了 Class 的 isAssignableFrom() 方法來判斷某個實例是不是指定類型的。好比 DLBasePluginActivity.class.isAssignableFrom(clazz) 表示 clazz 是不是 DLBasePluginActivity 類型的。
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
        // 1.判斷要啓動的 Activity 是不是插件 Activity
        if (mFrom == DLConstants.FROM_INTERNAL) {
            dlIntent.setClassName(context, dlIntent.getPluginClass());
            performStartActivityForResult(context, dlIntent, requestCode);
            return DLPluginManager.START_RESULT_SUCCESS;
        }

        // 2.判斷包名,獲取插件相關信息
        String packageName = dlIntent.getPluginPackage();
        if (TextUtils.isEmpty(packageName)) {
            throw new NullPointerException("disallow null packageName.");
        }

        DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
        if (pluginPackage == null) {
            return START_RESULT_NO_PKG;
        }

        // 3.使用插件的 DexClassLoader 加載啓動類
        final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
        Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
        if (clazz == null) {
            return START_RESULT_NO_CLASS;
        }

        Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
        if (activityClass == null) {
            return START_RESULT_TYPE_ERROR;
        }

        // 4.使用 DLIntent.setClass() 啓動代理類,並傳入插件類和包信息
        dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
        dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
        dlIntent.setClass(mContext, activityClass);
        performStartActivityForResult(context, dlIntent, requestCode);
        return START_RESULT_SUCCESS;
    }

    private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
        Class<? extends Activity> activityClass = null;
        if (DLBasePluginActivity.class.isAssignableFrom(clazz)) {
            activityClass = DLProxyActivity.class;
        } else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) {
            activityClass = DLProxyFragmentActivity.class;
        }

        return activityClass;
    }

    private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
        if (context instanceof Activity) {
            ((Activity) context).startActivityForResult(dlIntent, requestCode);
        } else {
            context.startActivity(dlIntent);
        }
    }
複製代碼

這裏須要注意下,咱們的插件 Activity 是須要繼承 DLBasePluginActivity 或者 DLProxyFragmentActivity。這兩個類中重寫了 Activity 的許多生命週期方法。在代理 Activity 啓動以後,代理 Activity 會被傳遞到前面兩個基類中。好比,當插件類想要獲取 AssetsManager 的時候,會調用到這兩個基類的 getAssetsManager(),而後基類經過代理類獲得以前咱們建立的 AssetsManager.插件

按照上述流程,代理類被正常啓動。啓動以後它會建立 DLProxyImpl 實例,並在 onCreate() 方法中調用 DLProxyImpl 的 onCreate() 方法:代理

public void onCreate(Intent intent) {
        intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);

        mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
        mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);

        mPluginManager = DLPluginManager.getInstance(mProxyActivity);
        mPluginPackage = mPluginManager.getPackage(mPackageName);
        mAssetManager = mPluginPackage.assetManager;
        mResources = mPluginPackage.resources;

        initializeActivityInfo();
        handleActivityInfo();
        launchTargetActivity();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    protected void launchTargetActivity() {
        try {
            Class<?> localClass = getClassLoader().loadClass(mClass);
            Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
            Object instance = localConstructor.newInstance(new Object[] {});
            mPluginActivity = (DLPlugin) instance;
            ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
            mPluginActivity.attach(mProxyActivity, mPluginPackage);

            Bundle bundle = new Bundle();
            bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
            mPluginActivity.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
複製代碼

這裏的主要邏輯在上述兩個方法中。第一個方法中會根據包名從 DLPluginManager 中獲取包的類加載器,而後使用該加載其加載器加載插件類,反射觸發其構造方法,獲取實例。而後調用代理 Activity 的 attach() 方法將該插件類複製給代理類。而後當 AMS 回調代理類的各個生命週期的時候,代理類調用插件類的各個生命週期。(這裏會使用類加載器再次加載插件類,其實這是不必的,咱們能夠直接使用 Intent 將插件類的 Class 經過序列化的方式傳遞過來,而後直接觸發其構造方法便可,無需再次執行類加載邏輯。)

好了,以上就是 DynamicLoadApk 的原理,其實本質就是:插件類做爲一個普通的類被調用,它不歸 AMS 負責。當咱們啓動插件的時候,實際啓動的是代理類,當 AMS 回調代理類的生命週期的時候,代理類再調用插件類的各個生命週期方法。只是,對資源和類加載的部分須要注意下,由於咱們須要進行自定義配置來把它們的路徑指向咱們的插件包。

相關文章
相關標籤/搜索