说明:由于在测试的时候使用的是Android 7.1的机器,所以郭霖大神书中的代码会出现以下问题,
(1)APP安装在机器上实测会出现调用相机无法打开的现象;
(2)APP调用裁剪工具的时候会有Toast提示“无法加载此图片”。
以下是对郭霖大神在书中代码的详细讲解以及适配,从而保证一切功能正常执行。
Step 1. 新建一个activity_main.xml布局,这个没什么问题。
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:id="@+id/take_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照"/>
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
Step 2. 新建MainActivity,以下是郭霖大神书中原来的代码。代码中比较难以理解的地方注释都写的很详细。
public class MainActivity extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
public static final int CROP_PHOTO = 2;
private Button takePhoto;
private ImageView imageView;
private Uri imageUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
takePhoto = (Button)findViewById(R.id.take_photo);
imageView = (ImageView)findViewById(R.id.image_view);
takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File outputImage = new File(Environment.getExternalStorageDirectory(),
"maybe.jpg");
if(outputImage.exists()){
outputImage.delete();
}
try {
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//将File对象转换成Uri对象,因为Uri对象标识着maybe.jpg这张图片的唯一地址。
//由于Intent能传递的数据空间有限,所以需要转化成Uri
//大图片用Uri,小图片用Bitmap
//以下代码解决了Android 7.0 打开相机崩溃的问题
if(Build.VERSION.SDK_INT > 24){
imageUri = FileProvider.getUriForFile(MainActivity.this,
"com.example.android.choosepicdemo.fileprovider",outputImage);
}else{
imageUri = Uri.fromFile(outputImage);
}
//Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
//第一个参数:一个Intent对象
//第二个参数:如果> = 0,当Activity结束时requestCode将归还在onActivityResult()中,
//以便确定返回的数据是从哪个Activity中返回
startActivityForResult(intent,TAKE_PHOTO);
}
});
}
/*第一个参数:这个整数requestCode提供给onActivityResult,是以便确认返回的数据是从哪个Activity返回的。
这个requestCode和startActivityForResult中的requestCode相对应。
第二个参数:这整数resultCode是由子Activity通过其setResult()方法返回。
第三个参数:一个Intent对象,带有返回的数据。*/
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case TAKE_PHOTO:
if(resultCode == RESULT_OK){
Intent intent = new Intent("com.android.camera.action.CROP");
//以下两行代码适配Android 7.0 解决了无法加载图片的问题
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(imageUri,"image/*");
intent.putExtra("scale",true);
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,CROP_PHOTO);
}
break;
case CROP_PHOTO:
if(resultCode == RESULT_OK){
// 因为imageUri是Uri类型的,需要转换才能被decodeStream使用
// 使用getContentResolver()
// 因为在Android系统里面,数据库是私有的。
// 一般情况下外部应用程序是没有权限读取其他应用程序的数据。
// 如果你想公开你自己的数据,你有两个选择:
// 你可以创建你自己的内容提供器(一个ContentProvider子类)或者
// 你可以给已有的提供器添加数据-如果存在一个控制同样类型数据的内容提供器且你拥有写的权限。
// 外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,
// 在Activity当中通过getContentResolver()可以得到当前应用的 ContentResolver实例
try {
//解析成Bitmap
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
imageView.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
需要注意的是以下代码是增加了的内容:
//以下代码解决了Android 7.0 打开相机崩溃的问题
if(Build.VERSION.SDK_INT > 24){
imageUri = FileProvider.getUriForFile(MainActivity.this,
"com.example.android.choosepicdemo.fileprovider",outputImage);
}else{
imageUri = Uri.fromFile(outputImage);
}
如果不这么处理的话,会导致在调用相机获取 Uri 的时候发生崩溃。
原因很明显,file:// 不被允许作为一个附加的 Uri 的意图,否则会抛出 FileUriExposedException 。其实背后有一个很好的理由,如果文件路径被发送到目标应用程序(相机应用程序在这种情况下),文件将完全访问通过相机应用程序的过程,而不仅仅只有发起者能收到。但让我们考虑一下,实际上是由我们的应用程序去启动摄像头拍照,并保存作为我们的应用程序的代表文件。因此,该文件的访问权限应该是我们的应用程序而不是摄像头应用程序本身。这就是为什么现在 file:// 在 targetSdkVersion 24 中要求每一位开发者都去完成这个任务。参考博文:http://www.sohu.com/a/136282885_659256
所以需要采用FileProvider来处理。
具体的步骤如下:
- 首先在AndroidManifest.xml中声明provider。这里要注意android:authorities要是你的包名一致,其他固定。
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:authorities="com.example.android.choosepicdemo.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
- 在res文件夹下创建xml文件夹,并创建provide_paths.xml(该名称应与上述android:resource里一致),maybe为图片保存时的名称,根据你自己命名而定。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name = "maybe" path = "."/>
</paths>
- 修改java代码如下
//以下代码解决了Android 7.0 打开相机崩溃的问题
if(Build.VERSION.SDK_INT > 24){
imageUri = FileProvider.getUriForFile(MainActivity.this,
"com.example.android.choosepicdemo.fileprovider",outputImage);
}else{
imageUri = Uri.fromFile(outputImage);
}
到这里就解决了在android 7.0以上平台上调用摄像头崩溃的问题。
Step 3. 解决调用裁剪工具时提示“无法加载图片”的问题
很简单,在MainActivity.java中加上两行代码就搞定。
//以下两行代码适配Android 7.0 解决了无法加载图片的问题
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
以上。
点击此处下载源码。