当前位置: 首页 > 工具软件 > NFC Smart Tag > 使用案例 >

Android NFC

萧丁雨
2023-12-01

Near Field Communication(NFC)

NFC是一种短程无线技术,一般距离小于等于4cm时才能建立连接。NFC可以让你在一个NFC标签和一个有此功能的Android设备之间或两个Android设备之间交换少量载荷的数据。

根据复杂度的不同可以对NFC标签进行分类。
- 简单的标签只提供对短语的读写操作,有时会带有one-time-programmable区域使NFC卡只读
- 更复杂一些的标签能提供数学运算,并包含加密硬件以对标签的读取进行授权验证。
- 目前最成熟的标签包含一个运行环境,可以通过运行在标签上的代码完成复杂的交互操作。

存储在标签中的数据可以有很多种格式,但大部分Android API都是基于NFC Forum标准的,也被称为NDEF(NFC Data Exchange Format)

带有NFC功能的Android设备可以同时支持以下三种主要的操作模式:
1. 读/写模式 允许NFC设备读取/写入无源NFC标签和NFC贴纸
2. P2P模式 允许NFC设备与其他NFC对等方交换数据,该操作在Android Beam中使用
3. 模拟卡片模式 允许将NFC设备作为一个NFC卡片使用。模拟NFC卡片之后可以被其他NFC读取设备操作,如NFC POS机


NFC基础介绍

这部分描述了Android是如何发现NFC标签并如何将数据通知给应用程序的。该部分还会介绍如何处理NDEF数据,并简单介绍Android中支持基本NFC功能的API

高级NFC介绍

这部分介绍Android支持的各种标签技术相关的API。当你处理的不是NDEF数据,或者处理的是Android设备无法完全解析的NDEF数据时,你必须使用你自己的协议栈以原始的bytes数据来手动读写标签。在这些情况下,Android提供了对检测特定标签技术和使用自定义协议栈与标签通信的支持。

基于设备的卡片模拟介绍

这部分介绍了Android设备是如何在不使用安全单元的情况下模拟NFC卡片功能的,并让所有的Android应用程序都能模拟一个NFC卡片并与NFC读取器直接通信。

NFC基础

这部分介绍Android中能完成的基础NFC任务,解释如何以NDEF数据包的形式发送接收NFC数据并描述支持这些功能的API。对于更高级的主题,如与非NDEF数据交互的问题,见高级NFC

Android与NDEF数据交互主要有以下两个用例:
1. 从NFC标签中读取NDEF数据
2. Android设备之间通过 Android Beam 交换NDEF数据

从NFC标签中读取NDEF数据是由标签分发系统完成的,该系统将分析发现的NFC标签,合适地对数据进行分类,并启动一个对该数据感兴趣的应用。一个希望处理扫描到的NFC标签的应用可以通过在intent filter中声明并请求处理数据。

Android Beam功能允许通过将两个设备物理地贴合在一起以在设备之间推送NDEF数据。这种数据交互技术比蓝牙等其他无线技术更为便利,因为NFC无需手动地发现设备或对设备进行匹配。当两个设备的距离进入NFC检测范围时,连接就自动建立了。通过一系列的NFC API可以开发Android Beam,因此任何应用程序都能在设备之间传输信息,如联系人、浏览器和Youtube应用都能通过Android Beam与其他设备共享联系人信息、网页和视频。

标签分发系统


有NFC功能的Android设备一般在屏幕解锁时会自动寻找NFC标签,除非NFC功能在系统设备中被禁用了。当Android设备发现了一个NFC标签时,期望的行为应该是无需询问用户选择哪个应用就能自动启动最适合处理该intent的activity,因为设备扫描NFC标签的范围很短,如果让用户选择应用的话可能会使设备远离标签而断开连接。因此,你应该开发只处理你所关心的NFC标签的activity并避免出现选择应用选项。

为了帮助你完成这一目标,Android提供了一个特殊的标签分发系统来分析、解析扫描到的NFC标签并尝试找出对扫描到的数据感兴趣的应用。该系统是这样实现的:

  1. 解析NFC标签并弄清楚标识标签中有效载荷的MIME类型或URI。
  2. 将MIME类型或URI以及有效载荷包含进一个intent。前两个步骤在NFC标签是如何映射MIME类型和URI的中介绍。
  3. 根据该intent启动一个activity。这一步骤在NFC标签是如何分发到应用程序的中介绍。

NFC标签是如何映射MIME类型和URI的

在编写NFC应用前,理解NFC标签的不同类型、标签系统是如何解析NFC标签的、当检测到NDEF数据时标签分发系统做了哪些特殊工作是很重要的。NFC标签是多种技术融合的结果,也能以多种方式写入数据。Android对NFC Forum定义的NDEF标准支持的最多。

NDEF数据包含在一个信息包(NdefMessage)中,其中包含了多条记录(NdefRecord)。每个NDEF记录必须根据你所希望创建的记录类型的规范进行规范定义。Android同样支持不包含NDEF数据的其他标签类型,你可以通过android.nfc.tech包中的类来处理那些数据。更多内容见高级NFC,如果处理这类标签的数据,你还需要编写自己的协议栈来与这些标签通信,因此谷歌建议为了开发的便利和Android对NDEF的支持,最好还是使用NDEF。

提示: 下载完整的NDEF规范,前往NFC Forum Specifications & Application Documents并参考创建常见类型的NDEF记录来理解如何构造NDEF记录。

现在你应该有了NFC标签的一些背景知识,接下来的部分将会详细介绍Android如何处理NDEF格式的标签。当一个Android设备扫描到一个包含NDEF格式数据的NFC标签时,它会解析信息并尝试弄清楚数据的MIME类型或URI。为了完成这一目的,系统会读取NdefMessage中的第一个NdefRecord来决定如何解析整个NDEF信息(一个NDEF信息可以包含多个NDEF记录)。在一个规范定义的NDEF记录中,第一个NdefRecord包含以下字段:

  • 3-bit TNF(Type Name Format)

    表示如何解析Variable length type字段,有效值见表1

  • Variable length type

    描述记录的类型。如果使用了TNF_WELL_KNOWN,用这一字段定义Record Type Definition (RTD),有效的RTD值见表2

  • Variable length ID

    记录的唯一标识符。这一字段不经常使用,但如果你需要唯一地标识一个标签,你可以为其创建一个ID

  • Variable length payload

    你所希望读写的实际数据载荷。一个NDEF信息可以包含多个NDEF记录,因此不要认为全部的载荷都在NDEF信息的第一条NDEF记录中。

标签分发系统利用TNF及类型字段来尝试将NDEF信息映射到一个MIME类型或URI。如果映射成功的话,它会将信息连有效载荷一起包含到一个ACTION_NDEF_DISCOVERED intent中,但有可能分发系统根据第一个NDEF记录无法映射到一个MIME类型或URI,或者NFC标签并不包含NDEF数据,在这种情况下,一个包含标签技术信息以及有效载荷的Tag对象会被包含到一个ACTION_TECH_DISCOVERED intent中。

表1描述了标签分发系统是如何映射TNF和类型域到MIME类型或URI的,也描述了哪些TNF无法映射到一个MIME类型或URI,在这些情况下,系统转而创建ACTION_TECH_DISCOVERED intent。

例如,当系统遇到一个类型为TNF_ABSOLUTE_URI的记录时,它会将该记录的variable length type字段映射到一个URI,系统会将该URI和标签的其他信息——如有效载荷——包含进ACTION_NDEF_DISCOVERED intent 的数据域中。另一方面,如果系统遇到一个TNF_UNKNOWN类型的记录,它则会创建一个包含该标签技术的intent。

表1. 支持的TNF值及其映射

Type Name Format(TNF)Mapping
TNF_ABSOLUTE_URIURI based on the type field.
TNF_EMPTYFalls back to ACTION_TECH_DISCOVERED.
TNF_EXTERNAL_TYPEURI based on the URN in the type field. The URN is encoded into the NDEF type field in a shortened form: :. Android maps this to a URI in the form: vnd.android.nfc://ext/:.
TNF_MIME_MEDIAMIME type based on the type field.
TNF_UNCHANGEDInvalid in the first record, so falls back to ACTION_TECH_DISCOVERED.
TNF_UNKNOWNFalls back to ACTION_TECH_DISCOVERED.
TNF_WELL_KNOWNMIME type or URI depending on the Record Type Definition (RTD), which you set in the type field. See Table 2 for more information on available RTDs and their mappings.

表2. TNF_WELL_KNOWN所支持的RTD及其映射

Record Type Definition (RTD)Mapping
RTD_ALTERNATIVE_CARRIERFalls back to ACTION_TECH_DISCOVERED.
RTD_HANDOVER_CARRIERFalls back to ACTION_TECH_DISCOVERED.
RTD_HANDOVER_REQUESTFalls back to ACTION_TECH_DISCOVERED.
RTD_HANDOVER_SELECTFalls back to ACTION_TECH_DISCOVERED.
RTD_SMART_POSTERURI based on parsing the payload.
RTD_TEXTMIME type of text/plain.
RTD_URIURI based on payload.

NFC标签是如何分发到应用程序的

当标签分发系统创建了一个包含NFC标签及其标识信息的intent时,它会将该intent发送对其感兴趣(在intent filter中声明)的应用程序中。如果多个应用功能程序都能处理该intent,则调用选择应用选项让用户选择。标签分发系统定义三种intent,根据优先级的高低如下所示:

  1. ACTION_NDEF_DISCOVERED:当一个含有NDEF载荷的标签被扫描到并且是可标识的类型时,该intent用来启动一个activity。这是最高优先级的intent,只要可能,系统总会在其他intent前启动与此intent相关的activity。

  2. ACTION_TECH_DISCOVERED:如果没有任何应用程序注册ACTION_NDEF_DISCOVERED intent,系统则会尝试启动此intent的activity。如果扫描到的标签不含可以映射到MIME类型和URI的NDEF数据,或是已知的但非NDEF的标签技术,该intent也会直接启动(但不会先启动ACTION_NDEF_DISCOVERED)。

  3. ACTION_TAG_DISCOVERED:如果没有activity能处理ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED,则启动此intent。

标签分发系统的基本工作方式如下所示:

  1. 当解析一个NFC标签时,尝试启动一个与分发系统创建的intent(ACTION_NDEF_DISCOVERED 或 ACTION_TECH_DISCOVERED)相关的activity。
  2. 如果没有activity处理上述intent,尝试启动一个与下一个低优先级的intent相关的activity,直到找到一个activity或尝试完所有可能的intent为止。
  3. 如果没有应用程序处理任一一个intent,什么也不做。

图1

图1.标签分发系统

尽量操作NDEF信息和ACTION_NDEF_DISCOVERED intent,因为该intent是三种intent中最规范的一个,该intent也能比其他两种intent适时地启动你的应用程序,带来更好的用户体验。

在Android Manifest中请求NFC权限


在你使用NFC硬件并处理NFC intent之前,在AndroidManifest.xml中声明以下内容:

  • NFC标签以使用NFC硬件
    <uses-permission android:name="android.permission.NFC" />
  • 应用程序能支持的最低SDK版本,API 9只能通过ACTION_TAG_DISCOVERD支持有限的标签分发,并只能通过EXTRA_NDEF_MESSAGES操作NDEF数据,也没有其他标签属性或IO操作。API 10则包含了大量NDEF读写支持,API 14提供了一种通过Android Beam的更为便利的推送NDEF信息的方式并能更便利地创建NDEF 记录。
    <uses-sdk android:minSdkVersion="10" />
  • uses-feature标签以使你的应用只在有NFC功能的设备的Google Play中出现:
    <uses-feature android:name="android.hardware.nfc" android:required="true" />

