2024 Android 大厂面试(五)插件化,安卓面试题

(3)我们使用 hook 思想代理 startActivity 这个方法,使用占坑的方式。

  1. startActivity 的时候最终会走到 AMS 的 startActivity 方法
  2. 系统会检查一堆的信息验证这个 Activity 是否合法。
  3. 然后会回调 ActivityThread 的 Handler 里的 handleLaunchActivity
  4. 在这里走到了 performLaunchActivity 方法去创建 Activity 并回调一系列生命周期的方法
  5. 创建 Activity 的时候会创建一个 LoaderApk对象,然后使用这个对象的 getClassLoader 来创建 Activity
  6. 我们查看 getClassLoader() 方法发现返回的是 PathClassLoader,然后他继承自 BaseDexClassLoader
  7. 然后我们查看 BaseDexClassLoader 发现他创建时创建了一个 DexPathList 类型的 pathList对象,然后在 findClass 时调用了 pathList.findClass 的方法
  8. 然后我们查看 DexPathList类 中的 findClass 发现他内部维护了一个 Element[] dexElements的dex 数组,findClass 时是从数组中遍历查找的

2.插件化原理分析

cloud.tencent.com/developer/a…

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader。
DexClassloader多传了一个optimizedDirectory

DexPathList

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

多DexClassLoader

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每个插件单独一个DexClassLoader,相对隔离,RePlugin采用该方案

单DexClassLoader

将插件的DexClassLoader中的pathList合并到主工程的DexClassLoader中。方便插件与宿主(插件)之间的调用,Small采用该方案

插件调用主工程
主工程的ClassLoader作为插件ClassLoader的父加载器

主工程调用插件
若使用多ClassLoader机制,通过插件的ClassLoader先加载类,再通过反射调用
若使用单ClassLoader机制,直接通过类名去访问插件中的类,弊端是库的版本可能不一致,需要规范

资源加载

//创建AssetManager对象
AssetManager assets = new AssetManager();
//将apk路径添加到AssetManager中
if (assets.addAssetPath(resDir) == 0){
return null;
}
//创建Resource对象

r = new Resources(assets, metrics, getConfiguration(), compInfo);

插件apk的路径加入到AssetManager中
通过反射去创建,并且部分Rom对创建的Resource类进行了修改,所以需要考虑不同Rom的兼容性。

资源路径的处理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Context的处理

// 第一步:创建Resource

if (Constants.COMBINE_RESOURCES) {
//插件和主工程资源合并时需要hook住主工程的资源
Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
ResourcesManager.hookResources(context, resources);
return resources;
} else {
//插件资源独立,该resource只能访问插件自己的资源
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}

//第二步:hook主工程的Resource

//对于合并式的资源访问方式,需要替换主工程的Resource,下面是具体替换的代码。

public static void hookResources(Context base, Resources resources) {
try {
ReflectUtil.setField(base.getClass(), base, “mResources”, resources);
Object loadedApk = ReflectUtil.getPackageInfo(base);
ReflectUtil.setField(loadedApk.getClass(), loadedApk, “mResources”, resources);

Object activityThread = ReflectUtil.getActivityThread(base);
Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, “mResourcesManager”);
if (Build.VERSION.SDK_INT < 24) {
Map<Object, WeakReference> map = (Map<Object, WeakReference>) ReflectUtil.getField(resManager.getClass(), resManager, “mActiveResources”);
Object key = map.keySet().iterator().next();
map.put(key, new WeakReference<>(resources));
} else {
// still hook Android N Resources, even though it’s unnecessary, then nobody will be strange.
Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, “mResourceImpls”);
Object key = map.keySet().iterator().next();
Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, “mResourcesImpl”);
map.put(key, new WeakReference<>(resourcesImpl));
}
} catch (Exception e) {
e.printStackTrace();
}

替换了主工程context中LoadedApk的mResource对象

将新的Resource添加到主工程ActivityThread的mResourceManager中,并且根据Android版本做了不同处理

//第三步:关联resource和Activity

Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
//设置Activity的mResources属性,Activity中访问资源时都通过mResources

ReflectUtil.setField(ContextThemeWrapper.class, activity, “mResources”, plugin.getResources());

资源冲突

资源id是由8位16进制数表示,表示为0xPPTTNNNN, 由三部分组成:PackageId+TypeId+EntryId

修改aapt源码,编译期修改PP段。
修改resources.arsc文件,该文件列出了资源id到具体资源路径的映射。

blog.csdn.net/jiangwei091…

// Main.cpp
result = handleCommand(&bundle);
case kCommandPackage: return doPackage(bundle);

// Command.cpp
int doPackage(Bundle* bundle) {
if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
err = buildResources(bundle, assets, builder);
if (err != 0) {
goto bail;
}
}
}

Resource.cpp
buildResources

ResourceTable.cpp

switch(mPackageType) {
case App:
case AppFeature:
packageId = 0x7f;
break;
case System:
packageId = 0x01;
break;
case SharedLibrary:
packageId = 0x00;
break;
}

