mkdir() 在内部闪存中工作,但不是 SD 卡?
mkdir() works while inside internal flash storage, but not SD card?
我目前正在构建一个文件管理应用程序,允许用户浏览其设备的文件系统。用户从他们设备的根目录 /
开始,但可以浏览到他们想要的任何位置,例如内部闪存或 SD 卡。
此应用程序的关键要求之一是允许用户在任何地方创建新文件夹。像这样的功能将对应用程序非常有用。但是File#mkdir()
方法在SD卡目录下根本不起作用
我向清单文件添加了适当的权限。我还编写了一个测试来查看哪些目录(所有这些都存在于我的 Lollipop 5.0 设备上)允许创建新文件夹。根据我的观察,File#mkdir()
仅在内部闪存存储目录中有效。
注意:请不要混淆Environment#getExternalStorageDirectory()
with the SD card location, as explained by this article。同样在 Lollipop 5.0 上,我相信 /storage/emulated/0/
和 /storage/sdcard0/
指的是内部闪存,而 /storage/emulated/1/
和 /storage/sdcard1/
指的是 SD 卡(至少对于设备来说是这样我正在测试。
如何在非根 Android 设备上的外部存储路径之外的区域创建新文件和文件夹?
清单:
...
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
测试:
...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final String NEW_FOLDER_NAME = "TestFolder";
testPath(new File(Environment.getExternalStorageDirectory(), NEW_FOLDER_NAME));
testPath(new File("/storage/emulated/0/", NEW_FOLDER_NAME));
testPath(new File("/storage/emulated/1/", NEW_FOLDER_NAME));
testPath(new File("/storage/sdcard0/Download/", NEW_FOLDER_NAME));
testPath(new File("/storage/sdcard1/Pictures/", NEW_FOLDER_NAME));
}
private void testPath(File path) {
String TAG = "Debug.MainActivity.java";
String FOLDER_CREATION_SUCCESS = " mkdir() success: ";
boolean success = path.mkdir();
Log.d(TAG, path.getAbsolutePath() + FOLDER_CREATION_SUCCESS + success);
path.delete();
}
}
输出:
/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/1/TestFolder mkdir() success: false
/storage/sdcard0/Download/TestFolder mkdir() success: true
/storage/sdcard1/Pictures/TestFolder mkdir() success: false
当目录已经存在时,path.mkdir()
也会失败。
您可以先加个支票:
if (!path.exists()) {
boolean success = path.mkdir();
Log.d(TAG, path.getAbsolutePath() + FOLDER_CREATION_SUCCESS + success);
path.delete();
} else {
Log.d(TAG, path.getAbsolutePath() + "already exists");
}
首先,如果目录已经存在,您应该注意 file.mkdir()
和 file.mkdirs()
returns false
。如果您想知道 return 上是否存在该目录,请使用 (file.mkdir() || file.isDirectory())
或忽略 return 值并调用 file.isDirectory()
(请参阅文档)。
也就是说,您真正的问题是您需要获得在 Android 5.0+ 上的可移动存储上创建目录的权限。在 Android 上使用可移动 SD 卡非常可怕。
在 Android 4.4 (KitKat) 上,Google 限制对 SD 卡的访问(如果您需要在 [=68= 上的可移动 SD 卡上创建目录,请参阅 here, here, and here). See this Whosebug answer which leads to this XDA post ] 4.4(奇巧)。
在 Android 5.0 (Lollipop) 上,Google 引入了新的 SD 卡访问 APIs。示例用法请参考这个Whosebug answer.
基本上,您需要使用DocumentFile#createDirectory(String displayName)
来创建您的目录。在创建此目录之前,您需要请求用户授予您的应用程序权限。
注意: 这适用于可移动存储。如果您拥有 android.permission.WRITE_EXTERNAL_STORAGE
.
权限,则使用 File#mkdirs()
将适用于内部存储(通常与 Android 上的外部存储混淆)
我将 post 下面的一些示例代码:
检查是否需要请求许可:
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
),你可以显示一个对话框来向用户解释他们需要 select "SD Card" 来给你的应用创建 files/directories 的权限,然后启动以下 activity:
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
}
您现在需要检查 onActivityResult
中的 resultCode
和 requestCode
:
@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);
}
这应该能让您在 Android 5.0+ 上开始使用 DocumentFile
和可移动 SD 卡。可以是PITA。
此外,没有 public API 获取可移动 SD 卡的路径(如果存在的话)。你不应该依赖硬编码 "/storage/sdcard1"
! Whosebug 上有很多关于它的 post。许多解决方案都使用环境变量 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;
}
试试这个。它对我来说很好用。
final String NEW_FOLDER_NAME = "TestFolder";
String extStore = System.getenv("EXTERNAL_STORAGE");
File f_exts = new File(extStore, NEW_FOLDER_NAME);
String secStore = System.getenv("SECONDARY_STORAGE");
File f_secs = new File(secStore, NEW_FOLDER_NAME);
testPath(f_exts);
textPath(f_secs);
并在testPath
函数中改变布尔值如下
boolean success;
if(path.exists()) {
// already created
success = true;
} else {
success = path.mkdir();
}
如果文件夹已经存在,path.mkdir()
方法 return false。
完成了。!!!
参考自 this 问题。
在 Kitkat 上 google 限制了对外部 SD 卡的访问,因此您将无法在 Kitkat 上写入外部存储。
在 Lollipop google 中创建了一个新的框架来将数据写入外部存储你必须使用新的 DocumentFile
class 向后兼容。
基本上你可以在应用程序启动时请求权限到应用程序的根目录,然后你可以创建目录
我目前正在构建一个文件管理应用程序,允许用户浏览其设备的文件系统。用户从他们设备的根目录 /
开始,但可以浏览到他们想要的任何位置,例如内部闪存或 SD 卡。
此应用程序的关键要求之一是允许用户在任何地方创建新文件夹。像这样的功能将对应用程序非常有用。但是File#mkdir()
方法在SD卡目录下根本不起作用
我向清单文件添加了适当的权限。我还编写了一个测试来查看哪些目录(所有这些都存在于我的 Lollipop 5.0 设备上)允许创建新文件夹。根据我的观察,File#mkdir()
仅在内部闪存存储目录中有效。
注意:请不要混淆Environment#getExternalStorageDirectory()
with the SD card location, as explained by this article。同样在 Lollipop 5.0 上,我相信 /storage/emulated/0/
和 /storage/sdcard0/
指的是内部闪存,而 /storage/emulated/1/
和 /storage/sdcard1/
指的是 SD 卡(至少对于设备来说是这样我正在测试。
如何在非根 Android 设备上的外部存储路径之外的区域创建新文件和文件夹?
清单:
...
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
测试:
...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final String NEW_FOLDER_NAME = "TestFolder";
testPath(new File(Environment.getExternalStorageDirectory(), NEW_FOLDER_NAME));
testPath(new File("/storage/emulated/0/", NEW_FOLDER_NAME));
testPath(new File("/storage/emulated/1/", NEW_FOLDER_NAME));
testPath(new File("/storage/sdcard0/Download/", NEW_FOLDER_NAME));
testPath(new File("/storage/sdcard1/Pictures/", NEW_FOLDER_NAME));
}
private void testPath(File path) {
String TAG = "Debug.MainActivity.java";
String FOLDER_CREATION_SUCCESS = " mkdir() success: ";
boolean success = path.mkdir();
Log.d(TAG, path.getAbsolutePath() + FOLDER_CREATION_SUCCESS + success);
path.delete();
}
}
输出:
/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/1/TestFolder mkdir() success: false
/storage/sdcard0/Download/TestFolder mkdir() success: true
/storage/sdcard1/Pictures/TestFolder mkdir() success: false
path.mkdir()
也会失败。
您可以先加个支票:
if (!path.exists()) {
boolean success = path.mkdir();
Log.d(TAG, path.getAbsolutePath() + FOLDER_CREATION_SUCCESS + success);
path.delete();
} else {
Log.d(TAG, path.getAbsolutePath() + "already exists");
}
首先,如果目录已经存在,您应该注意 file.mkdir()
和 file.mkdirs()
returns false
。如果您想知道 return 上是否存在该目录,请使用 (file.mkdir() || file.isDirectory())
或忽略 return 值并调用 file.isDirectory()
(请参阅文档)。
也就是说,您真正的问题是您需要获得在 Android 5.0+ 上的可移动存储上创建目录的权限。在 Android 上使用可移动 SD 卡非常可怕。
在 Android 4.4 (KitKat) 上,Google 限制对 SD 卡的访问(如果您需要在 [=68= 上的可移动 SD 卡上创建目录,请参阅 here, here, and here). See this Whosebug answer which leads to this XDA post ] 4.4(奇巧)。
在 Android 5.0 (Lollipop) 上,Google 引入了新的 SD 卡访问 APIs。示例用法请参考这个Whosebug answer.
基本上,您需要使用DocumentFile#createDirectory(String displayName)
来创建您的目录。在创建此目录之前,您需要请求用户授予您的应用程序权限。
注意: 这适用于可移动存储。如果您拥有 android.permission.WRITE_EXTERNAL_STORAGE
.
File#mkdirs()
将适用于内部存储(通常与 Android 上的外部存储混淆)
我将 post 下面的一些示例代码:
检查是否需要请求许可:
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
),你可以显示一个对话框来向用户解释他们需要 select "SD Card" 来给你的应用创建 files/directories 的权限,然后启动以下 activity:
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
}
您现在需要检查 onActivityResult
中的 resultCode
和 requestCode
:
@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);
}
这应该能让您在 Android 5.0+ 上开始使用 DocumentFile
和可移动 SD 卡。可以是PITA。
此外,没有 public API 获取可移动 SD 卡的路径(如果存在的话)。你不应该依赖硬编码 "/storage/sdcard1"
! Whosebug 上有很多关于它的 post。许多解决方案都使用环境变量 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;
}
试试这个。它对我来说很好用。
final String NEW_FOLDER_NAME = "TestFolder";
String extStore = System.getenv("EXTERNAL_STORAGE");
File f_exts = new File(extStore, NEW_FOLDER_NAME);
String secStore = System.getenv("SECONDARY_STORAGE");
File f_secs = new File(secStore, NEW_FOLDER_NAME);
testPath(f_exts);
textPath(f_secs);
并在testPath
函数中改变布尔值如下
boolean success;
if(path.exists()) {
// already created
success = true;
} else {
success = path.mkdir();
}
如果文件夹已经存在,path.mkdir()
方法 return false。
完成了。!!!
参考自 this 问题。
在 Kitkat 上 google 限制了对外部 SD 卡的访问,因此您将无法在 Kitkat 上写入外部存储。
在 Lollipop google 中创建了一个新的框架来将数据写入外部存储你必须使用新的 DocumentFile
class 向后兼容。
基本上你可以在应用程序启动时请求权限到应用程序的根目录,然后你可以创建目录