自從Android6.0之後,運行時權限申請就成爲了一大痛點!動態申請吧,說它難吧,它又不難,說它不難吧,申請起來賊複雜;不申請吧,還要給你整閃退。java
其實如今有不少權限申請框架了,那麼爲何我還在造輪子呢?其實緣由很簡單,多是年紀大了,開始喜歡簡約風了,能少寫一行的我絕對不想多寫一個字母!說到底仍是以爲現有的不少框架,用起來不是那麼順手,配置相對複雜,因此萌生了再整一個一行代碼實現權限申請的想法。git
在工做之餘,打算將一些經常使用的邏輯頁面,模塊,功能點作成library庫,這樣當有類似需求的時候,能夠作到插拔式開發!如今系列中有如下內容github
具體的實現demo已經放到Github了,效果圖什麼的也在上面,上面也有寫好的demo apk提供下載嘗試,若是你以爲有用,記得star哦!哈哈哈,就是這麼不要臉~~~app
Github地址框架
權限申請無非三板斧,第一:檢查是否已受權,第二:發起受權申請,第三:處理受權結果!第一步和第二步是固定的操做,只不過咱們能夠將繁瑣的步驟封裝起來。ide
第三步纔是很差處理的核心,正常來講,當咱們發起權限申請以後,必需要重寫onRequestPermissionsResult方法來對受權結果進行處理,這就致使了不管你怎麼封裝,仍是必須侵入到每一個Activity的onRequestPermissionsResult中去,這就意味着無論怎樣,你都至少要在兩個地方寫上對應的代碼才能實現受權功能。可是做爲調用者來講,我但願的操做只有兩個,一,提供我須要受權的權限,二,回調給我受權結果。工具
如何實現經過回調的方式反饋給調用者受權結果呢?最開始想法是寫一個透明寬高各1像素大小的Activity,將權限申請和受權結果的操做都放在裏面處理,處理完以後關閉此Activity,對於用戶來講其實也是無感知的。可是有一個最大的問題就是,當在這個Activity處理完onRequestPermissionsResult以後,如何才能經過回調的方式告知調用方呢?因爲打開新的Activity以後,調用方Activity和處理邏輯的Activity是兩個不一樣的頁面,在不借助其餘技術的狀況下,是很差實現相似接口回調的方式進行通信的,因此摒棄了這個方案。oop
後來想到,既然Activity不能勝任這份工做,能夠嘗試一下它的兄弟Fragment,Fragment中也能夠發起受權申請並處理受權結果,最重要的是,Fragment是依附於Activity的,因此咱們能夠在同一個Activity下進行權限的申請和處理,而且不會污染到宿主Activity的邏輯,美滋滋。優化
配置並申請權限ui
對於調用者來講,他須要作的就是配置他須要申請的權限列表。因此咱們須要提供一個入口給他。
FanPermissionUtils.with(MainActivity.this)
//添加全部你須要申請的權限
.addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
.addPermissions(Manifest.permission.CALL_PHONE)
.addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
.addPermissions(Manifest.permission.CAMERA)
...
複製代碼
咱們經過FanPermissionUtils.with方法構造出一個FanPermissionUtils工具類,使用鏈式方法讓用戶配置他須要申請的全部權限。FanPermissionUtils的內部維護了權限列表,存儲全部須要受權的權限。
調用者配置好權限以後,調用startCheckPermission開始進行權限申請。
FanPermissionUtils.with(MainActivity.this)
//添加全部你須要申請的權限
.addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
.addPermissions(Manifest.permission.CALL_PHONE)
.addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
.addPermissions(Manifest.permission.CAMERA)
//開始受權
.startCheckPermission();
複製代碼
此時,咱們會構造一個沒有頁面的FanPermissionFragment來處理受權相關的邏輯,並將構造出的Fragment添加到宿主Activity中。
//構造FanPermissionFragment並將本身添加到宿主Activity中
FanPermissionFragment.newInstance(permissions).start(mContext);
/** * 開始請求 */
public void start(Activity activity) {
if (activity != null) {
mContext = activity;
if (Looper.getMainLooper() != Looper.myLooper()) {
return;
}
activity.getFragmentManager().beginTransaction().add(this, activity.getClass().getName()).commit();
}
}
複製代碼
一旦FanPermissionFragment被構造,就會對調用方提供的權限列表進行逐一排查,羅列出還未受權的權限列表,並對這些未受權的權限發起二次權限申請。
//記錄未受權的權限
List<String> deniedPermissions = new ArrayList<>();
for (String permission : permissions) {
int check = ContextCompat.checkSelfPermission(getActivity(), permission);
if (check == PackageManager.PERMISSION_GRANTED) {
//受權經過了已經 do nothing
} else {
deniedPermissions.add(permission);
}
}
if (deniedPermissions.size() != 0) {
//有權限沒有經過
requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), PERMISSION_REQUEST_CODE);
} else {
//受權所有經過
}
複製代碼
這樣咱們就完成了第一步和第二步。
對受權結果進行處理
對於調用者來講,最佳的體驗就是回調的方式反饋受權結果。先定義一個回調接口。
public interface FanPermissionListener {
/* * 受權所有經過 */
void permissionRequestSuccess();
/* * 受權未經過 * @param grantedPermissions 已經過的權限 * @param deniedPermissions 拒絕的權限 * @param forceDeniedPermissions 永久拒絕的權限(也就是用戶點擊了再也不提醒的那些權限) */
void permissionRequestFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions);
}
複製代碼
調用者在構造FanPermissionUtils的時候,設置回調監聽便可。
FanPermissionUtils.with(MainActivity.this)
//添加權限申請回調監聽 若是申請失敗 會返回已申請成功的權限列表,用戶拒絕的權限列表和用戶點擊了再也不提醒的永久拒絕的權限列表
.setPermissionsCheckListener(new FanPermissionListener() {
@Override
public void permissionRequestSuccess() {
//全部權限受權成功纔會回調這裏
}
@Override
public void permissionRequestFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions) {
//當有權限沒有被受權就會回調這裏
}
});
複製代碼
咱們在FanPermissionUtils中會保存調用者設置的回調接口,而後在FanPermissionFragment被構造的時候將這個回調接口傳給FanPermissionFragment。
FanPermissionFragment.newInstance(permissions).setPermissionCheckListener(listener).start(mContext);
複製代碼
而後在FanPermissionFragment中去處理申請受權以後的受權回調操做。將受權回調的結果經過調用者設置的回調監聽返回給調用者。來實現侵入性最小的權限申請方案。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
//記錄點擊了再也不提醒的未受權權限
List<String> forceDeniedPermissions = new ArrayList<>();
//記錄點擊了普通的未受權權限
List<String> normalDeniedPermissions = new ArrayList<>();
List<String> grantedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
String permission = permissions[i];
if (grantResult == PackageManager.PERMISSION_GRANTED) {
//受權經過
grantedPermissions.add(permission);
} else {
//受權拒絕
if (!ActivityCompat.shouldShowRequestPermissionRationale(mContext, permission)) {
//用戶勾選了再也不提示
forceDeniedPermissions.add(permission);
} else {
//用戶僅僅點擊了拒絕 未勾選再也不提示
normalDeniedPermissions.add(permission);
}
}
}
if (forceDeniedPermissions.size() == 0 && normalDeniedPermissions.size() == 0) {
//所有受權經過
requestPermissionsSuccess();
} else {
//受權未經過
for (String permission : this.permissions) {
if (grantedPermissions.contains(permission)
|| normalDeniedPermissions.contains(permission)
|| forceDeniedPermissions.contains(permission)) {
} else {
//若是三者都不包含他 包名這個權限不是隱私權限 直接給就完事了 因此要放到已受權的權限列表裏面去
grantedPermissions.add(permission);
}
}
requestPermissionsFail(grantedPermissions.toArray(new String[grantedPermissions.size()]),
normalDeniedPermissions.toArray(new String[normalDeniedPermissions.size()]),
forceDeniedPermissions.toArray(new String[forceDeniedPermissions.size()]));
}
}
}
//受權所有經過
private void requestPermissionsSuccess() {
if (permissionCheckListener != null) {
permissionCheckListener.permissionRequestSuccess();
}
mContext.getFragmentManager().beginTransaction().remove(this).commit();
}
//受權未經過
private void requestPermissionsFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions) {
if (permissionCheckListener != null) {
permissionCheckListener.permissionRequestFail(grantedPermissions, deniedPermissions, forceDeniedPermissions);
}
mContext.getFragmentManager().beginTransaction().remove(this).commit();
}
複製代碼
此外還有一種需求,當用戶不受權,就不給用。這種狀況下咱們須要在申請權限的時候,當用戶點擊拒絕的時候,一直無限申請權限,直到用戶點擊容許爲止,對,就是這麼流氓!
細心的同窗在上面的代碼中已經看到了一些邏輯,對應的就是另一種一種狀況,用戶在你連續彈出幾回申請權限的彈窗以後,會選擇勾選再也不提醒,這個時候你再去申請權限的時候,系統會直接回調給你拒絕,而且不會彈出受權彈窗,這個時候咱們須要調用ActivityCompat.shouldShowRequestPermissionRationale(mContext, permission)方法來判斷下用戶是否勾選了再也不提醒的按鈕,若是用戶勾選了,那麼咱們就須要指引用戶去設置頁面手動受權,由於咱們在app內部已經無能爲力了。
if (normalDeniedPermissions.size() != 0) {
//還有普通拒絕的權限能夠彈窗
requestPermission();
} else {
//全部沒有經過的權限都是用戶點擊了再也不提示的 我擦 這裏原本是想把未受權的全部權限的名稱列出來展現的 後來想一想以爲配置有點麻煩
new AlertDialog.Builder(mContext)
.setTitle(mContext.getString(R.string.permissions_check_warn))
.setMessage(checkConfig == null ? forceDeniedPermissionTips : checkConfig.getForceDeniedPermissionTips())
.setCancelable(false)
.setPositiveButton(mContext.getString(R.string.permissions_check_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
openSettingPage();
}
}).show();
}
複製代碼
以前不是說一行代碼實現嗎?下面就是整個申請權限的功能,的確只有一個結尾的分號,我說一行不算吹牛吧!~~~
FanPermissionUtils.with(MainActivity.this)
//添加全部你須要申請的權限
.addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
.addPermissions(Manifest.permission.CALL_PHONE)
.addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
.addPermissions(Manifest.permission.CAMERA)
//添加權限申請回調監聽 若是會返回已申請成功的權限列表,用戶拒絕的權限列表和用戶點擊了再也不提醒的永久列表
.setPermissionsCheckListener(new FanPermissionListener() {
@Override
public void permissionRequestSuccess() {
//全部權限受權成功纔會回調這裏
((TextVfindViewById(R.id.tv_result)).setText("受權結果\n\n全部權限都受權成;
Toast.makeText(MainActivity.this, "全部權限都受權Toast.LENGTH_SHORT).show();
@Override
public void permissionRequestFail(String[] grantedPermissions, StrideniedPermissions, String[] forceDeniedPermissions) {
//當有權限沒有被受權就會回調這裏
StringBuilder result = new StringBuilder("受權結果\n\n受權失敗\n\n");
result.append("受權經過的權限:\n");
for (String grantedPermission : grantedPermissions) {
result.append(grantedPermission + "\n");
}
result.append("\n臨時拒絕的權限:\n");
for (String deniedPermission : deniedPermissions) {
result.append(deniedPermission + "\n");
}
result.append("\n永久拒絕的權限:\n");
for (String forceDeniedPermission : forceDeniedPermissions) {
result.append(forceDeniedPermission + "\n");
}
((TextView) findViewById(R.id.tv_result)).setText(result);
Toast.makeText(MainActivity.this, "受權失敗", Toast.LENGTH_SHORT).show();
}
})
//開始受權
.startCheckPermission();
複製代碼
簡書首頁,連接是 www.jianshu.com/u/123f97613…
掘金首頁,連接是 juejin.im/user/5838d5…
Github首頁,連接是 github.com/MZCretin
CSDN首頁,連接是 blog.csdn.net/u010998327
我是Cretin,一個可愛的小男孩。