本文记录使用Android时关于图片所遇到的坑。
主要记录以下问题:
- 如何获取相册中的图片
- 如何剪裁所得到的图片
- 如何拍照并剪裁图片
- 如何压缩图片
- 图片保存在手机本地的存取
- 图片在APP与服务器之间的传输
如何获取相册中的图片
简单来说,我们就是要写一个Intent去相册中挑选图片,见代码:
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI);
startActivityForResult(intent, GET_FROM_GALLERY);
这样我们就能改onActivtyResult中接收的到的数据:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case GET_FROM_GALLERY:
if(resultCode == Activity.RESULT_OK){
//获取所选照片的URI
Uri selectedImage = data.getData();
// 设置照片
// Bitmap bitmap = null;
// try {
// bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), selectedImage);
// } catch (IOException e) {
// e.printStackTrace();
// }
// image.setImageBitmap(bitmap);
performCrop(selectedImage);
}
break;
这样我们就能简单的获得所选图片的Uri,从而进一步对图片操作,剪裁图片的方法就再performCrop(selectedImage)里。如果你想直接将所选择的图片设置起来,就是上面代码片段中被注释掉的那一部分了。
如何剪裁所得到的图片
首先我们要的到图片的Uri,然后再写一个Intent,没错,剪裁其实是调用了系统的图片编辑器,所以还是要用Intent,如果你想实现自己的剪裁器或某些特效(比如微信的头像剪裁),那就要辛苦一下自己写个工具类了。
关于启动系统剪裁的Intent是这么写的:
/**
* 剪裁图片, 要得到图片的uri才能使用, 可以先启动选择图片的intent, 然后在onActivityResult中
* 得到uri, 然后再调用这个方法
* @param contentUri 需要剪裁的图片的uri
*/
private void performCrop(Uri contentUri) {
try {
//Start Crop Activity
Intent cropIntent = new Intent("com.android.camera.action.CROP");
cropIntent.setDataAndType(contentUri, "image/*");
// set crop properties
cropIntent.putExtra("crop", "true");
// indicate aspect of desired crop
cropIntent.putExtra("aspectX", 1);
cropIntent.putExtra("aspectY", 1);
// indicate output X and Y
cropIntent.putExtra("outputX", 280);
cropIntent.putExtra("outputY", 280);
// retrieve data on return
cropIntent.putExtra("return-data", true);
// start the activity - we handle returning in onActivityResult
startActivityForResult(cropIntent, GET_FROM_GALLERY_CROP);
}
// respond to users whose devices do not support the crop action
catch (ActivityNotFoundException anfe) {
// display an error message
String errorMessage = "your device doesn't support the crop action!";
Toast toast = Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT);
toast.show();
}
}
我们看到Intent中设置了一些参数,比如剪裁图片的位置,还有剪裁图片的宽高比例,见名知意,我就不解释了。如果我们完成剪裁,那么接下来还是要在onActivityResult()方法中接受数据:
case GET_FROM_GALLERY_CROP:
if(resultCode == Activity.RESULT_OK){
//设置
Bundle extras = data.getExtras();
Bitmap selectedBitmap = extras.getParcelable("data");
image.setImageBitmap(selectedBitmap);
这样得到bitmap就是符合我们要求的bitmap了。
如何拍照并剪裁图片
当然了,剪裁图片不只一种方式,那我们该如何通过拍照获得图片并剪裁呢?
首先当然还是要写一个Intent,然后使用这个Intent去启动系统相机应用获得照片:
/**
* 创建拍照的intent并存放照片的文件
*/
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile(loginUserPrefix);
} catch (IOException ex) {
Log.e("LocalImageTest", "创建存储文件失败");
}
if (photoFile != null) {
Uri photoURI = Uri.fromFile(photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, GET_FROM_CAMERA);
}
}
}
上面的代码除了简单的启动了相机之外,还指定了文件的存储位置。这样可以方便对图片存取的操作,在下面几节,这就不索罗。
那在得到了图片之后我们该怎么做了,当然还是老一套,先得到图片的Uri,然后写一个Intent去启动系统的剪裁应用呗:
case GET_FROM_CAMERA:
if(resultCode == Activity.RESULT_OK){
Uri selectedImage = Uri.fromFile(new File(mCurrentPhotoPath));
performCrop(selectedImage);
}
break;
在onActivity中接受图片,然后把图片的Uri得到,位置我们之前已经将图片保存的文件创建好了,所以这里可以很简单就得到Uri。
如何压缩图片
FileOutputStream out = null;
try {
out = new FileOutputStream(mCurrentPhotoPath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, out);
压缩图片相对来说比较简单,只是调用了一个方法就完成了,需要创建一个输出流,对应的是压缩之后的图片的位置。
图片在手机本地的存取
一般每个应用在手机里都有一个自己的文件夹,用于存储本应用的图片以及其他文件,所以这一部分可以说是在APP开发过程中很重要的一步。
我写了一个工具类,里面是关于图片在手机中存取的整个方法,每个方法都由注释,名字也很好懂,我就不啰嗦了:
package utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileUtils {
public static final String HAPPY_GO = "HappyGo";
public static final String PHOTO_SUFFIX = ".jpg";
public static final String TAG = "TAG";
public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), HAPPY_GO);
/**
* 从文件中加载bitmap
* @param name 文件名
* @return Bitmap对象
*/
public static Bitmap loadImageFromStorage(String name)
{
if (!APP_DIR.exists()) {
if(!APP_DIR.mkdir()){
Log.e(TAG, "创建App_Dir失败");
return null;
}
}
File file = getMediaFile(name);
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
return bitmap;
}
}
/**
* 将bitmap存储到文件中
* @param name 文件名
* @param image bitmap
*/
public static void storeImageIntoStorage(String name, Bitmap image) {
File pictureFile = getMediaFile(name);
if (pictureFile == null) {
Log.e(TAG, "Error creating media file, check storage permissions: ");
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
/**
* 创建文件
* @param name 文件名
* @return File对象
*/
public static File getMediaFile(String name){
File mediaFile;
String fileName = name + PHOTO_SUFFIX;
mediaFile = new File(APP_DIR, fileName);
return mediaFile;
}
/**
* 删除文件
* @param name 文件名
* @return 删除成功则返回true
*/
public static boolean deleteMediaFile(String name){
File mediaFile;
String fileName = name + PHOTO_SUFFIX;
mediaFile = new File(APP_DIR, fileName);
boolean result = mediaFile.delete();
return result;
}
}
图片在APP与服务器之间的传输
如果要实现一个上传头像的功能,我相信图片在APP和服务器之间的传输是必不可少的,那么传输到底该怎么传呢?可不是直接传一个bitmap,一般后台都没这个类。我的方式是把图片转码成字符串,然后将这个字符串传给后台,保存在数据库,然后需要的时候,后台在把这个由图片转变来的字符串传回前端。
当然了,这里涉及一个工具类,把一个图片文件转码成字符串。这个jar包是BASE64Encoder。
有个这个jar包,我们就能直接写两个方法,实现字符串和文件之间的相互转化了:
/**
* 将手机内存的某一图片转化为字符串
* @param fileName 文件名
* @return 字符串
*/
public static String changePictureIntoString(String fileName) {
byte[] data = null;
String path = FileUtils.getMediaFile(fileName).getAbsolutePath();
try {
InputStream in = new FileInputStream(path);
data = new byte[in.available()];
in.read(data);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);
}
/**
* 将字符串还原成jpg图片,然后存储在手机内存中
* @param imgStr 图片对应的字符串
* @param fileName 文件名
* @return 操作是否成功, true表示成功
*/
public static boolean changeStringToPictureAndSave(String imgStr, String fileName) {
if (imgStr == null)
return false;
BASE64Decoder decoder = new BASE64Decoder();
try {
// Base64解码
byte[] bytes = decoder.decodeBuffer(imgStr);
for (int i = 0; i < bytes.length; ++i) {
if (bytes[i] < 0) {
bytes[i] += 256;
}
}
//获取文件的绝对路径
String path = FileUtils.getMediaFile(fileName).getAbsolutePath();
//将数据保存到文件中
OutputStream out = new FileOutputStream(path);
out.write(bytes);
out.flush();
out.close();
return true;
} catch (Exception e) {
return false;
}
}
/**
* 将字符串直接转化为bitmap
* @param string 字符串
* @return bitmap
*/
public static Bitmap stringToBitmap(String string){
Bitmap bitmap=null;
try {
byte[]bitmapArray;
bitmapArray= Base64.decode(string, Base64.DEFAULT);
bitmap= BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
/**
* 将bitmap直接转化为字符串
* @param bitmap bitmap
* @return 字符串
*/
public static String bitmapToString(Bitmap bitmap){
String string=null;
ByteArrayOutputStream bStream=new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 30, bStream);
byte[]bytes=bStream.toByteArray();
string= Base64.encodeToString(bytes, Base64.DEFAULT);
return string;
}
这里有四个方法,前两个方法是需要文件操作的,而后面两个方法是很简单,直接实现字符串和bitmap对象之前的相互转化,不过它的代码是原本10k的文件变成了160k-220k的文件,在服务器上很占地方,而且传输很慢,很消耗流量。所以我还是推荐使用前两个方法。方法都由解释,我就不复述了。