前言:你們平時在開發的過程當中是否會遇到這種狀況:不少產品體驗上的細節,特別是涉及到技術相關的細節,產品與設計可能並不會給出詳細的解決方案,甚至可能並不太關注這方面的體驗細節。例如,應用的緩存清理機制該怎麼實現?權限申請的時機應該放在哪?用戶沒有給予應用必要的權限該怎麼處理......這種時候,做爲一個開發人員,特別是對自家產品的使用體驗有追求的開發人員,其實徹底能夠充當一回產品,從產品的角度出發去思考,該怎樣在技術實現的細節上,讓自家的APP體驗變得更好。千萬不要小瞧這些細節,一個產品的極致體驗,就是由無數的細節堆砌而成的。html
衆所周知,推送對於一個APP來講是很重要的功能。推送在好的產品設計中能夠有效地提升產品活躍度,增長用戶的忠誠度以及留存率。可是,用戶有可能會在無心中把應用的通知權限給禁止了,致使收不到推送(用戶主動禁止應用的通知權限除外)。例如,華爲手機會在通知中心直接提示用戶是否關掉某個應用的通知權限。若是用戶,特別是小白用戶一不當心把通知權限給禁止了,致使應用收不到推送,反而可能還會把這種狀況當作bug來向客服反饋(任什麼時候候都千萬不要高估用戶對於智能手機使用的瞭解,尤爲是你的APP的目標用戶還包括中老年人的時候)。android
若是你們有細心觀察的話會發現,當應用的通知權限被禁止的時候,體驗好的應用會在適當的時機以及場景下出現提示,告知用戶通知在應用中起到的做用,嘗試去消除用戶的不信任和謹慎心理,並引導用戶去打開通知權限。緩存
那麼,咱們怎麼知道本身的應用程序通知權限被禁止了呢?若是被禁止了又該怎麼辦呢?下面就來講一下解決方案。bash
檢測應用的通知權限其實比較簡單。經過查詢 官方文檔 能夠發現,在support
庫的API 24.0.0
版本,已經有現成的方法能夠直接查詢應用的通知權限狀態:微信
NotificationManagerCompat.from(this).areNotificationEnable();複製代碼
可是,這就意味着應用的 compileSdkVersion
也須要與support
庫的版本保持一致。若是應用目前所使用的compileSdkVersion
低於 24.0.0
或者因爲某些歷史緣由而不能將 compileSdkVersion
升到 24.0.0
以上,那麼就沒有辦法檢測到應用的通知權限了嗎?app
其實,辦法仍是有的。經過查看系統源碼,能夠發現,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
接口的實現類對象,並且在靜態代碼塊中經過判斷手機系統所使用的版本號來初始化不一樣的實現類:ide
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;
}
}複製代碼
這種反射的方式實際上就是經過AppOpsManager
和AppOpsService
去獲取位於/data/system/
目錄下的文件Appops.xml
裏的數據。因此,這個系統源碼裏的方法能夠單獨抽取出來做爲一個通用的檢測通知權限狀態的方法。有關AppOpsManager
,這裏先不作展開,等下在1.4章節單獨說一下。
既然已經能檢測到應用的通知權限狀態,當應用的通知權限被禁止的時候,應該出現提示告知用戶通知在應用中起到的做用,並引導用戶去打開通知權限。如下爲跳轉到通知權限設置的通用方法:
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
如下,暫時尚未找到能夠直接跳轉到通知權限設置界面的方法,因此目前的作法是跳轉某個應用的設置界面,在設置界面列表中應該會有通知權限管理的入口。若是你有更好的作法,歡迎在評論中指出來。這裏再另外拋出一個問題,供你們思考:關於通知權限提示的方案,應該在什麼時機或場景下出現好?出現提示的頻率爲多少好呢?是隻提示一次呢,仍是隻要用戶沒打開通知權限就一直提示呢?歡迎你們在留言中說出本身的見解。
雖然跟通知權限的優化沒什麼關係,不過在這裏仍是要提一下。這個是在調研通知權限優化問題的時候偶然發現的坑:在大部分的機型上,當應用的通知權限被關閉後,系統的 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
。一樣的,若是你們有什麼好的解決方案,也歡迎在留言中指出來。
既然上面提到了AppOpsManager
,那麼這裏來簡單地介紹一下它的工做原理。AppOpsManager
的工做框架圖以下:
Setting UI
經過AppOpsManager
與AppOpsService
交互,給用戶提供入口管理各個app
的操做。AppOpsService
具體處理用戶的各項設置,用戶的設置項存儲在 /data/system/appops.xml
文件中。AppOpsService
也會被注入到各個相關的系統服務中,進行權限操做的檢驗。
各個權限操做對應的系統服務(好比定位相關的Location Service
,Audio
相關的Audio Service
等)中注入AppOpsService
的判斷。若是用戶作了相應的設置,那麼這些系統服務就要作出相應的處理。好比,LocationManagerSerivce
的定位相關接口在實現時,會有判斷調用該接口的app是否被用戶設置成禁止該操做,若是有該設置,就不會繼續進行定位。
因爲篇幅的緣由,這裏就拿相機權限來舉例。假設需求以下:
在須要使用相機的場景下,先提早檢測相機權限是否打開,若是沒有打開,則嘗試申請相機權限,若是用戶仍是拒絕,則出現權限提示,引導用戶去開啓相機權限。下面是微信的處理方式:
(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
如下,有些國產系統的權限管理根本就不在設置裏面,而是須要到官方提供的手機管家類型應用裏面才能夠進行權限的管理。那麼多的國產系統,須要適配有點困難,若是沒有很好的解決方案,那麼還不如不要擅自幫用戶作決定。能夠看得出微信在跳轉到權限設置界面的適配上也通過了一番考量,最後選擇了這種折中的方案。
說到這裏,既然權限提示優化的思路已經有了,你們也能夠在本身的項目中封裝一個權限管理類,「檢查權限-被禁止-嘗試申請權限-被拒絕-彈出提示框-引導用戶去打開權限」經過一個方法一鼓作氣。
本文涉及到的知識點可能並不深奧,更多的是想向你們展現一下,假如開發人員從產品的角度去提高應用的體驗,能夠從什麼角度去切入。若是能給你們帶來一些啓發就行了。看完文章後,不凡思考一下,本身的應用在權限提示上的體驗是否作到最好了呢?若是還有能夠改善的地方,那麼趕忙根據自家應用的實際狀況,改善一下權限提示的體驗吧。相信我,作了這件事,你的用戶會感激你的。