概述
在安卓6.0以前,安卓应用的权限会在安装阶段向用户展示。但是在使用应用的阶段不需要再申请任何权限。
而在6.0以后,普通股权限只需要在清单文件里注册即可,但是危险权限,则除了注册在清单里之外,还需要在使用app时动态申请。
权限分类
普通权限
在程序运行时自动获取,只需要在清单文件里声明即可。比如 internet。
危险权限
App在一些操作中需要访问用户的隐私信息,比如图库,通讯录。此时安卓系统就会向用户展示所需的权限。
完整的权限申请流程
图示
解释说明
-
- 判断sdk版本是否低于23,如果是,则不需要申请权限,流程结束。否则往下。
-
- 调用
checkPermission
判断 权限是否已申请成功,如果返回true,流程结束。否则往下。
- 调用
-
- 调用
requestPermission
向系统主动发送申请权限的操作。
- 调用
但是在 requestPermission
之前,有一个 shouldShowRequestPermissionRationale
的判断
关键的 shouldShowRequestPermissionRationale
- 此判断如果返回了
true
,则说明,之前app已经尝试申请过权限,但是用户点击了拒绝
,但是并没有选择Never ask again
(以后不再提示).
这表示,用户没有永久拒绝权限,app还能再次调用 requestPermission
尝试申请。
- 如果返回了
false
, 有两种情况:- app从来没有申请过此权限
- 此时,只需要 再次调用
requestPermission
尝试申请
- 此时,只需要 再次调用
- app申请过此权限,但是被用户拒绝,并且勾选了
Never ask again
(永久拒绝)- 此时,只能弹出自定义对话框,提示用户此操作必须具备权限之后才能继续,并且在点击某个按钮之后进入到 app的权限管理页面。
- app从来没有申请过此权限
特别注意 ,以上返回true的情况,在某些国产手机上被屏蔽,比如华为,小米,也就是说,这些手机上动态申请权限,只有两种情况,允许
,或者永久拒绝
, 不存在介于两者中间的 暂时拒绝
的选项。
标准代码示范
通常,我们需要按照 activity.requestPermissions()
传入 permissions权限数组,以及 requestCode回调码,然后在Activity内进行回调处理。
但是现在有了更优雅的方式,支持我们在 任意代码的位置,不限定activity内,支持fragment,service,甚至一个普通类内进行处理。
基本思想为: 开启一个透明不可见的Activity作为申请权限的载体,申请的动作,以及 申请后的回调 全部放在此Activity内。主要代码如下:
权限申请结果接口
java
/**
* 权限申请结果接口
*/
public interface IPermissionCallback {
/**
* 授予权限
*/
void granted(int requestCode);
/**
* 这次拒绝,但是并没有勾选"以后不再提示"
*/
void denied(int requestCode);
/**
* 勾选"以后不再提示",并且拒绝
*/
void deniedForever(int requestCode);
}
权限基本功能类
java
public class PermissionUtil {
/**
* 判断所有权限是否都同意了,都同意返回true 否则返回false
*
* @param context context
* @param permissions permission list
* @return return true if all permissions granted else false
*/
public static boolean hasSelfPermissions(Context context, String... permissions) {
for (String permission : permissions) {
if (!hasSelfPermission(context, permission)) {
return false;
}
}
return true;
}
/**
* 判断单个权限是否同意
*
* @param context context
* @param permission permission
* @return return true if permission granted
*/
private static boolean hasSelfPermission(Context context, String permission) {
return ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
}
/**
* 检查是否都赋予权限
*
* @param grantResults grantResults
* @return 所有都同意返回true 否则返回false
*/
public static boolean verifyPermissions(int... grantResults) {
if (grantResults.length == 0) return false;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 检查所给权限List是否需要给提示
*
* @param activity Activity
* @param permissions 权限list
* @return 如果某个权限需要提示则返回true
*/
public static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) {
for (String permission : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
return true;
}
}
return false;
}
}
权限申请核心类,唯一入口
java
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import org.jetbrains.annotations.Nullable;
public class PermissionAspectActivity extends Activity {
private final static String permissionsTag = "permissions";
private final static String requestCodeTag = "requestCode";
private static IPermissionCallback mCallback;
/**
* 启动当前这个Activity
*/
public static void startActivity(Context context, String[] permissions, int requestCode, IPermissionCallback callback) {
Log.d("PermissionAspectTag", "context is : " + context.getClass().getSimpleName());
if (context == null) return;
mCallback = callback;
//启动当前这个Activity并且取消切换动画
Intent intent = new Intent(context, PermissionAspectActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);//开启新的任务栈并且清除栈顶...为何要清除栈顶
intent.putExtra(permissionsTag, permissions);
intent.putExtra(requestCodeTag, requestCode);
context.startActivity(intent);//利用context启动activity
if (context instanceof Activity) {//并且,如果是activity启动的,那么还要屏蔽掉activity切换动画
((Activity) context).overridePendingTransition(0, 0);
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String[] permissions = intent.getStringArrayExtra(permissionsTag);
int requestCode = intent.getIntExtra(requestCodeTag, 0);
if (PermissionUtil.hasSelfPermissions(this, permissions)) {
mCallback.granted(requestCode);
finish();
overridePendingTransition(0, 0);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, requestCode);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// 现在拿到了权限的申请结果,那么如何处理,我这个Activity只是为了申请,然后把结果告诉外界,所以结果的处理只能是外界传进来
boolean granted = PermissionUtil.verifyPermissions(grantResults);
if (granted) {//如果用户给了权限
mCallback.granted(requestCode);
} else {
if (PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) { // 判断是否还能继续申请
mCallback.denied(requestCode);
} else { // 不能再次申请,用户已经完全拒绝
mCallback.deniedForever(requestCode);
}
}
this.finish();
overridePendingTransition(0, 0);
}
}
注意,此 PermissionAspectActivity 必须在清单文件中注册
而且它的风格是透明的:
效果如下,任意类中都能使用权限申请: