在Android 編程中經常會用到Uri轉化為文件路徑,如我們從相冊選擇圖片上傳至伺服器,一般上傳前需要對圖片進行壓縮,這時候就要用到圖片的絕對路徑。 下麵對我開發中uri轉path路徑遇到的問題進行總結,其中涉及到Android不同api下對於uri的處理,還有對於Google相冊圖片該如何獲取其 ...
在Android 編程中經常會用到Uri轉化為文件路徑,如我們從相冊選擇圖片上傳至伺服器,一般上傳前需要對圖片進行壓縮,這時候就要用到圖片的絕對路徑。
下麵對我開發中uri轉path路徑遇到的問題進行總結,其中涉及到Android不同api下對於uri的處理,還有對於Google相冊圖片該如何獲取其圖片路徑。
1. 從相冊獲取圖片
我們從相冊獲取的圖片的代碼如下:
1 // 激活系統圖庫,選擇一張圖片 2 Intent intent = new Intent(Intent.ACTION_PICK); 3 intent.setType("image/*"); 4 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 5 startActivityForResult(intent, Constants.PHOTO_REQUEST_GALLERY);
當然針對Android 6.0以上系統還需要獲取WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE操作手機sd卡許可權才行。
然後再在onActivityResult中獲取到圖片的uri路徑:
1 @Override 2 public void onActivityResult(int requestCode, int resultCode, Intent data) { 3 super.onActivityResult(requestCode, resultCode, data); 4 if (resultCode == Activity.RESULT_OK) { 5 switch (requestCode) { 6 case Constants.PHOTO_REQUEST_GALLERY: 7 Uri uri = data.getData(); 8 break; 9 } 10 } 11 }
Uri:通用資源標誌符(Universal Resource Identifier, 簡稱”URI”),Uri代表要操作的數據,Android上可用的每種資源(如圖像、視頻片段等)都可以用Uri來表示。
Uri一般由三部分組成:訪問資源的命名機制。存放資源的主機名。資源自身的名稱,由路徑表示。
2.根據Uri獲取path路徑
2.1 Android 4.4以下獲取圖片路徑
1 /** 2 * 獲取小於api19時獲取相冊中圖片真正的uri 3 * 對於路徑是:content://media/external/images/media/33517這種的,需要轉成/storage/emulated/0/DCIM/Camera/IMG_20160807_133403.jpg路徑,也是使用這種方法 4 * @param context 5 * @param uri 6 * @return 7 */ 8 public static String getFilePath_below19(Context context,Uri uri) { 9 //這裡開始的第二部分,獲取圖片的路徑:低版本的是沒問題的,但是sdk>19會獲取不到 10 Cursor cursor = null; 11 String path = ""; 12 try { 13 String[] proj = {MediaStore.Images.Media.DATA}; 14 //好像是android多媒體資料庫的封裝介面,具體的看Android文檔 15 cursor = context.getContentResolver().query(uri, proj, null, null, null); 16 //獲得用戶選擇的圖片的索引值 17 int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 18 //將游標移至開頭 ,這個很重要,不小心很容易引起越界 19 cursor.moveToFirst(); 20 //最後根據索引值獲取圖片路徑 結果類似:/mnt/sdcard/DCIM/Camera/IMG_20151124_013332.jpg 21 path = cursor.getString(column_index); 22 } finally { 23 if (cursor != null) { 24 cursor.close(); 25 } 26 } 27 return path; 28 }
2.2.2 對於Android 4.4及以上機型獲取path
1 /** 2 * @param context 上下文對象 3 * @param uri 當前相冊照片的Uri 4 * @return 解析後的Uri對應的String 5 */ 6 @SuppressLint("NewApi") 7 public static String getPath(final Context context, final Uri uri) { 8 9 final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 10 String pathHead = "file:///"; 11 // 1. DocumentProvider 12 if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 13 // 1.1 ExternalStorageProvider 14 if (isExternalStorageDocument(uri)) { 15 final String docId = DocumentsContract.getDocumentId(uri); 16 final String[] split = docId.split(":"); 17 final String type = split[0]; 18 if ("primary".equalsIgnoreCase(type)) { 19 return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1]; 20 } 21 } 22 // 1.2 DownloadsProvider 23 else if (isDownloadsDocument(uri)) { 24 final String id = DocumentsContract.getDocumentId(uri); 25 final Uri contentUri = ContentUris. 26 withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 27 return pathHead + getDataColumn(context, 28 contentUri, null, null); 29 } 30 // 1.3 MediaProvider 31 else if (isMediaDocument(uri)) { 32 final String docId = DocumentsContract.getDocumentId(uri); 33 final String[] split = docId.split(":"); 34 final String type = split[0]; 35 36 Uri contentUri = null; 37 if ("image".equals(type)) { 38 contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 39 } else if ("video".equals(type)) { 40 contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 41 } else if ("audio".equals(type)) { 42 contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 43 } 44 45 final String selection = "_id=?"; 46 final String[] selectionArgs = new String[]{split[1]}; 47 48 return pathHead + getDataColumn(context, contentUri, selection, selectionArgs); 49 } 50 } 51 // 2. MediaStore (and general) 52 else if ("content".equalsIgnoreCase(uri.getScheme())) { 53 if (isGooglePhotosUri(uri)) {//判斷是否是google相冊圖片 54 return uri.getLastPathSegment(); 55 } else if (isGooglePlayPhotosUri(uri)) {//判斷是否是Google相冊圖片 56 return getImageUrlWithAuthority(context, uri); 57 } else {//其他類似於media這樣的圖片,和android4.4以下獲取圖片path方法類似 58 return getFilePath_below19(context, uri); 59 } 60 } 61 // 3. 判斷是否是文件形式 File 62 else if ("file".equalsIgnoreCase(uri.getScheme())) { 63 return pathHead + uri.getPath(); 64 } 65 return null; 66 } 67 /** 68 * @param uri 69 * The Uri to check. 70 * @return Whether the Uri authority is ExternalStorageProvider. 71 */ 72 private static boolean isExternalStorageDocument(Uri uri) { 73 return "com.android.externalstorage.documents".equals(uri.getAuthority()); 74 } 75 76 /** 77 * @param uri 78 * The Uri to check. 79 * @return Whether the Uri authority is DownloadsProvider. 80 */ 81 private static boolean isDownloadsDocument(Uri uri) { 82 return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 83 } 84 85 /** 86 * @param uri 87 * The Uri to check. 88 * @return Whether the Uri authority is MediaProvider. 89 */ 90 private static boolean isMediaDocument(Uri uri) { 91 return "com.android.providers.media.documents".equals(uri.getAuthority()); 92 } 93 /** 94 * 判斷是否是Google相冊的圖片,類似於content://com.google.android.apps.photos.content/... 95 **/ 96 public static boolean isGooglePhotosUri(Uri uri) { 97 return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 98 } 99 100 /** 101 * 判斷是否是Google相冊的圖片,類似於content://com.google.android.apps.photos.contentprovider/0/1/mediakey:/local%3A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1075342619 102 **/ 103 public static boolean isGooglePlayPhotosUri(Uri uri) { 104 return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority()); 105 }
2.2.3 針對Google相冊圖片獲取方法
從相冊中選擇圖片,如果手機安裝了Google Photo,它的路徑格式如下:
content://com.google.android.apps.photos.contentprovider/0/1/mediakey%3A%2Flocal%253A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1754758324
用原來的方式獲取是不起作用的,path會是null,我們可以通過下麵的形式獲取:
1 /** 2 * Google相冊圖片獲取路徑 3 **/ 4 public static String getImageUrlWithAuthority(Context context, Uri uri) { 5 InputStream is = null; 6 if (uri.getAuthority() != null) { 7 try { 8 is = context.getContentResolver().openInputStream(uri); 9 Bitmap bmp = BitmapFactory.decodeStream(is); 10 return writeToTempImageAndGetPathUri(context, bmp).toString(); 11 } catch (FileNotFoundException e) { 12 e.printStackTrace(); 13 }finally { 14 try { 15 is.close(); 16 } catch (IOException e) { 17 e.printStackTrace(); 18 } 19 } 20 } 21 return null; 22 } 23 /** 24 * 將圖片流讀取出來保存到手機本地相冊中 25 **/ 26 public static Uri writeToTempImageAndGetPathUri(Context inContext, Bitmap inImage) { 27 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 28 inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes); 29 String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null); 30 return Uri.parse(path); 31 }
通過流的形式將圖片讀進來,轉成bitmap形式,再寫進手機媒體中,轉換成路徑如下:content://media/external/images/media/1754758324,因為Google中的圖片並不在我們系統手機相冊中,需要先下載,再轉存。
3. 針對Android 7.0及以上機型打開Uri適配
Android7.0以上機型,若要在應用間共用文件,應發送一項 content:// URI,並授予 Uri臨時訪問許可權。進行此授權的最簡單方式是使用 FileProvider 類。如需瞭解有關許可權和共用文件的詳細信息,請參閱官方的共用文件。
具體需要在AndroidMainfest.xml文件中添加provider標簽,並定義相應的路徑,具體可參考鴻洋寫的Android 7.0 行為變更 通過FileProvider在應用間共用文件吧
具體裁剪圖片代碼如下
1 /** 2 * @param activity 當前activity 3 * @param orgUri 剪裁原圖的Uri 4 * @param desUri 剪裁後的圖片的Uri 5 * @param aspectX X方向的比例 6 * @param aspectY Y方向的比例 7 * @param width 剪裁圖片的寬度 8 * @param height 剪裁圖片高度 9 * @param requestCode 剪裁圖片的請求碼 10 */ 11 public static void cropImageUri(Activity activity, Uri orgUri, 12 Uri desUri, int aspectX, int aspectY, 13 int width, int height, int requestCode) { 14 Intent intent = new Intent("com.android.camera.action.CROP"); 15 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 16 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 17 orgUri = FileProvider.getUriForFile(activity, "com.smilexie.storytree.fileprovider", new File(newUri.getPath())); 18 } 19 intent.setDataAndType(orgUri, "image/*"); 20 intent.putExtra("crop", "true"); 21 intent.putExtra("outputX", width); 22 intent.putExtra("outputY", height); 23 intent.putExtra("scale", true); 24 //將剪切的圖片保存到目標Uri中 25 intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri); 26 intent.putExtra("return-data", false); 27 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); 28 intent.putExtra("noFaceDetection", true); 29 activity.startActivityForResult(intent, requestCode); 30 }
只要是打開文件,uri都需要更改,不然解析異常,但是保存進uri則不需要修改。
總結
Android各版本,各機型適配不得不說有很多坑,比如我在開發中遇到google相冊圖片,老是獲取不到路徑。只能平時多關註Android新版本發佈時,會對現有應用產生什麼影響,遇到問題多查看別人的解決辦法,多歸納,多總結,這樣才能使咱們的應用越來越完善。