web-dev-qa-db-fra.com

Android 6.0 Guimauve. Impossible d'écrire sur la carte SD

J'ai une application qui utilise un stockage externe pour stocker des photographies. Au besoin, dans son manifeste, les autorisations suivantes sont demandées

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

et il utilise ce qui suit pour récupérer le répertoire requis

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

L'application fonctionne correctement sur Android 5.1 à 2.3; Il est sur Google Play depuis plus d'un an.

Suite à la mise à niveau de l'un de mes téléphones testeurs (Android One) vers la version 6, une erreur est maintenant renvoyée lors de la tentative de création du répertoire requis, "/ sdcard/Pictures/myapp-yy-mm".

La carte SD est configurée en tant que "stockage portable". J'ai formaté la carte SD. Je l'ai remplacé. J'ai redémarré Tout en rien.

De plus, la fonctionnalité de capture d'écran intégrée Android (via Power + Volume inférieur) échoue "en raison d'un espace de stockage limité ou non autorisé par l'application ou votre organisation".

Des idées?

51
RudyF

J'ai rencontré le même problème. Il existe deux types d'autorisations dans Android:

  • Dangereux (accès aux contacts, écriture sur un stockage externe ...)
  • Ordinaire

Les autorisations normales sont automatiquement approuvées par Android, tandis que les autorisations dangereuses doivent être approuvées par les utilisateurs de Android.

Voici la stratégie pour obtenir des autorisations dangereuses dans Android 6.0

  1. Vérifiez si vous avez l'autorisation accordée
  2. Si l'autorisation est déjà accordée à votre application, continuez normalement.
  3. Si votre application ne dispose pas encore de l'autorisation, demandez à l'utilisateur d'approuver
  4. Écouter l'approbation de l'utilisateur dans onRequestPermissionsResult

Voici mon cas: j'ai besoin d'écrire sur un stockage externe.

Tout d'abord, je vérifie si j'ai l'autorisation:

...
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);
}

Puis vérifiez l'approbation de l'utilisateur:

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

}

Vous pouvez en savoir plus sur le nouveau modèle de permission ici: https://developer.Android.com/training/permissions/requesting.html

49
codingpuss

Tout d'abord, je vous donnerai la liste des autorisations dangereuses dans Android M et la version ultérieure

enter image description hereenter image description here

Ensuite, vous donner un exemple de la façon de demander une autorisation dans Android M et plus tard version.

Je demande à l'utilisateur de l'autorisation WRITE_EXTERNAL_STORAGE .

Ajoutez d'abord la permission dans votre fichier de manifeste Android

Étape 1 Déclare requestcode

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

Étape 2 Ajoutez ce code lorsque vous souhaitez demander à l'utilisateur une permission.

 //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);
    }

Étape 3 Ajouter une méthode de substitution pour la requête

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

Remarque: N'oubliez pas d'ajouter une permission dans le fichier de manifeste

MEILLEUR EXEMPLE CI-DESSOUS AVEC PLUSIEURS AUTORISATIONS ET COUVRE TOUT LE SCÉNARIO

J'ai ajouté des commentaires pour que vous puissiez facilement comprendre.

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

            }

        }

    }


}

Cas particulier pour le changement de configuration

Il est possible que l'utilisateur fasse pivoter le périphérique ou déclenche une modification de configuration alors que notre boîte de dialogue d'autorisation est au premier plan. Puisque notre activité est toujours visible derrière cette boîte de dialogue, nous sommes détruits et recréés… mais nous ne souhaitons pas ressusciter à nouveau la boîte de dialogue d'autorisation.

C'est pourquoi nous avons un booléen, nommé isInPermission, qui indique si nous sommes en train de demander des autorisations. Nous conservons cette valeur dans onSaveInstanceState():

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

Nous le restaurons dans onCreate(). Si nous ne détenons pas toutes les autorisations souhaitées, mais que isInPermission a la valeur true, nous ne demandons pas les autorisations, car nous sommes déjà en train de le faire.

21
Arpit Patel

Android a modifié le fonctionnement des autorisations avec Android 6.0, ce qui explique vos erreurs. Vous devez réellement demander et vérifier si l'autorisation a été accordée par l'utilisateur à utiliser. Ainsi, les autorisations dans le fichier manifeste ne fonctionneront que pour les api inférieurs à 21. Vérifiez sur ce lien un extrait de la manière dont les autorisations sont demandées dans api23 http://Android-developers.blogspot.nl/2015/09/google-play- services-81-and-Android-60.html? m = 1

Code:-

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

Peut-être que vous ne pouvez pas utiliser la classe de manifeste à partir du code généré dans votre projet. Vous pouvez donc utiliser la classe de manifestes à partir de Android sdk "Android.Manifest.permission.WRITE_EXTERNAL_STORAGE". Mais dans la version Marsmallow, 2 autorisations doivent être accordées sont WRITE et READ EXTERNAL STORAGE dans la catégorie de stockage. Voir mon programme, mon programme demandera une autorisation jusqu'à ce que l'utilisateur choisisse oui et fasse quelque chose une fois les autorisations accordées.

            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
            }
6
icaksama

Droite. J'ai donc enfin compris le problème: il s'agissait d'une mise à niveau OTA in situ bâclée.

Mes soupçons se sont intensifiés après que mon Garmin Fenix ​​2 n’ait pas été en mesure de se connecter via Bluetooth et après la recherche de "problèmes de mise à niveau de Marshmallow" sur Google. Quoi qu'il en soit, une "réinitialisation d'usine" a corrigé le problème.

Étonnamment, la réinitialisation n'a pas renvoyé le téléphone au KitKat d'origine; Au lieu de cela, le processus de nettoyage a récupéré le package de mise à niveau OTA téléchargé 6.0 et s’exécute, ce qui a pour résultat (je suppose) une mise à niveau "plus propre".

Bien entendu, cela signifie que le téléphone a perdu toutes les applications que j'avais installées. Mais les applications fraîchement installées, y compris la mienne, fonctionnent sans aucune modification (c'est-à-dire qu'il existe une compatibilité ascendante). Ouf!

3
RudyF

La documentation Android sur Manifest.permission.Manifest.permission.WRITE_EXTERNAL_STORAGE déclare:

À partir de l'API de niveau 19, cette autorisation n'est pas requise pour lire/écrire des fichiers dans vos répertoires spécifiques à l'application, renvoyés par getExternalFilesDir (String) et getExternalCacheDir (). .


Je pense que cela signifie que vous n'avez pas à coder pour l'implémentation à l'exécution du WRITE_EXTERNAL_STORAGE autorisation, sauf si l'application est en train d'écrire dans un répertoire qui n'est pas spécifique à votre application.

Vous pouvez définir la version max de sdk dans le manifeste par autorisation comme suit:

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

Assurez-vous également de changer le SDK cible dans le fichier build.graddle et non le manifeste, les paramètres de gradation écrasent toujours les paramètres du manifeste.

Android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
    minSdkVersion 17
    targetSdkVersion 22
}
3
Stefano D