以一定的频率来监控app的cpu,内存,流量,电量等性能指标,输出到xls文件中。再通过xls软件即可方便的绘制出性能曲线,用于app客户端的性能测试。同时,该app支持对安装在手机里的app进行monkey测试,而不需要连接数据线通过adb工具来启动monkey测试。

程序实现的思路很简单

1、获取已安装的应用app list,排除系统应用,因为我们的目标是去测试各种应用app

2、上述app list中,选择一个待测试的app,即可进行手工测试,或者monkey测试,同时测试过程中自动采集相关的性能数据,cpu ,内存,流量,电量。


那么如何获取已安装应用的app list呢?

1、通过PackageManager的getInstalledApplications方法获取所有安装的app list

/**
	 * get information of all applications.
	 * 
	 * @param context
	 *            context of activity
	 * @return packages information of all applications
	 */
	private List<ApplicationInfo> getPackagesInfo(Context context) {
		PackageManager pm = context.getApplicationContext().getPackageManager();
		List<ApplicationInfo> appList = pm
				.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
		return appList;
	}



2、过滤掉系统应用

/**
	 * get information of all running processes,including package name ,process
	 * name ,icon ,pid and uid.
	 * 
	 * @param context
	 *            context of activity
	 * @return running processes list
	 */
	public List<Programe> getRunningProcess(Context context) {
		Log.i(LOG_TAG, "get running processes");

		ActivityManager am = (ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE);
		List<RunningAppProcessInfo> run = am.getRunningAppProcesses();
		PackageManager pm = context.getPackageManager();
		List<Programe> progressList = new ArrayList<Programe>();
		boolean launchTag;

		for (ApplicationInfo appinfo : getPackagesInfo(context)) {
			launchTag = false;
			Programe programe = new Programe();
			if (((appinfo.flags & ApplicationInfo.FLAG_SYSTEM) > 0)
					|| ((appinfo.processName != null) && (appinfo.processName
							.equals(PACKAGE_NAME)))) {
				continue;
			}
			for (RunningAppProcessInfo runningProcess : run) {
				if ((runningProcess.processName != null)
						&& runningProcess.processName
								.equals(appinfo.processName)) {
					launchTag = true;
					programe.setPid(runningProcess.pid);
					programe.setUid(runningProcess.uid);
					break;
				}
			}
			programe.setPackageName(appinfo.processName);
			programe.setProcessName(appinfo.loadLabel(pm).toString());
			if (launchTag) {
				programe.setIcon(appinfo.loadIcon(pm));
			}
			progressList.add(programe);
		}
		return progressList;
	}



3、将上述app list放到listview中

private class ListAdapter extends BaseAdapter {
		List<Programe> programe;
		int tempPosition = -1;

		/**
		 * save status of all installed processes
		 * 
		 * @author andrewleo
		 */
		class Viewholder {
			TextView txtAppName;
			ImageView imgViAppIcon;
			RadioButton rdoBtnApp;
		}

		public ListAdapter() {
			programe = processInfo.getRunningProcess(getBaseContext());
		}

		@Override
		public int getCount() {
			return programe.size();
		}

