美文网首页
插桩式实现插件化(一)

插桩式实现插件化(一)

作者: VictorBXv | 来源:发表于2018-04-28 00:11 被阅读0次

    插件化(一)

    一、前言

    • 定义:将整个app拆分成多个模块,这些模块包括一个宿主多个插件,每个模块都是一个apk文件(组件化的每个模块为lib文件),最终打包时,宿主apk插件apk分开打包。(组件化最后只有一个apk文件)
    • 插件化优势:
      1. 宿主和插件分开编译,互不影响;
      2. 并发开发,宿主和插件分开同时开发;
      3. 动态更新插件;
      4. 按需求下载模块插件
      5. 解决65535问题;
    • 插件化的难度
      1. 插件的四大组件生命周期不好管理,因为插件并没有安装,所以没有上下文,所有与生命周期有关的方法都不能直接调用;
      2. android 9.0@hide注解,导致这个方法对开发者不可见。
      3. 兼容性问题;
      4. 宿主和插件之间的通信问题,因此必须制定一套标准,让宿主和插件都遵循这套标准,从而解决通信问题和插件的生命周期的管理问题。

    二、 实现思路分析

    新建一个library专门用来定义标准,然后让宿主apk插件apk都依赖这个library,而从宿主apk插件apk都必须实现这套标准,插件apk的生命周期就得到了管理。

        public interface PayInterfaceActivity {
        /**
         *通过这个方法将宿主activity的context传递给插件
         *该方法被宿主调用
         */        
        void attach(Activity proxyActivity);
    
        /**
         * 生命周期
         */
        void onCreate(Bundle savedInstanceState);
    
        void onStart();
    
        void onResume();
    
        void onPause();
    
        void onStop();
            
        void onRestart();    
    
        void onDestroy();
    
        void onSaveInstanceState(Bundle outState);
    
        boolean onTouchEvent(MotionEvent event);
    
        void onBackPressed();
    }
    

    宿主apk代码实现

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
        private Button mBtn_load;
        private Button mBtn_jump;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            PluginManager.getInstance().setContext(this);
            mBtn_load = findViewById(R.id.btn_load);
            mBtn_jump = findViewById(R.id.btn_jump);
            setListener();
        }
    
        private void setListener() {
            mBtn_load.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //加载插件,先将插件复制到制定目录
                    loadPlugin();
                }
            });
            mBtn_jump.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //从宿主activity跳转到插件的activity
                    //实际上就是从宿主的其他activity跳转到自己的ProxyActivity,proxyActivity再去加载插件的activity
                    Intent intent = new Intent(MainActivity.this, ProxyActivity.class);
                    //activities[0]代表launcher的activity
                    intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
                    startActivity(intent);
                }
            });
        }
    
    
        private void loadPlugin() {
            //这个目录下文件只能被自己的app访问,其他的没有区别
            File pluginDir = getDir("plugin", Context.MODE_PRIVATE);
            String name = "pluginb.apk";
            String filePath = new File(pluginDir, name).getAbsolutePath();
            File file = new File(filePath);
            if (file.exists()) {
                file.delete();
            }
            InputStream is = null;
            OutputStream os = null;
            try {
                File file1 = new File(Environment.getExternalStorageDirectory(), name);
                Log.i(TAG, "load: file1  = " + file1.getAbsolutePath());
                is = new FileInputStream(file1);
                os = new FileOutputStream(file);
                int len = 0;
                byte[] buffer = new byte[1024];
                while ((len = is.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                }
                if (new File(filePath).exists()) {
                    Toast.makeText(this, "加载成功", Toast.LENGTH_SHORT).show();
                }
    
                //文件复制成功,加载插件
                PluginManager.getInstance().loadPath(this);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                    if (os != null) {
                        os.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    插件管理类

    /**
     * description:插件管理类,专门用来管理插件的加载过程,
     */
    public class PluginManager {
    
        private Context mContext;
    
        private PluginManager() {
        }
    
        private static final PluginManager instance = new PluginManager();
    
        public static PluginManager getInstance() {
            return instance;
        }
    
    
        private PackageInfo mPackageInfo;
        private DexClassLoader mDexClassLoader;
        private Resources mResources;
    
    
        //加载插件
        public void loadPath(Context context) {
            //获取所有的activity
            //首先找到插件的位置
            String pluginbPath = getPluginPath(context);
    
            //获取插件apk里面所有的activity
            PackageManager manager = context.getPackageManager();
            mPackageInfo = manager.getPackageArchiveInfo(pluginbPath, PackageManager.GET_ACTIVITIES);
    
            File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
            /**
             * 实例化DexClassLoader和mResources对象
             * dexPath:一个apk文件的路径
             * optimizedDirectory:缓存路径,这是虚拟机做优化用的
             * librarySearchPath:依赖的包,可以为空
             * ClassLoader
             */
            mDexClassLoader = new DexClassLoader(pluginbPath, dexOutFile.getAbsolutePath(), null, context.getClassLoader());
            try {
                //因为构造方法被@hide注释了,所以只能使用反射
                AssetManager assetManager = AssetManager.class.newInstance();
                Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
                addAssetPath.invoke(assetManager, pluginbPath);
                mResources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        private String getPluginPath(Context context) {
            File pluginDir = context.getDir("plugin", Context.MODE_PRIVATE);
            String name = "pluginb.apk";
            return new File(pluginDir, name).getAbsolutePath();
        }
    
        public DexClassLoader getDexClassLoader() {
            return mDexClassLoader;
        }
    
        public Resources getResources() {
            return mResources;
        }
    
        public PackageInfo getPackageInfo() {
            return mPackageInfo;
        }
    
        public void setContext(Context context) {
            mContext = context;
        }
    }
    

    宿主中专门加载插件中的activity

    /**
     * description:ProxyActivity专门用来加载淘票票的内容,和一般的activity不一样,
     * 加载一个activity,需要知道两个东西,xxxx.class,资源assert,不仅仅是resource
     * 这里仅是为了理解插件化的原理,因此通过intent来传递class的类名
     */
    public class ProxyActivity extends Activity {
        //需要加载的淘票票的activity的全类名
        private String className;
        private static final String TAG = "ProxyActivity";
        private PayInterfaceActivity mPayInterfaceActivity;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            className = getIntent().getStringExtra("className");
            Log.i(TAG, "onCreate: className = " + className);
            //拿到对应的class文件(why?),这里不能通过反射来获取Class文件,因为插件app没有安装(为什么没有安装就不能获取?),
            //通过classLoader来获取class文件
            try {
                //className--->Class--反射-->className对应的activity对象--->调用activity的onCreate()方法实现activity的加载
                Class<?> activityClass = getClassLoader().loadClass(className);
                //实例化activity对象,就是插件apk的类,具体到这里就是插件的mainActivity
                Constructor<?> constructor = activityClass.getConstructor();
                //这里的instance实际上就是activity,要想让这个activity现实出来,必须调用onCreate()方法
                //当然可以通过反射调用onCreate()方法,但是反射有性能上的消耗
                Object instance = constructor.newInstance();
                //因为instance就是插件apk的MainActivity,而插件apk的Activity实现了PayInterfaceActivity接口,因此可以强转
                // TODO: 2018/4/27 java.lang.ClassCastException: com.wvbx.alipayplugin.MainActivity cannot be cast to com.wvbx.paystandard.PayInterfaceActivity
                mPayInterfaceActivity = (PayInterfaceActivity) instance;
                mPayInterfaceActivity.attach(this);
                //如果宿主activity想给插件activity传值的话,就可以通过这个bundle实现
                Bundle bundle = new Bundle();
                mPayInterfaceActivity.onCreate(bundle);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        @Override
        public void startActivity(Intent intent) {
            //从插件的第一个activity跳转到插件的第二个activity要做的事情
            String className = intent.getStringExtra("className");
            Intent intent1 = new Intent(this, ProxyActivity.class);
            intent1.putExtra("className", className);
            super.startActivity(intent1);
        }
    
        //因为ProxyActivity里面放的是插件的activity,
        //所以通过控制ProxyActivity的生命周期来控制插件的activity的生命周期
        //插件实现了PayInterfaceActivity接口,所以可以打到这个目的
        @Override
        protected void onStart() {
            super.onStart();
            mPayInterfaceActivity.onStart();
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            mPayInterfaceActivity.onPause();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mPayInterfaceActivity.onDestroy();
        }
    
        //专门用来加载插件的classLoader,通过插件
        @Override
        public ClassLoader getClassLoader() {
            return PluginManager.getInstance().getDexClassLoader();
        }
    
        @Override
        public Resources getResources() {
            return PluginManager.getInstance().getResources();
        }
    }
    

    插件activity的基类

    /**
     * description:
     *         由于插件apk没有安装,所以灭有上下文,所以所有需要上下文的方法都不能按照以前的方法使用,
     *         需要将宿主的activity传递给插件,通过宿主的上下文来完成插件里面与上下文有关的方法的调用
     */
    public class BaseActivity extends Activity implements PayInterfaceActivity {
    
        //宿主传递过来的activity
        protected Activity that;
    
        @Override
        public void attach(Activity proxyActivity) {
            this.that = proxyActivity;
        }
    
    
        @Override
        public <T extends View> T findViewById(int id) {
            if (that != null) {
                return that.findViewById(id);
            } else {
                return super.findViewById(id);
            }
        }
    
        @Override
        public void setContentView(int layoutResID) {
            if (that != null) {
                that.setContentView(layoutResID);
            } else {
                super.setContentView(layoutResID);
            }
        }
    
        @Override
        public Intent getIntent() {
            if (that != null) {
                return that.getIntent();
            } else {
                return super.getIntent();
            }
        }
    
        @Override
        public ClassLoader getClassLoader() {
            if (that != null) {
                return that.getClassLoader();
            } else {
                return super.getClassLoader();
            }
        }
    
        @NonNull
        @Override
        public LayoutInflater getLayoutInflater() {
            if (that != null) {
                return that.getLayoutInflater();
            } else {
                return super.getLayoutInflater();
            }
        }
    
        @Override
        public ApplicationInfo getApplicationInfo() {
            if (that != null) {
                return that.getApplicationInfo();
            } else {
                return super.getApplicationInfo();
            }
        }
    
        @Override
        public Window getWindow() {
            if (that != null) {
                return that.getWindow();
            } else {
                return super.getWindow();
            }
        }
    
    
        @Override
        public void startActivity(Intent intent) {
            //插件里面的activity跳转,本质上是宿主activity的跳转,即从proxyActivity1 跳转到proxyActivity2
            //proxyActivity-->className
            Intent i = new Intent();
            //这里activity只能是standard模式
            i.putExtra("className",intent.getComponent().getClassName());
            //这里的that即proxyActivity
            that.startActivity(i);
        }
    
        @Override
        public void setContentView(View view) {
            if (that != null) {
                that.setContentView(view);
            } else {
                super.setContentView(view);
            }
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
    
        }
    
        @Override
        public void onStart() {
    
        }
    
        @Override
        public void onResume() {
    
        }
    
        @Override
        public void onPause() {
    
        }
    
        @Override
        public void onRestart() {
    
        }
    
        @Override
        public void onStop() {
    
        }
    
        @Override
        public void onDestroy() {
    
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
    
        }
    }
    

    继承了BaseActivity的子activity

    public class MainActivity extends BaseActivity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //这里不能在xml中写onclick事件,因为没有上下文,
            findViewById(R.id.main).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //这里不能用MainActivity.this,因为没有上下文
                    Toast.makeText(that, "加载成功", Toast.LENGTH_SHORT).show();
                    that.startActivity(new Intent(that, SecondActivity.class));
                }
            });
        }
        
    }
    

    相关文章

      网友评论

          本文标题:插桩式实现插件化(一)

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