背景
之前統(tǒng)計(jì)crash信息時(shí)統(tǒng)計(jì)到的top5的崩潰幕随,OOM導(dǎo)致的崩潰數(shù)量排在第二位
Android sdk的OOM崩潰率持續(xù)增長(zhǎng)晒喷,為了檢測(cè)出內(nèi)存泄漏問(wèn)題脓规,決定改造下LeakCanary解決目前測(cè)試中的幾個(gè)痛點(diǎn)源织。
-
Memory leak發(fā)生時(shí)不能很快的將信息傳遞給開(kāi)發(fā)人員,之前需要打開(kāi)leak activity 然后找到發(fā)生的leak記錄誉己,打開(kāi)每級(jí)的stack信息眉尸。截圖給開(kāi)發(fā),簡(jiǎn)直不能忍巨双。
- 原始memory leak發(fā)生時(shí)的通知提示噪猾,打開(kāi)以后如下圖,需要將這個(gè)圖給到開(kāi)發(fā)定位問(wèn)題炉峰。
image 當(dāng)測(cè)試需要清除應(yīng)用數(shù)據(jù)時(shí)會(huì)造成leak信息一并被清除,導(dǎo)致數(shù)據(jù)丟失脉执,leak信息無(wú)法追溯疼阔。
當(dāng)使用monkey等自動(dòng)測(cè)試手段進(jìn)行穩(wěn)定性測(cè)試時(shí),查看leak trace的界面是一個(gè)新的Activity半夷,在跑Monkey的過(guò)程中會(huì)進(jìn)入這個(gè)activity不斷的操作婆廊,會(huì)刪除已經(jīng)產(chǎn)生的leak信息,并且會(huì)有相當(dāng)長(zhǎng)一段時(shí)間可能無(wú)法回到測(cè)試產(chǎn)品自身的界面中操作巫橄。
Leakcanary的使用
-
原理圖
-
過(guò)程解析
RefWatcher.watch() 創(chuàng)建一個(gè) KeyedWeakReference 到要被監(jiān)控的對(duì)象淘邻。
然后在后臺(tái)線程檢查引用是否被清除,如果沒(méi)有湘换,調(diào)用GC宾舅。
如果引用還是未被清除,把 heap 內(nèi)存 dump 到 APP 對(duì)應(yīng)的文件系統(tǒng)中的一個(gè) .hprof 文件中彩倚。
在另外一個(gè)進(jìn)程中的 HeapAnalyzerService 有一個(gè) HeapAnalyzer 使用HAHA 解析這個(gè)文件筹我。
得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內(nèi)存泄漏帆离。
HeapAnalyzer 計(jì)算 到 GC roots 的最短強(qiáng)引用路徑蔬蕊,并確定是否是泄漏。如果是的話哥谷,建立導(dǎo)致泄漏的引用鏈岸夯。
引用鏈傳遞到 APP 進(jìn)程中的 DisplayLeakService麻献, 并以通知的形式展示出來(lái)。
一猜扮、開(kāi)始使用
在項(xiàng)目 build.gradle 中加入引用勉吻,不同的編譯使用不同的引用:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
在 Application 中:
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
這樣,在 debug build 中破镰,如果檢測(cè)到某個(gè) activity 有內(nèi)存泄露餐曼,LeakCanary 就是自動(dòng)地顯示一個(gè)通知。點(diǎn)擊通知即可進(jìn)入leak activity進(jìn)行查看相關(guān)的leak詳情鲜漩。
二源譬、改造
1、新建LeakUploadService
類
在Application類所在的package內(nèi)新建一個(gè)LeakUploadService
類孕似,繼承DisplayLeakService
類:
import com.squareup.leakcanary.*
public class LeakUploadService extends DisplayLeakService {
@Override
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
if (!result.leakFound || result.excludedLeak){
return踩娘;
}
uploadService();
}
}
其中,發(fā)生泄漏的類名為result.className.toString();喉祭,其余信息諸如軟件包名养渴、軟件版本號(hào)、leak trace等泛烙,均在leakInfo中一般將軟件包名理卑、版本號(hào)、leak trace(第一行下面蔽氨,* Retaining: 131 KB.之上的部分)藐唠、泄漏大小等信息上傳到數(shù)據(jù)庫(kù)即可,有了這些信息鹉究,開(kāi)發(fā)就可以定位問(wèn)題了宇立。
處理方法也很簡(jiǎn)單,就是對(duì)String對(duì)象進(jìn)行一些操作自赔。這里我是將發(fā)送泄漏的類名妈嘹、軟件包名、軟件版本號(hào)绍妨、整個(gè)泄漏信息(除了Details部分)上傳到數(shù)據(jù)庫(kù)
String className = result.className.toString();
String pkgName = leakInfo.trim().split(":")[0].split(" ")[1]
String pkgVer = leakInfo.trim().split(":")[1]
String leakDetail = leakInfo.split("\n\n")[0] + "\n\n" + leakInfo.split("\n\n")[1];
使用Okhttp編寫一個(gè)簡(jiǎn)單的上傳leak信息的代碼润脸,在LeakUploadService
中檢測(cè)到memoryload時(shí)調(diào)用上傳即可。
2他去、注冊(cè)service
接下來(lái)需要在AndroidManifest.xml
中注冊(cè)service津函,即在<application android:name=...>和</application>之間添加<service android:name="com.squareup.leakcanary.LeakUploadService" android:exported="false"/>
,根據(jù)LeakUploadService所在的包自行調(diào)整孤页。
3尔苦、改造屏蔽leak activity
DisplayLeakActivity
類是LeakCanary展示leak trace的類,這個(gè)類的存在,會(huì)導(dǎo)致跑Monkey的過(guò)程中允坚,多次進(jìn)入這個(gè)Activity魂那,在其中操作,從而減少在軟件本身界面中的操作時(shí)間稠项。
屏蔽DisplayLeakActivity
類
-
由于
LeakCanary
類正好是在前面接入LeakCanary時(shí)添加代碼LeakCanary.install(this);
用到的類涯雅,因此想到的方法是自己創(chuàng)建一個(gè)新的類LeakCanaryWithoutDisplay
,位置在com/squareup/leakcanary
下(需要自己新建這個(gè)package)展运,里面的代碼直接復(fù)制LeakCanary
類活逆,然后做如下修改:把
public final class LeakCanary {
改為public final class LeakCanaryWithoutDisplay {
把
private LeakCanary() {
改為private LeakCanaryWithoutDisplay() {
修改
enableDisplayLeakActivity()
函數(shù),將true
改為false
將主Application類中安裝LeakCanary的代碼
LeakCanary.install();
改為LeakCanaryWithoutDisplay.install();
調(diào)用LeakCanaryWithoutDisplay.enableDisplayLeakActivity(this);之后就會(huì)屏蔽activity和通知信息拗胜。
最后修改主Application類中的安裝方式蔗候,改為:
public class MainApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanaryWithoutDisplay.install(this);
LeakCanaryWithoutDisplay.enableDisplayLeakActivity(this);
}
}
做了如上操作后,每次發(fā)生泄漏都不會(huì)出現(xiàn)通知和leak activity埂软,并且會(huì)自動(dòng)將發(fā)送泄漏的類名锈遥、軟件包名、軟件版本號(hào)勘畔、泄漏信息等上傳指定服務(wù)端了所灸。
4、 服務(wù)端簡(jiǎn)單實(shí)現(xiàn)
- 主要功能實(shí)現(xiàn)
接受LeakUploadService post過(guò)來(lái)的leak數(shù)據(jù)
對(duì)數(shù)據(jù)處理并入庫(kù)
定時(shí)任務(wù)觸發(fā)炫七,通過(guò)郵件的形式上報(bào)收集到的leak信息爬立,并更新數(shù)據(jù)庫(kù)狀態(tài)
重復(fù)leak的過(guò)濾
2 重復(fù)leak信息過(guò)濾,主要依據(jù)infer生成的report json對(duì)比實(shí)現(xiàn)万哪,當(dāng)前可以完全過(guò)濾掉重復(fù)的leak信息侠驯。
通過(guò)對(duì)比發(fā)現(xiàn)生成的數(shù)據(jù)中,可以通過(guò)下面的代碼過(guò)濾重復(fù)信息
三壤圃、 測(cè)試
測(cè)試中無(wú)需關(guān)注leak的發(fā)生陵霉,只需要關(guān)注被測(cè)應(yīng)用的功能測(cè)試琅轧,當(dāng)leak發(fā)生時(shí)會(huì)自動(dòng)上傳leak信息到服務(wù)端伍绳,服務(wù)端會(huì)自動(dòng)郵件通知開(kāi)發(fā)者。
四乍桂、改造以后于原始方式的對(duì)比
改造以后可以通過(guò)郵件直接通知開(kāi)發(fā)冲杀,memoryleak的發(fā)生,還有泄露的詳細(xì)信息睹酌,
測(cè)試過(guò)程中完全可以不再關(guān)注leak的發(fā)生权谁,發(fā)生leak也不會(huì)再中斷測(cè)試過(guò)程。
郵件通知憋沿,減少測(cè)試與開(kāi)發(fā)之間溝通的成本
提升測(cè)試工作效率旺芽,減少不必要的時(shí)間開(kāi)支