在Android上优雅的申请权限

Snackbar.LENGTH_INDEFINITE)
.setAction(“确定”) {
requestPermissions(arrayOf(Manifest.permission.CALL_PHONE), REQUEST_PERMISSION_CODE_CALL_PHONE)
}.setDuration(3000)
.show()
}

/**

  • 用户点击拨打电话按钮,先进行申请权限
    */
    private fun requestPermmission(context: Context) {

// 判断是否需要运行时申请权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// 判断是否需要对用户进行提醒,用户点击过拒绝&&没有勾选不再提醒时进行提示
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
// 给用于予以权限解释, 对于已经拒绝过的情况,先提示申请理由,再进行申请
showPermissionRationale(“需要打开电话权限直接进行拨打电话,方便您的操作”)
} else {
// 无需说明理由的情况下,直接进行申请。如第一次使用该功能(第一次申请权限),用户拒绝权限并勾选了不再提醒
// 将引导跳转设置操作放在请求结果回调中处理
requestPermissions(arrayOf(Manifest.permission.CALL_PHONE), REQUEST_PERMISSION_CODE_CALL_PHONE)
}
} else {
// 拥有权限直接进行功能调用
callPhone()
}
}

/**

  • 权限申请回调
    */
    @TargetApi(Build.VERSION_CODES.M)
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
    // 根据requestCode判断是那个权限请求的回调
    if (requestCode == REQUEST_PERMISSION_CODE_CALL_PHONE) {
    // 判断用户是否同意了请求
    if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    callPhone()
    } else {
    // 未同意的情况
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
    // 给用于予以权限解释, 对于已经拒绝过的情况,先提示申请理由,再进行申请
    showPermissionRationale(“需要打开电话权限直接进行拨打电话,方便您的操作”)
    } else {
    // 用户勾选了不再提醒,引导用户进入设置界面进行开启权限
    Snackbar.make(view, “需要打开权限才能使用该功能,您也可以前往设置->应用。。。开启权限”,
    Snackbar.LENGTH_INDEFINITE)
    .setAction(“确定”) {
    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
    intent.data = Uri.parse(“package:$packageName”)
    startActivityForResult(intent,REQUEST_SETTINGS_CODE)
    }
    .show()
    }
    }
    } else {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
    }

public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_SETTINGS_CODE) {
Toast.makeText(this, “再次判断是否同意了权限,再进行自定义处理”,
Toast.LENGTH_LONG).show()
}
}

}

EasyPermissions使用及存在问题

上面介绍了单一权限的申请,简单的一个申请代码量其实已经不小了,对于某一个功能需要多个权限更是需要复杂的逻辑判断。google给我们推出了一个权限申请的开源框架,下面围绕着EasyPermission进行说明。
使用方法不介绍了,看一下demo就可以了,网上也有很多的文章这里引用前人的总结。

blog.csdn.net/hexingen/ar…

我在使用的时候发现了有这样一个问题,使用版本是pub.devrel:easypermissions:2.0.0,在demo中使用多个权限申请的时候同意一个,拒绝一个,没有勾选不在提醒。这个时候,第二次申请权限,在提示用户使用权限时候点击取消,会弹出跳转到设置手动开启的弹框。这个做法是不合适的,用户并没有点击不在提醒,可以在app内部引导用户授权,肯定是哪里的逻辑有问题。先贴图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从最后的设置界面也可以看出,app并没有拒绝某些权限,还处于询问状态。
为了了解为什么出现这样的异常情况,那就跟我一起read the XXXX source code吧。
先说结论,在提示用户点击取消的时候会进入下面方法

@Override
public void onPermissionsDenied(int requestCode, @NonNull List perms) {
Log.d(TAG, “onPermissionsDenied:” + requestCode + “:” + perms.size());

// (Optional) Check whether the user denied any permissions and checked “NEVER ASK AGAIN.”
// This will display a dialog directing them to enable the permission in app settings.
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
new AppSettingsDialog.Builder(this).build().show();
}
}

在判断EasyPermissions.somePermissionPermanentlyDenied()的时候判断出了问题,弹出了dialog(这里的对话框使用Activity实现的)

EasyPermissions源码分析

这里我会跟着demo使用的思路,对源码进行阅读。建议下载源码,上面有链接
在点击两个权限的按钮之后调用如下方法

@AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM)
public void locationAndContactsTask() {
if (hasLocationAndContactsPermissions()) {
// 如果有权限,toast
Toast.makeText(this, “TODO: Location and Contacts things”, Toast.LENGTH_LONG).show();
} else {
// 没有权限,进行申请权限,交由EasyPermission类管理
EasyPermissions.requestPermissions(
this,
getString(R.string.rationale_location_contacts),
RC_LOCATION_CONTACTS_PERM,
LOCATION_AND_CONTACTS);
}
}

按照使用的思路梳理,先不管注解部分。跟进EasyPermissions.requestPermissions

/**

  • 请求多个权限,如果系统需要就弹出权限说明
  • @param host context
  • @param rationale 想用户说明为什么需要这些权限
  • @param requestCode 请求码用于onRequestPermissionsResult回调中确定是哪一次申请
  • @param perms 具体需要的权限
    */
    public static void requestPermissions(
    @NonNull Activity host, @NonNull String rationale,
    int requestCode, @Size(min = 1) @NonNull String… perms) {
    requestPermissions(
    new PermissionRequest.Builder(host, requestCode, perms)
    .setRationale(rationale)
    .build());
    }

很明显,调用了内部的requestPermissions()方法,继续跟

public static void requestPermissions(
@NonNull Fragment host, @NonNull String rationale,
int requestCode, @Size(min = 1) @NonNull String… perms) {
requestPermissions(
new PermissionRequest.Builder(host, requestCode, perms)
.setRationale(rationale)
.build());
}

构建者Builder模式创建了一个PermissionRequest.Builder对象,传入真正的requestPermissions()方法,跟吧

public static void requestPermissions(PermissionRequest request) {

// 在请求权限之前检查是否已经包含了这些权限
if (hasPermissions(request.getHelper().getContext(), request.getPerms())) {
// 已经存在了权限,给权限状态数组赋值PERMISSION_GRANTED,并进入请求完成部分。不进行这条处理分支的分析,自己看一下吧
notifyAlreadyHasPermissions(
request.getHelper().getHost(), request.getRequestCode(), request.getPerms());
return;
}

// 通过helper类来辅助调用系统api申请权限
request.getHelper().requestPermissions(
request.getRationale(),
request.getPositiveButtonText(),
request.getNegativeButtonText(),
request.getTheme(),
request.getRequestCode(),
request.getPerms());
}

requestPermissions()方法

public void requestPermissions(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String… perms) {
// 这里遍历调用系统api ,shouldShowRequestPermissionRationale,是否需要提示用户申请说明
if (shouldShowRationale(perms)) {
showRequestPermissionRationale(
rationale, positiveButton, negativeButton, theme, requestCode, perms);
} else {
// 抽象方法,其实就是在不同的子类里调用系统api
// ActivityCompat.requestPermissions(getHost(), perms, requestCode);方法
directRequestPermissions(requestCode, perms);
}
}

到这里,第一次的请求流程已经结束,与用户交互,按我们上面gif的演示,对一个权限允许,一个权限拒绝。
这时候回到Activity中的回调onRequestPermissionsResult方法中

@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

// 交给EasyPermissions类进行处理事件
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}

跟进去!

public static void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults,
@NonNull Object… receivers) {
// 创建两个list用于收集请求权限的结果
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);
}
}

// 遍历
for (Object object : receivers) {
// 如果有某个权限被同意了,回调到Activity中的onPermissionsGranted方法
if (!granted.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
}
}

// 如果有某个权限被拒绝了,回调到Activity中的onPermissionsDenied方法

if (!denied.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
}
}

// 如果请求的权限都被同意了,进入我们被@AfterPermissionGranted注解的方法,这里对注解的使用不进行详细分析了。
if (!granted.isEmpty() && denied.isEmpty()) {
runAnnotatedMethods(object, requestCode);
}
}
}

我们对权限一个允许一个拒绝,所以会回调onPermissionsGrantedonPermissionsDenied。在demo中的onPermissionsDenied方法进行了处理

@Override
public void onPermissionsDenied(int requestCode, @NonNull List perms) {
Log.d(TAG, “onPermissionsDenied:” + requestCode + “:” + perms.size());

// (Optional) Check whether the user denied any permissions and checked “NEVER ASK AGAIN.”
// This will display a dialog directing them to enable the permission in app settings.
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
new AppSettingsDialog.Builder(this).build().show();
}
}

做了一个判断,`EasyPermissions.somePermissionPermanentlyDenied,这里回调传入的是一个list,我们来继续分析。跟进去,一直跟!

public static boolean somePermissionPermanentlyDenied(@NonNull Activity host,
@NonNull List deniedPermissions) {
return PermissionHelper.newInstance(host)
.somePermissionPermanentlyDenied(deniedPermissions);
}

又进入了helper辅助类

public boolean somePermissionPermanentlyDenied(@NonNull List perms) {
for (String deniedPermission : perms) {
if (permissionPermanentlyDenied(deniedPermission)) {
return true;
}
}

return false;
}

循环遍历了每一权限。有一个是true就返回true。继续跟!

public boolean permissionPermanentlyDenied(@NonNull String perms) {
// 返回了shouldShowRequestPermissionRationale的非值,就是系统API shouldShowRequestPermissionRationale的非值
return !shouldShowRequestPermissionRationale(perms);
}

这里并没有过滤掉用户已经同意的权限,正常的交互不会进入new AppSettingsDialog.Builder(this).build().show();,但是在Rationale弹框点击取消的时候会出问题,我们看一下关于权限说明的rationale弹框的具体实现。

从demo申请权限requestPermissions方法中,调用的showRequestPermissionRationale方法。在ActivityPermissionHelper类中找到具体的实现

@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String… perms) {
FragmentManager fm = getHost().getFragmentManager();

// Check if fragment is already showing
Fragment fragment = fm.findFragmentByTag(RationaleDialogFragment.TAG);
if (fragment instanceof RationaleDialogFragment) {
Log.d(TAG, “Found existing fragment, not showing rationale.”);
return;
}
// 创建了一个DialogFragment并显示出来
RationaleDialogFragment
.newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragment.TAG);
}

查看RationaleDialogFragment类,里面代码不多,找到取消按钮的实现。

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Rationale dialog should not be cancelable
setCancelable(false);

// 创建listener
RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
RationaleDialogClickListener clickListener =
new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);

// 将listener传入dialog中
return config.createFrameworkDialog(getActivity(), clickListener);
}

查看RationaleDialogClickListener代码

@Override
public void onClick(DialogInterface dialog, int which) {
int requestCode = mConfig.requestCode;
if (which == Dialog.BUTTON_POSITIVE) { // 点击确定
String[] permissions = mConfig.permissions;
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleAccepted(requestCode);
}
if (mHost instanceof Fragment) {
PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(requestCode, permissions);
} else if (mHost instanceof Activity) {
PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(requestCode, permissions);
} else {
throw new RuntimeException(“Host must be an Activity or Fragment!”);
}
} else { // 点击取消
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleDenied(requestCode);
}
// 调用下面方法
notifyPermissionDenied();
}
}

private void notifyPermissionDenied() {
if (mCallbacks != null) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-9r3GKbDT-1713536684211)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值