安卓性能优化手册
本手册适合至少有初级经验的开发者查阅或复习相关知识使用,新手可能会看不懂。
1、java代码优化
1.1安卓如何执行代码
dvm:.java->.class->.dex->.apk
- 优化斐波那契数列:
斐波那契数列的递推公式是f(n)=f(n-1)+f(n-2),特征方程为:x2=x+1,解该方程得(1+sqrt(5))/2,(1-sqrt(5))/2.所以f(n)=Ax1n+Bx2n,带入f(0)=0,f(1)=1得A=sqrt(5)/5,B=-sqrt(5)/5.则f(n)求出。
- BigInteger:
用这个类来解决溢出问题。
斐波那契数列有如下性质:
(fn fn-1)=(fn-1 fn-2)*A,可以得出A=(1 1;1 0)
递推可得:(fn fn-1)=(fn-1 fn-2)*A=(fn-2 fn-3)*A2=…=(f1 f0)*An-1
缓存结果:
用HashMap来充当缓存,当键是整数时,用SparseArray效率更高。LruCache:
适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
-
private LruCache<integer, string=""> mJsonCache;
/**
* 缓存图片信息
*/
private LruCache<integer, bitmap=""> mBitmapCache;
public Util() {
mJsonCache = new LruCache<integer, string="">(1 * 1024 * 1024);
mBitmapCache = new LruCache<integer, bitmap="">(2 * 1024 * 1024);
}
数据结构:
安卓在这些数据结构中增加了自身的一些实现,一般是为了提高性能,LruCache\SparseArray\SparseBooleanArray\SparseIntArray\Pair
还有Arrays和Collections类。例如使用Arrays.sort对数组排序。响应能力:熟悉生命周期,例如设备方向变化时,配置Android:configChanges属性
- 推迟初始化:例如可以用android.view.ViewStub来推迟初始化。由于内存分配需要花时间,等到对象真正需要时才进行分配,也是一个很好的选择。
- StricMode:主线程不要用来访问网络等耗时操作。
- SQLite语句:加快要执行的SQL语句字符串的创建速度,在这种情况下使用+运算符来连接字符串不是最有效的方法,而使用StringBuilder对象,或调用String.format可以提高性能。
- 事务:一次性事务解决。可以使用SQLiteOpenHelper来替代手动创建数据库。
- 查询:创建cursor只获取第一列
2、NDK
主要由c/c++编写,是安卓原生开发套件。
1、创建本地方法:
public native String encode(String text, int length);
public native String decode(String text, int length);
}
2、实现JNI粘合层:
使用jdk的javah工具自动生成。
- 在项目根目录下创建jni文件夹
- 在jni文件中创建一个c文件
在java代码中,创建一个本地方法helloFromC
public native String helloFromC();
在jni中定义函数实现这个方法,函数名必须为
jstring Java_com_zhilinghui_helloworld1_MainActivity_helloFromC(JNIEnv* env, jobject obj)
返回一个字符串,用c定义一个字符串
char* cstr = "hello from c";
把c的字符串转换成java的字符串
jstring jstr = (*env)->NewStringUTF(env, cstr); return jstr;
- 在jni中创建Android.mk文件
- 在c文件中添加
安卓中如何使用c/c++代码(总结)
前面可以说的有点乱,这里简单概括一下:
- 步骤一:
在java中定义一个c方法的接口 ,相当于在java代码中定义了一个接口 接口的实现方法是C语言实现的。
public native String hello(); 步骤二:
实现C代码
方法名 严格按照jni的规范步骤三:
创建android.mk 告诉编译器 如何把c代码打包成函数库
LOCAL_PATH := $(call my-dir)步骤四:
把c代码 打包成函数库 用到了安装的环境 到相应目录下使用ndk-build打包步骤五:
在java代码中 引入库函数- 步骤六:
使用方法
3、NDK进阶
汇编优化:安卓 NDK内置了GCC编译器的功能。
要确保使用正确版本的objdump反编译目标文件和库。
ARM模式和Thumb模式。
- 色彩转换:图形程序中最常用的操作是把颜色从一种格式转换为另一种,ARGB8888和RGB565.
- RAM指令
提高性能的技巧
- 内联函数:即直接在调用处实现替换调用。在函数定义前加上”inline“关键字就可以了。
- 循环展开:展开可以积极调度(或管道化)循环以掩盖一些延迟。如果有足够的空闲寄存器使变量保持活动状态,因为通过展开相关性链展露了关键路径,这将非常有用。
- 内存预读取:1.GCC的——builtin_prefetch();2、在ARM汇编代码中使用PLD和PLDW指令,还可以使用PLI指令。
- 用LDM/STM替换LDR/STD:使用单个指令加载多个寄存器比用多个LDR指令加载寄存器快。
4、高效使用内存
java的char是16位(UTF-16),java的long是64位,而c的long是32位,long long是64位。
访问内存:
减少数据缓存读未命中几率的方法:1、在有大量数据存储在数组中时,使用尽可能小的数据类型;2、选择顺序访问而不是随机访问,最大限度的重用已在缓存中的数据。
垃圾收集:
内存泄漏
只有当某个对象不再引用时,它的内存才会被回收,当该被释放的对象引用仍然存在的时候就会发生OOM。
例如:屏幕旋转时。Eclipse中可以在DDMS中的Heap及Allocation Tracker跟踪。
也可以使用StricMode类来检测潜在在内存泄漏.
引用
强引用: 程序中绝大部分是这种引用。就是“正常”引用。
–BigIntegerbi=BigInteger.valueOf(n);//强引用
Integer i=new Integer(n);//强引用
i=null;//Integer对象变成可回收的垃圾。软、弱、虚引用:
软可及对象,垃圾收集器自己决定想回收旧回收。弱可及对象,基本会被回收。虚引用,几乎很少用,可以用来注册引用队列。
- 垃圾收集
可能会在不定的时间触发,几乎无法控制其发生,例如堆被占满不能进行内存分配时,在分配新对象要进行内存回收。
API
- ActivityManager的getMemoryInfo()、getMemoryClass()、getLargeMemoryClass();
- Debug的dumHprofData()、getMemoryInfo、getNativeHeapAllocatedSize()、getNativeHeapSize().
-
ActivityManager am=(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memInfo=new ActivityManager.MemoryInfo();
am.gettMemoryInfo(memInfo);
5、多线程和同步
线程
Thread对象。run()方法可以被复写,Runnable对象传递给Thread构造函数。记得调用start()。
线程优先级,一般是1-10(最高),默认是5.而Linux的优先级是从-20到19(最低).
AsyncTask
AsyncTask启动任务栈执行的输入参数,后台任务执行的百分比,后台任务执行返回iud结果,匿名类。
例如:后台下载文件时刷新前台进度条。
Handler和Looper
他们是多线程应用线程间通信的基石。
Handler
- Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
Handler在run()方法中创建,因为它需要被绑定到指定的Looper,这Looper就是在run()方法中调用Looper.prepare(0创建的。在线程产生之前,调用getHandler()返回null.
Looper
有消息循环的线程。
- Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所
Activity生命周期
7个生命周期
调用AsyncTask.cancel()会在doInbackground()返回后触发onCancelled(),而不是onPoseExecute().
6、性能评测和剖析
DDMS中的Traceview
查看跟踪代码的执行时间,分析哪些是耗时操作 ;可以用于跟踪方法的调用,尤其是Android Framework层的方法调用关系
TraceView罗列出了是所有监听到的方法,当然也包括Android系统很多方法的耗时,如何在这么多方法里面查找到自己关心的? 可以通过TraceView 底部的find 来查找,通常Android app都是有包名的,可以先针对某些关心的列排序后,在通过包名进行一个个查找,这些就省去自己筛选出自己app 方法耗时排行的时间。
7、延长电池续航时间
一般屏幕和wifi很耗电。
测量电池用量
可以通过检索固定的Intent。在应用启动时就获取电池当前电量,运行一段时间,在退出时再次获取电池电量。但是这个值并不准确,因为还有其他应用运行。
禁用广播接收器
只有在需要时才启用广播接收器。
- 因为BatteryManager广播的是一个sticky的intent实体,这就意味着你不用非得注册一个广播接收者来让你的程序接受这个广播,你可以仅仅就是通过调用registerReceiver这个方法,在需要添加广播接收者位置的参数上传入null,当然你也可以新建一个广播接收者,并在注册广播接收者的时候传入。
网络
最终解决问题,AT&T与来自密歇根大学的同事们的工作人员进行了深入的端到端数据传输路径的综合调查,最终发现是在设备和蜂窝网络之间复杂的相互作用的问题的根源,相互作用,是很难看到,给出分层性网络架构,故意隐藏低层协议的开发者在应用层的工作。
更多文章可以参考:http://www.research.att.com/editions/201106_home.html
位置
使用
WakeLock
例如,在用户观看视频或电影时,cpu需要做视频解码,同时保持屏幕开启,让用户能够观看。
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); WakeLock sCpuWakeLock = pm.newWakeLock(
PowerManager.FULL_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP,"okTag");
if (sCpuWakeLock!= null) {
sCpuWakeLock.release();
sCpuWakeLock = null;
}
- PARTIAL_WAKE_LOCK:保持CPU 运转,屏幕和键盘灯有可能是关闭的。
SCREEN_DIM_WAKE_LOCK:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯
SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,允许关闭键盘灯
FULL_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度
ACQUIRE_CAUSES_WAKEUP:不会唤醒设备,强制屏幕马上高亮显示,键盘灯开启。有一个例外,如果有notification弹出的话,会唤醒设备。
- ON_AFTER_RELEASE:WakeLock 被释放后,维持屏幕亮度一小段时间,减少WakeLock 循环时的闪烁情况
如果申请了partial wakelock,那么即使按Power键,系统也不会进Sleep,如Music播放时 如果申请了其它的wakelocks,按Power键,系统还是会进Sleep
但如果不领会Android设计者的意图而滥用Wake Lock API,为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态,就会成为待机电池杀手。
提醒
AlarmManage有一个AlarmManagerService,该服务程序主要维护app注册下来的各类Alarm,并且一直监听Alarm设备,一旦有Alarm触发,或者是Alarm事件发生,AlarmManagerService就会遍历Alarm列表,找到相应的注册Alarm并发出广播。
Alarm Manager会维持一个cpu的wake lock。这样能保证电话休眠时,也能处理alarm的广播。一旦alarm receiver的onReceive() 方法执行完,wake lock会迅速被释放。如果在receiver中开启一个service,有可能service还没启动,wake lock已经被释放了。所以此时要实现单独的wake lock策略。
有4种Alarm类型:
1)RTC_WAKEUP
在指定的时刻(设置Alarm的时候),唤醒设备来触发Intent。
2)RTC
在一个显式的时间触发Intent,但不唤醒设备。
3)ELAPSED_REALTIME
从设备启动后,如果流逝的时间达到总时间,那么触发Intent,但不唤醒设备。流逝的时间包括设备睡眠的任何时间。注意一点的是,时间流逝的计算点是自从它最后一次启动算起。
4)ELAPSED_REALTIME_WAKEUP
从设备启动后,达到流逝的总时间后,如果需要将唤醒设备并触发Intent。
传感器、图形
这里不做详细的介绍了
8、图形
布局优化
调用setContentView()。xml布局的到时候要减少创建对象数量,可以用不同的布局达到同样的视觉效果,消除不必要的对象,或者推迟创建对象。
嵌套线性布局会深化布局层次,从而导致布局和按键处理变慢。当显示10个及以上的项目的时候就需要相对布局。
用
<merge />
标签来合并布局
-
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/add"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/delete"/>
* 使用<include />
标签重用布局
<include layout="@layout/titlebar"/>
- ViewStub:
推迟加载
<ViewStub
android:id="@+id/viewstub_demo_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_marginTop="10dip"
android:layout="@layout/viewstub_demo_text_layout"/>
布局工具
在sdk->tools目录下,有hierarchyviewer和layoutopt,可以查看和分析布局
OpenGL ES
OpenGL ES (为OpenGL for Embedded System的缩写) 为适用于嵌入式系统的一个免费二维和三维图形库。
任何复杂的2D或是3D图形都是通过这三种几何图形构造而成的。 OpenGL ES提供了两类方法来绘制一个空间几何图形:
public abstract void glDrawArrays(int mode, int first, int count) 使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。
public abstract void glDrawElements(int mode, int count, int type, Buffer indices) ,可以重新定义顶点的顺序,顶点的顺序由indices Buffer 指定。
mode列表:GL_POINTS 绘制独立的点、GL_LINE_STRIP绘制一条线段、GL_LINE_LOOP绘制一条封闭线段(首位相连)、GL_LINES绘制多条线段、GL_TRIANGLES绘制多个三角形(两两不相邻)、GL_TRIANGLE_STRIP绘制多个三角形(两两相邻)、GL_TRIANGLE_FAN以一个点为顶点绘制多个相邻的三角形 对应顶点除了可以为其定义坐标外,还可以指定颜色,材质,法线(用于光照处理)等。 glEnableClientState 和 glDisableClientState 可以控制的pipeline开关可以有:GL_COLOR_ARRAY (颜色) ,GL_NORMAL_ARRAY (法线), GL_TEXTURE_COORD_ARRAY (材质), GL_VERTEX_ARRAY(顶点), GL_POINT_SIZE_ARRAY_OES等。 对应的传入颜色,顶点,材质,法线的方法如下: glColorPointer(int size,int type,int stride,Buffer pointer) glVertexPointer(int size, int type, int stride, Buffer pointer) glTexCoordPointer(int size, int type, int stride, Buffer pointer) glNormalPointer(int type, int stride, Buffer pointer) OpenGL ES 内部存放图形数据的Buffer有COLOR ,DEPTH (深度信息)等,在绘制图形只前一般需要清空COLOR 和 DEPTH Buffer。通用的矩阵变换指令 这里介绍对应指定的坐标系(比如viewmodel, projection或是viewport) Android OpenGL ES支持的一些矩阵运算及操作。 矩阵本身可以支持加减乘除,对角线全为1的4X4 矩阵成为单位矩阵Identity Matrix 。
将当前矩阵设为单位矩阵的指令 为glLoadIdentity().
矩阵相乘的指令glMultMatrix*() 允许指定任意矩阵和当前矩阵相乘。
选择当前矩阵种类glMatrixMode(). OpenGL ES 可以运行指定GL_PROJECTION,GL_MODELVIEW等坐标系,后续的矩阵操作将针对选定的坐标。
将当前矩阵设置成任意指定矩阵glLoadMatrix*()
在栈中保存当前矩阵和从栈中恢复所存矩阵,可以使用glPushMatrix()和glPopMatrix()
特定的矩阵变换平移glTranslatef(),旋转glRotatef() 和缩放glScalef()
纹理压缩
具体可以在官网看:http://developer.android.com/training/graphics/opengl/index.html
着色、场景复杂性、消隐、渲染、
只有当场景变化才渲染帧。
9、RenderScript
RenderScript 是一种低级的高性能编程语言,用于3D渲染和处理密集型计算(3D播放等和关于CPU密集型的计算)。一直以来Android 在绘图性能的表现一直差强人意,引入NDK之后才有所改善,而在Honeycomb 中发布了RenderScript 这一杀手级在Framework 后,大大的增加了Android 本地语言的执行能力和计算能力。
RenderScript 在机器上进行第一遍编译,然后在目标设备上进行最后一遍编译(Just-In-Time Compiling),因而带来更高效的原生二进制代码。这也就是意味着,凡是支持RenderScript 的设备都可以运行你的代码。不用管什么架构。
目前 ,RenderScript 带来的代码只能在主处理器上运行,它会自动生成可利用多个核心的代码(如果设备上有多个核心)。就因此,编译出来的程序是针对该机器的最佳优化,这解决了Device Fragmentation,也就是说开发者再也不必担心使用者的手机、平板够不够好、有没有GPU…等等问题,全都交给RenderScript 去担心就好。没有GPU,RenderScript 写好的程序就交由CPU来处理(背后的编译技术其实是使用的LLVM)。