前言:大家平时在开发的过程中是否会遇到这种情况:很多产品体验上的细节,特别是涉及到技术相关的细节,产品与设计可能并不会给出详细的解决方案,甚至可能并不太关注这方面的体验细节。例如,应用的缓存清理机制该怎么实现?权限申请的时机应该放在哪?用户没有给予应用必要的权限该怎么处理......这种时候,作为一个开发人员,特别是对自家产品的使用体验有追求的开发人员,其实完全可以充当一回产品,从产品的角度出发去思考,该怎样在技术实现的细节上,让自家的APP体验变得更好。千万不要小瞧这些细节,一个产品的极致体验,就是由无数的细节堆砌而成的。

1. 应用通知权限的优化

众所周知,推送对于一个APP来说是很重要的功能。推送在好的产品设计中可以有效地提高产品活跃度,增加用户的忠诚度以及留存率。但是,用户有可能会在无意中把应用的通知权限给禁止了,导致收不到推送(用户主动禁止应用的通知权限除外)。例如,华为手机会在通知中心直接提示用户是否关掉某个应用的通知权限。如果用户,特别是小白用户一不小心把通知权限给禁止了,导致应用收不到推送,反而可能还会把这种情况当做bug来向客服反馈(任何时候都千万不要高估用户对于智能手机使用的了解,尤其是你的APP的目标用户还包括中老年人的时候)。

华为手机的推送中心

如果大家有细心观察的话会发现,当应用的通知权限被禁止的时候,体验好的应用会在适当的时机以及场景下出现提示,告知用户通知在应用中起到的作用,尝试去消除用户的不信任和谨慎心理,并引导用户去打开通知权限。

那么,我们怎么知道自己的应用程序通知权限被禁止了呢?如果被禁止了又该怎么办呢?下面就来说一下解决方案。

1.1 检测应用的通知权限状态

检测应用的通知权限其实比较简单。通过查询 官方文档 可以发现,在support库的API 24.0.0 版本,已经有现成的方法可以直接查询应用的通知权限状态:

NotificationManagerCompat.from(this).areNotificationEnable();复制代码

但是,这就意味着应用的 compileSdkVersion 也需要与support库的版本保持一致。如果应用目前所使用的compileSdkVersion 低于 24.0.0 或者由于某些历史原因而不能将 compileSdkVersion 升到 24.0.0以上,那么就没有办法检测到应用的通知权限了吗?

其实,办法还是有的。通过查看系统源码,可以发现,NotificationManagerCompat.from(this).areNotificationEnable() 这个方法在不同版本的SDK上会有不同的实现。

/*** Returns whether notifications from the calling package are not blocked.*/
public boolean areNotificationsEnabled() {return IMPL.areNotificationsEnabled(mContext, mNotificationManager);
}复制代码

IMPL是一个实现了Impl接口的实现类对象,而且在静态代码块中通过判断手机系统所使用的版本号来初始化不同的实现类:

static {if (BuildCompat.isAtLeastN()) {IMPL = new ImplApi24();} else if (Build.VERSION.SDK_INT >= 19) {IMPL = new ImplKitKat();}  else if (Build.VERSION.SDK_INT >= 14) {IMPL = new ImplIceCreamSandwich();} else {IMPL = new ImplBase();}SIDE_CHANNEL_BIND_FLAGS = IMPL.getSideChannelBindFlags();
}复制代码

当手机系统Android版本小于4.4.0的时候, areNotificationEnable()方法会默认返回true。所以,此方法只有在4.4.0以上的手机系统上才能返回准确的结果。

ImplApi24类中areNotificationEnable()的实现如下:

/*** Returns whether notifications from the calling package are blocked.*/
public boolean areNotificationsEnabled() {INotificationManager service = getService();try {return service.areNotificationsEnabled(mContext.getPackageName());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}复制代码

ImplKitKat类中areNotificationEnable()的实现如下:

public static boolean areNotificationsEnabled(Context context) {AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);ApplicationInfo appInfo = context.getApplicationInfo();String pkg = context.getApplicationContext().getPackageName();int uid = appInfo.uid;try {Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName());Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE,Integer.TYPE, String.class);Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);int value = (int) opPostNotificationValue.get(Integer.class);return ((int) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg)== AppOpsManager.MODE_ALLOWED);} catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException |InvocationTargetException | IllegalAccessException | RuntimeException e) {return true;}
}复制代码

