likes
comments
collection
share

Android插件化原理

作者站长头像
站长
· 阅读数 5

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上下载到: 宿主:github.com/hwldzh/plug… 插件:github.com/hwldzh/plug…