关于Android7.0相机闪退以及相册获取不到图片问题

来源:互联网 发布:指尖软件 编辑:程序博客网 时间:2024/05/01 12:17
文档说明:关于Android7.0及以上机型调取相机闪退情况处理。






现象:
    因开发中遇到需要调用系统相机或相册获取图片,于是也没有多思考就使用相关指定的Action去调取相机或者相册,在开始测试时未出现问题,直到这个APK包被装到一个中兴手机(型号A2017)手机上,于是坑就出现了:在该手机上调用相机时出现应用闪退,获取相册也有同样的问题。于是本人有换了华为P9 Plus,问题同样存在。因本人logcat出了问题,没有日志,一开始以为是6.0出现的动态获取下权限问题,因为这种情况在魅族MX6上也出现过,当时解决的方案是手动去设置了对应用的相机权限,从而解决了该问题。于是在代码中增加了权限获取代码:


if (Build.VERSION.SDK_INT>22){
   if (ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){
           //先判断有没有权限 ,没有就在这里进行权限的申请
           requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_OK);
                   
    }else {
            //说明已经获取到摄像头权限了 想干嘛干嘛  
            toSelectPhotoOrOpenCamera();
    }
}else {
      //这个说明系统版本在6.0之下,不需要动态获取权限。
      toSelectPhotoOrOpenCamera();
}






并重写了权限获取方法:
/**
* 权限操作结果处理
*/
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
switch (requestCode) {
case CAMERA_OK: 
   if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户已授权
toSelectPhotoOrOpenCamera();
   } else {
//用户拒绝权限
ToastUtils.show(this, "缺少相机权限,暂时无法提供扫描功能,请尝试在设置中打开相机权限!", Toast.LENGTH_LONG);
   }
   break;

   }
}


事实证明这个坑不浅,增加以上代码过后,依然出现闪退,然而该代码在魅族MX6和OPPOA59s上则不存在该问题。










原因锁定:
    因没有日志可看,于是在网上查找关于该问题的现象,终于将问题锁定为因Android7.0对手机相关权限回收而出现的,这也说明为什么该种情况只在中兴A2017和华为P9 plus上出现(中兴华为的android版本为7.1.1和7.0,而魅族MX6和OPPOA59s的android版本则为6.0)。




原因简述:
    其实不仅是调用相机和相册,只要是访问文件,都会出现这个错误,其原因是Android 7.0 做了一些系统权限更改,为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问,此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。而此权限更改有多重副作用,其中之一就是当传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。在应用间共享文件对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。要在应用间共享文件,应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。原文在这里[Android 7.0 行为变更](https://developer.android.Google.cn/about/versions/nougat/android-7.0-changes.html)。






解决方案:
针对android版本不同,做不同的处理。即当android版本大于等于N时,则使用另一套调取相机和相册的机制。于是产生了以下代码:

调取相机:
    if (android.os.Build.VERSION.SDK_INT>=24) {
PhotoUtilsAbove23.openCamera(MainActivity.this);
    }else {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
ContentValues values = new ContentValues();
photoUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT,photoUri);
startActivityForResult(intent, SELECT_PIC_BY_TACK_PHOTO);
}else {
ToastUtils.show(MainActivity.this,"SD卡不存在",Toast.LENGTH_SHORT);
}
    }




调取相册:
    if (Build.VERSION.SDK_INT >= 24) {
PhotoUtilsAbove23.openPhotos(MainActivity.this);
    }else {
Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, SELECT_PIC_BY_PICK_PHOTO);
    }




