去年的时候,公司的系统版本升级到了Android6.0,在维护之前的产品的时候,发现了关于权限管理的相关代码实现的不是很好。于是,我果断上第三方Jar。
直接请求权限
如图:
//点击Camera按钮
findViewById(R.id.button_camera).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cameraTask();
}
});
//判断有没有申请权限
private boolean hasCameraPermission() {
return EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA);
}
@AfterPermissionGranted(RC_CAMERA_PERM)
public void cameraTask() {
if (hasCameraPermission()) {
// Have permission, do the thing!
Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
} else {
// 请求一个权限
EasyPermissions.requestPermissions(
this,
getString(R.string.rationale_camera),
RC_CAMERA_PERM,
Manifest.permission.CAMERA);
}
}
请求权限的时候,通过EasyPermissions
调用requestPermissions
方法,然后将需要的权限值传入就可以了。下面我们理一下这些类中都做了什么。
EasyPermissions
public class EasyPermissions {
/**
*权限申请成功与失败的回调
*/
public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {
void onPermissionsGranted(int requestCode, @NonNull List<String> perms);
void onPermissionsDenied(int requestCode, @NonNull List<String> perms);
}
private static final String TAG = "EasyPermissions";
/**
* 检查权限
*/
public static boolean hasPermissions(@NonNull Context context,
@Size(min = 1) @NonNull String... perms) {
//如果版本小于6.0,直接return
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
//没有上下文直接抛出异常
if (context == null) {
throw new IllegalArgumentException("Can't check permissions for null context");
}
//检查有没有授予权限
for (String perm : perms) {
if (ContextCompat.checkSelfPermission(context, perm)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//请求权限,传入需要请求权限的上下文等各种配置信息
@Deprecated
public static void requestPermissions(
@NonNull Fragment host, @NonNull String rationale,
@StringRes int positiveButton, @StringRes int negativeButton,
int requestCode, @Size(min = 1) @NonNull String... perms) {
requestPermissions(
new PermissionRequest.Builder(host, requestCode, perms)
.setRationale(rationale)
.setPositiveButtonText(positiveButton)
.setNegativeButtonText(negativeButton)
.build());
}
// 委托请求权限
public static void requestPermissions(PermissionRequest request) {
...省略代码...
request.getHelper().requestPermissions(
request.getRationale(),
request.getPositiveButtonText(),
request.getNegativeButtonText(),
request.getTheme(),
request.getRequestCode(),
request.getPerms());
}
...省略代码...
}
EasyPermissions
主要封装了请求权限与各种条件下的Dialog
弹窗回调信息,让调用者使用更加方便。通过PermissionRequest
里面的PermissionHelper
来进行真正的权限请求,前者是模型对象,后者是对象帮助类。
PermissionRequest
public final class PermissionRequest {
private final PermissionHelper mHelper;
private final String[] mPerms;//权限数组,可以是一个,也可以是多个
private final int mRequestCode;//权限值
private final String mRationale;//文字描述
private final String mPositiveButtonText;//确定按钮描述
private final String mNegativeButtonText;//取消按钮描述
private final int mTheme;//主题
//构造方法中传入值
private PermissionRequest(PermissionHelper helper,
String[] perms,
int requestCode,
String rationale,
String positiveButtonText,
String negativeButtonText,
int theme) {
mHelper = helper;
mPerms = perms.clone();
mRequestCode = requestCode;
mRationale = rationale;
mPositiveButtonText = positiveButtonText;
mNegativeButtonText = negativeButtonText;
mTheme = theme;
}
//
@NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public PermissionHelper getHelper() {
return mHelper;
}
/**
* 建造者模式,生产具有各种选项的权限请求
*
* @see PermissionRequest
*/
public static final class Builder {
private final PermissionHelper mHelper;
private final int mRequestCode;
private final String[] mPerms;
private String mRationale;
private String mPositiveButtonText;
private String mNegativeButtonText;
private int mTheme = -1;
/**
* 获取PermissionHelper的实例
* 用于Activity
*
*/
public Builder(@NonNull Activity activity, int requestCode,
@NonNull @Size(min = 1) String... perms) {
mHelper = PermissionHelper.newInstance(activity);
mRequestCode = requestCode;
mPerms = perms;
}
...省略代码...
/**
*
* 如果用户拒绝了请求的权限,再次请求权限的时候,那么就显示一个理由说明。
* 如果用户永久拒绝请求的权限,那么请使用
* use the {@link AppSettingsDialog}
* <p>
* 默认显示的具体文本
*
*/
@NonNull
public Builder setRationale(@Nullable String rationale) {
mRationale = rationale;
return this;
}
/**
*生产权限请求
*/
@NonNull
public PermissionRequest build() {
...省略代码...
return new PermissionRequest(
mHelper,
mPerms,
mRequestCode,
mRationale,
mPositiveButtonText,
mNegativeButtonText,
mTheme);
}
}
}
在PermissionRequest
中,又调用了PermissionHelper
的requestPermissions
方法,而PermissionHelper
是个抽象类,库的作者为了方便管理,根据请求权限的上下文,分别抽象出了AppCompatActivityPermissionHelper
,FrameworkFragmentPermissionHelper
这些类来真正的请求对应的权限。
PermissionHelper
/**
* 委托类,供Activity fragment 代理调用
*/
public abstract class PermissionHelper<T> {
private T mHost;
//自动判断当前的上下文是什么
@NonNull
public static PermissionHelper<? extends Activity> newInstance(Activity host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return new LowApiPermissionsHelper<>(host);
}
if (host instanceof AppCompatActivity)
return new AppCompatActivityPermissionHelper((AppCompatActivity) host);
else {
return new ActivityPermissionHelper(host);
}
}
...省略代码...
// ============================================================================
// Public concrete methods
// ============================================================================
public PermissionHelper(@NonNull T host) {
mHost = host;
}
//判断是否需要显示说明权限的弹窗
private boolean shouldShowRationale(@NonNull String... perms) {
for (String perm : perms) {
if (shouldShowRequestPermissionRationale(perm)) {
return true;
}
}
return false;
}
//请求权限,这里调用抽象方法。
public void requestPermissions(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
if (shouldShowRationale(perms)) {
Log.e("PermissionHelper","showRequestPermissionRationale");
//显示权限的提示框
showRequestPermissionRationale(
rationale, positiveButton, negativeButton, theme, requestCode, perms);
} else {
Log.e("PermissionHelper","directRequestPermissions");
//直接显示请求的权限框
directRequestPermissions(requestCode, perms);
}
}
@NonNull
public T getHost() {
return mHost;
}
// ============================================================================
// Public abstract methods
// ============================================================================
public abstract void directRequestPermissions(int requestCode, @NonNull String... perms);
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String perm);
public abstract void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms);
public abstract Context getContext();
}
抽象方法showRequestPermissionRationale
与directRequestPermissions
分别显示两种不同的弹窗状态。PermissionHelper
可以作为基类让其他子类实现弹出窗的逻辑。
public abstract class BaseFrameworkPermissionsHelper<T> extends PermissionHelper<T> {
public BaseFrameworkPermissionsHelper(@NonNull T host) {
super(host);
}
public abstract FragmentManager getFragmentManager();
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
RationaleDialogFragment
.newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms)
.showAllowingStateLoss(getFragmentManager(), RationaleDialogFragment.TAG);
}
}
class ActivityPermissionHelper extends BaseFrameworkPermissionsHelper<Activity> {
public ActivityPermissionHelper(Activity host) {
super(host);
}
@Override
public FragmentManager getFragmentManager() {
return getHost().getFragmentManager();
}
// 真正请求权限的地方,直接请求权限
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}
//显示权限说明的弹框
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
@Override
public Context getContext() {
return getHost();
}
}
Builder模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
OK,上面贴了这么多代码。也就处理了第一种情况,直接请求权限。
拒绝权限,再次申请权限
当用户拒绝了权限之后,为了更好的用户体验,我们可以弹出框,说明一下,为什么要请求这个权限。
如下图:
在代码中,基本调用流程和上面一样,只不过在PermissionHelper
请求权限的时候,添加一个判断方法shouldShowRationale
,来让系统判定是需要为用户说明权限的重要性。
private boolean shouldShowRationale(@NonNull String... perms) {
for (String perm : perms) {
if (shouldShowRequestPermissionRationale(perm)) {
return true;
}
}
return false;
}
这个判断,会让继承抽象类的子类去覆写对应的方法。例如在AppCompatActivityPermissionHelper
类中:
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
用户点击了不在询问按钮
如果用户不耐烦的点击了不在询问的选项,那么这个权限弹窗就弹不出来了,那这样就不给用户使用这个权限了吗?当然不行。比如我们请求摄像头,用户不小心点击了不在询问,那我们也应该再次询问用户,你怎么重新获取权限等方式。或者直接跳转到系统权限列表界面。
如下图:
具体的流程是这样的:用户点击了“不在询问”之后,也就是权限申请失败了,那么会通过EasyPermissions.PermissionCallbacks
回调到onPermissionsDenied
方法中,这个时候,我们再给他弹个窗,来让用户点击。具体的判断用户是否点击了“不在询问”看代码
//PermissionHelper方法中
public boolean somePermissionPermanentlyDenied(@NonNull List<String> perms) {
for (String deniedPermission : perms) {
if (permissionPermanentlyDenied(deniedPermission)) {
return true;
}
}
return false;
}
//如果用户点击了不在询问之后,那么也不会显示权限具体说明的弹框了。所以通过这个来设置布尔值
//shouldShowRequestPermissionRationale方法是抽象方法,在子类中去具体实现,和上面两种情况一样
public boolean permissionPermanentlyDenied(@NonNull String perms) {
return !shouldShowRequestPermissionRationale(perms);
}
点击确定之后,弹出设置的界面。如下代码:
startActivityForResult(
new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", getPackageName(), null)),
APP_SETTINGS_RC);
那么这个库叫什么呢?大家都用过easypermissions。
最近出差,生病了一周,现在的身体就像一团卫生纸一样,一扣就烂。