首先看一下连接:Android7.0 完美适配——FileProvider 拍照裁剪全解析,这里面有关问题讲的很详细。 总之一句话在Android7.0以前拍照,读取相册,裁剪是能够经过file://Uri 来传递意图,以后便不被容许。解决办法是使用FileProvider操做。可是我按照文中所讲在小米7.0的手机和小米6.0的手机中不能彻底适配。折腾好久以后才找到解决办法。以下: 首先在AndroidManifest中添加以下代码:android
<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/provider_paths" /> </provider>
其中“applicationId”表示的是包名,“fileprovider”是随意起的名称,在以后使用FileProvider.getUriForFile()方法时会用到。 以后在res目录下添加xml文件夹,里面添加paths文件,文件名是provider_paths,代码以下:数组
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- xml文件是惟一设置分享的目录 ,不能用代码设置 1.<files-path> getFilesDir() /data/data//files目录 2.<cache-path> getCacheDir() /data/data//cache目录 3.<external-path> Environment.getExternalStorageDirectory() SDCard/Android/data/你的应用的包名/files/ 目录 4.<external-files-path> Context#getExternalFilesDir(String) Context.getExternalFilesDir(null). 5.<external-cache-path> Context.getExternalCacheDir(). --> <!-- path :表明设置的目录下一级目录 eg:<external-path path="images/" 整个目录为Environment.getExternalStorageDirectory()+"/images/" name: 表明定义在Content中的字段 eg:name = "myimages" ,而且请求的内容的文件名为default_image.jpg 则 返回一个URI content://com.example.myapp.fileprovider/myimages/default_image.jpg --> <!--当path 为空时 5个全配置就能够解决--> <external-path name="external_files" path="/com.ooli/"/> </paths>
上文中path里的路径要注意,本文使用的是HttpUrl.photoPath=Environment.getExternalStorageDirectory()+"/com.ooli";必定要对应好,否则在以后会因路径不对致使出错。 再以后即是在代码中添加拍照,读取相册,裁剪的逻辑,以下:app
// 建立文件夹 file = new File(HttpUrl.photoPath); if (!file.exists()) { file.mkdirs(); } cemera_tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (hasSdcard()) { //第二个参数是须要申请的权限 if (ContextCompat.checkSelfPermission(CenterOfUserActivity.this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { //权限尚未授予,须要在这里写申请权限的代码 /* 第二个参数是一个字符串数组,里面是你须要申请的权限。既然是一个数组,那么就说明你一次能够申请多个权限。 最后一个参数是一个整型常量,用于标志你此次申请的权限,该常量在onRequestPermissionsResult(…)方法中会用到。 */ ActivityCompat.requestPermissions(CenterOfUserActivity.this, new String[]{android.Manifest.permission.CAMERA}, Util.MY_PERMISSIONS_REQUEST_CALL_PHOTO); } else { //权限已经被授予,在这里直接写要执行的相应方法便可 startTakePhoto(); } } else { Util.showToast(CenterOfUserActivity.this, “存储卡不可用”); } } }); album_tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (hasSdcard()) { //判断是否有读写手机存储的权限 if (ContextCompat.checkSelfPermission(CenterOfUserActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { //权限尚未授予,须要在这里写申请权限的代码 /* 第二个参数是一个字符串数组,里面是你须要申请的权限。既然是一个数组,那么就说明你一次能够申请多个权限。 最后一个参数是一个整型常量,用于标志你此次申请的权限,该常量在onRequestPermissionsResult(…)方法中会用到。 */ ActivityCompat.requestPermissions(CenterOfUserActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Util.MY_PERMISSIONS_REQUEST_WRITE); } else { startphotoAlbum(); } } else { Util.showToast(CenterOfUserActivity.this, getResources().getString(R.string.sdcard_usable)); } } }); /** * 检查设备是否存在SDCard的工具方法 */ public boolean hasSdcard() { try { String state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { // 有存储的SDCard return true; } } catch (Exception e) { LogUtil.e(getClass(), "hasSdcard()", e); } return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); //判断是拍照的权限 if (requestCode == Util.MY_PERMISSIONS_REQUEST_CALL_PHOTO) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { startTakePhoto(); } else { Util.intentPermission(CenterOfUserActivity.this, getResources().getString(R.string.allow_take_photo)); } } //判断是读写手机存储的权限 if (requestCode == Util.MY_PERMISSIONS_REQUEST_WRITE) { if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { Util.intentPermission(CenterOfUserActivity.this, getResources().getString(R.string.allow_take_write)); } else { startphotoAlbum(); } } }
以上代码只是判断是否有权限, 第二步即是发起读取相册和拍照的代码:ide
//读取相册 private void startphotoAlbum() { try { Intent intentFromGallery = new Intent(); // 设置文件类型 intentFromGallery.setType("image/*"); intentFromGallery.setAction(Intent.ACTION_GET_CONTENT); intentFromGallery.addCategory(Intent.CATEGORY_OPENABLE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//若是大于等于7.0使用FileProvider File outFile = new File(file, System.currentTimeMillis() + ".jpg"); Uri uriForFile = FileProvider.getUriForFile(CenterOfUserActivity.this, getPackageName() + ".fileprovider", outFile); intentFromGallery.putExtra(MediaStore.EXTRA_OUTPUT, uriForFile); intentFromGallery.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intentFromGallery.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } startActivityForResult(intentFromGallery, Util.CODE_GALLERY_REQUEST); } catch (Exception e) { LogUtil.e(getClass(), "startphotoAlbum()", e); } } //执行拍照 private void startTakePhoto() { try { Intent intentFromCapture = new Intent( MediaStore.ACTION_IMAGE_CAPTURE); IMAGE_FILE_NAME = System.currentTimeMillis() + ".jpg"; File outFile = new File(file, IMAGE_FILE_NAME); //若是该文件以经存在,则删除,不然建立一个 if (outFile.exists()) { outFile.delete(); } try { outFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } Uri photoUri = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intentFromCapture.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intentFromCapture.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); photoUri = FileProvider.getUriForFile(CenterOfUserActivity.this, getPackageName() + ".fileprovider", outFile); } else { photoUri = Uri.fromFile(outFile); } intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); startActivityForResult(intentFromCapture, Util.CODE_CAMERA_REQUEST); } catch (Exception e) { LogUtil.e(getClass(), "startTakePhoto()", e); } }
在这以后即是在onActivityResult()中接收照片,处理逻辑:工具
@Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { try { super.onActivityResult(requestCode, resultCode, intent); // 用户没有进行有效的设置操做,返回 if (resultCode == RESULT_CANCELED) { return; } switch (requestCode) { case Util.CODE_GALLERY_REQUEST: //从相册中选择图片 if (intent != null) { cropRawPhoto(intent.getData()); } break; case Util.CODE_CAMERA_REQUEST: //拍照完成时 if (hasSdcard() && IMAGE_FILE_NAME != null) { File outFile = new File(file, IMAGE_FILE_NAME); Uri photoUri = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { photoUri = FileProvider.getUriForFile(CenterOfUserActivity.this, getPackageName() + ".fileprovider", outFile); } else { photoUri = Uri.fromFile(outFile); } cropRawPhoto(photoUri); } else { Util.showToast(CenterOfUserActivity.this, getResources().getString(R.string.no_sdcard)); } break; case Util.CODE_RESULT_REQUEST: if (intent != null) { //裁剪完成以后经过intent.getDataString();获取图片Uri地址进行下一步操做(通常是使用七牛云上传本地图片). //本文中将裁剪后的图片的Uri写成全局变量,能够直接获取到地址。便是下文代码中的imageUri 。 } break; } } catch (Exception e) { LogUtil.e( getClass(), "onActivityResult(int requestCode, int resultCode,Intent intent)", e); } }
其中从相册中选取照片和拍照完成以后都要进行裁剪,要说明的是在相册返回中,应该是经过intent.getData()获取在相册中选择的照片的Uri,不用进行其它的操做,将Uri传递给要裁剪的方法便可,裁剪代码以下:ui
/** * 裁剪原始的图片 */ public void cropRawPhoto(Uri uri) { try { File outFile = new File(file, System.currentTimeMillis() + ".jpg"); Intent intent = new Intent("com.android.camera.action.CROP"); Uri photoUri = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); photoUri = Uri.fromFile(outFile); intent.setDataAndType(uri, "image/*");// 剪切特定的图片 } else { photoUri = Uri.fromFile(outFile); intent.setDataAndType(uri, "image/*"); } imageUri = photoUri; // 设置裁剪 intent.putExtra("crop", "true"); // aspectX , aspectY :宽高的比例 intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); // outputX , outputY : 裁剪图片宽高 intent.putExtra("outputX", 480); intent.putExtra("outputY", 480); // return-data为true时,会直接返回bitmap数据,可是大图裁剪时会出现问题 // return-data为false时,不会返回bitmap,但须要指定一个MediaStore.EXTRA_OUTPUT保存图片uri intent.putExtra("return-data", false);// 是否返回数据 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);// 图像保存的路径 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());// 返回的格式 intent.putExtra("noFaceDetection", true);// 是否去除面部检测, // 若是你须要特定的比例去裁剪图片,那么这个必定要去掉,由于它会破坏掉特定的比例。 startActivityForResult(intent, Util.CODE_RESULT_REQUEST); } catch (Exception e) { LogUtil.e(getClass(), "cropRawPhoto(Uri uri)", e); } }
其中注意一点的是在裁剪中是经过Uri.fromFile()获取对应的Uri 的,添加if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)判断只是为了动态获取读写文件的权限。在读取相册和拍照时才经过FileProvider.getUriForFile()获取Uri,这一点要分清。this