Resumen detallado del desarrollo de Android NFC y análisis de ejemplos de lectura de tarjetas NFC


prefacio

El desarrollo de la capa de software de Android de la empresa de Internet de las cosas debe tener el paso de comunicarse con el hardware, ya sea logística, seguridad, atención médica, educación y otras industrias, excepto por el método de llamar directamente al SDK, la tecnología directamente relacionado con el hardware, la comunicación es solo la más utilizada, varios tipos, a saber, comunicación en serie, comunicación USB, Bluetooth, infrarrojos, NFC, etc.

Las tecnologías de comunicación de puerto serie y USB se han presentado en los artículos anteriores [Resumen del desarrollo de comunicación serie y análisis de ejemplo , Resumen del desarrollo de comunicación USB y análisis de ejemplo de desarrollo de impresora térmica] . Este artículo registra la tecnología relacionada con el desarrollo de NFC y los desvíos que ha tomado, con la esperanza de ayudar a las personas necesitadas mientras se ayudan a sí mismos a consolidar su conocimiento.

Además del resumen del desarrollo, el caso de este artículo es la tarjeta M1 de lectura NFC.
De hecho, para el requisito de lectura de tarjetas, nuestra investigación y desarrollo generalmente se divide en llamar al SDK y usar la función NFC que viene con Android
.


1. ¿Qué es NFC?

NFC es actualmente un elemento de hardware de configuración principal para teléfonos móviles con Android. El nombre completo es Near Field Communication, y el medio es Near Field Communication, también conocido como tecnología de comunicación inalámbrica de corto alcance. Los dispositivos que utilizan la tecnología NFC (como los teléfonos móviles) pueden intercambiar datos cuando están cerca unos de otros, lo que evolucionó a partir de la integración de la identificación por radiofrecuencia (RFID) sin contacto y las tecnologías de interconexión.

2. Conocimientos básicos

Lo que debes saber antes de comenzar el desarrollo

1. ¿Qué es NDEF?

Los datos almacenados en las etiquetas NFC se pueden escribir en una variedad de formatos, pero muchas API del marco de trabajo de Android se basan en un estándar del Foro NFC llamado NDEF (Formato de intercambio de datos NFC). .

En pocas palabras, es un estándar de formato de datos común

2. Modo de funcionamiento de la tecnología NFC

(1) Modo lector/grabador: Admite dispositivos NFC para leer y/o escribir etiquetas y adhesivos NFC pasivos.
(2) Modo punto a punto: admite dispositivos NFC para intercambiar datos con otros dispositivos NFC pares; Android Beam utiliza este modo de funcionamiento.
(3) Modo de emulación de tarjeta: admita el propio dispositivo NFC como una tarjeta NFC. A continuación, se puede acceder a la tarjeta NFC emulada mediante un lector NFC externo, como un punto de venta NFC.

El uso principal de este estuche es para leer y escribir tarjetas , que es la demanda normal de lectura y escritura de tarjetas.Más adelante, si tiene la oportunidad de entrar en contacto con las necesidades de simulación punto a punto y tarjetas, podrá hacer suplementos en este artículo.

3. El tipo técnico de la etiqueta

Por lo general, cada categoría de etiquetas (tarjetas) admite una o más tecnologías, y
la relación correspondiente es la siguiente

tecnología describir tipo de tarjeta
NFCA Brinda acceso a capacidades NFC-A (ISO 14443-3A) y operaciones de E/S. tarjeta M1
NfcB Proporciona capacidades NFC-B (ISO 14443-3B) y acceso a operaciones de E/S.
NFC Brinda acceso a capacidades NFC-F (JIS 6319-4) y operaciones de E/S.
NFCV Brinda acceso a capacidades NFC-V (ISO 15693) y operaciones de E/S. 15693 tarjetas
IsoDep Proporciona acceso a capacidades ISO-DEP (ISO 14443-4) y operaciones de E/S. tarjeta de CPU
ndef Brinda acceso a datos y operaciones en etiquetas NFC que han sido formateadas como NDEF.
NdefFormatable Una etiqueta que proporciona una tabla formateable que se puede formatear como NDEF.
MifareClásico Si este dispositivo Android es compatible con MIFARE, proporciona acceso al rendimiento de MIFARE Classic y las operaciones de E/S. tarjeta m1
MifareUltraligero Si este dispositivo Android es compatible con MIFARE, proporciona acceso a MIFARE para un rendimiento ultraligero y operaciones de E/S.

