一 预备知识
1.java反射机制
这里整理一个demo所需的简单工具类
2.代理模式,动态代理,hook实现
3.handler机制,handler.dispatchMessage中可通过Handler.Callback并让callback方法返回false,对消息进行加工
4.activity启动过程
a.应用程序进程通过binder与AMS进行通信:
startActivity ->startActivityForResult -> Instrumentation.execStartActivity ->
android 10 ActivityTaskManager.getService().startActivity
android 8及以上ActivityManager.getService().startActivity
android 8以下ActivityManagerNative.getDefault().startActivity
b.AMS处理activity的信息:
通过调用PMS检查activity是否注册(如果未注册,则返回的值会Instrumentation.checkStartActivityResult里触发应用程序的异常)
暂停原先栈顶的应用
将activity启动信息封装成ActivityRecord
判断应用所在的任务栈、进程,是否存在,如果不存在,就通知Zygote进fork出新的进程
将启动Activity的信息,传给ApplicationThread,通过binder,从SystemServer进程到应用进程
具体调用AMS.startActivity -> ...->ActivityStack.startPausingLocked,ActivityStackSupervisor.startSpecificActivityLocked
ActivityStack.startPausingLocked:调用prev.app.thread.schedulePauseActivity,通过Binder连接到应用进程的ApplicationThread,通过handler通信到ActivityThread执行handlePauseActivity,最后执行activity的onPause
ActivityStackSupervisor.startSpecificActivityLocked:判断应用程序进程ProcessRecord与ApplicationThread是否存在, 不存在调用AMS.startProcessLocked通知Zygote进程fork出新的进程再调用ActivityThread的main方法,存在调用realStartActivityLocked,双方最后都会调用调用ApplicationThread.scheduleLaunchActivity,通过binder转到应用进程
c.ApplicationThread创建activcity:
通过handler发送消息->handleLaunchActivity->performLaunchActivity,通过反射创建activity,并调用onCreate、onStart、onResume
5.classLoader加载dex文件过程
DexClassLoader classLoader = newDexClassLoader(dexPath, optimizedDirectory, libraryPath, parentClassLoader);
classLoader通过findClass方法查找类
参数意义:
dexPath:dex,apk或者jar的路径,多个路径的话默认用":"隔开
optimizedDirectory 优化后的dex文件存放目录,不能为null
libraryPath目标类中使用的C/C++库的列表,每个目录用File.pathSeparator间隔开; 可以为null
parentClassLoader 该类装载器的父装载器,一般用当前执行类的装载器
原理:DexClassLoader构造方法super(dexPath, null, librarySearchPath, parent) ->BaseDexClassLoader
BaseDexClassLoader构造函数中将dexPath包装成DexPathList,赋值给pathList属性
DexPathList构造方法中,将dexPath封装成Element对象(如果dexPath带分隔符,可能生成多个Element)加入自身的dexElements名字的数组中
Element对象封装了DexFile,用于加载dex文件,每个dex对应一个Element对象,加载类时通过Element.findClass进入DexFile.loadClassBinaryName查找类
BaseDexClassLoader.findClass ->pathList.findClass -> DexPathList.findClass ->遍历dexElements名字的数组-> Element.findClass -> DexFile.loadClassBinaryName
6. 资源加载机制
Resources 类对外提供getString, getText ,
getDrawable 等各种方法,但其实都是间接调用AssetManager 的私有方法,AssetManager 负责向Android 系统要资源。AssetManager获取资源是根据指向的路径来完成。
运行应用的时候,Resources中AssetManager的路径默认指向了该apk的文件,如果是普通应用,一般apk文件被放置在data/app目录下,系统应用的apk包放在system/app下
AssetManager 中有一个addAssetPath(String path)方法, App 启动的时候, 会把当前apk的路径传进去,接下来AssetManager 和Resources 就能访问当前apk 的所有资源了。
理论上我们改变AssetManager读取的路径,再用该AssetManager生成对应的Resources对象,就可以指定加载自己的资源
二.插件化实践
1. 插件化需要解决的几个基本问题:
插件的资源加载问题或者加载插件资源
插件类的类加载问题
如何启动插件的activity
2. 资源加载:
AssetManager的addAssetPath方法是不对外的,可以通过反射把插件apk 的路径传入这个方法,那么就把插件资源添加到资源池中了,再用该AssetManager生成对应的Resources对象,就可以指定加载插件的资源,如果把宿主的资源路径也用addAssetPath加入资源池,那生成的Resources对象就可以加载两者的资源,将新的Resources赋值给Application
3. 类加载:
既然类加载最后会通过遍历Elements数组,那可以通过创建插件dex对应的DexClassLoader,通过反射获取到该DexClassLoader中DexPathList的Elements数组,将其加入到宿主ClassLoader的DexPathList的Elements数组中去,这样宿主的ClassLoader就能加载插件中的类了
4. 插件中activity的启动:
启动插件中的activity需要解决以下几个问题:
加载插件中的dex
加载插件中的资源
启动未在AndroidManifest.xml中注册过的activity
前两个已经解决,只看最后一个,启动未在AndroidManifest.xml中注册过的activity,需要完成以下两步:
使用AndroidManifest.xml中注册过的占位activity欺骗AMS躲过它的检测
在ApplicationThread中还原为目标activity的信息进行反射创建实例
5.使用AndroidManifest.xml中注册过的占位activity欺骗AMS躲过它的检测:
hook AMS在应用程序进程中的binder对象,将它的startActivity方法中的intent参数换成占位的activity对应的intent
android 10 binder对象为IActivityTaskManager类型,通过ActivityTaskManager.getService()在ActivityTaskManager中的IActivityTaskManagerSingleton单例静态对象中获取,对应mInstance字段
android 8及以上 binder对象为IActivityManager类型,通过ActivityManager.getService()在ActivityManager中的IActivityTaskManagerSingleton单例静态对象中获取,对应mInstance字段
android 8以下binder对象为IActivityManager类型,通过 ActivityManagerNative.getDefault()在ActivityManagerNative中的gDefault单例静态对象中获取,对应mInstance字段
在不同版本下,通过反射获取到该静态单例对象,并通过反射获取到该静态单例对象中的mInstance字段,通过动态代理创建同样实现IActivityManager/IActivityTaskManager接口,且startActivity方法中将intent参数替换为占位intent的代理对象(将目标intent放在占位intent的extra中,还原时候需要取出),然后将该代理对象赋值给静态单例对象中的mInstance字段
6.在ApplicationThread中还原为目标activity的信息进行反射创建实例:
ApplicationThread的scheduleRelaunchActivity中,利用ActivityThread中的handler类型的变量mH,通过handler走到handleLaunchActivity中,通过反射创建activity的实例
android8.0及以下,对应的handler消息为LAUNCH_ACTIVITY(数值为100),msg.obj为ActivityClientRecord对象,activity对应的intent在ActivityClientRecord中的intent字段上
android8.0以上,对应的handler消息为EXECUTE_TRANSACTION(数值为159),msg.obj为ClientTransaction对象,执行方法为TransactionExecutor.execute(ClientTransaction)
会遍历ClientTransaction中的字段名为mActivityCallbacks的列表,执行里面元素的方法ClientTransactionItem.execute,ClientTransaction对象的mActivityCallbacks在ActivityStackSupervisor中的realStartActivityLocked中进行add操作,加入的元素为
LaunchActivityItem,LaunchActivityItem的execute方法会将自己的mIntent属性封装成ActivityClientRecord对象,调用handleLaunchActivity
可以hook ActivityThread中的mH中的mCallback字段,ActivityThread只有一个实例,为自身的sCurrentActivityThread字段
android8.0及以下,当msg.what为100时,获取msg.obj(ActivityClientRecord类型),通过反射将其intent字段替换为目标intent
android8.0以上,当msg.what为159时,获取msg.obj(ClientTransaction类型),通过反射获取mActivityCallbacks列表,如果其中item的类名为LaunchActivityItem,将他的mIntent字段替换为目标intent
7.额外注意事项
经过上面两步,可以启动插件中的activity,但该activity无法加载插件中的资源,目前他是属于宿主的application,需要重写它的getResources,getAssets,getTheme方法,将其替换成application的Resources(此时为宿主的application的Resources,可以加载插件资源)
至此,插件化的基本逻辑已经实现,demo地址:
https://download.csdn.net/download/fyfl13/19431377