问题:在上传图片的时候,如果用户选择了一张破损图片上传,你怎么应对?
凶案现场:
某年某月某日,我正在写代码时,一人拿着手机,怒气冲冲的朝我跑过来,吼道:“怎么回事儿,我上传图片的时候,点这个图片图片就闪退了,点那个图片没事儿。” 我迫不及待的看了下,我靠,还真这么诡异。为什么会这样?
二话不说,插上电脑,打开终端,输入:
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;
}
}
通过这个错误,我深刻的意识到:
- 写代码时,自己考虑的还是太少;
- 进行图片上传时,要对破损图片进行拦截;