您的位置:首页 > 移动开发 > Android开发

Android进程性能监控工具Honeybadger实现

2015-02-07 19:52 2646 查看
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控件中,实现选中具体项的功能:

布局文件内容为:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tmptv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textColor="#000000"
android:textSize="16sp"
android:text="@string/waitchoose"/>
<Button
android:id="@+id/button_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:background="@drawable/btn_selector"
android:text="@string/commit"/>
</RelativeLayout>
<ListView
android:id="@+id/lv"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/row_selector"
android:scrollbars="vertical"/>
</LinearLayout>


后台代码为:

public class AUTListActivity extends Activity {
private StringpackageName ="";
private StringappName ="";
private Buttonbtn;
private TextViewtv;
private ProgressDialogprogressDialog =null;
private ListViewlv;
List<Map<String, Object>> listData=null;

Handler handler = null;

class myThreadimplements Runnable{
public void run() {
getData();
handler.post(runnableUi); //把该runnable对象放到handle对象关联的线程中执行。因为runnable中是对UI的处理,所以该handler对象一定要与UI线程关联
}
}

Runnable runnableUi=new  Runnable(){
@Override
public void run() {
//更新界面
progressDialog.dismiss();
bindDataToListView();
}
};

@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_autlist);

setTitle("选择测试对象");

progressDialog =ProgressDialog.show(AUTListActivity.this,
"请稍等...","正在读取本机上安装的App...",true);

btn =(Button)findViewById(R.id.button_back);
btn.setOnClickListener(newView.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent();
if(AUTListActivity.this.packageName!=null){
intent.putExtra("packageName",AUTListActivity.this.packageName);
Log.v("放进去的package name为",packageName);
intent.putExtra("appName",AUTListActivity.this.appName);
intent.setClass(AUTListActivity.this,OptionActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.new_dync_in_from_right,
R.anim.new_dync_out_to_left);

}
else{
Toast.makeText(getApplicationContext(),
"请从列表选择待测试的应用",
Toast.LENGTH_SHORT).show();
}

finish();
}
});

tv =(TextView)findViewById(R.id.tmptv);

lv = (ListView)findViewById(R.id.lv);
lv.setScrollContainer(true);//---------------否则不能滚动
handler = new Handler(); //一定要在UI线程中创建该Handler对象,因为Handler对象总是与创建它的线程的事件队列相关联
new Thread(newmyThread()).start();

lv.setOnItemClickListener(newOnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> arg0, Viewarg1, int arg2,long arg3) {
HashMap<String,String> map=(HashMap<String,String>)lv.getItemAtPosition(arg2);
String name=map.get("name");
String packagename=map.get("packagename");
tv.setText("已选择:"+name);
AUTListActivity.this.packageName = packagename;

AUTListActivity.this.appName = name;
}

});
}

private voidbindDataToListView(){
SimpleAdapteradapter = new SimpleAdapter(this,listData,R.layout.item_layout,
new String[]{"icon","name","version","packagename"},
new int[]{R.id.icon,R.id.name,R.id.version,R.id.packagename});
adapter.setViewBinder(newSimpleAdapter.ViewBinder(){ //显示图标,不能直接绑定Drawable
public boolean setViewValue(Viewview,Object data,String textRepresentation){
if(viewinstanceof ImageView && datainstanceof Drawable){
ImageView iv=(ImageView)view;
iv.setImageDrawable((Drawable)data);
returntrue;
}
else return false;
}
});
lv.setAdapter(adapter);
}

private void getData() {
ArrayList<AppEntity>appList = getAppEntityList();
List<Map<String,Object>> list = new ArrayList<Map<String, Object>>();
for(int i=0;i<appList.size(); i++){
AppEntityapp = (AppEntity)appList.get(i);
Map<String,Object> map = new HashMap<String, Object>();
//BitmapDrawable bd = (BitmapDrawable)app.appIcon;
//Bitmap bm = bd.getBitmap();
map.put("icon", app.appIcon);
map.put("name", app.appName);
map.put("version", app.versionName);
map.put("packagename", app.packageName);
list.add(map);
}
listData =  list;
}

private ArrayList<AppEntity>getAppEntityList(){
List<PackageInfo>packages = getPackageManager().getInstalledPackages(0); //@
ArrayList<AppEntity>appList = new ArrayList<AppEntity>();//用来存储获取的应用信息数据
AppEntitytmpEntity;
for(inti=0;i<packages.size();i++) {
PackageInfopackageInfo = packages.get(i);
if((packageInfo.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM)==ApplicationInfo.FLAG_SYSTEM){//过滤掉系统程序
//Log.d("System software","We met System"+i+"th software: "+packageInfo.packageName);
continue;
}
tmpEntity=new AppEntity();
tmpEntity.appName = packageInfo.applicationInfo.loadLabel(getPackageManager()).toString();
tmpEntity.packageName = packageInfo.packageName;
tmpEntity.versionName ="版本:"+packageInfo.versionName;
tmpEntity.versionCode = packageInfo.versionCode;
tmpEntity.appIcon = packageInfo.applicationInfo.loadIcon(getPackageManager());
appList.add(tmpEntity);
}
return appList;
}

@Override
public boolean onCreateOptionsMenu(Menumenu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}

@Override
public booleanonOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
//响应每个菜单项(通过菜单项的ID)
case R.id.item1:
Intentintent = new Intent();
intent.setClass(AUTListActivity.this,InfoActivity.class);
startActivity(intent);
break;
case R.id.item2:
Toast.makeText(AUTListActivity.this,"分享至微信", Toast.LENGTH_SHORT).show();
break;
default:
//对没有处理的事件,交给父类来处理
return super.onOptionsItemSelected(item);
}
//返回true表示处理完菜单项的事件,不需要将该事件继续传播下去了
return true;
}


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<RunningAppProcessInfo> 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<RunningAppProcessInfo> 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: