close
当前位置: 物联网在线 > 技术文库 > android >

滴滴插件化方案 VirtualApk 源码解析

之前一直没有写过插件化相关的博客,刚好最近滴滴和360分别开源了自家的插件化方案,赶紧学习下,写两篇博客,第一篇是滴滴的方案:

https://github.com/didi/VirtualAPK

那么其中的难点很明显是对四大组件支持,因为大家都清楚,四大组件都是需要在AndroidManifest中注册的,而插件apk中的组件是不可能预先知晓名字,提前注册中宿主apk中的,所以现在基本都采用一些hack方案类解决,VirtualAPK大体方案如下:

Activity:在宿主apk中提前占几个坑,然后通过“欺上瞒下”(这个词好像是360之前的ppt中提到)的方式,启动插件apk的Activity;因为要支持不同的launchMode以及一些特殊的属性,需要占多个坑。

Service:通过代理Service的方式去分发;主进程和其他进程,VirtualAPK使用了两个代理Service。

BroadcastReceiver:静态转动态

ContentProvider:通过一个代理Provider进行分发。

这些占坑的数量并不是固定的,比如Activity想支持某个属性,该属性不能动态设置,只能在Manifest中设置,那就需要去占坑支持。所以占坑数量这些,可以根据自己的需求进行调整。

下面就逐一去分析代码啦~

注:本篇博客涉及到的framework逻辑,为API 22.

分期版本为 com.didi.virtualapk:core:0.9.0

二、Activity的支持

这里就不按照某个流程一行行代码往下读了,针对性的讲一些关键流程,可能更好阅读一些。

首先看一段启动插件Activity的代码:

final String pkg = "com.didi.virtualapk.demo"; if (PluginManager.getInstance(this).getLoadedPlugin(pkg) == null) { Toast.makeText(this, "plugin [com.didi.virtualapk.demo] not loaded", Toast.LENGTH_SHORT).show(); return; } // test Activity and Service Intent intent = new Intent(); intent.setClassName(pkg, "com.didi.virtualapk.demo.aidl.BookManagerActivity"); startActivity(intent);

可以看到优先根据包名判断该插件是否已经加载,所以在插件使用前其实还需要调用

pluginManager.loadPlugin(apk);

加载插件。

这里就不赘述源码了,大致为调用 PackageParser.parsePackage 解析apk,获得该apk对应的PackageInfo,资源相关(AssetManager,Resources),DexClassLoader(加载类),四大组件相关集合(mActivityInfos,mServiceInfos,mReceiverInfos,mProviderInfos),针对Plugin的PluginContext等一堆信息,封装为LoadedPlugin对象。

详细可以参考 com.didi.virtualapk.internal.LoadedPlugin 类。

ok,如果该插件以及加载过,则直接通过startActivity去启动插件中目标Activity。

(1)替换Activity

这里大家肯定会有疑惑,该Activity必然没有在Manifest中注册,这么启动不会报错吗?

正常肯定会报错呀,所以我们看看它是怎么做的吧。

跟进startActivity的调用流程,会发现其最终会进入Instrumentation的execStartActivity方法,然后再通过ActivityManagerProxy与AMS进行交互。

而Activity是否存在的校验是发生在AMS端,所以我们在于AMS交互前,提前将Activity的ComponentName进行替换为占坑的名字不就好了么?

这里可以选择hook Instrumentation,或者ActivityManagerProxy都可以达到目标,VirtualAPK选择了hook Instrumentation.

打开 PluginManager 可以看到如下方法:

