Android NFC开发

前言

  近场通信(NFC)是一种NXP半导体公司提出来的被动技术,它可以在支持NFC的设备上使用“tag”。它是一种有效距离非常小的无线技术,这个距离大概是4cm,但是也可以到10cm,这取决于设备的无线电波和tag的大小。

  不像蓝牙的Beacon,NFC tag不需要电源。这使它在半永久的地方使用非常合理,而且可以作为一个媒介来使任务自动化,或者把信息分发到一系列的位置。

NFC之NDEF

  信息以比特数据的形式保存在NDEF(NFC数据交换格式)信息里。每个NDEF消息包含至少一个NDEF记录,该记录包含以下字段:

  • 3 bit类型名格式(TNF)
  • 变量长度类型
  • 变量长度ID(可选)
  • 变量长度有效负载

  TNF字段包含的值是给Android系统用来判断如何处理剩下的NDEF消息中的信息的。剩下的数据通常包含在一个物理的tag内部。但是,使用类似Android Beam的技术,一个设备本身可以可以承担物理tag的角色。

  请注意,不是所有的NFC tag都可以在Android设备内部的NFC读取器不完全兼容。根据NFC论坛的定义,有以下类型的NFC tag:

  • Type 1 : 基于ISO/IEC 14443A,可读可写,能设置为只读,而且只有96B的空间,但是可以扩展到2KB。
  • Type 2 : 基于ISO/IEC 14443A,可读可写,能设置为只读,而且由48B的空间,但是可以扩展到2KB。
  • Type 3 : 基于(JIS) X 6319-4,预设值成可读和可写或者只读,而且内存最多可以到1MB。
  • Type 4 :与ISO/IEC 14443兼容,预设值成可读和可写或者只读,而且内存最多可以到32KB。
  • MIFARE Classic : 与ISO/IEC 14443兼容,可读可写,能设置成只读,而且有1KB或者4KB可用空间。

Android中使用NFC

  这些是最通用的tag类型,但是有些正在流通的NFC tag不符合NFC论坛的标准。这些tag不能确保与所有的NFC硬件兼容。根据设备制造商的不同,你可以发现一些Android设备可以读那些tag,但是其他的却不行。MIFARE传统是这种tag的一个例子,有些Android设备读取或者写不了它。知道这一点很重要的。因为它可能让一些用户感觉很困惑,尤其当他们换了设备突然发现一些tag在新设备上不工作了。

  在应用中使用NFC要求有NFC权限。若要把这个权限加到应用中,可以打开manifest XML文件,然后加入以下的代码:

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

  另外,你也可以把<users-feature />元素加到manifest中,这样Google Play Store可以对应用进行过滤,以致没有NFC的设备没法下载它。这是可选的,但是它可以使你不让用户失望。把下列代码加到应用manifest XML中可以开启Google Play Store的过滤功能:

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

  当你的设备扫描tag的时候,它会读取存在于TNF中的数据,并判断tag的MIME类型或URL。内部的tag分发系统用来判断tag是否是兼容的、空的,或者它应该是在一个特定的应用里打开。决定使用哪个应用打开依赖于创建的Intent,然后与适合的Activity进行匹配。
  
  如果你的应用要响应Intent,则需要过滤一个或者多个下面的Intent:

  • ACTION_NDEF_DISCOVERED
  • ACTION_TECH_DISCOVERED
  • ACTION_TAG_DISCOVERED

ACTION_NDEF_DISCOVERED

  如果要过滤这个Intent,可以基于MIME类型或者在URI上实现。下面是一个对MIME类型的text/plain的进行过滤:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

  过滤URI的方法类似,但是要把<data>元素的属性android:mimetype改成android:scheme,而且还需要添加一些属性。下面是一个过滤http://www.android.com/index.html的URI的例子:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:scheme="http"
        android:host="www.android.com"
        android:pathPrefix="/index.html" />
</intent-filter>

ACTION_TECH_DISCOVERED

  当过滤这个Intent的时候,需要创建一个包含所有你想监控的技术类型的资源XML文件。这可确保当扫描到一个tag的时候,你的应用只在tag包含期望的技术的时候才打开。这个文件应该存在于工程的/res/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>

  为了引用到你的XML技术列表,需要加一个<meta-data>的标签到应用manifest XML中,这回包含你的资源列表的路径。下面是一个使用需要引用到ACTION_TECH_DISCOVERED<intent-filter><meta-data>的例子:

