NFC权限在MIUI系统中需要运行时权限
在谷歌原生系统中NFC权限属于正常权限,但是在MIUI系统中,NFC权限被声明为运行时权限并且在使用相关功能时,系统会弹窗告知用户是否允许,用户也可以在应用设置界面开启或者关闭NFC权限。(暂未查询到MIUI几开始引入)
NFC
Added in API level 9
public static final String NFC 复制代码
Allows applications to perform I/O operations over NFC.
Protection level: normal
Constant Value: "android.permission.NFC"
正常用法:
在AndroidManifest.xml
文件中声明权限即可正常使用NFC相关功能
<uses-permission android:name="android.permission.NFC"/> 复制代码
MIUI运行时用户确认
在App首次调用相关NFC系统API时,会触发系统弹窗让用户授权App是否允许使用NFC。用户也可以直接在应用权限管理界面设置NFC权限的开启与关闭。
如果用户未在倒计时结束前允许、主动关闭权限,那么在使用NFC的读卡器模式(其他模式猜测一致)读卡时会报IOException。
MIUI检查App是否获取了NFC权限
利用反射 AppOpsManager#checkOpNoThrow(@NonNull String op, int uid, @NonNull String packageName)
获取op值对应权限的授权状态,NFC的op为10016
,返回值0表示允许,1表示禁止,5表示询问
Android权限管理源码解析就能了解权限相关状态的存储位置为/data/system/appOps/xxx.xml
用一台root的MIUI手机查看相关xml文件能找到NFC的op值为10016
//检查NFC权限的核心代码 @IntDef(value = [PERMISSION_GRANTED, PERMISSION_DENIED, PERMISSION_ASK, PERMISSION_UNKNOWN]) @kotlin.annotation.Retention( AnnotationRetention.SOURCE ) annotation class PermissionResult { companion object { /** * Permission check result: this is returned by [.check] * if the permission has been granted to the given package. */ const val PERMISSION_GRANTED = 0 /** * Permission check result: this is returned by [.check] * if the permission has not been granted to the given package. */ const val PERMISSION_DENIED = -1 const val PERMISSION_ASK = 1 const val PERMISSION_UNKNOWN = 2 } } /** * 检测NFC权限是否有授权. */ @PermissionResult @RequiresApi(Build.VERSION_CODES.KITKAT) fun checkNfcPermission(context: Context): Int { try { val mAppOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager val pkg = context.applicationContext.packageName val uid = context.applicationInfo.uid val appOpsClass = Class.forName(AppOpsManager::class.java.name) val checkOpNoThrowMethod = appOpsClass.getDeclaredMethod( "checkOpNoThrow", Integer.TYPE, Integer.TYPE, String::class.java ) //the ops of NFC is 10016,check /data/system/appops/xxx.xml val invoke = checkOpNoThrowMethod.invoke(mAppOps, 10016, uid, pkg) if (invoke == null) { Logger.get().println( "MIUI check permission checkOpNoThrowMethod(AppOpsManager) invoke result is null" ) return PERMISSION_UNKNOWN } val result = invoke.toString() Logger.get().println( "MIUI check permission checkOpNoThrowMethod(AppOpsManager) invoke result = $result" ) when (result) { "0" -> return PERMISSION_GRANTED "1" -> return PERMISSION_DENIED "5" -> return PERMISSION_ASK } } catch (e: Exception) { Logger.get().println("check nfc permission fail ${e.message}", e) } return PERMISSION_UNKNOWN } 复制代码
最佳做法
场景1:用户正常打开NFC读卡界面,等待NFC后续读取到卡标签
作者:河婆墟邓紫棋
链接:https://juejin.cn/post/7030819721851174942