Android插件化原理

一、前言

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

二、分析

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

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中:git

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()方法中:github

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文件,这就增长了该方法的复杂程度。bash

**那是否还有其余方法来实现插件化呢(方法二)?**从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对象。app

三、实现

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

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。ui

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,已达到建立真正启动类的结果。

四、总结

通过了上述的分步骤实现,已经能够将一个插件的Activity启动起来了。具体的demo能够在github上下载到: 宿主:github.com/hwldzh/plug… 插件:github.com/hwldzh/plug…

相关文章
相关标签/搜索