在谷歌原生系统中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后续读取到卡标签
graph LR
A[用户] -->B(打开NFC操作界面)-->H{是否支持NFC} -->|是|C{是否有权限}
H -->|否|Z(结束)
C -->|permission=0| G{NFC是否打开} -->|否|I[弹窗提示跳转系统NFC设置]-->|确定-返回界面|G
C -->|permission=5| E[弹窗提示需要的NFC权限说明]-->|确定|J
C -->|permission=1| F[弹窗提示跳转应用权限设置授权]-->|取消|Z
F -->|确定-返回界面|C
E -->|取消|Z
G -->|是|J(注册NFC)
I -->|取消|Z
相关代码
github地址:NfcSample