美文网首页
Android 性能监控 - 页面监控

Android 性能监控 - 页面监控

作者: ModestStorm | 来源:发表于2022-09-15 12:31 被阅读0次

文章转载自:https://zhuanlan.zhihu.com/p/467573337

监控原理

v2-91b3af5685ff106f06e989ddbbf36ca4_1440w.jpg

如图所示,按照页面Activity的生命周期将页面分为三个阶段,开始阶段,运行阶段,结束阶段,具体如下:

开始阶段:在Activity的onStart()回调中开始采集cpu/内存/线程数量信息。

运行阶段:在运行期间注册帧率监听回调,开启线程定时采集cpu/内存/线程数量信息保存在缓存中,等待结束时将数据上传服务器。

结束阶段:在onStop执行之前停止信息采集,收集信息上传。不在onPause中做耗时任务是因为会影响下一个Activity的启动,onStop是在主线程空闲的时候执行的。

采集信息

Activity生命周期观察者模式
在Application中注册Activity生命周期回调监测生命周期函数:
application.registerActivityLifeCallBacks();

服务端根据目标用户的城市/高中低机型/业务场景维度下发采集开关及采集时长策略。
内存/FPS/CPU/线程数量/电量/流量指标维度进行信息采集上报。

1.在onActivityStart回调中使用HandlerThread根据服务端下发的定时时长采集内存/CPU/线程数量/流量信息,仅采集app处于前台的信息,app切换回后台后不再采集。
2.在onActivityPause中暂停信息采集,在生命周期onActivityStart回调中恢复执行。

3.帧率的信息采集是在onActivityResume生命周期函数中定时执行信息采集,在onActivityStop中暂停。

采集信息Sampler.java中的实现逻辑:

/**
 * 采用HandlerThread子线程,Handler定时发送消息采取cpu 内存 线程数数据
 * 根据服务端下发定时时间,默认是3s
 * 使用单利设计模式,责任链设计模式
 */
public class Sampler {
    public static final String TAG = "ActivityPerformanceSampler";

    private static Sampler mInstance;
    /** 采集线程*/
    private HandlerThread mHandlerThread;
    private Handler mHandler;
    /**是否开始采集*/
    private boolean isStart;
    private BaseCollector mCollector;

    private Sampler() {
        /**初始化HandlerThread工作子线程*/
        mHandlerThread = new HandlerThread(TAG);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());

        /**数据采集责任链设计模式初始化*/
        CpuCollector cpuCollector = new CpuCollector();/**cpu*/
        MemoryCollector memoryCollector = new MemoryCollector();/**内存*/
        ThreadNumCollector threadNumCollector = new ThreadNumCollector();/**线程数*/
        /**封装责任链*/
        cpuCollector.setSuccessor(memoryCollector);
        memoryCollector.setSuccessor(threadNumCollector);
        mCollector = cpuCollector;
    }

    public static synchronized Sampler getInstance() {
        if (null == mInstance) {
            mInstance = new Sampler();
        }
        return mInstance;
    }

    /** 定时采样线程*/
    private final Runnable mWorkRunnable = new Runnable() {
        public void run() {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.d(TAG, "Sample task run....");
            }

            doSample();

            if (服务端下发字段是否采集) {
                if (isStart && mHandler != null) {
                    mHandler.postDelayed(this, 3000);
                }
            } else {
                stop();
            }
        }
    };

    public boolean isStarted() {
        return this.isStart;
    }

    /** 启动采样线程*/
    public void start() {
        if (!isStart) {
            isStart = true;
            mHandler.post(mWorkRunnable);
        } else {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY){
                Log.w(TAG, "Sample thread has already start.");
            }
        }
    }

    /**
     * 停止采样线程
     */
    public void stop() {
        if (isStart) {
            isStart = false;
            mHandler.removeCallbacksAndMessages(null);
        } else {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.w(TAG, "Sample thread is not start.");
            }
        }
    }

    /**立即执行doSample操作*/
    public void postDoSample() {
        if (isStart) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY){
                        Log.w(TAG, "doSample directly.");
                    }
                    doSample();
                }
            });
        }
    }

    /**
     * 进行性能数据的采样并且上报
     */
    private void doSample() {
        if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
            Log.d(TAG, "perform doSample");
        }

        try {
            HashMap<String, String> data = new HashMap<>();
            /** 这个方法是责任链开始工作 传入一个map集合 */
            mCollector.process(data);
            /**
             * 上报数据
             */
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }
}

