android性能

android性能测试指南
 

Android 性能测试,跟 pc 性能测试一样分为客户端及服务器,但在客户端上的性能测试分为 2 类:

  • 一类为 rom 版本的性能测试
  • 一类为应用的性能测试

对于应用性能测试,包括很多测试项,如启动时间、内存、CPU、GPU、功耗、流量等。 但针对 rom 版本的性能测试,一般关注功耗。

先写下内存篇

内存的采集:

Android的内存的采集这边介绍三种方式:

1,通过Dumpsys 来取值

adb shell dumpsys meminfo

这里可以看到当前所有进程的内存信息!

如果你要看详细的内存:

adb shell dumpsys meminfo pakagename or Pid

看其中的Size 可以发现 Native Heap 和Dalvik Heap 占据了Heap Size
dalvik就是我们平常说的java堆,我们创建的对象是在这里面分配的。
对于内存的限制 这里纠正一下:是 dalvik heap不能超过最大限制,跟Native heap没有关系!
最大限制查看:

#查看单个应用程序最大内存限制 adb shell getprop|grep heapgrowthlimit

得到结果:

|[dalvik.vm.heapgrowthlimit]:  [96m]

这个96M是单个程序限制最大内存,而meminfo 里面的dalvik heap size 的最大值若果超出了96m 那就很可能会发生OOM
dalvik.vm.heapgrowthlimit和dalvik.vm.heapsize都是java虚拟机的最大内存限制,应用如果不想在dalvik heap达到heapgrowthlimit限制的时候出现OOM,需要在Manifest中的application标签中声明android:largeHeap=“true”,声明后应用dalvik heap 达到heapsize的时候才会出现OOM!

注:设备的不一样 最大内存限制也可能不一样

现在大多数手机 的android程序内存一般限制在96M以上甚至更高,也可能更低。

3,用/system/xbin/procrank工具 来取值很直观

adb shell procrank

VSS – Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS – Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS – Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS – Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

USS 是针对某个进程开始有可疑内存泄露的情况, 是一个程序启动了会产生的虚拟内存,一旦这个程序进程杀掉就会释放!

3,使用ActivityManager的getMemoryInfo(ActivityManager.MemoryInfo outInfo)(这个方法是写一个简单的app去监控的时候用到的,轻便简单)

private  void  GetMemory ( )  {  final  ActivityManager  activityManager  =  ( ActivityManager )  getSystemService ( ACTIVITY_SERVICE ); ActivityManager . MemoryInfo  info  =  new  ActivityManager . MemoryInfo ();  activityManager . getMemoryInfo ( info );  Log . i ( tag , "系统剩余内存:" +( info . availMem  >>  10 )+ "k" );  Log . i ( tag , "系统是否处于低内存运行:" + info . lowMemory );  Log . i ( tag , "当系统剩余内存低于" + info . threshold + "时就看成低内存运行" ); }

availMem:表示系统剩余内存

lowMemory:它是boolean值,表示系统是否处于低内存运行

hreshold:它表示当系统剩余内存低于好多时就看成低内存运行

我用过以上三种最多,其实Top 也可以 还有很多方法都可以。

adb shell top

内存拿到后怎么去用呢?

这里我用的方法是用java封装Adb shell dumpsys meminfo再用字符串截取 打印的方式

public  static  String  GetMemory ( String  packageName )  throws  IOException ,  InterruptedException  {  String  str3 = null ;  Runtime runtime  =  Runtime . getRuntime ();  Process  proc  =  runtime . exec ( "adb shell dumpsys meminfo " + packageName );  try  {  if ( proc . waitFor ()  !=  0 )  {  System . err . println ( "exit value = "  +  proc . exitValue ());  }  BufferedReader  in  =  new  BufferedReader ( new InputStreamReader (  proc . getInputStream ()));  StringBuffer  stringBuffer  =  new  StringBuffer ();  String  line  =  null ;  while  (( line  = in . readLine ())  !=  null )  {  stringBuffer . append ( line + " " );  }  String  str1 = stringBuffer . toString ();  String str2 = str1 . substring ( str1 . indexOf ( "Objects" )- 60 , str1 . indexOf ( "Objects" ));  str3 = str2 . substring ( 0 , 10 );  str3 . trim ();  }  catch ( InterruptedException  e )  {  System . err . println ( e );  } finally {  try  {  proc . destroy ();  }  catch  ( Exception  e2 )  {  }  }  return  str3  ;  }  }

