[尊重一点点努力,转载请注明出处http://blog.csdn.net/luochoudan/article/details/71076556]

我们都知道Android6.0以前权限的申请非常简单,只需要在mainfest声明所需的权限即可。而6.0以后,Android将权限的管理进一步严格化,它要求用户在使用某些敏感权限时,必须在mainfest中先声明之后再动态申请。在一定程度上约束了应用对权限的索取,保证了用户的数据隐私;当然,反过来也增加了开发者的难度。

想必也都知道,6.0将权限粗分成了两种Normal和Dangerous。所谓的Normal就是一些不需要用户授权就直接拥有的权限,这类权限往往不涉及用户隐私数据,比如访问网络/wifi等,你从来没有见过一个应用弹出对话框让用户授权访问网络的吧;另外一种就是Dangerous,可以想象,这种权限一般都会访问到用户的私人数据,比如联系人、存储卡等;至于具体权限的分类所属,请查看相关文档,这里不再赘述。

每一个开发者在初次接触权限问题时,一定头大过,也一定都会思考我什么时间申请、我需不需要申请、怎么申请、申请结果怎么处理诸如此类问题。在Activity长达7000多行的源码中,关于权限的部分并不是很多。除去两个分发权限结果的私有函数dispatchRequestPermissionsResult和dispatchRequestPermissionsResultToFragment之外,真正与我们直接相关的api其实只有3个,在加上ContextCompat中有一个检查权限的函数,一共是4个。权限问题,归根结底就是这四个函数如何使用的问题,巧合的是,这四个函数也正好解决了前面所说的几个问题。

  • 如何检查权限?
    权限的检查很简单,ContextCompat(它的子类ActivityCompat中也有)中提供了一个静态函数checkSelfPermission(context, permission),它会返回当前Activity是否拥有相关权限,通过比对PackageManager相关常量,就可以做出判定;举个例子,存储卡权限就可以这样写:
if(ContextCompat.checkPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED) {  //没有权限,需要在此处申请了} else {  //已经拥有权限}
  • 什么时间申请?
    这个很好解决,就在你需要这个权限的时候申请。比如说我的应用要支持语音消息,很显然需要麦克风权限。一般的做法是,当你在Activty/Fragment发送语音时(而不是进入Activty/Fragment时),检查是否拥有麦克风权限,没有的话必须申请,否则不能正常使用。

  • 如何申请?
    申请的关键是对requestPermissions(Activity activity,String[] permissions, int requestCode)的使用,三个参数分别是申请的activity、申请的权限数组、请求码(必须>=0)。虽然Activity(Fragment)和ActivityCompat中都提供了这个函数,但前者是final型的,后者却是static型的,应用较多的是ActivityCompat中的这个(两个没什么区别,static型的用着更方便封装)。配合着上 main的权限检查,代码就变成了这样:

int requestCode = 1if(ContextCompat.checkPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED) {    ActivityCompat.requestPermission(this, new String[{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);} else {    //已经拥有权限}
  • 申请结果怎么处理?
    权限的申请已经发起,但是否授予却掌握在用户手里。应用必须要妥善处理用户可能的两种操作:同意、拒绝。好在强大的Android已经帮我们封装好了,Activity(Fragment)中提供了onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults)这样一个回调函数来处理用户的授权结果。3个参数分表表示请求码(就是requestPermission中传进去的那个)、一次申请的多个权限、对应的授予结果可以这样写:
@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    if (requestCode == 1) {       if (grantResults[0] ==            PackageManager.PERMISSION_GRANTED) {           Log.d(TAG, "onRequestPermissionsResult: 已经有权限");       } else {           Log.d(TAG, "onRequestPermissionsResult: 去申请");         }    }}

权限的申请及结果处理都有了,但是我们只用了3个函数,还有一个shouldShowRequestPermissionRationale(String permission)
,api中说它是检查是否该给用户一个提示,来解释一下为什么我需要这个权限。所以大多数权限相关的博客(包括鸿洋大神、还有)中都这样使用它:

@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {     if (requestCode == 0) {        if (grantResults[0] ==                PackageManager.PERMISSION_GRANTED) {            Log.d(TAG, "onRequestPermissionsResult: 已经有权限");        } else {                if(shouldShowRequestPermissionRationale(permissions[0]){              //弹框给用户一个解释           } else {              //接着申请              requestPermission(this,                          new String[{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);           }           //if(shouldShowRequestPermissionRationale(permissions[0])) {           // requestPermission(this,            // new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);            //} else {           //           //}         }    }}

上面提供了两种写法:注释掉的和未注释的。遗憾的是,这两种写法都让人崩溃。注释掉的写法,显然是用错了shouldShowRequestPermissionRationale,造成的后果是,如果用户如果不勾选“不再询问”,权限申请的弹框会不断弹出,让人抓狂。没注释的写法也是错的,分析之前先来看两张图。

图1 权限弹窗第一次出现的样子

图2 第一次拒绝之后权限弹窗再次出现时的样子

测试发现,不管是图1还是图2,只要只点击deny,shouldShowRequestPermissionRationale返回的都是true;如果图2中勾选了“don’t ask again”,并点击了deny时shouldShowRequestPermissionRationale才会返回false。

由上面的结论再来看未注释的代码,发现如果勾选了“don’t ask again”,并且deny了,shouldShowRequestPermissionRationale此时返回的是false。程序会进入else分支重新requestPermission,而因为勾选了“don’t ask again”导致权限弹窗不会再出现,造成的后果是权限直接获取失败,不幸的是shouldShowRequestPermissionRationale一直返回false,又会进行下一次requestPermission。就这样,不断地循环,一直到crash。

事实上,个人觉得这个函数的设计比较鸡肋,它名义上是来判断是否需要给用户一个权限申请的说明。可实际情况上,当我们动态地向用户申请某个权限时,只要用户拒绝,不管shouldShowRequestPermissionRationale返回什么,我们都必须要给出一个提醒(一般是来告诉用户为什么要开这个权限,怎样开);如果不提醒用户,用户很可能不知道自己曾经拒绝了某个权限,进而导致无法正常使用app;大多数应用包括微信等,都是采用的这种策略。那代码该如何展示呢?

public class Permission extends AppCompatActivity {    private static final String TAG = "MainActivity";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        if (checkPermission(this, Manifest.permission.READ_CONTACTS) !=             PackageManager.PERMISSION_GRANTED) {           requestPermission(this, new String[]{Manifest.permission.READ_CONTACTS}, 0);        }    }    private int checkPermission(Context context, String permission) {        return ContextCompat.checkSelfPermission(context, permission);    }    private void requestPermission(Context context, String[] permissions, int         requestCode) {        ActivityCompat.requestPermissions((Activity) context, permissions,         requestCode);    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[]        permissions,@NonNull int[] grantResults) {        if (requestCode == 0) {            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {                Log.d(TAG, "onRequestPermissionsResult: 已经有权限");            } else {                createDialog();            }        }    }    private void createDialog() {        final TextView hint = new TextView(this);        hint.setText("你可以在设置中打开联系人权限");        AlertDialog.Builder builder = new AlertDialog.Builder(this);        builder.setTitle("权限申 请").setIcon(android.R.drawable.ic_dialog_info)               .setView(hint)               .setNegativeButton("取消", null);        builder.setPositiveButton("确定", (dialog, which) -> {            Log.d(TAG, "createDialog: 去设置");            //一般是打开手机设置        });        builder.show();    }}

当然,AndroidMainfest中的权限声明也不能少。这里只是给了个示例,完全可以按照自己的喜好来进行相关封装;或者使用注解等方法来让代码变得更简单。最后,强力吐槽一下这个代码编辑器,已经尽力把它做到最美观了……

关于权限处理,这里总结几点:
1 权限+系统api,这两个任意一个都比较棘手;
2 需要多测试。不同手机可能对系统api进行了定制,导致手机的表现可能不尽相同,比如华为和小米。
3 权限弹窗是系统定制的,不可更改,基本每个品牌一个风格。。
4 一些重要权限,比如存储卡,一般使用了存储的应用都要满足“如果没有存储卡权限,应用就退出”的原则(微信就是这么干的),最好放在基类的onResume里申请(因为存储权限使用的地方太多了,不可能在每一处都动态申请,也是为什么“没有存储权限就退出”的原因)。别问为什么在onResume里面,因为用户可能会用的好好的,手痒去把权限给关了。。。
5 通过inetnt的方式打开手机上的某些应用,比如打开通讯录页面,不需要你自己去申请权限;因为你只是打开通讯录并没有读取通讯录数据;真正需要申请权限的是通讯录这个系统应用;
6 多动手试试,不要盲从。

有问题,欢迎留言.如果可能,将第一时间为你解答。

更多相关文章

  1. android实现仿QQ登陆界面的多账号保存
  2. 移动平台前端开发总结(针对iphone,Android等手机)
  3. Android(安卓)JNI配置及入门
  4. Android(安卓)init 启动过程分析
  5. [置顶] Android(安卓)进程常驻(5)----开机广播的简单守护以及总结
  6. android利用数据库实现搜索联想功能
  7. Android应用开发实例篇(2)-----挂接电震动
  8. Looper中的消息队列处理机制
  9. TopGeek:移动互联网时代,选择Android还是iOS?

随机推荐

  1. android API Demo之使用ViewFlipper制作
  2. How to build Android(安卓)Windows SDK
  3. android打电话程序
  4. ':app:transformClassesWithDexForDebug'
  5. Android锁屏监听
  6. android Edittext内容字体大小动态变化
  7. android 检查gps
  8. [Android] 备份手机上的超级终端、VIM
  9. Android使用SAX解析XML(4)
  10. Android聊天软件开发(基于网易云IM即时通