CPU信息

Proc文件系统可利用reader以文件系统读取方式从内存中proc文件读取内核cpu信息,可获取用户态的cpu时间、负进程的cpu时间、核心时间、io等待和非io等待时间、硬中断时间、软中断时间。比如:/proc/stat,可获取cpu总使用时间情况,获取文件内容如下

cpu 432661 13295 86656 422145968 171474 233 5346
cpu0 123075 2462 23494 105543694 16586 0 4615
cpu1 111917 4124 23858 105503820 69697 123 371
cpu2 103164 3554 21530 105521167 64032 106 334
cpu3 94504 3153 17772 105577285 21158 4 24
intr 1065711094 1057275779 92 0 6 6 0 4 0 3527 0 0 0 70 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7376958 0 0 0 0 0 0 0 1054602 0 0 0 0 0 0 0 30 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 19067887
btime 1139187531
processes 270014
procs_running 1
procs_blocked 0

参数 解释

user (432661) 从系统启动开始累计到当前时刻,用户态的CPU时间(单位:jiffies) ,不包含 nice值为负进程。1jiffies=0.01秒
nice (13295) 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间(单位:jiffies)
system (86656) 从系统启动开始累计到当前时刻,核心时间(单位:jiffies)
idle (422145968) 从系统启动开始累计到当前时刻,除硬盘IO等待时间以外其它等待时间(单位:jiffies)
iowait (171474) 从系统启动开始累计到当前时刻,硬盘IO等待时间(单位:jiffies) ,
irq (233) 从系统启动开始累计到当前时刻,硬中断时间(单位:jiffies)
softirq (5346) 从系统启动开始累计到当前时刻,软中断时间(单位:jiffies)

其中,cpu0-cpu3表示当前cpu为四核,计算时我们主要考虑第一个,也就是这行信息即可:

cpu 432661 13295 86656 422145968 171474 233 5346

实现方式

计算公式:

1)totalTime = user+system+nice+idle+iowait+irq+softtirq

2)cpuWorkTime = user+system+nice

3)cpu占用率 = (work - workBefore) / (totalCpu - totalCpuBefore)

解释:totalTime为cpu总使用时间,workTime为cpu工作时间,两个时间都为连续的片段,所以,我们在计算总占用率时,需要获取两次数据,两次数据的获取时间间隔可以自己设置,1s or 60s都可以,我们用第二次获取的工作时间-历史获取工作时间,就是这段时间内cpu的工作耗时,再除总耗时,即可获取cpu占用率。

//将读取的cpu信息存于字符串数组
String[] cpuInfos = null;
//计算总cpu占用率
try {
    //读文件的形式从proc文件系统中读取stat的信息
    BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/stat")),           1000);
    String load = reader.readLine();
    reader.close();
    //存储信息
    cpuInfos = load.split(" ");
} catch (IOException ex) {
    ex.printStackTrace();
}
//totalCpu = user+system+nice+idle+iowait+irq+softtirq
work = Long.parseLong(cpuInfos[2])
        + Long.parseLong(cpuInfos[3]) + Long.parseLong(cpuInfos[4]);
totalCpu = work
        + Long.parseLong(cpuInfos[6]) + Long.parseLong(cpuInfos[5])
        + Long.parseLong(cpuInfos[7]) + Long.parseLong(cpuInfos[8]);

......(省略获取历史工作时间的方式,可使用计时器再本次数据保存为历史数据)
//cpu占用率 = (work - workBefore) / (totalCpu - totalCpuBefore)
float CpuRate = 100 * ((float) (work - workBefore) / (float) (totalCpu - totalCpuBefore));

CPU监控

/**
 * 责任链设计模式
 * 精简到关注两项指标
 * (1)cpu总体使用率。
 * (2)应用程序cpu占用率。
 */
public class CpuCollector extends BaseCollector {

    /**静态变量是防止高频率重复创建对象,减小内存变化*/
    /**记录第一次cpu信息*/
    private static CpuInfo cpuInfo1 = new CpuInfo();
    /**记录第二次cpu信息*/
    private static CpuInfo cpuInfo2 = new CpuInfo();
    private static final int BUFFER_SIZE = 1024;//buffer 大小
    /**隔开50ms后对/proc/stat或者/proc/+mPid+/stat中cpu数据采样*/
    private static final int SLEEP_TIME = 50;
    /**区分是第几次获取cpu信息*/
    private boolean isFirst = true;

