问题:在上传图片的时候,如果用户选择了一张破损图片上传,你怎么应对?
凶案现场:
某年某月某日,我正在写代码时,一人拿着手机,怒气冲冲的朝我跑过来,吼道:“怎么回事儿,我上传图片的时候,点这个图片图片就闪退了,点那个图片没事儿。” 我迫不及待的看了下,我靠,还真这么诡异。为什么会这样?
二话不说,插上电脑,打开终端,输入:
adb logcat *:E
于是,错误尽览无余:
很明显在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呢?
- 不可能是该图片不存在,如果图片不存在,也就是路径不存在的话,代码是不会走到这里的。
- 那么会是什么引起的呢?根据log中的path路径,找到了图库中的该图片,点开后,提示:
无法显示该图片缩略图
,并且在图库中无该图片的预览。
至此,终于找到了答案,这是一张破损图片。
于是,进行尝试——
- 将一个文件 MainActivity.java 重命名为 MainActivity.jpg ,push 到手机上。没错,你敢说,MainActivity.jpg不是一张图片吗?没错,它确实是一张图片,一张破损图片,在Mac上无法查看的图片。
- 把 xxxx.mp3 、xxx.mp4 这样的文件重命名为xxxx.jpg or xxx.png , push 到手机上。问题,都会复现。
至此,问题找到了,在上传破损图片时,是无法上传成功。
那么,该怎么办?
在用户上传破损图片时,进行拦截,提示!
问题来了,我在程序中怎么会知道该图片是不是为破损图片呢?
刚才在解析图片时,返回的bitmap为null。那么,是不是,只要是破损图片,解析后返回的bitmap都为null呢?
在进行上面的尝试时,屡试不爽,确实在解析图片时,如果该图片为破损图片,返回的bitmap为null。从这儿得到启发:
在解析时:
- 如果返回的bitmap为null,进行拦截,并提示;
- 如果返回的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; } }
通过这个错误,我深刻的意识到:
- 写代码时,自己考虑的还是太少;
- 进行图片上传时,要对破损图片进行拦截;