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

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

作者: 小城哇哇 | 来源:发表于2022-11-20 17:36 被阅读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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/uwuwxdtx.html