首先看一下連接: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