Android 6.0 Marshmallow。无法写入SD卡

时间:2015-10-15 04:10:39

标签: android android-sdcard android-6.0-marshmallow

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

lot_name = i.getStringArrayExtra("lotName");

并使用以下内容检索所需目录

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

该应用在Android 5.1到2.3上运行良好;它已在Google Play上使用了一年多。

在将我的一个测试手机(Android One)升级到6之后,它现在在尝试创建必需目录时返回错误,&#34; / sdcard / Pictures / myapp-yy-mm& #34;

SD卡配置为&#34;便携式存储&#34;。我已经格式化了SD卡。我已经取代了它。我重新启动了。一切都没有用。

此外,内置的Android屏幕截图功能(通过Power + Lower volume)失败&#34;由于存储空间有限,或者应用程序或您的组织不允许这样做。&#34;。< / p>

有什么想法吗?

6 个答案:

答案 0 :(得分:49)

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

  • 危险(访问联系人,写入外部存储空间......)
  • 正常

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

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

  1. 检查您是否已获得许可
  2. 如果您的应用已获得许可,请继续正常运行。
  3. 如果您的应用尚未获得许可,请要求用户批准
  4. 在onRequestPermissionsResult中收听用户批准
  5. 以下是我的情况:我需要写入外部存储空间。

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

    ...
    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

答案 1 :(得分:21)

首先,我将为您提供 Android M中的危险权限列表及更高版本

enter image description here enter image description here

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

我要求用户 WRITE_EXTERNAL_STORAGE 权限。

首先在Android menifest文件中添加权限

第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;
        }
    }
}

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

最佳例子以及多个允许加上所有情景

我添加了评论,因此您可以轻松理解。

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

            }

        }

    }


}

配置更改的特殊情况

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

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

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

我们在onCreate()恢复它。如果我们没有保留所有所需的权限,但isInPermission为true,我们会跳过请求权限,因为我们在 中间已经这样做了。

答案 2 :(得分:7)

Android改变了权限与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();
            }
        }
    }
}

答案 3 :(得分:6)

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

            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
            }

答案 4 :(得分:3)

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

我的怀疑在我的Garmin Fenix 2无法通过蓝牙连接和谷歌搜索“棉花糖升级问题”之后愈演愈烈。无论如何,“工厂重置”修复了这个问题。

令人惊讶的是,重置没有将手机归还原来的Kitkat;相反,擦除过程选择了OTA下载的6.0升级包并随之运行,结果(我猜)在“更干净”的升级中。

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

答案 5 :(得分:3)

XamlRenderingBackgroundTask

  

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

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

您可以在每个权限的清单中定义max sdk版本,如:

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

另外,请确保更改build.graddle中的目标SDK而不是清单,草图设置将始终覆盖清单设置。

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