Como se muestra en la figura a continuación, esta es la información de la etiqueta NFC que muestra la demostración.
La parte rodeada por un círculo es la tecnología compatible con esta etiqueta NFC, que se usará al analizar los datos más adelante. Después de obtenerlos, puede usar la clase correspondiente para analizar los datos de la etiqueta.
inserte la descripción de la imagen aquí
Durante el desarrollo, tenemos los métodos correspondientes para obtener los métodos de análisis admitidos por esta etiqueta, que presentaré más adelante.

4. Clasificación de los métodos de implementación

(1) Método de registro de manifiesto: este método es principalmente para configurar filtros bajo la actividad correspondiente al archivo de manifiesto para responder a diferentes tipos de acciones NFC. Con este método, al deslizar una tarjeta, si hay varias aplicaciones en el teléfono móvil que tienen la implementación de NFC, el sistema mostrará una lista emergente de aplicaciones que pueden responder a los eventos de NFC para que el usuario elija, y el usuario debe haga clic en la aplicación de destino para responder a este evento de deslizamiento de tarjeta NFC.

(2) El método de respuesta en primer plano no requiere el Manifiesto para reconfigurar el filtro y utiliza directamente la actividad en primer plano para capturar eventos NFC y responder.

Las diferencias son las siguientes:
Diferentes métodos de respuesta: el sistema distribuye los eventos NFC registrados en el Manifiesto y se debe seleccionar una aplicación para responder al evento. El método de
       respuesta en primer plano es capturado por la actividad en primer plano para responder a los eventos NFC.
Prioridades diferentes: la prioridad del método de respuesta en primer plano es mayor que la registrada en el modo Manifiesto (
     si se instalan varias aplicaciones registradas en el Manifiesto y una aplicación en el modo de captura en primer plano, la que tiene la prioridad más alta después de deslizar la tarjeta es capturado en primer plano, si la aplicación correspondiente en primer plano no está abierta, aparecerá una lista para que el usuario elija registrarse en la aplicación calificada de manifiesto)

El primer tipo es más adecuado para la aplicación que debe llamarse deslizando la tarjeta, y el dispositivo no tiene múltiples dispositivos IoT que respondan al programa de etiquetas NFC (porque la aplicación del paquete de tarjeta, WeChat, etc. Cuando se usa la aplicación , la operación será engorrosa)

El segundo tipo es más adecuado para la lectura de tarjetas en la interfaz frontal, y cuando hay varias aplicaciones,
elija el método de implementación adecuado según los requisitos de su propio proyecto.

5. Proceso

En primer lugar, bajo la premisa de que el dispositivo es compatible con la autorización NFC, independientemente del método que se utilice, primero se desliza la tarjeta y el sistema distribuye la actividad correspondiente para obtener la etiqueta o la actividad en primer plano captura la etiqueta. Luego analice los datos de acuerdo con la tecnología admitida por esta etiqueta.

3. Obtenga el contenido de la etiqueta

1. Revisa el entorno

Primero agregue permisos en Manifiesto

    <uses-permission android:name="android.permission.NFC" />

Determine si admitirá NFC y abra la función

 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
        if (null == adapter) {
    
    
            Toast.makeText(this, "不支持NFC功能", Toast.LENGTH_SHORT).show();
        } else if (!adapter.isEnabled()) {
    
    
            Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
            // 根据包名打开对应的设置界面
            startActivity(intent);
        }

 

2. Obtenga la etiqueta NFC

2.1 Obtener Tag registrándose en Manifiesto

Aquí hay tres tipos de filtros de intención.
Las características de este método se presentan en la [Clasificación de modos de implementación] anterior. Este método de distribución por parte del sistema de programación de etiquetas necesita definir un filtro de intención fijo en el Manifiesto. El sistema de programación de etiquetas define tres Intents, que se enumeran a continuación en orden de prioridad de mayor a menor:

ACTION_NDEF_DISCOVERED : si se escanea una etiqueta que contiene una carga útil NDEF y se reconoce su tipo, use esta intención para iniciar la actividad. Este es el Intent con la prioridad más alta. El sistema de programación de etiquetas intentará usar este Intent para iniciar la Actividad tanto como sea posible e intentará usar otros Intents cuando falle.

ACTION_TECH_DISCOVERED : si no hay actividad registrada para manejar el Intent ACTION_NDEF_DISCOVERED, el sistema de envío de pestañas intentará usar este Intent para iniciar la aplicación. Además, si una etiqueta escaneada contiene datos NDEF que no se pueden asignar a un tipo MIME o URI, o si la etiqueta no contiene datos NDEF pero usa una tecnología de etiqueta conocida, esta intención también se activa directamente (sin activar primero ACTION_NDEF_DISCOVERED).

ACTION_TAG_DISCOVERED : si no hay ninguna actividad que maneje la intención ACTION_NDEF_DISCOVERED o ACTION_TECH_DISCOVERED, use esta intención para iniciar la actividad.

Agregue un filtro de intención
Este es el primero más simple y de mayor prioridad, que ya satisface las necesidades

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            </intent-filter>
        </activity>

Por supuesto, también puedes elegir el segundo

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
            </intent-filter>
            <meta-data android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/filter_nfc" />
        </activity>

El archivo filter_nfc
es lo que TECH_DISCOVERED necesita configurar.Entre ellos, la lógica o relación entre tech-lists, y la lógica y relación entre tech, son similares al principio y uso de techLists en el Esquema 2.

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
 
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NfcA</tech>
    </tech-list>
 
    <tech-list>
        <tech>android.nfc.tech.NfcB</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcF</tech>
    </tech-list>
</resources>

último que queda

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

No tiene mucho sentido que generalmente sea inútil.

Luego puede obtener la etiqueta en el método onCreate de la Actividad correspondiente

class NfcActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_nfc)
        val adapter = NfcAdapter.getDefaultAdapter(this)
        if (null == adapter) {
    
    
            Toast.makeText(this, "不支持NFC功能", Toast.LENGTH_SHORT).show()
        } else if (!adapter.isEnabled) {
    
    
            val intent = Intent(Settings.ACTION_NFC_SETTINGS)
            // 根据包名打开对应的设置界面
            startActivity(intent)
        }
        val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
    }
}

2.1 Obtener la etiqueta a través de la captura de actividad en primer plano

class MainActivity : AppCompatActivity() {
    
    
    var mNfcAdapter: NfcAdapter? = null
    var pIntent: PendingIntent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
       super.onCreate(savedInstanceState)
       initNfc()
 

    }
    private fun initNfc() {
    
    
        mNfcAdapter = M1CardUtils.isNfcAble(this)
        pIntent = PendingIntent.getActivity(this, 0,  
        //在Manifest里或者这里设置当前activity启动模式,否则每次响应NFC事件,activity会重复创建
        Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
    }
    
    override fun onResume() {
    
    
        super.onResume()
        mNfcAdapter?.let {
    
    
            val ndef = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
            val tag = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
            val tech = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
            val filters = arrayOf(ndef, tag, tech)
            val techList = arrayOf(
                arrayOf(
                    "android.nfc.tech.Ndef",
                    "android.nfc.tech.NfcA",
                    "android.nfc.tech.NfcB",
                    "android.nfc.tech.NfcF",
                    "android.nfc.tech.NfcV",
                    "android.nfc.tech.NdefFormatable",
                    "android.nfc.tech.MifareClassic",
                    "android.nfc.tech.MifareUltralight",
                    "android.nfc.tech.NfcBarcode"
                )
            )
            it.enableForegroundDispatch(this, pIntent, filters, techList)
            XLog.d("开始捕获NFC数据")
        }
    }
    override fun onPause() {
    
    
        super.onPause()
        mNfcAdapter?.disableForegroundDispatch(this)
    }
    override fun onNewIntent(intent: Intent?) {
    
    
        super.onNewIntent(intent)
        //这里必须setIntent,set  NFC事件响应后的intent才能拿到数据
        setIntent(intent)
        val tag = getIntent().getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
        //M1CardUtils 我后面会贴出来的
        if (M1CardUtils.isMifareClassic(tag)) {
    
    
            try {
    
    
                val reader = M1CardUtils.readCard(tag)
                XLog.d("读卡内容:$reader")
                val data = reader.split("|")
            } catch (e: IOException) {
    
    
                e.printStackTrace()
            }
        }
    }
}

