1.插件化
插件可以理解为免安装的apk,而支持插件的app称为宿主。
一个apk通常会包含如下几个部分:
classes.dex:Java代码字节码
res:资源文件
lib:so 文件
assets:静态资产文件
AndroidManifest.xml:清单文件
Android系统在打开应用之后,只是开辟进程,然后使用ClassLoader加载classes.dex至进程中,执行对应的组件而已。
插件化让apk中的代码(主要是指Android组件)能够免安装运行,这样带来很多好处:
①减少安装Apk的体积、按需下载模块
②动态更新插件
③宿主和插件分开编译,提升开发效率
④解决方法数超过65535的问题
插件化与热修复:
插件化和热修复不是同一个概念,虽然在技术实现的角度来说,它们都是从系统加载器的角度出发,通过“欺骗”Android系统的方式来让宿主正常的加载和运行插件/补丁中的内容,但是它们的出发点是不同的。插件化是把需要实现的模块或功能独立出来,减少宿主的规模,当需要使用到相应功能时再去加载相应的模块。热修复则是从修复bug的角度出发,强调的是在不需要二次安装应用的前提下修复已知的bug。
插件化与组件化:
组件化是将一个App分成多个模块,每个模块都是一个组件(module),开发过程中可以让这些组件相互依赖或独立编译、调试部分组件,但是这些组件最终会合并成一个完整的apk发布到应用市场。组件化开发有一个弊端 ,就是多个模块必须是并发开发 , 模块之间相互依赖 , 如果修改了一个模块 ,那就必须重新打包 。而插件化是将整个app拆分成若干模块,其中有1个宿主模块、若干插件模块 。宿主模块和插件模块可以分开进行编译,二者之间互不影响,各个模块可以并发进行开发。打包时,将宿主模块和插件模块分开进行打包,即宿主模块和插件模块都各自是一个单独apk安装文件 。只需发布宿主Apk到应用市场,插件Apk通过动态按需下发到宿主Apk,实现宿主模块动态更新插件。
现在大型Android项目基本都是组件化+插件化开发。项目架构上都是组件化的框架,某些修改频繁的Module模块设置成插件模块,编译成独立的apk文件,以插件的形式进行部署,供宿主模块调用。应用运行时,点击启动某个插件apk中的界面,首先下载对应的插件apk文件,将其放在内置存储区中,然后加载该apk文件,主要是类加载器加载dex文件中的Class字节码数据。
插件包apk是运行后才加载的,将该apk文件中的dex加载到内存之后,其中的Activity、Service等组件类与当前应用运行的组件是有区别的:
①插件包apk中的类是通过DexClassLoader加载到内存中的,仅仅加载了字节码数据,因此插件包中的组件类没有上下文,组件的初始化、赋予上下文等必须由开发者手动完成,应用系统是不管的。而应用中的Activity、Service等组件初始化不需要开发者干预,都是由系统完成的,这些组件都在清单文件中注册过了,系统按照清单初始化相关组件。
②加载的插件中的Activity并不是Activity组件,只是有Activity方法、成员、继承关系的字节码类。而且插件Activity类没有生命周期,不在AMS管理范畴内。因此这些通过插件包加载进来的组件类的生命周期,需要开发者进行管理。
2.插件化实现步骤
①使用DexClassLoader加载插件apk中Activity对应的Class字节码类对象 。
要让插件Apk真正运行起来,首先要找到插件apk的存放位置,然后解析加载apk里面的代码。由于插件是未安装的apk,系统不会处理其中的类,所以需要使用ClassLoader加载Apk,然后反射里面的代码。
首先需要创建DexClassLoader实例,调用其loadClass(className)方法就可以加载插件中的类了。然后通过反射执行类的方法:
Class loadClass = pluginClassLoader.loadClass( activityName);
loadClass.getMethod("test",null).invoke(loadClass);
这个过程叫做ClassLoader注入。完成注入后,所有来自宿主的类使用宿主的ClassLoader进行加载,所有来自插件Apk的类使用插件ClassLoader 进行加载。由于ClassLoader的双亲委派机制,实际上系统类不会受ClassLoader的类隔离机制影响,这样宿主apk就可以在宿主进程中使用来自于插件的组件类了。
②为加载进来的Activity类注入上下文并管理生命周期,让系统能调用插件apk中的组件。
Android系统中四大组件是需要在系统中注册的,并且由系统管理生命周期,具体是在Android系统的AMS和PMS中注册,四大组件的解析和启动都需要依赖AMS和PMS。而插件是动态加载的,所以插件中的四大组件不可能注册到宿主的Manifest文件中,因此不能和系统直接进行交互。 而且仅仅构造出四大组件的实例是没用的,还需要管理组件的生命周期。
插件化如何支持组件生命周期的管理,大致分为两种方式:运行时容器技术(ProxyActivity代理)和预埋StubActivity,hook系统启动Activity的过程。
以运行时容器技术为例,就是在宿主apk中预埋一些空的Android组件。比如Activity,可以在宿主中预置一个ContainerActivity extends Activity ,并且在AndroidManifest.xml中注册它。这样,ContainerActivity就作为插件Activity的容器,它从Intent接收插件的不同信息,如pluginName、pluginApkPath、pluginActivityName等。当ContainerActivity启动时,就加载插件的 ClassLoader、Resource,并反射pluginActivityName对应的Activity类。当完成加载后,ContainerActivity要做两件事:
1)转发所有来自系统的生命周期回调至插件Activity
2)接受插件Activity的系统方法调用,并转发回系统
第一步可以通过复写ContainerActivity的生命周期方法来完成,而第二步需要定义一个 PluginActivity,然后在编写插件apk中的Activity 组件时,需要继承自PluginActivity。
大概原理就是这么简单,启动插件组件需要依赖容器,容器负责加载插件组件并且完成双向转发,转发来自系统的生命周期回调至插件组件,同时转发来自插件组件的系统调用至系统。
③使用AssetManager将插件apk中的资源主动加载进来
这一步就是资源注入,资源注入主要依靠两个接口:
1)PackageManager.getPackageArchiveInfo:根据apk路径解析一个未安装Apk的PackageInfo
2)PackageManager.getResourcesForApplication:根据ApplicationInfo创建一个Resources实例
可以在ContainerActivity的onCreate中加载插件apk的时候,用这两个方法创建出一份插件资源实例。具体就是先用 PackageManager.getPackageArchiveInfo拿到插件apk的PackageInfo,有了PacakgeInfo之后就可以组装一份ApplicationInfo,然后通过 PackageManager.getResourcesForApplication来创建资源实例。大概代码像这样:
PackageManager packageManager = getPackageManager();
PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(
pluginApk