分类
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;
}
原理网上有很多,这里不详细探讨。这个方法有俩个问题:
- 4.3以下不能用,因为AppOpsManager是API19新增的类。
- 不兼容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);
}
其余特殊权限
其余特殊权限如悬浮窗、无障碍辅助都有各自的方法,需要专程适配,后续会逐渐补充。
网友评论