Disons que vous avez une méthode comme celle-ci:
public boolean saveFile (Url url, String content) {
// save the file, this can be done a lot of different ways, but
// basically the point is...
return save_was_successful;
}
Tout au long de votre application, si vous souhaitez enregistrer un fichier sur un stockage externe, vous faites quelque chose comme ...
if (saveFile(external_storage_url, "this is a test")) {
// yay, success!
} else { // notify the user something was wrong or handle the error }
Ceci est un exemple simplifié, alors ne vous attardez pas sur le blocage de l'interface utilisateur, la gestion correcte des exceptions, etc. Si vous n'aimez pas l'enregistrement de fichiers, vous pouvez imaginer une fonction getContact()
ou getPhoneState()
ou peu importe. Le fait est que c'est une opération nécessitant une autorisation qui renvoie une ou plusieurs valeurs et qui est utilisée dans toute l'application.
Dans Android <= Lollipop, si l'utilisateur avait installé et accepté d'accorder Android.permission.WRITE_EXTERNAL_STORAGE
Ou autre, tout serait bien.
Mais dans le nouveau Marshmallow (API 23) modèle d'autorisation d'exécution , avant de pouvoir enregistrer le fichier sur un stockage externe, vous êtes censé (1) vérifier si l'autorisation a été accordée. Sinon , éventuellement (2) montrer la justification de la demande (si le le système pense que c'est une bonne idée ) avec un toast ou autre chose et (3) demander à l'utilisateur d'accorder l'autorisation via une boîte de dialogue, puis en gros, attendez un rappel ...
(pour que votre application reste assise, en attendant ...)
(4) Lorsque l'utilisateur répond finalement à la boîte de dialogue, la méthode onRequestPermissionsResult () se déclenche, et votre code maintenant (5) doit passer au crible QUI permission demande qu'ils soient en fait répondant à , si l'utilisateur a dit oui ou non (à ma connaissance, il n'y a aucun moyen de gérer "non" vs "non et ne plus demander"), (6) comprendre ce qu'ils essayaient d'accomplir en premier lieu ce qui a incité tout le processus de demande d'autorisations, afin que le programme puisse enfin (7) aller de l'avant et faire cette chose.
Pour savoir ce que l'utilisateur tentait de faire à l'étape (6), il faut avoir préalablement passé un code (la "réponse de la demande d'autorisation") qui est décrit dans les documents comme identifiant du type de - demande de permission (camera/contact/etc.) mais me semble plus comme un code "spécifiquement, ce que vous essayiez de faire quand vous avez réalisé que vous auriez besoin de demander des permissions", étant donné que le même l'autorisation/le groupe peut être utilisé à plusieurs fins dans votre code, vous devez donc utiliser ce code pour renvoyer l'exécution à l'endroit approprié après avoir obtenu l'autorisation.
Je pourrais être totalement incompréhensible sur la façon dont cela est censé fonctionner - alors s'il vous plaît, faites-moi savoir si je suis loin - mais le point le plus important est que je suis vraiment ne sais pas comment penser de faire tout ce qui précède avec la méthode saveFile()
décrite précédemment en raison de la partie asynchrone "attendre que l'utilisateur réponde". Les idées que j'ai envisagées sont assez hackeuses et certainement fausses.
Aujourd'hui Android Developer Podcast a laissé entendre qu'il pourrait y avoir une solution synchrone au coin de la rue, et il a même été question d'un outil magique, en une seule étape de type alt-enter "ajouter une demande d'autorisations" dans Android Studio. Pourtant, comment le processus d'autorisation d'exécution pourrait être poussé dans une saveFile()
ou autre chose - je pense à quelque chose comme:
public boolean saveFile(Url url, String content) {
// this next line will check for the permission, ask the user
// for permission if required, maybe even handle the rationale
// situation
if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
R.string.permission_storage_rationale))
{
return false; // or throw an exception or whatever
} else {
// try to save the file
return save_was_successful;
}
}
Ainsi, la checkPermission()
ci-dessus échouerait si l'utilisateur n'en avait pas et refusait également d'accorder l'autorisation. Peut-être que l'on pourrait utiliser une boucle autour de checkPermission()
pour essayer de demander jusqu'à 3 fois ou quelque chose, ou mieux serait si une politique saine de ne pas être ennuyeux était gérée par la méthode.
Une telle chose est possible? Souhaitable? Une telle solution bloquerait-elle le thread d'interface utilisateur? D'après le podcast, cela ressemblait à Google - mai avoir une solution comme celle-ci à venir, mais je voulais savoir s'il y avait quelque chose - une classe de commodité, un modèle, quelque chose - - cela n'implique pas que tout le monde doive refactoriser toutes les opérations nécessitant une autorisation, ce qui, je suppose, pourrait devenir assez compliqué.
Désolé pour la longue question, mais je voulais être aussi complète que possible. Je vais retirer ma réponse de l'antenne. Merci!
Mettre à jour: Voici la transcription du podcast mentionné ci-dessus.
Écoutez environ 41:2 . Dans cette discussion:
Transcription grossière:
Tor Norbye (équipe Outils): " Il ne semble donc pas que cela devrait être beaucoup de travail pour les développeurs. Mais je comprends qu'une partie du problème est que ce ne sont pas des appels synchrones, non?. devez- en fait changer la façon dont votre activité est écrite pour avoir un rappel - donc c'est vraiment un peu comme une machine à états où ... pour cet état, vous - "
Poiesz (chef de produit): " Ah- je pensais- il y en avait un - il pourrait y avoir une option pour une réponse synchrone -"
Norbye: " Oh. Ça ferait des choses -"
Poiesz: " Je peux parler avec des gens en interne. Je me souviens d'une discussion sur synchrone - mais nous pouvons le découvrir."
Norbye: " Ouais. En fait, nous devrions probablement le faire dans les outils. Où vous avez une refactorisation facile ..."
Ensuite, il parle d'utiliser des annotations dans les outils pour déterminer quelles API nécessitent des autorisations .. (ce qui ne fonctionne pas si bien que ça à l'heure actuelle) et comment il veut qu'un jour les outils génèrent réellement le code requis s'il trouve un "dangereux" non vérifié. "appel de méthode:
Norbye: "... alors si vous êtes également sur M, il dira:" Hé, vérifiez-vous réellement cette autorisation ou attrapez-vous des exceptions de sécurité? ", Et si vous ne l'êtes pas, nous dirons "vous devez probablement faire quelque chose pour demander la permission ici." Ce que je voudrais cependant, c'est que cela ait une solution rapide où vous pouvez aller 'CHING!' et il insère toutes les bonnes choses pour demander, mais la façon dont les choses se passaient quand je regardais, cela nécessitait de restructurer beaucoup de choses - ajouter des interfaces et des rappels, et changer le flux, et ce que nous ne pouvions pas faire. Mais s'il y a un mode synchrone facile comme une chose temporaire ou une chose permanente, ce serait [génial]. "
En ce qui concerne Marshmallow, je crois comprendre que vous ne pouvez pas.
J'ai dû résoudre le même problème dans mon application. Voici comment je l'ai fait:
.
if (ContextCompat.checkSelfPermission(this, Manifest.permission.SOME_PERMISSION) == PackageManager.PERMISSION_GRANTED)
doStuffThatRequiresPermission();
else
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SOME_PERMISSION}, Const.PERM_REQUEST_DO_STUFF_THAT_REQUIRES_PERMISSION);
onRequestPermissionsResult()
dans chaque Activity
qui demande des autorisations. Vérifiez si les autorisations demandées ont été accordées et utilisez le code de demande pour déterminer la méthode à appeler.Sachez que l'API nécessite un Activity
pour les demandes d'autorisation d'exécution. Si vous avez des composants non interactifs (comme un Service
), jetez un œil à Comment demander des autorisations à un service dans Android Marshmallow for des conseils sur la façon de résoudre ce problème. Fondamentalement, le moyen le plus simple consiste à afficher une notification qui fera apparaître un Activity
qui ne fait que présenter la boîte de dialogue des autorisations d'exécution. Ici est la façon dont je abordé cela dans mon application.
Réponse courte: non, il n'y a pas d'opération de synchronisation aujourd'hui. Vous devez vérifier si vous avez la bonne autorisation avant de terminer l'opération ou, comme dernière option, vous pouvez mettre un bloc try/catch pour l'exception de sécurité. Dans le bloc catch, vous pouvez informer l'utilisateur que l'opération a échoué en raison de problèmes d'autorisation. De plus, il y a un autre point: lorsqu'une autorisation est révoquée, l'application ne redémarre pas à partir de l'activité principale, vous devez donc vérifier l'autorisation même dans votre onResume ().
Voici comment j'ai résolu le problème de "synchronicité" sans avoir à bloquer explicitement (et à attendre), ou sans nécessiter une activité distincte de "chargeur de démarrage". Je refactorisé l'activité de l'écran de démarrage comme suit :
mise à jour: Un exemple plus complet peut être trouvé ici.
remarque: Parce que l'API requestPermissions()
appelle startActivityForResult()
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}
la logique de création de vue principale a été déplacée de OnCreate()
vers OnCreate2()
et OnCreate()
gère désormais la vérification des autorisations. Si RequestPermissions()
doit être appelée, alors la OnRequestPermissionsResult()
associée redémarre cette activité (transfert d'une copie du bundle d'origine).
[Activity(Label = "MarshmellowActivated",
MainLauncher = true,
Theme = "@style/Theme.Transparent",
Icon = "@drawable/icon"
////////////////////////////////////////////////////////////////
// THIS PREVENTS OnRequestPermissionsResult() from being called
//NoHistory = true
)]
public class MarshmellowActivated : Activity
{
private const int Android_PERMISSION_REQUEST_CODE__SDCARD = 112;
private Bundle _savedInstanceState;
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode)
{
case Android_PERMISSION_REQUEST_CODE__SDCARD:
if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
{
Intent restartThisActivityIntent = new Intent(this, this.GetType());
if (_savedInstanceState != null)
{
// *ref1: Forward bundle from one intent to another
restartThisActivityIntent.PutExtras(_savedInstanceState);
}
StartActivity(restartThisActivityIntent);
}
else
{
throw new Exception("SD Card Write Access: Denied.");
}
break;
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Android v6 requires explicit permission granting from user at runtime for extra sweet security goodness
Permission extStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
//if(extStoragePerm == Permission.Denied)
if (extStoragePerm != Permission.Granted)
{
_savedInstanceState = savedInstanceState;
// **calls startActivityForResult()**
RequestPermissions(new[] { Android.Manifest.Permission.WriteExternalStorage }, Android_PERMISSION_REQUEST_CODE__SDCARD);
}
else
{
OnCreate2(savedInstanceState);
}
}
private void OnCreate2(Bundle savedInstanceState)
{
//...
}
}
ref1: Transférer le bundle d'une intention à une autre
note: Cela peut être refactorisé pour gérer plus d'autorisations en général. Il gère actuellement uniquement la permission d'écriture de la carte SD, qui devrait transmettre la logique pertinente avec suffisamment de clarté.
Je déteste donc répondre à ma propre question spécifiquement en ce qui concerne l'autorisation Android.permission.WRITE_EXTERNAL_STORAGE
Utilisée dans mon exemple, mais que diable.
Pour lire et/ou écrire un fichier, il existe en fait un moyen d'éviter complètement d'avoir à demander et ensuite vérifier l'autorisation et ainsi contourner tout le flux que j'ai décrit ci-dessus. De cette façon, la méthode saveFile (Url url, String content)
que j'ai donnée comme exemple pourrait continuer à fonctionner de manière synchrone.
La solution, qui, je crois, fonctionne dans l'API 19+, élimine le besoin de l'autorisation WRITE_EXTERNAL_STORAGE
En ayant un DocumentsProvider
agissant comme un "intermédiaire" pour demander essentiellement à l'utilisateur au nom de votre application "s'il vous plaît sélectionnez le fichier spécifique dans lequel écrire "(c'est-à-dire un" sélecteur de fichiers "), puis une fois que l'utilisateur a choisi un fichier (ou a tapé un nouveau nom de fichier), l'application est désormais autorisée par magie à le faire pour cet Uri, car l'utilisateur l'a expressément accordée.
Aucune autorisation "officielle" WRITE_EXTERNAL_STORAGE
Requise.
Ce type de permission d'emprunt fait partie du Storage Access Framework , et une discussion à ce sujet a été faite au Big Android BBQ par Ian Lake. Voici une vidéo appelé Oubliez l'autorisation de stockage: Alternatives de partage et de collaboration qui passe en revue les bases et comment vous l'utilisez spécifiquement pour contourner le Exigence d'autorisation WRITE_EXTERNAL_STORAGE
Entièrement.
Cela ne résout pas complètement le problème des autorisations de synchronisation/async dans tous les cas, mais pour tout type de document externe, ou même celui proposé par un fournisseur (tel que gDrive, Box.net, Dropbox, etc.), cela peut être une solution à découvrir.
Vous pouvez ajouter une méthode d'aide au blocage comme celle-ci:
@TargetApi(23)
public static void checkForPermissionsMAndAboveBlocking(Activity act) {
Log.i(Prefs.TAG, "checkForPermissions() called");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Here, thisActivity is the current activity
if (act.checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// No explanation needed, we can request the permission.
act.requestPermissions(
new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
},
0);
while (true) {
if (act.checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.i(Prefs.TAG, "Got permissions, exiting block loop");
break;
}
Log.i(Prefs.TAG, "Sleeping, waiting for permissions");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
// permission already granted
else {
Log.i(Prefs.TAG, "permission already granted");
}
}
else {
Log.i(Prefs.TAG, "Below M, permissions not via code");
}
}