View的getDrawingCache为空,解决办法

这两天帮同事解决一个问题;

View.getDrawingCache获得数据始终为null,但是在某些设备上并不为null,纠结够 久啊,网上说了一些原因:

1) (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING  这个值为true

2) (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED 为false,buildDrawingCache没执行

3) buildDrawingCache执行失败

这些在源码中都可以看到,在获得缓存数据的时候,跟背景色(drawingCacheBackgroundColor),透明度isOpaque,use32BitCache这些有关系,看是细看这些东西都是表面的,是系统在buildDrawingCache的时候,根据View或都系统设置而来的;有些属性是不能更改的;这样一来当一个固定大小的View在不同的设备上生成的图片就可能有所不同,我同事这边存在的问题就是,设置View的固定大小为1360*768,而我的设备分辨率为1024*600,而源码里可以看到这样代码:

 if (width <= 0 || height <= 0 ||
                     // Projected bitmap size in bytes
                    (width * height * (opaque && !use32BitCache ? 2 : 4) >
                            ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
                destroyDrawingCache();
                mCachingFailed = true;
                return;
            }

当我们在buildDrawingCache的时候,系统给了我们默认最大的DrawingCacheSize为屏幕宽*高*4;而我的View的CacheSize大小超过了某些设备默认值,就会导致获得为空;开始想着用反射的方法去改变这些属性,或者设置背景颜色来改变图片质量,这样一来CacheSize大小 就可能会变小,但是这样始终不能达到效果;


最终解决方案:

查看系统buildDrawingCache方法可以看到:

public void buildDrawingCache(boolean autoScale) {
        if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ?
                mDrawingCache == null : mUnscaledDrawingCache == null)) {
            mCachingFailed = false;

            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
            }

            int width = mRight - mLeft;
            int height = mBottom - mTop;

            final AttachInfo attachInfo = mAttachInfo;
            final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;

            if (autoScale && scalingRequired) {
                width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
                height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
            }

            final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
            final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
            final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;

            if (width <= 0 || height <= 0 ||
                     // Projected bitmap size in bytes
                    (width * height * (opaque && !use32BitCache ? 2 : 4) >
                            ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
                destroyDrawingCache();
                mCachingFailed = true;
                return;
            }

            boolean clear = true;
            Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;

            if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
                Bitmap.Config quality;
                if (!opaque) {
                    // Never pick ARGB_4444 because it looks awful
                    // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
                    switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
                        case DRAWING_CACHE_QUALITY_AUTO:
                            quality = Bitmap.Config.ARGB_8888;
                            break;
                        case DRAWING_CACHE_QUALITY_LOW:
                            quality = Bitmap.Config.ARGB_8888;
                            break;
                        case DRAWING_CACHE_QUALITY_HIGH:
                            quality = Bitmap.Config.ARGB_8888;
                            break;
                        default:
                            quality = Bitmap.Config.ARGB_8888;
                            break;
                    }
                } else {
                    // Optimization for translucent windows
                    // If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
                    quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
                }

                // Try to cleanup memory
                if (bitmap != null) bitmap.recycle();

                try {
                    bitmap = Bitmap.createBitmap(width, height, quality);
                    bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
                    if (autoScale) {
                        mDrawingCache = bitmap;
                    } else {
                        mUnscaledDrawingCache = bitmap;
                    }
                    if (opaque && use32BitCache) bitmap.setHasAlpha(false);
                } catch (OutOfMemoryError e) {
                    // If there is not enough memory to create the bitmap cache, just
                    // ignore the issue as bitmap caches are not required to draw the
                    // view hierarchy
                    if (autoScale) {
                        mDrawingCache = null;
                    } else {
                        mUnscaledDrawingCache = null;
                    }
                    mCachingFailed = true;
                    return;
                }

                clear = drawingCacheBackgroundColor != 0;
            }

            Canvas canvas;
            if (attachInfo != null) {
                canvas = attachInfo.mCanvas;
                if (canvas == null) {
                    canvas = new Canvas();
                }
                canvas.setBitmap(bitmap);
                // Temporarily clobber the cached Canvas in case one of our children
                // is also using a drawing cache. Without this, the children would
                // steal the canvas by attaching their own bitmap to it and bad, bad
                // thing would happen (invisible views, corrupted drawings, etc.)
                attachInfo.mCanvas = null;
            } else {
                // This case should hopefully never or seldom happen
                canvas = new Canvas(bitmap);
            }

            if (clear) {
                bitmap.eraseColor(drawingCacheBackgroundColor);
            }

            computeScroll();
            final int restoreCount = canvas.save();

            if (autoScale && scalingRequired) {
                final float scale = attachInfo.mApplicationScale;
                canvas.scale(scale, scale);
            }

            canvas.translate(-mScrollX, -mScrollY);

            mPrivateFlags |= DRAWN;
            if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
                    mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= DRAWING_CACHE_VALID;
            }

            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
                if (ViewDebug.TRACE_HIERARCHY) {
                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
                }
                mPrivateFlags &= ~DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
                draw(canvas);
            }

            canvas.restoreToCount(restoreCount);
            canvas.setBitmap(null);

            if (attachInfo != null) {
                // Restore the cached Canvas for our siblings
                attachInfo.mCanvas = canvas;
            }
        }
    }

    /**
     * Create a snapshot of the view into a bitmap.  We should probably make
     * some form of this public, but should think about the API.
     */
    Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
        int width = mRight - mLeft;
        int height = mBottom - mTop;

        final AttachInfo attachInfo = mAttachInfo;
        final float scale = attachInfo != null ? attachInfo.mApplicationScale : 1.0f;
        width = (int) ((width * scale) + 0.5f);
        height = (int) ((height * scale) + 0.5f);

        Bitmap bitmap = Bitmap.createBitmap(width > 0 ? width : 1, height > 0 ? height : 1, quality);
        if (bitmap == null) {
            throw new OutOfMemoryError();
        }

        Resources resources = getResources();
        if (resources != null) {
            bitmap.setDensity(resources.getDisplayMetrics().densityDpi);
        }

        Canvas canvas;
        if (attachInfo != null) {
            canvas = attachInfo.mCanvas;
            if (canvas == null) {
                canvas = new Canvas();
            }
            canvas.setBitmap(bitmap);
            // Temporarily clobber the cached Canvas in case one of our children
            // is also using a drawing cache. Without this, the children would
            // steal the canvas by attaching their own bitmap to it and bad, bad
            // things would happen (invisible views, corrupted drawings, etc.)
            attachInfo.mCanvas = null;
        } else {
            // This case should hopefully never or seldom happen
            canvas = new Canvas(bitmap);
        }

        if ((backgroundColor & 0xff000000) != 0) {
            bitmap.eraseColor(backgroundColor);
        }

        computeScroll();
        final int restoreCount = canvas.save();
        canvas.scale(scale, scale);
        canvas.translate(-mScrollX, -mScrollY);

        // Temporarily remove the dirty mask
        int flags = mPrivateFlags;
        mPrivateFlags &= ~DIRTY_MASK;

        // Fast path for layouts with no backgrounds
        if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
            dispatchDraw(canvas);
        } else {
            draw(canvas);
        }

        mPrivateFlags = flags;

        canvas.restoreToCount(restoreCount);
        canvas.setBitmap(null);

        if (attachInfo != null) {
            // Restore the cached Canvas for our siblings
            attachInfo.mCanvas = canvas;
        }

        return bitmap;
    }

生成DrawingCache的过程貌似就是利用获得View的Canvas然后画到bitmap上,直接返回对应 的bitmap,这样一来,就是我们用getDrawingCache获得的bitmap;跟我们直接将View画到bitmap貌似区别 不是很大,受启发;如下:

自己生成Bitmap;

public static Bitmap loadBitmapFromView(View v, boolean isParemt) {
		if (v == null) {
			return null;
		}
		Bitmap screenshot;
		screenshot = Bitmap.createBitmap(v.getWidth(), v.getHeight(), HDConstantSet.BITMAP_QUALITY);
		Canvas c = new Canvas(screenshot);
		v.draw(c);
		return screenshot;
	}

这样也就将View生成了我们需要的bitmap了,但是有些情况下:比如ViewPager在用getDrawingCache和我自己生成的Bitmap时候,会有区别,ViewPager第一屏是正常的,滑动到第二屏幕的时候,我手动生成的Bitmap不见了,而系统getDrawingCache方法生成 的Bitmap是可见的,郁闷,,,详细看了一下系统buildDrawingCache访求,发现在Canvas绘制Bitmap之后,多了一个步骤:

 computeScroll();
        final int restoreCount = canvas.save();
        canvas.scale(scale, scale);
        canvas.translate(-mScrollX, -mScrollY);

很明显,系统Canvas,对默认位置进行了移动,即启发:我们在用哥滑动View获得它的Bitmap时候,获得的是整个View的区域(包括隐藏的),如果想得到当前区域,需要重新定位到当前可显示的区域;自己的代码修改:


public static Bitmap loadBitmapFromView(View v, boolean isParemt) {
		if (v == null) {
			return null;
		}
		Bitmap screenshot;
		screenshot = Bitmap.createBitmap(v.getWidth(), v.getHeight(), HDConstantSet.BITMAP_QUALITY);
		Canvas c = new Canvas(screenshot);
		c.translate(-v.getScrollX(), -v.getScrollY());
		v.draw(c);
		return screenshot;
	}

完美解决用自己生成 的Bitmap替换系统的getDrawingCache()方法;

当然系统getDrawingCache()考虑的因素很多,这一些我们也可以自己直接定义,比如透明磁,大小 ,图片质量等;最重要就是

c.translate(-v.getScrollX(), -v.getScrollY());
这行一代码需要理解 ;



上面个人见解,如有理解不对的地方,大神留言指导啊........

你可能感兴趣的:(Android)