Android性能优化

319 阅读8分钟

一、Android性能优化的方面

针对Android的性能优化,主要有以下几个有效的优化方法:

  • 布局优化
  • 绘制优化
  • 内存泄漏优化
  • 响应速度优化
  • ListView、RecyclerView及Bitmap优化
  • 线程优化
  • 其他性能优化的建立


二、布局优化

关于布局优化的思想很简单,就是尽量减少布局文件的层级。这个道理很浅显,布局中的层级少了,就意味着Android绘制时的工作量少了,那么程序的性能自然就提高了。


如何进行布局优化?

  • 删除布局中无用的空间和层次,其次有选择地使用性能比较低的ViewGroup

关于有选择地使用性能比较低的ViewGroup,这就需要我们开发就实际灵活选择

如:如果布局中既可以使用LinearLayout,也可以使用RelativeLayout,那么久采用LinearLayout,因为RelativeLayout功能比较复杂,它的布局过程需要花费更多的CPU时间FrameLayoutLinearLayout一样都是一种简单高效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一般在什么情况下会出现内存泄漏

  1. 集合类泄漏
  2. 单例/静态变量造成的内存泄漏
  3. 匿名内部类/非静态内部类
  4. 资源未关闭造成的内存泄漏

大致可以分为上述几类,还有一些经常会听说的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等
  • 适当采用软引用和弱引用
  • 采用内存缓存和磁盘缓存
  • 尽量采用静态颞部类,这样可以避免潜在的由于内部类导致的内存泄漏。