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_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的主要功能:

  1. 方法中插入i和o记录方法信息
  2. 维护了时间偏移量sCurrentDiffTime,用一个循环线程,每隔5ms(不包含消息队列休眠时间,同时5ms的误差对于耗时分析可忽略不计)更新一次sCurrentDiffTime
  3. 方法运行时将methodId、i/o、方法耗时合成一个long数据,节省内存
  4. maskIndex用来标记获取堆栈信息的起点(便于回溯),维护了一个顺序链表,用于映射标记和方法index(index是当前方法在sBuffer中的位置)
  5. copyData取出从maskIndex标记开始当前的方法信息
相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
liangshanbo12153 天前
React 性能优化实战
性能优化·react
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas