年假在家裡休息無所事事,昨天花一個下午的時間稍微試一下Android的外部記憶體,也就是在裝置中的/sdcard/目錄的使用方式。
在Android 4.4之前,只要在AndroidManifest.xml中加入android.permission.WRITE_EXTERNAL_STORAGE,就可以自由地使用外部路徑,使用API Environment.getExternalStorageDirectory()可以取得外部記憶體的路徑。約在一年前,Android作業系統大部分都使用在手機上,那時的HTC最高階的手機是HTC Sensation,同一時期的各家廠牌手機,外部記憶體通常指的就是Micro SD卡,開發App時,將檔案寫在外部記憶體很方便,能透過ADB或USB隨身碟的方式將檔案拉到PC上。
一直到了HTC機皇是HTC One X的時期,原本是外部記憶體路徑的/sdcard/,變成虛擬外部記憶體,也就是說,這個/sdcard/目錄,並不是實體Micro SD卡的路徑;而是手機內部空間虛擬成的外部記憶體,這個虛擬而成的外部記憶體一樣須加上permission後才能正常讀寫檔案,在開發階段很方便,不需要真的取準備一張實體的Micro SD卡,就可以自由地將檔案從手機拉到電腦上。
但也衍生一個問題,如果真的想把檔案存到實體的Micro SD卡中,該怎麼辦呢?目前Android也沒有提供相關的API取得實體外部路徑,但在網路上有一些非官方的方法可以取得真正SD卡路徑的,但不是每隻手機都適用。
在Android 4.4剛出來的時候,很興奮的把我的Asus Nexus 7升級,升級之後原本的程式一直當機,追查後發現是Android 4.4以後的版本,sdcard有做一層保護,App不能直接使用外部記憶體(不管是虛擬或實體外部記憶體),需透過手機的設定,才能把整隻App移到sdcard上,但還是有一個缺點,把App移動sdcard上後,只要移除該App,所有和該App關聯的檔案(包含使用該App所產生出來的所有檔案)全部都會被刪除。天殺的Google這樣改,超級麻煩,在開發階段的App,動不動就須移除程式重新安裝,產生好的假資料每安裝一次就要再建立一次,怕麻煩的我決定重回Android 4.2的懷抱,把Nexus 7先丟一旁,拿實驗室的其他手機來開發。
最近使用ES File Browser (最初它也不能再Android 4.4上正常運作),突然發現它在我的Nexus 7上竟然可以正常存取外部記憶體中的檔案,於是我好奇地重回Android 4.4的懷抱,參考這裡寫了一隻在外部記憶體上寫檔案和讀檔案的程式,檔案竟然可以操作了!操作方法跟以前一模一樣。
在Android 4.4多了一個permission:android.permission.READ_EXTERNAL_STORAGE,顧名思義就是讀檔案的權限。
寫檔案的權限已經包含讀檔案的權限,也就是說如果加入android.permission.WRITE_EXTERNAL_STORAGE,就可以不用再加入android.permission.READ_EXTERNAL_STORAGE,Google也很用心地提供了isExternalStorageWritable()和isExternalStorageReadable()兩個方法,用來判斷外部記憶體是否能讀寫。
於是我做了小實驗,觀察isExternalStorageWritable()和isExternalStorageReadable()的回傳值:
在沒有加入任何權限的情況下,
isWritable()回傳true
isReadable()回傳true
但當我執行檔案讀取或檔案寫入都會失敗,
蝦咪!?明明不能讀寫檔案還給我回傳true。
追了一下程式發現這兩個方法都是藉由Environment.getExternalStorageState()的回傳字串判斷外部記憶體的狀態是否可讀可寫,於是我又在HTC Butterfly測試,HTC Butterfly可以插實體外部記憶體(Micro SD卡),觀察實體外部記憶體在插上或拔除的時候Environment.getExternalStorageState()會不會取得其他不同的狀態,結果當然沒有這麼順利,它竟然永遠都是回傳Environment.MEDIA_MOUNTED。
PS: HTC Butterfly的"/sdcard/"目錄是屬於虛擬外部記憶體而非實體外部記憶體。
接著我決定實作看看『正確地判斷/sdcard/可讀或可寫』的方法,花了約六個小時的時間(因為嚐鮮透過Android Studio加上最近剛學的TDD實作),這邊直接給大家我完成的程式碼。
先定義一下我所個人認為的isExternalStorageReadable()和isExternalStorageWritable(),兩個方法的理想行為
isExternalStorageReadable():
在沒有加入android.permission.READ_EXTERNAL_STORAGE或android.permission.WRITE_EXTERNAL_STORAGE時,回傳false;反之則回傳true,代表可以讀取在外部記憶體的檔案。
isExternalStorageWritable():
在沒有加入android.permission.WRITE_EXTERNAL_STORAGE時,回傳false;反之則回傳true,代表可以寫資料在外部記憶體內。
實作內容如下(黃色部分):
----------------------------------------------------------------------------------------------------
boolean isExternalStorageReadable() {
return Environment.getExternalStorageDirectory().canRead();
}
boolean isExternalStorageWritable() {
boolean writable;
final String filePath = Environment.getExternalStorageDirectory() +
"/" + "testExternalStorageWritableFile";
FileOutputStream fos = null;
try {
fos = new FileOutputStream(filePath);
writable = true;
// delete test file
File file = new File(filePath);
file.delete();
} catch (FileNotFoundException e) {
writable = false;
} finally {
if(fos != null)
try {
fos.close();
} catch(IOException e) {
e.printStackTrace();
}
}
return writable;
}
----------------------------------------------------------------------------------------------------
最初的isExternalStorageWritable()使用Environment.getExternalStorageDirectory().canWrite();
但我加入android.permission.READ_EXTERNAL_STORAGE後它就回傳true了,在這個情境下要回傳false才對。
所以最後用了比較麻煩的方法:
如果沒有加入android.permission.WRITE_EXTERNAL_STORAGE,
執行new FileOutputStream(...)就會捕捉到FileNotFoundException,此時代表外部記憶體不可寫。
加入android.permission.WRITE_EXTERNAL_STORAGE後new FileOutputStream(...)不會發生例外且產生檔案,代表外部記憶體可寫,但最後必須將產生的檔案刪除。
以上是我小小的分享,祝各位新年快樂!
有疑問就去挖(一直"那些事"好煩喔,暫時讓它消失,有需要再讓它出現)
沒有留言:
張貼留言