如何应对 Android 面试官 -> 手写 APM 核心实现

653 阅读7分钟

前言


本章我们主要是来手写一个 APM 框架;

上一章节补充


上一周我们讲解了包体积优化,讲到了 SO 的动态加载,另外我们在加载 SO 的时候,经常会遇到

UnSatisfiedLinkError

这个错误,主要的原因是兼容性问题,包 SO 的裁剪,由于国内厂商 Rom 的魔改,修改了 SO 的加载路径;

这里我们通常都是使用一个开源框架 ReLinker 来解决这个问题。

核心是:解析 so 的二进制文件,获取 so 的依赖属性(这个是 Android 系统行为导致,如果这个 so 有依赖其他的 so, 那么就要先加载依赖的 so 然后才能加载目标 so,这个能力在 7.0 及以后的版本解决掉了)

APM 重点关注指标


搭建 APM 框架,我们要重点关注一些指标

  • 稳定性问题
  • 流量/网络问题(Http的可达率、拦截器)
  • 电量
  • 流量消耗
  • 内存(指标的统计、内存泄露)
  • FPS
  • 启动耗时监控

整体的 APM 框架,我们可以借助开源框架的思想来实现,这里大家可以深入学习了解下 ArgusAPMMatrix

电量检测

整体的 APM 框架,我们通常需要定义一些核心接口类,让各个检测模块分别来实现,我们简单定义一个接口 ITracker 用来规范相关采集接口

public interface ITracker {
    
    void destroy(Application application);

    void startTrack(Application application);

    void pauseTrack(Application application);
}

再来定义一个生命周期接口处理类,我们在所需要的采集模块复写对应的生命周期接口即可

public abstract class ActivityLifeCycleCallbacks implements Application.ActivityLifecycleCallbacks {
    @Override
    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
        
    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {

    }

    @Override
    public void onActivityResumed(@NonNull Activity activity) {

    }

    @Override
    public void onActivityPaused(@NonNull Activity activity) {

    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {

    }

    @Override
    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {

    }
}

电量检测这块,本质就是接收电量信息的 receiver 发过来的电量信息,这个我们可以在页面的 onActivityStarted 和 onActivityStoped 的时候出来相关获取信息;

我们先来定一个电量信息类

public class BatteryInfo {
    public boolean charging;
    public String activityName;
    public float cost;
    public long duration;
    public String display;
    public int total;
    public int voltage;
    public float screenBrightness;
}

接下来我们来定义采集类 BatteryStatsTracker 我们在 onSctivityStarted 的时候获取电量的百分比,在 onActivityStoped 的时候采集相关信息,并将采集的信息回调出去;

回调接口定义类:

public interface IBatteryListener {
    void onBatteryCost(BatteryInfo batteryInfo);
}

BatteryStatsTracker 实现

public class BatteryStatsTracker  extends ActivityLifeCycleCallbacks implements ITracker {
    private static BatteryStatsTracker sInstance;
    private Handler mHandler;
    private String display;
    private int mStartPercent;
    private HandlerThread handlerThread;

    private BatteryStatsTracker() {
        handlerThread = new HandlerThread("BatteryStats",Thread.NORM_PRIORITY);
        mHandler = new Handler(handlerThread.getLooper());
    }

    public static BatteryStatsTracker getInstance() {
        if (sInstance == null) {
            synchronized (BatteryStatsTracker.class) {
                if (sInstance == null) {
                    sInstance = new BatteryStatsTracker();
                }
            }
        }
        return sInstance;
    }

    @Override
    public void destroy(Application application) {

    }

    @Override
    public void startTrack(Application application) {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
    }

