Android 運行時權限管理最佳實踐

歡迎訪問我的個人博客 傳送門

從 Android 6.0(API 級別 23)開始,用戶開始在應用運行時向其授予權限,而不是在應用安裝時授予。此方法可以簡化應用安裝過程,因爲用戶在安裝或更新應用時不需要授予權限。它還讓用戶可以對應用的功能進行更多控制;例如,用戶可以選擇爲相機應用提供相機訪問權限,而不提供設備位置的訪問權限。用戶可以隨時進入應用的“Settings”屏幕調用權限。

正常權限和危險權限

系統權限分爲幾個保護級別。需要了解的兩個最重要保護級別是正常權限和危險權限,如果應用聲明其需要正常權限,系統會自動向應用授予該權限,如:訪問網絡。如果應用聲明其需要危險權限,則用戶必須明確嚮應用授予該權限,如:訪問聯繫人、讀寫權限。

正常權限

官網可查 點擊查詢

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMINbaidu_push
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS

危險權限

可通過adb 命令獲取 adb shell pm list permissions -d -g

group:android.permission-group.RCS_PERMISSION

group:com.google.android.gms.permission.CAR_INFORMATION
  permission:com.google.android.gms.permission.CAR_VENDOR_EXTENSION
  permission:com.google.android.gms.permission.CAR_MILEAGE
  permission:com.google.android.gms.permission.CAR_FUEL

group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.ANSWER_PHONE_CALLS
  permission:android.permission.READ_PHONE_NUMBERS
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA
  permission:android.permission.CAMERA

group:android.permission-group.SENSORS
  permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:com.google.android.gms.permission.CAR_SPEED
  permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE

group:com.sina.weibo.permission-group
  permission:com.sina.weibo.permission.USER

group:android.permission-group.MICROPHONE
  permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS

從上面的權限列表中可以看出危險權限都是分組的,如果應用請求其清單中列出的危險權限,而應用在同一權限組中已有另一項危險權限,則系統會立即授予該權限,而無需與用戶進行任何交互。例如,如果某應用已經請求並且被授予了 READ_CONTACTS 權限,然後它又請求 WRITE_CONTACTS,系統將立即授予該權限。

請求權限

這裏以申請日曆讀寫權限爲例

1.在 AndroidMainifest 中聲明所需權限

<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>

2.檢查權限

  private fun checkPermissions(): Boolean {
        return when (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR)) {
            PackageManager.PERMISSION_GRANTED -> {//有此權限
                true
            }
            PackageManager.PERMISSION_DENIED -> {//無此權限
                false
            }
            else -> false
        }
    }

這裏用到系統提供的 ContextCompat.checkSelfPermission 方法,用於檢測某個權限是否已經被授予,方法返回值爲 PackageManager.PERMISSION_GRANTED 表示已權限,爲PackageManager.PERMISSION_DENIED 表示無此權限需要進行申請授權。

<span id = "requestPermission"></span>

3.申請權限

private fun requestPermissions() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.READ_CALENDAR)) {
            //  是否需要向用戶解釋爲何申請權限 
             toast(this,"需要此權限管理日曆")
        } else {
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.READ_CALENDAR),
                    1)
        }
    }

ActivityCompat.shouldShowRequestPermissionRationale 方法 用於在實際顯示權限對話框之前是否顯示一個對正在請求權限的解釋,在app第一次安裝的時候。這個方法會返回false,因此你可以直接請求任何需要的權限。
如果用戶以前拒絕了一個請求,則分爲兩種情況:

  • 如果用戶僅拒絕沒有點不再提示,這個方法將返回 true
  • 如果用戶拒絕並點擊不再提示,這個方法將返回 false

ActivityCompat.requestPermissions 方法 用於申請權限,第二個參數爲 所需權限數組,也就是可申請一個,或多個權限。第三個參數爲 requestCode 回調的時候使用

4.處理權限申請回調

 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) {
                    toast(this, "已授權")
                } else {
                    toast(this, "未授權")
                }
            }
            else -> {
            }
        }
    }

處理權限要實現 onRequestPermissionsResult 方法,該方法有三個參數

  • requestCode 和申請權限時 requestCode 對應,
  • permissions 申請的權限數組
  • grantResults 申請結果

完整的代碼如下:

    fun click(view: View?) {
        when (view?.id) {
            R.id.bt_query_permissions -> when (checkPermissions()) {
                true -> toast(this, "有權限")
                false -> toast(this, "無權限")
            }
            R.id.bt_request_permissions -> requestPermissions()
        }
    }

    private fun checkPermissions(): Boolean {
        return when (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR)) {
            PackageManager.PERMISSION_GRANTED -> {//有此權限
                true
            }
            PackageManager.PERMISSION_DENIED -> {//無此權限
                false
            }
            else -> false
        }
    }

    private fun requestPermissions() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.READ_CALENDAR)) {
            toast(this,"需要此權限管理日曆")

        } else {
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.READ_CALENDAR),
                    1)
        }
    }

    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) {
                    toast(this, "已授權")
                } else {
                    toast(this, "未授權")
                }
            }
            else -> {
            }
        }
    }

Android 運行時權限管理最佳實踐 Android 運行時權限管理最佳實踐

推薦使用 RxPermissions

RxPermissions 是一個基於 RxJava 實現的權限框架,比使用 Android 自帶的 API 方便很多,可擴展性高。GitHub 地址

引入

這裏以 Rxjava2 爲例

repositories {
    jcenter() // If not already there
}

dependencies {
    //compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'  // Rxjava1 
    compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' // Rxjava2 
    compile "io.reactivex.rxjava2:rxjava:2.1.7"
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
}

請求權限

request 可申請一個或多個權限 直接返回是否授權成功

 val rxPermissions = RxPermissions(this)
 rxPermissions.request(Manifest.permission.READ_CALENDAR)
                .subscribe({ t ->
                     if (t) {
                         toast(this, "已授權")
                     } else {
                         toast(this, "未授權")

                     }
                 })               

requestEach or ensureEach 來分別獲取每一個權限請求的結果

 rxPermissions.requestEach(Manifest.permission.READ_CALENDAR, Manifest.permission.CAMERA)
                .subscribe({ t ->
                    when {
                        t.granted -> toast(this, "${t.name} 已授權")
                        t.shouldShowRequestPermissionRationale -> toast(this, "${t.name} 未授權")
                        else -> toast(this, "${t.name} 已拒絕,並不提示")
                    }
                })

這裏的 shouldShowRequestPermissionRationale 參照上文 權限申請

Android 運行時權限管理最佳實踐

最後

權限申請的坑還有很多,特別是在國產手機上有各種各樣的bug,這個就要具體踩坑,具體解決了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章