Android M之後,有的在manifest定位的權限需要在運行時動態申請纔可以使用,這方面的詳細信息可以參考官方文檔:https://developer.android.com/training/permissions/requesting?hl=zh-cn,一般是需要申請權限時,需要檢查是否已經有權限,如果沒有則申請,主要使用到如下步驟:
首先使用ContextCompat.checkSelfPermission檢查,第一參數是context,第二個是要檢查的權限名稱,然後用ActivityCompat.requestPermissions去申請權限,第一個參數是context,第二個是要申請的權限,第三個是這次申請的requestCode
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
在調用ActivityCompat.requestPermissions申請後,系統會彈出一個提示框提示用戶是否允許,當用戶點了允許或拒絕時會回調到activity的onRequestPermissionsResult函數:
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
第一個參數是申請時的requestCode,第二個是申請的權限,第三個是申請權限的結果.
以上是官方的教程,如果按這個教程去做的話,你需要在每個activity都寫一遍onRequestPermissionsResult回調的處理,這對於代碼的維護來說是非常不好的,也干擾了正常的業務邏輯代碼。那提取一個權限申請庫就非常有必要了,要實現申請功能並不難,難點在於,怎麼讓申請庫的代碼不跟原有的activity代碼耦合在一起呢,下面就介紹一種解耦的方法:
方法一:
自己建一個用來權限申請沒有界面的activity,在申請是啓動這個activity去申請,然後在這個activty裏的回調來接收結果,這種方法可以做到解耦,但是需要在manifest裏定義這個activity,AndPermission就是這樣實現的,這樣個人感覺還不夠極致,那進一步的方法是方法
方法二:
這種方法是不使用activity了,而是使用Fragment,因爲Fragment也有activity一樣的申請權限函數和回調,這種就不用在manifest中定義什麼了,RxPermission就是這樣實現的
這裏不去具體講實現的代碼了,這裏是想分享一種跟生命週期有關的邏輯怎麼跟原有代碼邏輯解耦方法,上面兩種其實都是可以達到目的的方法,這種方法在一些開源庫中其實都有,在Glide中,就有使用Fragment在onDestroy時做資源釋放的,代碼:
// RequestmanagerRetriever.java
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
// RequestManagerFragment.java
@NonNull
ActivityFragmentLifecycle getGlideLifecycle() {
return lifecycle;
}
@Override
public void onStop() {
super.onStop();
lifecycle.onStop();
}
// RequestManagerFragment.java
@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
unregisterFragmentWithRoot();
}
Glide在Fragment的onDestory會調用Glide的lifecycle的onDestroy,然後做資源的釋放,這個使用的過程不會有任何干擾,開發者只管放心地使用,那用這種方式使用的權限申請庫也是一樣,開發者不用關心這些細節,只要簡單的調用接口就好,下面實現的申請庫使用示例:
PermissionChecker.with(this)
.permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, "存儲權限不可用", "XXX需要存儲權限以正常工作,是否允許?")
.permission(Manifest.permission.ACCESS_COARSE_LOCATION, "定位權限不可用", "XXX需要定位權限以正常工作,是否允許?")
.onAllow(new PermissionChecker.AllowCallback() {
@Override
public void onAllow(String permission) {
Toast.makeText(MainActivity.this, "allow", Toast.LENGTH_SHORT).show();
}
}).onDeny(new PermissionChecker.DenyCallback() {
@Override
public void onDeny(String permission) {
Toast.makeText(MainActivity.this, "deny", Toast.LENGTH_SHORT).show();
}
}).build().check();