V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
2bab
V2EX  ›  Android

新出炉:解决 Android Manifest Merge 冲突的小插件

  •  1
     
  •   2bab · 2017-05-23 20:28:15 +08:00 · 10687 次点击
    这是一个创建于 2742 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原文地址

    Seal-Github

    最近做一些 SDK 升级时,有些包引入后会有诸如此类的报错:

    AndroidManifest.xml:22:9-40 Error: Attribute application@theme value=(@style/AppTheme) from AndroidManifest.xml:22:9-40 is also present at [some:libraries:version] AndroidManifest.xml:9:18-62 value=(@style/AnotherTheme). Suggestion: add 'tools:replace="android:theme"' to <application> element at AndroidManifest.xml:18:5-65:19 to override.

    这是一个很常见的错误了,照着提示做 replace 就 OK 了。但是当我加上 replace 的代码后,发现依旧报错:

    Multiple entries with same key: @android:theme=REPLACE and android:theme=REPLACE.

    百思不得其解,查看了一下依赖库的 AndroidManifest.xml 源码,发现它也设置了tools:replace="android:theme",而 Manifest Merger 把这个视为冲突抛了出来。

    思考

    如果只是跟着 官方的 Manifest Merge,这个问题恐怕无解。StackOverflow 上也有人问过这个问题,但是没有更多的解法回复。

    为什么依赖库会想不开去设置 replace 属性呢?很大的一个可能是:他也碰到了他的依赖库和他的 Manifest 有冲突的情况。那么我们能做什么?我们始终还是想要把他的某些属性给替换掉的( theme/allowBackup/...),不管他是出于什么样的目的,都不能阻止我想打出包的心!

    解法

    通过简单的观察和源码查看,我们发现 merge 是发生在 process${variant}Manifest 这个 Task。那么就得想办法在执行这个任务之前 Precheck 一下所有依赖的 AndroidManifest.xml

    Seal - A gradle plugin to do precheck of Android Manifest.

    我写了一个简易的插件来做这件事,目前支持两个功能:

    1. 删除 Application 节点的某些属性,如 debuggablethemeallowBackup
    2. 删除 Application 节点中 tools:replace 属性的某些值,如 android:iconandroid:themeandroid:allowBackup

    这个插件不仅能解决上述提到的问题,还能顺带修复诸如下面这种 Warning:

    Warning: AndroidManifest.xml already defines debuggable (in http://schemas.android.com/apk/res/android); using existing value in manifest.

    而我们所需要做的,仅仅是指定我们不需要 libraries 的那些属性:

    def projectRoot = project.getRootProject().rootDir.absolutePath
    
    // 依赖库的 Manifest 文件搜索路径
    // 1. Gradle plugin 2.3.0 或者更高版本,会默认开启 build-cache 功能,Release 版本的库会解压到这里
    // 2. 但是我们同样需要对 SNAPSHOT 的库做预检查,所以还需要加入 exploded-aar 的目录
    // 3. 有更多自定义的目录或者 module,请自行添加
    def manifestPath = [
            // for AAR of Release
            // see note below
            projectRoot + '/build-cache', 
            // for AAR of SNAPSHOT
            projectRoot + '/app/build/intermediates/exploded-aar'
    ]
    
    def removeAttrs = [
            'android:debuggable'
    ]
    
    def replaceValues = [
            'android:allowBackup'
    ]
    
    seal {
        enabled = true
        manifests = manifestPath
    
        appAttrs {
            enabled = true
            attrsShouldRemove = removeAttrs
        }
    
        appReplaceValues {
            enabled = true
            valuesShouldRemove = replaceValues
        }
    }
    

    需要注意的是,如果开启了 build-cache, Seal 建议你把 build-cache 的文件夹放在工程目录内(就是上面配置里的 build-cache 位置)。

    //gradle.properties
    android.buildCacheDir=./build-cache
    ...
    

    更多信息,请参考 Github 仓库内的说明,欢迎大家提 PR 和 ISSUE。

    2 条回复    2017-07-23 16:14:22 +08:00
    lastmayday
        1
    lastmayday  
       2017-05-23 23:45:14 +08:00
    超棒 der!
    veightz
        2
    veightz  
       2017-07-23 16:14:22 +08:00
    好棒棒~
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2699 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 15:16 · PVG 23:16 · LAX 07:16 · JFK 10:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.