Android External Storage 外部儲存使用方法
APP 開發External Storage 中的檔案能與其他 APP 共享資源,也可以使用手機內建的檔案管理 APP 來瀏覽這些檔案。 傳統的 External Storage 通常代表 SD 卡,但實際上 External Storage 區分 Primary External Storage 及 Secondary External Storage 兩種類型,每個類型還區分 APP 專屬目錄及共享目錄。 這幾種類型主要差異只有目錄取得的方式,後續的各項操作都大同小異。 由於 External Storage 儲存設備可能被使用者移除,因此比較適合用來儲存 APP 執行時非必要的檔案,就是被刪除也不影響 APP 執行的檔案。 在使用檔案前,也建議應該先檢查設備是否可讀可寫。
External Storage 類型介紹
下圖是 External Storage 不同類型的目錄位置,位於 APP 專屬目錄 (app-specific) 中的檔案,會在 APP 解除安裝時一併被移除,共享目錄則不會移除。
Primary External Storage
Primary External Storage 直接翻譯就是主要外部儲存空間,這個空間是位於手機內部的儲存空間,因此無論有沒有 SD 卡,APP 都一定有 Primary External Storage 可以使用。
APP 專屬目錄 (app-specific)
// 取得 app-specific 目錄路徑
// 路徑:/storage/emulated/0/Android/data/[Package Name]/files
ContextCompat.getExternalFilesDirs(getApplicationContext(), null)[0];
getExternalFilesDir(null);
// 取得 app-specific 中特定類型目錄路徑
// 路徑:/storage/emulated/0/Android/data/[Package Name]/files/Download
ContextCompat.getExternalFilesDirs(getApplicationContext(), Environment.DIRECTORY_DOWNLOADS)[0];
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
// 取得 app-specific 快取目錄路徑
// 路徑:/storage/emulated/0/Android/data/[Package Name]/cache
ContextCompat.getExternalCacheDirs(getApplicationContext())[0];
getExternalCacheDir();
共享目錄 (專屬目錄以外)
這類型操作需要額外的權限,詳細說明請參考第 3 章。
// 取得共享目錄路徑
// 路徑:/storage/emulated/0
Environment.getExternalStorageDirectory();
// 取得特定類型共享目錄
// 路徑:/storage/emulated/0/Download
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
Secondary External Storage
Secondary External Storage 就是指 Primary External Storag 以外的儲存空間,一般常見的就是 SD 卡或是 USB 外接隨身碟。
APP 專屬目錄 (app-specific)
// 取得 app-specific 目錄路徑
// 路徑:/storage/0000-0000/Android/data/[Package Name]/files
ContextCompat.getExternalFilesDirs(getApplicationContext(), null)[1];
// 取得 app-specific 快取目錄路徑
// 路徑:/storage/0000-0000/Android/data/[Package Name]/cache
ContextCompat.getExternalCacheDirs(getApplicationContext())[1];
共享目錄 (專屬目錄以外)
目前尚無有效方法可操作此類型目錄。
確認儲存空間是否可用
由於 External Storage 使用的儲存設備可能會被使用者移除,因此在使用前需要先檢查儲存空間是否可以使用。
// 確認 External Storage 是否可讀取可寫入
private boolean isExternalStorageWritable() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
// 確認 External Storage 是否可讀取
private boolean isExternalStorageReadable() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}
存取共享目錄所需權限
存取共享目錄需要在 AndroidManifest.xml 檔案中,設定 READ_EXTERNAL_STORAGE 及 WRITE_EXTERNAL_STORAGE 權限,並將 requestLegacyExternalStorage 設定為 true。
<manifest>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:requestLegacyExternalStorage="true">
...
</application>
</manifest>
之後在程式碼中,檢查是否具備權限。
public int REQUEST_CODE_PERMISSION_STORAGE = 100;
protected void onCreate(Bundle savedInstanceState) {
String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
for (String string : permissions) {
if (this.checkSelfPermission(string) != PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(permissions, REQUEST_CODE_PERMISSION_STORAGE);
return;
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_PERMISSION_STORAGE) {
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
// 使用者拒絕權限
} else {
// 使用者允許權限
}
}
}
檔案操作
以下是 External Storage 常用操作程式碼,提供大家參考。
取得目錄
// 取得 app-specific 目錄 (Primary External Storage)
ContextCompat.getExternalFilesDirs(getApplicationContext(), null)[0];
getExternalFilesDir(null);
// 取得 app-specific 快取目錄 (Primary External Storage)
ContextCompat.getExternalCacheDirs(getApplicationContext())[0];
getExternalCacheDir()
// 取得 app-specific 目錄 (Secondary External Storage)
ContextCompat.getExternalFilesDirs(getApplicationContext(), null)[1];
// 取得 app-specific 快取目錄 (Secondary External Storage)
ContextCompat.getExternalCacheDirs(getApplicationContext())[1];
// 取得共享目錄 (需要權限)
Environment.getExternalStorageDirectory();
寫入
public void writeFileToExternal(String fileName, String fileContents) throws IOException {
File file = new File(getExternalFilesDir(null), fileName);
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(fileContents.getBytes());
outputStream.close();
}
writeFileToExternal("test", "測試資料");
讀取
public void readFileFromExternal(String fileName) throws IOException {
File file = new File(getExternalFilesDir(null), fileName);
FileInputStream inputStream = new FileInputStream(file);
BufferedInputStream bufferedStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (int result = bufferedStream.read(); result != -1; result = bufferedStream.read()) {
outputStream.write((byte) result);
}
// 關閉串流
inputStream.close();
// 回傳檔案字串
return outputStream.toString("UTF-8");
}
readFileFromExternal("test", "測試資料");
刪除
public void removeFileFromExternal(String fileName) {
File file = new File(getExternalFilesDir(null), fileName);
File file = new File(parentFile, fileName);
file.delete();
}
removeFileFromExternal("test");
從網址取得檔案儲存至 External Storage
上述範例是將純文字寫入至本機檔案儲存,但在實際案例中,很少單純將文字寫入到檔案而已。 通常都是透過網址將遠端的檔案下載至本機儲存,請參考以下範例。
new Thread(() -> {
try {
URL url = new URL ("檔案網址");
InputStream inputStream = url.openStream();
OutputStream outputStream = new FileOutputStream(getExternalFilesDir(null).getPath() + "/檔名");
byte [] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
outputStream.flush();
outputStream.close();
inputStream.close();
} catch (IOException ignored) {
}
}).start();
0 則留言