    @Override
    public void doCollect(HashMap<String, String> vector) {
        CpuEntity entity;
        /**Api 26以上 执行(top -n 1)命令获取cpu信息*/
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            entity = getCpuDataForO();
        } else {
            entity = getCpuEntity();
        }
        if (entity == null || (TextUtils.isEmpty(entity.app) || TextUtils.isEmpty(entity.total)))
            return;

        vector.put("cpuTotal", entity.total);
        vector.put("cpuApp", entity.app);
    }

    /**
     * Api 26以上 执行(top -n 1)命令获取cpu信息
     */
    private CpuEntity getCpuDataForO() {
        CpuEntity cpuEntity = new CpuEntity();
        java.lang.Process process = null;
        try {
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            process = Runtime.getRuntime().exec("top -n 1");
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            int cpuIndex = -1;
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (TextUtils.isEmpty(line)) {
                    continue;
                }
                int tempIndex = getCPUIndex(line);
                if (tempIndex != -1) {
                    cpuIndex = tempIndex;
                    continue;
                }
                if (line.startsWith(String.valueOf(Process.myPid()))) {
                    if (cpuIndex == -1) {
                        continue;
                    }
                    String[] param = line.split("\\s+");
                    /**说明获取的cpu信息不全*/
                    if (param.length <= cpuIndex) {
                        continue;
                    }
                    /**根据cpuIndex获取cpu信息*/
                    String cpu = param[cpuIndex];
                    if (cpu.endsWith("%")) {
                        cpu = cpu.substring(0, cpu.lastIndexOf("%"));
                    }
                    float rate = Float.parseFloat(cpu) / Runtime.getRuntime().availableProcessors();
                    NumberFormat format = NumberFormat.getNumberInstance();
                    format.setMaximumFractionDigits(0);
                    format.setRoundingMode(RoundingMode.UP);

                    cpuEntity.total = format.format(0);
                    cpuEntity.app = format.format(rate);
                    return cpuEntity;
                }
            }
        } catch (Throwable e) {
        } finally {
            if (process != null) {
                process.destroy();
            }
        }
        return cpuEntity;
    }

    /**
     * 获取CPU下标索引 用于获取cpu信息
     * @param line
     * @return
     */
    private int getCPUIndex(String line) {
        if (line.contains("CPU")) {
            String[] titles = line.split("\\s+");
            for (int i = 0; i < titles.length; i++) {
                if (titles[i].contains("CPU")) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Api 26以下获取cpu使用率
     * 通过proc文件系统的cpuinfo文件获取
     * 不需要root,/proc文件系统是一个伪文件系统,存在于内存内,以文件系统的方式为内核与进程提供通信接口。
     */
    private synchronized CpuEntity getCpuEntity() {
        CpuEntity entity = new CpuEntity();
        try {

            if (cpuInfo1 != null) cpuInfo1.reset();

            if (cpuInfo2 != null) cpuInfo2.reset();

            cpuInfo1 = parse(getCpuInfo(), getMyPidCpuInfo());
            if (cpuInfo1 == null) {
                return entity;
            }
            try {
                /**两次采集隔开50ms*/
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cpuInfo2 = parse(getCpuInfo(), getMyPidCpuInfo());
            if (cpuInfo2 == null) {
                return entity;
            }
            /**两次采集cpu空闲时间差*/
            long idleTime = cpuInfo2.idle - cpuInfo1.idle;
            /**两次采集cpu总共时间差*/
            long totalTime = cpuInfo2.total - cpuInfo1.total;
            /**系统cpu使用率*/
            double cpu = ((double) (totalTime - idleTime)) / totalTime;
            /**app进程cpu使用率*/
            double app = ((double) (cpuInfo2.appCpuTime - cpuInfo1.appCpuTime)) / totalTime;

            NumberFormat format = NumberFormat.getNumberInstance();
            format.setMaximumFractionDigits(0);
            format.setRoundingMode(RoundingMode.UP);

            /**该段时间内的cpu使用率,总时间减去空闲时间再除以总时间,(totaltime-idletime)/totaltime*/
            entity.total = format.format(cpu * 100);
            /**该段时间内app进程的cpu使用率, ,(pidCpuTime2-pidCpuTime1)/totaltime*/
            entity.app = format.format(app * 100);
        } catch (Exception e) {
            if (Log.LOGSWITCH) {
                e.printStackTrace();
            }
        }
        return entity;
    }

    /**
     * 获取{@link CpuInfo}类型数据
     * @return 第一次返回 {@link #cpuInfo1} 第二次返回 {@link #cpuInfo2}
     */
    private CpuInfo parse(String cpuInfo, String pidCpuInfo) {

        if (TextUtils.isEmpty(cpuInfo)) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.e("CpuCollector method prase cpuInfo is a null object reference");
            }
            return null;
        }

        String[] cpuInfoArray = cpuInfo.split(" ");
        /**获取的cpu使用率数据不正确返回空值*/
        if (cpuInfoArray.length < 9) {
            return null;
        }

        String[] pidCpuInfoArray = pidCpuInfo.split(" ");
        /**获取应用进程cpu使用率数据不正确,返回空值*/
        if (pidCpuInfoArray.length < 17) {
            return null;
        }
        /**if条件来判断是否进行了第一次数据采集*/
        if (isFirst) {
            cpuInfo1.user = Long.parseLong(cpuInfoArray[2]);
            cpuInfo1.nice = Long.parseLong(cpuInfoArray[3]);
            cpuInfo1.system = Long.parseLong(cpuInfoArray[4]);
            cpuInfo1.idle = Long.parseLong(cpuInfoArray[5]);
            cpuInfo1.ioWait = Long.parseLong(cpuInfoArray[6]);
            /**user+system+nice+idle+iowait+irq+softtirq*/
            cpuInfo1.total = cpuInfo1.user + cpuInfo1.nice + cpuInfo1.system + cpuInfo1.idle + cpuInfo1.ioWait
                    + Long.parseLong(cpuInfoArray[7])
                    + Long.parseLong(cpuInfoArray[8]);
            /**
             * 13 majflt: 当前进程等待子进程的majflt
             * 14 utime: 该进程处于用户态的时间,单位jiffies
             * 15 stime: 该进程处于内核态的时间,单位jiffies
             * 16 cutime: 当前进程等待子进程的utime
             */
            cpuInfo1.appCpuTime = Long.parseLong(pidCpuInfoArray[13])
                    + Long.parseLong(pidCpuInfoArray[14])
                    + Long.parseLong(pidCpuInfoArray[15])
                    + Long.parseLong(pidCpuInfoArray[16]);
            isFirst = false;
            return cpuInfo1;
        } else {
            cpuInfo2.user = Long.parseLong(cpuInfoArray[2]);
            cpuInfo2.nice = Long.parseLong(cpuInfoArray[3]);
            cpuInfo2.system = Long.parseLong(cpuInfoArray[4]);
            cpuInfo2.idle = Long.parseLong(cpuInfoArray[5]);
            cpuInfo2.ioWait = Long.parseLong(cpuInfoArray[6]);
            cpuInfo2.total = cpuInfo2.user + cpuInfo2.nice + cpuInfo2.system + cpuInfo2.idle + cpuInfo2.ioWait
                    + Long.parseLong(cpuInfoArray[7])
                    + Long.parseLong(cpuInfoArray[8]);
            cpuInfo2.appCpuTime = Long.parseLong(pidCpuInfoArray[13])
                    + Long.parseLong(pidCpuInfoArray[14])
                    + Long.parseLong(pidCpuInfoArray[15])
                    + Long.parseLong(pidCpuInfoArray[16]);
            isFirst = true;
            return cpuInfo2;
        }
    }

    /**
     * 获取/proc/stat文件中的第一行cpu信息
     * @return 当前总的cpu使用信息
     */
    private String getCpuInfo() {
        BufferedReader cpuReader = null;
        String cpuRate = null;
        try {
            cpuReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/stat")), BUFFER_SIZE);
            /**获取/proc/stat文件中的第一行cpu信息*/
            cpuRate = cpuReader.readLine();
            if (cpuRate == null) {
                cpuRate = "";
                return "";
            }
        } catch (FileNotFoundException e) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.d("/proc/stat not found");
            }
        } catch (IOException e) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY){
                Log.d("open /proc/stat fail");
            }
        } finally {
            try {
                if (cpuReader != null) {
                    cpuReader.close();
                }
            } catch (IOException e) {
                if (Log.LOGSWITCH) {
                    e.printStackTrace();
                }
            }
        }
        return cpuRate;
    }

    /**
     * 获取应用进程/proc/myPid/stat文件中的第一行cpu信息
     * @return 当前总的cpu使用信息
     */
    private String getMyPidCpuInfo() {
        BufferedReader pidReader = null;
        String pidCpuRate = null;
        try {
            int mPid = android.os.Process.myPid();
            pidReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
            pidCpuRate = pidReader.readLine();
            if (pidCpuRate == null) {
                pidCpuRate = "";
                return pidCpuRate;
            }
        } catch (FileNotFoundException e) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.d("my application /proc/mPid/stat not found");
            }
        } catch (IOException e) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.d("my application /proc/mPid/stat open fail");
            }
        } finally {
            try {
                if (pidReader != null) {
                    pidReader.close();
                }
            } catch (IOException e) {
                if (Log.LOGSWITCH) {
                    e.printStackTrace();
                }
            }
        }
        return pidCpuRate;
    }

    class CpuEntity {
        /**该段时间内的cpu使用率,总时间减去空闲时间再除以总时间,(totaltime-idletime)/totaltime*/
        public String total;
        /**该段时间内该进程的cpu使用率,进程使用的时间片除以总时间,(pidCpuTime2-pidCpuTime1)/totaltime*/
        public String app;
    }

    /**
     * 内部类,记录cpu的信息,本类内部cpuInfo都是该类型
     */
    private static class CpuInfo {
        /**用户态时间*/
        long user;
        /**用户态时间(低优先级,nice>0)*/
        long nice;
        /**内核态时间*/
        long system;
        /**空闲时间*/
        long idle;
        /**I/O等待时间*/
        long ioWait;
        /**硬中断时间*/
        long irq;
        /**软中断时间*/
        long softtirq;
        /**系统cpu总使用时间*/
        long total;
        /**app进程cpu总使用时间*/
        long appCpuTime;

        public void reset() {
            user = 0;
            nice = 0;
            system = 0;
            idle = 0;
            ioWait = 0;
            total = 0;
            appCpuTime = 0;
        }
    }
}

