} finally {
 try {
 if (null != inputStream) {
 inputStream.close();
 }
 if (null != fileOutputStream) {
 fileOutputStream.close();
 }
 } catch (Exception e) {
 Log.i(TAG, "extractAssets: " + e.getMessage());
 }
 }
 return false;
 }

2.2 ActivityManager的Hook

这一块儿的hook,简单来说就是通过动态代理的方式将ActivityManager.getService所获取到的Binder代理对象进行替换,那么我们就能够对诸如start Activity等方法进行入侵了。相关代码实现如下。RefInvoke.java类可到github上查看。

public static void hookAMN() {
 try {
 //通过反射获取到ActivityManager的class对象
 Class<?> mActivityManagerCls = RefInvoke.getClass("android.app.ActivityManager"); //首先通过反射获取到ActivityManager类中的单例对象IActivityManagerSingleton //然后通过反射获取到对象对象IActivityManagerSingleton的值 Object mIActivityManagerSingletonObj = RefInvoke.getStaticFieldValue(RefInvoke.getField(mActivityManagerCls, "IActivityManagerSingleton"), mActivityManagerCls); //获取到ActivityManager与AMS的Binder通信接口IActivityManager的class对象,用于后续生成对应的代理对象 Class<?> mIActivityManagerCls = RefInvoke.getClass(“android.app.IActivityManager”);
 if (null != mIActivityManagerSingletonObj) {
 //因为上述的单例对象是Singleton实现类,所以通过反射首先获取到该类中的mInstance属性
 Field mInstanceField = RefInvoke.getField(“android.util.Singleton”, “mInstance”);
 //然后通过反射获取到上述单例对象中的mInstance属性对应的值
 Object mInstance = RefInvoke.getFieldValue(mInstanceField, mIActivityManagerSingletonObj);
 //根据上述提供的接口以及当前的ClassLoader生成代理对象
 Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mIActivityManagerCls}, new AMSHookHelperInvocationHandler(mInstance));
 //将ActivityManager.getService获取到的单例对象替换成代理对象
 RefInvoke.setFieldValue(mInstanceField, mIActivityManagerSingletonObj, proxy);
 } else {
 Log.i(TAG, “IActivityManagerSingleton not exists”);
 }
 } catch (Exception e) {
 Log.i(TAG, "hook ATM failed " + e);
 }
 }接下来就是实现InvocationHandler接口对startActivity方法进行入侵了,相关代码如下。