如果你的应用使用了NFC功能但却不是应用的重要功能,你也可以省略uses-feature标签而在运行时检查NFC是否可用,通过检查getDefaultAdapter()是否为null来判断。

Filtering for NFC intents


为了在你所期望处理的NFC标签被扫描到时启动你的应用,你的应用可以在Android manifest中filter一个、两个或三个NFCintent。但为了在应用启动时能得到最好的控制,最好能filter ACTION_NDEF_DISCOVERED。ACTION_TECH_DISCOVERED则是在没有应用程序处理ACTION_NDEF_DISCOVERED或载荷不是NDEF时的次选。filter ACTION_TAG_DISCOVERED则太过于宽泛,大部分应用程序会在ACTION_TAG_DISCOVERED之前filter ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED,因此你的应用启动的机会会很小。只有在没有应用程序处理ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED时才会发送ACTION_TAG_DISCOVERED。

ACTION_NDEF_DISCOVERED

为了filter ACTION_NDEF_DISCOVERED,声明该intent以及期望处理的数据类型。下例是对MIME类型为text/plain的ACTION_NDEF_DISCOVERED intent的filter:

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

下例是以http://developer.android.com/index.html的形式对一个URI intent的filter:

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

ACTION_TECH_DISCOVERED

如果你的activity想filter ACTION_TECH_DISCOVERED intent的话,你必须创建一个XML资源文件,并在tech-list集合中指明你的activity所支持的技术。如果一个tech-list集合是标签所支持的技术的子集,该activity则可以认为是匹配的,你可以通过调用getTechList()来获得此标签支持的技术。

例如,扫描到的标签支持MifareClassic、NdefFormatable以及NfcA,你的tech-list集合必须指明所有三种、其中两种或一种技术(以及无任何技术)来让你的activity匹配这个标签。

下例定义了所有的技术,你可以去除你不需要的。将此文件(任意名称)保存在\/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.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()返回的技术集合的子集时,activity就是匹配的。这为匹配规则定义了AND和OR语义。下例表明可以匹配同时支持NfcA和Ndef或同时支持NfcB和Ndef技术的标签。

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

    <resources xmlns="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>

在你的AndroidManifest.xml文件中,在\标签中的\标签中指定你刚创建的资源文件:

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

更多关于操作标签技术以及ACTION_TECH_DISCOVERED intent的信息,见高级NFC中的working with supported tag technologies

ACTION_TAG_DISCOVERED

使用下例方法来filter ACTION_TAG_DISCOVERED

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

从intent中获取信息

如果一个activity是从一个NFC intent启动的,你可以从该intent中获取到关于扫描到的NFC标签的信息。根据扫描到的标签,intent中可以包含下列数据:

  • EXTRA_TAG(必需):代表扫描到的标签的Tag对象
  • EXTRA_NDEF_MESSAGES:从标签中解析出的NDEF消息数组。在ACTION_NDEF_DISCOVERED intent中这一字段是必需的
  • EXTRA_ID:标签的低级ID

