zoukankan      html  css  js  c++  java
  • 解决资源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了

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

  • 相关阅读:
    C++ 将对象写入文件 并读取
    IronPython fail to add reference to WebDriver.dll
    How to Capture and Decrypt Lync Server 2010 TLS Traffic Using Microsoft Tools
    .net code injection
    数学系学生应该知道的十个学术网站
    Difference Between Currency Swap and FX Swap
    Swift开源parser
    谈谈我对证券公司一些部门的理解(前、中、后台)[z]
    JDK8记FullGC时候Metaspace内存不会被垃圾回收
    JVM源码分析之JDK8下的僵尸(无法回收)类加载器[z]
  • 原文地址:https://www.cnblogs.com/anni-qianqian/p/10115726.html
Copyright © 2011-2022 走看看