public class AMSHookHelperInvocationHandler implements InvocationHandler {
 private static final String TAG = Constants.TAG + “AMSHookHandler”;//被代理对象
 private Object mBase;public AMSHookHelperInvocationHandler(Object base) {
 mBase = base;
 }@Override
 public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
 //劫持startActivity方法对上层应用真正要启动的Activity进行替换
 if (TextUtils.equals(method.getName(), “startActivity”)) {
 Log.i(TAG, “replace start up activity”);int index = -1;
 //获取上层应用传递过来的Intent对象
 for (int i = 0; i < objects.length; i++) {
 if (objects[i] instanceof Intent) {
 index = i;
 break;
 }
 }
 if (-1 == index) {
 Log.i(TAG, “not found intent in params”);
 return method.invoke(mBase, objects);
 }//这就是上层应用所需要启动的插件Activity对应的Intent了
 Intent realIntent = (Intent) objects[index];//根据宿主中预先声明的Activity生成对应的Intent对象用于替换上层应用传递过来的插件Activity相关的Intent对象,以达到欺骗AMS的目的
 Intent replacedStartUpIntent = realIntent.getParcelableExtra(Constants.REPLACED_START_UP_INTENT);
 if (null != replacedStartUpIntent) {
 Log.i(TAG, “origin intent is " + realIntent);
 realIntent.putExtra(Constants.REPLACED_START_UP_INTENT,”");
 replacedStartUpIntent.putExtra(Constants.START_UP_INTENT, realIntent);
 objects[index] = replacedStartUpIntent;
 Log.i(TAG, "replaced start up intent is " + replacedStartUpIntent);
 } else {
 Log.i(TAG, “replaced intent activity is null”);
 }
 }
 //继续通过Binder的方式调用到AMS
 return method.invoke(mBase, objects);
 }
 }有了上述对ActivityManager的hook过程,接下来我们就可以直接在应用中启动插件Activity了,使用方式如下。
public void click(View view) {
 int id = view.getId();
 switch (id) {
 case R.id.plugin:
 Intent intent = new Intent();
 ComponentName componentName = new ComponentName(getPackageName(), “com.xx.xx.pluginActivity”);
 intent.setComponent(componentName);
 intent.putExtra(Constants.REPLACED_START_UP_INTENT, createStartUpIntent());startActivity(intent);
 break;
 }}
 //使用宿主中预先声明好的Activity构造Intent对象用于欺骗AMS,后续统称为中转页面
 private Intent createStartUpIntent() {
 Intent startUpIntent = new Intent();
 ComponentName componentName = new ComponentName(DePluginApplication.getContext(), StandardStubActivity.class.getName());
 startUpIntent.setComponent(componentName);
 startUpIntent.putExtra(Constants.DEX_PATH, DePluginSP.getInstance(DePluginApplication.getContext()).getString(Constants.COPY_FILE_PATH, “”));
 return startUpIntent;
 }

虽然我们能够在宿主中直接去启动插件中的Activity并且不会报出ActivityNotFound异常了,但是最后会发现启动的Activity并不是插件中的Activity,而是我们的中转页面StandardStubActivity。因此为了能够实现最终启动Activity是插件中的Activity,我们还需要对ActivityThread中中的各个对象进行hook。

2.3 ActivityThread中的hook

这一块儿所涉及的流程就稍微复杂一点了,因此代码量也稍微多一点。所以,建议大家有时间多可以去瞅瞅Activity启动流程源码分析相关的文章。

2.3.1 mH的hook

对于ActivityThread中mH属性,如果采用生成Handler对象直接通过反射的方式去替换,最终系统会无情的给你抛出一个hook H failed java.lang.IllegalArgumentException;这是因为虽然mH属性对应的类继承了Handler对象,但是它的实际引用类型却是H。所以此路肯定是行不通的。这个时候我们不妨去看看Handler.java类中最终msg分发的dispatchMessage函数源码实现,如下:

public void dispatchMessage(Message msg) {
 if (msg.callback != null) {
 handleCallback(msg);
 } else {
 //重点我们看这里,如果当前Handler中的CallBack实例对象为空才会走到handleMessage方法
 //因此我们可以为mH这个继承了Handler的实例对象构造一个实现了CallBack的接口实例对象,那说干就干
 if (mCallback != null) {
 if (mCallback.handleMessage(msg)) {
 return;
 }
 }
 handleMessage(msg);
 }
 }

所以我们首先构造一个实现了Handler中CallBack接口的实例对象,并通过反射的方式将这个实例对象赋值给mH对象。代码如下:

public static void hookH() {
 try {
 //通过反射获取到ActivityThread实例对象
 Object sCurrentActivityThread = RefInvoke.getStaticFieldValue(RefInvoke.getField(“android.app.ActivityThread”, “sCurrentActivityThread”), RefInvoke.getClass(“android.app.ActivityThread”));
 //获取到ActivityThread中的mH实例对象
 Field mHField = RefInvoke.getField(sCurrentActivityThread.getClass(), “mH”);
 Handler mH = (Handler) RefInvoke.getFieldValue(mHField, sCurrentActivityThread);
 //首先通过反射获取到Handler中的mCallBack属性
 //通过反射的方式将mH实例对象中的mCallBack属性赋值为ActivityThreadHandler的实例对象
 RefInvoke.setFieldValue(RefInvoke.getField(Handler.class, “mCallback”), mH, new ActivityThreadHandler(mH));
 Log.i(TAG, “hook H complete”);
 } catch (Exception e) {
 Log.i(TAG, "hook H failed " + e);
 }
 }接着就是在CallBack中的handleMessage方法中对Activity的启动进行拦截了,然后将需要加载的Activity替换成插件中的Activity,并将加载Activity的ClassLoader对象替换成以插件apk生成的ClassLoader对象,最后在ActivityThread中实际所加载的Activity就是插件中的Activity了。
public boolean handleMessage(@NonNull Message message) {
 int what = message.what;
 switch (what) {
 //这里为什么是159可以到ActivityThread中找到答案
 case 159:
 //首先获取从AMS中传递过来的ClientTransaction对象
 Object object = message.obj;
 try {
 //这里的CallBack对象就是实现Activity生命周期的各个对象了
 List mActivityCallbacks = RefInvoke.on(object, “getCallbacks”).invoke();
 //获取开始执行Activity onCreate方法的实例对象,并将其中的Intent对象中的ComponentName对象替换成插件Activity对应的ComponentName对象
 Class<?> mLaunchActivityItemCls = RefInvoke.getClass(“android.app.servertransaction.LaunchActivityItem”);
 for (Object obj : mActivityCallbacks) {
 if (mLaunchActivityItemCls.isInstance(obj)) {
 Intent intent = getIntent(mLaunchActivityItemCls, obj);
 if (null == intent) {
 break;
 }
 //只对需要实现插件化的Activity进行拦截,防止出现误拦截的情况
 String path = intent.getStringExtra(Constants.DEX_PATH);
 if (TextUtils.isEmpty(path)) {
 Log.i(TAG, “dex path is empty,so do need replace class loader”);
 break;
 }
 //替换成加载插件类的ClassLoader
 replaceClassloader(mLaunchActivityItemCls, obj, path);
 //将实际需要加载的Activity替换成插件中的Activity
 replace(intent);
 break;
 }
 }
 } catch (Exception e) {
 Log.e(TAG, "getActivityToken failed " + e.getMessage());
 }
 break;
 default:}
 mBase.handleMessage(message);
 return true;
 }
2.3.2 ClassLoader的hook

对于加载Activity的ClassLoader替换则稍显复杂了,因此在代码实现之前我们还是简单去看一下源码,了解一下如何将加载Activity的ClassLoader替换成加载插件中Activity的ClassLoader。

源码解析

ActivityThread中对Activity初始化是在performLaunchActivity中完成,部分源码如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 ActivityInfo aInfo = r.activityInfo;
 //生成LoadedApk对象
 if (r.packageInfo == null) {
 r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
 Context.CONTEXT_INCLUDE_CODE);
 }ComponentName component = r.intent.getComponent();
 if (component == null) {
 component = r.intent.resolveActivity(
 mInitialApplication.getPackageManager());
 r.intent.setComponent(component);
 }if (r.activityInfo.targetActivity != null) {
 component = new ComponentName(r.activityInfo.packageName,
 r.activityInfo.targetActivity);
 }
 //为当前启动的Activity生成对应的Context对象
 ContextImpl appContext = createBaseContextForActivity®;
 Activity activity = null;
 try {
 //获取ClassLoader对象以加载需要启动的Activity类
 java.lang.ClassLoader cl = appContext.getClassLoader();
 activity = mInstrumentation.newActivity(
 cl, component.getClassName(), r.intent);
 …
 } catch (Exception e) {
 if (!mInstrumentation.onException(activity, e)) {
 throw new RuntimeException(
 "Unable to instantiate activity " + component• ": " + e.toString(), e);
 }
 }
 …return activity;
 }在该方法中首先会根据要启动Activity中所携带的ApplicationInfo等对象生成LoadedApk对象,然后通过LoadedApk中携带的ClassLoader属性为当前需要启动的Activity生成对对应的Context对象,并通过该ClassLoader加载需要启动的Activity类。其中Context对象创建是在ContextImpl的createActivityContext方法中完成,部分源码如下:
static ContextImpl createActivityContext(ActivityThread mainThread,
 LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
 Configuration overrideConfiguration) {
 if (packageInfo == null) throw new IllegalArgumentException(“packageInfo”);String[] splitDirs = packageInfo.getSplitResDirs();
 //获取LoadedApk中的ClassLoader对象,并根据该ClassLoader创建对应的Context对象
 ClassLoader classLoader = packageInfo.getClassLoader();
 …
 ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
 activityToken, null, 0, classLoader);
 …
 final ResourcesManager resourcesManager = ResourcesManager.getInstance(); context.setResources(resourcesManager.createBaseActivityResources(activityToken,
 packageInfo.getResDir(),
 splitDirs,
 packageInfo.getOverlayDirs(),
 packageInfo.getApplicationInfo().sharedLibraryFiles,
 displayId,
 overrideConfiguration,
 compatInfo,
 classLoader));
 context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
 context.getResources());
 return context;
 }

既然最终加载Activity类的ClassLoader是从LoadedApk对象来的,所以我们只需要将上述getPackageInfo方法所得来的LoadedApk对象中的ClassLoader对象替换成通过插件apk生成的插件就行了。但是有个问题就是这里的getPackageInfo方法我们并不能hook住,因此并不能把握住该方法的调用时机,所以通过getPackageInfo方法生成的LoadedApk我们并不能动态的去替换掉;

