Android-自定义插件---彻底解决method-not-found-问题

本文探讨了在Android项目中,由于aar版本不同导致的method-not-found错误,特别是在多个业务仓集成时的问题。文章指出,升级基础库时不能简单地强制所有业务仓同步升级。解决方案是在编译时分析class,使用javassit工具检查方法是否存在。通过自定义transform插件,动态获取android.jar路径并检查字节码,避免误报错误。提供了关键代码实现和AndroidSdk类以获取android.jar路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目多了以后 这些业务仓 可能会多大几十个,甚至上百个,大部分情况下 这些业务仓都是以aar的形式集成到最终的app包中, 且这几十个业务仓 都分别属于不同的团队开发, 然后就渐渐演变成下图的样子:

大家都知道 android在打包的时候 如果一个aar 有不同的版本存在,那么默认总是引用版本号最高的版本。 这个时候就会出现一个问题了:

基础库在迭代升级的时候 很可能要对某些方法进行修改,比如修改方法的返回值 ,修改方法的参数,甚至于要删除方法等等,但是如果你碰到上述的场景就要小心了,因为很多业务仓 依赖的还是老版本的基础库,他们运行是正常的,而你的新版本的基础库版本号提高以后删除了某个方法,假设他们又用到了这个方法,那么实际运行的时候 就会报method not found的 错误了。

有人问 那你每次升级基础库的时候强制要求业务仓也跟着升级不就行了?当然是不行的。。。因为很多都是跨部门的业务,没有合理的理由 他们是不愿意 每次都跟着你的升级而升级的。 除非你告诉他们: 兄弟 你某个类的某个方法 我们这版本修改过了,你必须要升级一下,否则crash(method not found)

如何解决这个问题?

其实解决问题的思路 无非就是在编译的时候 拿到这些class的方法的信息,想办法分析分析 有哪些方法里面 某一行调用的某个方法已经不存在了。 显而易见的 我们首先要做的就是 拿到全部的class。 插件中 想拿到全部的class 很简单,主要注册一个transform就可以了,transform 的input 可以给出我们想要的全部class, 注意只要使用了transform 那么有input 就必须要有output,否则你的app 运行起来就会报class not found的错误了。

拿到这些class以后 问题就简单了,我们可以利用javassit 这个工具 来分析我们的class,然后用一个空的ExprEditor来触发一个异常, 只要报了异常就说明在classpool 里面 没有这个类 或者说有这个类 但是没有这个方法。

这里还要注意的就是,构建javassit的classpool的时候 一定要记得把android.jar 也加进去,否则会报很多android的系统方法找不到。 这里不同的project 使用的android sdk 版本都不同。所以我们还需要实现一个小功能 就是动态的获取android.jar的路径。

好了,实现该插件的思路和要点就阐述完毕了 下面直接上代码把

代码实现

动态获取 project下的android.jar的 路径(这里要感谢didi-booster给出的简洁实现 给我省了很多事);

import java.io.File
import java.io.FileNotFoundException
import java.util.Properties

private val HOME = System.getProperty(“user.home”)

private val CWD = System.getProperty(“user.dir”)