内存监控

获取的是分配给app的总内存量,app已使用的内存总量,当前页面使用内存的增加量

/**
 * 责任链设计模式中获取分配APP内存总量、内存使用量、使用内存增加量
 */
public class MemoryCollector extends BaseCollector {

    @Override
    public void doCollect(HashMap<String, String> vector) {
        MemoryEntity entity = getMemoryEntity();
        /**系统可分配给App的最大内存*/
        vector.put("memoryMax", String.valueOf(entity.maxMemory/1024/1024));
        /**当前使用内存大小*/
        vector.put("memoryUsed", String.valueOf(entity.usedMemory/1024/1024));

        Session session = ActivityPerfMonitor.getInstance().getCurrentSession();
        if (session != null){
            /**计算并记录最大内存增量*/
            session.recordMemoryUsage(entity.usedMemory);
        }
    }

    /**
     * get the memory of process with certain pid.
     *
     * @return memory usage of certain process
     */
    private MemoryEntity getMemoryEntity() {
        MemoryEntity entity = new MemoryEntity();
        try {
            long totalMemory = Runtime.getRuntime().totalMemory();
            long freeMemory = Runtime.getRuntime().freeMemory();
            entity.maxMemory = Runtime.getRuntime().maxMemory();
            entity.usedMemory = (totalMemory - freeMemory);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return entity;
    }

    /**
     * 获取内存使用量
     * @return
     */
    public static long getUsedMemory() {
        long totalMemory = Runtime.getRuntime().totalMemory();
        long freeMemory = Runtime.getRuntime().freeMemory();
        return (totalMemory - freeMemory);
    }
    
    /**
     * 内存采样信息
     *  Runtime.getRuntime().maxMemory();
     *  Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
     */
    class MemoryEntity {
        public long maxMemory;           //系统可分配给App的最大内存
        public long usedMemory;          //当前使用内存大小
    }
}

流量监控

在onActivityStarted方法中开始统计进入页面的流量戳,在onActivityStoped方法中开始统计离开页面的流量戳。

/**
 * 通过broadcastReceiver广播状态来判定当前网络是否为移动数据
 * 然后根据状态的切换记录使用app消耗了多少流量
 */
public class MobileTrafficCalculator {
    public static int uid = -1;

