程序猿如何從產(chǎn)品的角度去提升應(yīng)用的體驗(yàn)之Android權(quán)限優(yōu)化篇
前言:大家平時(shí)在開發(fā)的過程中是否會(huì)遇到這種情況:很多產(chǎn)品體驗(yàn)上的細(xì)節(jié),特別是涉及到技術(shù)相關(guān)的細(xì)節(jié),產(chǎn)品與設(shè)計(jì)可能并不會(huì)給出詳細(xì)的解決方案,甚至可能并不太關(guān)注這方面的體驗(yàn)細(xì)節(jié)。例如,應(yīng)用的緩存清理機(jī)制該怎么實(shí)現(xiàn)?權(quán)限申請的時(shí)機(jī)應(yīng)該放在哪?用戶沒有給予應(yīng)用必要的權(quán)限該怎么處理......這種時(shí)候,作為一個(gè)開發(fā)人員,特別是對(duì)自家產(chǎn)品的使用體驗(yàn)有追求的開發(fā)人員,其實(shí)完全可以充當(dāng)一回產(chǎn)品,從產(chǎn)品的角度出發(fā)去思考,該怎樣在技術(shù)實(shí)現(xiàn)的細(xì)節(jié)上,讓自家的APP體驗(yàn)變得更好。千萬不要小瞧這些細(xì)節(jié),一個(gè)產(chǎn)品的***體驗(yàn),就是由無數(shù)的細(xì)節(jié)堆砌而成的。
1. 應(yīng)用通知權(quán)限的優(yōu)化
眾所周知,推送對(duì)于一個(gè)APP來說是很重要的功能。推送在好的產(chǎn)品設(shè)計(jì)中可以有效地提高產(chǎn)品活躍度,增加用戶的忠誠度以及留存率。但是,用戶有可能會(huì)在無意中把應(yīng)用的通知權(quán)限給禁止了,導(dǎo)致收不到推送(用戶主動(dòng)禁止應(yīng)用的通知權(quán)限除外)。例如,華為手機(jī)會(huì)在通知中心直接提示用戶是否關(guān)掉某個(gè)應(yīng)用的通知權(quán)限。如果用戶,特別是小白用戶一不小心把通知權(quán)限給禁止了,導(dǎo)致應(yīng)用收不到推送,反而可能還會(huì)把這種情況當(dāng)做bug來向客服反饋(任何時(shí)候都千萬不要高估用戶對(duì)于智能手機(jī)使用的了解,尤其是你的APP的目標(biāo)用戶還包括中老年人的時(shí)候)。
華為手機(jī)的推送中心
如果大家有細(xì)心觀察的話會(huì)發(fā)現(xiàn),當(dāng)應(yīng)用的通知權(quán)限被禁止的時(shí)候,體驗(yàn)好的應(yīng)用會(huì)在適當(dāng)?shù)臅r(shí)機(jī)以及場景下出現(xiàn)提示,告知用戶通知在應(yīng)用中起到的作用,嘗試去消除用戶的不信任和謹(jǐn)慎心理,并引導(dǎo)用戶去打開通知權(quán)限。
那么,我們怎么知道自己的應(yīng)用程序通知權(quán)限被禁止了呢?如果被禁止了又該怎么辦呢?下面就來說一下解決方案。
1.1 檢測應(yīng)用的通知權(quán)限狀態(tài)
檢測應(yīng)用的通知權(quán)限其實(shí)比較簡單。通過查詢 官方文檔 可以發(fā)現(xiàn),在support庫的API 24.0.0 版本,已經(jīng)有現(xiàn)成的方法可以直接查詢應(yīng)用的通知權(quán)限狀態(tài):
- NotificationManagerCompat.from(this).areNotificationEnable();
但是,這就意味著應(yīng)用的 compileSdkVersion 也需要與support庫的版本保持一致。如果應(yīng)用目前所使用的compileSdkVersion 低于 24.0.0 或者由于某些歷史原因而不能將compileSdkVersion 升到 24.0.0以上,那么就沒有辦法檢測到應(yīng)用的通知權(quán)限了嗎?
其實(shí),辦法還是有的。通過查看系統(tǒng)源碼,可以發(fā)現(xiàn),NotificationManagerCompat.from(this).areNotificationEnable() 這個(gè)方法在不同版本的SDK上會(huì)有不同的實(shí)現(xiàn)。
- /**
- * Returns whether notifications from the calling package are not blocked.
- */
- public boolean areNotificationsEnabled() {
- return IMPL.areNotificationsEnabled(mContext, mNotificationManager);
- }
IMPL是一個(gè)實(shí)現(xiàn)了Impl接口的實(shí)現(xiàn)類對(duì)象,而且在靜態(tài)代碼塊中通過判斷手機(jī)系統(tǒng)所使用的版本號(hào)來初始化不同的實(shí)現(xiàn)類:
- 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();
- }
當(dāng)手機(jī)系統(tǒng)Android版本小于4.4.0的時(shí)候, areNotificationEnable()方法會(huì)默認(rèn)返回true。所以,此方法只有在4.4.0以上的手機(jī)系統(tǒng)上才能返回準(zhǔn)確的結(jié)果。
- /**
- * 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();
- }
- }
這種反射的方式實(shí)際上就是通過AppOpsManager和AppOpsService去獲取位于/data/system/目錄下的文件Appops.xml里的數(shù)據(jù)。所以,這個(gè)系統(tǒng)源碼里的方法可以單獨(dú)抽取出來作為一個(gè)通用的檢測通知權(quán)限狀態(tài)的方法。有關(guān)AppOpsManager,這里先不做展開,等下在1.4章節(jié)單獨(dú)說一下。
1.2 引導(dǎo)用戶跳轉(zhuǎn)到通知權(quán)限設(shè)置界面
既然已經(jīng)能檢測到應(yīng)用的通知權(quán)限狀態(tài),當(dāng)應(yīng)用的通知權(quán)限被禁止的時(shí)候,應(yīng)該出現(xiàn)提示告知用戶通知在應(yīng)用中起到的作用,并引導(dǎo)用戶去打開通知權(quán)限。以下為跳轉(zhuǎn)到通知權(quán)限設(shè)置的通用方法:
- 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以上,可以直接跳轉(zhuǎn)到某個(gè)應(yīng)用的通知權(quán)限快捷設(shè)置界面。但是在5.0以下,暫時(shí)還沒有找到可以直接跳轉(zhuǎn)到通知權(quán)限設(shè)置界面的方法,所以目前的做法是跳轉(zhuǎn)某個(gè)應(yīng)用的設(shè)置界面,在設(shè)置界面列表中應(yīng)該會(huì)有通知權(quán)限管理的入口。如果你有更好的做法,歡迎在評(píng)論中指出來。這里再另外拋出一個(gè)問題,供大家思考:關(guān)于通知權(quán)限提示的方案,應(yīng)該在什么時(shí)機(jī)或場景下出現(xiàn)好?出現(xiàn)提示的頻率為多少好呢?是只提示一次呢,還是只要用戶沒打開通知權(quán)限就一直提示呢?歡迎大家在留言中說出自己的看法。
1.3 當(dāng)通知權(quán)限被關(guān)閉后,Toast可能無法正常工作的問題
雖然跟通知權(quán)限的優(yōu)化沒什么關(guān)系,不過在這里還是要提一下。這個(gè)是在調(diào)研通知權(quán)限優(yōu)化問題的時(shí)候偶然發(fā)現(xiàn)的坑:在大部分的機(jī)型上,當(dāng)應(yīng)用的通知權(quán)限被關(guān)閉后,系統(tǒng)的 Toast會(huì)直接無法正常工作。
以下是我測試過的數(shù)據(jù):
可以看出,這個(gè)坑影響的機(jī)型范圍還是挺大的。為什么會(huì)這樣呢?
查閱源碼后可以發(fā)現(xiàn) Toast 里也用到了NotificationManagerService。在Toast執(zhí)行show()方法后,執(zhí)行到enqueueToast()的時(shí)候如下:
- if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) { if (!isSystemToast) {
- Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request."); return;
- }
- }
原來這里也用到了檢測通知權(quán)限的方法noteNotificationOp()。如果通知權(quán)限被禁止了,那么Toast也就無法正常工作。
對(duì)于Android手機(jī)來說,Toast在應(yīng)用中隨處可見,如果因?yàn)橥ㄖ獧?quán)限導(dǎo)致Toast不工作那么影響還是挺大的。所以,在這里建議大家尋找一下Toast的替代方案,不要在項(xiàng)目中直接使用系統(tǒng)自帶的Toast。同樣的,如果大家有什么好的解決方案,也歡迎在留言中指出來。
1.4 AppOpsManager的工作原理
既然上面提到了AppOpsManager,那么這里來簡單地介紹一下它的工作原理。AppOpsManager的工作框架圖如下:
Setting UI通過AppOpsManager與AppOpsService 交互,給用戶提供入口管理各個(gè)app的操作。
AppOpsService具體處理用戶的各項(xiàng)設(shè)置,用戶的設(shè)置項(xiàng)存儲(chǔ)在 /data/system/appops.xml文件中。
AppOpsService也會(huì)被注入到各個(gè)相關(guān)的系統(tǒng)服務(wù)中,進(jìn)行權(quán)限操作的檢驗(yàn)。
各個(gè)權(quán)限操作對(duì)應(yīng)的系統(tǒng)服務(wù)(比如定位相關(guān)的Location Service,Audio相關(guān)的Audio Service等)中注入AppOpsService的判斷。如果用戶做了相應(yīng)的設(shè)置,那么這些系統(tǒng)服務(wù)就要做出相應(yīng)的處理。比如,LocationManagerSerivce的定位相關(guān)接口在實(shí)現(xiàn)時(shí),會(huì)有判斷調(diào)用該接口的app是否被用戶設(shè)置成禁止該操作,如果有該設(shè)置,就不會(huì)繼續(xù)進(jìn)行定位。
2. 應(yīng)用權(quán)限的提示優(yōu)化
由于篇幅的原因,這里就拿相機(jī)權(quán)限來舉例。假設(shè)需求如下:
在需要使用相機(jī)的場景下,先提前檢測相機(jī)權(quán)限是否打開,如果沒有打開,則嘗試申請相機(jī)權(quán)限,如果用戶還是拒絕,則出現(xiàn)權(quán)限提示,引導(dǎo)用戶去開啟相機(jī)權(quán)限。下面是微信的處理方式:
(1)6.0系統(tǒng)以上,先嘗試申請相機(jī)權(quán)限,用戶點(diǎn)擊禁止后彈出引導(dǎo)界面
(2)6.0系統(tǒng)以下,用戶點(diǎn)擊保持禁止后彈出引導(dǎo)界面
在6.0以上,我們一般可以通過系統(tǒng)自帶的方法來檢測某個(gè)權(quán)限是否被允許:
- ActivityCompat.checkSelfPermission(context, permission)
但是,在6.0以下,如果想檢測相機(jī)權(quán)限,卻沒有一個(gè)很好的方法。***,經(jīng)過查閱各種資料,發(fā)現(xiàn)好像只能通過一種簡單粗暴的方式去檢測相機(jī)權(quán)限:
- /**
- * 在6.0系統(tǒng)以下,通過嘗試打開相機(jī)的方式判斷有無拍照權(quán)限
- *
- * @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;
- }
當(dāng)使用這個(gè)方法的時(shí)候,在6.0以下的手機(jī),一般調(diào)用 Camera.open()方法,如果相機(jī)權(quán)限沒打開,會(huì)先彈出相機(jī)權(quán)限申請彈框。如果點(diǎn)擊允許,那么該方法就會(huì)返回true,點(diǎn)擊禁止則返回false。這里需要指出的是,如果你使用相機(jī)的方式是通過Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)來跳轉(zhuǎn)到系統(tǒng)的相機(jī)界面的話,那么即使應(yīng)用的相機(jī)權(quán)限被禁止了,也還是可以正常使用相機(jī)來拍照的。這種情況下要不要做權(quán)限檢查,就看個(gè)人的看法了。
不知道大家有沒注意到,微信在6.0以上和6.0以下彈出的提示對(duì)話框有點(diǎn)不同。在6.0以上提供“去設(shè)置”的選項(xiàng),點(diǎn)擊會(huì)跳轉(zhuǎn)到設(shè)置里的應(yīng)用列表界面,在6.0以下僅僅是提示。這也是一個(gè)產(chǎn)品的細(xì)節(jié)。個(gè)人看法,因?yàn)樵?.0以下,有些國產(chǎn)系統(tǒng)的權(quán)限管理根本就不在設(shè)置里面,而是需要到官方提供的手機(jī)管家類型應(yīng)用里面才可以進(jìn)行權(quán)限的管理。那么多的國產(chǎn)系統(tǒng),需要適配有點(diǎn)困難,如果沒有很好的解決方案,那么還不如不要擅自幫用戶做決定。可以看得出微信在跳轉(zhuǎn)到權(quán)限設(shè)置界面的適配上也經(jīng)過了一番考量,***選擇了這種折中的方案。
說到這里,既然權(quán)限提示優(yōu)化的思路已經(jīng)有了,大家也可以在自己的項(xiàng)目中封裝一個(gè)權(quán)限管理類,“檢查權(quán)限-被禁止-嘗試申請權(quán)限-被拒絕-彈出提示框-引導(dǎo)用戶去打開權(quán)限”通過一個(gè)方法一氣呵成。
3. 總結(jié)
本文涉及到的知識(shí)點(diǎn)可能并不深?yuàn)W,更多的是想向大家展示一下,假如開發(fā)人員從產(chǎn)品的角度去提升應(yīng)用的體驗(yàn),可以從什么角度去切入。如果能給大家?guī)硪恍﹩l(fā)就好了??赐晡恼潞螅环菜伎家幌?,自己的應(yīng)用在權(quán)限提示上的體驗(yàn)是否做到***了呢?如果還有可以改善的地方,那么趕緊根據(jù)自家應(yīng)用的實(shí)際情況,改善一下權(quán)限提示的體驗(yàn)吧。相信我,做了這件事,你的用戶會(huì)感激你的。