帶你封裝自己的『權(quán)限管理』框架

前言

本文已經(jīng)收錄到我的 Github 個(gè)人博客,歡迎大佬們光臨寒舍:

我的 Github 博客

本篇文章需要已經(jīng)具備的知識(shí):

  • GitGithub 的基本使用
  • Kotlin 語(yǔ)法基礎(chǔ)
  • Android 開發(fā)基礎(chǔ)

學(xué)習(xí)清單:

  • 如何封裝自己的權(quán)限框架
  • 將開源庫(kù)發(fā)布到 JitPack 倉(cāng)庫(kù)的一整套流程

一.為什么要封裝這套框架

我們?cè)谌粘i_發(fā)中,經(jīng)常需要用到申請(qǐng)運(yùn)行時(shí)權(quán)限的知識(shí),于是,經(jīng)常就寫了下面的一大串代碼


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    ...
    //申請(qǐng) CALL_PHONE 權(quán)限
    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()
            }
        }
    }
}

麻鴨,頭疼,這么多代碼,不僅寫著難受,看著更是頭疼

頭疼

這時(shí)候,如果這個(gè)世界簡(jiǎn)單點(diǎn),純粹點(diǎn),就好了

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

是不是感覺世界又友好了很多呢?這段代碼比之前的代碼量少了很多不說(shuō),邏輯更是清晰了很多鴨!

滾來(lái)了

很顯然,上面用到了自己封裝的框架,有可能你會(huì)一臉不屑:『這算啥?Github 上一堆權(quán)限申請(qǐng)框架,他們寫的這個(gè)簡(jiǎn)潔又漂亮,功能又多又全,超帥的』

我想說(shuō):『是的,你說(shuō)的對(duì),雖然 Github 上有這么多,跑得又快又棒的輪子,但是,別人做的菜總歸沒有自己的香鴨!我們可以通過(guò)自己封裝一個(gè)簡(jiǎn)單的權(quán)限申請(qǐng)框架開始,學(xué)習(xí)發(fā)布開源庫(kù)到 Jitpack / Jcenter 的一整套流程,從而激發(fā)自己的學(xué)習(xí)興趣,以后自己也多多造輪子(xia zhe teng)!成為 Android 界的輪子哥』

先為大佬送上筆者已經(jīng)封裝好的輪子:https://github.com/LoveLifeEveryday/XPermissions

XPermission
上車

二.入坑之路

2.1 創(chuàng)建 Android 項(xiàng)目

新建一個(gè)空的 Android 項(xiàng)目

創(chuàng)建項(xiàng)目

2.2 創(chuàng)建 Github 項(xiàng)目

建庫(kù)
  • 然后,把該項(xiàng)目 clone 到一個(gè)上面已經(jīng)創(chuàng)建的 Android 項(xiàng)目的位置

  • 將克隆下來(lái)的所有文件全部復(fù)制到上一層目錄(注意:復(fù)制的時(shí)候不要忘記復(fù)制 .git 文件)

  • 將克隆的 XPermission 目錄刪除

  • 執(zhí)行一系列的 git add . git commit -m "First commit" git push origin master 操作

2.3 實(shí)現(xiàn) XPermission

  1. 對(duì)著最頂層的 XPermission ,新建一個(gè) module ,選擇 Android Library
新建 module

看到 library 就行,如下

新建 library 成功

然后,我們思考下,運(yùn)行時(shí)權(quán)限的實(shí)現(xiàn)思路,有以下三種:

  • 將運(yùn)行時(shí)權(quán)限的操作封裝到 BaseActivity
  • 提供一個(gè)透明的 Activity 來(lái)處理
  • 提供一個(gè)隱藏的 Fragment 來(lái)處理

本文,將根據(jù)最后一個(gè)思路進(jìn)行實(shí)現(xiàn)

2.3.1 創(chuàng)建 InvisibleFragment

//給  (Boolean, List<String>) -> Unit 指定一個(gè)別名
typealias PermissionCallback = (Boolean, List<String>) -> Unit

class InvisibleFragment : Fragment() {

    //定義一個(gè) callback 作為運(yùn)行時(shí)權(quán)限申請(qǐng)結(jié)果的回調(diào)通知方式
    private var callback: PermissionCallback? = null
    
    //定義申請(qǐng)權(quán)限的方法,vararg 表示可變長(zhǎng)度的 permissions 參數(shù)列表
    fun requestNow(cb: PermissionCallback, vararg permission: String) {
        callback = cb
        requestPermissions(permission, 1)
    }

    /**
     * 請(qǐng)求返回結(jié)果
     * @param requestCode Int 請(qǐng)求碼
     * @param permissions Array<String> 權(quán)限
     * @param grantResults IntArray 請(qǐng)求結(jié)果
     */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        if (requestCode == 1) {
            // deniedList 用來(lái)記錄被用戶拒絕的權(quán)限
            val deniedList = ArrayList<String>()
            for ((index, result) in grantResults.withIndex()) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    deniedList.add(permissions[index])
                }
            }
            // allGranted 用來(lái)標(biāo)識(shí)是否所有申請(qǐng)的權(quán)限都已經(jīng)授權(quán)
            val allGranted = deniedList.isEmpty()
            //對(duì)申請(qǐng)權(quán)限的結(jié)果進(jìn)行回調(diào)
            callback?.let { it(allGranted, deniedList) }
        }
    }
}
  • 首先,我們定義一個(gè) callback 作為運(yùn)行時(shí)權(quán)限申請(qǐng)結(jié)果的回調(diào)通知方式
  • 然后,定義一個(gè) requestNow 方法
  • 最后重寫 onRequestPermissionsResult 方法
第一大步

2.3.2 創(chuàng)建 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 前面加個(gè)星號(hào)的意思是:將數(shù)組轉(zhuǎn)化為可變長(zhǎng)度參數(shù)傳遞過(guò)去
        fragment.requestNow(callback, *permission)
    }
}

相信代碼大家都看得懂,所以筆者就不寫很多注釋了(其實(shí)是因?yàn)閼?.)

2.4 測(cè)試

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')
}

然后進(jìn)行你喜歡的權(quán)限申請(qǐng)

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)
    }
}

如果可以的話,恭喜你,你已經(jīng)成功一大步了

2.5 發(fā)布到 JitPack

2.5.1 JitPack 簡(jiǎn)介

JitPack 是一個(gè)網(wǎng)站,它允許你把 git 托管的 javaandroid 項(xiàng)目(貌似目前僅支持github和碼云),輕松發(fā)布到 jitpackmaven 倉(cāng)庫(kù)上,它所有內(nèi)容都通過(guò)內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)使用加密 https 連接獲取

2.5.2 為什么用 JitPack

優(yōu)點(diǎn):打包比較簡(jiǎn)單,省時(shí)間,背靠 Github 這座大山

缺點(diǎn):每次導(dǎo)入庫(kù)的時(shí)候,都要先在根的 build.gradle 文件中添加 maven

添加 maven

2.5.3 步驟

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

點(diǎn)擊查看最新版本

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 賬號(hào)名,我的是:LoveLifeEveryday
group='com.github.LoveLifeEveryday'
android {
...
}
  • 同步一下
同步
  • 在命令行中輸入 gradlew install ,從而構(gòu)建你的 library 到你的本地 maven 倉(cāng)庫(kù)
gradlew install

等待 BUILD SUCCESSFUL,BUILD FAIL,說(shuō)明構(gòu)建失敗,這時(shí)候你就要按照失敗提示去排錯(cuò),排錯(cuò)完后在執(zhí)行一遍 gradlew install 命令,直到出現(xiàn) BUILD SUCCESS

  • 把代碼提交到本地 git 倉(cāng)庫(kù)

git add .git commit -m “XX”

  • 在本地 git 倉(cāng)庫(kù)打 tag
git tag -a 1.0.0 -m "第一版"
git push origin 1.0.0
  • 打開你的 libarygithub 界面,點(diǎn)擊 release,如下:
release
  • 點(diǎn)擊 Draft a new release,新建一個(gè) release,如下:
image-20200424182958983
  • 然后填信息,如下:
填信息
  • 填好信息后,點(diǎn)擊publich release,如下:
image
  • GitHub 賬號(hào)登陸、注冊(cè) jitpack
  • 登陸后,在地址欄中輸入你的 librarygithub 項(xiàng)目地址,然后點(diǎn)擊 Look Up ,如下:
image
  • 然后點(diǎn)擊 Get it,它會(huì)滾到下面去,你要滾回上面去,先等一會(huì),等 jitpack 那里構(gòu)建完,會(huì)出現(xiàn)一個(gè)綠色的 log,則構(gòu)建成功,如下:
image

然后你就可以愉快的在項(xiàng)目中按照它的提示引用你的開源庫(kù)

image
  • 點(diǎn)擊那個(gè) jitpack ,把它的鏈接復(fù)制到你的 Readme 中去,如下:
jitpack

2.6 嘗試使用你的框架

當(dāng)然是在 app\build.gradle

//引用自己的開源庫(kù)
implementation 'com.github.LoveLifeEveryday:XPermissions:1.0.0'

然后嘗試使用吧

完成

2.7 美化你的項(xiàng)目

一個(gè)優(yōu)秀的開源項(xiàng)目,readme 一定不會(huì)差

魯迅說(shuō):『雖然這些工作不會(huì)讓你的項(xiàng)目變得牛逼,但會(huì)讓你的項(xiàng)目變得漂亮,方便了其他人去了解你這個(gè)項(xiàng)目』

詳細(xì)的美化操作,可以參考這篇文章:如何讓你的 GitHub 項(xiàng)目表面上更專業(yè)

好看

三.我在使用中遇到的問題

3.1 在模擬器上 Call 權(quán)限申請(qǐng)無(wú)反應(yīng)

  • 發(fā)生情景:在逍遙模擬器上測(cè)試 Call 權(quán)限

至于我為什么要使用逍遙模擬器,這又是另一個(gè)故事了

  • 解決:真機(jī)測(cè)試正常申請(qǐng)權(quán)限,于是百度了一波,發(fā)現(xiàn)很多模擬器沒有 Call 這個(gè)權(quán)限(such as 夜神模擬器),我覺得原裝的模擬器應(yīng)該是可以正常運(yùn)行的
  • 結(jié)論:模擬器的鍋

3.2 上傳到 Jcenter 時(shí) Failed

  • 發(fā)生情景:執(zhí)行上傳命令的時(shí)候,運(yùn)行到最后發(fā)生錯(cuò)誤
  • 錯(cuò)誤:
* What went wrong:
Execution failed for task ':utils:bintrayUpload'.
> org.apache.http.NoHttpResponseException: The target server failed to respond
  • 過(guò)程:Google && Baidu
  • 結(jié)論:網(wǎng)絡(luò)問題
  • 結(jié)果:嘗試了普通網(wǎng)絡(luò)和 Ke Xue 上網(wǎng),還是無(wú)法解決,轉(zhuǎn)為使用 JitPack

如果想了解,怎么上傳到 Jcenter 的話,可以看下這篇文章:AS上傳Library到JCenter 教程+踩坑記錄

bug 退散

如果文章對(duì)您有一點(diǎn)幫助的話,希望您能點(diǎn)一下贊,您的點(diǎn)贊,是我前進(jìn)的動(dòng)力

本文參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容