页面在滑动的过程中如果要感觉流程,必须要达到每秒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、应用:
2、Activity
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 拷贝而来,如果不允许,请给我留言。
private void performDraw() {
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
try {
draw(fullRedrawNeeded);
} finally {
}
}
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);
}
}
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;
}
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);
}
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;
}
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;
}
}
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();
}
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);
}
}