插桩式插件化跳转

插桩式插件化跳转

  1. 什么是插件化

    所谓插件化 就是将app分为一个宿主apk和多个插件apk,由宿主apk启动插件apk,在宿主apk中实现不安装插件apk也可完成之间活动跳转,发送广播,启动服务等。举个例子:比如 支付宝内接了海量的应用,如 ofo小黄车,饿了么等等,首先我们知道他们不可能是在一个项目里完成开发,海量的应用,维护起来简直是个噩梦。支付宝就是一个宿主app,内接的海量应用就是插件apk,当我们在支付宝内点击饿了么图标时,就是先从服务器上下载下饿了么的apk,然后由支付宝调用饿了么插件apk的首页,完成跳转到另外一个apk。

  2. 插件化分为几种
    插件化 原理上可分为:插桩式 和 Hook式

  3. 插桩式原理

    • 由宿主apk订制一套规则,宿主和插件共同遵守,只有按规则开发才能接入到宿主apk内
    • 宿主context语法
    • 瞒天过海式代理跳转

下面 一个个解读:
这里写图片描述

1.宿主app订制的规则是什么,按什么规则开发:

项目中有两个app,一个支付宝app(宿主app),另外一个为饿了么app(插件app),还有一个制定的标准lib,两个app都需要依赖于这个标准,此lib中有三个接口,分别为Acticity,广播,服务 定制的标准接口,以activity的标准为例
这里写图片描述

public interface PayInterfaceActivity {

    /**
     * 生命周期的方法
     */
    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();
}

两个apk会通过这些接口来进行调用或传值,以实现接口的规则进行开发,后面会有详细代码说明。

  1. 宿主context语法是什么?
    此时你会不会想到 既然我是从一个宿主app内跳转到另外一个没有安装的插件apk时,那插件内部还能否继续调用自身的Context上下文?答案肯定是不能的,因为插件apk都没有进行安装,也没有运行,肯定是拿不到自身上下文的,那么插件app中要用到上下文怎么办?答案是用宿主传过来的Context。
public class BaseActivity  extends Activity implements PayInterfaceActivity {
//这是插件的基类  在插件的基类中实现订制标准的接口  在attach中将从宿主app内传过来的上下文进行赋值
//然后重写那些用到上下文的方法,将上下文改为宿主的上下文对象,此即为Context语法
    protected  Activity that;
    @Override
    public void attach(Activity proxyActivity) {
        this.that = proxyActivity;
    }


    @Override
    public void setContentView(View view) {
        if (that != null) {
            that.setContentView(view);
        }else {
            super.setContentView(view);
        }
    }

    @Override
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }

    @Override
    public ComponentName startService(Intent service) {
        Intent m = new Intent();
        m.putExtra("serviceName", service.getComponent().getClassName());
        return that.startService(m);
    }

    @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();
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {

        return that.registerReceiver(receiver, filter);
    }

    @Override
    public void sendBroadcast(Intent intent) {
        that.sendBroadcast(intent);
    }

    @Override
    public void startActivity(Intent intent) {
//        ProxyActivity --->className
        Intent m = new Intent();
        m.putExtra("className", intent.getComponent().getClassName());
        that.startActivity(m);
    }

    @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 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) {

    }
}
  1. 重头戏:瞒天过海式代理跳转

3.1 首先在宿主app内将插件的app加载到宿主app的目录中去,在跳转前一定要先完成加载,宿主apk可放在服务器上,在点击跳转前从服务器下载apk到本地。

private void loadPlugin() {
        File filesDir = this.getDir("plugin", Context.MODE_PRIVATE);
        String name = "pluginb.apk"; //插件名称
        String filePath = new File(filesDir, name).getAbsolutePath();
        File file = new File(filePath);
        if (file.exists()) {
            file.delete();
        }
        InputStream is = null;
        FileOutputStream os = null;
        try {
            Log.i(TAG, "加载插件 " + new File(Environment.getExternalStorageDirectory(), name).getAbsolutePath());
            is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), name));
            os = new FileOutputStream(filePath);
            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(this, "dex overwrite", Toast.LENGTH_SHORT).show();
            }
            PluginManager.getInstance().loadPath(this);
        } 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();
            }
        }
    }

