• 解决资源id冲突


    --摘自《android插件化开发指南》

    1.一套完整的Android App打包流程(Gradle方案)

    第一步:aapt。为res目录下的资源生成R.java文件,同时为AndroidManifest.xml生成Manifest.java文件

    第二步:aidl。把项目中自定义的aidl文件生成相应的Java代码文件

    第三步:javac。把项目中所有的Java代码编译成class文件。包括三部分Java代码,自己写的的业务逻辑代码,aapt生成的Java文件,aidl生成的Java文件

    第四步:proguard。混淆同时生成proguardMapping.txt。这一步是可选的

    第五步:dex。把所有的class文件(包括第三方库的class文件)转换成dex文件

    第六步:aapt。把res目录下的资源、assets目录下的文件,打包成一个.ap_文件

    第七步:apkbuilder。将所有的dex文件、ap_文件、AndroidManifest.xml打包为.apk文件,这是一个未签名的apk包

    第八步:jarsigner。对apk进行签名

    第九步:zipaligin。对要发布的apk文件进行对齐操作,以便在运行时节省内存

    2.res目录下的所有资源会生成一个R.java文件,每个资源都对应一个R中的十六进制整数变量,由三部分组成,即PackageId+TypeId+EntryId(一般是默认0x7f+两位+四位)

    3.aapt命令在打包过程中都做了什么

      1)把assets和res目录下的所有资源、AndroidManifest.xml,都保存在一个后缀名ap_的文件中,就是一个压缩包

      2)为res目录的每个资源,生成一个资源id常量,把id值和资源名称的对应关系,存放在resources.arsc文件中

      3)把这些资源id常量,都定义在R.java文件中

    4.在aapt命令执行完,才会执行javac命令,把包括R.java在内的素有java文件,进行编译

    ***插件化中资源id冲突的解决方案***

    方案1:

    把宿主和插件的资源都合并到一起

    方案1.1:重写AAPT命令,在插件apk打包过程中,通过指定资源id的前缀,比如0x71,来保证宿主和插件的资源id永远不会冲突

      1)在AAPT的命令行参数中传递apk打包时的前缀值

      2)把这个值设置给Bundle实体的mApkModule字段,作为ResourceTable构造函数的参数传递进去

      3)在ResourceTable的构造函数,读取Bundle参数中的mApkModule值,也就是前缀值,设置给packageId

      都是c代码,就不贴了

      4)把新的AAPT工具命名为aapt_mac,放到项目根目录下

    apply plugin: 'com.android.application'
    
    import com.android.sdklib.BuildToolInfo
    import java.lang.reflect.Method
    
    Task modifyAaptPathTask = task('modifyAaptPath') << {
        android.applicationVariants.all { variant ->
            BuildToolInfo buildToolInfo = variant.androidBuilder.getTargetInfo().getBuildTools()
            Method addMethod = BuildToolInfo.class.getDeclaredMethod("add", BuildToolInfo.PathId.class, File.class)
            addMethod.setAccessible(true)
            addMethod.invoke(buildToolInfo, BuildToolInfo.PathId.AAPT, new File(rootDir, "aapt_mac"))
            println "[LOG] new aapt path = " + buildToolInfo.getPath(BuildToolInfo.PathId.AAPT)
        }
    }
    
    android {
        compileSdkVersion 25
        buildToolsVersion "25.0.3"
    
        defaultConfig {
            applicationId "jianqiang.com.testreflection"
            minSdkVersion 21
            targetSdkVersion 25
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
        preBuild.doFirst {
            modifyAaptPathTask.execute()
        }
    
        aaptOptions {
            aaptOptions.additionalParameters '--PLUG-resoure-id', '0x71'
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:25.2.0'
    }

    通过反射,把AAPT路径临时修改为指向当前app根路径下的mac_aapt

    方案1.2:在插件apk打包后,修改R,java和resources.arsc中存储的资源id值,比如默认的0x7f前缀,修改为0x71,这样就保证了宿主和插件的资源id永远不会冲突

    方案1.3:在public.xml中指定apk中所有资源的id值。但每增加一个资源,都要维护public.xml。所以只能用于固定几个特定的值

    afterEvaluate {
        for (variant in android.applicationVariants) {
            def scope = variant.getVariantData().getScope()
            String mergeTaskName = scope.getMergeResourcesTask().name
    
            def mergeTask = tasks.getByName(mergeTaskName)
    
            mergeTask.doLast {
                copy {
                    int i = 0
                    println android.sourceSets.main.res.srcDirs
                    from(android.sourceSets.main.res.srcDirs) {
                        include 'values/public.xml'
                        rename 'public.xml', (i++ == 0 ? "public.xml" : "public_${i}.xml")
                    }
    
                    into(mergeTask.outputDir)
                }
            }
        }
    }

     string.xml

    <resources>
        <string name="app_name">ActivityHook1</string>
    
        <string name="string1">Test String</string>
    </resources>

    public.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <resources>
        <public type = "string" name="string1" id = "0x7f050024"/>
    </resources>

    宿主的资源值固定了以后,插件如果想使用宿主的资源,只要把宿主打包成jar,然后复制到插件项目的某个位置,使用gradle脚本provided就可以了,这样打出的插件不会包含宿主的代码

    方案2:如果不事先合并资源,那就为每个插件创建一个AssetManager,每个AssetManager都是通过反射调用addAssetPath方法,把插件自己的资源添加进去。详细的代码见资源的插件化

    方案1的缺点是资源id的前缀是有限的,就256个值,当一个app中有多于256个插件时,就要考虑方案2了

    欢迎关注我的微信公众号:安卓圈

  • 相关阅读:
    【NOIp模拟赛】种花
    【NOIP模拟赛】质数序列
    【NOIp模拟赛】兔子
    【NOIp模拟赛】圆桌游戏
    【NOIp模拟赛】花
    【洛谷P2345】奶牛集会
    【洛谷P1774】最接近神的人_NOI导刊2010提高(02)
    【洛谷P1495】 曹冲养猪
    【洛谷P1287】 盒子与球
    NOIP2009 Hankson 的趣味题
  • 原文地址:https://www.cnblogs.com/anni-qianqian/p/10115726.html
Copyright © 2020-2023  润新知