    /**
     * 获取app的瞬间流量值
     * @return  流量值
     */
    public static long getApplicationTrafficFlow() {
        Context context = Sentry.getApplication().getApplicationContext();
        if (context == null) return -1;
        long totalBytes = 0;
        long rxBytes = 0;
        long txBytes = 0;
        if(uid == -1){
            try {
                /**根据包名获取uid*/
                PackageManager pm = context.getPackageManager();
                ApplicationInfo ai = pm.getApplicationInfo(ProcessUtil.getPackName(), PackageManager.GET_ACTIVITIES);
                uid = ai.uid;
                if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                    Log.d("self_flowData", "uid= " + uid);
                }
            } catch (PackageManager.NameNotFoundException e) {
                if (Log.LOGSWITCH) {
                    e.printStackTrace();
                }
            }
        }
        try {
            /**获取到目前为止此uid共接收的总字节数//2.2以上版本适用*/
            /**根据UID获取某个网络接收和发送字节的总和*/
            rxBytes = TrafficStats.getUidRxBytes(uid);
            txBytes = TrafficStats.getUidTxBytes(uid);
            totalBytes = rxBytes + txBytes;
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.d("self_flowData", "uid=" + uid + "  ;  totalBytes=" + totalBytes + "     ;  rxBytes=" + rxBytes + "     ;  txBytes=" + txBytes);
            }
        } catch (Throwable e) {
            // TODO: handle exception
            if (Log.LOGSWITCH) {
                e.printStackTrace();
            }
        }
        return totalBytes;
    }
}

线程数量监控

通过Thread.getAllStackTraces()方法返回Map<Thread,StackTraceElement[]>集合,遍历集合可以获取线程数量,名称和调用栈信息。

/**
 * 责任链设计模式中获取线程数和线程名
 */
public class ThreadNumCollector extends BaseCollector {

    @Override
    public void doCollect(HashMap<String, String> vector) {
        int num = getThreadNum();
        if (num <= 0) return;
        vector.put("threadNum", String.valueOf(num));
        vector.put("threadName", getAllThreadName());
    }

    /**
     * 获取当前进程中的线程数目
     *
     * @return 返回int类型
     */
    public int getThreadNum() {
        Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
        if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
            Log.d(Configuration.COMMON_TAG, "当前线程数:" + map.size());
        }
        return map.size();
    }

    /**
     * 获取当前进程中的所有线程的名字
     *
     * @return 返回一个由线程名字拼接成的字符串,由空格分割
     */
    public String getAllThreadName() {
        Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
        StringBuilder builder = new StringBuilder();
        Iterator<Thread> iterator = map.keySet().iterator();
        boolean isFirst = true;

        while (iterator.hasNext()) {
            if (!isFirst) {
                builder.append("|||");
            }
            builder.append(iterator.next().getName());
            isFirst = false;
        }
        return String.valueOf(builder);
    }
}

帧率监控

在onActivityResume方法中注册帧绘制回调,开始帧率计算,在onActivityPause方法中注销掉监听,暂停帧率采集,在onChange方法中设置监听项

