Android 开发——NFC标签开发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/da_caoyuan/article/details/79656990

1.NFC的工作模式

NFC支持如下3种工作模式:读卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、点对点模式(P2P mode)。

下来分别看一下这三种模式:

(1)读卡器模式

数据在NFC芯片中,可以简单理解成“刷标签”。本质上就是通过支持NFC的手机或其它电子设备从带有NFC芯片的标签、贴纸、名片等媒介中读写信息。通常NFC标签是不需要外部供电的。当支持NFC的外设向NFC读写数据时,它会发送某种磁场,而这个磁场会自动的向NFC标签供电。

(2)仿真卡模式

数据在支持NFC的手机或其它电子设备中,可以简单理解成“刷手机”。本质上就是将支持NFC的手机或其它电子设备当成借记卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息凭证封装成数据包存储在支持NFC的外设中 。
在使用时还需要一个NFC射频器(相当于刷卡器)。将手机靠近NFC射频器,手机就会接收到NFC射频器发过来的信号,在通过一系列复杂的验证后,将IC卡的相应信息传入NFC射频器,最后这些IC卡数据会传入NFC射频器连接的电脑,并进行相应的处理(如电子转帐、开门等操作)。

(3)点对点模式

该模式与蓝牙、红外差不多,用于不同NFC设备之间进行数据交换,不过这个模式已经没有有“刷”的感觉了。其有效距离一般不能超过4厘米,但传输建立速度要比红外和蓝牙技术快很多,传输速度比红外块得多,如过双方都使用Android4.2,NFC会直接利用蓝牙传输。这种技术被称为Android Beam。所以使用Android Beam传输数据的两部设备不再限于4厘米之内。
点对点模式的典型应用是两部支持NFC的手机或平板电脑实现数据的点对点传输,例如,交换图片或同步设备联系人。因此,通过NFC,多个设备如数字相机,计算机,手机之间,都可以快速连接,并交换资料或者服务。

下面看一下NFC、蓝牙和红外之间的差异:
这里写图片描述

Android对NFC的支持

不同的NFC标签之间差异很大,有的只支持简单的读写操作,有时还会采用支持一次性写入的芯片,将NFC标签设计成只读的。当然,也存在一些复杂的NFC标签,例如,有一些NFC标签可以通过硬件加密的方式限制对某一区域的访问。还有一些标签自带操作环境,允许NFC设备与这些标签进行更复杂的交互。这些标签中的数据也会采用不同的格式。但Android SDK API主要支持NFC论坛标准(Forum Standard),这种标准被称为NDEF(NFC Data Exchange Format,NFC数据交换格式)。

进入正题

一: 基本配置

首先肯定要申请相应的权限:

<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />

同时要注意最小api的版本,根据谷歌官方的说话,NFC在Android上,也是从API9才开始支持的,但是到了API14 Google才对NFC大力开发,所以等到了API15的时候,NFC的传输速度就得到了很大的加强,所以最小api最好设置为14.

<uses-sdk android:minSdkVersion="14"/>

然后在相应的要接收nfc信息的activity中注册intent filter:

<intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
                <data android:mimeType="text/plain"/>
            </intent-filter>
            <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"/>
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>

注意:通常来说,所有处理NFC的Activity都要设置launchMode属性为singleTop或者singleTask,保证了无论NFC标签靠近手机多少次,Activity实例只有一个。

如果您的活动过滤器是ACTION_TECH_DISCOVERED,你必须创建一个指定的活动支持内技术的XML资源文件(在res目录下创建xml文件夹),可以通过android.nfc.Tag类的getTechList()获取子集
其中名为nfc_tech_filter的xml文件如下:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
          
        <tech>android.nfc.tech.IsoDep</tech>
          
        <tech>android.nfc.tech.NfcA</tech>
          
        <tech>android.nfc.tech.NfcB</tech>
          
        <tech>android.nfc.tech.NfcF</tech>
          
        <tech>android.nfc.tech.NfcV</tech>
          
        <tech>android.nfc.tech.Ndef</tech>
          
        <tech>android.nfc.tech.NdefFormatable</tech>
          
        <tech>android.nfc.tech.MifareClassic</tech>
          
        <tech>android.nfc.tech.MifareUltralight</tech>
          
    </tech-list>