<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" />

  请注意,<meta-data>元素中使用的资源路径由一个属性值为@xml/nfc_tech_filter。这个值指向工程中的文件/res/xml/nfc_tech_filter.xml

ACTION_TAG_DISCOVERED

  ACTION_TAG_DISCOVERED是最后一个Intent,也可能是最容易实现的了。因为你不需要用到过滤的tag包含的类型技术或者信息的时候,可以使用如下<intent-filter>

<intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>

  读/写信息到NFC tag的时候,要求你定义自己的协议栈。下面的代码演示了如何使用相当普遍的MIFARE Ultralight tag

public class MifareUltralightTagTester {
    private static final String TAG =
    MifareUltralightTagTester.class.getSimpleName();
    // 写到tag中:
    public void writeTag(Tag tag, String tagText) {
        MifareUltralight ultralight = MifareUltralight.get(tag);
        try {
            ultralight.connect();
            ultralight.writePage(4, "abcd".getBytes(Charset.forName("US-ASCII")));
            ultralight.writePage(5, "efgh".getBytes(Charset.forName("US-ASCII")));
            ultralight.writePage(6, "ijkl".getBytes(Charset.forName("US-ASCII")));
            ultralight.writePage(7, "mnop".getBytes(Charset.forName("US-ASCII")));
        } catch (IOException e) {
            Log.e(TAG, "IOException while closing MifareUltralight", e);
        } finally {
            if (ultralight != null) {
                try {
                    ultralight.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException while closing MifareUltralight", e);
                }
            }
        }
    }
    // 读取tag信息:
    public String readTag(Tag tag) {
        MifareUltralight mifare = MifareUltralight.get(tag);
        try {
            mifare.connect();
            byte[] payload = mifare.readPages(4);
            return new String(payload, Charset.forName("US-ASCII"));
        } catch (IOException e) {
            Log.e(TAG, "IOException while writing MifareUltralight message", e);
        } finally {
            if (mifare != null) {
                try {
                    mifare.close();
                }
                catch (IOException e) {
                    Log.e(TAG, "Error closing tag", e);
                }
            }
        }
        return null;
    }
}

  你可能想知道这段代码是如何工作的,比如,当附近由一个tag的时候,你已经定义了一个Intent去触发。如果没有这个问题的解决方案,每次你把tag放在手机旁边,它不会写而会不断地读这个tag。这是Foreground Dispatch System的由来。
  Foreground Dispatch System允许你破解一个Intent而阻止它去正常情况下要去的地方。它要求在应用的onCreate()方法中加一个PendingIntent,以及在onPause()里使用disableForegroundDispatch()以及在onResume()函数里调用enableForegroundDispatch()。最后,必须要创建一个方法来处理来自扫描NFC tag的数据。
  下面演示了如何使用Foreground Dispatch System

@Override
protected void onCreate(Bundle savedInstanceState) {
    // 函数代码在这里
    PendingIntent pendingIntent = PendingIntent.getActivity(
        this, 0, new Intent(this, getClass())
        .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    // 添加一个IntentFilter来告之拦截的是什么。
    IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
    try {
        // 这会捕获所有的MIME
        ndef.addDataType("*/*");
    } catch (MalformedMimeTypeException e) {
        throw new RuntimeException("fail", e);
    }
    intentFiltersArray = new IntentFilter[] { };
    // techListsArray是用来创建你讲支持的技术列表
    // 这是开启前台分发时使用的。
    techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
}
@Override
public void onPause() {
    super.onPause();
    // 释放以恢复默认的扫描行为
    myAdapter.disableForegroundDispatch(this);
}
@Override
public void onResume() {
    super.onResume();
    // 开启以拦截默认的扫描行为
    myAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray,
        techListsArray);
}
public void onNewIntent(Intent intent) {
    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    // 这里的逻辑用来处理tagFromIntent
}

附录

  • Android Development Patterns-Best Practices for Professional Developers
    • [美]Phil Dutson
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值