为了获得这些数据,检查你的activity是否从某个NFC intent启动的,以保证标签能够扫描到。下例检查ACTION_NDEF_DISCOVERED intent以从intent中获得NDEF信息。

    @Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    ...
    if (intent != null && 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中获取Tag对象,其中包含了有效载荷并可以枚举出所有的标签技术。

    Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

创建常见类型的NDEF记录


这部分描述如何创建常见的集中NDEF记录以帮助你向NFC标签中写入信息或者通过Android Beam发送数据。从Android 4.0(API 14)开始,createUri()方法可以用来自动创建URI记录。从Android 4.1(API 16)开始,createExternal()和createMime()方法可以用来创建MIME和外部类型的NDEF记录。尽量使用这些辅助性方法以避免手动创建NDEF记录可能出现的错误。

这部分还描述了如何为记录创建对应的intent filter,下述所有NDEF记录示例都必须包含在你所写入或发送的NDEF信息的第一个NDEF记录中。

TNF_ABSOLUTE_URI

注: 建议使用RTD_URI而非TNF_ABSOLUTE_URI,因为更有效

你可以根据以下方式创建一个TNF_ABSOLUTE_URI NDEF记录

    NdefRecord uriRecord = new NdefRecord(
        NdefRecord.TNF_ABSOLUTE_URI,
        "http://developter.android.com/index.html".getBytes(Charset.forName("US-ASCII")),
        new byte[0], new byte[0]);

该NDEF记录对应的intent filter如下:

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

TNF_MIME_MEDIA

你可以根据以下方式创建TNF_MIME_MEDIA:

使用createMime()方法:

    NdefRecord mimeRecord = NdefRecord.createMime("application/vnd.com.example.android.beam",
    "Beam me up, Android".getBytes(Charset.forName("US_ASCII")));

手动创建NdefRecord:

    NdefRecord mimeRecord = new NdefRecord(
        NdefRecord.TNF_MIME_MEDIA,
        "application/vnd.com.example.android.beam".getBytes(Charset.forName("US_ASCII")),
        new byte[0], "Beam me up, Android!".getBytes(Charset.forName("US_ASCII")));

上述NDEF记录对应的intent filter为:

    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="application/vnd.com.example.android.beam" />
    </intent-filter>

带RTD_TEXT的TNF_WELL_KNOWN

你可以根据以下方式创建TNF_WELL_KNOWN NDEF记录:

    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;
}

对应该NDEF记录的intent filter如下:

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

带RTD_URI的TNF_WELL_KNOWN

你可以根据以下方式创建TNF_WELL_KNOWN记录:

使用createUri(String)方法:

    NdefRecord rtdUriRecord1 = NdefRecord.createUri("http://example.com");

使用createUri(Uri)方法:

    Uri uri = new Uri("http://example.com");
    NdefRecord rtdUriRecord2 = NdefRecord.createUri(uri);

手动创建NdefRecord:

    byte[] uriField = "example.com".getBytes(Charset.forName("US_ASCII"));
    byte[] payload = new byte[uriField.length + 1];     // 加1作为URI前缀
    byte payload[0] = 0x01;     // 在URI前加上http:www.前缀
    System.arraycopy(uriField, 0, payload, 1, uriField.length);
    NdefRecord rtdUriRecord = new NdefRecord(
        NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, 
        new byte[0], payload);

上述NDEF记录对应的intent filter如下:

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

TNF_EXTERNAL_TYPE

你可以根据以下方式创建TNF_EXTERNAL_TYPE:

使用createExternal()方法:

    byte[] payload;     // 你的数据
    String domain = "com.example";      // 通常是你的app包名
    String type = "externalType";
    NdefRecord extRecord = NdefRecord.createExternal(domain, type, payload);

手动创建NdefRecord:

    byte[] payload;
    ...
    NdefRecord extRecord = new NdefRecord(
        NdefRecord.TNF_EXTERNAL_TYPE, "com.example:externalType", new byte[0], payload);

对应上述NDEF记录的intent filter为:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="vnd.android.nfc"
    android:host="ext"
    android:pathPrefix="/com.example:externalType" />
</intent-filter>

为通用的NFC标签使用TNF_EXTERNAL_TYPE能更好地支持Android和非Android设备。

注: TNF_EXTERNAL_TYPE的URN有一个标准格式 urn:nfc:ext:example.com:externalType,但NFC Forum RTD规范认为URN的 urn:nfc:ext: 部分应被省略。因此你所需要提供的是域名(此例中是example.com)以及类型(此例中是externalType)。当分发TNF_EXTERNAL_TYPE时,Android会将 urn:nfc:ext:example.com:externalType URN转换为 vnd.android.nfc://ext/example.com:externalType URI,该URI也是例子中的intent-filter声明的。

Android应用记录

