varios studio shouldshowrequestpermissionrationale requestpermissions permission permisos pedir onrequestpermissionsresult multiples example android android-permissions android-6.0-marshmallow

studio - ¿Se pueden solicitar permisos de forma sincrónica en el modelo de permisos de tiempo de ejecución de Android Marshmallow(API 23)?



requestpermissions android example (5)

A partir de Marshmallow, entiendo que no puedes.

Tuve que resolver el mismo problema en mi aplicación. Así es como lo hice:

  1. Refactoring : mueva cada fragmento de código que depende de los permisos de algún tipo en un método propio.
  2. Más refactorización : identifique el desencadenante de cada método (como iniciar una actividad, tocando un control, etc.) y agrupe los métodos si tienen el mismo activador. (Si dos métodos terminan teniendo el mismo activador Y que requieren el mismo conjunto de permisos, considere fusionarlos).
  3. Aún más refactorización : averigüe qué depende de los nuevos métodos que se hayan invocado anteriormente, y asegúrese de que sea flexible el momento en que se invocan estos métodos: muévalo al método en sí o al menos asegúrese de que lo que haga no lo haga lanzar excepciones si su método no ha sido llamado antes, y que comienza a comportarse como se espera tan pronto como se llame al método.
  4. Códigos de solicitud : para cada uno de estos métodos (o grupos de los mismos), defina una constante entera que se utilizará como código de solicitud.
  5. Comprobación y solicitud de permisos : ajuste cada uno de estos métodos / grupos de métodos en el siguiente código:

.

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

  1. Manejo de respuestas : escriba una implementación para onRequestPermissionsResult() en cada Activity que solicite permisos. Compruebe si se otorgaron los permisos solicitados, y use el código de solicitud para determinar a qué método se debe llamar.

Tenga en cuenta que la API requiere una Activity para las solicitudes de permisos de tiempo de ejecución. Si tiene componentes no interactivos (como un Service ), eche un vistazo a Cómo solicitar permisos de un servicio en Android Marshmallow para obtener asesoramiento sobre cómo abordar esto. Básicamente, la forma más fácil es mostrar una notificación que luego mostrará una Activity que no hace más que presentar el cuadro de diálogo de permisos de tiempo de ejecución. Here es cómo he abordado esto en mi aplicación.

Digamos que tienes un método como este:

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

En toda su aplicación, si desea guardar un archivo en el almacenamiento externo, haga algo como ...

if (saveFile(external_storage_url, "this is a test")) { // yay, success! } else { // notify the user something was wrong or handle the error }

Este es un ejemplo simplificado, así que no entienda mi caso sobre el bloqueo de la interfaz de usuario, el manejo adecuado de las excepciones, etc. Si no le gusta guardar archivos, podría imaginar getContact() o getPhoneState() o lo que sea. El punto es que es una operación que necesita permiso que devuelve algunos valores y se usa en toda la aplicación.

En Android <= Lollipop, si el usuario hubiera instalado y aceptado otorgar android.permission.WRITE_EXTERNAL_STORAGE o lo que sea, todo estaría bien.

Pero en el nuevo modelo de permiso de tiempo de ejecución de Marshmallow (API 23), antes de que pueda guardar el archivo en un almacenamiento externo, se supone que debe (1) verificar si se ha otorgado el permiso. Si no , posiblemente (2) muestre una justificación para la solicitud (si el sistema piensa que es una buena idea ) con un brindis o lo que sea y (3) pida al usuario que conceda permiso a través de un diálogo, luego simplemente recuéstese y espere un llamar de vuelta...

(entonces tu aplicación se queda, esperando ...)

(4) Cuando el usuario responde finalmente al diálogo, el método onRequestPermissionsResult() se dispara y su código ahora (5) tiene que seleccionar a qué solicitud de permission están permission , si el usuario dijo sí o no (a mi saber que no hay manera de manejar "no" vs. "no y no preguntar de nuevo"), (6) descubrir qué estaban tratando de lograr en primer lugar, lo que motivó todo el proceso de solicitud de permisos, por lo que el programa finalmente puede (7) seguir adelante y hacer eso.

Para saber lo que el usuario estaba tratando de hacer en el paso (6) implica haber pasado previamente un code especial (la "respuesta de solicitud de permisos") que se describe en los documentos como un identificador del tipo de solicitud de permiso (cámara / contacto / etc. .) pero me parece más como un "específicamente, lo que estabas tratando de hacer cuando te diste cuenta de que necesitarías pedir permisos", dado que el mismo permiso / grupo puede ser usado para múltiples propósitos en tu código, entonces necesitaría usar este código para devolver la ejecución al lugar apropiado después de obtener el permiso.

Podría estar totalmente mal entendiendo cómo se supone que funciona, así que avíseme si estoy lejos, pero el punto más importante es que realmente no estoy seguro de cómo pensar en hacer todo lo anterior con el saveFile() descrito anteriormente debido a la parte asíncrona "esperar a que el usuario responda". Las ideas que he considerado son bastante hacky y ciertamente incorrectas.

El podcast para desarrolladores de Android de hoy insinuaba que podría haber una solución sincrónica a la vuelta de la esquina, e incluso se habló sobre un tipo de entrada mágica y alt-enter de la herramienta "agregar una solicitud de permisos" en Android Studio. Aún así, cómo el proceso de permiso de tiempo de ejecución podría ser saveFile() en saveFile() o lo que saveFile() Estoy pensando algo así como:

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

Entonces, la checkPermission() fallaría si el usuario no tuviera y también se negó a otorgar el permiso. Tal vez uno podría usar un bucle alrededor de checkPermission() para intentar preguntar hasta 3 veces o algo, o mejor sería si el método manejara una sana política de no ser molesto.

¿Es posible tal cosa? ¿Deseable? ¿Alguna solución de este tipo bloquearía el hilo de UI? Desde el podcast, parecía que Google podría tener una solución como esta a la vuelta de la esquina, pero quería saber si había algo -una clase de conveniencia, un patrón, algo- que no implique que todos tengan que refactorizar todas las operaciones que requieren permiso, lo que debo suponer podría ser bastante complicado.

Perdón por la pregunta larga, pero quería ser lo más completa posible. Voy a sacar mi respuesta del aire. ¡Gracias!

Actualización : Aquí está la transcripción del podcast mencionado anteriormente.

Escucha a eso de las 41:20 . En esta discusión:

Transcripción aproximada:

Tor Norbye (Equipo de herramientas): " Por lo tanto, no parece que debería ser mucho trabajo para los desarrolladores. Pero entiendo que parte del problema es que no se trata de llamadas sincrónicas, ¿no es así? cambia la forma en que se escribe tu actividad para que te devuelva la llamada, así que es realmente como una máquina de estado donde ... para este estado, tú- "

Poiesz (gerente de producto): " Ah, pensé que había una opción para la respuesta síncrona "

Norbye: " Oh, eso haría las cosas-- "

Poiesz: " Puedo hablar con la gente internamente. Recuerdo una discusión sobre sincrónico, pero podemos averiguarlo " .

Norbye: " Sí. De hecho, probablemente deberíamos convertirlo en herramientas. Donde tienes una fácil refactorización ... "

Luego habla sobre el uso de anotaciones en las herramientas para determinar qué API requieren permisos ... (que a partir de ahora no funciona esa gran IMO) y cómo quiere que las herramientas algún día generen realmente el código requerido si encuentra una opción "peligrosa" "llamada de método:

Norbye: " ... entonces, si también estás en M, dirá: ''oye, ¿en verdad estás buscando este permiso o estás atrapando excepciones de seguridad?'', Y si no lo estás, diremos ''Probablemente necesites hacer algo para solicitar el permiso aquí''. Sin embargo, lo que me gustaría es que tenga una solución rápida donde pueda ir ''CHING!'' e inserta todo lo correcto para preguntar, pero la forma en que estaban las cosas cuando miré, esto requería reestructurar un montón de cosas: agregar interfaces y callbacks, y cambiar el flujo, y eso no podíamos hacer. Pero si hay un modo sincrónico fácil como algo temporal o permanente, eso sería [genial] " .


Así es como resolví el problema de la "sincronicidad" sin tener que bloquear explícitamente (y ocupado-esperar), o sin requerir una actividad separada de "cargador de arranque". Refactoré la actividad de la pantalla de inicio de la siguiente manera :

actualización: un ejemplo más completo se puede encontrar here.

nota: Porque la API startActivityForResult() llama a startActivityForResult()

public final void requestPermissions(@NonNull String[] permissions, int requestCode) { Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null); }