使用到的工具类:可以参考demo中的PhotoUtilsAbove23类。其中有两个方法:
在7.0以上的打开相机方式:
public static void openCamera(Context context){


Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {//判断是否有相机应用
// Create the File where the photo should go
try {
photoFile = createImageFile(context);//创建临时图片文件
} catch (IOException ex) {
ex.printStackTrace();
}
// Continue only if the File was successfully created
if (photoFile != null) {
//FileProvider 是一个特殊的 ContentProvider 的子类,
//它使用 content:// Uri 代替了 file:/// Uri. ,更便利而且安全的为另一个app分享文件
Uri photoURI = FileProvider.getUriForFile(context,
context.getPackageName()+".fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
((MainActivity)context).startActivityForResult(takePictureIntent, SELECT_PIC_BY_TACK_PHOTO);
}
}
}




在7.0以上的打开相册方式:
public static void openPhotos(Context context){
//mGalleryFile = new File(getExternalDir(), IMAGE_GALLERY_NAME);
try {
mGalleryFile=createImageFile(context);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
if (Build.VERSION.SDK_INT >= 24) {//如果大于等于7.0使用FileProvider
Uri uriForFile = FileProvider.getUriForFile(context, context.getPackageName()+".fileprovider", mGalleryFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uriForFile);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
((MainActivity)context).startActivityForResult(intent, SELECT_PIC_BY_PICK_PHOTO);
}


}


其他工具方法则不一一列举。








在使用针对不同版本的android使用不同的调取方式后,还需要做一些配置,才能正常调起相机和相册,其中包括Manifest.xml和res目录下的xml文件夹。(网上很多方法,在调取相册时使用的常规调用方式,确实能打开相册,但是打开相册后,选择某一张图片,也确实返回到了onActivityResult方法中,同时也可以去到图片的路径:类似于这种路径:/storage/emulated/0/DICM/***/***.jpg,但是这个路径无法通过BitmapFactory的decodeFile等方法拿出来一个bitmap,至于原因,因为没有日志,暂时不清楚,但依照网上描述,依然和7.0针对权限的处理有关,于是我们针对7.0以上的调用相册使用了另一种方式,也就是上文提到的方式)。


在manifest中配置如下:
 <!-- 新增7.0针对文件权限的provider -->
        <provider 
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.opencameraabove23demo.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data 
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths"/>
            
        </provider>




在res目录下新增xml文件夹,并增加一个对应文件filepaths.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
     <paths>
        <external-path name="my_images" path="Android/data/com.example.opencameraabove23demo/files/Pictures" />
    </paths>
</resources>






注意:com.example.opencameraabove23demo为你的app的包名,同时需要给provider处理Uri的授权grantUriPermissions=true






接着在onActivityResult中,我们做了如下处理。
在7.0之前,我们都是使用图片的真是路径path来通过BitmapFactory的decode方法获取图片,在7.0之后,将使用Uri来获取图片。




/**
* 获取选择照片时的Uri,最终获得drawable
*/
@SuppressWarnings("unused")
private void getPhotoUri(int requestCode, int resultCode, Intent data) {
 
isSelectPhoto=false;
if (requestCode == SELECT_PIC_BY_PICK_PHOTO) {//如果选择照片成功,则获取其Uri
if (android.os.Build.VERSION.SDK_INT>=24) {
//content://com.android.providers.media.documents/document/image%3A41837
photoUri=data.getData();
isSelectPhoto=true;
picPath=PhotoUtilsAbove23.getGalleryFile().getAbsolutePath();
drawable=PhotoUtilsAbove23.getAddressByUriAbove23(this,picPath,photoUri,isSelectPhoto);
}else {
if (data == null) {
ToastUtils.show(MainActivity.this,
"error file",Toast.LENGTH_SHORT);
return;
}
photoUri = data.getData();
if (photoUri == null) {
ToastUtils.show(MainActivity.this,
"error file",Toast.LENGTH_SHORT);
return;
}
drawable=PhotoUtilsAbove23.getAddressByUri(this,photoUri,isSelectPhoto);
}
}else if (requestCode == SELECT_PIC_BY_TACK_PHOTO) {
if (android.os.Build.VERSION.SDK_INT>=24) {
///storage/emulated/0/Android/data/cn.com.fdbank.ui/files/Pictures/JPEG_20170505_154338_738550391.jpg
//注意此处我们的图片路径不在是/storage/emulated/0/DICM/***/***.jpg,而是一个临时授权的地址
picPath=PhotoUtilsAbove23.getPhotoFile().getAbsolutePath();
drawable=PhotoUtilsAbove23.getAddressByUriAbove23(this,picPath,photoUri,isSelectPhoto);
}else {
drawable=PhotoUtilsAbove23.getAddressByUri(this,photoUri,isSelectPhoto);
}
}

}




这里同样使用到了PhotoUtilsAbove23工具类中的方法,详细情况见demo中的该工具方法。这个工具类中的一些方法我也是借鉴网上一些大神提供的方法,如有不足,请大神指点!关于这个工具类我已经放在一个文档中,这个文档有一个demo和demo说明,有问题的朋友可以通过下面链接来下载:http://download.csdn.net/detail/xiangxiang_8_8/9838317

上面的demo步骤有些复杂,其实这里还有个操作步骤更简单的demo,是我没事写的JSBridgr交互实现的一个相机及相册功能,同样兼容7.0,懂一点android和js的人应该都能看懂,其实java这边都是一样的,只不过发起是在JS,关于这个JSBridge的demo的地址,可以在下面链接下载:

http://download.csdn.net/detail/xiangxiang_8_8/9885531

最后感谢我同事----科蓝浩哥的耐心指导。





















0 0
原创粉丝点击