因此这里我们所采用的是直接通过getPackageInfo方法创建一个属于我们自己的LoadedApk对象,至于我们为什么可以这样做,还是需要去看一下源码才知道。performLaunchActivity中调用的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 ref;
 if (differentUser) {
 ref = null;
 //最终会进入到这个if中
 //可以看到的是首先会去mPackages这个ArrayMap中查找该Activity所属的包名是否存在LoadedApk的缓存,如果存在缓存则直接使用
 } else if (includeCode) {
 ref = mPackages.get(aInfo.packageName);
 } else {
 ref = mResourcePackages.get(aInfo.packageName);
 }
 //如果不存在缓存则重新生成LoadedApk对象,并添加到mPackages
 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(packageInfo));
 } else {
 mResourcePackages.put(aInfo.packageName,
 new WeakReference(packageInfo));
 }
 }
 return packageInfo;
 }
 }

根据上述的源码解析,如何生成对应LoadedApk对象以及ClassLoader对象我们的思路就很明确了。

(1)首先我们通过反射的方式以插件apk生成对应的ApplicationInfo对象以及CompatibilityInfo;

(2)接着调用如下方法生成对应的LoadedApk对象并将ActivityInfo所对应的ApplicationInfo中的PackageName设置为插件的ApplicatioInfo,这样Activity初始化的时候就能够直接获取缓存中的我们所生成的LoadedApk对象了;

(3)最后再将以插件Apk生成的ClassLoader再以反射的方式赋值给LoadedApk对象中的成员变量,到这里整个Activity的插件化也就完成了一大半了。

public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
 int flags) {
 …


至于如何创建插件apk对应的ApplicationInfo对象,这里我们提供了两种方式,第一种是通过反射调用到类PackageParser中的generateApplicationInfo方法;第二种则稍显简单,直接获取到宿主应用到PackageManager对象,然后调用到PackageManager中的getPackageArchiveInfo方法。篇幅有限,这里介绍第二种,有需要的可到github上了解一下第一种方法。

ApplicationInfo对象创建

private static ApplicationInfo getApplicationInfoByPackageArchiveInfo(String pluginPath) {
 //根据插件apk路径首先生成对应的PackageInfo对象
 PackageManager packageManager = DePluginApplication.getContext().getPackageManager();
 if (null == packageManager) {
 Log.i(TAG, “get PackageManager failed”);
 return null;
 }
 PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginPath, 0);
 if (null == packageInfo) {
 Log.i(TAG, “get packageInfo failed”);
 return null;
 }
 //返回PackageInfo中的ApplicationInfo对象
 return packageInfo.applicationInfo;
 }public static ApplicationInfo generateApplicationInfo(String pluginPath) {
 try {
 ApplicationInfo applicationInfo = getApplicationInfoByPackageArchiveInfo(pluginPath);
 if (null == applicationInfo) {
 Log.i(TAG, “get applicationInfo failed”);
 return null;
 }
 //设置资源加载路径(后续会有对应的文章)
 applicationInfo.sourceDir = pluginPath;
 applicationInfo.publicSourceDir = pluginPath;
 //设置隶属于哪一个uid
 applicationInfo.uid = Process.myUid();
 return applicationInfo;
 } catch (Exception e) {
 Log.i(TAG, "generateApplicationzInfo failed " + e.getMessage());
 }
 return null;
 }

