再次封裝權限動態申請工具類

一、寫在前面

關於Android6.0的動態權限申請的博客已經多到沒辦法再多了,我爲什麼要再次封裝呢?因爲那些博客給的都是一些零零散散的代碼,或者一個完整的庫,不容易修改。我這裏會把工具類用kotlin寫成一個文件,只要複製粘貼過去你項目就能用了,不行的話再做小修改就行。

二、使用方法

先上使用方法,後面再貼代碼,和解釋。

已Camera爲例,先在Manifest裏面聲明。

<uses-permission android:name="android.permission.CAMERA" />
檢查某權限是否已經允許
val has = hasPermissions(this, arrayOf(Manifest.permission.CAMERA))
ToastUtils.showToast(has.toString())
申請某個權限列表
runOnPermissions(this, arrayOf(Manifest.permission.CAMERA), object : OnPermissionCallback {
    override fun onAllGranted() {
        // 所有權限都允許後回調。
        Toast.makeText(this@MainActivity, "onAllGranted", Toast.LENGTH_SHORT).show()
    }

    override fun onAllDenied() {
        // 只有傳入的權限列表中有一個拒絕了,都會回調。
        Toast.makeText(this@MainActivity, "onAllDenied", Toast.LENGTH_SHORT).show()
    }

    override fun onShowRationale() {
        // 拒絕後再次申請,彈框提示用戶爲什麼需要這個權限
        Toast.makeText(this@MainActivity, "onShowRationale", Toast.LENGTH_SHORT).show()
    }

    override fun onNeverAskAgain() {
        // 用戶選擇不再詢問的時候回調
        Toast.makeText(this@MainActivity, "onNeverAskAgain", Toast.LENGTH_SHORT).show()
    }
})
必須允許某個權限才能使用某功能
// 判斷某些權限是否已經允許,未允許則申請。
// 用戶拒絕後會彈框提示用戶必須允許,否則finish。
runOnPermissionsOrFinish(this, arrayOf(Manifest.permission.CAMERA), {
    Toast.makeText(this, "onAllGranted", Toast.LENGTH_SHORT).show()
})
檢查類似小米那樣獨有的權限是否已經允許
// 檢查小米NFC權限是否已經打開
val has = hasOpPermission(this, 10016)
Toast.makeText(this, has.toString(), Toast.LENGTH_SHORT).show()

三、源碼、註釋

package com.audienl.superlibrary.utils

import android.app.Activity
import android.app.AppOpsManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Process
import android.util.SparseArray
import androidx.appcompat.app.AlertDialog
import androidx.collection.SimpleArrayMap
import androidx.core.app.ActivityCompat
import androidx.core.content.PermissionChecker
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import java.lang.Exception

private const val TAG = "PermissionHelper"

// ------------------- 公開方法 START -------------------

// 注意:某些手機在onCreate的時候請求權限,同時又跳轉到下一個頁面,
// 下一個頁面會蓋住權限對話框,看起來就想沒彈出,實際上只是蓋住了。

/**
 * @param permissions e.g. `Manifest.permission.CAMERA`
 * @return true if all granted, false otherwise.
 */
fun hasPermissions(activity: Activity, permissions: Array<String>): Boolean {
    for (permission in permissions) {
        checkPermissionIfInManifest(activity, permission)

        if (isExistsInSdk(permission) && !hasSelfPermission(activity, permission)) {
            return false
        }
    }
    return true
}

/**
 * 判斷某些權限是否已經允許,未允許則申請。
 * 用戶拒絕後會彈框提示用戶必須允許,否則finish。
 */
fun runOnPermissionsOrFinish(
        activity: FragmentActivity,
        permissions: Array<String>,
        onGranted: () -> Unit,
        message: String = "必須允許權限才能使用",
        positiveButtonText: String = "重新申請"
) {
    runOnPermissions(activity, permissions, object : OnPermissionCallback {
        override fun onAllGranted() {
            onGranted.invoke()
        }

        override fun onAllDenied() {
            AlertDialog.Builder(activity)
                    .setMessage(message)
                    .setPositiveButton(positiveButtonText) { _, _ ->
                        runOnPermissions(activity, permissions, this)
                    }
                    .setCancelable(false)
                    .show()
        }

        override fun onShowRationale() {
            runOnPermissions(activity, permissions, this, false)
        }

        override fun onNeverAskAgain() {
            AlertDialog.Builder(activity)
                    .setMessage(message)
                    .setPositiveButton("知道了") { _, _ ->
                        activity.finish()
                    }
                    .setCancelable(false)
                    .show()
        }
    })
}

/**
 * 判斷某些權限是否已經允許,未允許則申請,並回調結果到[callback]
 * @param checkRationale 是否判斷[shouldShowRequestPermissionRationale]方法
 */
fun runOnPermissions(
        activity: FragmentActivity,
        permissions: Array<String>,
        callback: OnPermissionCallback,
        checkRationale: Boolean = true
) {

    if (hasPermissions(activity, permissions)) {
        callback.onAllGranted()
        return
    }

    // 拒絕後再次申請,彈框提示用戶爲什麼需要這個權限
    if (checkRationale && shouldShowRequestPermissionRationale(activity, permissions)) {
        callback.onShowRationale()
        return
    }

    var fragment: Fragment? = activity.supportFragmentManager.findFragmentByTag(TAG)
    if (fragment == null) {
        fragment = PermissionFragment()
        val fragmentManager = activity.supportFragmentManager
        fragmentManager.beginTransaction().add(fragment, TAG).commitAllowingStateLoss()
        fragmentManager.executePendingTransactions()
    }

    mOnPermissionCallbacks.put(mRequestCode, callback)
    fragment.requestPermissions(permissions, mRequestCode)
    mRequestCode++
}

/**
 * 檢查類似小米那樣獨有的權限是否已經允許。
 * 比如:NFC、後臺彈出界面等非官方權限。
 *
 * @param op 取值如下:
 * * op=10016 對應 NFC
 * * op=10021 對應 後臺彈出界面
 * * 其它未知,根據博客的方法自己去找你需要的
 *
 * @return true爲允許,false爲詢問或者拒絕。
 */
fun hasOpPermission(context: Context, op: Int): Boolean {
    return try {
        val manager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
        val method = manager.javaClass.getMethod("checkOpNoThrow", Int::class.java, Int::class.java, String::class.java)
        val result = method.invoke(manager, op, Process.myUid(), context.packageName)
        AppOpsManager.MODE_ALLOWED == result
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

// ------------------- 公開方法 END ---------------------

// 某些新版本新增的權限,舊版本的SDK中沒有。
private var MIN_SDK_PERMISSIONS = SimpleArrayMap<String, Int>(8).apply {
    put("com.android.voicemail.permission.ADD_VOICEMAIL", 14)
    put("android.permission.BODY_SENSORS", 20)
    put("android.permission.READ_CALL_LOG", 16)
    put("android.permission.READ_EXTERNAL_STORAGE", 16)
    put("android.permission.USE_SIP", 9)
    put("android.permission.WRITE_CALL_LOG", 16)
    put("android.permission.SYSTEM_ALERT_WINDOW", 23)
    put("android.permission.WRITE_SETTINGS", 23)
}

// manifest中聲明的權限
private var permissionsInManifest: Array<String>? = null

// 讀取manifest中聲明的權限
private fun checkPermissionIfInManifest(activity: Activity, permission: String) {
    if (permissionsInManifest == null) {
        permissionsInManifest = try {
            val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, PackageManager.GET_PERMISSIONS)
            packageInfo.requestedPermissions
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
            arrayOf()
        }
    }

    // 檢查manifest中是否聲明瞭此權限,未聲明直接拋出異常
    val declare = permissionsInManifest!!.contains(permission)
    require(declare) { "the permission $permission is not registered in AndroidManifest.xml" }
}

// 當前安卓SDK是否存在此權限
private fun isExistsInSdk(permission: String): Boolean {
    val minSdkVersion = MIN_SDK_PERMISSIONS.get(permission)
    if (minSdkVersion != null && Build.VERSION.SDK_INT < minSdkVersion) {
        return false
    }
    return true
}

// 檢查權限
private fun hasSelfPermission(context: Context, permission: String): Boolean {
    return try {
        PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED
    } catch (e: Exception) {
        return false
    }
}

// 用戶拒絕後再次申請,是否需要提示用戶
private fun shouldShowRequestPermissionRationale(activity: Activity?, permissions: Array<out String>): Boolean {
    try {
        permissions.forEach {
            if (ActivityCompat.shouldShowRequestPermissionRationale(activity!!, it)) {
                return true
            }
        }
    } catch (e: Exception) {
        // nothing
    }
    return false
}

interface OnPermissionCallback {
    /**
     * 所有權限都允許後回調。
     */
    fun onAllGranted()

    /**
     * 只有傳入的權限列表中有一個拒絕了,都會回調。
     */
    fun onAllDenied()

    /**
     * 拒絕後再次申請,彈框提示用戶爲什麼需要這個權限
     */
    fun onShowRationale()

    /**
     * 用戶選擇不再詢問的時候回調。
     */
    fun onNeverAskAgain()
}

private var mRequestCode = 1
private val mOnPermissionCallbacks by lazy { SparseArray<OnPermissionCallback>() }// requestCode對應的callback

// must be a public static class to be  properly recreated from instance state.
class PermissionFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retainInstance = true
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        mOnPermissionCallbacks.get(requestCode)?.let { callback ->
            var allGranted = true

            // 在Activity A申請權限,然後馬上跳轉到Activity B,則grantResults.length=0
            if (grantResults.isEmpty()) allGranted = false

            // 有一個不通過,都判斷爲不通過
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false
                }
            }

            if (allGranted) {
                callback.onAllGranted()
            } else {
                // 如果沒有一個shouldShowRequestPermissionRationale,則判定爲用戶選擇了不再詢問
                if (!shouldShowRequestPermissionRationale(activity, permissions)) {
                    callback.onNeverAskAgain()
                } else {
                    callback.onAllDenied()
                }
            }

            mOnPermissionCallbacks.delete(requestCode)
        }
    }
}

四、寫在後面

源碼上傳到githut了,也可以直接在gradle裏配置引用吧,鏈接:https://github.com/audientlin/PermissionHelper

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