转载请标明出处:

http://blog.csdn.net/lmj623565791/article/details/50709663

本文出自:【张鸿洋的博客】

一、概述

随着Android 6.0公布以及普及。我们开发人员所要应对的主要就是新版本号SDK带来的一些变化,首先关注的就是权限机制的变化。对于6.0的几个基本的变化,查看查看官网的这篇文章http://developer.android.com/intl/zh-cn/about/versions/marshmallow/android-6.0-changes.html。当中当然包括Runtime Permissions

ok。本篇文章目的之中的一个就是对执行时权限处理的一个介绍。以及对眼下权限相关的库的一些了解。

当然非常推荐阅读官网权限相关文章:

本文也是在上述文章基础上理解、实验以及封装。

二、执行时权限的变化及特点

对于6.0下面的权限及在安装的时候,依据权限声明产生一个权限列表,用户仅仅有在允许之后才干完毕app的安装。造成了我们想要使用某个app。就要默默忍受其一些不必要的权限(比方是个app都要訪问通讯录、短信等)。而在6.0以后,我们能够直接安装,当app须要我们授予不恰当的权限的时候,我们能够予以拒绝(比方:单机的象棋对战。请求訪问不论什么权限,我都是不允许的)。

当然你也能够在设置界面对每一个app的权限进行查看。以及对单个权限进行授权或者解除授权。

新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不须要用户进行授权的,比方手机震动、訪问网络等;还有一类是Dangerous Permission,通常是涉及到用户隐私的。须要用户进行授权,比方读取sdcard、訪问通讯录等。

  • Normal Permissions例如以下
ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
  • Dangerous Permissions:
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR group:android.permission-group.CAMERA
permission:android.permission.CAMERA group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS

能够通过adb shell pm list permissions -d -g进行查看。

看到上面的dangerous permissions,会发现一个问题,好像危急权限都是一组一组的。恩,没错。的确是这种。

那么有个问题:分组对我们的权限机制有什么影响吗?

的确是有影响的,假设app执行在Android 6.x的机器上,对于授权机制是这种。假设你申请某个危急的权限,假设你的app早已被用户授权了同一组的某个危急权限,那么系统会马上授权,而不须要用户去点击授权。比方你的app对READ_CONTACTS已经授权了,当你的app申请WRITE_CONTACTS时,系统会直接授权通过。

此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(ps:这个dialog是不能进行定制的)。

只是须要注意的是。不要对权限组过多的依赖,尽可能对每一个危急权限都进行正常流程的申请,由于在后期的版本号中这个权限组可能会产生变化。

三、相关API

好在执行时相关的API也比較简单,所以适配起来并不会非常痛苦。

API的解说就跟着申请权限步骤一起了:

  1. 在AndroidManifest文件里加入须要的权限。

    这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有声明的权限可能会导致程序崩溃。

  2. 检查权限

    if (ContextCompat.checkSelfPermission(thisActivity,
    Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {
    }else{
    //
    }

    这里涉及到一个API。ContextCompat.checkSelfPermission,主要用于检測某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED

    当返回DENIED就须要进行申请授权了。

  3. 申请授权

     ActivityCompat.requestPermissions(thisActivity,
    new String[]{Manifest.permission.READ_CONTACTS},
    MY_PERMISSIONS_REQUEST_READ_CONTACTS);

    该方法是异步的,第一个參数是Context;第二个參数是须要申请的权限的字符串数组。第三个參数为requestCode,主要用于回调的时候检測。

    能够从方法名requestPermissions以及第二个參数看出,是支持一次性申请多个权限的。系统会通过对话框逐一询问用户是否授权。

  4. 处理权限申请回调

    @Override
    public void onRequestPermissionsResult(int requestCode,
    String permissions[], int[] grantResults) {
    switch (requestCode) {
    case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
    // If request is cancelled, the result arrays are empty.
    if (grantResults.length > 0
    && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the
    // contacts-related task you need to do. } else { // permission denied, boo! Disable the
    // functionality that depends on this permission.
    }
    return;
    }
    }
    }

    ok,对于权限的申请结果。首先验证requestCode定位到你的申请。然后验证grantResults相应于申请的结果,这里的数组相应于申请时的第二个权限字符串数组。假设你同一时候申请两个权限。那么grantResults的length就为2,分别记录你两个权限的申请结果。假设申请成功,就能够做你的事情了~

当然,到此我们的权限申请的不走,基本介绍就如上述。只是还有个API值得提一下:

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS))
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission. }

