Android性能优化

Android性能优化

  本文主要介绍一些有效的性能优化方法,包括布局优化、绘制优化、内存泄露优化、响应速度优化、ListView优化、Bitmap优化、线程优化以及一些性能优化的建议,同时还介绍了对ANR日志的分析方法以及内存泄露分析工具MAT的使用。

1、布局优化

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

  如何进行布局优化呢?首先删除布局中无用的控件和层级,其次有选择的使用性能较低的ViewGroup,比如RelativeLayout。如果布局既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,这是因为RelativeLayout的功能比较复杂,它的布局过程需要花费更多的CPU时间。FrameLayout和LinearLayout一样都是一种简单高效的ViewGroup,因此可能考虑使用它们,但是很多时候单纯通过一个LinearLayout或者FrameLayout无法实现产品效果,需要通过嵌套的方式来完成。这种情况下还是建议采用RelativeLayout,因为ViewGroup的嵌套就相当于增加了布局的层级,同样会降低程序的性能。

  布局优化的另一种手段是采用标签,标签和ViewStub。

a.标签

  标签主要用于布局重用,可将一个指定的布局文件加载到当前布局文件中。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include layout="@layout/view_toolbar" />

    <FrameLayout
        android:id="@+id/fragment_containers"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
LinearLayout>

通过这种方式,就不用把view_toolbar这个布局文件再重复写一遍了,这就是标签的好处。

b.标签

  标签一般和标签一起使用从而减少布局的层级。在上面示例中,由于当前布局是一个竖直方向的LinearLayout,这个时候如果被包含的布局文件中也采用了竖直方向的LinearLayout,那么显然被包含的布局文件中的LinearLayout是多余的,通过标签就可以去掉多余的那一层LinearLayout,如下所示


<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <Button
        android:id="@+id/bt_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Button1" />

    <Button
        android:id="@+id/bt_2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Button2" />
merge>

c.ViewStub

  ViewStub继承了View,它非常轻量级且宽高都是0,因此它本身不参与任何的布局和绘制过程。ViewStub的意义在于按需加载所需的布局文件,在实际开发中,有很多布局文件在正常情况下不会显示,比如网络异常时的界面,这个时候就没有必要再整个界面初始化的时候将其加载进来,通过ViewStub就可以做到在使用的时候再加载,提高了程序初始化时的性能。

<ViewStub
        android:id="@+id/stub_import"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/layout_network_error" />

其中stub_import是ViewStub的id,而panel_import是layout/layout_network_error这个布局的根元素的id。如何做到按需加载呢?在需要加载ViewStub中的布局时,可以按照如下两种方式进行:

((ViewStub)findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);

或者

 View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

当ViewStub通过setVisibility或者inflate方法加载后,ViewStub就会被它内部的布局替换掉,这个时候ViewStub就不再是整个布局结构中的一部分了。另外,目前ViewStub还不支持标签。

2、绘制优化

  绘制优化是指View的onDraw方法要避免执行大量的操作,这主要体现在两个方面。
首先,onDraw中不要创建新的局部对象,这是因为onDraw方法可能被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁的gc,降低了程序的执行效率。

另一方面,onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅。按照Google官方给出的性能优化典范中的标准,View的绘制帧率保证60fps是最佳的,这就要求每一帧的绘制时间不超过16ms,虽然程序有时候很难保证16ms,但是尽量降低onDraw方法的复杂度总是切实有效的。

3、内存泄露优化

  内存泄露在开发过程中是一个需要重视的问题,这个需要开发人员有一定的经验和意识,一个是避免写出有内存泄露的代码,另一方面是通过一些分析工具比如MAT来找出潜在的内存泄露继而解决。

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

public class MainActivity extends Activity{

    private static Context sContent;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sContent = this;
    }
}

Activity无法正常销毁,因此静态变量sContext引用了它。

public class MainActivity extends Activity{

    private static View sView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sView = new View(this);
    }
}

上面代码改造下,同理sView是一个静态变量,它内部持有了当前Activity,所以Activity仍然无法释放。

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

  单例模式带来的内存泄露是我们容易忽视的,例如一个单例TestManager可以接收外部的注册监听并存储起来。

public class TestManager{
    private List mListeners = new ArrayList<>();

    private static class Single{
        public static final TestManager sInstance = new TestManager();
    }

    private TestManager(){ }

    public static TestManager getInstance(){
        return Single.sInstance;
    }

    public synchronized void registerListener(OnChangeListener listener){
        if(!mListeners.contains(listener)){
            mListeners.add(listener);
        }
    }

