Android M 危险权限申请流程

Android M 危险权限申请流程

危险权限基本都涉及到用户的隐私,诸如拍照、读取短信、写存储、录音等,Android 系统将这些危险权限分为 9 组,获取分组中某个权限的同时也就获取了同组中的其他权限,危险权限不仅需要在 AndroidManifest.xml 中注册,还需要动态地申请权限

Permission Group Permissions
CALENDAR READ_CALENDAR
WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATIO
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

需要动态申请权限的三个条件,缺一不可

  1. 危险权限
  2. Android 版本 >= 6.0
  3. targetSdkVersion >= 23

SDK 提供的 API

为方便开发者实现权限管理,Google 提供了 4 个 API:

API 作用
checkSelfPermission() 判断权限是否具有某项权限
requestPermissions() 申请权限
onRequestPermissionsResult() 申请权限回调方法
shouldShowRequestPermissionRationale() 是否要提示用户申请该权限的缘由

判断权限是否已申请

// ActivityCompat.java
public static int checkSelfPermission(
    @NonNull Context context, 
    @NonNull String permission)

请求权限

// ActivityCompat.java
public static void requestPermissions(
    final @NonNull Activity activity, 
    final @NonNull String[] permissions, // 需要申请的权限数组
    final @IntRange(from = 0) int requestCode) // from 0x0000 to 0xffff

是否需要提示用户申请权限的理由

// ActivityCompat.java
public static boolean shouldShowRequestPermissionRationale(
    @NonNull Activity activity, 
    @NonNull String permission)
序号 是否授予了权限 ssrpr()返回 是否勾选不再询问 说明
1 false - 首次请求权限,不提示用户理由
2 true 需要提示用户理由
3 true 需要提示用户理由
需要提示用户理由
i true 需要提示用户理由
i + 1 - false - 用户勾选了不再提示,不再需要提示理由

可以看出只有两种情况shouldShowRequestPermissionRationale返回了 false

权限申请结果回调

// FragmentActivity.java
@Override
public void onRequestPermissionsResult(
    int requestCode, 
    @NonNull String[] permissions, // 权限数组 
    @NonNull int[] grantResults) // 结果数组

权限申请流程

权限申请流程

权限动态申请库的封装,使用 KOTLIN 语言

定义接口代理

interface RequestPermissionsDelegate {

    fun onRequestPermissionsResult(
        permissions: Array<out String>, 
        grantResults: IntArray)
}

BaseActivity

abstract class MPsActivity : AppCompatActivity() {

    private val mRequestPermissionsDelegateMap : MutableMap<Int, RequestPermissionsDelegate> = HashMap()

    fun addRequestPermissionsDelegate(requestCode: Int, requestPermissionsDelegate: RequestPermissionsDelegate) {
        mRequestPermissionsDelegateMap.put(requestCode, requestPermissionsDelegate)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        val requestPermissionsDelegate : RequestPermissionsDelegate ?= mRequestPermissionsDelegateMap[requestCode]
        requestPermissionsDelegate?.onRequestPermissionsResult(permissions, grantResults)
        mRequestPermissionsDelegateMap.remove(requestCode)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            REQUEST_CODE_SETTING -> {
                onSettingReturn()
            }
        }
    }

    /**
     * 从设置页返回
     */
    abstract fun onSettingReturn()
}

使用一个 MAP 管理所有的权限回调结果

定义九组权限的 RequestCode

val PERMISSION_REQUEST_CODE_CALENDAR = 0x0001
val PERMISSION_REQUEST_CODE_CAMERA = 0x0002
val PERMISSION_REQUEST_CODE_CONTACTS = 0x0003
val PERMISSION_REQUEST_CODE_LOCATION = 0x0004
val PERMISSION_REQUEST_CODE_MICROPHONE = 0x0005
val PERMISSION_REQUEST_CODE_PHONE = 0x0006
val PERMISSION_REQUEST_CODE_SENSORS = 0x0007
val PERMISSION_REQUEST_CODE_SMS = 0x0008
val PERMISSION_REQUEST_CODE_STORAGE = 0x0009

Abstract Helper

abstract class Helper {

    interface Listener {
        fun onPermissionDenied()
        fun onPermissionGranted()
    }

    private fun checkSelfPermission(context: Context): Boolean {
        val result = ActivityCompat.checkSelfPermission(context, permission())
        return PackageManager.PERMISSION_GRANTED == result
    }

