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

Posted

技术标签:

【中文标题】安卓 6.0 棉花糖。无法写入 SD 卡【英文标题】:Android 6.0 Marshmallow. Cannot write to SD Card 【发布时间】:2016-01-13 09:25:53 【问题描述】:

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

<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 屏幕截图功能(通过电源+降低音量)失败。

有什么想法吗?

【问题讨论】:

你能发布你的 Logcat 吗? 你的targetSdkVersion 23 吗?还是更早的版本? logcat 中没有异常,可能是因为“错误”被应用程序捕获了。 您是否要求运行时权限! '..在尝试创建必需目录“/sdcard/Pictures/”时返回错误。不,这不是您的代码中发生的情况。您正在尝试创建一个失败的目录,例如 /sdcard/Pictures/myfolder。你甚至没有检查 /sdcard/Pictures 是否存在。 【参考方案1】:

首先我会给你Android M中的危险权限列表及更高版本

然后举例说明如何在Android M更高版本版本中请求权限。

我请求用户 WRITE_EXTERNAL_STORAGE 许可。

首先在你的 android 清单文件中添加权限

第 1 步声明请求代码

 private static String TAG = "PermissionDemo";
 private static final int REQUEST_WRITE_STORAGE = 112; 

第 2 步当您想请求用户许可时添加此代码

 //ask for the permission in android M
    int permission = ContextCompat.checkSelfPermission(this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (permission != PackageManager.PERMISSION_GRANTED) 
        Log.i(TAG, "Permission to record denied");

        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) 
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Permission to access the SD-CARD is required for this app to Download PDF.")
                    .setTitle("Permission required");

            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() 

                public void onClick(DialogInterface dialog, int id) 
                    Log.i(TAG, "Clicked");
                    makeRequest();
                
            );

            AlertDialog dialog = builder.create();
            dialog.show();

         else 
            makeRequest();
        
    

    protected void makeRequest() 
        ActivityCompat.requestPermissions(this,
                new String[]Manifest.permission.WRITE_EXTERNAL_STORAGE,
                REQUEST_WRITE_STORAGE);
    

第 3 步为请求添加覆盖方法

 @Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) 
    switch (requestCode) 
        case REQUEST_WRITE_STORAGE: 

            if (grantResults.length == 0
                    || grantResults[0] !=
                    PackageManager.PERMISSION_GRANTED) 

                Log.i(TAG, "Permission has been denied by user");

             else 

                Log.i(TAG, "Permission has been granted by user");

            
            return;
        
    

注意:不要忘记在清单文件中添加权限

下面的最佳示例,包含多个权限以及涵盖所有场景

我添加了 cmets,以便您可以轻松理解。

import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.production.hometech.busycoder.R;

import java.util.ArrayList;

