一個(gè)由sdk更新導(dǎo)致的NoSuchMethodError

1. 問題

??一個(gè)App將其依賴的glide sdk 從4.9版本降級(jí)到4.7版本孙援,然后進(jìn)行灰度發(fā)布,隨后出現(xiàn)較多的線上崩潰扇雕。崩潰內(nèi)容如下:

java.lang.NoSuchMethodError: No virtual method circleCrop()Lcom/bumptech/glide/request/BaseRequestOptions; in class Lcom/bumptech/glide/request/RequestOptions; or its super classes (declaration of 'com.bumptech.glide.request.RequestOptions' appears in base.apk)

2. 排查及解決

??根據(jù)崩潰的調(diào)用棧我們定位到崩潰所對(duì)應(yīng)的源碼如下拓售,這是我們依賴的另一個(gè)sdk(后面稱為“組件B”)內(nèi)部的代碼,組件B也依賴了glide sdk镶奉。

Glide.with(getActivity()).load(bean.getUrl()).apply(RequestOptions.centerCropTransform().circleCrop()).into(getImageView());

??但源碼并沒有出現(xiàn)調(diào)用circleCrop()Lcom/bumptech/glide/request/BaseRequestOptions;方法础淤,雖然這里有調(diào)用circleCrop()方法,但其返回值是RequestOptions哨苛。隨后我們對(duì)apk進(jìn)行反編譯查看其相關(guān)smali代碼如下:

注意如下這條語句:

invoke-virtual {v3}, Lcom/bumptech/glide/request/RequestOptions;->circleCrop()Lcom/bumptech/glide/request/BaseRequestOptions;

??原來在編譯后的字節(jié)碼里circleCrop()的返回值是BaseRequestOptions鸽凶,其中BaseRequestOptionsglide 4.9 版本新增的。

??我們梳理了glide sdk的依賴情況如下:

??到這里我們找到了問題原因建峭,因?yàn)樽止?jié)碼里調(diào)用了一個(gè)返回類型BaseRequestOptionscircleCrop()方法玻侥,而事實(shí)上宿主App打包后,apk里面是不包含返回類型BaseRequestOptionscircleCrop()方法亿蒸,所以運(yùn)行到那段代碼的時(shí)候會(huì)會(huì)因找不到對(duì)應(yīng)的方法而拋NoSuchMethodError異常導(dǎo)致崩潰使碾。

??因此解決方法也比較簡(jiǎn)單,組件B基于glide 4.7重新出包給宿主App升級(jí)即可祝懂。

3. 為什么

??這里還有些疑問票摇,看起來這顯然是依賴沖突導(dǎo)致的問題,這種算是比較低級(jí)的問題為什么在灰度發(fā)布前沒有發(fā)現(xiàn)呢砚蓬?

??其實(shí)宿主App在降級(jí)glide sdk到4.7的時(shí)候矢门,是有讓組件B的同學(xué)確認(rèn)組件B是否需要重新出包給宿主升級(jí)。

??組件B的同學(xué)基于當(dāng)前宿主App依賴的組件B版本的代碼沒做任何修改灰蛙,只是將glide的版本號(hào)從4.9改成4.7祟剔,隨后編譯發(fā)現(xiàn)沒有任何報(bào)錯(cuò),并且運(yùn)行demo也沒有問題摩梧,于是他們認(rèn)為宿主App當(dāng)前依賴的組件B版本是可以直接兼容glide 4.7的物延,因?yàn)榇a都不需要修改,直接改版本號(hào)就能打包通過了仅父。

因此這里有兩個(gè)問題需要搞清楚:

3.1. 組件B的源碼里只是調(diào)用了類自身的circleCrop()方法叛薯,為什么字節(jié)碼里會(huì)出現(xiàn)返回值BaseRequestOptions

??對(duì)于字節(jié)碼而言浑吟,方法的調(diào)用是通過方法簽名來識(shí)別的,而方法簽名就包含了方法返回值耗溜,無論我們?cè)创a里有沒有使用到該方法的返回值组力。

??這里我們先看下glide 4.7 circleCrop() 方法的代碼。該方法直接定義在RequestOptions類里面抖拴。

public class RequestOptions implements Cloneable {
  public RequestOptions circleCrop() {
    return transform(DownsampleStrategy.CENTER_INSIDE, new CircleCrop());
  }
}

下面是glide 4.9 circleCrop() 方法相關(guān)代碼燎字,我們發(fā)現(xiàn)新版本增加了基類,該方法被移動(dòng)到基類去了阿宅。

public class RequestOptions extends BaseRequestOptions<RequestOptions> {

}

public abstract class BaseRequestOptions<T extends BaseRequestOptions<T>> implements Cloneable {
  public T circleCrop() {
    return transform(DownsampleStrategy.CENTER_INSIDE, new CircleCrop())
  }
}

??由于glide 4.9 circleCrop()方法被聲明為泛型<T>候衍,該泛型T要求繼承于BaseRequestOptions類,由于在編譯成字節(jié)碼時(shí)會(huì)進(jìn)行泛型擦除洒放,因此在調(diào)用該方法的地方并不存在<T>而是直接被替換為泛型的基類BaseRequestOptions脱柱,所以才出現(xiàn)了該問題。

??除了方法的返回值類型拉馋,還有一些其它場(chǎng)景也會(huì)在編譯時(shí)將源碼不存在的內(nèi)容添加到字節(jié)碼里面榨为,比如一些參數(shù)類型的自動(dòng)轉(zhuǎn)換,拆箱裝箱或lambda表達(dá)式等煌茴。

3.2. 為什么宿主App在編譯打包時(shí)不會(huì)報(bào)錯(cuò)随闺,運(yùn)行時(shí)才拋異常呢

??因?yàn)?strong>組件B是以aar的形式被宿主依賴,aar內(nèi)部包含的是jar包蔓腐,也就是class字節(jié)碼矩乐。因此宿主在編譯時(shí),組件B是不參與編譯成字節(jié)碼這一環(huán)節(jié)(當(dāng)然宿主想這么做也做不到回论,因?yàn)樗拗鞑]有組件B的源代碼)散罕。

4. 總結(jié)

??前面提到的解決方法是通過組件B基于glide 4.7出新包給宿主更新,那是不是有其它方法呢傀蓉,比如通過exclude欧漱,compileOnlyruntimeOnly這些配置是否也能解決sdk依賴版本沖突問題呢葬燎。

??這里大概說下我對(duì)這幾個(gè)配置的理解:exclude頂多只能解決編譯問題误甚,甚至隱藏了運(yùn)行時(shí)可能出現(xiàn)的潛在問題,而compileOnly配置只會(huì)把問題復(fù)雜化(僅限于本文提及的這個(gè)問題)谱净,比如組件B使用compileOnly依賴了glide 4.9窑邦,那么宿主在梳理glide sdk依賴關(guān)系的時(shí)候,甚至無法得知組件B有依賴glide sdk壕探,runtimeOnly會(huì)保證在編譯時(shí)沒有調(diào)用到所依賴的sdk的相關(guān)api冈钦,也不適合這邊討論的這個(gè)問題。

??對(duì)于較大型的App李请,所依賴的sdk組件可能達(dá)幾十個(gè)甚至上百個(gè)瞧筛,各個(gè)sdk更新的情況也會(huì)非常頻繁厉熟,因此想絕對(duì)避免sdk依賴版本沖突的問題,幾乎是很難做到的驾窟。因此這里建議對(duì)于版本跨度較大或者降級(jí)這種特殊場(chǎng)景下的sdk版本變更時(shí)庆猫,變更前必須要梳理出sdk依賴關(guān)系认轨,確保各組件都能同時(shí)升級(jí)到一致的版本绅络。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嘁字,隨后出現(xiàn)的幾起案子恩急,更是在濱河造成了極大的恐慌,老刑警劉巖纪蜒,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衷恭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡纯续,警方通過查閱死者的電腦和手機(jī)随珠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猬错,“玉大人窗看,你說我怎么就攤上這事【氤矗” “怎么了显沈?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逢唤。 經(jīng)常有香客問我拉讯,道長(zhǎng),這世上最難降的妖魔是什么鳖藕? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任魔慷,我火速辦了婚禮,結(jié)果婚禮上著恩,老公的妹妹穿的比我還像新娘盖彭。我一直安慰自己,他們只是感情好页滚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布召边。 她就那樣靜靜地躺著,像睡著了一般裹驰。 火紅的嫁衣襯著肌膚如雪隧熙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天幻林,我揣著相機(jī)與錄音贞盯,去河邊找鬼音念。 笑死,一個(gè)胖子當(dāng)著我的面吹牛躏敢,可吹牛的內(nèi)容都是我干的闷愤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼件余,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼讥脐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起啼器,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤旬渠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后端壳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體告丢,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年损谦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了岖免。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡照捡,死狀恐怖颅湘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情麻敌,我是刑警寧澤栅炒,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站术羔,受9級(jí)特大地震影響赢赊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜级历,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一释移、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寥殖,春花似錦玩讳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粤策,卻和暖如春樟澜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工秩贰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霹俺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓毒费,卻偏偏與公主長(zhǎng)得像丙唧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子觅玻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容