Android mkdir()在内部闪存中工作,但在SD卡中不工作?

Android mkdir()在内部闪存中工作,但在SD卡中不工作?,android,file-permissions,Android,File Permissions,我目前正在构建一个文件管理应用程序,允许用户浏览其设备的文件系统。用户从设备的根目录/开始,但可以浏览到他们想要的任何位置,如内部闪存或SD卡 此应用程序的关键要求之一是允许用户在任何地方创建新文件夹。这样的功能对应用程序非常有用。但是,该方法在SD卡目录中根本不起作用 我向清单文件添加了相应的权限。我还编写了一个测试,查看哪些目录(所有目录都存在于我的Lollipop5.0设备上)允许创建新文件夹。根据我的观察,只有在内部闪存存储目录中才能工作 注意:请不要与SD卡位置混淆,如所述。同样在棒棒

我目前正在构建一个文件管理应用程序,允许用户浏览其设备的文件系统。用户从设备的根目录
/
开始,但可以浏览到他们想要的任何位置,如内部闪存或SD卡

此应用程序的关键要求之一是允许用户在任何地方创建新文件夹。这样的功能对应用程序非常有用。但是,该方法在SD卡目录中根本不起作用

我向清单文件添加了相应的权限。我还编写了一个测试,查看哪些目录(所有目录都存在于我的Lollipop5.0设备上)允许创建新文件夹。根据我的观察,只有在内部闪存存储目录中才能工作

注意:请不要与SD卡位置混淆,如所述。同样在棒棒糖5.0上,我相信
/storage/emulated/0/
/storage/sdcard0/
指的是内部闪存,而
/storage/emulated/1/
/storage/sdcard1/
指的是SD卡(至少对于我正在测试的设备是这样)

如何在非根设备上的外部存储路径之外的区域创建新文件和文件夹


清单:

...
<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
输出:

...
<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()
返回
false
。如果您想知道返回时目录是否存在,请使用
(file.mkdir()| | file.isDirectory())
,或者忽略返回值并调用
file.isDirectory()
(请参阅文档)

也就是说,您真正的问题是,您需要在Android 5.0+上的可移动存储上创建目录的权限。在Android上使用可移动SD卡是可怕的

在Android 4.4(KitKat)上,谷歌限制了SD卡的访问(请参阅和)。如果您需要在安卓4.4(KitKat)上的可移动SD卡上创建目录,请参见这一点,这将导致出现这种情况

在安卓5.0(棒棒糖)上,谷歌推出了新的SD卡访问API。有关示例用法,请参阅此

基本上,您需要使用来创建目录。在创建此目录之前,您需要请求用户向您的应用授予权限


注意:这是用于可移动存储的。如果您具有Android.permission.WRITE_external_storage权限,则使用
File 35; mkdirs()
可以在内部存储(通常与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
检查您是否需要请求许可:

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;
      }
    }
  }
}
您现在需要在以下位置检查
resultCode
requestCode


这将为您在安卓5.0+上使用
DocumentFile
和可移动SD卡提供一个良好的开端。它可以是一个皮塔


此外,没有公共API来获取可移动SD卡的路径(如果存在的话)。您不应该依赖硬编码
“/storage/sdcard1”
!在StackOverflow上有很多关于它的帖子。许多解决方案使用环境变量
辅助存储
。以下是可用于查找可移动存储设备的两种方法:

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;
}
公共静态列表GetRemovaBeStores(上下文)引发异常{
列表存储=新的ArrayList();
方法getService=Class.forName(“android.os.ServiceManager”)
.getDeclaredMethod(“getService”,String.class);
如果(!getService.isAccessible())getService.setAccessible(true);
IBinder服务=(IBinder)getService.invoke(null,“mount”);
方法asInterface=Class.forName(“android.os.storage.IMountService$Stub”)
.getDeclaredMethod(“接口”,IBinder.class);
如果(!asInterface.isAccessible())asInterface.setAccessible(true);
对象mountService=asInterface.invoke(null,service);
对象[]存储卷;
if(Build.VERSION.SDK\u INT>=Build.VERSION\u code.M){
字符串packageName=context.getPackageName();
int uid=context.getPackageManager().getPackageInfo(packageName,0).applicationInfo.uid;
方法getVolumeList=mountService.getClass().getDeclaredMethod(
“getVolumeList”、int.class、String.class、int.class);
如果(!getVolumeList.isAccessible())getVolumeList.setAccessible(true);
storageVolumes=(对象[])getVolumeList.invoke(mountService,uid,packageName,0);
}否则{
方法getVolumeList=mountService.getClass().getDeclaredMethod(“getVolumeList”);
如果(!getVolumeList.isAccessible())getVolumeList.setAccessible(true);
storageVolumes=(对象[])getVolumeList.invoke(装载服务,(对象[])null);
}
用于(对象存储卷:存储卷){
Class cls=storageVolume.getClass();
方法isRemovable=cls.getDeclaredMethod(“isRemovable”);
如果(!isRemovable.isAccessible())isRemovable.setAccessible(true);
if((布尔)isRemovable.invoke(storageVolume,(Object[])null)){
方法getState=cls.getDeclaredMethod(“getState”);
如果(!getState.isAccessible())getState.setAccessible(true);
String state=(String)getState.invoke(storageVolume,(Object[])null);
if(state.equals(“mounted”)){
方法getPath=cls.getDeclaredMethod(“getPath”);
如果(!getPath.isAccessible())getPath.setAccessible(true);
字符串路径=(字符串)getPath.invoke(storageVolume,(Object[])null);
添加(新文件(路径));
}
}
}
退货仓库;
}
公共静态文件getRemovabeStorageDir(上下文){
试一试{
列表存储=GetRemovaBeStores(上下文);
如果(!storages.isEmpty()){
返回存储。获取(0);
}
}捕获