    @Override
    public void pauseTrack(Application application) {

    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
                Intent batteryStatus = activity.getApplication().registerReceiver(null, filter);
                // 获取电量百分比
                mStartPercent = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            }
        });
    }


    @Override
    public void onActivityStopped(@NonNull Activity activity) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mListeners.size() > 0) {
                    BatteryInfo batteryInfo = getBatteryInfo(activity.getApplication());
                    for (IBatteryListener listener : mListeners) {
                        listener.onBatteryCost(batteryInfo);
                    }
                }
            }
        });
    }

    private BatteryInfo getBatteryInfo(Application application) {
        if (TextUtils.isEmpty(display)) {
            display = "" + application.getResources().getDisplayMetrics().widthPixels + "*" + application.getResources().getDisplayMetrics().heightPixels;
        }
        BatteryInfo batteryInfo = new BatteryInfo();
        try {
            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
            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);
            batteryInfo.charging = isCharging;
            batteryInfo.cost = isCharging ? 0 : mStartPercent - batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            // 时间这里取了个巧,就是在 provider 中申请一个静态变量,这样进程启动的时候获取下当前时间
            batteryInfo.duration += (SystemClock.uptimeMillis() - LauncherHelpProvider.sStartUpTimeStamp) / 1000;
            batteryInfo.screenBrightness = getSystemScreenBrightnessValue(application);
            batteryInfo.display = display;
            batteryInfo.total = scale;
            Log.v("Battery", "total " + batteryInfo.total + " 用时间 " + batteryInfo.duration / 1000 + " 耗电  " + batteryInfo.cost);
        } catch (Exception e) {
            //
        }

        return batteryInfo;
    }

    public int getSystemScreenBrightnessValue(Application application) {
        ContentResolver contentResolver = application.getContentResolver();
        int defVal = 125;
        return Settings.System.getInt(contentResolver,
                Settings.System.SCREEN_BRIGHTNESS, defVal);
    }

    private List<IBatteryListener> mListeners = new ArrayList<>();

    public void addBatteryListener(IBatteryListener listener) {
        mListeners.add(listener);
    }

    public void removeBatteryListener(IBatteryListener listener) {
        mListeners.remove(listener);
    }
}

LaunchHelpProvider

public class LauncherHelpProvider extends ContentProvider {

    // 核心代码就在这里
    public static long sStartUpTimeStamp = SystemClock.uptimeMillis();

    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}

这样,我们就简单实现了 电量的采集处理;

内存信息

我们先来定义内存信息类 MemoryInfo

public class MemoryInfo {
    public String procName;
    public AppMemory appMemory;
    public SystemMemory systemMemoryInfo;
    public String display;
    public int activityCount;

    public static class AppMemory {
        public long dalvikPss;//java占用内存大小
        public long nativePss;//前进程总私有已用内存大小
        public long totalPss;//当前进程总内存大小
        public Debug.MemoryInfo mMemoryInfo;
    }

    public static class SystemMemory {
        public long availMem;
        public boolean lowMemory;
        public long threshold;
        public long totalMem;
    }
}

内存这块主要分为两块,一个是内存指标的获取,另一个就是内存泄露;

我们来定义一个内存泄露回调接口类 ITrackMemoryListener

public interface ITrackMemoryListener {

    void onLeakActivity(String activity, int count);

    void onCurrentMemoryCost(MemoryInfo trackMemoryInfo);
}

接下来我们来定义采集类

public class MemoryLeakTrack extends ActivityLifeCycleCallbacks implements ITracker {

    private static volatile MemoryLeakTrack sInstance = null;
    private HandlerThread handlerThread = new HandlerThread("BatteryStats",Thread.NORM_PRIORITY);

    private MemoryLeakTrack() {
    }

    public static MemoryLeakTrack getInstance() {
        if (sInstance == null) {
            synchronized (MemoryLeakTrack.class) {
                if (sInstance == null) {
                    sInstance = new MemoryLeakTrack();
                }
            }
        }
        return sInstance;
    }

    private Handler mHandler = new Handler(handlerThread.getLooper());
    private WeakHashMap<Activity, String> mActivityStringWeakHashMap = new WeakHashMap<>();

    @Override
    public void destroy(final Application application) {
        mHandler.removeCallbacksAndMessages(null);
    }

    @Override
    public void startTrack(final Application application) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (mMemoryListeners.size() > 0 && !ActivityStack.getInstance().isInBackGround()) {
                    MemoryInfo trackMemoryInfo = collectMemoryInfo(application);
                    for (ITrackMemoryListener listener : mMemoryListeners) {
                        listener.onCurrentMemoryCost(trackMemoryInfo);
                    }
                }
                mHandler.postDelayed(this, 30 * 1000);
            }
        }, 30 * 1000);
    }

    @Override
    public void pauseTrack(Application application) {

    }

    private Set<ITrackMemoryListener> mMemoryListeners = new HashSet<>();

    public void addOnMemoryLeakListener(ITrackMemoryListener leakListener) {
        mMemoryListeners.add(leakListener);
    }

    public void removeOnMemoryLeakListener(ITrackMemoryListener leakListener) {
        mMemoryListeners.remove(leakListener);
    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {
        //  退后台,GC 找LeakActivity
        if (!ActivityStack.getInstance().isInBackGround()) {
            return;
        }
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mallocBigMem();
                Runtime.getRuntime().gc();
            }
        }, 1000);

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    if (!ActivityStack.getInstance().isInBackGround()) {
                        return;
                    }
                    //  分配大点内存促进GC
                    mallocBigMem();
                    Runtime.getRuntime().gc();
                    SystemClock.sleep(100);
                    System.runFinalization();
                    HashMap<String, Integer> hashMap = new HashMap<>();
                    for (Map.Entry<Activity, String> activityStringEntry : mActivityStringWeakHashMap.entrySet()) {
                        String name = activityStringEntry.getKey().getClass().getSimpleName();
                        Integer value = hashMap.get(name);
                        if (value == null) {
                            hashMap.put(name, 1);
                        } else {
                            hashMap.put(name, value + 1);
                        }
                    }
                    if (mMemoryListeners.size() > 0) {
                        for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
                            for (ITrackMemoryListener listener : mMemoryListeners) {
                                listener.onLeakActivity(entry.getKey(), entry.getValue());
                            }
                        }
                    }
                } catch (Exception ignored) {
                }
            }
        }, 10000);
    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {
        mActivityStringWeakHashMap.put(activity, activity.getClass().getSimpleName());
    }

    private static String display;

    private MemoryInfo collectMemoryInfo(Application application) {
        if (TextUtils.isEmpty(display)) {
            display = "" + application.getResources().getDisplayMetrics().widthPixels + "*" + application.getResources().getDisplayMetrics().heightPixels;
        }
        ActivityManager activityManager = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
        // 系统内存
        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
        activityManager.getMemoryInfo(memoryInfo);
        MemoryInfo.SystemMemory systemMemory = new MemoryInfo.SystemMemory();
        systemMemory.availMem = memoryInfo.availMem >> 20;
        systemMemory.totalMem = memoryInfo.totalMem >> 20;
        systemMemory.lowMemory = memoryInfo.lowMemory;
        systemMemory.threshold = memoryInfo.threshold >> 20;

        //java内存
        Runtime rt = Runtime.getRuntime();

        //进程Native内存
        MemoryInfo.AppMemory appMemory = new MemoryInfo.AppMemory();
        Debug.MemoryInfo debugMemoryInfo = new Debug.MemoryInfo();
        Debug.getMemoryInfo(debugMemoryInfo);
        appMemory.nativePss = debugMemoryInfo.nativePss >> 10;
        appMemory.dalvikPss = debugMemoryInfo.dalvikPss >> 10;
        appMemory.totalPss = debugMemoryInfo.getTotalPss() >> 10;
        appMemory.mMemoryInfo = debugMemoryInfo;

        MemoryInfo trackMemoryInfo = new MemoryInfo();
        trackMemoryInfo.systemMemoryInfo = systemMemory;
        trackMemoryInfo.appMemory = appMemory;
        trackMemoryInfo.procName = ProcessUtils.Companion.getCurrentProcessName(application);
        trackMemoryInfo.display = display;
        trackMemoryInfo.activityCount = ActivityStack.getInstance().getSize();
        return trackMemoryInfo;
    }

    private void mallocBigMem() {
        byte[] leakHelpBytes = new byte[4 * 1024 * 1024];
        for (int i = 0; i < leakHelpBytes.length; i += 1024) {
            leakHelpBytes[i] = 1;
        }
    }
}

