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 = -1;
private static OnPermissionListener mOnPermissionListener;
public interface OnPermissionListener {
void onPermissionGranted();
void onPermissionDenied(String[] deniedPermissions, boolean alwaysDenied);
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestPermissionsAgain(@NonNull Context context, @NonNull String[] permissions,
@NonNull int requestCode) {
if (context instanceof Activity) {
((Activity) context).requestPermissions(permissions, requestCode);
} else {
throw new IllegalArgumentException("Context must be an Activity");
}
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestPermissions(@NonNull Context context, @NonNull int requestCode,
@NonNull String[] permissions, OnPermissionListener listener) {
mRequestCode = requestCode;
mOnPermissionListener = listener;
String[] deniedPermissions = getDeniedPermissions(context, permissions);
if (deniedPermissions.length > 0) {
requestPermissionsAgain(context, permissions, requestCode);
} else {
if (mOnPermissionListener != null) mOnPermissionListener.onPermissionGranted();
}
}
/**
* 請求權限結果,對應Activity中onRequestPermissionsResult()方法。
*/
public static void onRequestPermissionsResult(@NonNull Activity context, int requestCode,
@NonNull String[] permissions, int[] grantResults) {
if (mRequestCode != -1 && requestCode == mRequestCode) {
if (mOnPermissionListener != null) {
String[] deniedPermissions = getDeniedPermissions(context, permissions);
if (deniedPermissions.length > 0) {
boolean alwaysDenied = hasAlwaysDeniedPermission(context, permissions);
mOnPermissionListener.onPermissionDenied(deniedPermissions, alwaysDenied);
} else {
mOnPermissionListener.onPermissionGranted();
}
}
}
}
/**
* 獲取請求權限中需要授權的權限
*/
private static String[] getDeniedPermissions(@NonNull Context context, @NonNull 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 boolean hasAlwaysDeniedPermission(@NonNull Context context, @NonNull 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
,只要有一個權限需要提示就返回true2>判斷是否徹底禁止權限方法
hasAlwaysDeniedPermission
,只要有一個徹底禁止就返回true3>為了節省代碼在發起請求與請求結果中用了同樣的方法獲取未授權的權限
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 = 0; i < permissions.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i]);
}
}
return deniedPermissions.toArray(new String[deniedPermissions.size()]);
}
0x03:使用方式
以打開相機為例
1、首先AndroidManifest
中配置必要的權限
<uses-permission android:name="android.permission.CAMERA"/>
2、在基類中加上回調方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
XPermissionUtils.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
3、調用工具類方法
XPermissionUtils.requestPermissions(Context context, int requestCode, String[] permissions, OnPermissionListener listener)
這里主要注意這個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(final String[] deniedPermissions, boolean alwaysDenied) {
Toast.makeText(context, "獲取相機權限失敗", Toast.LENGTH_SHORT).show();
if (alwaysDenied) { // 拒絕后不再詢問 -> 提示跳轉到設置
DialogUtil.showPermissionManagerDialog(MainActivity.this, "相機");
} else { // 拒絕 -> 提示此公告的意義,并可再次嘗試獲取權限
new AlertDialog.Builder(context).setTitle("溫馨提示")
.setMessage("我們需要相機權限才能正常使用該功能")
.setNegativeButton("取消", null)
.setPositiveButton("驗證權限", new DialogInterface.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(DialogInterface dialog, int which) {
XPermissionUtils.requestPermissionsAgain(context, deniedPermissions,
RequestCode.CAMERA);
}
})
.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, boolean alwaysDenied) {
StringBuilder sBuilder = new StringBuilder();
for (String deniedPermission : deniedPermissions) {
if (deniedPermission.equals(Manifest.permission.WRITE_CONTACTS)) {
sBuilder.append("聯系人");
sBuilder.append(",");
}
if (deniedPermission.equals(Manifest.permission.READ_SMS)) {
sBuilder.append("短信");
sBuilder.append(",");
}
}
if (sBuilder.length() > 0) {
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
Toast.makeText(context, "獲取" + sBuilder.toString() + "權限失敗", Toast.LENGTH_SHORT).show();
if (alwaysDenied) {
DialogUtil.showPermissionManagerDialog(MainActivity.this, sBuilder.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