</resources>

当然您也可以指定多个tech-list组。每个的tech-list集独立地考虑,并且您的活动被认为是一个匹配,如果任何一个 tech-list组是由返回的技术的一个子集getTechList(),这提供了AND与OR匹配技术,语义。
下面的例子相匹配,可以支持NFCA和NDEF技术或可以支持NfcB和NDEF技术代码:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
</resources>

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
</resources>

二: 读取数据

关键代码:

......//关键代码,onCreate中
        // 获取默认的NFC控制器
        mAdapter = NfcAdapter.getDefaultAdapter(this);
        if (mAdapter == null) {
            promt.setText("设备不支持NFC!");
            return;
        }
        if (!mAdapter.isEnabled()) {
            promt.setText("请在系统设置中先启用NFC功能!");
            return;
        }
        //创建intent检测nfc
        mPendingIntent = PendingIntent
                .getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
      //onResume
      if (this.mAdapter == null)
            return;
        if (!this.mAdapter.isEnabled()) {
            promt.setText("请在系统设置中先启用NFC功能!");
        }

         //开启监听nfc设备
        this.mAdapter.enableForegroundDispatch(this, this.mPendingIntent, null, null);

    @Override
    protected void onPause() {
        super.onPause();
        if (nfcAdapter != null) {
            Log.i(TAG, "onPause: 关闭监听nfc设备");
            mAdapter.disableForegroundDispatch(this);
        }

    }

因为我们注册了Intent Filter,当扫描到设备后,系统会调用我们的app,进而会进入activity的onNewIntent(Intent paramIntent)方法

@Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
       //我们接受到消息啦,可以处理了
        // 得到是否检测到TAG触发
        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())
                || NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())
                || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
            // 处理该intent
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

            // 获取标签id数组
            byte[] bytesId = tag.getId();

            //获取消息内容
            NfcMessageParser nfcMessageParser = new NfcMessageParser(intent);
            List<String> tagMessage = nfcMessageParser.getTagMessage();

            if (tagMessage == null || tagMessage.size() == 0) {

                Toast.makeText(this, "NFC格式不支持...", Toast.LENGTH_LONG).show();
            } else {
                for (int i = 0; i < tagMessage.size(); i++) {
                    Log.e("tag", tagMessage.get(i));
                }
                datas = tagMessage.get(0);
            }
            String info = "";
            if (datas != null) {
                info += "内容:" + datas + "\n卡片ID:" + bytesToHexString(bytesId) + "\n";
            } else {
                info += "内容:空" + "\n卡片ID:" + bytesToHexString(bytesId) + "\n";
            }


            String[] techList = tag.getTechList();

            //分析NFC卡的类型: Mifare Classic/UltraLight Info
            String cardType = "";


            for (String aTechList : techList) {

                if (TextUtils.equals(aTechList, "android.nfc.tech.Ndef")) {
                    Ndef ndef = Ndef.get(tag);
                    cardType += "最大数据尺寸:" + ndef.getMaxSize() + "字节";
                }
            }

            info += cardType;

            Toast.makeText(this, "NFC信息如下:" + info, Toast.LENGTH_SHORT).show();
            loadWebView(AddressConfig.NFCWebViewAddress + info);
            // tvTip.setText("NFC信息如下:\n" + info);


        }
    }

传来的intent中包含了:
EXTRA_TAG:一个Tag对象,表示扫描标签。
EXTRA_NDEF_MESSAGES(可选):从标签解析NDEF消息的数组,这个就是我们要的数据,显示到屏幕上。
EXTRA_ID(可选):标签的ID。
处理消息的代码如下:

import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Parcelable;

import java.util.ArrayList;
import java.util.List;

/**
 * 类名 NfcMessageParser
 */


public class NfcMessageParser {

    private Intent tagIntent;
    private String TAG = "NfcMessageParser";

    public NfcMessageParser() {

    }

    public NfcMessageParser(Intent intent) {
        this.tagIntent = intent;
    }

    // 解析NFC信息,
    public List<String> getTagMessage() {
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(tagIntent.getAction())) {
            NdefMessage[] msgs = getTagNdef(tagIntent);
            List<String> ndefList = getNdefString(msgs);

            if (ndefList != null && ndefList.size() != 0) {
                return ndefList;
            }
        }
        return null;
    }


    // 得到Intent中的NDEF数据
    private NdefMessage[] getTagNdef(Intent intent) {
        // TODO Auto-generated method stub
        NdefMessage[] msgs = null;
        Parcelable[] rawMsgs = intent
                .getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

        //把序列化数据转成Messaeg对象
        if (rawMsgs != null) {
            msgs = new NdefMessage[rawMsgs.length];
            for (int i = 0; i < rawMsgs.length; i++) {
                msgs[i] = (NdefMessage) rawMsgs[i];
            }
        } else {
            // Unknown tag type
            byte[] empty = new byte[]{};
            NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty,
                    empty, empty);
            NdefMessage msg = new NdefMessage(new NdefRecord[]{record});
            msgs = new NdefMessage[]{msg};
        }
        return msgs;
    }

    // 把Message转成List
    private List<String> getNdefString(NdefMessage[] msgs) {
        // TODO Auto-generated method stub
        if (msgs != null && msgs.length != 0) {
            List<String> tagMessage = parser(msgs[0]);
            return tagMessage;
        }
        return null;
    }

    // 把NDEF中的信息系转化为Record,并最终转化为String
    private List<String> parser(NdefMessage ndefMessage) {
        // TODO Auto-generated method stub
        NdefRecord[] records = ndefMessage.getRecords();
        List<String> elements = new ArrayList<>();
        for (NdefRecord ndefRecord : records) {
            if (!TextRecord.isText(ndefRecord)) {
                return null;
            }
            elements.add(TextRecord.parse(ndefRecord));
        }
        return elements;
    }

    // 字符序列转换为16进制字符串
    private String bytesToHexString(byte[] src) {
        return bytesToHexString(src, true);
    }

    private String bytesToHexString(byte[] src, boolean isPrefix) {
        StringBuilder stringBuilder = new StringBuilder();
        if (isPrefix) {
            stringBuilder.append("0x");
        }
        if (src == null || src.length <= 0) {
            return null;
        }
        char[] buffer = new char[2];
        for (int i = 0; i < src.length; i++) {
            buffer[0] = Character.toUpperCase(Character.forDigit(
                    (src[i] >>> 4) & 0x0F, 16));
            buffer[1] = Character.toUpperCase(Character.forDigit(src[i] & 0x0F,
                    16));
            System.out.println(buffer);
            stringBuilder.append(buffer);
        }
        return stringBuilder.toString();
    }

}

TextRecord.java

import android.nfc.NdefRecord;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * 类名 TextRecord
 * 功能 标签管理
 */


public class TextRecord {

