權限設立的目的是保護安全。
一.權限機制:
Android底層是基於Linux系統的,而Linux權限訪問由進程和文件兩個部分組成。
系統權限分爲三種類型:
- Android所有者權限,相當於擁有Android Rom開發權限,可以獲取所有的權限;
- Android Root權限,相當於取得Linux系統中的最高用戶權限,可以任意對文件進行修改;
- Android應用程序權限,獲取只能通過AndroidManifest中聲明權限,然後由用戶授權來獲取。
protectionLevel權限水平分爲:normal/dangerous/signature/signatureOrSystem:
normal:風險普通,安裝時不會直接提示用戶,點擊全部纔會展示;
dangerous:風險較高,任何應用都可以申請,安裝時需要用戶確認才能使用;
signature:僅當申請改權限的應用程序與聲明改權限的應用程序位於相同的簽名時,才能賦予權限;
signatureOrSystem:僅當申請權限的應用程序位於相同的Android系統鏡像中,或者申請權限的應用程序和聲明該權限的程序擁有相同簽名時,才能賦予權限。
二.動態權限AppOps:
在原生的ROM中,AppOps權限設置在系統的SettingsApp中,路徑爲Settings->權限管理。在AppOps中可以看到安裝的應用所要申請的權限列表,可以選擇允許權限和禁止權限,還有使用時提示。
AppOps運行原理:
- 在SettingsUI中請求更改調整權限的變更;
- AppOpsManager提供訪問接口;
- AppOpsService提供真正實現權限控制;
- app_ops.xml位於每個App的/data/system/中,存儲各個App的權限設置和操作信息;
- 連接到其他不同的服務,告知其需要變更響應的設置。
從Android6.0(API23)開始便具有AppOps,這樣可以讓用戶在安裝時節省時間,而且可以更方便地控制應用的權限。用戶可以按照對應用的需求控制應用的權限。
在Android O(API26)之前,如應用在運行時請求權限並且被授予該權限,系統會錯誤地將屬於同一權限組在清單中註冊的其他權限也一起授予該應用。在Android O之後的應用,則此行爲被糾正。系統只會授予應用明確的請求權限,然而一旦用戶爲一應用授予某個權限,則所有後續對該權限組中權限的請求都將被自動批准。
三.組件化權限:
我們可以將normal級別的權限申請都放到Base module中,然後在各個module中分別申請dangerous的權限。這樣分配的好處在於當添加或移除單一權限時,隱私權限的申請也會跟隨移除,能做到最大程序的權限解耦。
當項目需要適配到Android6.0以上的動態權限申請時,需要在Base module中添加自己封裝的一套權限申請工具,其他組件層的module都可以使用這套工具。
四.動態權限框架:
AndPermission是一款封裝性並使用的工具。
- 請求權限使用建造者模式來封裝:
AndPermission.with(this)
.runtime()
.permission(Permission.Group.STORAGE)
.onGranted(permissions -> {
// Storage permission are allowed.
})
.onDenied(permissions -> {
// Storage permission are not allowed.
})
.start();
- 適配方案:
部分國內ROM的rational功能,第一次拒絕後,不會再返回true,並回調申請失效,因爲第一次拒絕後默認勾選不再提示,建議使用settingDialog,提示用戶在系統設置中授權。
如使用權限提示框選擇權限,AppOpsManger中的併爲同步。這裏可以判斷爲運行時擁有權限或AppOpsManger。
四.路由攔截:
當調用其他模塊的功能時,很可能獨立跳轉到該模塊的一個單獨頁面。此時就是路由攔截就起作用了。將路由攔截和權限申請結合在一起,攔截跳轉的同時進行權限申請的驗證處理。當ARouter跳轉前會遍歷Intercept,然後通過判斷跳轉路徑來來找到需要攔截的對象。
在Application的registerActivityLifecycleCallback方法可以當前最頂層Activity對象。
public class TopBaseApplication extends Application {
private static Activity topConext;
@RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onCreate() {
super.onCreate();
this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
//在創建時設置
topConext = activity;
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
//在Activity聲明週期時設置Activity對象
topConext = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
//獲取底層Activity對象
public static Activity getTopConext() {
return topConext;
}
}
在Application需要繼承TopBaseApplication。其他功能模塊都可以使用全局方法getTopContext獲取到頂層Activity對象。
如一個ActivityA返回到它之前的ActivityB頁面後,在生命週期中設置context全局對象,ActivityB調用destroy方法時,ActivityB對象還被全局content引用,將引起內存泄露。
ActivityB返回會先調用ActivityB onPause的生命週期,然後再調用Activity onstart和onResume生命週期,稍後Activity會調用onPause和onDestroy。
可以肯定的是,ActivityA的onResume肯定會比ActivityB的onDestroy提前完成,這意味着,在onResume已經設定了返回頁面的Activity對象爲頂層Activity時,ActivityB調用onDestroy,靜態context不會強引用ActivityB對象,也不會造成內存泄露。