| / | 公司 | 运营状态 | 开源状态 |
|---|---|---|---|
| Matrix | 腾讯 | ✅ | ✅ |
| Booster | 滴滴 | ✅ | ✅ |
| xCrash | 爱奇艺 | ✅ | ✅ |
| ArgusAPM | 360 | ❌ | ✅ |
| Emmagee | 网易 | ❌ | ✅ |
| U-Meng | 阿里 | ✅ | ❌ |
| Bugly | 腾讯 | ✅ | ❌ |
| Tingyun | 听云 | ✅ | ❌ |
- 开源地址
- Matrix:github.com/Tencent/mat…
- Booster:github.com/didi/booste…
- xCrash:github.com/iqiyi/xCras…
- ArgusAPM:github.com/Qihoo360/Ar…
- Emmagee:github.com/NetEase/Emm…
- 服务页面
- U-Meng:www.umeng.com/
- Bugly:bugly.qq.com/
- Tingyun:www.tingyun.com/
核心指标
-
Crash上报
-
消耗性指标:
- 流量
- 电量
- CPU
-
启动时间
- 应用启动时间
- 页面启动时间
-
应用状态
- FPS/卡顿/ANR
- 内存泄露/内存状态侦测
Crash上报
Java Crash
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
// Record
}
});
Native Crash
Android 平台 Native 代码的崩溃捕获机制及实现:cloud.tencent.com/developer/a…
- 核心原理
- 核心函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact));
- 核心工具
- dladdr()获取共享库起始地址,减去崩溃处pc值,即可得到崩溃处相对地址
- addr2line通过相对地址得到函数调用栈
启动时间
应用启动
- sStartUpTimeStamp
// ContentProvider >>> Application
public class LauncherHelpProvider extends ContentProvider {
public static long sStartUpTimeStamp = SystemClock.uptimeMillis();
}
- coldLauncherTime
public class MainApplication extends Application implements Application.ActivityLifecycleCallbacks {
@Override
public void onCreate() {
registerActivityLifecycleCallbacks(this);
}
@Override
public void onActivityResumed(@NonNull final Activity activity) {
onActivityVisible(activity, () -> {
final long coldLauncherTime = SystemClock.uptimeMillis() - LauncherHelpProvider.sStartUpTimeStamp;
});
}
private void onActivityVisible(Activity activity, Runnable runnable) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
activity.runOnUiThread(runnable);
} else {
activity.getWindow().getDecorView().getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> runnable.run());
}
}
}
页面启动
- 上一个Activity的onPause和下一个Activity的onResume
@Override
public void onActivityPaused(@NonNull Activity activity) {
mActivityLauncherTimeStamp = SystemClock.uptimeMillis();
}
@Override
public void onActivityResumed(@NonNull final Activity activity) {
onActivityVisible(activity, () -> {
final long actvityLauncherTime = SystemClock.uptimeMillis() - mActivityLauncherTimeStamp;
});
}
页面状态
FPS
- 瞬时掉帧FPS >>> 平均FPS
- Matrix标准:github.com/Tencent/mat…
掉帧数小于 3 帧的情况属于最佳,依次类推
启动时机
@Override
public void onActivityResumed(@NonNull final Activity activity) {
activity.getWindow().getDecorView().getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> {
resumeTrack();
}
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
pauseTrack();
}
检测方法
- ArgusAPM:监测Choreographer VSYNC信号时间差
无需页面刷新时,监测过于频繁,占用CPU
Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {
mFpsCount++;
mFrameTimeNanos = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
});
- BlockCanary:监测UI线程Message耗时
// Looper源码
public static void loop() {
// ...
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// ...
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
// 自定义Printer
class LooperPrinter implements Printer {
@Override
public void println(String x) {
// ">>>>>"为开始,"<<<<<"为结束
dispatch(x.charAt(0) == '>', x);
}
}
- Matrix:反射添加Choreographer.FrameCallback,区分doFrame
- UIThreadMonitor初始化
private static final String ADD_CALLBACK = "addCallbackLocked";
public void init(TraceConfig config) {
callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
if (null != callbackQueues) {
addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
}
}
- UIThreadMonitor.addFrameCallback反射调用Choreographer.addCallbackLocked,而不是postCallback
private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
// ...
synchronized (callbackQueueLock) {
Method method = null;
switch (type) {
case CALLBACK_INPUT:
method = addInputQueue;
break;
case CALLBACK_ANIMATION:
method = addAnimationQueue;
break;
case CALLBACK_TRAVERSAL:
method = addTraversalQueue;
break;
}
if (null != method) {
method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
callbackExist[type] = true;
}
}
// ...
}
- UIThreadMonitor一直观测Looper分发,并监测CALLBACK_INPUT队列消息
doFrameBegin -> isVsyncFrame = true -> doFrameEnd
sequenceDiagram
UIThreadMonitor ->> Choreographer: addFrameCallback(CALLBACK_INPUT, this)
par run
Choreographer ->> UIThreadMonitor: run
UIThreadMonitor ->> UIThreadMonitor: doFrameBegin
end
LooperMonitor ->> UIThreadMonitor: dispatchBegin
alt isVsyncFrame
LooperMonitor ->> UIThreadMonitor: dispatchEnd
UIThreadMonitor ->> UIThreadMonitor: doFrameEnd
end
内存监测
- 内存泄露,LeakCanary:github.com/square/leak…
@Override
public void onActivityDestroyed(@NonNull final Activity activity) {
mActivityStringWeakHashMap.put(activity, activity.getClass().getSimpleName());
}
@Override
public void onActivityStopped(@NonNull final Activity activity) {
Runtime.getRuntime().gc();
mHandler.postDelayed(() -> {
Runtime.getRuntime().gc();
SystemClock.sleep(100);
System.runFinalization();
HashMap<String, Integer> countMap = new HashMap<>();
for (Map.Entry entry : mActivityStringWeakHashMap.entrySet()) {
String name = entry.getKey().getClass().getName();
Integer count = countMap.get(name);
countMap.put(name, count != null ? count + 1 : 1);
}
// notify leak
}, 10 * 1000);
}
- 内存状态
- TotalPss(整体内存,native+dalvik+共享)
- nativePss(native内存)
- dalvikPss(java内存 OOM原因)
Debug.MemoryInfo debugMemoryInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(debugMemoryInfo);
appMemory.nativePss = debugMemoryInfo.nativePss >> 10;
appMemory.dalvikPss = debugMemoryInfo.dalvikPss >> 10;
appMemory.totalPss = debugMemoryInfo.getTotalPss() >> 10;
流量/电量统计
- 流量:统一网络流量
@Override
public void onActivityStarted(@NonNull Activity activity) {
mStartBytes = TrafficStats.getUidRxBytes(Process.myUid());
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
mCurrentBytes = TrafficStats.getUidRxBytes(Process.myUid());
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
mTotalCount = TrafficStats.getUidRxBytes(Process.myUid()) - mStartBytes;
}
- 电量:百分比电量
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
android.content.Intent batteryStatus = application.registerReceiver(null, filter);
int status = batteryStatus.getIntExtra("status", 0);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
无侵入
- Dexposed
- AspectD