la lógica de creación de vistas principal se movió de OnCreate() a OnCreate2() , y OnCreate() ahora maneja la verificación de permisos. Si RequestPermissions() necesita ser llamado, entonces el OnRequestPermissionsResult() asociado reinicia esta actividad (reenviando una copia del paquete original).

[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: Reenviar paquete de un intento a otro

Nota: Esto se puede refactorizar para manejar más permisos en general. Actualmente maneja solo el permiso de escritura de tarjeta sd, que debe transmitir la lógica pertinente con suficiente claridad.


Así que odio responder mi propia pregunta específicamente con respecto al permiso android.permission.WRITE_EXTERNAL_STORAGE utilizado en mi ejemplo, pero qué diablos.

Para leer y / o escribir un archivo, en realidad hay una manera de evitar por completo tener que pedir y luego verificar el permiso y evitar así todo el flujo que describí anteriormente. De esta forma, el saveFile (Url url, String content) que di como ejemplo podría seguir funcionando sincrónicamente.

La solución, que creo que funciona en API 19+, elimina la necesidad del permiso WRITE_EXTERNAL_STORAGE haciendo que DocumentsProvider actúe como un "intermediario" para básicamente preguntar al usuario en nombre de su aplicación "seleccione el archivo específico para escribir" (es decir, , un "selector de archivos") y luego, una vez que el usuario elige un archivo (o escribe un nuevo nombre de archivo), ahora la aplicación tiene permiso mágico para hacerlo para ese Uri, ya que el usuario lo ha otorgado específicamente.

No se necesita permiso WRITE_EXTERNAL_STORAGE "oficial".

Esta forma de tipo de permisos de préstamo es parte del Marco de acceso de almacenamiento , y una discusión sobre esto se realizó en Big BBQ de Ian Lake. A continuación, se incluye un video llamado Olvidar el permiso de almacenamiento: Alternativas para compartir y colaborar que WRITE_EXTERNAL_STORAGE los aspectos básicos y cómo lo usa específicamente para eludir por WRITE_EXTERNAL_STORAGE requisito de permiso WRITE_EXTERNAL_STORAGE .

Esto no resuelve completamente el problema de permisos de sincronización / asincronización para todos los casos, pero para cualquier tipo de documento externo, o incluso uno que sea ofrecido por un proveedor (como gDrive, Box.net, Dropbox, etc.), esto puede ser una solución que vale la pena mirar.


Puede agregar un método de ayuda de bloqueo como este:

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


Respuesta corta: no, no hay ninguna operación de sincronización hoy. Debe verificar si tiene el permiso correcto antes de completar la operación o, como última opción, puede colocar un bloque de prueba / captura para la excepción de seguridad. En el bloque catch, puede informar al usuario que la operación falló debido a problemas de permisos. Además, hay otro punto: cuando se revoca un permiso, la aplicación no se reinicia desde la actividad principal, por lo que debe verificar el permiso incluso en onResume ().