KMM中NFC实现技术求助:JetPackCompose环境下NFC开发咨询
KMM中类卡片式NFC功能实现方案
核心原理
KMM的NFC能力无法在共享层直接实现,因为NFC是平台原生API,需采用**预期-实际(Expect-Actual)**模式:在共享模块定义抽象接口,Android/iOS平台分别实现具体逻辑,再通过共享ViewModel调用。
步骤1:共享层定义NFC抽象接口
在共享模块(commonMain)中创建NFC操作的抽象类与跨平台数据模型:
// commonMain/NfcManager.kt expect class NfcManager { fun startCardDetection(callback: (NfcCardData?) -> Unit) fun stopCardDetection() fun isNfcAvailable(): Boolean } // 跨平台传递NFC卡片信息的数据类 data class NfcCardData( val tagId: String, val cardType: String, val rawData: ByteArray? = null )
步骤2:Android平台实现NFC逻辑
配置权限与清单
在AndroidManifest.xml中添加NFC相关配置:
<uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc" android:required="false" /> <activity ...> <intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED" /> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> </activity>
在res/xml目录创建nfc_tech_filter.xml,指定支持的类卡片NFC技术:
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.IsoDep</tech> <tech>android.nfc.tech.MifareClassic</tech> <tech>android.nfc.tech.MifareUltralight</tech> </tech-list> </resources>
实现Actual类
// androidMain/NfcManager.kt actual class NfcManager(private val context: Context) { private var nfcAdapter: NfcAdapter? = null private var pendingIntent: PendingIntent? = null private var detectionCallback: ((NfcCardData?) -> Unit)? = null init { nfcAdapter = NfcAdapter.getDefaultAdapter(context) pendingIntent = PendingIntent.getActivity( context, 0, Intent(context, context::class.java).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) } actual fun startCardDetection(callback: (NfcCardData?) -> Unit) { detectionCallback = callback nfcAdapter?.enableForegroundDispatch( context as Activity, pendingIntent, arrayOf(IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)), arrayOf(arrayOf(IsoDep::class.java.name, MifareClassic::class.java.name)) ) } actual fun stopCardDetection() { nfcAdapter?.disableForegroundDispatch(context as Activity) detectionCallback = null } actual fun isNfcAvailable(): Boolean = nfcAdapter != null && nfcAdapter?.isEnabled == true // 需在Activity的onNewIntent中调用此方法处理NFC事件 fun handleNfcIntent(intent: Intent) { if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) { val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG) tag?.let { val tagId = it.id.joinToString(separator = "") { byte -> "%02x".format(byte) } val cardType = it.techList.joinToString(", ") val rawData = it.id detectionCallback?.invoke(NfcCardData(tagId, cardType, rawData)) } } } }
步骤3:iOS平台实现NFC逻辑
配置权限描述
在Info.plist中添加NFC权限与支持的卡片AID:
<key>NFCReaderUsageDescription</key> <string>需要访问NFC功能读取卡片信息</string> <key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key> <array> <string>A0000002471001</string> <!-- 兼容常见类卡片的AID示例 --> </array>
实现Actual类
// iosMain/NfcManager.kt import CoreNFC.NFCISO7816TagReaderSession import CoreNFC.NFCReaderError import platform.Foundation.NSError actual class NfcManager { private var session: NFCISO7816TagReaderSession? = null private var detectionCallback: ((NfcCardData?) -> Unit)? = null actual fun startCardDetection(callback: (NfcCardData?) -> Unit) { detectionCallback = callback session = NFCISO7816TagReaderSession(pollingOption = NFCISO7816TagReaderSession.PollingOptionISO14443, delegate = object : NFCISO7816TagReaderSessionDelegateProtocol { override fun tagReaderSessionDidBecomeActive(session: NFCISO7816TagReaderSession) {} override fun tagReaderSession(session: NFCISO7816TagReaderSession, didInvalidateWithError error: NSError) { if (error.code != NFCReaderError.Code.readerSessionInvalidationErrorUserCanceled.rawValue) { detectionCallback?.invoke(null) } this@NfcManager.session = null } override fun tagReaderSession(session: NFCISO7816TagReaderSession, didDetectTags tags: List<*>?) { tags?.firstOrNull() as? NFCISO7816Tag?.let { tag -> val tagId = tag.identifier?.toByteArray()?.joinToString(separator = "") { byte -> "%02x".format(byte) } ?: "" val cardType = "ISO7816" val rawData = tag.identifier?.toByteArray() detectionCallback?.invoke(NfcCardData(tagId, cardType, rawData)) session.invalidate() } } }) session?.begin() } actual fun stopCardDetection() { session?.invalidate() session = null detectionCallback = null } actual fun isNfcAvailable(): Boolean = NFCISO7816TagReaderSession.readingAvailable() }
步骤4:共享ViewModel集成NFC逻辑
在共享ViewModel中封装NFC操作状态与逻辑:
// commonMain/NfcViewModel.kt import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel class NfcViewModel(private val nfcManager: NfcManager) : ViewModel() { val nfcStatus = mutableStateOf("未启动扫描") val detectedCard = mutableStateOf<NfcCardData?>(null) fun startScanning() { if (!nfcManager.isNfcAvailable()) { nfcStatus.value = "NFC不可用" return } nfcStatus.value = "正在扫描卡片..." nfcManager.startCardDetection { cardData -> cardData?.let { detectedCard.value = it nfcStatus.value = "已检测到卡片" } ?: run { nfcStatus.value = "扫描出错" } } } fun stopScanning() { nfcManager.stopCardDetection() nfcStatus.value = "已停止扫描" detectedCard.value = null } override fun onCleared() { super.onCleared() stopScanning() } }
步骤5:Compose界面调用
在平台Compose界面中实现NFC交互UI:
// 平台通用Compose代码(平台侧负责注入NfcManager实例) @Composable fun NfcScreen(viewModel: NfcViewModel = viewModel()) { Column( modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = viewModel.nfcStatus.value) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { viewModel.startScanning() }) { Text("开始扫描卡片") } Spacer(modifier = Modifier.height(8.dp)) Button(onClick = { viewModel.stopScanning() }) { Text("停止扫描") } viewModel.detectedCard.value?.let { card -> Spacer(modifier = Modifier.height(24.dp)) Card(modifier = Modifier.fillMaxWidth().padding(8.dp)) { Column(modifier = Modifier.padding(16.dp)) { Text("卡片ID: ${card.tagId}") Text("卡片类型: ${card.cardType}") } } } } }
关键注意事项
- Android端需在Android 6+动态申请NFC权限,且仅在Activity中能启用前台调度
- iOS端仅支持iOS 13+,必须在实体设备上测试(模拟器无NFC功能)
- 共享层仅处理抽象定义与状态管理,所有平台专属逻辑需放在对应平台模块
内容的提问来源于stack exchange,提问作者Artur Andruszkiewicz
相关产品推荐
相关产品推荐

