一、常见性能问题
1、ANR:
-
ANR全名Application Not Responding, 也就是"应用无响应". 当操作在一段时间内系统无法处理时, 系统层面会弹出如图那样的ANR对话框.
- 产生原因:在主线程(UI线程)里面做了太多的阻塞耗时操作,例如文件读写, 数据库读写, 网络查询等等.
- 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等)
- BroadcastReceiver在10s内无法结束
参考文章:App优化之ANR详解: https://www.jianshu.com/p/6d855e984b99
- 产生原因:在主线程(UI线程)里面做了太多的阻塞耗时操作,例如文件读写, 数据库读写, 网络查询等等.
2、OOM:
- OOM全称Out of Memory,也就是内存溢出
- 内存泄漏会导致内存溢出
二、Android的性能优化方法
1、布局优化
-
思想:尽量减少布局文件的层级
-
方法:有选择的使用ViewGroup
- 性能较低:RelataiveLayout(需要处理相对定位)
- 简单高效:FrameLayout和LinearLayout
-
通过标签简化:
- < include >
- < merge >:一般与搭配使用
- < ViewStub>
- 继承了View,轻量级,宽高都是零
- 本身不参与任何布局和绘制过程
- 意义在于按需加载所需的布局文件(使用时再加载)
- 当ViewStub被加载后就会被其内部的布局替换掉,此时ViewStub就不再是整个布局结构中的一部分了
- 加载的两个方法
或((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
- 目前ViewStub还不支持
2、绘制优化
-
思想:View的onDraw方法中要避免执行大量的操作
-
方法:
- (1)onDraw()中不要创建新的局部对象
- 因为onDraw方法可能会被频繁调用,这样在一瞬间可能会产生大量的临时对象
- (2)onDraw方法中不要做耗时的操作
- 要避免大量循环导致CPU时间片被抢占,出现绘制不流畅的情况
- 官方优化标准帧率:60fps
- 即每帧的绘制时间不超过16ms(16ms = 1000ms / 60)
- (1)onDraw()中不要创建新的局部对象
3、内存泄漏优化
-
思想:
- 一方面是在开发过程中避免写出内存泄漏的代码
- 另一方面是通过一些分析工具比如MAT来找出潜在的内存泄漏继而解决
-
场景1:静态变量导致的内存泄漏
- 静态持有因为其使用的生命周期不一致而导致内存泄露
- 改进:可以在适当的时候讲静态量重置为null,使其不再持有引用,这样也可以避免内存泄露
- 静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。
//Info内部持有了当前的Activity,所以Activity仍然无法释放 public class MainActivity extends AppCompatActivity { private static Info sInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (sInfo != null) { sInfo = new Info(this); } } } class Info { public Info(Activity activity) { } }
- 静态持有因为其使用的生命周期不一致而导致内存泄露
-
场景2:单例模式导致的内存泄漏
- 单例模式因为其生命周期与Application保持一致而常常导致内存泄漏
public class AppSettings { private static AppSettings sInstance; private Context mContext; private AppSettings(Context context) { this.mContext = context; } public static AppSettings getInstance(Context context) { if (sInstance == null) { sInstance = new AppSettings(context); } return sInstance; } }
-
在调用getInstance(Context context)方法的时候传入的context参数是Activity、Service等上下文,就会导致内存泄露
- 在获取单例时传入的Context的生命周期与单例的生命周期不一致,当退出Context时,该Context就没有用了,但是单例仍然持有对Context的引用,导致此Context无法正常回收,从而导致内存泄漏
-
改进:将引用的生命周期改到和单例模式一样长
- eg:可以将context参数改为全局的上下文
//全局的上下文Application Context就是应用程序的上下文 //和单例的生命周期一样长 private AppSettings(Context context) { this.mContext = context.getApplicationContext(); }
- eg:可以将context参数改为全局的上下文
-
场景3:属性动画导致的内存泄露
- 属性动画中有一类无限循环动画,在Activity中播放此类动画且没有在onDestroy中停止动画,则动画会一直播放下去,无论界面上是否有动画效果。此时Activity的View被动画持有,View又持有Activity,最终导致Activity无法被正常释放
- 改进:在Activity的onDestroy()中调用animator.cancel()
4、响应速度优化和ANR日志分析
-
核心思想:避免在主线程中去做耗时操作
-
出现:
- 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等)
- BroadcastReceiver在10s内无法结束.
-
定位问题:通过系统在
/data/anr
目录下创建的文件 traces.txt 分析adb pull /data/anr/traces.txt .
5、ListView和Bitmap优化
6、线程优化
- 思想:采用线程池
- 线程池可以重用内部的线程
- 避免程序中存在大量的Thread
- 避免了Thread创建和销毁带来的性能开销
- 避免了大量Thread因为互相抢占资源从而导致阻塞现象发生
7、一些性能优化的小建议
- 避免创建过多的对象
- 不要过多的使用枚举
- 常量请使用static final来修饰
- 使用一些Android特有的数据结构,比如SparseArray和Pair等,它们都具有更好的性能
- 适当的使用软引用和弱引用
- 采用内存缓存和磁盘缓存
- 尽量采用静态内部类,避免潜在的由于内部类而导致的内存泄漏
三、内存泄漏分析之MAT工具
- MAT:Eclipse Memory Analyzer