5章 性能平台GodEye源码分析-第三方模块

5. 第三方模块

5.1 Crash(XCrash)

5章 性能平台GodEye源码分析-第三方模块_第1张图片

Crash监控崩溃后的堆栈上传,作者采用接入爱奇艺的XCrash框架

源码分析

1、启动Crash的监控

Crash的监控通过反射启动XCrash框架,因为XCrash的框架引入,可能会和你的项目有些框架的冲突,作者将XCrash的初始化代码放置在另一个Module中,这样方便热插拔当前的崩溃框架

public class Crash extends ProduceableSubject<List<CrashInfo>> implements Install<CrashConfig> {
     
    private boolean mInstalled;
    private CrashConfig mConfig;

    @Override
    public synchronized boolean install(final CrashConfig crashContext) {
     
        if (mInstalled) {
     
            L.d("Crash already installed, ignore.");
            return true;
        }
        Consumer<List<CrashInfo>> consumer = this::produce;
        // auto detect crash collector provider
        try {
     
            ReflectUtil.invokeStaticMethodUnSafe("cn.hikyson.android.godeye.xcrash.GodEyePluginXCrash", "init",
                    new Class<?>[]{
     CrashConfig.class, Consumer.class}, new Object[]{
     crashContext, consumer});
        } catch (Exception e) {
     
            L.d("Crash can not be installed:", e.getLocalizedMessage());
            return false;
        }
        mConfig = crashContext;
        mInstalled = true;
        L.d("Crash installed.");
        return true;
    }
}

2、采集Crash信息

Crash初始化主要参考XCrash文档

public class GodEyePluginXCrash {
     

    /**
     * entrace
     *
     * @param crashContext
     * @param consumer
     */
    public static void init(CrashConfig crashContext, Consumer<List<CrashInfo>> consumer) {
     
        ICrashCallback callback = (logPath, emergency) -> {
     
            try {
     
                sendThenDeleteCrashLog(logPath, emergency, crashContext, consumer);
            } catch (IOException e) {
     
                L.e(e);
            }
        };
        XCrash.init(GodEye.instance().getApplication(), new XCrash.InitParameters()
                .setAppVersion(getAppVersion(GodEye.instance().getApplication()))
                .setJavaRethrow(true)
                .setJavaLogCountMax(10)
                .setJavaDumpAllThreadsWhiteList(new String[]{
     "^main$", "^Binder:.*", ".*Finalizer.*"})
                .setJavaDumpAllThreadsCountMax(10)
                .setJavaCallback(callback)
                .setNativeRethrow(true)
                .setNativeLogCountMax(10)
                .setNativeDumpAllThreadsWhiteList(new String[]{
     "^xcrash\\.sample$", "^Signal Catcher$", "^Jit thread pool$", ".*(R|r)ender.*", ".*Chrome.*"})
                .setNativeDumpAllThreadsCountMax(10)
                .setNativeCallback(callback)
                .setAnrRethrow(true)
                .setAnrLogCountMax(10)
                .setAnrCallback(callback)
                .setPlaceholderCountMax(3)
                .setPlaceholderSizeKb(512)
                .setLogFileMaintainDelayMs(1000));
        Schedulers.computation().scheduleDirect(() -> {
     
            try {
     
                sendThenDeleteCrashLogs(consumer);
            } catch (Exception e) {
     
                L.e(e);
            }
        });
    }
}

Crash的初始化之后,会通过sendThenDeleteCrashLogs获取当前已有的崩溃堆栈信息,参数consumer就是通过反射传递过来的日志器,如果当前存在Crash的信息,则获取XCrash的崩溃信息,将Crash信息封装后发送出去

private static void sendThenDeleteCrashLogs(Consumer<List<CrashInfo>> consumer) throws Exception {
     
    File[] files = TombstoneManager.getAllTombstones();
    List<CrashInfo> crashes = new ArrayList<>();
    for (File f : files) {
     
        try {
     
            crashes.add(wrapCrashMessage(TombstoneParser.parse(f.getAbsolutePath(), null)));
        } catch (IOException e) {
     
            L.e(e);
        }
    }
    if (!crashes.isEmpty()) {
     
        L.d("Crash produce message when install, crash count:%s", crashes.size());
        consumer.accept(crashes);
        TombstoneManager.clearAllTombstones();
    }
}

