說起來這是第一次在簡書寫文章,由于最近項目使用的機器是Android 6.0的機器,一些危險的權限就需要我們在運行時自己來動態的申請了,所以今天就抽空寫下運行時權限管理。
前言
眾所周知,在API23之前,只要在AndroidManifest.xml中注冊過的權限,程序運行時都會自動獲取到。但是到了23及更高,危險的權限就需要我們自己來動態的申請了,而此時用戶也就有了拒絕我們需要的權限的權力,這當然會導致我們程序的運行不正常,甚至是造成程序的崩潰。所以我們就需要盡可能的提示用戶同意我們的權限申請。
好了,講了一堆廢話,今天就以申請"打電話"權限這個例子來講一下在實際項目中,我們應該怎么去申請權限。
必知
系統權限分為幾個保護級別。需要了解的兩個最重要保護級別是正常權限和危險權限。首先,你必須得知道哪些是需要動態申請的權限,也就是我前面所提到的危險權限,并且危險權限也分組,所以到底哪些是危險權限呢?想知道其實很簡單,官方文檔早就告訴了我們,相信有的童鞋早已看到過,這里貼一下地址:https://developer.android.com/guide/topics/security/permissions.html
看不了的童鞋也沒事,我這里直接把頁面截出來了:
準備
這里以PHONE權限組的CALL_PHONE權限舉例講下怎么申請。
首先在AndroidManifest.xml中加入我們想要的權限:
<uses-permission android:name="android.permission.CALL_PHONE" />
必須要強調一下,如果申請的權限在AndroidManifest.xml中并沒有注冊的話,是不會彈出權限申請窗口的。:)
然后看下布局:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.AppCompatButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/button_activity_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/permission_request"
tools:context=".BaseActivity" />
嗯,是不是XML中的一股清流,這樣就完啦?完了...
呵呵,這里就是為了演示,我就用了1個Button,點擊時去申請權限,實際中肯定是在進入應用時或者使用到相關功能前就得申請了。
至此,準備工作完成。
開始
由于項目中肯定會有很多地方需要申請權限,所以這里我把權限管理的重復的邏輯寫在了基類之中。
可能沒有使用過6.0及更高版本手機的童鞋并不知道這個窗口是怎樣的,在這里我還是截了出來:
哦,這里我用的是Nexus 6P,想入手的童鞋千萬別買,2K屏,一天3充。
先把需要申請的權限寫成常量,這里就是Manifest.permission.CALL_PHONE這個權限:
public static final String PERMISSION_CALL_PHONE = Manifest.permission.CALL_PHONE;
然后看下判斷權限是否已經被用戶授予的函數:
public boolean hasPermission(String... permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
核心函數就是ContextCompat.checkSelfPermission();,從字面意思大家也能看出來是什么函數。參數是我們需要驗證的權限,值得注意的是參數類型是String...,所以我們既可以傳入一個String,也可以傳入String[],也就是說我們可以傳入一組權限。當它返回true時表示權限已經被授予,反之就表示權限未被授予,這個時候我們就需要去主動的彈出權限申請的窗口了,requestCode在接下來會用到:
public void requestPermission(int requestCode, String... permissions) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
}
在基類中我還寫了三個函數,分別是權限授予成功時執行的函數、權限授予失敗時執行的函數以及讓自定義的對話框消失的函數,這個對話框不是系統自身彈出的對話框。
public abstract void requestPermissionSuccess();
public abstract void requestPermissionFail();
public abstract void dismissDialog();
前面說到requestPermission()是用來請求權限的,其中有一個requestCode,這個requestCode就是用來判斷是否執行requestPermissionSuccess()和requestPermissionFail()的。
在基類中我們重寫onRequestPermissionsResult(),它就是requestPermission()回調的函數,可以看到我們根據grantResults[0]的值來執行成功或失敗的函數。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case Constants.REQUEST_CODE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
requestPermissionSuccess();
} else {
requestPermissionFail();
}
break;
default:
break;
}
}
接著
子類繼承基類之后,看看子類需要做什么:
點擊Button之后會啟動callPhone(),這個函數的作用就是判斷權限是否被授予了,如果true,執行requestPermissionSuccess(),如果false,繼續請求授予,也就是繼續彈出權限申請窗口:
private void callPhone() {
if (hasPermission(PERMISSION_CALL_PHONE)) {
requestPermissionSuccess();
} else {
requestPermission(Constants.REQUEST_CODE_PERMISSION, PERMISSION_CALL_PHONE);
}
}
授予成功時還需要判斷到底有沒有被授予權限,否則代碼會警告我們判斷是否被授予權限。然后當然就是打電話,這里不厚道地打向了10086,大家可別學我啊:):
@Override
public void requestPermissionSuccess() {
if (ActivityCompat.checkSelfPermission(context, PERMISSION_CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
return;
}
Intent intent = new Intent(Intent.ACTION_CALL);
Uri uri = Uri.parse("tel:" + "10086");
intent.setData(uri);
startActivity(intent);//打電話
ToastUtil.toastLong(context, getString(R.string.permission_success));
}
在這里失敗時分兩種情況:第一種是用戶會不斷的拒絕,此時首先我們在用戶拒絕之后彈出一個對話框,這個對話框的作用就是提示用戶我們為什么需要這個權限,ad就是AlertDialog,AlertDialog的用法這里不討論,用戶點擊確定時我們會再次彈出權限申請窗口:
@Override
public void requestPermissionFail() {
showDialog();
ToastUtil.toastLong(context, getString(R.string.permission_fail));
}
@Override
public void dismissDialog() {
if (ad != null) {
ad.dismiss();
}
}
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(getString(R.string.permission_dialog_message));
builder.setPositiveButton(getString(R.string.permission_dialog_confirm), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermission(Constants.REQUEST_CODE_PERMISSION, PERMISSION_CALL_PHONE);
}
});
ad = builder.show();
}
第二種情況就是用戶勾選了"不再詢問"并且拒絕的情況。在原生的系統下,第一次彈出來的權限申請窗口是看不到"不再詢問"選項的,但是第二次彈出來就會看到:
這個時候問題就來了,假如用戶勾選了"不再詢問",又拒絕了怎么辦?我們是不是永遠不能申請到權限了呢?這個時候當然還是有救命稻草的,讓我們回到基類,還是看onRequestPermissionsResult()。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case Constants.REQUEST_CODE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
requestPermissionSuccess();
} else {
requestPermissionFail();
}
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {//判斷用戶是否勾選了不再詢問
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[i])) {
ToastUtil.toastLong(context, getString(R.string.permission_settings));
dismissDialog();
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, Constants.REQUEST_CODE_SETTINGS);
}
}
}
break;
default:
break;
}
}
增加的代碼作用就是判斷用戶是否勾選了不再詢問,如果勾選了,首先讓我們自定義的對話框消失,其次就跳轉到應用信息頁面,并且提示用戶手動去權限中開啟權限。
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[i])) {
ToastUtil.toastLong(context, getString(R.string.permission_settings));
dismissDialog();
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, Constants.REQUEST_CODE_SETTINGS);
}
}
}
相信大家一定注意到了這里用的是startActivityForResult(),這是因為跳轉到應用信息頁面之后,是可以使用返回鍵回到之前的Activity的,這個時候用戶是有可能什么都沒做就返回的。所以這個時候我們還需要在基類的onActivityResult()中判斷是否被授予了權限,在這里我在未被授予時直接強關了應用:):
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (hasPermission(PERMISSION_CALL_PHONE)) {
requestPermissionSuccess();
} else {
application.finishAllActivity();
}
}
結尾
此時如果用戶一直不同意授予權限的話,他看到的情況就是每次進入應用都會自動跳轉到應用信息頁面:),到這里流程就差不多了,相信大家也能看得懂,不過寫著寫著發現自己也有點亂,所以最后我還是準備了Demo給大家下載,沒錯,我就是來騙star的 :) ...看著Demo大家一定能明白運行時權限管理是怎樣的一個流程,好,有時間我再寫點文章分享,再見。
提示
1.同一權限組的權限,只要申請其中一個,同組的其余權限就不用再申請了。
2.如果將App的targetSdkVersion設置為23以下,就可以規避危險權限的檢查,也就是在安裝的時候就賦予該App所申請的所有權限,但是不建議這么做,畢竟人要往前看,不是么。