“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
严格来说我们是基于 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 操作服务端和客户端压力不小
用法:
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
重复内存泄漏只钉钉上报一次
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); }