启动相机有两种方式:[不指定路径]和[指定路径]
[第一种]:不指定路径
这种方式是最简单的一种方式,代码如下:
//启动系统相机
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQUEST_CODE);
即用Intent启动系统相机,且指定action为MediaStore.ACTION_IMAGE_CAPTURE。
当拍完照关闭或者直接关闭系统相机页面时,此时开始执行onActivityResult方法,接收相机返回数据,数据是通过Intent方式传递的,onActivityResult方法有三个参数,分别是requestCode、resultCode、data。
- requestCode:请求码
- resultCode:响应码
- data:数据
代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if(requestCode == REQUEST_CODE && resultCode == RESULT_OK){
if(data != null && data.hasExtra("data")){
Bitmap bitmap = data.getParcelableExtra("data");
imageview.setImageBitmap(bitmap);
}
}
}
由于请求系统相机时没有指定路径,所以在Intent里拿到的数据是Bitmap,这个Bitmap的分辨率大小是由系统相机决定的,为了防止内存的浪费,一般系统相机默认返回的Bitmap是特别特别小的。
如图:

当然,Bitmap较小也为我们提供一个思路,这种方式可以用于设置头像或者设置缩略图。
由于Bitmap分辨率比较小,基本很难满足项目的大部分需求,显示图片过小或者不清晰,为了解决这个问题,则需要指定图片路径,将拍照的图片保存到本地。
指定文件目录的代码很简单,如下:
文件写权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
调用系统相机代码:
//获得项目缓存路径
String filePath = Environment.getExternalStorageDirectory() + File.separator + "1" + File.separator;
//如果目录不存在则必须创建目录
File cameraFolder = new File(filePath);
if (!cameraFolder.exists()) {
cameraFolder.mkdirs();
}
//根据时间随机生成图片名
String photoName = new DateFormat().format("yyyyMMddhhmmss", Calendar.getInstance(Locale.CHINA)) + ".jpg";
filePath = filePath + photoName;
File mOutImage = new File(filePath);
mImageUri = Uri.fromFile(mOutImage);
//启动相机
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
startActivityForResult(intent, REQUEST_CODE);
onActivityResult处理代码如下:
if(requestCode == REQUEST_CODE && resultCode == RESULT_OK){
if(mImageUri != null){
Glide.with(MainActivity.this)
.asBitmap()
.load(mOutImage)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageview);
}
}
需要注意的是,保存照片的目录必须存在,如果不存在相机返回时本地没有图片,甚至有些手机的相机根本无法返回。
以上代码支持6.x或6.x以下的手机,在7.0或7.0以上的手机则无效,所以还需要对7.0以上的手机做下适配。
如果没有针对Android 7.0做处理,那么可能会出现以下问题。


Android7.0中尝试传递file:///开头的URI 会触发FileUriExposedException,因为在Android7.0之后Google认为直接使用本地的根目录即file:///作为URI是不安全的操作,直接访问会抛出FileUriExposedExCeption异常,那么如何解决这个问题呢?
Android7.0为我们提供了“file:///”向FileProvider转化的方法,Android可以通过FileProvider获取对应资源的URI。
兼容7.0代码如下:
【第一步】 权限
指定文件目录的拍照方式需要保存文件,所以需要在AndroidManifest中添加写文件的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
【第二步】 在AndroidManifest中配置一个provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="org.bytedeco.javacpp.takephoto.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
其中,authorities需要保证唯一性,file_paths是指定需要授权访问的目录。
【第三步】 新建file_paths文件
在res目录下,新建xml文件夹,并在文件夹中新建file_paths文件,资源文件目录如下:

代码如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="demo/"/>
</paths>
external-path代表与Environment.getExternalStorageDirectory()相同的文件路径。除此之外,还有其它名称表示不同的文件目录:
files-path:代表与Context.getFileDir()相同的文件路径
cache-path:代表与getCacheDir()相同的文件路径
external-files-path:代表与Context.getExternalFilesDir(String) 和Context.getExternalFilesDir(null)相同的文件路径
external-cache-path:代表与Context.getExternalCacheDir()相同的文件路径
这个配置表示,如果拍照后的图片允许在Environment.getExternalStorageDirectory()+"/demo/"目录下保存。
【第四步】 启动系统相机
//获得项目缓存路径
String filePath = Environment.getExternalStorageDirectory() + File.separator + "demo" + File.separator;
//如果目录不存在则必须创建目录
File cameraFolder = new File(filePath);
if (!cameraFolder.exists()) {
cameraFolder.mkdirs();
}
//根据时间随机生成图片名
String photoName = new DateFormat().format("yyyyMMddhhmmss", Calendar.getInstance(Locale.CHINA)) + ".jpg";
filePath = filePath + photoName;
File mOutImage = new File(filePath);
//如果是7.0以上 那么就把uir包装
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mImageUri = FileProvider.getUriForFile(MainActivity.this, "org.bytedeco.javacpp.takephoto.fileprovider", mOutImage);
} else {
//否则就用老系统的默认模式
mImageUri = Uri.fromFile(mOutImage);
}
//启动相机
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
startActivityForResult(intent, REQUEST_CODE);
其中,FileProvider.getUriForFile()的第二个参数必须和AndroidManifest.xml文件中声明的provider中的authorities一致,文件的保存目录必须和file_paths.xml文件制定的文件路径一致。
【第五步】 返回处理
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if(requestCode == REQUEST_CODE && resultCode == RESULT_OK){
if(data != null && data.hasExtra("data")){
Bitmap bitmap = data.getParcelableExtra("data");
imageview.setImageBitmap(bitmap);
}else{
if(mImageUri != null){
Glide.with(MainActivity.this)
.asBitmap()
.load(mImageUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageview);
}
}
}
}
以上解决7.0以上手机的兼容处理其实比较繁琐,需要在AndroidManifest中添加provider,需要配置授权目录,更需要FileProvider.getUriForFile方法获取URI,我们可以采用严格模式(StrictMode)一步到位,只需要在自定义Application的onCreate方法中添加如下代码即可:
// android 7.0系统解决拍照的问题
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
builder.detectFileUriExposure();
}
}
总结:
以上有关兼容7.0以上手机有两种方式
- 采用FileProvider方式,这种方式代码比较繁琐,优点是比较安全;
- 采用严格模式,这种方式代码非常简单,但没有前者安全。
[本章完...]