Android 如何選取圖片或是檔案?


在設計Android App時,常常需要讓使用者選擇自己裝置內的圖片、音樂、影片或是文件,來進行一些處理,例如上傳檔案或是編輯檔案等等的功能。但是要如何讓使用者選擇檔案呢?Java程式語言Swing函式庫中,提供了JFileChooser類別,可以直接產生出一個檔案選取的視窗,能讓使用者選取電腦內的檔案,還可以使用FileFilter過濾檔案選取視窗顯示出來的檔案類型,十分方便。雖然Android的程式是使用Java程式語言進行開發,但Android上的Java只能算是半殘的Java,有許多類別不能使用,其中當然包括JFileChooser,不然就不用著寫這篇文章了。

要讓Android程式擁有檔案選取的功能,最保險、可靠而且一定能Work的方式,那就是自己使用File類別或是ContentProvider實作出一個能選取檔案的Activity或是Dialog。如果不知道要怎麼自己做出檔案選取器的話,也可以使用別人寫好的,在網路上都可以找得到程式碼,只是品質無法保證。

這篇文章將不探討如何直接在自己的Android App內實作出檔案選取器,而是要應用Android裝置已經安裝的檔案瀏覽器之類的App來選取檔案。舉例來說,假設要選取一張圖片,而且裝置上已經有安裝「圖片庫(Google Gallery)」或是「快圖瀏覽(QuickPic)」之類的App,那麼將可以直接使用它們來選取圖片,就不需要在程式專案內實作出複雜的檔案選取和圖片瀏覽的功能了!

Android上的檔案類型

在選取檔案之前,必須要清楚知道要選取什麼樣類型的檔案。在Android系統中,跟內容(Content)相關的資訊都可由ContentProvider來取得,當使用者儲存了一個檔案,檔案的實際路徑與類型就會被儲存在ContentProvider中,因此只要查找ContentProvider中的檔案類型,就可以很容易地取得裝置中所有屬於這種類型的檔案。

Android的檔案類型採用MIME(Multipurpose Internet Mail Extensions)標準,MIME使用特定的字串格式來表示內容類型(當然也包括檔案),MIME的字串格式如下:

[主類型]/[子類型]

以檔案來說,可能會有以下幾種類型:

如果要表示所有類型的檔案,可以使用「*」字元。舉例來說,MIME類型若為「image/*」,表示所有的圖片類型;MIME類型若為「*/*」表示所有類型。

透過MIME選取檔案

有了其他Android App的幫助,設計師就不需要自己使用ContentProvider來取的指定MIME類型下的檔案清單,只需要讓這些擁有檔案選取功能的App知道應該要選擇什麼樣MIME類型的檔案即可。

Android的Intent可以用在Application或是Activity之間的溝通,只要使用Intent的ACTION_GET_CONTENT,系統就會幫使用者找到裝置內合適的App來取得指定MIME類型的內容,程式可以這樣寫:

建議用法

如果程式只有寫成這樣的話,將會遇到兩個恰恰相反的問題。第一個是Android系統找不到符合指定MIME類型的內容選取器,程式將會因為找不到可執行的Activity而直接閃退,這個問題甚至可能會沒辦法直接用try-catch來解決。第二個可能會遇到的問題是,當Android系統找到兩種以上可用的App或是Activity支援指定的MIME類型時,可能會自動使用其中一種,此時也許就會選到無法正常使用的App或是Activity,連帶使我們的App永遠無法正常使用。

要解決第一個找不到Activity的問題,可以事先使用PackageManager查詢可以使用該MIME類型的Activity列表來解決。而要解決第二個可用App或是Activity有兩個以上的問題的話,可以使用系統內建的Intent Chooser,跳出選單讓使用者選擇要使用哪個。因此選取檔案的程式可以寫成如以下的樣子,大概就沒有什麼問題了。

複選檔案

如果要複選檔案,可以在picker的Intent中,加入EXTRA_ALLOW_MULTIPLE欄位,並將其值設為true,如下:

EXTRA_ALLOW_MULTIPLE不一定能在所有的Activity上成功使用,要看該Activity有沒有用到EXTRA_ALLOW_MULTIPLE,並且實作出檔案複選的功能。

只能選擇本地端的檔案

如果只要選擇裝置內本地端的檔案,也可以在picker的Intent中,加入EXTRA_LOCAL_ONLY欄位,並將其值設為true。

