调用系统相机完成相机功能
系统适配:安卓6.0 安卓7.0
参考链接:1. 2.
开发测试工具:Android Studio
代码链接:Github_TestSystemCamera
最近公司的项目需要用到相机拍照的功能,但是原有的代码过于陈旧,对于有些机型兼容得不是那么理想,为此,特意研究了一下相关代码,写一篇文章记录一下一路遇到的问题。
1.判断是否有相机设备
// 判断是否有相机设备
PackageManager pm = getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) && !pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
return;
}
// Ensure that there's a camera activity to handle the intent
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) == null){
return;
}
2.调用系统相机
为 Intent 对象设置 Action 的值为 MediaStore.ACTION_IMAGE_CAPTURE,就可以将意图指向系统相机。接下来,对于不同需求,我们可以进行不同的设置。
2.1 只获取缩略图作为小图显示
对于这种情况,我们只需要利用设置了指定 Action 的 Intent ,直接进行跳转即可。
Intent thumbIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(thumbIntent, REQ_THUMB_NAIL_CAPTURE);
在相机操作结束后,我们在 Activity 的 onActivityResult 方法中可以直接获取到对应的 Bitmap 对象
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.i(TAG, "onActivityResult: requestCode-" + requestCode + ",resultCode-" + resultCode + ",data:" + data);
switch (requestCode) {
case REQ_THUMB_NAIL_CAPTURE:
if (resultCode != RESULT_OK) return;
Bundle b = data.getExtras();
Bitmap thumbBitmap = (Bitmap) b.get("data");
mImageView.setImageBitmap(thumbBitmap);
break;
}
}
利用这种办法获取的图片,图片质量不高,像素数和色彩都没法满足一般的图片展示需求,只能作为缩略图展示,应用比较狭窄。因此,我们主要介绍下面一种方法来获取全尺寸的照片。
2.2 拍摄获取全尺寸的图片
同样的,我们也需要一个 Action 为 MediaStore.ACTION_IMAGE_CAPTURE 的 Intent 对象,此时我们需要将图片的路径保存到 Intent 对象中,接下来就到了我们的第一个重点,构造图片存储路径。
2.2.1 图片存储路径
拍摄全尺寸图片的时候,我们需要在 Intent 当中传入图片存储路径的 Uri( 对应的 Key 值为 MediaStore.EXTRA_OUTPUT),在安卓7.0之前,我们一般用 Uri.fromFile(file) 获取文件 Uri。但是安卓7.0对于系统内部的路径管理更加严苛,如果通过上面的方法获取 Uri 会报错,因此,需要使用新的技术。这里,我们使用 FileProvider 来解决这个问题。关于 FileProvider,我尝试过自己翻译官方的 Api,但是水平太差,不再贴出,之前找了一篇翻译还不错,各位看官可以凑合看看:
链接:FileProvider文档翻译
这里,我们对安卓7.0的 uri 用 FileProvider.getUriForFile(context, authority, file) 方法获取。获取到文件的 uri 后,我们将 uri 存入跳转的 Intent(对应 key 值为 MeidaStore.EXTRA_OUTPUT),同时访问该路径下文件需要获取临时权限,因此对于 Intent 我们还需要用 setFlag 方法设置相应的临时权限。代码如下:
// 创建图片路径
String timeTip = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date());
String curImgPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + timeTip + ".jpg";
Log.d(TAG, "picPath:" + curImgPath);
File file = new File(curImgPath);
// 获取uri
Uri uri = null;
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(MainActivity.this, "com.test.fileprovider", file);
} else {
uri = Uri.fromFile(file);
}
Log.d(TAG, "uri: " + uri);
// 跳转
Intent fullIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
fullIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
fullIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(fullIntent, REQ_FULL_SIZE_CAPTURE);
2.2.2 获取图片
和拍摄缩略图不同的是,我们在 onActivityResult 回调中并不能直接获得对应的 Bitmap( 毕竟太大会OOM )。因此,我们需要将上面获取的 curImgPath 保存下来,在回调中根据需求,利用路径获取流或者 Bitmap。
代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.i(TAG, "onActivityResult: requestCode:" + requestCode + ",resultCode:" + resultCode + ",data:" + data + ",curImgPath:" + curImgPath);
switch (requestCode) {
case REQ_FULL_SIZE_CAPTURE:
if (resultCode != RESULT_OK) return;
Bitmap bitmap = BitmapFactory.decodeFile(curImgPath);
bitmap = compressBitmap(bitmap);// 压缩图片
mImageView.setImageBitmap(bitmap);
break;
}
}
2.2.3 图片压缩
按照一般的情况,我们界面的 ImageView 使用的图片资源在 2M 以内,太大会造成 OOM。因此,拍照获取到图片必须进行图片压缩才能进行展示。这方面我也查了一些资料,总结如下:
现有的图片压缩主要有两种:一种是质量压缩,它不会减少图片像素,在保证像素的前提下改变图片的位深、透明度等属性,来减小图片的大小;另一种是采样率压缩,通过改变采样率降低图片的像素,从而达到减小图片大小的目的。
第一种质量压缩方法的代码:
// inSampleSize 为大于等于 1 的值,1 表示不压缩
BitmapFactory.Options newOpts = new BitmapFactory.Options();
int inSampleSize = 2;
newOpts.inSampleSize = inSampleSize;
ByteArrayInputStream isBm = new ByteArrayInputStream(out.toByteArray());
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null , null );
第二种质量压缩方法的代码:
// quality 为 0-100 的值,100表示不压缩
int quality = 100;
ByteArrayOutputStream out = new ByteArrayOutputStream();
bitmmap.compress(Bitmap.CompressFormat.JPEG, quality, out);
利用上述代码进行压缩,可以得到不同场景需求的代码,需要注意的是 Bitmap.compress 是一个耗时操作,UI 线程中执行的时候需要有界面提示改善用户体验。
3.系统相机遇到的 bug
相机拍摄后,点击完成按钮,无法退出拍摄界面,回到原界面
原因:图片保存的路径有问题,图片无法正常保存,界面无法结束返回
解决:修改正确路径相机拍摄后,返回,无法显示图片
原因 1:图片过大
解决 1:利用 Bitmap.compress 压缩图片到 2M 以下压缩到 1M 以内还是无法显示
原因 2:图片采样率过高
解决 2:压缩图片,更改图片采样率三星手机进入相机会横竖屏切换,返回到原来界面时会横竖屏切换回来,如果图片路径是全局变量保存的话,需要进行横竖屏切换的适配
上面的 bug 我在 Github 的代码中都已经适配,欢迎下载。链接
各位有遇到过什么有意思的 bug 欢迎评论分享给我啊~