首先找到入口类:Main.cpp:main函数,解析参数,然后调用handleCommand函数处理参数对应的逻辑,我们看到了有一个函数doPackage。

然后就搜索到了Command.cpp:在他内部的doPackage函数中进行编译工具的一个函数:buildResources函数,在全局搜索,发现了Resource.cpp:发现这里就是处理编译工作,构建ResourceTable的逻辑,在ResourceTable.cpp中,也是获取PackageId的地方,下面我们就来看看如何修改呢?

其实最好的方法是,能够修改aapt源码,添加一个参数,把我们想要编译的PackageId作为输入值,传进来最好了,那就是Bundle类型,他是从Main.cpp中的main函数传递到了最后的buildResources函数中,那么我们就可以把这个参数用Bundle进行携带。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

juejin.im/entry/68449… www.jianshu.com/p/8d691b6bf…

————————————————————————————————————————————————

cloud.tencent.com/developer/a…

在整个过程中,需要修改到R文件、resources.arsc和二进制的xml文件

四大组件支持

ProxyActivity代理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代理方式的关键总结起来有下面两点:

ProxyActivity中需要重写getResouces,getAssets,getClassLoader方法返回插件的相应对象。生命周期函数以及和用户交互相关函数,如onResume,onStop,onBackPressedon,KeyUponWindow,FocusChanged等需要转发给插件。
PluginActivity中所有调用context的相关的方法,如setContentView,getLayoutInflater,getSystemService等都需要调用ProxyActivity的相应方法。

该方式有几个明显缺点:

插件中的Activity必须继承PluginActivity,开发侵入性强。
如果想支持Activity的singleTask,singleInstance等launchMode时,需要自己管理Activity栈,实现起来很繁琐。
插件中需要小心处理Context,容易出错。
如果想把之前的模块改造成插件需要很多额外的工作。

预埋StubActivity,hook系统启动Activity的过程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

VirtualAPK通过替换了系统的Instrumentation,hook了Activity的启动和创建,省去了手动管理插件Activity生命周期的繁琐,让插件Activity像正常的Activity一样被系统管理,并且插件Activity在开发时和常规一样,即能独立运行又能作为插件被主工程调用。

其他插件框架在处理Activity时思想大都差不多,无非是这两种方式之一或者两者的结合。在hook时,不同的框架可能会选择不同的hook点。如360的RePlugin框架选择hook了系统的ClassLoader,即构造Activity2的ClassLoader,在判断出待启动的Activity是插件中的时,会调用插件的ClassLoader构造相应对象。另外RePlugin为了系统稳定性,选择了尽量少的hook,因此它并没有选择hook系统的startActivity方法来替换intent,而是通过重写Activity的startActivity,因此其插件Activity是需要继承一个类似PluginActivity的基类的。不过RePlugin提供了一个Gradle插件将插件中的Activity的基类换成了PluginActivity,用户在开发插件Activity时也是没有感知的。

www.jianshu.com/p/ac96420fc…

sanjay-f.github.io/2016/04/17/…

www.jianshu.com/p/d43e1fb42…

Service插件化总结

初始化时通过ActivityManagerProxy Hook住了IActivityManager。
服务启动时通过ActivityManagerProxy拦截,判断是否为远程服务,如果为远程服务,启动RemoteService,如果为同进程服务则启动LocalService。
如果为LocalService,则通过DexClassLoader加载目标Service,然后反射调用attach方法绑定Context,然后执行Service的onCreate、onStartCommand方法
如果为RemoteService,则先加载插件的远程Service,后续跟LocalService一致。

3.模块化实现(好处,原因)

www.cnblogs.com/Jackie-zhan…

1、模块间解耦,复用。
(原因:对业务进行模块化拆分后,为了使各业务模块间解耦,因此各个都是独立的模块,它们之间是没有依赖关系。
每个模块负责的功能不同,业务逻辑不同,模块间业务解耦。模块功能比较单一,可在多个项目中使用。)

2、可单独编译某个模块,提升开发效率。
(原因:每个模块实际上也是一个完整的项目,可以进行单独编译,调试)

3、可以多团队并行开发,测试。
原因:每个团队负责不同的模块,提升开发,测试效率。

组件化与模块化

组件化是指以重用化为目的,将一个系统拆分为一个个单独的组件

避免重复造轮子,节省开发维护成本;
降低项目复杂性,提升开发效率;
多个团队公用同一个组件,在一定层度上确保了技术方案的统一性。

模块化业务分层:由下到上

基础组件层:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

上述【高清技术脑图】以及【配套的架构技术PDF】可以关注我免费获取

Android学习PDF+架构视频+面试文档+源码笔记

把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

[外链图片转存中…(img-W2aa7EcO-1711037933073)]

[外链图片转存中…(img-I6Hf7zPn-1711037933073)]

上述【高清技术脑图】以及【配套的架构技术PDF】可以关注我免费获取

Android学习PDF+架构视频+面试文档+源码笔记

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值