• ScrollView嵌套子View的getDrawingCache为空的解决方法


    ScrollView嵌套子View的getDrawingCache为空的解决方法

    问题

    将组件的显示布局改为可以滚动的,然后用ScrollView作为了View的父类,发现View的getDrawingCache返回为null了,组件的滚动是必须要实现的,所以探究一下getDrawingCache为空的解决方法。

    问题原因

    查看日志可以发现

    `02-01 14:21:33.512 3461-3461/com.example.sample.job W/View: View too large to fit into drawing cache, needs 9338145 bytes, only 3686400 available
    

    这条信息,意思是我的view太大了不能放入图像缓存中。
    考虑到之前没有ScrollView的时候,并没有出现这个错误,所以问题是出在ScrollView嵌套了View上,这样的布局结构导致在使用getDrawingCache()时,系统因缓存超量直接取消了写入操作,drawingCache中成为了空值。
    通常,使用getDrawngCache()方法获取控件截图的代码是:

        public Bitmap getBitmap(View view) {
            view.setDrawingCacheEnabled(true);
            view.buildDrawingCache();
            Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, view.getMeasuredWidth(), view.getMeasuredHeight() - view.getPaddingBottom());
            view.setDrawingCacheEnabled(false);
            view.destroyDrawingCache();
            return bitmap;
        }
    

    查看一下getDrawingCache的实现:

        /**
         * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p>
         *
         * @return A non-scaled bitmap representing this view or null if cache is disabled.
         *
         * @see #getDrawingCache(boolean)
         */
        public Bitmap getDrawingCache() {
            return getDrawingCache(false);
        }
    

    发现getDrawingCache()方法调用了getDrawingCache(false)方法,接着查看getDrawingCache(false)方法:

        /**
         * <p>Returns the bitmap in which this view drawing is cached. The returned bitmap
         * is null when caching is disabled. If caching is enabled and the cache is not ready,
         * this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not
         * draw from the cache when the cache is enabled. To benefit from the cache, you must
         * request the drawing cache by calling this method and draw it on screen if the
         * returned bitmap is not null.</p>
         *
         * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
         * this method will create a bitmap of the same size as this view. Because this bitmap
         * will be drawn scaled by the parent ViewGroup, the result on screen might show
         * scaling artifacts. To avoid such artifacts, you should call this method by setting
         * the auto scaling to true. Doing so, however, will generate a bitmap of a different
         * size than the view. This implies that your application must be able to handle this
         * size.</p>
         *
         * @param autoScale Indicates whether the generated bitmap should be scaled based on
         *        the current density of the screen when the application is in compatibility
         *        mode.
         *
         * @return A bitmap representing this view or null if cache is disabled.
         *
         * @see #setDrawingCacheEnabled(boolean)
         * @see #isDrawingCacheEnabled()
         * @see #buildDrawingCache(boolean)
         * @see #destroyDrawingCache()
         */
        public Bitmap getDrawingCache(boolean autoScale) {
            if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
                return null;
            }
            if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
                buildDrawingCache(autoScale);
            }
            return autoScale ? mDrawingCache : mUnscaledDrawingCache;
        }
    

    查看getDrawingCache(false)没有看到有用的信息,所以继续查看调用的方法buildDrawingCache(autoScale):

        /**
         * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>
         *
         * <p>If you call {@link #buildDrawingCache()} manually without calling
         * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
         * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p>
         *
         * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
         * this method will create a bitmap of the same size as this view. Because this bitmap
         * will be drawn scaled by the parent ViewGroup, the result on screen might show
         * scaling artifacts. To avoid such artifacts, you should call this method by setting
         * the auto scaling to true. Doing so, however, will generate a bitmap of a different
         * size than the view. This implies that your application must be able to handle this
         * size.</p>
         *
         * <p>You should avoid calling this method when hardware acceleration is enabled. If
         * you do not need the drawing cache bitmap, calling this method will increase memory
         * usage and cause the view to be rendered in software once, thus negatively impacting
         * performance.</p>
         *
         * @see #getDrawingCache()
         * @see #destroyDrawingCache()
         */
        public void buildDrawingCache(boolean autoScale) {
            if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                    mDrawingCache == null : mUnscaledDrawingCache == null)) {
                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                            "buildDrawingCache/SW Layer for " + getClass().getSimpleName());
                }
                try {
                    buildDrawingCacheImpl(autoScale);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }
        }
    

    查看buildDrawingCache(boolean autoScale)方法也没有看到有用的信息,所以查看调用的方法buildDrawingCacheImpl(boolean autoScale):

      /**
         * private, internal implementation of buildDrawingCache, used to enable tracing
         */
        private void buildDrawingCacheImpl(boolean autoScale) {
            mCachingFailed = false;
    
            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;
    
            final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
            final long drawingCacheSize =
                    ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
            if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
                if (width > 0 && height > 0) {
                    Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
                            + " too large to fit into a software layer (or drawing cache), needs "
                            + projectedBitmapSize + " bytes, only "
                            + drawingCacheSize + " available");
                }
                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:
                        case DRAWING_CACHE_QUALITY_LOW:
                        case DRAWING_CACHE_QUALITY_HIGH:
                        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(mResources.getDisplayMetrics(),
                            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 |= PFLAG_DRAWN;
            if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
                    mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
            }
    
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
                if (mOverlay != null && !mOverlay.isEmpty()) {
                    mOverlay.getOverlayView().draw(canvas);
                }
            } else {
                draw(canvas);
            }
    
            canvas.restoreToCount(restoreCount);
            canvas.setBitmap(null);
    
            if (attachInfo != null) {
                // Restore the cached Canvas for our siblings
                attachInfo.mCanvas = canvas;
            }
        }
    

    在这个方法中,我们可以看到我们的日志报错的内容

            if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
                if (width > 0 && height > 0) {
                    Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
                            + " too large to fit into a software layer (or drawing cache), needs "
                            + projectedBitmapSize + " bytes, only "
                            + drawingCacheSize + " available");
                }
                destroyDrawingCache();
                mCachingFailed = true;
                return;
            }
    

    可以看到报错的原因是projectedBitmapSize > drawingCacheSize,可以看到这两个值得计算:

            final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
            final long drawingCacheSize =
                    ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
    

    从这两个变量的计算上看,drawingCacheSize是当前设备系统所允许的最大绘制缓存值,一个固定的值,问题可能就是projectedBitmapSize的值有问题,而(opaque && !use32BitCache ? 2 : 4)不可能为0,而width是屏幕的宽,没有问题,那么就是height的值出现了异常,所以,接着查看视图高度的计算代码:

        /**
         * Return the height of your view.
         *
         * @return The height of your view, in pixels.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public final int getHeight() {
            return mBottom - mTop;
        }
    

    从代码可以看到height的值是mBottom - mTop,其中mBottom指的是视图自身的底边到父视图顶边的距离,mTop指的是视图自身的顶边到父视图顶边的距离。如果对子视图设置layout_height="match_parent",子视图充满父视图,那么getHeight()实际就可以看做是父视图的高。但是在ScrollView中不同,设置它的子View为match_parent是没用的(并且一般设置的都是wrap_content),ScrollView的高度会随着子View的高度变化而变化,而且意味着getHeight()返回的不再是屏幕可见范围的高度,而是整个加载出来的实际界面内容高度。ScrollView的子控件执行的截屏操作会将用户未看到的图片一并截取。

    所以,使用ScrollView嵌套的子View,采用buildDrawingCache方法进行截图时,getHeight()获得的数值远大于当前屏幕高度。所以projectedBitmapSize > drawingCacheSize的判断正确,报出了错误。

    解决问题

    既然ScrollView嵌套的子View,使用getDrawingCache()截图失败,那就换成使用canvas直接复制视图内容的方式实现:

        public Bitmap getBitmapByCanvas(View view) {
            Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            if (Build.VERSION.SDK_INT >= 11) {
                view.measure(
                        View.MeasureSpec.makeMeasureSpec(
                                view.getWidth(), View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(
                                view.getHeight(), View.MeasureSpec.EXACTLY)
               );
            } else {
                view.measure(
                        View.MeasureSpec.makeMeasureSpec(
                            0, View.MeasureSpec.UNSPECIFIED),
                        View.MeasureSpec.makeMeasureSpec(
                            0, View.MeasureSpec.UNSPECIFIED)
                );
                view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
            }
            view.draw(canvas);
            return bitmap;
        }
    

    问题就这样解决了。

    参考文章

    https://blog.csdn.net/lz8362/article/details/79284492

  • 相关阅读:
    MYSQL数据类型——字符串类型
    MYSQL——记录长度
    MYSQL数据类型——时间日期类型
    MYSQL数据类型——数值类型
    为什么在 IDEA jsp 中直接使用 out.println 会出错
    花指令行为大赏
    EasyCpp 题解
    [SUCTF2019] hardcpp 题解
    洛谷 P1650 田忌赛马题解
    Dict 协议是什么
  • 原文地址:https://www.cnblogs.com/zhangmiao14/p/9746307.html
Copyright © 2020-2023  润新知