Android知识Android开发Android开发

Android 运行时权限详解

2018-02-05  本文已影响0人  ZHLeo

Android系统权限

权限的使用

Android App 默认无任何权限,如果需要使用系统权限必须在AndroidManifest.xml文件中声明权限。

    //声明网络使用权限
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.permission">

    <uses-permission android:name="android.permission.INTERNE"/>
    

    <application ...>
        ...
    </application>

    </manifest>

如果所需权限为正常权限(即不会对用户隐私或设备操作造成很大风险的权限),系统会自动授予这些权限。
如果所需权限为危险权限(即可能影响用户隐私或设备正常操作的权限),系统会要求用户明确授予这些权限。Android 发出请求的方式取决于系统版本(即targetSdkVersion):

正常权限

正常权限涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。此类权限都是正常保护的权限,只需要在Manifest文件中简单声明,安装即授权。

危险权限

危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。

特殊权限

有许多权限其行为方式与正常权限及危险权限都不同。SYSTEM_ALERT_WINDOWWRITE_SETTINGS 特别敏感,因此大多数应用不应该使用它们。如果某应用需要其中一种权限,必须在清单中声明该权限,并且发送请求用户授权的 intent。系统将向用户显示详细信息,以响应该 intent。

权限组

所有危险权限都拥有对应权限组,如果运行在 Android 6.0 及以上版本,App targetSdkVersion 大于23,则当用户请求危险权限时系统会发生以下行为:

Android O的运行时权限策略变化

运行时请求权限

检查权限及兼容

(1)对于运行在 Android 6.0及以上 App targetSdkVersion 大于23的应用

    // Assume thisActivity is the current activity  
    int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.WRITE_CALENDAR);

(2)对于运行在 Android 6.0及以上 App targetSdkVersion 大于23的应用

(3)对于运行在 Android 6.0以下 App targetSdkVersion 大于23的应用

    public static int checkPermission(@NonNull Context context, @NonNull String permission,
            int pid, int uid, String packageName) {
        if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
            return PERMISSION_DENIED;
        }

        String op = AppOpsManagerCompat.permissionToOp(permission);
        if (op == null) {
            return PERMISSION_GRANTED;
        }

        if (packageName == null) {
            String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
            if (packageNames == null || packageNames.length <= 0) {
                return PERMISSION_DENIED;
            }
            packageName = packageNames[0];
        }

        if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
                != AppOpsManagerCompat.MODE_ALLOWED) {
            return PERMISSION_DENIED_APP_OP;
        }

        return PERMISSION_GRANTED;
    }

checkSelfPermission通过上述四个判断语句进行权限校验

private static final AppOpsManagerImpl IMPL;
    static {
        if (Build.VERSION.SDK_INT >= 23) {
            IMPL = new AppOpsManager23();
        } else {
            IMPL = new AppOpsManagerImpl();
        }
    }
private static class AppOpsManagerImpl {
        AppOpsManagerImpl() {
        }

        public String permissionToOp(String permission) {
            return null;
        }

        public int noteOp(Context context, String op, int uid, String packageName) {
            return MODE_IGNORED;
        }

        public int noteProxyOp(Context context, String op, String proxiedPackageName) {
            return MODE_IGNORED;
        }
    }  
public int checkOp(String op, int uid, String packageName) {
        return checkOp(strOpToOp(op), uid, packageName);
    }


@hide 
public int checkOp(int op, int uid, String packageName) {
        try {
            int mode = mService.checkOperation(op, uid, packageName);
            if (mode == MODE_ERRORED) {
                throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
            }
            return mode;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

请求权限

对于App targetSdkVersion 大于23的应用,在应用需要使用的危险权限,必须要进行动态权限申请。Android 提供了多种权限请求方式。调用这些方法将显示一个标准的 Android 对话框(不允许开发者自定义)。
  调用requestPermissions() 方法,可进行动态权限申请,该方法是异步的。在用户响应对话框之后,系统会回调onRequestPermissionsResult。代码实例如下:

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
            Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
        Manifest.permission.READ_CONTACTS)) {

    // Show an expanation to the user *asynchronously* -- don't block
    // this thread waiting for the user's response! After the user
    // sees the explanation, try again to request the permission.

} else {

    // No explanation needed, we can request the permission.

    ActivityCompat.requestPermissions(thisActivity,
            new String[]{Manifest.permission.READ_CONTACTS},
            MY_PERMISSIONS_REQUEST_READ_CONTACTS);

    // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
    // app-defined int constant. The callback method gets the
    // result of the request.
}
}  

处理权限请求回调

在用户响应对话框之后,系统会回调 onRequestPermissionsResult() 方法。代码实例如下:

@Override
public void onRequestPermissionsResult(int requestCode,
    String permissions[], int[] grantResults) {
switch (requestCode) {
    case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
        // If request is cancelled, the result arrays are empty.
        if (grantResults.length > 0
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // permission was granted, yay! Do the
            // contacts-related task you need to do.

        } else {

            // permission denied, boo! Disable the
            // functionality that depends on this permission.
        }
        return;
    }

    // other 'case' lines to check for other
    // permissions this app might request
}
}

权限申请被拒绝

如果用户拒绝了某项权限请求,应用应采取适当的操作进行引导,应用应当显示一个对话框,解释应用为什么需要此权限,以及使用该权限有何影响。
当系统要求用户授予权限时,用户可以选择指统不再要求提供该权限。这种情况下,无论应用在什么时候使用 requestPermissions() 请求该权限,系统都会立即拒绝此请求。系统会调用您的 onRequestPermissionsResult() 回调方法,并传递 PERMISSION_DENIED,如果用户再次明确拒绝了权限的请求,系统将采用相同方式操作。这意味着当调用 requestPermissions()时,不一定会出现系统权限请求弹窗。
此时可借助shouldShowRequestPermissionRationale()这个回调方法,如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。如果用户拒绝了权限请求,并在权限请求系统对话框中选择了不再询问选项,此方法将返回 false。如果设备默认禁止应用具有该权限,此方法也会返回 false。

shouldShowRequestPermissionRationale()

关于运行时权限的一些建议

(1)只请求需要的权限,减少请求的次数,或用隐式Intent来让其他的应用来处理。

(2)防止一次请求太多的权限或请求次数太多,用户可能对你的应用感到厌烦,在应用启动的时候,最好先请求应用必须的一些权限,非必须权限在使用的时候才请求,建议整理并按照上述分类管理自己的权限:

例如,一个摄影的App可能需要使用定位服务,因为它需要用位置标记照片。一般的用户可能会不理解,他们会困惑为什么他们的App想要知道他的位置。所以在这种情况下,所以你需要在requestpermissions()之前告诉用户你为什么需要这个权限。

(3)使用兼容库support-v4中的方法

(4)将动态权限区分为必须和非必须授予

国产机权限问题整理

  • 部分中国厂商生产手机(例如小米某型号)的Rationale功能,在第一次拒绝后,第二次申请时不会返回true,并且会回调申请失败,也就是说在第一次拒绝后默认勾选了不再提示。
  • 部分中国厂商生产手机(例如小米、华为某型号)在申请权限时,用户点击确定授权后,还是回调我们申请失败,这个时候其实我们是拥有权限的,所以我们可以在失败的方法中使用AppOpsManager进行权限判断。
  • 部分中国厂商生产手机(例如vivo、Oppo某型号)在用户允许权限,并且回调了权限授权成功的方法,但是实际执行代码时并没有这个权限,建议开发者在回调成功的方法中也利用AppOpsManager判断下。
  • 在某些手机的Setting中授权后实际检查时还是没有权限,部分执行代码也是没有权限。

从系统版本看国产机型的权限申请特点

  • 5.0:此时 google 还未着手处理动态权限申请这么个东西,但是我们的小米、魅族等厂商就开始提前设置了强大的权限管理,所以 6.0 权限申请代码在 5.0 上压根不管用,但是说来也简单,5.0 的权限申请对话框激活就是靠触发危险权限代码,然后根据返回值来判断权限是否获取到了(不同手机的返回值判断方式不同,此处需要一一定制)。
  • 6.0:国产大部分机型手机的申请权限实际上应该细致地分为申请权限和应用权限 。它们的 ContextCompat.checkSelfPermission(Context, String) 判断是根据是否 AndroidManifest.xml 中声明了该权限来决定返回值,在 AndroidManifest.xml 中声明了权限就返回 true,当然也会有一些会返回 false,这个是申请权限的过程。而真正对话框的弹出是在开发者应用权限的过程中,什么叫做应用权限?就是调用了会触发权限的代码,这个时候就会激活对话框,但是如果仅到这里那就 too young too simple 了,当用户点击拒绝授权时,还是可能会回调授权成功的方法。另外,国产机大部分权限是有三个状态——询问、允许、拒绝——大部分权限都是询问状态,但是有些权限默认是允许状态,有些是拒绝状态,这就导致了调用 ContextCompat.checkSelfPermission(Context, String) 方法时会更畸形,例如小米手机的获取 READ_PHONE_STATE 状态,默认是授予状态。

Tanks

上一篇 下一篇

猜你喜欢

热点阅读