前言
上一篇我们学习了类加载机制原理以及如何调用插件apk的dex方法Android插件化学习之初识类加载机制,这一篇我们紧接着上一篇的内容,尝试去启动插件apk中未注册的activity。
实现思路
Activity启动流程
既然想要实现启动插件中未注册的activity,那么就必须对AMS启动activity流程有深刻的认知;AMS启动activity详细流程分析可参考:Android启动流程
这里我们简单看下activity的启动流程图:
实现思路
我们想要启动插件中的activity,由于插件中activity没有在清单文件中注册,就必须绕开AMS
中activity的注册检查,因此我们的思路是:
1.Hook AMS
:在调用startActivity后,首先替换成我们在宿主apk中定义的ProxyActivity
,由于ProxyActivity
是在宿主中已注册过的,因此AMS
做检查时,就可以轻而易举绕开;
2.Hook Hander H
:我们知道startActivity
最终会交给ApplicationThread
的Handler H
去处理启动activity的消息,在这里,我们再将ProxyActivity
替换成我们要启动的插件activity,完成最终activity启动;
经过上述流程分析下来,是不是比较简单,在这里我实现的技术主要用到动态代理
和反射
,对这一块不熟悉的小伙伴可以先学习下;
代码流程
- 定义插件
PluginActivity
并打包插件apk,存放至sdcard路径下【这里为了图方便,实现开发应存放至/data/data/package/目录下】
PluginActivity
,代码比较简单,注意注释下加载布局,先不加载资源,否则会出错;
class PluginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//先不加载资源
//setContentView(R.layout.activity_plugin)
Log.e(PluginActivity::class.java.simpleName,"onCreate:启动插件Activity")
}
}
- 定义宿主apk中的启动插件
PluginActivity
方法以及ProxyActivity
【均需要在清单文件中注册】
启动插件Activity
class JumpActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_plugin)
findViewById<Button>(R.id.btn).setOnClickListener {
//跳转至插件里的类
val intent = Intent()
intent.component = ComponentName("com.dongxian.plugin","com.dongxian.plugin.PluginActivity")
startActivity(intent)
}
}
}
ProxyActivity
class ProxyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e(ProxyActivity::class.java.simpleName, "onCreate")
}
}
- 合并插件apk dex文件至宿主apk中
public class LoadUtils {
public static void loadPluginDexFile(Context context) {
try {
Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
pathListField.setAccessible(true);
Class<?> dexPathListClazz = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClazz.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
//获取宿主dexElements
ClassLoader hostClassLoader = context.getClassLoader();
Object hostPathList = pathListField.get(hostClassLoader);
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
//获取插件dexElements
DexClassLoader pluginDexClassLoader = new DexClassLoader("/sdcard/plugin.apk", context.getCacheDir().getAbsolutePath(), null, hostClassLoader);
Object pluginPathList = pathListField.get(pluginDexClassLoader);
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
//合并两个dex Element数组
Object[] resultDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),hostDexElements.length + pluginDexElements.length);
System.arraycopy(hostDexElements, 0, resultDexElements, 0, hostDexElements.length);
System.arraycopy(pluginDexElements, 0, resultDexElements, hostDexElements.length, pluginDexElements.length);
//将合并的结果复制给宿主dex Elements
dexElementsField.set(hostPathList, resultDexElements);
} catch (Exception e) {
e.printStackTrace();
Log.e("LoadUtils", e.toString());
}
}
}
Hook AMS流程
【需要注意版本适配】
private static final String HOOK_INTENT_DATA = "hook_intent_data";
/**
* hook startActivity 到 AMS流程,重点是替换intent中的启动的目标activity
* 关键代码,ActivityTaskManager.getService()是接口,可以考虑通过动态代理方式 代理startActivity方法,进行hook
* int result = ActivityTaskManager.getService().startActivity(whoThread,
* who.getBasePackageName(), who.getAttributionTag(), intent,
* intent.resolveTypeIfNeeded(who.getContentResolver()), token,
* target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
*/
public static void hookAMS(Context context) {
Field singletonFiled = null;
try {
// 小于8.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
singletonFiled = clazz.getDeclaredField("gDefault");
//小于10.0
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Class<?> activityTaskManagerClazz = Class.forName("android.app.ActivityManager");
singletonFiled = activityTaskManagerClazz.getDeclaredField("IActivityManagerSingleton");
} else {
Class<?> activityTaskManagerClazz = Class.forName("android.app.ActivityTaskManager");
singletonFiled = activityTaskManagerClazz.getDeclaredField("IActivityTaskManagerSingleton");
}
singletonFiled.setAccessible(true);
//由于private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton 是静态的 ,可以直接获取对应值
Object singleton = singletonFiled.get(null);
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//通过activityTaskManagerSingleTon对象的mInstance属性就可以获取ActivityTaskManager.getService()的值,正是我们要代理的对象
Object mInstance = mInstanceField.get(singleton);
Class<?> iActivityManagerClass = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
//IActivityManager#startActivity @UnsupportedAppUsage( maxTargetSdk = 29) 受限制的灰名单,直接反射 Class.forName 无法代理到startActivity方法
iActivityManagerClass = Class.forName("android.app.IActivityManager");
} else {
iActivityManagerClass = Class.forName("android.app.IActivityTaskManager");
}
ClassLoader parent = context.getClassLoader().getParent();
Object proxyInstance = Proxy.newProxyInstance(parent, new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//循环遍历找到intent
if ("startActivity".equals(method.getName())) {
int intentIndex = -1;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
intentIndex = i;
break;
}
}
//原有的intent数据,需要保存起来,便于后面替换回来
Intent targetIntent = (Intent) args[intentIndex];
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.dongxian.studytotaldemo", "com.dongxian.studytotaldemo.ProxyActivity");
proxyIntent.putExtra(HOOK_INTENT_DATA, targetIntent);
args[intentIndex] = proxyIntent;
}
//返回值表示是否执行原有方法,这里保持原有方法不变
return method.invoke(mInstance, args);
}
});
//把代理对象复制给原来的对象
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
Log.e(HookUtils.class.getSimpleName(), e.toString());
}
}
Hook Hander
【需要注意版本适配】
/**
* hook ActivityThread 流程 AMS 到launchActivity,把intent替换回来
*/
public static void hookHandler() {
try {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object sCurrentActivityThread = currentActivityThreadField.get(null);
Field mHField = activityThreadClazz.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(sCurrentActivityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
//表示启动activity
switch (msg.what) {
case 100:
try {
//msg.obj == ActivityClientRecord [ActivityClientRecord中存在intent对象]
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);
if (intent != null && intent.getExtras() != null && intent.getExtras().get(HOOK_INTENT_DATA) instanceof Intent) {
Intent target = (Intent) intent.getExtras().get(HOOK_INTENT_DATA);
intentField.set(msg.obj, target);
}
} catch (Exception e) {
e.printStackTrace();
Log.e(HookUtils.class.getSimpleName(), e.toString());
}
break;
case 159:
//android10适配,启动流程有所变化
Log.e("HookUtils", "this is Android High Version");
try {
//获取 List<ClientTransactionItem> mActivityCallbacks对象
Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
//获取mActivityCallbacks对象对应的 List<ClientTransactionItem>
List<ClientTransactionItem> items = (List<ClientTransactionItem>) mActivityCallbacksField.get(msg.obj);
for (ClientTransactionItem item : items) {
//找到启动activity对应的item对象
if ("android.app.servertransaction.LaunchActivityItem".equals(item.getClass().getName())) {
//修改其中的intent数据
Field intentField = item.getClass().getDeclaredField("mIntent");
intentField.setAccessible(true);
Intent targetIntent = (Intent) intentField.get(item);
if (targetIntent != null && targetIntent.getExtras() != null && targetIntent.getExtras().get(HOOK_INTENT_DATA) instanceof Intent) {
Intent target = (Intent) targetIntent.getExtras().get(HOOK_INTENT_DATA);
intentField.set(item, target);
}
}
}
} catch (Exception e) {
Log.e(HookUtils.class.getSimpleName(), e.toString());
e.printStackTrace();
}
break;
default:
break;
}
//结果一点要反正false
return false;
}
};
//结果替换
mCallbackField.set(mH, callback);
} catch (Exception e) {
e.printStackTrace();
Log.e(HookUtils.class.getSimpleName(), e.toString());
}
}
- 代码中调用
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
LoadUtils.loadPluginDexFile(this)
HookUtils.hookAMS(this)
HookUtils.hookHandler()
}
}
总结
其实我们发现最大的难点是做各个版本适配
,因为Android启动流程每个版本的更新,那就需要我们每更新一个版本就需要去阅读源码进行相关适配,这是一个让人比较头疼的问题,因此插件化方案也在慢慢被抛弃,但我们通过学习阅读源码,去尝试实现各种黑科技,理解插件化方案实现的思想,这又何尝不是一种进步呢;
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )