atlas使用笔记

作者: zhetengxiang | 来源:发表于2019-11-17 18:44 被阅读0次

    最近项目需要进行插件化改造,使用阿里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过程分析

    相关文章

      网友评论

        本文标题:atlas使用笔记

        本文链接:https://www.haomeiwen.com/subject/qsyyictx.html