Android插件化1-Activity插件化

1 插件化概述

插件化屬於動態加載技術
動態加載技術:插件化、熱修復

動態加載技術:在應用程序運行時,動態加載一些程序中原本不存在的可執行文件並運行這些文件裏的代碼邏輯,可執行文件總的來說分爲兩個,一個是動態鏈接庫so,另一種是dex相關文件(dex文件包含jar/apk文件)。

插件化的作用:主要用於解決應用越來越龐大以及功能模塊的解耦,所以小項目中一般用的不多。

熱修復:主要用於修復bug。

業務複雜,模塊的解耦。
應用的接入。
65536限制,內存佔用大。

2 插樁式原理與實現

在宿主 App 中使用反射加載插件中的類 A, A 是沒有生命週期的,就是一個普普通通的類而已 。比如插件裏的 Activity , 就算我們“欺騙了” AMS 檢查 AndroidManifest 的過程,這個Activity 也啓動不了,類似 onResume 、 onPaused 這些生命週期函數都不能被正常調用,因爲宿主 App 根本就不把它當作 Activity 來對待 。爲此,我們在宿主 App 中設計一個代理類 ProxyActivity ,這是一個 Activity ,是宿主 App所認識的 。 讓 ProxyActivity 內部有一個對插件 ActivityA 的引用,讓ProxyActivity 的任何生命週期函數都調用 ActivityA 中同名函數。

在這裏插入圖片描述

圖:代理思想


在這裏插入圖片描述

圖:宿主與插件的關係

新建Android項目,並創建兩個Module:

  • Module1:pluginstand,是一個Android Library;
  • Module2:taopiaopiao,是一個Phone Module。

將pluginstand作爲依賴庫分別添加到app Module和taopiaopiao Module中。

在這裏插入圖片描述

因爲插件(tiaopiaopiao)只是一個apk,並沒有安裝到手機上,不具有生命週期,這就需要通過獲取宿主(app)的生命週期。

2.1 pluginstand

在pluginstand中創建一個接口PayInterfaceActivity,包含了各個生命週期的方法,如下:

public interface PayInterfaceActivity {
    void attach(Activity proxyActivity);
    void onCreate(Bundle saveInstanceState);
    void onStart();
    void onResume();
    void onPause();
    void onStop();
    void onDestroy();
    void onSaveInstanceState(Bundle outState);
    boolean onTouchEvent(MotionEvent event);
    void onBackPressed();
}

2.2 插件taopiaopiao

在taopiaopiao中創建一個基類BaseActivity實現PayInterfaceActivity接口,如下:

public class BaseActivity extends Activity implements PayInterfaceActivity {
    protected Activity that;

    @Override
    public void attach(Activity proxyActivity) {//1
        this.that = proxyActivity;
    }
    @Override
    public void setContentView(View view) {
        if(that != null){
            that.setContentView(view);
        } else {
            super.setContentView(view);
        }
    }
    @Override
    public void setContentView(int layoutResID) {
        if(that != null){
            that.setContentView(layoutResID);
        } else {
            super.setContentView(layoutResID);
        }
    }
    @Override
    public void startActivity(Intent intent) {
        Intent m  = new Intent();
        m.putExtra("className",intent.getComponent().getClassName());
        that.startActivity(m);
    }
    @Override
    public View findViewById(int id) {
        return that.findViewById(id);
    }
    @Override
    public void onCreate(Bundle saveInstanceState) {

    }
    @Override
    public void onStart() {

    }
    @Override
    public void onResume() {

    }
    @Override
    public void onPause() {

    }
    @Override
    public void onStop() {

    }
    @Override
    public void onDestroy() {

    }
    @Override
    public void onSaveInstanceState(Bundle outState) {

    }
}

註釋1:通過attach方法,將宿主(app)的ProxyActivity傳入


插件taopiaopiao中的主頁面TaoMainActivity,如下:

public class TaoMainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {//1
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//2
        findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {//3
            @Override
            public void onClick(View v) {
                Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
                startActivity(new Intent(that, SceondActivity.class));//4
            }
        });
    }
}

註釋1:覆寫了BaseActivity中的onCreate方法。
註釋2:調用BaseActivity的setContentView方法。
註釋3:調用BaseActivity的findViewById方法。
註釋4:調用BaseActivity的startActivity方法實現在插件中跳轉到另一個頁面。

