安卓 NFC 功能使用小结

最近整理接手项目代码,其中有用到 NFC 功能,今天抽了个时间了解了一下,简单使用应该不难,高级扩展我就不知道喽,下面小结一番!

知识了解

网上看到一篇博客介绍 NFC 功能的,分了理论篇和实践篇,理论篇就是大致了解一下 NFC 这个功能,我看了觉得有了挺详细的概念,但是他那篇文章写的有点乱,后面看了下官方文档,发现官方文档也不错,而且有中文版,其实也是该先看官方文档:

https://developer.android.google.cn/guide/topics/connectivity/nfc

下面是参考的那篇博客:

东西太多了,其实我们只要掌握如何使用就可以的,下面讲解,有两种方式。

前台使用

1、配置权限

配置AndroidMenifest.xml:

<!--NFC权限-->
<uses-permission android:name="android.permission.NFC" />
<!-- 要求当前设备必须要有NFC芯片 -->
<uses-feature android:name="android.hardware.nfc" android:required="true" />
2、获取 NfcAdapter 对象
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate();
        mNfcAdapter= NfcAdapter.getDefaultAdapter(this);//设备的NfcAdapter对象
        if(mNfcAdapter==null){//判断设备是否支持NFC功能
            Toast.makeText(this,"设备不支持NFC功能!",Toast.LENGTH_SHORT);
            finish();
            return;
        }
        if (!mNfcAdapter.isEnabled()){//判断设备NFC功能是否打开
            Toast.makeText(this,"请到系统设置中打开NFC功能!",Toast.LENGTH_SHORT);
            finish();
            return;
        }
        mPendingIntent=PendingIntent.getActivity(this,0,new Intent(this,getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),0);//创建PendingIntent对象,当检测到一个Tag标签就会执行此Intent,设置 SINGLE_TOP 标志,只打开当前活动
    }

这里还是在 onCreate 中写吧!

3、开启/关闭前台发布系统
	//页面获取焦点
	@Override
    protected void onResume() {
        super.onResume();
        if (mNfcAdapter!=null){          mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,null,null);//打开前台发布系统,使页面优于其它nfc处理.当检测到一个Tag标签就会执行mPendingItent
        }
    }

	//页面失去焦点
	@Override
    protected void onPause() {
        super.onPause();
        if(mNfcAdapter!=null){
            mNfcAdapter.disableForegroundDispatch(this);//关闭前台发布系统
        }
    }

只在当前页面显示的时候使用 NFC,是很合理的。

4、获取 TAG 对象
	@Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        mTag=intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);//获取到Tag标签对象
        String[] techList=mTag.getTechList();
        System.out.println("标签支持的tachnology类型:");
        for (String tech:techList){
            System.out.println(tech);
        }
    }

因为设置的 SINGLE_TOP 标志,所以在 onNewIntent 中获取 TAG,类型就是 TAG。

解析 TAG 数据

这里要看我们支持的技术标准了,看下面图:

ClassDescription
TagTechnology这个接口是下面所有tag technology类必须实现的。
NfcA支持ISO 14443-3A 标准的操作。Provides access to NFC-A (ISO 14443-3A) properties and I/O operations.
NfcBProvides access to NFC-B (ISO 14443-3B) properties and I/O operations.
NfcFProvides access to NFC-F (JIS 6319-4) properties and I/O operations.
NfcVProvides access to NFC-V (ISO 15693) properties and I/O operations.
IsoDepProvides access to ISO-DEP (ISO 14443-4) properties and I/O operations.
Ndef提供对那些被格式化为NDEF的tag的数据的访问和其他操作。 Provides access to NDEF data and operations on NFC tags that have been formatted as NDEF.
NdefFormatable对那些可以被格式化成NDEF格式的tag提供一个格式化的操作
MifareClassic如果android设备支持MIFARE,提供对MIFARE Classic目标的属性和I/O操作。
MifareUltralight如果android设备支持MIFARE,提供对MIFARE Ultralight目标的属性和I/O操作。

可以通过 Tag 对象的 getTechList() 取到标签的技术类型,下面写一个 NDEF 格式读取:

//读取Ndef标签中数据
    private void readNdef(){
        if (mTag==null){
            Toast.makeText(this,"不能识别的标签类型!",Toast.LENGTH_SHORT);
            finish();
            return;
        }
        Ndef ndef=Ndef.get(mTag);//获取ndef对象
        try {
            ndef.connect();//连接
            NdefMessage ndefMessage=ndef.getNdefMessage();//获取NdefMessage对象
            if (ndefMessage!=null)               readEdt.setText(parseTextRecord(ndefMessage.getRecords()[0]));
            Toast.makeText(this,"数据读取成功!",Toast.LENGTH_SHORT);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (FormatException e) {
            e.printStackTrace();
        }finally {
            try {
                ndef.close();//关闭链接
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解析NDEF文本数据,从第三个字节开始,后面的文本数据
     * @param ndefRecord
     * @return
     */
    public static String parseTextRecord(NdefRecord ndefRecord) {
        /**
         * 判断数据是否为NDEF格式
         */
        //判断TNF
        if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) {
            return null;
        }
        //判断可变的长度的类型
        if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
            return null;
        }
        try {
            //获得字节数组,然后进行分析
            byte[] payload = ndefRecord.getPayload();
            //下面开始NDEF文本数据第一个字节,状态字节
            //判断文本是基于UTF-8还是UTF-16的,取第一个字节"位与"上16进制的80,16进制的80也就是最高位是1,
            //其他位都是0,所以进行"位与"运算后就会保留最高位
            String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16";
            //3f最高两位是0,第六位是1,所以进行"位与"运算后获得第六位
            int languageCodeLength = payload[0] & 0x3f;
            //下面开始NDEF文本数据第二个字节,语言编码
            //获得语言编码
            String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
            //下面开始NDEF文本数据后面的字节,解析出文本
            String textRecord = new String(payload, languageCodeLength + 1,
                    payload.length - languageCodeLength - 1, textEncoding);
            return textRecord;
        } catch (Exception e) {
            throw new IllegalArgumentException();
        }
    }

如果想要详细使用,可以看看官方文档,到这应该就获取到数据了。

Intent 形式使用

Intent 形式就是通过 intent-filter 来匹配 NFC 的 intent 实现获取数据的,流程和上面类似:

1、权限配置

同上

2、AndroidManifest.xml相关配置
<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.nfc.action.TAG_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
    </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" />
</activity>

这里就是三个 NCF 相关的 intent-filter,并指定我们的 technologies 列表。

3、配置 technologies 列表
<resources>
    <!-- 可以处理所有Android支持的NFC类型 -->
    <tech-list>
        <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.IsoDep</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>

因为我们的 intent-filter 里面用到了 TECH_DISCOVERED,它要求我们在资源文件里指定要支持technologies 列表。

4、获取 NfcAdapter 对象
// 1.初始化NFC适配器
private void initNfc() {
    // 获取系统默认的NFC适配器
    mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    if (mNfcAdapter == null) {
        tv_nfc_result.setText("当前手机不支持NFC");
    } else if (!mNfcAdapter.isEnabled()) {
        tv_nfc_result.setText("请先在系统设置中启用NFC功能");
    } else {
        tv_nfc_result.setText("当前手机支持NFC");
    }
}

和上面一样在 onCreate 方法里获得 NfcAdapter 对象。

5、检查 NFC Intent 启动
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        ...
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
            Parcelable[] rawMessages =
                intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            if (rawMessages != null) {
                NdefMessage[] messages = new NdefMessage[rawMessages.length];
                for (int i = 0; i < rawMessages.length; i++) {
                    messages[i] = (NdefMessage) rawMessages[i];
                }
                // Process the messages array.
                ...
            }
        }
    }

Intent 可以包含以下 extra,具体取决于扫描到的标签:

  • EXTRA_TAG(必需):一个 Tag 对象,表示扫描到的标签。
  • EXTRA_NDEF_MESSAGES(可选):从标签中解析出的一组 NDEF 消息。此 extra 对于 ACTION_NDEF_DISCOVERED Intent 而言是必需的。
  • EXTRA_ID(可选):标签的低级别 ID。

这里拿的官网的代码,通过 intent-filter 打开活动可能在 onCreate 中,也可能在 onNewIntent 中,同时需要对 action 进行一次判断。

6、获取 TAG 对象
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

这里可以在 Intent 中,拿到 TAG 对象。

7、解析 TAG 数据

这里换一个,写一下 MifareClassic 类的。

 	// 读取小区门禁卡信息
    public String readGuardCard(Tag tag) {
        MifareClassic classic = MifareClassic.get(tag);
        for (String tech : tag.getTechList()) {
            Log.d(TAG, "当前设备支持" + tech); //显示设备支持技术
        }
        String info;
        try {
            classic.connect(); // 连接卡片数据
            int type = classic.getType(); //获取TAG的类型
            String typeDesc;
            if (type == MifareClassic.TYPE_CLASSIC) {
                typeDesc = "传统类型";
            } else if (type == MifareClassic.TYPE_PLUS) {
                typeDesc = "增强类型";
            } else if (type == MifareClassic.TYPE_PRO) {
                typeDesc = "专业类型";
            } else {
                typeDesc = "未知类型";
            }
            info = String.format("\t卡片类型:%s\n\t扇区数量:%d\n\t分块个数:%d\n\t存储空间:%d字节",
                    typeDesc, classic.getSectorCount(), classic.getBlockCount(), classic.getSize());
        } catch (Exception e) {
            e.printStackTrace();
            info = e.getMessage();
        } finally { // 无论是否发生异常,都要释放资源
            try {
                classic.close(); // 释放卡片数据
            } catch (Exception e) {
                e.printStackTrace();
                info = e.getMessage();
            }
        }
        return info;
    }
 
    public static String ByteArrayToHexString(byte[] bytesId) {
        int i, j, in;
        String[] hex = {
                "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
        };
        String output = "";
 
        for (j = 0; j < bytesId.length; ++j) {
            in = bytesId[j] & 0xff;
            i = (in >> 4) & 0x0f;
            output += hex[i];
            i = in & 0x0f;
            output += hex[i];
        }
        return output;
    }

下面是MifareClassic类的方法说明:

  • get:从Tag对象中获取卡片对象的信息。该方法为静态方法。
  • connect:链接卡片数据。
  • close:释放卡片数据。
  • getType:获取卡片的类型。TYPE_CLASSIC表示传统类型,TYPE_PLUS表示增强类型,
  • TYPE_PRO表示专业类型。
  • getSectorCount:获取卡片的扇区数量。
  • getBlockCount:获取卡片的分块个数。
  • getSize:获取卡片的存储空间大小,单位字节。

结语

这里还是推荐官方文档吧,内容全一点,上面两种方式应该都可以获取到数据,当然只是简单的使用了,复杂的还得自己研究。

end

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值