mkdir () funciona mientras está dentro del almacenamiento flash interno, pero no en la tarjeta SD?

Actualmente estoy construyendo una aplicación de administración de archivos que permite al usuario navegar por el sistema de archivos de su dispositivo. El usuario comienza en el directorio raíz / de su dispositivo, pero puede navegar a cualquier ubicación que desee, como el almacenamiento flash interno o la tarjeta SD.

Uno de los requisitos fundamentales de esta aplicación es permitir al usuario crear nuevas carpetas en cualquier lugar. Una característica como esta sería inmensamente útil para la aplicación. Sin embargo, el método File#mkdir() no funciona en absoluto en el directorio de la tarjeta SD.

Agregué los permisos apropiados al archivo de manifiesto. También escribí una prueba para ver qué directorios (todos los cuales existen en mi dispositivo Lollipop 5.0) permiten la creación de una nueva carpeta. Según mis observaciones, File#mkdir() solo funciona cuando está dentro del directorio de almacenamiento flash interno.

Nota: no confunda Environment#getExternalStorageDirectory() con la ubicación de la tarjeta SD, como se explica en este artículo . También en Lollipop 5.0, creo /storage/emulated/0/ y /storage/sdcard0/ referirme al almacenamiento flash interno mientras /storage/emulated/1/ y /storage/sdcard1/ referirme a la tarjeta SD (que es al menos verdadero para el dispositivo con el que estoy probando).

¿Cómo puedo crear nuevos archivos y carpetas en áreas fuera de la ruta de almacenamiento externo en dispositivos Android no rooteados?


Manifiesto:

 ...   ... 

Prueba:

 ... 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(); } } 

Salida:

 /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 

    Primero, debe tener en cuenta que file.mkdir() y file.mkdirs() devuelven false si el directorio ya existía. Si desea saber si el directorio existe a la vuelta, use (file.mkdir() || file.isDirectory()) o simplemente ignore el valor devuelto y llame a file.isDirectory() (consulte la documentación).

    Dicho esto, su verdadero problema es que necesita permiso para crear el directorio en almacenamiento extraíble en Android 5.0+. Trabajar con tarjetas SD extraíbles en Android es horrendo.

    En Android 4.4 (KitKat), Google restringió el acceso a las tarjetas SD (ver aquí , aquí y aquí ). Vea esta respuesta de StackOverflow que conduce a esta publicación de XDA si necesita crear un directorio en una tarjeta SD extraíble en Android 4.4 (KitKat).

    En Android 5.0 (Lollipop), Google introdujo nuevas API de acceso a la tarjeta SD. Para el uso de la muestra, consulte esta respuesta stackoverflow .

    Básicamente, necesita usar DocumentFile#createDirectory(String displayName) para crear su directorio. Deberá pedirle al usuario que otorgue permisos a su aplicación antes de crear este directorio.


    NOTA: Esto es para almacenamiento extraíble. El uso de File#mkdirs() funcionará en el almacenamiento interno (que a menudo se confunde con el almacenamiento externo en Android) si tiene el permiso android.permission.WRITE_EXTERNAL_STORAGE .


    Voy a publicar un código de ejemplo a continuación:

    Verifica si necesitas pedir permiso:

     File sdcard = ... // the removable SD card List 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; } } } } 

    A continuación (si needPermissions es true ), puede mostrar un cuadro de diálogo para explicarle al usuario que debe seleccionar la “Tarjeta SD” para otorgar permisos a su aplicación para crear archivos / directorios y luego iniciar la siguiente actividad:

     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 } 

    Ahora deberá verificar el resultCode y el resultCode de onActivityResult en el 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); } 

    Eso debería darle un buen comienzo para trabajar con DocumentFile y tarjetas SD extraíbles en Android 5.0+. Puede ser un PITA.


    Además, no hay una API pública para obtener la ruta a una tarjeta SD extraíble (si es que existe alguna). ¡No debe confiar en la encoding rígida "/storage/sdcard1" ! Hay bastantes publicaciones sobre esto en StackOverflow. Muchas de las soluciones usan la variable de entorno SECONDARY_STORAGE . A continuación hay dos métodos que puede usar para encontrar dispositivos de almacenamiento extraíbles:

     public static List getRemovabeStorages(Context context) throws Exception { List 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 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; } 

    path.mkdir() también falla cuando el directorio ya existe. Puede agregar un cheque primero:

     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"); } 

    en Kitkat Google restringió el acceso a la tarjeta SD externa, por lo que no podrá escribir en el almacenamiento externo en Kitkat.

    En Lollipop, Google creó un nuevo FrameWork para escribir datos en el almacenamiento externo, tiene que usar el nuevo DocumentFile

    clase que es retrocompatible.

    Básicamente puede solicitar el permiso al inicio de la aplicación en el directorio raíz de la aplicación y luego puede crear el directorio

    Prueba con esto Funciona bien para mí.

     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); 

    y cambie el valor booleano en la función testPath siguiente manera

     boolean success; if(path.exists()) { // already created success = true; } else { success = path.mkdir(); } 

    Si la carpeta ya existe, el método path.mkdir() devuelve falso.

    y hecho.!!!

    referencia de esta pregunta.