插件化

为什么插件化

  • 将 app 中一些不常用的功能模块做成插件(免安装apk):
    • 一方面减小了安装包的大小,启动更快,
    • 另一方面可以实现 app 功能的动态插拔
    • 附带好处升级``ABTest``换肤``多开
  • 插件化和组件化的区别:
    • 组件化是拆成业务组件和功能组件,虽然可以单独调试,但发布的时候依然依赖进来统一编译一个apk;
    • 插件化在组件化基础上更进一步, 把独立出来的模块编译成单独apk,打包时宿主和插件分开打包;

插件化的开源框架发展

特性DroidPluginRePluginVirtualAPK
作者360360滴滴
支持四大组件全支持全支持全支持
组件无需在宿主manifest中预注册
插件可以依赖宿主×
支持PendingIntent
Android特性支持几乎全部几乎全部几乎全部
兼容性适配
插件构建Gradle插 件Gradle插件

需要根据自身的需求来,如果加载的插件不需要和宿主有任何耦合,也无须和宿主进
行通信,比如加载第三方 App,那么推荐使用 RePlugin,其他的情况推荐使用 VirtualApk。

插件化的难点

1. 如何加载插件的类?

虚拟机类加载三件事

  1. (Art)读取该类的二进制字节流
  2. 将静态的字节流转为方法区的数据结构
  3. 在堆生成该类的Class对象,作为访问方法区数据入口
    • 类的Class对象:当前类的抽象类的对象,比当前类实例化拿到更多属性和方法
    • 怎么获取类的对象?
      • getClass()
      • Class.forName()
      • getClassLoader().loadClass()

java Android 类加载机制 区别

  • java里面加载class 文件 ,Android 中 DVM 和 ART 加载的是 dex 文件

了解APP实际使用的ClassLoader

// 在 onCreate 中执行下面代码 
ClassLoader classLoader = getClassLoader();
 while (classLoader != null) {
   Log.e("tag", "classLoader:" + classLoader);
   classLoader = classLoader.getParent();
  }
 Log.e("tag", "classLoader:" + Activity.class.getClassLoader());
  Log.e("tag", "classLoader:" + AppCompatActivity.class.getClassLoader());

结果:
classLoader:dalvik.system.PathClassLoader  //默认由pathClassLoader加载当前应用 和依赖库
classLoader:java.lang.BootClassLoader@a26e88d //父类
classLoader:java.lang.BootClassLoader@a26e88d//源码SDK默认由BootClassLoade加载,也就是部分Framework去掉依赖进来的系统源码;
classLoader:dalvik.system.PathClassLoader //依赖进来的系统源码 由pathClassLoader加载

如何使用类加载器去加载一个类

//用法
DexClassLoader dexClassLoader = new DexClassLoader(dexPath,context.getCacheDir().getAbsolutePath()//路径是 apkPath
        , null, context.getClassLoader());
Class<?> clazz = dexClassLoader.loadClass("com.xxx.xxx.Test");//类名
Method method = clazz.getMethod("xxx");
method.invoke(null);

-----------------------------------------------
//源码:机制

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(parent);
// 初始化 pathList
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null); 
}

//接着再来看 DexPathList 类中的 findClass 方法。
private Element[] dexElements;
public Class<?> findClass(String name, List<Throwable> suppressed) {
//通过 Element 获取 Class 对象
  for (Element element : dexElements) {
    Class<?> clazz = element.findClass(name, definingContext, suppressed);
    if (clazz != null) {
    return clazz;
  }
 }
 return null;
}
//首先检测这个类是否已经当前类加载器加载了,如果找到了,直接获取返回;
//如果没有找到,调用父类的方法loadclass,检测是否被父类加载器加载了,依次递归
//到最顶层父类,判断是否已经加载了,
//如果没有抛异常依次返回到当前类加器加载;
--------/**-双亲委托*/--------

插件化类加载实现原理:

  1. 创建插件的 DexClassLoader 类加载器,然后通过反射获取插件的 dexElements 值。
  2. 获取宿主的 PathClassLoader 类加载器,然后通过反射获取宿主的 dexElements 值。
  3. 合并宿主的 dexElements 与 插件的 dexElements,生成新的 Element[]。 4. 最后通过反射将新的 Element[] 赋值给宿主的 dexElements 。
// 获取 pathList 的字段
public static void loadClass(Context context) {
Class.forName("dalvik.system.BaseDexClassLoader");
getDeclaredField("pathList");
// 获取 dexElements 字段
Class.forName("dalvik.system.DexPathList");
getDeclaredField("dexElements");

//注意上面获取的是类相关与对象无关,不同对象可以获得不同的值
//所以下面宿主和插件都可以用

//获取宿主的 dexElements[]
(PathClassLoader) context.getClassLoader();
Object hostPathList = pathListField.get(pathClassLoader);
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);

//获取插件的 dexElements[]
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(), null, context.getClassLoader());
Object pluginPathList = pathListField.get(dexClassLoader);
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);

2. 如何加载插件的资源?

//Resources去访问 res中的资源,使用 AssetManager 访问 assets 里面的资源
String appName = getResources().getString(R.string.app_name);
InputStream is = getAssets().open("icon_1.png");

