问题背景
通过 gradle 构建编译时, 在切换分支或代码变更较大后, 经常出现 javassist.NotFoundException: broken jar file? 的编译失败错误, 一直以来只能靠重启 Android Studio 来解决, 非常痛苦. 最近花了些时间终于解决了这个问题.
原因分析过程
* What went wrong:
Execution failed for task ':app:transformClassesWithXXXXJarMergingForDebugQA'.
> javassist.NotFoundException: broken jar file?: com.xx.BaseRecyclerAdapter
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
首先查看相关源码分析:
public InputStream openClassfile(String classname)
throws NotFoundException
{
URL jarURL = find(classname);
if (null != jarURL)
try {
java.net.URLConnection con = jarURL.openConnection();
con.setUseCaches(false);
return con.getInputStream();
}
catch (IOException e) {
throw new NotFoundException("broken jar file?: "
+ classname);
}
return null;
}
报错"broken jar file?" 是由于发生了IOException, 这个异常通常跟文件读取有关的, 猜测是打开 jar 文件失败引起的, 那么这个 jar 文件可能是因为被其他进程占用了. 通过 lsof 命令查看jar文件占用, 过滤出 jar 文件占用的进程.
lsof | egrep '\.jar$'
这里列出部分跟当前 project 相关的 jar 占用进程信息:
java 23717 luliang 504r REG 1,4 64883 8625155549 /Users/luliang/git/$myProjectXXX/app/build/intermediates/transforms/TrafficStats/debugQA/111.jar
java 23717 luliang 527r REG 1,4 422787 8625152311 /Users/luliang/git/$myProjectXXX/app/build/intermediates/transforms/GsonJarTransform/debugQA/56.jar
java 23717 luliang 542r REG 1,4 22062 8625152326 /Users/luliang/git/$myProjectXXX/app/build/intermediates/transforms/GsonJarTransform/debugQA/71.jar
java 23717 luliang 568r REG 1,4 475187 8625152557
发现是pid 为 23717 的 java 进程占用了当前 project 中多个编译时生成的 jar 文件, 通过命令 kill 23717 杀掉这个进程后, 再重新编译就正常了.
那这个 java 进程到底是谁启动的呢? 既然是 java 进程,我们可以通过 jps 命令查看:
> jps
23717 GradleDaemon
23847 KotlinCompileDaemon
23963 Jps
可以看出这是一个 gradle 守护进程.
到这里,问题的原因就找到了, 是 gradle 的坑:
gradle 守护进程一直持有 jar 文件句柄, 编译进程无法打开读取 jar 文件导致编译失败.
以前关闭 Android Studio 的解决方法能够生效是因为, 关闭 Android Studio 也会同时关闭 gradle 守护进程.
解决方法
- 方法一(推荐): 在编译前通过 ./gradlew --stop 命令停止守护进程.
在切换分支或代码发生较大变更时, 开发需要有意识的执行命令停止守护进程.
- 方法二: 默认关闭守护进程, 修改 gradle 配置:
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
缺点:
- 每次编译都比较慢, 无法享有守护进程提高编译速度的特性;
- 仅限命令行执行编译有效, android studio 中Run仍然会开启守护进程