Android 模块打包成aar遇到的空指针的问题

AGP 7.4.2 AAR打包问题及升级AGP 8.4.0解决方案

问题背景

在Androd项目开发中遇到了AGP 7.4.2版本的AAR打包问题,以及升级到AGP 8.4.0后R.id字段变化导致的编译问题。

问题1:AGP 7.4.2 AAR打包失败

问题现象

项目使用AGP 7.4.2时出现奇怪的现象:

  • 直接引入module:编译和运行完全正常
  • 打包成AAR后引入:出现D8编译器错误

错误信息:

bash 复制代码
> Task :library:mergeDebugJavaResource FAILED
Execution failed for task ':library:mergeDebugJavaResource'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction
> com.android.tools.r8.CompilationFailedException: Compilation failed to complete
> com.android.tools.r8.utils.AbortException: Error: java.lang.NullPointerException
    at YourClass$1.class (Anonymous inner class)

问题分析

这是AGP 7.4.2中D8编译器的一个已知Bug,具体表现为:

  • 在AAR打包过程中,D8编译器处理匿名内部类时出现NullPointerException
  • 直接module引用时不会触发此bug,只有在AAR打包时才会出现
  • 错误指向YourClass$1.class等匿名内部类文件

尝试的解决方案

方案1:修改匿名内部类

将匿名内部类改为命名内部类:

java 复制代码
// 修改前
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // 执行任务
    }
}, delay);

// 修改后
private static class CustomTimerTask extends TimerTask {
    @Override
    public void run() {
        // 执行任务
    }
}
Timer timer = new Timer();
timer.schedule(new CustomTimerTask(), delay);

结果:失败,问题依然存在

方案2:移除Stream API

移除Java 8 Stream API的使用:

java 复制代码
// 修改前
import java.util.stream.Collectors;
List<DataItem> result = dataList.stream()
    .filter(item -> item.isValid())
    .collect(Collectors.toList());

// 修改后
List<DataItem> result = new ArrayList<>();
for (DataItem item : dataList) {
    if (item.isValid()) {
        result.add(item);
    }
}

结果:失败,问题依然存在

方案3:降级AGP版本

尝试降级到AGP 7.3.1:

gradle 复制代码
classpath 'com.android.tools.build:gradle:7.3.1'

结果:失败,问题依然存在

方案4:调整gradle配置

尝试各种gradle.properties配置:

properties 复制代码
android.enableD8.desugaring=true
android.enableR8=false
android.enableR8.fullMode=false

结果:失败,问题依然存在

最终解决方案:升级到AGP 8.4.0

经过各种尝试都无法解决问题后,最终选择升级到AGP 8.4.0:

gradle 复制代码
// 项目级 build.gradle
buildscript {
    dependencies {
        classpath 'com.android.tools.build:gradle:8.4.0'
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20'
    }
}
properties 复制代码
# gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip

配套更新:

gradle 复制代码
// 模块级 build.gradle
android {
    namespace 'com.example.yourpackage'  // AGP 8.0+强制要求
    compileSdkVersion 34
    
    defaultConfig {
        targetSdkVersion 34
    }
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }
}

结果:成功解决D8编译器Bug,AAR打包正常


问题2:AGP 8.0+ R.id字段变化导致的switch语句问题

问题现象

升级到AGP 8.4.0后,出现新的编译错误:

bash 复制代码
> Task :app:compileDebugJavaWithJavac FAILED
MainActivity.java:56: 错误: 需要常量表达式
    case R.id.button1:
                     ^

问题原因

AGP 8.0+改变了R.id字段的生成方式:

AGP 7.x生成的R.java:

java 复制代码
public final class R {
    public static final class id {
        public static final int button1 = 0x7f050001;  // 编译时常量
        public static final int button2 = 0x7f050002;  // 编译时常量
    }
}

AGP 8.0+生成的R.java:

