在 Android 開發中,有時需要將打包在 APK 內的原始資源(如 res/raw 目錄下的音訊檔案)複製到外部儲存裝置(SD 卡),以便其他應用或系統媒體掃描器能夠識別。許多開發者嘗試使用 FileOutputStream 直接寫入,卻發現檔案並未實際生成,且程式碼執行過程中沒有拋出明顯異常。
以下代碼片段展示了典型的嘗試:
InputStream fIn = getResources().openRawResource(R.raw.song);
byte[] buffer = new byte[fIn.available()];
fIn.read(buffer);
fIn.close();
String path = "/sdcard/media/audio/ringtones/";
File dir = new File(path);
if (!dir.exists()) dir.mkdirs();
FileOutputStream save = new FileOutputStream(path + "examplefile.ogg");
save.write(buffer);
save.flush();
save.close();
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.parse("file://" + path + "examplefile.ogg")));
雖然邏輯看似正確,但忽略了兩個關鍵點:
WRITE_EXTERNAL_STORAGE,舊版本則需在 Manifest 中聲明。Environment.getExternalStorageDirectory() 獲取根目錄,而非固定 /sdcard/。<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
針對 Android 10 (API 29) 以上,建議改用 MediaStore API 或應用專屬目錄(getExternalFilesDir()),無需權限即可讀寫。
// 在子執行緒執行 IO 操作
new Thread(() -> {
try (InputStream in = getResources().openRawResource(R.raw.song);
FileOutputStream out = new FileOutputStream(new File(
getExternalFilesDir(Environment.DIRECTORY_RINGTONES), "song.ogg"))) {
byte[] buf = new byte[4096];
int len;
while ((len = in.read(buf)) > 0) out.write(buf, 0, len);
// 通知媒體掃描
MediaScannerConnection.scanFile(this,
new String[]{out.getFile().getAbsolutePath()}, null, null);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
getExternalFilesDir() 或 MediaStore,避免權限問題。MediaScannerConnection.scanFile() 更新媒體庫。採用上述方法,即可可靠地將原始資源導出為獨立媒體檔案,供系統鈴聲選擇器或其他應用使用。