3、总结

借助当前的第三方的优秀框架,通过热插拔的形式,引入到我们的性能监控中

5.2 LeakCanary

5章 性能平台GodEye源码分析-第三方模块_第2张图片

LeakCanary监控应用的内存泄漏信息,作者采用接入LeakCanary框架

源码分析

1、启动LeakCanary的监控

LeakCanary的监控通过反射启动LeakCanary框架

public class Leak extends ProduceableSubject<LeakInfo> implements Install<LeakConfig> {
     
    private boolean mInstalled;
    private LeakConfig mConfig;

    @Override
    public synchronized boolean install(LeakConfig config) {
     
        if (mInstalled) {
     
            L.d("Leak already installed, ignore.");
            return true;
        }
        mConfig = config;
        try {
     
            ReflectUtil.invokeStaticMethodUnSafe("cn.hikyson.android.godeye.leakcanary.GodEyePluginLeakCanary", "install",
                    new Class<?>[]{
     Application.class, Leak.class}, new Object[]{
     GodEye.instance().getApplication(), this});
        } catch (Exception e) {
     
            L.d("Leak can not be installed, please add android-godeye-leakcanary dependency first:", e);
            return false;
        }
        mInstalled = true;
        L.d("Leak installed.");
        return true;
    }
}

2、采集LeakCanary信息

LeakCanary初始化主要参考LeakCanary文档,当发生内存泄漏的时候,将当前的内存信息封装后发送出去

public class GodEyePluginLeakCanary {
     
    @Keep
    public static void install(final Application application, final Leak leakModule) {
     
        ThreadUtil.sMain.execute(new Runnable() {
     
            @Override
            public void run() {
     
                AppWatcher.INSTANCE.manualInstall(application);
                LeakCanary.INSTANCE.showLeakDisplayActivityLauncherIcon(false);
                LeakCanary.setConfig(new LeakCanary.Config().newBuilder()
                        .requestWriteExternalStoragePermission(false)
                        .dumpHeap(true)
                        .onHeapAnalyzedListener(new OnHeapAnalyzedListener() {
     
                            @Override
                            public void onHeapAnalyzed(@NotNull HeapAnalysis heapAnalysis) {
     
                                if (heapAnalysis instanceof HeapAnalysisFailure) {
     
                                    L.w("GodEyePluginLeakCanary leak analysis failure:" + heapAnalysis.toString());
                                    return;
                                }
                                if (!(heapAnalysis instanceof HeapAnalysisSuccess)) {
     
                                    L.w("GodEyePluginLeakCanary leak analysis type error: " + heapAnalysis.getClass().getName());
                                    return;
                                }
                                final HeapAnalysisSuccess analysisSuccess = (HeapAnalysisSuccess) heapAnalysis;
                                IteratorUtil.forEach(analysisSuccess.getAllLeaks().iterator(), new Consumer<shark.Leak>() {
     
                                    @Override
                                    public void accept(shark.Leak leak) {
     
                                        leakModule.produce(new LeakInfo(analysisSuccess.getCreatedAtTimeMillis(), analysisSuccess.getAnalysisDurationMillis(), leak));
                                    }
                                });
                            }
                        }).build());
                AppWatcher.setConfig(new AppWatcher.Config().newBuilder().enabled(true).build());
            }
        });
    }
}

3、总结

原理同样很简单,借助当前的第三方的优秀框架,通过热插拔的形式,引入到我们的性能监控中

5.3 NetWork(OkHttp)

5章 性能平台GodEye源码分析-第三方模块_第3张图片

5章 性能平台GodEye源码分析-第三方模块_第4张图片

NetWork的监控主要是计算网络的请求时间,通过OkHttp提供的拦截器中,有对应的回调

源码分析

1、启动NetWork的监控

NetWork的监控通过OkHttp自定义拦截器和回调去实现,项目的网络请求,就由当前初始化的这个OkHttp客户端去请求,即可达到监听的效果

  • eventListenerFactory:表示OkHttp监听请求到结束所有过程的监听器
  • addNetworkInterceptor:表示设置有网络时候的拦截器
