canvas教程

用两张图告诉你,为什么你的App会卡顿? 微码农(5)

字号+ 作者:H5之家 来源:H5之家 2017-04-18 18:00 我要评论( )

image private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {//这个方法用于接收Vsync信号public void onVsync(){...Message msg = Message.obtain(mHandler, this);m

image

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { //这个方法用于接收Vsync信号 public void onVsync(){ ... Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); //这里并没有设置消息的类型 //其实就是默认为0,即MSG_DO_FRAME类型的消息 //它其实就是通知Choreographer开始回调CallbackQueue[]中的Callback了 //也就是开始绘制下一帧的内容了 } //这个方法是在父类中的,写在这方便看 public void scheduleVsync() { ... nativeScheduleVsync(mReceiverPtr); //请求一个Vsync信号 } }

这给类功能比较明确,而且很重要!

用两张图告诉你,为什么你的App会卡顿?

image

上面一直在说向FrameHandler中发消息,搞得神神秘秘的。接下来就来看看FrameHandler本尊。请在图中找到对应位置哦。

private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_FRAME: //开始回调Callback,以开始绘制下一帧内容 doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC: //请求一个Vsync信号 doScheduleVsync(); break; case MSG_DO_SCHEDULE_CALLBACK: //实际也是请求一个Vsync信号 doScheduleCallback(msg.arg1); break; } } }

FrameHandler主要在UI线程处理3种类型的消息。

FrameHandler并不复杂,但在UI的绘制过程中具有重要的作用,所以一定要结合图梳理下这个流程。

SurfaceFlinger和Surface简单说

在介绍Vsync的时候,我们可能已经看到了,现在Android系统会将HW_VSYNC虚拟化为两个Vsync信号。一个是VSYNC,被发送给上面一直在讲的Choreographer,用于触发视图树的绘制渲染。另一个是SF_VSYNC,被发送给我接下来要讲的SurfaceFlinger,用于触发Surface的合成,即各个Window窗口画面的合成。接下来我们就简单的看下SurfaceFlinger和Surface。由于这部分基本是c++编写的,我着重讲原理。

隐藏在背后的Surface

平时同学们都知道,我们的视图需要被绘制。那么它们被绘制到那了呢?也许很多童鞋脑海里立即浮现出一个词:Canvas。但是,~没错!就是绘制到了Canvas上。那么Canvas又是怎么来的呢?是的,它可以New出来的。但是前面提到过,我们Window中的视图树都是被绘制到一个由Surface提供的Canvas上。忘了的童鞋面壁思过。

用两张图告诉你,为什么你的App会卡顿?

image

Canvas实际代表了一块内存,用于储存绘制出来的数据。在Canvas的构造器中你可以看到:

public Canvas() { ... mNativeCanvasWrapper = initRaster(null); //申请一块内存,并且返回该内存的一个long类型的标记或者索引。 ... }

可以看到,Canvas实际主要就是持有了一块用于绘制的内存块的索引long mNativeCanvasWrapper。每次绘制时就通过这个索引找到对应的内存块,然后将数据绘制到内存中。比如:

public void drawRect(@NonNull RectF rect, @NonNull Paint paint) { native_drawRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance()); //在mNativeCanvasWrapper标记的内存中绘制一个矩形。 }

简单的说一下。Android绘制图形是通过图形库Skia(主要针对2D)或OpenGL(主要针对3D)进行。图形库是个什么概念?就好比你在PC上用画板画图,此时画板就相当于Android中的图形库,它提供了一系列标准化的工具供我们画图使用。比如我们drawRect()实际就是操作图形库在内存上写入了一个矩形的数据。

扯多了,我们继续回到Surface上。当ViewRootImpl执行到draw()方法(即开始绘制图形数据了),会根据是否开启了硬件(从Android 4.0开始默认是开启的)加速来决定是使用CPU软绘制还是使用GPU硬绘制。如果使用软绘制,图形数据会绘制在Surface默认的CompatibleCanvas上(和普通Canvas的唯一区别就是对Matrix进行了处理,提高在不同设备上的兼容性)。如果使用了硬绘制,图形数据会被绘制在DisplayListCanvas上。DisplayListCanvas会通过GPU使用openGL图形库进行绘制,因此具有更高的效率。

前面也简单说了一下,每一个Window都会有一个自己的Surface,也就是说一个应用程序中会存在多个Surface。通过上面的讲解,童鞋们也都知道了Surface的作用就是管理用于绘制视图树的Canvas的。这个Surface是和SurfaceFlinger共享,从它实现了Parcelable接口也可以才想到它会被序列化传递。事实上,Surface中的绘制数据是通过匿名共享内存的方式和SurfaceFlinger共享的,这样SurfaceFlinger可以根据不同的Surface,找到它所对应的内存区域中的绘制数据,然后进行合成。

合成师SurfaceFlinger

SurfaceFlinger是系统的一个服务。前面也一直在提到它专门负责把每个Surface中的内容合成缓存,以待显示到屏幕上。SurfaceFlinger在合成Surface时是根据Surface的Z-order顺序一层一层进行。比如一个Dialog的Surface就会在Activity的Surface上面。然后这个东西不多提了。

终于可以说说你的App为什么这么卡的原因了

通过对Android绘制机制的了解,我们知道造成应用卡顿的根源就在于16ms内不能完成绘制渲染合成过程,因为Android平台的硬件刷新率为60HZ,大概就是16ms刷新一次。如果没能在16ms内完成这个过程,就会使屏幕重复显示上一帧的内容,即造成了卡顿。在这16ms内,需要完成视图树的所有测量、布局、绘制渲染及合成。而我们的优化工作主要就是针对这个过程的。

复杂的视图树

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 为什么要学习HTML5

    为什么要学习HTML5

    2017-03-26 08:00

  • 为什么使用html5的十大原因!!!

    为什么使用html5的十大原因!!!

    2017-03-19 12:00

  • 用Canvas画图时为什么会闪烁,(只画一条线)

    用Canvas画图时为什么会闪烁,(只画一条线)

    2017-03-09 09:04

  • 为什么要用画图工具来画原型?

    为什么要用画图工具来画原型?

    2017-02-12 14:00

网友点评
A