Android的加壳与脱壳 之 Android类加载器(二)
2020-07-19 本文已影响0人
Sharkchilli
前言
上一章我们讲述了Android中如何动态加载dex,并执行其中类中的方法。本文我们探讨Activity的加载,Activity加载调用OnCreate时所遇到的问题。
需加载Dex编写
我们可以在上一章中,加入要加载的Activity,其代码如下:
TestActivity.java
package com.shark.testdex;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
public class TestActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("Shark","com.shark.testdex.TestActivity.onCreate()调用");
}
}
加载apk编写
对上一章使用同样的方式,去加载调用TestActivity的onCreate方法
//直接使用context.startActivity(new Intent(context,clazz));方式能否启动Activity?
public void startTestActivity(Context context,String dexfilepath){
File optfile=context.getDir("opt_dex",0);
File libfile=context.getDir("lib_path",0);
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),
libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
Class<?> clazz=null;
try {
clazz = dexClassLoader.loadClass("com.shark.testdex.TestActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(clazz!=null){
context.startActivity(new Intent(context,clazz));
}
}
运行结果如下:
image.png
查看Android源码可以发现组件是由LoadedApk的mClassLoader加载,而这个ClassLoader是一个PathClassLoader,所以由我们自己创建的ClassLoader并不在这个mClassLoader中。因此ActivityManagerService无法管理到TestActivity,所以报出上面错误。
这里将有关Android app的加载流程 Activity加载流程 我们这里暂时不讨论只要先记住结论就好了
两种解决方案:
1、替换系统组件类加载器为我们的DexClassLoader,同时设置DexClassLoader的parent为系统组件类加载器;
2、打破原有的双亲关系,在系统组件类加载器和BootClassLoader的中间插入我们自己的DexClassLoader即可;
方案一
image.png使用第一种解决方案代码如下
具体解释在注释中已经写明,这里需要阅读Android源码,本文暂时不做讨论
//替换系统组件类加载器为我们的DexClassLoader,同时设置DexClassLoader的parent为系统组件类加载器;
public void startTestActivityFirstMethod(Context context,String dexfilepath){
File optfile=context.getDir("opt_dex",0);
File libfile=context.getDir("lib_path",0);
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),
libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
//替换系统组件类加载器为我们的DexClassLoader
replaceClassloader(dexClassLoader);
Class<?> clazz=null;
try {
clazz = dexClassLoader.loadClass("com.shark.testdex.TestActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
context.startActivity(new Intent(context,clazz));
}
/**
* 我们的目的为替换系统组件类加载器,
* 通过Android源码得知mClassLoader在LoadedApk的mClassLoader中
* 而LoadedApk在ActivityThread的mPackages中 key为包名,ActivityThread中currentActivityThread就是这个类的实例
* 使用包名得到WeakReference再得到LoadedApk再得到mClassLoader并且替换之
* 所以以下代码是如何去获得mClassLoader
* @param classloader
*/
public void replaceClassloader(ClassLoader classloader){
try {
//得到sCurrentActivityThread
Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThreadObj=currentActivityThreadMethod.invoke(null);
//得到mPackages 其定义如下
//final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj);
WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
Object loadedApkObj=wr.get();
Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
//private ClassLoader mClassLoader;
Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
//下面得到的同一个ClassLoader,也就是mClassLoader,所以上面创建指定的父节点就是老的mClassLoader
ClassLoader temoClassLoader = (ClassLoader) mClassLoaderField.get(loadedApkObj);
ClassLoader temoClassLoader2 = MainActivity.class.getClassLoader();
mClassLoaderField.set(loadedApkObj,classloader);
} catch (Exception e) {
e.printStackTrace();
}
}
运行
image.png可以看到结果正确得到了,我们将自己创建的DexClassLoader替换过去,因为双亲委派模式既不会影响原来的类加载器也可以将我们新的DexClassLoader在ActivityManagerService管理到
方案二
image.png使用第二种解决方案代码如下
原理和第一种一样,只不过这个新的类加载器插到了中间
//打破原有的双亲关系,在系统组件类加载器和BootClassLoader的中间插入我们自己的DexClassLoader
public void startTestActivitySecondMethod(Context context, String dexfilepath) {
File optfile = context.getDir("opt_dex", 0);
File libfile = context.getDir("lib_path", 0);
//得到当前MainActivity的ClassLoader即mClassLoader
ClassLoader pathClassloader = MainActivity.class.getClassLoader();
//得到mClassLoader的父节点
ClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();
//创建新的Classloader设置其父节点为mClassLoader的父节点
DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,
optfile.getAbsolutePath(), libfile.getAbsolutePath(), bootClassloader);
try {
//利用反射设置mClassLoader的父节点为我们新建的dexClassLoader
Field parentField = ClassLoader.class.getDeclaredField("parent");
parentField.setAccessible(true);
parentField.set(pathClassloader, dexClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
/**
* 下面代码打印插入后的类加载器父子关系
* I/SharkChilli: this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.shark.loaddex-2/base.apk"],nativeLibraryDirectories=[/data/app/com.shark.loaddex-2/lib/x86, /system/lib, /vendor/lib]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/3.dex"],nativeLibraryDirectories=[/data/user/0/com.shark.loaddex/app_lib_path, /system/lib, /vendor/lib]]]
* I/SharkChilli: this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/3.dex"],nativeLibraryDirectories=[/data/user/0/com.shark.loaddex/app_lib_path, /system/lib, /vendor/lib]]]--parent:java.lang.BootClassLoader@57e43f5
*
* 其父子关系如下:
* PathClassLoader->DexClassLoader(我们新建的类加载器)->BootClassLoader
*/
ClassLoader tmpClassloader = pathClassloader;
ClassLoader parentClassloader = pathClassloader.getParent();
while (parentClassloader != null) {
Log.i("SharkChilli", "this:" + tmpClassloader + "--parent:" + parentClassloader);
tmpClassloader = parentClassloader;
parentClassloader = parentClassloader.getParent();
}
Class<?> clazz = null;
try {
clazz = dexClassLoader.loadClass("com.shark.testdex.TestActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
context.startActivity(new Intent(context, clazz));
}