从Android 4.0(API 14)开始,当扫描到一个NFC标签时,Android应用记录(AAR)更能确保应用能够启动。一个AAR在NDEF记录中包含了应用的包名,你可以将AAR添加到NDEF消息中的任一个NDEF记录中,因为Android会在整个NDEF消息中搜索AAR。如果发现了一个AAR,Android将根据AAR中的包名启动应用。如果设备中没有该应用,将启动Google Play以下载该应用。

如果你希望防止在扫描到特定标签时启动其他应用,AAR是非常有用的。AAR只在应用层支持,因为包名的限制,并且不在intent filtering的Activity层。如果你希望在Activity层处理intent,使用intent filter

如果标签包含AAR,标签分发系统会根据以下方式分发:

  1. 正常使用intent filter尝试启动一个Activity,如果匹配该intent的Activity同样匹配AAR,启动该Activity。
  2. 如果匹配该intent的Activity不匹配AAR,或者多个Activity能够处理该intent,或者没有Activity能处理该intent,根据AAR中指定的包名启动应用。
  3. 如果没有该包名的应用,前往Google Play下载该应用。

注: 你可以重写AAR以及有前台分发系统的intent分发系统,这可以让前台应用在发现一个NFC标签时能拥有优先权。使用这个方法,Activity必须处于前台以重写AAR和intent分发系统。

如果你仍然希望filter不包含AAR的标签,你可以按普通的方式声明intent-filter。例如,也许你不仅希望应用能保证处理你自己部署的标签,也需要处理第三方部署的标签。要记住AAR在Android 4.0及以后才可用,因此当你部署标签时,你可能结合使用AAR和MIME类型/URI来支持更多的设备。并且在你部署NFC标签时,思考一下你应该如何写入NFC标签来支持大多数设备(Android设备及其他设备)。一个可行方法是定义一个相对比较独特的MIME类型或URI来使你的应用更容易区分。

Android提供了创建AAR的简单API,createApplicationRecord()。你所需要做的就是将AAR嵌入到NdefMessage的任一位置。一般你不应使用NdefMessage中的第一条记录,除非AAR是NdefMessage中的唯一记录。这是因为Android系统会检查NdefMessage中的第一条记录来确定标签的MIME类型或URI,以创建相应的intent。以下代码告诉你应该如何创建一个AAR:

    NdefMessage msg = new NdefMessage(
        new NdefRecord[] {
          ...,
          NdefRecord.createApplicationRecord("com.example.android.beam")});

Android Beam


Android Beam可以在Android设备之间进行简单地P2P数据交换。希望发送数据的应用必须处于前台,而接收数据的设备必须是解锁状态。当发送设备与接收设备距离近到一定范围时,发送设备会跳出“触摸以发送”界面,用户则可以选择是否向接收设备发送数据。

注: API 10提供了一种类似Android Beam功能的前台NDEF推送API,现在这些API已经被遗弃了,但依然可以用来支持一些老设备。阅读enableForegroundNdefPush()获取更多信息。

你可以通过调用下列方法之一来让你的应用使用Android Beam:

  • setNdefPushMessage(): 接受一个NdefMessage来作为要发送的信息。当设备之间足够接近时自动将信息发送出去。
  • setNedfPushMessageCallback():接受一个createNdefMessage()方法作为回调,当设备之间足够接近时调用该方法,该回调方法可以让你只在必要时创建NDEF信息。

一个activity一次只能发送一个NDEF信息,因此如果同时设置了以上两种方法时,则只调用setNdefPushMessageCallback()。使用Android Beam时,还必须要满足以下通用规范:
- 发送数据的activity必须处于前台,两个设备必须都要处于解锁状态。
- 必须要将要发送的数据包含在NdefMessage对象中。
- 接收数据的设备必须支持com.android.npp NDEF推送协议或NFC Forum的SNEP(simple NDEF Exchange Protocol)。对于API 9(Android 2.3)到API 13(Android 3.2)的设备而言,需要支持com.android.npp协议,API 14(Android 4.0)及以后的设备需要同时支持com.android.npp和SNEP。

