您可以在Android Marshmallow(API 23)的运行时权限模型中同步请求权限吗?

假设你有这样的方法:

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

在整个应用程序中,如果要将文件保存到外部存储,则可以执行以下操作:

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

这是一个简单的例子,所以不要在我的情况下阻止用户界面,正确处理exception等。如果你不喜欢文件保存,你可以想象一个getContact()getPhoneState()等等。 重点是它是一个需要许可的操作,它返回一些值,并在整个应用程序中使用。

在Android <= Lollipop,如果用户已经安装并同意授予android.permission.WRITE_EXTERNAL_STORAGE什么的,一切都会好起来的。

但在新的Marshmallow(API 23) 运行时权限模型中 ,在将文件保存到外部存储之前,应该(1)检查权限是否已被授予。 如果没有 ,可能(2)用敬酒或其他方式显示请求的理由 (如果系统认为这是个好主意 ),(3)要求用户通过对话授予许可,然后基本上坐下来等待回电话…

(所以你的应用程序,等待…)

(4)当用户最后响应对话时, onRequestPermissionsResult()方法被触发,你的代码现在(5)必须筛选他们实际正在响应的 WHICH 权限请求,无论用户说是或否知识是没有办法处理“不”,而不是“不要再问”),(6)弄清楚他们试图完成什么,首先促使整个请求权限的过程,所以程序最后可以(7)继续做这件事情。

为了知道用户在步骤(6)中想要做什么,涉及先前已经通过在文档中描述为特许请求types的标识符(相机/联系人/等等)的特殊代码 (“许可请求响应” ),但在我看来,更像是一个“具体地说,当你意识到你需要请求权限的时候你想要做什么”的代码,因为你的代码中可能有多个用途的相同的权限/组,您需要使用此代码在获得权限后将执行返回到适当的位置。

我可能完全误解了这个应该如何工作 – 所以请让我知道,如果我走了 – 但更大的一点是我真的不知道如何甚至想着做所有上述w /之前描述的saveFile()方法是由于asynchronous的“等待用户响应”部分。 我考虑过的想法是相当黑客,当然是错误的。

今天的Android开发者Podcast暗示可能会有一个同步的解决scheme,甚至有人谈论Android Studio中的“添加权限请求”工具。 不过,运行时权限过程如何被推到saveFile()或者其他什么东西中 – 我正在想的是:

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

所以上面的checkPermission()会失败,如果用户没有,也拒绝授予权限。 也许可以使用一个围绕checkPermission()的循环来尝试最多3次或更多,或者更好的是,如果一个理智的不烦人的政策是由该方法处理。

这样的事情可能吗? 可取? 任何这样的解决scheme会阻止UI线程? 从播客中听起来,谷歌似乎有这样一个解决scheme即将到来,但我想知道是否有什么东西 – 一个便利的类,一个模式,一些东西 – 不涉及每个人都必须重构所有需要许可的操作,我必须假设可能会非常混乱。

抱歉,这个冗长的问题,但我想尽可能完整。 我会把我的答案停播。 谢谢!


更新 :这是上面提到的播客的抄本。

听大约41:20 。 在这个讨论中:

粗略的logging:

Tor Norbye(工具团队):“ 所以开发人员似乎不应该做很多工作,但我知道部分问题是这些不是同步调用,对吧?改变你的活动写回来的方式 – 所以它真的有点像状态机,在这个状态下,你 –

Poiesz(产品经理):“ 啊,我以为 – 有一个 – 可能有同步响应的选项 –

Norbye:“ 哦,那会使事情变成现实 –

Poiesz:“ 我可以在内部与人们交谈,我记得有关同步的讨论,但是我们可以找出答案

Norbye:“ 是的,实际上我们应该把它变成工具,在那里你可以轻松地重构…

然后他谈到了在工具中使用注释来确定哪些API需要权限..(目前为止,这并不是很好的IMO),并且他希望有一天能够用这些工具实际生成所需的代码,如果它发现一个未经检查的“危险的“方法调用:

Norbye:“ 那么如果你在M上面,它会说:'嘿,你是在检查这个权限吗?还是你在捕捉安全exception?',如果你不是,我们会说“你可能需要做点什么来请求这里的许可。 我想要的是,为了能够快速解决,你可以去“清”! 它插入所有正确的要求,但是当我看,事情回来的方式需要重组许多事情 – 添加接口和callback,改变stream程,我们不能这样做,但如果有一个简单的同步模式,作为一个暂时的事物或一个永久的事物,那将是非常棒的。

至于棉花糖,我的理解是你不能。

我必须在我的应用程序中解决同样的问题。 以下是我如何做到的:

  1. 重构 :将依赖某种权限的每一块代码移动到它自己的方法中。
  2. 更多的重构 :确定每种方法的触发器(如开始一个活动,点击一个控件等),如果它们具有相同的触发器,则将方法组合在一起。 (如果两个方法最终具有相同的触发器并且需要相同的权限集,请考虑合并它们。)
  3. 甚至更多的重构 :找出依赖于之前调用过的新方法,并确保它们在调用这些方法时具有灵活性:将其移动到方法本身,或者至less确保不pipe你做什么如果您的方法之前没有被调用,则会抛出exception,并且只要方法被调用,就会像预期的那样开始执行。
  4. 请求代码 :对于这些方法(或其组)中的每一个,定义一个要用作请求代码的整数常量。
  5. 检查并询问权限 :将以下每个方法/方法组合成以下代码:

 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. 处理响应 :在每个请求权限的Activity中为onRequestPermissionsResult()写一个实现。 检查请求的权限是否被授予,并使用请求代码来确定需要调用的方法。

请注意,该API需要一个针对运行时权限请求的Activity 。 如果您有非交互式组件(例如Service ),请参阅如何从Android Marshmallow中的服务请求权限以获取有关如何解决此问题的build议。 基本上,最简单的方法是显示一个通知,然后调出一个Activity ,除了提供运行时权限对话框外什么也不做。 这是我在我的应用程序中如何解决这个问题。

简答:不,今天没有任何同步操作。 您必须在完成操作之前检查您是否拥有正确的权限,或者作为最后一个选项,您可以为安全exception放置一个try / catch块。 在catch块中,您可以通知用户由于权限问题导致操作失败。 另外,还有一点:当权限被撤销时,应用程序不会从主要活动重新启动,因此即使在您的onResume()中也必须检查权限。

所以我不想回答我自己的问题,特别是在我的例子中使用android.permission.WRITE_EXTERNAL_STORAGE权限,但是什么。

为了读取和/或写入文件, 实际上有一种方法可以完全避免必须要求然后检查许可 ,从而绕过上述的整个stream程。 通过这种方式,我给出的saveFile (Url url, String content)方法可以继续同步工作。

我相信在API 19+中的解决scheme,通过让一个DocumentsProvider充当“中间人”来代替您的应用程序代表用户“请select要写入的特定文件”(即,请select要写入的特定文件),从而消除了对WRITE_EXTERNAL_STORAGE权限的需求。 ,一个“文件select器”),然后一旦用户select了一个文件(或者input一个新的文件名),这个应用程序现在被神奇地授予了这个Uri的许可,因为这个用户已经明确地授予了它。

不需要“官方” WRITE_EXTERNAL_STORAGE权限。

这种借用权限的方式是存储访问框架的一部分,Ian Lake在Big Android BBQ上对此进行了讨论。 以下是一个名为“ 忘记存储权限:共享和协作的替代方法” 的video ,其中介绍了如何使用它来彻底绕过WRITE_EXTERNAL_STORAGE权限要求。

这并不能完全解决所有情况下的同步/asynchronous权限问题,但是对于任何types的外部文档,甚至是由提供者提供的(例如gDrive,Box.net,Dropbox等),这可能是一个值得检查的解决scheme。

以下是我如何解决“同步”问题,而不必显式阻止(和忙等待),或不需要单独的“boot-loader”活动。 我重构了启animation面活动,如下所示

更新:更完整的例子可以在这里find。


注意:因为requestPermissions() API调用startActivityForResult()

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

主视图创build逻辑从OnCreate()移到OnCreate2()OnCreate()现在处理权限检查。 如果需要调用RequestPermissions() ,则关联的OnRequestPermissionsResult()重新启动此活动(转发原始包的副本)。


 [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:将 包从一个意向转发到另一个意向

注意:这可以被重构来处理更多的权限。 它目前只处理SD卡写入权限,它应该足够清楚地传达相关的逻辑。

你可以像这样添加一个阻塞帮助器方法:

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