前言
实现一个类似于微信的图片界面,包括拍照和相册,拍照包括裁剪,相册包括预览,可以选中指定张数的图片,将图片转换为base64上传到服务器。可以从服务器将已经上传的图片资源,通过base64字符串下载,然后将base64转换为图片,在界面显示,具体效果图:
这个类似于微信的效果,是我在网上找了个demo,这篇文章主要是谈base64和图片转换遇到的问题,实现效果可以自行下载这个demo,仿微信选择图片demo,(不知道为什么,现在csdn上传资源,不能设置分数,直接默认5积分,本来想免费分享的,因为这个demo是别人的成果。。。)可以自行修改需要的样式。涉及到图片上传,就会涉及到图片压缩,网上现在流行的几种压缩库,本来使用的是Compressor,发现压缩.png文件会报错,问题可以查看这个ExifInterface got an unsupported image,最后用了LuBan,Curzibn/Luban
实现
首先,根据以上demo,可以对照片进行处理,相册可以预览,拍照可以裁剪,现在就进行对图片处理
先贴几个对图片进行处理的方法
/**
* 将文件中的数据读取到Byte数组中
*
* @param file
* @return
*/
public static byte[] readRealFileToByte(File file)
{
Long filelength = file.length(); // 获取文件长度
byte[] filecontent = new byte[filelength.intValue()];
try
{
FileInputStream in = new FileInputStream(file);
in.read(filecontent);
in.close();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
return filecontent; // 返回文件内容,默认编码
}
public static String getEncoded64ImageStringFromByte(byte[] bytes)
{
if (bytes == null || bytes.length == 0)
return null;
String imgString = Base64.encodeToString(bytes, Base64.DEFAULT);
return imgString;
}
/**
* base64字符串转化成图片
*/
public static String GenerateImage(String base64Code, String savePathKey)
{
//对字节数组字符串进行Base64解码并生成图片
if (base64Code == null)
{ //图像数据为空
return "";
}
try
{
byte[] buffer = Base64.decode(base64Code, Base64.DEFAULT);
// 新生成的jpg图片
// 新图片的文件夹, 如果没有, 就创建
File fileDir = new File(dirPath);
if (!fileDir.exists())
{
fileDir.mkdirs();
}
// 文件夹现在存在了, 可以在此文件夹下创建图片了
String imgFilePath = dirPath + savePathKey + ".jpg";
File file = new File(imgFilePath);
if (!file.exists())
{
file.createNewFile();
}
OutputStream out = new FileOutputStream(imgFilePath);
out.write(buffer);
out.flush();
out.close();
return imgFilePath;
}
catch (Exception e)
{
return "";
}
}
/**
* 删除指定目录下文件及目录
*/
public static void deleteFolderFile(String filePath)
{
if (!TextUtils.isEmpty(filePath))
{
try
{
File file = new File(filePath);
if (file.isDirectory())
{// 处理目录
File files[] = file.listFiles();
for (int i = 0; i < files.length; i++)
{
deleteFolderFile(files[i].getAbsolutePath());
}
}
if (!file.isDirectory())
{// 如果是文件,删除
file.delete();
}
else
{// 目录
if (file.listFiles().length == 0)
{// 目录下没有文件或者目录,删除
file.delete();
}
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static Bitmap imageZoom(Bitmap bitMap,double maxSize) {
//图片允许最大空间 单位:KB
//将bitmap放至数组中,意在bitmap的大小(与实际读取的原文件要大)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitMap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] b = baos.toByteArray();
//将字节换成KB
double mid = b.length/1024;
//判断bitmap占用空间是否大于允许最大空间 如果大于则压缩 小于则不压缩
if (mid > maxSize) {
//获取bitmap大小 是允许最大大小的多少倍
double i = mid / maxSize;
//开始压缩 此处用到平方根 将宽带和高度压缩掉对应的平方根倍 (1.保持刻度和高度和原bitmap比率一致,压缩后也达到了最大大小占用空间的大小)
bitMap = zoomImage(bitMap, bitMap.getWidth() / Math.sqrt(i),
bitMap.getHeight() / Math.sqrt(i));
}
return bitMap;
}
/***
* 图片的缩放方法
*
* @param bgimage
* :源图片资源
* @param newWidth
* :缩放后宽度
* @param newHeight
* :缩放后高度
* @return
*/
private static Bitmap zoomImage(Bitmap bgimage, double newWidth,
double newHeight) {
// 获取这个图片的宽和高
float width = bgimage.getWidth();
float height = bgimage.getHeight();
// 创建操作图片用的matrix对象
Matrix matrix = new Matrix();
// 计算宽高缩放率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 缩放图片动作
matrix.postScale(scaleWidth, scaleHeight);
Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
(int) height, matrix, true);
return bitmap;
}
图片上传
首先,对已经选择的图片,进行压缩,然后转换为base64,调用接口上传,具体代码
private ArrayList<ImageItem> selImageList; //当前选择的所有图片
......
private void processSubmit()
{
showProgressDialog("请稍候", null, null);
//selImageList已选中图片列表
if (selImageList.size() > 0)
{
List<File> selImageFileList = new ArrayList<>();
ArrayList<String> base64List = new ArrayList<>();
for (ImageItem imageItem : selImageList)
{
try
{
File imageFile = new File(imageItem.path);
selImageFileList.add(imageFile);
}
catch (Exception e)
{
e.printStackTrace();
}
}
Luban.with(this).load(selImageFileList).ignoreBy(100).setCompressListener(new OnCompressListener()
{
@Override
public void onStart()
{
// TODO 压缩开始前调用,可以在方法内启动 loading UI
}
@Override
public void onSuccess(File file)
{
/*
String filetype = "";
Log.e("file length", ((float) imageFile.length() / 1024 / 1024) + "MB");
*/
//有些情况下,发现Luban压缩不到指定尺寸,可以手动写一个方法,算是个备选方案
Bitmap bitmap = ImageUtil.imageZoom(BitmapFactory.decodeFile(file.getPath()),50);
String base64 = "";
if (bitmap == null)
{
ToastUtil.showShortToast("获取图片失败");
return;
}
Log.e("bitmap size", ((float) (bitmap.getRowBytes() * bitmap.getHeight()) / 1024 / 1024) + "MB");
base64 = ImageUtil.bitmaptoString(bitmap);
/* // TODO 压缩成功后调用,返回压缩后的图片文件
byte[] bytes = ImageUtil.readRealFileToByte(file);
Log.i("MyImageTest", "After Compress bytes.length= " + ((float) bytes.length / 1024 / 1024) + "MB");
String base64 = ImageUtil.getEncoded64ImageStringFromByte(bytes);*/
base64List.add(base64);
if (base64List.size() == selImageList.size())
{
uploadImg(base64List);
}
}
@Override
public void onError(Throwable e)
{
// TODO 当压缩过程出现问题时调用
dismissProgressDialog();
ToastUtil.showShortToast("图片转换失败");
}
}).launch();
}
else
{
uploadImg(null);
}
}
上传很简单,选择图片–>压缩–>转成base64–>调用接口上传
下载图片
下载图片的流程:调用接口–>获取base64–>转换为图片–>进行显示
首先,为了配合上边说的仿微信的demo,尽量少改动,我这边是将base64转成的图片,保存到sd卡中的一个文件夹下,然后在上传图片结束以后,将该文件夹删除。保存到本地以后,就可以跟其他本地图片一样操作了。
踩坑
重点重点:就这个把base64转成图片,保存到本地,然后显示的这样一个功能,有一个很大的坑,因为demo里边显示图片用的是Glide,我保存图片是保存到sd卡,所以,坑就是:如果我下载下来的图片,命名为1.jpg,然后更改过图片,重新上传,然后重新下载,保存到本地,也是被命名为1.jpg,本地sd卡中的图片已经更改了,但是,Glide的缓存机制,
具体参看Glide v4文档
所以,Glide会先去缓存中获取图片,现在缓存中是有1.jpg的,所以根本不会去sd卡里获取,所以显示的还是老图片,这样就是一个问题,因为其他功能也在使用Glide,所以不敢修改Glide的缓存配置,现在的方法就是,将每次下载的base64转换成的图片的命名,设置为唯一的,这样内存中就不会有相同姓名的图片,只能到sd卡里读取,我这边命名是根据时间加索引,本来只是时间,结果有可能同一秒内可以处理多张图片,也会出现问题,所以加上索引就是唯一了
下载图片实现
if (data.getRstData().size() > 0)
{
//图片实体类
uploadImgBean = data.getRstData().get(0);
//多个图片通过§字符分割
if (uploadImgBean.getYWIMG().contains("§"))
{
String[] base64s = uploadImgBean.getYWIMG().split("§");
for (int i = 0; i < base64s.length; i++)
{
//将接口获取的图片临时保存到本地,命名必须唯一,如果缓存中有相同名称的图片,会优先获取缓存中的,不会度本地sd中的,容易造成显示不正确的bug
ImageItem imageItem = new ImageItem();
imageItem.path = ImageUtil.GenerateImage(base64s[i], DateUtil.getNowDateHHmmssString() + i);
selImageList.add(imageItem);
}
}
else
{
if(!uploadImgBean.getYWIMG().equals("")){
ImageItem imageItem = new ImageItem();
imageItem.path = ImageUtil.GenerateImage(uploadImgBean.getYWIMG(), DateUtil.getNowDateHHmmssString() + "10");
selImageList.add(imageItem);
}
}
adapter.setImages(selImageList);
// adapter.notifyDataSetChanged();
isButtonCanClick(false);
dismissProgressDialog();
@Override
protected void onDestroy()
{
super.onDestroy();
try
{
ImageUtil.deleteFolderFile(ImageUtil.dirPath);
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* 获得当前时间
* <p/>
* 格式:10:38:53
*
* @return
*/
public static String getNowDateHHmmssString()
{
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
Date currentTime = new Date();
String dateString = formatter.format(currentTime);
return dateString;
}