截取好之后呢 可以跟 其他的一些系统资源值拼在一起打印出来:

拿到这些值之后可以配合手工或自动化来做数据收集,你会看到有些步骤内存占用很高或者Cpu消耗也会较高,这样你就可以去check一下 关于这个步骤相关 的Code

关于Android 的Cpu占用率需要注意以下三种情况:

1.空闲状态下的应用CPU消耗情况 简单说这种情况呢就是说被测应用在系统资源非常空闲的情况下的占用率,比如只开一个被测应用

2.中等规格状态下的应用CPU消耗情况 简单说这种情况就是后台已经有几个应用在运行已经并且消耗了系统的一些资源的情况下进行测试。

3.满规格状态下的应用CPU消耗情况 这个就不要说了,你们懂得!

数据采集方案:

1.

adb shell dumpsys cpuinfo


这里可以看到所有进程的Cpu占用率

大家看第一个应用CPU占用率68%,这个过程是在用户(user)中花61%的时间,并在内核空间(kernel)花费7.1%的时间。

如果你想筛选出你自己的应用的话可以用下面这一段:

adb shell dumpsys cpuinfo |grep packagename

2.使用top命令:

进入Adb shell

adb shelltop -m 10 -s cpu

可查看占用cpu最高的前10个程序(-t 显示进程名称,-s 按指定行排序,-n 在退出前刷新几次,-d 刷新间隔,-m 显示最大数量)

如果你想筛选出你自己的应用的话可以用下面这一段:

adb shell top -n 1| grep PackageName

拿到这些数据怎么用

1,你可以从代码里面获取:

(dumpsys)

adb shell dumpsys cpuinfo  public  static  String  GetCpu ( String  packageName )  throws  IOException  {  String  str3 = null ;  Runtime runtime  =  Runtime . getRuntime ();  Process  proc  =  runtime . exec ( "adb shell dumpsys cpuinfo $" + packageName );  try  {  if ( proc . waitFor ()  !=  0 )  {  System . err . println ( "exit value = "  +  proc . exitValue ());  }  BufferedReader  in  =  new  BufferedReader ( new InputStreamReader (  proc . getInputStream ()));  StringBuffer  stringBuffer  =  new  StringBuffer ();  String  line  =  null ;  while  (( line  = in . readLine ())  !=  null )  {  stringBuffer . append ( line + " " );  }  String  str1 = stringBuffer . toString ();  String str2 = str1 . substring ( str1 . indexOf ( packageName ), str1 . indexOf ( packageName )+ 28 );  str3 = str2 . substring ( 18 , 23 );  }  catch ( InterruptedException  e )  {  System . err . println ( e );  } finally {  try  {  proc . destroy ();  }  catch  ( Exception  e2 )  {  }  }  return  str3 ;  }  }

(Top)

public  static  double  cpu ( String  PackageName )  throws  IOException  {  double  Cpu  =  0 ;  try {  Runtime  runtime  = Runtime . getRuntime ();  Process  proc  =  runtime . exec ( "adb shell top -n 1| grep " + PackageName );  try  {  if  ( proc . waitFor ()  !=  0 )  { System . err . println ( "exit value = "  +  proc . exitValue ());  }  BufferedReader  in  =  new  BufferedReader ( new  InputStreamReader ( proc . getInputStream ()));  StringBuffer  stringBuffer  =  new  StringBuffer ();  String  line  =  null ;  while  (( line  =  in . readLine ())  !=  null )  { stringBuffer . append ( line + " " );  }  String  str1 = stringBuffer . toString ();  String str3 = str1 . substring ( str1 . indexOf ( PackageName )- 43 , str1 . indexOf ( PackageName ));  String  cpu =  str3 . substring ( 0 , 4 ); cpu = cpu . trim ();  Cpu = Double . parseDouble ( cpu );  }  catch  ( InterruptedException  e )  {  System . err . println ( e );  } finally {  try  { proc . destroy ();  }  catch  ( Exception  e2 )  {  }  }  }  catch  ( Exception  StringIndexOutOfBoundsException )  {  System . out . print ( "请检查设备是否连接" );  }  return  Cpu ;  }