3.2 跳转到插件的MainActivity

    public void click(View view) {

        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className",        PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }

咦,咋一看,这也不对啊,你这是骗我啊,说好得跳转到MainActivity,怎么一言不合就跳转到本地的ProxyActivity中去了,别慌,首先看名字,ProxyActivity 代理activity的意思,作为跳转插件apk的代理app,一定要在xml文件中注册,否则 会报错,一会看ProxyActivity中具体做了什么,先来看看 intent.putExtra中以className为键,以PluginManager.getInstance().getPackageInfo().activities[0].name)为值,这个值传了什么,首先先看最重要的类PluginManager。

public class PluginManager {
    private PackageInfo packageInfo;
    private Resources resources;
    private Context context;
    private DexClassLoader dexClassLoader;//首先理解DexClassLoader和PathClassLoader的区别
                                          //简要来说DexClassLoader加载任意路径的类,PathclassLoader只能加载本地自己应用的类
    private static final PluginManager ourInstance = new PluginManager();

    public static PluginManager getInstance() {
        return ourInstance;
    }

    private PluginManager() {
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public PackageInfo getPackageInfo() {
        return packageInfo;
    }

    public void loadPath(Context context) {
       //找到插件apk的文件 用PackageManager管理器获取PackageInfo,PackageInfo内有我们想要的插件apk的包名,类名等信息。
        File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
        String name = "pluginb.apk";
        String path = new File(filesDir, name).getAbsolutePath();

        PackageManager packageManager=context.getPackageManager();
          packageInfo=packageManager.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);


   //根据插件apk的路径,加载路径,缓存路径,和本地类加载器 生成一个插件的类加载器,通过类加载器加载插件中的类
        File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
        dexClassLoader = new DexClassLoader(path, dexOutFile.getAbsolutePath()
                , null, context.getClassLoader());

        try {
        //通过反射获取插件中resources 资源,包括布局文件等
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath=AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, path);
            resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
        } catch ( Exception e) {
            e.printStackTrace();
        }
     }


    public Resources getResources() {
        return resources;
    }

    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }
}

回过头来再继续看

  intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);

packageInfo.activities[0].name返回的就是插件的第一个activity的全路径,即com.test.eleme.MainActivity,将类全路径传到代理ProxyActivity中去,用ProxyActivity来欺骗AMS,通过AMS检查,接着看最后一个关键点,代理类ProxyActivity中做了什么:

public class ProxyActivity  extends Activity {
    private String className;
    PayInterfaceActivity payInterfaceActivity;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState  ) {
        super.onCreate(savedInstanceState );
        //1.获取到从上一个活动传进来的启动插件的类全名
        className = getIntent().getStringExtra("className");

        try {
           //2.用类加载器创建出MainActivity的类
            Class activityClass = getClassLoader().loadClass(className);
            Constructor constructor = activityClass.getConstructor(new Class[]{});
            Object instance= constructor.newInstance(new Object[]{});
            //到此 instance已经是MainActivity的类对象了
            //将MainActivity强转成订制的activity标准
            payInterfaceActivity = (PayInterfaceActivity) instance;
            //将上下文对象传入到插件中去,
            payInterfaceActivity.attach(this);
            //此处可以对插件Activity进行传值
            Bundle bundle = new Bundle();
            //依次调用插件activity生命周期的方法 完成启动
            payInterfaceActivity.onCreate(bundle);

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

    @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);
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getDexClassLoader();
    }


    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }


    @Override
    protected void onStart() {
        super.onStart();
        payInterfaceActivity.onStart();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        payInterfaceActivity.onDestroy();
    }

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

总结:到此,插桩式插件化Activity跳转已经完成,首先双方需要共同遵守一套标准的接口,由接口进行传值和调用,用类加载器实现加载插件中的类,用反射的方法调用插件中的资源,插件中因为没有自己的上下文对象,所以凡是用到Context上下文对象时,都要调用从宿主app中传进来的Context对象。

猜你喜欢

转载自blog.csdn.net/mhy960314/article/details/81221557