创建新的Resources对象

如何创建新的Resources对象返回给插件使用assert.addAssertPath(插件资源),传入宿主的application;

LoadUtil.class
public static Resources loadResource(Context context) {
    try {
      Class<?> assetManagerClass = AssetManager.class;
      AssetManager assetManager = (AssetManager) assetManagerClass.newInstance();
      Method addAssetPathMethod = assetManagerClass.getDeclaredMethod("addAssetPath", String.class);
      addAssetPathMethod.setAccessible(true);
      addAssetPathMethod.invoke(assetManager, apkPath);
      Resources resources = context.getResources();
      //用来加载插件包中的资源
      return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
    } catch (Exception e) { 
       e.printStackTrace();
    }
    return null;
 }

如何调用loadResource方法

  • 实现方式
  1. 宿主资源和插件资源合并,冲突的话aapt解决
    aapt流程 :生成R.java和srec
    apk打包流程:待补充
    ziplign四字节对齐:1.运行快 — mmap

  2. 资源不合并,插件单独加载,在插件中基类重写getResource();也会产生冲突,id不同

// 插件中代码
//然后让插件的 Activity 都继承自 BaseActivity;
resources 对象,也就可以拿到资源了。
public abstract class BaseActivity extends Activity {
      @Override
      public Resources getResources() {
         //思考为什么传入getApplication ?
          Resources resources = LoadUtil.loadResource(getApplication);
       return resources == null ? super.getResources() : resources;
     }
 }
  • 在插件getApplication 获取到的宿主还是插件的?宿主的,双亲机制,是同一个,因为插件的application不会执行
  • 假如继承AppCompatActivity,空指针–
    • 解决方法:创建插件自己的Context,绑定启动插件资源的Resource;
public class BaseActivity extends AppCompatActivity {

    protected Context mContext;
    // 将创建的插件reource,绑定到插件的context里面,创建插件的context
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Resources resources = LoadUtil.getResources(getApplication());

        mContext = new ContextThemeWrapper(getBaseContext(), 0);

        Class<? extends Context> clazz = mContext.getClass();
        try {
            Field mResourcesField = clazz.getDeclaredField("mResources");
            mResourcesField.setAccessible(true);
            mResourcesField.set(mContext, resources);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//然后在MainActivity 使用插件的context
public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 暂时注释,没涉及到资源
//        setContentView(R.layout.activity_main);
        View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main, null);
        setContentView(view);
    }
}

3. 如何调用插件类?解决注册的问题

HOOK原则:最好静态变量或单例对象,其次public对象和方法(goole对外的);

看源码疑问?凭啥hook只改那一点点就可以,其他方法不需要重写?只能代理接口?
1. method.invoke
2. 只能代理接口,但是invoke传入被代理对象的实例

Object mInstanceProxy = Proxy.newProxyInstance(, A类, new InvocationHandler() { 
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//invoke 回调几次 取决去A类有几个方法;
//这里我们开心在在invoke前后随意操作 -----
//mInstance 原来对象
return method.invoke(mInstance, args);
---这一步保证A类的所有方法全部保留,想当于super.xxx------

Activity的启动流程

参考app启动流程
找出hook点 ----替换Intent
通过这张图我们可以确定 Hook 点的大致位置。

在进入 AMS 之前,找到一个 Hook 点,用来将插件 Activity 替换为 ProxyActivity。

// android/app/Instrumentation.java
public ActivityResult execStartActivity(
           Context who, IBinder contextThread, IBinder token, Activity target,
           Intent intent, int requestCode, Bundle options) {
     // 这儿就是我们的 Hook 点,替换传入 startActivity 方法中的 intent 参数
    try {
          intent.migrateExtraStreamToClipData();
          intent.prepareToLeaveProcess(who);
          int result = ActivityManager.getService()
              .startActivity(whoThread, who.getBasePackageName(), intent,
                      intent.resolveTypeIfNeeded(who.getContentResolver()),
                      token, target, requestCode, 0, null, options);
          checkStartActivityResult(result, intent);
      } catch (RemoteException e) {
          throw new RuntimeException("Failure from system", e);
      }
     }

生成一个代理对象,代理ActivityManager.getService()

public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

下面我们就生成代理对象,并且当执行的方法是startActivity 的时候,替换它的参数 intent。

Object mInstanceProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
      new Class[]{iActivityManagerClass}, new InvocationHandler() {
          
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             // 当执行的方法是 startActivity 时作处理
             if ("startActivity".equals(method.getName())) {
             int index = 0;
             // 获取 Intent 参数在 args 数组中的index值
             for (int i = 0; i < args.length; i++) {
                 if (args[i] instanceof Intent) {
                     index = i;
                     break;
                  }
              }
              // 得到原始的 Intent 对象 -- 唐僧(插件)的Intent
              Intent intent = (Intent) args[index];
              // 生成代理proxyIntent -- 孙悟空(代理)的Intent
              Intent proxyIntent = new Intent();
              proxyIntent.setClassName("com.enjoy.pluginactivity", ProxyActivity.class.getName());
              // 保存原始的Intent对象
              proxyIntent.putExtra(TARGET_INTENT, intent);
              // 使用proxyIntent替换数组中的Intent
              args[index] = proxyIntent;
          }
          return method.invoke(mInstance, args);
      }
  });