2.3 宿主 app

在MainActivity有兩個按鈕,一個用於加載插件,一個用於跳轉到插件的頁面

public void load(View view) {
        PluginManager.getInstance().loadPath(this);
    }
  public void click(View view) {
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }

下面是具體實現。

2.3.1 獲取插件

將taopiaopiao中生成的apk拷貝到data/data/com.hongx.plugin/app_plugin

在這裏插入圖片描述

創建PluginManager用於加載apk,並獲取Resources、DexClassLoader和PackageInfo,如下:

public class PluginManager {
    
    private static final PluginManager ourInstance = new PluginManager();
    private DexClassLoader dexClassLoader;
    private Resources resources;
    private PackageInfo packageInfo;
    
    public static PluginManager getInstance(){
        return ourInstance;
    }

    public PluginManager() {
    }
    
    public void loadPath(Context context){
        File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
        String name = "plugin.apk";
        String path = new File(filesDir, name).getAbsolutePath();

        PackageManager packageManager = context.getPackageManager();
        packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);

        //activity
        ///data/data/com.hongx.plugin/app_dex/plugin.dex
        File dex = context.getDir("dex", Context.MODE_PRIVATE);
        dexClassLoader = new DexClassLoader(path, dex.getAbsolutePath(), null, context.getClassLoader());

        //resource
        try {
            AssetManager manager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            //path = /data/data/com.hongx.plugin/app_plugin/plugin.apk
            addAssetPath.invoke(manager, path);
            resources = new Resources(manager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Resources getResources(){
        return  resources;
    }

    public DexClassLoader getDexClassLoader(){
        return dexClassLoader;
    }

    public PackageInfo getPackageInfo(){
        return packageInfo;
    }
}

2.3.2 創建ProxyActivity

public class ProxyActivity extends Activity {
    //需要加載插件的全類名
    private String className;
    PayInterfaceActivity payInterfaceActivity;

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

        try {
            //TaoMainActivity
            Class<?> aClass = getClassLoader().loadClass(className);
            Constructor constructor = aClass.getConstructor(new Class[]{});
            Object in = constructor.newInstance(new Object[]{});//2
            payInterfaceActivity = (PayInterfaceActivity) in;
            payInterfaceActivity.attach(this);//3

            //如果需要參數,可以使用Bundle
            Bundle bundle = new Bundle();
            payInterfaceActivity.onCreate(bundle);//4
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }

    //重寫加載類
    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getDexClassLoader();
    }

    //重寫加載資源
    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }

    @Override
    protected void onStart() {
        super.onStart();
        payInterfaceActivity.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        payInterfaceActivity.onResume();
    }

    @Override
    protected void onStop() {
        super.onStop();
        payInterfaceActivity.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        payInterfaceActivity.onDestroy();
    }
}

註釋1:在上面已經提到點擊頁面跳轉按鈕跳轉至插件的TaoMainActivity。PluginManager.getInstance().getPackageInfo()得到的是插件的包信息,activities[0]對應的就是ToMainActivity,也就是獲取到ToMainActivity的類名並將類名傳遞到ProxyActivity中以供使用。

註釋2:通過類加載器和反射機制獲取到了TaoMainActivity對象。

註釋3:將ProxyActivity傳遞到TaoMainActivity中。

註釋4:調用了TaoMainActivity的onCreate方法。


再來分析下TaoMainActivity:

public class TaoMainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//1
        findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {//2
            @Override
            public void onClick(View v) {
                Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
                startActivity(new Intent(that, SceondActivity.class));
            }
        });
    }
}
  • 註釋1:調用的是BaseActivity中的setContentView方法,其實最終調用的是ProxyActivity的setContentView方法,代碼如下:
  @Override
    public void setContentView(int layoutResID) {
        if(that != null){
            that.setContentView(layoutResID);
        } else {
            super.setContentView(layoutResID);
        }
    }
  • 註釋2:跳轉到taopiaopiao的SceondActivity

BaseActivity的startActivity方法如下:

    @Override
    public void startActivity(Intent intent) {
        Intent m  = new Intent();
        m.putExtra("className",intent.getComponent().getClassName());
        that.startActivity(m);
    }

最後還是使用了ProxyActivity的startActivity方法,如下:

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }

className就是SecondActivity的類名,流程和加載TaoMainActivity一樣,也是將類名傳遞到ProxyActivity中。

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