我是靠谱客的博主 灵巧项链,最近开发中收集的这篇文章主要介绍Android 滑动绘制流程探究 系统是如何提高滑动性能?Android 滑动绘制流程探究 系统是如何提高滑动性能?,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Android 滑动绘制流程探究 系统是如何提高滑动性能?


页面在滑动的过程中如果要感觉流程,必须要达到每秒60帧,当然多了也是浪费,因为那样人眼也是无法区分开来的。Android 图形绘制通过VSYNC机制来保证每秒的绘制帧数达到60帧。Android系统每隔16ms发出VSYNC信号,触发GPU对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。那一帧占用内存数呢?当画面的分辨率是1080×1920(目前最主流的分辨率),刷新率要达到60帧/秒时,那么显卡在一秒钟内需要处理的像素量就达到了“1080×1920×60=124416000”。那么一个“像素量”,相当与占用多少内存?我们用位图来代替粗略计算,把分辨率是1080×1920看成一张对应大小的位图,通过位图的大小来大概计算占用的内存大小。 1080×1920×16/8 = 4147200B = 4050KB = 3 MB,每秒钟需要处理3*60 = 180M的内存。这个数据量是相当大的了,所以滑动绘制过程必定采取了相应的策略增加绘制效率。

我们可以首先研究下ScrollView的滑动绘制原理,ScrollView相对于ListView等其他滑动控件功能更加单一,而且非常常用。说到滑动,必须先说明下View的绘制流程,这部分流程是相当的复杂的。分为以下几种情况1、开启了硬件加速 且滑动视图区域硬件加速关闭了但设置了软加速(缓存bitmap)2、开启硬件加速,滑动视图层硬件加速被关闭,软加速也没开启  3、开启了硬件加速 且滑动视图区域硬件加速也开启了。4、整体硬件加速没开启,但滑动视图区域软加速开启了 5、整体硬件加速没开启,软加速也没开启。听起来听绕口的,其实就是根据硬件加速分情况,View的层级上不能单独的开启硬件加速。所以列举出的情况是五种,而不是六种。关于硬件加速各层级开启方式如下:

在Android中,可以四给不同层次上开启硬件加速:
1、应用:
<application android:hardwareAccelerated="true">
2、Activity
<activity android:hardwareAccelerated="true">
3、Window
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
4、View
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
在这四个层次中,应用和Activity是可以选择的,Window只能打开,View只能关闭。
在apk的AndroidManifest中,如果指定了minSDKVersion&targetSDKVersion=7,会使得应用无法使用硬件加速进行绘图。

以上部分是从http://blog.csdn.net/oujunli/article/details/8570902 拷贝而来,如果不允许,请给我留言。


开启硬件加速View的绘制流程

大家应该知道绘制指令是从ViewRootImp 的performTraversals分发出来的。关于这部分知识,大家可以在网上找相关资料,在这里我们先不展开说明。为了简化流程,我们假设绘制的过程没有动画,有动画的情况会稍微复杂点。当我们手动调用invalidate或者通过改变控件内容等操作间接触发invalidate最后都会向上递归,最后调用ViewRooImp的invalidate方法,然后触发scheduleTraversals,最后会触发performTraversals。这个方法大概有着一千行的代码。大概说下,会分别触发performMeasure,performLayout,performDraw等方法,分别对应着测量,布局,绘制三部分。我们今天重点讨论绘制部分,事实上在滑动过程中不会重复触发测量和布局的方法,而绘制会不断的调用,多以这部分是性能优化的重点。

private void performDraw() {
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
try {
draw(fullRedrawNeeded);
} finally {
}
}



performDraw调用draw(fullRedrawNeeded),参数意味着是否需要全部绘制,performDraw调用过来的全部为false。


 private void draw(boolean fullRedrawNeeded) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
// Draw with hardware renderer.
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
if (invalidateRoot) {
mAttachInfo.mHardwareRenderer.invalidateRoot();
}
dirty.setEmpty();
// Stage the content drawn size now. It will be transferred to the renderer
// shortly before the draw commands get send to the renderer.
final boolean updated = updateContentDrawBounds();
if (mReportNextDraw) {
// report next draw overrides setStopped()
// This value is re-sync'd to the value of mStopped
// in the handling of mReportNextDraw post-draw.
mAttachInfo.mHardwareRenderer.setStopped(false);
}
if (updated) {
requestDrawWindow();
}
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
}
}



 if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled())意味着如果开启了硬件加速,我只是截取了一部分代码,后面逻辑没有截出来,待会讨论。 mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);使用mHardwareRenderer来构建视图,我们看看里面具体实现:


void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
attachInfo.mIgnoreDirtyState = false;
}



内部调用了updateRootDisplayList 这个方法是关键。

  
 private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
