效果展示
以上是演示請求一個相機權(quán)限的過程:
首次申請(頂部提醒)-拒絕-再次申請(頂部提醒)-再次拒絕(并勾選禁止再次詢問)-再次申請(中部彈窗引導(dǎo))-在設(shè)置頁不授權(quán)-返回-再次申請(中部彈窗引導(dǎo))-在設(shè)置頁將相機權(quán)限設(shè)為允許-返回app
在此過程中,任意一次拒絕和允許争剿,都可以及時的拿到允許/拒絕的結(jié)果功氨,進(jìn)而在頁面展示授權(quán)狀態(tài)或者執(zhí)行自己授權(quán)后的邏輯。以上的這些所有邏輯振惰,只需要一句代碼就能搞定,你相信嗎垄懂?
是的骑晶,EasyPermission她來了,在項目中集成之后草慧,安卓權(quán)限的檢查桶蛔、申請、提示漫谷、結(jié)果回執(zhí)仔雷,一句代碼就可以搞定。
EasyPermission.build()
.mRequestCode(RC_CODE_PERMISSION)//請求code舔示,自己定義
.mPerms(Manifest.permission.CAMERA)//權(quán)限碟婆,可支持多個
.setAutoOpenAppDetails(true)//默認(rèn)true
.mAlertInfo( new PermissionAlertInfo("**需要申請攝像頭權(quán)限",
"**需要申請攝像頭拍攝權(quán)限,以便您能夠通過掃一掃實現(xiàn)掃描二維碼惕稻;通過拍照更換您帳號的頭像竖共;拍照上傳一些注冊帳號需要的證件信息。拒絕或取消授權(quán)將影響以上功能俺祠,不影響使用其他服務(wù)"))
.mResult(new EasyPermissionResult() {
@Override
public void onPermissionsAccess(int requestCode) {
super.onPermissionsAccess(requestCode);
//權(quán)限已通過
}
@Override
public void onPermissionsDismiss(int requestCode, @NonNull List<String> permissions) {
super.onPermissionsDismiss(requestCode, permissions);
//權(quán)限被拒絕
}
).requestPermission();
接下來我們就看下它是怎么實現(xiàn)的肘迎。
需求來源
最近國家工信部對手機隱私安全越來越重視甥温,權(quán)限不能濫用,不能隨意申請(有些應(yīng)用在啟動時就申請一堆權(quán)限)妓布,更不能強制申請(有些權(quán)限不通過就不讓進(jìn)入應(yīng)用的姻蚓,已經(jīng)被下架了)。最近公司又接到上頭通知:公司某款app中有申請定位權(quán)限時沒有對用戶解釋說明匣沼,需要及時整改狰挡。
和產(chǎn)品溝通后借鑒小紅書、京東释涛,類似這種在請求權(quán)限的時候加叁,在底下彈出系統(tǒng)彈窗時,同時在頂部浮出說明信息唇撬;如果權(quán)限被禁止了它匕,就在中部彈出說明彈窗,引導(dǎo)去設(shè)置頁面中完成授權(quán)窖认。
方案分析
咨詢了隔壁IOS的現(xiàn)狀豫柬、權(quán)限在項目中,權(quán)限的申請可以也必須配置說明文案扑浸,那么在使用申請權(quán)限時系統(tǒng)就會彈出信息說明烧给。
可是安卓的動態(tài)權(quán)限管理比較松散(無情的吐槽),請求權(quán)限真的是干巴巴的請求喝噪,就像上面圖1底部的系統(tǒng)彈窗础嫡。要像京東、小紅書那樣實現(xiàn)申請彈窗時提示信息酝惧,收到權(quán)限或者拒絕時隱藏榴鼎,權(quán)限有可能被禁止(拒絕并勾選不再提示),禁止時需要彈窗提醒晚唇,引導(dǎo)去設(shè)置頁返回時又得不到有效的回調(diào)(是的檬贰,京東從設(shè)置頁回來的時候,授權(quán)后不會繼續(xù)執(zhí)行申請權(quán)限的邏輯缺亮,我們的EasyPermission卻完全可以做到)。
根據(jù)大致的分析桥言,其實流程也是挺清晰的萌踱,實現(xiàn)以上效果的流程圖如下(最終的EasyPermission庫的最終實現(xiàn)思路也是基于此做的):
方案中需要考慮的問題
看到以上流程圖,其實實現(xiàn)起來感覺還好号阿,但是有一些問題:
- 安卓的權(quán)限申請并鸵、回調(diào)函數(shù)、彈窗都是依賴于界面的扔涧,也就是跟activity相關(guān)园担。那么在后臺服務(wù)中届谈、或者某個View中,如果用到權(quán)限怎么辦弯汰?
- 一個頁面中用到多個權(quán)限艰山,這些回調(diào)、彈窗是不是就串在一起了很亂咏闪?
- 假如在好多頁面都用到某個權(quán)限曙搬,全要實現(xiàn)一遍這樣的邏輯?
- 公司有幾個app鸽嫂,每個app都要去大量的處理權(quán)限邏輯和彈窗纵装?
尋找成熟的方案
發(fā)現(xiàn)幾個用的比較多的,RxPermissions据某,easypermissions(googlesamples)橡娄,兩個庫都有幾千的star,說明還是挺多人用的癣籽。但是有個問題沒有解決(activity的關(guān)聯(lián))挽唉。
想起之前封裝的EasyPermission,雖然只是簡單的實現(xiàn)了權(quán)限的請求和回調(diào)才避,最終還是決定拿她孵化一下橱夭,進(jìn)化進(jìn)化,實現(xiàn)新的設(shè)想桑逝。
最終確實想到了解決方案棘劣,通過在初始化時,注冊一個ActivityLifecycleCallback楞遏,監(jiān)聽activity的變動茬暇,這樣在請求權(quán)限和彈窗時就可以隨時取用最頂層的activity。
當(dāng)然封裝包括后面試用時寡喝,還是發(fā)現(xiàn)了不少問題糙俗,比如單例的頁面ActivityLifecycleCallback回調(diào)的比較遲,可能導(dǎo)致拿到的activity是無效的预鬓;安卓沒有直接查詢權(quán)限是否被禁止的方法等巧骚,終是一一克服了。最終是上傳到了jetpack上格二,可以直接引用劈彪。接下來就看下怎么使用一句話來實現(xiàn)權(quán)限的請求、彈窗顶猜、回調(diào)沧奴。
集成引導(dǎo)
第一步. 添加依賴
1. 根build.gradle中添加maven倉庫的依賴.
repositories {
...
maven { url 'https://jitpack.io' }
}
}
2. 項目主Module的bulid.gradle添加工具庫最新版本依賴
implementation 'com.gitee.zhang-yanqiang:easypermission:v2.0.12'
}
最新版本查看鏈接:https://jitpack.io/#com.gitee.zhang-yanqiang/easypermission
第三步. 初始化配置
1.在Application的onCreate中完成初始化
public void onCreate() {
super.onCreate();
//首次使用權(quán)限申請之前完成初始化,建議放在Application onCreate()中完成
EasyPermissionHelper.getInstance().init(this);
}
2.將要使用EasyPermission的Activity中的onRequestPermissionsResult和onActivityResult,
- 在對應(yīng)的Activity調(diào)用EasyPermissionHelper.getInstance().onRequestPermissionsResult和onActivityResult即可长窄;
- 如果你有BaseActivity滔吠,那么只需要在BaseActivity中設(shè)置一次即可纲菌。
- 這兩個方方法為了實現(xiàn)授權(quán)結(jié)果的自動回調(diào),如果不需要回調(diào)可以不配置
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//使用EasyPermissionHelper注入回調(diào)(授權(quán)彈窗回調(diào))
EasyPermissionHelper.getInstance().onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//使用EasyPermissionHelper注入回調(diào)(系統(tǒng)設(shè)置返回使用)
EasyPermissionHelper.getInstance().onActivityResult(requestCode, resultCode, data);
}
功能使用
接下來看下怎么使用疮绷。
1.檢測權(quán)限
只需要調(diào)用EasyPermission的hasPermission方法,支持多個權(quán)限同時傳入翰舌。
EasyPermission.build().hasPermission(Manifest.permission.CAMERA);
2.申請權(quán)限
如果你在應(yīng)用啟動時需要申請權(quán)限,而且并不關(guān)注權(quán)限的結(jié)果矗愧, 只需要調(diào)用EasyPermission的requestPermission方法灶芝,支持多個權(quán)限傳入。
EasyPermission.build().requestPermission(Manifest.permission.CAMERA);
3.需要權(quán)限的結(jié)果
- 如果你需要知道申請權(quán)限后用戶的選擇結(jié)果唉韭,同時去執(zhí)行自己的方法myVoid(),
- 那么在onPermissionsAccess中去做就可以了夜涕,
- 在onPermissionsDismiss是用戶拒絕了權(quán)限的反饋。
EasyPermission.build()
.mRequestCode(RC_CODE_PERMISSION)//請求code属愤,自己定義
.mPerms(Manifest.permission.CAMERA)//權(quán)限女器,可支持多個
.mResult(new EasyPermissionResult() {//回調(diào)
@Override
public void onPermissionsAccess(int requestCode) {
super.onPermissionsAccess(requestCode);
//權(quán)限已通過
}
@Override
public void onPermissionsDismiss(int requestCode, @NonNull List<String> permissions) {
super.onPermissionsDismiss(requestCode, permissions);
//權(quán)限被拒絕
}
}).requestPermission();
4.有時用戶拒絕了權(quán)限,而且禁止了彈出詢問住诸,我該怎么辦驾胆?想要在申請權(quán)限時彈窗告知用戶權(quán)限的必要性怎么辦?
- 事實上贱呐,在新版本只需要通過mAlertInfo設(shè)置了提示文本丧诺,現(xiàn)在已經(jīng)默認(rèn)處理了彈窗的展示,也就是說不需要去重寫onDismissAsk和openAppDetails方法了
- 如果想要自己處理彈窗邏輯奄薇,可以通過setAutoOpenAppDetails=false關(guān)閉自動處理的邏輯
- 只要在onDismissAsk中驳阎,就可以得到被禁止的結(jié)果,同時你要注意onDismissAsk默認(rèn)返回false
- 如果你自己修改return true馁蒂,將視為已經(jīng)處理了禁止結(jié)果呵晚,將不再回調(diào)onPermissionsDismiss這個方法
- 調(diào)用openAppDetails方法,可以彈窗引導(dǎo)用戶去設(shè)置界面設(shè)置權(quán)限沫屡,成功后會自動回調(diào)
EasyPermission.build()
.mRequestCode(RC_CODE_PERMISSION)//請求code饵隙,自己定義
.mPerms(Manifest.permission.CAMERA)//權(quán)限,可支持多個
.setAutoOpenAppDetails(true)//默認(rèn)true
.mAlertInfo( new PermissionAlertInfo("**需要申請攝像頭權(quán)限",
"**需要申請攝像頭拍攝權(quán)限沮脖,以便您能夠通過掃一掃實現(xiàn)掃描二維碼金矛;通過拍照更換您帳號的頭像;拍照上傳一些注冊帳號需要的證件信息勺届。拒絕或取消授權(quán)將影響以上功能驶俊,不影響使用其他服務(wù)"))
.mResult(new EasyPermissionResult() {
@Override
public void onPermissionsAccess(int requestCode) {
super.onPermissionsAccess(requestCode);
//權(quán)限已通過
}
@Override
public void onPermissionsDismiss(int requestCode, @NonNull List<String> permissions) {
super.onPermissionsDismiss(requestCode, permissions);
//權(quán)限被拒絕
}
@Override
public boolean onDismissAsk(int requestCode, @NonNull List<String> permissions) {
//權(quán)限被拒絕并禁止再次詢問
return super.onDismissAsk(requestCode,permissions);//這里true表示攔截處理,不再回調(diào)onPermissionsDismiss涮因;
}
@Override
public void openAppDetails() {
//彈出默認(rèn)的權(quán)限詳情設(shè)置提示彈出框,在設(shè)置頁完成允許操作后伺绽,會自動回調(diào)到onPermissionsAccess()
super.openAppDetails();
//如果樣式不滿意养泡,可以彈出自定義明彈窗嗜湃,在用戶確認(rèn)時調(diào)用 goToAppSettings();完成跳轉(zhuǎn)設(shè)置頁
}).requestPermission();
5.彈窗樣式自定義
權(quán)限庫用起來蠻方便的,但是彈窗的文字顏色需要改一下澜掩,又不像大動干戈地每次自己去寫彈窗购披,能不能設(shè)置一下文字大小、顏色肩榕?沒問題刚陡,咱們支持彈窗自定義樣式。
setDialogStyle在初始化方法init()之后任意時刻調(diào)用株汉,設(shè)置樣式后全局生效筐乳。
·使用默認(rèn)經(jīng)典樣式(類似京東、小紅書)
EasyPermissionHelper.getInstance().setDialogStyle(new EasyAppSettingDialogStyle(EasyAppSettingDialogStyle.DialogStyle.STYLE_DEFAULT));
·使用系統(tǒng)自帶彈窗樣式
使用系統(tǒng)自帶的AlertDialog樣式乔妈,具體的展示效果每個機型不太一樣
EasyPermissionHelper.getInstance().setDialogStyle(new EasyAppSettingDialogStyle(EasyAppSettingDialogStyle.DialogStyle.STYLE_SYSTEM));
·使用自定義彈窗樣式
EasyPermissionHelper.getInstance().setDialogStyle(
new EasyAppSettingDialogStyle(EasyAppSettingDialogStyle.DialogStyle.STYLE_CUSTOM)
.setTitleGravity(Gravity.CENTER)//設(shè)置居中
.setTitleSize(17)//設(shè)置標(biāo)題
.setTitleColor("#333333")
.setMessageSize(14)//設(shè)置內(nèi)容
.setMessageColor("#666666")
.setButtonTextSize(14)//設(shè)置按鈕
.setButtonThemeColor("#FF0000")
.setCancelText("取消")//設(shè)置文本
.setConfirmText("去打開"));
完全自定義彈窗
以上方式只需要在初始話后設(shè)置一次蝙云,全局生效。如果以上方式依然滿足不了你胃口路召,那只能自己去控制彈窗啦勃刨。 在EasyPermissionResult中重寫openAppDetails(),只會影響當(dāng)前權(quán)限的請求彈窗。
@Override
public void openAppDetails() {
//在前往應(yīng)用設(shè)置詳情頁展示自己的彈窗告知用戶我們需要哪些權(quán)限打開
//在用戶點擊確認(rèn)時調(diào)用easyPermission.goToAppSettings();完成跳轉(zhuǎn)設(shè)置頁
}
6.其它注意事項
- mAlertInfo不設(shè)置將不會自動彈出權(quán)限說明彈窗,為了滿足當(dāng)前的日益嚴(yán)格的隱私政策股淡,請對認(rèn)真對待每一個權(quán)限說明
- 權(quán)限的申請不建議在onNewIntent中獲取
- 相關(guān)日志tag為"EasyPermissionLog",默認(rèn)不輸出太多信息身隐,如果需要調(diào)試請打開EasyPermissionConfigs.setDebug(true)
- 增加setAutoOpenAppDetails,如果PermissionAlertInfo有值,則在被禁止時自動觸發(fā) openAppDetails()
- 如果openAppDetails()樣式不滿足唯灵,可以重寫openAppDetails()自定義彈出內(nèi)容贾铝,也可以直接在onDismissAsk()攔截
- 由于EasyPermission在init初始化時使用ActivityLifecycleCallbacks開始監(jiān)聽activity變化,所以在launchMode="singleTask" onNewIntent中如果需要請求權(quán)限早敬,需要重新設(shè)置activity忌傻。 可以使用兩種方式完成。
- 方式一:
EasyPermissionHelper.getInstance().updateTopActivity(mContext);
easyPermission.requestPermission();
- 方式二:
easyPermission.mContext(mContext).requestPermission();
7.其它工具
· 定位服務(wù)管理 EasyLocationTool
Android 9.0以后即使已經(jīng)獲得了用戶授權(quán)定位權(quán)限搞监,由于GPS定位服務(wù)未打開水孩,依然獲取不到定位,所以還需要對定位服務(wù)進(jìn)行處理琐驴,LocationTool支持以下方法:
- isLocationEnabled() 獲取當(dāng)前定位服務(wù)是否開啟
- gotoAppSettings() 直接跳轉(zhuǎn)到手機-設(shè)置-安全和隱私-定位服務(wù)開啟/關(guān)閉的頁面
· 通知服務(wù)管理 EasyNotificationTool
通知服務(wù)的權(quán)限在Android中也比較特殊俘种,它不像其它權(quán)限那樣去直接申請,像定位服務(wù)一樣需要去系統(tǒng)設(shè)置中開啟绝淡,所以也要去設(shè)置頁:
- isNotificationEnabled() 獲取當(dāng)前APP的通知權(quán)限是否開啟
- gotoAppSettings() 直接跳轉(zhuǎn)到手機-設(shè)置-通知和狀態(tài)欄-通知管理-APP通知設(shè)置頁
· 懸浮窗權(quán)限管理 EasyFloatWindowTool
懸浮窗權(quán)限在Android中也比較特殊宙刘,它不像其它權(quán)限那樣去直接申請,像定位服務(wù)一樣需要去系統(tǒng)設(shè)置中開啟牢酵,所以也要去設(shè)置頁:
- isFloatWindowEnabled() 獲取當(dāng)前APP的是否有懸浮窗權(quán)限
- gotoAppSettings() 直接跳轉(zhuǎn)到手機-設(shè)置-應(yīng)用管理-特殊應(yīng)用權(quán)限-顯示在其他應(yīng)用的上層-APP設(shè)置頁
結(jié)束語
- 如果又更好的方案和思路悬包,歡迎留言或者私信,可以git上提交解決方案或者issue
- 祝所有人平安幸福、家庭和睦馍乙、身體健康布近。
- 愿祖國早日完成統(tǒng)一大業(yè)垫释,世界和平共處,繁榮發(fā)展撑瞧。
- 有任何疑問棵譬,可以及時反饋給我;
- 如果你覺得還不錯预伺,請點贊o( ̄▽ ̄)d订咸。
源代碼
Gitee地址:https://gitee.com/zhang-yanqiang/easypermission
Github地址:https://github.com/githubZYQ/easypermission
由于Github訪問不是很通暢,通常gitee更新效率會高一些