Android權(quán)限使用PermissionX

一、前言:

在開始介紹PermissionX的具體用法之前,我們先來討論一下它的實現(xiàn)原理。

其實之前并不是沒有人嘗試過對運(yùn)行時權(quán)限處理進(jìn)行封裝,我之前在做直播公開課的時候也向大家演示過一種運(yùn)行時權(quán)限API的封裝過程。

但是,想要對運(yùn)行時權(quán)限的API進(jìn)行封裝并不是一件容易的事,因為這個操作是有特定的上下文依賴的,一般需要在Activity中接收onRequestPermissionsResult()方法的回調(diào)才行,所以不能簡單地將整個操作封裝到一個獨立的類中。

為此,也衍生出了一系列特殊的封裝方案,比如將運(yùn)行時權(quán)限的操作封裝到BaseActivity中,或者提供一個透明的Activity來處理運(yùn)行時權(quán)限等。

不過上述兩種方案都不夠輕量,因為改變Activity的繼承結(jié)構(gòu)這可是大事情,而提供一個透明的Activty則需要在AndroidManifest.xml中進(jìn)行額外的聲明。

現(xiàn)在,業(yè)內(nèi)普遍比較認(rèn)可使用另外一種小技巧來進(jìn)行實現(xiàn)。是什么小技巧呢?回想一下,之前所有申請運(yùn)行時權(quán)限的操作都是在Activity中進(jìn)行的,事實上,Android在Fragment中也提供了一份相同的API,使得我們在Fragment中也能申請運(yùn)行時權(quán)限。

但不同的是,F(xiàn)ragment并不像Activity那樣必須有界面,我們完全可以向Activity中添加一個隱藏的Fragment,然后在這個隱藏的Fragment中對運(yùn)行時權(quán)限的API進(jìn)行封裝。這是一種非常輕量級的做法,不用擔(dān)心隱藏Fragment會對Activity的性能造成什么影響。

這就是PermissionX的實現(xiàn)原理了,書中其實也已經(jīng)介紹過了這部分內(nèi)容。但是,在其實現(xiàn)原理的基礎(chǔ)之上,后期我又增加了很多新功能,讓PermissionX變得更加強(qiáng)大和好用,下面我們就來學(xué)習(xí)一下PermissionX的具體用法。

二、基本用法

1、引入jar包

要使用PermissionX之前,首先需要將其引入到項目當(dāng)中,如下所示:

    //權(quán)限
    implementation 'com.permissionx.guolindev:permissionx:1.4.0'

我在寫本篇文章時PermissionX的最新版本是1.4.0,想要查看它的當(dāng)前最新版本,請訪問PermissionX的主頁:https://github.com/guolindev/PermissionX

PermissionX的目的是為了讓運(yùn)行時權(quán)限處理盡可能的容易,因此怎么讓API變得簡單好用就是我優(yōu)先要考慮的問題。

比如同樣實現(xiàn)撥打電話的功能,使用PermissionX只需要這樣寫:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        makeCallBtn.setOnClickListener {
            PermissionX.init(this)
                .permissions(Manifest.permission.CALL_PHONE)
                .request { allGranted, grantedList, deniedList ->
                    if (allGranted) {
                        call()
                    } else {
                        Toast.makeText(this, "您拒絕了撥打電話權(quán)限", Toast.LENGTH_SHORT).show()
                    }
                }
        }
    }
    ...
}

是的,PermissionX的基本用法就這么簡單。首先調(diào)用init()方法來進(jìn)行初始化,并在初始化的時候傳入一個FragmentActivity參數(shù)。由于AppCompatActivity是FragmentActivity的子類,所以只要你的Activity是繼承自AppCompatActivity的,那么直接傳入this就可以了。

接下來調(diào)用permissions()方法傳入你要申請的權(quán)限名,這里傳入CALL_PHONE權(quán)限。你也可以在permissions()方法中傳入任意多個權(quán)限名,中間用逗號隔開即可。

