atlas使用笔记

最近项目需要进行插件化改造,使用阿里atlas开源框架。特地到github官网学习下,记录下主要功能,和使用过程中遇到的问题。

选择开源框架,一般需要满足以下需求:

  1. 满足业务需求
  2. 使用简单
  3. 大公司
  4. 代码更新时间

Atlas就满足我们的需求,不过Atlas使用较复杂,新版本进行了较大更新导致很多功能不能使用,所以文章使用的是之前版本进行学习。

文章主要记录:

  1. atlas简单介绍
  2. atlas接入
  3. 本地bundle实现
  4. 远程bundle实现
  5. 打补丁包
  6. 注意事项
  7. 遇到问题

1. atals简单介绍

1. atlas是什么

  1. Android动态组件化框架,提供了解耦化、组件化、动态性的支持。(支持Android 4.0及以上版本)
  • 开发期:Atlas让开发者能够独立开发,独立debug,因为组件(budle)独立。
  • 运行期:Atlas实现了组件生命周期管理,Class隔离和其他机制
  • 维护期:Atlas提供了快速增量更新和快速升级能力
  1. 特点:
    对比multidex,Atlas不仅解决了方法数限制(65535),还提供了其他很强的功能,例如:
  • 并行迭代
  • 快速开发
  • 灵活发版
  • 动态更新
  • 快速修复线上bug

Atlas不像其他Android插件化框架,是组件化(Bundle)。Atlas不是多进程框架。

  1. 三个核心库
  • atlas-core:核心库,负责安装每个bundle,运行时按需加载classes和resources
  • atlas-update:更新库,在更新或升级时提供dex merge的能力
  • atlas-gradle-plugin:gradle库,修改了部分Android默认包机制,包括atlas-aapt.

2. atlas框架的流程

Atlas对app的划分
  • 其中更详细的介绍,请见官网

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
  • 血泪史,如果不想折腾,对应版本最好按照以上来进行测试,否则会花费较多时间在编译上。
    准备工作:
  1. 准备一个demo项目


    demo项目结构

    运行效果图
  • publiclibrary模拟公共库,提供tools方法
  • localbundle模拟本地bundle,有一个activity,也会调用公共库方法
  • remotebundle模拟远程bundle,有一个activity,也会调用公共库方法
  • app模拟宿主,跳转localbundle,remotebundle,调用公共库方法
  1. 引用Atlas插件及依赖仓库,修改工程gradle文件
buildscript {
    dependencies {
        classpath "com.taobao.android:atlasplugin:3.0.1-rc71-3"
    }
}
  1. 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的使用

  1. localbundle接入,修改bundle的build.gradle
apply plugin: 'com.android.library'
apply plugin: 'com.taobao.atlas'
atlas {
    //声明为awb 即bundle工程
    bundleConfig {
        awbBundle true
    }
}

可能由于Atlas实现机制,经过上面的修改,remotebundle和publiclibrary也许进行修改

  1. remotebundle和publiclibrary的build.gradle文件添加
apply plugin: 'com.taobao.atlas'
atlas {
    bundleConfig {
        awbBundle false // 注意是false
    }
}
  1. 跳转到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);
}
  1. 查看Apk包结构,可以看到/lib/armeabi/libcom_ybxiang_localbundle.so


    本地bundle
  • so文件夹如果太多,可以优化下 app 下 build.gradle
buildTypes {
    ...
    debug {
        ndk {
            abiFilters "x86","armeabi"
        }
    }
}

4. remoteBundle的使用

  1. remotebudle的build.gradle文件修改
apply plugin: 'com.taobao.atlas'
atlas {
    bundleConfig {
        awbBundle true
    }
}
  1. app的build.gradle文件配置
atlas {
    atlasEnabled true
    tBuildConfig {
        outOfApkBundles = ['remotebundle'] // 远程bundle
    }
}
  1. 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;
        });
    }
  1. 跳转到remotebundle代码
switchToActivity("com.ybxiang.remotebundle.RemoteBundleActivity");
  1. 编译project,生成远程so文件


    remotebundle文件
  2. 推送插件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
  1. 测试跳转,正常跳转到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

  1. 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"
        }
    }
}

  1. 拷贝AtlasDemo的Updater到项目,Updater.java
  • 并在子线程调用patch更新
