You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 13:15:03