首先,你應該注意的是file.mkdir()
和file.mkdirs()
返回false
如果這個目錄已經存在。如果您想知道目錄是否存在,請使用(file.mkdir() || file.isDirectory())
或者直接忽略返回值並致電file.isDirectory()
(請參閱文檔)。
也就是說,你真正的問題是你需要權限才能在Android 5.0+上的可移動存儲上創建目錄。在Android上使用可移動SD卡非常可怕。
在Android 4.4(KitKat)上,Google限制訪問SD卡(請參閱here,here和here)。如果您需要在Android 4.4(KitKat)上的可移動SD卡上創建一個目錄,請參閱StackOverflow answer這導致此XDA post。
在Android 5.0(Lollipop)上,Google推出了新的SD卡訪問API。有關示例用法,請參閱此stackoverflow answer。
基本上,您需要使用DocumentFile#createDirectory(String displayName)
來創建您的目錄。在創建該目錄之前,您需要先向用戶授予對您的應用程序的權限。
注:這是可移動存儲設備。如果您有權限android.permission.WRITE_EXTERNAL_STORAGE
,使用File#mkdirs()
將可用於內部存儲器(通常與Android上的外部存儲器混淆)。
我會後下面的一些示例代碼:
檢查是否需要徵得他的許可:
File sdcard = ... // the removable SD card
List<UriPermission> permissions = context.getContentResolver().getPersistedUriPermissions();
DocumentFile documentFile = null;
boolean needPermissions = true;
for (UriPermission permission : permissions) {
if (permission.isWritePermission()) {
documentFile = DocumentFile.fromTreeUri(context, permission.getUri());
if (documentFile != null) {
if (documentFile.lastModified() == sdcard.lastModified()) {
needPermissions = false;
break;
}
}
}
}
下一頁(如果needPermissions
是true
),則可以顯示一個對話框向用戶解釋他們需要選擇「SD卡」以使您的應用具有創建文件/目錄的權限,然後開始以下活動:
if (needPermissions) {
// show a dialog explaining that you need permission to create the directory
// here, we will just launch to chooser (what you need to do after showing the dialog)
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), STORAGE_REQUEST_CODE);
} else {
// we already have permission to write to the removable SD card
// use DocumentFile#createDirectory
}
現在,您將需要檢查resultCode
和requestCode
在onActivityResult
:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == STORAGE_REQUEST_CODE && resultCode == RESULT_OK) {
File sdcard = ... // get the removable SD card
boolean needPermissions = true;
DocumentFile documentFile = DocumentFile.fromTreeUri(MainActivity.this, data.getData());
if (documentFile != null) {
if (documentFile.lastModified() == sdcard.lastModified()) {
needPermissions = false;
}
}
if (needPermissions) {
// The user didn't select the "SD Card".
// You should try the process over again or do something else.
} else {
// remember this permission grant so we don't need to ask again.
getContentResolver().takePersistableUriPermission(data.getData(),
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Now we can work with DocumentFile and create our directory
DocumentFile doc = DocumentFile.fromTreeUri(this, data.getData());
// do stuff...
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
這應該給你上DocumentFile
和可移動SD卡在Android 5.0+工作的一個良好的開端。它可以是PITA。
此外,沒有公共API來獲取可移動SD卡的路徑(如果有的話)。你不應該依靠硬編碼"/storage/sdcard1"
!在StackOverflow上有很多關於它的文章。許多解決方案使用環境變量SECONDARY_STORAGE
。以下是可用於查找可移動存儲設備的兩種方法:
public static List<File> getRemovabeStorages(Context context) throws Exception {
List<File> storages = new ArrayList<>();
Method getService = Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class);
if (!getService.isAccessible()) getService.setAccessible(true);
IBinder service = (IBinder) getService.invoke(null, "mount");
Method asInterface = Class.forName("android.os.storage.IMountService$Stub")
.getDeclaredMethod("asInterface", IBinder.class);
if (!asInterface.isAccessible()) asInterface.setAccessible(true);
Object mountService = asInterface.invoke(null, service);
Object[] storageVolumes;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String packageName = context.getPackageName();
int uid = context.getPackageManager().getPackageInfo(packageName, 0).applicationInfo.uid;
Method getVolumeList = mountService.getClass().getDeclaredMethod(
"getVolumeList", int.class, String.class, int.class);
if (!getVolumeList.isAccessible()) getVolumeList.setAccessible(true);
storageVolumes = (Object[]) getVolumeList.invoke(mountService, uid, packageName, 0);
} else {
Method getVolumeList = mountService.getClass().getDeclaredMethod("getVolumeList");
if (!getVolumeList.isAccessible()) getVolumeList.setAccessible(true);
storageVolumes = (Object[]) getVolumeList.invoke(mountService, (Object[]) null);
}
for (Object storageVolume : storageVolumes) {
Class<?> cls = storageVolume.getClass();
Method isRemovable = cls.getDeclaredMethod("isRemovable");
if (!isRemovable.isAccessible()) isRemovable.setAccessible(true);
if ((boolean) isRemovable.invoke(storageVolume, (Object[]) null)) {
Method getState = cls.getDeclaredMethod("getState");
if (!getState.isAccessible()) getState.setAccessible(true);
String state = (String) getState.invoke(storageVolume, (Object[]) null);
if (state.equals("mounted")) {
Method getPath = cls.getDeclaredMethod("getPath");
if (!getPath.isAccessible()) getPath.setAccessible(true);
String path = (String) getPath.invoke(storageVolume, (Object[]) null);
storages.add(new File(path));
}
}
}
return storages;
}
public static File getRemovabeStorageDir(Context context) {
try {
List<File> storages = getRemovabeStorages(context);
if (!storages.isEmpty()) {
return storages.get(0);
}
} catch (Exception ignored) {
}
final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
if (SECONDARY_STORAGE != null) {
return new File(SECONDARY_STORAGE.split(":")[0]);
}
return null;
}
你不能假設/存儲/ SD卡或/存儲/仿真地圖的任何東西。 OEM可以將這些重命名爲任何他們想要的東西。 –
SD卡是安裝還是隻讀? – DominicEU
@Gabe Sechan我知道這一點。我的應用程序實際上並沒有對SD卡和內置閃存驅動器的位置做任何假設,因爲它會加載'/ storage /'目錄,以便用戶可以選擇他們想要的安裝點。 – zxgear