天天看点

Android 6.0 运行时权限

1、运行时权限定义

android6.0 运行时权限是指抛弃以前我们在manifest.xml文件中一次性赋予app权限,转而在程序真正运行时授予权限(需要用户同意),如果没有授予权限,则出现异常的一种机制。

6.0之前的权限模型

  • 在AndroidManifest中声明可能用到的所有权限
  • 用户在安装时,系统展示所有权限,用户安装即授予所有权限,取消则拒绝安装

6.0新的运行时权限

  • 将权限分为一般权限和危险权限两种,一般权限跟以前一样在AndroidManifest声明,危险权限需要开发者在代码中手动的动态申请
  • 动态申请权限,系统弹出对话框,用户可点击确定或拒绝,系统提供用户的选择回调,从而开发者处理相应的逻辑
  • 用户可以选择拒绝并不再提醒
    Android 6.0 运行时权限

2、对比传统权限声明和运行时权限请求的区别

传统权限声明针对 Android 6.0 及其以下版本使用,Android 6.0对应的API版本23,声明的方式直接将所有应用程序用到的权限统一在 manifest 清单文件中定义,使用标签 ,应用程序点击安装的过程,罗列清单文件声明的所有权限,安装完成后用户可以选择是否授予应用程序某个隐私的权限,Android系统提供:允许、提示和禁止三种选择,下面看一组演示:

build.gradle 选择编译版本、目标版本都是 API 19,运行在Android 4.4.2系统(华为)效果:

Android 6.0 运行时权限

build.gradle 选择编译版本、目标版本都是 API 19,运行在Android 6.0.1系统(小米)效果

Android 6.0 运行时权限

build.gradle选择编译版本、目标版本都是API 23,运行在Android 4.4.2系统(华为)效果:

Android 6.0 运行时权限

build.gradle选择编译版本、目标版本都是API 23,运行在Android 6.0.1系统(小米)效果:

Android 6.0 运行时权限

图1演示传统权限授予过程,在完成安装的过程中可以选择某个权限是否允许、提示和禁止状态;

图2演示低版本应用程序在Android 6.0以上系统安装过程,默认授予应用程序清单文件声明的所有权限,小米手机测试无法修改权限状态;

图3和图4演示API版本23开发的应用程序分别安装在低版本和高版本系统权限授予过程,安装在低版本时授予权限过程和传统的方式一样,用户可以修改权限的状态;安装到高版本时授予权限过程发生了很大变化,用户安装过程无法修改权限状态,最后运行应用程序的录音功能,出现闪退、崩溃现象。

3 权限分类

android6.0以后将权限分为两类,分别是危险权限与普通权限

危险权限

危险权限即需要动态申请的权限,一共9组,取得一组中某一个权限的授权,则自动拥有该组的所有授权

Android 6.0 运行时权限

一般权限

除上面的危险权限之外的权限即为一般权限(普通权限)

android6.0 运行时权限机制

当编译版本、目标版本都是 API 23及其以上时,安装在Android 6.0系统以上的手机默认授予所有普通权限,不授予危险权限,而危险权限只在程序将要使用这个功能时才进行授予(将会弹出对话框提示用户,用户同意,用户已同意则直接授予)。如果这时没有授予响应权限,系统将会抛出异常。

4 相关API及运行时权限例子

运行时权限主要的API就是以下几个

ContextCompat
//检查是否授予某个权限
static int  checkSelfPermission(Context context, String permission) 
主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了


ActivityCompat
//申请授权
static void requestPermissions(Activity activity, String[] permissions, int requestCode)
该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。

Activity
//处理权限申请回调
public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults) 
对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了~

static boolean  shouldShowRequestPermissionRationale(Activity activity, String permission)
这个API主要用于给用户一个申请权限的解释,该方法只有在用户在上一次已经拒绝过你的这个权限申请。也就是说,用户已经拒绝一次了,你又弹个授权框,你需要给用户一个解释,为什么要授权,则使用该方法。
           

一般来说:视频运行时权限步骤如下:

1 在AndroidManifest文件中添加需要的权限

2 在代码中判断是否授予某个权限

3 没有授予权限进行权限的申请

4 对申请权限用户处理后的结果进行处理

例子:

一个打电话的小例子

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity
{

    private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = ;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void testCall(View view)
    {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED)
        {

            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CALL_PHONE},
                    MY_PERMISSIONS_REQUEST_CALL_PHONE);
        } else
        {
            callPhone();
        }
    }

    public void callPhone()
    {
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:" + "10086");
        intent.setData(data);
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
    {

        if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE)
        {
            if (grantResults[] == PackageManager.PERMISSION_GRANTED)
            {
                callPhone();
            } else
            {
                // Permission Denied
                Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();
            }
            return;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}
           

在Android 6.x上运行是,点击testCall,即会弹出授权窗口,如何你Allow则直接拨打电话,如果Denied则Toast弹出”Permission Denied”。

例子很简单,不过需要注意的是,对于Intent这种方式,很多情况下是不需要授权的甚至权限都不需要的,比如:你是到拨号界面而不是直接拨打电话,就不需要去申请权限;打开系统图库去选择照片;调用系统相机app去牌照等

5 运行时权限封装

一般来说我们申请权限的代码如下:

if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.XXXX)
                != PackageManager.PERMISSION_GRANTED)
{

    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.XXXX},
            MY_PERMISSIONS_REQUEST_CODE_XX);
} else
{
    xxx();
}
           

