帶你封裝自己的『許可權管理』框架

前言

本文已經收錄到我的 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 界的輪子哥』

先為大佬送上筆者已經封裝好的輪子://github.com/LoveLifeEveryday/XPermissions

XPermission
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
新建 module

看到 library 就行,如下

新建 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: PermissionCallbackvararg 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
添加 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
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
release
  • 點擊 Draft a new release,新建一個 release,如下:
image-20200424182958983
image-20200424182958983
  • 然後填資訊,如下:
填資訊
填資訊
  • 填好資訊後,點擊publich release,如下:
  • GitHub 帳號登陸、註冊 jitpack
  • 登陸後,在地址欄中輸入你的 librarygithub 項目地址,然後點擊 Look Up ,如下:
  • 然後點擊 Get it,它會滾到下面去,你要滾回上面去,先等一會,等 jitpack 那裡構建完,會出現一個綠色的 log,則構建成功,如下:

然後你就可以愉快的在項目中按照它的提示引用你的開源庫

  • 點擊那個 jitpack ,把它的鏈接複製到你的 Readme 中去,如下:
jitpack
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 退散
bug 退散

如果文章對您有一點幫助的話,希望您能點一下贊,您的點贊,是我前進的動力

本文參考鏈接:

本文使用 mdnice 排版