本篇博客从基本的AndroidN开始说怎么适配其拍照,其与7.0以下有何区别?
再详细分析如何封装在 avtivity和fragment中的区别? (重点)
再详细说明下本封装库如何集成与使用。
博主最近为了适配 AndroidN的拍照,浏览了很多技术文章,有些是需要4到5个类来实现、有些不能在fragment中实现… … ,个人敢想 “可以一个类实现封装全部拍照工作吗?” , 扬起袖子就是干!在辛苦的四个小时,终于把这个封装给弄出来的!
已经放在GitHub上了(强烈推荐Star):https://github.com/xuhongv/TakePhotoAndroidN-master
已经兼容在小米手机出现Attempt to invoke interface method ‘boolean android.database.Cursor.moveToFirst()问题 。(2017/8/19)
已经兼容在fragment出现权限授权不回调的bug。(2017/8/19)
已经兼容在fragmenr出现图片不回调的bug。(2017/8/18)
由于从Android7.0(下面统一为AndroidN)开始,直接使用真实的路径的Uri会被认为是不安全的,会抛出一个FileUriExposedException这样的异常。需要使用FileProvider,选择性地将封装过的Uri共享到外部。
出于以上问题,很多事情都意味着要适配,比如你在AndroidN以下,可以跳转到拍照和图库界面,但是在AndroidN就不行了!但是会有error等级的log输出,出现FileUriExposedException这样的异常,原因是Andorid7.0的“私有目录被限制访问”,“StrictMode API 政策”。
谷歌这样做,出自用户隐私的考虑。既然这样,我们就必须要通过FileProvider(Provider的一个子类)共享其URL到外部即可。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
provider>
2、在您工程的 res 文件夹根目录下创建一个xml文件夹,里面再新建一个file_provider_paths文件夹,其对应都是在上面的meta-data标签下面的android:resource值的。
<resources>
<paths>
<external-path path="" name="myFile"/>
paths>
resources>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
①、你先要创建一个URL作为你拍照后得到的图片的URL , 这里我们使用 intent , 制定Action为 MediaStore.ACTION_IMAGE_CAPTURE , 这样就可以跳转到相机界面了 ,别忘了,在intent上把你要传的URL放上去,名字一定要是 :MediaStore.EXTRA_OUTPUT 。最后使用startActivityForResult()跳转,别忘了回调码。
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);
startActivityForResult(intent, CODE_ORIGINAL_PHOTO_CAMERA)
②、等到跳转相机界面后,我们不需理会用户怎么操作,我们只需关心传回来的Uri数据是否为空,毕竟在拍照后用户可能点击了舍弃,导致拿到相片为空。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//相片处理
if (resultCode != RESULT_CANCELED) {
switch (requestCode) {
//相册数据,回调码要和上面一致。
case IMAGE_REQUEST_CODE:
//判断返回的数据Uri是否为空?
if(imgUri!=null){
//doyourthings
}
break;
}
}
下面是整个拍照流程图:
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, IMAGE_REQUEST_CODE);
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//相片处理
if (resultCode != RESULT_CANCELED) {
switch (requestCode) {
//相册数据,回调码要和上面一致。
case IMAGE_REQUEST_CODE:
//判断返回的数据data.getData()是否为空?
if(data.getData()!=null){
//doyourthings
}
break;
}
}
下面是整个相册选择图片流程图:
让不少人烦恼的是,在安卓6.0之后,需要动态授权,那么作为拍照、写入SD卡和读取SD卡,这些“危险权限”,动态授权是必然的。
我这里采用郭神的做法,如果用户拒绝的某些权限的话,会通过接口提示。代码如下:
/**
* 申请运行时权限
* 来自郭神公开课
*/
private void requestRuntimePermission(String[] permissions, PermissionListener listener) {
permissionListener = listener;
List permissionList = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(mContext, permission) != PackageManager.PERMISSION_GRANTED) {
permissionList.add(permission);
}
}
//此处兼容了无法在fragment回调监听事件
if (!permissionList.isEmpty()) {
if (isActicity) {
ActivityCompat.requestPermissions((Activity) mContext, permissionList.toArray(new String[permissionList.size()]), 1);
} else {
mFragment.requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);
}
if (takeCallBacklistener != null) {
takeCallBacklistener.failed(1, permissionList);
}
} else {
permissionListener.onGranted();
}
}
private void statZoom(File srcFile, File output) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(getImageContentUri(mContext, srcFile), "image/*");
// crop为true是设置在开启的intent中设置显示的view可以剪裁
intent.putExtra("crop", "true");
// 是否缩放?如果不缩放,会出现黑边哦
intent.putExtra("scale", true);
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", aspectX);
intent.putExtra("aspectY", aspectY);
// outputX,outputY 是剪裁图片的宽高
intent.putExtra("outputX", outputX);
intent.putExtra("outputY", outputY);
intent.putExtra("return-data", false);//true:不返回uri,false:返回uri
intent.putExtra("scaleUpIfNeeded", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(output));
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
//此处兼容在fragment不会回调图片问题
if (isActicity) {
mActivity.startActivityForResult(intent, CODE_TAILOR_PHOTO);
} else {
mFragment.startActivityForResult(intent, CODE_TAILOR_PHOTO);
}
}
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
provider>
-第三步 :把demo的就仅仅一个类 TakePictureManager.class 复制过去就可以啦!别忘了在清单文件加相关权限哦!
示例:
TakePictureManager takePictureManager takePictureManager = new TakePictureManager(this);
//开启裁剪 比例 1:3 宽高 350 350 (默认不裁剪)
takePictureManager.setTailor(1, 3, 350, 350);
//拍照方式
takePictureManager.startTakeWayByCarema();
//监听回调
takePictureManager.setTakePictureCallBackListener(new TakePictureManager.takePictureCallBackListener() {
//成功拿到图片,isTailor 是否裁剪? ,outFile 拿到的文件 ,filePath拿到的URl
@Override
public void successful(boolean isTailor, File outFile, Uri filePath) {
}
//失败回调
@Override
public void failed(int errorCode, List deniedPermissions) {
}
});
//把本地的onActivityResult()方法回调绑定到对象
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
takePictureManager.attachToActivityForResult(requestCode, resultCode, data);
}
//onRequestPermissionsResult()方法权限回调绑定到对象
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
takePictureManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
使用详细步骤:
①、先new一个TakePictureManager 对象,构造方法只需传this即可(不管你在Activity还是在Fragment)。
②、重写onActivityResult()方法,调用对象的attachToActivityForResult()方法,参数依次是onActivityResult()回调的参数。 实现把拍照或相册回调发数据绑定在对象方法处理。
③、重写onRequestPermissionsResult()方法,调用对象的onRequestPermissionsResult()方法,参数依次是onRequestPermissionsResult()回调的参数。 实现把权限回调绑定在对象方法处理。
④、这时候,你只需调用对象方法,即可轻松调用相机或相册。具体的方法参数说明如下:
方法名 | 参数 | 说明 |
---|---|---|
setTailor(int aspectX, int aspectY, int outputX, int outputY) | 要裁剪的宽比例、要裁剪的高比例、要裁剪图片的宽、要裁剪图片的高 | 一旦调用,表示要裁剪,默认不裁剪 |
startTakeWayByCarema() | 无参数 | 调用相机 |
startTakeWayByAlbum() | 无参数 | 调用相册 |
setTakePictureCallBackListener(takePictureCallBackListener listener) | takePictureCallBackListener 回调接口 | 调用相机或相册后的回调 |
接口 | 方法 | 说明 |
---|---|---|
takePictureCallBackListener | successful(boolean isTailor, File outFile, Uri filePath) | 成功回调! isTailor : 是否已裁剪, outFile :输出的照片文件 ,filePath :输出的照片Uri 。 |
failed(int errorCode, List deniedPermissions) | 失败回调!errorCode :0表示相片已移除或不存在! 1表示权限被拒绝,deniedPermissions当权限被拒绝时候,会通过list传回 |
在Fragment使用时候,回调的相片数据被依附的activity的onActivityResult()方法拦截了!相信这个问题困扰许多人的问题,在使用他人代码时候,在Activity可以使用,但是在Fragment却失败。原因在于:
好了,到此为止了,如果你们在使用遇到什么问题,随时在本博客留言啦!
已经放在GitHub上了(推荐Star,你以后肯定会用到哦):https://github.com/xuhongv/TakePhotoAndroidN-master