安卓6.0棉花糖系统。无法写入SD卡。

52

我有一个应用程序,使用外部存储来存储照片。 在其清单中,按要求请求以下权限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

它使用以下内容来检索所需的目录

File sdDir = Environment
            .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd", Locale.US);
String date = dateFormat.format(new Date());
storageDir = new File(sdDir, getResources().getString(
            R.string.storagedir)
            + "-" + date);

// Create directory, error handling
if (!storageDir.exists() && !storageDir.mkdirs()) {
 ... fails here

该应用在Android 5.1到2.3上运行良好,已经在Google Play上发布了一年。

在我测试手机(Android One)升级到6之后,尝试创建所需目录“/sdcard/Pictures/myapp-yy-mm”时现在会返回一个错误。

SD卡被配置为“可移动存储”。我已格式化SD卡。我已更换它。我已重新启动。但都无济于事。

此外,内置的Android截屏功能(通过Power + Lower volume)失败了,“由于存储空间有限,或不被应用程序或您的组织允许”。

有什么想法吗?


1
你的 targetSdkVersion 是23吗?还是早期版本? - ianhanniballake
1
日志中没有什么异常,可能是因为应用程序已经捕获了“错误”。 <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="22" /> - RudyF
你正在请求运行时权限吗? - Muhammad Babar
当尝试创建所需目录“/sdcard/Pictures/”时,返回错误。不,这不是您代码中发生的情况。您正在尝试创建一个目录,例如/sdcard/Pictures/myfolder,但失败了。您甚至没有检查/sdcard/Pictures是否存在。 - greenapps
请展示storageDir.getAbsolutePath()的值。它包含不允许的字符吗?这个“date”格式是什么意思? - greenapps
显示剩余3条评论
5个回答

51

我遇到了相同的问题。Android中有两种类型的权限:

  • 危险权限(访问联系人,写入外部存储等)
  • 正常权限

正常权限由Android自动批准,而危险权限需要由Android用户批准。

以下是在Android 6.0中获取危险权限的策略:

  1. 检查您是否已被授予权限
  2. 如果您的应用程序已被授予权限,请继续正常执行。
  3. 如果您的应用程序尚未获得权限,请请求用户批准
  4. 监听onRequestPermissionsResult中的用户批准

这是我的情况:我需要写入外部存储。

首先,我检查是否拥有权限:

...
private static final int REQUEST_WRITE_STORAGE = 112;
...
boolean hasPermission = (ContextCompat.checkSelfPermission(activity,
            Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
if (!hasPermission) {
    ActivityCompat.requestPermissions(parentActivity,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                REQUEST_WRITE_STORAGE);
}

然后检查用户的批准:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode)
    {
        case REQUEST_WRITE_STORAGE: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
            {
                //reload my activity with permission granted or use the features what required the permission
            } else
            {
                Toast.makeText(parentActivity, "The app was not allowed to write to your storage. Hence, it cannot function properly. Please consider granting it this permission", Toast.LENGTH_LONG).show();
            }
        }
    }

}

你可以在这里阅读有关新权限模型的更多信息:https://developer.android.com/training/permissions/requesting.html

6
好的,我理解如果我的应用程序针对6.0版本进行目标设置,则存在不同的安全协议。但是让我们退一步重新审视问题。我只是让我的Android One手机升级到了6.0(就像我之前从4.4升级到5.0,然后再升级到5.1一样)。 然而,应用程序在写入SD卡时就停止工作了?[请注意,这些应用程序并没有针对API 23进行目标设置] - RudyF
1
你尝试将compileSdkVersion和targetSdkVersion设置为低于23吗?我这样做了,我的应用在Android 6.0上运行良好。 - Dũng Trần Trung
1
我正在升级Eclipse的SDK到API23(Marshmallow),之后计划将新的权限协议纳入我的应用程序中。然而,这并不能解决我的手机问题:在升级到6.0后,许多已安装的应用程序停止工作。现在,手机似乎受制于缓慢的开发人员(像我一样)。坦白地说,这根本没有道理...必须在6.0中有一些“向后兼容调整”。[目前,几乎1%的用户已经升级到6.0...我必须快速行动:)] - RudyF
如何将此操作应用于extSdCard(次要存储)? - Luis A. Florit
请记住,为了接收回调,我们必须设置noHistory=false。如果您没有收到回调,请参考此链接:https://dev59.com/NFwY5IYBdhLWcg3wWmtn#33080682。我浪费了几个小时才找出来。 - Atul
显示剩余2条评论

