0

    Android 6.0 运行时权限处理问题

    2023.06.02 | admin | 132次围观

    自从升级到Android M以来,最大的改变就是增加了运行时权限RuntimePermission运行时错误没有权限,6.0以上的系统如果没有做适配,运行了targetSDK=23的App时就会报权限错误。我们知道6.0以下的系统是按照的时候权限申请的,6.0和之后的版本是我们想要使用某个app的权限,去动态申请的,这也是基于安全上的考虑吧(比如:单机的象棋对战,请求访问通讯录权限等不合理的权限,这肯定是有问题的)。

    为了保护用户的隐私,谷歌官方将权限分为了两类,一个是正常权限(Normal Permissions),这类权限不涉及用户隐私,是不需要用户进行授权的,比如访问网络,手机震动等。还有一类是危险权限(Dangerous Permissions),一般是涉及到用户隐私的运行时错误没有权限,需要用户进行授权,比如操作SD卡的写入,相机,录音等。

    我们来看一张权限的清单文件:

    我们可以通过adb shell pm list permissions -d -g进行查看。

    权限如何申请

    那么对于我们开发者来说,怎么适配6.0呢?按着官方的api走就行:

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

    2,检查权限

    if (ContextCompat.checkSelfPermission(thisActivity,
                    Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {
    }else{
        //
    }

    建议这些检查权限的代码可以写到基类里面去。

    3,申请授权

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

    说明:第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以第二个参数看出,6.0是一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。

    4,处理权限申请回调

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

    不过这里有个情况,对于用户上次拒绝的权限,在下次需要这个权限的时候,系统怎么处理的了?

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

    所以完整的处理逻辑:

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

    项目实践

    权限工具类:

    public class PermissionUtils {
        private static final ArrayMap MIN_SDK_PERMISSIONS;
        static {
            MIN_SDK_PERMISSIONS = new ArrayMap<>(8);
            MIN_SDK_PERMISSIONS.put("com.android.voicemail.permission.ADD_VOICEMAIL", 14);
            MIN_SDK_PERMISSIONS.put("android.permission.BODY_SENSORS", 20);
            MIN_SDK_PERMISSIONS.put("android.permission.READ_CALL_LOG", 16);
            MIN_SDK_PERMISSIONS.put("android.permission.READ_EXTERNAL_STORAGE", 16);
            MIN_SDK_PERMISSIONS.put("android.permission.USE_SIP", 9);
            MIN_SDK_PERMISSIONS.put("android.permission.WRITE_CALL_LOG", 16);
            MIN_SDK_PERMISSIONS.put("android.permission.SYSTEM_ALERT_WINDOW", 23);
            MIN_SDK_PERMISSIONS.put("android.permission.WRITE_SETTINGS", 23);
        }
        private static volatile int targetSdkVersion = -1;
        public static boolean checkPermissions(int... grantResults) {
            if (grantResults.length == 0) {
                return false;
            }
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
            return true;
        }
        public static boolean hasSelfPermissions(Context context, String... permissions) {
            for (String permission : permissions) {
                if (permissionExists(permission) && !hasSelfPermission(context, permission)) {
                    return false;
                }
            }
            return true;
        }
        private static boolean permissionExists(String permission) {
            Integer minVersion = MIN_SDK_PERMISSIONS.get(permission);
            return minVersion == null || Build.VERSION.SDK_INT >= minVersion;
        }
        private static boolean hasSelfPermission(Context context, String permission) {
            try {
                return PermissionChecker.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
            } catch (RuntimeException t) {
                return false;
            }
        }
        public static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) {
            for (String permission : permissions) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                    return true;
                }
            }
            return false;
        }
        @TargetApi(Build.VERSION_CODES.DONUT)
        public static int getTargetSdkVersion(Context context) {
            try {
                if (targetSdkVersion != -1) {
                    return targetSdkVersion;
                }
                PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
                targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion;
            } catch (PackageManager.NameNotFoundException ignored) {
            }
            return targetSdkVersion;
        }
    }

    这里可以根据实际情况进行优化和扩展

    基类:

    public class BasePermissionActivity extends AppCompatActivity {
        private PermissionHandler mHandler=null;
        private static int requesrCode=001;
        /**
         * 请求权限
         */
        protected void requestPermission(String[] permissions, PermissionHandler handler) {
            if (PermissionUtils.hasSelfPermissions(this, permissions)) {
                handler.onGranted();
            } else {
                mHandler = handler;
                ActivityCompat.requestPermissions(this, permissions, requesrCode);
            }
        }
        protected void requestPermission( PermissionHandler handler,String permissions) {
            if (PermissionUtils.hasSelfPermissions(this, permissions)) {
                handler.onGranted();
            } else {
                mHandler = handler;
                ActivityCompat.requestPermissions(this, new String[]{permissions} , requesrCode);
            }
        }
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (mHandler == null) return;
            if (PermissionUtils.checkPermissions(grantResults)) {
                mHandler.onGranted();
            } else {
                if (!PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {
                    if (!mHandler.onNeverRequest()) {
                        Toast.makeText(this, "权限已被拒绝,请在设置-应用-权限中打开", Toast.LENGTH_SHORT).show();
                    }
                } else {
                    mHandler.onDenied();
                }
            }
        }
        public abstract class PermissionHandler {
            //权限通过
            public abstract void onGranted();
            //权限拒绝
            public void onDenied() {
            }
            //不再询问
            public boolean onNeverRequest() {
                return false;
            }
        }
    }

    测试:

    public class MainActivity extends BasePermissionActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
        }
        private void init() {
            request1();
        }
        private void request1() {
            requestPermission(new String[]{Manifest.permission.CAMERA}, new PermissionHandler() {
                @Override
                public void onGranted() {
                    Intent intent = new Intent(); //调用照相机
                    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
                    startActivity(intent);
                }
                @Override
                public void onDenied() {
                    Toast.makeText(MainActivity.this, "拒绝", Toast.LENGTH_SHORT).show();
                }
            });
        }
        private void request2() {
            requestPermission(new String[]{Manifest.permission.CAMERA}, new PermissionHandler() {
                @Override
                public void onGranted() {
                    Intent intent = new Intent(); //调用照相机
                    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
                    startActivity(intent);
                }
                @Override
                public void onDenied() {
                    Toast.makeText(MainActivity.this, "拒绝", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    代码:

    最后附上鸿洋封装的比较好的库:

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论