“A small leak will sink a great ship.” - Benjamin Franklin
“千里之堤,溃于蚁穴.” -《韩非子·喻老》
如果 APP 就像一艘在海里航行的大船,内存泄漏就像大船下的小漏洞。
当漏洞越来越多不去修复,船就可能沉没。APP 也可能因为内存泄漏越来越多造成内存溢出导致 APP 崩溃。
我们不断的在做代码迭代,迭代的过程中代码难免会存在一些小问题。例如内存泄漏,我们也会去修复它。Android 排查
内存泄漏的手段有很多,例如:
1 Mat
2 Android profiler
3 LeakCanary
三个工具的本质应该都是对 .hprof 文件进行分析。对于 1 和 2 需要更高的内存分析技巧, LeakCanary 会显得更直观,找出泄漏的引用链
今天文本的主角就是 LeakCanary
LeakCanary
严格来说我们是基于 LeakCanary2 , LeakCanary2 相比与 LeakCanary1 用法更简单简洁,核心改变的是 LeakCanary1 底层的
Deap 解析器是用的 haha, 而 LeakCanary2 底层用的解析器是 shark shark 会效率更高内存用量更小。
关于 LeakCanary 的内存泄漏检测原理网上的好文比较多,此处不再做赘述。
当前我将 LeakCanary + 钉钉提供的能力简单封装了一个库,这个库的功能就是自定义 LeakCanary 的内存泄漏上报到钉钉发送内存泄漏警报
到钉钉群里,做这个功能的目的是让我们对现有的内存泄漏更重视起来,和方便 teamleader 及时发现内存泄漏对泄漏及时做解决或者分发。
例如下图:
会将内存泄漏的:
1 引用链
2 泄漏的内存大小 例: (4383079 bytes retained by leaking objects)
3 机型以及 Android 版本
等信息上报到群中,开发人员可通过这些信息来排查和解决这些内存泄漏。
此处介绍一下为什么不上传 hprof 到服务器做分析。
1 需要服务器开发成本
2 hprof 文件比较大一般每个文件在 20 ~ 40 MB 不等,如果频繁有这种 IO 操作服务端和客户端压力不小
com.julive:leak:1.1.3
用法:
1
app:gardle 的 dependencies 中添加
debugImplementation ‘com.julive:leak:1.1.3’
sync 需 VPN
2
建议在 application
LeakCanaryManager.getInstance().init(accessToken);
API :
init
retainedVisibleThreshold 为上报阈值默认为 1 如果觉得分析和上报频繁可调大此参数
accessToken 上报到哪个钉钉群,详见 dingtalk Doc access to DingTalk post group token {@link @https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq}
setOnHeapInterceptListener
非必须调用,如果需要自己拦截处理,则返回 true 然后自己做实现处理,钉钉上报则不生效, 例:
LeakCanaryManager.getInstance().setOnHeapInterceptListener(new OnHeapInterceptListener() {
@Override
public boolean onHeapAnalyzed(@NotNull HeapAnalysis heapAnalysis) {
//TODO Do somethings
return true;
}
});
权限:
包增量:
Jar 包文件为: 24kb
最终打成 aar 约为 40kb
------------ 6.16 去依赖优化后 8kb
TODO
重复内存泄漏只钉钉上报一次
Thanks
LeakCanary
DingTalk
源代码:
class DingTalkPoster {
private static final MediaType jsonType = MediaType.parse("application/json; charset=utf-8");
DingTalkPoster() {
}
static void request(String leakString) {
OkHttpClient client = (new Builder()).sslSocketFactory(SSLSocketClient.getSSLSocketFactory()).build();
String jsonString = "{\"msgtype\": \"text\",\"text\": {\"content\": \"发现新的内存泄漏\n" + leakString + "\"}}";
DingTalkInfo info = new DingTalkInfo();
TextBean text = new TextBean();
text.setContent(leakString);
info.setText(text);
if (!ApiCheckUtils.foundSDK("com.google.gson.Gson")) {
Log.e(LeakCanaryManager.class.getSimpleName(), "Not found dependencies : Gson");
} else {
String jsonStr = (new Gson()).toJson(info, DingTalkInfo.class);
Log.e("DingTalkPoster", jsonString);
Log.e("DingTalkPoster", jsonStr);
if (ApiCheckUtils.foundSDK("okhttp3.OkHttpClient")) {
RequestBody body = RequestBody.create(jsonType, jsonStr);
Request request = (new okhttp3.Request.Builder()).url("https://oapi.dingtalk.com/robot/send?access_token=" + LeakCanaryManager.getInstance().getAccessToken()).addHeader("Content-Type", "application/json; charset=UTF-8").post(body).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(LeakCanaryManager.class.getSimpleName(), "onFailure IOException", e);
}
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Log.e(LeakCanaryManager.class.getSimpleName(), "onResponse" + response.body().string());
}
});
} else {
Log.e(LeakCanaryManager.class.getSimpleName(), "Not found dependencies : OkHttp3");
}
}
}
}
public class LeakCanaryManager {
private static LeakCanaryManager mInstance;
private int retainedVisibleThreshold = 1;
private String accessToken;
private OnHeapInterceptListener mOnHeapInterceptListener;
private LeakCanaryManager() {
}
public static LeakCanaryManager getInstance() {
if (mInstance == null) {
mInstance = new LeakCanaryManager();
}
return mInstance;
}
public void init(int retainedVisibleThreshold, String accessToken) {
this.accessToken = accessToken;
if (ApiCheckUtils.foundSDK("leakcanary.LeakCanary")) {
LeakCanary.setConfig(LeakCanary.getConfig().newBuilder().retainedVisibleThreshold(retainedVisibleThreshold).onHeapAnalyzedListener(new LeakUploader()).build());
} else {
Log.e(LeakCanaryManager.class.getSimpleName(), "Not found dependencies : leakcanary2");
}
}
public void init(String accessToken) {
this.init(this.retainedVisibleThreshold, accessToken);
}
public void setOnHeapInterceptListener(OnHeapInterceptListener onHeapInterceptListener) {
this.mOnHeapInterceptListener = onHeapInterceptListener;
}
OnHeapInterceptListener getOnHeapInterceptListener() {
return this.mOnHeapInterceptListener;
}
String getAccessToken() {
return this.accessToken;
}
}
class LeakUploader implements OnHeapAnalyzedListener {
private OnHeapAnalyzedListener listener;
LeakUploader() {
this.listener = DefaultOnHeapAnalyzedListener.Companion.create();
}
public void onHeapAnalyzed(@NotNull HeapAnalysis heapAnalysis) {
this.listener.onHeapAnalyzed(heapAnalysis);
if (LeakCanaryManager.getInstance().getOnHeapInterceptListener() == null || !LeakCanaryManager.getInstance().getOnHeapInterceptListener().onHeapAnalyzed(heapAnalysis)) {
DingTalkPoster.request(heapAnalysis.toString());
}
}
}
public interface OnHeapInterceptListener {
boolean onHeapAnalyzed(@NotNull HeapAnalysis var1);
}