android插件化简单实现(自用)

一 预备知识

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


demo中几个静态变量

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

你可能感兴趣的:(android插件化简单实现(自用))