前幾天碧注,在編寫公司普通業(yè)務(wù)代碼的過程中嚣伐,遇到個bug,修復之后覺得還是挺有意思的萍丐,所以跟大家分享下哈~
為什么覺得這個bug比較有意思呢轩端?因為這個bug不是本人最常見的老臉孔nullPointer、outOfIndexBounds了逝变,解決這個bug要了解Android的窗口管理機制基茵,要是我之前沒系統(tǒng)地學習過,估計我肯定百思不得其解壳影。拱层。。不過掌握了相關(guān)的知識后宴咧,解決這個bug那是小菜一碟根灯。所以內(nèi)心不禁沾沾自喜,畢竟我努力學進階知識很大的一個動機就是為了從容面對bug啊@臃巍纳猪!對一個bug毫無頭緒甚至連errorMessage都讀不懂的羞愧感我真的怕了。桃笙。氏堤。
具體的Android的窗口管理機制就不在此講了。如果你了解主線程的消息循壞機制搏明,那會對本文理解得更加清晰哈~
背景是這樣的:
公司的APP的業(yè)務(wù)大多是很常見的信息展示鼠锈、響應(yīng)用戶操作事件。對于每一個網(wǎng)絡(luò)請求星著,都會在等待后臺返回時顯示個Loading動畫脚祟,表示正在處理中(很多APP也是這樣做的啦)。邏輯大概是這樣
showLoading();
//這里進行網(wǎng)絡(luò)請求
dismissLoading(); //接收到響應(yīng)時隱藏掉
這樣做一直以來都很和諧强饮。直到前幾天在寫一個業(yè)務(wù),是讓用戶可以上傳圖片的为黎。寫過上傳圖片業(yè)務(wù)的朋友都應(yīng)該比較熟悉邮丰,一般的公司處理上傳圖片都要經(jīng)過兩步:第一步獲取token,第二步才上傳到七牛铭乾。獲取token對用戶來說是感知不到的剪廉,在他們眼中上傳圖片就是一步到位,所以我們這兩個網(wǎng)絡(luò)請求是第一步緊接著第二步的炕檩。當我想都沒想就寫下以下邏輯代碼并測試時斗蒋,APP閃退了:
showLoading();
//這里獲取token
dismissLoading();
showLoading();
//這里上傳圖片到七牛
dismissLoading();
Logcat信息如下:
(好吧還是老臉孔NullPointerException??)
日志顯示loading這個變量為空
因為APP里顯示的是統(tǒng)一的Loadind動畫,而這個Loading動畫是同事寫的笛质,我并不熟悉泉沾。于是點進去看看,首先是showLoading()和dismissLoading()兩個函數(shù)
protected void showLoading(){
loading = Loading.newInstance();
loading.show(getFragmentManager(),"loading");
}
protected void dismissLoading(){
if (loading != null) {
loading.dismiss();
}
}
這兩個函數(shù)就是通過loading這個變量來控制Loading動畫的了妇押,再點進去這個loading跷究,看看是何方神圣
public class Loading extends BaseDialogFragment{
private static Loading loading;
public static Loading newInstance() {
loading = new Loading();
Bundle bundle = new Bundle();
loading.setArguments(bundle);
return loading;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = loading.getArguments();
}
@Override
public void onDestroyView() {
super.onDestroyView();
loading = null;
}
}
可以看到,loading是個static變量敲霍,而且在每次showLoading()時俊马,loading會被賦予新變量 loading = new Loading(); 每次dismissLoading()時,loading變量會被設(shè)為空變量 loading = null;
然后debug一下肩杈,發(fā)覺奔潰是發(fā)生在第二次showLoading()的時候柴我,也就是第一步獲取token成功后,第二步上傳到七牛前的那個showLoading()扩然。
我覺得很奇怪艘儒,showLoading()方法中已經(jīng)確保了loading變量不為空 loading = Loading.newInstance(); 緊接著下一行代碼就是 loading.show(getFragmentManager(),"loading"); 了啊,為什么loading變量突然就變空了?彤悔?嘉抓?
冷靜下來再看看代碼,看到Loading是繼承BaseDialogFragment的晕窑。條件反射般聯(lián)想到抑片,Dialog是一種窗口!杨赤!對于窗口的管理應(yīng)該注意3ㄕ!對于我們APP的主線程來說疾牲,這里的創(chuàng)建/銷毀一個Dialog其實是一個異步非阻塞任務(wù)V采印!
我們來分析一下:第一次dismissLoading()時其實只是通過binder告訴WMS阳柔,我要銷毀這個Dialog焰枢,征求WMS的允許和調(diào)度;然后我們的APP還沒來得及接收到WMS的反饋舌剂,緊接著就繼續(xù)執(zhí)行第二步的showLoading()了济锄,即告訴WMS,我要顯示這個Dialog霍转。但注意此時Dialog還沒有真的被銷毀再顯示荐绝,我們的APP只是向WMS發(fā)出了請求,因為這兩個過程都需要WMS的允許和配合避消。
當我們的APP的主線程收到WMS的返回的msg時低滩,消息隊列里必定是銷毀Dialog的msg在先,顯示Dialog的msg在后的岩喷。處理銷毀Dialog的msg時恕沫,我們會執(zhí)行onDestroyView()里的 loading = null ,loading變量賦值為null纱意;緊接著再處理顯示Dialog的msg昏兆,就會執(zhí)行onCreate()里的loading.getArguments();這時候當然報NullPointerException啦~~
所以妇穴,這個bug時由于我沒重視到窗口的創(chuàng)建/銷毀是個異步任務(wù)所造成的~要是不了解這個機制爬虱,估計我根本不會意識到這個問題,而會覺得創(chuàng)建/銷毀Dialog跟View.setVisiable(boolean)差不多呢腾它。跑筝。。這種情況瞒滴,估計加多少班都修復不了這個bug曲梗。赞警。。
既然弄清了問題出在哪虏两,那解決方法很簡單愧旦,第一步和第二步之間不要操作Loadind動畫就好了~
showLoading();
//這里獲取token
//dismissLoading();
//showLoading();
//這里上傳圖片到七牛
dismissLoading();
感悟:Android的窗口管理機制涉及的東西比較廣,他們之間的調(diào)用鏈定罢,類和方法的具體名字笤虫,我看了又忘??。不過整個機制我是理解并且大體掌握的祖凫,這些知識不進是進階必須琼蚯,而且還是修復某些bug所必須掌握的利器??解決這個bug比平時解決的那些OutOfIndexException要爽多了??