最近在做学校实验室的项目的时候,师兄要求我对app的性能进行评估,主要是从电量、cpu占有率、python模型的响应时间三者进行统计分析,电量使用广播可以进行统计、python模型的运行速度用时间函数就可以计算,关于cpu占有率~interesting!这是值得研究一下的。
阅读指南:
1、想法一暂时无法解决问题。
2、想法二可以实现在java中动态获得app的cpu占有率,便于大家实现显示在前端界面的需求,但是,这种获得占有率的方法速度较慢,大概1s获得3~4个cpu占有率,所以如果对cpu占有率的采样率具有很高的要求的话,不建议使用第二种方法。
3、想法三可以满足你大幅度采样cpu占有率的需求,通过调整top指令中 -d [time] 参数的大小你可以控制cpu采样的速度,但是这个方法不方便展示在前端界面中。
我找了很久,也没找到有对应的接口,如果有的话,欢迎大家在评论区中指正,谢谢啦!!
我们都知道,android OS的底层其实也是基于Linux,所以在Linux上的一些命令也能够用在android系统中,但是我们先用Linux来试一下(大家没有Linux系统也没关系,我们后面会在java代码中调用指令可以达到相同的效果)。
top
图中,我们运行了top命令后,会发现出现一个不断刷新的表,其实这就是此时linux系统中正在运行的进程的状况,可以看见我们想要获得的参数cpu占有率就在倒数第三列,同时我们也可以发现其中的每一个进程都对应一个pid,它就相当于一个身份证,唯一标识着系统中运行的一个进程
在Linux中,你可以使用pstree指令获得系统进程树(方法不唯一,你可以通过这个来找到你关注的应用程序的pid)。
pstree -p
或者
pstree -p | grep [进程名]
我们可以看到firefox的进程id为2328,于是我们可以运行,结果如下图所示。
top -p [pid]
我们再加上-d参数控制刷新时间(s), 如top -p 2368 -d 0.5,命令行会0.5秒刷新一次输出
top -p [pid] -d [time]
于是我们可以得出结论,我们可以通过top命令获得应用程序的占有率!!!
先贴代码~
package com.example.bluechatapp.Performance;
import android.content.Context;
import android.os.Environment;
import com.example.bluechatapp.Static.Utils.LogUtils;
import com.example.bluechatapp.MainActivity.MainActivity;
import com.example.bluechatapp.Static.Utils.ToastUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class CPURunnable implements Runnable{
private PerformanceRecorder recorder ;
private Context context ;
private File performanceFile ;
public CPURunnable(PerformanceRecorder recorder,Context context) throws IOException {
this.recorder = recorder;
this.context = context ;
try{
// 初始化保存目录
initSaveDirectory() ;
}catch (Exception e){
// 保存错误日志
ToastUtil.showShortMessage(context,"错误报告正在生成");
LogUtils.recordLog(context,"CpuRunnable_cn",".txt",e.getMessage());
}
}
/**
* 初始化保存目录
*/
private void initSaveDirectory() throws IOException {
// 在app目录中初始化保存目录,并生成对应文件
File rootDir = new File(String.valueOf(Environment.getExternalStorageDirectory()),"EarMotion") ;
if (!rootDir.exists()){
rootDir.mkdirs() ;
}
File performanceDir = new File(rootDir.getPath(),"Performance") ;
if (!performanceDir.exists()){
performanceDir.mkdirs() ;
}
performanceDir = new File(performanceDir.getPath(),"CPU") ;
if (!performanceDir.exists()){
performanceDir.mkdirs() ;
}
int length = performanceDir.listFiles().length + 1 ;
performanceFile = new File(performanceDir.getPath(),(length+".txt"));
if (!performanceFile.exists()){
performanceFile.createNewFile() ;
}
OutputStream stream = new FileOutputStream(performanceFile,false) ;
stream.write("cpu rate:\ntime(s)\t\trate\n".getBytes() );
stream.close() ;
}
@Override
public void run() {
long begin = System.currentTimeMillis() ;
try {
OutputStream stream = new FileOutputStream(performanceFile,true);
while(recorder.isMonitoring()){
try {
// 主要注意这里,从recorder中获得cpu占有率
float rate = recorder.getCpuRateFromTerminal() ;
stream.write((String.valueOf((System.currentTimeMillis()-begin)/1000)+"\t\t"+rate+"%\n").getBytes());
MainActivity.setCpuTextView("CPU占比:"+rate+"%");
} catch (Exception e) {
ToastUtil.showShortMessage(context,"错误报告正在生成");
LogUtils.recordLog(context,"CpuRunnable",".txt",e.getMessage());
}
}
stream.close() ;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Process;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.example.bluechatapp.MusicActivity.MusicPlayerHelper.TAG;
public class PerformanceRecorder {
/**
* 上下文
*/
private Activity context ;
/**
* 终端执行的指令,注意这里!!!!!
*/
private static String COMMAND = "top -n 1 -p " ;
/**
* 单例模式
*/
private static volatile PerformanceRecorder recorder = null ;
/**
* 该应用程序的唯一标识
*/
private static int pid ;
/**
* 匹配浮点数
*/
private static String NUMBER_REGEX = "^[0-9]+(.[0-9]+)?$";
/**
* 构造函数,初始化pid
* @param context
*/
private PerformanceRecorder(Activity context) {
this.context = context;
// 获得该应用程序的进程pid
pid = Process.myPid() ;
// 指令合成
COMMAND = COMMAND + pid ;
}
/**
* 单例模式工厂,双重锁定
* @param context
* @return
*/
public static PerformanceRecorder newInstance(Activity context) {
if (recorder==null){
synchronized (PerformanceRecorder.class){
if (recorder==null)
recorder = new PerformanceRecorder(context) ;
}
}
return recorder ;
}
/**
* 从终端接收此时的平均cpu占比,如果返回-1,则是错误的
* @return
* @throws IOException
*/
public float getCpuRateFromTerminal() throws Exception {
// Linux运行指令!!注意这里!!
java.lang.Process runtime = Runtime.getRuntime().exec(COMMAND) ;
// 获得终端的输入流!!!
BufferedReader terminalBufferedStream = new BufferedReader(new InputStreamReader(runtime.getInputStream())) ;
String line = null ;
String lastLine = null ;
String dataLine = null ;
List<String> floatNumber = new ArrayList<>() ;
// 这里注意从命令行获得的输入流有多行,我们取最后一行,这里可以结合具体的输入流来看,会更好理解
while((line=terminalBufferedStream.readLine())!=null&&line!=" "){
dataLine = lastLine ;
lastLine = line ;
}
dataLine = dataLine.trim() ;
String []array = dataLine.split(" ") ;
// 正则表达式,提取其中的浮点数
for (String i:array){
if (i.matches(NUMBER_REGEX)){
floatNumber.add(i) ;
}
}
// 获得系统的cpu核心,用于求平均占比
int cpuCores = Runtime.getRuntime().availableProcessors() ;
if (floatNumber.size()==4&&floatNumber.get(0).equals(String.valueOf(pid))){
// 第三个浮点数就是cpu占比
Log.d(TAG, "cpu rate: "+floatNumber.get(2)+"/"+cpuCores+"%");
Log.d(TAG, "getCpuRateFromTerminal: "+line);
terminalBufferedStream.close();
runtime.destroy();
return Float.valueOf(floatNumber.get(2)) / cpuCores ;
}
terminalBufferedStream.close();
runtime.destroy();
return -1 ;
}
/**
* 启动cpu监视线程
*/
public void startCpuMonitor() throws IOException {
ExecutorService cpuPool = Executors.newSingleThreadExecutor();
cpuPool.submit(new CPURunnable(this,context)) ;
}
}
调用getCpuxxx函数,把返回值不断写到textView中就能实现以下效果,他是不断在变化的
实现效果:
以上的代码,大家只要清楚关键,就是通过java的RunTime类运行Linux指令,获得终端的输入流进行分析即可,具体可以根据自己的具体情况对代码进行调整
经常开发安卓的同学都应该知道有adb这种神器工具,它能够进入到手机内核中像在Linux一样观察各种进程的运行情况,那么在项目的最后我也是采用这种方法来完成师兄的需求。
adb shell
进入手机内核之后,我们可以像在Linux中一样运行各种终端指令
top -d [time(s)] | grep [包名] > /sdcard/[保存文件名]
指令运行后,会一直阻塞在这里,此时系统正在帮你统计程序的信息,我们通过ctrl + C可以结束并保存文件
adb pull /sdcard/[文件名]
至此,全剧终,完结撒花
可见我们实现了两种方法,来提取cpu占有率,其实获得内存占比等都是一样的,谢谢大家的阅读!您的观看是我前进的动力!欢迎大家在评论区中讨论,指出我的不足!
谢谢大家!!