/**
 * 帧率统计
 * 目前统计三个指标: 帧率,掉帧数量,掉帧程度
 */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class FrameTrace implements Choreographer.FrameCallback, ViewTreeObserver.OnDrawListener {
    public static final String TAG = FrameTrace.class.getSimpleName();

    private static FrameTrace mInstance;
    private Choreographer mChoreographer;
    /**记录上一帧绘制的时间*/
    private long mLastFrameNanos;
    private boolean isCreated;
    private volatile boolean isPause = true;
    /**标识页面是否将要去绘制*/
    private boolean isDrawing = false;
    /**标识是否在有效生命周期区间内*/
    private boolean isInvalid = false;
    /**当前停留页面*/
    private String mCurrentScene;
    /**相邻帧数据*/
    private LinkedList<Float> mFrameData;
    /**帧刷新累计时间*/
    private float mFrameTotalTime;

    private FrameTrace() {
    }

    public static synchronized FrameTrace getInstance() {
        if (null == mInstance) {
            mInstance = new FrameTrace();
        }
        return mInstance;
    }

    /**暂停帧率采集*/
    public void pause() {
        if (!isCreated) {
            return;
        }
        isPause = true;
        if (null != mChoreographer) {
            mChoreographer.removeFrameCallback(this);
            mLastFrameNanos = 0;
        }
    }

    /**启动帧率采集*/
    public void resume() {
        if (!ProcessUtil.isInMainThread(Thread.currentThread().getId())) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.e("FrameTrace init must create on main thread.");
            }
            return;
        }

        if (!isCreated) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.d("FrameTrace init.");
            }
            isCreated = true;
            mFrameData = new LinkedList<>();
            mChoreographer = Choreographer.getInstance();
        }
        /**activity在前台*/
        if (ApplicationLifecycleObservable.getInstance().isForeground()) {
            isPause = false;
            if (null != mChoreographer) {
                mChoreographer.removeFrameCallback(this);
                mChoreographer.postFrameCallback(this);
                mLastFrameNanos = 0;
            }
        }
    }

    public void setCurrentScene(String scene) {
        this.mCurrentScene = scene;
    }

    /**添加视图树观察者 来判断是否开始绘制*/
    public void addDrawListener(final Activity activity) {
        isInvalid = false;
        activity.getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                activity.getWindow().getDecorView().getViewTreeObserver().removeOnDrawListener(FrameTrace.this);
                activity.getWindow().getDecorView().getViewTreeObserver().addOnDrawListener(FrameTrace.this);
            }
        });
    }

    public void removeDrawListener(final Activity activity) {
        isInvalid = true;
        activity.getWindow().getDecorView().getViewTreeObserver().removeOnDrawListener(FrameTrace.this);
    }

    @Override
    public void onDraw() {
        isDrawing = true;
    }

    @Override
    public void doFrame(long frameTimeNanos) {
        if (isPause) {
            return;
        }
        if (frameTimeNanos < mLastFrameNanos || mLastFrameNanos <= 0) {
            mLastFrameNanos = frameTimeNanos;
            if (null != mChoreographer) {
                mChoreographer.postFrameCallback(this);
            }
            return;
        }

        /**开启监控*/
        if (!isInvalid && isDrawing) {
            handleDoFrame(frameTimeNanos);
        }

        if (null != mChoreographer) {
            mChoreographer.postFrameCallback(this);
        }
        mLastFrameNanos = frameTimeNanos;
        isDrawing = false;
    }

    /**数据缓存*/
    private void handleDoFrame(long frameNanos) {
        /**单帧绘制时间*/
        long offset = frameNanos - mLastFrameNanos;
        if (offset >= 5 * 1000000000L) {
            if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
                Log.w(TAG, "[handleDoFrame] WARNING drop frame! offset: " + offset + " ,scene: " + mCurrentScene);
            }
        }
        synchronized (this.getClass()) {
            if (!TextUtils.isEmpty(mCurrentScene)) {
                float offsetMs = nanoTime2Ms(offset);
                mFrameTotalTime += offsetMs;
                mFrameData.add(offsetMs);

                /** 帧率计算时间,即每隔多长时间计算一次帧率 默认2s计算一次*/
                if (mFrameTotalTime >= DEFAULT_FPS_TIME_SLICE_ALIVE_S * 1000) {
                    asyncReport();
                }
            }
        }
    }

    private float nanoTime2Ms(long nanos) {
        return nanos / 1000000f;
    }

    private void asyncReport() {
        ThreadUtil.getGlobalThreadPool().execute(workRunnable);
    }

    private Runnable workRunnable = new Runnable() {
        @Override
        public void run() {
            reportData();
        }
    };

    private void reportData() {
        LinkedList<Float> raw = null;
        synchronized (this.getClass()) {
            if (mFrameData.isEmpty()) {
                return;
            }
            raw = mFrameData;
            mFrameData = new LinkedList<>();
            mFrameTotalTime = 0f;
        }

        HashMap<String, String> result = new HashMap<>();
        /**累计总时间*/
        float sumTime = 0f;
        /**标记*/
        int markIndex = 0;
        /**累计刷新次数*/
        int count = 0;
        /**记录本时间段内掉帧水平*/
        int[] dropLevel = new int[DropStatus.values().length];
        /**记录本时间段内掉帧数量*/
        int[] dropSum = new int[DropStatus.values().length];
        /**
         * Android 16ms原理*:Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity).为什么是16ms,
         * 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.
         *
         * 每隔16ms,系统都会发出VSYNC信号,如果这时候我们的画面(view)准备好了,我们的view绘制就会很流畅。
         * 如果我们在这个16ms间隔内,没有准备好画面(view),那么这一次绘制,就不会展示在屏幕上,就相当于少绘制了一帧,画面就会出现卡顿,断断续续。
         */
        /**两帧帧间时长,一般等于16.7ms*/
        int refreshRate = (int) DEFAULT_DEVICE_REFRESH_RATE;

        for (float offset : raw) {
            sumTime += offset;
            count++;

            /**渲染掉帧参数计算 tmp: 渲染了多少次帧(也就是掉了多少帧)  offset:统计的绘制一帧所需要的时间 refreshRate: 标准绘制一帧时间 = 16.666667f*/
            int tmp = (int)(offset / refreshRate) - 1;
            /**掉帧数>=42 冻结*/
            if (tmp >= DEFAULT_DROPPED_FROZEN) {
                dropLevel[DropStatus.DROPPED_FROZEN.index]++;
                dropSum[DropStatus.DROPPED_FROZEN.index] += tmp;
            } else if (tmp >= DEFAULT_DROPPED_HIGH) {
                dropLevel[DropStatus.DROPPED_HIGH.index]++;
                dropSum[DropStatus.DROPPED_HIGH.index] += tmp;
            } else if (tmp >= DEFAULT_DROPPED_MIDDLE) {
                dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
                dropSum[DropStatus.DROPPED_MIDDLE.index] += tmp;
            } else if (tmp >= DEFAULT_DROPPED_NORMAL) {
                dropLevel[DropStatus.DROPPED_NORMAL.index]++;
                dropSum[DropStatus.DROPPED_NORMAL.index] += tmp;
            } else {
                dropLevel[DropStatus.DROPPED_BEST.index]++;
                dropSum[DropStatus.DROPPED_BEST.index] += (tmp < 0 ? 0 : tmp);
            }
        }
        /**帧率计算 帧率 = 每秒刷了多少帧*/
        /**标准的屏幕的刷新率为每秒60Hz*/
        float fps = Math.min(60.f, 1000.f * (count - markIndex) / sumTime);
        if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
            Log.d(TAG, "scene : " + mCurrentScene + " , fps ====> " + fps);
        }

        result.put("value", String.valueOf((int)fps));
        result.put("frozen", String.valueOf(dropLevel[DropStatus.DROPPED_FROZEN.index]));
        result.put("high", String.valueOf(dropLevel[DropStatus.DROPPED_HIGH.index]));
        result.put("middle", String.valueOf(dropLevel[DropStatus.DROPPED_MIDDLE.index]));
        result.put("normal", String.valueOf(dropLevel[DropStatus.DROPPED_NORMAL.index]));

        /**
         * 上报数据 result
         */
    }

    public enum DropStatus {
        DROPPED_FROZEN(4), DROPPED_HIGH(3), DROPPED_MIDDLE(2), DROPPED_NORMAL(1), DROPPED_BEST(0);
        int index;

        DropStatus(int index) {
            this.index = index;
        }
    }

    /**=========================帧率常量 ================================*/
    public static final int DEFAULT_FPS_TIME_SLICE_ALIVE_S = 2;
    public static final float DEFAULT_DEVICE_REFRESH_RATE = 16.666667f;

    public static final int DEFAULT_DROPPED_NORMAL = 3;   //正常
    public static final int DEFAULT_DROPPED_MIDDLE = 9;   //中等
    public static final int DEFAULT_DROPPED_HIGH = 24;    //严重
    public static final int DEFAULT_DROPPED_FROZEN = 42;  //冻结

优化流程:
1.UI层级嵌套绘制
2.静态代码扫描工具配合
3.Traceview定位主线程卡顿问题
4.Systrace分析
5.Strictmode严苛模式主线程耗时操作
6.Blockcanary非侵入式自动检测卡顿

相关文章

网友评论

      本文标题:Android 性能监控 - 页面监控

      本文链接:https://www.haomeiwen.com/subject/bxzkortx.html