您的位置:首页 > 其它

终于等到你--权限工具类

2018-01-18 13:41 267 查看

Foreword

之前总是有小伙伴问 AndroidUtilCode 中有没有权限工具类,但都被我怼回去了,让先用着其他第三方的,不过,到了如今的 1.11.0 版本的 AndroidUtilCode,这个一直拖欠着的权限工具类总算要问世了,以后小伙伴们如果用 AndroidUtilCode 需要动态授权的话,就不用再多依赖一个第三方库了,下面来介绍下其功能。

Functions

兼容安卓各版本,包括 Android 8.0
支持任意地方申请权限,不仅限于 Activity 和 Fragment 等
支持多权限同时申请
采用链式调用,一句话解决权限申请

Achieve

首先来介绍其实现方式,关于运行时权限的介绍可以在官网查看 -> 传送门。关于危险权限列表,我封装危险权限常量类 PermissionConstants.java,代码如下所示:
import android.Manifest;
import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* <pre>
*     author: Blankj
*     blog  : http://blankj.com *     time  : 2017/12/29
*     desc  : 权限相关常量
* </pre>
*/
@SuppressLint("InlinedApi")
public final class PermissionConstants {

public static final String CALENDAR   = Manifest.permission_group.CALENDAR;
public static final String CAMERA     = Manifest.permission_group.CAMERA;
public static final String CONTACTS   = Manifest.permission_group.CONTACTS;
public static final String LOCATION   = Manifest.permission_group.LOCATION;
public static final String MICROPHONE = Manifest.permission_group.MICROPHONE;
public static final String PHONE      = Manifest.permission_group.PHONE;
public static final String SENSORS    = Manifest.permission_group.SENSORS;
public static final String SMS        = Manifest.permission_group.SMS;
public static final String STORAGE    = Manifest.permission_group.STORAGE;

private static final String[] GROUP_CALENDAR   = {
permission.READ_CALENDAR, permission.WRITE_CALENDAR
};
private static final String[] GROUP_CAMERA     = {
permission.CAMERA
};
private static final String[] GROUP_CONTACTS   = {
permission.READ_CONTACTS, permission.WRITE_CONTACTS, permission.GET_ACCOUNTS
};
private static final String[] GROUP_LOCATION   = {
permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION
};
private static final String[] GROUP_MICROPHONE = {
permission.RECORD_AUDIO
};
private static final String[] GROUP_PHONE      = {
permission.READ_PHONE_STATE, permission.READ_PHONE_NUMBERS, permission.CALL_PHONE,
permission.ANSWER_PHONE_CALLS, permission.READ_CALL_LOG, permission.WRITE_CALL_LOG,
permission.ADD_VOICEMAIL, permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS
};
private static final String[] GROUP_SENSORS    = {
permission.BODY_SENSORS
};
private static final String[] GROUP_SMS        = {
permission.SEND_SMS, permission.RECEIVE_SMS, permission.READ_SMS,
permission.RECEIVE_WAP_PUSH, permission.RECEIVE_MMS,
};
private static final String[] GROUP_STORAGE    = {
permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE
};

@StringDef({CALENDAR, CAMERA, CONTACTS, LOCATION, MICROPHONE, PHONE, SENSORS, SMS, STORAGE,})
@Retention(RetentionPolicy.SOURCE)
public @interface Permission {
}

public static String[] getPermissions(@Permission final String permission) {
switch (permission) {
case CALENDAR:
return GROUP_CALENDAR;
case CAMERA:
return GROUP_CAMERA;
case CONTACTS:
return GROUP_CONTACTS;
case LOCATION:
return GROUP_LOCATION;
case MICROPHONE:
return GROUP_MICROPHONE;
case PHONE:
return GROUP_PHONE;
case SENSORS:
return GROUP_SENSORS;
case SMS:
return GROUP_SMS;
case STORAGE:
return GROUP_STORAGE;
}
return new String[]{permission};
}
}
为了适配 Android 8.0,我在申请权限的时候,会把清单文件中使用到的同组的权限都一次性申请完毕,相关代码如下所示:
private static final List<String> PERMISSIONS = getPermissions();

/**
* 获取应用权限
*
* @return 清单文件中的权限列表
*/
public static List<String> getPermissions() {
return getPermissions(Utils.getApp().getPackageName());
}

/**
* 获取应用权限
*
* @param packageName 包名
* @return 清单文件中的权限列表
*/
public static List<String> getPermissions(final String packageName) {
PackageManager pm = Utils.getApp().getPackageManager();
try {
return Arrays.asList(
pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
.requestedPermissions
);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return Collections.emptyList();
}
}

/**
* 设置请求权限
*
* @param permissions 要请求的权限
* @return {@link PermissionUtils}
*/
public static PermissionUtils permission(@Permission final String... permissions) {
return new PermissionUtils(permissions);
}

private PermissionUtils(final String... permissions) {
mPermissions = new LinkedHashSet<>();
for (String permission : permissions) {
for (String aPermission : PermissionConstants.getPermissions(permission)) {
if (PERMISSIONS.contains(aPermission)) {
mPermissions.add(aPermission);
}
}
}
sInstance = this;
}
为了支持任意地方都可以申请权限,我在 PermissionUtils.java 中封装了 PermissionActivity,源码如下所示:
@RequiresApi(api = Build.VERSION_CODES.M)
public static class PermissionActivity extends Activity {
public static void start(final Context context) {
Intent starter = new Intent(context, PermissionActivity.class);
starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(starter);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (sInstance.mThemeCallback != null) {
sInstance.mThemeCallback.onActivityCreate(this);
} else {
Window window = getWindow();
window.setBackgroundDrawable(null);
int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
window.getDecorView().setSystemUiVisibility(option);
window.setStatusBarColor(Color.TRANSPARENT);
}
super.onCreate(savedInstanceState);
if (sInstance.rationale(this)) {
finish();
return;
}
if (sInstance.mPermissionsRequest != null) {
int size = sInstance.mPermissionsRequest.size();
requestPermissions(sInstance.mPermissionsRequest.toArray(new String[size]), 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
sInstance.onRequestPermissionsResult(this);
finish();
}
}
这样我们便可以自己全权处理权限请求,但启动的这个 PermissionActivity 的主题并不一定符合小伙伴们应用的 Activity 相关主题,所以我留了个设置主题的回调接口,这样便可无感知地启动一个 Activity,如果没有设置主题回调的话就默认是透明状态栏和透明背景,这个应该能适配很多应用了。
当然,如果有设置 rationale 的话,也就是设置拒绝权限后再次请求的回调接口,此时便会走
sInstance.rationale(this)
,具体代码如下所示:
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean rationale(final Activity activity) {
boolean isRationale = false;
if (mOnRationaleListener != null) {
for (String permission : mPermissionsRequest) {
if (activity.shouldShowRequestPermissionRationale(permission)) {
getPermissionsStatus(activity);
mOnRationaleListener.rationale(new ShouldRequest() {
@Override
public void again(boolean again) {
if (again) {
startPermissionActivity();
} else {
requestCallback();
}
}
});
isRationale = true;
break;
}
}
mOnRationaleListener = null;
}
return isRationale;
}
逻辑就是如果 rationale 回调接口 执行了
shouldRequest.again(true);
,那么就会继续申请下去,反之则不再申请,多用在弹出一个提示对话框来让用户选择是否继续请求权限。
最终就是发起请求和接受请求,并把最终状态保存到
mPermissionsGranted
mPermissionsDenied
mPermissionsDeniedForever
中,最终回调 callback 的接口,相关代码如下所示:
private void getPermissionsStatus(final Activity activity) {
for (String permission : mPermissionsRequest) {
if (isGranted(permission)) {
mPermissionsGranted.add(permission);
} else {
mPermissionsDenied.add(permission);
if (!activity.shouldShowRequestPermissionRationale(permission)) {
mPermissionsDeniedForever.add(permission);
}
}
}
}

private void requestCallback() {
if (mSimpleCallback != null) {
if (mPermissionsRequest.size() == 0
|| mPermissions.size() == mPermissionsGranted.size()) {
mSimpleCallback.onGranted();
} else {
if (!mPermissionsDenied.isEmpty()) {
mSimpleCallback.onDenied();
}
}
mSimpleCallback = null;
}
if (mFullCallback != null) {
if (mPermissionsRequest.size() == 0
|| mPermissions.size() == mPermissionsGranted.size()) {
mFullCallback.onGranted(mPermissionsGranted);
} else {
if (!mPermissionsDenied.isEmpty()) {
mFullCallback.onDenied(mPermissionsDeniedForever, mPermissionsDenied);
}
}
mFullCallback = null;
}
mOnRationaleListener = null;
mThemeCallback = null;
}

private void onRequestPermissionsResult(final Activity activity) {
getPermissionsStatus(activity);
requestCallback();
}

Use

说了那么多,总算到使用了,其实使用起来非常方便,一句话即可,比如我们要申请
android.permission.READ_CALENDAR
权限,那么我们可以去 PermissionConstants.java 中找到其所属组,也就是 CALENDAR,而应用是全屏类型的应用,那么我们可以像下面这样发起请求。
PermissionUtils.permission(PermissionConstants.CALENDAR)
.rationale(new PermissionUtils.OnRationaleListener() {
@Override
public void rationale(final ShouldRequest shouldRequest) {
PermissionHelper.showRationaleDialog(shouldRequest);
}
})
.callback(new PermissionUtils.FullCallback() {
@Override
public void onGranted(List<String> permissionsGranted) {
updateAboutPermission();
}
@Override
public void onDenied(List<String> permissionsDeniedForever,
List<String> permissionsDenied) {
if (!permissionsDeniedForever.isEmpty()) {
PermissionHelper.showOpenAppSettingDialog();
}
LogUtils.d(permissionsDeniedForever, permissionsDenied);
}
})
.theme(new PermissionUtils.ThemeCallback() {
@Override
public void onActivityCreate(Activity activity) {
ScreenUtils.setFullScreen(activity);
}
})
.request();
如果还有不会的可以参考 AndroidUtilCode 中的 demo -> PermissionActivity.java
Tips:推荐小伙伴们最好把权限请求相关的操作都放在一个 helper 类中,就像我 AndroidUtilCode 中 demo 的做法,创建一个 PermissionHelper.java,毕竟有很多权限请求都是重复。

Conclusion

好了,本次的权限工具类介绍就到此结束了,在这么简洁的工具类背后都是本柯基辛勤付出的汗水,疯狂地 debug,疯狂地测试来消除内存泄漏的问题,虽然路途很艰辛,但最终还是成功地完成了该工具类,终于等到你。

作者:blankj
链接:https://juejin.im/post/5a55867af265da3e303c5fb3
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: