Activity 启动过程:
1.startActivity的时候最终会走到AMS的startActivity方法。
2.系统会检查一堆的信息验证这个Activity是否合法。
3.然后会回调ActivityThread的Handler里的 handleLaunchActivity。
4.在这里走到了performLaunchActivity方法去创建Activity并回调一系列生命周期的方法。
5.创建Activity的时候会创建一个LoaderApk对象,然后使用这个对象的getClassLoader来创建Activity。
6.我们查看getClassLoader()方法发现返回的是PathClassLoader,然后他继承自BaseDexClassLoader。
7.然后我们查看BaseDexClassLoader发现他创建时创建了一个DexPathList类型的pathList对象,
然后在findClass时调用了pathList.findClass的方法。
8.然后我们查看DexPathList类中的findClass发现他内部维护了一个Element[] dexElements的dex数组,
findClass时是从数组中遍历查找的。
插件化实现步骤:
1. 首先我们通过DexClassloader创建一个我们自己的DexClassloader对象去加载我们的插件apk。
//dex优化后路径
String cachePath = MainActivity.this.getCacheDir().getAbsolutePath();
//插件apk的路径
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/chajian.apk";
//创建一个属于我们自己插件的ClassLoader,我们分析过只能使用DexClassLoader
DexClassLoader mClassLoader = new DexClassLoader(apkPath, cachePath,cachePath, getClassLoader());
2. 拿到宿主apk里ClassLoader中的pathList对象和我们Classloader的pathList,因为最终加载时是执行了pathList.findClass方法。
//拿到本应用的ClassLoader
PathClassLoader pathLoader = (PathClassLoader) MyApplication.getContext().getClassLoader();
//获取宿主pathList
Object suZhuPathList = getPathList(pathLoader);
//获取插件pathList
Object chaJianPathList = getPathList(loader);
3. 然后我们拿到宿主pathList对象中的Element[]和我们创建的Classloader中的Element[]
//获取宿主数组
Object suzhuElements = getDexElements(suZhuPathList)
//获取插件数组
Object chajianElements = getDexElements(chaJianPathList)
4. 因为我们要加入一个dex文件,那么原数组的长度要增加,所有我们要新建一个新的Element类型的数组,长度是新旧的和
//获取原数组类型
Class> localClass = suzhu.getClass().getComponentType();
//获取原数组长度
int i = Array.getLength(suzhu);
//插件数组加上原数组的长度
int j = i + Array.getLength(chajian);
//创建一个新的数组用来存储
Object result = Array.newInstance(localClass, j);
5. 将我们的插件dex文件和宿主原来的dex文件都放入我们新建的数组中合并
//一个个的将dex文件设置到新数组中
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(suzhu, k));
} else {
Array.set(result, k, Array.get(chajian, k - i));
}
}
6. 将我们的新数组设值给pathList对象
setField(suZhuPathList, suZhuPathList.getClass(), "dexElements", result);
7. 代理系统启动Activity的方法,然后将要启动的Activity替换成我们占坑的Activity已达到欺骗系统去检查的目的。
//获取ActivityManagerNative的类
Class> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
//拿到gDefault字段
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
//从gDefault字段中取出这个对象的值
Object gDefault = gDefaultField.get(null);
// gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段
Class> singleton = Class.forName("android.util.Singleton");
//这个gDefault是一个Singleton类型的,我们需要从Singleton中再取出这个单例的AMS代理
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//ams的代理对象
Object rawIActivityManager = mInstanceField.get(gDefault);
现在我们已经拿到了这个ams的代理对象,现在我们需要创建一个我们自己的代理对象去拦截原ams中的方法。
class IActivityManagerHandler implements InvocationHandler {
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
Log.e("Main","startActivity方法拦截了");
// 找到参数里面的第一个Intent 对象
Intent raw;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
raw = (Intent) args[index];
//创建一个要被掉包的Intent
Intent newIntent = new Intent();
// 替身Activity的包名, 也就是我们自己的"包名"
String stubPackage = MyApplication.getContext().getPackageName();
// 这里我们把启动的Activity临时替换为 ZhanKengActivitiy
ComponentName componentName = new ComponentName(stubPackage, ZhanKengActivitiy.class.getName());
newIntent.setComponent(componentName);
// 把我们原始要启动的TargetActivity先存起来
newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);
// 替换掉Intent, 达到欺骗AMS的目的
args[index] = newIntent;
Log.e("Main","startActivity方法 hook 成功");
Log.e("Main","args[index] hook = " + args[index]);
return method.invoke(mBase, args);
}
return method.invoke(mBase, args);
}
}
然后我们使用动态代理去代理上面获取的ams。
// 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活,这里我们使用动态代理,
//动态代理依赖接口,而ams实现与IActivityManager
Class> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
//返回代理对象,IActivityManagerHandler是我们自己的代理对象,具体代码请下载demo
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
//将我们的代理设值给singleton的单例
mInstanceField.set(gDefault, proxy);
8. 等系统检查完了之后,再次代理拦截系统创建Activity的方法将原来我们替换的Activity再次替换回来,已到达启动不在AndroidManifest注册的目的。
try {
// 把替身恢复成真身
Field intent = obj.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent raw = (Intent) intent.get(obj);
Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
raw.setComponent(target.getComponent());
Log.e("Main","target = " + target);
} catch (Exception e) {
throw new RuntimeException("hook launch activity failed", e);
}
9. 最后调用我们之前写的这些代码,越早越好,在Application里调用也行,在Activity的attachBaseContext方法中也行。
Demo:https://github.com/suyimin/Plugin