Android memory leak nail reporting

Keywords: Android Memory Leak


"A small leak will sink a great ship." - Benjamin Franklin
"The dike of thousands of miles collapses in the ant's nest." - Han Feizi · Yu Lao

If the APP is like a big ship sailing in the sea, the memory leak is like a small leak under the big ship.

When more and more loopholes are not repaired, the ship may sink. APP may also crash due to memory overflow caused by more and more memory leaks.

We are constantly doing code iteration. It is inevitable that there will be some small problems in the code during the iteration. For example, if there is a memory leak, we will also repair it. Android troubleshooting

There are many ways to leak memory, such as:

1 Mat

2 Android profiler

3 LeakCanary

The essence of all three tools should be to analyze. hprof files. For 1 and 2, which require higher memory analysis skills, LeakCanary will be more intuitive to find the leaked reference chain

The protagonist of today's text is LeakCanary

LeakCanary

Strictly speaking, we are based on LeakCanary2. Compared with LeakCanary1, the usage of LeakCanary2 is simpler and simpler, and the core change is the underlying structure of LeakCanary1

The soap parser uses haha, while the underlying parser of LeakCanary2 uses shark, which is more efficient and uses less memory.

There are many good articles on LeakCanary's memory leak detection principle on the Internet, which will not be repeated here.

At present, I simply encapsulate the capabilities provided by LeakCanary + nail into a library. The function of this library is to customize the memory leak report nail of LeakCanary and send memory leak alarm

In the nail group, the purpose of this function is to make us pay more attention to the existing memory leakage, and facilitate the team leader to find the memory leakage in time, solve or distribute the leakage in time.

For example, the following figure:


Will leak memory:

1 reference chain

2. Example of leaked memory size: (4383079 bytes retained by leaking objects)

3 models and Android version

Such information is reported to the group, and developers can use this information to troubleshoot and solve these memory leaks.

Here's why you don't upload hprof to the server for analysis.

1. Server development cost required

2. Hprof files are relatively large. Generally, each file ranges from 20 to 40 MB. If such IO operations occur frequently, the pressure on the server and client is not small

com.julive:leak:1.1.3

Usage:

1

App: added in the dependencies of the garden
debugImplementation 'com.julive:leak:1.1.3'

sync requires VPN

2

Recommended in application

LeakCanaryManager.getInstance().init(accessToken);

API :
init


retainedVisibleThreshold is the reporting threshold, which is 1 by default. If you think analysis and reporting are frequent, you can increase this parameter
Refer to dingtalk doc access to dingtalk post group token {@ link for details of the nail group to which the accessToken is reported@ https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq }

setOnHeapInterceptListener


It is not necessary to call. If you need to intercept and process by yourself, return true, and then do the implementation processing by yourself. Nail reporting will not take effect, for example:

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

jurisdiction:

Package increment:

Jar package file: 24kb


The final aar is about 40kb

------------6.16 8 KB after de dependency optimization

TODO

Repeated memory leaks are reported only once

Thanks

LeakCanary
DingTalk

Source code:



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\": \"A new memory leak was found\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);
}

Posted by hermand on Sat, 18 Sep 2021 04:16:51 -0700