導語
android碎片化相信是每一個android開發者的痛。機型適配也是難以繞過去的坎。這其中Android動態權限檢測適配,相信對于很多開發者來說,都是被按在地上摩擦摩擦。本文就針對Android權限動態檢測提出一種解決方案。
一、Android權限介紹
談起Android權限機制,很多人都會想到Google在Android 6.0 提出運行時權限管理機制(Android Runtime Permission)。針對運行時權限管理機制在這里我們不做過多描述。相信對于大多數開發者而言,真正的痛點在于建立在6.0之前的權限機制的權限檢測。之所以被認為是痛點,我們會在之后給大家介紹。不同于Android Runtime Permission,6.0之前的權限機制,所申請只需要在AndroidManifest.xml列舉出來,這無疑給惡意軟件提供了可趁之機。
二、動態權限檢測
針對權限檢測,我們根據權限機制的不同,劃分為以下兩類:
(1) targetSdkVersion >= 23
當設備處于6.0或6.0以上時采用的運行時權限機制,權限檢測變得很簡單,只需要調用ContextCompat::checkSelfPermission()
,瀟瀟灑灑一行代碼即可檢測對應的權限是否被賦予,簡直不要太簡單。
(2) tagetSdkVersion <23
當設備是6.0以下系統時,使用的老的權限機制。不像大家想象的,應用所需要的權限只需要在清單文件里面列出就可以一勞永逸。如果真有這么簡單,那恐怕連一點裝逼的空間都沒有啦。事實上,android設備提供應用權限開關,達到用戶控制整個應用的權限的賦予。對于應用本身而言,應用的很多功能是建立在擁有某一或者某些權限的基礎上。一旦用戶收回某一功能的具體權限,對于該功能來說無疑是一種災難。開發者建立了良好的容災機制那么只是會使某一功能異常,影響用戶體驗,然而對于那些沒有
建立起好的容災處理的代碼而言,嚴重的可能導致應用閃退。到了線上飄紅的地步,領導請喝茶恐怕是跑不了。
針對建立在老的權限機制基礎上的動態權限檢測的方案,很多人第一時間就會想到我們是否可以像6.0以上設備一樣使用ContextCompat::checkSelfPermission()
。對此我們特地做了測試,然而在我們測試過程發現只有清單文件里面申明了該權限,那么不管用戶是否關閉或者開啟這一權限對應的開關,該方法都會返回true。這無疑宣告了該方案的根本無法滿足我們的需求。為了弄明白該方法為何會出現這一情況,我們從源碼入手,去驗證對應的邏輯。
在圖中可以看到ActivityCompat::requestPermissions()方法體里面會對當前api版本中做判斷,當api<23時,會調用PackageManager.checkPermission(),為了進一步了解我們接著去看PackageManager.checkPermission()方法體內有什么邏輯。
從圖上框出的文字中可以了解到判斷權限是否賦予是去判斷對應的package是否含有該權限,換句更容易理解的話來說就是如果該權限在清單文件中聲明了,即代表該權限被賦予。由此綜合前面的代碼片段來看當api<23時,使用
ContextCompat::checkSelfPermission()
是無法滿足我們的需求。那么除了使用
ContextCompat::checkSelfPermission()
還有什么辦法可以滿足我們的代碼需求。這里我們就不得不提起我們要介紹給大家的解決方案——AppOpsManager。
三、權限檢測解決方案——AppOpsManager
AppOpsManager究竟是什么呢?Google在Android開發者手冊對AppPosManager的描述為
API for interacting with "application operation" tracking.
這玩意說白了就是應用程序操作管理。AppOpsManager是在api 19引入,即Android 4.3。
通過查看Android開發者手冊,以及查看AppOpsManager的源碼我們會發現checkOp()的多態方法,其中checkOp(String op,int uid,String packageName)主要在api>23可以使用,而checkOp(int op,int uid,String packageName)則沒有這一限制。既然有了思路,那么就來介紹一下AppOpsManager針對權限控制應該主要那些東西。查看AppOpsManager的文檔,你會看到這樣一段文字:
This API is not generally intended for third party application developers; most features are only available to system applications.
這么一段文字扯了啥呢,其實主要表達的是AppOpsManager在設計之初并不是面向開發者。這一點其實在這個類的很多地方就體現出來了,同時也是我們需要注意的。在這個類中,有很多權限對應的常量被隱藏起來,如下圖所示:
與此同時,checkOp(int op,int uid,String packageName),這個方法本身也是hide,也就說我們無法通過AppOpsManager這個類正常調用該方法,以及訪問該屬性。走到這一步,然后再告訴大家,這個方法也走不通的話,我估計拍磚的肯定不少。那么處理這一問題,對于我們來說,這個時候當然少不了反射。你敢隱藏,只要你存在,我都要把你挖出來。以下為相關的代碼片段:
private boolean isPermissionGranted(String permissionCode) {
try {
Object object = getSystemService(Context.APP_OPS_SERVICE);
if (object == null) {
return false;
}
Class localClass = object.getClass();
Class[] arrayOfClass = new Class[3];
arrayOfClass[0] = Integer.TYPE;
arrayOfClass[1] = Integer.TYPE;
arrayOfClass[2] = String.class;
Method method = localClass.getMethod("checkOp", arrayOfClass);
if (method == null) {
return false;
}
Object[] arrayOfObject = new Object[3];
arrayOfObject[0] = Integer.valueOf(permissionCode);
arrayOfObject[1] = Integer.valueOf(Binder.getCallingUid());
arrayOfObject[2] = getPackageName();
int m = ((Integer) method.invoke(object, arrayOfObject)).intValue();
return m == AppOpsManager.MODE_ALLOWED;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
結語
權限動態檢測有多種解決方案,AppOpsManager只是其中一種維護成本相對較少的方案。其中在做權限動態檢測時,看到了大家提供了很多針對某一權限的腦洞極大的方案,不得不感慨這得被虐了死去活來才能得出的經驗教訓。同時有部分反應AppOpsManager這一解決方案在某些機型上依舊不能適配的問題,只能表示,
最后,AppOpsManager這個類還有潛在的彩蛋,通過這個工具可以打造一些裝逼利器,大家有興趣可以去挖掘。