通过上述的getPackageInfo方法生成对应的LoadedApk对象在有了ApplicationInfo对象之后,那么就差一个CompatibilityInfo对象,对于该对象我们可以直接在CompatibilityInfo.java源码中找到一个默认的实例对象,即DEFAULT_COMPATIBILITY_INFO。因此我们可以直接通过反射的方式拿到该对象。

生成LoadedApk对象并添加到缓存

有了上面的准备,那么我们就只欠东风了。getPackageInfo方法源码实现如下:

private void replaceClassloader(Class<?> mLaunchActivityItemCls, Object obj, String path) throws Exception {
 //获取到ActivityThread对象
 Object sCurrentActivityThread = RefInvoke.getStaticFieldValue(RefInvoke.getField(“android.app.ActivityThread”, “sCurrentActivityThread”), RefInvoke.getClass(“android.app.ActivityThread”));
 //获取缓存LoadedApk对象的map集合;
 //当然这里也可以省略,因为在调用getPackageInfo方法的结尾已经自动给我们做了缓存
 Field mPackagesField = RefInvoke.getField(sCurrentActivityThread.getClass(), “mPackages”);
 if (null == mPackagesField) {
 Log.i(TAG, “get mPackages field failed”);
 return;
 }
 ArrayMap mPackages = (ArrayMap) mPackagesField.get(sCurrentActivityThread);
 if (null == mPackages) {
 Log.i(TAG, “can not get mPackages”);
 return;
 }
 //获取插件apk对应的ApplicationInfo对象
 ApplicationInfo applicationInfo = Utils.generateApplicationInfo(DePluginSP.getInstance(DePluginApplication.getContext()).getString(Constants.COPY_FILE_PATH, “”));if (null != applicationInfo) {
 //获取CompatibilityInfo类中的默认对象
 Object defaultCompatibilityInfo = RefInvoke.getStaticFieldValue(RefInvoke.getField(“android.content.res.CompatibilityInfo”, “DEFAULT_COMPATIBILITY_INFO”), RefInvoke.getClass(“android.content.res.CompatibilityInfo”));
 //调用ActivityThread中的getPackageInfo方法生成对应的LoadedApk对象
 Object loadedApk = RefInvoke.on(sCurrentActivityThread, “getPackageInfo”, ApplicationInfo.class, RefInvoke.getClass(“android.content.res.CompatibilityInfo”), int.class).invoke(applicationInfo, defaultCompatibilityInfo, Context.CONTEXT_INCLUDE_CODE);String pluginPkgName = applicationInfo.packageName;
if (!TextUtils.isEmpty(pluginPkgName)) {
 Log.i(TAG, "plugin pkg name is " + pluginPkgName);
 //替换ActivityInfo中的包名为插件的包名,用于在启动过程中能够直接获取到我们创建的LoadedApk对象
 replacePkgName(mLaunchActivityItemCls, obj, pluginPkgName);
 //为LoadedApk对象生成插件ClassLoader对象用于加载我们需要启动的Activity类
 setClassloader(loadedApk, path);
 //因为我们在调用getPackageInfo方法的时候有自动为我们缓存LoadedApk对象,因此该步骤可省略
 //mPackages.put(pluginPkgName, new WeakReference<>(loadedApk));
 } else {
 Log.i(TAG, “get plugin pkg name failed”);
 }
 } else {
 Log.i(TAG, “can not get application info”);
 }
 }
 //替换ActivityInfo中的包名为插件apk包名
 private void replacePkgName(Class<?> mLaunchActivityItemCls, Object obj, String pkgName) throws Exception {