View not displayed because it is too large to fit into a software layer (or drawing cache), needs 28

Posted 楠之枫雪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了View not displayed because it is too large to fit into a software layer (or drawing cache), needs 28相关的知识,希望对你有一定的参考价值。

描述

在自定义view中,我设置了自定义view的宽度大小如下:

<myView
        android:layout_width="match_parent"
        android:layout_height="5000px"
        android:visibility="visible" />

结果view不能绘制,直接不会回调onDraw方法,提示如下:
View not displayed because it is too large to fit into a software layer (or drawing cache), needs 28800000 bytes, only 14745600 available
意思就是绘制的图层大小超过了最大可用值

原因分析

我的虚拟机的分辨率为:1440x2560。software layer最大值可用通过以下获取到,:

 long size=ViewConfiguration.get(getContext()).getScaledMaximumDrawingCacheSize()

返回得到:14745600。通过查看源码发现,这个计算方法为:

mMaximumDrawingCacheSize = 4 * maxWindowBounds.width() * maxWindowBounds.height();

而且我设置的大小是1440x5000,可以计算出:

28800000 =4 * 1440 *5000

明显得到了提示的数据。后面发现随意的View不会出现这个问题,经过排查,居然是设置这个导致的:

 setLayerType(View.LAYER_TYPE_SOFTWARE, null)

当我把这个注释掉就正常了
没法了,下面通过源码排查下出现的原因:
找到限制大小的方法:

  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;
        }
        ...
       }

明显可以看出,只要设置的view的大小够大,就一定会进去这个限制判断中,说明开启硬件加速情况下,是不会调用这个方法的,去看下什么时候调用这个方法,查到是到这个调用的:

 @Deprecated
    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);
            }
        }
    }

通过日志打印,发现开启硬件加速情况下确实不会回调这个方法。看下这个方法的说明:

/**
     * <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()
     *
     * @deprecated The view drawing cache was largely made obsolete with the introduction of
     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
     * layers are largely unnecessary and can easily result in a net loss in performance due to the
     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
     * software-rendered usages are discouraged and have compatibility issues with hardware-only
     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
     * reports or unit testing the {@link PixelCopy} API is recommended.
     */

重点这句话:
With hardware-acceleration, intermediate cache * layers are largely unnecessary and can easily result in a net loss in performance due to the * cost of creating and updating the layer
由于硬件加速下,中间缓存层是不必要的,反而导致一定的开销。软件加速情况下,需要缓存来进行优化。

结论

自定义绘制view时,如果开启软件加速,会开启缓存,缓存层大小如果超了最大值会抛出异常,导致无法绘制view。缓存bitmap大小计算为:

projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4)

而最大值计算大小为:

mMaximumDrawingCacheSize = 4 * maxWindowBounds.width() * maxWindowBounds.height()

明显与view大小、窗体大小有关。一般来说,view大小的乘积不大于屏幕分辨率大小的乘积是安全的,注意这里说的是一般来说。当开启硬件加速时,使用的是硬件缓存(HardwareLayer),就没有这个限制,而默认情况下NONE,不使用缓存,也是没有这个限制的,记得下开启软件加速,就会比较危险了。要记得有两种缓存软件缓存(bitmap)、硬件缓存(HardwareLayer),but,那就引出了另外一个问题,软件加速下缓存层的机制是怎么样的?

参考下这个文章:[Android]DrawingCache到底干了什么?
简单来说就是:通过drawingCache获取到view的视图,比如获取view的缩略图进行显示。通过查看源码,还发现了在view里面一个很有意思的方法:


    /**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @CallSuper
    public void draw(Canvas canvas) {

它可以把view及其内容绘制到指定canvas中,那不使用缓存也可以快速来绘制出view的视图。

转载注明本文来源:https://blog.csdn.net/u014614038/article/details/117533941

以上是关于View not displayed because it is too large to fit into a software layer (or drawing cache), needs 28的主要内容,如果未能解决你的问题,请参考以下文章

was not registered for synchronization because synchronization is not active错误

Method not loaded because @ConditionalOnClass

ssh无法登录,提示Pseudo-terminal will not be allocated because stdin is not a terminal.

Coefficients: (1 not defined because of singularities)

Pseudo-terminal will not be allocated because stdin is not a terminal.

The resource could not be loaded because the App Transport