Android 性能优化之绘制优化

绘制原理

绘制过程主要由CPU进行Measure、layout、record、execute的数据计算工作,由GPU进行栅格化、渲染。CPU和GPU通过图像驱动层进行连接,CPU往图形驱动层的队列里面添加display list,GPU 从中取出来绘制。和绘制优化最紧密关联的是app的帧数fps,即每秒刷新多少次。每一帧其实是一副静止的图像,一秒内刷新多张图像,给人眼的感觉就是运动的,例如我们看的电视剧,其实也是由一幅幅的图像组成的, 只是每秒钟刷新的图像很多,看起来就动了。人脑在帧数达到60帧时,就感觉不出来卡顿。所以要想让用户感觉不到卡顿,那么就要保证我们的绘制帧数至少达到60帧,即每16.667毫秒刷新一次,如果不能在16.667毫秒刷新,那么就会让用户感觉到有卡顿。

Android系统每隔16ms就发出一个VSYNC(Vertical Synchronization)信号,这是一种定时中断信号,一旦收到,CPU就开始处理各种数据,触发UI渲染,如果UI渲染不能在16毫秒画完,那么用户就会在32毫秒内看到两副同样的图像,此时系统又发出下一次的VSYNC信号,CPU和GPU就会丢掉旧的帧,再去画新的,造成视觉上的丢帧和卡顿。

卡顿原因

  1. 布局layout复杂,无法在16ms绘制完成
  2. 同一时间动画执行次数太多,造成CPU或者GPU负载过重
  3. view过度绘制,导致某些像素在同一帧时间被重复绘制
  4. UI线程执行了一些耗时操作
  5. GC回收时间暂停过长或者频繁的GC产生大量的停顿时间

分析工具

  1. Profile GPU Rendering(GPU 呈现模式分析)

打开手机的开发者选项的“GPU呈现模式分析“开关,通过柱状图可以看到某一帧的处理时长。绿色是警戒值,应尽可能保持在绿色警戒值以下,如图所示:

在这里插入图片描述

橙色: 处理时间

红色: 执行时间

蓝色: 绘制时间

可以用这个工具去看看哪些页面的绘制存在问题。

  1. Systrace

cd 到 /Users/username/Library/Android/sdk/platform-tools/systrace目录下,参数可配置,执行:

python systrace.py -o mynewtrace.html sched freq idle am wm gfx view \ binder_driver hal dalvik camera input res

就会生成一份文件 mynewtrace.html在systrace下,也可以指定其他的路径,用chrome打开。

在这里插入图片描述

  1. 用Profiler
  2. jconsole

绘制优化手段

一 布局优化

布局优化工具:

Layout Inspector

Android Lint:

代码检查工具,Android Studio -> Analyze - > Inspect code

布局优化手段

  1. 合理使用布局,推荐用ConstraintLayout。布局简单时,可以用LinearLayout,复杂时可以用ConstraintLayout,RelativeLayout性能比不上LinearLayout,因为元素间互相依赖;

  2. include复用布局

  3. merge去除多余层级:

    一般和include并用,例如外层布局和include的布局都是同一种布局,那么可以在include的布局中最外层父布局改成.

  4. 使用ViewStub:

    当布局中存在一个布局要在具体的条件下才能显示时,或者不需要初始化时就显示,可以通过ViewStub引进来,那么初始化时它就不会加载,也不会占用系统资源,只是占了一个位置(类似于GONE),但是性能比GONE到VISIBLE要好,用它就能够提高初始化时的性能。

    ViewStub是用来定义布局的,布局通过layout的形式,例如:

 <ViewStub
            android:id="@+id/dialog"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout="@layout/view_dialog"
            app:layout_constraintTop_toBottomOf="@id/change_user"
    />
    
     stub.inflate();
     stub.setVisibility(View.VISIBLE);

注意:

ViewStub继承自View,只能被inflate一次,因为inflate之后,会将ViewStub的Parent置空,再次inflate的话,会抛出“ViewStub must have a non-null ViewGroup viewParent”,其实可以理解,ViewStub只是一个占位的,当被inflate之后,占的位置就变成它layout进来的具体布局了,而它自己本身就没有作用了。

