APM框架Matrix源码分析(九)AppMethodBeat源码分析

在编译期间,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_DEFAULTSTATUS_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的主要功能:

  1. 方法中插入i和o记录方法信息
  2. 维护了时间偏移量sCurrentDiffTime,用一个循环线程,每隔5ms(不包含消息队列休眠时间,同时5ms的误差对于耗时分析可忽略不计)更新一次sCurrentDiffTime
  3. 方法运行时将methodId、i/o、方法耗时合成一个long数据,节省内存
  4. maskIndex用来标记获取堆栈信息的起点(便于回溯),维护了一个顺序链表,用于映射标记和方法index(index是当前方法在sBuffer中的位置)
  5. copyData取出从maskIndex标记开始当前的方法信息
相关推荐
洒家肉山大魔王4 小时前
TPS和QPS的区别
性能优化·qps·tps
深海呐5 小时前
Android AlertDialog圆角背景不生效的问题
android
ljl_jiaLiang5 小时前
android10 系统定制:增加应用使用数据埋点,应用使用时长统计
android·系统定制
花花鱼5 小时前
android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
android
落落落sss6 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
消失的旧时光-19438 小时前
kotlin的密封类
android·开发语言·kotlin
服装学院的IT男10 小时前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽11 小时前
Android 源码集成可卸载 APP
android
码农明明11 小时前
Android源码分析:从源头分析View事件的传递
android·操作系统·源码阅读
秋月霜风12 小时前
mariadb主从配置步骤
android·adb·mariadb