美文网首页
Android权限适配(一)

Android权限适配(一)

作者: Parallel_Lines | 来源:发表于2018-08-10 10:16 被阅读0次

    分类

    Android权限目前分为三种:正常权限、危险权限、特殊权限

    正常权限

    直接在AndroidManifest中配置即可获得的权限。大部分权限都归于此。

    危险权限

    危险权限,6.0之后将部分权限定义于此。
    危险权限不仅需要需要在AndroidManifest中配置,还需要在使用前check是否真正拥有权限,以动态申请。

    危险权限在6.0系统以上的手机中并不是不申请就一定没有权限,部分手机还是默认提供权限的。但是为了系统的兼容性,对于危险权限最好还是要先check。

    以下是目前Android定义的危险权限:

    危险权限有权限组的概念,即只要权限组中的任意一条获得了权限,该权限组就拥有了该权限。

    编号 权限组 权限
    0 CALENDAR READ_CALENDAR
    WRITE_CALENDAR
    1 CAMERA CAMERA
    2 CONTACTS READ_CONTACTS
    WRITE_CONTACTS
    GET_ACCOUNTS
    3 LOCATION ACCESS_FINE_LOCATION
    ACCESS_COARSE_LOCATION
    4 MICROPHONE RECORD_AUDIO
    5 PHONE READ_PHONE_STATE
    CALL_PHONE
    READ_CALL_LOG
    WRITE_CALL_LOG
    ADD_VOICEMAIL
    USE_SIP
    PROCESS_OUTGOING_CALLS
    6 SENSORS BODY_SENSORS
    7 SMS SEND_SMS
    RECEIVE_SMS
    READ_SMS
    RECEIVE_WAP_PUSH
    RECEIVE_MMS
    8 STORAGE READ_EXTERNAL_STORAGE
    WRITE_EXTERNAL_STORAGE

    总得来说,6.0及以上系统,对于危险权限,都要经过如下步骤。下面以文件读写权限为例(仅供参考):

    1. check是否有权限

    int permissionCheck = ContextCompat.checkSelfPermission(this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE);
    if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
        //有权限
    } else {
        //没权限
    }
    

    2. 解释权限用途

    用于向用户解释为什么需要这项权限。具体的使用时机在:

    第一次请求权限,用户拒绝;第二次请求时,该方法返回true;之后的请求,只要用户上一次点击了拒绝,而没有勾选不再询问,该方法返回true。

    ActivityCompat.shouldShowRequestPermissionRationale(Activity activity, String permission)
    

    3. 请求权限

    使用如下方法获取权限:

    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
    

    权限的获取结果,将会回调如下方法:

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

    ActivityCompat.requestPermissions( )在获得权限后反复调用,不会再次弹窗,但是会回调onRequestPermissionsResult( )。

    对于危险权限的使用,都要走以上流程。

    读写权限虽然是危险权限,但并不是只要读写就要配置这俩个权限:

    内置存储:
    Context.getFileDir():/data/data/应用包名/files/
    Context.getCacheDir():/data/data/应用包名/cache/
    内置存储读写不需要配置任何权限。

    外置sd卡:
    Context.getExternalFilesDir():SDCard/Android/data/应用包名/files/
    Context.getExternalCacheDir():SDCard/Android/data/应用包名/cache/
    API<19需要配置权限,API>=19不需要配置权限
    即对于配置了读写权限的app,使用"SDCard/Android/data/应用包名/"读写操作时,4.4系统以下因为配置了权限而能正常读写,4.4及以上系统因为不需要权限亦能正常读写。但是为了不配置多余的权限,建议如下写:
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="18"/>

    以上文件夹,当App卸载时会被系统自动删除。
    其余sd卡位置,6.0以上需要动态申请读写权限。

    读写权限只是为了限制App污染用户sd卡,对于App而言,读写操作是正常而必要的,故划分出以上几个文件,使App在无需权限的情况下能正常存储必要的文件,且能在被卸载时自动删除这些文件,以达到保护用户sd卡的目的。

    特殊权限

    诸如通知栏、自启动、悬浮窗和无障碍辅助。

    严格的来讲,这部分内容不属于Android权限部分。因为它们不需要在App中配置,而是需要用户到系统对应的设置页面打开开关。
    但是实际开发中,确实有这样的需求:检测能不能弹出通知,不能则提示用户,或直接跳转到对应页面,引导用户打开开关。
    故把这部分纳入权限的范畴。

    下面以通知栏权限为例,详述从检测通知栏权限、到获取弹出通知权限、再到弹出通知的过程。

    通知栏适配

    通过测试各版本、rom手机,总结出如下流程(目前适配到Android8.1)

    获取是否有通知栏权限(是否能弹出通知)

    目前网上提供了俩种方法:

    一种是通过反射AppOpsManager的方式。

        public static boolean isNotificationEnabled(Context context) {
    
            AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            ApplicationInfo appInfo = context.getApplicationInfo();
            String pkg = context.getApplicationContext().getPackageName();
            int uid = appInfo.uid;
    
            Class appOpsClass = null;
            try {
                appOpsClass = Class.forName(AppOpsManager.class.getName());
                Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
                        String.class);
                Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
    
                int value = (Integer) opPostNotificationValue.get(Integer.class);
                return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
    
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return false;
        }
    

    原理网上有很多,这里不详细探讨。这个方法有俩个问题:

    1. 4.3以下不能用,因为AppOpsManager是API19新增的类。
    2. 不兼容8.0及以上。8.0以上系统始终返回true。

    第二种是Android官方推荐的方法,需要引入依赖如'com.android.support:appcompat-v7:26.1.0'

    NotificationManagerCompat.from(context).areNotificationsEnabled()
    

    实测这个方法兼容5.0及以上系统,但是对于5.0以下的系统,始终返回true。(5.0以下系统通知栏默认都是true,所以实际影响并不大)

    跳转App设置页面开启权限

    当没有权限时,需要引导用户跳转到本App的通知设置页面,一键打开通知权限。

    这里分别适配了4.4、5.0及以上、8.0及以上三种情况:

    不适配8.0,会报页面找不到的错误。

        protected void requestNotificationPermission() {
    
            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Intent intent = new Intent();
                intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
                intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
                startActivity(intent);
            } else if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Intent intent = new Intent();
                intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
                intent.putExtra("app_package", getPackageName());
                intent.putExtra("app_uid", getApplicationInfo().uid);
                startActivity(intent);
            } else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
                Intent intent = new Intent();
                intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                intent.addCategory(Intent.CATEGORY_DEFAULT);
                intent.setData(Uri.parse("package:" + getPackageName()));
                startActivity(intent);
            }
        }
    

    调用此方法,会跳转到类似的页面:

    通知栏设置页.png

    实测该方法兼容了大部分手机,但是仍然还是有部分手机出现找不到页面的情况,所以健壮的app不仅需要在跳转前判断intent是否可用,还需要制定无法跳转时的处理。这里的代码仅是核心代码,仅供参考。

    弹出通知

    弹出通知同样有适配问题。

    原因是Android8.0强制要求通知添加渠道号,否则无法弹出通知。

    下面是兼容代码:

        private void showNotification() {
    
            //创建跳转的intent
            Intent intent = new Intent(this, Main2Activity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            final NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            //创建自定义视图
            RemoteViews view = new RemoteViews(getPackageName(), R.layout.activity_main2);
    
            //适配安卓8.0的消息渠道
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel("1", "my_channel", NotificationManager.IMPORTANCE_HIGH);
                manager.createNotificationChannel(channel);
            }
    
            Notification notification = new NotificationCompat.Builder(this).setContent(view)
                    .setTicker("title") //通知首次出现在通知栏,带上升动画效果的
                    .setOngoing(true)//设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐) 或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)
                    .setSmallIcon(R.mipmap.ic_launcher)//设置通知小ICON
                    .setContentTitle("title")//设置通知栏标题
                    .setContentText("content")//设置通知栏显示内容
                    .setContentIntent(pendingIntent)//设置通知栏点击意图
                    .setChannelId("1")
                    .build();
    
            //弹出通知
            manager.notify(0, notification);
        }
    

    其余特殊权限

    其余特殊权限如悬浮窗、无障碍辅助都有各自的方法,需要专程适配,后续会逐渐补充。

    悬浮窗权限适配 & 无障碍辅助适配

    相关文章

      网友评论

          本文标题:Android权限适配(一)

          本文链接:https://www.haomeiwen.com/subject/jcckbftx.html