44

假设您有这样的方法:

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 }

这是一个简化的例子,所以不要谈论我关于阻止 UI、正确处理异常等的案例。如果你不喜欢文件保存,你可以想象一个getContact()getPhoneState()什么。关键是它是一个需要权限的操作,它返回一些值,并且在整个应用程序中使用。

在 Android <= Lollipop 中,如果用户已安装并同意授予android.permission.WRITE_EXTERNAL_STORAGE或其他任何内容,一切都会好起来的。

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

(所以你的应用程序坐在那里,等待......)

(4) 当用户最终响应对话框时,onRequestPermissionsResult()方法将触发,您的代码现在 (5) 必须筛选他们实际响应的权限请求,无论用户说是还是否(对我的知道没有办法处理“不”与“不并且不要再问”),(6)首先弄清楚他们试图完成的事情,这促使整个请求许可过程,所以程序终于可以 (7)继续做那件事了。

要了解用户在步骤 (6) 中尝试执行的操作,需要事先传递一个特殊代码(“权限请求响应”),该代码在文档中描述为权限请求类型的标识符(相机/联系人/等) .) 但在我看来更像是“具体来说,当你意识到你需要请求权限时你正在尝试做的事情”代码,因为相同的权限/组可以在你的代码中用于多种目的,所以获得许可后,您需要使用此代码将执行返回到适当的位置。

我可能完全误解了这应该是如何工作的——所以请让我知道我是否离题了——但更大的一点是我真的不知道如何考虑用/saveFile()由于异步“等待用户响应”部分,前面描述的方法。我考虑过的想法很老套,而且肯定是错误的。

今天的Android Developer Podcast暗示可能会有一个同步解决方案指日可待,甚至有人谈到了 Android Studio 中一种神奇的、一步 alt-enter 类型的“添加权限请求”工具。尽管如此,如何将运行时许可过程推入一个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 次或其他内容,或者如果该方法处理了一个理智的不烦人的策略,那就更好了。

这样的事情可能吗?可取的?任何这样的解决方案会阻止 UI 线程吗?从播客看来,谷歌可能会有一个类似的解决方案即将推出,但我想了解是否有什么东西——一个便利类、一个模式、什么东西——不需要每个人都必须重构所有需要权限的操作,我必须假设这可能会变得非常混乱。

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


更新:这是上面提到的播客的成绩单。

大约在41:20 收听。在本次讨论中:

粗略的成绩单:

Tor Norbye(工具团队):“所以对于开发人员来说,这似乎不应该做很多工作。但我理解部分问题是这些不是同步调用,对吧?所以你必须——实际上改变你的 Activity 的编写方式,让它有一个回调——所以它真的有点像一个状态机……对于这个状态,你——

Poiesz(产品经理):“啊-我想-有一个-可能有同步响应的选项--

诺拜:“哦。那会让事情——

Poiesz:“我可以在内部与人们交谈。我记得有一次关于同步的讨论——但我们可以找到答案。

Norbye:“是的。事实上,我们可能应该把它变成工具。在那里你有一个简单的重构......

然后他谈到了在工具中使用注释来确定哪些 API 需要权限..(到目前为止,这在 IMO 中还没有那么好用),以及他希望有一天这些工具在发现未经检查的“危险”时如何实际生成所需的代码" 方法调用:

Norbye:“ ......那么如果你也在 M 上,它会说,‘嘿,你实际上是在检查这个权限还是在捕捉安全异常?’,如果你不是,我们会说“你可能需要做一些事情来请求这里的许可。” 不过,我想要的是有一个快速修复,你可以去“CHING!” 它插入了所有正确的东西来询问,但是当我看到事情的时候,这需要重组很多东西——添加接口和回调,改变流程,而我们做不到。但如果有一个简单的同步模式作为临时的东西或永久的东西,那会[很棒]。

4

5 回答 5

11

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

我必须在我的应用程序中解决同样的问题。我是这样做的:

  1. 重构:将依赖于某种权限的每一块代码移动到它自己的方法中。
  2. 更多重构:识别每个方法的触发器(例如启动活动、点击控件等),如果它们具有相同的触发器,则将它们组合在一起。(如果两种方法最终具有相同的触发器并且需要相同的权限集,请考虑合并它们。)
  3. 更多重构:找出之前调用过的新方法依赖于什么,并确保何时调用这些方法是灵活的:要么将其移入方法本身,要么至少确保你所做的任何事情都不会如果您的方法之前没有被调用过,并且一旦调用该方法就开始按预期运行,则抛出异常。
  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. 处理响应onRequestPermissionsResult():在每个Activity请求权限的情况下编写一个实现。检查请求的权限是否被授予,并使用请求代码来确定需要调用什么方法。

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

于 2016-07-09T14:38:43.393 回答
2

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

于 2015-08-21T15:55:35.773 回答
1

这是我解决“同步性”问题的方法,而无需显式阻止(和忙等待),也无需单独的“引导加载程序”活动。我重构了启动屏幕活动如下

更新:可以在此处找到更完整的示例。


注意:因为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);
}

主视图创建逻辑已从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: 将捆绑从一个意图转发到另一个意图

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

于 2017-06-05T16:34:02.313 回答
1

因此,我讨厌就我的示例中使用的权限专门回答我自己的问题android.permission.WRITE_EXTERNAL_STORAGE,但到底是什么。

对于读取和/或写入文件,实际上有一种方法可以完全避免请求然后检查权限,从而绕过我上面描述的整个流程。这样,saveFile (Url url, String content)我给出的示例方法就可以继续同步工作了。

我相信该解决方案在 API 19+ 中有效,WRITE_EXTERNAL_STORAGE通过 DocumentsProvider充当“中间人”基本上代表您的应用程序询问用户“请选择要写入的特定文件”(即, “文件选择器”),然后一旦用户选择了一个文件(或输入新的文件名),应用程序现在神奇地被授予对该 Uri 执行此操作的权限,因为用户已明确授予它。

不需要“官方”WRITE_EXTERNAL_STORAGE许可。

这种借用权限的方式是Storage Access Framework的一部分,Ian Lake 在 Big Android BBQ 上对此进行了讨论。 这是一个名为“忘记存储权限:共享和协作的替代方案”的视频,其中介绍了基础知识以及您如何专门使用它来完全绕过WRITE_EXTERNAL_STORAGE权限要求。

这并不能完全解决所有情况下的同步/异步权限问题,但对于任何类型的外部文档,甚至是由提供商(例如 gDrive、Box.net、Dropbox 等)提供的文档,这可能是值得一试的解决方案。

于 2016-04-01T05:01:29.773 回答
0

您可以像这样添加阻塞辅助方法:

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

}
于 2016-06-24T08:07:32.627 回答