Android P+通过反射调用系统API实现高级功能
通过反射调用系统API已经不算是什么新鲜事了,不过在Android P之后,Google对隐藏API的限制更加严格,不能再直接通过反射去调用系统API了,必须通过特定手段先打开隐藏API的限制,具体原理可以移步另一种绕过 Android P以上非公开API限制的办法,这里我们只讲实战用法。
1. 打开隐藏API
1.1 导入FreeReflection包
新建AS项目后,打开项目的settings.gradle
,在repositories
闭包中添加
maven { url 'https://jitpack.io' }
然后在具体module的build.gradle
中的dependencies
中加入以下依赖
implementation 'com.github.tiann:FreeReflection:3.1.0'
1.2 打开隐藏API
在moudle中添加一个继承自Application的类(有关Application的相关信息请自行查阅相关文档)并在AndroidManifest.xml中注册。
例:
新建的类
public class ShellApplication extends Application {
......
}
AndroidManifest.xml中注册
<application
android:name=".ShellApplication"
......
然后需要重写Application类的attachBaseContext
方法,并在这个方法中调用FreeReflection
库打开隐藏API
// ignore other import
import me.weishu.reflection.Reflection;
public class ShellApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Reflection.unseal(base);
}
}
至此,隐藏API就打开了,我们就能够像之前一样使用反射了
2. 反射系统服务并调用相应方法
不同Android版本,系统服务的架构可能存在差别,需要根据具体版本进行处理,思路基本相同,这里以Android Q为例,反射ActivityTaskManager
,注意ActivityTaskManager
是在Android Q中加入的,低版本不适用。
在开始反射之前,我们需要先找到我们想反射的方法是属于哪一个系统服务里面的,思路比较简单,跟流程,这里我需要反射的是startActivityAndWait
方法,这个方法是am
命令带-W
参数启动应用的实现,调用流程是Am
->ActivityManagerService
->ActivityTaskManagerService
。
但是我们知道,系统Service都是通过binder去调用的,这里ActivityTaskManagerSerice
是IActivityTaskManager
进行调用的,并且由ActivityTaskManager
进行代理,在ActivityTaskManager
中使用单例模式保存了IActivityTaskManager
的实例,并且提供了一个静态方法getService()
用于获取该实例(其实Android其他的系统服务都在对应的xxxManager中提供了getService()
方法去获取对应的实例),这就是我们着手的地方(反射不到实例就毫无用处)
代码如下:
Class atmClazz = Class.forName("android.app.ActivityTaskManager");
@SuppressLint("BlockedPrivateApi")
Method methodGetService = atmClazz.getDeclaredMethod("getService");
methodGetService.setAccessible(true);
// atmObj即IActivityTaskManager
Object atmObj = methodGetService.invoke(null);
获取到IActivityTaskManager
之后就是反射对应的方法了,例如我需要反射startActivityAndWait
方法,那么对应代码如下:
Class singletonClazz = Class.forName("android.app.IActivityTaskManager");
Class clazzIApplicationThread = Class.forName("android.app.IApplicationThread");
Class clazzProfileInfo = Class.forName("android.app.ProfilerInfo");
Method startMethod = singletonClazz.getDeclaredMethod("startActivityAndWait",
clazzIApplicationThread,
String.class,
Intent.class,
String.class,
IBinder.class,
String.class,
int.class,
int.class,
clazzProfileInfo,
Bundle.class,
int.class
);
startMethod.setAccessible(true);
startMethod.invoke(atmObj,
null, // caller
context.getPackageName(), // call package 这里context传入ApplicationContext即可
intent, // intent -> ActivityManagerShellCommand -> MakeIntent
// intent 可以通过context.getPackageManager().getLaunchIntentForPackage("com.xxx.xxx")获取
null, // resolveType
null, // resultInfo
null, // resultWho
0, // request code
0, // flags
null, // profile info
null,
0 // userId
);