这个API主要用于给用户一个申请权限的解释。该方法仅仅有在用户在上一次已经拒绝过你的这个权限申请。也就是说,用户已经拒绝一次了,你又弹个授权框。你须要给用户一个解释,为什么要授权,则使用该方法。

那么将上述几个步骤结合到一起就是:

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) { // Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) { // Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission. } else { // No explanation needed, we can request the permission. ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS); // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}

四、简单的样例

这里写一个简单的样例,针对于执行时权限。相信大家在最開始接触Android的时候,都利用Intent实验了打电话、发短信等功能。

我们看看直接拨打电话在Android 6.x的设备上权限须要怎样处理。

当然代码非常easy:

package com.zhy.android160217;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast; public class MainActivity extends AppCompatActivity
{ private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 1; @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void testCall(View view)
{
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED)
{ ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CALL_PHONE},
MY_PERMISSIONS_REQUEST_CALL_PHONE);
} else
{
callPhone();
}
} public void callPhone()
{
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "10086");
intent.setData(data);
startActivity(intent);
} @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{ if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE)
{
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
callPhone();
} else
{
// Permission Denied
Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

在Android 6.x上执行是,点击testCall,即会弹出授权窗体,怎样你Allow则直接拨打电话。假设Denied则Toast弹出”Permission Denied”。

样例非常easy,只是须要注意的是。对于Intent这种方式。非常多情况下是不须要授权的甚至权限都不须要的。比方:你是到拨号界面而不是直接拨打电话,就不须要去申请权限。打开系统图库去选择照片;调用系统相机app去牌照等。

很多其它请參考Consider Using an Intent.

当然,上例也说明了并不是全部的通过Intent的方式都不须要申请权限。普通情况下,你是通过Intent打开还有一个app,让用户通过该app去做一些事情,你仅仅关注结果(onActivityResult),那么权限是不须要你处理的,而是由打开的app去处理。

五、封装

(1)申请权限

尽管权限处理并不复杂,可是须要编写非常多反复的代码。所以眼下也有非常多库对使用方法进行了封装。大家能够在github首页搜索:android permission,对照了几个库的使用方式,发现https://github.com/lovedise/PermissionGen这个库据我所见相比較而言使用算是比較简单的,那么就以这个库的源代码为基础来解说,大家有兴趣能够多看几个库的源代码。

封装的代码非常easy,正如大家的对上面的权限代码所见,没有特别复杂的地方。

对于申请权限的代码,原本的编写为:

if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED)
{ ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CALL_PHONE},
MY_PERMISSIONS_REQUEST_CALL_PHONE);
} else
{
callPhone();
}

大家能够看到,对于其它的权限。事实上申请的逻辑是相似的。唯一不同的肯定就是參数,那么看上面的代码,我们须要3个參数:Activity|Fragment权限字符串数组int型申请码

也就是说,我们仅仅须要写个方法,接受这几个參数就可以,然后逻辑是统一的。

