6.0以上动态权限
将系统权限区分为正常权限和危险权限。开发者在使用到危险权限相关的功能时,不仅需要在Manifest文件中配置,还需要在代码中动态获取权限
需要注意的几个权限
- 6.0以上需要检测悬浮窗权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
/**
* 是否有悬浮窗权限
*/
static boolean isHasOverlaysPermission(Context context) {
if (isOverMarshmallow()) {
return Settings.canDrawOverlays(context);
}
return true;
}
/**
* 用于6.0悬浮窗权限请求
*/
// 跳转到允许安装未知来源设置页面
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getActivity().getPackageName()));
startActivityForResult(intent, getArguments().getInt(REQUEST_CODE));
- 8.0以上需要检测以下权限
//未知应用安装权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
/**
* 是否有安装权限
*/
static boolean isHasInstallPermission(Context context) {
if (isOverOreo()) {
return context.getPackageManager().canRequestPackageInstalls();
}
return true;
}
/**
* 用于8.0安装第三方应用权限请求
*/
// 跳转到悬浮窗设置页面
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getActivity().getPackageName()));
startActivityForResult(intent, getArguments().getInt(REQUEST_CODE));
//允许您的应用通过编程方式接听呼入电话。要在您的应用中处理呼入电话,您可以使用 acceptRingingCall() 函数。
ANSWER_PHONE_CALLS
//允许您的应用读取设备中存储的电话号码。
READ_PHONE_NUMBERS
代码
/**
* justin
* 危险权限请求类
*/
class JPermissions {
private var mActivity:Activity
private var mPermissions:MutableList<String> = ArrayList()
private var mRequestAgain: Boolean = false
private constructor(activity:Activity){
mActivity = activity
}
companion object {
/**
* 设置请求的对象
*/
fun with(activity: Activity): JPermissions {
return JPermissions(activity)
}
/**
* 检查某些权限是否全部授予了
*
* @param context 上下文对象
* @param permissions 需要请求的权限组
*/
fun isHasPermission(context: Context, vararg permissions: String): Boolean {
val failPermissions = PermissionUtils.getDeniedPermissions(context, Arrays.asList(*permissions))
return failPermissions == null || failPermissions.isEmpty()
}
/**
* 跳转到应用权限设置页面
*
* @param context 上下文对象
*/
fun gotoPermissionSettings(context: Context) {
PermissionSettingPage.start(context, false)
}
/**
* 跳转到应用权限设置页面
*
* @param context 上下文对象
* @param newTask 是否使用新的任务栈启动
*/
fun gotoPermissionSettings(context: Context, newTask: Boolean) {
PermissionSettingPage.start(context, newTask)
}
}
/**
* 设置需要请求的权限
*/
fun permissions(vararg permissions: String):JPermissions{
mPermissions.addAll(permissions)
return this
}
/**
* 被拒绝后继续申请,直到授权或者永久拒绝
*/
fun requestAgain(): JPermissions {
mRequestAgain = true
return this
}
/**
* 请求权限
*/
fun requestPermissions(permissionResult:OnPermissionsResult) {
// 如果没有指定请求的权限,就使用清单注册的权限进行请求
if (mPermissions.isEmpty()) mPermissions = PermissionUtils.getManifestPermissions(mActivity)
if (mPermissions.isEmpty()) throw IllegalArgumentException("The requested permission cannot be empty")
if (mActivity == null) throw IllegalArgumentException("The activity is empty")
if (permissionResult == null) throw IllegalArgumentException("The permission request callback interface must be implemented")
val checkTargetSdkVersion = PermissionUtils.checkTargetSdkVersion(mActivity,mPermissions)
if (!checkTargetSdkVersion) return
val deniedPermissions = PermissionUtils.getDeniedPermissions(mActivity, mPermissions)
//权限已经全部授予
if (deniedPermissions.isEmpty()){
permissionResult.agreePermission(mPermissions,true)
}else{
// 检测权限有没有在清单文件中注册
PermissionUtils.checkPermissionsIsInManifest(mActivity, mPermissions)
// 申请没有授予过的权限
PermissionFragment.newInstance(ArrayList(mPermissions), mRequestAgain).prepareRequest(mActivity, permissionResult)
}
}
}
/**
* author:justin
* time:2019/04/22
* desc:权限请求处理类
*/
class PermissionFragment : Fragment, Runnable {
constructor() : super()
companion object {
private val PERMISSIONS: String = "permissions" // 请求的权限
private val REQUEST_CODE: String = "request_code" // 请求码(自动生成)
private val REQUEST_AGAIN: String = "request_again" // 是否不断请求
private val sContainer: SparseArray<OnPermissionsResult> = SparseArray()
fun newInstance(permissions: ArrayList<String>, requestAgain: Boolean): PermissionFragment {
val fragment = PermissionFragment()
val bundle = Bundle()
var requestCode: Int
// 请求码随机生成,避免随机产生之前的请求码,必须进行循环判断
do {
// requestCode = new Random().nextInt(65535); // Studio编译的APK请求码必须小于65536
requestCode = Random().nextInt(255) // Eclipse编译的APK请求码必须小于256
} while (sContainer.get(requestCode) != null)
bundle.putInt(REQUEST_CODE, requestCode)
bundle.putStringArrayList(PERMISSIONS, permissions)
bundle.putBoolean(REQUEST_AGAIN, requestAgain)
fragment.arguments = bundle
return fragment
}
}
/**
* 用于6.0悬浮窗 8.0安装第三方应用权限请求
*/
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val permissions = arguments!!.getStringArrayList(PERMISSIONS)
if (permissions == null || permissions.isEmpty()) return
if (permissions.contains(Manifest.permission.SYSTEM_ALERT_WINDOW) && !PermissionUtils.isHasOverlaysPermission(getActivity())
||permissions.contains(Manifest.permission.REQUEST_INSTALL_PACKAGES) && !PermissionUtils.isHasInstallPermission(getActivity())) {
if (permissions.contains(Manifest.permission.SYSTEM_ALERT_WINDOW) && !PermissionUtils.isHasOverlaysPermission(getActivity())) {
// 跳转到悬浮窗设置页面
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity!!.getPackageName()))
startActivityForResult(intent, arguments!!.getInt(REQUEST_CODE))
}
if (permissions.contains(Manifest.permission.REQUEST_INSTALL_PACKAGES) && !PermissionUtils.isHasInstallPermission(getActivity())) {
// 跳转到允许安装未知来源设置页面
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + activity!!.getPackageName()))
startActivityForResult(intent, arguments!!.getInt(REQUEST_CODE))
}
return
}
requestPermission()
}
/**
* 准备请求
*/
fun prepareRequest(activity: Activity, call: OnPermissionsResult) {
// 将当前的请求码和对象添加到集合中
sContainer.put(arguments!!.getInt(REQUEST_CODE), call)
activity.fragmentManager.beginTransaction().add(this, activity.javaClass.name).commit()
}
/**
* 请求权限
*/
fun requestPermission() {
if (PermissionUtils.isOverMarshmallow()) {
val permissions = arguments!!.getStringArrayList(PERMISSIONS)
requestPermissions(permissions!!.toTypedArray(), arguments!!.getInt(REQUEST_CODE))
}
}
/**
* 权限请求结果回调(除过6.0悬浮窗 8.0安装第三方应用回调)
*/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
val onPermissionsResult = sContainer.get(requestCode)
if (onPermissionsResult == null) return
for (i in permissions.indices) {
// 重新检查悬浮窗权限
if (Permission.SYSTEM_ALERT_WINDOW == permissions[i]) {
if (PermissionUtils.isHasOverlaysPermission(activity)) {
grantResults[i] = PackageManager.PERMISSION_GRANTED
} else {
grantResults[i] = PackageManager.PERMISSION_DENIED
}
}
// 重新检查安装权限
if (Permission.REQUEST_INSTALL_PACKAGES == permissions[i]) {
if (PermissionUtils.isHasInstallPermission(activity)) {
grantResults[i] = PackageManager.PERMISSION_GRANTED
} else {
grantResults[i] = PackageManager.PERMISSION_DENIED
}
}
// 重新检查8.0的两个新权限
if (permissions[i] == Permission.ANSWER_PHONE_CALLS || permissions[i] == Permission.READ_PHONE_NUMBERS) {
// 检查当前的安卓版本是否符合要求
if (!PermissionUtils.isOverOreo()) {
grantResults[i] = PackageManager.PERMISSION_GRANTED
}
}
}
val deniedPermissions = PermissionUtils.getDeniedPermissions(activity as Context, permissions.asList())
if (deniedPermissions.isEmpty()) {
onPermissionsResult.agreePermission(permissions.toList(), true)
} else {
// 检查是否开启了继续申请模式,如果是则检查没有授予的权限是否还能继续申请
if (arguments!!.getBoolean(REQUEST_AGAIN) && PermissionUtils.isRequestDeniedPermission(activity as Activity, deniedPermissions)) {
// 如果有的话就继续申请权限,直到用户授权或者永久拒绝
requestPermission()
return
}
// 代表申请的权限中有不同意授予的,如果有某个权限被永久拒绝就返回true给开发人员,让开发者引导用户去设置界面开启权限
onPermissionsResult.disagreePermission(deniedPermissions, PermissionUtils.checkMorePermissionPermanentDenied(activity as Activity, deniedPermissions))
// 证明还有一部分权限被成功授予,回调成功接口
if (permissions.size>deniedPermissions.size){
val succeePermissions = permissions.toMutableList()
val succeed = succeePermissions.removeAll(deniedPermissions)
if (succeed) {
if (!succeePermissions.isEmpty()) {
onPermissionsResult.agreePermission(succeePermissions, false)
}
}
}
}
// 权限回调结束后要删除集合中的对象,避免重复请求
sContainer.remove(requestCode)
fragmentManager!!.beginTransaction().remove(this).commit()
}
private var isBackCall: Boolean = false // 是否已经回调了,避免安装权限和悬浮窗同时请求导致的重复回调
/**
* 6.0悬浮窗 8.0安装第三方应用回调
* 需要注意第三个Intent参数,kotlin的NULL检查机制,导致不写?一直回调不成功(坑)
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// super.onActivityResult(requestCode, resultCode, data);
if (!isBackCall && requestCode == arguments!!.getInt(REQUEST_CODE)) {
isBackCall = true
// 需要延迟执行,不然有些华为机型授权了但是获取不到权限
Handler(Looper.getMainLooper()).postDelayed(this, 500)
}
}
/**
* [Runnable.run]
*/
override fun run() {
// 请求其他危险权限
requestPermission()
}
}
PermissionFragment通过名字也能看出,这里采用了Fragment来处理请求回调,Fragment的妙用有很多,比如解决自定义view绑定生命周期的问题(百度地图还需要用户自己去绑定生命周期,不友好啊...)
/**
* 权限请求工具类
*/
class PermissionUtils {
companion object {
/**
* 获取清单文件中的权限
*/
fun getManifestPermissions(context: Context): MutableList<String> {
return context.packageManager.getPackageInfo(context.packageName,
PackageManager.GET_PERMISSIONS).requestedPermissions.toMutableList()
}
/**
* 检查targetSdkVersion是否符合要求
*
* @param context 上下文对象
* @param requestPermissions 请求的权限组
*/
fun checkTargetSdkVersion(context: Context, requestPermissions: List<String>):Boolean {
if (requestPermissions.contains(Manifest.permission.REQUEST_INSTALL_PACKAGES)
|| requestPermissions.contains(Manifest.permission.ANSWER_PHONE_CALLS)
|| requestPermissions.contains(Manifest.permission.READ_PHONE_NUMBERS)) {
// 必须设置 targetSdkVersion >= 26 才能正常检测权限
if (context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
return true
}
}
if (context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
return true
}
return false
}
/**
* 获取未授权权限
*/
fun getDeniedPermissions(context: Context, requestPermissions: List<String>):MutableList<String>{
var deniedList:MutableList<String> = ArrayList()
for (permission in requestPermissions) {
//检测6.0悬浮窗权限
if (TextUtils.equals(permission,Manifest.permission.SYSTEM_ALERT_WINDOW)){
if (!isHasOverlaysPermission(context)) {
deniedList.add(permission)
continue
}
}
//检测8.0以上安装未知来源应用权限
if (TextUtils.equals(permission,Manifest.permission.REQUEST_INSTALL_PACKAGES)){
if (!isHasInstallPermission(context)){
deniedList.add(permission)
continue
}
}
//检测8.0的两个新权限
if (TextUtils.equals(permission,Manifest.permission.ANSWER_PHONE_CALLS)
||TextUtils.equals(permission,Manifest.permission.READ_PHONE_NUMBERS)){
if (!isOverOreo()){
continue
}
}
// 把没有授予过的权限加入到集合中
if (ActivityCompat.checkSelfPermission(context,permission) == PackageManager.PERMISSION_DENIED) {
deniedList.add(permission)
}
}
return deniedList
}
/**
* 检测6.0悬浮窗权限
*/
fun isHasOverlaysPermission(context: Context): Boolean {
if (isOverMarshmallow()){
return Settings.canDrawOverlays(context)
}
return true
}
/**
* 检测8.0以上安装未知来源应用权限
*/
fun isHasInstallPermission(context: Context): Boolean {
if (isOverOreo()){
return context!!.packageManager.canRequestPackageInstalls()
}
return true
}
/**
* 是否是6.0以上版本
*/
fun isOverMarshmallow(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
}
/**
* 是否是8.0以上版本
*/
fun isOverOreo(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
}
/**
* 检测权限是否在清单文件中注册
*/
fun checkPermissionsIsInManifest(context: Context,requestPermissions: List<String>){
val manifestPermissions = getManifestPermissions(context)
if (manifestPermissions != null&&!manifestPermissions.isEmpty()){
for (permission in requestPermissions) {
if (!manifestPermissions.contains(permission)){
throw RuntimeException(permission + ": Permissions are not registered in the manifest file")
}
}
}else{
throw RuntimeException("No permissions are registered in the manifest file")
}
}
/**
* 在权限组中检查是否有某个权限是否被永久拒绝
*
* @param activity Activity对象
* @param permissions 请求的权限
*/
fun checkMorePermissionPermanentDenied(activity: Activity, permissions: List<String>): Boolean {
for (permission in permissions) {
// 安装权限和浮窗权限不算,本身申请方式和危险权限申请方式不同,因为没有永久拒绝的选项,所以这里返回false
if (permission == Permission.REQUEST_INSTALL_PACKAGES || permission == Permission.SYSTEM_ALERT_WINDOW) {
continue
}
if (checkSinglePermissionPermanentDenied(activity, permission)) {
return true
}
}
return false
}
/**
* 检查某个权限是否被永久拒绝
*
* @param activity Activity对象
* @param permission 请求的权限
*/
fun checkSinglePermissionPermanentDenied(activity: Activity, permission: String): Boolean {
// // 安装权限和浮窗权限不算,本身申请方式和危险权限申请方式不同,因为没有永久拒绝的选项,所以这里返回false
// if (permission.equals(Permission.REQUEST_INSTALL_PACKAGES) || permission.equals(Permission.SYSTEM_ALERT_WINDOW)) {
// return false;
// }
// 检测8.0的两个新权限
if (permission == Permission.ANSWER_PHONE_CALLS || permission == Permission.READ_PHONE_NUMBERS) {
// 检查当前的安卓版本是否符合要求
if (!isOverOreo()) {
return false
}
}
if (PermissionUtils.isOverMarshmallow()) {
if (activity.checkSelfPermission(permission) == PackageManager.PERMISSION_DENIED && !activity.shouldShowRequestPermissionRationale(permission)) {
return true
}
}
return false
}
/**
* 是否还能继续申请没有授予的权限
*
* @param activity Activity对象
* @param failPermissions 失败的权限
*/
fun isRequestDeniedPermission(activity: Activity, failPermissions: List<String>): Boolean {
for (permission in failPermissions) {
// 安装权限和浮窗权限不算,本身申请方式和危险权限申请方式不同,因为没有永久拒绝的选项,所以这里返回false
if (permission == Permission.REQUEST_INSTALL_PACKAGES || permission == Permission.SYSTEM_ALERT_WINDOW) {
continue
}
// 检查是否还有权限还能继续申请的(这里指没有被授予的权限但是也没有被永久拒绝的)
if (!checkSinglePermissionPermanentDenied(activity, permission)) {
return true
}
}
return false
}
/**
* 获取已授予的权限
*
* @param permissions 需要请求的权限组
* @param grantResults 允许结果组
*/
fun getSucceedPermissions(permissions: Array<String>, grantResults: IntArray): List<String> {
val succeedPermissions = java.util.ArrayList<String>()
for (i in grantResults.indices) {
// 把授予过的权限加入到集合中,-1表示没有授予,0表示已经授予
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
succeedPermissions.add(permissions[i])
}
}
return succeedPermissions
}
}
}
/**
* author:justin
* time:2019/04/24
* desc: 权限设置页(兼容大部分国产手机)
*/
internal object PermissionSettingPage {
/**
* 手机制造商
*/
private val MARK = Build.MANUFACTURER.toLowerCase()
/**
* 跳转到应用权限设置页面
*
* @param context 上下文对象
* @param newTask 是否使用新的任务栈启动
*/
fun start(context: Context, newTask: Boolean) {
var intent: Intent? = null
if (MARK.contains("huawei")) {
intent = huawei(context)
} else if (MARK.contains("xiaomi")) {
intent = xiaomi(context)
} else if (MARK.contains("oppo")) {
intent = oppo(context)
} else if (MARK.contains("vivo")) {
intent = vivo(context)
} else if (MARK.contains("meizu")) {
intent = meizu(context)
}
if (intent == null || !hasIntent(context, intent)) {
intent = google(context)
}
if (newTask) {
//如果用户在权限设置页面改动了权限,个别手机请求权限的activity会被重启,解决该问题
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
try {
context.startActivity(intent)
} catch (ignored: Exception) {
intent = google(context)
context.startActivity(intent)
}
}
private fun google(context: Context): Intent {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", context.packageName, null)
return intent
}
private fun huawei(context: Context): Intent {
val intent = Intent()
intent.component = ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity")
if (hasIntent(context, intent)) return intent
intent.component = ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity")
if (hasIntent(context, intent)) return intent
intent.component = ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity")
return intent
}
private fun xiaomi(context: Context): Intent {
val intent = Intent("miui.intent.action.APP_PERM_EDITOR")
intent.putExtra("extra_pkgname", context.packageName)
if (hasIntent(context, intent)) return intent
intent.setPackage("com.miui.securitycenter")
if (hasIntent(context, intent)) return intent
intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity")
if (hasIntent(context, intent)) return intent
intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity")
return intent
}
private fun oppo(context: Context): Intent {
val intent = Intent()
intent.putExtra("packageName", context.packageName)
intent.setClassName("com.color.safecenter", "com.color.safecenter.permission.floatwindow.FloatWindowListActivity")
if (hasIntent(context, intent)) return intent
intent.setClassName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity")
if (hasIntent(context, intent)) return intent
intent.setClassName("com.oppo.safe", "com.oppo.safe.permission.PermissionAppListActivity")
return intent
}
private fun vivo(context: Context): Intent {
val intent = Intent()
intent.setClassName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.FloatWindowManager")
intent.putExtra("packagename", context.packageName)
if (hasIntent(context, intent)) return intent
intent.component = ComponentName("com.iqoo.secure", "com.iqoo.secure.safeguard.SoftPermissionDetailActivity")
return intent
}
private fun meizu(context: Context): Intent {
val intent = Intent("com.meizu.safe.security.SHOW_APPSEC")
intent.putExtra("packageName", context.packageName)
intent.component = ComponentName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity")
return intent
}
private fun hasIntent(context: Context, intent: Intent): Boolean {
return !context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()
}
}
/**
* 权限请求结果回调接口
*/
interface OnPermissionsResult {
fun agreePermission(granted:List<String> , agreeAll:Boolean )
fun disagreePermission(disagree:List<String> , never:Boolean )
}
使用
JPermissions.with(this)
.permissions(Manifest.permission.CAMERA,Manifest.permission.CALL_PHONE)//如果不设置,自动获取manifest文件下权限
.requestAgain()//拒绝后再次请求,直到用户永久拒绝
.requestPermissions(object :OnPermissionsResult{
override fun agreePermission(granted: List<String>, agreeAll: Boolean) {
if (agreeAll){
//获取所有权限成功
Toast.makeText(this@MainActivity,"全部请求成功",Toast.LENGTH_LONG).show()
}else{
//获取部分权限成功
Toast.makeText(this@MainActivity,granted.toString()+"部分请求成功",Toast.LENGTH_LONG).show()
}
}
override fun disagreePermission(disagree: List<String>, never: Boolean) {
if (never){
Toast.makeText(this@MainActivity,disagree.toString()+"永久拒绝",Toast.LENGTH_LONG).show()
//被永久拒绝就跳转到应用权限系统设置页面
JPermissions.gotoPermissionSettings(this@MainActivity)
}else{
//获取权限失败
Toast.makeText(this@MainActivity,disagree.toString()+"获取失败",Toast.LENGTH_LONG).show()
}
}
})