java.lang.IllegalArgumentException: width and height must be > 0

遨游在代码的海洋中,难免会遇到一个大浪拍晕你。今天就遇到一个关于图片压缩方面的坑,且听我细细道来~~
想直接看解决方法的直接目录跳转 正确代码

问题出现场景:

项目里有处地方需要把下载下来的图片进行压缩,这里图片下载使用的是Glide,下载完会自动转成bitmap,所以只需要对bitmap进行压缩处理就好了。对于bitmap的处理对于做过多个项目的老手(不管你信不信,反正我是信了)来说我直接反手就是一段代码。

错误代码:

1public static Bitmap ResizeBitmap(Bitmap bitmap, int scale) {
2int width = bitmap.getWidth();
3int height = bitmap.getHeight();
4、        Matrix matrix = new Matrix();
5、        matrix.postScale(1 / scale, 1 / scale);
6、        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
7、        bitmap.recycle();
8return resizedBitmap;
    }

上述代码是从我的知识小金库中拿出来的,没想到一上来就反手给我一个Crash,报错完整如下:

java.lang.IllegalArgumentException: width and height must be > 0
   at android.graphics.Bitmap.createBitmap(Bitmap.java:933)
   at android.graphics.Bitmap.createBitmap(Bitmap.java:912)
   at android.graphics.Bitmap.createBitmap(Bitmap.java:843)
   at zz.xx.xx.common.view.seekbar.MarkSeekBar.ResizeBitmap(MarkSeekBar.java:223)
   at xxxx.MarkSeekBar$1$1.onResourceReady(MarkSeekBar.java:206)
   at xxxx.MarkSeekBar$1$1.onResourceReady(MarkSeekBar.java:203)
   at com.bumptech.glide.request.GenericRequest.onResourceReady(GenericRequest.java:525)
   at com.bumptech.glide.request.GenericRequest.onResourceReady(GenericRequest.java:507)
   at android.os.Handler.dispatchMessage(Handler.java:98)
   at android.os.Looper.loop(Looper.java:145)
   at android.app.ActivityThread.main(ActivityThread.java:5942)
   at java.lang.reflect.Method.invoke(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)

看见报错楼主是毫不慌张的,因为很明显宽高没有获取到,对于一个有经验的老手(可能是个假的)来说,开始分析,因为这是在自定义View里面,会不会是view没有绘制出来所以获取不到(很多情况下view的宽高获取不到都是这个原因),但是很快被否定了,因为这里根本不关view什么事,我们获取的是bitmap,为了稳妥起见,还是打了断点,发现width 和height的值是有的,而且大于0!!!我擦嘞,那它报错是几个意思?冷静下来。没办法只能debug了,发现在如上“错误代码”的第6行报错了(自己手动标的行数),接着只能从源码着手。

下面贴出Bitmap createBitmap()方法精简源码:

下述三个方法分别在Bitmap源码的 728行、874行、855行,有条件的可以自行查看。

代码一、

    public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
            Matrix m, boolean filter) {
        int neww = width;
        int newh = height;
        Canvas canvas = new Canvas();
        Bitmap bitmap;
        Paint paint;
        ...
        RectF dstR = new RectF(0, 0, width, height);
        ...
        ...
            RectF deviceR = new RectF();
            m.mapRect(deviceR, dstR);

            neww = Math.round(deviceR.width());
            newh = Math.round(deviceR.height());

            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
           transformed || source.hasAlpha());
        return bitmap;
    }

因为报错说的是width和height的值有问题,所以我们只需要跟踪width和height都做了哪些操作,接着看源码,开始width和height就被赋值neww和newh,接着做了m.mapRect操作,这里是做了个矩形转换操作。接着是Math.round的数学操作,这个也不用管,最后调用方法createBitmap,我们跟着跳进去看代码如下:

代码二、

private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
        return createBitmap(null, width, height, config, hasAlpha);
    }

这里又调用了createBitmap(null, width, height, config, hasAlpha) 方法,接着往里跳:

代码三、

private static Bitmap createBitmap(DisplayMetrics display, int width, int height,
            Config config, boolean hasAlpha) {
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("width and height must be > 0");
        }
        Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
        if (display != null) {
            bm.mDensity = display.densityDpi;
        }
        ...
        ...
        return bm;
    }

到了这里我们终于找到了异常报错的地方throw new IllegalArgumentException !!!回想整个流程发现并没有什么地方做了什么特殊处理。然后陷入沉思;没办法只能再从头想一遍,上述“错误代码”最复杂的一个方法我们已经分析完了,就剩下matrix.postScale(1 / scale, 1 / scale); 这一段了,我们还是跳入源码:

/**
     * Postconcats the matrix with the specified scale.
     * M' = S(sx, sy) * M
     */
    public boolean postScale(float sx, float sy) {
        native_postScale(native_instance, sx, sy);
        return true;
    }

方法很简单,也是只调了一个native方法;但是我们发现了一个问题,这个方法接收的参数是float类型!!!因为Java属于强语言,不会自动转换类型,所以大概可以猜出这里至少是一个问题,抱着最后的希望将参数改为float类型:

正确代码:

public static Bitmap ResizeBitmap(Bitmap bitmap, int scale) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        Matrix matrix = new Matrix();
        matrix.postScale(1f / scale, 1f / scale);
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
        bitmap.recycle();
        return resizedBitmap;
    }

结果不用说了,自然是这里的问题!

折腾一圈最后才发现问题其实很简单,只是一开始方向错了,但这个过程中至少我们学会了怎么去分析一个问题(至少安慰下自己 -_-)。

这里对上面提到过的View获取宽高失败的问题也提一下,之所以会获取失败是因为获取宽高的时候我们的view还没绘制完成,所以获取失败;这里解决方法是做一个延时操作:

View获取宽高

View.post(new Runnable() {
    @Override
    public void run() {
       //获取宽高
    }
});

或者对视图进行监听:

ViewTreeObserver vto = view.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                //获取宽高
            }
        });

还可以通过测量的方式,不过分不同情况:

match_parent 的情况: 直接放弃,无法 measure 出具体的宽高

具体的数值( dp/px )

比如宽高都是 100px ,如下 measure :
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);

wrap_content 的情况:

int widthMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);

注意到(1 << 30) - 1,通过分析MeasureSpec的实现可以知道,view的尺寸使用30位二进制表示的,也就是说最大是30个1即 2^30 - 1,也就是(1 << 30) - 1,在最大化模式下,我们用view理论上能支持的最大值去构造MeasureSpec是合理的。

好了,今天的坑就说到这里了,有什么不对的地方欢迎指出。

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值