public class PermissionInActivity extends AppCompatActivity implements View.OnClickListener 

    private static final int REQUEST_PERMISSION_SETTING = 99;
    private Button bt_camera;
    private static final String[] PARAMS_TAKE_PHOTO = 
            Manifest.permission.CAMERA,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    ;
    private static final int RESULT_PARAMS_TAKE_PHOTO = 11;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permission_in);

        bt_camera = (Button) findViewById(R.id.bt_camera);

        bt_camera.setOnClickListener(this);

    

    @Override
    public void onClick(View view) 

        switch (view.getId()) 

            case R.id.bt_camera:

                takePhoto();

                break;

        
    


    /**
     * shouldShowRequestPermissionRationale() = This will return true if the user had previously declined to grant you permission
     * NOTE :  that ActivityCompat also has a backwards-compatible implementation of
     * shouldShowRequestPermissionRationale(), so you can avoid your own API level
     * checks.
     * <p>
     * shouldShowRequestPermissionRationale() =  returns false if the user declined the permission and checked the checkbox to ask you to stop pestering the
     * user.
     * <p>
     * requestPermissions() = request for the permisssiion
     */
    private void takePhoto() 

        if (canTakePhoto()) 

            Toast.makeText(this, "You can take PHOTO", Toast.LENGTH_SHORT).show();

         else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) 

            Toast.makeText(this, "You should give permission", Toast.LENGTH_SHORT).show();
            ActivityCompat.requestPermissions(this, netPermisssion(PARAMS_TAKE_PHOTO), RESULT_PARAMS_TAKE_PHOTO);

         else 
            ActivityCompat.requestPermissions(this, netPermisssion(PARAMS_TAKE_PHOTO), RESULT_PARAMS_TAKE_PHOTO);
        

    

    //  This method return  permission denied String[] so we can request again
    private String[] netPermisssion(String[] wantedPermissions) 
        ArrayList<String> result = new ArrayList<>();

        for (String permission : wantedPermissions) 
            if (!hasPermission(permission)) 
                result.add(permission);
            
        

        return (result.toArray(new String[result.size()]));

    

    private boolean canTakePhoto() 
        return (hasPermission(Manifest.permission.CAMERA) && hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
    

    /**
     * checkSelfPermission() = you can check if you have been granted a runtime permission or not
     * ex = ContextCompat.checkSelfPermission(this,permissionString)== PackageManager.PERMISSION_GRANTED
     * <p>
     * ContextCompat offers a backwards-compatible implementation of checkSelfPermission(), ActivityCompat offers a backwards-compatible
     * implementation of requestPermissions() that you can use.
     *
     * @param permissionString
     * @return
     */
    private boolean hasPermission(String permissionString) 
        return (ContextCompat.checkSelfPermission(this, permissionString) == PackageManager.PERMISSION_GRANTED);
    

    /**
     * requestPermissions() action goes to onRequestPermissionsResult() whether user can GARNT or DENIED those permisssions
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == RESULT_PARAMS_TAKE_PHOTO) 

            if (canTakePhoto()) 

                Toast.makeText(this, "You can take picture", Toast.LENGTH_SHORT).show();

             else if (!(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE))) 


                final AlertDialog.Builder settingDialog = new AlertDialog.Builder(PermissionInActivity.this);
                settingDialog.setTitle("Permissioin");
                settingDialog.setMessage("Now you need to enable permisssion from the setting because without permission this app won't run properly \n\n  goto -> setting -> appInfo");
                settingDialog.setCancelable(false);

                settingDialog.setPositiveButton("Setting", new DialogInterface.OnClickListener() 
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) 

                        dialogInterface.cancel();

                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", getPackageName(), null);
                        intent.setData(uri);
                        startActivityForResult(intent, REQUEST_PERMISSION_SETTING);
                        Toast.makeText(getBaseContext(), "Go to Permissions to Grant all permission ENABLE", Toast.LENGTH_LONG).show();

                    
                );
                settingDialog.show();

                Toast.makeText(this, "You need to grant permission from setting", Toast.LENGTH_SHORT).show();

            

        

    

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) 
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_PERMISSION_SETTING) 

            if (canTakePhoto()) 

                Toast.makeText(this, "You can take PHOTO", Toast.LENGTH_SHORT).show();

            

        

    



配置更改的特殊情况

当我们的权限对话框在前台时,用户可能会旋转设备或以其他方式触发配置更改。由于我们的 Activity 在该对话框后面仍然可见,因此我们会被销毁并重新创建……但我们不想再次重新引发权限对话框。

这就是为什么我们有一个名为 isInPermission 的布尔值来跟踪是否 我们正在请求权限。我们坚持这个价值 onSaveInstanceState():

@Override
protected void onSaveInstanceState(Bundle outState) 
  super.onSaveInstanceState(outState);
  outState.putBoolean(STATE_IN_PERMISSION, isInPermission);

我们在onCreate() 中恢复它。如果我们不拥有所有所需的权限,但 isInPermission 为真,我们跳过请求权限,因为我们在 已经在这样做了。

【讨论】:

非活动状态下如何操作? 这可能对***.com/questions/38480671/…有帮助 这让@Arpit 很头疼。我已将课程更改为活动并删除了 setcontentview,因为我需要在同一个课程中执行异步任务。感谢搜索 @Prabs 惠康 @Arpit Patel 当用户旋转设备时,如何防止应用在对话框中崩溃?我设法通过在我的 Activity 的 onPause() 中将其关闭来防止我的 AlertDialog 崩溃,但是如果您现在旋转设备,如果显示 Google 的权限对话框,则应用程序仍然崩溃。【参考方案2】:

也许您不能在项目中使用生成代码中的清单类。因此,您可以使用来自 android sdk“android.Manifest.permission.WRITE_EXTERNAL_STORAGE”的清单类。但在 Marsmallow 版本中,必须授予 2 个权限,即存储类别中的 WRITE 和 READ EXTERNAL STORAGE。查看我的程序,我的程序将请求权限,直到用户选择是,并在授予权限后执行某些操作。

            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】:

Android Documentation on Manifest.permission.Manifest.permission.WRITE_EXTERNAL_STORAGE states:

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


认为这意味着您不必为 WRITE_EXTERNAL_STORAGE 权限的运行时实现编写代码,除非应用正在写入非特定目录到您的应用。

您可以根据权限在清单中定义最大 sdk 版本,例如:

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

还要确保在 build.graddle 而不是清单中更改目标 SDK,gradle 设置将始终覆盖清单设置。

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

【讨论】:

+1 帮了我很多,但它不应该读作:` `(而不是19),也就是说,如果您使用 getExternalFilesDir(String) 和 getExternalCacheDir(),则在 API 级别 19 之前,您将获得“旧方式”权限。【参考方案4】:

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

危险(访问联系人、写入外部存储...) 正常

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

这是在 Android 6.0 中获取危险权限的策略

    检查您是否获得了权限 如果您的应用已获得权限,请继续并正常执行。 如果您的应用还没有权限,请请求用户批准 在 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.0.0 版本,则存在不同的安全协议。但是,让我们退后一步,再看看这个问题。我所做的只是让我的 Android One 手机升级到 6.0(就像我之前从 4.4 升级到 5.0,然后是 5.1 一样)。而且,繁荣,写入 SD 的应用程序停止工作? [请注意,这些应用程序不针对 API 23] 您是否尝试过将 compileSdkVersion 和 targetSdkVersion 设置为低于 23?我已经做到了,我的应用程序在 android 6.0 上运行良好 我正在将 Eclipse 的 sdk 升级到 API23 (Marshmallow),之后我计划将新的权限协议合并到我的应用程序中。但是,这并不能解决我手机的问题:升级到 6.0 后,一些已安装的应用程序停止运行。手机现在似乎受到迟到的开发人员(比如我)的摆布。坦率地说,这没有任何意义...... 6.0 中只需要进行一些“向后兼容性调整”。 [就目前而言,我的用户中有近 1% 已经升级到 6.0 ......我必须快速行动 :) ] 又如何对 extSdCard(二级存储)做到这一点? 请记住我们必须有 noHistory=false 才能接收回调。如果您没有收到回拨,也请参考this。我浪费了几个小时来弄清楚。【参考方案5】:

Android 更改了权限在 Android 6.0 上的工作方式,这就是您出现错误的原因。您必须实际请求并检查用户是否授予使用权限。因此清单文件中的权限仅适用于 21 以下的 api。 检查此链接以获取有关如何在 api23 http://android-developers.blogspot.nl/2015/09/google-play-services-81-and-android-60.html?m=1 中请求权限的 sn-p

代码:-

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

【讨论】:

问题是,该应用程序不是针对 api 23。它只是在 6.0 上运行。不应该向后兼容吗?此外,使用 SD 卡的内置应用程序,如相机 (2.7.008),也因“没有可用的 SD 卡”而失败。如果我通过“设置->应用程序”查看“应用程序权限”,它会显示单个权限(例如相机、存储等)的新粒度,所有权限均正确授予。 另一件事:当配置为“便携式存储”时,sd 卡图标在通知区域中仍然可见。为什么? 安装了一个流行的相机应用程序,打开相机(100 万次下载)......它也无法保存照片。我开始怀疑 Marshmallow 是否需要 SD 卡中的特定规范(如果是,为什么它接受我尝试过的那些?)。 好吧,我还没有测试过上面提到的应用程序。可能开发者没有实现请求权限的重组方式。我只是说明为什么以及如何完成。 这对我不起作用....我的应用程序显示对话框,并接受用户回答(允许)。但是,除了/PathToExtSdCard/Android/data/myapp中的文件夹外,外置SD卡仍然无法写入应用程序。似乎允许或拒绝没有区别。有什么建议吗?【参考方案6】:

没错。所以我终于找到了问题的根源:这是一次拙劣的就地 OTA 升级。

在我的 Garmin Fenix 2 无法通过蓝牙连接并在谷歌上搜索“棉花糖升级问题”之后,我的怀疑更加强烈。无论如何,“恢复出厂设置”解决了这个问题。

令人惊讶的是,重置并没有将手机恢复到原来的 Kitkat;相反,擦除过程选择了 OTA 下载的 6.0 升级包并运行它,导致(我猜)“更干净”的升级。

当然,这意味着手机丢失了我安装的所有应用程序。但是,新安装的应用程序,包括我的应用程序,无需任何更改即可工作(即具有向后兼容性)。呼!

【讨论】:

因此,在将我的应用程序更新为目标 API23 并尝试为 6 实现权限处理的过程中,我更加困惑。由于我已经从 6 个用户那里获得了几百次下载(我目前的目标是 api21),我想知道我是否应该打扰,如果他们真的在使用我的应用程序就好了?这是一项真正的任务,试图解决 23 中的所有弃用问题,包括库代码,我担心我会从 SO 的各种示例中实现一些黑客攻击并导致用户问题,可能会导致差评。但是,我觉得我现在回到 我的项目的构建目标是 20,并且事情似乎进展顺利,因为 (a) 该应用程序在我的 Android One 手机上运行良好,目前升级到 6.0 和 (b) 几百6 位用户实际上没有投诉。因此,我看不出有任何令人信服的理由来匆忙修改代码周。一切顺利。

以上是关于安卓 6.0 棉花糖。无法写入 SD 卡的主要内容,如果未能解决你的问题,请参考以下文章

三星FOTA来了,棉花糖还远吗?

1.Android6.0运行时权限简介_2.Android6.0权限适配之WRITE_EXTERNAL_STORAGE(SD卡写入)3_.Android 6.0 运行时权限理解

Android 无法对 sd 卡进行写入和删除操作

安卓手机外置sd卡的权限怎么打开?

Android 6.0 系统棉花糖新的特性和功能

无法使用 STM32F407 上的 Chan FatFs 库通过 SPI 写入 SD 卡文件