2,直接 adb shell cat进去proc/cpuinfo/下面:

public  String []  getCpuInfo ( )  {  String  str1  =  "/proc/cpuinfo" ;  String  str2 = "" ;  String []  cpuInfo ={ "" , "" };  String []  arrayOfString ;  try  { FileReader  fr  =  new  FileReader ( str1 );  BufferedReader  localBufferedReader  =  new  BufferedReader ( fr ,  8192 );  str2  = localBufferedReader . readLine ();  arrayOfString  =  str2 . split ( "\\s+" );  for  ( int  i  =  2 ;  i  <  arrayOfString . length ;  i ++)  {  cpuInfo [ 0 ]  = cpuInfo [ 0 ]  +  arrayOfString [ i ]  +  " " ;  }  str2  =  localBufferedReader . readLine ();  arrayOfString  =  str2 . split ( "\\s+" );  cpuInfo [ 1 ]  += arrayOfString [ 2 ];  localBufferedReader . close ();  }  catch  ( IOException  e )  {  }  return  cpuInfo ;  }

取完你可以这么用》:

配合一些场景去采集数据:

这样可以看到每个步骤消耗的资源情况

然后汇总数据分析(最好多取几次求平均值):



流量篇

Android 2.2之前
对于Android2.2 的流量 版本以前的系统的流量信息都存放在 proc/net/dev(或者 proc/self/net/dev)文件下,读取文件然后对其进行解析就行了。读取某一个应用的流量,则读取proc/uid_stat/uid /tcp_rcv 文件进行解析(注:模拟器下不存在这个目录)。如需查看某个应用的流量信息,可以通过以下命令来实现:

adb  devices  列出所有设备  adb  - s  设备名称  shell  进入对应的设备  cd  proc  进入设备的属性目录  cd  uid_stat  进入  user  id  状态目录,每个应用程序在安装的时候系统会为每个应用分配一个对应的  uid  ls  列出  uid_stat  目录下所有应用对应的  user  id  目录  cd uid  进入对应应用的  uid  目录  ls  查看对应  uid  目录下的  tcp_rcv    tcp_snd  目录  cat  tcp_rcv  查看该应用接收的数据信息  cat tcp_snd  查看该应用发送的数据信息

Android 2.2之后

我这里有两种办法:

第一种
通过PID下面的net/dev
先找到应用的PID

adb shell ps

这边拿到PID:21896 然后在去/proc目录下的PID/net/dev面可以看到:

adb shell cat /proc/ "+Pid+"/net/dev "

这边的wlan0代表wifi 上传下载量标识! 上传下载量单位是字节可以/1024换算成KB
这里可以看到下载的字节数 、数据包 和 发送的字节数 、数据包

小技巧:wlan0这些值如何初始化0 很简单 你打开手机飞行模式再关掉就清0了

第二种

通过proc/net/xt_qtaguid/stats

在说第二种获取流量方法之前先给这边先给大家说下uid

uid的获取可以在对应的PID下面去查看status,里面会查到uid

adb shell cat /proc/<pid>/status

下面这个方法是通过PackageManager去取:

try  {  PackageManager  pm  =  getPackageManager ();  ApplicationInfo  ai  =  pm . getApplicationInfo ( "PackageName" , PackageManager . GET_ACTIVITIES );  Log . d ( "!!" ,  "!!"  +  ai . uid );  }  catch  ( NameNotFoundException  e )  {  e . printStackTrace ();  }

拿到UID后呢继续:

adb shell cat /proc/net/xt_qtaguid/stats | grep uid

其中第6和8列为 rx_bytes(接收数据)和tx_bytes(传输数据)包含tcp,udp等所有网络流量传输的统计。
一个uid可能对应多个 进程,所以这有两行流量是累加的就求和就行。

用java去获取打印
我这边是用先获取PID然后调用!你可以把获取PID作为一个变量传到GetFlow里面来!
我这边只获取下载流量,你可以把上传下载的流量都获取出来!

