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

本文记录了一次Bitmap压缩处理中遇到的宽度和高度必须大于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是合理的。

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

FATAL EXCEPTION: main Process: com.mercusys.halo, PID: 31585 java.lang.IllegalArgumentException: width and height must be > 0 at android.graphics.Bitmap.createBitmap(Bitmap.java:1209) at android.graphics.Bitmap.createBitmap(Bitmap.java:1175) at android.graphics.Bitmap.createBitmap(Bitmap.java:1123) at android.graphics.Bitmap.createBitmap(Bitmap.java:1082) at com.tplink.mercusys.component.onboarding.view.EarthAnimationViewDelegate.initLine(EarthAnimationViewDelegate.kt:102) at com.tplink.mercusys.component.onboarding.view.EarthAnimationViewDelegate.access$initLine(EarthAnimationViewDelegate.kt:23) at com.tplink.mercusys.component.onboarding.view.EarthAnimationViewDelegate$initEarth$$inlined$addListener$default$1.onAnimationEnd(Animator.kt:141) at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:719) at android.animation.Animator$AnimatorCaller$$ExternalSyntheticLambda1.call(Unknown Source:4) at android.animation.Animator.callOnList(Animator.java:677) at android.animation.Animator.notifyListeners(Animator.java:616) at android.animation.Animator.notifyEndListeners(Animator.java:641) at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1306) at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1585) at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:328) at android.animation.AnimationHandler.-$$Nest$mdoAnimationFrame(Unknown Source:0) at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:86) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1542) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1553) at android.view.Choreographer.doCallbacks(Choreographer.java:1109) at android.view.Choreographer.doFrame(Choreographer.java:984) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1527) at android.os.Handler.handleCallback(Handler.java:958) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:257) at android.os.Looper.loop(Looper.java:368) at android.app.ActivityThread.main(ActivityThread.java:8839) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:572) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
05-11
我在appium中执行这个语句: self.driver.execute_script('mobile: longClickGesture', { 'By.XPATH': '//*[@content-desc="书签标题:中国 - 百度"]', 'duration': 3000 }) 执行结果: Message: java.lang.IllegalArgumentException: Long click offset coordinates must be provided if element is not set Stacktrace: io.appium.uiautomator2.common.exceptions.InvalidArgumentException: java.lang.IllegalArgumentException: Long click offset coordinates must be provided if element is not set at io.appium.uiautomator2.handler.request.SafeRequestHandler.handle(SafeRequestHan dler.java:47) at io.appium.uiautomator2.server.AppiumServlet.handleRequest(AppiumServlet.java:26 2) at io.appium.uiautomator2.server.AppiumServlet.handleHttpRequest(AppiumServlet.jav a:256) at io.appium.uiautomator2.http.ServerHandler.channelRead(ServerHandler.java:68) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:366) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:352) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelH andlerContext.java:345) at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDeco der.java:102) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:366) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:352) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelH andlerContext.java:345) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.f ireChannelRead(CombinedChannelDuplexHandler.java:435) at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecode r.java:293) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.ja va:267) at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplex Handler.java:250) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:366) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:352) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelH andlerContext.java:345) at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:266 ) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:366) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:352) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelH andlerContext.java:345) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelP ipeline.java:1294) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:366) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChanne lHandlerContext.java:352) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline. java:911) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteC hannel.java:131) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:611) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.jav a:552) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:466) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:438) at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecu tor.java:140) at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(Defa ultThreadFactory.java:144) at java.lang.Thread.run(Thread.java:933) Caused by: java.lang.IllegalArgumentException: Long click offset coordinates must be provided if element is not set at io.appium.uiautomator2.handler.gestures.LongClick.safeHandle(LongClick.java:45) at io.appium.uiautomator2.handler.request.SafeRequestHandler.handle(SafeRequestHan dler.java:41) ... 33 more
最新发布
08-16
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值