Cuarto, analice los datos de la etiqueta

No importa qué método se use, cuando obtenemos la etiqueta TAG, el método de análisis es el mismo, y debemos elegir el método de análisis correspondiente de acuerdo con los diferentes tipos de tarjetas.Como se muestra en la figura, podemos obtener la información de la
inserte la descripción de la imagen aquí
tarjeta Correspondiente a:
tecnología soportada tipo
MifareClassic tipo
sector espacio de almacenamiento
número de sectores número de
bloques en un sector

1. Análisis de la tarjeta M1

Permítanme hablar sobre los conceptos básicos aquí. Ya sea NFC o lectura del módulo del lector de tarjetas, el proceso de análisis consiste en encontrar la tarjeta primero, luego verificar la contraseña del sector y obtener los datos del sector. Por ejemplo, si los datos para ser leído se sabe que está en el sector 2, luego busque Cuando la verificación de la tarjeta postal, pase el número de sector para ser verificado, la contraseña del sector y la contraseña de verificación tipo A/B del sector. , los datos se pueden leer.


import android.app.Activity
import android.nfc.NfcAdapter
import android.nfc.Tag
import com.hjq.toast.ToastUtils
import kotlin.Throws
import android.nfc.tech.MifareClassic
import com.elvishew.xlog.XLog
import java.io.IOException
import java.lang.StringBuilder
import java.nio.charset.Charset


object M1CardUtils {
    
    
    /**
     * 判断是否支持NFC
     *
     * @return
     */
    fun isNfcAble(mContext: Activity?): NfcAdapter? {
    
    
        val mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext)
        if (mNfcAdapter == null) {
    
    
            ToastUtils.show("设备不支持NFC!")
        }
        if (!mNfcAdapter!!.isEnabled) {
    
    
            ToastUtils.show("请在系统设置中先启用NFC功能!")
        }
        return mNfcAdapter
    }

    /**
     * 监测是否支持MifareClassic
     *
     * @param tag
     * @return
     */
    fun isMifareClassic(tag: Tag): Boolean {
    
    
        val techList = tag.techList
        var haveMifareUltralight = false
        for (tech in techList) {
    
    
            if (tech.contains("MifareClassic")) {
    
    
                haveMifareUltralight = true
                break
            }
        }
        if (!haveMifareUltralight) {
    
    
            ToastUtils.show("不支持MifareClassic")
            return false
        }
        return true
    }

    /**
     * 读取卡片信息
     *
     * @return
     */
    @Throws(IOException::class)
    fun readCard(tag: Tag?): String {
    
    
        val mifareClassic = MifareClassic.get(tag)
        return try {
    
    
            mifareClassic.connect()
            val metaInfo = StringBuilder()
            val gbk = Charset.forName("gbk")

            // 获取TAG中包含的扇区数
            val sectorCount = mifareClassic.sectorCount
            //            for (int j = 0; j < sectorCount; j++) {
    
    
            val bCount: Int //当前扇区的块数
            var bIndex: Int //当前扇区第一块
            if (m1Auth(mifareClassic, 2)) {
    
    
                bCount = mifareClassic.getBlockCountInSector(2)
                bIndex = mifareClassic.sectorToBlock(2)
                var length = 0
                for (i in 0 until bCount) {
    
    
                    val data = mifareClassic.readBlock(bIndex)
                    for (i1 in data.indices) {
    
    
                        if (data[i1] == 0.toByte()) {
    
    
                            length = i1
                        }
                    }
                    val dataString = String(data, 0, length, gbk).trim {
    
     it <= ' ' }
                    metaInfo.append(dataString)
                    bIndex++
                }
            } else {
    
    
                XLog.e("密码校验失败")
            }
            //            }
            metaInfo.toString()
        } catch (e: IOException) {
    
    
            throw IOException(e)
        } finally {
    
    
            try {
    
    
                mifareClassic.close()
            } catch (e: IOException) {
    
    
                throw IOException(e)
            }
        }
    }

    /**
     * 改写数据
     *
     * @param block
     * @param blockbyte
     */
    @Throws(IOException::class)
    fun writeBlock(tag: Tag?, block: Int, blockbyte: ByteArray?): Boolean {
    
    
        val mifareClassic = MifareClassic.get(tag)
        try {
    
    
            mifareClassic.connect()
            if (m1Auth(mifareClassic, block / 4)) {
    
    
                mifareClassic.writeBlock(block, blockbyte)
                XLog.e("writeBlock", "写入成功")
            } else {
    
    
                XLog.e("密码是", "没有找到密码")
                return false
            }
        } catch (e: IOException) {
    
    
            throw IOException(e)
        } finally {
    
    
            try {
    
    
                mifareClassic.close()
            } catch (e: IOException) {
    
    
                throw IOException(e)
            }
        }
        return true
    }

    /**
     * 密码校验
     *
     * @param mTag
     * @param position
     * @return
     * @throws IOException
     */
    @Throws(IOException::class)
    fun m1Auth(mTag: MifareClassic, position: Int): Boolean {
    
    
        if (mTag.authenticateSectorWithKeyA(position, MifareClassic.KEY_DEFAULT)) {
    
    
            return true
        } else if (mTag.authenticateSectorWithKeyB(position, MifareClassic.KEY_DEFAULT)) {
    
    
            return true
        }
        return false
    }


}

2. Análisis de la tarjeta iso15693

Esto no se usa en este caso, pero se necesita M1, así que esto no es necesario Esta es una clase encapsulada por otros grandes como referencia.

import android.nfc.tech.NfcV;
 
import com.haiheng.core.util.ByteUtils;
 
import java.io.IOException;
 
/**
 * NfcV(ISO 15693)读写操作
 *   用法
 *  NfcV mNfcV = NfcV.get(tag);
 *  mNfcV.connect();
 * <p>
 *  NfcVUtils mNfcVutil = new NfcVUtils(mNfcV);
 *  取得UID
 *  mNfcVutil.getUID();
 *  读取block在1位置的内容
 *  mNfcVutil.readOneBlock(1);
 *  从位置7开始读2个block的内容
 *  mNfcVutil.readBlocks(7, 2);
 *  取得block的个数
 *  mNfcVutil.getBlockNumber();
 *  取得1个block的长度
 *  mNfcVutil.getOneBlockSize();
 *  往位置1的block写内容
 *  mNfcVutil.writeBlock(1, new byte[]{0, 0, 0, 0})
 *
 * @author Kelly
 * @version 1.0.0
 * @filename NfcVUtils.java
 * @time 2018/10/30 10:29
 * @copyright(C) 2018 song
 */
public class NfcVUtils {
    
    
    private NfcV mNfcV;
    /**
     * UID数组行式
     */
    private byte[] ID;
    private String UID;
    private String DSFID;
    private String AFI;
    /**
     * block的个数
     */
    private int blockNumber;
    /**
     * 一个block长度
     */
    private int oneBlockSize;
    /**
     * 信息
     */
    private byte[] infoRmation;
 
    /**
     *  * 初始化
     *  * @param mNfcV NfcV对象
     *  * @throws IOException
     *  
     */
    public NfcVUtils(NfcV mNfcV) throws IOException {
    
    
        this.mNfcV = mNfcV;
        ID = this.mNfcV.getTag().getId();
        byte[] uid = new byte[ID.length];
        int j = 0;
        for (int i = ID.length - 1; i >= 0; i--) {
    
    
            uid[j] = ID[i];
            j++;
        }
        this.UID = ByteUtils.byteArrToHexString(uid);
        getInfoRmation();
    }
 
    public String getUID() {
    
    
        return UID;
    }
 
    /**
     *  * 取得标签信息 
     *  
     */
    private byte[] getInfoRmation() throws IOException {
    
    
        byte[] cmd = new byte[10];
        cmd[0] = (byte) 0x22; // flag
        cmd[1] = (byte) 0x2B; // command
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        infoRmation = mNfcV.transceive(cmd);
        blockNumber = infoRmation[12];
        oneBlockSize = infoRmation[13];
        AFI = ByteUtils.byteArrToHexString(new byte[]{
    
    infoRmation[11]});
        DSFID = ByteUtils.byteArrToHexString(new byte[]{
    
    infoRmation[10]});
        return infoRmation;
    }
 
    public String getDSFID() {
    
    
        return DSFID;
    }
 
    public String getAFI() {
    
    
        return AFI;
    }
 
    public int getBlockNumber() {
    
    
        return blockNumber + 1;
    }
 
    public int getOneBlockSize() {
    
    
        return oneBlockSize + 1;
    }
 
    /**
     *  * 读取一个位置在position的block
     *  * @param position 要读取的block位置
     *  * @return 返回内容字符串
     *  * @throws IOException
     *  
     */
    public String readOneBlock(int position) throws IOException {
    
    
        byte cmd[] = new byte[11];
        cmd[0] = (byte) 0x22;
        cmd[1] = (byte) 0x20;
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        cmd[10] = (byte) position;
        byte res[] = mNfcV.transceive(cmd);
        if (res[0] == 0x00) {
    
    
            byte block[] = new byte[res.length - 1];
            System.arraycopy(res, 1, block, 0, res.length - 1);
            return ByteUtils.byteArrToHexString(block);
        }
        return null;
    }
 
    /**
     *  * 读取从begin开始end个block
     *  * begin + count 不能超过blockNumber
     *  * @param begin block开始位置
     *  * @param count 读取block数量
     *  * @return 返回内容字符串
     *  * @throws IOException
     *  
     */
    public String readBlocks(int begin, int count) throws IOException {
    
    
        if ((begin + count) > blockNumber) {
    
    
            count = blockNumber - begin;
        }
        StringBuffer data = new StringBuffer();
        for (int i = begin; i < count + begin; i++) {
    
    
            data.append(readOneBlock(i));
        }
        return data.toString();
    }
 
 
    /**
     *  * 将数据写入到block,
     *  * @param position 要写内容的block位置
     *  * @param data 要写的内容,必须长度为blockOneSize
     *  * @return false为写入失败,true为写入成功
     *  * @throws IOException 
     *  
     */
    public boolean writeBlock(int position, byte[] data) throws IOException {
    
    
        byte cmd[] = new byte[15];
        cmd[0] = (byte) 0x22;
        cmd[1] = (byte) 0x21;
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        //block
        cmd[10] = (byte) position;
        //value
        System.arraycopy(data, 0, cmd, 11, data.length);
        byte[] rsp = mNfcV.transceive(cmd);
        if (rsp[0] == 0x00)
            return true;
        return false;
    }
}

Resumir

Lo anterior es de lo que quiero hablar hoy. Si hay algún error o mejora en el artículo, bienvenido a agregar y corregir. Este artículo solo presenta el uso de NFC y los escenarios de lectura y análisis de las tarjetas M1. La historia de NFC , tipos de tarjeta y tipos de filtro de intención se detallan. Puede consultar más documentos para obtener descripciones, otros escenarios de uso, etc. Aquí hay algunos artículos útiles que he visto, y puede hacer más referencias.

Varios tipos de tarjetas NFC, diferencias, introducción a la historia
https://zhuanlan.zhihu.com/p/344426747
Descripción en chino de varios materiales oficiales
https://blog.csdn.net/u013164293/article/details/124474247?spm=1001.2014 . 3001.5506

Supongo que te gusta

Origin blog.csdn.net/qq_39178733/article/details/129850034
Recomendado
Clasificación