Android内存泄漏钉钉上报

Android内存泄漏钉钉上报
“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 及时发现内存泄漏对泄漏及时做解决或者分发。

例如下图:

Android内存泄漏钉钉上报
会将内存泄漏的:

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

Android内存泄漏钉钉上报

LeakCanaryManager.getInstance().init(accessToken);

API :
init

Android内存泄漏钉钉上报
retainedVisibleThreshold 为上报阈值默认为 1 如果觉得分析和上报频繁可调大此参数
accessToken 上报到哪个钉钉群,详见 dingtalk Doc access to DingTalk post group token {@link @https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq}

setOnHeapInterceptListener

Android内存泄漏钉钉上报
非必须调用,如果需要自己拦截处理,则返回 true 然后自己做实现处理,钉钉上报则不生效, 例:

LeakCanaryManager.getInstance().setOnHeapInterceptListener(new OnHeapInterceptListener() {
    @Override
 public boolean onHeapAnalyzed(@NotNull HeapAnalysis heapAnalysis) {
        //TODO Do somethings
 return true;
 }
});

权限:

包增量:

Jar 包文件为: 24kb

Android内存泄漏钉钉上报
最终打成 aar 约为 40kb

------------ 6.16 去依赖优化后 8kb

Android内存泄漏钉钉上报

TODO

重复内存泄漏只钉钉上报一次

Thanks

LeakCanary
DingTalk

源代码:

Android内存泄漏钉钉上报
Android内存泄漏钉钉上报
Android内存泄漏钉钉上报

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);
}
上一篇:UE4_关于Texture中sRGB选项的解释


下一篇:UE4高级运动系统学习笔记之八方向移动