//获取PID  public  static  String  PID ( String  PackageName )  throws  IOException  {  String  PID = null ;  Runtime  runtime  = Runtime . getRuntime ();  Process  proc  =  runtime . exec ( "adb shell ps |grep " + PackageName );  try  {  if  ( proc . waitFor ()  !=  0 )  { System . err . println ( "exit value = "  +  proc . exitValue ());  }  BufferedReader  in  =  new  BufferedReader ( new InputStreamReader (  proc . getInputStream ()));  StringBuffer  stringBuffer  =  new  StringBuffer ();  String  line  =  null ;  while  (( line =  in . readLine ())  !=  null )  {  stringBuffer . append ( line + " " );  }  String  str1 = stringBuffer . toString ();  String str2 = str1 . substring ( str1 . indexOf ( " " + PackageName )- 46 , str1 . indexOf ( " " + PackageName ));  String  str3  = str2 . substring ( 0 , 7 ); str3  =  str3 . trim ();  PID = str3 ;  }  catch  ( InterruptedException  e )  {  System . err . println ( e );  } finally {  try  {  proc . destroy ();  }  catch ( Exception  e2 )  {  }  }  return  PID ;  }  //获取下载流量  public  static  double  GetFlow ( String  PackageName )  throws  IOException  { double  FlowSize = 0 ;  String  Pid = PID ( PackageName );  try {  Runtime  runtime  =  Runtime . getRuntime ();  Process  proc  = runtime . exec ( "adb shell cat /proc/" + Pid + "/net/dev" );  try  {  if  ( proc . waitFor ()  !=  0 )  {  System . err . println ( "exit value = "  + proc . exitValue ());  }  BufferedReader  in  =  new  BufferedReader ( new  InputStreamReader (  proc . getInputStream ())); StringBuffer  stringBuffer  =  new  StringBuffer ();  String  line  =  null ;  while  (( line  =  in . readLine ())  !=  null )  { stringBuffer . append ( line + " " );  }  String  str1 = stringBuffer . toString ();  String str2 = str1 . substring ( str1 . indexOf ( "wlan0:" ), str1 . indexOf ( "wlan0:" )+ 90 );  String  str4 = str2 . substring ( 7 , 16 );  str4  =  str4 . trim (); int  Flow = Integer . parseInt ( str4 );  FlowSize = Flow / 1024 ;  }  catch  ( InterruptedException  e )  {  System . err . println ( e );  } finally {  try  { proc . destroy ();  }  catch  ( Exception  e2 )  {  }  }  }  catch  ( Exception  StringIndexOutOfBoundsException )  {  }  return  FlowSize ;  }

获取每秒下载流量:

public  static  double  Flow ( String  PackageName )  throws  IOException ,  InterruptedException  {  double Flow1 = GetFlow ( PackageName );  Thread . sleep ( 1000 );  double  Flow = GetFlow ( PackageName )- Flow1 ; //System.out.println(GetFlow()-Flow1);  return  Flow  ;  }

场景设计

拿到流量值后在步骤前 将流量打印,再步骤完成后再打印一遍,再用步骤完成的流量值减去之前的流量值 得到这个步骤所消耗的流量!

场景案例:

拓展

下面的方法都是集成在Android 内部的方法:(仅供参考)

Android的TrafficStats类
前四个读取的/proc/net/dev里面的数据

Android 架构对流量的统计通过一个 TrafficStats 类可以直接获取  获取总接受流量 TrafficStats . getTotalRxBytes ()   获取总发送流量 TrafficStats . getTotalTxBytes ());  获取不包含 WIFI 的手机 GPRS 接收量 TrafficStats . getMobileRxBytes ());  获取不包含 Wifi 的手机 GPRS 发送量 TrafficStats . getMobileTxBytes ());  统计某一个进程的总接收量 TrafficStats . getUidRxBytes ( Uid ));  统计某一个进程的总发送量 TrafficStats . getUidTxBytes ( Uid )); package  cn . sunzn . trafficmanger ; import  android.app.Activity ; import android.net.TrafficStats ; import  android.os.Bundle ; import  android.view.Menu ; public  class  MainActivity  extends  Activity  { public  void  onCreate ( Bundle  savedInstanceState )  {  super . onCreate ( savedInstanceState ); setContentView ( R . layout . activity_main );  /** 获取手机通过 2G/3G 接收的字节流量总数 */  TrafficStats . getMobileRxBytes ();  /** 获取手机通过 2G/3G 接收的数据包总数 */  TrafficStats . getMobileRxPackets ();  /** 获取手机通过 2G/3G 发出的字节流量总数 */ TrafficStats . getMobileTxBytes ();  /** 获取手机通过 2G/3G 发出的数据包总数 */  TrafficStats . getMobileTxPackets ();  /** 获取手机通过所有网络方式接收的字节流量总数(包括 wifi) */  TrafficStats . getTotalRxBytes ();  /** 获取手机通过所有网络方式接收的数据包总数(包括 wifi) */  TrafficStats . getTotalRxPackets ();  /** 获取手机通过所有网络方式发送的字节流量总数(包括 wifi) */ TrafficStats . getTotalTxBytes ();  /** 获取手机通过所有网络方式发送的数据包总数(包括 wifi) */  TrafficStats . getTotalTxPackets (); /** 获取手机指定 UID 对应的应程序用通过所有网络方式接收的字节流量总数(包括 wifi) */  TrafficStats . getUidRxBytes ( uid );  /** 获取手机指定 UID 对应的应用程序通过所有网络方式发送的字节流量总数(包括 wifi) */  TrafficStats . getUidTxBytes ( uid );  }  public boolean  onCreateOptionsMenu ( Menu  menu )  {  getMenuInflater (). inflate ( R . menu . activity_main ,  menu );  return  true ;  } }

