一引言
Camera做为android手机必备的一款硬件,不管是二维码识别,拍照都离不开Camera。那么我们怎么使用Camera呢?
- 调用系统的相机
- 自己定义一个相机
二调用系统相机
调用系统相机其本质是跨应用间调用,所以我们可以通过隐私意图去打开系统相机,核心代码如下:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 100);复制代码
当然高版本有权限问题,我们在调用这段代码之前要确定已经动态获得相机权限,否则会报错:
<uses-permission android:name="android.permission.CAMERA" />复制代码
这个时候我们可以在onActivityResult方法回调中获取返回来的图片信息:
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
mImageView.setImageBitmap(imageBitmap);复制代码
注意:这里获取图片是通过getExtras获取,并且直接返回的是bitmap缩略图对象。而并未返回图片储存地址的uri。这样方式我们只能获取一次缩略图bitmap对象。我甚至都怀疑google没有物理储存这张拍摄的图片,而只是在内存中储存了缩略图。
且不管到底有没有物理储存,就返回缩略图这种方式而言,都满足不了我们需求。我们要的是原图。我们怎么才能获取拍出来的原图呢?
google给我们提供了能够传递图片储存路径的方法,让我们获取通过路径获取原图:
intent.putExtra(MediaStore.EXTRA_OUTPUT, camera_uri);复制代码
第二个参数需要一个uri,这个uri是通过File转换到:
file2 = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + ".jpg");
file2.getParentFile().mkdirs();
//根据路径获取uri
camera_uri = Uri.fromFile(file2);复制代码
当然为了适配高版本限制,我们需要判断大于等于24的版本,通过Provider获取这个uri,最后代码如下:
//创建隐私意图
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//创建图片储存路径
file2 = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + ".jpg");
file2.getParentFile().mkdirs();
//根据路径获取uri
camera_uri = Uri.fromFile(file2);
//如果版本是n,用另外一种方式获取uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
camera_uri = FileProvider.getUriForFile(MainActivity.this, "com.advertising.administrator.camera.FileProvider", file2);
//添加权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
//把储存路径传递过去
intent.putExtra(MediaStore.EXTRA_OUTPUT, camera_uri);
startActivityForResult(intent, 100);复制代码
清单配置如下:
<provider
android:name="android.support.v4.content.FileProvider"
//注意这里要和代码中字符串一样,最后以FileProvider结尾
android:authorities="com.advertising.administrator.camera.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="name,authorities,exported,grantUriPermissions">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"
tools:replace="name,resource" />复制代码
xml如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path name="camera_photos" path="" />
</paths>
</resources>复制代码
注意:这里再用上边的方式获取图片地址会报异常。别忘了获取读写外部储存和相机权限,否则会报错。
那么我们该怎么获取呢?
我们知道图片储存路径,就可以在回调中通过路径直接获取图片:
String absolutePath = file2.getAbsolutePath();
mImageView.setImageBitmap(BitmapFactory.decodeFile(absolutePath));复制代码
当然我们也知道储存图片的uri,我们同样可以根据uri去加载图片glide框架支持通过uri去加载图片。
这样我们就能获取原图片。当然有的需求是获取原图,然后裁剪。
那么怎么裁剪呢?
其实裁剪的本质还是通过隐私意图,调用系统的裁剪。
Intent intent = new Intent("com.android.camera.action.CROP")
activity.startActivityForResult(intent, requestCode);复制代码
如果需求是裁剪图片再返回,我们应该在第一次回调中开启裁剪的意图,然后第二次接受裁剪后图片的回调,这里我吧裁剪的跳转抽取成方法如下:
public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.setDataAndType(orgUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", aspectX);
intent.putExtra("aspectY", aspectY);
intent.putExtra("outputX", width);
intent.putExtra("outputY", height);
intent.putExtra("scale", true);
//将剪切的图片保存到目标Uri中
intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
activity.startActivityForResult(intent, requestCode);
}复制代码
我们只需要在第一次回调的时候调用这个静态方法即可:
case 100://打开系统相机获取图片地址
//把裁剪的路径转换成uri
fileCropUri = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis()+ "crop_photo.jpg");
Uri cropImageUri = Uri.fromFile(fileCropUri);
//开启裁剪
cropImageUri(this, camera_uri, cropImageUri, 1, 1, 480, 480, CODE_RESULT_REQUEST);
break;
case CODE_RESULT_REQUEST://裁剪的回调
mImageView.setImageBitmap(BitmapFactory.decodeFile(fileCropUri.getAbsolutePath()));
break;复制代码
这样我们就可以通过copy的File或者uri去获取Copy的图片。
不知道你有没有发现一个问题,我们拍的照片并未在手机相册中显示,如果我们想让拍摄的相片原图和缩略图都在手机系统相册中显示,我们应该怎么办呢?
先来解释一下为什么没有显示,原因是因为拍摄好的照片并未通知系统数据库,让其更新。
那么,怎么通知系统更新呢?
这里就需要用到广播:
private void galleryAddPic(File file) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
//file即文件的路径
Uri contentUri = Uri.fromFile(file);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}复制代码
在每次回调地方添加上这个方法,即可让裁剪图和原图都在系统相册中显示。通知数据类添加一个uri。
之后就是奉上完整代码:
请求相机代码:
//请求权限
new RxPermissions(this)
.request(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
if (aBoolean) {
//创建隐私意图
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//创建图片储存路径
file2 = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + ".jpg");
file2.getParentFile().mkdirs();
//根据路径获取uri
camera_uri = Uri.fromFile(file2);
//如果版本是大于等于n,用另外一种方式获取uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
camera_uri = FileProvider.getUriForFile(MainActivity.this, "com.advertising.administrator.camera.FileProvider", file2);
//添加权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
//把储存路径传递过去
intent.putExtra(MediaStore.EXTRA_OUTPUT, camera_uri);
startActivityForResult(intent, 100);
}
}
});复制代码
回调处理代码:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case 100://打开系统相机获取图片回调
//通知数据库,让其在系统相册中显示图片
galleryAddPic(file2);
//获取原图展示
// String absolutePath = file2.getAbsolutePath();
// mImageView.setImageBitmap(BitmapFactory.decodeFile(absolutePath));
// //把裁剪的路径转换成uri
fileCropUri = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + "crop_photo.jpg");
Uri cropImageUri = Uri.fromFile(fileCropUri);
//开启裁剪
cropImageUri(this, camera_uri, cropImageUri, 1, 1, 480, 480, CODE_RESULT_REQUEST);
break;
case CODE_RESULT_REQUEST://裁剪的回调
//通知数据库,让其在系统相册中显示图片
galleryAddPic(fileCropUri);
//获取裁剪后的图片展示
mImageView.setImageBitmap(BitmapFactory.decodeFile(fileCropUri.getAbsolutePath()));
break;
default:
}
}
}复制代码
通知数据库代码:
private void galleryAddPic(File file) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
//mCurrentPhotoPath即文件的路径
Uri contentUri = Uri.fromFile(file);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}复制代码
开启裁剪代码:
public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.setDataAndType(orgUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", aspectX);
intent.putExtra("aspectY", aspectY);
intent.putExtra("outputX", width);
intent.putExtra("outputY", height);
intent.putExtra("scale", true);
//将剪切的图片保存到目标Uri中
intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
activity.startActivityForResult(intent, requestCode);
}复制代码
调用相机基本就这些用法和适配。
三打开系统相册
上边已经介绍了调用系统相机的一些用法,本来笔者不打算介绍打开相册,但是写到这里,篇幅很尴尬,如果直接去介绍自定义相机,文章会变得特别长,于是决定介绍一下常常伴随着调用启动相机的功能,打开系统相册。
打开系统相册是跨应用之间共享数据库,还是需要隐私意图,去开启相册,代码如下:
Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
// 如果限制上传到服务器的图片类型时可以直接写如:"image/jpeg 、 image/png等的类型" 所有类型则写 "image/*
photoPickerIntent.setType("image/*");
startActivityForResult(photoPickerIntent, 200);复制代码
注意:需要有读取外部储存的权限,否则回调的时候会出错。
接下来就是看回调处理了,我们在回调中可以通过data获取图片的uri。
Uri uri = data.getData();复制代码
这个返回的uri在4.4之前是:
content://media/external/images/media/20复制代码
4.4之后是:
content://com.android.providers.media.documents/document/image%3A23606复制代码
你会发现4.4之后给的uri好像被加密处理一样。我们没有办法像4.4之前通过查询数据库去获取图片绝对路径:
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
}复制代码
public static String getPath(final Context context, final Uri uri) {
//判断版本是否大于18
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
//初始化一个字符串
String pathHead = "file://";
// 判断4.4之后的版本
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider,解析出来路径返回
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider,下载路径
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return pathHead + getDataColumn(context, contentUri, null, null);
}
// MediaProvider,多媒体
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{split[1]};
return pathHead + getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// 4.4之前的版本
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return pathHead + getDataColumn(context, uri, null, null);
}
// 4.4之前的版本,以File开头
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return pathHead + uri.getPath();
}
return null;
}
/**
* @param uri The Uri to check.
* @return 如果是外部储存
*/
private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* 通过内容提供者,获取图片的绝对路径
*
* @param context
* @param uri
* @param selection
* @param selectionArgs
* @return
*/
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}复制代码
这样我们就可以获取图片的路径:
Uri uri = data.getData();
String path = PhotoUtils.getPath(this, uri);
path = path.substring(7, path.length());
image.setImageBitmap(BitmapFactory.decodeFile(path));复制代码
这里之所以需要截取前七个字符,是因为绝对路径前边不需要file:///开头
还有裁剪和通知数据库更新的代码和拍照的代码一样的实现。
注意7.0后禁止在您的应用外部公开 file:// URI。
之前,对于开启系统相机和打开相册笔者一直迷糊,迷糊的原因是:系统4.4之后对uri进行加密需要翻译一下,和7.0不能外部公开 file:// URI混为一谈。以为是一回事。下一篇,开始进入自定义相机。