最近项目需要进行插件化改造,使用阿里atlas开源框架。特地到github官网学习下,记录下主要功能,和使用过程中遇到的问题。
选择开源框架,一般需要满足以下需求:
- 满足业务需求
- 使用简单
- 大公司
- 代码更新时间
Atlas就满足我们的需求,不过Atlas使用较复杂,新版本进行了较大更新导致很多功能不能使用,所以文章使用的是之前版本进行学习。
文章主要记录:
- atlas简单介绍
- atlas接入
- 本地bundle实现
- 远程bundle实现
- 打补丁包
- 注意事项
- 遇到问题
1. atals简单介绍
1. atlas是什么
- Android动态组件化框架,提供了解耦化、组件化、动态性的支持。(支持Android 4.0及以上版本)
- 开发期:Atlas让开发者能够独立开发,独立debug,因为组件(budle)独立。
- 运行期:Atlas实现了组件生命周期管理,Class隔离和其他机制
- 维护期:Atlas提供了快速增量更新和快速升级能力
- 特点:
对比multidex,Atlas不仅解决了方法数限制(65535),还提供了其他很强的功能,例如:
- 并行迭代
- 快速开发
- 灵活发版
- 动态更新
- 快速修复线上bug
Atlas不像其他Android插件化框架,是组件化(Bundle)。Atlas不是多进程框架。
- 三个核心库
- atlas-core:核心库,负责安装每个bundle,运行时按需加载classes和resources
- atlas-update:更新库,在更新或升级时提供dex merge的能力
- atlas-gradle-plugin:gradle库,修改了部分Android默认包机制,包括atlas-aapt.
2. atlas框架的流程
- 其中更详细的介绍,请见官网
2. atlas框架的接入
调试环境:
mac 10.14.4
Android studio 3.5.2
gradleVersion:4.6(过高会报错)
buildToolsVersion:26.0.2(过高会报错)
compileSdkVersion:android-26(过高会报错)
atlasPluginVersion:3.0.1-rc71-3(过高会报错)
项目gradle.properties进行配置
android.useAndroidX=false
android.enableAapt2=false
- 血泪史,如果不想折腾,对应版本最好按照以上来进行测试,否则会花费较多时间在编译上。
准备工作:
-
准备一个demo项目
- publiclibrary模拟公共库,提供tools方法
- localbundle模拟本地bundle,有一个activity,也会调用公共库方法
- remotebundle模拟远程bundle,有一个activity,也会调用公共库方法
- app模拟宿主,跳转localbundle,remotebundle,调用公共库方法
- 引用Atlas插件及依赖仓库,修改工程gradle文件
buildscript {
dependencies {
classpath "com.taobao.android:atlasplugin:3.0.1-rc71-3"
}
}
- app容器接入,修改app的build.gradle文件
apply plugin: 'com.android.application'
apply plugin: 'com.taobao.atlas'
...
dependencies {
//核心sdk
compile('com.taobao.android:atlas_core:5.1.0.8-RC1@aar')
//设置bundle依赖
bundleCompile project(':localbundle')
}
atlas {
atlasEnabled true
//...
}
不需要初始化函数,接入完毕。
4. localBundle的使用
- localbundle接入,修改bundle的build.gradle
apply plugin: 'com.android.library'
apply plugin: 'com.taobao.atlas'
atlas {
//声明为awb 即bundle工程
bundleConfig {
awbBundle true
}
}
可能由于Atlas实现机制,经过上面的修改,remotebundle和publiclibrary也许进行修改
- remotebundle和publiclibrary的build.gradle文件添加
apply plugin: 'com.taobao.atlas'
atlas {
bundleConfig {
awbBundle false // 注意是false
}
}
- 跳转到localBundle的LocalActivity
// startActivity(new Intent(this, LocaleActivity.class)); 不能直接调用,只能通过类名称
// localBundle内部可以使用class名称跳转
switchToActivity("com.ybxiang.localbundle.LocalBundleActivity");
public void switchToActivity(String activityName){
Intent intent = new Intent();
intent.setClassName(getBaseContext(),activityName);
startActivity(intent);
}
-
查看Apk包结构,可以看到/lib/armeabi/libcom_ybxiang_localbundle.so
- so文件夹如果太多,可以优化下 app 下 build.gradle
buildTypes {
...
debug {
ndk {
abiFilters "x86","armeabi"
}
}
}
4. remoteBundle的使用
- remotebudle的build.gradle文件修改
apply plugin: 'com.taobao.atlas'
atlas {
bundleConfig {
awbBundle true
}
}
- app的build.gradle文件配置
atlas {
atlasEnabled true
tBuildConfig {
outOfApkBundles = ['remotebundle'] // 远程bundle
}
}
- Application注册监听
/**
* 找不到指定类,加载对应的插件
*/
private void initAtlas() {
Atlas.getInstance().setClassNotFoundInterceptorCallback(intent -> {
final String className = intent.getComponent().getClassName();
final String bundleName = AtlasBundleInfoManager.instance().getBundleForComponet(className);
if (!TextUtils.isEmpty(bundleName) && !AtlasBundleInfoManager.instance().isInternalBundle(bundleName)) {
//远程bundle
Activity activity = ActivityTaskMgr.getInstance().peekTopActivity();
File remoteBundleFile = new File(activity.getExternalCacheDir(),"lib" + bundleName.replace(".","_") + ".so");
String path = "";
if (remoteBundleFile.exists()){
path = remoteBundleFile.getAbsolutePath();
}else {
Toast.makeText(activity, " 远程bundle不存在,请确定 : " + remoteBundleFile.getAbsolutePath() , Toast.LENGTH_LONG).show();
return intent;
}
PackageInfo info = activity.getPackageManager().getPackageArchiveInfo(path, 0);
try {
Atlas.getInstance().installBundle(info.packageName, new File(path));
//Atlas.getInstance().installBundle("com.ybxiang.remotebundle", new File(path));
} catch (BundleException e) {
Toast.makeText(activity, " 远程bundle 安装失败," + e.getMessage() , Toast.LENGTH_LONG).show();
e.printStackTrace();
}
activity.startActivities(new Intent[]{intent});
}
return intent;
});
}
- 跳转到remotebundle代码
switchToActivity("com.ybxiang.remotebundle.RemoteBundleActivity");
-
编译project,生成远程so文件
- 推送插件so到手机目录
adb push /Users/mac/work/code/android/atlasdemo/app/build/outputs/remote-bundles-debug/libcom_ybxiang_remotebundle.so /storage/emulated/0/Android/data/com.ybxiang.atlas_demo/cache/libcom_ybxiang_remotebundle.so
- 测试跳转,正常跳转到remotebundle
使用参考D:\code\github\atlas\atlas-demo\AtlasDemo\app\src\main\res\values\strings.xml中remote_bundle_step描述信息
"请根据下面步骤执行:"
"1、添加远程bundle的依赖, 如 `bundleCompile project(':remotebundle')` , 参考 app/build.gradle "
"2、声明远程bundle列表,
atlas { tBuildConfig { outOfApkBundles = ['remotebundle'] } "
"3、构建完整包 assembleDebug \n"
apk 路径:app/build/outputs/apk/"
远程bundle 路径:app/build/outputs/remote-bundles-debug"
"4、将远程sopush倒你的设备上"
"可以尝试PC上执行adb push app/build/outputs/remote-bundles-debug/libcom_taobao_remotebunle.so /sdcard/Android/data/com.taobao.demo/cache/libcom_taobao_remotebunle.so \n\n"
"5、点击下方按钮,加载远程bundle"
5. 发布插件版本
1. 发布ap到本地maven
- app/build.gradle配置
group = 'com.ybxiang.atlas-demo' // maven库标识
version = getEnvValue("versionName", "1.0.0")
def apVersion = getEnvValue("apVersion", "")
apply from: 'dexPatchWraper.gradle'
dependencies {
...
compile('com.taobao.android:atlasupdate:1.1.4.14@aar') // 更新patch需要
compile 'com.alibaba:fastjson:1.1.45.android@jar' // atlas Updater使用
...
}
atlas {
atlasEnabled true
tBuildConfig {
outOfApkBundles = ['remotebundle']
aaptConstantId true
}
patchConfigs {
debug {
createTPatch true
}
}
buildTypes {
debug {
if (apVersion) {
baseApDependency "com.ybxiang.atlas-demo:AP-debug:${apVersion}@ap"
patchConfig patchConfigs.debug
}
}
}
}
String getEnvValue(key, defValue) {
def val = System.getProperty(key);
if (null != val) {
return val;
}
val = System.getenv(key);
if (null != val) {
return val;
}
return defValue;
}
// patch备份到hisTpatch目录
tasks.whenTaskAdded { task ->
if (task.name.contains("DebugAndroidTest")) {
task.setEnabled(false);
}
if (task.name.contains("assemble")) {
def files = null;
def file = new File(task.project.getBuildDir(), "outputs");
if (file.exists() && new File(file, "tpatch-debug").exists()) {
files = new File(file, "tpatch-debug").listFiles();
}
if (files != null) {
for (File file1 : files) {
if (file1.getName().endsWith(".json") || file1.getName().endsWith(".tpatch")) {
if (!new File(task.project.getRootDir(), "hisTpatch").exists()) {
new File(task.project.getRootDir(), "hisTpatch").mkdirs();
}
org.apache.commons.io.FileUtils.copyFileToDirectory(file1, new File(task.project.getRootDir(), "hisTpatch"));
}
}
}
}
}
apply plugin: 'maven'
apply plugin: 'maven-publish'
publishing {
repositories {
mavenLocal()
}
}
publishing {
publications {
maven(MavenPublication) {
artifact "${project.buildDir}/outputs/apk/debug/${project.name}-debug.ap"
artifactId "AP-debug"
}
}
}
- 拷贝AtlasDemo的Updater到项目,Updater.java
- 并在子线程调用patch更新
new AsyncTask() {
@Override
protected Boolean doInBackground(Void... voids) {
boolean update = Updater.dexPatchUpdate(getBaseContext());
return update;
}
@Override
protected void onPostExecute(Boolean aVoid) {
if (aVoid) {
android.os.Process.killProcess(android.os.Process.myPid());
}
}
}.execute();
- localbundle/build.gradle配置bundle版本号,方便后续生成patch包
atlas {
//声明为awb 即bundle工程
bundleConfig {
awbBundle true
}
buildTypes {
debug {
baseApFile project.rootProject.file('app/build/outputs/apk/app-debug.ap')
}
}
}
version = '1.0.0'
- 项目build.gradle配置本地mavenLocal
buildscript {
repositories {
mavenLocal()
...
}
dependencies {
// classpath 'com.android.tools.build:gradle:3.0.1'
classpath "com.taobao.android:atlasplugin:3.0.1-rc71-3"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
...
}
}
...
- app目录执行,生成apk
../gradlew clean assembleDebug
- app目录执行,将编译的ap发布到本地maven仓库
../gradlew publish
- 拷贝出编译出的apk,安装
2. 生成差异包,并运行
- 修改localBundle的代码(不支持manifest的修改),完成修改后更新localbundle/build.gradle版本号
version = '1.0.1'
- app目录执行,打差异包
../gradlew clean assembleDebug -DapVersion=apVersion -DversionName=newVersion,
- apVersion为之前打的app的version
- newVersion为此次动态部署要生成的新的app的version
../gradlew clean assembleDebug -DapVersion=1.0.0 -DversionName=1.0.0
- 检查app/build/outputs/tpatch-debug目录下文件是否生成。
- 推送dexpatch-1.0.json、[email protected]到app的cache路径
adb push /Users/mac/work/code/android/atlasdemo/app/build/outputs/tpatch-debug/dexpatch-1.0.json /storage/emulated/0/Android/data/com.ybxiang.atlas_demo/cache/dexpatch-1.0.json
adb push /Users/mac/work/code/android/atlasdemo/app/build/outputs/tpatch-debug/[email protected] /storage/emulated/0/Android/data/com.ybxiang.atlas_demo/cache/[email protected]
- 运行app,在子线程调用Updater#Updater.dexPatchUpdate(mContext)加载patch。加载patch成功重启app。
- 修改app的代码,修改app的build.gradle文件version = getEnvValue("versionName", "1.0.1");
- app下执行patch生成命令
../gradlew clean assembleDebug -DapVersion=1.0.0 -DversionName=1.0.1
- 推送update-1.0.0.json、[email protected]到app的cache路径
参考资料:
- dexpatch使用教程
- https://github.com/alibaba/atlas/blob/master/atlas-demo/AtlasDemo/Tpatch.txt
- 实际调试过程中第一次能够生成patch,但是加载之后无效果。后续生成patch都会出问题,持续关注中。
3. 单模块调试模拟
官方方法暂时不支持
Task 'assemblePatchDebug' not found in project ':localbundle'.
6. bundle的注意点
遵循代码规范可以有效避免在运行时遇到难以排查的问题。
Bundle的AndroidManifest中不能有对bundle内的资源的引用;比如Activity的Theme,需要声明在主apk中。Bundle的Manifest会合并进主Manifest,如果有bundle的资源引用会直接引发构建出错;另外可以选择的方式是AndroidManifest里面不加Theme,改用在Activity的onCreate方法里面调用setTheme的方式
Activity通过overridePendingTransition使用的切换动画的文件要放在主apk中;
Bundle内的Class最好以特定的packageName开头,resource文件能够带有特定的前缀。这样一来可以避免资源或者类名重复而引起的覆盖,也可以在出现问题的时候及时定位到出问题的模块
Bundle内如果有用到自定义style,那么style的parent如果也是自定义的话,parent的定义必须位于主apk中,这是由于5.0以后系统内style查找的固有逻辑导致的,容器内暂不能完全兼容
Bundle内部如果有so,则安装时so由于无法解压到apk lib目录中,对于直接通过native层使用dlopen来使用so的情况,会存在一定限制,且会影响后续so动态部署,所以目前bundle内so不建议使用dlopen的方式来使用
Bundle内有使用主进程的contentProvider,则Bundle的AndroidManifest的contentprovider声明中最好带上
android:multiprocess="true"
android:process=":XX"
这样可以避免主进程一起来就去startBundle导致启动时间过长
7. 遇到的问题
-
运行报错,找不到AtlasBridgeApplication
android {
...
defaultConfig {
...
multiDexEnabled true
}
}
-
multiDexEnabled添加之后运行报错
-
gradle同步报错
- localbundle目录下执行assembleDebug
-
gradle报错
- gradle降到4.6
-
gradle报错
- sdk降到26,buildtools26.0.2
-
gradle报错
- 不使用androidX
-
gradle报错
- 之前的library build.gradle配置
apply plugin: 'com.taobao.atlas'
atlas {
//声明为awb 即bundle工程
bundleConfig {
awbBundle false
}
}
-
运行app报错
- 删除app/build文件夹
附源码atlas-demo
参考资料
- atlas github
- 阿里的Atlas组件化框架
- atlas过程分析