ps:打算從簡書搬到掘金來了,簡書沒討論氛圍,發了很久也不見有人討論,仍是掘金人氣旺,氛圍好,記得16年剛註冊那會還不讓發帖呢,申請做家都不給經過...java
有輪子就不要再本身造輪子了,這是行業公認的,我這裏不是從頭寫一個權限庫,而是在開源組件上再封裝以統一公司內部調用,隨時能夠替換第三方實現,從開源庫再封裝這個角度來寫文章的不多,我這裏帶你們領略另外一番風景,先說好不喜勿噴啊,我這水平幼兒園都沒畢業呢git
項目地址:BW_Libsgithub
不上 gif 了,錄這個時間太長,gif 太大網頁很卡。Demo 的思路以下,正常的判斷權限,有3個回調,用戶確認給予權限,用戶不給,和用戶點選不在顯示系統權限彈窗。這裏咱們在用戶不顯示彈窗後的回調裏啓動系統權限設置頁,在用戶關閉權限設置頁面事後,咱們再檢測下=剛剛用戶給沒給權限,沒給權限的話就本身顯示個彈窗,提示用戶不給權限就關閉頁面編程
Demo 代碼以下:ide
class PermissionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_permission)
btn_permission.setOnClickListener{
PermissionManage
.with(this)
.permission(Manifest.permission.CALL_PHONE)
.permission(Manifest.permission.CAMERA)
.permission(Manifest.permission.READ_PHONE_STATE)
.onSuccess { Toast.makeText(this@PermissionActivity, "申請成功", Toast.LENGTH_SHORT).show() }
.onDenial { Toast.makeText(this@PermissionActivity, "用戶拒絕", Toast.LENGTH_SHORT).show() }
.onDontShow { IntentUtils.startSettingActivityForResult(this, 200) }
.run()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == 200) {
var permissions = listOf(Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE)
if (PermissionManage.isHavePermissions(this, permissions)) {
Toast.makeText(this, "歡迎您給予的權限", Toast.LENGTH_SHORT).show()
} else {
showDialog()
}
}
}
private fun showDialog() {
var build: AlertDialog.Builder = AlertDialog.Builder(this)
build.setMessage("缺少權限,請求您給予權限")
build.setPositiveButton("申請權限", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface?, which: Int) {
IntentUtils.startSettingActivityForResult(this@PermissionActivity, 200)
dialog?.dismiss()
// dialog?.cancel()
}
})
build.setNegativeButton("不給權限", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface?, which: Int) {
Toast.makeText(this@PermissionActivity, "對不起,某些權限是必備選擇,不給權限不能運行", Toast.LENGTH_SHORT).show()
dialog?.dismiss()
this@PermissionActivity.finish()
}
})
build.show()
}
}
複製代碼
這點很重要,組件封裝完了是要給小夥伴們和本身用的,用起來費時費力,很差理解,不清不楚的都不行,達不到簡單易懂易用的組件都是不合格的,從這點來講,其實 Fresco 的 API 就不是很好,固然 Fresco 是很是複雜的了,可是另外一個圖片加載的小夥伴 Glide API 就很 Nice 啦函數式編程
這部分就是我封裝的權限組件的 API 了,仿照函數式編程,提供鏈式調用,這樣的代碼很好看,很是容易理解邏輯。函數式編程在處理連續複雜邏輯的代碼上有自然的優點,其風格以清晰著稱,是咱們封裝工具類組件的不二選擇函數
PermissionManage
.with(this)
.permission(Manifest.permission.CALL_PHONE)
.permission(Manifest.permission.CAMERA)
.permission(Manifest.permission.READ_PHONE_STATE)
.onSuccess { Toast.makeText(this@PermissionActivity, "申請成功", Toast.LENGTH_SHORT).show() }
.onDenial { Toast.makeText(this@PermissionActivity, "用戶拒絕", Toast.LENGTH_SHORT).show() }
.onDontShow { IntentUtils.startSettingActivityForResult(this, 200) }
.run()
複製代碼
我使用的是 AndPermission 這個開源庫,來看下我對 AndPermission 的包裝工具
2.1 - 抽取接口 第三方權限庫是幹嗎的,就是提供權限申請驗證的,抽泣其公共功能,就是1個,給參數而後執行,就是這麼簡單,爲何,由於功能單一唄,即便請求驗證權限測試
interface IPermissionExecuter {
fun run(permissionConfig: PermissionConfig)
}
複製代碼
class AndPermissinExecuterImpl : IPermissionExecuter {
override fun run(permissionConfig: PermissionConfig) {
AndPermission.with(permissionConfig.context)
.permission(permissionConfig.permissions.toTypedArray())
// 用戶給權限了
.onGranted({ permissions: List<String> -> permissionConfig.onSuccessAction() })
// 用戶拒絕權限,包括再也不顯示權限彈窗也在此列
.onDenied({ permissions: List<String> ->
// 判斷用戶是否是再也不顯示權限彈窗了,若再也不顯示的話進入權限設置頁
if (AndPermission.hasAlwaysDeniedPermission(permissionConfig.context, permissions)) {
// 打開權限設置頁
permissionConfig.onDontShowAction()
return@onDenied
}
permissionConfig.onDenialAction()
})
.start()
}
}
複製代碼
第三方權限庫須要的參數基本都同樣,無論有什麼,咱們都包裝到 PermissionConfig 裏面,而後經過PermissionConfig 把參數傳進來執行,這樣咱們想要替換 第三方實現時,只要再寫一個 IPermissionExecuter 的實現類就好了
這個就不用說了吧,工廠模式,打擊都熟悉的套路了
object ExecuterFactor {
@JvmField
val AND_PERMISSION = "AND_PERMISSION"
@JvmField
val RX_PERMISSION = "RX_PERMISSION"
@JvmField
val DEFAULT_EXECUTER = AND_PERMISSION
@JvmStatic
fun getInstance(): IPermissionExecuter {
return getInstance(DEFAULT_EXECUTER)
}
@JvmStatic
private fun getInstance(type: String): IPermissionExecuter {
return when (type) {
AND_PERMISSION -> AndPermissinExecuterImpl()
DEFAULT_EXECUTER -> AndPermissinExecuterImpl()
else -> AndPermissinExecuterImpl()
}
}
}
複製代碼
上面咱們把第三方所需參數包裝成了一個類 PermissionConfig,咱們來看看這個類
class PermissionConfig {
lateinit var context: Context
// 權限申請成功時回調
var onSuccessAction: () -> Unit = {}
// 權限申請失敗時回調
var onDenialAction: () -> Unit = {}
// 用戶設置不顯示權限申請彈窗時回調
var onDontShowAction: () -> Unit = {}
// 權限集合
var permissions = mutableListOf<String>()
private var type: String = ExecuterFactor.DEFAULT_EXECUTER
/** * 添加權限 */
fun addPermission(permission: String) {
if (!permission.isEmpty()) permissions.add(permission)
}
/** * 設置類型 */
fun setType(type: String): PermissionConfig {
if (!type.isEmpty()) this.type = type
return this
}
/** * 執行操做 */
fun run() {
ExecuterFactor.getInstance().run(this)
}
}
複製代碼
抽取出來的參數沒幾個,很好理解,所須要的參數,用集合來接收由於可能有多個嘛,而後是3個回調,贊成,不一樣意,關閉權限彈窗,藉助 kotlin 的語言,咱們不要再像 java 同樣去寫一個接口了,直接聲明成空實現就行,也沒有 null 的問題,而後提供設置這幾個參數的方法便可
可是吧,咱們不能就這麼直接使用 PermissionConfig ,由於之後隨着時間推移有變化,咱們須要一個統一的地方來統一構建參數包裝對象,這就是你們熟悉的 build 模式啦
class PermissionBuild(var context: Context) {
var permissionConfig: PermissionConfig = PermissionConfig()
init {
permissionConfig.context = this.context
}
/** * 設置類型 */
fun type(type: String): PermissionBuild {
if (!type.isEmpty()) permissionConfig.setType(type)
return this
}
/** * 添加權限 */
fun permission(permission: String): PermissionBuild {
if (!permission.isEmpty()) permissionConfig?.addPermission(permission)
return this
}
/** * 添加成功操做 */
fun onSuccess(onSuccessAction: () -> Unit): PermissionBuild {
if (onSuccessAction != null) permissionConfig.onSuccessAction = onSuccessAction
return this
}
/** * 添加失敗操做 */
fun onDenial(onDenialAction: () -> Unit): PermissionBuild {
if (onDenialAction != null) permissionConfig.onDenialAction = onDenialAction
return this
}
/** * 添加不顯示權限彈窗操做 */
fun onDontShow(onDontShowAction: () -> Unit): PermissionBuild {
if (onDontShowAction != null) permissionConfig.onDontShowAction = onDontShowAction
return this
}
/** * 執行操做 */
fun run() {
permissionConfig.run()
}
}
複製代碼
這裏的 build 邏輯很簡單,不寫也能夠,本着練手的原則仍是寫了,仍是得益於 kotlin 的語法,方法能夠直接接收函數參數,也不用咱們再去寫接口了,的確是方便了不少,尤爲是在咱們寫的時候能夠一鼓作氣,不用來回切換類,不會打斷思路是很是好的
做爲工具類,要有一個統一的入口,靜態的也行,new 對象也行,這裏推薦使用靜態方法的方式,方便理解
class PermissionManage {
/** * 提供相關靜態入口,效仿 Glide 經過 with 綁定上下文 * */
companion object {
@JvmStatic
fun with(context: Context): PermissionBuild {
return PermissionBuild(context)
}
@JvmStatic
fun isHavePermission(context: Context, permission: String): Boolean {
return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission)
}
@JvmStatic
fun isHavePermissions(context: Context, permissions: List<String>): Boolean {
for (it in permissions) {
if (!(PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, it))) return false
}
return true
}
}
}
複製代碼
相似於 Glide.with 在添加上下文以後返回 PermissionBuild 構建器用於添加數據,另外還提供了其餘工具方法,用來查詢是否有權限
我是從後向前走的順序,思路是基於一步步的實現的,中間的類都是基於我要包裝第三方庫的目的一步步產生的,簡單的功能庫基本都是基於這個角度來作
這個組件很簡單的,其實我以前用 java 寫過這個權限組件,一樣的思路,地址在這裏:簡單對權限開源庫進行功能性封裝 ,一開始我就是想把 java 換成 kotlin ,可是改改寫寫,最後基本完全拋棄之前的從新整理思路寫了一遍,舊的那個組件如今看來廢話太多,我差很少刪了一半的類,另外我又從新考慮了一遍命名,也是基本改了一半的,這個命名在我來看是最難的
新版類結構:
老版類結構:
這麼一看是否是很簡單啊,雖然簡單可是很好的完成了咱們的目標,包裝第三方組件,提供統一 API 實現,動態無縫切換第三放實現
提取相同,抽象不一樣 - 模板模式 包裝第三方實現,抽取 run 執行這個動做,把因此參數包裝成統一的配置類
統一切換不一樣第三方實現 - 工廠模式
build 統一構建數據 - 構造者模式
提供統一接口 - 門板模式
上面基本都是套路,權限這個組件功能單一,你們可能體會不到上面這幾個套路的神奇,這東西只能靠意淫來理解深化,得本身手把手的寫纔能有切身體會,才能最終榮輝貫通, UML 類圖有時間再放上吧
在寫這個組件時,測試時尼瑪我竟然忘了在配置文件裏聲明所需權限了,我怎麼調試怎麼都不對,我查了好多遍代碼也沒找到問題,老打擊人了,老鬱悶了,讓我一天的心情都老差勁了,浪費了2個多小時時間,後來想起來了,尼瑪我是抽了本身5分鐘的嘴巴子,太丟了
奉勸你們遇到問題時必定要冷靜啊,不冷靜的後果就是浪費人生,明明很簡單的事,代碼也寫的很好,一次過,就是忘了配置文件這件事,其餘都沒問題,可是就恰恰好事變壞事,哎,冷靜,遇事千萬要日常心,要不本身真遭罪
還有就是碎片化這個問題了,小米,魅族,華爲手機用公版代碼是打不開權限設置頁面的,非的要適配,真他媽蛋疼,我又取百度了下,還好直接就找找了,寫了工具類,可是考慮了下,這個工具是打開系統頁面的,我就沒放在權限組件裏,從工能上講風馬牛不相及的事不能放一塊兒的,這個類叫:IntentUtils ,具體我就不方了 Demo 裏面能夠找到這個類