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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容