美文网首页
Android插件化原理

Android插件化原理

作者: AndroidHint | 来源:发表于2018-02-11 20:17 被阅读0次

    1、前言

    这篇文章来讲一下Android插件化的原理和大概的一个运行流程,最后将一个demo呈现出来。

    2、分析

    插件说到底就是一个apk文件,我们要做的事情是从宿主中加载该apk文件的类对象(比如启动Activity)和使用该apk文件的资源等操作。我们知道系统是不会安装apk插件的,所以宿主是不知道我们的插件的任何信息。我们之前分析了Activity的启动过程,其实就是在ActivityThread的performLaunchActivity方法中创建了Activity对象,现在再看一下其中的逻辑:

    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    

    activity的创建是使用反射的方式进行的,而ClassLoader是由r.packageInfo提供的,这里的r.packageInfo是一个LoadedApk对象,LoadedApk对象存储了Apk文件的相关信息,而该对象的赋值是在H类的handleMessage中:

    case LAUNCH_ACTIVITY: {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
    
        r.packageInfo = getPackageInfoNoCheck(
                r.activityInfo.applicationInfo, r.compatInfo);
        handleLaunchActivity(r, null);
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    } break;
    

    可以看到r.packageInfo(LoadedApk对象)是通过getPackageInfoNoCheck()方法获取的,我们进入到getPackageInfoNoCheck()方法中:

    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
    }
    

    它又调用了getPackageInfo方法:

    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage) {
        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                // Caching not supported across users
                ref = null;
            } else if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }
    
            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (packageInfo == null || (packageInfo.mResources != null
                    && !packageInfo.mResources.getAssets().isUpToDate())) {
                if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                        : "Loading resource-only package ") + aInfo.packageName
                        + " (in " + (mBoundApplication != null
                                ? mBoundApplication.processName : null)
                        + ")");
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
    
                if (mSystemThread && "android".equals(aInfo.packageName)) {
                    packageInfo.installSystemApplicationInfo(aInfo,
                            getSystemContext().mPackageInfo.getClassLoader());
                }
    
                if (differentUser) {
                    // Caching not supported across users
                } else if (includeCode) {
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }
            }
            return packageInfo;
        }
    }
    

    它会从mPackages的这样的一个ArrayMap中去获取,如果获取到则返回,没有获取到则new了一个LoadedApk对象,然后将其存入到mPackages中。
    所以实现插件化我们可以这样做(方法一):
    由于插件的加载需要一个ClassLoader,而这个ClassLoader是自定义的,并且从上面的源码可以看到系统的ClassLoader是从LoadedApk对象中获取的,所以我们需要一个自定义的LoadedApk对象,从上面的源码可知,LoadedApk对象是从mPackages这样的一个ArrayMap中获取的,所以我们可以将我们自定义的LoadedApk对象存入到这个mPackages这个map中,key为插件的packagename,这样在要获取LoadedApk对象时就直接从map中返回了,然后得到的是我们自定义的LoadedApk对象,然后从该对象中得到自定义的ClassLoader对象,从而使用该ClassLoader可以加载外部插件了。
    这种方法是可行的,但是过程比较复杂。因为在上面的过程中需要hook住比较多的系统类和方法,并且在构建一个LoadedApk对象时,还需要一个ApplicationInfo对象,该对象需要解析插件的Manifest文件获取,所以我们还需要手动去解析Manifest文件,这就增加了该方法的复杂程度。

    那是否还有其他方法来实现插件化呢(方法二)?Dex的加载过程这篇文章中可以看到,类的加载最终是调用了DexPathList的findClass方法,而在DexPathList中维护了一份Element类型的dexElement数组,这个数组存放的就是dex文件的信息,所以我们可以将插件的dex文件信息也存放到该数组中,然后在加载插件类的时候,自然就能够从插件的dex文件中进行加载插件的类。
    这种方法相比于上述的第一种方法,显然是比较简洁并且便捷。但是也是存在着问题的。对于四大组件中的Activity、Service、ContentProvider是需要在Manifest注册的,否则会报错误异常。从Activity的启动过程这篇文章可以看出,我们可以hook住startActivity这个方法,使用一个占坑的Activity在Manifest中注册,从而解决问题。原理是这样的:
    首先在启动Activity的时候,我们将要启动的Activity替换为占坑的Activity,用这个占坑的Activity去进行系统的合法性验证,当验证通过的时候,在生成Activity对象时,再次hook住Activity的创建方法,将真正要启动的Activity替换回来,让系统创建一个真正需要启动的Activity对象。

    3、实现

    根据上述的分析,我们选择第二种方法进行demo实现,下面详细介绍实现的步骤:

    3.1 加载插件apk

    首先需要创建一个自定义的ClassLoader去加载外部插件,而加载外部插件的ClassLoader类型必须是DexClassLoader,具体实现如下:

    //插件的外部路径
    String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "plugin_demo.apk";
    //插件优化后的路径
    String cachePath = getCacheDir().getAbsolutePath();
    //自定义的ClassLoader
    DexClassLoader pluginLoader = new DexClassLoader(apkPath, cachePath, cachePath, getClassLoader());
    

    3.2 合并宿主和插件的Element[]数组

    拿到宿主的ClassLoader,通过ClassLoader获取pathList,通过pathList获取Element[]数组;拿到插件的ClassLoader,插件和宿主做同样的操作获取Element[]数组,然后将这两个数组合并,最后将合并的Element[]数组设置给宿主的pathList。

    public static void mergePathFileElement(ClassLoader pluginLoader) {
      //拿到宿主的ClassLoader
      ClassLoader originPathLoader = MainApplication.getContext().getClassLoader();
      //获取宿主的pathList
      Object originPathList = getPathList(originPathLoader);
      //获取插件的pathList
      Object pluginPathList = getPathList(pluginLoader);
      //获取宿主的Element[]数组
      Object originElements = getElement(originPathList);
      //获取插件的Element[]数组
      Object pluginElements = getElement(pluginPathList);
      //将宿主和插件的Element[]数组进行合并
      Object combineElements = combineElements(originElements, pluginElements);
      //将合并的Elements设置给宿主的pathList
      setDexElements(originPathList, combineElements);
    }
    

    上面的getPathList和getElement都是通过反射获取的,我们主要看一下数组合并的逻辑:

    private static Object combineElements(Object originElements, Object pluginElements) {
      Class<?> arrayType = originElements.getClass().getComponentType();
      int originLength = Array.getLength(originElements);
      int pluginLength = Array.getLength(pluginElements);
      int lengths = originLength + pluginLength;
      Object newArray = Array.newInstance(arrayType, lengths);
      for (int i = 0; i < lengths; i++) {
          if (i < originLength) {
              Array.set(newArray, i, Array.get(originElements, i));
          } else {
              Array.set(newArray, i, Array.get(pluginElements, i - originLength));
          }
      }
      return newArray;
    }
    

    合并的逻辑也很简单,首先通过反射获取数组的类型并使用Array.newInstance()新建一个数组,然后将原有的两个数组设置到新建的那个数组中。

    3.3 代理系统启动Activity的方法

    首先我们从Activity的启动过程这篇文章可以看出,Activity的启动最终会走到Instrumentation类的execStartActivity方法,在该方法中调用了ActivityManager.getService().startActivity方法,我们看一下这里的逻辑:

    int result = ActivityManager.getService()
                    .startActivity(whoThread, who.getBasePackageName(), intent,
                            intent.resolveTypeIfNeeded(who.getContentResolver()),
                            token, target != null ? target.mEmbeddedID : null,
                            requestCode, 0, null, options);
    
    public static IActivityManager getService() {
          return IActivityManagerSingleton.get();
    }
    

    ActivityManager.getService()方法返回了一个IActivityManager对象,而该对象是通过IActivityManagerSingleton.get()产生的,我们再看一下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;
          }
    };
    
    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是一个Singleton对象,get()方法返回了的是mInstance对象,而该对象是通过create()方法生成的,从create()方法中可以看到生成了一个IActivityManager类型的对象,该对象是一个远程代理类。
    因此我们要hook住startActivity方法,那么我们要生成一个IActivityManager对象的一个代理类,因此我们需要获取现有的IActivityManager对象,也就是说要获取ActivityManager.getService()方法返回的对象。

    Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
    Field singletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
    singletonField.setAccessible(true);
    Object singletonValue = singletonField.get(null);
    
    //singletonValue是一个 android.util.Singleton对象;取出这个单例里面的字段
    Class<?> singletonClass = Class.forName("android.util.Singleton");
    Field mInstance = singletonClass.getDeclaredField("mInstance");
    mInstance.setAccessible(true);
    //取出了AMS代理对象,这里的AMS代理对象就是singletonValue对象的值
    Object iActivityManager = mInstance.get(singletonValue);
    
    Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
    Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
            , new Class<?>[]{iActivityManagerInterface}, new IActivityManagerHandler(iActivityManager));
    //将singletonValue对象的值(即上面的iActivityManager对象)设置为(AMS代理对象的)代理对象的值
    mInstance.set(singletonValue, proxy);
    

    利用反射的原理,获取了ActivityManager.getService()方法返回的对象,然后利用该对象创建了一个代理对象,最后将这个代理对象设置给ActivityManager.getService()方法返回的对象。

    3.4 iActivityManager对象的代理对象

    public class IActivityManagerHandler implements InvocationHandler {
        private Object iActivityManagerHandler;
    
        public IActivityManagerHandler(Object iActivityManagerHandler) {
            this.iActivityManagerHandler = iActivityManagerHandler;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("startActivity")) {
                Log.d("ABC", "startActivity 被拦截了");
    
                Intent rawIntent = null;
                int intentIndex = 0;
                // 找到参数里面的第一个Intent对象
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        rawIntent = (Intent) args[i];
                        intentIndex = i;
                        break;
                    }
                }
                String packageName = MainApplication.getContext().getPackageName();
                //创建一个新的Intent
                Intent newIntent = new Intent();
                if (rawIntent != null) {
                    //把启动的Activity替换为占坑Activity
                    ComponentName componentName = new ComponentName(packageName, PitActivity.class.getName());
                    newIntent.setComponent(componentName);
                    //将我们真正要启动的Activity保存起来
                    newIntent.putExtra(ActivityHookHelper.RAW_INTENT, rawIntent);
                    //替换掉intent,已达到欺骗系统的作用
                    args[intentIndex] = newIntent;
                    Log.d("ABC", "startActivity hook 成功");
                }
                return method.invoke(iActivityManagerHandler, args);
    
            }
            return method.invoke(iActivityManagerHandler, args);
        }
    }
    

    创建一个iActivityManager对象的代理对象,并拦截startActivity方法,在其内部将要启动的Activity替换成占坑的Activity。

    3.5 拦截创建Activity的方法

    在系统检查完毕后,在创建Activity对象的时候,需要再次拦截创建Activity的方法,并创建真正要启动的Activity对象。
    系统检查完毕后,会回调ActivityThread里的scheduleLaunchActivity方法,这个方法发送了一个消息到ActivityThread的内部类H里,而H类是一个Handler,如下所示:

    case LAUNCH_ACTIVITY: {
      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
      final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
    
      r.packageInfo = getPackageInfoNoCheck(
              r.activityInfo.applicationInfo, r.compatInfo);
      handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    } break;
    

    而Handler是通过dispatchMessage(Message msg)方法来分发消息的,我们先来看一下其中逻辑:

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

    可以看到如果Handler设置了mCallback对象的话,则会走mCallback对象的handleMessage方法,而且如果mCallback对象的handleMessage方法返回为true则不会走到下面的handleMessage方法。我们再看一下H类,可以看到它并没有设置mCallback对象,因此我们可以利用反射给它设置一个mCallback对象,并在handleMessage方法中处理LAUNCH_ACTIVITY(msg.what = 100)的情况:

    public static void hookHandler() {
      try {
          Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
          //ActivityThread有一个静态方法返回了自己,这里可以获取activityThread对象
          Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
          currentActivityThread.setAccessible(true);
          Object activityThread = currentActivityThread.invoke(null);
    
          Field mH = activityThreadClass.getDeclaredField("mH");
          mH.setAccessible(true);
          Handler mHValue = (Handler) mH.get(activityThread); //获取activityThread对象中mH变量的值,其中mH的类型是Handler类型
    
          Field callback = Handler.class.getDeclaredField("mCallback");
          callback.setAccessible(true);
          callback.set(mHValue, new ActivityCallbackHandler(mHValue)); //将一个自定义的Callback设置给Handler
      } catch (Exception e) {
          e.printStackTrace();
      }
    }
    

    上面就是利用反射给ActivityThread对象的内部类H设置了一个mCallback对象,其mCallback对象如下:

    public class ActivityCallbackHandler implements Handler.Callback {
        private Handler handler;
        private int launchActivity = -1;
    
        public ActivityCallbackHandler(Handler handler) {
            this.handler = handler;
            try {
                Class<?> innerClass = Class.forName("android.app.ActivityThread$H");
                Field field = innerClass.getDeclaredField("LAUNCH_ACTIVITY");
                field.setAccessible(true);
                launchActivity = (int) field.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == launchActivity) {
                handleLaunchActivity(msg);
            }
            handler.handleMessage(msg);
            return true;
        }
    
        private void handleLaunchActivity(Message msg) {
            Object activityClientRecord = msg.obj;
            try {
                Field intent = activityClientRecord.getClass().getDeclaredField("intent");
                intent.setAccessible(true);
                Intent intentValue = (Intent) intent.get(activityClientRecord);
                Intent rawIntent = intentValue.getParcelableExtra(ActivityHookHelper.RAW_INTENT);
                if (rawIntent != null) {
                    intentValue.setComponent(rawIntent.getComponent());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    在msg.what = LAUNCH_ACTIVITY时,msg.obj是一个ActivityClientRecord类型,其中有一个字段是intent,该intent存储的信息就是我们要启动Activity时的一些信息,所以在该intent中拿到真正需要启动的Intent(之前通过putExtra存起来了),然后改变当前的intent的component,将其设置为真正要启动的intent的component,已达到创建真正启动类的结果。

    4、总结

    经过了上述的分步骤实现,已经可以将一个插件的Activity启动起来了。具体的demo可以在github上下载到:
    宿主:https://github.com/hwldzh/pluginTestDemo
    插件:https://github.com/hwldzh/plugin

    相关文章

      网友评论

          本文标题:Android插件化原理

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