public static void needPermission(Fragment fragment, int requestCode, String[] permissions)
{
requestPermissions(fragment, requestCode, permissions);
} @TargetApi(value = Build.VERSION_CODES.M)
private static void requestPermissions(Object object, int requestCode, String[] permissions)
{
if (!Utils.isOverMarshmallow())
{
doExecuteSuccess(object, requestCode);
return;
}
List<String> deniedPermissions = Utils.findDeniedPermissions(getActivity(object), permissions); if (deniedPermissions.size() > 0)
{
if (object instanceof Activity)
{
((Activity) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else if (object instanceof Fragment)
{
((Fragment) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else
{
throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
} } else
{
doExecuteSuccess(object, requestCode);
}
}

Utils.findDeniedPermissions事实上就是check没有授权的权限。

#Utils
@TargetApi(value = Build.VERSION_CODES.M)
public static List<String> findDeniedPermissions(Activity activity, String... permission)
{
List<String> denyPermissions = new ArrayList<>();
for (String value : permission)
{
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED)
{
denyPermissions.add(value);
}
}
return denyPermissions;
}

那么上述的逻辑就非常清晰了。须要的3种參数传入,先去除已经申请的权限,然后開始申请权限。

ok,我相信上面代码。大家扫一眼就能够了解了。

(2)处理权限回调

对于回调。由于要依据是否授权去执行不同的事情,所以非常多框架也须要些一连串的代码,或者和前面的申请代码耦合。

只是这个框架还是比較方便的,也是我选择它来解说的原因。

回调主要做的事情:

  1. 了解是否授权成功。
  2. 依据授权情况进行回调。

对于第一条逻辑都一样;对于第二条。由于涉及到两个分支。每一个分支执行不同的方法。

对于第二条,非常多框架选择将两个分支的方法在申请权限的时候进行注冊,然后在回调中依据requestCode进行匹配执行。只是这样须要在针对每次申请进行对象管理。

只是这个框架採取了一种非常有意思的做法。它利用注解去确定须要执行的方法,存在两个注解:

@PermissionSuccess(requestCode = 100)
@PermissionFail(requestCode = 100)

利用反射依据授权情况+requestCode就可以找到注解标注的方法。然后直接执行就可以。

大致的代码为:

@Override public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
PermissionGen.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
} private static void requestResult(Object obj, int requestCode, String[] permissions,
int[] grantResults)
{
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++)
{
if (grantResults[i] != PackageManager.PERMISSION_GRANTED)
{
deniedPermissions.add(permissions[i]);
}
} if (deniedPermissions.size() > 0)
{
doExecuteFail(obj, requestCode);
} else
{
doExecuteSuccess(obj, requestCode);
}
}

首先依据grantResults进行推断成功还是失败,对于成功则:

private static void doExecuteSuccess(Object activity, int requestCode)
{
Method executeMethod = Utils.findMethodWithRequestCode(activity.getClass(),
PermissionSuccess.class, requestCode); executeMethod(activity, executeMethod);
} #Utils
public static <A extends Annotation> Method findMethodWithRequestCode(Class clazz, Class<A> annotation, int requestCode)
{
for (Method method : clazz.getDeclaredMethods())
{
if (method.isAnnotationPresent(annotation))
{
if (isEqualRequestCodeFromAnntation(method, annotation, requestCode))
{
return method;
}
}
}
return null;
}

依据注解和requestCode找到方法,然后反射执行就可以。失败的逻辑相似,不贴代码了。

ok。到此我们的执行时权限相对于早起版本号的变化、特点、以及怎样处理和封装都介绍完了。

只是对于上面解说的库,肯定有人会说:执行时反射会影响效率,没错,只是我已经在上述代码的基础上将执行时注解改成Annotation Processor的方式了,即编译时注解,这种话。就不存在反射损失效率的问题了。

本来准备fork修改,然后PR,结果写完。修改太大,预计PR是不可能通过了,所以另起项目了。也方便后面的做一些扩展和维护。

详见库:https://github.com/hongyangAndroid/MPermissions.

六、MPermissions使用方法

对外的接口和PermissionGen基本一致,由于申请仅仅须要三个參数,抛弃了使用原本类库的单例的方式,直接一个几个静态方法,简单整洁暴力。

贴一个使用方法:

public class MainActivity extends AppCompatActivity
{ private Button mBtnSdcard;
private static final int REQUECT_CODE_SDCARD = 2; @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); mBtnSdcard = (Button) findViewById(R.id.id_btn_sdcard);
mBtnSdcard.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
MPermissions.requestPermissions(MainActivity.this, REQUECT_CODE_SDCARD, Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
});
} @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
MPermissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
} @PermissionGrant(REQUECT_CODE_SDCARD)
public void requestSdcardSuccess()
{
Toast.makeText(this, "GRANT ACCESS SDCARD!", Toast.LENGTH_SHORT).show();
} @PermissionDenied(REQUECT_CODE_SDCARD)
public void requestSdcardFailed()
{
Toast.makeText(this, "DENY ACCESS SDCARD!", Toast.LENGTH_SHORT).show();
}
}

是不是简单明了~~对于onRequestPermissionsResult全部的Activity都是一致的。所以能够放到BaseActivity中去。此外,在Fragment中使用的方式一致。详见demo。

详见库:https://github.com/hongyangAndroid/MPermissions.

至于为什么不直接介绍MPermission的源代码,由于主要涉及到Annotation Processor,所以以这种方式引出,后面考虑单篇博文介绍下我眼下所会的编译时注解的相关做法以及API的使用。

最后感谢PermissionGen库。祝大家元宵节快乐~。


欢迎关注我的微博:

http://weibo.com/u/3165018720


讨论群:284327959,欢迎入群

微信公众号:hongyangAndroid

(欢迎关注。第一时间推送博文信息)

