ps:打算從簡書搬到掘金來了,簡書沒討論氛圍,發了好久也不見有人討論,還是掘金人氣旺,氛圍好,記得16年剛註冊那會還不讓發帖呢,申請作家都不給通過...
有輪子就不要再自己造輪子了,這是行業公認的,我這裡不是從頭寫一個許可權庫,而是在開源元件上再封裝以統一公司內部呼叫,隨時可以替換第三方實現,從開源庫再封裝這個角度來寫文章的很少,我這裡帶大家領略另一番風景,先說好不喜勿噴啊,我這水平幼兒園都沒畢業呢
專案地址:BW_Libs
先看下 Demo 的 程式碼
不上 gif 了,錄這個時間太長,gif 太大網頁很卡。Demo 的思路如下,正常的判斷許可權,有3個回撥,使用者確認給予許可權,使用者不給,和使用者點選不在顯示系統許可權彈窗。這裡我們在使用者不顯示彈窗後的回撥裡啟動系統許可權設定頁,在使用者關閉許可權設定頁面過後,我們再檢測下=剛剛使用者給沒給許可權,沒給許可權的話就自己顯示個彈窗,提示使用者不給許可權就關閉頁面
Demo 程式碼如下:
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()
}
}
複製程式碼
元件封裝思路
1. 要好看,相應函數語言程式設計思想,提供鏈式呼叫
這點很重要,元件封裝完了是要給小夥伴們和自己用的,用起來費時費力,不好理解,不清不楚的都不行,達不到簡單易懂易用的元件都是不合格的,從這點來說,其實 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()
複製程式碼
2. 第三方庫高度,無痕可替換
我使用的是 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()
}
}
}
複製程式碼
3. 抽取公共引數,使用 build 構建
上面我們把第三方所需引數包裝成了一個類 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 的語法,方法可以直接接收函式引數,也不用我們再去寫介面了,的確是方便了很多,尤其是在我們寫的時候可以一氣呵成,不用來回切換類,不會打斷思路是非常好的
4. 提供統一入口
作為工具類,要有一個統一的入口,靜態的也行,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 裡面可以找到這個類