一赔桌、概述
從Android 6.0(API 23)開始渴逻,系統(tǒng)權限做了很大的改變惨奕。在之前,6.0以下的權限及在安裝時梨撞,會根據(jù)權限聲明產(chǎn)生一個權限列表卧波,用戶只有在同意之后才能完成app的安裝。也就是說當我們想要使用某個app時港粱,就要默默忍受一些不必要的權限(比如訪問通訊錄和短信等)。而在Android 6.0(API 23)以后锈颗,我們可以直接安裝app击吱。app的權限授予是在app運行時授予的,而不是在app安裝時授予覆醇。這樣既簡化app安裝過程永脓,當app需要我們授予不恰當?shù)臋嘞薜臅r候,我們也可以選擇拒絕授予這些權限常摧。已授予過的權限落午,我們還可以去app設置頁面去關閉授權。這站在用戶的角度上來說提高了安全性溃斋,可以防止一些應用惡意訪問用戶數(shù)據(jù)梗劫。但是對于開發(fā)者來說,也增加了不少工作量蛉威。當app要訪問敏感用戶數(shù)據(jù)(如聯(lián)系人和短信)以及某些系統(tǒng)功能(如相機和互聯(lián)網(wǎng)的權限)就必須先動態(tài)申請權限猫妙。根據(jù)功能的不同聚凹,系統(tǒng)可能會自動授予權限,或者可能會提示用戶批準請求彼哼。
和往常一樣湘今,主要是想總結(jié)一下對Android 6.0動態(tài)權限申請的學習過程以及一些需要注意的地方。
詳細請查看谷歌官方文檔:https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-runtime-permissions
二拴签、權限和權限組
新的權限機制更好的保護了用戶的隱私蚓哩,Google將權限分為兩類:正常權限(Normal Permissions)和危險權限(Dangerous Permission)
2.1、正常權限
這類權限一般不涉及用戶隱私喜颁,是不需要用戶進行授權的曹阔,只要app在其清單中列出了正常權限,系統(tǒng)將自動授予該權限赃份,比如設置時區(qū)芥炭,手機震動和訪問網(wǎng)絡等。
ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
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_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
2.2渺蒿、危險權限
這類權限一般是涉及到用戶隱私的彪薛,需要用戶進行授權善延,比如讀取sdcard、訪問通訊錄和短信等易遣。
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.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:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
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
我們可以通過adb shell pm list permissions -d -g
進行查看
在這里我們會發(fā)現(xiàn)危險權限都是分組存在的侨歉,其分組的權限機制為:當你申請某個危險的權限時揩魂,假設你的app早已被用戶授權了同一權限組中的某個危險權限時,那么系統(tǒng)會立即授權牵舵,不需要用戶再授權。比如你的app之前已經(jīng)請求并被授予了READ_CONTACTS權限担巩,然后當它請求WRITE_CONTACTS權限時没炒,系統(tǒng)會立即授予該權限,而不會向用戶顯示權限對話框祖很。
三漾脂、代碼的實現(xiàn)
按照慣例,我們先來看看效果圖
從效果圖中可以看到笨鸡,當頁面啟動時形耗,向用戶顯示要請求的權限對話框辙浑,分別是讀取外置存儲的權限、訪問攝像頭倦踢、讀取通訊錄和讀取短信侠草。當授權完成后,在onRequestPermissionsResult方法回調(diào)中晤碘,檢查如果沒有全部授予權限時功蜓,并提示目前缺少權限,要不要到其app設置頁面授權等腮介。在這里當app需要我們授予不恰當?shù)臋嘞薜臅r候端衰,我們也可以選擇拒絕授予這些權限。已授予過的權限灭抑,我們也可以到app設置頁面去關閉授權抵代。
好了,下面我們來看看具體的實現(xiàn)吧
首先案腺,必須在應用程序清單中通過 <uses-permission>來聲明所需的權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
然后就是完整的代碼實現(xiàn)啦康吵,代碼都有注釋晦嵌,在這里就不一一解釋啦
package per.lijuan.permissiondome
import android.Manifest
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import java.util.*
class MainActivity : AppCompatActivity() {
companion object {
private const val KEY_PERMISSIONS_REQUEST_COUNT = "KEY_PERMISSIONS_REQUEST_COUNT"
private const val MAX_NUMBER_REQUEST_PERMISSIONS = 1 //請求權限的最多次數(shù)
private const val REQUEST_CODE_PERMISSIONS = 101
}
private var mPermissionRequestCount: Int = 0
// 應用程序需要的權限列表
private val sPermissions = object : ArrayList<String>() {
init {
add(Manifest.permission.READ_EXTERNAL_STORAGE)//讀取外置存儲的權限
add(Manifest.permission.CAMERA)//訪問攝像頭
add(Manifest.permission.READ_CONTACTS)//讀取通訊錄
add(Manifest.permission.READ_SMS)//讀取短信
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null) {
mPermissionRequestCount = savedInstanceState.getInt(KEY_PERMISSIONS_REQUEST_COUNT, 0)
}
requestPermissionsIfNecessary()
}
/**
* 檢查是否授予權限
* 如果全部獲取, 則直接過.
* 如果權限缺失, 則提示Dialog
*
* @param requestCode 請求碼
* @param permissions 權限
* @param grantResults 結(jié)果
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS && !checkAllPermissions())
showMissingPermissionDialog()
}
/**
* 檢查目前應用是否有所有我們需要的權限
* 如果全部獲取, 則直接過.
* 如果權限缺失, 就判斷當前請求權限的次數(shù)是否已達到最大值旱函,如果沒有則再次請求權限描滔,否則就提示Dialog
*/
private fun requestPermissionsIfNecessary() {
if (!checkAllPermissions()) {
if (mPermissionRequestCount < MAX_NUMBER_REQUEST_PERMISSIONS) {
mPermissionRequestCount += 1
ActivityCompat.requestPermissions(
this,
sPermissions.toTypedArray(),
REQUEST_CODE_PERMISSIONS)
} else {
Toast.makeText(this,"缺失權限",Toast.LENGTH_LONG).show()
}
}
}
/**
* 檢查應用目前是否有這些權限
*/
private fun checkAllPermissions(): Boolean {
var hasPermissions = true
for (permission in sPermissions) {
hasPermissions = hasPermissions and (ContextCompat.checkSelfPermission(
this, permission) == PackageManager.PERMISSION_GRANTED)
}
return hasPermissions
}
/**
* 顯示缺失權限提示
*/
private fun showMissingPermissionDialog() {
val builder = AlertDialog.Builder(this)
builder.setTitle("幫助")
builder.setMessage("當前應用缺少必要權限含长。\n \n 請點擊 \"設置\"-\"權限\"-打開所需權限。")
builder.setNegativeButton("取消", DialogInterface.OnClickListener(){ _, _ ->
})
builder.setPositiveButton("設置", DialogInterface.OnClickListener { _, _ ->
// 跳轉(zhuǎn)到當前應用對應的設置頁面
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:" + this.packageName)
startActivity(intent)
})
builder.setCancelable(false)
builder.show()
}
}
這代碼還有待進一步完善颅眶,因為在的權限申請中田弥,我們看到它依賴于Activity或者Fragment的回調(diào)方法。但我覺得代碼有待封裝商叹,可以采用單獨的Activity來申請權限只泼,通過回調(diào)的方式通知業(yè)務層,這樣會方便點弥咪。
本篇文章大概就這么多,有什么疑問的酷勺,請在下面留言扳躬,有不足之處還望指導,感謝各位_