0%

Android 性能优化(一):Android 的性能优化方法

1. Google 官方 Android 性能优化专题

2. 布局优化概述

  • 思想:尽量减少布局文件的层级,减少 Android 的绘制工作量

  • 思路1:

    • 删除布局中无用的控件和层级,同时有选择性地使用性能较好的 ViewGroup
    • RelativeLayout 相对 FrameLayout 和 LinearLayout 功能较复杂、布局过程花费更多 CPU 时间。但同时可以实现较复杂的产品效果以及避免简单的层级嵌套
  • 思路 2:使用 <include><merge><ViewStub> 等标签

    • <include> 标签

      • 可以重用布局文件
      • 只支持以 android:layout_ 开头的属性,比如 android:layout_widthandroid:layout_height,其他属性是不支持的
      • android:id 这个属性是一个特例。如果 指定了这个 id 属性,同时被包含的布局文件的根元素也指定了 id 属性,那么 指定的 id 属性为准
      • 如果 标签指定了 android:layout_* 这种属性,那么要求 android:layout_widthandroid:layout_height 必须存在,否则其他 android:layout_* 形式的属性无法生效
    • <merge> 标签

      • <merge> 一般和 <include> 一起使用从而减少布局的层级
    • <ViewStub> 标签

      • ViewStub 继承了 View, 它非常轻量级且宽高都是 0,因此它本身不参与任何的布局和绘制过程

      • ViewStub 的意义在于按需加载所需的布局文件,提高了程序初始化时的性能

        1
        2
        3
        4
        5
        6
        7
        <ViewStub
        android:id="@+id/stub_import"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/layout_network_error"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />
        1
        2
        3
        // 两种方式按需加载 ViewStub
        ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
        View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
      • 目前 ViewStub 还不支持 <merge> 标签

3. 绘制优化概述

  • 含义:View 的 onDraw() 方法要避免执行 大量的操作

  • 思路 1:onDraw() 中不要创建新的局部对象

    • onDraw() 方法可能会被频繁调用,瞬间产生的大量的临时对象不仅会占用过多的内存还会导致系统更加频繁 gc,会降低程序的执行效率
  • 思路 2:onDraw() 中不要做耗时任务,也不能执行成千上万次的循环操作

    • 尽管每次循环都很轻量级,但大量循环仍会十分抢占 CPU,会导致 View 的绘制过程不流畅
    • Google 官方的性能优化典范的标准是:View 的绘制帧率保证 60 fps 是最佳的,这就要求每帧的绘制时间不超过 16ms(16ms = 1000/60)
    • 虽然程序很难保证 16ms 这个时间,但尽量降低 onDraw() 方法复杂度总是切实有效的

4. 内存泄漏优化概述

  • 思路

    • 开发过程中避免写出有内存泄漏的代码
    • 使用性能分析工具解决潜在的内存泄漏
  • 场景 1:静态变量导致的内存泄漏

    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;
    private static View sView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    sContext = this;
    sView = new View(this);
    }
    }
  • 场景 2:单例模式导致的内存泄漏

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

    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) {
    mOnDataArrivedListener.remove(listener);
    }

    public interface OnDataArrivedListener {
    public void onDataArrived(Object data);
    }
    }
    1
    2
    3
    4
    5
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TestManager.getInstance().registerListener(this);
    }
    • 上面代码缺少解注册的操作所以会引起内存泄漏,内存泄漏的原因是 Activity 的对象被单例模式的 TestManager 所持有
    • 单例模式的特点是其生命周期和 Application 保持一致,因此 Activity 的对象无法及时释放导致内存泄漏
  • 场景 3:属性动画导致的内存泄漏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mButton = (Button) findViewById(R.id.button1);

    ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0, 360).setDuration(2000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.start();
    // animator.cancel();
    }
    • 从 Android 3.0 开始,Google 提供了属性动画,属性动画中有一类无限循环的动画
    • 如果在 Activity 中播放此类动画且没有在 onDestroy() 中停止动画,那么动画会一直播放下去
    • 尽管已经无法在界面上看到动画效果了,但此时 Activity 的 View 会被动画持有,而 View 又持有了 Activity,最终 Activity 被动画间接持有无法释放导致内存泄漏
    • 解决方法是在 Activity 的 onDestroy() 方法中调用 animator.cancel() 来停止动画

5. 响应速度优化和 ANR 日志分析概述

  • 避免在主线程做耗时操作,可以采用异步方式在子线程中执行耗时操作
  • 当出现 ANR,系统会创建 /data/anr/traces.txt 文件
    • ANR 场景 1:Activity 5 秒内无响应、BroadcastReceiver 10 秒内无响应
    • ANR 场景 2:子线程和主线程竞争同步锁

6. ListView 和 Bitmap 优化概述

  • ListView

    • 采用 ViewHolder 并避免在 getView() 方法中执行耗时操作
    • 根据列表的滑动状态控制任务的执行频率,比如当列表快速滑动时显然不适合开启大量的异步任务
    • 尝试开启硬件加速使 ListView 的滑动更加流畅
  • Bitmap

    • 主要是通过 BitmapFactory.Options 根据需要对图片进行采样
    • 采样过程中主要用到了 BitmapFactory.OptionsinSampleSize 参数

7. 线程优化概述

  • 思想:采用线程池,避免程序中存在大量的 Thread
  • 线程池可以重用内部的线程,从而避免了线程创建和销毁所带来的性能开销
  • 同时线程池还能有效控制线程池的最大并发数,避免大量的线程因互相抢占系统资源导致阻塞

8. 性能优化建议

  • 避免创建过多的对象
  • 不用过多使用枚举,枚举占用的内存空间要比整型大
  • 使用 staticfinal 来修饰常量
  • 使用一些 Android 特有的性能较好的数据结构,比如 SparseArrayPair
  • 适当使用软引用弱引用
  • 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏
-------------------- 本文结束感谢您的阅读 --------------------