虽然权限处理并不复杂,但是需要编写很多重复的代码,所以目前也有很多库对用法进行了封装,大家可以在github首页搜索:android permission,对比了几个库的使用方式,发现https://github.com/lovedise/PermissionGen这个库还不错,很多大神也在推荐

对于申请权限,我们需要3个参数:Activity|Fragment、权限字符串数组、int型申请码。

也就是说,我们只需要写个方法,接受这几个参数即可,然后逻辑是统一的。

public static void needPermission(Fragment fragment, int requestCode, String[] permissions)
{
    requestPermissions(fragment, requestCode, permissions);
}

@TargetApi(value = Build.VERSION_CODES.M)
private static void requestPermissions(Object object, int requestCode, String[] permissions)
{
    if (!Utils.isOverMarshmallow())
    {
        doExecuteSuccess(object, requestCode);
        return;
    }
    List<String> deniedPermissions = Utils.findDeniedPermissions(getActivity(object), permissions);

    if (deniedPermissions.size() > )
    {
        if (object instanceof Activity)
        {
            ((Activity) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
        } else if (object instanceof Fragment)
        {
            ((Fragment) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
        } else
        {
            throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
        }

    } else
    {
        doExecuteSuccess(object, requestCode);
    }
}
           

Utils.findDeniedPermissions其实就是check没有授权的权限。

#Utils
@TargetApi(value = Build.VERSION_CODES.M)
public static List<String> findDeniedPermissions(Activity activity, String... permission)
{
    List<String> denyPermissions = new ArrayList<>();
    for (String value : permission)
    {
        if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED)
        {
            denyPermissions.add(value);
        }
    }
    return denyPermissions;
}
           

那么上述的逻辑就很清晰了,需要的3种参数传入,先去除已经申请的权限,然后开始申请权限。

ok,我相信上面代码,大家扫一眼就可以了解了。

处理权限回调

对于回调,因为要根据是否授权去执行不同的事情,所以很多框架也需要些一连串的代码,或者和前面的申请代码耦合。不过这个框架还是比较方便的,也是我选择它来讲解的原因。

回调主要做的事情:

了解是否授权成功。

根据授权情况进行回调。

对于第一条逻辑都一样;对于第二条,因为涉及到两个分支,每个分支执行不同的方法。

对于第二条,很多框架选择将两个分支的方法在申请权限的时候进行注册,然后在回调中根据requestCode进行匹配执行,不过这样需要在针对每次申请进行对象管理。

不过这个框架采取了一种很有意思的做法,它利用注解去确定需要执行的方法,存在两个注解:

@PermissionSuccess(requestCode = )
@PermissionFail(requestCode = )
           

利用反射根据授权情况+requestCode即可找到注解标注的方法,然后直接执行即可。

大致的代码为:

@Override 
public void onRequestPermissionsResult(int requestCode, String[] permissions,
      int[] grantResults) {
    PermissionGen.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}

private static void requestResult(Object obj, int requestCode, String[] permissions,
                                  int[] grantResults)
{
    List<String> deniedPermissions = new ArrayList<>();
    for (int i = ; i < grantResults.length; i++)
    {
        if (grantResults[i] != PackageManager.PERMISSION_GRANTED)
        {
            deniedPermissions.add(permissions[i]);
        }
    }

    if (deniedPermissions.size() > )
    {
        doExecuteFail(obj, requestCode);
    } else
    {
        doExecuteSuccess(obj, requestCode);
    }
}
           

首先根据grantResults进行判断成功还是失败,对于成功则:

private static void doExecuteSuccess(Object activity, int requestCode)
{
    Method executeMethod = Utils.findMethodWithRequestCode(activity.getClass(),
            PermissionSuccess.class, requestCode);

    executeMethod(activity, executeMethod);
}
           
#Utils
public static <A extends Annotation> Method findMethodWithRequestCode(Class clazz,  Class<A> annotation, int requestCode)
{
    for (Method method : clazz.getDeclaredMethods())
    {
        if (method.isAnnotationPresent(annotation))
        {
            if (isEqualRequestCodeFromAnntation(method, annotation, requestCode))
            {
                return method;
            }
        }
    }
    return null;
}
           

根据注解和requestCode找到方法,然后反射执行即可。失败的逻辑类似,不贴代码了。

ok,到此我们的运行时权限相对于早起版本的变化、特点、以及如何处理和封装都介绍完了。

不过这个库有个缺点,就是运行时注解,对性能有影响

参考:

http://blog.csdn.net/lmj623565791/article/details/50709663

http://www.jianshu.com/p/d4a9855e92d3

https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650238900&idx=1&sn=6ec8ef71058d5bf81e8fc03a0a4af1d5&chksm=88639edbbf1417cd66d93c549c5537e32e54cf2e068eb6bc8cd0476cd8873c865bf245abe208&mpshare=1&scene=23&srcid=0501hrGGRxXdPlcZQOeUR60i#rd

继续阅读