Android开发的艺术:插件化框架Virtual APK实现原理解析

// 宿主Context

this.mHostContext = context;

// 插件apk路径

this.mLocation = apk.getAbsolutePath();

this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);

// 插件apk metadata

this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;

// 插件apk package信息

this.mPackageInfo = new PackageInfo();

this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;

this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();

// 插件apk 签名信息

if (Build.VERSION.SDK_INT >= 28

|| (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview

try {

this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;

} catch (Throwable e) {

PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);

this.mPackageInfo.signatures = info.signatures;

}

} else {

this.mPackageInfo.signatures = this.mPackage.mSignatures;

}

// 插件apk 包名

this.mPackageInfo.packageName = this.mPackage.packageName;

// 如果已经加载过相同的apk, 抛出异常

if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {

throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);

}

this.mPackageInfo.versionCode = this.mPackage.mVersionCode;

this.mPackageInfo.versionName = this.mPackage.mVersionName;

this.mPackageInfo.permissions = new PermissionInfo[0];

this.mPackageManager = createPluginPackageManager();

this.mPluginContext = createPluginContext(null);

this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);

this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath();

// 创建插件的资源管理器

this.mResources = createResources(context, getPackageName(), apk);

// 创建 一个dexClassLoader

this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());

tryToCopyNativeLib(apk);

// Cache instrumentations

Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();

for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) {

instrumentations.put(instrumentation.getComponentName(), instrumentation.info);

}

this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);

this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]);

// Cache activities

// 保存插件apk的Activity信息

Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();

for (PackageParser.Activity activity : this.mPackage.activities) {

activity.info.metaData = activity.metaData;

activityInfos.put(activity.getComponentName(), activity.info);

}

this.mActivityInfos = Collections.unmodifiableMap(activityInfos);

this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);

// Cache services

// 保存插件apk的Service信息

Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();

for (PackageParser.Service service : this.mPackage.services) {

serviceInfos.put(service.getComponentName(), service.info);

}

this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);

this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);

// Cache providers

// 保存插件apk的ContentProvider信息

Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();

Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();

for (PackageParser.Provider provider : this.mPackage.providers) {

providers.put(provider.info.authority, provider.info);

providerInfos.put(provider.getComponentName(), provider.info);

}

this.mProviders = Collections.unmodifiableMap(providers);

this.mProviderInfos = Collections.unmodifiableMap(providerInfos);

this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);

// 将所有静态注册的广播全部改为动态注册

Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();

for (PackageParser.Activity receiver : this.mPackage.receivers) {

receivers.put(receiver.getComponentName(), receiver.info);

BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());

for (PackageParser.ActivityIntentInfo aii : receiver.intents) {

this.mHostContext.registerReceiver(br, aii);

}

}

this.mReceiverInfos = Collections.unmodifiableMap(receivers);

this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);

// try to invoke plugin’s application

// 创建插件apk的Application对象

invokeApplication();

}

2.2 Activity的启动处理及生命周期管理

Virtual APK启动插件APK中Activity的整体方案:

  1. Hook Instrumentaion 和主线程Halder的callback,在重要启动过程节点对Intent或Activity进行替换

  2. 在宿主APP中预先设置一些插桩Activity,这些插桩Activity并不会真正的启动,而是对AMS进行欺骗。如果启动的Activity是插件APK中的,则根据该Actiivty的启动模式选择合适的插桩Activity, AMS在启动阶段对插桩Activity处理后,在创建Activity实例阶段,实际创建插件APK中要启动的Activity。

  • 2.2.1 插桩Activity的声明:

插桩Activity有很多个,挑一些看一下:

<activity android:exported=“false” android:name=“.A$2” android:launchMode=“standard”

android:theme=“@android:style/Theme.Translucent” />

  • 2.2.2 hook Instrumentation
  1. 将系统提供的Instrumentation替换为自定义的VAInstrumentation,将主线程Handler的Callback也替换为VAInstrumentation(VAInstrumentation 实现了Handler.Callback接口)

protected void hookInstrumentationAndHandler() {

try {

// 获取当前进程的activityThread

ActivityThread activityThread = ActivityThread.currentActivityThread();

// 获取当前进程的Instrumentation

Instrumentation baseInstrumentation = activityThread.getInstrumentation();

// 创建自定义Instrumentation

final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);

// 将当前进程原有的Instrumentation对象替换为自定义的

Reflector.with(activityThread).field(“mInstrumentation”).set(instrumentation);

// 将当前进程原有的主线程Hander的callback替换为自定义的

Handler mainHandler = Reflector.with(activityThread).method(“getHandler”).call();

Reflector.with(mainHandler).field(“mCallback”).set(instrumentation);

this.mInstrumentation = instrumentation;

Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation);

} catch (Exception e) {

Log.w(TAG, e);

}

}

  • 2.2.3 启动Activity时对AMS进行欺骗

如果我们熟悉Activity启动流程的话,我们一定知道Activity的启动和生命周期管理,都间接通过Instrumentation进行管理的。–如果不熟悉也没关系,可以看我之前写的AMS系列文章,看完保证秒懂(雾)。VAInstrumentation重写了这个类的一些重要方法,我们根据Activity启动流程一个一个说

  • 2.2.3.1 execStartActivity

这个方法有很多个重载,挑其中一个:

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {

// 对原始Intent进行处理

injectIntent(intent);

return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);

}

injectIntent方法对Intent的处理在ComponentsHandler#markIntentIfNeeded方法,对原始Intent进行解析,获取目标Actiivty的包名和类名,如果目标Activity的包名和当前进程不同且该包名对应的LoadedPlugin对象存在,则说明它是我们加载过的插件APK中的Activity,则对该Intent的目标进行替换:

