前言
本章我们主要是来手写一个 APM 框架;
上一章节补充
上一周我们讲解了包体积优化,讲到了 SO 的动态加载,另外我们在加载 SO 的时候,经常会遇到
UnSatisfiedLinkError
这个错误,主要的原因是兼容性问题,包 SO 的裁剪,由于国内厂商 Rom 的魔改,修改了 SO 的加载路径;
这里我们通常都是使用一个开源框架 ReLinker 来解决这个问题。
核心是:解析 so 的二进制文件,获取 so 的依赖属性(这个是 Android 系统行为导致,如果这个 so 有依赖其他的 so, 那么就要先加载依赖的 so 然后才能加载目标 so,这个能力在 7.0 及以后的版本解决掉了)
APM 重点关注指标
搭建 APM 框架,我们要重点关注一些指标
- 稳定性问题
- 流量/网络问题(Http的可达率、拦截器)
- 电量
- 流量消耗
- 内存(指标的统计、内存泄露)
- FPS
- 启动耗时监控
整体的 APM 框架,我们可以借助开源框架的思想来实现,这里大家可以深入学习了解下 ArgusAPM 和 Matrix
电量检测
整体的 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 整体就讲到这里吧
欢迎三连
来都来了,点个关注,点个赞吧,你的支持是我最大的动力~