那如何替换

    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

下面我们再看下Singleton 是怎么样的。

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

可以看出,IActivityManagerSingleton.get() 返回的实际上就是 mInstance 对象。所以接下来我们要替换的就是这
个对象。

接着我们来实现第二步,从 AMS 出来后,再找一个 Hook 点,用来将 ProxyActivity 替换为插件 Activity。
在AMS检查之前将插件activity用ProxyActivity替换,因为它是宿主的可以注册,AMS检测没问题,回到Handler的时候替换回来,handleMessage();

public class HookUtil {

    private static final String TARGET_INTENT = "target_intent";

    public static void hookAMS() {
        try {
            // 获取 singleton 对象
            Field singletonField = null;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // 小于8.0
                Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
                singletonField = clazz.getDeclaredField("gDefault");
            } else {
                Class<?> clazz = Class.forName("android.app.ActivityManager");
                singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
            }

            singletonField.setAccessible(true);
            Object singleton = singletonField.get(null);

            // 获取 系统的 IActivityManager 对象
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            final Object mInstance = mInstanceField.get(singleton);

            Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");

            // 创建动态代理对象
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{iActivityManagerClass}, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            // do something
                            // Intent的修改 -- 过滤
                            /**
                             * IActivityManager类的方法
                             * startActivity(whoThread, who.getBasePackageName(), intent,
                             *                         intent.resolveTypeIfNeeded(who.getContentResolver()),
                             *                         token, target != null ? target.mEmbeddedID : null,
                             *                         requestCode, 0, null, options)
                             */
                            // 过滤
                            if ("startActivity".equals(method.getName())) {
                                int index = -1;

                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        index = i;
                                        break;
                                    }
                                }
                                // 启动插件的
                                Intent intent = (Intent) args[index];

                                Intent proxyIntent = new Intent();
                                proxyIntent.setClassName("com.enjoy.leo_plugin",
                                        "com.enjoy.leo_plugin.ProxyActivity");

                                proxyIntent.putExtra(TARGET_INTENT, intent);

                                args[index] = proxyIntent;
                            }

                            // args  method需要的参数  --- 不改变原有的执行流程
                            // mInstance 系统的 IActivityManager 对象
                            return method.invoke(mInstance, args);
                        }
                    });

            // ActivityManager.getService() 替换成 proxyInstance
            mInstanceField.set(singleton, proxyInstance);

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


    public static void hookHandler() {
        try {
            // 获取 ActivityThread 类的 Class 对象
            Class<?> clazz = Class.forName("android.app.ActivityThread");

            // 获取 ActivityThread 对象
            Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
            activityThreadField.setAccessible(true);
            Object activityThread = activityThreadField.get(null);

            // 获取 mH 对象
            Field mHField = clazz.getDeclaredField("mH");
            mHField.setAccessible(true);
            final Handler mH = (Handler) mHField.get(activityThread);

            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);

            // 创建的 callback
            Handler.Callback callback = new Handler.Callback() {

                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    // 通过msg  可以拿到 Intent,可以换回执行插件的Intent

                    // 找到 Intent的方便替换的地方  --- 在这个类里面 ActivityClientRecord --- Intent intent 非静态
                    // msg.obj == ActivityClientRecord
                    switch (msg.what) {
                        case 100:
                            try {
                                Field intentField = msg.obj.getClass().getDeclaredField("intent");
                                intentField.setAccessible(true);
                                // 启动代理Intent
                                Intent proxyIntent = (Intent) intentField.get(msg.obj);
                                // 启动插件的 Intent
                                Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                                if (intent != null) {
                                    intentField.set(msg.obj, intent);
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            break;
                        case 159:
                            try {
                                // 获取 mActivityCallbacks 对象
                                Field mActivityCallbacksField = msg.obj.getClass()
                                        .getDeclaredField("mActivityCallbacks");

                                mActivityCallbacksField.setAccessible(true);
                                List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);

                                for (int i = 0; i < mActivityCallbacks.size(); i++) {
                                    if (mActivityCallbacks.get(i).getClass().getName()
                                            .equals("android.app.servertransaction.LaunchActivityItem")) {
                                        Object launchActivityItem = mActivityCallbacks.get(i);

                                        // 获取启动代理的 Intent
                                        Field mIntentField = launchActivityItem.getClass()
                                                .getDeclaredField("mIntent");
                                        mIntentField.setAccessible(true);
                                        Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);

                                        // 目标 intent 替换 proxyIntent
                                        Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                                        if (intent != null) {
                                            mIntentField.set(launchActivityItem, intent);
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            break;
                    }
                    // 必须 return false
                    return false;
                }
            };

            // 替换系统的 callBack
            mCallbackField.set(mH, callback);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结

插件化涉及的技术其实是非常多的,比如
应用程序启动流程、四大组件启动流程、AMS原理、PMS原理、ClassLoader原理、Binder机制,动态代理等等;

插件化的框架使用

//待补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值