启动时间

关于应用的启动时间的测试,分为三类:

1. 首次启动 --应用首次启动所花费的时间
2. 非首次启动 --应用非首次启动所花费的时间
3. 应用界面切换--应用界面内切换所花费的时间

那么如何来做启动时间的测试呢,一般我们分为2类,一类为使用软件来测试,一类为使用硬件来测试,首先我们说说软件测试的方法,可能大部分人都比较通晓使用 android 提供的 DisplayManager 来获取 activity 的启动时间吧,在这里我简单说下如何通过批处理来 DIY

  • 通过日志过滤关键字 Displayed 来过滤所有 activity 所打印的,记录日志通过 adb logcat>/address/logcat.txt 然后使用 find “Displayed” /address/logcat.txt>/newaddress/fl.txt
  • 通过 activity 名来过滤获取所测应用 find “ActivityName” /newaddress/fl.txt>/newaddress/last.txt
  • 通过计算 activity 最后剩余的时间之和即可(这里可以使用 excel 表格自动相加也可以使用算法,我就不详细介绍了)

除了 DisplayManager 的打印时间方法后,还有通过关注 am 的启动时间及 DisplayManager 打印的结束时间,通过两者时间之间想减也能得到应用的启动时间,还有可以通过 PowerManager 来计算打印时间,在应用启动的时候,我们可以关注 ActivityManager-Launch 的变化来计算应用的启动时间,还有可以通过截图统计启动时间,对于自研应用,最效率的莫过于直接在程序中插入打印时间的节点了

说完了软件测试的方法,接下来我们聊聊硬件测试,这里我们可以使用高速相机或者手机,采用录像的方法把应用启动过程给录制下来,然后通过人工数帧或者程序数帧的方式计算启动时间

接下来我们聊聊大家不常关注的测试项-  功耗

功耗测试主要从以下几个方面入手进行测试

  • 测试手机安装目标APK前后待机功耗无明显差异
  • 常见使用场景中能够正常进入待机,待机电流在正常范围内.
  • 长时间连续使用应用无异常耗电现象

功耗测试的方法分为两类,一类为软件测试,一类为硬件测试

我们先说说软件测试,这里我们会聊聊一些DIY的思路,软件测试一般分为2类,

  • 第一种采用市场上提供的第三方工具,如金山电池管家之类的。

  • 第二种就是自写工具进行,这里一般会使用3种方法

    • 第一种基于android提供的PowerManager.WakeLock来进行,
    • 第二种比较复杂一点,功耗的计算=CPU消耗+Wake lock消耗+数据传输消耗+GPS消耗+Wi-Fi连接消耗
    • 第三种通过 adb shell dumpsys battery来获取

接着说硬件测试,在这里我们一般使用万用表或者功耗仪进行测试,使用功耗仪测试的时候,需要制作假电池来进行的,有些不能拔插电池的手机还需要焊接才能进行功耗测试

  
 
 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值