系列文章目录:
插件化基础(一)——加载插件的类
插件化基础(二)——加载插件资源
插件化基础(三)——启动插件组件
由于插件没有直接运行的环境,所以我们不能直接像平常那样启动它们,一般都需要通过宿主提供环境,或者将插件中要启动的内容添加到宿主中,借由宿主启动。启动插件主要有三种方式:
- 占位式:在宿主中为每种组件设置一个代理,通过代理将宿主的运行环境传递给插件,并协助控制插件组件的生命周期
- Hook 式:分别 Hook AMS 和 Handler,使得没在宿主 AndroidManifest 中注册的,插件中的 Activity 绕过 AMS 检查得以启动
- LoadedApk 式:也需要 Hook AMS 和 Handler,只是加载加载插件类的 ClassLoader 的构造方式与 Hook 式不同,严格来说不是一种单独的插件化方法,而是在 Hook 式上的演进
一、占位式
占位指的是宿主中四大组件的代理,插件组件的启动都要经由代理处理,代理的主要作用有二:
- 将本体传递给插件作为插件的运行环境
- 管理插件中各组件的生命周期
整个结构如下:
1.1 Activity
先看宿主如何启动插件的入口,首先定义一个 Activity 的接口:
public interface ActivityInterface {
// 给插件传入宿主运行环境
void insertAppContext(Activity activity);
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
}
插件的 BaseActivity 要实现这个接口:
public class BaseActivity extends Activity implements ActivityInterface {
protected Activity hostActivity;
@Override
public void insertAppContext(Activity activity) {
hostActivity = activity;
}
// 由于插件没有运行环境,所以像 context、super、this 这些是不能使用的,因此
// 无法调用 super.onCreate(),只能添加 @SuppressLint("MissingSuperCall")
@SuppressLint("MissingSuperCall")
@Override
public void onCreate(Bundle savedInstanceState) {
}
@SuppressLint("MissingSuperCall")
@Override
public void onStart() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onResume() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onPause() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onStop() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onDestroy() {
}
@Override
public void setContentView(int layoutResID) {
if (hostActivity != null) {
hostActivity.setContentView(layoutResID);
}
}
@Override
public <T extends View> T findViewById(int id) {
if (hostActivity != null) {
return hostActivity.findViewById(id);
}
// super 其实是用不了的
return super.findViewById(id);
}
}
由于没有直接运行的环境,所以像 setContentView()、findViewById() 这些方法都需要借用宿主的 Activity 实现。
插件中的所有 Activity 需要继承 BaseActivity:
public class PluginActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin);
}
}
宿主这边,启动插件的入口 Activity 时,其实是先启动代理的 ProxyActivity,将插件入口 Activity 的全类名存入 Intent:
private void startPlugin() {
// 待加载插件路径
String pluginPath = getFilesDir().getAbsolutePath() + File.separator + "placeholder_plugin-debug.apk";
// 获取插件的 PackageInfo
PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);
if (packageInfo != null) {
// 需要插件入口 Activity 在 AndroidManifest 中第一个定义
ActivityInfo activityInfo = packageInfo.activities[0];
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", activityInfo.name);
startActivity(intent);
}
}
代理从 Intent 中拿到要启动的插件类,转换成 ActivityInterface,再执行插件 Activity 的生命周期方法:
public class ProxyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String target = getIntent().getStringExtra("className");
try {
Class<?> targetClass = getClassLoader().loadClass(target);
ActivityInterface activityInterface = (ActivityInterface) targetClass.newInstance();
// 给插件注入宿主环境
activityInterface.insertAppContext(this);
// 调用插件 Activity 的 onCreate()
activityInterface.onCreate(new Bundle());
} catch (Exception e) {
e.printStackTrace();
}
}
}
这样宿主就能启动插件的入口 Activity 了,而插件内部 Activity 之间跳转,实际是要先跳到宿主的代理 ProxyActivity (因为需要通过 ProxyActivity 模拟 Activity 栈),再跳到真正要启动的插件 Activity,示例代码如下:
public class PluginActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin);
// 启动插件的 TestActivity
findViewById(R.id.button_activity).setOnClickListener((listener) -> {
Intent intent = new Intent(hostActivity, TestActivity.class);
startActivity(intent);
});
}
}
BaseActivity 要重写 startActivity(),将目标 Activity 的全类名放在 Intent 的 className 中传递到宿主的代理 ProxyActivity 中:
@Override
public void startActivity(Intent intent) {
Intent newIntent = new Intent();
newIntent.putExtra("className", intent.getComponent().getClassName());
hostActivity.startActivity(newIntent);
}
ProxyActivity 的 startActivity() 要先启动一个新的 ProxyActivity 实例(模拟 Activity 栈),Intent 还是用 className 保存目标 Activity 类名:
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent proxyIntent = new Intent(this, ProxyActivity.class);
proxyIntent.putExtra("className", className);
super.startActivity(proxyIntent);
}
这样新的 ProxyActivity 启动后,在 onCreate() 中就会反射创建目标 Activity 实例并调用其 onCreate()。
1.2 Service
跟 Activity 是类似的套路,先定义接口:
public interface ServiceInterface {
void insertAppContext(Service service);
void onCreate();
int onStartCommand(Intent intent, int flags, int startId);
void onDestroy();
}
插件中待启动的 Service 需要实现这个接口:
public class TestService extends Service implements ServiceInterface {
private static final String TAG = TestService.class.getSimpleName();
public TestService() {
}
@Override
public void insertAppContext(Service service) {
}
@Override
public void onCreate() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: TestService");
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在插件的 UI 中启动 TestService:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin);
...
// 启动插件 Service
findViewById(R.id.button_service).setOnClickListener((listener) ->
startService(new Intent(hostActivity, TestService.class))
);
}
然后重写 BaseActivity 的 startService():
@Override
public ComponentName startService(Intent service) {
Intent intent = new Intent();
intent.putExtra("className", service.getComponent().getClassName());
return hostActivity.startService(intent);
}
将 TestService 的全类名通过 Intent 传到宿主的 ProxyActivity 中:
@Override
public ComponentName startService(Intent service) {
Intent intent = new Intent(this, ProxyService.class);
intent.putExtra("className", service.getStringExtra("className"));
return super.startService(intent);
}
启动宿主中的代理 Service:
public class ProxyService extends Service {
public ProxyService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String serviceName = intent.getStringExtra("className");
try {
Class<?> clazz = getClassLoader().loadClass(serviceName);
ServiceInterface serviceInterface = (ServiceInterface) clazz.newInstance();
serviceInterface.insertAppContext(this);
serviceInterface.onStartCommand(intent, flags, startId);
} catch (Exception e) {
e.printStackTrace();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在 ProxyService 的 onStartCommand() 中实例化插件的 TestService 并调用其 onStartCommand()。
1.3 BroadcastReceiver
BroadcastReceiver 分为动态注册和静态注册两种方式,对两种方式的插件化处理也不同。
动态注册
动态注册的 BroadcastReceiver 的处理方式与前面类似,先来个接口:
public interface ReceiverInterface {
void onReceive(Context context, Intent intent);
}
插件的 BroadcastReceiver 实现该接口:
public class TestReceiver extends BroadcastReceiver implements ReceiverInterface {
private static final String TAG = TestReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive: " + intent);
}
}
动态注册、注销、发送广播:
public class PluginActivity extends BaseActivity {
private BroadcastReceiver mReceiver;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin);
// 注册插件动态广播
findViewById(R.id.button_register_dynamic_broadcast).setOnClickListener((listener) -> {
IntentFilter dynamicFilter = new IntentFilter();
dynamicFilter.addAction("com.apk.plugin.receiver.dynamic");
mReceiver = new TestReceiver();
// 在 BaseActivity 中重写
registerReceiver(mReceiver, dynamicFilter);
});
// 发送插件动态广播
findViewById(R.id.button_send_broadcast).setOnClickListener((listener) -> {
Intent intent = new Intent();
intent.setAction("com.apk.plugin.receiver.dynamic");
// 在 BaseActivity 中重写
sendBroadcast(intent);
});
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
}
BaseActivity 重写注册、注销、发广播的方法,都是要交给宿主的代理 ProxyActivity 处理:
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return hostActivity.registerReceiver(receiver, filter);
}
@Override
public void sendBroadcast(Intent intent) {
hostActivity.sendBroadcast(intent);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
hostActivity.unregisterReceiver(receiver);
}
其中注册广播的 registerReceiver() 要拿到待启动 BroadcastReceiver 的全类名给到代理的 ProxyReceiver:
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
String className = receiver.getClass().getName();
return super.registerReceiver(new ProxyReceiver(className), filter);
}
最后在代理中启动插件的 Receiver:
public class ProxyReceiver extends BroadcastReceiver {
private String className;
public ProxyReceiver(String className) {
this.className = className;
}
@Override
public void onReceive(Context context, Intent intent) {
try {
Class<?> clazz = context.getClassLoader().loadClass(className);
ReceiverInterface receiverInterface = (ReceiverInterface) clazz.newInstance();
receiverInterface.onReceive(context, intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
静态注册
静态注册的 BroadcastReceiver 都是在 AndroidManifest 文件中配置的,在应用安装时会系统会解析其 AndroidManifest 的内容,如果解析到静态广播就会进行注册。所以我们的思路就是,让未安装的插件 apk 也能被系统扫描解析,并注册静态广播。
源码中是通过 PackageParser.Package 的 parsePackage() 来解析 apk 的:
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}
if (packageFile.isDirectory()) {
parsed = parseClusterPackage(packageFile, flags);
} else {
parsed = parseMonolithicPackage(packageFile, flags);
}
cacheResult(packageFile, flags, parsed);
return parsed;
}
parsePackage() 解析的结果是一个 Package,而 PackageParser.Package 这个类保存着所有解析的结果:
public final static class Package implements Parcelable {
...
public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
// AndroidManifest 中注册的广播接收者集合,注意这个 Activity 不是四大组件的 Activity,而是 PackageParser 的内部类
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
public final ArrayList<Service> services = new ArrayList<Service>(0);
public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
public final ArrayList<String> requestedPermissions = new ArrayList<String>();
public ArrayList<String> protectedBroadcasts;
public Package parentPackage;
public ArrayList<Package> childPackages;
...
}
我们可以通过反射调用 parsePackage() 来解析插件 apk,然后从 receivers 集合中拿到静态注册的 BroadcastReceiver,最后再手动注册它们:
public void parseApk(Context context, List<String> pluginPaths) {
for (String pluginPath : pluginPaths) {
if (TextUtils.isEmpty(pluginPath)) {
Log.e(TAG, "插件包路径不能为空!");
return;
}
File pluginFile = new File(pluginPath);
if (!pluginFile.exists()) {
Log.e(TAG, "插件 apk 文件不存在!");
}
try {
// 调用 PackageParser 的 parsePackage() 解析 pluginFile,得到结果 packageObject 实际是 Package
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance();
Method parsePackageMethod = packageParserClass.getMethod("parsePackage",
File.class, int.class);
Object packageObject = parsePackageMethod.invoke(packageParser, pluginFile, PackageManager.GET_ACTIVITIES);
// 拿到 Package 内的 receivers 集合,可以把 receiver 理解成 AndroidManifest 中的 <receiver>
// 元素,解析出它的类名和 IntentFilter 信息即可手动注册这个 BroadcastReceiver
Field receiversField = packageObject.getClass().getDeclaredField("receivers");
ArrayList receivers = (ArrayList) receiversField.get(packageObject);
// ArrayList 的泛型类型是 PackageParser.Activity,不是四大组件那个 Activity,
// class Activity extends Component<ActivityIntentInfo>
// 接下来就遍历 ArrayList,对每一个 Receiver 进行手动注册
if (receivers != null) {
// Component 的 ArrayList<II> intents 字段保存着 IntentFilter 信息,
// 所以首先我们就通过反射拿到该 receiver 对象下所有的 IntentFilter
Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
Field intentsField = componentClass.getField("intents");
// 通过 PackageParser 的 generateActivityInfo() 得到包含 receiver 类名的
// ActivityInfo 对象,先构造该方法需要的参数 PackageUserState 对象
Class<?> packageUsageStateClass = Class.forName("android.content.pm.PackageUserState");
Class<?> userHandleClass = Class.forName("android.os.UserHandle");
for (Object receiver : receivers) {
// 尽量把公共代码提到循环外,减少反射的执行次数
ArrayList<IntentFilter> intents = (ArrayList) intentsField.get(receiver); // 直接用子类对象去拿
int userId = (int) userHandleClass.getMethod("getCallingUserId").invoke(null);
// 反射调用 generateActivityInfo() 生成 ActivityInfo 并拿到全类名
Method generateActivityInfoMethod = packageParserClass.getMethod("generateActivityInfo",
receiver.getClass(), int.class, packageUsageStateClass, int.class);
ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null,
receiver, 0, packageUsageStateClass.newInstance(), userId);
String receiverName = activityInfo.name;
// 注册
Class<?> receiverClass = context.getClassLoader().loadClass(receiverName);
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) receiverClass.newInstance();
for (IntentFilter intentFilter : intents) {
context.registerReceiver(broadcastReceiver, intentFilter);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试的话在插件的 AndroidManifest 中注册一个静态 BroadcastReceiver:
<receiver
android:name=".StaticReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.demo.placeholder.plugin.test"/>
</intent-filter>
</receiver>
发广播:
// 发送插件静态广播
findViewById(R.id.button_send_static_broadcast).setOnClickListener((listener) -> {
Intent intent = new Intent();
intent.setAction("com.demo.placeholder.plugin.test");
sendBroadcast(intent);
});
验证 StaticReceiver 中可以收到广播,回调 onReceive()。
1.4 ContentProvider
仍然需要在宿主中通过代理的 ContentProvider 去加载插件的 ContentProvider 对象,再调用对应的 CURD 方法,代码比较糙,主要看思路。
首先在宿主中新建代理的 ProxyProvider,配置好 android:authorities 属性:
<provider
android:name=".ProxyProvider"
android:authorities="com.demo.placeholder.host.ProxyProvider"
android:enabled="true"
android:exported="false"/>
Java 代码:
public class ProxyProvider extends ContentProvider {
private static final String TAG = ProxyProvider.class.getSimpleName();
// Uri path 与插件的 ContentProvider 全类名的映射关系表
private static final Map<String, String> sClassNameMap;
// 插件的 ContentProvider 全类名与实例对象的缓存
private Map<String, ContentProvider> mCache;
private ContentProvider mPluginProvider;
static {
sClassNameMap = new HashMap<>();
sClassNameMap.put("plugin00", "com.demo.placeholder.plugin.PluginProvider");
sClassNameMap.put("plugin01", "com.demo.placeholder.plugin.PluginProvider1");
}
/**
* 1.根据要操作的 Uri 先找出对应的插件 ContentProvider 的全类名
* 2.利用全类名反射得到插件 ContentProvider 的实例对象
* 后续 CURD 操作都需要先来拿 ContentProvider 实例再操作
*/
public ContentProvider loadPluginProvider(Uri uri) {
Log.d(TAG, "Uri: " + uri.toString());
// uri.getPath() 拿到的是 /plugin00,而 getPathSegments() 拿到的是
// 没有 / 的,每段路径名组成的集合,对于当前 demo 的代码直接取 pathSegments(0)
List<String> pathSegments = uri.getPathSegments();
String className = sClassNameMap.get(pathSegments.get(0));
// 先去缓存中找
if (mCache != null) {
ContentProvider contentProvider = mCache.get(className);
if (contentProvider != null) {
return contentProvider;
}
}
// 缓存没有再反射
try {
Class<?> clazz = getContext().getClassLoader().loadClass(className);
mPluginProvider = (ContentProvider) clazz.newInstance();
if (mCache == null) {
mCache = new HashMap<>();
}
mCache.put(className, mPluginProvider);
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "loadPluginProvider: " + mPluginProvider);
return mPluginProvider;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
ContentProvider pluginProvider = loadPluginProvider(uri);
if (pluginProvider != null) {
return pluginProvider.delete(uri, selection, selectionArgs);
}
return 0;
}
@Override
public String getType(Uri uri) {
ContentProvider pluginProvider = loadPluginProvider(uri);
if (pluginProvider != null) {
return pluginProvider.getType(uri);
}
return "";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
ContentProvider pluginProvider = loadPluginProvider(uri);
if (pluginProvider != null) {
return pluginProvider.insert(uri, values);
}
return null;
}
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate: ");
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
ContentProvider pluginProvider = loadPluginProvider(uri);
if (pluginProvider != null) {
return pluginProvider.query(uri, projection, selection, selectionArgs, sortOrder);
}
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
ContentProvider pluginProvider = loadPluginProvider(uri);
if (pluginProvider != null) {
pluginProvider.update(uri, values, selection, selectionArgs);
}
return 0;
}
}
插件这边新建两个测试用的 ContentProvider,需要实现的 6 个方法中只添加 log 看效果,没有实际操作。宿主添加一段测试代码,加载插件的 ContentProvider 做 CURD 测试:
private void testPluginProvider() {
ArrayList<Uri> list = new ArrayList<>();
list.add(Uri.parse("content://com.demo.placeholder.host.ProxyProvider/plugin00"));
list.add(Uri.parse("content://com.demo.placeholder.host.ProxyProvider/plugin01"));
ContentResolver contentResolver = getContentResolver();
for (Uri uri : list) {
contentResolver.query(uri, null, null, null, null);
contentResolver.insert(uri, new ContentValues());
contentResolver.delete(uri, "", new String[]{});
contentResolver.update(uri, new ContentValues(), "", new String[]{});
}
}
Log 截图:
二、Hook 式
2.1 startActivity() 过程
Hook 式插件化需要对 startActivity() 的执行流程有一定了解,先上幅图:
由 Activity1 启动 Activity2 的大致过程如上,贴点源码简单说说与 Hook 相关的部分。
Activity1 这边调用 startActivity() 后会执行到 startActivityForResult(),并执行到图中的第 1 步:
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
...
}
...
}
Instrumentation 的 execStartActivity() 会跨进程调用 AMS 的 startActivity() 并检查启动的结果,对应上图 2、3 步:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
try {
...
// 调用 AMS 的 startActivity() 并得到启动结果 result
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
// 检查这个 result
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
checkStartActivityResult() 检查 result 时如果有问题会抛出异常:
public static void checkStartActivityResult(int res, Object intent) {
if (!ActivityManager.isStartResultFatalError(res)) {
return;
}
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
// 要启动的 Activity 没在 Manifest 中注册会抛这个异常
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
case ActivityManager.START_PERMISSION_DENIED:
throw new SecurityException("Not allowed to start activity "
+ intent);
// 还有好多 case,省略了...
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
}
}
比如说,要启动的 Activity 如果没在 AndroidManifest 中注册,就会抛 ActivityNotFoundException 问你在 AndroidManifest 声明了吗……
第 4 ~ 7 步是 AMS 内部处理,这里就省略不表了。看第 8 步调用了 ActivityThread 中 Handler 的 handleMessage(),消息类型为
LAUNCH_ACTIVITY:
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
// 开始启动 Activity
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
} break;
...
}
...
}
第 9 步,handleLaunchActivity() 启动 Activity:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
...
// 将 Activity 实例化
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
// resume
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
// pause
performPauseActivityIfNeeded(r, reason);
...
}
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
步骤 10,performLaunchActivity() 生成一个 Activity 对象:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 赋值 ActivityClientRecord 的 packageInfo 字段
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
// 生成 Activity 对应的 ComponentName
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
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// Instrumentation 创建 Activity,其实就是
// return (Activity)cl.loadClass(className).newInstance();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
...
// attach 过程
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
...
// create 过程,会回调 Activity 的 onCreate(),即图中步骤 11
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}
}
...
return activity;
}
2.2 Hook 启动插件 Activity
了解了 Activity 的启动过程,再来看如何 Hook 启动插件中的 Activity。由于 AMS 会检查待启动的 Activity 是否在 AndroidManifest 中注册,所以直接用 startActivity() 启动插件 Activity 肯定不行,只能在 AMS 检查之前先换成宿主的代理 Activity 以通过检查,然后在第 10 步 Instrumentation.newActivity() 之前换回插件 Activity,被实例化,然后启动:
Hook 过程分为两段,其中 HookAMS 过程主要用到动态代理,HookHandler 过程主要用到反射。
此外,Hook 点不止一个,我们寻找 Hook 点的原则是:
- 尽量静态变量或者单例对象(容易获取且不易改变)
- 尽量 Hook public 的对象和方法(影响范围会小一点)
HookAMS
宿主中新建 ProxyActivity,它不用做任何操作,只需要在 AndroidManifest 中注册,帮助我们绕过 AMS 检测就好。
接下来用这个 ProxyActivity 去替换要启动的插件 Activity,替换要在 AMS 检查之前,也就是上图中第 2、3 步执行 AMS 的 startActivity() 之前,索性我们就选择这个方法作为 Hook 点:
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
将该方法第 3 个参数 intent 的目标改成 ProxyActivity 即可。在此之前,看下 ActivityManager 的 getService():
/**
* @hide
*/
public static IActivityManager getService() {
// Singleton 的 get() 得到的就是 create() 返回的对象
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
// 通过 ServiceManager 获取 ACTIVITY_SERVICE 服务,准备要 IPC
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
所以 Hook AMS 的代码:
public void hookAMS() {
try {
// 1.反射调用 ActivityManager.getService() 得到 IActivityManager 对象
Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
Method getServiceMethod = activityManagerClass.getMethod("getService");
Object iActivityManager = getServiceMethod.invoke(null);
Log.d("Frank", "hookAMS: " + getServiceMethod.getName());
// 2.生成动态代理对象,监听 IActivityManager 方法执行,替换掉 startActivity() 的参数 intent
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
Object iActivityManagerProxy = Proxy.newProxyInstance(mContext.getClassLoader(),
new Class[]{iActivityManagerClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过包名过滤一下,只处理插件中的方法
if (method.getDeclaringClass().getName().startsWith(PACKAGE_PLUGIN)) {
if ("startActivity".equals(method.getName())) {
for (Object arg : args) {
if (arg instanceof Intent) {
Intent proxyIntent = new Intent(mContext, ProxyActivity.class);
proxyIntent.putExtra("actionIntent", (Intent) arg);
arg = proxyIntent;
}
}
}
}
return method.invoke(iActivityManager, args);
}
});
// 3.用动态代理对象替换掉 IActivityManagerSingleton 内的 mInstance
Field iSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
iSingletonField.setAccessible(true);
Object IActivityManagerSingleton = iSingletonField.get(null);
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
mInstanceField.set(IActivityManagerSingleton, iActivityManagerProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
Hook 点并不是唯一的,Activity.startActivityForResult() 中 mInstrumentation.execStartActivity() 也可以作为 Hook 点,不过主要想法都是去想办法替换 Intent。
HookHandler
AMS 检查完毕后,需要将 ProxyActivity 再换成原本要启动的插件 Activity,我们选择在 ActivityThread 中的 Handler mH 处理 LAUNCH_ACTIVITY 消息时将 Activity 替换回来。为了不影响 mH 原本的执行过程,需要在 Handler.Callback 中做替换工作,而不是直接去修改 handleMessage():
Handler.java:
final Callback mCallback;
public void dispatchMessage(Message msg) {
// Message 的 callback 执行具有最高优先级
if (msg.callback != null) {
handleCallback(msg);
} else {
// mCallback 优先级居中
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// handleMessage() 只有当 mCallback.handleMessage()
// 返回 false 时才能被执行到,优先级最低
handleMessage(msg);
}
}
为了让 Handler 本身的 handleMessage() 执行不受影响,最合适的方法就是在 mCallback 的 handleMessage() 中执行替换,并且返回 false:
public static final int LAUNCH_ACTIVITY = 100;
private Handler.Callback mCallback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
if (message.what == LAUNCH_ACTIVITY) {
try {
// message.obj 实际上是一个 ActivityClientRecord,但是 @hide 的,
// 我们不能直接用,只能拿 ActivityClientRecord 的 intent
Field intentField = message.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(message.obj);
// 之前 Hook AMS 的时候我们把真正启动插件的 Intent 放在 Extra 里了
Intent actionIntent = intent.getParcelableExtra("actionIntent");
if (actionIntent != null) {
intentField.set(message.obj, actionIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
};
再将上面的 mCallback 对象设置给 ActivityThread 中的 Handler——mH 的 mCallback 字段:
public void hookHandler() {
try {
// 1.通过 currentActivityThread() 拿到 ActivityThread 对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread");
Object activityThread = currentActivityThreadMethod.invoke(null);
// 2.拿到 mH 对象
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(activityThread);
// 3.设置 mH 的 mCallback 字段
Class<?> handlerClass = Class.forName("android.os.Handler");
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, mCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
使用前先在 Application 中调用两端 Hook 方法:
@Override
public void onCreate() {
super.onCreate();
PluginManager pluginManager = PluginManager.getInstance(this);
String pluginPath = getFilesDir() + File.separator + "hook_plugin-debug.apk";
pluginManager.loadPlugin(pluginPath);
pluginManager.hookAMS();
pluginManager.hookHandler();
}
启动插件 Activity 时需要指定其 ComponentName:
public void startPluginActivity(View view) {
Intent intent = new Intent();
String pluginPackage = "com.demo.hook.plugin";
String pluginActivityClass = "com.demo.hook.plugin.PluginActivity";
intent.setComponent(new ComponentName(pluginPackage, pluginActivityClass));
startActivity(intent);
}
在 Application 中调用
经过 HookAMS 和 HookHandler 两步,Hook 式插件化就基本上完成了,再加上上一篇介绍过的加载插件过程:
public void loadPluginForHook(String pluginPath) {
if (TextUtils.isEmpty(pluginPath)) {
Log.e(TAG, "插件路径不能拿为空!");
}
File pluginFile = new File(pluginPath);
if (!pluginFile.exists()) {
Log.e(TAG, "插件包不存在!");
}
try {
// 1.获取宿主的 dexElements
Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = clazz.getDeclaredField("pathList");
pathListField.setAccessible(true);
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElements = dexPathListClass.getDeclaredField("dexElements");
dexElements.setAccessible(true);
ClassLoader pathClassLoader = mContext.getClassLoader();
Object dexPathList = pathListField.get(pathClassLoader);
Object[] hostElements = (Object[]) dexElements.get(dexPathList);
// 2.获取插件的 dexElements
DexClassLoader dexClassLoader = new DexClassLoader(pluginPath,
mContext.getCacheDir().getAbsolutePath(), null, pathClassLoader);
Object pluginPathList = pathListField.get(dexClassLoader);
Object[] pluginElements = (Object[]) dexElements.get(pluginPathList);
// 3.合并到新的 Element[] 中,先创建一个新数组
Object[] newElements = (Object[]) Array.newInstance(hostElements.getClass().getComponentType(),
hostElements.length + pluginElements.length);
System.arraycopy(hostElements, 0, newElements, 0, hostElements.length);
System.arraycopy(pluginElements, 0, newElements, hostElements.length, pluginElements.length);
// 4.赋值
dexElements.set(dexPathList, newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
最后在 Application 中调用这三个方法即可:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
PluginManager pluginManager = PluginManager.getInstance(this);
String pluginPath = getFilesDir() + File.separator + "hook_plugin-debug.apk";
// 加载插件类
pluginManager.loadPluginForHook(pluginPath);
// HookAMS
pluginManager.hookAMS();
// HookHandler
pluginManager.hookHandler();
}
}
2.3 兼容问题
插件化一个最大的问题就是兼容问题,特别是要反射系统源码时,兼容问题体现尤为突出,只要 Hook 的源码随着系统升级发生了改动,就要对新系统进行适配,这种适配也是插件化成本高的原因之一。
比如说,我们上面的例子在 8.0(API 26)上的系统是可以运行的,但是到了 9.0 就不行了,因为 Handler 启动 Activity 的流程变了,那么 HookHandler 的过程就要针对 9.0 进行适配。
9.0 将 Activity 的生命周期状态都用状态模式管理了,相应的在 ActivityThread 的 Handler 中,处理生命周期不再是用各种消息区分,而是统一用 EXECUTE_TRANSACTION 这个消息处理生命周期,所以 Hook Handler 的代码也要做出对应的更改。
先附上从 AMS 出来后,启动流程的时序图:
篇幅有限,仅说重要部分。首先,从 AMS 出来,会调用到 ActivityStackSupervisor 的 realStartActivityLocked():
final ActivityManagerService mService;
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
...
// 给 ClientTransaction 添加 Callback,内容类型是 LaunchActivityItem,
// LaunchActivityItem 就是执行 LaunchActivity 动作的 ClientTransactionItem 的子类,
// new Intent(r.intent) 就是启动 Activity 的那个 Intent,被封装在 LaunchActivityItem
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, mService.isNextTransitionForward(),
profilerInfo));
...
// Schedule transaction.
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
...
}
通过 AMS 拿到 ClientLifecycleManager 再开始生命周期调度:
void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
// 通过 ClientTransaction 的 getClient() 获取 IApplicationThread 类型的客户端
final IApplicationThread client = transaction.getClient();
transaction.schedule();
// 如果 client 是 Binder 实例需要回收
if (!(client instanceof Binder)) {
transaction.recycle();
}
}
ClientTransaction 内维护着一个客户端 IApplicationThread 对象:
/** Target client. */
private IApplicationThread mClient;
public IApplicationThread getClient() {
return mClient;
}
public void schedule() throws RemoteException {
// 调用 IApplicationThread 的 scheduleTransaction() 由 AMS 回到客户端 app
mClient.scheduleTransaction(this);
}
调用 IApplicationThread 的 scheduleTransaction() 经过 IPC 实际上调用的是客户端 app 中 ActivityThread 的内部类 ApplicationThread 的 scheduleTransaction():
@Override
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
ApplicationThread 继承了 ClientTransactionHandler,scheduleTransaction() 是 ClientTransactionHandler 中的方法,ApplicationThread 没有重写,所以来到 ClientTransactionHandler:
/** Prepare and schedule transaction for execution. */
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
// 发消息让 H 处理
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}
H 的 handleMessage() 处理消息 EXECUTE_TRANSACTION:
// An executor that performs multi-step transactions.
private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);
class H extends Handler {
public static final int EXECUTE_TRANSACTION = 159;
public void handleMessage(Message msg) {
switch (msg.what) {
case EXECUTE_TRANSACTION:
// msg.obj 是一个 ClientTransaction 对象
final ClientTransaction transaction = (ClientTransaction) msg.obj;
// 执行 ClientTransaction
mTransactionExecutor.execute(transaction);
if (isSystem()) {
transaction.recycle();
}
break;
}
}
}
TransactionExecutor 的 execute():
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
// 执行生命周期事务
executeCallbacks(transaction);
executeLifecycleState(transaction);
mPendingActions.clear();
}
// 循环遍历回调请求的所有状态,并在适当的时间执行它们
public void executeCallbacks(ClientTransaction transaction) {
// 获取回调列表,拿到的是 ClientTransaction 中的
// List<ClientTransactionItem> mActivityCallbacks
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null) {
return;
}
...
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
...
// 调用 ClientTransactionItem 的 execute()
item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);
...
}
...
}
ClientTransactionItem 有众多子类,如 LaunchActivityItem 负责启动一个 Activity(类似的还有 ResumeActivityItem、StopActivityItem 等等):
/**
* Request to launch an activity.
* @hide
*/
public class LaunchActivityItem extends ClientTransactionItem {
private Intent mIntent;
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
// 还是把要启动的 Activity 信息封装在 ActivityClientRecord 中
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
}
}
抽象类 ClientTransactionHandler 的子类是 ActivityThread,所以最后是调用 ActivityThread 的 handleLaunchActivity() 去启动 Activity 的。
这样源码看下来,可以梳理一下针对 9.0 系统 Hook Handler 的思路:
- 要替换的 Intent 被封装在 LaunchActivityItem 中
- LaunchActivityItem 被保存在 ClientTransaction 的 mActivityCallbacks 列表中
- ClientTransaction 是 H 类 handleMessage() 处理 EXECUTE_TRANSACTION 消息时能获取到的参数 msg.obj
所以更新 Hook Handler 的代码如下:
public static final int LAUNCH_ACTIVITY = 100;
public static final int EXECUTE_TRANSACTION = 159;
public static final String CLASS_NAME_LAUNCH_ACTIVITY_ITEM = "android.app.servertransaction.LaunchActivityItem";
private final Handler.Callback mCallback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
switch (message.what) {
case LAUNCH_ACTIVITY:
// LAUNCH_ACTIVITY 是给 9.0 之前用的,前面写过,这里省略
...
break;
case EXECUTE_TRANSACTION:
try {
// 拿到 mActivityCallbacks 列表
Field mActivityCallbacksField = message.obj.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
ArrayList mActivityCallbacks = (ArrayList) mActivityCallbacksField.get(message.obj);
// 从 mActivityCallbacks 中找到 LaunchActivityItem
Object launchActivityItem = null;
for (Object callback : mActivityCallbacks) {
if (CLASS_NAME_LAUNCH_ACTIVITY_ITEM.equals(callback.getClass().getName())) {
launchActivityItem = callback;
}
}
// 取出 Intent 并替换
if (launchActivityItem != null) {
Field mIntentField = launchActivityItem.getClass().getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent intent = (Intent) mIntentField.get(launchActivityItem);
Intent actionIntent = intent.getParcelableExtra("actionIntent");
if (actionIntent != null) {
mIntentField.set(launchActivityItem, actionIntent);
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
return false;
}
};
三、LoadedApk 式
LoadedApk 式并不是一套完整的插件化方式,它只是针对 Hook 式的部分流程做出了改进,进一步讲,它只是优化了 Hook 式中,生成可以加载插件类的 ClassLoader 的方法,HookAMS 和 HookHandler 的过程几乎没有变化。
Hook 式是将所有插件的 dexElements 都融合到宿主所使用的 PathClassLoader 中,当插件数量过多时,就会使得宿主的 dexElements 过大而占用过多内存。
但是 LoadedApk 式加载插件类入侵系统比 Hook 式更强,会使用到更多的反射和动态代理,相比于 Hook 式要更繁琐,这里也仅就做开拓思路用。
那么 LoadedApk 式是如何避免上述情况发生的呢?还是回到源码,在 ActivityThread 中 mH 的 handleMessage() 在处理 LAUNCH_ACTIVITY 消息时:
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
// 这次关注点在 getPackageInfoNoCheck() 上
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
} break;
}
}
ActivityClientRecord 的 packageInfo 属性是通过 getPackageInfoNoCheck() 获取的,这个 packageInfo 是一个 LoadedApk 对象:
// 缓存,key 是应用包名,value 是 LoadedApk 的弱引用
final ArrayMap<String, WeakReference<LoadedApk>> mPackages
= new ArrayMap<String, WeakReference<LoadedApk>>();
final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages
= new ArrayMap<String, WeakReference<LoadedApk>>();
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
// 获取包信息的方法,返回值类型为 LoadedApk。
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) {
// 先去缓存中找 LoadedApk 的弱引用
WeakReference<LoadedApk> ref;
if (differentUser) {
// 不支持跨用户缓存
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
// 初始化 packageInfo,若缓存中有就直接拿,否则先置 null
LoadedApk packageInfo = ref != null ? ref.get() : null;
// 如果 packageInfo 为 null,就进入生成阶段,否则最后直接 return 现有值
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// 创建 LoadedApk 类型的变量 packageInfo
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());
}
// 将新建的 packageInfo 放入缓存
if (differentUser) {
// 不支持跨用户缓存
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
}
}
getPackageInfo() 会先去缓存中找,如果没找到就创建一个 LoadedApk 类型的变量返回给 ActivityClientRecord 的 packageInfo 字段。
接下来执行 handleLaunchActivity(),由于前面贴过这部分源码了,这次就只贴与本节相关的部分了:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = performLaunchActivity(r, customIntent);
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
// ※获取 LoadedApk 的 ClassLoader,并用它加载 Activity 类
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
...
return activity;
}
performLaunchActivity() 调用 Instrumentation.newActivity() 创建一个 Activity 对象:
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
加载 Activity 所使用的 ClassLoader 对象正是来自于 getPackageInfo() 的返回值 LoadedApk,且 getPackageInfo() 都会先去缓存 mPackages 中找之前生成的 LoadedApk 对象:
既然宿主使用 LoadedApk 的 ClassLoader 加载 Activity,那么我们可以模仿它,自定义一个 LoadedApk 对象,让它的 ClassLoader 可以加载插件中的类,然后通过反射将它添加到 mPackages 中。
我们可以反射 getPackageInfoNoCheck(),把它返回的 LoadedApk 对象中的 mClassLoader 字段改成一个可以加载插件类的 ClassLoader 即可。
3.1 获取 ApplicationInfo 和 CompatibilityInfo
调用 getPackageInfoNoCheck() 需要 ApplicationInfo 和 CompatibilityInfo 先获取两个参数。PackageParser 的 generateApplicationInfo() 可以得到 ApplicationInfo:
/frameworks/base/core/java/android/content/pm/PackageParser.java:
/**
*
* @param p Package 是 PackageParser 的静态内部类,解析了 AndroidManifest 并保存
* 了解析结果。可以通过 PackageParser 的 parsePackage() 获取。
* @param flags 可以直接传 0
* @param state PackageUserState 对象可以通过 Class 反射后用 newInstance() 得到。
* @return
*/
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state) {
return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
}
执行代码:
/**
* 反射执行 PackageParser 的 generateApplicationInfo() 以获取 ApplicationInfo 对象
*/
private ApplicationInfo getPluginAppInfo() {
try {
// 1.调用 parsePackage() 得到 Package 对象
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage",
File.class, int.class);
Object packageParser = packageParserClass.newInstance();
Object packageObject = parsePackageMethod.invoke(packageParser, mPluginFile, PackageManager.GET_ACTIVITIES);
// 2.创建一个 PackageUserState 对象
Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Object packageUserState = packageUserStateClass.newInstance();
// 3. 调用 generateApplicationInfo()
Class<?> packageClass = Class.forName("android.content.pm.PackageParser$Package");
Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod(
"generateApplicationInfo", packageClass, int.class, packageUserStateClass);
ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(
null, packageObject, 0, packageUserState);
// 4.把 applicationInfo 中的路径设置成插件的路径
if (mPluginFile != null) {
String pluginApkPath = mPluginFile.getAbsolutePath();
applicationInfo.sourceDir = pluginApkPath;
applicationInfo.publicSourceDir = pluginApkPath;
}
return applicationInfo;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
CompatibilityInfo 对象可以直接去拿 CompatibilityInfo 类中的默认对象 DEFAULT_COMPATIBILITY_INFO:
public class CompatibilityInfo implements Parcelable {
/** default compatibility info object for compatible applications */
public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
};
}
执行代码:
Class mCompatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
Field defaultField = mCompatibilityInfoClass.getField("DEFAULT_COMPATIBILITY_INFO");
Object mCompatibilityInfo = defaultField.get(null);
3.2 执行 getPackageInfoNoCheck()
反射执行 getPackageInfoNoCheck() 得到一个 LoadedApk 对象,修改它的 ClassLoader 为可以加载插件类的 ClassLoader,并将其添加到 ActivityThread 的缓存 mPackages 中:
/**
* 自定义一个 LoadedApk,其 ClassLoader 可以加载插件类
*/
public void makeCustomLoadedApk(File pluginFile) {
try {
// 1.获取 ActivityThread 对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread");
Object activityThread = currentActivityThreadMethod.invoke(null);
// 2.获取 ApplicationInfo
ApplicationInfo pluginAppInfo = getPluginAppInfo(pluginFile);
// 3.获取 CompatibilityInfo
Class<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
Field defaultField = compatibilityInfoClass.getField("DEFAULT_COMPATIBILITY_INFO");
Object compatibilityInfo = defaultField.get(null);
// 4.反射调用 getPackageInfoNoCheck()
Method getPackageInfoNoCheckMethod = activityThreadClass.getMethod("getPackageInfoNoCheck",
ApplicationInfo.class, compatibilityInfoClass);
Object loadedApk = getPackageInfoNoCheckMethod.invoke(activityThread, pluginAppInfo, compatibilityInfo);
// 5.修改 loadedApk 中的 ClassLoader,替换成可以加载插件中类的 ClassLoader
ClassLoader classLoader = loadPluginForLoadedApk(pluginFile.getAbsolutePath());
Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");
Field mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
mClassLoaderField.set(loadedApk,classLoader);
// 6.将 loadedApk 存入 ActivityThread 的缓存 mPackages 中
Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackages = (ArrayMap) mPackagesField.get(activityThread);
// LoadedApk 是 @hide 的,所以 ArrayMap、WeakReference 无法指定泛型
WeakReference weakReference = new WeakReference(loadedApk);
mPackages.put(pluginAppInfo.packageName,weakReference);
} catch (Exception e) {
e.printStackTrace();
}
}
写到这里似乎大功告成了,但运行一下代码发现会抛异常:
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.demo.hook.plugin/com.demo.hook.plugin.PluginActivity}: java.lang.ClassNotFoundException: Didn't find class "com.demo.hook.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/base.apk"],nativeLibraryDirectories=[/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/lib/x86, /system/lib, /vendor/lib]]
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2718)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
...
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.demo.hook.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/base.apk"],nativeLibraryDirectories=[/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/lib/x86, /system/lib, /vendor/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
...
意思是无法加载插件的类,但可以加载插件的 ClassLoader 的 LoadedApk 对象已经存入 mPackages 中了,是哪里出的问题呢?回看源码,发现有个漏洞我们没有考虑到:
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
} break;
}
}
想一下这个 ActivityClientRecord 的包名是宿主的还是插件的?答案当然是宿主的,用宿主的包名去 mPackages 去拿到的一定是可以加载宿主的那个 LoadedApk,所以加载插件时才会找不到。
既然这样,在 Handler 处理 LAUNCH_ACTIVITY 消息时,我们就要对 ActivityClientRecord 对象的包名加以处理:
public static final int LAUNCH_ACTIVITY = 100;
private Handler.Callback mCallback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
if (message.what == LAUNCH_ACTIVITY) {
try {
// message.obj 实际上是一个 ActivityClientRecord,但是 @hide 的,
// 我们不能直接用,只能一步直接拿 ActivityClientRecord 的 intent
Field intentField = message.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(message.obj);
// 之前 Hook AMS 的时候我们把真正启动插件的 Intent 放在 Extra 里了
Intent actionIntent = intent.getParcelableExtra("actionIntent");
if (actionIntent != null) {
intentField.set(message.obj, actionIntent);
// 如果使用 LoadedApk 方式加载插件,需要给 ActivityInfo.applicationInfo 做包名区分
if (useLoadedApk) {
Field activityInfoField = message.obj.getClass().getDeclaredField("activityInfo");
activityInfoField.setAccessible(true);
ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(message.obj);
if (activityInfo != null) {
// actionIntent.getPackage() == null 说明是插件,就要取 Component 的包名
activityInfo.applicationInfo.packageName = actionIntent.getPackage() == null ?
actionIntent.getComponent().getPackageName() : actionIntent.getPackage();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
};
上面代码是直接在 Hook 式插件化的 HookHandler 过程中添加的,if (useLoadedApk) 是额外添加的部分,用来区分插件和宿主的包名。
3.3 绕过 PKMS 检测
再次运行程序,又抛出了新的异常:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.demo.hook.plugin/com.demo.hook.plugin.PluginActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.demo.hook.plugin; is package not installed?
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
...
Caused by: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.demo.hook.plugin; is package not installed?
at android.app.LoadedApk.makeApplication(LoadedApk.java:971)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2725)
...
Caused by: java.lang.IllegalStateException: Unable to get package info for com.demo.hook.plugin; is package not installed?
at android.app.LoadedApk.initializeJavaContextClassLoader(LoadedApk.java:793)
at android.app.LoadedApk.makeApplication(LoadedApk.java:961)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2725)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
...
这是因为插件应用没有安装,所以无法获取到 Package 信息。这是 PKMS 的一个检测机制,上源码看一下这个异常的抛出位置:
/frameworks/base/core/java/android/app/LoadedApk.java:
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
...
initializeJavaContextClassLoader();
...
}
private void initializeJavaContextClassLoader() {
IPackageManager pm = ActivityThread.getPackageManager();
android.content.pm.PackageInfo pi;
try {
// 应用没安装的话 getPackageInfo() 就返回 null,下面就会抛异常了
pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
UserHandle.myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (pi == null) {
throw new IllegalStateException("Unable to get package info for "
+ mPackageName + "; is package not installed?");
}
...
}
为了让 getPackageInfo() 在插件上不返回 null,可以使用动态代理监听 IPackageManager 接口的 getPackageInfo(),当对插件执行该方法时返回一个 PackageInfo:
public void hookGetPackageInfo() {
try {
// 反射获取 ActivityThread 内的静态变量 sPackageManager
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(null);
Class<?> iPackageManagerClass = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{iPackageManagerClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//
if ("getPackageInfo".equals(method.getName())) {
return new PackageInfo();
}
return method.invoke(sPackageManager, args);
}
});
sPackageManagerField.set(null, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
最后也是在 Application 中调用这些方法,hookAMS() 与 hookHandler() 与 Hook 式基本相同,只是加载插件类的方式发生了变化:
@Override
public void onCreate() {
super.onCreate();
PluginManager pluginManager = PluginManager.getInstance(this);
String pluginPath = getFilesDir() + File.separator + "hook_plugin-debug.apk";
pluginManager.hookAMS();
pluginManager.hookHandler();
if (useLoadedApk) {
// 使用 LoadedApk 式的 ClassLoader 加载插件
pluginManager.makeCustomLoadedApk(new File(pluginPath));
pluginManager.hookGetPackageInfo();
} else {
// 使用 Hook 式加载插件类
pluginManager.loadPluginForHook(pluginPath);
}
}
Demo 参考代码:PluginDemo