cpu,内存监控作为android性能测试的一部分,在日常工作中使用也比较频繁,一般测试人员都是直接adb命令输出结果或者android studio上查看内存,Cpu的趋势,再深入一点就是性能分析定位了。由于本人水平有限,就先分享个android apk 监控指定应用的cpu和内存。先附上两张效果图:
原理如下
输入需要监控的应用包名,输入监控的间隔时间,输入监控的总时间,点击监控就会执行监控命令,点击监控统计会在图标显示各项参数的最大值,最小值,平局值。点击监控详情会跳转到新的界面,展示详细的监控参数
代码如下
1.先在manifest文件加上如下权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2.MainActivity代码如下
package com.example.monitor;import android.content.Intent;
import android.os.CountDownTimer;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MainActivity extends AppCompatActivity {EditText editText_times,editText_min,editText_package;Button button_top,button_reporte,cpu_btn,start_app,start_time;TextView countDown,cpu_max,cpu_min,cpu_ave,rss_max,rss_min,rss_ave,vss_max,vss_min,vss_ave;TextView total_time,wait_time,completa_time;FileUtils fu=new FileUtils();List cpu,rss,vss=new ArrayList<String>();ListUtils lu=new ListUtils();private Handler handler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();//监控详情按钮,点击后通过intent传值到新的activitycpu_btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {try {cpu= fu.readcpu("/mnt/sdcard/topInfo.txt",editText_package);rss= fu.readrss("/mnt/sdcard/topInfo.txt",editText_package);vss=fu.readvss("/mnt/sdcard/topInfo.txt",editText_package);String times=fu.read("/mnt/sdcard/totime.csv");String totalTime=fu.read("/mnt/sdcard/totaltime.csv");Intent intent = new Intent(MainActivity.this,TopDetailActivity.class);intent.putExtra("times",times);intent.putExtra("totalTime",totalTime);intent.putStringArrayListExtra("cpu", (ArrayList) cpu);//key就是自己定义一个String的字符串就行了intent.putStringArrayListExtra("vss", (ArrayList) vss);intent.putStringArrayListExtra("rss", (ArrayList) rss);startActivity(intent);} catch (Exception e) {Toast toast=Toast.makeText(MainActivity.this, "监控文件被删除", Toast.LENGTH_SHORT);toast.show();}}});//监控报告按钮button_reporte.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Thread() {@Overridepublic void run() {//在run()方法实现业务逻辑;//...//更新UI操作;handler.post(new Runnable() {@Overridepublic void run() {List<String>str_cpu= null;List<String>str_vss= null;List<String>str_rss= null;try {str_cpu = fu.readcpu("/mnt/sdcard/topInfo.txt",editText_package);str_rss=fu.readrss("/mnt/sdcard/topInfo.txt",editText_package);str_vss=fu.readvss("/mnt/sdcard/topInfo.txt",editText_package);List cpu_info=lu.StrToInt(str_cpu);List rss_info=lu.StrToInt(str_rss);List vss_info=lu.StrToInt(str_vss);double ave=lu.listAverage(cpu_info);double ave1=lu.listAverage(rss_info);double ave2=lu.listAverage(vss_info);Log.i("cpuinfo", Collections.max(str_cpu));Log.i("cpuinfo", Collections.min(str_cpu));Log.i("cpuinfo", cpu_info+"");Log.i("cpuinfo", ave+"");cpu_max.setText(Collections.max(str_cpu)+"%");cpu_min.setText(Collections.min(str_cpu)+"%");cpu_ave.setText(ave+"%");rss_max.setText(Collections.max(str_rss)+"K");rss_min.setText(Collections.min(str_rss)+"K");rss_ave.setText(ave1+"K");vss_max.setText(Collections.max(str_vss)+"K");vss_min.setText(Collections.min(str_vss)+"K");vss_ave.setText(ave2+"K");} catch (IOException e) {Toast toast=Toast.makeText(MainActivity.this, "监控失败", Toast.LENGTH_SHORT);toast.show();}catch (NoSuchElementException elementException){Toast toast=Toast.makeText(MainActivity.this, "监控应用未启动", Toast.LENGTH_SHORT);toast.show();}}});}}.start();}});//监控按钮button_top.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if(editText_times.getText().toString().equals("")||editText_min.getText().toString().equals("")){Toast toast=Toast.makeText(MainActivity.this, "监控总时间和监控时间是必填参数", Toast.LENGTH_SHORT);toast.show();}else{String total=editText_min.getText().toString();threadpool(5);//timer.start();int totalTime=Integer.parseInt(total);new CountDownTimer(totalTime*1000*60, 1000) {@Overridepublic void onTick(long millisUntilFinished) {button_top.setEnabled(false);button_top.setText("监控中");countDown.setText((millisUntilFinished/1000)+"S");}@Overridepublic void onFinish() {button_top.setEnabled(true);button_top.setText("启动监控");countDown.setEnabled(true);countDown.setText("ok");}}.start();}}});}private void initView() {editText_min=(EditText) findViewById(R.id.ed_topmin);editText_times=(EditText)findViewById(R.id.ed_toptimes);editText_package=(EditText)findViewById(R.id.ed_package);button_top=(Button)findViewById(R.id.button_topbegin);button_reporte=(Button) findViewById(R.id.button_reporte);cpu_btn=(Button)findViewById(R.id.cpu_btn);countDown=(TextView)findViewById(R.id.timer);cpu_max=(TextView)findViewById(R.id.cpu_max);cpu_min=(TextView)findViewById(R.id.cpu_min);cpu_ave=(TextView) findViewById(R.id.cpu_ave);rss_max=(TextView) findViewById(R.id.rss_max);rss_min=(TextView)findViewById(R.id.rss_min);rss_ave=(TextView) findViewById(R.id.rss_ave);vss_max=(TextView) findViewById(R.id.vss_max);vss_min=(TextView) findViewById(R.id.vss_min);vss_ave=(TextView)findViewById(R.id.vss_ave);}public void threadpool(int case_num) {ExecutorService cacheThreadPool = Executors.newFixedThreadPool(5);if (case_num == 5) {cacheThreadPool.execute(new Runnable() {// final int index =1;@Overridepublic void run() {// String times=editText_times.getText().toString();String times = editText_times.getText().toString();//监控间隔时间int times_num = Integer.parseInt(times);String min = editText_min.getText().toString();//监控总时间int min_num = Integer.parseInt(min);//次数int n = min_num * 60 / times_num;//如果监控文件不存在则创建文件if (fu.topfileIsExists("/mnt/sdcard/totaltime.csv") == false) {ShellUtils.execCommand("touch /mnt/sdcard/totaltime.csv", true);}if (fu.topfileIsExists("/mnt/sdcard/totime.csv") == false) {ShellUtils.execCommand("touch /mnt/sdcard/totime.csv", false);}fu.writeTofile("/mnt/sdcard/totaltime.csv", min);fu.writeTofile("/mnt/sdcard/totime.csv", times);ShellUtils.execCommand("top -m 5 -d " + times + " -n " + n + " >/mnt/sdcard/topInfo.txt", false);}});}}}
3.MainActivity布局代码如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_tool"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><EditTextandroid:layout_width="match_parent"android:layout_height="40dp"android:id="@+id/ed_package"android:background="@drawable/edittext_border"android:hint="输入每次监控的应用包名"/><EditTextandroid:layout_width="match_parent"android:layout_height="40dp"android:inputType="number"android:id="@+id/ed_toptimes"android:maxLength="3"android:background="@drawable/edittext_border"android:hint="输入每次监控的时间间隔单位为秒"/><EditTextandroid:layout_width="match_parent"android:layout_height="40dp"android:inputType="number"android:id="@+id/ed_topmin"android:maxLength="5"android:background="@drawable/edittext_border"android:hint="输入监控的时间单位为分钟"/></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/button_topbegin"android:text="启动监控"/><TextViewandroid:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="center"android:gravity="center"android:background="@drawable/open_a_red_envelope_centre_open"android:id="@+id/timer"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/button_reporte"android:text="监控统计"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/cpu_btn"android:text="监控详情"/></LinearLayout><TableLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TableRow><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:text=" 监控项 "/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:text=" 最大值 "/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:text=" 最小值 "/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:text=" 平均值 "/></TableRow><TableRow><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:text=" cpu指标 "/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/cpu_max"/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/cpu_min"/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/cpu_ave"/></TableRow><TableRow><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:text=" rss指标 "/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/rss_max"/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/rss_min"/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/rss_ave"/></TableRow><TableRow><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:text=" vss指标 "/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/vss_max"/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/vss_min"/><TextViewandroid:layout_height="wrap_content"android:layout_width="wrap_content"android:background="@drawable/textview_border"android:id="@+id/vss_ave"/></TableRow></TableLayout></LinearLayout>
4.String转list工具类
package com.example.monitor;import android.util.Log;import java.util.ArrayList;
import java.util.List;/*** Created by Administrator on 2017/9/6.*/public class ListUtils {public List StrToInt(List<String> str){List cpu_num=new ArrayList();for(int i = 0;i<str.size();i++){int a;String b=str.get(i);a=Integer.parseInt(b.trim());cpu_num.add(a);}return cpu_num;}public double listAverage(List<Integer> list){int sum=0;double average;for (int i = 0; i < list.size(); i++) {sum+=list.get(i);}if(list.size()==0){average=0;}else{average = sum / list.size();}Log.i("ave","======"+average);return average;}
}
5.文件处理工具类
package com.example.monitor;import android.widget.EditText;import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** Created by Administrator on 2017/8/9.*/public class FileUtils {// 判断文件是否存在public static void judeDirExists(File file) {if (file.exists()&&file.isDirectory()) {System.out.println("dir exists");} else {System.out.println("dir not exists, create it ...");file.mkdir();}}public boolean topfileIsExists(String path){try{File f=new File(path);if(!f.exists()){return false;}}catch (Exception e) {// TODO: handle exceptionreturn false;}return true;}//写入到csv中public void writeTofile(String filepath, String content){String path=filepath;File writepath=new File(path);FileWriter writer=null;try {//"E:\\YallaTest\\YallaResult.csv"// 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件writer = new FileWriter(writepath,false);writer.write(content);//\r\n表示换行//,表示换一格} catch (IOException e) {e.printStackTrace();} finally {try {if(writer != null){writer.close();}} catch (IOException e) {e.printStackTrace();}}}public String read(String path) throws IOException{BufferedReader br = new BufferedReader(new FileReader(path));StringBuffer sb = new StringBuffer();String line = null;while((line= br.readLine()) != null) {sb.append(line);}System.out.println(sb.toString()); //sb包含所有文本内容return sb.toString();}public List readcpu(String path,EditText editText) throws IOException{String aa=editText.getText().toString();BufferedReader br = new BufferedReader(new FileReader(path));List list_str=new ArrayList();String line = null;while((line= br.readLine()) != null) {if(line.contains(aa)){String m=line.substring(line.indexOf("%")-2, line.indexOf("%"));//System.out.println(m);list_str.add(m);}}// System.out.println(sb.toString()); //sb包含所有文本内容return list_str;}public List readvss(String path,EditText editText) throws IOException{String aa=editText.getText().toString();BufferedReader br = new BufferedReader(new FileReader(path));List list_str=new ArrayList();String line = null;while((line= br.readLine()) != null) {if(line.contains(aa)){String q=line.substring(line.indexOf("K")-7, line.indexOf("K"));list_str.add(q);}}// System.out.println(sb.toString()); //sb包含所有文本内容return list_str;}public List readrss(String path,EditText editText) throws IOException{String aa=editText.getText().toString();BufferedReader br = new BufferedReader(new FileReader(path));List list_str=new ArrayList();String line = null;while((line= br.readLine()) != null) {if(line.contains(aa)){String g=line.substring(line.indexOf("K",line.indexOf("K")+1 )-6, line.indexOf("K",line.indexOf("K")+1 ));list_str.add(g);}}// System.out.println(sb.toString()); //sb包含所有文本内容return list_str;}}
6.shell工具类
package com.example.monitor;import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;/*** Created by Administrator on 2017/7/13.* 有效启动shell类*/public class ShellUtils {public static final String COMMAND_SU = "su";public static final String COMMAND_SH = "sh";public static final String COMMAND_EXIT = "exit\n";public static final String COMMAND_LINE_END = "\n";private ShellUtils() {throw new AssertionError();}/*** check whether has root permission** @return*/public static boolean checkRootPermission() {return execCommand("echo root", true, false).result == 0;}/*** execute shell command, default return result msg** @param command command* @param isRoot whether need to run with root* @return* @see ShellUtils#execCommand(String[], boolean, boolean)*/public static CommandResult execCommand(String command, boolean isRoot) {return execCommand(new String[] {command}, isRoot, true);}/*** execute shell commands, default return result msg** @param commands command list* @param isRoot whether need to run with root* @return* @see ShellUtils#execCommand(String[], boolean, boolean)*/public static CommandResult execCommand(List<String> commands, boolean isRoot) {return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true);}/*** execute shell commands, default return result msg** @param commands command array* @param isRoot whether need to run with root* @return* @see ShellUtils#execCommand(String[], boolean, boolean)*/public static CommandResult execCommand(String[] commands, boolean isRoot) {return execCommand(commands, isRoot, true);}/*** execute shell command** @param command command* @param isRoot whether need to run with root* @param isNeedResultMsg whether need result msg* @return* @see ShellUtils#execCommand(String[], boolean, boolean)*/public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {return execCommand(new String[] {command}, isRoot, isNeedResultMsg);}/*** execute shell commands** @param commands command list* @param isRoot whether need to run with root* @param isNeedResultMsg whether need result msg* @return* @see ShellUtils#execCommand(String[], boolean, boolean)*/public static CommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg) {return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, isNeedResultMsg);}/*** execute shell commands** @param commands command array* @param isRoot whether need to run with root* @param isNeedResultMsg whether need result msg* @return <ul>* <li>if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and* {@link CommandResult#errorMsg} is null.</li>* <li>if {@link CommandResult#result} is -1, there maybe some excepiton.</li>* </ul>*/public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) {int result = -1;if (commands == null || commands.length == 0) {return new CommandResult(result, null, null);}Process process = null;BufferedReader successResult = null;BufferedReader errorResult = null;StringBuilder successMsg = null;StringBuilder errorMsg = null;DataOutputStream os = null;try {process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);os = new DataOutputStream(process.getOutputStream());for (String command : commands) {if (command == null) {continue;}// donnot use os.writeBytes(commmand), avoid chinese charset erroros.write(command.getBytes());os.writeBytes(COMMAND_LINE_END);os.flush();}os.writeBytes(COMMAND_EXIT);os.flush();result = process.waitFor();// get command resultif (isNeedResultMsg) {successMsg = new StringBuilder();errorMsg = new StringBuilder();successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));String s;while ((s = successResult.readLine()) != null) {successMsg.append(s);}while ((s = errorResult.readLine()) != null) {errorMsg.append(s);}}} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();} finally {try {if (os != null) {os.close();}if (successResult != null) {successResult.close();}if (errorResult != null) {errorResult.close();}} catch (IOException e) {e.printStackTrace();}if (process != null) {process.destroy();}}return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null: errorMsg.toString());}/*** result of command* <ul>* <li>{@link CommandResult#result} means result of command, 0 means normal, else means error, same to excute in* linux shell</li>* <li>{@link CommandResult#successMsg} means success message of command result</li>* <li>{@link CommandResult#errorMsg} means error message of command result</li>* </ul>** @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-5-16*/public static class CommandResult {/** result of command **/public int result;/** success message of command result **/public String successMsg;/** error message of command result **/public String errorMsg;public CommandResult(int result) {this.result = result;}public CommandResult(int result, String successMsg, String errorMsg) {this.result = result;this.successMsg = successMsg;this.errorMsg = errorMsg;}}
}
7.新建一个activity命名为TopDetailActivity
package com.example.monitor;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class TopDetailActivity extends AppCompatActivity {private TextView textView_title;ArrayList<String> cpuList,rssList,vssList = new ArrayList<String>();String title;ListView listView_detail;List<Map<String,String>> mapList=new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);getSupportActionBar().hide();setContentView(R.layout.activity_top_detail);textView_title=(TextView) findViewById(R.id.monitor_title);listView_detail=(ListView) findViewById(R.id.listview_monitor);cpuList = getIntent().getStringArrayListExtra("cpu");vssList = getIntent().getStringArrayListExtra("vss");rssList = getIntent().getStringArrayListExtra("rss");// title=getIntent().getStringExtra("title");String times=getIntent().getStringExtra("times");String totalTime=getIntent().getStringExtra("totalTime");textView_title.setText("监控总时间为"+totalTime+"分钟,监控间隔时间为"+times+"S");for(int i=0;i<vssList.size();i++){Map<String, String> items=new HashMap<String,String>();if(i==0){items.put("cpuinfo","CPU ");items.put("rssinfo","RSS ");items.put("vssinfo","VSS ");}else{items.put("cpuinfo",cpuList.get(i)+"% ");items.put("rssinfo",rssList.get(i)+"K ");items.put("vssinfo",vssList.get(i)+"K ");}mapList.add(items);}SimpleAdapter simplead = new SimpleAdapter(this, mapList,R.layout.monitor_item, new String[] { "cpuinfo", "rssinfo","vssinfo"},new int[] {R.id.monitor_cpuitem,R.id.monitor_rssitem,R.id.monitor_vssitem});listView_detail.setAdapter(simplead);}
}
8.TopDetailActivity布局代码如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="40dp"android:id="@+id/monitor_title"/><ListViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"android:id="@+id/listview_monitor"></ListView></LinearLayout>
9.新建一个布局文件monitor_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal" android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="wrap_content"android:id="@+id/monitor_cpuitem"android:layout_height="30dp"/><TextViewandroid:layout_width="wrap_content"android:id="@+id/monitor_rssitem"android:layout_height="30dp"/><TextViewandroid:layout_width="wrap_content"android:id="@+id/monitor_vssitem"android:layout_height="30dp"/></LinearLayout>
10.在drawable目录下新建edittext_border.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="#ffffff" /><stroke android:width="1dip" android:color="#afd54f"/>
</shape>
11.在drawable目录下新建textview_border.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="#ffffff" /><stroke android:width="1dip" android:color="#4fa5d5"/>
</shape>