架構之佔位式插件化框架 --組件通信

佔位式,也叫插裝式。運行的APP,也稱之爲宿主。

Activity通信

通過宿主來加載Plugin Activity

1. 環境準備

項目分爲3個基礎模塊,分別是 宿主module(可以啓動),插件module(最終會打包成單獨apk文件),標準module(Android Library)。宿主和插件分別依賴標準

  1. 新建 插件module: plugin_package,該module只是爲打包成apk文件。
  2. 新建 標準module:standard,該module是爲了維護宿主和插件。是一個activity library
  3. 添加依賴關係,宿主和插件分別依賴標準stander

標準制定(組件的管理):

  1. 制定標準
    /**
     * standard 模塊
     * 制定組件Activity的標準
     */
    public interface ActivityInterface {
    
        /**
         * 把宿主的環境給插件
         * @param appActivity
         */
        void insertAppActivity(Activity appActivity);
    
        void onCreate(Bundle savedInstanceState);
    
        void onResume();
    
        void onPause();
    
        void onStop();
    
        void onDestroy();
    
    }
    
  2. 插件的組件實現該標準
    /**
     * 插件 module 中的 Activity組件基類
     */
    public class BaseActivity extends Activity implements ActivityInterface {
    
        protected Activity appActivity; //宿主的 Activity環境
    
        @Override
        public void insertAppActivity(Activity appActivity) {
            this.appActivity = appActivity;
        }
    
        @SuppressLint("MissingSuperCall")
        @Override
        public void onCreate(Bundle savedInstanceState) {
    
        }
    
        @SuppressLint("MissingSuperCall")
        @Override
        public void onResume() {
    
        }
    
        @SuppressLint("MissingSuperCall")
        @Override
        public void onPause() {
    
        }
    
        @SuppressLint("MissingSuperCall")
        @Override
        public void onStop() {
    
        }
    
        @SuppressLint("MissingSuperCall")
        @Override
        public void onDestroy() {
    
        }
    }
    
    

插件的特點:插件沒有組件環境,可以將宿主的組件環境給插件,可以通過標準傳遞給插件
加載插件:①加載插件中的Activity組件,②加載插件中的資源文件

2. 加載

在宿主APP,通過用戶觸發加載插件apk文件;而APK文件通過服務器下發,保存在本地存儲。
加載包含兩部分:①加載插件的Class ②加載插件的layout


    /**
     * 加載插件:
     * 1. 加載activity
     * 2.加載layout
     */

    public void loadPlugin() {

        try {
            // 1. 加載plugin class
            File file = getPluginPath();
            if (!file.exists()) {
                Log.e(TAG, "loadPlugin: 插件不存在 ");
                return;
            }

            String pluginPath = file.getAbsolutePath();
            // dexClassLoader需要一個緩存目錄
            // getDir 生成路徑: /data/data/當前應用包名/pDir。
            String optimizedDirectory = mContext.getDir("pDir", Context.MODE_PRIVATE).getAbsolutePath();
            /**
             * 加載class
             * @param String dexPath,  插件路徑
             * @param String optimizedDirectory,  加載插件apk 需要一個緩存目錄
             * @param String librarySearchPath, 底層c/c++ 庫的目錄
             * @param ClassLoader parent ClassLoader
             */
            mDexClassLoader = new DexClassLoader(pluginPath, optimizedDirectory, null, mContext.getClassLoader());

            // 2. 加載plugin layout
            //////////////////////////////////////////////////////////////////////////////////////////////////////
            //////////////////////////////////////////////////////////////////////////////////////////////////////
            // public final class AssetManager 不能直接new  需要通過反射調用
            AssetManager assetManager = AssetManager.class.newInstance();
            // 執行 android.content.res.AssetManager#addAssetPath ,將插件包的路徑添加進去,加載resource  可以加載zip apk文件
            /**
             * @param parameterTypes 不是真正的類型,而是類類型
             */
            Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);//
            /**
             * @param obj 執行的方法所對應的類對象
             * @param args 執行方法的參數
             */
            addAssetPathMethod.invoke(assetManager,pluginPath);


            // 至此,assetManager就可以加載資源文件
            //////////////////////////////////////////////////////////////////////////////////////////////////////
            //////////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             *  @param assets  資源管理類
             *  @param metrics 資源的配置信息  對應不同的資源分辨率
             *  @param config
             */
            Resources res = mContext.getResources(); // 宿主資源配置信息
            mResources = new Resources(assetManager, res.getDisplayMetrics(), res.getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3. 啓動插件

Activity啓動採用任務棧的啓動,由於插件Activity沒有安裝是不能啓動的,採用代理Activity啓動插件Activity

宿主Activity中,觸發啓動插件。

    // 啓動插件裏面的Activity
    public void startPluginActivity(View view) {
        PackageManager packageManager = getPackageManager();
        String pluginPath = PluginManager.getPluginPath().getAbsolutePath();
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);
//        for (int i = 0; i < packageInfo.activities.length; i++) {
//            Log.e(TAG, "packageInfo.activities:" + packageInfo.activities[i]);
//        }
        // 拿到第一個activity
        ActivityInfo activityInfo = packageInfo.activities[0];
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className", activityInfo.name);
        startActivity(intent);
    }

在代理Activity中做真正的啓動,首先要將宿主的環境注入到插件

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String className = getIntent().getStringExtra("className");

        //真正的加載 插件裏面的Activity

        // 動態獲取className , 不能寫死(包名+類名)
        // String className = null;
        try {
            // 拿到插件包中的第一個Activity
            Class pluginClass = getClassLoader().loadClass(className);

            // 實例化插件包中的Activity
            Constructor constructor = pluginClass.getConstructor(new Class[]{});
            Object pluginActivity = constructor.newInstance(new Object[]{});

            // 強轉爲ActivityInterface
            activityInterface = (ActivityInterface) pluginActivity;

            // 將宿主的環境注入給插件
            activityInterface.insertAppActivity(this);

            // 執行插件onCreate 方法
            // 可以從宿主 攜帶參數 給 插件
            Bundle bundle = new Bundle();
            bundle.putString("fromAppInfo", "我是來自宿主的一條信息");
            // 間接調用pluginActivity的onCreate方法
            activityInterface.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

插件Activity 的onCreate方法:

	// com.purang.plugin_package.PluginMainActivity#onCreate
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 在父類BaseActivity 通過 宿主環境 appActivity 來加載 Resource
        setContentView(R.layout.activity_main_plugin);

        Log.e(TAG, "onCreate: ");
        // toast 不能通過this,只能通過宿主傳遞過來的appActivity 環境
        Toast.makeText(appActivity, "我是插件", Toast.LENGTH_SHORT).show();

        // 子類複寫了父類的onCreate方法, 繼續調用onCreate
        String info = savedInstanceState.getString("fromAppInfo");
        Log.e(TAG, "子類收到宿主傳遞的信息: " + info);
    }

4. 插件Activity的生命週期

在 宿主的ProxyActivity中,通過activityInterface.onCreate(bundle);啓動pluginActivity。因爲插件的Activity實現了這一標準。所以,插件的Activity的生命週期也可以通過這種方式來加載:

/**
 * 代理/佔位Activity  用來啓動插件 Activity
 */
public class ProxyActivity extends Activity {

    private ActivityInterface activityInterface;
    @Override
    protected void onStart() {
        super.onStart();
        if (activityInterface != null)
            activityInterface.onStart();
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
	 	// ... 省略代碼
		 // 強轉爲ActivityInterface
		 activityInterface = (ActivityInterface) pluginActivity;   
		 activityInterface.onCreate(bundle);
      	// ... 省略代碼
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (activityInterface != null)
            activityInterface.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (activityInterface != null)
            activityInterface.onPause();
    }
}

插件PluginMainActivity代碼:

public class PluginMainActivity extends BaseActivity {

    private static final String TAG = PluginMainActivity.class.getSimpleName();

    @Override
    public void onStart() {
        super.onStart();
        Log.e(TAG, "onStart: ");
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 在父類BaseActivity 通過 宿主環境 appActivity 來加載 Resource
        setContentView(R.layout.activity_main_plugin);
        Log.e(TAG, "onCreate: ");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.e(TAG, "onResume:");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.e(TAG, "onPause:");
    }
}

日誌輸出:

2020-02-22 22:00:05.065 7863-7863/com.purang.plugin E/PluginMainActivity: onCreate: 
2020-02-22 22:00:05.072 7863-7863/com.purang.plugin E/PluginMainActivity: 子類收到宿主傳遞的信息: 我是來自宿主的一條信息
2020-02-22 22:00:05.074 7863-7863/com.purang.plugin E/PluginMainActivity: onStart: 
2020-02-22 22:00:05.075 7863-7863/com.purang.plugin E/PluginMainActivity: onResume:
2020-02-22 22:00:27.941 7863-7863/com.purang.plugin E/PluginMainActivity: onPause:

可以看出,先調用了onCreate來加載Activity,然後纔是onStart onResume

插件內部 Activity 加載

在插件內部實現跳轉到新的Activity實現分析:
由於是在插件內部沒有上下文環境,所以所有涉及到上下文環境的操作,都必須藉助宿主 ProxyActivity注入給插件的上下文 appActivity來實現。如:findViewById startActivity等等。Activity跳轉橋接到ProxyActivity,在ProxyActivity內部實現自己跳轉自己

查看Activity task stack

    Running activities (most recent first):
      TaskRecord{459ab29 #11615 A=com.purang.plugin U=0 StackId=1967 sz=3}
        Run #2: ActivityRecord{4528a48 u0 com.purang.plugin/.ProxyActivity t11615}
        Run #1: ActivityRecord{40d3487 u0 com.purang.plugin/.ProxyActivity t11615}
        Run #0: ActivityRecord{43aa009 u0 com.purang.plugin/.MainActivity t11615}

改變ProxyActivity launchMode,嘗試採用Activity的四種啓動模式,查看任務棧及生命週期方法。具體分析查看

Service通信

service實現思路與activity思路一致,具體查看代碼。

動態廣播的使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章