Android 插件化原理入门笔记

Android开发笔记 onGithub

笔记,参考7.2中所列参考文章所写,DEMO地址在PluginTestDemoApplication

1.综述

2015年是Android插件化技术突飞猛进的一年,随着业务的发展各大厂商都碰到了Android Native平台的瓶颈。 从技术上讲,业务逻辑的复杂导致代码量急剧膨胀,各大厂商陆续出到65535方法数的天花板;同时,运营为王的时代对于模块热更新提出了更高的要求。 在业务层面上,功能模块的解耦以及维护团队的分离也是大势所趋;各个团队维护着同一个App的不同模块,如果每个模块升级新功能都需要对整个app进行升级,那么发布流程不仅复杂而且效率低下;在讲究小步快跑和持续迭代的移动互联网必将遭到淘汰。 H5和Hybird可以解决这些问题,但是始终比不上native的用户体验;于是,国外的FaceBook推出了react-native;而国内各大厂商几乎都选择纯native的插件化技术。可以说,Android的未来必将是react-native和插件化的天下。 react-native资料很多,但是讲述插件化的却凤毛菱角;

插件化技术听起来高深莫测,实际上要解决的就是两个问题:

  • 1.代码加载
  • 2.资源加载
1.1 需要解决的问题
  • 1.代码的加载

    类的加载可以使用Java的ClassLoader机制,但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给它们注入活力,也就是所谓的组件生命周期管理;

    另外,如何管理加载进来的类也是一个问题。假设多个插件依赖了相同的类,是抽取公共依赖进行管理还是插件单独依赖?这就是ClassLoader的管理问题;

  • 2.资源的加载

    资源加载方案大家使用的原理都差不多,都是用AssetManager的隐藏方法addAssetPath;但是,不同插件的资源如何管理?是公用一套资源还是插件独立资源?共用资源如何避免资源冲突?对于资源加载,有的方案共用一套资源并采用资源分段机制解决冲突(要么修改aapt要么添加编译插件);有的方案选择独立资源,不同插件管理自己的资源。

1.2 Android中加载类 和 Java加载类的区别

Android中许多组件类(如Activity、Service等)是需要在Manifest文件里面注册后才能工作的(系统会检查该组件有没有注册),所以即使动态加载了一个新的组件类进来,没有注册的话还是无法工作;

Res资源是Android开发中经常用到的,而Android是把这些资源用对应的R.id注册好,运行时通过这些ID从Resource实例中获取对应的资源。如果是运行时动态加载进来的新类,那类里面用到R.id的地方将会抛出找不到资源或者用错资源的异常,因为新类的资源ID根本和现有的Resource实例中保存的资源ID对不上;

2.实现思路

目前国内开源的较成熟的插件方案有DL和DroidPlugin;但是DL方案仅仅对Framework的表层做了处理,严重依赖that语法,编写插件代码和主程序代码需单独区分;而DroidPlugin通过Hook增强了Framework层的很多系统服务,开发插件就跟开发独立app差不多;就拿Activity生命周期的管理来说,DL的代理方式就像是牵线木偶,插件只不过是操纵傀儡而已;而DroidPlugin则是借尸还魂,插件是有血有肉的系统管理的真正组件;DroidPlugin Hook了系统几乎所有的Sevice,欺骗了大部分的系统API;掌握这个Hook过程需要掌握很多系统原理,因此学习DroidPlugin对于整个Android FrameWork层大有裨益。

以DroidPlugin为例的方式

  • 1.构造DexClassLoader : 既然我们知道如果想启动插件apk就需一个Classloader,那么我们换一种想法,能不能我们将我们的插件apk的信息告诉系统的这个Classloader,然后让系统的Classloader来帮我们加载及创建呢?答案是肯定之前我们说过讲过android中的Classloader主要分析PathClassLoader和DexClassLoader,系统通过PathClassLoader来加载系统类和主dex中的类。而DexClassLoader则用于加载其他dex文件中的类。他们都是继承自BaseDexClassLoader。(如果没有看过的建议先看看 插件化知识详细分解及原理 之ClassLoader及dex加载过程)

  • 2.拿到宿主apk里ClassLoader中的pathList对象和我们Classloader的pathList,进行合并

    • 2.1 通过PathClassLoader拿到宿主应用的dexPathList,通过DexClassLoader拿到插件的dexPathList。
    • 2.2 拿到宿主和插件的dex列表
    • 2.3 将宿主和插件的dexPathList合并
  • 3.hook住startActivity方法,使用占坑的方式,也就是说我们可以提前在AndroidManifest中固定写死一个Activity,这个Activity只不过是一个傀儡,我们在启动我们插件apk的时候使用它去系统层校检合法性,然后等真正创建Activity的时候再通过hook思想拦截Activity的创建方法,提前将信息更换回来创建真正的插件apk。

    • 3.1 通过反射,拿到AMS的代理对象
    • 3.2 自定义InvocationHandler拦截startActivty方法
    • 3.3 带系统检查完Activity合法性后重新执行原Activity

