关于android权限请求的文章和资料网上比比皆是,博主就github的一个开源框架EasyPermissions来分析该框架的代码设计以及实现原理。
博主研读完该框架的倒是体会到了不少的东西,现在尽可能多的分享出来其中所得。
研究android的源码可以发现,不论是Activity还是Fragment,或者androidx的Fragment,这几个组件都有关于权限验证的下面三个方法:
androidx.fragment.app.Fragment
void requestPermissions(@NonNull String[] permissions, int requestCode)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults)
boolean shouldShowRequestPermissionRationale(@NonNull String permission)
android.app.Fragment
void requestPermissions(@NonNull String[] permissions, int requestCode)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults)
boolean shouldShowRequestPermissionRationale(@NonNull String permission)
android.support.v4.app.Fragment
void requestPermissions(@NonNull String[] permissions, int requestCode)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults)
boolean shouldShowRequestPermissionRationale(@NonNull String permission)
android.app.Activity
void requestPermissions(@NonNull String[] permissions, int requestCode)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults)
boolean shouldShowRequestPermissionRationale(@NonNull String permission)
android.support.v4.app.FragmentActivity
void requestPermissions(@NonNull String[] permissions, int requestCode)---继承了Activity
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults)
方法shouldShowRequestPermissionRationale
的意思是在请求权限的时候选择了拒绝,但是不勾选“不再提醒”的选项时,该方法就返回true。这个方法的意图就是:防止用户一直拒绝某权限,而给用户解释为什么需要这个权限(Rationale:这个单词的意思就是根本原因)。当然如果用户很讨厌看到这样的提示,那么直接拒绝权限的时候勾选不再提醒选即可,伪代码如下。
//用户一直拒绝但是又不选择“不在提示”
if(shouldShowRequestPermissionRationale){
给个提示,比如一个弹框,说明该一下不给权限的后果,然后继续处理权限请求的逻辑
}else{
//请求权限
requestPermissions();
}
简单的运行效果如下:
前面说了,Activity,Fragment都有上面三个方法,所以写代码的时候请求同样的权限都要写类似的代码,避免不了ctrl+c,ctrl+v这一波操作,用起来很不方便;所以各种权限请求的封装框架github一搜一大把。
其实说白了也就是Android自己的这一套权限请求的从整体设计违反了单一职责的设计理念而导致的。而网上的一些框架无外乎就是将权限请求的逻辑从Activity或者Frament剥离出来做了一个代理操作:即提供了一个专门来用于权限请求这一职责的工具。EasyPermissions从设计理念上也是用了这个代理思想。其整体代码结构如下图所示:
为了统一整合权限请求的逻辑,EasyPermission在代码设计上用了模板方法模式提供了一个抽象类PermissionHelper,该类承接了权限请求的职责,但是将具体的请求方法交给子类去实现,比如ActivityPermissionHelper处理Activity页面的权限请求,SupportFragmentPermissionHelper提供了Fragment页面的权限请求,PermissionHelper的部分代码如下:
/**
*代理了activity和fragment的权限请求
* Delegate class to make permission calls based on the 'host' (Fragment, Activity, etc).
*/
public abstract class PermissionHelper {
public abstract void directRequestPermissions(int requestCode, @NonNull String... perms);
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String perm);
}
方法directRequestPermissions
和shouldShowRequestPermissionRationale
这两个抽象方法其实就是对原Activity和Fragment的权限请求做了一个抽象封装。让我们来看看PermissionHelper的继承结构:
顾名思义,各个子类负责自己的权限请求逻辑,比如LowApiPermissonsHelper负责处理低于6.0版本的逻辑,其实就是默认的什么都不做而已。ActivityPermissonHelper则是负责处理Activity的权限请求,其实现也很简单:
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
//使用ActivityCompat这个Uitls来处理
ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
而SupportFragmentPermissonHelper则是用直接调用了Fragment自己的原生api来处理:
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
//注意getHost返回的就是Fragment对象
getHost().requestPermissions(perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
//注意getHost返回的就是Fragment对象
return getHost().shouldShowRequestPermissionRationale(perm);
}
那么怎么确定是上述对象的哪一个Helper对象呢?在PermissionHelper提供了如下两个工厂方法:
///处理Activity模块的权限请求
@NonNull
public static PermissionHelper extends Activity> newInstance(Activity host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
//小于6.0的权限处理逻辑
return new LowApiPermissionsHelper<>(host);
}
if (host instanceof AppCompatActivity){//使用AppcompatActivity的模块
return new AppCompatActivityPermissionsHelper((AppCompatActivity) host);
}else {/普通的Activity
return new ActivityPermissionHelper(host);
}
}
//处理Fragment模块的权限请求
@NonNull
public static PermissionHelper newInstance(Fragment host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return new LowApiPermissionsHelper<>(host);
}
return new SupportFragmentPermissionHelper(host);
}
所以代码整体架构很简单,就是简单的模板方法模式,专门把负责权限请求的逻辑做了一层封装。因为是对权限进行请求,所以根据面向对象的思想,EasyPermission每个请求视为一个对象,即PermissionRequest对象(该对象用到了Builder模式):
public final class PermissionRequest {
private final PermissionHelper mHelper;
//要请求的权限数组
private final String[] mPerms;
//权限请求码
private final int mRequestCode;
//第二次拒绝权限时弹出提示框的内容,比如上图中的:“不给权限不给玩”
private final String mRationale;
//dialog的ok按钮文字
private final String mPositiveButtonText;
//dialog的cancel按钮文字
private final String mNegativeButtonText;
}
既然有请求,当然有相应,所以EasyPermission设计了如下两个接口:
public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {
//被允许的权限
void onPermissionsGranted(int requestCode, @NonNull List perms);
//被拒绝的权限
void onPermissionsDenied(int requestCode, @NonNull List perms);
}
public interface RationaleCallbacks {
//点击dialog的确认按钮回调此方法
void onRationaleAccepted(int requestCode);
//点击dialog的取消按钮回调此方法
void onRationaleDenied(int requestCode);
}
到此为止,EasyPermission的源码基本分析完毕,看看到底是怎么使用这个小框架的,博主demo代码如下:
public class MainActivity extends AppCompatActivity implements
EasyPermissions.PermissionCallbacks,
EasyPermissions.RationaleCallbacks{
@Override
protected void onResume() {
super.onResume();
//构建权限请求对象
PermissionRequest permissionRequest =
new PermissionRequest.Builder(this,1, Manifest.permission.CAMERA)
.setRationale("不给权限不给玩!").build();
//开始权限请求
EasyPermissions.requestPermissions(permissionRequest);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//将请求结果传递EasyPermission库处理
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, List perms) {
Log.e("Permissions", "用户授权成功");
}
@Override
public void onPermissionsDenied(int requestCode, List perms) {
Log.e("Permissions", "用户授权失败");
}
@Override
public void onRationaleAccepted(int requestCode) {
Log.e("Permissions", "用户看到我们的提示选择了继续申请权限");
}
@Override
public void onRationaleDenied(int requestCode) {
Log.e("Permissions", "用户看到我们的提示依然选择了残忍拒绝");
}
@AfterPermissionGranted(1)
public void allGranted(){
Log.e("Permissions", "请求的权限全部被允许时调用调用此方法");
}
}
可以看出对于Activity来说需要实现EasyPermissions.PermissionCallbacks,EasyPermissions.RationaleCallbacks
这两个接口。然后通过EasyPermissions.requestPermissions(permissionRequest);
进行权限请求,在Activity的onRequestPermissionsResult方法中使用EasyPermissions.onRequestPermissionsResult
收集权限请求的结果,另外当请求的权限全部被允许后会回调@AfterPermissionGranted(1)
注解的方法,简单来看一下onRequestPermissionsResult都做了什么:
public static void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults,
@NonNull Object... receivers) {
//收集被允许的权限
List granted = new ArrayList<>();
//收集被拒绝的权限
List denied = new ArrayList<>();
for (int i = 0; i < permissions.length; i++) {
String perm = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
granted.add(perm);
} else {
denied.add(perm);
}
}
//对host进行回调
for (Object object : receivers) {
// Report granted permissions, if any.
if (!granted.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
}
}
// Report denied permissions, if any.
if (!denied.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
}
}
//如果全部都被允许则调用被@AfterPermissionGranted注解的方法
if (!granted.isEmpty() && denied.isEmpty()) {
runAnnotatedMethods(object, requestCode);
}
}
}
方法很简单,就不在做过多解释。至于EasyPermissions.requestPermissions(permissionRequest);
方法,通过辗转最终会调用PermissionHelper的requestPermissions方法:
public void requestPermissions(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
//用户拒绝(没有点击不在提示)后再次请求权限
if (shouldShowRationale(perms)) {
//弹出对应的dialog框:比如上图”不给权限不给玩“的dialog框
showRequestPermissionRationale(
rationale, positiveButton, negativeButton, theme, requestCode, perms);
} else {
//进行权限请求
directRequestPermissions(requestCode, perms);
}
}
注意如果用户选择了拒绝并且点击不在提醒,则会权限请求失败。
到此为止EasyPermission的源码分析完毕,如有不当之处欢迎批评指正;虽然该框架代码量不是很多,但是从代码结构设计上倒是有很多可圈可点的地方,比如用了代理思想,模板方法模式,builder模式等。该框架还是值得阅读借鉴一下的。