在编译期间,AppMethodBeat的i、o、at会插桩到相应的位置(i在方法入口,o在方法出口,at在Activity的onWindowFocusChanged)
当执行第一个函数(Application的attachBaseContext)记录时间,会执行AppMethodBeat.i
i方法
java
public static void i(int methodId) {
if (status <= STATUS_STOPPED) {
return;
}
if (methodId >= METHOD_ID_MAX) {
return;
}
if (status == STATUS_DEFAULT) {
synchronized (statusLock) {
if (status == STATUS_DEFAULT) {
//【realExecute】只有第一个方法进来才会执行(Application的attachBaseContext)
realExecute();
status = STATUS_READY;
}
}
}
//主线程才会执行此逻辑
if (threadId == sMainThreadId) {
if (assertIn) {
android.util.Log.e(TAG, "ERROR!!! AppMethodBeat.i Recursive calls!!!");
return;
}
assertIn = true;
if (sIndex < Constants.BUFFER_SIZE) {
//【mergeData】存方法信息,传true表示方法开始
mergeData(methodId, sIndex, true);
} else {
//超过数组长度,重新从0开始存
sIndex = 0;
mergeData(methodId, sIndex, true);
}
//自增
++sIndex;
assertIn = false;
}
}
status用来控制状态,第一个i方法进来会执行realExecute(),只会执行一次。
realExecute
java
private static void realExecute() {
//1.sUpdateDiffTimeRunnable更新时间间隔用于计算方法执行耗时
sHandler.postDelayed(sUpdateDiffTimeRunnable, Constants.TIME_UPDATE_CYCLE_MS);
//2.checkStartExpiredRunnable超时检测(10s)将AppMethodBeat置为不可用状态
sHandler.postDelayed(checkStartExpiredRunnable = new Runnable() {
@Override
public void run() {
synchronized (statusLock) {
MatrixLog.i(TAG, "[startExpired] timestamp:%s status:%s", System.currentTimeMillis(), status);
if (status == STATUS_DEFAULT || status == STATUS_READY) {
status = STATUS_EXPIRED_START;
}
}
}
}, Constants.DEFAULT_RELEASE_BUFFER_DELAY);
ActivityThreadHacker.hackSysHandlerCallback();
//监听消息开始和消息结束
LooperMonitor.register(looperMonitorListener);
}
1)sUpdateDiffTimeRunnable
如果在方法首尾直接用SystemClock.uptimeMillis()
计算方法耗时,大量方法执行这个操作时,对性能的影响就会明显放大,所以这里采用了循环线程,每隔5ms(不包含消息队列休眠时间,同时5ms的误差对于耗时分析可忽略不计)更新一次sCurrentDiffTime
java
private static volatile boolean isPauseUpdateTime = false;
private static Runnable sUpdateDiffTimeRunnable = new Runnable() {
@Override
public void run() {
try {
while (true) {
//满足条件则5ms更新一次时间,isPauseUpdateTime是一个重要控制条件
while (!isPauseUpdateTime && status > STATUS_STOPPED) {
sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
SystemClock.sleep(Constants.TIME_UPDATE_CYCLE_MS);
}
//不满足条件进入挂起状态,等待唤醒
synchronized (updateTimeLock) {
updateTimeLock.wait();
}
}
} catch (Exception e) {
MatrixLog.e(TAG, "" + e.toString());
}
}
};
//消息执行开始
private static void dispatchBegin() {
sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
//isPauseUpdateTime置为false,满足循环条件
isPauseUpdateTime = false;
//唤醒循环线程继续更新时间
synchronized (updateTimeLock) {
updateTimeLock.notify();
}
}
//消息执行结束
private static void dispatchEnd() {
//isPauseUpdateTime置为true,停止循环,进入挂起状态
isPauseUpdateTime = true;
}
sUpdateDiffTimeRunnable是一个循环线程,isPauseUpdateTime是一个重要循环控制条件,当主线程的当前消息执行结束,下一个消息还未执行期间sUpdateDiffTimeRunnable会进入挂起状态(减少cpu占用), 否则每隔5ms更新一次时间。
2)checkStartExpiredRunnable
checkStartExpiredRunnable超时检测线程,i方法第一次执行10s后,status
状态仍为STATUS_DEFAULT
或STATUS_READY
,即认为超时,将状态置为STATUS_EXPIRED_START
(即AppMethodBeat不可用,如i方法和o方法不工作,sUpdateDiffTimeRunnable不更新时间等)
那么这个status又是在哪里更改的呢
java
public class TracePlugin extends Plugin {
@Override
public void start() {
//...省略其它代码
if (traceConfig.isAppMethodBeatEnable()) {
AppMethodBeat.getInstance().onStart();
} else {
AppMethodBeat.getInstance().forceStop();
}
}
@Override
public void stop() {
//...省略其它代码
AppMethodBeat.getInstance().onStop();
}
}
java
public class AppMethodBeat implements BeatLifecycle {
@Override
public void onStart() {
synchronized (statusLock) {
if (status < STATUS_STARTED && status >= STATUS_EXPIRED_START) {
sHandler.removeCallbacks(checkStartExpiredRunnable);
MatrixHandlerThread.getDefaultHandler().removeCallbacks(realReleaseRunnable);
if (sBuffer == null) {
throw new RuntimeException(TAG + " sBuffer == null");
}
MatrixLog.i(TAG, "[onStart] preStatus:%s", status, Utils.getStack());
//状态置为STATUS_STARTED
status = STATUS_STARTED;
} else {
MatrixLog.w(TAG, "[onStart] current status:%s", status);
}
}
}
}
AppMethodBeat是跟随TracePlugin的生命周期的,TracePlugin启动了AppMethodBeat只才能工作,AppBeatMethod的i、o等方法被外部调用,场景多,这样做可以使TracePlugin真正控制AppBeatMethod。
mergeData
结合下图(来自官方)来看,mergeData将methodId、i/o、方法耗时合成一个long数据,long二进制有64位,0 ~ 42存时间戳偏移量,43 ~ 62存methodId,最高位存i/o,这样一个long存储3条信息,减少内存占用。
java
private static void mergeData(int methodId, int index, boolean isIn) {
//消息分发开始和结束传入METHOD_ID_DISPATCH,更新时间偏移量
if (methodId == AppMethodBeat.METHOD_ID_DISPATCH) {
sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
}
try {
//long最高位存i/o,0表示退出方法,1表示进入方法
long trueId = 0L;
if (isIn) {
//如果是方法开始,最高位存1
trueId |= 1L << 63;
}
//43 ~ 62存methodId
trueId |= (long) methodId << 43;
//将sCurrentDiffTime转为二进制存入0 ~ 42
//7对应二进制111,F对应1111,0x7FFFFFFFFFFL表示111...1111,共43位,取& 得到sCurrentDiffTime二进制原值
trueId |= sCurrentDiffTime & 0x7FFFFFFFFFFL;
//此时trueId包含i/o、methodId、sCurrentDiffTime,存入sBuffer
sBuffer[index] = trueId;
//处理堆积状态,sBuffer装满后从0开始覆盖老数据,标记老数据为无效数据
checkPileup(index);
sLastIndex = index;
} catch (Throwable t) {
MatrixLog.e(TAG, t.getMessage());
}
}
o方法
java
public static void o(int methodId) {
if (status <= STATUS_STOPPED) {
return;
}
if (methodId >= METHOD_ID_MAX) {
return;
}
if (Thread.currentThread().getId() == sMainThreadId) {
if (sIndex < Constants.BUFFER_SIZE) {
//mergeData存方法信息,传false表示方法结束
mergeData(methodId, sIndex, false);
} else {
sIndex = 0;
mergeData(methodId, sIndex, false);
}
++sIndex;
}
}
和i方法一样,调用mergeData存方法信息,传入false表示方法结束。
at方法
java
public static void at(Activity activity, boolean isFocus) {
String activityName = activity.getClass().getName();
if (isFocus) {
//sFocusActivitySet去重
if (sFocusActivitySet.add(activityName)) {
synchronized (listeners) {
for (IAppMethodBeatListener listener : listeners) {
//回调给StartupTracer计算Activity启动耗时
listener.onActivityFocused(activity);
}
}
MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "attach");
}
} else {
if (sFocusActivitySet.remove(activityName)) {
MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "detach");
}
}
}
at方法在Activity执行onWindowFocusChanged时回调给StartupTracer计算Activity启动耗时。
maskIndex
maskIndex用来标记获取堆栈信息的起点,便于回溯。
java
public IndexRecord maskIndex(String source) {
//AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex")
if (sIndexRecordHead == null) {
//首次调用时创建一个链表,sIndex是当前方法在sBuffer中的位置
sIndexRecordHead = new IndexRecord(sIndex - 1);
//IndexRecord封装了source标记,和当前方法做映射
sIndexRecordHead.source = source;
return sIndexRecordHead;
} else {
IndexRecord indexRecord = new IndexRecord(sIndex - 1);
indexRecord.source = source;
IndexRecord record = sIndexRecordHead;
IndexRecord last = null;
//链表存在,将新的indexRecord节点按照index从小到大排序插入到链表中
while (record != null) {
if (indexRecord.index <= record.index) {
//last为null表示第一次循环,也就是标记点index <= 链表中最小,也就是第一个index
if (null == last) {
//新节点插到链表头部
IndexRecord tmp = sIndexRecordHead;
sIndexRecordHead = indexRecord;
indexRecord.next = tmp;
} else {
//新节点插到 (链表中第一个 >= 标记index) 的前面
IndexRecord tmp = last.next;
last.next = indexRecord;
indexRecord.next = tmp;
}
return indexRecord;
}
//双指针操作,保存上一个节点,为了指向新的节点
last = record;
//原链表遍历
record = record.next;
}
//到这里说明没走if (indexRecord.index <= record.index),因为这里会return,也就是标记点index > 链表中最大index,直接插到末尾
last.next = indexRecord;
//返回新插入的节点
return indexRecord;
}
}
index是当前方法在sBuffer中的位置,这里维护了一个按照index从小到大顺序的链表,链表节点IndexRecord封装了source标记和index, sIndexRecordHead是链表的头节点。
copyData
maskIndex标记起点就是为了copyData取出两个index之间的方法信息。
java
public long[] copyData(IndexRecord startRecord) {
return copyData(startRecord, new IndexRecord(sIndex - 1));
}
//startRecord就是之前maskIndex标记的IndexRecord,endRecord当前IndexRecord
private long[] copyData(IndexRecord startRecord, IndexRecord endRecord) {
long current = System.currentTimeMillis();
long[] data = new long[0];
try {
if (startRecord.isValid && endRecord.isValid) {
int length;
int start = Math.max(0, startRecord.index);
int end = Math.max(0, endRecord.index);
if (end > start) {
//end > start 拷贝 start到end 之间的数据
length = end - start + 1;
data = new long[length];
System.arraycopy(sBuffer, start, data, 0, length);
} else if (end < start) {
// 方法数超过100w,index会从0开始,拷贝 start到sBuffer末尾 + 0到end
length = 1 + end + (sBuffer.length - start);
data = new long[length];
System.arraycopy(sBuffer, start, data, 0, sBuffer.length - start);
System.arraycopy(sBuffer, 0, data, sBuffer.length - start, end + 1);
}
return data;
}
return data;
} catch (Throwable t) {
MatrixLog.e(TAG, t.toString());
return data;
} finally {
MatrixLog.i(TAG, "[copyData] [%s:%s] length:%s cost:%sms", Math.max(0, startRecord.index), endRecord.index, data.length, System.currentTimeMillis() - current);
}
}
copyData返回了一个long数组,至于怎么处理的后面再分析。
小结
AppMethodBeat的主要功能:
- 方法中插入i和o记录方法信息
- 维护了时间偏移量sCurrentDiffTime,用一个循环线程,每隔5ms(不包含消息队列休眠时间,同时5ms的误差对于耗时分析可忽略不计)更新一次sCurrentDiffTime
- 方法运行时将methodId、i/o、方法耗时合成一个long数据,节省内存
- maskIndex用来标记获取堆栈信息的起点(便于回溯),维护了一个顺序链表,用于映射标记和方法index(index是当前方法在sBuffer中的位置)
- copyData取出从maskIndex标记开始当前的方法信息