一、Android性能优化的方面
针对Android的性能优化,主要有以下几个有效的优化方法:
- 布局优化
- 绘制优化
- 内存泄漏优化
- 响应速度优化
- ListView、RecyclerView及Bitmap优化
- 线程优化
- 其他性能优化的建立
二、布局优化
关于布局优化的思想很简单,就是尽量减少布局文件的层级。这个道理很浅显,布局中的层级少了,就意味着Android绘制时的工作量少了,那么程序的性能自然就提高了。
如何进行布局优化?
- 删除布局中无用的空间和层次,其次有选择地使用性能比较低的ViewGroup
关于有选择地使用性能比较低的ViewGroup,这就需要我们开发就实际灵活选择
如:如果布局中既可以使用LinearLayout,也可以使用RelativeLayout,那么久采用LinearLayout,因为RelativeLayout的功能比较复杂,它的布局过程需要花费更多的CPU时间。FrameLayout和LinearLayout一样都是一种简单高效的ViewGroup,因此可以考虑使用他们。
但很多时候单纯通过一个LinearLayout或者FragmentLayout无法实现产品效果,需要通过嵌套的方式来完成。这种情况下还是建议采用RelativeLayout,因为ViewGroup的嵌套相当于增加了布局的层级,同样会降低程序的性能。
- 采用标签(ViewStub)
标签主要用于布局重用
标签一般是配合使用,可以降低减少布局的层级。
ViewStub提供了按需加载的功能,当需要时才会将ViewStub中的布局加载到内存,提高了程序初始化效率。
- 避免多度绘制
过度绘制(Overdraw)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。
在多层次重叠的UI结构里面,如果不可见UI也在做绘制的操作,会导致某些像素区域被绘制多次,同时也会浪费大量CPU以及GPU资源。
过度绘制参考这篇文章。
三、绘制优化
绘制优化是指“View的onDraw方法要避免执行大量的操作”
这个主要体现在两个方面:
onDraw中不要创建新的局部对象
因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁GC,降低了程序的执行效率。
- onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅
按照Google官方给出的性能优化典范中的标准,View的绘制频率保证60fps是最佳的,这就要求每帧绘制时间不超过16ms,虽然程序很难保证16ms,虽然程序很难确保16ms这个时间,但是尽量降低onDraw方法的复杂度总是切实有效的。
四、内存泄漏优化
内存泄漏是开发过程总一个需要重视的问题,但是由于内存泄漏问题对开发人员的经验和开发一是有较高的要求,因而也是开发人员最容易犯的错误之一。
内存泄漏的优化分为两个部分:
- 在开发过程中避免写出内存泄漏的代码
- 通过一些分析工具比如MAT来找出潜在的内存泄漏,然后解决。
对应于这两个不同情况,一是了解内存泄漏的可能场景以及如何避免,二是怎么查找内存泄漏
1、什么是内存泄漏?
Java有垃圾回收机制,这使得Java程序员比C++程序员轻松很多,存储申请后不用心心念念要加一句释放,Java虚拟机会通过GC回收机制不定时回收哪些不再被需要的内存空间(注意回收的不是对象本身,而是对象占据的内存空间)
- 什么叫不再被需要的内存空间
Java没有指针,全拼引用和对象进行关联,铜鼓引用来操作对象。如果一个对象没有与任何引用关联,那么这个对象也就不太可能被使用到,回收器便会把这些“无任何引用的对象”作为目标,回收它们占据的内存空间。
- 如何分辨为对象无引用?
两种方法:
引用计数法:直接计算,简单高效,每当与一个引用该对象,这对应的计数器加一,若释放引用该对象,则计数器减一,但是这种方法无法解决相互应用的情况。
可达性分析法:这种方法设置一系列的“GC Roots”对象作为引用起点,若某个对象与起点对象之间无可达路径,这该对象就会被回收。
常见的“GC Roots”对象:
(1)方法的本地变量表
(2)方法区的静态变量
(3)方法区的常量
(4)本地方法区的native方法
- 有了回收机制,仍需要注意内存泄漏
虽然垃圾回收器会帮助我们干掉大多数的无用内存空间,但是对于还保持着引用,但逻辑上已经不会再用到的对象,垃圾回收器不会回收它们。这些对象积累在内存中,直到程序结束,就是我们常说的“内存泄漏”。
2、Android一般在什么情况下会出现内存泄漏
- 集合类泄漏
- 单例/静态变量造成的内存泄漏
- 匿名内部类/非静态内部类
- 资源未关闭造成的内存泄漏
大致可以分为上述几类,还有一些经常会听说的Handler、AsyncTask引起内存泄漏,都属于上述第3类的情况
3、Android怎样分析内存泄漏?
通过MAT(Memory Analyzer Tool),或者LeakCanary来检测Android中的内存泄漏
五、响应速度优化
响应速度优化的核心思想是:避免在主线程中做耗时操作
如果有耗时操作,可以开启子线程执行,即采用异步的方法来执行耗时操作。
如果在主线程中做了太多事情,会导致Activity启动时出现黑屏现象,甚至ANR。
关于ANR,Android中规定:activity,5s;broadcastReceiver,10s;Service,20s
超过这个时间就会出现ANR(Android Not Responding),系统无响应
为了避免ANR,可以开启子线程执行耗时操作,但是子线程不能更新UI,所以需要子线程与主线程进行通信来解决子线程执行耗时任务后,再通知主线程更新UI的场景。
这部分需要掌握Handler消息机制,AsyncTask,IntentService等内容
六、ListView/RecycleView及Bitmap优化
1、ListView/RecycleView的优化思想主要从以下几个方面入手
- 使用ViewHolder模式来提高效率
- 异步加载:耗时的操作放在异步线程中
- ListView/RecyclerView的滑动时停止加载和分页加载
具体优化的建即详情,可参考:ListView的优化
2、Bitmap优化
- 主动释放Bitmap资源
当确定这个Bitmap不再使用时,可以手动按调用recycle()方法,释放其native内存:
if(bitmap != null && !bitmap.isRecycled()){
bitmap.recycle();
bitmap = null;
}但是这样,程序重新启动或onResume()时,需要等待更长的时间,因为加载图片需要一定的时间。
调用bitmap.recycle之后,这个Bitmap如果没有被引用,就会被GC回收;如不主动调用,GC回收器回收时间不确定。
- 主动释放ImageView的资源
在实际开发中,很多情况是在xml文件中设置ImageView的src或者在代码中调用ImageView.setIamgeResource/setImageURI/setIamgeDrawable等方法设置图像,下面代码可以回收这个ImageView所对应的资源。
private static void recycleImageViewBitMap(ImageView imageView) {
if (imageView != null) {
BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
rceycleBitmapDrawable(bd);
}
}
private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
if (bitmapDrawable != null) {
Bitmap bitmap = bitmapDrawable.getBitmap();
rceycleBitmap(bitmap);
}
bitmapDrawable = null;
}
private static void rceycleBitmap(Bitmap bitmap) {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
}- 主动释放ImageView的背景资源
与释放ImageView的图片资源类似
public static void recycleBackgroundBitMap(ImageView view) {
if (view != null) {
BitmapDrawable bd = (BitmapDrawable) view.getBackground();
rceycleBitmapDrawable(bd);
}
}
public static void recycleImageViewBitMap(ImageView imageView) {
if (imageView != null) {
BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
rceycleBitmapDrawable(bd);
}
}
private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
if (bitmapDrawable != null) {
Bitmap bitmap = bitmapDrawable.getBitmap();
rceycleBitmap(bitmap);
}
bitmapDrawable = null;
}- 尽量少用PNG的图片,多实用NinePatch的图
因为手机的分辨率越来越高,图片资源在被加载后所占用的内存也会越来越大,所以要尽量边使用大的PNG图,而多实用NinePatch资源。
- 使用大图前,尽量对其进行压缩
通过BitmapFactory.Options中的inSampleSize进行采样压缩
七、线程优化
线程优化的思想
采用线程池(ThreadPoolExecutor),避免程序中存在大量的Thread,
线程池可以重用内部的线程,从而避免了线程的创建和销毁锁带来的性能开销,同时线程池还能有效地控制线程池的最大并发数,避免大量的线程因相互抢占系统资源从而导致阻塞现象的发送。
八、其他性能优化
- 避免过度的创建对象
- 不要过度使用枚举,枚举占用的内存空间要比整型大
- 常量请使用static final来修饰
- 使用一些Android特有的数据结构,如Parcelable等
- 适当采用软引用和弱引用
- 采用内存缓存和磁盘缓存
- 尽量采用静态颞部类,这样可以避免潜在的由于内部类导致的内存泄漏。