Android M 运行时权限

Android M 新的运行时权限开发者须要知道的一切

发表于 2015-08-30    |   分类于 android那些事儿    |   14条评论

android M 的名字官方刚发布不久,最终正式版即未来临!
android在不断发展,最近的更新 M 很是不一样,一些主要的变化例如运行时权限将有颠覆性影响。惊讶的是android社区鲜有谈论这事儿,尽管这事很重要或许在不远的未来会引起很严重的问题。
这是今天我写这篇博客的缘由。这里有一切关于android运行时权限你须要知道的,包括如何在代码中实现。如今亡羊补牢还不晚。 android

新运行时权限

android的权限系统一直是首要的安全概念,由于这些权限只在安装的时候被询问一次。一旦安装了,app能够在用户绝不知晓的状况下访问权限内的全部东西。
难怪一些坏蛋利用这个缺陷恶意收集用户数据用来作坏事了!
android小组也知道这事儿。7年了!权限系统终于被从新设计了。在android6.0棉花糖,app将不会在安装的时候授予权限。取而代之的是,app不得不在运行时一个一个询问用户授予权限。
git

注意权限询问对话框不会本身弹出来。开发者不得不本身调用。若是开发者要调用的一些函数须要某权限而用户又拒绝受权的话,函数将抛出异常直接致使程序崩溃。 github

另外,用户也能够随时在设置里取消已经受权的权限。 api

你或许已经感受到背后生出一阵寒意。。。若是你是个android开发者,意味着要彻底改变你的程序逻辑。你不能像之前那样直接调用方法了,你不得不为每一个须要的地方检察权限,不然app就崩溃了!
是的。我不能哄你说这是简单的事儿。尽管这对用户来讲是好事,可是对开发者来讲就是噩梦。咱们不得不修改编码否则不论短时间仍是长远来看都是潜在的问题。
这个新的运行时权限仅当咱们设置targetSdkVersion to 23(这意味着你已经在23上测试经过了)才起做用,固然还要是M系统的手机。app在6.0以前的设备依然使用旧的权限系统。 安全

已经发布了的app会发生什么

新运行时权限可能已经让你开始恐慌了。“hey,伙计!我三年前发布的app可咋整呢。若是他被装到android 6.0上,个人app会崩溃吗?!?”
莫慌张,放轻松。android小队又不傻,确定考虑到了这状况。若是app的targetSdkVersion 低于 23,那将被认为app没有用23新权限测试过,那将被继续使用旧有规则:用户在安装的时候不得不接受全部权限,安装后app就有了那些权限咯! 网络

而后app像之前同样奔跑!注意,此时用户依然能够取消已经赞成的受权!用户取消受权时,android 6.0系统会警告,但这不妨碍用户取消受权。 app

问题又来了,这时候你的app崩溃吗?
善意的主把这事也告诉了android小组,当咱们在targetSdkVersion 低于23的app调用一个须要权限的函数时,这个权限若是被用户取消受权了的话,不抛出异常。可是他将啥都不干,结果致使函数返回值是null或者0. ide

别高兴的太早。尽管app不会调用这个函数时崩溃,返回值null或者0可能接下来依然致使崩溃。
好消息(至少目前看来)是这类取消权限的状况比较少,我相信不多用户这么搞。若是他们这么办了,后果自负咯。
但从长远看来,我相信仍是会有大量用户会关闭一些权限。咱们app不能再新设备完美运行这是不可接受的。
怎样让他完美运行呢,你最好修改代码支持最新的权限系统,并且我建议你马上着手搞起!
代码没有成功改成支持最新运行时权限的app,不要设置targetSdkVersion 23 发布,不然你就有麻烦了。只有当你测试过了,再改成targetSdkVersion 23 。
警告:如今你在android studio新建项目,targetSdkVersion 会自动设置为 23。若是你还没支持新运行时权限,我建议你首先把targetSdkVersion 降级到22 函数

PROTECTION_NORMAL类权限

当用户安装或更新应用时,系统将授予应用所请求的属于 PROTECTION_NORMAL 的全部权限(安装时受权的一类基本权限)。这类权限包括: 测试

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
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.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission.BROADCAST_STICKY android.permission.CHANGE_NETWORK_STATE android.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.FLASHLIGHT android.permission.GET_ACCOUNTS android.permission.GET_PACKAGE_SIZE android.permission.INTERNET android.permission.KILL_BACKGROUND_PROCESSES android.permission.MODIFY_AUDIO_SETTINGS android.permission.NFC android.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.VIBRATE android.permission.WAKE_LOCK android.permission.WRITE_SYNC_SETTINGS com.android.alarm.permission.SET_ALARM com.android.launcher.permission.INSTALL_SHORTCUT com.android.launcher.permission.UNINSTALL_SHORTCUT

只须要在AndroidManifest.xml中简单声明这些权限就好,安装时就受权。不须要每次使用时都检查权限,并且用户不能取消以上受权。

让你的app支持新运行时权限

是时候让咱们的app支持新权限模型了,从设置compileSdkVersion and targetSdkVersion为 23开始吧.

