Android插件化(一)

作者: 小窦子 | 来源:发表于2019-07-28 00:21 被阅读0次

    插件化(一)

    组件化和插件化的区别:

    • 组件化是将一个app分成多个模块,每个模块都是一个组件(module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件,但是最终发布的时候将这些组件合并成一个统一的apk,这就是组件化开发。
    • 插件化开发和组件化不同,插件化开发就是将整个app拆分成很多模块,每个模块都是一个apk(组件化的每个模块是一个lib),最终打包的时候将宿主apk和插件apk分开打包,插件apk通过动态下发到宿主apk,这就是插件化。

    插件化的好处:

    • 宿主和插件分开编译
    • 是可以并发开发的。宿主和插件说白了就是apk,开发是互不影响的(只需要宿主给插件一个上下文)。
    • 动态更新插件,不需要安装,下载之后就接就可以打开,
    • 按需下载模块
    • 可以解决方法树的爆棚问题65535

    插件化的标准

    是不是每个app都能运行在我们的宿主app里面呢?

    • 肯定不是每个app都能运行在我们的宿主app里面的,我们必须在宿主app里面设计一套标准,让插件app满足我们这个标准才能够运行。最主要的是插件app因为没有安装,所以是没有上下文的,就需要我们的宿主将上下文传过去
    • 插件化的activity也没有生命周期,所以我们的宿主也要对插件的生命周期进行管理,以及资源、layout等。

    所以主要是设计一套标准,让插件app能够像安装的那样真正的运行起来

    新建model 接口 以及activity 最后让宿主activity和插件都去实现。

    通过插桩式来实现加载插件

    下图是我简单实现的一个demo 第一个页面是宿主app的 后面试插件的页面

    plugin.gif

    我们先看一下工程的结构:

    1564241263256.png

    其实标准里面实际上就是一个接口:

    public interface ProxyActivityInterface {
    
        //生命周期的activity
    
        public void attach(Activity proxyActivity);
    
    
        public void onCreate(Bundle savedInstanceState);
    
        public void onStart();
    
        public void onResume();
    
        public void onPause();
    
        public void onStop();
    
        public void onDestroy();
    
        public void onSaveInstanceState(Bundle outState);
    
        public boolean onTouchEvent(MotionEvent event);
    
        public void onBackPressed();
    }
    

    主要就是生命周期方法,我们定义了这些, 到时候宿主app肯定是拿到插件的对象 来调用的这些方法,从而维护了插件的生命周期

    再看看我们的插件app是怎么样使用这个标准的,要接入我们的肯定首先 有一个BaseActivity 来实现我们的接口

    // 这是插件的基类,所有的activity都要继承这个类,
    public class BaseActivity extends Activity implements ProxyActivityInterface {
        public Activity that;//这里的that 指的是我们的宿主app,因为插件是没有安装的 是没有上下文的
    
        @Override
        public void attach(Activity proxyActivity) {
            that = proxyActivity;
        }
    
        @Override
        public void setContentView(View view) {//最终调用宿主的activity
            if (that != null) {
                that.setContentView(view);
            } else {
                super.setContentView(view);
            }
        }
    
        @Override
        public void setContentView(int layoutResID) {
            that.setContentView(layoutResID);
        }
    
        @Override
        public View findViewById(int id) {
            return that.findViewById(id);
        }
    
        @Override
        public Intent getIntent() {
            if (that != null) {
                return that.getIntent();
            }
            return super.getIntent();
        }
    
        @Override
        public ClassLoader getClassLoader() {
            return that.getClassLoader();
        }
    
        @NonNull
        @Override
        public LayoutInflater getLayoutInflater() {
            return that.getLayoutInflater();
        }
    
        @Override
        public ApplicationInfo getApplicationInfo() {
            return that.getApplicationInfo();
        }
    
        @Override
        public Window getWindow() {
            return that.getWindow();
        }
    
    
        @Override
        public WindowManager getWindowManager() {
            return that.getWindowManager();
        }
    
        @Override
        public void startActivity(Intent intent) {
    //        ProxyActivity --->className
            Intent m = new Intent();
            m.putExtra("ClassName", intent.getComponent().getClassName());
            that.startActivity(m);
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
        }
    
        @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) {
    
        }
    

    //从上面代码看出,插件里面的页面只要是和上下文有关的操作,全部都要用that,用宿主的上下文

    下面是我们插件的主页面

    public class MainActivity extends BaseActivity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //这里是启动第二个activity
            findViewById(R.id.mBtnStart).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(that, SecondActivity.class);
                    //这里其实调用父类的 最终会调用宿主里面的startActivity方法,下面会对其进行重写
                    startActivity(intent);
                }
            });
        }
    }
    

    上面们的插件和接口以及定义好了 接下来就是好戏了 看看我们的宿主里面是怎么写的

    宿主app首页的代码:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            findViewById(R.id.mBtnLoadPlugin)
                    .setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            loadPlugin();
                        }
                    });
    
    
            findViewById(R.id.mBtnStartProxy).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    startProxy();
                }
    
    
            });
        }
    
        /**
         * 加载插件
         */
        private void loadPlugin() {
            HookManager.getInstance().loadPlugin(this);
            Toast.makeText(this, "加载完成", Toast.LENGTH_LONG).show();
        }
    
        /**
         * 跳转插件
         */
        private void startProxy() {
            Intent intent = new Intent(this, ProxyActivity.class);//这里就是一个占坑的activity
            //这里是拿到我们加载的插件的第一个activity的全类名
            intent.putExtra("ClassName", HookManager.getInstance().packageInfo.activities[0].name);
            
            startActivity(intent);
        }
    

    Hookmanager的代码 是核心 主要加载我们的插件apk和插件资源的

    public class HookManager {
        private static final HookManager ourInstance = new HookManager();
        private Resources resources;
        private DexClassLoader loader;
        public PackageInfo packageInfo;
    
        public static HookManager getInstance() {
            return ourInstance;
        }
    
        private HookManager() {
        }
    
    
        //用来加载插件
        public void loadPlugin(Activity activity) {
            // 假如这里是从网络获取的插件 我们直接从sd卡获取 然后读取到我们的cache目录
            String pluginName = "plugin.apk";
            File filesDir = activity.getDir("plugin", activity.MODE_PRIVATE);
            String filePath = new File(filesDir, pluginName).getAbsolutePath();
            File file = new File(filePath);
            if (file.exists()) {
                file.delete();
            }
            FileInputStream is = null;
            FileOutputStream os = null;
            //读取的目录
            try {
                is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), pluginName));
                //要输入的目录
                os = new FileOutputStream(filePath);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            try {
                int len = 0;
                byte[] buffer = new byte[1024];
                while ((len = is.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                }
                File f = new File(filePath);
                if (f.exists()) {
                    Toast.makeText(activity, "dex overwrite", Toast.LENGTH_SHORT).show();
                }
                loadPathToPlugin(activity);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                try {
                    os.close();
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
    
    
        }
    
        private void loadPathToPlugin(Activity activity) {
            File filesDir = activity.getDir("plugin", activity.MODE_PRIVATE);
            String name = "plugin.apk";
            String path = new File(filesDir, name).getAbsolutePath();
    
            //然后我们开始加载我们的apk 使用DexClassLoader
            File dexOutDir = activity.getDir("dex", activity.MODE_PRIVATE);
            loader = new DexClassLoader(path, dexOutDir.getAbsolutePath(), null, activity.getClassLoader());
    
            //通过PackAgemanager 来获取插件的第一个activity是哪一个
            PackageManager packageManager = activity.getPackageManager();
            packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
    
    
            //然后开始加载我们的资源 肯定要使用Resource 但是它是AssetManager创建出来的 就是AssertManager 有一个addAssertPath 这个方法 但是私有的 所有使用反射
            Class<?> assetManagerClass = AssetManager.class;
            try {
                AssetManager assertManagerObj = (AssetManager) assetManagerClass.newInstance();
                Method addAssetPathMethod = assetManagerClass.getMethod("addAssetPath", String.class);
                addAssetPathMethod.setAccessible(true);
                addAssetPathMethod.invoke(assertManagerObj, path);
                //在创建一个Resource
                resources = new Resources(assertManagerObj, activity.getResources().getDisplayMetrics(), activity.getResources().getConfiguration());
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
        //对外提供插件的classLoader
        public ClassLoader getClassLoader() {
            return loader;
        }
    
        //插件中的Resource
        public Resources getResource() {
            return resources;
        }
    }
    

    下面是我们的占坑的activity:

    public class ProxyActivity extends AppCompatActivity {
    
        private ProxyActivityInterface pluginObj;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //在这里拿到了真实跳转的activity 拿出来 再去启动真实的activity
    
            String className = getIntent().getStringExtra("ClassName");
    
            //通过反射在去启动一个真实的activity 拿到Class对象
            try {
                Class<?> plugClass = getClassLoader().loadClass(className);
                Constructor<?> pluginConstructor = plugClass.getConstructor(new Class[]{});
                //因为插件的activity实现了我们的标准
                pluginObj = (ProxyActivityInterface) pluginConstructor.newInstance(new Object[]{});
                pluginObj.attach(this);//注入上下文
                pluginObj.onCreate(new Bundle());//一定要调用onCreate 
            } catch (Exception e) {
                if (e.getClass().getSimpleName() .equals("ClassCastException")){
                    //我这里是直接拿到异常判断的 ,也可的 拿到上面的plugClass对象判断有没有实现我们的接口
                    finish();
                    Toast.makeText(this,"非法页面",Toast.LENGTH_LONG).show();
                    return;
                }
                e.printStackTrace();
            }
        }
    ]
        //为什么要重写这个呢 因为这个是插件内部startactivity调用的 将真正要开启的activity的类名穿过来
        //然后取出来,启动我们的占坑的activity 在我们真正要启动的赛进去
        @Override
        public void startActivity(Intent intent) {
            String className1=intent.getStringExtra("ClassName");
            Intent intent1 = new Intent(this, ProxyActivity.class);
            intent1.putExtra("ClassName", className1);
            super.startActivity(intent1);
        }
    
        //重写classLoader
        @Override
        public ClassLoader getClassLoader() {
            return HookManager.getInstance().getClassLoader();
        }
    
        //重写Resource
        @Override
        public Resources getResources() {
            return HookManager.getInstance().getResource();
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            pluginObj.onStart();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            pluginObj.onDestroy();
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            pluginObj.onPause();
        }
    }
    

    至此一个简单的插装式的插件化就完成了,只是完成了activity的跳转,还有service和广播。都是一样的

    主要是明白插装式的原理,是通过一个占坑的组件,然后实现同一个接口,来完成生命周期的调用。

    上面插件内部的跳转其实也就是在开同一个activity。

    总结

    1. 宿主程序和插件完全独立
    2. 宿主程序开放部分接口提供插件与之通信
    3. 但是宿主程序耦合了插件的部分业务逻辑
    4. 通过插装式可以实现Activity、Service、BroadcastReceiver、ContentProvider 这些组件的代理

    相关文章

      网友评论

        本文标题:Android插件化(一)

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