public void markIntentIfNeeded(Intent intent) {

String targetPackageName = intent.getComponent().getPackageName();

String targetClassName = intent.getComponent().getClassName();

// 判断是否需要启动的是插件Apk的Activity

if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {

// 将原始Intent的目标Acitivy替换为预设的插桩Activity中的一个

dispatchStubActivity(intent);

}

}

dispatchStubActivity方法根据原始Intent的启动模式选择合适的插桩Activity,将原始Intent中的类名修改为插桩Activity的类名,示例代码:

case ActivityInfo.LAUNCH_SINGLE_TOP: {

usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;

stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);

break;

}

case ActivityInfo.LAUNCH_SINGLE_TASK: {

usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;

stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);

break;

}

case ActivityInfo.LAUNCH_SINGLE_INSTANCE: {

usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;

stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);

break;

}

  • 2.2.3.2 newActivity

如果只是对原始Intent进行替换,那么最终启动的会是插桩Activity,这显然达不到启动插件Apk中Acitivty的目的,在Activity实例创建阶段,还需要对实际创建的Actiivty进行替换,方法在VAInstrumentation#newActivity:

@Override

public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {

try {

cl.loadClass(className);

Log.i(TAG, String.format(“newActivity[%s]”, className));

} catch (ClassNotFoundException e) {

ComponentName component = PluginUtil.getComponent(intent);

String targetClassName = component.getClassName();

Log.i(TAG, String.format(“newActivity[%s : %s/%s]”, className, component.getPackageName(), targetClassName));

LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);

// 使用在LoadedPlugin对象中创建的DexClassLoader进行类加载,该ClassLoader指向插件APK所在路径

Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);

activity.setIntent(intent);

// 插件Activity实例创建后,将Resource替换为插件APK的资源

Reflector.QuietReflector.with(activity).field(“mResources”).set(plugin.getResources());

return newActivity(activity);

}

return newActivity(mBase.newActivity(cl, className, intent));

}

如果我们启动的是插件APK里的Activity,这个方法的Catch语句块是一定会被执行的,因为入参className已经被替换为插桩Activity的,但是我们只是在宿主App的AndroidManifest.xml中定义了这些Actiivty,并没有真正的实现。在进入Catch语句块后,使用LoadedPlugin中保存的DexClassloader进行Activity的创建。

  • 2.2.3.3 AMS对插件APK中的Activity管理

看到这里,可能就会有同学有问题了,你把要启动的Activity给替换了,但是AMS中不是还记录的是插桩Actiivty么,那么这个Activity实例后续跟AMS的交互怎么办?那岂不是在AMS中的记录找不到了?放心,不会出现这个问题的。复习之前AMS系列文章我们就会知道,AMS中对Activity管理的依据是一个叫appToken的Binder实例,在客户端对应的token会在Instrumentation#newActivity执行完成后调用Activity#attach方法传递给Actiivty。

这也是为什么对AMS进行欺骗这种插件化方案可行的原因,因为后续管理是使用的token,如果Android使用className之类的来管理的话,恐怕这种方案就不太好实现了。

  • 2.2.3.4 替换Context、applicaiton、Resources

在系统创建插件Activity的Context创建完成之后,需要将其替换为PluginContext,PluginContext和Context的区别是其内部保存有一个LoadedPlugin对象,方便对Context中的资源进行替换。代码在VAInstrumentaiton#injectActivity,调用处在VAInstrumentaiton#callActivityOnCreate

protected void injectActivity(Activity activity) {

final Intent intent = activity.getIntent();

if (PluginUtil.isIntentFromPlugin(intent)) {

Context base = activity.getBaseContext();

try {

LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);

Reflector.with(base).field(“mResources”).set(plugin.getResources());

Reflector reflector = Reflector.with(activity);

reflector.field(“mBase”).set(plugin.createPluginContext(activity.getBaseContext()));

reflector.field(“mApplication”).set(plugin.getApplication());

// set screenOrientation

ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));

if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {

activity.setRequestedOrientation(activityInfo.screenOrientation);

}

// for native activity

ComponentName component = PluginUtil.getComponent(intent);

Intent wrapperIntent = new Intent(intent);

wrapperIntent.setClassName(component.getPackageName(), component.getClassName());

wrapperIntent.setExtrasClassLoader(activity.getClassLoader());

activity.setIntent(wrapperIntent);

} catch (Exception e) {

Log.w(TAG, e);

}

}

}

2.3 Service的处理

Virtual APK启动插件APK中Activity的整体方案:

  1. 使用动态代理代理宿主APP中所有关于Service的请求

  2. 判断是否为插件APK中的Service,如果不是,则说明为宿主 APP中的,直接打开即可

  3. 如果是插件APK中的Service,则判断是否为远端Service,如果是远端Service,则启动RemoteService,并在其StartCommand方法中根据所代理的生命周期方法进行处理

  4. 如果是本地Service,则启动LocalService,并在其StartCommand方法中根据所代理的生命周期方法进行处理

  • 2.3.1 插件化框架初始化时代理系统的IActivityManager

IActivityManager是AMS的实现接口,它的实现类分别是ActivityManagerService和其proxy

这里我们需要代理的是Proxy,实现方法在PluginManager#hookSystemServices

protected void hookSystemServices() {

try {

Singleton<IActivityManager对象> defaultSingleton;

// 获取IActivityManager对象

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

defaultSingleton = Reflector.on(ActivityManager.class).field(“IActivityManagerSingleton”).get();

} else {

defaultSingleton = Reflector.on(ActivityManagerNative.class).field(“gDefault”).get();

}

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

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

图片转存中…(img-CNfZyyow-1712157377464)]

[外链图片转存中…(img-jPEqs8sX-1712157377465)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值