美文网首页Android开发Android插件化技术Android开发
Android插件化技术之旅 1 开篇 - 实现启动插件与调用插

Android插件化技术之旅 1 开篇 - 实现启动插件与调用插

作者: 289346a467da | 来源:发表于2018-12-18 11:25 被阅读14次

    前言

    Android技术如今已很成熟了,组件化、插件化、热修复等等框架层出不穷,如果只停留在单纯的会用框架上,技术永远得不到成长,只有懂得其原理,能够婉婉道来,能够自己手动写出,技术才会得到成长,与其焦虑未来,不如把握现在。本篇将手写教大家写出插件化框架,插件化技术是Android高级工程师必备的技术之一,懂其思想,知其原理。本篇专题将由10篇文章来详细的讲解插件化技术,深耕一个技术领域,才能懂得如何更广阔的横向发展。

    本专题代码地址

    什么是插件化?

    插件化技术的起源于免安装运行APK的想法,这个免安装的APK就是一个插件,支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展。

    在过去几年有一款叫23code的应用,不知大家是否知道,这款应用大火在于,它可以直接下载demo并运行起来,这可以说是最早的插件化应用。

    现在并没有什么新的插件化项目了,比较流行的框架有如下:

    第一代:dynamic-load-apk最早使用ProxyActivity这种静态代理技术,由ProxyActivity去控制插件中PluginActivity的生命周期。该种方式缺点明显,插件中的activity必须继承PluginActivity,开发时要小心处理context。而DroidPlugin通过Hook系统服务的方式启动插件中的Activity,使得开发插件的过程和开发普通的app没有什么区别,但是由于hook过多系统服务,异常复杂且不够稳定。

    第二代:为了同时达到插件开发的低侵入性(像开发普通app一样开发插件)和框架的稳定性,在实现原理上都是趋近于选择尽量少的hook,并通过在manifest中预埋一些组件实现对四大组件的插件化。另外各个框架根据其设计思想都做了不同程度的扩展,其中Small更是做成了一个跨平台,组件化的开发框架。

    第三代:VirtualApp比较厉害,能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术。Atlas是阿里今年开源出来的一个结合组件化和热修复技术的一个app基础框架,其广泛的应用与阿里系的各个app,其号称是一个容器化框架。

    插件化的未来:去年比较火的ReactNative,尽管它不可能成为最终方案,但是移动应用Web化是一个必然的趋势,就好像曾经的桌面应用由C/S到B/S的转变。从Google今年推崇的Flutter,移动应用越来越趋向于Web化。

    插件化与组件化的区别?

    至于什么是组件化,大家可以看我写的关于组件化的专题

    组件化是将各个模块分成独立的组件,每个组件都要打进apk包中。

    而插件化各个组件都可以单独打成apk包,在需要时下载apk包,并加载运行。

    插件化原理

    1 设计接纳标准,符合这个标准的app都能接入进来,该标准跟生命周期相关。

    2 设计好插件遵循这个标准,由于插件没有安装(上下文均不可用),需要给插件传递一个上下文。

    3 用一个空壳Activity 放入插件内容。

    4 反射得到Apk里面MainActivity的全类名。

    5 加载

    image.png

    从上图可以看出,启动一个差价,核心代码就是ProxyActivity和PluginInterfaceActivity.下面我们着重讲解核心思想的代码。

    代码其实很简单,启动插件的过程:

    1. 首先需要一个空壳的ProxyActivity来启动插件的Activity。
    2. ProxyActivity根据插件apk包的信息,拿到插件中的ClassLoader和Resource,然后通过反射并创建MainActivity 转换为PluginInterfaceActivity,并调用其onCreate方法。
    3. 插件调用的setContentView被重写了,会去调用ProxyActivity的setContentView,由于ProxyActivity的getClassLoader和gerResource被重写是插件中的resource,所以ProxyActivity的setContentView能够访问插件中的资源,findViewById同样也是调用ProxyActivity的。
    4. ProxyActivity中的其他生命周期回调函数中调用相应PluginActivity的生命周期。
    image.png

    直接上代码

    下面代码定义了插件Activity必须要实现的一个接口,也可说定义的一个标准,由于插件并没有安装到手机上,无法拿到上下文,生命周期自然也无法调用,我们需要将宿主的一个空壳ProxyActivity,将生命周期传递给插件。插件需要用到上下文的方法都需要重写。

    public interface PluginInterfaceActivity {
    
        /**
         * 上下文的传递 通过上下文来启动Activity
         *
         * @param activity
         */
        void attach(Activity activity);
    
        //---------------------- 生命周期 传递到插件中 -------------------------//
    
        void onCreate(@Nullable Bundle savedInstanceState);
    
        void onStart();
    
        void onResume();
    
        void onPause();
    
        void onStop();
    
        void onRestart();
    
        void onDestroy();
    
        void onSaveInstanceState(Bundle outState);
    
        boolean onTouchEvent(MotionEvent event);
    
        void onBackPressed();
    }
    

    重写必须要用到的方法,插件的Activity都需要继承BaseActivity

    public class BaseActivity extends AppCompatActivity implements PluginInterfaceActivity {
    
        protected Activity mActivity;
    
        @Override
        public void attach(Activity proxyActivity) {
            this.mActivity = proxyActivity;
        }
    
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
    
        }
    
        @Override
        public void onStart() {
    
        }
    
        @Override
        public void onResume() {
    
        }
    
        @Override
        public void onPause() {
    
        }
    
        @Override
        public void onStop() {
    
        }
    
        @Override
        public void onRestart() {
    
        }
    
        @Override
        public void onDestroy() {
    
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
    
        }
    
        //--------------------------凡事用到上下文的地方都需要重写,需要重写的方法-----------------------------//
    
        /**
         * 这里我们要使用宿主传递过来的上下文进行加载view
         *
         * @param view
         */
        @Override
        public void setContentView(View view) {
            if (mActivity != null) {
                mActivity.setContentView(view);
            } else {
                super.setContentView(view);
            }
        }
    
        @Override
        public void setContentView(int layoutResID) {
            if (mActivity != null) {
                mActivity.setContentView(layoutResID);
            } else {
                super.setContentView(layoutResID);
            }
        }
    
        @Override
        public <T extends View> T findViewById(int id) {
            if (mActivity != null) {
                return mActivity.findViewById(id);
            } else {
                return super.findViewById(id);
            }
        }
    
        @Override
        public Intent getIntent() {
            if (mActivity != null) {
                return mActivity.getIntent();
            }
            return super.getIntent();
        }
    
        @Override
        public ClassLoader getClassLoader() {
            if (mActivity != null) {
                return mActivity.getClassLoader();
            }
            return super.getClassLoader();
        }
    
        @Override
        public LayoutInflater getLayoutInflater() {
            if (mActivity != null) {
                return mActivity.getLayoutInflater();
            }
            return super.getLayoutInflater();
        }
    
        @Override
        public ApplicationInfo getApplicationInfo() {
            if (mActivity != null) {
                return mActivity.getApplicationInfo();
            }
            return super.getApplicationInfo();
        }
    
        @Override
        public Window getWindow() {
            return mActivity.getWindow();
        }
    
    
        @Override
        public WindowManager getWindowManager() {
            return mActivity.getWindowManager();
        }
    
        @Override
        public void startActivity(Intent intent) {
            if (mActivity != null) {
                //插件的launchMode 只能够是标准的
                Intent intent1 = new Intent();
                intent1.putExtra("className", intent.getComponent().getClassName());
                mActivity.startActivity(intent1);
            } else {
                super.startActivity(intent);
            }
        }
    
        @Override
        public ComponentName startService(Intent service) {
            if (mActivity != null) {
                Intent m = new Intent();
                m.putExtra("serviceName", service.getComponent().getClassName());
                return mActivity.startService(m);
            } else {
                return super.startService(service);
            }
        }
    }
    

    下面来看宿主是如何启动插件的呢?
    通过一个空壳的Activity
    ProxyActivity代理的方式最早是由dynamic-load-apk提出的,其思想很简单,在主工程中放一个ProxyActivy,启动插件中的Activity时会先启动ProxyActivity,在ProxyActivity中创建插件Activity,并同步生命周期。

    public class ProxyActivity extends AppCompatActivity {
    
        //需要加载插件的全类名
        private String className;
    
        private PluginInterfaceActivity pluginInterfaceActivity;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            className = getIntent().getStringExtra("className");
    //        Class.forName()
            //插件并没有被安装到手机上的
            try {
                //className 代表activity的全类名
                Class activityClass = getClassLoader().loadClass(className);
                //调用构造函数
                Constructor constructors = activityClass.getConstructor(new Class[]{});
                //得到Activity对象
                Object newInstance = constructors.newInstance(new Object[]{});
                //最好不要反射 onCreate()
                //通过标准来
                pluginInterfaceActivity = (PluginInterfaceActivity) newInstance;
                //注入上下文
                pluginInterfaceActivity.attach(this);
                Bundle bundle = new Bundle();//将一些信息传递
                bundle.putString("test", "我是宿主给你传递数据");
                pluginInterfaceActivity.onCreate(bundle);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException 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 ComponentName startService(Intent service) {
            String serviceName = service.getStringExtra("serviceName");
            Intent intent = new Intent(this, ProxyService.class);
            intent.putExtra("serviceName", serviceName);
            return super.startService(intent);
        }
    
        //对外
        @Override
        public ClassLoader getClassLoader() {
            return PluginManager.getInstance().getClassLoader();
        }
    
        @Override
        public Resources getResources() {
            return PluginManager.getInstance().getResources();
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            pluginInterfaceActivity.onStart();
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            pluginInterfaceActivity.onResume();
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            pluginInterfaceActivity.onPause();
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            pluginInterfaceActivity.onStop();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            pluginInterfaceActivity.onDestroy();
        }
    }
    

    启动插件的代码如下:

    public void loadPlugin(View view) {
            loadDownPlugin();
            Intent intent = new Intent(this, ProxyActivity.class);
            //得到全类名
            intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
            startActivity(intent);
        }
    

    其实,启动插件就是启动宿主的一个空壳的Activity,将这个空壳的Activity的上文和生命周期传递到插件的Activity。

    这便是启动插件的核心代码:

     //className 代表activity的全类名
                Class activityClass = getClassLoader().loadClass(className);
                //调用构造函数
                Constructor constructors = activityClass.getConstructor(new Class[]{});
                //得到Activity对象
                Object newInstance = constructors.newInstance(new Object[]{});
                //最好不要反射 onCreate()
                //通过标准来
                pluginInterfaceActivity = (PluginInterfaceActivity) newInstance;
                //注入上下文
                pluginInterfaceActivity.attach(this);
                Bundle bundle = new Bundle();//将一些信息传递
                bundle.putString("test", "我是宿主给你传递数据");
                pluginInterfaceActivity.onCreate(bundle);
    

    其实我们就是启动了宿主中的一个空壳Activity,然后加载插件APK包中的资源,并将生命周期传递,那么下面我们思考一个问题:

    插件中的MainActivity调用插件中的OtherActivity,是如何调用的呢?startActivity需不需要重写?

    答案是肯定的,启用插件中的其他Activity,其实就是重新创建一个新的空壳的Activity。

       //重写插件中的startActivity 将要启动的Activity的全类名传递给ProxyActivity
       @Override
        public void startActivity(Intent intent) {
            if (mActivity != null) {
                //插件的launchMode 只能够是标准的
                Intent intent1 = new Intent();
                intent1.putExtra("className", intent.getComponent().getClassName());
                mActivity.startActivity(intent1);
            } else {
                super.startActivity(intent);
            }
        }
        
        //重写ProxyActivity的startActivity,跳转一个新的ProxyActivity,并跳转Activity的全类名传递过去,相当于重走了一边上面的过程
        @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);
        }
    

    ProxyActivity代理方式主要注意两点:

    • ProxyActivity中需要重写getResouces,getAssets,getClassLoader方法返回插件的相应对象。生命周期函数以及和用户交互相关函数,如onResume,onStop,onBackPressedon,KeyUponWindow,FocusChanged等需要转发给插件。
    • PluginActivity中所有调用context的相关的方法,如setContentView,getLayoutInflater,getSystemService等都需要调用ProxyActivity的相应方法。

    调用插件中的Service

    通过上述的讲解,我们知道了调用插件中的Activity,其实就是在宿主中创建一个空壳的Acitvity,然后加载插件中的资源,传递上下文。

    那么调用插件中的Service呢?原理是一样的,原理是一样的还是在宿主中创建一个空壳的Service ProxyService,ProxyService 将生命周期传递给插件中的Service

    自己可以去实现一下,这里我只把核心代码给出

    public class ProxyService extends Service {
        private String serviceName;
    
        private PluginInterfaceService pluginInterfaceService;
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        private void init(Intent intent) {
            //开启某个服务的全类名
            serviceName = intent.getStringExtra("serviceName");
            //生成一个class
            DexClassLoader classLoader = PluginManager.getInstance().getClassLoader();
            try {
                Class<?> aClass = classLoader.loadClass(serviceName);
                Constructor<?> constructor = aClass.getConstructor(new Class[]{});
                //获取某个service对象
                Object newInstance = constructor.newInstance(new Object[]{});
                pluginInterfaceService = (PluginInterfaceService) newInstance;
                pluginInterfaceService.attach(this);
                Bundle bundle = new Bundle();
                pluginInterfaceService.onCreate();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (pluginInterfaceService == null) {
                init(intent);
            }
            return pluginInterfaceService.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
            pluginInterfaceService.onStart(intent, startId);
        }
    
        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            pluginInterfaceService.onConfigurationChanged(newConfig);
        }
    
        @Override
        public void onLowMemory() {
            super.onLowMemory();
            pluginInterfaceService.onLowMemory();
        }
    
        @Override
        public void onTrimMemory(int level) {
            super.onTrimMemory(level);
            pluginInterfaceService.onTrimMemory(level);
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            pluginInterfaceService.onUnbind(intent);
            return super.onUnbind(intent);
        }
    
        @Override
        public void onRebind(Intent intent) {
            super.onRebind(intent);
            pluginInterfaceService.onRebind(intent);
        }
    
        @Override
        public void onTaskRemoved(Intent rootIntent) {
            super.onTaskRemoved(rootIntent);
            pluginInterfaceService.onTaskRemoved(rootIntent);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            pluginInterfaceService.onDestroy();
        }
    }
    
    

    需要注意的是:ProxyActivity和ProxyService一定要在宿主的 Manifest 中注册。

    OK,这编文章的讲解是插件化最初的时候出现的设计原理,接下来会一步步深入讲解。

    相关文章

      网友评论

        本文标题:Android插件化技术之旅 1 开篇 - 实现启动插件与调用插

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