    public synchronized void unregisterListener(OnChangeListener listener){
        mListeners.remove(listener);
    }

    public interface OnChangeListener{
        void onChange();
    }

接着再让Activity实现OnChangeListener接口,并向TestManager注册监听,若缺少反注册的操作,那么就会造成内存泄露,因为TestManager持有Activity的对象。

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

  从Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,如果在Activity中播放此类动画而且没有在onDestroy中去停止动画,那么动画会一直播放下去,尽管已经无法在界面上看到动画效果了。这时,Activity中的View会被动画持有,而View又持有了Activity,最终Activity无法释放

    @Override
    protected void onCreate(@Nullable 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(); // 没有调用停止动画
    }

4、响应速度优化和ANR日志分析

  响应速度优化的核心思想是避免在主线程中做耗时操作,采用异步的方式执行耗时操作。响应速度过慢更多地体现在Activity的启动速度上面,如果主线程中做太多事情,会导致Activity启动时出现黑屏现象,甚至ANR。Android规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现ANR,而BoradcastReceiver如果10秒钟之内还未执行完操作也会出现ANR。在实际开发中,ANR是很难从代码上发现的,如果开发过程中遇到ANR,那么怎么定位问题呢?其实当一个进程发生ANR了以后,系统会在/data/anr目录下创建一个traces.txt,通过分析这个文件就能定位出ANR的原因了。

adb pull /data/anr/traces.txt

5、ListView和Bitmap优化

建模中… 等待更新

6、线程优化

建模中… 等待更新

7、一些性能优化建议

  • 避免创建过多的对象
  • 不要过多使用枚举,枚举占用的内存空间要比整型大
  • 常量请使用 static final 来修饰
  • 使用一些Android特有的数据结构,比如SparseArray和Pair等,它们都具有更好的性能
  • 适当使用软引用和弱引用
  • 采用内存缓存和磁盘缓存
  • 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露。

内存泄露分析之MAT工具

  MAT的全称是Eclipse Memory Analyzer,它是一款强大的内存泄露分析工具,MAT不需要安装,下载后解压即可使用,下载地址为http://www.eclipse.org/mat/downloads.php。对于Eclipse来说,MAT也有插件版,但是不建议使用插件版,因为独立版使用起来更加方便,即使不安装Eclipse也可以正常使用,当然前提是具有内存分析后的hprof文件。

为了采用MAT来分析内存泄露,下面模拟一种简单的内存泄露

public class TestActivity extends Activity{

    private static Context sContext;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        sContext = this;
    }
}

  编译安装,然后打开DDMS界面,其中AndroidStudio的DDMS位于Monitor中。接着用鼠标选中要分析的进程,然后使用待分析应用的一些功能,这样做是为了将尽量多的内存泄露暴露出来,然后点击Dump HPROF file这个按钮, 等待一小段时间即可导出一个hprof后缀的文件,
Android性能优化_第1张图片

导出的hprof文件并不能使用它来进行分析,因为它不能被MAT直接识别,需要通过hprof-conv命令转换一下。hprof-conv命令是Android SDK提供的工具,它位于Android SDK的platform-tools目录下:

hprof-conv 源文件 输出文件
如: hprof-conv com.ctao.music.debug.hprof com.ctao.music.debug.conv.hprof

经过上面的步骤,接下来就可以直接通过MAT来进行内存分析了,通过菜单打开刚才转换后的com.ctao.music.debug.conv.hprof
Android性能优化_第2张图片

MAT提供了很多功能,但是最常用的只有Histogram和DominatorTree,通过Histogram可以直接地看出内存中不同类型的buffer的数量和占用的内存大小,而DominatorTree则把内存中的对象安装从大到小的顺序进行排序,并且分析对象之间的引用关系,内存泄露分析就是通过DominatorTree来完成的。

Android性能优化_第3张图片

为了分析内存泄露,我们需要分析DominatorTree里面的信息,一般这里是不会直接显示出来的,这个时候需要按照从大到小的顺序去排查一遍。如上图,选中一个占用最大的,然后单击右键->Path To GC Roots->exclude wake/soft references[排除弱引用和软引用,因为二者都有较大几率被gc回收,它们并不造成内存泄露]

Android性能优化_第4张图片

如此就直接定位到我们的内存泄露对象sContenxt。另外MAT还有很多其他功能,这里就不再一一介绍了。读者直接体验吧。


学习摘录于《Android开发艺术探索》第15章

你可能感兴趣的:(android,性能优化,内存泄露,Android,性能优化,内存泄露检查)