1. 前言
在Android插件化开发指南——插件化技术简介一文中曾提到插件化技术的实现需要使用 Android 系统底层的各种 Hook 。在这篇博客中将来简单的介绍下什么是Hook ,以及在Android 中的一些实践。
Hook 中文意思为钩子,在编程中意为钩子函数。Hook 原理为在某段SDK 源码执行的过程中,通过把原始对象替换为我们的代理对象,我们就可以为所欲为,也就是Hook 。
其实在上篇Android插件化开发指南——类加载器最后给了一个案例,在案例中我们可以通过DexClassLoader 来动态加载外部apk 、dex 等中的字节码问题。这种感觉和插件化很类似,但是确有一个致命的问题没有解决,不妨再次观察下这个加载代码:
public class OtherActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
File file = new File(Environment.getExternalStorageDirectory(), "plugin-debug.apk");
DexClassLoader dexClassLoader = new DexClassLoader(
file.getAbsolutePath(),
getDir("cache_plugin", MODE_PRIVATE).getAbsolutePath(),
null,
getClassLoader()
);
try {
Class<?> aClass = dexClassLoader.loadClass("com.weizu.plugin.ToastUtils");
Method showInfo = aClass.getMethod("showInfo", Context.class);
showInfo.setAccessible(true);
showInfo.invoke(aClass.newInstance(), OtherActivity.this);
} catch (Exception e) {
e.printStackTrace();
}
}
}
但是需要注意的是,上面这种方式虽然可以加载外部插件,但是需要调用者每次都得到DexClassLoader 对象。虽然我们可以写为单例模式,但是从代码的书写角度来讲,每次都需要来得到外部dex 的DexClassLoader 比较麻烦。而且如果在一个应用中有很多个外部dex 或者apk 插件的时候,难道需要让程序员记住每个dex 中有哪些类?这显然不现实。所以需要通过另外一种方式来实现。
2. 将外部dex加载到宿主app的dexElements中
为了知道宿主App 中在哪里加载类的,所以需要从类加载器开始看起。这里从OtherActivity 中的getClassLoader() 方法开始追踪。如下图所示:
这里的Context为一个抽象类,且getClassLoader 方法为一个抽象方法,所以我们需要找到其实现类ContextImpl 。其源码可以查看链接:ContextImpl.java。
final LoadedApk mPackageInfo;
@Override
public ClassLoader getClassLoader() {
return mPackageInfo != null ?
mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}
所以需要先了解下mPackageInfo 这个变量在哪里赋值:
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread,
packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetrics());
return context;
}
也就是说这个LoadedApk 是和Main 线程挂钩的。这里继续查看getClassLoader 这个方法:
public ClassLoader getClassLoader(){
synchronized (this) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null);
}
return mClassLoader;
}
}
至于这个createOrUpdateClassLoaderLocked 方法中,其实也是使用ClassLoader.getSystemClassLoader() 来获取一个类加载器。所以这里就关注于这个方法:
ClassLoader.getSystemClassLoader()
这个方法最终会调用ClassLoader.createSystemLoader() 方法,该方法如下:
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
也就是说其实返回的是一个PathClassLoader 类加载器。也就是说在ContextImpl 文件中的getClassLoader() 方法调用之后,返回得到的是一个PathClassLoader 类加载器。找到PathClassLoader.java 的源文件:PathClassLoader.java。一目了然,这个类的功能基本来自其父类BaseDexClassLoader ,因为其只有两个构造方法。所以这里可以查看BaseDexClassLoader.java的源文件。
这个文件的代码也比较简单,如下:
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
...
}
从上面的代码中可以看出其实查找是从其属性字段DexPathList 中查找。也就是这里其实还需要进一步查找这个类的源码,因为DexPathList.java这个类的代码较多,这里就只查看pathList.findClass 方法。
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
观察上面的代码可以知道其实在执行findClass 的时候,其实是在dexElements 中进行查找。而这个dexElements 直接定义为一个数组:
private Element[] dexElements;
故而如果我们能够将外部插件的dex 或者apk 文件中的dexElements 加入到宿主app 的dexElements 中就可以完成预期。
不妨将上面的逻辑用时序图来进行表示:
那么对应的可以写一个工具类,用于完成上面的步骤。代码如下:
public class LoadUtils {
private static String pluginPath = "/sdcard/plugin-debug.apk";
public static void init(Context context) {
if(context == null) return;
try {
PathClassLoader classLoader = (PathClassLoader) context.getClassLoader();
Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field dexPathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
dexPathListField.setAccessible(true);
Object dexPathListValue = dexPathListField.get(classLoader);
Field dexElementsField = dexPathListValue.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object dexElementsValue = dexElementsField.get(dexPathListValue);
DexClassLoader dexClassLoader = new DexClassLoader(pluginPath,
context.getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(),
null, context.getClassLoader());
Object pluginDexPathListValue = dexPathListField.get(dexClassLoader);
Object pluginDexElementsValue = dexElementsField.get(pluginDexPathListValue);
int appDexElementsLength = Array.getLength(dexElementsValue);
int pluginDexElementsLength = Array.getLength(pluginDexElementsValue);
int newLength = appDexElementsLength + pluginDexElementsLength;
Class<?> componentType = dexElementsValue.getClass().getComponentType();
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(dexElementsValue, 0, newArray, 0, appDexElementsLength);
System.arraycopy(pluginDexElementsValue, 0, newArray, appDexElementsLength, pluginDexElementsLength);
dexElementsField.set(dexPathListValue, newArray);
} catch (Exception e){
e.printStackTrace();
}
}
}
那么对应的加载方法为:
public class OtherActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
LoadUtils.init(this);
try {
Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.ToastUtils");
Method showInfo = aClass.getMethod("showInfo", Context.class);
showInfo.setAccessible(true);
showInfo.invoke(aClass.newInstance(), OtherActivity.this);
} catch (Exception e) {
e.printStackTrace();
}
}
}
就可以直接使用context 中得到的PathClassLoader 来进行反射。结果为: 至于其余的细节部分可以参考:Android插件化开发指南——类加载器。
3. 插件中四大组件的调用思路
在上面的加载外部apk 中的类的时候,我们加载的只是一个普通的类。而在Android 中四大组件具有一定的特殊性,因为都需要在清单文件中注册。对于外部插件中的Activity 或者Service 等我们却又不可能在宿主App 中进行注册,所以实际上更加复杂。因为我们至少需要一种方式可以绕过系统加载的时候对于清单文件中配置信息的检查。
我们知道在实际场景中我们的插件中都会涉及到一些界面的更新,而这个更新过程也就是会涉及到Activity 的增加或者修改。所以实际中上面的简单加载外部普通类确实不适用。不妨来个简单的案例,因为在之前导出的plugin_debug.apk 中其实包含了一个Activity :
所以这里可以在app 模块中进行测试:
public class OtherActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
File file = new File(Environment.getExternalStorageDirectory(), "plugin-debug.apk");
DexClassLoader dexClassLoader = new DexClassLoader(
file.getAbsolutePath(),
getDir("cache_plugin", MODE_PRIVATE).getAbsolutePath(),
null,
getClassLoader()
);
try {
Class<?> aClass = dexClassLoader.loadClass("com.weizu.plugin.MainActivity");
Intent intent = new Intent(OtherActivity.this, aClass);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
这里其实也再次证明了四大组件的特殊性。所以我们需要更加合理的方式来绕开检查。
4. Hook
2.1 对startActivity进行Hook
2.1.1 AMS
要对startActivity 进行Hook ,那么就首选需要搞清楚在startActivity(intent) 之后发生了什么事情。那么首先需要了解的就是AMS (ActivityManagerService )虽然这个字面意思是Activity 的管理服务,但是其实四大组件都归它管。
AMS 主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似。当发起进程启动或者组件启动时,都会通过Binder 通信机制将请求传递给AMS ,AMS 再做统一处理。
既然AMS 管理着四大组件,那么为什么不直接在AMS 层对想要的功能进行Hook 呢?因为如果可以在AMS 层进行Hook ,很明显就是病毒程序了,所以在Android 中也不允许这么做。所以我们的Hook 点只能是在四大组件,因为至少需要保证受影响的只是当前程序,而不能影响别的程序。
这里以ActivityA 启动ActivityB 为例:
ActivityA 向AMS 发送需要启动ActivityB 的消息;AMS 保存ActivityB 的信息,同时AMS 要检查ActivityB 是否在清单文件中注册,如果注册了才继续下面的步骤;ActivityA 休眠,AMS 通知ActivityThread 去启动ActivityB ;
2.1.2 源码分析
从自定义的OtherActivity.java 文件中的startActivity(intent); 出发,可以看见下面的调用流程:
也就是说通过startActivity(intent); 最终会请求Activity 类的startActivityForResult 方法,在方法中可以看见两个成员变量:
mInstrumentation
mMainThread
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
那么首先看下Instrumentation 这个类中的execStartActivity 方法,地址为:Instrumentation.java。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
在execStartActivity 这个方法中,最终会通过ActivityManagerNative.getDefault().startActivity() 方法来启动目标的Activity 。而ActivityManagerNative.getDefault() 最后返回的其实也就是一个IActivityManager 对象,也就是常说的AMS 对象。不妨继续看看这个AMS 是如何得到的,我们继续追踪:
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}
而对于这里的单例泛型类Singleton 为:
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;
}
}
}
从上面的代码中可以知道,这里的AMS 定义为单例对象,这个单例使用上面的Singleton<T> 来进行修饰,真正的创建方法由new 的时候来指定。而在gDefault 的创建过程中,使用了IBinder 和ActivityManagerProxy 进行转换。
2.1.2.1 得到AMS实例对象
那么如果我们需要通过反射来得到应用程序中的AMS ,那么我们就可以在通过反射来得到这个单例对象gDefault 。然后再得到其中定义为泛型的mInstance ,也就是AMS 对象。然后就可以通过动态代理的方式来拦截AMS 调用的startActivity 方法。那么这里可以简单通过反射来得到AMS 对象,由于AMS 是在ActivityManagerNative.java 文件中,通过getDefault() 得到的,所以这里为:
public class HookAMSUtils {
public static void getActivityManagerService() {
try {
Class<?> aClass = Class.forName("android.app.ActivityManagerNative");
Field getDefault = aClass.getDeclaredField("gDefault");
getDefault.setAccessible(true);
Object getDefaultObj = getDefault.get(null);
Class<?> singletonClazz = Class.forName("android.util.Singleton");
Field mInstance = singletonClazz.getDeclaredField("mInstance");
mInstance.setAccessible(true);
Object amsObj = mInstance.get(getDefaultObj);
Log.e("TAG", "getActivityManagerService: " + amsObj.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
但是很遗憾: 因为我看的源代码为:Nougat - 7.1.2_r36。可以查看一下Android版本和代号等对应的API级别。可以查看:代号、标记和细分版本号,这里摘要部分:
代号 | 版本 | API 级别 |
---|
Oreo | 8.1.0 | API 级别 27 | Oreo | 8.0.0 | API 级别 26 | Nougat | 7.1 | API 级别 25 | Nougat | 7.0 | API 级别 24 | Marshmallow | 6.0 | API 级别 23 | Lollipop | 5.1 | API 级别 22 | Lollipop | 5.0 | API 级别 21 |
而我的项目中设置的buildToolsVersion 为30 。所以这里还是换为API 级别 25 ,下载对应的SDK : 创建一个对应版本的虚拟设备: 然后修改gradle 文件:
android {
compileSdkVersion 28
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.weizu.myapplication"
minSdkVersion 25
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
...
然后继续运行:
也就是说这里可以Hook容纳AMS 的单例得到得到AMS 对象。
上面分析的这个过程的时序图可以表示为:
2.1.3 对startActivity进行Hook
经过上面的逻辑分析,我们知道当一个Activity 去启动另一个Activity 后,最终根据一系列的调用会到AMS 的startActivity 方法,这里再次粘贴一下相关代码:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
在前面得到的AMS 对象,根据ActivityManagerNative 中getDefault() 的返回值类型,很容易我们知道其为IActivityManager 接口对象。
而如果我们需要在AMS 中做欺骗,即绕过清单文件中对四大组件的注册检查。这里需要使用动态代理模式,然后拦截AMS 的startActivity 方法。
2.1.3.1 创建AMS的代理对象
关于动态代理可以查看:Android常见设计模式——代理模式(Proxy Pattern)。
因为这里是代理接口IActivityManager.java ,源码地址为:IActivityManager.java。所以使用动态代理在进行invoke 的时候会得到很多的方法,而这里我们只需要startActivity ,这个方法定义为:
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
那么我们在动态代理方法中,就可以拦截到这个startActivity 方法。为了能够做到绕过清单文件检查的目的,我们可以事先在清单文件中注册一个代理的Activity ,然后在拦截到的startActivity 方法中进行Activity 对象的替换即可。比如下面的代码:
public class HookAMSUtils {
public static final String ORIGIN_INTENT = "ORIGIN_INTENT";
public static void getActivityManagerService(Context context, Class<? extends Activity> proxyActivityClazz) {
try {
Class<?> aClass = Class.forName("android.app.ActivityManagerNative");
Field getDefault = aClass.getDeclaredField("gDefault");
getDefault.setAccessible(true);
Object getDefaultObj = getDefault.get(null);
Class<?> singletonClazz = Class.forName("android.util.Singleton");
Field mInstance = singletonClazz.getDeclaredField("mInstance");
mInstance.setAccessible(true);
Object amsObj = mInstance.get(getDefaultObj);
Log.e("TAG", "getActivityManagerService: " + amsObj.toString());
Class<?> aClass1 = Class.forName("android.app.IActivityManager");
Object amsProxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{aClass1}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("TAG", "invoke: startActivity");
if (method.getName().equals("startActivity")) {
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
Intent oldIntent = (Intent) args[index];
String name = oldIntent.getStringExtra("NAME");
Log.e("TAG", "invoke: " + name);
Intent newIntent = new Intent(context, proxyActivityClazz);
newIntent.putExtra(ORIGIN_INTENT, oldIntent);
args[index] = newIntent;
}
return method.invoke(amsObj, args);
}
});
mInstance.set(getDefaultObj, amsProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
}
当然需要创建一个在清单文件中注册的ProxyActivity 类。然后在MainActivity 中测试:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoadUtils.init(this);
HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);
try {
Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.MainActivity");
Log.e("TAG", "onCreate: " + aClass.getName());
Intent intent = new Intent(MainActivity.this, aClass);
intent.putExtra("NAME", "123");
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
也就是说这里的跳转替换为了代理的Activity 对象。所以我们还需要在某个地方将原本的目标com.weizu.plugin.MainActivity 替换回来。当然,具体将这里的代理Activity 替换为原本的MainActivity 这里需要在ActivityThread 中完成。这个过程的时序图可以表示为:
从上图中可以知道在ActivityThread 中使用了Hanlder 来发送消息。所以我们可以处理Handler 的回调接口来进行Activity 的替换。故而首先第一步为得到ActivityThread 的实例对象,然后再将处理消息的方法设置为我们自己的方法。而ActivityThread 中定义了自己的一个静态引用,故而可以比较容易的得到该对象。对应的代码为:
public static void hookActivityThreadToLaunchActivity(){
try {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object activityThreadValue = sCurrentActivityThreadField.get(null);
Field mHField = activityThreadClazz.getDeclaredField("mH");
mHField.setAccessible(true);
Object mHValue = mHField.get(activityThreadValue);
Class<?> handlerClazz = Class.forName("android.os.Handler");
Field mCallBackField = handlerClazz.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mHValue, new HandlerCallBack());
} catch (Exception e) {
e.printStackTrace();
}
}
private static class HandlerCallBack implements Handler.Callback{
@Override
public boolean handleMessage(Message message) {
if(message.what == 100) {
handleLaunchActivity(message);
}
return false;
}
private void handleLaunchActivity(Message message) {
try {
Object r = message.obj;
Field intentField = r.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent newIntent = (Intent) intentField.get(r);
Intent oldIntent = newIntent.getParcelableExtra(ORIGIN_INTENT);
Log.e("TAG", "handleLaunchActivity: " + oldIntent.toString());
if(oldIntent != null){
intentField.set(r, oldIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
那么在调用的时候使用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoadUtils.init(this);
HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);
HookAMSUtils.hookActivityThreadToLaunchActivity();
}
public void jump(View view){
try {
Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.MainActivity");
Log.e("TAG", "onCreate: " + aClass.getName());
Intent intent = new Intent(MainActivity.this, aClass);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
即可实现点击文本框然后进行跳转。需要注意的是在跳转到插件的MainActivity 后,其实使用的布局的文件还是当前应用的布局文件。其实如果需要完整加载Activity 组件,其实还需要解决加载布局文件(资源文件)的问题。这部分内容将放置在下篇进行介绍。
5. References
References
6. 完整代码
涉及到的两个类的完整代码:
public class LoadUtils {
private static String pluginPath = "/sdcard/plugin-debug.apk";
public static void init(Context context) {
if(context == null) return;
try {
PathClassLoader classLoader = (PathClassLoader) context.getClassLoader();
Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field dexPathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
dexPathListField.setAccessible(true);
Object dexPathListValue = dexPathListField.get(classLoader);
Field dexElementsField = dexPathListValue.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object dexElementsValue = dexElementsField.get(dexPathListValue);
DexClassLoader dexClassLoader = new DexClassLoader(pluginPath,
context.getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(),
null, context.getClassLoader());
Object pluginDexPathListValue = dexPathListField.get(dexClassLoader);
Object pluginDexElementsValue = dexElementsField.get(pluginDexPathListValue);
int appDexElementsLength = Array.getLength(dexElementsValue);
int pluginDexElementsLength = Array.getLength(pluginDexElementsValue);
int newLength = appDexElementsLength + pluginDexElementsLength;
Class<?> componentType = dexElementsValue.getClass().getComponentType();
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(dexElementsValue, 0, newArray, 0, appDexElementsLength);
System.arraycopy(pluginDexElementsValue, 0, newArray, appDexElementsLength, pluginDexElementsLength);
dexElementsField.set(dexPathListValue, newArray);
} catch (Exception e){
e.printStackTrace();
}
}
}
public class HookAMSUtils {
public static final String ORIGIN_INTENT = "ORIGIN_INTENT";
public static void getActivityManagerService(Context context, Class<? extends Activity> proxyActivityClazz) {
try {
Class<?> aClass = Class.forName("android.app.ActivityManagerNative");
Field getDefault = aClass.getDeclaredField("gDefault");
getDefault.setAccessible(true);
Object getDefaultObj = getDefault.get(null);
Class<?> singletonClazz = Class.forName("android.util.Singleton");
Field mInstance = singletonClazz.getDeclaredField("mInstance");
mInstance.setAccessible(true);
Object amsObj = mInstance.get(getDefaultObj);
Log.e("TAG", "getActivityManagerService: " + amsObj.toString());
Class<?> aClass1 = Class.forName("android.app.IActivityManager");
Object amsProxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{aClass1}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("startActivity")) {
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
Intent oldIntent = (Intent) args[index];
Intent newIntent = new Intent(context, proxyActivityClazz);
newIntent.putExtra(ORIGIN_INTENT, oldIntent);
args[index] = newIntent;
}
return method.invoke(amsObj, args);
}
});
mInstance.set(getDefaultObj, amsProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void hookActivityThreadToLaunchActivity(){
try {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object activityThreadValue = sCurrentActivityThreadField.get(null);
Field mHField = activityThreadClazz.getDeclaredField("mH");
mHField.setAccessible(true);
Object mHValue = mHField.get(activityThreadValue);
Class<?> handlerClazz = Class.forName("android.os.Handler");
Field mCallBackField = handlerClazz.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mHValue, new HandlerCallBack());
} catch (Exception e) {
e.printStackTrace();
}
}
private static class HandlerCallBack implements Handler.Callback{
@Override
public boolean handleMessage(Message message) {
if(message.what == 100) {
handleLaunchActivity(message);
}
return false;
}
private void handleLaunchActivity(Message message) {
try {
Object r = message.obj;
Field intentField = r.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent newIntent = (Intent) intentField.get(r);
Intent oldIntent = newIntent.getParcelableExtra(ORIGIN_INTENT);
Log.e("TAG", "handleLaunchActivity: " + oldIntent.toString());
if(oldIntent != null){
intentField.set(r, oldIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
以及调用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoadUtils.init(this);
HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);
HookAMSUtils.hookActivityThreadToLaunchActivity();
}
public void jump(View view){
try {
Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.MainActivity");
Log.e("TAG", "onCreate: " + aClass.getName());
Intent intent = new Intent(MainActivity.this, aClass);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
清单文件中,注册权限和代理Activity :
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<activity android:name=".ProxyActivity"></activity>
|