最近在做性能优化的东西,研究了一下相关的内存监测,卡顿监测,以及ANR监测开源框架,对里面的核心原理做了总结,并手写一份简易版以便加深印象,为后续搭建线上日志监控做铺垫,在此做一个记录,线上监控框架可根据业务在此基础上做扩展。收集必要的日志信息,排查问题及时修复BUG,提升性能和稳定性,也是每个Android工程师必不可少的技能。
BlockCanary
- 通过采样工具类,在子线程中获取主线程的堆栈信息并保存到Map,暴露一个接口返回采样结果
- 通过日志监控类,实现Printer接口,判断是否卡顿(采样开始到采样结束的时间间隔是否超过阈值),如果卡顿调用接口获取返回的采样结果在子线程中日志打印
- App出现卡顿,会阻塞主线程的dispatchMessage,主线程Looper的loop方法中有一个Printer在每个Message处理前后被调用,所以设置主线程的MessageLogging为自定义的Printer
采样工具类
开启一个采样子线程,设置原子变量记录本次是否采样保证多线程同步,避免重复开始和结束,开始采样post一个采样任务到子线程,存入当前时间戳和对应的主线程堆栈信息到Map中,如果本次仍然需采样继续延迟间隔时间执行采样任务,暴露一个获取主线程堆栈信息的接口方法,返回当前堆栈信息列表1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| public class StackSampler { public static final String SEPARATOR = "\r\n"; public static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); private Handler mHandler; private Map<Long, String> mStackMap = new LinkedHashMap<>(); private int mMaxCount = 100; private long mSampleInterval; protected AtomicBoolean mShouldSample = new AtomicBoolean(false);
public StackSampler(long sampleInterval) { mSampleInterval = sampleInterval; HandlerThread handlerThread = new HandlerThread("block-canary-sampler"); handlerThread.start(); mHandler = new Handler(handlerThread.getLooper()); }
public void startDump() { if (mShouldSample.get()) { return; } mShouldSample.set(true); mHandler.removeCallbacks(mRunnable); mHandler.postDelayed(mRunnable, mSampleInterval); }
public void stopDump() { if (!mShouldSample.get()) { return; } mShouldSample.set(false);
mHandler.removeCallbacks(mRunnable); }
public List<String> getStacks(long startTime, long endTime) { ArrayList<String> result = new ArrayList<>(); synchronized (mStackMap) { for (Long entryTime : mStackMap.keySet()) { if (startTime < entryTime && entryTime < endTime) { result.add(TIME_FORMATTER.format(entryTime) + SEPARATOR + SEPARATOR + mStackMap.get(entryTime)); } } } return result; }
private Runnable mRunnable = new Runnable() { @Override public void run() { StringBuilder sb = new StringBuilder(); StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace(); for (StackTraceElement s : stackTrace) { sb.append(s.toString()).append("\n"); } synchronized (mStackMap) { if (mStackMap.size() == mMaxCount) { mStackMap.remove(mStackMap.keySet().iterator().next()); } mStackMap.put(System.currentTimeMillis(), sb.toString()); } if (mShouldSample.get()) { mHandler.postDelayed(mRunnable, mSampleInterval); } } };
}
|
卡顿监控工具类
初始化采样工具类,开启打印日志子线程,记录采样的开始时间和结束时间,如果时间大于设定的卡顿阈值则判定为卡顿状态,调用采样工具类获取主线程堆栈信息1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class LogMonitor implements Printer {
private StackSampler mStackSampler; private boolean mPrintingStarted = false; private long mStartTimestamp; private long mBlockThresholdMillis = 3000; private long mSampleInterval = 1000;
private Handler mLogHandler;
public LogMonitor() { mStackSampler = new StackSampler(mSampleInterval); HandlerThread handlerThread = new HandlerThread("block-canary-io"); handlerThread.start(); mLogHandler = new Handler(handlerThread.getLooper()); } @Override public void println(String x) { if (!mPrintingStarted) { mStartTimestamp = System.currentTimeMillis(); mPrintingStarted = true; mStackSampler.startDump(); } else { final long endTime = System.currentTimeMillis(); mPrintingStarted = false; if (isBlock(endTime)) { notifyBlockEvent(endTime); } mStackSampler.stopDump(); } }
private void notifyBlockEvent(final long endTime) { mLogHandler.post(new Runnable() { @Override public void run() { List<String> stacks = mStackSampler.getStacks(mStartTimestamp, endTime); for (String stack : stacks) { Log.e("block-canary", stack); } } }); }
private boolean isBlock(long endTime) { return endTime - mStartTimestamp > mBlockThresholdMillis; } }
|
卡顿监控入口
设置主线程的MessageLogging为自定义的Printer1 2 3 4 5 6
| public class BlockCanary { public static void install() { LogMonitor logMonitor = new LogMonitor(); Looper.getMainLooper().setMessageLogging(logMonitor); } }
|
LeakCanary
Java中WeakReference和ReferenceQueue联合使用是监控某个对象是否被gc回收的手段,LeakCanary正是利用这个原理实现的。
WeakReference和ReferenceQueue联合使用
创建一个对象,包装到弱引用对象中并关联引用队列,把对象置空,强制GC,取出引用队列的弱引用对象是否与关联时的弱引用对象相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static void main(String[] args) { ReferenceQueue referenceQueue = new ReferenceQueue(); Object obj = new Object();
WeakReference weakReference = new WeakReference(obj,referenceQueue); obj = null; Runtime.getRuntime().gc();
try{ Thread.sleep(1000); }catch (Exception e){}
Reference findRef = null; do{ findRef = referenceQueue.poll(); System.out.println("findRef = " +findRef + "是否等于上面的weakReference = " + (findRef == weakReference)); }while(findRef !=null); }
|
封装包含key和name的弱引用类
继承WeakReference弱引用类,方便根据key删除对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class KeyWeakReference<T> extends WeakReference<T> {
private String key; private String name;
public KeyWeakReference(T referent, String key, String name) { super(referent); this.key = key; this.name = name; }
public KeyWeakReference(T referent, ReferenceQueue<? super T> q, String key, String name) { super(referent, q); this.key = key; this.name = name; }
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { final StringBuffer sb = new StringBuffer("KeyWeakReference{"); sb.append("key='").append(key).append('\''); sb.append(", name='").append(name).append('\''); sb.append('}'); return sb.toString(); } }
|
监控工具类
创建一个观察对象Map,怀疑对象Map和引用队列,先清理一遍被GC的对象,遍历引用队列的所有弱引用对象,清理观察对象Map和怀疑对象Map中对应的对象,被监控的对象生成UUID作为key,把对象放入弱引用并与引用队列关联,放入到观察对象Map中,5秒后判断该对象是否还存在观察对象Map中,还存在则说明没有被GC,将该对象移动到怀疑对象Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| public class Watcher { private HashMap<String, KeyWeakReference> watchedReferences = new HashMap<>(); private HashMap<String, KeyWeakReference> retainedReferences = new HashMap<>();
private ReferenceQueue queue = new ReferenceQueue();
public Watcher() { }
private void removeWeaklyReachableReferences() { KeyWeakReference findRef = null; do { findRef = (KeyWeakReference) queue.poll(); if (findRef != null) { Reference removedRef = watchedReferences.remove(findRef.getKey()); if (removedRef == null) { retainedReferences.remove(findRef.getKey()); } } } while (findRef != null); }
private synchronized void moveToRetained(String key) { System.out.println("加入到怀疑列表..."); removeWeaklyReachableReferences(); KeyWeakReference retainedRef = watchedReferences.remove(key); if (retainedRef != null) { retainedReferences.put(key, retainedRef); } }
public void watch(Object watchedReference, String referenceName) { removeWeaklyReachableReferences(); final String key = UUID.randomUUID().toString(); KeyWeakReference reference = new KeyWeakReference(watchedReference, queue, key, ""); watchedReferences.put(key, reference);
Executor executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { Utils.sleep(5000); moveToRetained(key); });
}
public HashMap<String, KeyWeakReference> getRetainedReferences() { retainedReferences.forEach((key, keyWeakReference) -> { System.out.println("key: " + key + " , obj: " + keyWeakReference.get() + " , keyWeakReference: " + keyWeakReference); } ); return retainedReferences; } }
|
监控泄漏对象
创建对象,调用监控工具类的监控方法,查看怀疑对象Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) { Watcher watcher = new Watcher(); Object obj = new Object(); watcher.watch(obj,""); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } obj = null; Runtime.getRuntime().gc(); sleep(100); System.runFinalization(); System.out.println("查看是否在怀疑对象Map:" + watcher.getRetainedReferences().size()); }
|
ANRWatchDog
FileObserver
监控Android系统的anr日志目录/data/anr/,利用FileObserver监控目录下的文件操作,间接监控ANR问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class ANRFileObserver extends FileObserver {
public ANRFileObserver(String path) { super(path); }
public ANRFileObserver(String path, int mask) { super(path, mask); }
@Override public void onEvent(int event, @Nullable String path) { switch (event) { case FileObserver.ACCESS: Log.i("Zero", "ACCESS: " + path); break; case FileObserver.ATTRIB: Log.i("Zero", "ATTRIB: " + path); break; case FileObserver.CLOSE_NOWRITE: Log.i("Zero", "CLOSE_NOWRITE: " + path); break; case FileObserver.CLOSE_WRITE: Log.i("Zero", "CLOSE_WRITE: " + path); break; case FileObserver.CREATE: Log.i("Zero", "CREATE: " + path); break; case FileObserver.DELETE: Log.i("Zero", "DELETE: " + path); break; case FileObserver.DELETE_SELF: Log.i("Zero", "DELETE_SELF: " + path); break; case FileObserver.MODIFY: Log.i("Zero", "MODIFY: " + path); break; case FileObserver.MOVE_SELF: Log.i("Zero", "MOVE_SELF: " + path); break; case FileObserver.MOVED_FROM: Log.i("Zero", "MOVED_FROM: " + path); break; case FileObserver.MOVED_TO: Log.i("Zero", "MOVED_TO: " + path); break; case FileObserver.OPEN: Log.i("Zero", "OPEN: " + path); break; default: Log.i("Zero", "DEFAULT(" + event + "): " + path); break; } } }
|
线程
开启ANR监控后台线程,检查是否ANR,通过检查标志位和时间差判断规定时间内是否执行完成,如果没执行完成则可能发生ANR卡住了,那么打印主线程堆栈信息并调用回调接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| public class ANRWatchDog extends Thread { private static final String TAG = "ANR"; private int timeout = 5000; private boolean ignoreDebugger = true;
static ANRWatchDog sWatchdog;
private Handler mainHandler = new Handler(Looper.getMainLooper());
private class ANRChecker implements Runnable {
private boolean mCompleted; private long mStartTime; private long executeTime = SystemClock.uptimeMillis();
@Override public void run() { synchronized (ANRWatchDog.this) { mCompleted = true; executeTime = SystemClock.uptimeMillis(); } }
void schedule() { mCompleted = false; mStartTime = SystemClock.uptimeMillis(); mainHandler.postAtFrontOfQueue(this); }
boolean isBlocked() { return !mCompleted || executeTime - mStartTime >= timeout; } }
public interface ANRListener { void onAnrHappened(String stackTraceInfo); }
private ANRChecker anrChecker = new ANRChecker();
private ANRListener anrListener;
public void addANRListener(ANRListener listener){ this.anrListener = listener; }
public static ANRWatchDog getInstance(){ if(sWatchdog == null){ sWatchdog = new ANRWatchDog(); } return sWatchdog; }
private ANRWatchDog(){ super("ANR-WatchDog-Thread"); }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while(true){ while (!isInterrupted()) { synchronized (this) { anrChecker.schedule(); long waitTime = timeout; long start = SystemClock.uptimeMillis(); while (waitTime > 0) { try { wait(waitTime); } catch (InterruptedException e) { Log.w(TAG, e.toString()); } waitTime = timeout - (SystemClock.uptimeMillis() - start); } if (!anrChecker.isBlocked()) { continue; } } if (!ignoreDebugger && Debug.isDebuggerConnected()) { continue; } String stackTraceInfo = getStackTraceInfo(); if (anrListener != null) { anrListener.onAnrHappened(stackTraceInfo); } } anrListener = null; } } private String getStackTraceInfo() { StringBuilder stringBuilder = new StringBuilder(); for (StackTraceElement stackTraceElement : Looper.getMainLooper().getThread().getStackTrace()) { stringBuilder .append(stackTraceElement.toString()) .append("\r\n"); } return stringBuilder.toString(); } }
ANRWatchDog.getInstance().addANRListener(new ANRWatchDog.ANRListener() { @Override public void onAnrHappened(String stackTraceInfo) { Log.i(TAG, "发生了ANR: "+ stackTraceInfo); } }); ANRWatchDog.getInstance().start();
|