1. 程式人生 > >android開啟系統圖庫終極適配

android開啟系統圖庫終極適配

android中呼叫系統圖庫本來是一個很基本的東西,幾乎每個app都用的到(最基本的更換使用者頭像),網上的相關

容很多,本來找了幾篇看了一下,拿幾臺測試機試了一下感覺就沒什麼問題了,但是適配問題慢慢就來了。

一.開啟相簿的基本方法。

通過查詢資料,呼叫系統圖庫基本有3種方法。

1.使用Intent.ACTION_PICK

Intent i = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media. EXTERNAL_CONTENT_URI); //呼叫android的相簿
//i.setType("image/*");//不可設定type,否則noactivityfound
startActivityForResult(Intent. createChooser(i,null) , 2) ;

這個intent在android6.0以下是可以用的,但是到了6.0就無效了,具體列印log忘記了,官方文件也只用了這

Intent來獲取留聯絡人,所以pass掉。

2.使用Intent.ACTION_GET_CONTENT

Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT) ; //"android.intent.action.GET_CONTENT"
innerIntent.setType( "image/*"); //檢視型別 String IMAGE_UNSPECIFIED = "image/*";
innerIntent.addCategory(Intent. CATEGORY_OPENABLE );
startActivityForResult(Intent. createChooser(innerIntent, null) , 2) ;

並且有個bool型的extra EXTRA_ALLOW_MULTIPLE來支援多選功能(4.3及以上版本支援)。

官方文件上說,這個intent是用來“檢索一個特定型別的檔案”並且回返回檢索到的檔案的一個引用(檔案的copy)

這個貌似是可以用的,而且也是大多數人選擇。

3.使用Intent.ACTION_OPEN_DOCUMENT

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT) ;
intent.addCategory(Intent. CATEGORY_OPENABLE);
intent.setType("image/*") ;
startActivityForResult(intent , 2) ;

這個action只支援4.4以上的版本。官方文件上說,這個intent是用來“開啟一個特定型別的檔案”,與檢索相比

感覺跟適合我們的應用場景。同樣可以使用EXTRA_ALLOW_MULTIPLE來支援多選。這個貌似更適合,只是需要判斷sd

版本,4.4及以上使用ACTION_OPEN_DOCUMENT,4.3及以下使用ACTION_OPEN_DOCUMENT,這也是一些比較好的攻略裡

使用方法。

二.選擇圖片後,圖片檔案的解析

當在系統圖庫中選擇好圖片後,可以在進入相簿的Activity的onActivityResult()方法中通過返回的Uri進行解析

並且理論上Uri是content形式。而android的儲存框架在4.4進行了一次改變。

(1)4.3及以下返回的uri是content://media/external/images/media/3951的形式,檔案解析方式如下:

protected void onActivityResult( int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data) ;
   if(requestCode== 2&&Activity.RESULT_OK==resultCode&& null!=data){
      Uri selectedImage = data.getData();
      String picturePath = null;
      try {
         String[] filePathColumns={MediaStore.Images.Media.DATA} ;
         Cursor c = this.getContentResolver().query(selectedImage, filePathColumns, null,null, null) ;
         c.moveToFirst() ;
         int columnIndex = c.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);//getColumnIndex(filePathColumns[0]);
         picturePath= c.getString(columnIndex) ;
         c.close() ;
      } catch (Exception e) {
         picturePath = selectedImage.getPath();
      }
      Bundle bdl = new Bundle();
      if(picturePath != null){
         bdl.putString("image", picturePath);
         UIUtil. openActivity(this, PicCutActivity. class, bdl, 3);
      }


4.4及以上返回的uri是content://com.android.providers.media.documents/document/image:3952的形式,

析方式如下:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		if(requestCode==2&&Activity.RESULT_OK==resultCode&&null!=data){
			Uri selectedImage = data.getData();
			String picturePath  = selectedImage.getPath();
			try {
				final String docId = DocumentsContract.getDocumentId(selectedImage);
				final String[] split = docId.split(":");
				final String type = split[0];
				Uri contentUri = null;
				if ("primary".equalsIgnoreCase(type)) {
					picturePath = Environment.getExternalStorageDirectory() + "/" + split[1];
				} else {
					if ("image".equals(type)) {
						contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
					} else if ("video".equals(type)) {
						contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
					} else if ("audio".equals(type)) {
						contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
					}
					final String selection = "_id=?";
					final String[] selectionArgs = new String[]{
							split[1]
					};
					picturePath = FileUtils.getDataColumn(this, contentUri, selection, selectionArgs);
				}
			} catch (Exception e) {
					picturePath = selectedImage.getPath();
		}

	}


三.結論,最終的適配方案

1.出現問題前

最開始,我使用的方法開啟系統圖庫的方法是第三種方法中的適配方法。

(1)開啟相簿:

if(sdk版本<4.4)
			使用Intent.ACTION_GET_CONTENT開啟相簿
		else
			使用Intent.ACTION_OPEN_DOCUMENT開啟相簿


(2)解析uri獲取檔案路徑:

if(sdk版本<4.4)
			使用第一種解析方式
		else
			使用第二種解析方法


貌似一切都邏輯都很合理,但最終都被碎片化的各個android手機廠商的定製系統給打破了。

2.問題出現

(1)第一個出現的問題是小米手機,測試的各種版本的小米手機都是4.4以上的系統,使

Intent.ACTION_OPEN_DOCUMENT,哪怕是通過setYpe("image/*")

設定了MIME type為圖片,小米系統卻還是給你打開了一個樹形目錄的檔案管理器。雖然理論上我們應該去儘快適

android版本的更新,使用最新的api,但是現實中,我們只有使用Intent.ACTION_GET_CONTENT。

(2)

現在確定使用了Intent.ACTION_GET_CONTENT,而使用該Intent返回的Uri型別可以是content:// ;file://  ;

http://中的一種(Intent.ACTION_OPEN_DOCUMENT只有content://一種,雖然不用,但還是瞭解一下的好),雖然

論上開啟相簿返回的都應該是content://的型別,但測試中確實發現有有個別機型直接就返回了file://形式的文

路徑,因此需要先判斷是否直接返回了檔案路徑,是的檔案路徑的話就不用解析,否則進行解析。

(3)隨著測試的機型的增多,理論上4.4以上的機型返回的content形式的Uri都是應該第二種解析方法適用的格式,

某些機型卻會返回第一種解析方法適用的格式,並在解析過程中丟擲異常。由於兩種解析方式的原理我並沒有仔細

研究,於是就在catch語句塊中再進行了一次解析。

3.綜上所述,我的開啟相簿的策略如下

(1)開啟相簿使用Intent.ACTION_GET_CONTENT

(2)選擇圖片檔案解析:

		Uri selectedImage = data.getData();//data是onActivityResult返回的intent
		String picturePath  = selectedImage.getPath();
		if(!new File(picturePath).exists){//1.判斷返回的uri是否是file:// 型別
			try{
				if(sdk < 4.4)
					第一種解析方式//2. 4.4以下版本使用第一種方式
				else 
					第二種解析方式//3. 4.4及以上使用第二種方式
			}catch(Exception e){
				try{
					if(sdk  >= 4.4)
						第一種解析方式//4. 4.4及以上使用第二種方式丟擲異常,則用第一種方式再解析一次
				}catch(..){..}
			}
		}


若使用第一種解析方式丟擲異常(雖然我沒有遇見過),則picturePath = selectedImage.getPath();。在使

picturePath時最好再判斷一下檔案是否存在。以上就是我自己總結的邏輯,雖然實現方式也許不是很好,但邏輯

算是比較周全了,歡迎指正。