这种反射的方式实际上就是通过AppOpsManagerAppOpsService去获取位于/data/system/目录下的文件Appops.xml里的数据。所以,这个系统源码里的方法可以单独抽取出来作为一个通用的检测通知权限状态的方法。有关AppOpsManager,这里先不做展开,等下在1.4章节单独说一下。

1.2 引导用户跳转到通知权限设置界面

既然已经能检测到应用的通知权限状态,当应用的通知权限被禁止的时候,应该出现提示告知用户通知在应用中起到的作用,并引导用户去打开通知权限。以下为跳转到通知权限设置的通用方法:

public void toNotificationSetting() {if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {Intent intent = new Intent();intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");intent.putExtra("app_package", this.getPackageName());intent.putExtra("app_uid", this.getApplicationInfo().uid);startActivity(intent);} else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {Intent intent = new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);intent.addCategory(Intent.CATEGORY_DEFAULT);intent.setData(Uri.parse("package:" + this.getPackageName()));startActivity(intent);}
}复制代码

5.0以上,可以直接跳转到某个应用的通知权限快捷设置界面。但是在5.0以下,暂时还没有找到可以直接跳转到通知权限设置界面的方法,所以目前的做法是跳转某个应用的设置界面,在设置界面列表中应该会有通知权限管理的入口。如果你有更好的做法,欢迎在评论中指出来。这里再另外抛出一个问题,供大家思考:关于通知权限提示的方案,应该在什么时机或场景下出现好?出现提示的频率为多少好呢?是只提示一次呢,还是只要用户没打开通知权限就一直提示呢?欢迎大家在留言中说出自己的看法。

1.3 当通知权限被关闭后,Toast可能无法正常工作的问题

虽然跟通知权限的优化没什么关系,不过在这里还是要提一下。这个是在调研通知权限优化问题的时候偶然发现的坑:在大部分的机型上,当应用的通知权限被关闭后,系统的 Toast 会直接无法正常工作。

以下是我测试过的数据:

可以看出,这个坑影响的机型范围还是挺大的。为什么会这样呢?

查阅源码后可以发现 Toast 里也用到了NotificationManagerService。在Toast执行show()方法后,执行到enqueueToast()的时候如下:

if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {if (!isSystemToast) {Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");return;}
}复制代码

原来这里也用到了检测通知权限的方法noteNotificationOp()。如果通知权限被禁止了,那么Toast也就无法正常工作。

对于Android手机来说,Toast在应用中随处可见,如果因为通知权限导致Toast不工作那么影响还是挺大的。所以,在这里建议大家寻找一下Toast的替代方案,不要在项目中直接使用系统自带的Toast同样的,如果大家有什么好的解决方案,也欢迎在留言中指出来。

1.4 AppOpsManager的工作原理

既然上面提到了AppOpsManager,那么这里来简单地介绍一下它的工作原理。AppOpsManager的工作框架图如下:

Setting UI通过AppOpsManagerAppOpsService 交互,给用户提供入口管理各个app的操作。
AppOpsService具体处理用户的各项设置,用户的设置项存储在 /data/system/appops.xml文件中。
AppOpsService也会被注入到各个相关的系统服务中,进行权限操作的检验。

各个权限操作对应的系统服务(比如定位相关的Location ServiceAudio相关的Audio Service等)中注入AppOpsService的判断。如果用户做了相应的设置,那么这些系统服务就要做出相应的处理。比如,LocationManagerSerivce的定位相关接口在实现时,会有判断调用该接口的app是否被用户设置成禁止该操作,如果有该设置,就不会继续进行定位。

2. 应用权限的提示优化

由于篇幅的原因,这里就拿相机权限来举例。假设需求如下:
在需要使用相机的场景下,先提前检测相机权限是否打开,如果没有打开,则尝试申请相机权限,如果用户还是拒绝,则出现权限提示,引导用户去开启相机权限。下面是微信的处理方式:

(1)6.0系统以上,先尝试申请相机权限,用户点击禁止后弹出引导界面

(2)6.0系统以下,用户点击保持禁止后弹出引导界面

6.0以上,我们一般可以通过系统自带的方法来检测某个权限是否被允许:

ActivityCompat.checkSelfPermission(context, permission)复制代码

但是,在6.0以下,如果想检测相机权限,却没有一个很好的方法。最后,经过查阅各种资料,发现好像只能通过一种简单粗暴的方式去检测相机权限:

 /*** 在6.0系统以下,通过尝试打开相机的方式判断有无拍照权限** @return*/
private boolean checkCameraPermissionUnderM() {boolean isCanUse = true;Camera mCamera = null;try {mCamera = Camera.open();Camera.Parameters mParameters = mCamera.getParameters();mCamera.setParameters(mParameters);} catch (Exception e) {isCanUse = false;}if (mCamera != null) {try {mCamera.release();} catch (Exception e) {e.printStackTrace();return isCanUse;}}return isCanUse;
}复制代码

当使用这个方法的时候,在6.0以下的手机,一般调用 Camera.open()方法,如果相机权限没打开,会先弹出相机权限申请弹框。如果点击允许,那么该方法就会返回true,点击禁止则返回false这里需要指出的是,如果你使用相机的方式是通过Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)来跳转到系统的相机界面的话,那么即使应用的相机权限被禁止了,也还是可以正常使用相机来拍照的。这种情况下要不要做权限检查,就看个人的看法了。

不知道大家有没注意到,微信在6.0以上和6.0以下弹出的提示对话框有点不同。在6.0以上提供“去设置”的选项,点击会跳转到设置里的应用列表界面,在6.0以下仅仅是提示。这也是一个产品的细节。个人看法,因为在6.0以下,有些国产系统的权限管理根本就不在设置里面,而是需要到官方提供的手机管家类型应用里面才可以进行权限的管理。那么多的国产系统,需要适配有点困难,如果没有很好的解决方案,那么还不如不要擅自帮用户做决定。可以看得出微信在跳转到权限设置界面的适配上也经过了一番考量,最后选择了这种折中的方案。

说到这里,既然权限提示优化的思路已经有了,大家也可以在自己的项目中封装一个权限管理类,“检查权限-被禁止-尝试申请权限-被拒绝-弹出提示框-引导用户去打开权限”通过一个方法一气呵成。

3. 总结

本文涉及到的知识点可能并不深奥,更多的是想向大家展示一下,假如开发人员从产品的角度去提升应用的体验,可以从什么角度去切入。如果能给大家带来一些启发就好了。看完文章后,不凡思考一下,自己的应用在权限提示上的体验是否做到最好了呢?如果还有可以改善的地方,那么赶紧根据自家应用的实际情况,改善一下权限提示的体验吧。相信我,做了这件事,你的用户会感激你的。

[贝聊科技] 程序猿如何从产品的角度去提升应用的体验之Android权限优化篇相关推荐

  1. [贝聊科技]如何将 iOS 项目的编译速度提高5倍

    前言 贝聊目前开发的两款App分别是贝聊家长版和贝聊老师版,最近因为在快速迭代开发新功能,项目规模急速增长,单个端业务代码约23万行,私有库约6万行,第三方库代码约15万行,单个客户端的代码行数约60 ...

  2. 从程序猿到SAP产品经理,我是如何转型的?

    文章作者:Jason Xia(夏建军) Jerry: 今天的文章来自Jason Xia, 我的老同事,和我一样从2007年进入SAP成都研究院工作至今.这篇文章讲述了Jason是如何从一名SAP资深开 ...

  3. [贝聊科技]如何实现一个 AttributedLabel

    作者:陈浩 贝聊科技移动开发部 iOS 工程师 Core Text 是苹果提供的富文本排版技术,可以定制开发图文混排功能,DTCoreText.Nimbus.YYLabel 等优秀的开源库底层都是基于 ...

  4. [贝聊科技]贝聊 iPhone X 适配实战

    @NewPan 贝聊科技 iOS 菜鸟工程师 这款为天猫定制的 iPhone,你买了吗?由于没摸过真机,所以严格意义上来说,这篇文章应该有一个更加接地气的名字:"模拟器适配实战". ...

  5. [贝聊科技]贝聊 IAP 实战之订单绑定

    大家好,我是贝聊科技 的 iOS 工程师 @NewPan. 注意:文章中讨论的 IAP 是指使用苹果内购购买消耗性的项目. 这次为大家带来我司 IAP 的实现过程详解,鉴于支付功能的重要性以及复杂性, ...

  6. [贝聊科技]网页端「应用跳转」技术实现演变

    本文作者:Mr.Luo ,贝聊前端经理.本文同时发布于作者 个人博客 . 由于网页传播的便捷性,从网页向APP导流几乎是所有APP厂商都会采用的推广手段,具体来说就是在网页上提供一些触发点(例如按钮. ...

  7. [贝聊科技]iOS 代码架构(一)如何创建一个易复用的组件

    前言 贝聊的移动客户端分别有家长端和老师端,一家公司里同时维护多个业务上有关联性的app这种情况其实很常见,例如一些提供 O2O 服务的公司,经常会分用户端和商家端.这些客户端虽然各自负责着一个业务环 ...

  8. 屌丝程序猿对一个产品的思考

    本人屌丝程序猿一枚,2011年年底回家过春节,正值各大喜事举办高峰,一次看到父母在随礼前捧着纸质礼簿查看的一幕,瞬间萌发了开发此应用的念想,只为帮助到更多像父母一样拿本记账的人.说来惭愧,一直心系此事 ...

  9. [贝聊科技]谈谈 iOS 如何动态切换 APP 的主题

    在移动互联网的下半场,越来越多的 APP 更加注重用户体验,以期来打动用户.主题的切换就是可以增强用户体验.结合运营活动的一个点:譬如 QQ 的夜间模式,节日里电商 APP 的皮肤切换等等的这些小细节 ...

最新文章

  1. ue4中面部动画制作视频教程 Facial Animation More In Unreal Engine 4
  2. spark sql 本地调试_干货 | 如何成为大数据Spark高手
  3. 这三大“监控系统”是机房重中之重?
  4. C语言循环遍历文件夹查找文件内容(搜素/proc文件夹下的内容获取进程pid)
  5. qq数据泄露_如何保护企业移动端的数据安全?
  6. Android Handler的使用方法
  7. 信息学奥赛一本通 1160:倒序数
  8. 一步一步搭建oracle 11gR2 rac+dg之环境准备(二)【转】
  9. Redis学习笔记~Redis并发锁机制
  10. excel转word后表格超出页面_excel转word后表格显示不全
  11. MATLAB数据类型结构
  12. 用Python - Requests给项目加个短信验证码注册登录,只花了3分钟
  13. HTML页面嵌入视频无法播放的常见原因
  14. 安装iso格式的软件安装程序
  15. enumerate()函数详解
  16. 行为识别阅读笔记(paper + parted code):Beyond Frame-level CNN Saliency-Aware 3-D CNN with LSTM for Video Acti
  17. 职场生涯规划中必须学会的十种能力
  18. RNC/SGSN/PDP/IUPS概念
  19. 数据中心趋势:提高功率密度
  20. windows下,基于python3的wxpython体验+cxfreeze6.0使用

热门文章

  1. 听云研发总监杨金全:以Tracing为核心的可观测性体系
  2. db2 bigint java_db2 - 一个bigint问题
  3. Java创建对象详解:Java创建对象的几种方法
  4. python中matplotlib库的学习1
  5. 七日杀端口映射的正确姿势
  6. 如何高效的学习Android动画
  7. 百度之星之J:百度的新大厦
  8. ESP32连接蓝牙小票打印机
  9. android auto 华为p30手机,华为p30专业模式如何使用?
  10. html文本框获取表格内容,html表格、表单