插件化学习

什么是插件化

也可以说插件化是有什么效果。
我们平时开发当中的app,主apk,称之为宿主APP。
下载下来的APK,称之为插件app

了解使用反射

在这里插入图片描述
反射耗性能,那么是耗在什么地方,我们能放心使用吗?

1、产生大量的临时对象。
2、检查可见性需要多耗性能。
3、当大量反射—至少也得上千,会编译成机器码(jvm会把部分大量使用的代码编译成机器码直接插入到底层运行,加快运行),这部分代码是没有经过优化。
4、类型转换,大量的类型装箱拆箱。

一般而言,反射该用还是得用,我们也不会用到那么庞大,注意项目混淆的问题即可。

类加载器

在这里插入图片描述

在之前的一篇文章中我就介绍过相关信息:虚拟机与类加载器

PathClassLoader 和 DexClassLoader
在8.0(API 26)之前,它们二者的唯一区别是 第二个参数 optimizedDirectory,这个参数的意 思是生成的 odex(优化的dex)存放的路径。 在8.0(API 26)及之后,二者就完全一样了。

在这里插入图片描述1、避免重复加载,当父加载器已经加载了该类的 时候,就没有必要子ClassLoader再加载一次。
2、安全性考虑,防止核心API库被随意篡改。

bootClassLoader的findClass最终调用ClassForName是native方法我们也不再跟下去了。

parent找不到才会自己找findClass。

classLoader里面的findClass没有实现,直接抛了个异常。
看它的子类BaseDexClassLoader里面的实现:
在这里插入图片描述
用的PathList里面的findClass
在这里插入图片描述
element的findClass
在这里插入图片描述
dexFile就是dex文件的抽象类

一个Element 代表这一个dex文件,整个apk所有的文件都在Element[]数组里面。dexElement
1、获取宿主的dexElement
2、获取插件的dexElement
3、合并两个dexElement
4、将新的dexElement赋值到宿主dexElement
经过上面四部,就能将插件的类全部加载了。
说到这里热修复其实也是差不多同样的操作,将插件的dex放前面加载。
目标:dexElements --> DexPathList类的对象 --> BaseDexClassLoader的对象,类加载器

 Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = clazz.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);

            // 宿主的 类加载器
            ClassLoader pathClassLoader = context.getClassLoader();
            // DexPathList类的对象
            Object hostPathList = pathListField.get(pathClassLoader);
            // 宿主的 dexElements
            Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);

            // 插件的 类加载器
            ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(),
                    null, pathClassLoader);
            // DexPathList类的对象
            Object pluginPathList = pathListField.get(dexClassLoader);
            // 插件的 dexElements
            Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);

            // 宿主dexElements = 宿主dexElements + 插件dexElements
//            Object[] obj = new Object[]; // 不行

            // 创建一个新数组
            Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
                    hostDexElements.length + pluginDexElements.length);

            System.arraycopy(hostDexElements, 0, newDexElements,
                    0, hostDexElements.length);
            System.arraycopy(pluginDexElements, 0, newDexElements,
                    hostDexElements.length, pluginDexElements.length);

            // 赋值
            // hostDexElements = newDexElements
            dexElementsField.set(hostPathList, newDexElements);

怎么启动四大组件

普通类的启动我们直接new对象即可,但是四大组件的启动就不行了,它必须是在清单文件中注册过才行。类似activity这种我们不仅需要它像一个普通类那样启动,我们还需要它的生命周期等,所以必须要在清单文件中注册。

APK的安装是通过PackageManagerService的扫描安装,所有的信息都被该服务持有,但是AMS是我们服务的大管家,实际上我们大部分的操作都是通过binder跨进程到AMS,然后AMS根据需要调用其他的服务来验证的,四大组件的启动也是同样的流程。

思路:我们需要在宿主APP的清单文件中注册一个对应的四大组件,例如注册一个Activity,在发送给AMS检测前Hook一下,变成该Activity,用来通过给AMS检测通过,当它返回的时候我们再改成启动我们需要的对应组件,这就需要我们对四大组件的启动流程熟悉了。

四大组件的启动都差不多,我们就Activity的启动为例。

找Hook点

查找Hook点的原则:
1、尽量静态变量或者单例对象
2、尽量Hook public的对象和方法
好获取,不容易改变,如果一个对象被实例化了多个,确认是哪个就比较麻烦了。

首先我们不可能夸进程去hook AMS,而且不是替换完对象就完事,Activity的启动不是一次性买卖,其他正常的启动我们不能替换,那么就需要类似观察者模式这样的回调,不仅可以让我知道启动了Activity且我们还需要拿到启动时候传递过去的参数,我们需要动态代理,既有回调也有传递的参数。
我们知道app进程跟AMS是通过binder夸进程通讯的,两边都有同样的接口实现类,直接找到这里。

我们启动一个Activity是通过Intent传递信息,最终都会走到这里交给Instrumentation.ActivityResul
跟进execStartActivity方法

 public void startActivityForResult(
            String who, Intent intent, int requestCode, @Nullable Bundle options) {
        Uri referrer = onProvideReferrer();
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, who,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, who, requestCode,
                ar.getResultCode(), ar.getResultData());
        }
        cancelInputsAndStartExitTransition(options);
    }

最终来到:ActivityTaskManager.getService().startActivity

 try {
            intent.migrateExtraStreamToClipData(who);
            intent.prepareToLeaveProcess(who);
            int result = ActivityTaskManager.getService().startActivity(whoThread,
                    who.getBasePackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                    target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

ActivityTaskManager.getService()是实现类,我们需要动态代理它的接口,并反射替换它,那么每次执行该对象的方法都会回调到我们动态代理的回调方法里。

   public static IActivityTaskManager getService() {
        return IActivityTaskManagerSingleton.get();
    }

ActivityTaskManager.getService()对象实现了IActivityTaskManager
它是通过IActivityTaskManagerSingleton的get方法获取

 private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    return IActivityTaskManager.Stub.asInterface(b);
                }
            };

IActivityTaskManagerSingleton 是Singleton
我们看下Singleton

public abstract class Singleton<T> {

    @UnsupportedAppUsage
    public Singleton() {
    }

    @UnsupportedAppUsage
    private T mInstance;

    protected abstract T create();

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

IActivityTaskManager可以通过Singleton来修改

那么接下来的代码需要代理IActivityTaskManager接口,并修改它的实现类 private T mInstance;

在这里插入图片描述

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

8.0之前case是100,后面变成了159。

前面的几个方法调用有兴趣的朋友可以去跟下,这里就不介绍了,每个版本的代码都有些差异。

void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

从AMS出来到ActivityThread这里就容易找多了。
所有的信息都会跑到sendMessage,然后调用ActivityThread.H

class H extends Handler 

随便看下几个case的处理:

   case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;
                case RECEIVER:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                    handleReceiver((ReceiverData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

类似:AppBindData data = (AppBindData)msg.obj;,将Object强转转为对应的类型
8.0之前直接是ActivityClientRecord里面的intent
之后是在mActivityCallbacks

H 是一个Handler,它直接new对象出来的,然后重写了handleMessage

 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在handleMessage(msg);之前还会根据mCallback 是否为空进行

 if (mCallback.handleMessage(msg)) {
                    return;
                }

这个Callback 是个接口,有一个唯一的实现类,这简直天生符合我们的需求,能动态代理。

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }

但是实际上我们在这里不需要动态代理,检查 H 这个类的创建,里面没有实现Callback,也就是说它是空的对象,我们直接创建一个给它即可。

关键代码:

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

资源加载

raw : Android会自动的为这目录中的所有资源文件生成一个ID,这意味着很容易就可以访问到这个资源,甚至在xml 中都是可以访问的,使用ID访问速度是最快的。

assets : 不会生成ID,只能通过AssetManager访问,xml中不能访问,访问速度会慢些,不过操作更加方便。

AssetManager相关的这里就不再细讲了,在前面有说明:插件化换肤思路

  private final static String apkPath = "/sdcard/plugin-debug.apk";

    private static Resources mResources;

    public static Resources getResources(Context context) {
        if (mResources == null) {
            mResources = loadResource(context);
        }
        return mResources;
    }

    private static Resources loadResource(Context context) {
        // assets.addAssetPath(key.mResDir)
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            // 让 这个 AssetManager对象 加载的 资源为插件的
            Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPathMethod.invoke(assetManager, apkPath);

            // 如果传入的是Activity的 context 会不断循环,导致崩溃
            Resources resources = context.getResources();

            // 加载插件的资源的 resources
            return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

在BaseActivity里面调用使用,但是还有一个问题,我们插件中如果是继承自AppCompatActivity,那么启动的时候也会需要一些资源,这些资源存在跟宿主APP的资源冲突的可能。

所以我们需要宿主的context里面的资源是加载我们上面getResources
在baseActivity的onCreate里

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

对应宿主Activity实例化的时候必须是使用宿主中我们自己设置的context:

        View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main, null);
        setContentView(view);

大概了解插件化的原理,扩展知识面,自己写一个插件化工具出来明显要关注的点会非常多,每个版本的代码差异都需要研究,目前市面开源能直接用的也很多,了解对应的原理对我们也有好处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值