Android外掛化的一種方案

毕哥發表於2024-05-25

思路:開啟外掛包裡邊的activity時 統一都用 一個ProxyActivity作為代理

1、首先宿主APP提供ProxyActivity,當宿主需要開啟外掛包中的Activity時,一律是啟動的ProxyActivity,

在啟動ProxyActivity的intent中攜帶我們真正需要去開啟的外掛包中Activity的類全名。

public class ProxyActivity extends Activity {
    private IPluginActivity mPluginActivity;//用來接收外掛包所有Activity的介面物件

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String className = getIntent().getStringExtra("className");
        try {
            Class activityClass = getClassLoader().loadClass(className);
            Constructor constructor = activityClass.getConstructor(new Class[]{});
            Object instance = constructor.newInstance(new Object[]{});
            mPluginActivity = (PluginBaseActivity) instance;
            mPluginActivity.onCreate(savedInstanceState);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

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

    @Override
    protected void onPause() {
        super.onPause();
        mPluginActivity.onPause();
    }

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

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

  

在Activity中有很重要的兩個回撥方法:

@Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getPluginBean().getDexClassLoader();
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getPluginBean().getResources();
    }

  

  • getClassLoader返回的是ClassLoader 物件,Activity內部在使用反射new物件時,都會去使用這裡返回的ClassLoader 來進行反射,所以我們ProxyActivity中需要提供的應該是當前載入外掛包對應的ClassLoader 。
  • getResources返回的是Resources 物件,Activity內部在使用資原始檔時,都會去使用這裡返回的Resources 來獲取資源,所以我們ProxyActivity中需要提供的應該是當前載入外掛包對應的Resources 。
  我們宿主APP提供了一個ProxyActivity,我們開啟外掛包Activity其實都是開啟的ProxyActivity,只不過ProxyActivity不做任何其他事情,只負責例項化外掛包Activity,並且將ProxyActivity中的所有事  件驅動通知給外掛包Activity,也就是ProxyActivity呼叫了外掛包Activity中的程式碼來實現外掛包要實現的功能。
獲得classLoader和resource:
            File pluginFile;//外掛包檔案
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginFile.getAbsolutePath(), PackageManager.GET_ACTIVITIES);

            File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
            DexClassLoader dexClassLoader = new DexClassLoader(pluginFile.getAbsolutePath(), dexOutFile.getAbsolutePath()
                    , null, context.getClassLoader());

            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, pluginFile.getAbsolutePath());
            Resources resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

  

PackageInfo 可以獲取外掛包manifest中所有註冊了的元件資訊,例如主Activity的全類名的獲取為:

packageInfo.activities[0].name

  

下面是完整的

public abstract class PluginBaseActivity extends AppCompatActivity implements IPluginActivity {

    protected Activity that;//宿主Activity

    @Override
    public void attach(ProxyActivity proxyActivity) {
        if (that != null)
            throw new RuntimeException("Plugin's activity already has been attached!");
        this.that = proxyActivity;
        attachBaseContext(proxyActivity);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        if (that == null) {
            super.onCreate(savedInstanceState);
        }
    }

    @Override
    public void onStart() {
        if (that == null) {
            super.onStart();
        }
    }

    @Override
    public void onResume() {
        if (that == null) {
            super.onResume();
        }
    }

    @Override
    public void onPause() {
        if (that == null) {
            super.onPause();
        }
    }

    @Override
    public void onStop() {
        if (that == null) {
            super.onStop();
        }
    }

    @Override
    public void onDestroy() {
        if (that == null) {
            super.onDestroy();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (that == null) {
            super.onSaveInstanceState(outState);
        }
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        if (that == null) {
            super.onRestoreInstanceState(savedInstanceState);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (that == null) {
            return super.onTouchEvent(event);
        }
        return false;
    }

    @Override
    public void onBackPressed() {
        if (that == null) {
            super.onBackPressed();
        }
    }

    @Override
    public void setContentView(View view) {
        if (that == null) {
            super.setContentView(view);
        } else {
            that.setContentView(view);
        }
    }

    @Override
    public void setContentView(int layoutResID) {
        if (that == null) {
            super.setContentView(layoutResID);
        } else {
            that.setContentView(layoutResID);
        }
    }

    @Override
    public void startActivity(Intent intent) {
        if (that == null) {
            super.startActivity(intent);
        } else {//篡改intent開啟頁面為proxyActivity
            intent.putExtra("className", intent.getComponent().getClassName());
            intent.setClassName(intent.getComponent().getPackageName(), ProxyActivity.class.getName());
            that.startActivity(intent);
        }
    }

    @Override
    public ComponentName startService(Intent intent) {
        if (that == null) {
            return super.startService(intent);
        } else {
            intent.putExtra("className", intent.getComponent().getClassName());
            intent.setClassName(intent.getComponent().getPackageName(), ProxyService.class.getName());
            return that.startService(intent);
        }
    }

    @Override
    public View findViewById(int id) {
        if (that == null) {
            return super.findViewById(id);
        } else {
            return that.findViewById(id);
        }
    }


    @Override
    public Intent getIntent() {
        if (that == null) {
            return super.getIntent();
        } else {
            return that.getIntent();
        }
    }


    @Override
    public Window getWindow() {
        if (that == null) {
            return super.getWindow();
        } else {
            return that.getWindow();
        }
    }

    @Override
    public WindowManager getWindowManager() {
        if (that == null) {
            return super.getWindowManager();
        } else {
            return that.getWindowManager();
        }
    }
}

  

public interface IPluginActivity {
    void attach(ProxyActivity proxyActivity);

    void onCreate(@Nullable Bundle savedInstanceState);

    void onStart();


    void onResume();

    void onPause();

    void onStop();

    void onDestroy();

    void onSaveInstanceState(Bundle outState);

    void onRestoreInstanceState(Bundle savedInstanceState);

    boolean onTouchEvent(MotionEvent event);

    void onBackPressed();

    void setContentView(View view);

    void setContentView(int layoutResID);

    void startActivity(Intent intent);

    ComponentName startService(Intent intent);

    View findViewById(int id);


    Intent getIntent();


    Window getWindow();

    WindowManager getWindowManager();
}

  以上步驟實則每次新開啟的外掛裡的頁面其實都是ProxyActivity 做的代理

相關文章