5. 第三方模块
5.1 Crash(XCrash)
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
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)
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
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主要是依赖于作者的第三方库,通过框架的回调,获取我们想要的函数信息