在android6.0之前的系统要想申请权限,只需要在AndroidManifest.xml中申明一下就行,而用户在安装软件的时候系统会显示该软件会用到那些权限,用户觉得不合理可以选择不安装,这个设计的初衷是好的,可是总是会出现店大欺客的现象,比如QQ,微信,支付宝等等,明明不需要修改通话记录的权限和修改联系人的权限,可他们就是恬不知耻的申请了,你还必须得同意,不能拒绝安装,因为你的朋友们都在用。或者生活中经常得用到他。
后来android6.0加入了运行时权限
这样的话,一些会涉及到用户隐私和安全的权限即使软件申明了,用户不同意还是可以安装,安装后在使用过程中,软件需要某种权限的话得向用户请求,用户可以选择允许或者拒绝,用户不需要担心软件一直申请,用户可以选择不再询问。同时,如果这样做了得话,只有用户去设置->应用管理->APP->权限管理里手动开启权限了。
那么,现在就来看看这个运行时权限怎么弄吧
如何向用户申请权限
最基本简单的方式就是,首先建立一个项目,RunTimePermissionsTest,判断程序是否拥有权限,未拥有就申请,拥有就进行你想做的事,比如拨打电话。
public class MainActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取拨号权限名,返回一个字符串
String callPhonePermission = Manifest.permission.CALL_PHONE;
/**
* ContextCompat.checkSelfPermission()检查程序是否拥有某个权限,
* 接收两个参数:第一个参数时Context,第二个参数是权限名
* 返回两个结果:
* PackageManager.PERMISSION_GRANTED表示已拥有
* PackageManager.PERMISSION_DENIED表示未拥有
*/
if (ContextCompat.checkSelfPermission(MainActivity.this, callPhonePermission)
!= PackageManager.PERMISSION_GRANTED) {
//当程序未拥有某个权限的时候进行权限申请
/**
* ActivityCompat.requestPermissions() 向用户申请权限
* 接收两个参数:
* 第一个参数是Activity实例
* 第二个参数是String数组,把需要申请的权限名放进去
* 第三个参数是唯一的权限请求码,这里就写个1就行了
*/
ActivityCompat.requestPermissions(MainActivity.this, new String[]{callPhonePermission}, 1);
} else {
//当程序拥有某个权限的时候运行需要进行的操作
//这里就进行拨号操作
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
}
});
}
/**
* 申请授权后,系统会回调这个方法
*
* @param requestCode 权限申请码
* @param permissions 申请的权限名数组
* @param grantResults 申请的结果数组
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
//建立一个List,存储申请失败的权限
List<String> deniedList = new ArrayList<>();
//遍历grantResults数组
for (int i = 0; i < grantResults.length; i++) {
String mPermissions = permissions[i];
int mGrantResult = grantResults[i];
//如果结果是没有授权成功就把对应的权限名添加进List
if (mGrantResult != PackageManager.PERMISSION_GRANTED) {
deniedList.add(mPermissions);
}
}
//判断失败的权限数组是不是空的,是空的就代表权限申请成功了
if(deniedList.isEmpty()){
//权限申请成功,做点什么把,这里还是打电话
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}else {
//Tost失败的权限
for (String i : deniedList) {
Toast.makeText(this, "申请失败的权限有" + i, Toast.LENGTH_SHORT).show();
}
}
}
这是基础简单的方式,但是代码量太大了,程序也不能一次性要所有权限,不然用户会觉得很奇怪,你总不能在申请读取联系人权限的时候顺带申请SD卡权限把?这样做用户很可能会拒绝,那只有在需要权限的时候进行申请,每次申请都这么多代码,那肯定不符合我们面向对象的思想的,那么我们就进阶改进一下,把申请权限的代码封装起来,这样在需要权限的时候直接调用封装好的方法,输入要申请的权限,以及通过或者未通过时运行的代码。这篇博文也是在看了郭神也就是郭霖的直播后写的,郭神讲的就是android运行时权限,然后讲了怎么把这个过程封装起来,那么,现在开始吧。
封装android6.0运行时权限申请
首先,我们新建一个接口,两个类:
- PermissionsListen
- ActivityCollector
- BestActivity
PermissionsListen接口的作用是对申请结果进行监听,一共有两个抽象方法:
- void onGranted();
- void onDenied(List
permission);
- onGranted方法在申请成功后进行回调
- onDenied方法在申请失败后进行回调,接收一个String泛型的List参数,就是申请失败的权限List
代码如下:
package com.example.runtimepermissiontest;
import java.util.List;
public interface PermissionsListen {
void onGranted();
void onDenied(List<String> permission);
}
ActivityCollectot的作用是对收集在Activity栈里的Activity,因为申请权限必须要传入Activity,我们希望在任何情况下都能进行权限申请而不是只能在Activity里进行申请,所以需要收集栈里面的Activity,然后在申请权限的时候传入位于栈顶的Activity,好了,上代码
package com.example.runtimepermissiontest;
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
public class ActivityCollector {
//存储Activity的LIst
public static List<Activity> activities = new ArrayList<>();
/**
* 向List添加Activity
* @param activity 传入要添加的Activity
*/
public static void addActivity(Activity activity){
activities.add(activity);
}
/**
* 从List中移除Activity
* @param activity 传入要移除的Activity
*/
public static void removeActivity(Activity activity){
activities.remove(activity);
}
/**
* 干掉所有的活动
*/
public static void finishAll(){
for (Activity activity : activities){
if(!activity.isFinishing()){
activity.finish();
}
}
}
}
BaseActivity的作用就是进行申请权限操作,这里讲一下这个处理逻辑,因为申请权限需要一个Activity实例作为参数,并且我想在程序的任何地方都能进行权限申请而不是局限于只能在Activity中申请,所以我建立了一个ActivityCollector来收集Activity实例,然后在onCreate方法中把实例添加进去,在onDestory方法中把实例移除,活动继承BaseActivity,这样ActivityCollector中的实例栈顶的Activity就是Task的栈顶活动,参数的问题解决了。
在申请到权限或者没有申请到权限的时候会需要有进行相应的操作,所以就新建了一个接口,声明了两个抽象方法,调用的时候实现一下就行。
然后就是申请权限的逻辑,检查要申请的权限有没有获得授权,没有就添加到一个数组,接着检查数组是否为空,不为空就代表有未授权的权限,把这个数组放到requsetPermissions方法中向用户进行权限申请,为空就回调PermissionsListen中的onGranted方法
权限申请后系统会回调onRequestPermissionsResult方法,重写一下这个方法,在方法里对申请结果进行判断,授权失败了就回调onDenied方法,成功了就回调onGranted方法。好,看代码走一下
package com.example.runtimepermissiontest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
/**
* Created by jethro on 2016/12/28.
*/
public class BestActivity extends AppCompatActivity {
// 申明一个全局PermissionsListen对象,在程序调用onRequestPermissions方法时为他赋值
// 这个对象会在回调onRequestPermissionsResult方法时用到
static PermissionsListen permissionsListen;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//把活动添加到ActivityCollector中去
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//活动被销毁的时候,把活动从ActivityCollector中移除
ActivityCollector.removeActivity(this);
}
/**
* 这是重头戏,在申请权限时调用的就是这个方法
*
* @param permissions 这个参数就是权限数组
* @param listen 这个参数是一个回调接口,实现两个抽象方法,
* 之前在建立PermissionsListen时有介绍这两个抽象方法
*/
public static void onRequestPermissions(String[] permissions, PermissionsListen listen) {
permissionsListen = listen;
//获取在栈顶的Activity
int index = ActivityCollector.activities.size() - 1;
Activity activity = ActivityCollector.activities.get(index);
//建立一个List,存储没有获得授权的权限,以备在后面申请权限的时候使用
List<String> mpermissions = new ArrayList<>();
//遍历判断那些权限没有获得授权
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(activity, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
//将没有授权的权限添加到mpermissions数组
mpermissions.add(permissions[i]);
}
}
//判断mpermissions是否为空
if (mpermissions.isEmpty()) {
//如果是空的,就执行PermissionsListen对象的onGranted方法
listen.onGranted();
} else {
//如果不是空的就把mpermissions数组转换成String[]数组
String[] str = mpermissions.toArray(new String[mpermissions.size()]);
//进行权限申请,传入Activity,还有权限数组,以及权限申请码
ActivityCompat.requestPermissions(activity, str, 1);
}
}
/**
* 权限申请后系统就会回调这个方法,用户点了不再询问,系统会直接调用这个方法
*
* @param requestCode 权限申请码
* @param permissions 申请的权限名数组
* @param grantResults 申请的结果数组
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
String mpermissions = permissions[i];
if (grantResult != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(mpermissions);
}
}
if (deniedPermissions.isEmpty()) {
//申请成功
permissionsListen.onGranted();
} else {
//申请失败
permissionsListen.onDenied(deniedPermissions);
}
break;
default:
break;
}
}
}
好啦,运行时权限处理已经被封装好了,那么,现在就来试一下把
package com.example.runtimepermissiontest;
import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import java.util.List;
public class MainActivity extends BestActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//Button的onClick
public void getPermissions(View view) {
String callPhone = Manifest.permission.CALL_PHONE;
String carmera = Manifest.permission.CAMERA;
String storagr = Manifest.permission.WRITE_EXTERNAL_STORAGE;
String[] permissions = {callPhone, carmera, storagr};
onRequestPermissions(permissions, new PermissionsListen() {
/**
* 实现申请成功后调用的代码
*/
@Override
public void onGranted() {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
//如果IDE有错误提示,不用在意,只是让我们应该检查一下是否有权限
startActivity(intent);
}
/**
* 实现申请失败后调用的代码
* @param permission
*/
@Override
public void onDenied(List<String> permission) {
for (String i : permission) {
Toast.makeText(MainActivity.this, "权限" + i + "的申请被拒绝", Toast.LENGTH_SHORT).show();
}
}
});
}
}
即使是在不同的类里面也能进行权限申请
就像这样用:
package com.example.runtimepermissiontest;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.widget.Toast;
import java.util.List;
/**
* Created by jethro on 2016/12/29.
*/
public class Test {
public void onTest() {
//获取栈顶活动
final Activity activity = ActivityCollector.activities.get(ActivityCollector.activities.size() - 1);
String callPhone = Manifest.permission.CALL_PHONE;
String carmera = Manifest.permission.CAMERA;
String storagr = Manifest.permission.WRITE_EXTERNAL_STORAGE;
String[] permissions = {callPhone, carmera, storagr};
BestActivity.onRequestPermissions(permissions, new PermissionsListen() {
@Override
public void onGranted() {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
//如果IDE有错误提示,不用在意,只是让我们应该检查一下是否有权限
activity.startActivity(intent);
}
/**
* 实现申请失败后调用的代码
* @param permission
*/
@Override
public void onDenied(List<String> permission) {
for (String i : permission) {
Toast.makeText(activity, "权限" + i + "的申请被拒绝", Toast.LENGTH_SHORT).show();
}
}
});
}
}
感谢郭神,这个封装方式太巧妙了!