占位式插件化(二):怎样在宿主中启动插件中的组件

2022-11-20  本文已影响0人  小城哇哇

怎样在宿主中启动插件中的组件

首先,我们能够调用插件中的组件,得益于Java中提供的反射机制与类型强转机制。由于我们不能拿到插件中类的真实类型,想要调用到插件中的组件,需要制定一个标准,只有所有插件都实现了 此标准,才能够在宿主App中调用插件中的功能。那这里所谓的标准,就是Java中的接口。制定一套接口,让所有插件和宿主都依赖该接口,就可以在宿主内通过反射获取到插件内部的组件,然后强转为实现的Interface类型,这样就可以进行通信了。
创建一个项目,包含3个module,其中一个为插件化标准:standard,另外两个分别为宿主module:app以及插件module人:plugin_package,二者都需要依赖于standard。


制定Activity的标准-ActivityInterface

package com.oliver.standard;

public interface ActivityInterface {

    /**
     * 把宿主(App)的环境给插件
     *
     * @param appActivity 宿主Activity
     */
    void insertAppContext(Activity appActivity);

    void onActivityCreate(Bundle savedInstanceState);

    void onActivityStart();

    void onActivityResume();

    void onActivityDestroy();

    void onActivityPause();

    void onActivityStop();

    void onActivityRestart();

    void onActivityNewIntent(Intent intent);
}

在plugin_package中添加BaseActivity,并实现ActivityInterface

package com.oliver.plugin_package;

public class BaseActivity extends Activity implements ActivityInterface {


    // 宿主的Activity环境
    public Activity appActivity;

    @Override
    public void insertAppContext(Activity appActivity) {
        this.appActivity = appActivity;
    }

    @Override
    public void onActivityCreate(Bundle savedInstanceState) {

    }

    @Override
    public void onActivityStart() {

    }

    @Override
    public void onActivityResume() {

    }

    @Override
    public void onActivityDestroy() {

    }

    @Override
    public void onActivityPause() {
        
    }

    @Override
    public void onActivityStop() {

    }

    @Override
    public void onActivityRestart() {

    }

    @Override
    public void onActivityNewIntent(Intent intent) {

    }
    
    // 重写findViewByID方法,因为该方法会涉及到Activity对应的环境。由于这里的Activity是插件中的,故而没有
    // 使用环境,因此通过宿主appActivity来调用。在回调到AppActivity的时候,就可以通过PluginManager获取到
    // DexClassLoader和插件Resources,用来加载classes文件和资源
    // 同理,和使用环境挂钩的都需要重写,使用宿主AppActivity来替代
    @Override
    public <T extends View> T findViewById(int id) {
        return appActivity.findViewById(id);
    }
    
    @Override
    public void setContentView(int res) {
        appActivity.setContentView(res);
    }

    public void  startActivity(Class<? extends Activity> clazz){
        Intent intent = new Intent();
        intent.putExtra(Const.COMPONENT_NAME,clazz.getName());
        appActivity.startActivity(intent);
    }
    
    @Override
    public ComponentName startService(Intent service) {
        Intent intent = new Intent();
        intent.putExtra(Const.COMPONENT_NAME,service.getComponent().getClassName());
        return appActivity.startService(intent);
    }

}
@Nullable
    public <T extends View> T findViewById(@IdRes int id) {
        // 一个没有启动过的Activity是没有环境的,故而也不存在window
        return getWindow().findViewById(id);
    }

创建PluginActivity并继承BaseActivity

public class PluginActivity extends BaseActivity {

    @Override
    public void onActivityCreate(Bundle savedInstanceState) {
        super.onActivityCreate(savedInstanceState);
        // 调用父类
        // 该布局文件中定义了两个按钮,分别用来启动插件内部的Activity和Service
        setContentView(R.layout.activity_plugin);
        // 这里的上下文不能使用this,同样的道理,当前Activity没有使用环境
        // 注意,凡是在插件中使用到上下文的都应该使用宿主传过来的AppActivity
        Toast.makeText(appActivity, "这是插件的吐司", Toast.LENGTH_SHORT).show();
        
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(TestActivity.class);
            }
        });
        findViewById(R.id.btn_start_service).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(new Intent(appActivity, TestService.class));
            }
        });
    }
}

在宿主module中启动PluginActivity

● 初始化PluginManager

package com.oliver.plugin;

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        PluginManager.initContext(this);
    }
}

ProxyActivity

