美文网首页Android技术知识Android开发Android进阶之路
带你封装自己的『权限管理』框架

带你封装自己的『权限管理』框架

作者: 许朋友爱玩 | 来源:发表于2020-04-25 11:18 被阅读0次

    前言

    本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍:

    我的 Github 博客

    本篇文章需要已经具备的知识:

    • GitGithub 的基本使用
    • Kotlin 语法基础
    • Android 开发基础

    学习清单:

    • 如何封装自己的权限框架
    • 将开源库发布到 JitPack 仓库的一整套流程

    一.为什么要封装这套框架

    我们在日常开发中,经常需要用到申请运行时权限的知识,于是,经常就写了下面的一大串代码

    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    
        ...
        //申请 CALL_PHONE 权限
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CALL_PHONE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1)
        } else {
            call()
        }
    }
    
    
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            1 -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    call()
                } else {
                    Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    

    麻鸭,头疼,这么多代码,不仅写着难受,看着更是头疼

    头疼

    这时候,如果这个世界简单点,纯粹点,就好了

    XPermission.request(
        this,
        Manifest.permission.CALL_PHONE
    ) { allGranted, deniedList ->
        if (allGranted) {
            call()
        } else {
            Toast.makeText(this, "You denied $deniedList", Toast.LENGTH_SHORT).show()
        }
    }
    

    是不是感觉世界又友好了很多呢?这段代码比之前的代码量少了很多不说,逻辑更是清晰了很多鸭!

    滚来了

    很显然,上面用到了自己封装的框架,有可能你会一脸不屑:『这算啥?Github 上一堆权限申请框架,他们写的这个简洁又漂亮,功能又多又全,超帅的』

    我想说:『是的,你说的对,虽然 Github 上有这么多,跑得又快又棒的轮子,但是,别人做的菜总归没有自己的香鸭!我们可以通过自己封装一个简单的权限申请框架开始,学习发布开源库到 Jitpack / Jcenter 的一整套流程,从而激发自己的学习兴趣,以后自己也多多造轮子(xia zhe teng)!成为 Android 界的轮子哥』

    先为大佬送上笔者已经封装好的轮子:https://github.com/LoveLifeEveryday/XPermissions

    XPermission 上车

    二.入坑之路

    2.1 创建 Android 项目

    新建一个空的 Android 项目

    创建项目

    2.2 创建 Github 项目

    建库
    • 然后,把该项目 clone 到一个上面已经创建的 Android 项目的位置

    • 将克隆下来的所有文件全部复制到上一层目录(注意:复制的时候不要忘记复制 .git 文件)

    • 将克隆的 XPermission 目录删除

    • 执行一系列的 git add . git commit -m "First commit" git push origin master 操作

    2.3 实现 XPermission

    1. 对着最顶层的 XPermission ,新建一个 module ,选择 Android Library
    新建 module

    看到 library 就行,如下

    新建 library 成功

    然后,我们思考下,运行时权限的实现思路,有以下三种:

    • 将运行时权限的操作封装到 BaseActivity
    • 提供一个透明的 Activity 来处理
    • 提供一个隐藏的 Fragment 来处理

    本文,将根据最后一个思路进行实现

    2.3.1 创建 InvisibleFragment

    //给  (Boolean, List<String>) -> Unit 指定一个别名
    typealias PermissionCallback = (Boolean, List<String>) -> Unit
    
    class InvisibleFragment : Fragment() {
    
        //定义一个 callback 作为运行时权限申请结果的回调通知方式
        private var callback: PermissionCallback? = null
        
        //定义申请权限的方法,vararg 表示可变长度的 permissions 参数列表
        fun requestNow(cb: PermissionCallback, vararg permission: String) {
            callback = cb
            requestPermissions(permission, 1)
        }
    
        /**
         * 请求返回结果
         * @param requestCode Int 请求码
         * @param permissions Array<String> 权限
         * @param grantResults IntArray 请求结果
         */
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<String>,
            grantResults: IntArray
        ) {
            if (requestCode == 1) {
                // deniedList 用来记录被用户拒绝的权限
                val deniedList = ArrayList<String>()
                for ((index, result) in grantResults.withIndex()) {
                    if (result != PackageManager.PERMISSION_GRANTED) {
                        deniedList.add(permissions[index])
                    }
                }
                // allGranted 用来标识是否所有申请的权限都已经授权
                val allGranted = deniedList.isEmpty()
                //对申请权限的结果进行回调
                callback?.let { it(allGranted, deniedList) }
            }
        }
    }
    
    • 首先,我们定义一个 callback 作为运行时权限申请结果的回调通知方式
    • 然后,定义一个 requestNow 方法
    • 最后重写 onRequestPermissionsResult 方法
    第一大步

    2.3.2 创建 XPermission

    object XPermission {
        private const val TAG = "InvisibleFragment"
        fun request(
            activity: FragmentActivity,
            vararg permission: String,
            callback: PermissionCallback
        ) {
            val fragmentManager = activity.supportFragmentManager
            val existedFragment = fragmentManager.findFragmentByTag(TAG)
            val fragment = if (existedFragment != null) {
                existedFragment as InvisibleFragment
            } else {
                val invisibleFragment = InvisibleFragment()
                fragmentManager.beginTransaction().add(invisibleFragment, TAG).commitNow()
                invisibleFragment
            }
            //这里在 permission 前面加个星号的意思是:将数组转化为可变长度参数传递过去
            fragment.requestNow(callback, *permission)
        }
    }
    

    相信代码大家都看得懂,所以笔者就不写很多注释了(其实是因为懒..)

    2.4 测试

    app\build.gradle 中引入 library

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'androidx.core:core-ktx:1.2.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
        //添加这行就行
        implementation project(':library')
    }
    

    然后进行你喜欢的权限申请

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            makeCallBtn.setOnClickListener {
                XPermission.request(this, Manifest.permission.CALL_PHONE) { allGranted, deniedList ->
                    if (allGranted) {
                        call()
                    } else {
                        Toast.makeText(this, "You  Denied $deniedList", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    
        private fun call() {
            val intent = Intent(Intent.ACTION_CALL)
            intent.data = Uri.parse("tel:10086")
            startActivity(intent)
        }
    }
    

    如果可以的话,恭喜你,你已经成功一大步了

    2.5 发布到 JitPack

    2.5.1 JitPack 简介

    JitPack 是一个网站,它允许你把 git 托管的 javaandroid 项目(貌似目前仅支持github和码云),轻松发布到 jitpackmaven 仓库上,它所有内容都通过内容分发网络(CDN)使用加密 https 连接获取

    2.5.2 为什么用 JitPack

    优点:打包比较简单,省时间,背靠 Github 这座大山

    缺点:每次导入库的时候,都要先在根的 build.gradle 文件中添加 maven

    添加 maven

    2.5.3 步骤

    • 在根的 build.gradle 中添加 maven 插件

    点击查看最新版本

    buildscript {
        ext.kotlin_version = '1.3.71'
        repositories {
            google()
            jcenter()
    
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.5.3'
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
            //添加 maven 插件
            classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    • library目录的 build.gradleapply 插件和添加 group
    apply plugin: 'com.android.library'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    //添加下面两行
    apply plugin: 'com.github.dcendents.android-maven'
    //这里 LoveLifeEveryday 改为你的 github 账号名,我的是:LoveLifeEveryday
    group='com.github.LoveLifeEveryday'
    android {
    ...
    }
    
    • 同步一下
    同步
    • 在命令行中输入 gradlew install ,从而构建你的 library 到你的本地 maven 仓库
    gradlew install

    等待 BUILD SUCCESSFUL,BUILD FAIL,说明构建失败,这时候你就要按照失败提示去排错,排错完后在执行一遍 gradlew install 命令,直到出现 BUILD SUCCESS

    • 把代码提交到本地 git 仓库

    git add .git commit -m “XX”

    • 在本地 git 仓库打 tag
    git tag -a 1.0.0 -m "第一版"
    git push origin 1.0.0
    
    • 打开你的 libarygithub 界面,点击 release,如下:
    release
    • 点击 Draft a new release,新建一个 release,如下:
    image-20200424182958983
    • 然后填信息,如下:
    填信息
    • 填好信息后,点击publich release,如下:
    image
    • GitHub 账号登陆、注册 jitpack
    • 登陆后,在地址栏中输入你的 librarygithub 项目地址,然后点击 Look Up ,如下:
    image
    • 然后点击 Get it,它会滚到下面去,你要滚回上面去,先等一会,等 jitpack 那里构建完,会出现一个绿色的 log,则构建成功,如下:
    image

    然后你就可以愉快的在项目中按照它的提示引用你的开源库

    image
    • 点击那个 jitpack ,把它的链接复制到你的 Readme 中去,如下:
    jitpack

    2.6 尝试使用你的框架

    当然是在 app\build.gradle

    //引用自己的开源库
    implementation 'com.github.LoveLifeEveryday:XPermissions:1.0.0'
    

    然后尝试使用吧

    完成

    2.7 美化你的项目

    一个优秀的开源项目,readme 一定不会差

    鲁迅说:『虽然这些工作不会让你的项目变得牛逼,但会让你的项目变得漂亮,方便了其他人去了解你这个项目』

    详细的美化操作,可以参考这篇文章:如何让你的 GitHub 项目表面上更专业

    好看

    三.我在使用中遇到的问题

    3.1 在模拟器上 Call 权限申请无反应

    • 发生情景:在逍遥模拟器上测试 Call 权限

    至于我为什么要使用逍遥模拟器,这又是另一个故事了

    • 解决:真机测试正常申请权限,于是百度了一波,发现很多模拟器没有 Call 这个权限(such as 夜神模拟器),我觉得原装的模拟器应该是可以正常运行的
    • 结论:模拟器的锅

    3.2 上传到 JcenterFailed

    • 发生情景:执行上传命令的时候,运行到最后发生错误
    • 错误:
    * What went wrong:
    Execution failed for task ':utils:bintrayUpload'.
    > org.apache.http.NoHttpResponseException: The target server failed to respond
    
    • 过程:Google && Baidu
    • 结论:网络问题
    • 结果:尝试了普通网络和 Ke Xue 上网,还是无法解决,转为使用 JitPack

    如果想了解,怎么上传到 Jcenter 的话,可以看下这篇文章:AS上传Library到JCenter 教程+踩坑记录

    bug 退散

    如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

    本文参考链接:

    相关文章

      网友评论

        本文标题:带你封装自己的『权限管理』框架

        本文链接:https://www.haomeiwen.com/subject/pavhwhtx.html