Android权限适配(一)

分类

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及以上系统因为不需要权限亦能正常读写。但是为了不配置多余的权限,建议如下写:
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权限适配(一))