1 2 3 4 5 6 7 8 9
android {  compileSdkVersion 23  ...    defaultConfig {  ...  targetSdkVersion 23  ...  }

例子,我想用一下方法添加联系人。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
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, set up a new raw contact.  ContentProviderOperation.Builder op =  ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)  .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)  .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);  operations.add(op.build());    // Next, set the 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 permissions sample");  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, "Could not add a new contact: " + e.getMessage());  } }

上面代码须要WRITE_CONTACTS权限。若是不询问受权,app就崩了。
下一步像之前同样在AndroidManifest.xml添加声明权限。

1
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>

下一步,不得再也不写个方法检查有没有权限。若是没有弹个对话框询问用户受权。而后你才能够下一步建立联系人。
权限被分组了,以下表:

同一组的任何一个权限被受权了,其余权限也自动被受权。例如,一旦WRITE_CONTACTS被受权了,app也有READ_CONTACTS和GET_ACCOUNTS了。
源码中被用来检查和请求权限的方法分别是Activity的checkSelfPermission和requestPermissions。这些方法api23引入。

1 2 3 4 5 6 7 8 9 10 11
final private int REQUEST_CODE_ASK_PERMISSIONS = 123;   private void insertDummyContactWrapper() {  int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);  if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {  requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},  REQUEST_CODE_ASK_PERMISSIONS);  return;  }  insertDummyContact(); }

若是已有权限,insertDummyContact()会执行。不然,requestPermissions被执行来弹出请求受权对话框,以下:

不论用户赞成仍是拒绝,activity的onRequestPermissionsResult会被回调来通知结果(经过第三个参数),grantResults,以下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {  switch (requestCode) {  case REQUEST_CODE_ASK_PERMISSIONS:  if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // Permission Granted  insertDummyContact();  } else {  // Permission Denied  Toast.makeText(MainActivity.this, "WRITE_CONTACTS Denied", Toast.LENGTH_SHORT)  .show();  }  break;  default:  super.onRequestPermissionsResult(requestCode, permissions, grantResults);  } }

这就是新权限模型工做过程。代码真复杂可是只能去习惯它。。。为了让app很好兼容新权限模型,你不得不用以上相似方法处理全部须要的状况。
若是你想捶墙,如今是时候了。。。

处理 “再也不提醒”

若是用户拒绝某受权。下一次弹框,用户会有一个“再也不提醒”的选项的来防止app之后继续请求受权。

若是这个选项在拒绝受权前被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,结果就是,app啥都不干。
这将是不好的用户体验,用户作了操做却得不到响应。这种状况须要好好处理一下。在请求requestPermissions前,咱们须要检查是否须要展现请求权限的提示经过activity的shouldShowRequestPermissionRationale,代码以下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
final private int REQUEST_CODE_ASK_PERMISSIONS = 123;   private void insertDummyContactWrapper() {  int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);  if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {  if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {  showMessageOKCancel("You need to allow access to Contacts",  new DialogInterface.OnClickListener() {  @Override  public void onClick(DialogInterface dialog, int which) {  requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},  REQUEST_CODE_ASK_PERMISSIONS);  }  });  return;  }  requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},  REQUEST_CODE_ASK_PERMISSIONS);  return;  }  insertDummyContact(); }   private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {  new AlertDialog.Builder(MainActivity.this)  .setMessage(message)  .setPositiveButton("OK", okListener)  .setNegativeButton("Cancel", null)  .create()  .show(); }

当一个权限第一次被请求和用户标记过再也不提醒的时候,咱们写的对话框被展现。
后一种状况,onRequestPermissionsResult 会收到PERMISSION_DENIED ,系统询问对话框不展现。

搞定!

一次请求多个权限

固然了有时候须要好多权限,能够用上面方法一次请求多个权限。不要忘了为每一个权限检查“再也不提醒”的设置。
修改后的代码:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;   private void insertDummyContactWrapper() {  List<String> permissionsNeeded = new ArrayList<String>();    final List<String> permissionsList = new ArrayList<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 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;  }    insertDummyContact(); }   private boolean addPermission(List<String> permissionsList, String permission) {  if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {  permissionsList.add(permission);  // Check for Rationale Option  if (!shouldShowRequestPermissionRationale(permission))  return false;  }  return true; }

若是全部权限被受权,依然回调onRequestPermissionsResult,我用hashmap让代码整洁便于阅读。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
@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以前就不行了,由于没有那些方法。
粗暴的方法是检查版本

1 2 3 4 5
if (Build.VERSION.SDK_INT >= 23) {  // Marshmallow+ } else {  // Pre-Marshmallow }

可是太复杂,我建议用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新建工程时,不要忘了修改!

说一下代码修改。这是大事,若是代码结构被设计的不够好,你须要一些很蛋疼的重构。每一个app都要被修正。如上所说,咱们没的选择。。。
列出全部你须要请求的权限全部情形,若是A被受权,B被拒绝,会发生什么。blah,blah。
祝重构顺利。把它列为你须要作的大事,从如今就开始着手作,以保证M正式发布的时候没有问题。
但愿本文对你有用,快乐编码!艹

译文来自 http://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en

相关文章
相关标签/搜索