騰訊效能監控框架Matrix原始碼分析(十七)SQLiteLint耗電分析2之架構設計

語言: CN / TW / HK

BatteryMonitorPlugin

好了,經過上一篇文章《Android App 電量統計原理與優化》的分析,相信你已經完全掌握了 Android App 電量的計算方式,現在可以開始給自己的 App 開發電量異常檢測功能了。

跟其他 APM 指標優化框架一樣,電量優化框架 BatteryCanary 也是作為一個相對獨立的 Plugin 功能整合在 Matrix 框架裡面。不過電量相關的問題比較複雜,一般來說,比較 “客觀” 的異常檢測(比如 Crash 或者 ANR),都能做到 “開箱即用”(out-of-box);而像卡頓反饋(沒有 ANR 彈窗但使用者感覺卡頓)這類比較 “主觀” 的異常檢測,就需要做一些額外的自定義配置了。

電量異常的判斷標準則要比卡頓問題 “主觀更多”,而且導致耗電的原因更是多種多樣,比如執行緒問題、WakeLock 問題、Wifi / 藍芽 / GPS 掃描頻繁問題等等。相應的 BatteryCanary 也開發了許多針對以上種種問題的功能模組,從而導致 BatteryCanary 的設計相比其他 APM 框架繁瑣了不少,使用前需要根據自己 App 需要指定啟用哪些模組以及相應的自定義配置。

初始化

首先在App的入口初始化配置,MatrixApplication onCreate // Configure battery canary. BatteryMonitorPlugin batteryMonitorPlugin = configureBatteryCanary(); builder.plugin(batteryMonitorPlugin); Matrix.init(builder.build()); 生成的程式碼較多,委託給了BatteryCanaryInitHelper private BatteryMonitorPlugin configureBatteryCanary() { // Configuration of battery plugin is really complicated. // See it in BatteryCanaryInitHelper. return BatteryCanaryInitHelper.createMonitor(); } 電量的統計支援非常多的配置和回撥監控 ``` public static BatteryMonitorPlugin createMonitor() { if (sBatteryConfig != null) { throw new IllegalStateException("Duplicated init!"); }

sBatteryConfig = new BatteryMonitorConfig.Builder()
        // Thread Activities Monitor
        .enable(JiffiesMonitorFeature.class)
        .enableStatPidProc(true)
        .greyJiffiesTime(3 * 1000L)
        .enableBackgroundMode(false)
        .backgroundLoopCheckTime(30 * 60 * 1000L)
        .enableForegroundMode(true)
        .foregroundLoopCheckTime(20 * 60 * 1000L)
        .setBgThreadWatchingLimit(5000)
        .setBgThreadWatchingLimit(8000)

        // App & Device Status Monitor For Better Invalid Battery Activities Configure
        .setOverHeatCount(1024)
        .enable(DeviceStatMonitorFeature.class)
        .enable(AppStatMonitorFeature.class)
        .setSceneSupplier(new Callable<String>() {
            @Override
            public String call() {
                return "Current AppScene";
            }
        })

        // AMS Activities Monitor:
        // alarm/wakelock watch
        .enableAmsHook(true)
        .enable(AlarmMonitorFeature.class)
        .enable(WakeLockMonitorFeature.class)
        .wakelockTimeout(2 * 60 * 1000L)
        .wakelockWarnCount(3)
        .addWakeLockWhiteList("Ignore WakeLock TAG1")
        .addWakeLockWhiteList("Ignore WakeLock TAG2")
        // scanning watch (wifi/gps/bluetooth)
        .enable(WifiMonitorFeature.class)
        .enable(LocationMonitorFeature.class)
        .enable(BlueToothMonitorFeature.class)
        // .enable(NotificationMonitorFeature.class)

        // Lab Feature:
        // network monitor
        // looper task monitor
        .enable(TrafficMonitorFeature.class)
        .enable(LooperTaskMonitorFeature.class)
        .addLooperWatchList("main")
        .useThreadClock(false)
        .enableAggressive(true)

        // Monitor Callback
        .setCallback(new BatteryMonitorCallback() {
            @Override
            public void onTraceBegin() {

            }

            @Override
            public void onTraceEnd(boolean isForeground) {

            }

            @Override
            public void onAlarmDuplicated(int duplicatedCount, AlarmMonitorFeature.AlarmRecord record) {

            }

            @Override
            public void onForegroundServiceLeak(boolean isMyself, int appImportance, int globalAppImportance, ComponentName componentName, long millis) {

            }

            @Override
            public void onAppSateLeak(boolean isMyself, int appImportance, ComponentName componentName, long millis) {

            }

            @Override
            public void onReportInternalJiffies(Delta<AbsTaskMonitorFeature.TaskJiffiesSnapshot> delta) {

            }

            @Override
            public void onParseError(int pid, int tid) {

            }

            @Override
            public void onWatchingThreads(MonitorFeature.Snapshot.Entry.ListEntry<? extends JiffiesSnapshot.ThreadJiffiesEntry> threadJiffiesList) {

            }

            @Override
            public void onTaskTrace(Thread thread, List<LooperTaskMonitorFeature.TaskTraceInfo> sortList) {

            }

            @Override
            public void onLooperTaskOverHeat(@NonNull List<Delta<AbsTaskMonitorFeature.TaskJiffiesSnapshot>> deltas) {

            }

            @Override
            public void onLooperConcurrentOverHeat(String key, int concurrentCount, long duringMillis) {

            }

            @Override
            public void onNotify(@NonNull NotificationMonitorFeature.BadNotification notify) {

            }

            @Override
            public void onWakeLockTimeout(int warningCount, WakeLockRecord record) {

            }

            @Override
            public void onWakeLockTimeout(WakeLockRecord record, long backgroundMillis) {

            }
        })
        .build();