在 ActivityStop 的时候,分别大内存,触发 gc,然后将泄露的 Activity 收集起来并回调出去;

ANR 检测

ANR 的检测借鉴的开源思路;

class AnrError(
    private val stackTraceThrowable: StackTraceCollector.StackTraceThrowable,
    private val duration: Long
) : Error("Application Not Responding for at least $duration ms.", stackTraceThrowable) {

    override fun fillInStackTrace(): Throwable {
        stackTrace = emptyArray()
        return this
    }

    companion object {
        private fun threadTitle(thread: Thread): String {
            return "${thread.name} (state = ${thread.state})"
        }

        fun newMainInstance(duration: Long): AnrError {
            val mainThread = Looper.getMainLooper().thread
            val stackTraces = mainThread.stackTrace
            return AnrError(
                StackTraceCollector(
                    threadTitle(mainThread),
                    stackTraces
                ).StackTraceThrowable(null), duration
            )
        }

        fun newInstance(
            duration: Long,
            prefix: String,
            logThreadsWithoutStackTrace: Boolean
        ): AnrError {
            val mainThread = Looper.getMainLooper().thread
            val threadStackTraces =
                sortedMapOf<Thread, Array<StackTraceElement>>(ThreadComparator())
            for (entry in Thread.getAllStackTraces().entries) {
                if (entry.key == mainThread || entry.key.name.startsWith(prefix) && (logThreadsWithoutStackTrace || !entry.value.isNullOrEmpty())) {
                    threadStackTraces[entry.key] = entry.value
                }
            }

            if (!threadStackTraces.containsKey(mainThread)) {
                threadStackTraces[mainThread] = mainThread.stackTrace
            }
            var throwable: StackTraceCollector.StackTraceThrowable? = null
            for (entry in threadStackTraces.entries) {
                throwable = StackTraceCollector(threadTitle(entry.key), entry.value).StackTraceThrowable(throwable)
            }
            return AnrError(throwable!!, duration)
        }
    }
}

主要核心思想在收集 anr,我们来定义 AnrMonitor

