本文记录使用Android时关于图片所遇到的坑。
主要记录以下问题:
- 如何获取相册中的图片
- 如何剪裁所得到的图片
- 如何拍照并剪裁图片
- 如何压缩图片
- 图片保存在手机本地的存取
- 图片在APP与服务器之间的传输
如何获取相册中的图片
简单来说,我们就是要写一个Intent去相册中挑选图片,见代码:
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI);
startActivityForResult(intent, GET_FROM_GALLERY);
这样我们就能改onActivtyResult中接收的到的数据:
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {case GET_FROM_GALLERY:if(resultCode == Activity.RESULT_OK){//获取所选照片的URIUri 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 ActivityIntent cropIntent = new Intent("com.android.camera.action.CROP");cropIntent.setDataAndType(contentUri, "image/*");// set crop propertiescropIntent.putExtra("crop", "true");// indicate aspect of desired cropcropIntent.putExtra("aspectX", 1);cropIntent.putExtra("aspectY", 1);// indicate output X and YcropIntent.putExtra("outputX", 280);cropIntent.putExtra("outputY", 280);// retrieve data on returncropIntent.putExtra("return-data", true);// start the activity - we handle returning in onActivityResultstartActivityForResult(cropIntent, GET_FROM_GALLERY_CROP);}// respond to users whose devices do not support the crop actioncatch (ActivityNotFoundException anfe) {// display an error messageString 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的文件,在服务器上很占地方,而且传输很慢,很消耗流量。所以我还是推荐使用前两个方法。方法都由解释,我就不复述了。