强制访问外部可移动microSD卡

时间:2017-12-10 01:15:30

标签: java android android-sdcard android-external-storage

我正在使用三星A3,Android 5.0.2。我正在使用this setup来编译应用程序,即Android 4.1 Jelly Bean(API 16)目标。

我确切知道外置可移动microSD卡的路径,它是/mnt/extSdCard/(另请参阅下面的注释#7)。

问题:我注意到了

File myDir = new File("/mnt/extSdCard/test");
myDir.mkdirs();

不起作用:没有创建目录。

此外:

File file = new File("/mnt/extSdCard/books/test.txt");   // the folder "books" already exists on the external microSD card, has been created from computer with USB connection
FileOutputStream fos = new FileOutputStream(file);

产生此错误:

  

java.io.FileNotFoundException:/mnt/extSdCard/books/test.txt:open failed:libcore.io.IoBridge.open中的EACCES(Permission denied)(...

如何强制对外部可移动microSD卡进行读写访问?

备注:

  1. Environment.getExternalStorageDirectory().toString()提供/storage/emulated/0这是我的手机内部存储空间,即不是我想要的。

  2. getExternalFilesDir(null)提供/storage/emulated/0/Android/data/com.blahblah.appname/files/即不是我想要的。请注意,我不能将getExternalFilesDirs与最终s一起使用,因为这在API16中不可用。此外,API16中也没有运行时权限。

  3. 我已经<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />,还有READ_EXTERNAL_STORAGE

  4. 我阅读了很多主题,例如this onethis one,实际上可能有二十个类似的问题,但最后它看起来非常复杂,所有内容都与之相反。那是我的,我正在寻找特定于这种情况的解决方案。

  5. 我不想ACTION_OPEN_DOCUMENTACTION_CREATE_DOCUMENT,实际上我不想要任何GUI解决方案。

  6. 我有一些应用程序(Sync Resilio)可以成功修改/mnt/extSdCard/music/,在那里创建新文件等。

  7. 顺便说一下,ls -la /mnt/extSdCard/给出了

    drwxrwx--x root     sdcard_r          2017-10-15 01:21 Android
    drwxrwx--- root     sdcard_r          2017-10-14 00:59 LOST.DIR
    drwxrwx--- root     sdcard_r          2017-12-05 16:44 books
    drwxrwx--- root     sdcard_r          2017-11-21 22:55 music
    

3 个答案:

答案 0 :(得分:2)

请记住,某些Android设备的SD卡路径不同,有些设备没有可移动的SD卡。

您无需直接设置路径!

File myDir = new File("/mnt/extSdCard/test");
myDir.mkdirs();

您可以先检查您的设备是否安装了可移动SD卡:

public static boolean isSDCardAvailable(Context context) {
    File[] storages = ContextCompat.getExternalFilesDirs(context, null);
    if (storages.length > 1 && storages[0] != null && storages[1] != null)
        return true;
    else
        return false;
}

为什么要获取外部目录&gt; 1,因为大部分Android设备都有外部存储作为主目录,可移动SD卡作为第二个目录:

introducir la descripción de la imagen aquí

但您可以使用一种方法来获取可移动microSD卡的真实路径:

public static String getRemovableSDCardPath(Context context) {
    File[] storages = ContextCompat.getExternalFilesDirs(context, null);
    if (storages.length > 1 && storages[0] != null && storages[1] != null)
        return storages[1].toString();
    else
        return "";
}

然后就这样做:

File myDir = new File(getRemovableSDCardPath(getApplicationContext()),"test");
if(myDir.mkdirs()){
  Log.i(TAG, "Directory was succesfully create!");
}else{
  Log.i(TAG, "Error creating directory!");
}

例如使用方法:

   String pathSDCard = getRemovableSDCardPath(getApplicationContext());

结果是我的移动SD卡的路径(如果我没有可移动的SD卡,我的路径就是“”,所以你可以实现验证以避免创建文件夹):

/storage/extSdCard/Android/data/com.jorgesys.myapplication/files

现在在里面创建一个新文件夹:

    File myDir = new File(getRemovableSDCardPath(getApplicationContext()),"test");
    if(myDir.mkdirs()){
        Log.i(TAG, "Directory was succesfully create!");
    }else{
        Log.i(TAG, "Error creating directory!");
    }

现在我创建了目录/test

enter image description here

答案 1 :(得分:1)

当我在同样的问题上苦苦挣扎时,我会分享一下。 我也从这些资源中获得了帮助,这要归功于他们:

DocumentFile Android DocsFrom Here

我已经测试了 5.1.1 6.0.1 上的代码,而不是我尚未测试过的设备的其余部分,但它应该可以正常工作。

5.0.2 上写入外部设备,您必须征得用户的同意。

使用以下代码,在询问此权限之前,您需要指示用户选择根SD卡,以便您可以访问整个外部存储。

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 25);

现在在onActivityResult中保存API的UriTree返回,因为稍后您将需要它。

 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == 25&&resultCode == RESULT_OK) {
            getContentResolver().takePersistableUriPermission(data.getData(), Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    } 
    super.onActivityResult(requestCode, resultCode, data);
}

获得root UriTree后,您可以从外部存储创建修改删除文件或目录,以便从UriTree获取DocumentFile

要使Document UriTree使用以下代码。