updateViewTreeDisplayList(view);
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onHardwarePreDraw(canvas);
canvas.insertReorderBarrier();
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.insertInorderBarrier();
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
mRootNode.end(canvas);
}
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}



有没有发现硬件加速使用的是DisplayListCanvas ,它是通过DisplayListCanvas canvas = renderNode.start(width, height)来获得,用于硬件加速的画布。上面代码中的updateViewTreeDisplayList(view),会更新RenderNode数据,所以仅仅是透明度这种样式的改变,是不需要重新绘制的。而且RenderNode缓存了整个View树的绘制命令,可以不通过调用View的draw方法,而完整的绘制出整个视图。部分updateViewTreeDisplayList(view)代码如下:


public RenderNode updateDisplayListIfDirty() {
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid()
|| (mRecreateDisplayList)) {
if (renderNode.isValid()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode; // no work needed
}
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
} else {
draw(canvas);
}
}
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}


大概解释下上述代码,第一个判断语句为了判断当前的RenderNode是否需要更新,如果不需要更新则直接返回使用。第二个判断语句,判断当前的View的layerType看是否开启软加速,软加速其实是将绘制保存在一个bitmap中,gpu渲染bitmap的速度是相当快的,在一定程度上可以提高视图渲染性能,但bitmap缓存却会占用大量的内存空间,所以是否开启软加速要视情况而定。
上面大概的介绍了下硬件加速绘图的整体流程,我这里再总结下。系统发出绘制指令,可以是手动调用invilidate触发,也可能是系统自主触发,最后都会触发ViewRootImpl的performTraversals,然后会调用draw(boolean fullRedrawNeeded) 进行绘图,内部会判断是否是硬件加速,如果是则会创建DisplayListCanvas来更新或者复用RenderNode,最后通过RenderNode将绘图完成。所以如果是滑动,系统不需要遍历整个View树来绘制,而只需要简单更新RenderNode便可以完成整个滑动过程。在这里大家可以发现在硬件加速环境下,确实可以很大程度上提高滑动性能,那非硬件加速环境下呢?


未开启硬件加速View的绘制流程

未开启硬件加速整体的流程和之前大体是相同的,不过调用draw(boolean fullRedrawNeeded)不会再触发mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this),而是调用了drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)看名字也知道,这个绘制软件层的视图。大概流程如下:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
Canvas
canvas = mSurface.lockCanvas(dirty);
mView.draw(canvas);
return true;
}
mSurface.lockCanvas(dirty);会调用如下代码:
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
Canvas mCanvas = new CompatibleCanvas();
return mCanvas;
}
}



从这里就可以看出,软件层的绘制使用的是CompatibleCanvas,并且会触发根视图的draw方法,其实draw方法又会触发dispatchDraw方法,然后触发drawChild方法,完成整个View树的更新。不过其中也是有优化,比如通过canvas的canvas.quickReject快速拒绝,可以拒绝绘制屏幕外的内容。如果开启了LAYER_TYPE_SOFTWARE,软加速,会对一屏的View进行bitmap缓存,所以每次滑动都只会绘制新出现的那个View。


ScrollView滑动流程分析


这部分比较简单,大概说下流程,以拖动滑动为例。触摸事件的消费肯定在public boolean onTouchEvent(MotionEvent ev)方法中,我们找下case MotionEvent.ACTION_MOVE这个条件分支,处理手势移动事件,里面方法比较多,不过会触发overScrollBy方法然后间接触发onOverScrolled方法,onOverScrolled有实现,如下:

protected void onOverScrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {
// Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished()) {
final int oldX = mScrollX;
final int oldY = mScrollY;
mScrollX = scrollX;
mScrollY = scrollY;
invalidateParentIfNeeded();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (clampedY) {
mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
}
} else {
super.scrollTo(scrollX, scrollY);
}
awakenScrollBars();
}



大家看到,如果没有动画则会调用super.scrollTo(scrollX, scrollY);方法实现位置的改变,有动画的情况也是一样的,会不断的触发scrollTo实现动画的效果。scrollTo会调用

postInvalidateOnAnimationScroll来更新视图。
public void postInvalidateOnAnimation() {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this);
}
}



大家看到又将事件传回到ViewRootImp中了,其实所有的刷新事件都是最后由ViewRootImp来消费的。最后也都会触发performTraversals来更新视图,具体的逻辑大家可以自己跟踪下,边边角角的逻辑,其实不需要了解太详情,真要使用到的时候再详细的看。



最后

以上就是灵巧项链为你收集整理的Android 滑动绘制流程探究 系统是如何提高滑动性能?Android 滑动绘制流程探究 系统是如何提高滑动性能?的全部内容,希望文章能够帮你解决Android 滑动绘制流程探究 系统是如何提高滑动性能?Android 滑动绘制流程探究 系统是如何提高滑动性能?所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部