		@Override
		public Object getItem(int position) {
			return programe.get(position);
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
}



其中listview的每一个item中均有一个RadioButton,选中即可进行后续的操作,手工测试或者monkey测试。


那么又如何进行性能数据采集呢?

这就需要用到service了。被测程序启动的时候,同时启动监测service,该service负责相关数据的采集。

电量的监控,service注册一个电量变化时间的额广播接收器即可。

流量的监控,android2.2以上的版本,可以直接调用TrafficStats这个类读取app相关的流量值,包括上下行,总流量等。低于android2.2版本的手机,则需要读取系统文件。这个要根据不同的手机来区别对待,因为碎片化的原因,很多手机存放cpu 内存 流量等值的文件并不是在一个特定的目录。各有不同。

cpu监控,均是通过读文件的方式进行获取.

某一具体进程的cpu占用,可以在这个目录读取,/proc/processPid /stat

整个系统的cpu占用,可以在这个目录读取,/proc/stat

/**
	 * read the status of CPU.
	 * 
	 * @throws FileNotFoundException
	 */
	public void readCpuStat() {
		Log.i(LOG_TAG,"READ CPU ,PID="+pid);
		String processPid = Integer.toString(pid);
		String cpuStatPath = "/proc/" + processPid + "/stat";
		try {
			// monitor cpu stat of certain process
			RandomAccessFile processCpuInfo = new RandomAccessFile(cpuStatPath,
					"r");
			String line = "";
			StringBuffer stringBuffer = new StringBuffer();
			stringBuffer.setLength(0);
			while ((line = processCpuInfo.readLine()) != null) {
				stringBuffer.append(line + "\n");
			}
			String[] tok = stringBuffer.toString().split(" ");
			processCpu = Long.parseLong(tok[13]) + Long.parseLong(tok[14]);
			processCpuInfo.close();
		} catch (FileNotFoundException e) {
			Log.e(LOG_TAG, "FileNotFoundException: " + e.getMessage());
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			// monitor total and idle cpu stat of certain process
			RandomAccessFile cpuInfo = new RandomAccessFile("/proc/stat", "r");
			String[] toks = cpuInfo.readLine().split("\\s+");
			idleCpu = Long.parseLong(toks[4]);
			totalCpu = Long.parseLong(toks[1]) + Long.parseLong(toks[2])
					+ Long.parseLong(toks[3]) + Long.parseLong(toks[4])
					+ Long.parseLong(toks[6]) + Long.parseLong(toks[5])
					+ Long.parseLong(toks[7]);
			cpuInfo.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}




内存的监控,可以直接调用ActivityManager的getProcessMemoryInfo方法,然后然会PSS值。

/**
	 * get the memory of process with certain pid.
	 * 
	 * @param pid
	 *            pid of process
	 * @param context
	 *            context of certain activity
	 * @return memory usage of certain process
	 */
	public int getPidMemorySize(int pid, Context context) {
		Log.i(LOG_TAG,"GET PSS MEM,PID="+pid);
		ActivityManager am = (ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE);
		int[] myMempid = new int[] { pid };
		Debug.MemoryInfo[] memoryInfo = am.getProcessMemoryInfo(myMempid);
		memoryInfo[0].getTotalSharedDirty();

		// int memSize = memoryInfo[0].dalvikPrivateDirty;
		// TODO PSS
		int memSize = memoryInfo[0].getTotalPss();
		// int memSize = memoryInfo[0].getTotalPrivateDirty();
		return memSize;
	}





又如何做到定时采集呢?

直接在service启动的时候,调用一个timertask,或者用handler的postDelayed方法。其中task里面再次调用handler的postDelayed方法,即可做到定时采集。

private Runnable task = new Runnable() {

		public void run() {
			if (!isServiceStop) {
				dataRefresh();
				handler.postDelayed(this, delaytime);
			} else {
				Intent intent = new Intent();
				intent.putExtra("isServiceStop", true);
				intent.setAction("com.klaus.test.myservice");
				sendBroadcast(intent);
				stopSelf();
			}
		}
	};



当然,这个service是前台运行的。在onstart函数中调用service的startForeground方法即可。


又是如何不通过数据线连接手机就可以执行monkey测试的呢?

直接在app中调用如下命令行即可

String cmd = "monkey -p "+packagename+" -s 98345678 --pct-touch "+touchevent+" --pct-motion "+motionevent+"    --throttle 500  1000000";

当然需要在su模式下才能运行。

android自带的monkey测试,以前都是通过adb shell去驱动的,现在直接在手机里运行,不再通过adb桥接。


主线程UI,service,monkey线程,如何通信?

通过Handler机制进行通信,通过handler,三者可以进行通信。