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

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> 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 map=(HashMap)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() {
       ArrayListappList = getAppEntityList();
       List> list = new ArrayList>();
       for(int i=0;i map = new HashMap();
           //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 ArrayListgetAppEntityList(){
       Listpackages = getPackageManager().getInstalledPackages(0); //@
       ArrayListappList = new ArrayList();//用来存储获取的应用信息数据
       AppEntitytmpEntity;
       for(inti=0;i


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

你可能感兴趣的:(移动开发,android,测试,监控工具,性能,android)