public View inflate() {
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final View view = inflateViewNoAdd(parent);
                replaceSelfWithView(view, parent);

                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }

二 避免GPU过度绘制

  1. 避免一个区域重复绘制背景
  2. 避免View的onDraw()的同一个区域重复绘制多次,可以在onDraw()中用canvas.clipRect()来指定绘制区域,防止重复的组件发送过度绘制。

可以打开手机的“调试GPU过度绘制”查看绘制情况,颜色越深说明重复绘制次数越多,合格的页面应该以白色、蓝色为主,绿色以上区域不能超过三分一,颜色越浅越好。

性能优化之内存优化

内存泄漏

  1. 非静态内部类的静态实例
    非静态内部类的静态实例长期持有外部类的引用,导致外部类无法被回收,内部类隐式的持有外部类的引用,静态内部类属于类,不会持有外部类引用,例如:
public class TestAcy extends Activity {
    private static InnerClass innerClass;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        innerClass = new InnerClass();
    }

    private class InnerClass{
       InnerClass(){}
   }

  1. 多线程相关的匿名内部类/非静态内部类

匿名内部类持有的外部类实例Activity,由于内部线程执行时间很长,即使Activity退出了,也无法销毁,因为内部类还持有外部类的实例,解决方法就是自定义Thread,设置成静态的。

public class TestAcy extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        innerClass = new InnerClass();
        getSomeData();
    }
    
    private void getSomeData(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
 }   
  1. Handler内存泄漏
    Handler持有外部实例,有些message无法被马上处理,所以activity退出了,也无法被回收。示例:
public class TestAcy extends Activity {

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       Handler handler = new Handler(){
           @Override
           public void handleMessage(Message msg) {
               super.handleMessage(msg);
           }
       };
       handler.sendEmptyMessageDelayed(0,20000);  //这里发送了一个消息,在队列中等待20s才执行,因此即使按返回键finish了activity,也无法回收它,因为还被handler持有。
   }
}

解决方法:

(1)自定义静态Handler,持有弱引用的Activity:

public class TestAcy extends Activity {
   private MyHandler handler = new MyHandler(new WeakReference<>(this));

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       handler.sendEmptyMessageDelayed(0,20000);
   }
   
   public void show(){}
   
   private static class MyHandler extends Handler{
       private final WeakReference<TestAcy> activityref;

       public MyHandler(WeakReference<TestAcy> activityref) {
           this.activityref = activityref;
       }

       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
           if (activityref!= null && activityref.get() != null){
               activityref.get().show();
           }
       }
   }
}

(2)在onDestroy中移除所有的Handler和callback。

  1. Context

静态实例持有了Activity的Context,导致Activity无法回收。

  1. 静态View

持有activity的实例,导致activity无法回收,在onDestroy将静态View设置为空。

  1. 资源对象未关闭

例如cursor、file、IO ,在使用完毕一定要在finally中close()掉。

  1. 集合中对象没有清理

把一些对象加入了静态集合,但是没有及时清理,那么集合越来越大。

  1. Bimap

创建了较大的Bitmap,经过变化得到新的bitmap,那么旧的要及时清理。

  1. 监听器未关闭

例如添加了一些需要register和unregister的监视器,那么需要在合适的时候移除掉。

内存分析工具

1. Profiler

查看内存使用情况,内存增大以及GC回收的情况以及内存抖动,

内存抖动:短时间内发生多次内存分配和释放,原因是频繁的创建对象,很可能是循环内创建了对象,内存为了应对这种情况,就会频繁的GC,引起内存抖动,视图上看起来像锯齿一样,频繁的GC可能引起界面卡顿,对于非并行的虚拟机来说,因为它会“Stop The World”。

可以dump出文件进行分析hrof文件用MAT进行分析。

2.jconsole

3.LeakCanary

猜你喜欢

转载自blog.csdn.net/qq_26984087/article/details/89321184