向每一个错误致敬——用户上传破损图片时,你的App会怎样?

时间:2022-03-07 13:21:13

问题:在上传图片的时候,如果用户选择了一张破损图片上传,你怎么应对?

凶案现场:

某年某月某日,我正在写代码时,一人拿着手机,怒气冲冲的朝我跑过来,吼道:“怎么回事儿,我上传图片的时候,点这个图片图片就闪退了,点那个图片没事儿。” 我迫不及待的看了下,我靠,还真这么诡异。为什么会这样?

二话不说,插上电脑,打开终端,输入:

 adb logcat *:E

于是,错误尽览无余:

向每一个错误致敬——用户上传破损图片时,你的App会怎样?

很明显在EyishengAPI.java的第600行报了一个NullPointerException

二话不说,追代码,如下:

 599 Bitmap bitmap = BitmapFactory.decodeFile(image1, o2);
 600 bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);

代码的第600行在进行bitmap 的compress.

baos 是啥呢?如下:

ByteArrayOutputStream baos = new ByteArrayOutputStream();

一个字节流,new 了一个ByteArrayOutputStream,这个时候baos肯定不是null,那么……

至此,可以断定,bitmap 为null,BitmapFactory.decodeFile(image1, o2)返回了 null

为什么bitmap为null呢?

  1. 不可能是该图片不存在,如果图片不存在,也就是路径不存在的话,代码是不会走到这里的。
  2. 那么会是什么引起的呢?根据log中的path路径,找到了图库中的该图片,点开后,提示:无法显示该图片缩略图,并且在图库中无该图片的预览。

至此,终于找到了答案,这是一张破损图片。

于是,进行尝试——

  1. 将一个文件 MainActivity.java 重命名为 MainActivity.jpg ,push 到手机上。没错,你敢说,MainActivity.jpg不是一张图片吗?没错,它确实是一张图片,一张破损图片,在Mac上无法查看的图片。
  2. 把 xxxx.mp3 、xxx.mp4 这样的文件重命名为xxxx.jpg or xxx.png , push 到手机上。问题,都会复现。

至此,问题找到了,在上传破损图片时,是无法上传成功。

那么,该怎么办?

在用户上传破损图片时,进行拦截,提示!

问题来了,我在程序中怎么会知道该图片是不是为破损图片呢?

刚才在解析图片时,返回的bitmap为null。那么,是不是,只要是破损图片,解析后返回的bitmap都为null呢?

在进行上面的尝试时,屡试不爽,确实在解析图片时,如果该图片为破损图片,返回的bitmap为null。从这儿得到启发:

在解析时:

  1. 如果返回的bitmap为null,进行拦截,并提示;
  2. 如果返回的bitmap不为null,则放行,走上传图片逻辑
BitmapDecoder decoder = new BitmapDecoder() {
    @Override

    public Bitmap decodeBitmapWithOption(BitmapFactory.Options options) {
        return BitmapFactory.decodeFile(picuri , options);
    }
};
Bitmap  bitmap_temp = decoder.decodeBitmap("width" , "height");

if (bitmap_temp == null){

    L.et("decoder" , "bitmap is null");

    Toast.makeText(getActivity(), "图片解析失败,请检查该图片是否正常", Toast.LENGTH_SHORT).show();

    if (dialog!=null){
        dialog.dismiss();
    }
    return;
}else{
    .....
    //正常逻辑处理
}

用到的BitmapDecoder类:



package com.Util; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory.Options; import android.util.Log; /** * 封装先加载图片bound,计算出inSmallSize之后再加载图片的逻辑操作 * */ public abstract class BitmapDecoder { /** * @param options * @return */ public abstract Bitmap decodeBitmapWithOption(Options options); /** * @param width 图片的目标宽度 * @param height 图片的目标高度 * @return */ public Bitmap decodeBitmap(int width, int height) { // 如果请求原图,则直接加载原图 if (width <= 0 || height <= 0) { return decodeBitmapWithOption(null); } // 1、获取只加载Bitmap宽高等数据的Option, 即设置options.inJustDecodeBounds = true; Options options = getJustDecodeBoundsOptions(); // 2、通过options加载bitmap,此时返回的bitmap为空,数据将存储在options中 decodeBitmapWithOption(options); // 3、计算缩放比例, 并且将options.inJustDecodeBounds设置为false; calculateInSmall(options, width, height); // 4、通过options设置的缩放比例加载图片 return decodeBitmapWithOption(options); } /** * 加载原图 * * @return */ public Bitmap decodeOriginBitmap() { return decodeBitmapWithOption(null); } /** * @return */ private Options getJustDecodeBoundsOptions() { // Options options = new Options(); // 设置为true,表示解析Bitmap对象,该对象不占内存 options.inJustDecodeBounds = true; return options; } /** * @param options * @param width * @param height */ protected void calculateInSmall(Options options, int width, int height) { // 设置缩放比例 options.inSampleSize = computeInSmallSize(options, width, height); Log.d("", "$## inSampleSize = " + options.inSampleSize + ", width = " + width + ", height= " + height); // 图片质量 options.inPreferredConfig = Config.RGB_565; // 设置为false,解析Bitmap对象加入到内存中 options.inJustDecodeBounds = false; options.inPurgeable = true; options.inInputShareable = true; } private int computeInSmallSize(Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // Calculate ratios of height and width to requested height and // width final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // Choose the smallest ratio as inSampleSize value, // this will guarantee a final image // with both dimensions larger than or equal to the requested // height and width. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; // This offers some additional logic in case the image has a strange // aspect ratio. For example, a panorama may have a much larger // width than height. In these cases the total pixels might still // end up being too large to fit comfortably in memory, so we should // be more aggressive with sample down the image (=larger // inSampleSize). final float totalPixels = width * height; // Anything more than 2x the requested pixels we'll sample down // further final float totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { inSampleSize++; } } return inSampleSize; } } 

通过这个错误,我深刻的意识到:

  1. 写代码时,自己考虑的还是太少;
  2. 进行图片上传时,要对破损图片进行拦截;