android從MM開始,添加了動態(tài)權(quán)限機制欠拾。
針對一些特別的敏感權(quán)限胰锌,要求程序主動向用戶申請,用戶同意之后才可以使用相關(guān)權(quán)限清蚀。
個人覺得處理添加動態(tài)權(quán)限最方便的是寫一個基類(類似于PermissionBaseActivity匕荸,PermissionBaseFragment),然后讓需要申請權(quán)限的界面(某個Activity或者Fragment)繼承于對應(yīng)的基類枷邪。
因為在基類中已經(jīng)寫好了申請權(quán)限的基本方法榛搔,所以繼承自基類的子類,可以方便的處理權(quán)限相關(guān)的邏輯东揣。
以Activity為例践惑,新建基類PermissonBaseActivity繼承于AppCompatActivity
public abstract class PermissionBaseActivity extends AppCompatActivity {
private Set<String> mPermsAll = new HashSet();
private Set<String> mPermsNeedful = new HashSet();
protected String TAG;
protected Context mContext;
protected static final int PERMISSION_REQUEST_CODE = 110;
protected FragmentManager mFragmentManager;
private int mTransactionIndex = 1;
private int getTransactionId() {
return mTransactionIndex++;
}
private void resetTransactionId(@IntRange(from = 0) int v) {
mTransactionIndex = v;
}
public interface IPermissionCallback {
void onPermissionResult(@NonNull String perm, int result);
}
private static final String KEY_PERMISSION = "permission";
private final SparseArray<HashMap<String, IPermissionCallback>> mPermissionMapHandler = new SparseArray<>();
private IPermissionCallback pushPermissionsHolderData(int key, String[] perms, @NonNull IPermissionCallback permissionCallback) {
HashMap<String, IPermissionCallback> permKey = mPermissionMapHandler.get(key);
if (permKey == null) {
permKey = new HashMap<>();
}
permKey.clear();
for (String perm: perms) {
permKey.put(perm, permissionCallback);
}
mPermissionMapHandler.put(key, permKey);
return permissionCallback;
}
private void addPermissionsHolderGuardKey(int key, @NonNull IPermissionCallback permissionCallback) {
HashMap<String, IPermissionCallback> permKey = mPermissionMapHandler.get(key);
if (permKey == null) {
permKey = new HashMap<>();
}
permKey.put(KEY_PERMISSION, permissionCallback);
mPermissionMapHandler.put(key, permKey);
}
private IPermissionCallback removePermissionHolder(int key, @NonNull String perm) {
HashMap<String, IPermissionCallback> permCallback = mPermissionMapHandler.get(key);
if (permCallback != null) {
return permCallback.get(perm);
}
return null;
}
private IPermissionCallback getPermissionCallback(int key) {
HashMap<String, IPermissionCallback> permissionMap = mPermissionMapHandler.get(key);
for (IPermissionCallback callback: permissionMap.values()) {
if (callback != null) {
return callback;
}
}
return null;
}
private void iteratePermissionHolder(int key) {
HashMap<String, IPermissionCallback> permsMap = mPermissionMapHandler.get(key);
if (permsMap == null) {
return;
}
Iterator<Map.Entry<String, IPermissionCallback>> it = permsMap.entrySet().iterator();
Map.Entry<String, IPermissionCallback> entry;
while (it.hasNext()) {
entry = it.next();
if (!entry.getKey().equals(KEY_PERMISSION)) {
entry.getValue().onPermissionResult(entry.getKey(), PackageManager.PERMISSION_GRANTED);
}
}
mPermissionMapHandler.remove(key);
}
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getApplicationContext();
TAG = this.getClass().getSimpleName();
mFragmentManager = getFragmentManager();
}
protected void pushPermsData(String... perms) {
//略
}
protected void updatePermInfo() {
//略
}
protected boolean checkPermission(String perm) {
if (Build.VERSION.SDK_INT >= M) {
return ActivityCompat.checkSelfPermission(mContext, perm) == PackageManager.PERMISSION_GRANTED;
}
return true;
}
protected boolean checkPermState() {
updatePermInfo();
//略
}
protected ArrayList<String> updatePermissions(String[] perms, ArrayList<String> granted) {
if (perms == null) {
return null;
}
ArrayList<String> resultPerms = new ArrayList<>();
for (String perm : perms) {
if (ActivityCompat.checkSelfPermission(mContext, perm) == PackageManager.PERMISSION_GRANTED) {
granted.add(perm);
} else {
resultPerms.add(perm);
}
}
return resultPerms;
}
private void requestPermissionManage(@NonNull String[] perms, @NonNull IPermissionCallback permissionCallback) {
int key = getTransactionId();
if (key != -1 && (key & 0xffff0000) != 0) {
key = 1;
resetTransactionId(1);
}
if (perms.length == 1) {
//如果只申請一個權(quán)限,調(diào)用這里
requestSingleCompatPermission(key, perms[0], permissionCallback);
} else {
//申請多個權(quán)限嘶卧,調(diào)用這里
requestMultiCompatPermissions(key, perms, permissionCallback);
}
}
private void requestMultiCompatPermissions(@IntRange(from = 0) int key, @NonNull String[] perms, @NonNull IPermissionCallback permissionCallback) {
ArrayList<String> granted = new ArrayList<>();
//申請多個權(quán)限的時候尔觉,需要過濾權(quán)限,篩選出已經(jīng)獲得過的權(quán)限
ArrayList<String> needRequest = updatePermissions(perms, granted);
if (granted.size() > 0) {
if (needRequest.size() > 0) {
String[] grantedArray = new String[granted.size()];
granted.toArray(grantedArray);
//這個稍后補充說明
pushPermissionsHolderData(key, grantedArray, permissionCallback);
} else {
//如果想申請的所有權(quán)限都已經(jīng)被授予過了芥吟,那么直接回調(diào)返回
for (String perm: granted) {
permissionCallback.onPermissionResult(perm, PackageManager.PERMISSION_GRANTED);
}
return;
}
}
//TODO check ActivityCompat.shouldShowRequestPermissionRationale for every permission
if (needRequest.size() > 0) {
String[] permsArray = new String[needRequest.size()];
needRequest.toArray(permsArray);
addPermissionsHolderGuardKey(key, permissionCallback);
//開始申請那些還未獲得的權(quán)限
ActivityCompat.requestPermissions(this, permsArray, key);
}
}
private void requestSingleCompatPermission(@IntRange(from = 0) int key, @NonNull String perm, @NonNull IPermissionCallback permissionCallback) {
if (ActivityCompat.checkSelfPermission(mContext, perm) == PackageManager.PERMISSION_GRANTED) {
//如果要申請的權(quán)限已經(jīng)被授予侦铜,直接回調(diào)返回
permissionCallback.onPermissionResult(perm, PackageManager.PERMISSION_GRANTED);
} else {
//以前申請權(quán)限時专甩,用戶是否勾選過不再提示的復(fù)選框
boolean should = ActivityCompat.shouldShowRequestPermissionRationale(this, perm);
if (!should) {//如果勾選過,那么直接跳到APP詳細(xì)信息頁面
jumpToAppDetailsDialog();
} else {//開始申請權(quán)限
addPermissionsHolderGuardKey(key, permissionCallback);
ActivityCompat.requestPermissions(this, new String[]{perm}, key);
}
}
}
//申請權(quán)限從這里開始钉稍,String... perms就是需要申請的權(quán)限涤躲,permissionCallback是申請成功或者失敗后的回調(diào)
protected void requestPermissions(IPermissionCallback permissionCallback, String... perms) {
if (perms == null) {
return;
}
if (permissionCallback == null) {
return;
}
if (Build.VERSION.SDK_INT >= M) {
//如果是MM平臺以上,跳轉(zhuǎn)去處理申請權(quán)限
requestPermissionManage(perms, permissionCallback);
} else {
//如果MM平臺之前的贡未,直接處理回調(diào)
for (String perm: perms) {
permissionCallback.onPermissionResult(perm, PackageManager.PERMISSION_GRANTED);
}
}
}
protected void requestPermissions(IPermissionCallback permissionCallback) {
//略
}
protected void requestPermissions() {
//略
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
updatePermInfo();
permissionResultHandler(requestCode, permissions, grantResults);
}
private void permissionResultHandler(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (mPermissionMapHandler.size() > 0) {
//這里主要是回調(diào)而已
IPermissionCallback callback = getPermissionCallback(requestCode);
iteratePermissionHolder(requestCode);
for (int i = 0; i < permissions.length; i++) {
if (callback != null) {
callback.onPermissionResult(permissions[i], grantResults[i]);
}
}
}
}
protected void jumpToAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
private void jumpToAppDetailsDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle("Title")
.setMessage("Message")
.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
jumpToAppSettings();
}
})
.setNegativeButton("Exit", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create();
builder.show();
}
}
解釋下pushPermissionsHolderData(key, grantedArray, permissionCallback)
在開頭的時候种樱,定義了一個SparseArray:
private final SparseArray<HashMap<String, IPermissionCallback>> mPermissionMapHandler = new SparseArray<>();
IPermissionCallback pushPermissionsHolderData(int key, String[] perms,IPermissionCallback permissionCallback) { HashMap<String, IPermissionCallback> permKey = mPermissionMapHandler.get(key); if (permKey == null) { permKey = new HashMap<>(); } permKey.clear(); for (String perm: perms) {//以權(quán)限和回調(diào)接口建立鍵值對 permKey.put(perm, permissionCallback); } mPermissionMapHandler.put(key, permKey); return permissionCallback; }
每次申請權(quán)限,都為這一次權(quán)限申請事件賦予一個獨立的事務(wù)id : mTransactionIndex ,.
private int mTransactionIndex = 1; private int getTransactionId() { return mTransactionIndex++; }
當(dāng)申請權(quán)限時俊卤,以這個自定義的mTransactionIndex 做為申請權(quán)限傳入的requestCode:
int key = getTransactionId(); ActivityCompat.requestPermissions(this, permsArray, key);
這樣在onRequestPermissionsResult方法中嫩挤,就可以通過
HashMap<String, IPermissionCallback> permissionMap = mPermissionMapHandler.get(key);
獲取到正確的權(quán)限事件映射。
在方法onRequestPermissionsResult中消恍,獲取到requestCode對應(yīng)的HashMap鍵值對之后岂昭,就可以遍歷想申請的權(quán)限,然后回調(diào)申請的結(jié)果:
private void permissionResultHandler(int requestCode, String[] permissions, @NonNull int[] grantResults) { if (mPermissionMapHandler.size() > 0) { IPermissionCallback callback = getPermissionCallback(requestCode); iteratePermissionHolder(requestCode); for (int i = 0; i < permissions.length; i++) { if (callback != null) { callback.onPermissionResult(permissions[i], grantResults[i]); } } } }
大體流程就是這樣哺哼,具體的使用方法如下:
requestPermissions(new IPermissionCallback() { @Override public void onPermissionResult(@NonNull String perm, int result) { Log.e(TAG, "onPermissionResult2 " + perm + " " + result); } }, Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_CONTACTS, Manifest.permission.CAMERA );
不管用戶最后是同意還是拒絕權(quán)限佩抹,最后都回調(diào)到 onPermissionResult(@NonNull String perm, int result) 方法。perm表示權(quán)限的名字取董,result是這個perm權(quán)限申請的結(jié)果棍苹,是同意(0)還是拒絕(-1)。
我使用動態(tài)權(quán)限的想法是茵汰,比如在需要錄音權(quán)限的地方枢里,這樣使用
requestPermissions(new IPermissionCallback() { @Override public void onPermissionResult(@NonNull String perm, int result) { if (result == PackageManager.PERMISSION_GRANTED) { //開始錄音 } else { //無法錄音,添加用戶提示 } } }, Manifest.permission.RECORD_AUDIO);
這是個人偏好的用法蹂午,分享出來栏豺。