java 复制代码
public final class R {
    public static final class id {
        public static int button1;  // 运行时赋值,不是常量
        public static int button2;  // 运行时赋值,不是常量
    }
}

由于Java要求switch语句的case标签必须是编译时常量,而AGP 8.0+的R.id字段不再是编译时常量,因此switch语句无法编译通过。

解决方案:switch改为if-else

需要将所有使用R.id的switch语句改为if-else结构:

修改前:

java 复制代码
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.button1:
            doAction1();
            break;
        case R.id.button2:
            doAction2();
            break;
    }
}

修改后:

java 复制代码
@Override
public void onClick(View v) {
    int id = v.getId();
    if (id == R.id.button1) {
        doAction1();
    } else if (id == R.id.button2) {
        doAction2();
    }
}

复杂switch语句的处理

对于复杂的switch语句,如DetailActivity.java

修改前:

java 复制代码
switch (v.getId()) {
    case R.id.btn_back:
        finish();
        break;
    case R.id.btn_play:
        if (isPlaying) {
            mediaPlayer.pause();
        } else {
            startPlayback();
        }
        break;
    case R.id.btn_settings:
        // 处理设置相关逻辑
        break;
    // ... 更多case
}

修改后:

java 复制代码
int id = v.getId();
if (id == R.id.btn_back) {
    finish();
} else if (id == R.id.btn_play) {
    if (isPlaying) {
        mediaPlayer.pause();
    } else {
        startPlayback();
    }
} else if (id == R.id.btn_settings) {
    // 处理设置相关逻辑
}
// ... 更多else if

注意:内部使用常量的switch语句可以保留,如:

java 复制代码
// 这种switch可以保留,因为使用的是常量
switch (dataItem.getType()) {
    case Constants.TYPE_HEADER:
        // 处理逻辑
        break;
    case Constants.TYPE_CONTENT:
        // 处理逻辑
        break;
}

总结

问题解决路径

  1. AGP 7.4.2 AAR打包问题

    • 现象:module正常,AAR打包失败
    • 原因:D8编译器Bug
    • 解决:升级到AGP 8.4.0
  2. AGP 8.0+ R.id问题

    • 现象:switch语句编译失败
    • 原因:R.id不再是编译时常量
    • 解决:switch改为if-else

关键配置

最终的项目配置:

gradle 复制代码
// build.gradle
classpath 'com.android.tools.build:gradle:8.4.0'

// gradle-wrapper.properties  
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip

// 模块build.gradle
android {
    namespace 'com.example.yourpackage'
    compileSdkVersion 34
    targetSdkVersion 34
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }
}

通过升级AGP版本和修改switch语句,最终彻底解决了所有编译问题。

相关推荐
豆豆豆大王18 分钟前
Android studio图像视图和相对布局知识点
android·ide·android studio
我命由我123451 小时前
Android 实例 - Android 圆形蒙版(Android 圆形蒙版实现、圆形蒙版解读)
android·java·java-ee·android studio·安卓·android-studio·android runtime
天若有情6732 小时前
【Android】Android项目目录结构及其作用
android
灿烂阳光g2 小时前
Android Automotive OS架构
android
一碗情深3 小时前
Android 开发环境解析:从SDK、NDK到版本兼容性指南
android·安卓·sdk·ndk
00后程序员张3 小时前
App 上架全流程指南,iOS 应用发布步骤、ipa 文件上传工具、TestFlight 分发与 App Store 审核经验分享
android·ios·小程序·https·uni-app·iphone·webview
2501_916013743 小时前
iOS App 上架流程详解,苹果应用发布步骤、App Store 审核规则、ipa 文件上传与测试分发实战经验
android·ios·小程序·https·uni-app·iphone·webview
周杰伦的稻香5 小时前
MySQL中的空间碎片率计算分析
android·数据库·mysql
用户096 小时前
MVI架构如何改变Android开发模式
android·面试·kotlin
梦终剧7 小时前
【Android之路】.sp和界面层次结构
android