Android 通過 ACTION_GET_CONTENT 選擇文件返回 content:// URI,不能直接用 File 構造路徑讀取。正確方式是用 ContentResolver.openInputStream(uri) 獲取輸入流,無論文件在 SD 卡、Downloads、雲端還是其他 App 提供。
// ❌ 錯誤:data.getPath() 返回類似 "/document/primary:Download/test.csv",非真實文件路徑
File f = new File(data.getPath());
FileInputStream fis = new FileInputStream(f); // FileNotFoundException
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == SELECT_CSV_Dialog && resultCode == RESULT_OK && data != null) {
Uri uri = data.getData(); // content:// URI
readCsvFromUri(uri);
}
}
private void readCsvFromUri(Uri uri) {
new Thread(() -> {
try (InputStream is = getContentResolver().openInputStream(uri);
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line;
boolean firstLine = true;
while ((line = reader.readLine()) != null) {
if (firstLine) { firstLine = false; continue; } // 跳過表頭
String[] columns = line.split(","); // 簡單分割,複雜 CSV 建議用 OpenCSV
// 處理 columns...
runOnUiThread(() -> updateUI(columns));
}
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> Toast.makeText(this, "讀取失敗: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
}).start();
}
// 依賴: implementation 'com.opencsv:opencsv:5.8'
try (InputStream is = getContentResolver().openInputStream(uri);
CSVReader reader = new CSVReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String[] header = reader.readNext(); // 讀取表頭
String[] row;
while ((row = reader.readNext()) != null) {
// row 已正確處理引號內逗號、換行等
String col1 = row[0];
String col2 = row[1];
// ...
}
}
| Android 版本 | 權限需求 | 備註 |
|---|---|---|
| Android 10 (API 29) 以下 | READ_EXTERNAL_STORAGE | 需運行時申請 |
| Android 10 (API 29) | 無需權限 (分區存儲) | 僅能訪問用戶選擇的文件 |
| Android 11+ (API 30+) | 無需權限 | 僅能訪問用戶選擇的文件,ACTION_OPEN_DOCUMENT 可獲取持久權限 |
// 使用 ACTION_OPEN_DOCUMENT 替代 ACTION_GET_CONTENT
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/csv");
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT);
// onActivityResult 中獲取持久權限
Uri uri = data.getData();
final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
// 後續可直接用 uri 訪問,無需用戶再次選擇
uri.getPath() 構造 File:content URI 路徑無意義,必須用 ContentResolver。new InputStreamReader(is, StandardCharsets.UTF_8),避免中文亂碼。split(",") 無法處理 "張三,李四",25 這類引號內逗號。核心原則:Android 文件訪問統一用 ContentResolver + URI,拋棄 File 絕對路徑思維,擁抱「文件即內容流」的現代存儲模型。