new AsyncTask<Void, Void, Boolean>() {
    @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();
  1. 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'
  1. 项目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()
        ...
    }
}
...
  1. app目录执行,生成apk
../gradlew clean assembleDebug
  1. app目录执行,将编译的ap发布到本地maven仓库
../gradlew publish
  1. 拷贝出编译出的apk,安装

2. 生成差异包,并运行

  1. 修改localBundle的代码(不支持manifest的修改),完成修改后更新localbundle/build.gradle版本号
version = '1.0.1'
  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
  1. 检查app/build/outputs/tpatch-debug目录下文件是否生成。
  • 推送dexpatch-1.0.json、1.0@1.0.tpatch到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/1.0@1.0.tpatch /storage/emulated/0/Android/data/com.ybxiang.atlas_demo/cache/1.0@1.0.tpatch
  1. 运行app,在子线程调用Updater#Updater.dexPatchUpdate(mContext)加载patch。加载patch成功重启app。

  1. 修改app的代码,修改app的build.gradle文件version = getEnvValue("versionName", "1.0.1");
  2. app下执行patch生成命令
../gradlew clean assembleDebug -DapVersion=1.0.0 -DversionName=1.0.1
  • 推送update-1.0.0.json、patch-1.0.1@1.0.0.tpatch到app的cache路径

参考资料:

3. 单模块调试模拟

官方方法暂时不支持

Task 'assemblePatchDebug' not found in project ':localbundle'.

6. bundle的注意点

遵循代码规范可以有效避免在运行时遇到难以排查的问题。

  1. Bundle的AndroidManifest中不能有对bundle内的资源的引用;比如Activity的Theme,需要声明在主apk中。Bundle的Manifest会合并进主Manifest,如果有bundle的资源引用会直接引发构建出错;另外可以选择的方式是AndroidManifest里面不加Theme,改用在Activity的onCreate方法里面调用setTheme的方式

  2. Activity通过overridePendingTransition使用的切换动画的文件要放在主apk中;

  3. Bundle内的Class最好以特定的packageName开头,resource文件能够带有特定的前缀。这样一来可以避免资源或者类名重复而引起的覆盖,也可以在出现问题的时候及时定位到出问题的模块

  4. Bundle内如果有用到自定义style,那么style的parent如果也是自定义的话,parent的定义必须位于主apk中,这是由于5.0以后系统内style查找的固有逻辑导致的,容器内暂不能完全兼容

  5. Bundle内部如果有so,则安装时so由于无法解压到apk lib目录中,对于直接通过native层使用dlopen来使用so的情况,会存在一定限制,且会影响后续so动态部署,所以目前bundle内so不建议使用dlopen的方式来使用

  6. Bundle内有使用主进程的contentProvider,则Bundle的AndroidManifest的contentprovider声明中最好带上

    android:multiprocess="true"
    android:process=":XX"

这样可以避免主进程一起来就去startBundle导致启动时间过长

7. 遇到的问题

  1. 运行报错,找不到AtlasBridgeApplication


    image.png
android {
    ...
    defaultConfig {
        ...
        multiDexEnabled true
    }
}
  1. multiDexEnabled添加之后运行报错


    image.png
  2. gradle同步报错


    gradle同步报错
  • localbundle目录下执行assembleDebug
  1. gradle报错


    image.png
  • gradle降到4.6
  1. gradle报错


    image.png
  • sdk降到26,buildtools26.0.2
  1. gradle报错


    image.png
  • 不使用androidX
  1. gradle报错


    image.png
  • 之前的library build.gradle配置
apply plugin: 'com.taobao.atlas'
atlas {
    //声明为awb 即bundle工程
    bundleConfig {
        awbBundle false
    }
}
  1. 运行app报错


    Didn't find class "com.google.devtools.build.android.desugar.runtime.ThrowableExtension"
  • 删除app/build文件夹

附源码atlas-demo

参考资料

  1. atlas github
  2. 阿里的Atlas组件化框架
  3. atlas过程分析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容