布局优化
- 删除布局中无用的控件和层级,其次有选择的使用性能较低的
ViewGroup
,比如RelativeLayout
,ConstraintLayout
。同样层级的话更优先选择使用LinearLayout
,因为RelativeLayout
的布局过程需要花费更多的CPU
时间。如果需要嵌套的方式来做,更建议采用RelativeLayout
,因为ViewGroup
的嵌套就相当于增加了布局的层级,同样会降低程序的性能。 - 采用
<include>
标签,如果多个布局中需要一个相同的layout
,可以使用<include>
来避免写重复的布局文件。 - 采用
<merge>
标签。一般和<include>
标签一起使用而减少布局的层级。 ViewStub
。它非常轻量级,而且高/宽都是 0,因此它本身不参与任何的布局和绘制过程。ViewStub
的意义在于按需加载所需的布局文件,在实际开发中,有很多布局文件在正常情况下不会显示,这个时候就没有必要在整个界面初始化的时候将其加载进来,通过ViewStub
就可以做到在使用的时候再加载。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<ViewStub
android:id="@+id/stub_import"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:inflatedId="@+id/id_login"
android:layout="@layout/refresh_layout" />
其中 stub_import 是 ViewStub 的 id,id_login 是 refresh_layout 这个布局根元素的 id, 需要加载 ViewStub 的布局时,有两种方式:
((ViewStub)findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
或者
View layout = ((ViewStub)findViewById(R.id.stub_import)).inflate();
绘制优化
绘制优化是指 View
的 onDraw
方法要避免执行大量的操作。
onDraw
里不要创建新的局部对象 不要做耗时的操作。
内存泄露优化
内存泄露优化分为两个方面,一方面是在开发过程中避免写出有内存泄漏的代码,另一方面是通过一些分析工具找出潜在的内存泄漏继而解决。
场景1 静态变量导致的内存泄漏
下面的代码将导致 Activity
无法正常销毁,因为静态变量 sContext
引用了它。1
2
3
4
5
6
7
8
9
10
11
12
13
14public class MainActivity extends Activity{
private static final String TAG = "MainActivity";
private static Context sContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sContext = this;
}
}
解决方案:
- 在
onDestroy
中释放sContext = null
。 - 变成虚引用
private static WeakReference<Context> context
。
场景2 单例模式导致的内存泄漏
如下所示,提供一个单例模式的 TestManager
,TestManager
可以接受外部的注册并将外部的监听器存储起来。
1 | public class TestManager{ |
让 Activity
实现 OnDataArrivedListener
并向 TestManager
注册监听,如下所示。下面的代码由于缺少解注册的操作而引起内存泄漏,泄露的原因是 Activity
的对象被单例模式的 TestManager
所持有,而单例模式的特点是其生命周期和 Application
保持一致,因此 Activity
无法被及时释放。
1 | public class MainActivity extends Activity{ |
解决方案:
在 onDestroy
中 TestManager.getInstance().unregisterListener(this);
。
场景3 属性动画导致的内存泄漏
属性动画中有一类无限循环的动画,如果在 Activity
中播放此类动画并且没有在 onDestroy
中去停止动画,那么动画会一直播放下去,尽管已经无法在页面上看到动画效果了,并且这个时候 Activity
的 View
会被动画持有,而 View
又持有了 Activity
,最终 Activity
无法释放。
解决方法
是在 onDestroy
中停止动画。
场景4 非静态内部类导致的内存泄漏
比如 Handler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public class MainActivity extends AppCompatActivity {
private MyHandler handler;
private static class MyHandler extends Handler {
private WeakReference<MainActivity> reference;
public MyHandler(MainActivity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
//TODO: do something
reference.get().doSomeThing();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new MyHandler(this);
}
@Override
protected void onDestroy() {
handler.removeMessages(1);
super.onDestroy();
}
}
响应速度优化和 ANR 日志分析
响应速度优化的核心思想是避免在主线程中做耗时操作。响应速度过慢更多的体现在 Activity
的启动速度上,如果在主线程中做太多事情,会导致 Activity
启动时出现黑屏现象,甚至出现 ANR(Application not response)
。Android
规定,Activity
如果 5s 内无法响应屏幕触摸事件或者键盘输入事件就会出现 ANR
, BroadcastReceiver
如果 10s 之内还未执行完操作也会出现 ANR
,Service
如果 20s 之内还未执行完操作也会出现 ANR
。
当一个进程发生了 ANR
以后,系统会在data/anr
目录下创建一个 traces.txt
,通过分析这个文件就能定位出 ANR
的原因。
ListView 优化 和 Bitmap 优化
ListView 优化方式
- 布局复用
- 使用
ViewHolder
,减小findViewById
的使用,避免在getView
中执行耗时操作 - 在列表快速滑动时不适合开启大量的异步任务的。
- 数据可以分页加载。
- 可以尝试开启硬件加速来使
ListView
的滑动更加流畅。 - 尽量让 ItemView 的 Layout 层次结构简单。
- ItemView 元素避免半透明。
- 尽量能保证 Adapter 的 hasStableIds() 返回 true,这样在 notifyDataSetChanged() 的时候,如果 id 不变,ListView 将不会重新绘制这个 View,达到优化的目的。
- 每个 ItemView 不能太高,特别是不要超过屏幕的高度。
Bitmap 优化方式
- 加载合适尺寸的图片(二次采样,inSampleSize)。
线程优化
线程优化的思想是使用线程池,避免程序中存在大量的 Thread
。线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还可以有效的控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致堵塞现象的发生。
一些性能优化建议
- 避免创建过多的对象。
- 不要过多使用枚举,枚举占用的内存空间要比整形大。(不过枚举要是能影响性能的话,就太差了)
- 常量请使用
static final
来修饰。 - 使用
Android
特有的数据结构,比如SparseArray
和Pair
等。 - 适当使用软引用和弱引用。
- 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏。