Android性能优化(三)-绘制优化

1、Android UI渲染简述

一、屏幕刷新机制

在一个典型的显示系统中,通常包括CPU、GPU、display三个部分,其中CPU负责计算数据,把计算好的数据交给GPU,GPU对图形数据进行渲染,渲染好后放到buffer中存起来,而后display负责把buffer的数据呈现到屏幕上。java

在Android中也是如此,由CPU/GPU准备好数据,存入buffer,display每隔一段时间去buffer里取数据,而后显示出来android

display在Android中,读取的频率是固定的,为16ms。之因此是16ms是由于人眼与大脑之间的协做没法感知超过60fps的画面更新。canvas

vsync.jpg

咱们在使用APP的时候,当界面出现卡顿不流畅的状况,是由于当前界面UI的处理超过了16ms,会占用下一个16ms,这就致使16ms*2都是同一帧,也就是“卡”了。缓存

drop_frame.jpg

了解了Android渲染机制后,咱们来分析app为何会超过16ms的重绘时间,一般有如下缘由:性能优化

一、布局层级不合理markdown

二、布局存在过分绘制app

针对上述状况,咱们接下来说述一些常见的监控手段和布局和优化手段异步

2、监控手段

一、使用Layout Inspater检查布局层级

Layout Inspater是AndroidStudio自带的工具,用于分析布局层级ide

一、在链接的设备或模拟上运行你的应用工具

二、点击Tools > Layout Inspector

三、在出现的Choose Process对话框中,选择你想要检查的应用进程,而后点击OK。

四、默认状况下,Choose Process 对话框仅会为 Android Studio 中当前打开的项目列出进程,而且该项目必须在设备上运行。 若是想要检查设备上的其余应用,请点击 Show all processes。 若是正在使用已取得 root 权限的设备或者没有安装 Google Play 商店的模拟器,那么您会看到全部正在运行的应用。 不然,您只能看到能够调试的运行中应用。

layout_inspector.jpg

View Tree:视图在布局中的层次结构

Screenshot:带每一个视图可视边界的设备屏幕截图。

Properties Table:选定视图的布局属性

其中,咱们最须要使用的是View Tree,像本案例中,设置页的每个栏目,由SettingItem包着RelativeLayout,其中SettingItem这个自定义View自己又继承了RelativeLayout,其实就多余了,能够用merge标签进行优化,下降层级

固然,最好使用约束布局,能够大大下降层级

二、使用调试GPU过分绘制功能检查过分绘制

从开发者模式中找到调试GPU过分绘制功能开关,打开

过分绘制显示.jpg

  • 蓝色、绿色、浅红、深红
  • 分为四个等级,其中蓝色为可接受的,当出现红色就应该要优化了

这种过分绘制常见于background的设置

三、使用Choreographer监控帧率

public class FPSMonitor implements Choreographer.FrameCallback, Runnable{

    private HandlerThread mHandlerThread;

    private long startTime = -1;

    private long endTime = -1;

    private final int MONITOR_TIME = 1000;

    private Handler mWorkHandler;

    private int mFpsCount;

    @Override
    public void doFrame(long frameTimeNanos) {
        if (startTime == -1) {
            startTime = frameTimeNanos;
        }
        mFpsCount++;
        //超过一秒了,发消息到工做线程,计算帧率
        long duration = (frameTimeNanos - startTime) / 1000000L;
        if (duration >= MONITOR_TIME) {
            endTime = frameTimeNanos;
            mWorkHandler.post(this);
        } else {
            //没到一秒,设定下一帧监听
            Choreographer.getInstance().postFrameCallback(this);
        }
    }

    @Override
    public void run() {
        //计算帧率
        long duration = (endTime - startTime) / 1000000L;
        float frame = 1000.0f * mFpsCount / duration;
        Log.i("Restart", "当前帧率: " + frame);
        //开启下一秒的计算
        start();

    }

    public void start() {
        if (mHandlerThread == null) {
            mHandlerThread = new HandlerThread("FPS Monitor Thread");
            mHandlerThread.start();
            mWorkHandler = new Handler(mHandlerThread.getLooper());
        }
        //重置计算值
        startTime = -1;
        endTime = -1;
        mFpsCount = 0;
        //设置帧绘制监听器
        Choreographer.getInstance().postFrameCallback(this);
    }
}

复制代码

四、使用setFactory2统计某个View建立耗时

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        //注意:要在super.onCreate(savedInstanceState);以前调用才不会报错
        LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                long start = System.currentTimeMillis();
                AppCompatDelegate delegate = getDelegate();
                View view = delegate.createView(parent, name, context, attrs);
                long duration = System.currentTimeMillis() - start;
                Log.i("Restart", name + "的绘制耗时: " + duration);
                return view;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        });
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
    }
复制代码

3、常见的布局优化手段

一、使用merge标签下降层级

下面介绍一个最经常使用的场景:

//假定自定义View为RelativeLayout
public class LoginButton extends RelativeLayout {
 	。。。
}
//在xml标签中使用merge做为根标签,而不要再次使用RelativeLayout做为根标签,能够省去一个层级
复制代码

二、使用ViewStub

ViewStub的使用场景为当一个布局可能须要加载,也可能不须要的状况。假如布局delayInflateLayout可能不须要也可能须要。则能够用ViewStub标签,以下代码所示,在布局文件中使用。

<ViewStub android:id="@+id/contentPanel" android:inflatedId="@+id/inflatedStart" android:layout="@layout/delayInflateLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" />
复制代码

再在代码中控制是否真正加载它

//调用inflate则会真正的加载它,可是只能调用一次
viewStub.inflate();
复制代码

三、使用clipRect

clipRect的功能能够理解为在一个大的画布中,用一些大小可变的矩形一个一个来裁切,在某一个矩形内,绘制想要绘制的图形,超出的不进行绘制,当咱们的app这样进行写自定义View的时候,能够避免view与view之间的叠加,从而产生同一个像素点被绘制屡次的状况,原理就是这样。举一个案例:

clipRect.jpg

上述的三张图,相互重叠的状况,onDraw方法优化以下:

@Override
protected void onDraw(Canvas canvas) {

    super.onDraw(canvas);

    canvas.save();
    canvas.translate(20, 120);
    for (int i = 0; i < mCards.length; i++)
    {
        canvas.translate(120, 0);
        canvas.save();
        if (i < mCards.length - 1)
        {
            //裁剪画布,减小没必要要的绘制
            canvas.clipRect(0, 0, 120, mCards[i].getHeight());
        }
        canvas.drawBitmap(mCards[i], 0, 0, null);
        canvas.restore();
    }
    canvas.restore();

}
复制代码

四、使用约束布局下降层级

五、使用AsyncLayoutInflator异步加载布局文件

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
                setContentView(R.layout.activity_main);
            }
        });
        
    }
复制代码

使用AsyncLayoutInflater 的局限性:

全部构建的View中必须不能直接使用 Handler 或者是调用 Looper.myLooper(),由于异步线程默认没有调用 Looper.prepare ();

异步转换出来的 View 并无被加到 parent view中,必须手动添加;

AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;

同时缓存队列默认 10 的大小限制若是超过了10个则会致使主线程的等待;

4、小结

开发Android应用程序时也不可能无限制的使用CPU和内存,若是对CPU和内存使用不当也会形成应用的卡顿和内存溢出等问题。所以,性能优化是每一个Android开发人员都应该去注意的,本文介绍了绘制相关的一些监控手段和常见的优化手段,但愿对你们有帮助~