0x00:前言
對于Android6.0運作時權限的處理方式網上有很多,包括注解,RxJava等等。一直沒有正面提到我關心的問題–如果我不在Activity或者Fragment裡面,需要運作時權限該怎麼去做?導緻我開始一直以為運作時權限的處理必需要在Activity或者Fragment之中。
那麼:
我有一個錄音的自定義控件在很多頁面需要使用怎麼辦?
我有一個聯系人清單,要在adapter裡面撥打電話怎麼辦?
我有一個定位的工具類要在多個頁面使用怎麼辦?
等等…
之前我還問過一些同行,他說用回調,回調到Activity或者Fragment,我當時覺得是一種解決方案,但是卻很麻煩,如果有多個頁面使用,那不是要處理很多次。
直到某一天在github上看到一個分享了簡單的工具類MPermissionUtils ,一下子解決了我的疑惑,雖然他也沒有明确給出答案,但是我從他的使用上卻恍然大悟,原來是一開始我就了解錯了。我們隻需要把系統回調方法
onRequestPermissionsResult
放到BaseActivity裡面,當然你所有的用到權限的Activity必需繼承自BaseActivity,将處理結果通過工具類調出來,加一個自定義的回調到請求的發起處即可。
因為你要用到運作時權限的地方總要依賴于Activity的存在,如果不再Activity裡面或者目前代碼擷取不到Activity,那就傳過去,一切的處理結果都會回到你發起請求所在的Activity。
那麼一不做二不休,我們這時候有沒有考慮Fragment裡面的處理其實是多餘的,我們可不可以都放到Activity裡面來處理。于是就化繁為簡産生了我的XPermissionUtils
0x01:代碼實作
public class XPermissionUtils {
private static int mRequestCode = -;
private static OnPermissionListener mOnPermissionListener;
public interface OnPermissionListener {
void onPermissionGranted();
void onPermissionDenied(String[] deniedPermissions);
}
public abstract static class RationaleHandler {
private Context context;
private int requestCode;
private String[] permissions;
protected abstract void showRationale();
void showRationale(Context context, int requestCode, String[] permissions) {
this.context = context;
this.requestCode = requestCode;
this.permissions = permissions;
showRationale();
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissionsAgain() {
((Activity) context).requestPermissions(permissions, requestCode);
}
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestPermissions(Context context, int requestCode
, String[] permissions, OnPermissionListener listener) {
requestPermissions(context, requestCode, permissions, listener, null);
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestPermissions(Context context, int requestCode
, String[] permissions, OnPermissionListener listener, RationaleHandler handler) {
if (context instanceof Activity) {
mRequestCode = requestCode;
mOnPermissionListener = listener;
String[] deniedPermissions = getDeniedPermissions(context, permissions);
if (deniedPermissions.length > ) {
boolean rationale = shouldShowRequestPermissionRationale(context, deniedPermissions);
if (rationale && handler != null) {
handler.showRationale(context, requestCode, deniedPermissions);
} else {
((Activity) context).requestPermissions(deniedPermissions, requestCode);
}
} else {
if (mOnPermissionListener != null)
mOnPermissionListener.onPermissionGranted();
}
} else {
throw new RuntimeException("Context must be an Activity");
}
}
/**
* 請求權限結果,對應Activity中onRequestPermissionsResult()方法。
*/
public static void onRequestPermissionsResult(Activity context, int requestCode, String[] permissions, int[]
grantResults) {
if (mRequestCode != - && requestCode == mRequestCode) {
if (mOnPermissionListener != null) {
String[] deniedPermissions = getDeniedPermissions(context, permissions);
if (deniedPermissions.length > ) {
mOnPermissionListener.onPermissionDenied(deniedPermissions);
} else {
mOnPermissionListener.onPermissionGranted();
}
}
}
}
/**
* 擷取請求權限中需要授權的權限
*/
private static String[] getDeniedPermissions(Context context, String[] permissions) {
List<String> deniedPermissions = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) {
deniedPermissions.add(permission);
}
}
return deniedPermissions.toArray(new String[deniedPermissions.size()]);
}
/**
* 是否徹底拒絕了某項權限
*/
public static boolean hasAlwaysDeniedPermission(Context context, String... deniedPermissions) {
for (String deniedPermission : deniedPermissions) {
if (!shouldShowRequestPermissionRationale(context, deniedPermission)) {
return true;
}
}
return false;
}
/**
* 是否有權限需要說明提示
*/
private static boolean shouldShowRequestPermissionRationale(Context context, String... deniedPermissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
boolean rationale;
for (String permission : deniedPermissions) {
rationale = ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission);
if (rationale) return true;
}
return false;
}
}
0x02:實作思路
在最開始的時候本人的實作沒有支援
shouldShowRequestPermissionRationale
,是本人的疏忽。因為開始我用的小米手機沒有這個功能,後來發現有的手機支援有的不支援。顧名思義這個方法的意思是否需要給使用者申請該權限的提示,當使用者拒絕權限之後如果沒有勾選不再提示,下次申請權限的時候可以加一個自定義的彈窗提示,使用者點繼續驗證可以再次驗證權限。
大緻實作思路如下:
注意:
1>判斷是否需要提示方法
shouldShowRequestPermissionRationale
,隻要有一個權限需要提示就傳回true
2>判斷是否徹底禁止權限方法
hasAlwaysDeniedPermission
,隻要有一個徹底禁止就傳回true
3>為了節省代碼在發起請求與請求結果中用了同樣的方法擷取未授權的權限
private static String[] getDeniedPermissions(Context context, String[] permissions) {
List<String> deniedPermissions = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) {
deniedPermissions.add(permission);
}
}
return deniedPermissions.toArray(new String[deniedPermissions.size()]);
}
此外在請求結果的時候還可以用另外的方法擷取,結果是一樣的
private static String[] getDeniedPermissions(String[] permissions, int[] grantResults) {
List<String> deniedPermissions = new ArrayList<>();
for (int i = ; i < permissions.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i]);
}
}
return deniedPermissions.toArray(new String[deniedPermissions.size()]);
}
0x03:使用方式
以打開相機為例
1、首先 AndroidManifest
中配置必要的權限
AndroidManifest
<uses-permission android:name="android.permission.CAMERA"/>
2、在基類中加上回調方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
grantResults) {
XPermissionUtils.onRequestPermissionsResult(requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
3、調用工具類方法
1>拒絕後無提示
XPermissionUtils.requestPermissions(Context context, int requestCode, String[] permissions, OnPermissionListener listener)
2>拒絕後再次申請給出提示
XPermissionUtils.requestPermissions(Context context, int requestCode, String[] permissions, OnPermissionListener listener, RationaleHandler handler)
這裡主要注意這個Context必需是一個Activity
如果在Activity中可以傳
this
;
如果在Fragment中傳
getActivity()
;
如果在View中傳
getContext()
;
等等…..
private void doOpenCamera() {
XPermissionUtils.requestPermissions(this, RequestCode.CAMERA, new String[]{Manifest
.permission.CAMERA}
, new XPermissionUtils.OnPermissionListener() {
@Override
public void onPermissionGranted() {
if (PermissionHelper.isCameraEnable()) {
Toast.makeText(MainActivity.this, "打開相機操作", Toast.LENGTH_LONG).show();
} else {
DialogUtil.showPermissionManagerDialog(MainActivity.this, "相機");
}
}
@Override
public void onPermissionDenied(String[] deniedPermissions) {
Toast.makeText(context, "擷取相機權限失敗", Toast.LENGTH_SHORT).show();
if (XPermissionUtils.hasAlwaysDeniedPermission(context, deniedPermissions)) {
DialogUtil.showPermissionManagerDialog(MainActivity.this, "相機");
}
}
}, new XPermissionUtils.RationaleHandler() {
@Override
protected void showRationale() {
new AlertDialog.Builder(context)
.setTitle("溫馨提示")
.setMessage("我們需要相機權限才能正常使用該功能")
.setNegativeButton("取消", null)
.setPositiveButton("驗證權限", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissionsAgain();
}
}).show();
}
});
}
4、一次申請多個權限
使用者可能部分拒絕,是以在
onPermissionDenied(String[] deniedPermissions)
回調中傳回了請求結果中所有被拒絕的權限,使用者可用于比對判斷出哪些權限被拒絕,給使用者明确的提示
private void doMorePermission() {
XPermissionUtils.requestPermissions(this, RequestCode.MORE, new String[]{Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_SMS}, new XPermissionUtils.OnPermissionListener() {
@Override
public void onPermissionGranted() {
Toast.makeText(context, "擷取聯系人,短信權限成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionDenied(String[] deniedPermissions) {
StringBuilder sBuider = new StringBuilder();
for (String deniedPermission : deniedPermissions) {
if (deniedPermission.equals(Manifest.permission.WRITE_CONTACTS)) {
sBuider.append("聯系人");
sBuider.append(",");
}
if (deniedPermission.equals(Manifest.permission.READ_SMS)) {
sBuider.append("短信");
sBuider.append(",");
}
}
if (sBuider.length() > ) sBuider.deleteCharAt(sBuider.length() - );
Toast.makeText(context, "擷取" + sBuider.toString() + "權限失敗", Toast.LENGTH_SHORT).show();
if (XPermissionUtils.hasAlwaysDeniedPermission(context, deniedPermissions)) {
DialogUtil.showPermissionManagerDialog(MainActivity.this, sBuider.toString());
}
}
});
}
0x04:各種運作時權限處理詳談
其實在6.0之前已經存在運作時權限,隻不過沒有明确提出這個概念,在6.0之前,擷取位置、讀取通訊錄、拍照、錄音等都是需要在操作的時候去擷取權限的。那麼這些權限的差別是6.0以後需要我們去寫請求擷取權限的代碼,而之前是當代碼執行到需要權限的地方就會彈出提示框。
那麼針對不同的權限可能有不同的處理方式,下面簡單列舉,如果需要看代碼可以在源碼的Demo中檢視
1、撥打電話
撥打電話在某些手機上(如小米)拒絕之後是每次申請都有提示的,因為他顯示的是“拒絕一次”。撥打電話其實如果不是産品要求直接撥出去可以使用調轉到撥号頁面實作的,這個不需要權限:
Intent intent = new Intent(Intent.ACTION_DIAL);
Uri data = Uri.parse("tel:10010");
intent.setData(data);
startActivity(intent);
2、錄音
(1)錄音權限在6.0之前是無法判斷是否擷取權限的,隻能通過非正常的方法擷取,詳見項目Demo
(2)長按按鈕錄音,在第一次擷取權限的時候需要特殊處理,彈出擷取權限的提示框之後手指已經離開,不能進行錄音的操作。
3、打開相機
相機權限在6.0之前同樣也是無法判斷是否擷取權限的,隻能通過非正常的方法擷取,詳見項目Demo
4、擷取位置
(1)首先手機需要開啟位置服務,如果沒有開啟,那麼即使app開啟擷取位置權限也是擷取不到的
(2)在6.0以下沒有辦法判斷是否開啟位置權限,可以根據具體使用場景進行判斷。
(3)(使用系統Api)要注意在室内如果選擇Gps定位會擷取不到位置,這裡可以參考Demo中
LocationUtils
的實作思路。
(4)使用百度或者高德地圖可能不适用,因為他自己已經帶有請求權限的處理,貌似不需要系統權限也能定位,沒有深入研究。
5、擷取外部存儲
這個在有些手機上比較特殊,比如打開圖庫這樣的功能,在小米手機上就不需要運作時權限,華為就需要,這個還是需要在使用的時候主動請求一下。
0x05 特别鳴謝
MPermissionUtils
PermissionGen
AndPermission
如有不足,歡迎指正。最後附上源碼位址
XPermissionUtils
轉載請注明出處http://www.jianshu.com/p/4a60b064a0ab