7
Android 6.0改变了权限的工作方式,这就是您遇到错误的原因。您必须实际请求并检查用户是否授予了权限才能使用。因此,清单文件中的权限仅适用于21以下的API。 请参阅此链接,了解有关如何在api23中请求权限的片段:http://android-developers.blogspot.nl/2015/09/google-play-services-81-and-android-60.html?m=1 代码:
If (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) !=
                PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_RC);
            return;
        }`


` @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == STORAGE_PERMISSION_RC) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //permission granted  start reading
            } else {
                Toast.makeText(this, "No permission to read external storage.", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

1
问题是,该应用程序没有针对API 23进行定位。它只是在6.0上运行。难道不应该有向后兼容性吗?此外,使用SD卡的内置应用程序(例如Camera(2.7.008))也会出现“无可用SD卡”的故障。如果我通过“设置->应用程序”查看“应用程序权限”,它将显示单个权限的新细粒度(例如相机,存储等),所有权限都被正确授予。 - RudyF
另外一件事:当配置为“便携式存储”时,SD卡图标仍然会显示在通知区域。为什么? - RudyF
安装了一款流行的相机应用程序Open Camera(100万次下载)...也无法保存照片。我开始怀疑Marshmallow是否要求SD卡具有某种特定规格(如果是这样,为什么它接受我尝试过的那些卡?)。 - RudyF
我还没有测试过提到的应用程序。也许开发人员还没有实现重新请求权限的方式。我只是在说明为什么以及如何完成它。 - codeFreak
1
这对我不起作用...我的应用程序显示对话框,并接受用户答案(允许)。然而,除了/PathToExtSdCard/Android/data/myapp文件夹外,外部SD卡仍无法写入应用程序。似乎允许或拒绝没有任何区别。有什么建议吗? - Luis A. Florit

6

也许你无法在项目中使用从生成代码中得到的清单类。因此,你可以使用来自Android SDK的清单类"android.Manifest.permission.WRITE_EXTERNAL_STORAGE"。但是,在Android 6.0版本中,存储分类下必须授予2个权限:读取和写入外部存储。请查看我的程序,它会一直请求权限,直到用户选择允许并在授权后执行某些操作。

            if (Build.VERSION.SDK_INT >= 23) {
                if (ContextCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(LoginActivity.this,
                            new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE},
                            1);
                } else {
                    //do something
                }
            } else {
                    //do something
            }

3

没错。所以我终于找到了问题的根源:这是一个失败的OTA升级。

我的怀疑加剧是因为我的Garmin Fenix 2无法通过蓝牙连接,以及搜索“Marshmallow升级问题”。总之,“出厂设置”解决了问题。

令人惊讶的是,重置并没有将手机恢复到原始的Kitkat版本;相反,擦除过程捕捉到了下载的6.0 OTA升级包并进行了安装,结果(我猜)是一个更“干净”的升级。

当然,这意味着手机失去了我安装的所有应用程序。但新安装的应用程序(包括我的),可以在不做任何更改的情况下正常工作(即存在向后兼容性)。太好了!


更新我的应用程序以针对API23并尝试为6实现权限处理时,我感到更加困惑。由于我从6用户那里获得了几百次下载(我当前的目标是api21),我想知道是否应该费心去实现权限控制?如果他们实际上可以很好地使用我的应用程序,那么这是否有意义呢?尝试解决所有23中弃用的内容(包括库代码)真是一项艰巨的任务,我担心我会根据SO上的各种示例实现某些东西,并导致用户出现问题,潜在地引起糟糕的评论。我觉得现在回到<23的版本太晚了。 - Mr Chops
我的项目构建目标是20,目前情况看起来非常顺利,(a) 应用程序在我升级到6.0的Android One手机上运行良好,(b) 几百个6用户实际上没有抱怨。因此,我看不出任何迫切需要修改代码的理由 :) 当我这样做时,我可能会(a) 先调整我最不受欢迎的应用程序,(b) 逐步生产化变化,在几周内完成。祝一切顺利。 - RudyF

3

Android文档中关于Manifest.permission.WRITE_EXTERNAL_STORAGE权限的说明如下:

从API 19级开始,不需要此权限即可在通过getExternalFilesDir(String)和getExternalCacheDir()返回的应用程序特定目录中读写文件。


认为这意味着,除非应用程序正在写入不归属于应用程序的目录,否则您不必编写WRITE_EXTERNAL_STORAGE权限的运行时实现代码。

您可以在清单文件中按权限定义最大SDK版本,例如:

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="19" />

同时,请确保在build.gradle中更改目标SDK版本,而不是在清单文件中更改。gradle设置会覆盖清单文件中的设置。

android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
    minSdkVersion 17
    targetSdkVersion 22
}

1
+1 对我帮助很大,但是它不应该是这样写的吗:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />(而不是19),也就是说,在API级别19之前,如果您使用getExternalFilesDir(String)和getExternalCacheDir(),则仍然需要以“旧方式”获取权限,而在API级别19中不再需要获取权限。 - kalabalik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接