强制访问外部可移动microSD卡

问题描述 投票:12回答:3

我正在使用三星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(权限被拒绝)(...

如何强制读取+写入外部可移动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
java android android-sdcard android-external-storage
3个回答
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;
}

为什么外部目录> 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
投票

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

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,因此您将不得不再次请求许可。


0
投票

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

  1. 理想情况下,使用Storage Access FrameworkDocumentFile作为Jared Rummler pointed。要么:
  2. 使用your app specific path/storage/extSdCard/Android/data/com.myapp.example/files
  3. 为此路径添加read/write permission to manifest用于pre-KitKat,no permission required later
  4. 尝试使用point2目录和Doomsknight's methods考虑KitKat and Samsung case
  5. 在KitKat之前过滤并使用getStorageDirectories point 2路径和读/写权限。
  6. 自KitKat记住ContextCompat.getExternalFilesDirs以来Samsung returns internal first

Write My Package的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中,内部闪存上的主外部存储卷仍为STILL。

三星选择包括一个SD卡插槽,但没有将其标记为主要的外部存储介质。


更新:

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

你可以在Kitkat之前使用基于目标版本的qazxsw poi和qazxsw poi / qazxsw poi代码,或者Doomsknight's method 1

从KitKat开始,第三方应用程序无法在随机位置添加更多自己的文件...

为什么现在?

答案,首字母缩略词,是if

自4.2以来,应用程序不应写入辅助存储卷的规则也在文档中

但是,else验证辅助存储是否在非特定于应用程序的目录中具有正确的只读权限,可能是因为新的API最终将这些路径暴露给应用程序开发人员。一旦CTS包含这些规则,OEM就必须支持他们在船上使用GMS(Google Play等)运送设备。

分享文件怎么样?

这是一个有效的问题。如果应用程序需要共享它在辅助外部存储卷上创建的文件,该怎么办?谷歌的答案似乎是,那些积极决定超越主要外部存储以写入内容的应用程序也应该使用building multiple APKsCTS公开一种安全的共享方式。

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

© www.soinside.com 2019 - 2024. All rights reserved.