class AnrMonitor(private val timeoutInterval: Long = DEFAULT_ANR_TIMEOUT) :
    LifecycleEventObserver {
    private val mainHandler: Handler = Handler(Looper.getMainLooper())
    private var handlerThread: HandlerThread? = null
    private var backgroundHandler: Handler? = null

    init {
        handlerThread = object : HandlerThread(TAG, Thread.NORM_PRIORITY) {
            override fun onLooperPrepared() {
                super.onLooperPrepared()
                backgroundHandler = Handler(handlerThread!!.looper)
            }
        }
    }

    private var mAnrInterceptor: AnrInterceptor? = DEFAULT_ANR_INTERCEPTOR
    private var mAnrListener: AnrListener? = DEFAULT_ANR_LISTENER

    private var mPrefix: String? = ""
    private var mLogThreadWithoutStackTrace = false
    private var mIgnoreDebugger = false

    @Volatile
    private var mTick = 0L

    @Volatile
    private var mReported = false

    @Volatile
    private var mInterval: Long = timeoutInterval

    private val mTicker = Runnable {
        mTick = 0
        mReported = false
    }

    private val mAnrCollector = Runnable {
        // If the main thread has not handled mTicker, it is blocked. ANR.
        if (mTick != 0L && !mReported) {
            //noinspection ConstantConditions
            if (!mIgnoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
                Log.i("tag","An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))")
                mReported = true
                delayToTryCollectingAnr()
                return@Runnable
            }
            mInterval = mAnrInterceptor?.intercept(mTick) ?: 0
            if (mInterval > 0) {
                delayToTryCollectingAnr()
                return@Runnable
            }
            val anrError = if (mPrefix == null) {
                AnrError.newMainInstance(mTick)
            } else {
                AnrError.newInstance(mTick, mPrefix!!, mLogThreadWithoutStackTrace)
            }
            mAnrListener?.onAppNotResponding(anrError)
            mInterval = timeoutInterval
            mReported = true
        }
        delayToTryCollectingAnr()
    }

    fun setAnrListener(anrListener: AnrListener?): AnrMonitor {
        mAnrListener = anrListener ?: DEFAULT_ANR_LISTENER
        return this
    }

    fun setAnrInterceptor(anrInterceptor: AnrInterceptor?): AnrMonitor {
        mAnrInterceptor = anrInterceptor ?: DEFAULT_ANR_INTERCEPTOR
        return this
    }

    fun setReportThreadNamePrefix(prefix: String): AnrMonitor {
        mPrefix = prefix
        return this
    }

    fun setReportMainThreadOnly(): AnrMonitor {
        mPrefix = null
        return this
    }

    fun setReportAllThreads(): AnrMonitor {
        mPrefix = ""
        return this
    }

    fun setLogThreadWithoutStackTrace(logThreadsWithoutStackTrace: Boolean): AnrMonitor {
        mLogThreadWithoutStackTrace = logThreadsWithoutStackTrace
        return this
    }

    fun setIgnoreDebugger(ignoreDebugger: Boolean): AnrMonitor {
        mIgnoreDebugger = ignoreDebugger
        return this
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (event == Lifecycle.Event.ON_CREATE) {//app is created
            onAppCreate()
        } else if (event == Lifecycle.Event.ON_STOP) {//no activities in stack
            onAppStop()
        } else if (event == Lifecycle.Event.ON_START) {//when first activity is started
            onAppStart()
        }
    }

    private fun onAppStart() {
        delayToTryCollectingAnr()
    }

    @Synchronized
    private fun delayToTryCollectingAnr() {
        val needPost = mTick == 0L
        mTick += mInterval
        if (needPost) {
            mainHandler.post(mTicker)
        }
        backgroundHandler?.postDelayed(mAnrCollector, ANR_COLLECTING_INTERVAL)
    }

    private fun onAppCreate() {
        handlerThread?.start()
    }

    private fun onAppStop() {
        backgroundHandler?.removeCallbacksAndMessages(null)
        mainHandler.removeCallbacksAndMessages(null)
    }

    fun onAppTerminate() {
        handlerThread?.quitSafely()
        handlerThread = null
    }

    companion object {
        const val DEFAULT_ANR_TIMEOUT = 5000L
        const val ANR_COLLECTING_INTERVAL = 2000L
        private const val TAG = "||ANR-Monitor||"

        private val DEFAULT_ANR_LISTENER = object : AnrListener {
            override fun onAppNotResponding(error: AnrError) {
                throw error
            }
        }

        private val DEFAULT_ANR_INTERCEPTOR = object : AnrInterceptor {
            override fun intercept(duration: Long): Long {
                return 0
            }
        }
    }
}

主要逻辑在 mAnrCollector 这个 Runnable 中;

卡顿

卡顿这块在将卡顿和布局优化的时候,也有提到过几种监控方式,这里可以采用 编舞者 的方式

private void getFps(){
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
        return;
    }
    Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long timeNanos) {
            if(mStartFrameTime == 0){
                mStartFrameTime = timeNanos;
            }

            float interval = (timeNanos - mStartFrameTime) / 1000000.0f;
            if(interval > MONITOR_INTERVAL){
                double fps = (mStartFrameCount*1000L)/interval;
                Log.e("tag","fps"+fps);
                mStartFrameCount = 0;
                mStartFrameTime = 0;
            }else{
                ++mStartFrameCount;
            }
            Choreographer.getInstance().postFrameCallback(this);
        }
    });
}

好了,APM 整体就讲到这里吧

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~