自測清單
開發(fā)結(jié)束后假夺,開發(fā)人員做好以下自測點(diǎn),保證提測質(zhì)量
- 對照需求文檔斋攀,測試邏輯是否吻合完整
- 對可能有問題的地方進(jìn)行簡單的壓力測試已卷,是否會出現(xiàn)閃退Bug
- 對照Umeng或者Bugly開發(fā)階段出現(xiàn)的閃退bug是否都已解決
- 檢測UI是否有變形,比如常見的TextView淳蔼,是否需要行數(shù)限制侧蘸,文字太多,是否或出現(xiàn)頁面顯示異常
- 在不同分辨率的機(jī)型上鹉梨,檢查布局是否有Bug,特別是現(xiàn)在
全面屏
讳癌,劉海屏
,需要有全屏操作
是存皂,在有虛擬導(dǎo)航欄的手機(jī)
上的適配問題 - 版本升級,從低版本升級上來析桥,會不會有問題 比如可能會出現(xiàn)數(shù)據(jù)庫不兼容的問題
- 利用手機(jī)的開發(fā)者選項中的 “調(diào)試GPU過度繪制” ,“GPU呈現(xiàn)模式分析” 和 “顯示FPS和功耗” 功能艰垂,看自己的新功能是否會導(dǎo)致過度繪制、是否會掉幀
- app業(yè)務(wù)斷網(wǎng)埋虹,再聯(lián)網(wǎng)猜憎,相關(guān)業(yè)務(wù)是否正常。
Review清單
清理操作
- Activity退出銷毀操作
* 是否調(diào)用Handler的removeCallbacksAndMessages(null)來清空Handler里的消息搔课;
* 是否取消了還沒完成的請求胰柑;
* 在頁面里注冊的監(jiān)聽,是否反注冊;
* 假如自己用到觀察者模式柬讨,是否反注冊崩瓤;
* 假如用了RxJava的話,是否解除訂閱踩官;
- 數(shù)據(jù)庫的游標(biāo)是否已經(jīng)關(guān)閉
- 打開過的文件流是否關(guān)閉
- WebView使用完是否調(diào)用了其destory()函數(shù)
- Bitmap是否調(diào)用recycle
Android 3.0以下的版本却桶,使用完的Bitmap是否調(diào)用recycle(),否則會一直占用內(nèi)存 而Android 3.0及以上的版本不需要調(diào)用recycle()蔗牡,因?yàn)檫@些版本的Bitmap全部放到虛擬機(jī)的堆內(nèi)存中颖系,讓GC自動回收。
代碼性能優(yōu)化
- 檢查是否使用多余的for循環(huán)辩越,UI線程是否有耗時操作
- 是否可以用String代替StringBuilder,是否可以用StringBuilder代替StringBuffer
- 保存在內(nèi)存中的圖片嘁扼,是否做過壓縮處理再保存在內(nèi)存里 否則可能由于圖片質(zhì)量太高,導(dǎo)致OOM
- Intent傳遞的數(shù)據(jù)太大黔攒,會導(dǎo)致頁面跳轉(zhuǎn)過慢趁啸。太大的數(shù)據(jù)可以通過持久化的形式傳遞,例如讀寫文件或者是否實(shí)現(xiàn)Parcelable
- 頻繁地操作同一個文件或者執(zhí)行同一個數(shù)據(jù)庫操作督惰,是否考慮把它用靜態(tài)變量或者局部變量的形式緩存在內(nèi)存里不傅。用空間換時間
- Listview或者RecycleView的item中,bindData的時候姑丑,避免SharePreference的讀取或者存儲操作,是否可以采用全局變量來解決
- Sqlite數(shù)據(jù)庫增刪改查是否處理了多線程訪問的并非操作蛤签,建議采用事務(wù)處理,高效栅哀,盡量用synchronized震肮,效率太慢
- 頁面布局,是否可以考慮用ViewStub來優(yōu)化啟動速度
- xml中的布局文件是否有多余的嵌套留拾,影響性能
gradle第三方依賴包
- build.gradle遠(yuǎn)程依賴第三方包時戳晌,版本號建議寫死,不要使用+號 避免由于新版本的第三方包引入了新的問題
- 調(diào)用第三方的包或者JDK的方法時痴柔,要跳進(jìn)他們的源碼沦偎,看要不要加 try-catch 否則可能會導(dǎo)致自己應(yīng)用的崩潰
- 使用第三方包時,是否加上其混淆規(guī)則 若漏掉加上第三方包的混淆規(guī)則咳蔚,會導(dǎo)致第三方包不該混淆的代碼被混淆豪嚎。在Debug版本沒有發(fā)現(xiàn)問題,但是Release版本就會出現(xiàn)問題
檢查成對的方法是否同時出現(xiàn)
- 系統(tǒng)的谈火、自己寫的侈询,注冊和反注冊的方法,是否成對出現(xiàn)
- 在生命周期的回調(diào)里糯耍,創(chuàng)建和銷毀的代碼是否對應(yīng)起來 比如:onCreate()里面創(chuàng)建了Adapter扔字,那么對應(yīng)Adapter的退出處理操作(比如清空Image緩存)囊嘉,一般就要寫在onDestory(),而不能寫在onDestoryView()革为。
- 若ListView的item復(fù)用了瓷耙,對Item里View的操作是否成對出現(xiàn) 比如:
switch (type) {
case ArticleListItem.TYPE_AD: ......
mTitleView.setText(tencentAdBean.title);
mGreenLabelView.setVisibility(VISIBLE);
mRedLabelView.setText("");
mRedLabelView.setVisibility(GONE);
break;
case ArticleListItem.TYPE_ARTICLE: ......
mTitleView.setText(mzAdBean.adData.getTitle());
mGreenLabelView.setVisibility(GONE);
mRedLabelView.setText("ABC");
mRedLabelView.setVisibility(VISIBLE);
break;
}
內(nèi)存泄漏
- 內(nèi)部類播瞳,比如Handler埃唯、Listener懂诗、Callback是否是成static class 因?yàn)榉庆o態(tài)內(nèi)部類會持有外部類的引用】叶祝或者在頁面銷毀的時候虐块,是否設(shè)置listener,CallBack等為null
- 要求傳入Activity作為參數(shù)的函數(shù),是否可以改用getApplicationContext()來作為參數(shù),比如數(shù)據(jù)庫嘉蕾,SharePreference,getSystemService等
//Kotlin代碼
private val mAudioManager = context.applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private val sp=context.applicationContext.getSharedPreferences(name, Context.MODE_PRIVATE)
- 單例模式不要持有Activity的引用贺奠,同時要記得注冊和反注冊的監(jiān)聽,如下:
QmPayHelper.getInstance().setPayListener(object: onPayListener{
override fun onWxPay(){
}
})
一定要在頁面銷毀的時候错忱,設(shè)置為null,不然因?yàn)閛nPayListener持有Activity的引用而內(nèi)存泄漏
QmPayHelper.getInstance().setPayListener(null)
- 每次Activity關(guān)閉的時候儡率,Handler相關(guān)的操作是否需要移除:
mHandler.removeCallbacksAndMessages(null)
- 頁面關(guān)閉的時候,如果有動畫在執(zhí)行以清,是否清除相關(guān)動畫,比如:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
textView = (TextView)findViewById(R.id.text_view);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
}
@Override
protected void onDestroy(){
super.onDestroy();
//取消動畫
objectAnimator.cancel();
}
Handler操作
- Handler使用的時候是否采用靜態(tài)內(nèi)部類以及當(dāng)前類弱引用的方式出現(xiàn),如下:
//Kotlin代碼
inner class KpcHandler(controller: QmPlayerController) : Handler() {
private var mWrf = WeakReference(controller)
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
val controller = mWrf.get()
controller?.hide()
}
}
- 使用View.post()是否會有問題 因?yàn)樵赩iew處于detached狀態(tài)期間儿普,post()里面的Runnable是不會被執(zhí)行的。只有在此View處于attached狀態(tài)時才會被執(zhí)行掷倔。
如果想改Runnable每次肯定會被執(zhí)行眉孩,那么應(yīng)該是用Handler.post來替代:
Handler(Looper.myLooper()).post { } //Kotlin代碼
- 假如程序可能多次在同一個Handler里post同一個Runnable,每次post之前都應(yīng)該先清空這個Handler中還沒執(zhí)行的該Runnable 如:
if (mCloudRun != null) {
mHandler.removeCallbacks(mCloudRun);
mCloudRun = null;}
mCloudRun = new Runnable() {
@Override
public void run() {
CloudAccelerateSwitchRequest request = new CloudAccelerateSwitchRequest();
request.setPriority(RequestTask.PRIORITY_LOW);
RequestQueue.getInstance().addRequest(request);
}
};
mHandler.post(mCloudRun);
高概率閃退點(diǎn)以及邏輯健全Review
- 除數(shù)是否做了非0判斷
- 某些情況下勒葱,某變量是否會為Null,而且在函數(shù)體內(nèi)浪汪,處理參數(shù)前,必須加上判空語句
- 異步回調(diào)函數(shù)是否處理好,回調(diào)函數(shù)很容易出問題凛虽。比如網(wǎng)絡(luò)請求的回調(diào)死遭,需要判斷此時的Aciivity或者Fragment等是否還存在,再進(jìn)行調(diào)用凯旋。因?yàn)楫惒讲僮骰貋硌教叮珹ctivity可能就消失不存在了。 而且還要對一些可能被回收的變量進(jìn)行判空至非。
- 修改數(shù)據(jù)庫后钠署,是否把數(shù)據(jù)庫的版本號+1,升級邏輯是否處理合理
- 啟動第三方的Activity時荒椭,是否判斷了該Intent能否被解析
Intent sendIntent = new Intent(mContext, Demo.class);
// 這種方式判斷是否存在
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
若Activity不存在谐鼎,會出現(xiàn)ActivityNotFoundException的異常
- 檢查if()邏輯后是否處理了else()的相關(guān)操作,比如常見的操作,listview中的item復(fù)用的時候,如下:
if(hasTitle){
mTitleView.setText(tencentAdBean.title);
mGreenLabelView.setVisibility(VISIBLE);
}else{
//如果不處理,則mTitleView可能會不顯示
mTitleView.setText("");
mGreenLabelView.setVisibility(GONE);
}
- 不要在Activity的onCreate里調(diào)用PopupWindow的showAsLoaction方法戳杀,由于Activity還沒被加載完该面,會報錯
- 系統(tǒng)6.0的動態(tài)權(quán)限申請,7.0的文件訪問信卡,8.0的通知欄渠道channel等適配操作是否完善