public static DocumentFile getDocumentFile(final File file) {
        String baseFolder = getExtSdCardFolder(file);
        String relativePath = null;

        if (baseFolder == null) {
            return null;
        }

        try {
            String fullPath = file.getCanonicalPath();
            relativePath = fullPath.substring(baseFolder.length() + 1);
        } catch (IOException e) {
            Logger.log(e.getMessage());
            return null;
        }
        Uri treeUri = Common.getInstance().getContentResolver().getPersistedUriPermissions().get(0).getUri();

        if (treeUri == null) {
            return null;
        }

        // start with root of SD card and then parse through document tree.
        DocumentFile document = DocumentFile.fromTreeUri(Common.getInstance(), treeUri);

        String[] parts = relativePath.split("\\/");

        for (String part : parts) {
            DocumentFile nextDocument = document.findFile(part);
            if (nextDocument != null) {
                document = nextDocument;
            }
        }

        return document;
    }


    public static String getExtSdCardFolder(File file) {
        String[] extSdPaths = getExtSdCardPaths();
        try {
            for (int i = 0; i < extSdPaths.length; i++) {
                if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
                    return extSdPaths[i];
                }
            }
        } catch (IOException e) {
            return null;
        }
        return null;
    }


@TargetApi(Build.VERSION_CODES.KITKAT)
public static String[] getExtSdCardPaths() {
    List<String> paths = new ArrayList<>();
    for (File file : Common.getInstance().getExternalFilesDirs("external")) {

        if (file != null && !file.equals(Common.getInstance().getExternalFilesDir("external"))) {
            int index = file.getAbsolutePath().lastIndexOf("/Android/data");
            if (index < 0) {
                Log.w("asd", "Unexpected external file dir: " + file.getAbsolutePath());
            } else {
                String path = file.getAbsolutePath().substring(0, index);
                try {
                    path = new File(path).getCanonicalPath();
                } catch (IOException e) {
                    // Keep non-canonical path.
                }
                paths.add(path);
            }
        }
    }
    return paths.toArray(new String[paths.size()]);
}

上面的代码将使用您可以执行所需操作的任何文件的DocumentFile版本返回。

如果你想看到这个代码在运行中检查我在我的open source project中使用它来修改外部存储上的mp3文件。

希望在遇到疑惑时帮助我知道。

忘了说我刚才问过同样的问题Here就是那个问题。

修改 使用此代码,您可以检查用户是否已授予非

的权限
public static boolean hasPermission() {
    List<UriPermission> uriPermission = Common.getInstance().getContentResolver().getPersistedUriPermissions();
    return uriPermission != null && uriPermission.size() > 0;
}

如果许可被撤销,那么将不会有UriTree,因此您将不得不再次请求许可。

答案 2 :(得分:0)

基于Doomsknight's answermineDave SmithMark Murphy blog posts: 123

  1. 理想情况下,使用Storage Access FrameworkDocumentFileJared Rummler pointed。或者:
  2. 使用your app specific path /storage/extSdCard/Android/data/com.myapp.example/files
  3. 为预先KitKat添加read/write permission to manifest,为此路径添加no permission required later
  4. 尝试使用point2目录和Doomsknight's methods,考虑KitKat and Samsung case
  5. 在KitKat之前过滤并使用getStorageDirectories第2点路径和读/写权限。
  6. ContextCompat.getExternalFilesDirs,因为KitKat记得Samsung returns internal first
  7. 编写我的软件包的Android数据目录适用于主存储和二级存储:

      

    在KitKat中,特定于应用程序的数据目录的完全所有权是   给予应用程序的唯一用户ID。这意味着,向前,没有   应用程序必须具有读取/写入其特定权限的权限   外部存储上的目录...

         

    android.permission.WRITE_EXTERNAL_STORAGE权限现在授予   sdcard_rsdcard_rw ...

    中的成员资格      

    无需任何权限即可使用任何外部存储卷   特定于应用程序的文件。

         

    注意:ls -la /mnt/extSdCard/...个列表中,sdcard_r组具有完整的+ rwx权限,这在实践中显然不正确...因为FUSE守护程序是修改在运行时应用于应用程序的权限。

    三星:案例研究

      

    在Android 4.3中,三星模拟了主外部存储卷   在设备的内部闪存(不是可移动SD卡)上。 SD   卡片内部始终被标记为次要外部   存储介质...

         

    在Android 4.4中,主外部存储卷仍处于开启状态   内部闪光灯。

         

    三星选择加入SD卡插槽,但没有标记   作为主要的外部存储介质。

    <强>更新

    作为explained here并回答您关于使用根路径共享文件的评论:

    您可以在Kikat之前使用基于目标版本的Doomsknight's method 1if / else代码,或building multiple APKs

      

    由于KitKat,第三方应用程序只是无法添加更多文件   他们自己在随机的位置...

    为什么现在?

      

    答案是一个缩写词   CTS

         

    规则说明辅助存储卷不应该是可写的   自4.2以来,应用程序也一直在文档中      

    但是,new tests were added in CTS for 4.4验证辅助存储是否具有正确的存储   大概是非特定应用程序目录中的只读权限   因为新API最终会将这些路径暴露给应用程序   开发人员。一旦CTS包含这些规则,OEM就必须支持   他们要在船上保留装有GMS(Google Play等)的设备。

    如何分享文件?

      

    这是一个有效的问题。如果应用程序需要共享文件该怎么办?   它是在辅助外部存储卷上创建的?谷歌的   答案似乎是那些积极决定去的应用程序   超出主外部存储器写入内容也应该暴露一个   安全的共享方式,使用content provider   或new Storage Access Framework

    正如我在你提出帮助的问题上所解释的那样,我从来没有尝试过这个,但我的观点是,理论上你可以使用方法1之前在KitKat之前使用根路径,以及稍后的这些替代方案。