android 權限組件設計

ps:打算從簡書搬到掘金來了,簡書沒討論氛圍,發了很久也不見有人討論,仍是掘金人氣旺,氛圍好,記得16年剛註冊那會還不讓發帖呢,申請做家都不給經過...java

美女鎮樓

有輪子就不要再本身造輪子了,這是行業公認的,我這裏不是從頭寫一個權限庫,而是在開源組件上再封裝以統一公司內部調用,隨時能夠替換第三方實現,從開源庫再封裝這個角度來寫文章的不多,我這裏帶你們領略另外一番風景,先說好不喜勿噴啊,我這水平幼兒園都沒畢業呢git

項目地址:BW_Libsgithub


先看下 Demo 的 代碼

不上 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()
    }

}
複製代碼

組件封裝思路

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)
}
複製代碼

2.2 - 實現接口 ,包裝第三方庫ui

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 的實現類就好了

2.3 - 提供一個工廠類,實現切換管理

這個就不用說了吧,工廠模式,打擊都熟悉的套路了

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 裏面能夠找到這個類

相關文章
相關標籤/搜索