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

本文记录了一次Bitmap压缩处理中遇到的宽度和高度必须大于0的异常,并详细分析了问题原因及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

问题出现场景:

项目里有处地方需要把下载下来的图片进行压缩,这里图片下载使用的是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是合理的。

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

### 问题分析与解决方法 在 Java 编程中,`java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String` 是一种常见的运行时异常。这种异常通常发生在尝试对 `java.util.Date` 和 `java.lang.String` 类型进行比较时[^1]。具体来说,当 SQL 查询条件中包含对日期字段的字符串判断逻辑(例如 `createTime = ' '`),而实际传递的参数是 `Date` 类型时,就会引发此异常[^2]。 为了解决这一问题,需要确保在代码中正确处理类型转换,并避免直接对不同类型的对象进行比较。以下是具体的解决方案: --- ### 解决方案 #### 1. 修改 XML 或 SQL 条件逻辑 如果问题出现在 MyBatis 的 XML 配置文件中,可以通过调整 `<if>` 标签的条件逻辑来避免类型不匹配的问题。例如,将以下代码: ```xml <if test="deadline != null and deadline != ''"> deadline = #{deadline}, </if> ``` 修改为仅检查 `deadline` 是否为 `null`,而不进行字符串比较: ```xml <if test="deadline != null"> deadline = #{deadline}, </if> ``` 这样可以有效避免 `java.util.Date` 和 `java.lang.String` 的类型冲突[^3]。 --- #### 2. 确保参数类型一致性 在传递参数到 SQL 查询之前,应确保所有参数的类型一致。例如,如果数据库中的字段是 `DATE` 类型,则应该传递 `java.util.Date` 或其子类(如 `java.sql.Date`)作为参数,而不是传递字符串。 可以在代码中显式地进行类型转换,例如使用 `SimpleDateFormat` 将字符串转换为日期: ```java import java.text.SimpleDateFormat; import java.util.Date; public class DateConverter { public static Date convertToDate(String dateString) throws Exception { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); return dateFormat.parse(dateString); } } ``` 然后,在调用 SQL 查询之前,先将字符串参数转换为 `Date` 类型: ```java try { String deadlineStr = "2023-10-01"; Date deadlineDate = DateConverter.convertToDate(deadlineStr); // 将 deadlineDate 作为参数传递给 SQL 查询 } catch (Exception e) { e.printStackTrace(); } ``` --- #### 3. 使用 MyBatis 的类型处理器 MyBatis 提供了类型处理器(TypeHandler)机制,用于在 Java 对象和数据库字段之间进行类型转换。可以通过自定义类型处理器来确保 `java.util.Date` 和 `String` 之间的正确转换。 例如,定义一个自定义的类型处理器: ```java import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; public class DateStringTypeHandler extends BaseTypeHandler<Date> { private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); @Override public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, DATE_FORMAT.format(parameter)); } @Override public Date getNullableResult(ResultSet rs, String columnName) throws SQLException { String dateStr = rs.getString(columnName); return dateStr == null ? null : parseDate(dateStr); } @Override public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String dateStr = rs.getString(columnIndex); return dateStr == null ? null : parseDate(dateStr); } @Override public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String dateStr = cs.getString(columnIndex); return dateStr == null ? null : parseDate(dateStr); } private Date parseDate(String dateStr) { try { return DATE_FORMAT.parse(dateStr); } catch (Exception e) { throw new RuntimeException("Failed to parse date string: " + dateStr, e); } } } ``` 接着,在 MyBatis 的配置文件中注册该类型处理器: ```xml <typeHandlers> <typeHandler handler="com.example.DateStringTypeHandler" javaType="java.util.Date" jdbcType="VARCHAR"/> </typeHandlers> ``` --- ### 总结 通过上述方法,可以有效解决 `java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String` 的问题。核心在于确保参数类型的一致性,并避免在代码或 SQL 中直接对不同类型的对象进行比较。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值