Android进程性能监控工具Honeybadger实现
这是一个Android平台上的进程监控工具,可以实现对本机上安装的非系统app的进程的CPU利用率、PSS、网络上下行流量等数据进行准实时监控,并展示图形化结果(折线图)。
工具名称为:Honeybadger
百度移动下载地址:http://as.baidu.com/a/item?docid=6528832&pre=web_am_se
腾讯应用宝下载地址:
http://m4.qq.com/app/appDetails.htm?apkName=com.winstonwu.honeybadger&apkCode=1&dp=2&kword=&appId=10623688#
运行的效果图如下:
关键点总结:
1.进程CPU的利用率如何计算
Android应用是以Linux用户的模式运行在Linux之上,Linux的/proc路径下有相关统计文件,如/proc/stat文件记录了系统自开机以来的CPU时间片使用情况,该文件的第一行记录即为总的CPU使用情况记录,而如果想要查看某一指定进程的时间片段使用情况,需要首先查询到该进程的pid(可以在运行app后,通过adb shell 查询),之后/proc/pid/stat文件中便记录了该进程的时间片使用情况。(更进一步,线程级的时间片使用情况记录在/proc/pid/task/tid/stat文件中)。文件的具体格式这里不多叙述,网上有很多介绍。
某进程CPU使用率计算的方法就是:分别读出某一初始时间CPU总的时间片使用情况,以及该时间点某一进程的时间片使用情况,然后经过一个很短的时间片段,比如800毫秒,再一次读取CPU总的时间片使用情况以及该待测进程的时间片使用情况。那么在这800毫秒内该进程的CPU占用率可以通过该段时间内该进程时间片的增量与CPU总的时间片增量作商得到。
分析CPU文件的类实现如下,pickOne方法用来实现一次取样:
importjava.io.BufferedReader;
import java.io.File;
importjava.io.FileReader;
importjava.io.IOException;
import java.lang.Thread;
public class ParseStatFile {
private int pid;
public ParseStatFile(int i){
pid = i;
}
public float pickOne() throwsInterruptedException{
try{
CPU c1 = new CPU();
CPUofProccp1 = new CPUofProc(pid);
c1.fillCPU();
cp1.fillCPUofProc();
Thread.sleep(800);
CPU c2 = new CPU();
CPUofProccp2 = new CPUofProc(pid);
c2.fillCPU();
cp2.fillCPUofProc();
returncpuRateOfProc(c1,c2,cp1,cp2);
}
catch (Exception ex){
return 0;
}
}
public static StringgetStringFromFile(String filename){
StringFileName=filename;
StringfileStr = "";
File myFile=new File(FileName);
if(!myFile.exists())
{
System.err.println("Can't Find " + FileName);
}
try
{
BufferedReader in = new BufferedReader(newFileReader(myFile));
String str;
while ((str =in.readLine()) !=null)
{
fileStr+=str;
}
in.close();
}
catch (IOException e)
{
e.getStackTrace();
}
return fileStr;
}
private class CPU{
int user =0;
int nice =0;
int system = 0;
int idle = 0;
int iowait=0;
int irq = 0;
int softtirq = 0;
int stealstolen = 0;
int guest = 0;
public CPU(){
}
public void fillCPU(){
StringfileName = "/proc/stat";
StringstrOfFile = getStringFromFile(fileName);
String[]arr= strOfFile.split("\\s+");//@
user = Integer.parseInt(arr[1]);
nice = Integer.parseInt(arr[2]);
system = Integer.parseInt(arr[3]);
idle = Integer.parseInt(arr[4]);
iowait = Integer.parseInt(arr[5]);
irq = Integer.parseInt(arr[6]);
softtirq = Integer.parseInt(arr[7]);
stealstolen = Integer.parseInt(arr[8]);
guest = Integer.parseInt(arr[9]);
}
public int sum(){
returnuser+nice+system+idle+iowait+irq+softtirq+stealstolen+guest;
}
}
private class CPUofProc{
int pid;
int utime=0;
int stime=0;
int cutime=0;
int cstime=0;
public CPUofProc(int i){
pid=i;
}
public void fillCPUofProc(){
StringfileName = "/proc/"+Integer.toString(pid)+"/stat";
StringstrOfFile = getStringFromFile(fileName);
String[]arr= strOfFile.split("\\s+");
utime = Integer.parseInt(arr[13]);
stime = Integer.parseInt(arr[14]);
cutime = Integer.parseInt(arr[15]);
cstime = Integer.parseInt(arr[16]);
}
public int sum(){
returnutime+stime+cutime+cstime;
}
}
private float cpuRateOfProc(CPUc1,CPU c2,CPUofProc cp1,CPUofProc cp2){
float x =(float)(cp2.sum()-cp1.sum());
float y = (float)(c2.sum()-c1.sum());
float r = x/y;
if(r<0){
return 0;
}
if(r>1){
return 0.9999f;
}
return x/y;
}
}
2.实际物理内存占用以及网络流量情况如何计算
内存占用情况同样可以通过分析/proc下的文件得到,不过Android已经提供了现成的API封装了这些操作:
计算指定pid的PSS占用:
private ActivityManageram;
am = (ActivityManager)FxService.this.getSystemService(ACTIVITY_SERVICE);
MemoryInfo pidMemoryInfo=am.getProcessMemoryInfo(pids)[0];
int totalPss =pidMemoryInfo.getTotalPss();
计算指定uid当前已发送的字节数:
TrafficStats.getUidTxBytes(uid);
已接收的字节数:
TrafficStats.getUidRxBytes(uid);
取样两次,作差,便能得到某一时间段内的上下行流量情况。
注意,这里需要传入的参数是进程的uid而不是pid。
3.读取当前设备上已安装应用的列表,将包名、应用名、版本信息等显示到一个ListView控件中,实现选中具体项的功能:
布局文件内容为:
后台代码为:
public class AUTListActivity extends Activity {
private StringpackageName ="";
private StringappName ="";
private Buttonbtn;
private TextViewtv;
private ProgressDialogprogressDialog =null;
private ListViewlv;
List
4.创建悬浮窗,并使其始终显示在前端,不会因为打开其他应用,内存不足而被关闭
工具实现了一个悬浮窗,准实时地显示当前的取样数据,为了保证在操作待测应用的过程中,悬浮窗不会因为内存短缺而被系统回收,所以将悬浮窗的实现挂在一个Service上(而不是Activity),并且将Service设置为前台Service:
绘制悬浮窗:
private void createFloatView()
{
wmParams = new WindowManager.LayoutParams();
mWindowManager =(WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);
wmParams.type = LayoutParams.TYPE_PHONE;
wmParams.format = PixelFormat.RGBA_8888;
wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
wmParams.gravity = Gravity.LEFT | Gravity.TOP;
wmParams.x = 300;
wmParams.y = 300;
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
LayoutInflater inflater =LayoutInflater.from(getApplication());
mFloatLayout =(LinearLayout)inflater.inflate(R.layout.float_layout,null);
mWindowManager.addView(mFloatLayout,wmParams);
mFloatView = (LinearLayout)mFloatLayout.findViewById(R.id.float_id);
btnArrow = (ImageButton)mFloatLayout.findViewById(R.id.btn_float);
float_info =(TextView)mFloatLayout.findViewById(R.id.float_info);
float_title=(TextView)mFloatLayout.findViewById(R.id.float_title);
btnArrow.setOnClickListener(newOnClickListener()
{
@Override
public void onClick(View v)
{
Intentintent = new Intent(FxService.this, ResultActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//@@@@@@@@@@
startActivity(intent);
}
});
mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED),View.MeasureSpec
.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED));
//设置监听浮动窗口的触摸移动
mFloatView.setOnTouchListener(newOnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEventevent)
{
wmParams.x = (int) event.getRawX() -mFloatView.getMeasuredWidth()/2;
wmParams.y = (int) event.getRawY() -mFloatView.getMeasuredHeight()/2 -25;
mWindowManager.updateViewLayout(mFloatLayout,wmParams);
returnfalse; //此处必须返回false,否则OnClickListener获取不到监听
}
});
}
设置前台Service:
Notification notification = newNotification(R.drawable.ic_launcher,"Honeybadger is working!",System.currentTimeMillis());
notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT;
Intent notificationIntent = new Intent(this, ResultActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0);
notification.setLatestEventInfo(FxService.this,"Honeybadger is working!","正在测试"+targetPackage,pendingIntent);
startForeground(ONGOING_NOTIFICATION, notification);
5.由应用包名获取进程pid
因为进程没有运行时是无法获取到pid的,所以在点击开始测试的按钮后,应该先判断一下待测应用当前是否在运行,如果没有,需要先打开待测应用,并设置一个时间延长让系统初始化相应的目录。
public static boolean isRunning(Contextcontext,String packageName){
ActivityManager am = (ActivityManager)context.getSystemService(ACTIVITY_SERVICE);
List infos= am.getRunningAppProcesses();
for(RunningAppProcessInfo rapi : infos){
if(rapi.processName.equals(packageName))
returntrue;
}
return false;
}
public int getPidFromPackagename(String p){
Log.v("查看","进入getPidFromPackagename");
Log.v("查看","待匹配包为"+p.trim());
int pid = -1;
if(!isRunning(FxService.this,p)){
//先打开应用
PackageManager packageManager = this.getPackageManager();
Intent intent=new Intent();
try{
intent=packageManager.getLaunchIntentForPackage(p.trim());
//intent=packageManager.getLaunchIntentForPackage("com.tencent.mm");
if(intent ==null){
Log.v("错误","intent为null");
}
startActivity(intent);
Toast.makeText(FxService.this,"正在打开待测对象,请稍等...", Toast.LENGTH_LONG).show();
Thread.sleep(5000);
}
catch(Exception ex){
Log.v("错误","未能成功打开待测应用");
ex.printStackTrace();
return pid;
}
}
//再获取id
ActivityManager am = (ActivityManager)this.getSystemService(this.ACTIVITY_SERVICE);
List infos =am.getRunningAppProcesses();
Log.v("查看","准备进入匹配循环");
for (RunningAppProcessInfo runningAppProcessInfo : infos) {
//Log.v("查看","匹配"+runningAppProcessInfo.processName);
if(runningAppProcessInfo.processName.indexOf("android") != -1){
continue;
}
if(runningAppProcessInfo.processName.equals(p.trim())){
//Log.v("查看","匹配到了包名"+p);
pid = runningAppProcessInfo.pid;
//Log.v("查看","pid为"+String.valueOf(pid));
break;
}
}
return pid;
}
6.定时器的代码片段如下
主要完成两件工作:一是获取采样数据并存入静态ArrayList以便后续导出使用,二是修改悬浮窗上的数据。
Handler handler=null;
public Runnable runnable = new Runnable(){
public void run(){
if(FxService.float_info!=null){
try {
String tmpStr = "";
if(CPU==true){
float f =psf.pickOne()*100;
DecimalFormat fnum = new DecimalFormat("##0.00");
String s=fnum.format(f);
DataStall.ratesOfCPU.add(s);
tmpStr+="CPU: "+s+" %\n";
}
if(PSS==true){
MemoryInfo pidMemoryInfo=am.getProcessMemoryInfo(pids)[0];
int totalPss = pidMemoryInfo.getTotalPss();
DataStall.ratesOfPSS.add(String.valueOf(totalPss));
tmpStr+="PSS: "+String.valueOf(totalPss)+" KB\n";
}
if(NET==true){
end_send =TrafficStats.getUidTxBytes(uid);
end_recv =TrafficStats.getUidRxBytes(uid);
tmpStr+="SEND: "+String.valueOf(end_send-begin_send)+" b\n";
tmpStr+="RECV: "+String.valueOf(end_recv-begin_recv)+" b";
DataStall.ratesOfNET_SEND.add(String.valueOf(end_send-begin_send));
DataStall.ratesOfNET_RECV.add(String.valueOf(end_recv-begin_recv));
}
FxService.float_info.setText(tmpStr);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else{
Toast.makeText(FxService.this, "FxService.float_info为空", Toast.LENGTH_SHORT).show();
}
}
};
参考文献:
android ListView详解:http://www.cnblogs.com/allin/archive/2010/05/11/1732200.html
Android 进程内存、CPU使用查看:http://blog.csdn.net/hgl868/article/details/6793041
Linux平台Cpu使用率的计算:http://www.blogjava.net/fjzag/articles/317773.html
android悬浮窗口的实现:http://blog.csdn.net/stevenhu_223/article/details/8504058
Android新手之旅(9) 自定义的折线图:
http://www.cnblogs.com/jetz/archive/2011/07/24/2115238.html
内存耗用:VSS/RSS/PSS/USS:http://blog.csdn.net/adaptiver/article/details/7084364
What is dirty memory?:http://www.linuxformat.com/forums/viewtopic.php?p=56436