GodEyePluginOkNetwork godEyePluginOkNetwork = new GodEyePluginOkNetwork();
mZygote = new OkHttpClient.Builder().eventListenerFactory(godEyePluginOkNetwork).addNetworkInterceptor(godEyePluginOkNetwork).build();

2、采集NetWork信息

采集网络信息都在拦截器和监听器里面

public class GodEyePluginOkNetwork extends OkHttpNetworkContentInterceptor implements EventListener.Factory {
     

    public GodEyePluginOkNetwork() {
     
        super(new HttpContentTimeMapping());
    }

    @Override
    public EventListener create(Call call) {
     
        return new OkNetworkEventListener(this.mHttpContentTimeMapping);
    }
}

由于OkHttp封装得很好,所有的信息回调都会有,这里只是做时间的记录和一些关键的信息的记录

  • RequestHeader时间
  • RequestBody时间
  • ResponseHeader时间
  • ResponseBody时间
  • Connect时间
  • Dns时间
  • Other时间
class OkNetworkEventListener extends EventListener {
     
    private NetworkInfo<HttpContent> mNetworkInfo;
    private long mCallStartTimeMillis;
    private long mDnsStartTimeMillis;
    private long mConnectionStartTimeMillis;
    private long mRequestHeadersStartTimeMillis;
    private long mRequestBodyStartTimeMillis;
    private long mResponseHeadersStartTimeMillis;
    private long mResponseBodyStartTimeMillis;
    private HttpContentTimeMapping mHttpContentTimeMapping;

    OkNetworkEventListener(HttpContentTimeMapping httpContentTimeMapping) {
     
        this.mHttpContentTimeMapping = httpContentTimeMapping;
        this.mNetworkInfo = new NetworkInfo<>();
        this.mNetworkInfo.networkTime = new NetworkTime();
        this.mNetworkInfo.extraInfo = new HashMap<>();
    }

    @Override
    public void callStart(Call call) {
     
        super.callStart(call);
        mCallStartTimeMillis = System.currentTimeMillis();
        this.mNetworkInfo.summary = call.request().method() + " " + call.request().url();
    }

    @Override
    public void dnsStart(Call call, String domainName) {
     
        super.dnsStart(call, domainName);
        mDnsStartTimeMillis = System.currentTimeMillis();
    }

    @Override
    public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
     
        super.dnsEnd(call, domainName, inetAddressList);
        this.mNetworkInfo.networkTime.networkTimeMillisMap.put("DnsTime", System.currentTimeMillis() - mDnsStartTimeMillis);
    }

    @Override
    public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
     
        super.connectStart(call, inetSocketAddress, proxy);
        mConnectionStartTimeMillis = System.currentTimeMillis();
    }

    @Override
    public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol) {
     
        super.connectEnd(call, inetSocketAddress, proxy, protocol);
        this.mNetworkInfo.networkTime.networkTimeMillisMap.put("ConnectTime", System.currentTimeMillis() - mConnectionStartTimeMillis);
    }

    @Override
    public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol,
                              IOException ioe) {
     
        super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
        this.mNetworkInfo.networkTime.networkTimeMillisMap.put("ConnectTime", System.currentTimeMillis() - mConnectionStartTimeMillis);
    }

    @Override
    public void connectionAcquired(Call call, Connection connection) {
     
        super.connectionAcquired(call, connection);
        Handshake handshake = connection.handshake();
        String cipherSuite = "";
        String tlsVersion = "";
        if (handshake != null) {
     
            cipherSuite = handshake.cipherSuite().javaName();
            tlsVersion = handshake.tlsVersion().javaName();
        }
        Socket socket = connection.socket();
        int localPort = socket.getLocalPort();
        int remotePort = socket.getPort();
        String localIp = "";
        String remoteIp = "";
        InetAddress localAddress = socket.getLocalAddress();
        if (localAddress != null) {
     
            localIp = localAddress.getHostAddress();
        }
        InetAddress remoteAddress = socket.getInetAddress();
        if (remoteAddress != null) {
     
            remoteIp = remoteAddress.getHostAddress();
        }
        mNetworkInfo.extraInfo.put("cipherSuite", cipherSuite);
        mNetworkInfo.extraInfo.put("tlsVersion", tlsVersion);
        mNetworkInfo.extraInfo.put("localIp", localIp);
        mNetworkInfo.extraInfo.put("localPort", localPort);
        mNetworkInfo.extraInfo.put("remoteIp", remoteIp);
        mNetworkInfo.extraInfo.put("remotePort", remotePort);
    }

    @Override
    public void requestHeadersStart(Call call) {
     
        super.requestHeadersStart(call);
        mRequestHeadersStartTimeMillis = System.currentTimeMillis();
    }

    @Override
    public void requestHeadersEnd(Call call, Request request) {
     
        super.requestHeadersEnd(call, request);
        this.mNetworkInfo.networkTime.networkTimeMillisMap.put("RequestHeadersTime", System.currentTimeMillis() - mRequestHeadersStartTimeMillis);
    }

    @Override
    public void requestBodyStart(Call call) {
     
        super.requestBodyStart(call);
        mRequestBodyStartTimeMillis = System.currentTimeMillis();
    }

    @Override
    public void requestBodyEnd(Call call, long byteCount) {
     
        super.requestBodyEnd(call, byteCount);
        this.mNetworkInfo.networkTime.networkTimeMillisMap.put("RequestBodyTime", System.currentTimeMillis() - mRequestBodyStartTimeMillis);
    }

    @Override
    public void responseHeadersStart(Call call) {
     
        super.responseHeadersStart(call);
        mResponseHeadersStartTimeMillis = System.currentTimeMillis();
    }

    @Override
    public void responseHeadersEnd(Call call, Response response) {
     
        super.responseHeadersEnd(call, response);
        this.mNetworkInfo.networkTime.networkTimeMillisMap.put("ResponseHeadersTime", System.currentTimeMillis() - mResponseHeadersStartTimeMillis);
    }

    @Override
    public void responseBodyStart(Call call) {
     
        super.responseBodyStart(call);
        mResponseBodyStartTimeMillis = System.currentTimeMillis();
    }

    @Override
    public void responseBodyEnd(Call call, long byteCount) {
     
        super.responseBodyEnd(call, byteCount);
        this.mNetworkInfo.networkTime.networkTimeMillisMap.put("ResponseBodyTime", System.currentTimeMillis() - mResponseBodyStartTimeMillis);
    }

    @Override
    public void callEnd(Call call) {
     
        super.callEnd(call);
        this.mNetworkInfo.networkTime.totalTimeMillis = System.currentTimeMillis() - mCallStartTimeMillis;
        mNetworkInfo.networkContent = mHttpContentTimeMapping.removeAndGetRecord(call);
        if (mNetworkInfo.networkContent != null) {
     
            if (mNetworkInfo.networkContent.httpResponse != null) {
     
                mNetworkInfo.isSuccessful = isSuccessful(mNetworkInfo.networkContent.httpResponse.code);
            }
            if (mNetworkInfo.networkContent.httpResponse != null) {
     
                mNetworkInfo.message = mNetworkInfo.networkContent.httpResponse.message;
            }
        }
        try {
     
            GodEyeHelper.onNetworkEnd(mNetworkInfo);
        } catch (UninstallException e) {
     
            e.printStackTrace();
        }
    }

    @Override
    public void callFailed(Call call, IOException ioe) {
     
        super.callFailed(call, ioe);
        this.mNetworkInfo.networkTime.totalTimeMillis = System.currentTimeMillis() - mCallStartTimeMillis;
        mNetworkInfo.isSuccessful = false;
        mNetworkInfo.message = String.valueOf(ioe);
        mNetworkInfo.networkContent = mHttpContentTimeMapping.removeAndGetRecord(call);
        try {
     
            GodEyeHelper.onNetworkEnd(mNetworkInfo);
        } catch (UninstallException e) {
     
            e.printStackTrace();
        }
    }

    private static boolean isSuccessful(int code) {
     
        return code >= 200 && code < 300;
    }
}

最后通过GodEyeHelper.onNetworkEnd(mNetworkInfo);将数据发送出去

public static void onNetworkEnd(NetworkInfo networkInfo) throws UninstallException {
     
    GodEye.instance().<Network>getModule(GodEye.ModuleName.NETWORK).produce(networkInfo);
    L.d("GodEyeHelper onNetworkEnd: %s", networkInfo == null ? "null" : networkInfo.toSummaryString());
}