private void hookInstrumentationAndHandler() { try { Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext); if (baseInstrumentation.getClass().getName().contains("lbe")) { // reject executing in paralell space, for example, lbe. System.exit(0); } final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation); Object activityThread = ReflectUtil.getActivityThread(this.mContext); ReflectUtil.setInstrumentation(activityThread, instrumentation); ReflectUtil.setHandlerCallback(this.mContext, instrumentation); this.mInstrumentation = instrumentation; } catch (Exception e) { e.printStackTrace(); } }

可以看到首先通过反射拿到了原本的 Instrumentation 对象,拿的过程是首先拿到ActivityThread,由于ActivityThread可以通过静态变量 sCurrentActivityThread 或者静态方法 currentActivityThread() 获取,所以拿到其对象相当轻松。拿到ActivityThread对象后,调用其 getInstrumentation() 方法,即可获取当前的Instrumentation对象。

然后自己创建了一个VAInstrumentation对象,接下来就直接反射将VAInstrumentation对象设置给ActivityThread对象即可。

这样就完成了hook Instrumentation,之后调用Instrumentation的任何方法,都可以在VAInstrumentation进行拦截并做一些修改。

这里还hook了ActivityThread的mH类的Callback,暂不赘述。

刚才说了,可以通过Instrumentation的execStartActivity方法进行偷梁换柱,所以我们直接看对应的方法:

public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent); // null component is an implicitly intent if (intent.getComponent() != null) { Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(), intent.getComponent().getClassName())); // resolve intent with Stub Activity if needed this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); } ActivityResult result = realExecStartActivity(who, contextThread, token, target, intent, requestCode, options); return result; }
(责任编辑:ioter)

用户喜欢...

Android开发周报:微信模块化重构实践、滴滴插件化项目开源

7月份安卓各版本份额:牛轧糖终于突破两位数:谷歌近日给出了安卓系统各版本最新的份额数据,去年推出牛轧糖版本(7.0,7.1)终于达到了两位数的市场份额,占11.5%。2015年推出的棉花糖...


Android Project 检查依赖库和插件版本

随着项目的开发,引用的库也不断增加,维护引用的版本也是一个耗时的问题.比如一个项目的依赖库如下: compile libraries.supportAppCompatcompile libraries.rxJavacompile libraries.rxAndroidcompile libraries.retrofit...


说一说Android Studio和IDEA中一个很有用的内存调试插件

JetBrains JVM Debugger Memory View plugin 在我最近的研发活动期间寻找新的工具,以提高我的开发经验,使Android Studio的生活更轻松,我发现一个有用的插件,我从来没有听说过。 这就是为什么,我决...


APP项目如何与插件化无缝结合(二)

上一篇主要介绍插件化的一些概念和作用,以及我为什么选择Small。现在来具体介绍下small。 Small的原理 1.动态加载class Android类由DexClassLoader 加载,如果直接在编译搜索这个类的时候出现下面这...


APP项目如何与插件化无缝结合(一)

插件化之旅 一直热衷于插件化,热更新相关,利用每天下班后的空余时间去研究,踩过很多坑,曾经为了一个坑,不解决难以入睡。都是血泪史,请珍爱每一个搬砖码字的程序猿。好吧,废...


Android闹钟设置的解决方案

Android设置闹钟并不像IOS那样这么简单,做过Android设置闹钟的开发者都知道里面的坑有多深。下面记录一下,我解决Android闹钟设置的解决方案。 主要问题 API19开始AlarmManager的机制修改。 应用...


AndroidStudio的Gradle插件版本更新简介

Android构建系统使用Android的Gradle插件通过Gradle的构建工具来支持构建Android程序。Android的Gradle插件独立于AndroidStudio运行,所以该插件和Gradle构建系统需要独立更新。 更新Android的Gradle插件 自动...


最全最好用的Android Studio插件整理

现在Android的开发者基本上都使用Android Studio进行开发(如果你还在使用eclipse那也行,毕竟你乐意怎么样都行)。使用好Android Studio插件能大量的减少我们的工作量。 1.GsonFormat 快速将json字符串转...


打造酷炫AndroidStudio插件

前面几篇文章学习了AndroidStudio插件的基础后,这篇文章打算开发一个酷炫一点的插件。因为会用到前面的基础,所以如果没有看前面系列文章的话,请先返回。当然,如果有基础的可以忽略...