最后調(diào)用request()方法來執(zhí)行權(quán)限申請,并在Lambda表達(dá)式中處理申請結(jié)果。可以看到,Lambda表達(dá)式中有3個參數(shù):allGranted表示是否所有申請的權(quán)限都已被授權(quán),grantedList用于記錄所有已被授權(quán)的權(quán)限,deniedList用于記錄所有被拒絕的權(quán)限。

因為我們只申請了一個CALL_PHONE權(quán)限,因此這里直接判斷:如果allGranted為true,那么就調(diào)用call()方法,否則彈出一個Toast提示。

運(yùn)行結(jié)果如下:


20200517221351551.gif

2、核心用法

然而我們目前還只是處理了最普通的場景,剛才提到的,假如用戶拒絕了某個權(quán)限,在下次申請之前,我們最好彈出一個對話框來向用戶解釋申請這個權(quán)限的原因,這個又該怎么實現(xiàn)呢?

別擔(dān)心,PermissionX對這些情況進(jìn)行了充分的考慮。

onExplainRequestReason()方法可以用于監(jiān)聽那些被用戶拒絕,而又可以再次去申請的權(quán)限。從方法名上也可以看出來了,應(yīng)該在這個方法中解釋申請這些權(quán)限的原因。

而我們只需要將onExplainRequestReason()方法串接到request()方法之前即可,如下所示:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
    .onExplainRequestReason { deniedList ->
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(this, "所有申請的權(quán)限都已通過", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "您拒絕了如下權(quán)限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

這種情況下,所有被用戶拒絕的權(quán)限會優(yōu)先進(jìn)入onExplainRequestReason()方法進(jìn)行處理,拒絕的權(quán)限都記錄在deniedList參數(shù)當(dāng)中。接下來,我們只需要在這個方法中調(diào)用showRequestReasonDialog()方法,即可彈出解釋權(quán)限申請原因的對話框,如下所示:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
    .onExplainRequestReason { deniedList ->
        showRequestReasonDialog(deniedList, "即將重新申請的權(quán)限是程序必須依賴的權(quán)限", "我已明白", "取消")
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(this, "所有申請的權(quán)限都已通過", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "您拒絕了如下權(quán)限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

showRequestReasonDialog()方法接受4個參數(shù):第一個參數(shù)是要重新申請的權(quán)限列表,這里直接將deniedList參數(shù)傳入。第二個參數(shù)則是要向用戶解釋的原因,我只是隨便寫了一句話,這個參數(shù)描述的越詳細(xì)越好。第三個參數(shù)是對話框上確定按鈕的文字,點擊該按鈕后將會重新執(zhí)行權(quán)限申請操作。第四個參數(shù)是一個可選參數(shù),如果不傳的話相當(dāng)于用戶必須同意申請的這些權(quán)限,否則對話框無法關(guān)閉,而如果傳入的話,對話框上會有一個取消按鈕,點擊取消后不會重新進(jìn)行權(quán)限申請,而是會把當(dāng)前的申請結(jié)果回調(diào)到request()方法當(dāng)中。

另外始終要記得將所有申請的權(quán)限都在AndroidManifest.xml中進(jìn)行聲明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.permissionx.app">

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    ...
</manifest>

重新運(yùn)行一下程序,效果如下圖所示:


222222.gif

當(dāng)前版本解釋權(quán)限申請原因?qū)υ捒虻臉邮竭€無法自定義,1.3.0版本當(dāng)中已支持了自定義權(quán)限提醒對話框樣式的功能,詳情請參閱 PermissionX重磅更新,支持自定義權(quán)限提醒對話框 。

當(dāng)然,我們也可以指定要對哪些權(quán)限重新申請,比如上述申請的3個權(quán)限中,我認(rèn)為CAMERA權(quán)限是必不可少的,而其他兩個權(quán)限則可有可無,那么在重新申請的時候也可以只申請CAMERA權(quán)限:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION)
    .onExplainRequestReason { deniedList ->
        val filteredList = deniedList.filter {
            it == Manifest.permission.CAMERA
        }
        showRequestReasonDialog(filteredList, "攝像機(jī)權(quán)限是程序必須依賴的權(quán)限", "我已明白", "取消")
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(this, "所有申請的權(quán)限都已通過", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "您拒絕了如下權(quán)限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

這樣當(dāng)再次申請權(quán)限的時候就只會申請CAMERA權(quán)限,剩下的兩個權(quán)限最終會被傳入到request()方法的deniedList參數(shù)當(dāng)中。

解決了向用戶解釋權(quán)限申請原因的問題,接下來還有一個頭疼的問題要解決:如果用戶不理會我們的解釋,仍然執(zhí)意拒絕權(quán)限申請,并且還選擇了拒絕且不再詢問的選項,這該怎么辦?通常這種情況下,程序?qū)用嬉呀?jīng)無法再次做出權(quán)限申請,唯一能做的就是提示用戶到應(yīng)用程序設(shè)置當(dāng)中手動打開權(quán)限。

那么PermissionX是如何處理這種情況的呢?我相信絕對會給你帶來驚喜。PermissionX中還提供了一個onForwardToSettings()方法,專門用于監(jiān)聽那些被用戶永久拒絕的權(quán)限。另外從方法名上就可以看出,我們可以在這里提醒用戶手動去應(yīng)用程序設(shè)置當(dāng)中打開權(quán)限。代碼如下所示:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
    .onExplainRequestReason { deniedList ->
        showRequestReasonDialog(deniedList, "即將重新申請的權(quán)限是程序必須依賴的權(quán)限", "我已明白", "取消")
    }
    .onForwardToSettings { deniedList ->
        showForwardToSettingsDialog(deniedList, "您需要去應(yīng)用程序設(shè)置當(dāng)中手動開啟權(quán)限", "我已明白", "取消")
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(this, "所有申請的權(quán)限都已通過", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "您拒絕了如下權(quán)限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

可以看到,這里又串接了一個onForwardToSettings()方法,所有被用戶選擇了拒絕且不再詢問的權(quán)限都會進(jìn)行到這個方法中處理,拒絕的權(quán)限都記錄在deniedList參數(shù)當(dāng)中。

接下來,你并不需要自己彈出一個Toast或是對話框來提醒用戶手動去應(yīng)用程序設(shè)置當(dāng)中打開權(quán)限,而是直接調(diào)用showForwardToSettingsDialog()方法即可。類似地,showForwardToSettingsDialog()方法也接收4個參數(shù),每個參數(shù)的作用和剛才的showRequestReasonDialog()方法完全一致,我這里就不再重復(fù)解釋了。

showForwardToSettingsDialog()方法將會彈出一個對話框,當(dāng)用戶點擊對話框上的我已明白按鈕時,將會自動跳轉(zhuǎn)到當(dāng)前應(yīng)用程序的設(shè)置界面,從而不需要用戶自己慢慢進(jìn)入設(shè)置當(dāng)中尋找當(dāng)前應(yīng)用了。另外,當(dāng)用戶從設(shè)置中返回時,PermissionX將會自動重新請求相應(yīng)的權(quán)限,并將最終的授權(quán)結(jié)果回調(diào)到request()方法當(dāng)中。效果如下圖所示:


333333.gif

同樣,1.3.0版本也支持了自定義這個對話框樣式的功能,詳情請參閱 PermissionX重磅更新,支持自定義權(quán)限提醒對話框 。

3、更多用法

PermissionX最主要的功能大概就是這些,不過我在使用一些App的時候發(fā)現(xiàn),有些App喜歡在第一次請求權(quán)限之前就先彈出一個對話框向用戶解釋自己需要哪些權(quán)限,然后才會進(jìn)行權(quán)限申請。這種做法是比較提倡的,因為用戶同意授權(quán)的概率會更高。

那么PermissionX中要如何實現(xiàn)這樣的功能呢?

其實非常簡單,PermissionX還提供了一個explainReasonBeforeRequest()方法,只需要將它也串接到request()方法之前就可以了,代碼如下所示:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
    .explainReasonBeforeRequest()
    .onExplainRequestReason { deniedList ->
        showRequestReasonDialog(deniedList, "即將申請的權(quán)限是程序必須依賴的權(quán)限", "我已明白")
    }
    .onForwardToSettings { deniedList ->
        showForwardToSettingsDialog(deniedList, "您需要去應(yīng)用程序設(shè)置當(dāng)中手動開啟權(quán)限", "我已明白")
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(this, "所有申請的權(quán)限都已通過", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "您拒絕了如下權(quán)限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

這樣,當(dāng)每次請求權(quán)限時,會優(yōu)先進(jìn)入onExplainRequestReason()方法,彈出解釋權(quán)限申請原因的對話框,用戶點擊我已明白按鈕之后才會執(zhí)行權(quán)限申請。效果如下圖所示:

444444.gif

不過,你在使用explainReasonBeforeRequest()方法時,其實還有一些關(guān)鍵的點需要注意。

第一,單獨使用explainReasonBeforeRequest()方法是無效的,必須配合onExplainRequestReason()方法一起使用才能起作用。這個很好理解,因為沒有配置onExplainRequestReason()方法,我們怎么向用戶解釋權(quán)限申請原因呢?

第二,在使用explainReasonBeforeRequest()方法時,如果onExplainRequestReason()方法中編寫了權(quán)限過濾的邏輯,最終的運(yùn)行結(jié)果可能和你期望的會不一致。這一點可能會稍微有點難理解,我用一個具體的示例來解釋一下。

觀察如下代碼:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
    .explainReasonBeforeRequest()
    .onExplainRequestReason { deniedList ->
        val filteredList = deniedList.filter {
            it == Manifest.permission.CAMERA
        }
        showRequestReasonDialog(filteredList, "攝像機(jī)權(quán)限是程序必須依賴的權(quán)限", "我已明白")
    }
    ...

這里在onExplainRequestReason()方法中編寫了剛才用到的權(quán)限過濾邏輯,當(dāng)有多個權(quán)限被拒絕時,我們只重新申請CAMERA權(quán)限。

在沒有加入explainReasonBeforeRequest()方法時,一切都可以按照我們所預(yù)期的那樣正常運(yùn)行。但如果加上了explainReasonBeforeRequest()方法,在執(zhí)行權(quán)限請求之前會先進(jìn)入onExplainRequestReason()方法,而這里將除了CAMERA之外的其他權(quán)限都過濾掉了,因此實際上PermissionX只會請求CAMERA這一個權(quán)限,剩下的權(quán)限將完全不會嘗試去請求,而是直接作為被拒絕的權(quán)限回調(diào)到最終的request()方法當(dāng)中。

效果如下圖所示:


55555.gif

針對于這種情況,PermissionX在onExplainRequestReason()方法中提供了一個額外的beforeRequest參數(shù),用于標(biāo)識當(dāng)前上下文是在權(quán)限請求之前還是之后,借助這個參數(shù)在onExplainRequestReason()方法中執(zhí)行不同的邏輯,即可很好地解決這個問題,示例代碼如下:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
    .explainReasonBeforeRequest()
    .onExplainRequestReason { deniedList, beforeRequest ->
        if (beforeRequest) {
            showRequestReasonDialog(deniedList, "為了保證程序正常工作,請您同意以下權(quán)限申請", "我已明白")
        } else {
            val filteredList = deniedList.filter {
                it == Manifest.permission.CAMERA
            }
            showRequestReasonDialog(filteredList, "攝像機(jī)權(quán)限是程序必須依賴的權(quán)限", "我已明白")
        }
    }
    ...

可以看到,當(dāng)beforeRequest為true時,說明此時還未執(zhí)行權(quán)限申請,那么我們將完整的deniedList傳入showRequestReasonDialog()方法當(dāng)中。

而當(dāng)beforeRequest為false時,說明某些權(quán)限被用戶拒絕了,此時我們只重新申請CAMERA權(quán)限,因為它是必不可少的,其他權(quán)限則可有可無。


66666.gif

參考:https://blog.csdn.net/guolin_blog/article/details/106181780

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

相關(guān)閱讀更多精彩內(nèi)容

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