创建一个代理Activity,称之为ProxyActivity。如果我们直接在宿主中调用startActivity启动PluginActivity,肯定是会报错的。因为最起码PluginActivity没有在宿主App中的manifest.xml文件中进行注册。所以我们需要一个ProxyActivity,通过ProxyActivity重写getClassLoader()和getResources()方法,在各个声明周期方法回调的时候通过制定的标准去通知PluginActivity,这样就可以将PluginActivity的生命周期方法正常调用。由于ProxyActivity不做别的事,仅仅是一个用来给PluginActivity进行占位使用的,故而该类方式又称之为占位式插件化。

public class ProxyActivity extends Activity {

    private static final String TAG = "ProxyActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 该参数来自MainActivity中,表示PluginActivity的全类型定名
        String className = getIntent().getStringExtra(Const.COMPONENT_NAME);
        try {
            // 加载插件中的PluginActivity
            Class<?> pluginActivityClass = getClassLoader().loadClass(className);

            // 实例化插件包里面的Activity
            Constructor<?> constructor = pluginActivityClass.getConstructor(new Class[]{});
            Object instance = constructor.newInstance(new Object[]{});
            ActivityInterface activityInterface = (ActivityInterface) instance;
            // 给插件注入宿主环境
            activityInterface.insertAppContext(this);

            Bundle bundle = new Bundle();
            bundle.putString("appName", "宿主传递过来的信息");
            // 执行插件里面的onActivityCreate()
            activityInterface.onActivityCreate(bundle);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

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

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
    }

    /**
     * 插件内部启动Activity
     */
    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra(Const.COMPONENT_NAME);
        // 要给TestActivity进栈,相当于使用ProxyActivity去加载TestActivity的布局,然后ProxyActivity占据的
        // 回退栈的位置,实际上应该是TestActivity所在,只不过其功能全部由ProxyActivity代理了而已
        Intent proxyIntent = new Intent(this, ProxyActivity.class);
        proxyIntent.putExtra(Const.COMPONENT_NAME, className);
        // 这里需要使用super调用,否则会造成死递归
        super.startActivity(proxyIntent);
    }

    @Override
    public ComponentName startService(Intent service) {
        String className = service.getStringExtra(Const.COMPONENT_NAME);
        // 要给TestActivity进栈
        Intent proxyIntent = new Intent(this, ProxyService.class);
        proxyIntent.putExtra(Const.COMPONENT_NAME, className);
        return super.startService(proxyIntent);
    }

    /**
     * 重写该方法,表示加载的资源是插件中的资源
     */
    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }

    /**
     * 重写该方法,表示加载的classes是插件中的类
     */
    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getClassLoader();
    }
}

注意:ProxyActivity只能继承至Activity,如果继承AppCompatActivity的话,就会报错。原因是调用以下代码段的时候,R.id.decor_content_parent在宿主和插件中的值不一致导致。经过Debug发现,调用到这里的时候,使用的是插件中的id值,导致找不到DecorContentParent,接下来就直接报NPE。
报错代码段:

// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext).inflate(R.layout.abc_screen_toolbar, null);

mDecorContentParent = (DecorContentParent) subDecor.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());

报错堆栈信息:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.oliver.plugin, PID: 8909
    java.lang.NullPointerException: Attempt to invoke interface method 'void androidx.appcompat.widget.DecorContentParent.setWindowCallback(android.view.Window$Callback)' on a null object reference
        at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:900)
        at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:806)
        at androidx.appcompat.app.AppCompatDelegateImpl.onPostCreate(AppCompatDelegateImpl.java:527)
        at androidx.appcompat.app.AppCompatActivity.onPostCreate(AppCompatActivity.java:127)
        at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1343)

MainActivity中通过按钮点击事件启动插件中的PluginActivity

package com.oliver.plugin;

    // 启动插件中的Activity
    public void startPlugin(View view) {
        // 插件包
        String dexPath = PluginManager.getInstance().getDexPath();
        if (TextUtils.isEmpty(dexPath)) {
            Log.e(TAG, "startPlugin: 插件文件不存在");
        }
        // 获取插件包里面的Activity
        PackageManager packageManager = getPackageManager();
        // 获取包名对应的应用的PackageInfo,通过该对象可以获取到应用的诸多信息,例如,所有的组件等
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
        // 获取manifest.xml 第一个注册的Activity,也就是PluginActivity
        ActivityInfo activityInfo = packageInfo.activities[0];

        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra(Const.COMPONENT_NAME,activityInfo.name);
        startActivity(intent);
    }

经过以上步骤的编写,我们就可以通过宿主启动插件的Activity组件了。

来自:https://www.yuque.com/bujianlbusan/lqku3r/gcg09k#ln5mH

上一篇下一篇

猜你喜欢

热点阅读