從6.0 MarshMallow開始,Android支持動態權限管理,即有些權限須要在使用到的時候動態申請,根據用戶的選擇須要有不一樣的處理,具體表現能夠看下圖:html
本文並不關心權限適配的原理,原理能夠參考[Android權限管理原理
](http://www.jianshu.com/p/9938...,這裏只是針對6.0中的表現作適配,先思考如下幾個問題:java
爲何6.0權限須要適配android
什麼權限須要動態適配git
怎樣動態適配權限github
怎麼樣實現第三方庫,簡化代碼及適配流程 權限兼容庫 PermissionCompat安全
對於國產ROM的影響網絡
6.0以前Android的權限都是在安裝的時候授予的,6.0以後,爲了簡化安裝流程,而且方便用戶控制權限,Android容許在運行的時候動態控制權限。對於開發而言就是將targetSdkVersion設置爲23,當運行在Android 6.0 +的手機上時,就會調用6.0相關的API,達到動態控制權限的目的。可是,若是僅僅是將targetSdkVersion設置爲23,而在代碼層面沒有針對Android 6.0作適配,就可能在申請系統服務的時候,因爲權限不足,引起崩潰。app
targetSDKVersion:該屬性用於通知系統,您已針對目標版本進行測試,標識App可以適配的系統版本,有些新的API是隻有新的系統纔有的。ide
並不是全部的權限都須要動態申請,Android6.0將權限分爲兩種,普通權限跟敏感(危險)權限,普通權限是不須要動態申請的,可是敏感權限須要動態申請。函數
一、普通權限(Normal permissions):不會泄露用戶隱私,同時也不會致使手機安全問題。如網絡請求權限、WIFI狀態等,這類權限只須要在Manifest列出來,以後,系統會自動賦給APP權限:
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
二、敏感權限(Dangerous permissions):與普通權限對應,可能會影響用戶的隱私,存儲數據等,好比拍照、存儲、通信錄、地理GPS等,這類權限須要在Manifest列出來,在須要的的時候,顯示的請求用戶准許。
CALENDAR
CAMERA
CONTACTS
LOCATION
PHONE
SENSORS
SMS
STORAGE
敏感權限的請求是按照分組進行提醒的,並不是僅僅針對一條,好比通信錄的讀取權限與寫權限,只要一個權限獲到,下次請求權限的時候會自動提供,固然也要請求。不然仍是有問題。
三、特殊權限(Special Permissions) --不在本文分析範圍
There are a couple of permissions that don't behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS
對於敏感權限的適配有一個原則,那就是實時檢查,由於權限隨時可能被回收,好比用戶能夠在設置裏面把權限給取消,可是APP並不必定知道,所以每次都須要檢查,一旦沒有,就須要請求,以後,根據返回結果處理後續邏輯。
一、在Manifest中列出來
不管普通權限仍是敏感權限,都須要在Manifest中列出來,同時也是對6.0以前的版本的一種兼容。 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.snail.labaffinity"> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CALL_PHONE"/>
二、須要時,顯示的請求
在權限沒被授予前提下,系統會顯示受權對話框,讓用戶操做,目前受權對話框不可定製,不過能夠在申請以前添加一些解釋,告訴用戶爲何須要該權限,可是Google提醒,不要作過多的解釋,可能會使用戶感到厭煩,用法以下:
ActivityCompat.requestPermissions(target.getActivity(), permissions, requestCode); public static void requestPermissions(final @NonNull Activity activity, final @NonNull String[] permissions, final int requestCode) { if (Build.VERSION.SDK_INT >= 23) { ActivityCompatApi23.requestPermissions(activity, permissions, requestCode); } else if (activity instanceof OnRequestPermissionsResultCallback) { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { final int[] grantResults = new int[permissions.length]; PackageManager packageManager = activity.getPackageManager(); String packageName = activity.getPackageName(); final int permissionCount = permissions.length; for (int i = 0; i < permissionCount; i++) { grantResults[i] = packageManager.checkPermission( permissions[i], packageName); } ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult( requestCode, permissions, grantResults); } }); } }
三、處理受權回調
兼容6.0以前的處理:在這裏只須要處理得到權限便可,由於6.0以前只存在Install權限,一旦安裝,全部權限都是默認授予的,雖然國內ROM對權限管理作了本身的一些定製,但基本都是兼容的。
須要對6.0的受權成功、失敗、永不詢問作處理
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if(this.mOnGrantedListener != null) { <!--6.0以前--> if(PermissionUtils.getTargetSdkVersion(this) < 23 && !PermissionUtils.hasSelfPermissions(this, permissions)) { this.mOnGrantedListener.onGranted(this, permissions); return; } //<!--6.0以後--> 須要根據結果進行驗證 if(PermissionUtils.verifyPermissions(grantResults)) { this.mOnGrantedListener.onGranted(this, permissions); } else if(!PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) { this.mOnGrantedListener.onNeverAsk(this, permissions); } else { this.mOnGrantedListener.onDenied(this, permissions); } } }
一、簡單的封裝回調
二、基於APT,採用註解方式簡化編碼邏輯,自動封封回調
先看一下直接回調的方式
首先在基類Activity或者Fragment中統一設置受權回調監聽,這裏咱們用一個
public class BasePermissionCompatActivity extends AppCompatActivity { private SparseArray<OnGrantedListener<BasePermissionCompatActivity>> mOnGrantedListeners = new SparseArray<>(); @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); OnGrantedListener<BasePermissionCompatActivity> listener = mOnGrantedListeners.get(requestCode); if (listener == null) return; if (PermissionUtils.verifyPermissions(grantResults)) { listener.onGranted(this, permissions); } else { if (PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) { listener.onDenied(this, permissions); } else { listener.onNeverAsk(this, permissions); } } } @Override protected void onDestroy() { super.onDestroy(); mOnGrantedListeners.clear(); mOnGrantedListeners = null; } public void requestPermissions(final @NonNull String[] permissions, OnGrantedListener<BasePermissionCompatActivity> onGrantedListener) { int requestCode = getNextRequestCode(); ActivityCompat.requestPermissions(this, permissions, requestCode); mOnGrantedListeners.put(requestCode, onGrantedListener); } private static int sNextCode; private static int getNextRequestCode() { return sNextCode++; } }
以後在須要時候的請求,並根據結果處理後續邏輯便可。
requestPermissions(activity, P_CAMERA, new OnGrantedListener() { // 根據permissions自行處理,可合併,可分開 @Override public void onGranted(SecondActivity target, String[] permissions,int requestCode) { } @Override public void onDenied(SecondActivity target, String[] permissions,int requestCode) { } @Override public void onNeverAsk(SecondActivity target, String[] permissions,int requestCode) { } @Override public void onShowRationale(SecondActivity target, String[] permissions,int requestCode) { });
上面的方法比較直接,靈活,不過每次都要本身實現回調監聽Listener,接下來看第二種實現,基於APT,經過註解的方式,自動添加Listener,這種實現參考了ButterKnife的實現方式。
一、基於APT,定義一系列Annotation,並動態生成輔助Listener類
二、添加Android支持庫,在基類統一處理回調,
三、添加工具類,鏈接綁定Listener與Activity(Fragment)
相應的實現分三個庫:
註解庫
APT生成支持庫
Android支持庫
主要用來定義一些回調方法註解、及請求實體的類註解
* ActivityPermission * FragmentPermission * OnDenied * OnGranted * OnGrantedListener * OnNeverAsk * OnShowRationale
主要用來在編譯階段,動態生Listener類
PermissionProcessor.java
部分參考代碼:
@AutoService(Processor.class) public class PermissionProcessor extends AbstractProcessor { private Elements elementUtils; private Set<Class<? extends Annotation>> getSupportedAnnotations() { Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>(); annotations.add(OnDenied.class); annotations.add(OnGranted.class); annotations.add(OnNeverAsk.class); annotations.add(OnShowRationale.class); return annotations; } @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); elementUtils = env.getElementUtils(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (!checkIntegrity(roundEnv)) return false; Set<? extends Element> elementActivities = roundEnv.getElementsAnnotatedWith(ActivityPermission.class); Set<? extends Element> elementFragments = roundEnv.getElementsAnnotatedWith(FragmentPermission.class); return makeListenerJavaFile(elementActivities) && makeListenerJavaFile(elementFragments); } ...
主要會封裝了一些工具類,基類以及對回調的處理
* BasePermissionCompatActivity.java * BasePermissionCompatFragment.java * PermissionCompat.java * PermissionUtils.java
參考代碼:
public class PermissionCompat { private static int sNextRequestCode; static final Map<Class<?>, OnGrantedListener> BINDERS = new LinkedHashMap<>(); // 分批次請求權限 public static void requestPermission(BasePermissionCompatActivity target, String[] permissions) { Class<?> targetClass = target.getClass(); try { // 找到監聽Listener類,並實例一個 OnGrantedListener<BasePermissionCompatActivity> listener = findOnGrantedListenerForClass(targetClass, permissions); if (PermissionUtils.hasSelfPermissions(target, permissions)) { listener.onGranted(target, permissions); } else if (PermissionUtils.shouldShowRequestPermissionRationale(target, permissions)) { // 拒絕過,再次請求的時候,這個函數是否有必要,不在詢問後,返回false,第一次返回false, //listener.onShowRationale(target, permissions); startRequest(target, listener, permissions); } else { startRequest(target, listener, permissions); } } catch (Exception e) { throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e); } } private static void startRequest(BasePermissionCompatActivity target, OnGrantedListener listener, final @NonNull String[] permissions) { target.setOnGrantedListener(listener); ActivityCompat.requestPermissions(target, permissions, getNextRequestCode()); }
一、Activity繼承BasePermissionCompatActivity
二、用註解寫回調函數,支持權限分組,跟單獨處理,可是每一個分組都要寫本身的回調函數(目前回調函數,不支持參數)
三、回調必需配套,也就是一個權限必須對應四個函數,不然編譯不經過
四、請求的權限必須有回調函數,否則報運行時錯誤--崩潰
@ActivityPermission
public class PermssionActivity extends BasePermissionCompatActivity {
。。。 @OnGranted(value = {Manifest.permission.CAMERA}) void granted() { LogUtils.v("granted"); } @OnDenied(value = {Manifest.permission.CAMERA}) void onDenied() { LogUtils.v("onDenied"); } @OnNeverAsk(value = {Manifest.permission.CAMERA}) void OnNeverAsk() { LogUtils.v("OnNeverAsk"); } @OnShowRationale(value = {Manifest.permission.CAMERA}) void OnShowRationale() { LogUtils.v("OnShowRationale"); } <!--什麼時候的時機調用--> @OnClick(R.id.get) void get() { PermissionCompat.requestPermission(this, new String[]{Manifest.permission.CAMERA}); }
}
6.0以前權限管理即不是原生功能又沒有制定相應標準,每一個廠家的實現都是徹底不一樣的,雖然4.3 Google官方試圖推出AppOpsManager來動態適配權限管理,但因爲不成熟,一直到6.0也沒走向前臺。不過,看6.0以前國內ROM的表現,基本是在每一個服務內部觸發鑑權請求,對原生權限的判斷並沒多大影響,因此兼容沒太大問題。
最後附上GitHub Demo及第三方庫連接 權限兼容庫 PermissionCompat
做者:看書的小蝸牛
原文連接: Android6.0權限適配及兼容庫的實現
一、Requesting Permissions at Run Time
二、PermissionDispatcher
三、Android6.0權限適配之WRITE_EXTERNAL_STORAGE(SD卡寫入)