[Flutter笔记]Flutter Android的APM

1,676 阅读2分钟
/公司运营状态开源状态
Matrix腾讯
Booster滴滴
xCrash爱奇艺
ArgusAPM360
Emmagee网易
U-Meng阿里
Bugly腾讯
Tingyun听云
  • 开源地址
  1. Matrix:github.com/Tencent/mat…
  2. Booster:github.com/didi/booste…
  3. xCrash:github.com/iqiyi/xCras…
  4. ArgusAPM:github.com/Qihoo360/Ar…
  5. Emmagee:github.com/NetEase/Emm…
  • 服务页面
  1. U-Meng:www.umeng.com/
  2. Bugly:bugly.qq.com/
  3. 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…

  • 核心原理

image.png

  • 核心函数
#include <signal.h> 
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact));
  • 核心工具
  1. dladdr()获取共享库起始地址,减去崩溃处pc值,即可得到崩溃处相对地址
  2. 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

image.png

掉帧数小于 3 帧的情况属于最佳,依次类推

image.png

启动时机

@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耗时

github.com/markzhai/An…

// 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
  1. 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);
    }
}
  1. 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;
        }
    }
    // ...
}
  1. UIThreadMonitor一直观测Looper分发,并监测CALLBACK_INPUT队列消息

doFrameBegin -> isVsyncFrame = true -> doFrameEnd

github.com/Tencent/mat…

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

内存监测

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);
}
  • 内存状态
  1. TotalPss(整体内存,native+dalvik+共享)
  2. nativePss(native内存)
  3. 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

参考