漫谈Android组件化及Web化

2,522 阅读7分钟


内容来源:2018 年 04 月 14 日,高级Android工程师陈家伟在“2018互联网开发者大会”进行《漫谈Android组件化及Web化》演讲分享。IT 大咖说(微信id:itdakashuo)作为独家视频合作方,经主办方和讲者审阅授权发布。

阅读字数:3326 | 9分钟阅读

嘉宾演讲视频及PPT:t.cn/Rr62oSm

摘要

本次分享主要讲述Android组件化和Android web化的原理与实践。

Android动态化介绍

动态化演进

安卓的动态化主要包含三个部分,分别是组件化、插件化、模块化。模块化很容易理解,指的是为了解耦将某一个功能拆分成独立的模块,最常见的模块有网络模块和下载模块。插件化和组件化的概念就比较模糊,不同的框架所做的定义都不一样,它们之间的边界也不太明显。

上图是某App对插件化和组件化的理解图示,左面表示的是组件化,右边表示的是插件化。组件化的机器人是由多个组件构成,插件化的机器人是一个整体可以进行分发。

这张示意图乍看没什么问题,但是其实还是存在漏洞,比如当组件化的机器人某一部分变的足够大的时候,该部分其实可以脱离出来成为新的机器人,而当插件化机器人功能越来越弱小的时候也可以演变从一个组件。总的来说组件化和插件化的边界并不是很明显,只是根据站的角度和处理问题的方法不同而产生的概念性上的定义。

Android动态化需要解决的问题

Android动态化需要解决4个问题,分别是Dex加载、资源加载、SO加载、四大组件加载。下文将介绍这四个问题所涉及的安卓的具体部分。

Dex是安卓编译后的产物,Java会被编译成class,安卓则对这些class文件进行压缩处理得到一个Dex。安卓的资源比较多,有图片、布局文件、动画等。SO是安卓的动态链接库,一般由C或者C++写成。安卓的四大组件Activity,Service,Content Provider,BroadcastReceiver必须在AndroidManifest.xml配置文件中声明才能够正常加载。

主流动态化框架

目前主流的动态化框架有Atlas、RePlugin、DroidPlugin、VirtualAPK,除开Atlas将自己定义为组件化框架外,其他三个都将自己定义为插件化框架。根据个人的观察发现,他们主要的区别在于对安卓的四大组件的处理上,Atlas是先定义这些组件再通过打包的方式处理。但是去年Atlas也做了一些插件化的处理,这使得目前的这四个框架都涉及到了插件化。

组件化和插件化

相信大家都经常遇到产品的某些需求要紧急上线的情况,但是App不同于H5开发那样可以随时发版,需要经过众多的渠道进行发布,而如果能够做到动态发版,对整个产品的演进和动态开发都会有比较好的推进作用。

另外减少包体积同样也很重要,一般同个App,iOS的包体积会比Android的更大,这是由于iOS无法进行本地代码的动态下发,而国内的安卓渠道审核相对比较松一些。还有一个需要关注的是热修复,它可以让我们即时的修复线上的BUG。

上文提到的这三点其实就是组件化和插件化共同目的,只不过他们在实现手段上有所不同。一般组件是和主工程一起打包,插件则可以独立打包、另外组件需要借助打包完成更多工作

动态加载原理

动态加载App思路之类加载

前面提到过插件化要解决的其中一个问题就是Dex加载。在Java中可以通过ClassLoader加载class文件,安卓方面则提供了BaseDexClassLoader。BaseDexClassLoader内部有很多DexFile,它是Dex的文件描述,要想实现类加载,可以直接将插件的DexFile前插到BaseDexClassLoader。这种方式就是单类加载器。

安卓中系统类由BootClassLoader加载,PathClassLoader继承自BootClassLoader,加载的是App类。Altlas为了实现类加载,将PathClassLoader替换为了自己的ClassLoader——DelegateClassLoader,这时所有类的加载都会经过DelegateClassLoader,且每个插件都由独立的PluginClassLoader加载,这些类的查找则由DelegateClassLoader完成。这种方式是多类加载器。

动态加载App思路之资源加载

安卓在打包的时候会为每个资源分配一个32位Int型的ID,采用16进制表示。0x后面是类似PPTTEEEE的形式,TT代表类别,EEEE代表条目,安卓中所有打包资源ID的PP都是7F。

安卓中的资源加载有两种方式,第一种是资源隔离。指的是每个插件由不同的Resources对象加载资源(安卓中通过Resources对象获取资源),这是为了避免由于资源ID相同造成的资源冲突问题。

第二个资源加载方式是资源分区,它通过修改安卓的打包工具,使得可以变更资源ID的PP,已达到避免ID重复的目的。

对比这两种方式可以发现,资源隔离的好处在于不需要修改打包工具, 毕竟打包工具是使用C++写的,维护起来也比较麻烦(aapt2已经支持资源分区)。资源分区的优势在于能够资源共用,因为它可以共用一个Resources对象,同时还可以避免资源冲突。

动态加载App之四大组件加载

四大组件的加载同样有两种方式。一种是通过合并manifest信息方式,比如手动拷贝或者打包,将插件Menifest信息合并入主APP。另一种是事先声明空的四大组件,再通过Hock掉系统的入口来启动四大组件。

Google玩转动态化

虽然动态框架在国内很流行,但国外对此却不是很热衷,他们更多的还是使用React Native。

国内的动态框架主要是研究如何通过反射调用或者Hock掉系统API来达到目的,不过系统API的调用其实存在着风险,因为每个版本的私有API的变动都是挺大的。为此Google也提供了一种动态框架——Instant,它通过Google Play Service加载,即有插件化也有组件化的特点,通过aapt2来完成资源分区,对于四大组件的加载采用预埋、代理的方式启动Activity service。

Web化介绍

一般App的活动页都是使用H5开发,因为H5可以进行动态更新。但是H5体验上还是不如Native,在动画以及一些高级功能方面也不够强。

而组件化也存在着问题,在最新发布的Android P版本中限制了对私有API的访问,一旦访问私有API 应用就会崩溃。虽然可以通过某些技术手段攻克这一限制,但是其实还有另一种方式——SPA(单页面应用)。 对于Web端的SPA,它只有一个HTML文件,然后通过JS渲染,以达到在一个HTML的进行页面跳转的目的。

下面来看下Android中的web化。

首先是React Native。React Native中每个页面都是一个View,且都在Activity中,它通过控制View的切换来进行页面跳转。

Android提供了一种布局容器——Fragment,Activity可以承载很多Fragment,通过切换Fragment也可以达到页面切换的效果。这就是Android Native的web化。

Android web核心原理

Android Native web实现的核心是多类加载器、资源隔离以及context替换。

因为要保证命名和混淆规则不能出现同一个类名,所以无法使用单类加载器。多类加载器由于采用不同ClassLoader加载插件,因此不用顾虑命名是否重复。

Context替换指的是将Fragment中的Context替换成我们自定义的Context。Fragment中所有的类和资源都是通过Context访问,而通过自定义Context就能达到动态加载Activity外部插件的目的。