    public static boolean isText(NdefRecord ndefRecord) {
        // TODO Auto-generated method stub
        try {
            parse(ndefRecord);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    /**
     * 吧Ndef记录转为String,这里的格式是TNF_WELL_KNOWN with RTD_TEXT
     * @param ndefRecord
     * @return
     */
    public static String parse(NdefRecord ndefRecord) {
        // TODO Auto-generated method stub
        if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN
                || !Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
            throw new IllegalArgumentException("NFC类型不正确...");
        }
        try {
            byte[] payload = ndefRecord.getPayload();
            //把byte转为String
            String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8"
                    : "UTF-16";
            int languageCodeLength = payload[0] & 0077;
            String languageCode = new String(payload, 1, languageCodeLength,
                    "US-ASCII");
            String text = new String(payload, languageCodeLength + 1,
                    payload.length - languageCodeLength - 1, textEncoding);
            return text;
        } catch (UnsupportedEncodingException e) {
            // should never happen unless we get a malformed tag.
            throw new IllegalArgumentException(e);
        }
    }

}

三: 写入数据

写入数据最关键的就是创建一个NdefRecord对象,然后通过Ndef对象的writeNdefMessage(NdefMessage message)方法写入,当然前提还是要检测到设备,这里格式是TNF_WELL_KNOWN with RTD_TEXT,即写入文本字符串,如果你想写入其他数据,请参考官方文档

/**
     * 创建record,格式为TNF_WELL_KNOWN with RTD_TEXT
     *
     * @param payload      你要写入的数据
     * @param locale
     * @param encodeInUtf8 编码
     * @return
     */
public NdefRecord createTextRecord(String payload, Locale locale, boolean encodeInUtf8) {
    byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
    Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
    byte[] textBytes = payload.getBytes(utfEncoding);
    int utfBit = encodeInUtf8 ? 0 : (1 << 7);
    char status = (char) (utfBit + langBytes.length);
    byte[] data = new byte[1 + langBytes.length + textBytes.length];
    data[0] = (byte) status;
    System.arraycopy(langBytes, 0, data, 1, langBytes.length);
    System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
    NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
    NdefRecord.RTD_TEXT, new byte[0], data);
    return record;
}

生成需要的NdefMessage对象

   //textRecord就是上面生成的NdefRecord
    NdefMessage message = new NdefMessage(new NdefRecord[]{textRecord});

最后写入数据:

/**
     * 写入数据
     * @param message
     * @param tag  intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
     * @return
     */
    boolean writeTag(NdefMessage message, Tag tag) {
        int size = message.toByteArray().length;
        try {
            //链接nfc
            Ndef ndef = Ndef.get(tag);
            if (ndef != null) {
                ndef.connect();
                if (!ndef.isWritable()) {
Toast.makeText(this, "tag不允许写入", Toast.LENGTH_SHORT).show();

                    return false;
                }
                if (ndef.getMaxSize() < size) {
Toast.makeText(this,"文件大小超出容量", Toast.LENGTH_SHORT).show();

                    return false;
                }

                ndef.writeNdefMessage(message);
Toast.makeText(this,"写入数据成功.", Toast.LENGTH_SHORT).show();

                isWrite = false;
                finish();
                return true;
            } else {
                NdefFormatable format = NdefFormatable.get(tag);
                if (format != null) {
                    try {
                        format.connect();
                        format.format(message);
                        Toast.makeText(this,"格式化tag并且写入message", Toast.LENGTH_SHORT).show();

                        return true;
                    } catch (IOException e) {
                            Toast.makeText(this, "格式化tag失败.", Toast.LENGTH_SHORT).show();

                        return false;
                    }
                } else {
                        Toast.makeText(this, "Tag不支持NDEF", Toast.LENGTH_SHORT).show();

                    return false;
                }
            }
        } catch (Exception e) {
                Toast.makeText(this,"写入数据失败", Toast.LENGTH_SHORT).show();

        }

        return false;
    }

上述文章中源码git地址


参考博客:

Android 高级开发——NFC标签开发深度解析 (写的很不错) 该博客项目中源码git地址

Android NFC(二)

nfc-reader:https://github.com/nadam/nfc-reader (值得参考)

这两个差不多不知谁仿的谁,哈哈:

Android NFC开发教程(上)

猜你喜欢

转载自blog.csdn.net/da_caoyuan/article/details/79656990