调用系统相机很简单,只需要一个intent就可以跳转到相几界面,然后再通过onActivityResult来取得图片即可。
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 系统常量, 启动相机的关键
startActivityForResult(openCameraIntent, REQUEST_CODE_TAKE_PICTURE); // 参数常量为自定义的request code, 在取返回结果时有用
重写onActivityResult方法,获取request_code.
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case REQUEST_CODE_TAKE_PICTURE:
// 此处写“如何获取图片”...
break;
}
}
从上边可以知道打开相机的 Intent Action: MediaStore.ACTION_IMAGE_CAPTURE
,该Action会打开照相机拍照然后返回结果。
如果在没有设置额外的控制图片存储路径的参数MediaStore.EXTRA_OUTPUT
情况下,返回的结果将是一个小的Bitmap。
Bitmap bm = (Bitmap) data.getExtras().get("data");
ImageView.setImageBitmap(bm);
上述方法仅仅只适合于应用程序需要一张小的图片的时候。而且经过测试发现,保存图片后的图片与拍照时的预览图片对比后,发现前者失真严重。原因是现在相机的像素都很大,随便一张图片都上M,而Android系统分配给每个应用的最大内存是16M,如果直接将图片通过内存方式返回给调用者会占用过大的内存,因此这里得到的是一个处理后的缩略图。
第二种方法获取图片,就是在intent里指定图片保存位置,这种方法取得的是原图。通过设置MediaStore.EXTRA_OUTPUT
参数实现。此方法将保存原始图片到指定的Uri中,不会压缩, 代码如下:
// 打开相机Intent
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 给拍摄的照片指定存储位置
String f = System.currentTimeMillis()+".jpg"; // 指定名字
Uri fileUri = Uri.fromFile(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), f)); // 指定图片保存的uri,此处将图片保存在系统相册中
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); //指定图片存放位置,指定后,在onActivityResult里得到的Data将为null
// 启动相机
startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA);
上述“指定存储目录”的方法在一般情况下,运行没有任何问题;可是当把targetSdkVersion指定成24及之上并且在API>=24(Android 7.0以上)的设备上运行时,会抛出异常:
android.os.FileUriExposedException: 你指定的文件路径和文件名(即上边的fileUri) exposed beyond app through ClipData.Item.getUri()
原因:Android不再允许在app中把file://Uri
暴露给其他app,包括但不局限于通过Intent或ClipData 等方法。原因在于使用file://Uri会有一些风险,比如:文件是私有的,接收file://Uri的app无法访问该文件。
在Android6.0之后引入运行时权限,如果接收file://Uri的app没有申请READ_EXTERNAL_STORAGE
权限,在读取文件时会引发崩溃。
因此,Google提供了FileProvider,使用它可以生成content://Uri来替代file://Uri。
FileProvider的简单使用可以参考:http://gelitenight.github.io/android/2017/01/29/solve-FileUriExposedException-caused-by-file-uri-with-FileProvider.html#fileprovider-1
我是参考这里边的讲述来使用FileProvider,具体方法不做赘述,仅贴代码。
provider_paths.xml
代码中:
// 打开相机Intent
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 给拍摄的照片指定存储位置
String f = System.currentTimeMillis()+".jpg"; // 指定名字
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), f); // 指定文件
fileUri = FileProvider.getUriForFile(MainActivity.this, getPackageName() + ".provider", file); // 路径转换
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); //指定图片存放位置,指定后,在onActivityResult里得到的Data将为null
startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA);
下边讲一下在用链接里的方法时遇到的一些问题。
AndroidManifest.xml文件中添加provider时android:name="android.support.v4.content.FileProvider
"无法识别的问题。
解决方案:android.support.v4.content.FileProvider
修改为androidx.core.content.FileProvider
。Android库迁移到 androidx后造成 android.support.v4.content.FileProvider找不到。更换库。
打开相机时报错:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
出现这个错误是因为AndroidManifest.xml
处的android:authorities
跟mActivity.getPackageName() + ".provider"
不一致。
AndroidManifest.xml中:
android:authorities="${applicationId}.fileprovider"
代码中:
fileUri = FileProvider.getUriForFile(MainActivity.this, getPackageName() + ".provider", file); // 路径转换
将代码改为:
fileUri = FileProvider.getUriForFile(MainActivity.this, getPackageName() + ".fileprovider", file); // 路径转换
问题解决。