为了启动Android Beam:

  1. 创建一个包含你希望发送的NdefRecord的NdefMessage。
  2. 在activity的onCreate()方法中用NdefMessage来调用setNdefPushMessage()方法或通过传一个NdefAdapter.CreateNdefMessageCallback对象来调用setNdefPushMessageCallback()。一般来说,如果你的activity每次都只推送同一个NDEF message的话,只需要使用setNdefPushMessage()。而当你所要发送的消息与应用当前的context有关或消息取决于用户的操作时,使用setNdefPushMessageCallback()方法。

下面这段代码展示了一个简单的activity如果在onCreate()方法中调用NfcAdapter.CreateNdefMessageCallback(完整的例子见AndroidBeamDemo)。这段代码还展示了如何创建一个MIME记录:

package com.example.android.beam;

import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.TextView;
import android.widget.Toast;
import java.nio.charset.Charset;


public class Beam extends Activity implements CreateNdefMessageCallback {
    NfcAdapter mNfcAdapter;
    TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView textView = (TextView) findViewById(R.id.textView);
        // Check for available NFC Adapter
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (mNfcAdapter == null) {
            Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
        // Register callback
        mNfcAdapter.setNdefPushMessageCallback(this, this);
    }

    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        String text = ("Beam me up, Android!\n\n" +
                "Beam Time: " + System.currentTimeMillis());
        NdefMessage msg = new NdefMessage(
                new NdefRecord[] { createMime(
                        "application/vnd.com.example.android.beam", text.getBytes())
         /**
          * The Android Application Record (AAR) is commented out. When a device
          * receives a push with an AAR in it, the application specified in the AAR
          * is guaranteed to run. The AAR overrides the tag dispatch system.
          * You can add it back in to guarantee that this
          * activity starts when receiving a beamed message. For now, this code
          * uses the tag dispatch system.
          */
          //,NdefRecord.createApplicationRecord("com.example.android.beam")
        });
        return msg;
    }

    @Override
    public void onResume() {
        super.onResume();
        // Check to see that the Activity started due to an Android Beam
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            processIntent(getIntent());
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        // onResume gets called after this to handle the intent
        setIntent(intent);
    }

    /**
     * Parses the NDEF Message from the intent and prints to the TextView
     */
    void processIntent(Intent intent) {
        textView = (TextView) findViewById(R.id.textView);
        Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
                NfcAdapter.EXTRA_NDEF_MESSAGES);
        // only one message sent during the beam
        NdefMessage msg = (NdefMessage) rawMsgs[0];
        // record 0 contains the MIME type, record 1 is the AAR, if present
        textView.setText(new String(msg.getRecords()[0].getPayload()));
    }
}

这里将AAR部分注释了,当然你也可以去除注释。如果你使用了AAR,AAR所指定的应用永远会接收Android Beam信息。如果没有该应用则会启动Google Play下载该应用。因此如果使用AAR的话,下面这段intent-filter也不是必需的:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="application/vnd.com.example.android.beam" />
</intent-filter>

使用这个intent-filter,当扫描到一个NFC标签,或接收到一个AAR为com.example.android.beam的Android Beam,或NDEF信息包含MIME记录类型为application/vnd.com.example.android.beam时,应用com.example.android.beam都会启动。

即使AAR保证了应用会被启动或被下载,还是建议你加上intent-filter,因为这可以让你根据应用中的配置启动一个activity而不是AAR指定的包中的main activity。AAR并不关心activity的等级问题。而且,由于一些Android设备并不支持AAR,以防万一,你同样应该在NDEF信息的第一个NDEF记录中包含标识信息并filter该信息。见创建类型的NDEF记录获取更多关于创建记录的信息。

高级NFC

基于设备的卡片模拟

翻译自:https://developer.android.com/guide/topics/connectivity/nfc/nfc.html

 类似资料:

相关阅读

相关文章

相关问答