    private fun requestPermission(activity: MPsActivity, packageName: String, listener: Listener) {
        activity.addRequestPermissionsDelegate(requestCode(), object : RequestPermissionsDelegate {
            override fun onRequestPermissionsResult(permissions: Array<out String>, grantResults: IntArray) {
                if (grantResults.isEmpty() || PackageManager.PERMISSION_GRANTED != grantResults[0]) {
                    if (!shouldShowRequestPermissionRationale(activity)) {
                        // 说明用户勾选了不再提示
                        showMustGrantDialog(activity, packageName, listener)
                    } else {
                        // 首次拒绝或未勾选不再提示的拒绝
                        listener.onPermissionDenied()
                    }
                } else {
                    listener.onPermissionGranted()
                }
            }
        })

        ActivityCompat.requestPermissions(activity, arrayOf(permission()), requestCode())
    }

    private fun shouldShowRequestPermissionRationale(activity: Activity): Boolean {
        return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission())
    }

    /**
     * 跳转系统设置
     */
    private fun goSettings(activity: MPsActivity, packageName: String) {
        val localIntent = Intent()
        // localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        localIntent.action = "android.settings.APPLICATION_DETAILS_SETTINGS"
        localIntent.data = Uri.fromParts("package", packageName, null)
        activity.startActivityForResult(localIntent, REQUEST_CODE_SETTING)
    }

    private fun showMustGrantDialog(activity: MPsActivity, packageName: String, listener: Listener) {

        val builder: AlertDialog.Builder = AlertDialog.Builder(activity)
        builder.setTitle("APP 怒了")
        builder.setMessage(permissionRequiredHint())
        builder.setNegativeButton("残忍拒绝") { dialog, which ->
            listener.onPermissionDenied()
            dialog.dismiss()
        }
        builder.setPositiveButton("欣然同意") { dialog, which ->
            goSettings(activity, packageName)
            dialog.dismiss()
        }
        builder.create().show()
    }

    fun requestPermissionIfNeed(activity: MPsActivity, packageName: String, listener: Listener) {
        if (!checkSelfPermission(activity)) {
            requestPermission(activity, packageName, listener)
        } else {
            listener.onPermissionGranted()
        }
    }

    /**
     * @see Manifest.permission.READ_CALENDAR
     * @see Manifest.permission.WRITE_CALENDAR
     *
     * @see Manifest.permission.CAMERA
     *
     * @see Manifest.permission.READ_CONTACTS
     * @see Manifest.permission.WRITE_CONTACTS
     * @see Manifest.permission.GET_ACCOUNTS
     *
     * @see Manifest.permission.ACCESS_FINE_LOCATION
     * @see Manifest.permission.ACCESS_COARSE_LOCATION
     *
     * @see Manifest.permission.RECORD_AUDIO
     *
     * @see Manifest.permission.READ_PHONE_STATE
     * @see Manifest.permission.CALL_PHONE
     * @see Manifest.permission.READ_CALL_LOG
     * @see Manifest.permission.WRITE_CALL_LOG
     * @see Manifest.permission.ADD_VOICEMAIL
     * @see Manifest.permission.USE_SIP
     * @see Manifest.permission.PROCESS_OUTGOING_CALLS
     *
     * @see Manifest.permission.BODY_SENSORS
     *
     * @see Manifest.permission.SEND_SMS
     * @see Manifest.permission.RECEIVE_SMS
     * @see Manifest.permission.READ_SMS
     * @see Manifest.permission.RECEIVE_WAP_PUSH
     * @see Manifest.permission.RECEIVE_MMS
     *
     * @see Manifest.permission.WRITE_EXTERNAL_STORAGE
     * @see Manifest.permission.READ_EXTERNAL_STORAGE
     */
    protected abstract fun permission(): String

    /**
     * @see PERMISSION_REQUEST_CODE_CALENDAR
     * @see PERMISSION_REQUEST_CODE_CAMERA
     * @see PERMISSION_REQUEST_CODE_CONTACTS
     * @see PERMISSION_REQUEST_CODE_LOCATION
     * @see PERMISSION_REQUEST_CODE_MICROPHONE
     * @see PERMISSION_REQUEST_CODE_PHONE
     * @see PERMISSION_REQUEST_CODE_SENSORS
     * @see PERMISSION_REQUEST_CODE_SMS
     * @see PERMISSION_REQUEST_CODE_STORAGE
     */
    protected abstract fun requestCode(): Int

    @StringRes
    protected abstract fun permissionRequiredHint(): Int
}
helper

如何使用

class RequestPermissionActivity : MPsActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        checkPermissions()
    }

    override fun onSettingReturn() {
        checkPermissions()
    }

    private fun checkPermissions() {
        StoragePermissionHelper.requestPermissionIfNeed(this, packageName, object : Helper.Listener {
            override fun onPermissionDenied() {
                toast("拒绝存储")
            }

            override fun onPermissionGranted() {
                toast("同意存储")
            }
        })
    }
}

源码

MPermissions

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容