return new BatteryMonitorPlugin(sBatteryConfig);

} 註冊好監控以後就可以開始監控了 Plugin plugin = Matrix.with().getPluginByClass(BatteryMonitorPlugin.class); if (!plugin.isPluginStarted()) { if (!BatteryEventDelegate.isInit()) { BatteryEventDelegate.init(this.getApplication()); }

MatrixLog.i(TAG, "plugin-battery start");
plugin.start();

} 進入BatteryMonitorPlugin `start` 方法 public class BatteryMonitorPlugin extends Plugin { private static final String TAG = "Matrix.battery.BatteryMonitorPlugin";

final BatteryMonitorCore mDelegate;
private static String sPackageName = null;
private static String sProcessName = null;

public BatteryMonitorPlugin(BatteryMonitorConfig config) {
    mDelegate = new BatteryMonitorCore(config);
    MatrixLog.i(TAG, "setUp battery monitor plugin with configs: " + config);
}

public BatteryMonitorCore core() {
    return mDelegate;
}

@Override
public void init(Application app, PluginListener listener) {
    super.init(app, listener);
    if (!mDelegate.getConfig().isBuiltinForegroundNotifyEnabled) {
        AppActiveMatrixDelegate.INSTANCE.removeListener(this);
    }
}

@Override
public String getTag() {
    return "BatteryMonitorPlugin";
}

@Override
public void start() {
    super.start();
    mDelegate.start();
}

@Override
public void stop() {
    super.stop();
    mDelegate.stop();
}

`` 真正的執行者其實是BatteryMonitorCore`

BatteryMonitorCore

com.tencent.matrix.batterycanary.monitor.BatteryMonitorCore

首先初始化相關引數,然後開始遍歷MonitorFeature 的configure方法 ``` public BatteryMonitorCore(BatteryMonitorConfig config) { mConfig = config; if (config.callback instanceof BatteryMonitorCallback.BatteryPrinter) ((BatteryMonitorCallback.BatteryPrinter) config.callback).attach(this); if (config.onSceneSupplier != null) { mSupplier = config.onSceneSupplier; }

mHandler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper(), this);
enableForegroundLoopCheck(config.isForegroundModeEnabled);
enableBackgroundLoopCheck(config.isBackgroundModeEnabled);
mMonitorDelayMillis = config.greyTime;
mFgLooperMillis = config.foregroundLoopCheckTime;
mBgLooperMillis = config.backgroundLoopCheckTime;

for (MonitorFeature plugin : config.features) {
    plugin.configure(this);
}

} 再看看start stop其實也是遍歷各個feature public void start() { synchronized (BatteryMonitorCore.class) { if (!mTurnOn) { for (MonitorFeature plugin : mConfig.features) { plugin.onTurnOn(); } mTurnOn = true; } if (BatteryEventDelegate.isInit()) { BatteryEventDelegate.getInstance().attach(this).startListening(); } } }

