转载请注明出处:http://blog.csdn.net/demokui/article/details/54376634
首先先给出一张相关文档截图:
可以看的出Google在6.0上的新增功能还是不少的,但对于咱们开发小伙伴来说最为关注的就是文档中列举的“更完整的应用权限管理”,那么今天就记录一下之前学习Android6.0运行时权限的重要实现过程。(高手勿喷,欢迎指正,谢谢。。。)
做Android的伙伴应该都知道Google将android的权限分为三种
这里说一下:Android app在6.0之前使用危险权限时,直接在Manifest文件中配置即可,完了安装程序时系统会将该app中使用到的所有危险权限罗列出来告知用户,此处只能同意所有权限才能安装程序,否则程序无法安装,后来可能Google觉得这样不妥,应该让用户先安装程序,然后自己管理自己的权限,然而就在2015-05-28的Google I/O大会上就将该功能正式发布了,只有用户在执行某个操作涉及到危险权限时系统才会给出相关授权提示,从而达到更好的用户体验。
放在最前面:6.0 权限使用步骤,第一步:在Manifest文件中配置;第二步:执行以下操作。
以上是运行时权限涉及到的所有API,相关请求参数以及回调参数详解在下文中的代码会详细给出。可以看得的出API很少,那么实现起来也不会很吃力。
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "检查出电话权限没有授权");
//申请权限,参数1:当前上下文对象 参数2:要申请的权限数组集 参数3:请求码
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 0);
} else {
Log.e(TAG, "检查出电话权限已经授权");
// 打电话
playCall();
}
/**
* 申请权限的回调
*
* @param requestCode 请求码
* @param permissions 请求的权限数组
* @param grantResults 权限请求结果(授权或者未授权)
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 0: // 请求码如果是刚刚设置的“0”
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授权
//打电话
playCall();
} else { // 未授权
// 给出用户提示
}
break;
}
}
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CALL_PHONE)) {
// 不用做什么处理
Log.e(TAG, "点击了拒绝按钮");
} else { // 打钩不再提示并点击了拒绝授权
if (isFirst) { // 第一次点击拒绝授权
Log.e(TAG, "点击了不在提示按钮");
isFirst = !isFirst;
} else { // 非第一次点击拒绝授权
Log.e(TAG, "去设置里面手动开启权限");
}
}
切记在Manifest文件中也要配置相应权限,这里使用拨打权限作为示例开发。代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button mCall;
private boolean isFirst = true; // 是否是第一次点击了不再提示授权信息
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化UI
initView();
}
private void initView() {
mCall = (Button) findViewById(R.id.id_call);
mCall.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.id_call:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "检查出电话权限没有授权");
//申请权限,参数1:当前上下文对象 参数2:要申请的权限数组集 参数3:请求码
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 0);
} else {
Log.e(TAG, "检查出电话权限已经授权");
// 打电话
playCall();
}
break;
}
}
/**
* 申请权限的回调
*
* @param requestCode 请求码
* @param permissions 请求的权限数组
* @param grantResults 权限请求结果(授权或者未授权)
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 0:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授权
Log.e(TAG, "点击了允许按钮");
playCall();
} else { // 未授权
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CALL_PHONE)) {
// 不用做什么处理
Log.e(TAG, "点击了拒绝按钮");
} else { // 打钩不再提示并点击了拒绝授权
if (isFirst) { // 第一次点击拒绝授权
Log.e(TAG, "点击了不在提示按钮");
isFirst = !isFirst;
} else { // 非第一次点击拒绝授权
Log.e(TAG, "去设置里面手动开启权限");
}
}
}
break;
}
}
private void playCall() {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "10086");
intent.setData(data);
startActivity(intent); // 小小提示:这里IDE会报红,不过可以编译也可以运行,是google设计的。属于正常情况
}
}
原谅我没有录屏(根本不会啊!目前还没研究怎么录屏,,,)相关截图如下:
乐视X820手机第一次申请授权:
当乐视第一次点击拒绝按钮就会显示出不再提示选项:
这里给出测试时log日志:
同时我也拿华为mate8手机试了一下,由于手机系统定制的原因UI长的不太一样,但功能一模一样,但是我在国民手机小米MAX机型演示时出了点问题:当用户第一次点击拒绝授权后第二次申请授权时不再弹出提示框,后来经过log日志分析出来,原因是小米MAX这款手机拒绝 = 不再提示,也就是说当点击了拒绝按钮后,只能去设置里面手动开启权限了。(本人就测试了这三款机型,均是真机测试,并且出厂时就是6.0以上的系统,而不是从低版本升级上来的系统,郭神说升级上来的系统都已经默认授权了,我没试过。。。有兴趣的朋友可以试一下)
基于郭霖大神2016年12月27日在csdn直播Android 6.0运行时权限思路封装(纯属搬运工,不过学习这种封装思路很重要,并且动手实现也很重要。。。)
猛戳郭霖csdn直播视频
封装思路如下:
1.请求申请授权的第一个参数:当前视图的上下文对象,那么我们必须在工程的任何地方都应该能获取到当前的上下文对象。(Activity任务栈可以获取最顶层的视图,而最顶层的视图肯定是当前的Activity实例对象)所以代码编写如下:
public class ActivityCollector {
private static List activityList = new ArrayList<>();
public static void addActivity(Activity activity) {
activityList.add(activity);
}
public static void removeActivity(Activity activity) {
activityList.remove(activity);
}
public static Activity getTopActivity() {
if (activityList.isEmpty()) {
return null;
} else {
return activityList.get(activityList.size() - 1);
}
}
}
2.请求申请授权的第二个参数:需要申请的权限数组集,这个只有子类自己才知道自己想要申请什么权限,那么这个参数必须由子类传入,第三个参数可自行定义。因此我们可以在我们项目中的BaseActivity中对外公开一个这样的静态方法(静态是为了在非视图下直接调用该方法申请权限),如果申请时用户已经申请了该权限,那么我们还要对此逻辑做出处理,而这一块的逻辑也只有子类自己知道要做什么,所以我们可以定义一个接口,让子类去实现该接口完成相应逻辑处理,代码如下:
public interface PermissionListener {
// 已授权
void onGranted();
// 未授权 参数说明:用来提示用户某个权限授权失败的信息
void onDenied(List deniedPermission);
}
public static void requestRuntimePermission(String[] permissions, PermissionListener listener) {
// 获取Activity任务栈中最顶层视图
Activity topActivity = ActivityCollector.getTopActivity();
if (topActivity == null) {
return;
}
// 定义全局的请求结果监听接口,下文请求结果回调中也要用到
mListener = listener;
// 创建一个集合用来存储未授权的权限
List permissionList = new ArrayList<>();
// 遍历子类请求的所有权限判断是否已经授权
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(topActivity, permission) != PackageManager.PERMISSION_GRANTED) {
// 如果没有授权则加入 permissionList 集合中
permissionList.add(permission);
}
}
// 如果permissionList不为空,说明有某个权限没被授权,那么去做申请权限处理逻辑
if (!permissionList.isEmpty()) {
ActivityCompat.requestPermissions(topActivity, permissionList.toArray(new String[permissionList.size()]), 1);
} else {
// 如果都已经授权,交由子类去处理
mListener.onGranted();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
// 创建一个deniedPermission 集合,用来封装未被授权成功的权限
List deniedPermission = new ArrayList<>();
// 遍历授权结果获取每个申请的权限以及每个权限的申请结果值
for (int i = 0; i < grantResults.length; i++) {
// 获取每个权限的申请结果值
int grantResult = grantResults[i];
// 获取申请的每个权限
String permission = permissions[i];
if (grantResult != PackageManager.PERMISSION_GRANTED) {
// 如果没有授权则加入deniedPermission集合
deniedPermission.add(permission);
}
}
// deniedPermission集合为空,则说明所有权限均被授权
if (deniedPermission.isEmpty()) {
// 全部授权,交由子类实现下一步操作逻辑
mListener.onGranted();
} else {
// 某个权限未被授权,交由子类实现下一步操作逻辑
mListener.onDenied(deniedPermission);
}
}
default:
break;
}
}
然后在BaseActivity中的onCreate()和onDestroy()添加任务栈和移除任务栈:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
封装到这里就结束。下面给出调用实现实例:
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mButton= (Button) findViewById(R.id.id_call);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 这里直接调用父类的方法即可
requestRuntimePermission(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_CONTACTS}, new PermissionListener() {
@Override
public void onGranted() {
Toast.makeText(MainActivity.this,"所有权限都同意了",Toast.LENGTH_SHORT).show();
}
@Override
public void onDenied(List deniedPermission) {
for(String persmission:deniedPermission){
Toast.makeText(MainActivity.this,"被拒绝的权限:"+persmission,Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
}
public class Test {
public void text(){
// 调用BaseActivity的静态方法(现在知道为什么要写成静态了吧)
BaseActivity.requestRuntimePermission(new String[]{},new PermissionListener(){
@Override
public void onGranted() {
}
@Override
public void onDenied(List deniedPermission) {
}
});
}
}
好了,封装结束。哦,不对,有同学在郭神直播完后的问答环节问了一个重要的问题:在Test中这样使用会不会导致内存泄漏?,郭神的回答是很有可能。因为这里一直获取的我们保存的Activity的实例对象的最顶层的一个。这里会存在软引用,导致内存泄漏,这位提问的同学很是牛逼啊,,,不过郭神重要讲的是这种封装思路,简单易懂。大家可以以后遇到封装可以套用该思路,举一反三。(如果在非视图下申请权限那就不用这个方法了啊,直接弹出一个透明的Activity就好啦。。。。)
https://github.com/tbruyelle/RxPermissions
https://github.com/lovedise/PermissionGen
鸿神对PermissionGen的讲解以及MPermissions的封装
https://github.com/hongyangAndroid/MPermissions
欢迎大家点赞/评论,更欢迎高手指正,谢谢!!!