Android 性能优化

布局优化

  • 删除布局中无用的控件和层级,其次有选择的使用性能较低的 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();

绘制优化

绘制优化是指 ViewonDraw 方法要避免执行大量的操作。

onDraw 里不要创建新的局部对象 不要做耗时的操作。

内存泄露优化

内存泄露优化分为两个方面,一方面是在开发过程中避免写出有内存泄漏的代码,另一方面是通过一些分析工具找出潜在的内存泄漏继而解决。

场景1 静态变量导致的内存泄漏

下面的代码将导致 Activity 无法正常销毁,因为静态变量 sContext 引用了它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public 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;
}
}

解决方案:

  1. onDestroy 中释放 sContext = null
  2. 变成虚引用 private static WeakReference<Context> context

场景2 单例模式导致的内存泄漏

如下所示,提供一个单例模式的 TestManagerTestManager可以接受外部的注册并将外部的监听器存储起来。

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
public class TestManager{
private List<OnDataArrivedListener> mOnDataArrivedListeners = new ArrayList<>();

private static class SingletonHolder {
public static final TestManager INSTANCE = new TestManager();
}

private TestManager(){

}

public static TestManager getInstance(){
return SingletonHolder.INSTANCE;
}

public synchronized void registerListener(OnDataArrivedListener listener){
if(!mOnDataArrivedListeners.contains(listener)){
mOnDataArrivedListeners.add(listener);
}
}

public synchronized void unregisterListener(OnDataArrivedListener listener){
mOnDataArrivedListeners.remove(listener);
}

public interface OnDataArrivedListener{
void onDataArrived(Object data);
}
}

Activity 实现 OnDataArrivedListener 并向 TestManager 注册监听,如下所示。下面的代码由于缺少解注册的操作而引起内存泄漏,泄露的原因是 Activity 的对象被单例模式的 TestManager 所持有,而单例模式的特点是其生命周期和 Application 保持一致,因此 Activity 无法被及时释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public 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);

TestManager.getInstance().registerListener(this);
}
}

解决方案:

onDestroyTestManager.getInstance().unregisterListener(this);

场景3 属性动画导致的内存泄漏

属性动画中有一类无限循环的动画,如果在 Activity 中播放此类动画并且没有在 onDestroy 中去停止动画,那么动画会一直播放下去,尽管已经无法在页面上看到动画效果了,并且这个时候 ActivityView 会被动画持有,而 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
33
public 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 内无法响应屏幕触摸事件或者键盘输入事件就会出现 ANRBroadcastReceiver 如果 10s 之内还未执行完操作也会出现 ANRService 如果 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 特有的数据结构,比如 SparseArrayPair 等。
  • 适当使用软引用和弱引用。
  • 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×

keyboard_arrow_up 回到顶端