前段時間發(fā)過這樣一篇文章 Android Studio 打包Jar,因為任務需要將項目中一個模塊打包成jar包提供給第三方公司使用,實話說打包完,并且提供給N個公司使用,那感覺。。。
不過裝逼過度總是要還的,這不 前兩天打臉的來了。。。
劇情
劇情有點繁瑣,不想看的童鞋可以跳的后面的錯誤原因或錯誤重現(xiàn)那。。。
那是一個挺(熱)悠(成)閑(狗)的早上,剛到公司打開電腦,正準備瀏覽幾篇技術(shù)文章,再開始一天的工作呢。突然 本公司與B公司戰(zhàn)略合作群 里出現(xiàn)對方技術(shù)人員的疑問
B公司-Android:我這邊調(diào)用SDK崩潰 @xxx
看見這個問題,我第一瞬間想到肯定是沒按照步驟進行配置,因為之前給別家接SDK時也遇到過調(diào)用失敗的問題
我:是不是配置出錯了,是按照文檔中的要求配置的嗎?權(quán)限有給嗎?
沒一會
B公司-Android: 都按照文檔中配置了,權(quán)限也都申請了,但還是使用不了。
剛準備質(zhì)問一下是否真的配置全了
啪 。。 啪 。。 啪 。。。
對方接連貼了N張配置的截圖,我仔細看看,的確都是按照文檔中配置的。。。
裝逼第一步 。。。 失敗 。。。
我:能把日志給我看看嗎。。
啪 。。 啪 。。 啪 。。
小伙子挺喜歡啪啊 。。。
我盯著那日志看了半天,沒找到任何問題,連一個紅色的報錯都沒有,這TM什么鬼。。
我:全部日志就這些嗎?沒有看見報錯啊。。你確定出錯了?
還沒等我繼續(xù)廢話呢
啪 。。
小伙子 你真的很喜歡啪啊
哎? 不是圖片啊? 再一看 <font size=5>測試包?。?!</font>我也是服氣的。。。
算了,看在群里這么多老板的份上,我忍了。。。掏出測試機。。。安裝測試包。。
運行。。
果然,程序運行到我的SDK模塊時,軟件崩潰了。。。
打開Studio 日志,翻了一個遍,的確和他剛才給的日志一樣,并沒有找到錯誤點,這TM就很奇怪了。。。
再運行一次,依舊是這樣,不過這次我發(fā)現(xiàn)一點奇怪的地方,APP崩潰后并沒有直接退出程序,而是重啟了一遍程序,難道是這里做的怪?
打開Studio日志,盯著日志打印,運行程序,程序崩潰后果然看見一片紅色的打印??!然而當APP自動重啟后,日志記錄中所有的報錯部分全都沒了!看來的確是這個重啟刷新了日志,導致錯誤信息看不見了。
其實這個問題以我以往的經(jīng)驗,應該是Activity的啟動模式設(shè)置成了android:launchMode="singleTask"
,所有的Activity都在單獨的任務棧中,如果Activity使用默認啟動模式,都在一個任務棧中,當某個Activity崩潰時會導致整個程序的退出,而使用 singleTask
會導致Activity崩潰,程序重啟到前一個Activity,同時會重啟一個新的進程。
<b>那該怎樣查看崩潰的日志信息呢?</b>
很簡單,Android Studio查看日志的時候可以選擇不同的進程
例如我這里選取的進程是com.lcm.test
,而當出現(xiàn)上面的那種情況時,一般情況下我們都會在這里看見一個與當前進程同名的一個進程,不過進程后會多一個[DEAD]
,例如com.lcm.test[DEAD]
,我們選取這個進程,就可以看見剛才崩潰的那個進程的日志信息了。
既然能找到錯誤了,我們就來看看是什么錯
很明白直接的一個錯誤 <font color=red> Resources$NotFoundException
</font> ,資源文件缺失。
這里先回顧一下:
SDK中包含一個Activity,而Activity的Layout文件以及一些資源文件是單獨提供給第三方的,第三方將jar包以及資源文件放到項目的相關(guān)目錄下,SDK中通過反射獲取第三方APP資源文件對應的ID,然后再加載相應的資源文件。
所以看到 <font color=red> Resources$NotFoundException
</font>,我立馬就懷疑是不是對方?jīng)]有加入我提供的資源文件。
我:我這邊看見是資源文件未找到的錯誤,你那邊使用SDK時有拷貝提供的資源文件到項目中嗎?
發(fā)完這句話我就后悔了,文檔中說的很清楚,一般人不會忘記這一步吧,果然
B公司-Android: 都拷貝過來了,你看
啪 。。
果然不會犯這么低級的錯誤,繼續(xù)研究日志,在 Warn
級別的日志中發(fā)現(xiàn)這樣一個警告
難道是 R 文件沒有找到?
這里貼上SDK中反射獲取資源文件的代碼
/**
*
* @param context 上下文
* @param className 資源文件的類型 layout、id、drawable
* @param name 資源文件的名字
* @return
*/
public static int getIdByName(Context context, String className, String name) {
String packageName = context.getPackageName();
Class r = null;
int id = 0;
try {
r = Class.forName(packageName + ".R");
Class[] classes = r.getClasses();
Class desireClass = null;
for (int i = 0; i < classes.length; ++i) {
if (classes[i].getName().split("\\$")[1].equals(className)) {
desireClass = classes[i];
break;
}
}
if (desireClass != null)
id = desireClass.getField(name).getInt(desireClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return id;
}
通過代碼 我們知道,我們是通過 Class.forname(包名+R)
來獲取APP的R文件,然后在R文件中找到我們所需要的資源文件對應的ID,具體可以看我之前的文章 Android Studio 打包Jar
關(guān)于 <font color=red> ClassNotFoundException
</font>的錯誤,不管百度還是google常見的有幾種可能的原因。
- jar包未引入,相應的類無法找到
- Manifest.xml 中注冊Activity時,類名寫錯
- App混淆時,未保留相關(guān)類,導致混淆后無法加載相關(guān)類
這里能正確的調(diào)用SDK中的方法,說明jar包是正常引入的,所以排除第一種可能。
讓對方再次檢查了一遍Manifest 文件,確定配置注冊的Activity完整類名填寫沒問題,排除第二種可能性。
剩第三種,詢問后得知,對方的確開啟了APP代碼混淆,立馬想到讓他在代碼混淆配置文件中添加保留R文件代碼
-keep class **.R$* {
*;
}
以防萬一,還讓他添加了保留我的SDK代碼的邏輯,雖然我的jar包已經(jīng)做過代碼混淆了。
讓他再次測試運行
B公司-Android:還是一樣的結(jié)果
啪 。。
順手還貼了個測試包過來。。。
安裝 運行,的確錯誤信息依然存在,真是xxxx 。。。
突然,我想起以前遇到的一個坑 multiDex導致NoClassDefFoundError錯誤 ,大概就是Android 打包時遇到 65535 錯誤,采取 multiDex 進行分包,但是在分包后程序運行過程中會遇到 NoClassDefFoundError
的錯誤,也是類加載失敗。我突然想會不會是這個原因呢?
我:你的項目中是不是開啟了
multiDexEnabled true
配置
B公司-Android:嗯嗯 是的
啊哈!果然有進行分包處理!肯定是這里的錯!
為了避免又被打臉的尷尬,我強裝冷靜道
我:我懷疑是這個分包導致的錯,這樣,你按照我說的進行配置。。
大致配置情況,在我的這篇博客中有寫 multiDex導致NoClassDefFoundError錯誤 ,大致原理就是在進行分包的時候,手動將自己需要的類保留到主要的包中,使其在APP啟動時就加載。為了避免太裝逼,我沒有直接把自己的博客地址給他 ??。
這回應該沒錯了吧,哈哈,喝口水休息下。。??匆幌聲r間,都快到中午了。。。
但是,沒過五分鐘。。
B公司-Android:還是不行啊,還是一樣的錯。。
我擦嘞?。。≌娴募俚模。。?/p>
趕緊讓他又發(fā)了個測試包過來,安裝運行,果然錯誤信息連變都沒變。。。
不甘心的我
我 :你確定是安照我說的配置了嗎?
啪 。。啪 。。 啪 。。 啪 。。
朋友!你體驗過絕望嗎? 我體驗過??!
接下來的一天,基本上就是陪著他檢查各種可能的情況,一遍的調(diào)試,一遍遍的被打臉。。
我都準備讓他把源碼發(fā)過來,自己運行檢查,但是一般公司怎么可能輕易把代碼外流啊。。
錯誤原因
萬萬沒想到我最終還是解決了這個BUG!(咋突然跳到了王大錘的節(jié)奏呢。。哈哈??)
正當我們一籌莫展的時候,我突然發(fā)現(xiàn)一個奇怪的地方,對方的build.gradle中配置的applicationId = aa.bb.cc
和AndroidManifest.xml 中
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="aa.bb.dd.cc">
package
配置的包名不一致。
我:你們這Build.gradle 和 manifest.xml 中配置的包名為什么不一致呢?
B公司-Android:這個項目以前是從Ecipse轉(zhuǎn)過來的,中間有次改過包名,Android Studio 改包名只要修改 build.gradle中的
applicationId
就可以了。
哦?是嗎?
我再回頭看看錯誤原因 java.lang.ClassNotFoundException:aa.bb.cc.R
,
這里尋找的是 build.gradle 中配置的包名對應的R文件,我靈機一動
我:你能看看項目 build/intermediates/classes/debug/項目包名/R 目錄下的R文件是否存在嗎?
B公司-Android:存在的
我:那你看看這個目錄中的項目包名是什么?
B公司-android:是 aa.bb.dd.cc
我擦,難道真的是這里的原因,項目編譯時產(chǎn)生的R文件存在的位置是與Manifest 中配置包名也就是項目的工程目錄相對應的目錄中,而代碼中獲取的項目包名是 build.gradle 中配置的applicationId對應的包名,如果再使用這個包名去反射獲取R文件當然是失敗的了??!
我不是很自信的跟他說到
我: 你把這兩個地方的包名改成一致的試試看。死馬當活馬醫(yī)了。。
B公司-android:。。。。。。好吧
然后。。就沒有然后了。。。。問題就這么解決了。。。
錯誤重現(xiàn)
創(chuàng)建工程
正常創(chuàng)建一個工程,在一個Activity中加載一張圖片,這里我們使用反射獲取資源文件
public class MainActivity extends AppCompatActivity {
private ImageView ivImg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(MResource.getIdByName(getApplicationContext(), "layout", "activity_main"));
ivImg = (ImageView) findViewById(R.id.image);
int imgId = MResource.getIdByName(getApplicationContext(), "drawable", "iv_img");
ivImg.setImageResource(imgId);
}
}
build.gradle 以及 Manifest中配置包名都為 com.lcm.classNotFound
build 目錄結(jié)構(gòu)如下
正常顯示結(jié)果如下
修改包名
修改 build.gradle 中的 applicationId 為 com.lcm.test
運行
出現(xiàn) ClassNotFoundException
錯誤,且反射R文件包名對應為build.gradle中配置包名。
小結(jié)
雖然是友方出現(xiàn)的問題,但也實實在在的鍛煉了我的解決錯誤的能力,我記錄下整個過程,是為了給自己一個好的示范,真正解決過程中,還是走了一些彎路的,只不過這里沒有記錄。這里記錄下的是我認為正確的過程,遇到BUG不要怕,靜下心來,分享日志,分析代碼,一步步排出可能出現(xiàn)的原因。既然出現(xiàn)問題,肯定有導致問題的原因,發(fā)現(xiàn)根源,然后解決它?。。?/p>