/**

  • 这个类主要用来取 当前工程的 android.jar 的 绝对路径
  • 因为不一样的人 不一样的操作系统 不一样的 project 他们的 android.jar 路径并不一样
  • 我们需要拿到这个路径 添加到classPath中 才可以对字节码做相关的操作 否则asm和 javassist 都有可能出问题

*/
class AndroidSdk {

companion object {

/**

  • 输入apiLevel 你就可以得到 你使用的 android.jar 的path了
  • @param apiLevel
  • @return
    */
    fun getAndroidJar(apiLevel: Int = findPlatform()): File {
    val jar = File(getLocation(), “platforms F i l e . s e p a r a t o r a n d r o i d − {File.separator}android- File.separatorandroid{apiLevel}${File.separator}android.jar”)
    return jar.takeIf { it.exists() } ?: throw FileNotFoundException(jar.path)
    }

fun findPlatform(): Int = File(getLocation(), “platforms”).listFiles()?.filter {
it.name.startsWith(“android-”) && File(it, “android.jar”).exists()
}?.map {
it.name.substringAfter(“android-”)
}?.max()?.toInt() ?: throw RuntimeException(“No platform found”)

/**

  • 找到当前系统的sdk 安装目录,按照下面的顺序去找
  • 如果4种方法都找不到 那就只能抛异常了
  • 1. ANDROID_HOME environment variable
  • 2. android command in PATH
  • 3. local.properties
  • 4. platform dependent path:
  • - macosx: ~/Library/Android/sdk
    
  • - linux: ~/Android/sdk
    
  • - windows: ~\AppData\Local\Android\sdk
    

*/
fun getLocation(): File = System.getenv(“ANDROID_HOME”)?.takeIf {
it.isNotBlank()
}?.let {
File(it)
}?.takeIf {
it.exists() && it.isDirectory
} ?: System.getenv(“PATH”).splitToSequence(File.pathSeparator).map {
File(it, “android”)
}.find {
it.exists() && it.canExecute()
}?.canonicalFile?.parentFile?.parentFile ?: File(CWD, “local.properties”).let { local ->
if (local.exists()) {
val props = Properties();
local.inputStream().use {
props.load(it)
}
props.getProperty(“sdk.dir”, null)?.let {
File(it)
}?.takeIf {
it.exists() && it.isDirectory
}
} else {
null
}
} ?: when {
OS.isMac() -> File(HOME, “Library F i l e . s e p a r a t o r A n d r o i d {File.separator}Android File.separatorAndroid{File.separator}sdk”).takeIf { it.exists() && it.isDirectory }
OS.isLinux() -> File(HOME, “AndroidKaTeX parse error: Expected '}', got '&' at position 44: … { it.exists() &̲& it.isDirector…{File.separator}Local F i l e . s e p a r a t o r A n d r o i d {File.separator}Android File.separatorAndroid{File.separator}sdk”).takeIf { it.exists() && it.isDirectory }
else -> null
}
?: throw RuntimeException(“ANDROID_HOME is not set and android command not in your PATH”)
}

}

看一下最关键的transform怎么写:

import com.android.build.api.transform.Format
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.internal.pipeline.TransformManager
import javassist.ClassPool
import javassist.expr.ExprEditor
import javassist.expr.MethodCall
import org.gradle.api.Project
import java.io.File
import java.util.zip.ZipFile

class MethodNotFoundTransform(project: Project) : Transform() {

val project = project

override fun getName(): String {
return “MethodNotFoundTransform”
}

override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
return TransformManager.CONTENT_CLASS
}

override fun isIncremental(): Boolean {
return false
}

override fun getScopes(): MutableSet

override fun transform(transformInvocation: TransformInvocation) {

val outputProvider = transformInvocation.outputProvider
val classPool = ClassPool()
val androidSdkPath = AndroidSdk.getAndroidJar(ProjectFileRead.getCompileSdkVersion(project)).absolutePath
println(“-------------androidSdkPath: $androidSdkPath”)
//这里必须要将编译时使用的 android.jar 也加入到path中 否则会出现很多系统方法找不到 从而误报的情况
classPool.appendClassPath(androidSdkPath)

val errorInfoPath = JenkisHelper.getJenkinsFindDir(project) + File.separator + “MethodDetect.txt”
println(“将method not found 信息 写入:” + errorInfoPath)
val errorInfoFile = File(errorInfoPath)
var errorInfoMarkString = “”
val destJarList = ArrayList()
//处理全部class的输入
transformInvocation.inputs.forEach { input ->
//处理jar包
input.jarInputs.forEach { jarInput ->
//有输入 就必须要有输出,否则会出错 导致很多class 丢失
val dest = outputProvider.getContentLocation(jarInput.file.absolutePath, jarInput.contentTypes, jarInput.scopes, Format.JAR)
//拷贝的过程 一定不能丢
jarInput.file.copyTo(dest, true)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

答应大伙的备战金三银四,大厂面试真题来啦!

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

跳动、阿里、百度等BAT大厂 2020-2021面试真题解析**

[外链图片转存中…(img-0rd6qGwG-1712203690499)]

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值