gradle升级带来的java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/appcompat/R$drawable错误

背景

最近将我们项目的android gradle plugin(以下简称agp)以及gradle版本进行了升级,之前因为依赖集团内部的打包插件,agp版本还停留在很老的3.4.2版本。这次先升级到了4.0.2版本(对应gradle版本为6.1.1)后,遇到了打出来的包会报错R文件相关产物缺失的运行时崩溃。

问题原因与解决方案

关于这个问题,首先在google上搜到了这样一个文章,感觉是类似的问题:
java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/appcompat/R$drawable;
其中的原因是在agp版本3.6.0-alpha01之后,R文件不在跟别的文件一起生成R.java,而是单独生成R.jar产物,因此对于一些gradle plugin,需要单独将这个产物一起输出。而在适配比较老的agp版本的很多gradle plugin里,这点都被忽略了。
因此,如果你在gradle升级时遇到了这个问题,第一步应该是先确定当前项目所依赖的gradle plugin哪个插件引入了这个问题,然后就想上面的文章中所提到的那样,去升级这个plugin到适配了3.6.0以上agp的版本

========== 大部分人的分割线 ===========

对大部分安卓开发者来说不需要关心下面的内容。

自己动手修改plugin适配更高版本agp

但是如果做完以上步骤后,你和我一样发现出问题的plugin已经没有更新的版本了,或者不再维护了,而你需要自己手动来改。这时,才需要阅读下面的内容。
首先,我找到了前面文章中提到的realm项目,翻阅了它的release notes和issues后,找到了当时这个项目升级适配的改动,并且照着他们的改动对我们的plugin进行了修改。以下是核心代码:

    @Override
    public void transform(TransformInvocation invocation)
            throws IOException, TransformException, InterruptedException {
        for (TransformInput input : invocation.getInputs()) {
            for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
                // ... 原先的逻辑,处理所有的directory输入
            }

            // 增加的逻辑,对于jar类型的输入进行处理,一般就是拷贝到目标目录即可
            for (JarInput jarInput : input.getJarInputs()) {
                File inputFile = jarInput.getFile()
                // ===========  核心代码块 start =====================
                File outputDir = invocation
                        .getOutputProvider()
                        .getContentLocation(
                                jarInput.getName(),
                                getOutputTypes(),
                                getScopes(),
                                Format.JAR);
                if (inputFile == null || !inputFile.exists() || outputDir == null) {
                    continue
                }
                Files.createParentDirs(outputDir);
                Files.write(Files.asByteSource(inputFile).read(), outputDir);
                // ===========  核心代码块 end =====================
            }
        }
    }

这个改动的点在于增加一段对于jar类型文件的拷贝处理。这里我参考了原来我们项目中的写法,和realm项目的改动在写法细节上不太一致,但是基本原理是一样的。在着手改动时,首先找到之前directoryInput处理的地方,在后面增加一段处理即可。

你可能感兴趣的:(gradle升级带来的java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/appcompat/R$drawable错误)