3、总结

原理同样很简单,借助当前的第三方的优秀框架提供的回调参数,通过热插拔的形式,引入到我们的性能监控中

5.4 MethodCanary

5章 性能平台GodEye源码分析-第三方模块_第5张图片

MethodCanary用的是作者的另一个框架,通过调用开始和结束,采集当前时间段内线程中执行的函数和时间线

源码分析

1、启动MethodCanary的监控

MethodCanary的启动没做任何事情,因为其采集是需要手动调用才可以

public class MethodCanary extends ProduceableSubject<MethodsRecordInfo> implements Install<MethodCanaryConfig> {
     
    private boolean mInstalled = false;
    private MethodCanaryConfig mMethodCanaryContext;

    @Override
    public synchronized boolean install(final MethodCanaryConfig methodCanaryContext) {
     
        if (this.mInstalled) {
     
            L.d("MethodCanary already installed, ignore.");
            return true;
        }
        this.mMethodCanaryContext = methodCanaryContext;
        this.mInstalled = true;
        L.d("MethodCanary installed.");
        return true;
    }
}

2、采集MethodCanary信息

采集的过程使用MethodCanary提供的Api,需要手动调用启动和结束,结束后将返回的方法信息发送出去

public synchronized void startMonitor(String tag) {
     
    try {
     
        if (!isInstalled()) {
     
            L.d("MethodCanary start monitor fail, not installed.");
            return;
        }
        cn.hikyson.methodcanary.lib.MethodCanary.get().startMethodTracing(tag);
        L.d("MethodCanary start monitor success.");
    } catch (Exception e) {
     
        L.d("MethodCanary start monitor fail:" + e);
    }
}

public synchronized void stopMonitor(String tag) {
     
    try {
     
        if (!isInstalled()) {
     
            L.d("MethodCanary stop monitor fail, not installed.");
            return;
        }
        cn.hikyson.methodcanary.lib.MethodCanary.get().stopMethodTracing(tag
                , new cn.hikyson.methodcanary.lib.MethodCanaryConfig(this.mMethodCanaryContext.lowCostMethodThresholdMillis()), (sessionTag, startMillis, stopMillis, methodEventMap) -> {
     
                    long start0 = System.currentTimeMillis();
                    MethodsRecordInfo methodsRecordInfo = MethodCanaryConverter.convertToMethodsRecordInfo(startMillis, stopMillis, methodEventMap);
//                        recordToFile(methodEventMap, methodsRecordInfo);
                    long start1 = System.currentTimeMillis();
                    MethodCanaryConverter.filter(methodsRecordInfo, this.mMethodCanaryContext);
                    long end = System.currentTimeMillis();
                    L.d(String.format("MethodCanary output success! cost %s ms, filter cost %s ms", end - start0, end - start1));
                    produce(methodsRecordInfo);
                });
        L.d("MethodCanary stopped monitor and output processing...");
    } catch (Exception e) {
     
        L.d("MethodCanary stop monitor fail:" + e);
    }
}

作者也是通过Web点击开始和结束操作当前的MethodCanary

public class WebSocketMethodCanaryProcessor implements WebSocketProcessor {
     
    @Override
    public void process(WebSocket webSocket, JSONObject msgJSONObject) {
     
        try {
     
            if ("start".equals(msgJSONObject.optString("payload"))) {
     
                GodEyeHelper.startMethodCanaryRecording("AndroidGodEye-Monitor-Tag");
            } else if ("stop".equals(msgJSONObject.optString("payload"))) {
     
                GodEyeHelper.stopMethodCanaryRecording("AndroidGodEye-Monitor-Tag");
            }
            webSocket.send(new ServerMessage("methodCanaryMonitorState", Collections.singletonMap("isRunning", GodEyeHelper.isMethodCanaryRecording("AndroidGodEye-Monitor-Tag"))).toString());
        } catch (UninstallException e) {
     
            L.e(String.valueOf(e));
        }
    }
}

3、总结

MethodCanary主要是依赖于作者的第三方库,通过框架的回调,获取我们想要的函数信息

你可能感兴趣的:(android,GodEye,性能,性能监控,APM)