Android 6.0 执行时权限处理全然解析的更多相关文章

  1. Android 6.0 运行时权限处理完全解析

    一.概述 随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化.对于6.0的几个主要的变化,查看查看官网的这篇文章http:// ...

  2. Android 6.0 运行时权限处理完全解析 (摘抄)

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/50709663: 本文出自:[张鸿洋的博客] 一.概述 随着Android 6. ...

  3. Android权限管理之Android 6.0运行时权限及解决办法

    前言: 今天还是围绕着最近面试的一个热门话题Android 6.0权限适配来总结学习,其实Android 6.0权限适配我们公司是在今年5月份才开始做,算是比较晚的吧,不过现在Android 6.0以 ...

  4. Android6.0执行时权限解析,RxPermissions的使用,自己封装一套权限框架

    Android6.0执行时权限解析,RxPermissions的使用.自己封装一套权限框架 在Android6.0中,新添加了一个执行时的权限,我相信非常多人都已经知道了.预计也知道怎么用了,这篇博客 ...

  5. Android 6.0+ 运行时权限

    1.权限被分为了普通和危险两种 2.打电话的Demo import android.Manifest; import android.app.Activity; import android.cont ...

  6. Android 6.0 运行时权限处理

    在运行时请求权限 从Android 6.0(API级别23)开始,用户权限授予应用程序在应用程序运行时,当他们安装程序.这种方法简化了应用程序的安装过程,因为用户不需要安装或更新应用程序时授予权限.这 ...

  7. Android 6.0运行时权限第三方库的使用-----RxPermissions

    运行时权限的讲解在前一篇博客已经算是说的比较清楚了,这里就不说了,如果对6.0这个新特性不是很了解的朋友建议先看看(地址:http://blog.csdn.net/qq_33923079/articl ...

  8. Android数据存储之Android 6.0运行时权限下文件存储的思考

    前言: 在我们做App开发的过程中基本上都会用到文件存储,所以文件存储对于我们来说是相当熟悉了,不过自从Android 6.0发布之后,基于运行时权限机制访问外置sdcard是需要动态申请权限,所以以 ...

  9. 谈谈Android 6.0运行时权限理解

    前言 谷歌在2015年8月份时候,发布了Android 6.0版本,代号叫做“棉花糖”(Marshmallow ),其中的很大的一部分变化,是在用户权限授权上,或许是感觉之前默认授权的不合理,现在6. ...

随机推荐

  1. 查看Linux 服务器是 32位还是64位的

    查看Linux 服务器是 32位还是64位的 getconf LONG_BIT 返回 64 代表就是 64位的: 返回 32 代表就是 32位的:

  2. 【python爬虫和正则表达式】爬取表格中的的二级链接

    开始进公司实习的一个任务是整理一个网页页面上二级链接的内容整理到EXCEL中,这项工作把我头都搞大了,整理了好几天,实习生就是端茶送水的.前段时间学了爬虫,于是我想能不能用python写一个爬虫一个个 ...

  3. /etc/rsyncd.conf

    [root@backup ~]# cat /etc/rsyncd.conf #Rsync server#created by oldboy ##rsyncd.conf start##uid = rsy ...

  4. Java 学习(11): 面向对象编程—继承(super,this)

    Java 继承 what: 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为.子类从它的父类中继承可访问的数据域和方法,也 ...

  5. mac和iphone处理视频

    今天在微信上面发现有视频打不开,也无法下载到相册 而到电脑上可以打开 搜了一下,发现格式不对,mp4有很多格式,有的是苹果支持不了的. 要下载一个转换器,我下载了“超级转霸”,然后把视频转成了ipho ...

  6. Java读书笔记一(异常处理)

    1.介绍 在开发过程中.都会遇到程序执行时的异常,比如逻辑错误.硬件故障等,本篇博客将会具体的介绍一下Java中的异常处理机制. 2.Try和Catch捕获异常 我们在处理异常过程中经经常使用到的就是 ...

  7. bzoj1050【HAOI2006】旅行comf

    1050: [HAOI2006]旅行comf Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 2205  Solved: 1174 [Submit][ ...

  8. 手动配置三大框架整合:Spring+Struts2+mybatis

    如今主流的项目框架中,数据库持久层有可能不是hibernate,而是mybatis或者ibatis,事实上它们都是一样的,以下我来把环境搭建一下: [导入相关jar包]新建web项目projectms ...

  9. 【LeetCode-面试算法经典-Java实现】【064-Minimum Path Sum(最小路径和)】

    [064-Minimum Path Sum(最小路径和)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a m x n grid filled with ...

  10. css3模糊图片

    image的css样式 filter: blur(10px); -webkit-filter: blur(10px); -moz-filter: blur(10px); -ms-filter: blu ...