3.准备工作

3.1 理解应用的启动过程

下面是大致的流程,详细内容可以看Activity的笔记或者罗升阳的《Android源码分析》

应用程序是由Launcher启动起来的,其实Launcher本身也是一个应用程序。

启动一个应用时会调用 startActivitySafely -> startActivity -> startActivityForResult

最终会调用InstrumentationexecStartActivity方法。

execStartActivity中会调用ActivityManagerNative.getDefault().startActivity ,实际上就是调用了AMS的startActivity方法。

execStartActivity中还会调用checkStartActivityResult方法对start的结果进行检查。

AMS的startActivity->startActivityAsUser->mActivityStarter.startActivityMayWait,最终调用 ActivityStackSupervisorrealStartActivityLocked方法。

realStartActivityLocked方法中,会调用ActivityThreadscheduleLaunchActivity方法。

scheduleLaunchActivity方法中 有一行代码 sendMessage(H.LAUNCH_ACTIVITY, r);

ActivityThread内部的H类中调用

public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
    

        }
复制代码

handleLaunchActivity->performLaunchActivity

performLaunchActivity中通过Instrumentation.newActivity创建Activity,并创建Application和Context

3.2 理解Activity和Service的启动过程

performLaunchActivity方法中

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

复制代码

实际是通过r.packageInfo这个LoadedApk类型的对象获得。

LoadedApk保存了Apk文件的相关信息。

H类里通过getPackageInfoNoCheck方法获得LoadedApk,针对代码的信息会存到mPackages表里

apk被安装之后,apk的文件代码以及资源会被系统存放在固定的目录比如/data/app/package_name/base.apk)中,系统在进行类加载的时候,会自动去这一个或者几个特定的路径来寻找这个类。(可以在Android Studio的Device File Explorer去查看)

在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等;我们上文所述的,启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的:

3.3 理解DexClassLoader 和 PathClassLoader

3.3.1 DexClassLoader

构造函数:

public DexClassLoader (String dexPath, String dexOutputDir, String libPath, ClassLoader parent)

复制代码
dexPath:dex文件路径列表,多个路径使用”:”分隔 
dexOutputDir:经过优化的dex文件(odex)文件输出目录 
libPath:动态库路径(将被添加到app动态库搜索路径列表中) 
parent:这是一个ClassLoader,这个参数的主要作用是保留java中ClassLoader的委托机制(优先父类加载器加载classes,由上而下的加载机制,防止重复加载类字节码)
复制代码

DexClassLoader是一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。这个类加载器必须要一个app的私有、可写目录来缓存经过优化的classes(odex文件)

3.3.2 PathClassLoader

PathClassLoader提供两个常用构造方法

public PathClassLoader (String path, ClassLoader parent)
1
public PathClassLoader (String path, String libPath, ClassLoader parent)
复制代码
path:文件或者目录的列表 
libPath:包含lib库的目录列表 
parent:父类加载器
复制代码
3.3.3区别
  • DexClassLoader:能够加载未安装的jar\apk dex。
  • PathClassLoader:Android系统通过PathClassLoader来加载系统类和主dex中的类。

参考:

3.4 理解反射和Hook

首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?自然是容易找到的对象。什么样的对象容易找到?静态变量和单例;在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。我们根据这个原则找到所谓的Hook点。


4.其他

4.1 热门热修复框架

目前比较有名的插件化框架: 任玉刚的:dynamic-load-apk,这个项目使用的是一种代理的方式去实现 https://github.com/singwhatiwanna/dynamic-load-apk

360的:DroidPlugin,这个项目是通过hook系统类来实现 https://github.com/Qihoo360/DroidPlugin

目前比较火的热修复框架: 阿里的:andfix,用于对方法的修复,可以立即生效,不支持资源及类替换 https://github.com/alibaba/AndFix

腾讯的:tinker,除了不支持立即生效,全部支持 https://github.com/Tencent/tinker

美团的:robust,不开源


7.2 参考文章

总述

Hook

生命周期管理

插件的加载

下面这几篇文章是参考上面的文章所写,主要参考上面的几篇文章,下面的文章作为补充

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值