新运行时权限
android的权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次。一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西。
注意权限询问对话框不会自己弹出来。开发者不得不自己调用。如果开发者要调用的一些函数需要某权限而用户又拒绝授权的话,函数将抛出异常直接导致程序崩溃。
另外,用户也可以随时在设置里取消已经授权的权限。
你或许已经感觉到背后生出一阵寒意。。。如果你是个android开发者,意味着要完全改变你的程序逻辑。你不能像以前那样直接调用方法了,你不得不为每个需要的地方检察权限,否则app就崩溃了!
已经发布了的app会发生什么
新运行时权限可能已经让你开始恐慌了。“hey,伙计!我三年前发布的app可咋整呢。如果他被装到android 6.0上,我的app会崩溃吗?!?”
然后app像以前一样奔跑!注意,此时用户依然可以取消已经同意的授权!用户取消授权时,android 6.0系统会警告,但这不妨碍用户取消授权。
问题又来了,这时候你的app崩溃吗?
别高兴的太早。尽管app不会调用这个函数时崩溃,返回值null或者0可能接下来依然导致崩溃。
PROTECTION_NORMAL类权限
当用户安装或更新应用时,系统将授予应用所请求的属于 PROTECTION_NORMAL 的所有权限(安装时授权的一类基本权限)。这类权限包括:
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS android.permission.ACCESS_NETWORK_STATE android.permission.ACCESS_NOTIFICATION_POLICY android.permission.ACCESS_WIFI_STATE android.permission.ACCESS_WIMAX_STATE android.permission.BLUETOOTHandroid.permission.BLUETOOTH_ADMIN android.permission.BROADCAST_STICKY android.permission.CHANGE.permission.CHANGE_WIFI_MULTICAST_STATE android.permission.CHANGE_WIFI_STATE android.permission.CHANGE_WIMAX_STATE android.permission.DISABLE_KEYGUARD android.permission.EXPAND_STATUS_BAR android.permission.FLASHLIGHTandroid.permission.GET_ACCOUNTS android.permission.GET_PACKAGE_SIZE android.permission.INTERNETandroid.permission.KILL_BACKGROUND_PROCESSES android.permission.MODIFY_AUDIO_SETTINGS android.permission.NFCandroid.permission.READ_SYNC_SETTINGS android.permission.READ_SYNC_STATS android.permission.RECEIVE_BOOT_COMPLETED android.permission.REORDER_TASKS android.permission.REQUEST_INSTALL_PACKAGES android.permission.SET_TIME_ZONE android.permission.SET_WALLPAPER android.permission.SET_WALLPAPER_HINTS android.permission.SUBSCRIBED_FEEDS_READ android.permission.TRANSMIT_IR android.permission.USE_FINGERPRINT android.permission.VIBRATEandroid.permission.WAKE_LOCK android.permission.WRITE_SYNC_SETTINGScom.android.alarm.permission.SET_ALARMcom.android.launcher.permission.INSTALL_SHORTCUTcom.android.launcher.permission.UNINSTALL_SHORTCUT
只需要在AndroidManifest.xml中简单声明这些权限就好,安装时就授权。不需要每次使用时都检查权限,而且用户不能取消以上授权。
让你的app支持新运行时权限
是时候让我们的app支持新权限模型了,从设置compileSdkVersion
and
targetSdkVersion
为 23开始吧.
android { compileSdkVersion23...defaultConfig {...targetSdkVersion23...}
例子,我想用一下方法添加联系人。
private static final String TAG ="Contacts";private void insertDummyContact { // Two operations are needed to insert a new contact. ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(2);// First,setup a new raw contact. ContentProviderOperation.Builderop = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null).withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);operations.add(op.build);// Next,setthe name for the contact. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,0).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,"__DUMMY CONTACT from runtime permissionssample");operations.add(op.build);// Apply the operations. ContentResolver resolver = getContentResolver;try { resolver.applyBatch(ContactsContract.AUTHORITY, operations);} catch (RemoteException e) { Log.d(TAG,"Could not add a new contact: "+ e.getMessage);} catch (OperationApplicationException e) { Log.d(TAG,+ e.getMessage);} }
WRITE_CONTACTS
权限。如果不询问授权,app就崩了。
AndroidManifest.xml
添加声明权限。
<uses-permissionandroid:name="android.permission.WRITE_CONTACTS"/>
下一步,不得不再写个方法检查有没有权限。如果没有弹个对话框询问用户授权。然后你才可以下一步创建联系人。
同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦
WRITE_CONTACTS
被授权了,app也有
READ_CONTACTS
和
GET_ACCOUNTS
了。
checkSelfPermission
和
requestPermissions
。这些方法api23引入。
finalprivateintREQUEST_CODE_ASK_PERMISSIONS =123;privatevoidinsertDummyContactWrapper{inthasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);if(hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) { requestPermissions(newString {Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_ASK_PERMISSIONS);return; } insertDummyContact; }
如果已有权限,
insertDummyContact
会执行。否则,
不论用户同意还是拒绝,activity的
onRequestPermissionsResult
会被回调来通知结果(通过第三个参数),
grantResults
,如下:
@OverridepublicvoidonRequestPermissionsResult(intrequestCode, String permissions,int grantResults) {switch(requestCode) {caseREQUEST_CODE_ASK_PERMISSIONS:if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {// Permission GrantedinsertDummyContact; }else{// Permission DeniedToast.makeText(MainActivity.this"WRITE_CONTACTS Denied", Toast.LENGTH_SHORT) .show; }break;default:super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
这就是新权限模型工作过程。代码真复杂但是只能去习惯它。。。为了让app很好兼容新权限模型,你不得不用以上类似方法处理所有需要的情况。
处理 “不再提醒”
如果用户拒绝某授权。下一次弹框,用户会有一个“不再提醒”的选项的来防止app以后继续请求授权。
如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求时,对话框就不弹出来了,结果就是,app啥都不干。
shouldShowRequestPermissionRationale
,代码如下:
finalprivateintREQUEST_CODE_ASK_PERMISSIONS =123;privatevoidinsertDummyContactWrapper{inthasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);if(hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {if(!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) { showMessageOKCancel("You need to allow access to Contacts"newDialogInterface.OnClickListener {@OverridepublicvoidonClick(DialogInterface dialog,intwhich) { requestPermissions(newString {Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_ASK_PERMISSIONS); } });return; } requestPermissions(newString {Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_ASK_PERMISSIONS);return; } insertDummyContact; }privatevoidshowMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {newAlertDialog.Builder(MainActivity.this) .setMessage(message) .setPositiveButton("OK", okListener) .setNegativeButton("Cancel"null) .create .show; }
当一个权限第一次被请求和用户标记过不再提醒的时候,我们写的对话框被展示。
搞定!
一次请求多个权限
当然了有时候需要好多权限,可以用上面方法一次请求多个权限。不要忘了为每个权限检查“不再提醒”的设置。
finalprivateintREQUEST_CODE_ASK_MULTIPLE_PERMISSIONS =124;privatevoidinsertDummyContactWrapper { List<String> permissionsNeeded =newArrayList<String>;finalList<String> permissionsList =newArrayList<String>;if(!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION)) permissionsNeeded.add("GPS");if(!addPermission(permissionsList, Manifest.permission.READ_CONTACTS)) permissionsNeeded.add("Read Contacts");if(!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS)) permissionsNeeded.add("Write Contacts");if(permissionsList.size >0) {if(permissionsNeeded.size >0) {// Need RationaleString message ="You need to grant access to "+ permissionsNeeded.get(0);for(inti =1; i < permissionsNeeded.size; i++) message = message +", "+ permissionsNeeded.get(i); showMessageOKCancel(message,newDialogInterface.OnClickListener {@OverridepublicvoidonClick(DialogInterface dialog,intwhich) { requestPermissions(permissionsList.toArray(newString[permissionsList.size]), REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); } });return; } requestPermissions(permissionsList.toArray(newString[permissionsList.size]), REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);return; } insertDummyContact; }privatebooleanaddPermission(List<String> permissionsList, String permission) {if(checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { permissionsList.add(permission);// Check for Rationale Optionif(!shouldShowRequestPermissionRationale(permission))returnfalse; }returntrue; }
如果所有权限被授权,依然回调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.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.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之前就不行了,因为没有那些方法。
if(Build.VERSION.SDK_INT >=23) {// Marshmallow+}else{// Pre-Marshmallow}
但是太复杂,我建议用v4兼容库,已对这个做过兼容,用这个方法代替:
privatevoidinsertDummyContactWrapper{inthasWriteContactsPermission = 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"newDialogInterface.OnClickListener {@OverridepublicvoidonClick(DialogInterface dialog,intwhich) { ActivityCompat.requestPermissions(MainActivity.thisnewString {Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_ASK_PERMISSIONS); } });return; } ActivityCompat.requestPermissions(MainActivity.thisnewString {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都要被修正。如上所说,我们没的选择。。。