至於什麼時候有能選擇裝置外的檔案的情況,當Activity有瀏覽雲端檔案或是同步之類的功能時,所選的檔案就可能不在本地端,如果此時要抓取檔案的實際路徑來做些應用,就可能會因實際路徑不在本地端上,而出現問題。EXTRA_LOCAL_ONLY也是一樣不一定能在所有的Activity上成功使用,要看該Activity的實作方式。

常見的錯誤用法

網路上常常可以看到有別人是使用Intent的ACTION_PICK來選取檔案,寫法其實和ACTION_GET_CONTENT類似,如下:

雖然執行結果好像跟ACTION_GET_CONTENT沒有兩樣,但是其實這樣的用法是錯誤的。原因在於ACTION_PICK實際上是用來選擇一個URI目錄下的資料,比較正確的用法如下:

取得檔案位於裝置上的實際路徑

使用Intent的ACTION_GET_CONTENT和MIME類型來選擇檔案十分容易,但要如何取得使用者選定的檔案呢?那就得看使用者所用來選取檔案的Activity,是如何傳回檔案資料的,這部份並沒有特定的格式,而且也可能會有不符合指定MIME類型的檔案。

但是,大部份可選擇檔案的Activity都是在onActivityResult時,將選擇的結果包在傳回的Intent內。如果要使用onActivityResult來取得Activity的回傳結果,那麼當初在開啟Activity時,就要使用startActivityForResult方法,而不是startActivity方法。選取的結果,單選和複選的狀況應該要分開探討。

單選檔案

使用getData方法取得傳回的Intent所包含的URI,這個URI通常可以直接被用來反查ContentProvider內的檔案實際路徑。

複選檔案

Intent的EXTRA_ALLOW_MULTIPLE欄位定義是在Android API 18之後才出現的,但是API 18之前的Activity究竟有沒有支援檔案複選也很難說。可以確定的是,複選檔案的結果大多是包在Intent的ClipData中,因此可以使用getClipData方法取得傳回的Intent所包含的ClipData,再用迴圈抓出ClipData中所有項目的URI,這個URI通常可以直接被用來反查ContentProvider內的檔案實際路徑。建議從Android API 16開始支援檔案複選的功能。

實際路徑

有了檔案的URI後,接著就要找出URI所對應的檔案,在Android裝置上實際的路徑。在Android 4.4(API 19)之前的版本,URI都可以直接透過ContentProvider來查詢其對應的檔案實際路徑,但在Android 4.4以及之後的版本,系統似乎內建了內容瀏覽器,並且使用了Android 4.4之後才新增的文件URI格式,回傳的URI Authority將會使用「com.android.externalstorage.documents」、「com.android.providers.downloads.documents」或是「com.android.providers.media.documents」。如果直接用這樣的URI查詢ContentProvider,將會得到空的路徑,因此這樣的URI需要進行一些轉換,才能被用來查詢ContentProvider。

實作檔案選取的程式

由於檔案選取算是一個常用的功能,所以可以將它寫成一個獨立的類別來重複使用。程式碼如下:

MagicFileChooser的用法十分容易,如果只是要用來取得URI的實體路徑,甚至不用使用new運算子來實體化出MagicFileChooser物件。

如果要使用MagicFileChooser來選取檔案的話,一開始必須先將MagicFileChooser實體化,並且要在建構子的參數傳入當前的Activity。接著使用showFileChooser方法來顯示出Android裝置內可用的檔案選取App,或是檔案選取App的列表。如果showFileChooser方法回傳false,表示裝置內並沒有適合的Activity能作為檔案選取器。為了取得MagicFileChooser的檔案選取結果,必須要覆寫Activity的onActivityResult方法,要在Activity的onActivityResult方法中呼叫MagicFileChooser的onActivityResult方法。如果選取結果有回傳成功,MagicFileChooser的onActivityResult方法會傳回true,此時可以使用MagicFileChooser的getChosenFiles方法,取得使用者所選的檔案,型態為File陣列。如此一來變能輕鬆無負擔地選取檔案了!

Android 如何選取圖片或是檔案?

關於作者

Magic Len

各位好,我是Magic Len,是這網站的管理員。我是台灣台中大肚山上人,畢業於台中高工資訊科和台灣科技大學資訊工程系,曾在桃機航警局服役。我熱愛自然也熱愛科學,喜歡和別人分享自己的知識與經驗。如果你有興趣認識我,可以加我的Facebook,並且請註明是從MagicLen來的。

相關文章