public void stop() { synchronized (BatteryMonitorCore.class) { if (mTurnOn) { mHandler.removeCallbacksAndMessages(null); for (MonitorFeature plugin : mConfig.features) { plugin.onTurnOff(); } mTurnOn = false; } } } ``` 因為電量涉及到不同的模組,所以通過feature分門別類,進一步解耦,動態實現可插拔,以及支援我們自定義的擴充套件,秒啊!這些features便是我們初始化配置新增進去的,我們看看有哪些feature.

圖片.png

見名知意 涉及到Location時間, Alarm次數,Net、WIFi、藍芽訪問量,wake_lock 持有時間,CPU 耗電情況等,looper,app解鎖屏,是否充電狀態,通知使用情況等等

當然BatteryMonitorCore 核心類的名字不是白叫的,除了啟動各個監控模組,自己還在記錄前後資訊以及通知其他模組當前app生命週期資訊 ``` private class ForegroundLoopCheckTask implements Runnable { int lastWhat = MSG_ID_JIFFIES_START; @Override public void run() { if (mForegroundModeEnabled) { Message message = Message.obtain(mHandler); message.what = lastWhat; message.arg1 = MSG_ARG_FOREGROUND; mHandler.sendMessageAtFrontOfQueue(message); lastWhat = (lastWhat == MSG_ID_JIFFIES_END ? MSG_ID_JIFFIES_START : MSG_ID_JIFFIES_END); mHandler.postDelayed(this, mFgLooperMillis); } } }

private class BackgroundLoopCheckTask implements Runnable { int round = 0; @Override public void run() { round++; MatrixLog.i(TAG, "#onBackgroundLoopCheck, round = " + round); if (!isForeground()) { synchronized (BatteryMonitorCore.class) { for (MonitorFeature plugin : mConfig.features) { plugin.onBackgroundCheck(mBgLooperMillis * round); } } } if (!isForeground()) { mHandler.postDelayed(this, mBgLooperMillis); } } } 前後臺切換時記錄資訊 public void onForeground(boolean isForeground) { if (!Matrix.isInstalled()) { MatrixLog.e(TAG, "Matrix was not installed yet, just ignore the event"); return; } mAppForeground = isForeground;

if (BatteryEventDelegate.isInit()) {
    BatteryEventDelegate.getInstance().onForeground(isForeground);
}

if (!isForeground) {
    // back:
    // 1. remove all checks
    mHandler.removeCallbacksAndMessages(null);

    // 2. start background jiffies check
    Message message = Message.obtain(mHandler);
    message.what = MSG_ID_JIFFIES_START;
    mHandler.sendMessageDelayed(message, mMonitorDelayMillis);

    // 3. start background loop check task
    if (mBackgroundModeEnabled) {
        if (mBgLooperTask != null) {
            mHandler.removeCallbacks(mBgLooperTask);
            mBgLooperTask = null;
        }
        mBgLooperTask = new BackgroundLoopCheckTask();
        mHandler.postDelayed(mBgLooperTask, mBgLooperMillis);
    }

} else if (!mHandler.hasMessages(MSG_ID_JIFFIES_START)) {
    // fore:
    // 1. remove background loop task
    if (mBgLooperTask != null) {
        mHandler.removeCallbacks(mBgLooperTask);
        mBgLooperTask = null;
    }

    // 2. finish background jiffies check
    Message message = Message.obtain(mHandler);
    message.what = MSG_ID_JIFFIES_END;
    mHandler.sendMessageAtFrontOfQueue(message);

    // 3. start foreground jiffies loop check
    if (mForegroundModeEnabled && mFgLooperTask != null) {
        mHandler.removeCallbacks(mFgLooperTask);
        mFgLooperTask.lastWhat = MSG_ID_JIFFIES_START;
        mHandler.post(mFgLooperTask);
    }
}

for (MonitorFeature plugin : mConfig.features) {
    plugin.onForeground(isForeground);
}

} 接收不同的模組的真正的回撥 比如鬧鐘,通知,app狀態等等

