新的权限获取方式除了要求像之前版本一样在AndroidManifest文件中静态申请之外,应用还需根据需要请求权限,方式采用向用户显示一个请求权限的对话框。这些被动态申请的权限可以在系统设置中被手动关闭。另外,对于类别为NORMAL的权限,仍然只需要在AndroidManifest文件中静态申请,系统安装时会直接获取,对于NORMAL权限下文有详细的说明。
PROTECTION_NORMAL类权限
当用户安装或更新应用时,系统将授予应用所请求的属于 PROTECTION_NORMAL 的所有权限(安装时授权的一类基本权限)。这类权限包括:
<code class="css"><span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.ACCESS_LOCATION_EXTRA_COMMANDS</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.ACCESS_NETWORK_STATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.ACCESS_NOTIFICATION_POLICY</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.ACCESS_WIFI_STATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.ACCESS_WIMAX_STATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.BLUETOOTH</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.BLUETOOTH_ADMIN</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.BROADCAST_STICKY</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.CHANGE_NETWORK_STATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.CHANGE_WIFI_MULTICAST_STATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.CHANGE_WIFI_STATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.CHANGE_WIMAX_STATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.DISABLE_KEYGUARD</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.EXPAND_STATUS_BAR</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.FLASHLIGHT</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.GET_ACCOUNTS</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.GET_PACKAGE_SIZE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.INTERNET</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.KILL_BACKGROUND_PROCESSES</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.MODIFY_AUDIO_SETTINGS</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.NFC</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.READ_SYNC_SETTINGS</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.READ_SYNC_STATS</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.RECEIVE_BOOT_COMPLETED</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.REORDER_TASKS</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.REQUEST_INSTALL_PACKAGES</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.SET_TIME_ZONE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.SET_WALLPAPER</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.SET_WALLPAPER_HINTS</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.SUBSCRIBED_FEEDS_READ</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.TRANSMIT_IR</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.USE_FINGERPRINT</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.VIBRATE</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.WAKE_LOCK</span>
<span class="hljs-tag">android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.WRITE_SYNC_SETTINGS</span>
<span class="hljs-tag">com</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.alarm</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.SET_ALARM</span>
<span class="hljs-tag">com</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.launcher</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.INSTALL_SHORTCUT</span>
<span class="hljs-tag">com</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.android</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.launcher</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.permission</span><span class="hljs-class" style="color: rgb(155, 112, 63);">.UNINSTALL_SHORTCUT</span></code>
只需要在AndroidManifest.xml中简单声明这些权限就好,安装时就授权。不需要每次使用时都检查权限,而且用户不能取消以上授权。
权限组
新的权限模型中还提出了一个权限组的概念,可以简单理解为如果一个权限组内的某个权限被获取了,那么这个组中剩余的权限也会被自动获取。例如:Android.permission-group.CALENDAR
中的android.permission.WRITE_CALENDAR
权限被获取,那么应用会自动获取android.permission.READ_CALENDAR
权限。
一般动态获取方法
tip1
判定是否有权限:checkSelfPermission()
tip2
如果没有权限,弹出dialog给用户选择:requestPermission(),第二个参数code与onRequestPermissionResult()方法中的code对应
- if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(
- new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
- REQUEST_CODE_ASK_PERMISSON);
- }
tip3
判断用户是否确认了权限onRequestPermissionResult ()
- public void onRequestPermissionsResult(int requestCode,
- String permissions[], int[] grantResults) {
- switch (requestCode) {
- case REQUEST_CODE_ASK_PERMISSON:
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // Permission Granted
- } else {
- // Permission Denied
- }
- break;
- default:
- super.onRequestPermissionsResult(requestCode, permissions,
- grantResults);
- }
- }
tip4
在弹出权限选择的对话框前给用户show一个dialog,用于引导用户进行选择。shouldShowRequestPermissionRationale()
- if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- // Explain to the user why we need to read the contacts
- showMessage("请允许应用对SD卡进行读写操作",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- requestPermissions(
- new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
- REQUEST_CODE_ASK_PERMISSIONS);
- }
- });
- return;
- }
- requestPermissions(
- new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
- REQUEST_CODE_ASK_PERMISSIONS);
- }
- private void showMessage(String message,
- DialogInterface.OnClickListener okListener) {
- new AlertDialog.Builder(MainActivity.this).setMessage(message)
- .setPositiveButton("OK", okListener).create().show();
- }
tip5
如果用户在选择权限对话框拒绝了某个权限的申请,那么再次申请该权限时会多出一个“不再询问”的checkbox,如果勾选,那么即便程序再调用requestPermission(),对话框也不会再弹出了。
tip6
一次性处理多个权限申请
- private void requestPermissions() {
- List<String> permissionsNeeded = new ArrayList<String>();
- final List<String> permissionsList = new ArrayList<String>();
- if (!addPermission(permissionsList, Manifest.permission.READ_PHONE_STATE))
- permissionsNeeded.add("Phone State");
- if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE))
- permissionsNeeded.add("Write SDcard");
- if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS))
- permissionsNeeded.add("Write Contacts");
- if (permissionsList.size() > 0) {
- if (permissionsNeeded.size() > 0) {
- // Need Rationale
- String message = "You need to grant access to " + permissionsNeeded.get(0);
- for (int i = 1; i < permissionsNeeded.size(); i++)
- message = message + ", " + permissionsNeeded.get(i);
- showMessageOKCancel(message,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
- REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
- }
- });
- return;
- }
- requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
- REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
- return;
- }
- }
- private boolean addPermission(List<String> permissionsList, String permission) {
- if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
- permissionsList.add(permission);
- if (!shouldShowRequestPermissionRationale(permission)){
- return false;
- }
- }
- return true;
- }
如果所有权限被授权,依然回调onRequestPermissionsResult,我用hashmap让代码整洁便于阅读。
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- switch (requestCode) {
- case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
- {
- Map<String, Integer> perms = new HashMap<String, Integer>();
- // Initial
- perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
- perms.put(Manifest.permission.READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
- perms.put(Manifest.permission.WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
- // Fill with results
- for (int i = 0; i < permissions.length; i++)
- perms.put(permissions[i], grantResults[i]);
- // Check for ACCESS_FINE_LOCATION
- if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
- && perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
- && perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
- // All Permissions Granted
- insertDummyContact();
- } else {
- // Permission Denied
- Toast.makeText(MainActivity.this, "Some Permission is Denied", Toast.LENGTH_SHORT)
- .show();
- }
- }
- break;
- default:
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- }
用兼容库使代码兼容旧版
以上代码在android 6.0以上运行没问题,但是23 api之前就不行了,因为没有那些方法。
粗暴的方法是检查版本
<code class="cpp"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (Build.VERSION.SDK_INT >= <span class="hljs-number" style="color: rgb(0, 102, 102);">23</span>) {
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// Marshmallow+</span>
} <span class="hljs-keyword" style="color: rgb(0, 0, 136);">else</span> {
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// Pre-Marshmallow</span>
}</code>
但是太复杂,我建议用v4兼容库,已对这个做过兼容,用这个方法代替:
- ContextCompat.checkSelfPermission()
被授权函数返回PERMISSION_GRANTED,否则返回PERMISSION_DENIED ,在所有版本都是如此。 - ActivityCompat.requestPermissions()
这个方法在M之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED 。 - ActivityCompat.shouldShowRequestPermissionRationale()
在M之前版本调用,永远返回false。
用v4包的这三方法,完美兼容所有版本!这个方法需要额外的参数,Context or Activity。别的就没啥特别的了。下面是代码:
- private void insertDummyContactWrapper() {
- int hasWriteContactsPermission = ContextCompat.checkSelfPermission(MainActivity.this,
- Manifest.permission.WRITE_CONTACTS);
- if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
- if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
- Manifest.permission.WRITE_CONTACTS)) {
- showMessageOKCancel("You need to allow access to Contacts",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- ActivityCompat.requestPermissions(MainActivity.this,
- new String[] {Manifest.permission.WRITE_CONTACTS},
- REQUEST_CODE_ASK_PERMISSIONS);
- }
- });
- return;
- }
- ActivityCompat.requestPermissions(MainActivity.this,
- new String[] {Manifest.permission.WRITE_CONTACTS},
- REQUEST_CODE_ASK_PERMISSIONS);
- return;
- }
- insertDummyContact();
- }
后两个方法,我们也可以在Fragment中使用,用v13兼容包:FragmentCompat.requestPermissions() and FragmentCompat.shouldShowRequestPermissionRationale().和activity效果一样。
第三方库简化代码
以上代码真尼玛复杂。为解决这事,有许多第三方库已经问世了,真屌真有速度。我试了很多最终找到了个满意的hotchemi's PermissionsDispatcher。
他和我上面做的一样,只是简化了代码。灵活易扩展,试一下吧。如果不满足你可以找些其他的。
如果我的app还开着呢,权限被撤销了,会发生生么
权限随时可以被撤销。
当app开着的时候被撤消了会发生什么呢?我试过了发现这时app会突然终止 terminated。app中的一切都被简单粗暴的停止了,因为terminated!对我来说这可以理解,因为系统如果允许它继续运行(没有某权限),这会召唤弗雷迪到我的噩梦里。或许更糟...
结论建议
我相信你对新权限模型已经有了清晰的认识。我相信你也意识到了问题的严峻。
但是你没得选择。新运行时权限已经在棉花糖中被使用了。我们没有退路。我们现在唯一能做的就是保证app适配新权限模型.
欣慰的是只有少数权限需要运行时权限模型。大多数常用的权限,例如,网络访问,属于Normal Permission 在安装时自动会授权,当然你要声明,以后无需检查。因此,只有少部分代码你需要修改。
两个建议:
1.严肃对待新权限模型
2.如果你代码没支持新权限,不要设置targetSdkVersion 23 。尤其是当你在Studio新建工程时,不要忘了修改!
参考:http://www.jianshu.com/p/e1ab1a179fbb