public class BatteryMonitorCore implements LooperTaskMonitorFeature.LooperTaskListener, WakeLockMonitorFeature.WakeLockListener, AlarmMonitorFeature.AlarmListener, JiffiesMonitorFeature.JiffiesListener, AppStatMonitorFeature.AppStatListener, NotificationMonitorFeature.NotificationListener, Handler.Callback {

private void notifyTraceBegin() { MatrixLog.d(TAG, "#onTraceBegin"); getConfig().callback.onTraceBegin(); }

private void notifyTraceEnd(boolean isForeground) { MatrixLog.d(TAG, "#onTraceEnd"); getConfig().callback.onTraceEnd(isForeground); }

@Override public void onTaskTrace(Thread thread, List sortList) { MatrixLog.d(TAG, "#onTaskTrace, thread = " + thread.getName()); getConfig().callback.onTaskTrace(thread, sortList); }

@Override public void onLooperTaskOverHeat(@NonNull List> deltas) { getConfig().callback.onLooperTaskOverHeat(deltas); }

@Override public void onLooperConcurrentOverHeat(String key, int concurrentCount, long duringMillis) { getConfig().callback.onLooperConcurrentOverHeat(key, concurrentCount, duringMillis); }

@Override public void onWakeLockTimeout(int warningCount, WakeLockRecord record) { getConfig().callback.onWakeLockTimeout(warningCount, record); }

@Override public void onWakeLockTimeout(WakeLockRecord record, long backgroundMillis) { getConfig().callback.onWakeLockTimeout(record, backgroundMillis); }

@Override public void onAlarmDuplicated(int duplicatedCount, AlarmMonitorFeature.AlarmRecord record) { getConfig().callback.onAlarmDuplicated(duplicatedCount, record); }

@Override public void onParseError(int pid, int tid) { getConfig().callback.onParseError(pid, tid); }

@Override public void onWatchingThreads(ListEntry<? extends ThreadJiffiesEntry> threadJiffiesList) { getConfig().callback.onWatchingThreads(threadJiffiesList); }

@Override public void onForegroundServiceLeak(boolean isMyself, int appImportance, int globalAppImportance, ComponentName componentName, long millis) { getConfig().callback.onForegroundServiceLeak(isMyself, appImportance, globalAppImportance, componentName, millis); }

@Override public void onAppSateLeak(boolean isMyself, int appImportance, ComponentName componentName, long millis) { getConfig().callback.onAppSateLeak(isMyself, appImportance, componentName, millis); }

@Override public void onNotify(BadNotification notification) { getConfig().callback.onNotify(notification); } ```

由次我們知道BatteryMonitorCore是電量監控的中樞大腦,負責各個模組的運作以及各種監控結果的回撥和處理。

Feature

接下來我們看看feature的設計,先進入上層介面 包含基礎的開關,前後配置生命週期以及資訊的記錄抽象類Snapshot ``` public interface MonitorFeature { void configure(BatteryMonitorCore monitor); void onTurnOn(); void onTurnOff(); void onForeground(boolean isForeground); void onBackgroundCheck(long duringMillis); int weight();

@SuppressWarnings("rawtypes")
abstract class Snapshot<RECORD extends Snapshot> {

接下來進入實現介面的抽象類,做了相關日誌的輸出 public abstract class AbsMonitorFeature implements MonitorFeature { private static final String TAG = "Matrix.battery.MonitorFeature";

protected String getTag() {
    return TAG;
}

@SuppressWarnings("NotNullFieldNotInitialized")
@NonNull
protected BatteryMonitorCore mCore;

@CallSuper
@Override
public void configure(BatteryMonitorCore monitor) {
    MatrixLog.i(getTag(), "#configure");
    this.mCore = monitor;
}

@CallSuper
@Override
public void onTurnOn() {
    MatrixLog.i(getTag(), "#onTurnOn");
}

@CallSuper
@Override
public void onTurnOff() {
    MatrixLog.i(getTag(), "#onTurnOff");
}

@CallSuper
@Override
public void onForeground(boolean isForeground) {
    MatrixLog.i(getTag(), "#onForeground, foreground = " + isForeground);
}

@CallSuper
@WorkerThread
@Override
public void onBackgroundCheck(long duringMillis) {
    MatrixLog.i(getTag(), "#onBackgroundCheck, since background started millis = " + duringMillis);
}

protected boolean shouldTracing() {
    if (mCore.getConfig().isAggressiveMode) return true;
    return  0 != (mCore.getContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE);
}

@Override
public String toString() {
    return getTag();
}

} ``` 再看看實現類,就是我們真正監控的三大模組了

圖片.png

對於大部分模組的監控需要hook systemservice,這一類的程式碼都非常相似,我們以WifiMonitorFeature分析為例

WifiMonitorFeature

onTurnOn是啟動的入口 這裡hook了 wifi onStartScan onGetScanResults方法,並記錄下相關資訊,回撥的收集比較簡單,核心的邏輯在於WifiManagerServiceHooker.addListener(mListener)是如何hook的?我們進入看看

``` public final class WifiMonitorFeature extends AbsMonitorFeature { private static final String TAG = "Matrix.battery.WifiMonitorFeature"; final WifiTracing mTracing = new WifiTracing(); WifiManagerServiceHooker.IListener mListener;

@Override
protected String getTag() {
    return TAG;
}

@Override
public void onTurnOn() {
    super.onTurnOn();
    if (mCore.getConfig().isAmsHookEnabled) {
        mListener = new WifiManagerServiceHooker.IListener() {
            @Override
            public void onStartScan() {
                String stack = shouldTracing() ? BatteryCanaryUtil.stackTraceToString(new Throwable().getStackTrace()) : "";
                MatrixLog.i(TAG, "#onStartScan, stack = " + stack);
                mTracing.setStack(stack);
                mTracing.onStartScan();
            }

            @Override
            public void onGetScanResults() {
                String stack = shouldTracing() ? BatteryCanaryUtil.stackTraceToString(new Throwable().getStackTrace()) : "";
                MatrixLog.i(TAG, "#onGetScanResults, stack = " + stack);
                mTracing.setStack(stack);
                mTracing.onGetScanResults();

            }
        };
        WifiManagerServiceHooker.addListener(mListener);
    }
}

@Override
public void onTurnOff() {
    super.onTurnOff();
    WifiManagerServiceHooker.removeListener(mListener);
    mTracing.onClear();
}

@Override
public int weight() {
    return Integer.MIN_VALUE;
}

@NonNull
public WifiTracing getTracing() {
    return mTracing;
}

public WifiSnapshot currentSnapshot() {
    return mTracing.getSnapshot();
}

public static final class WifiTracing {
    private int mScanCount;
    private int mQueryCount;
    private String mLastConfiguredStack = "";

    public void setStack(String stack) {
        if (!TextUtils.isEmpty(stack)) {
            mLastConfiguredStack = stack;
        }
    }

    public void onStartScan() {
        mScanCount++;
    }

    public void onGetScanResults() {
        mQueryCount++;
    }

    public void onClear() {
        mScanCount = 0;
        mQueryCount = 0;
    }

    public WifiSnapshot getSnapshot() {
        WifiSnapshot snapshot = new WifiSnapshot();
        snapshot.scanCount = Snapshot.Entry.DigitEntry.of(mScanCount);
        snapshot.queryCount = Snapshot.Entry.DigitEntry.of(mQueryCount);
        snapshot.stack = mLastConfiguredStack;
        return snapshot;
    }
}

public static class WifiSnapshot extends Snapshot<WifiSnapshot> {
    public Entry.DigitEntry<Integer> scanCount;
    public Entry.DigitEntry<Integer> queryCount;
    public String stack;

    @Override
    public Delta<WifiSnapshot> diff(WifiSnapshot bgn) {
        return new Delta<WifiSnapshot>(bgn, this) {
            @Override
            protected WifiSnapshot computeDelta() {
                WifiSnapshot snapshot = new WifiSnapshot();
                snapshot.scanCount = Differ.DigitDiffer.globalDiff(bgn.scanCount, end.scanCount);
                snapshot.queryCount = Differ.DigitDiffer.globalDiff(bgn.queryCount, end.queryCount);
                snapshot.stack = end.stack;
                return snapshot;
            }
        };
    }
}

} ```

WifiManagerServiceHooker

新增監聽,包含去重邏輯 ``` public synchronized static void addListener(IListener listener) { if (listener == null) { return; }

if (sListeners.contains(listener)) {
    return;
}

sListeners.add(listener);
//真正的hook點
checkHook();

} 檢查並hook private static void checkHook() { if (sTryHook) { return; }

if (sListeners.isEmpty()) {
    return;
}

boolean hookRet = sHookHelper.doHook();
MatrixLog.i(TAG, "checkHook hookRet:%b", hookRet);
sTryHook = true;

} 真正的執行者其實是sHookHelper,作為WifiManagerServiceHooker的變數已經提前生成了 private static SystemServiceBinderHooker.HookCallback sHookCallback = new SystemServiceBinderHooker.HookCallback() { @Override public void onServiceMethodInvoke(Method method, Object[] args) { if ("startScan".equals(method.getName())) { dispatchStartScan(); } else if ("getScanResults".equals(method.getName())) { dispatchGetScanResults(); } }

@Nullable
@Override
public Object onServiceMethodIntercept(Object receiver, Method method, Object[] args) {
    return null;
}

};

private static SystemServiceBinderHooker sHookHelper = new SystemServiceBinderHooker(Context.WIFI_SERVICE, "android.net.wifi.IWifiManager", sHookCallback); `` sHookCallback便是我們hook系統服務後的回撥,這裡做了一下中轉。因為hook系統服務基於的原理都是一樣的,無非就是動態代理,反射,安卓獲取的系統服務大都完全一樣,所以這裡專門封裝了SystemServiceBinderHooker`支援不同的服務呼叫,那麼如何實現的呢? 關注我,下篇分享

閱讀之前首先你需要掌握 反射 動態代理 hook 可以回顧我的文章有詳細的說明