Android NFC问道

概述

NFC(Near Field Communication),无线近场通讯技术。

  • 技术指标
  • 初始连接距离 :<= 4cm
  • 通讯距离 : 10cm (理论)
  • 射频频率 : 13.56MHz
  • 传输速率 :106 / 212 / 424 kbps

应用

Bridging The Physical and Virtual World,NFC 是现实世界与虚拟世界的桥梁 -- Google IO

  • 生活中的 NFC应用

  • 读写信息,通过 NFC 读取海报上的电子标签(射频识别信息)等

  • 数据交换,不同 NFC设备间建立连接后进行数据交换,手机间发送图片,音乐等,和蓝牙类似

  • 内置卡片,手机 NFC 功能支持公交刷卡,门禁刷卡等

  • 基于此,Android NFC支持三种功能操作模式

  • 读卡器模式(Reader/Writer Mode) ,允许 NFC设备读/写电子标签(RFID,射频识别)

  • P2P模式(P2P Mode),建立连接的 NFC 设备间进行端到端的数据交换/传输

  • 模拟卡模式(Card Emulation Mode),允许 NFC设备内置IC卡信息,充当 IC 卡,模拟卡模式供电方式由读卡器的 供电,手机关机时亦可工作

开发与运用

NFC基础

简介在 Android 平台上运行的 NFC 任务,及其如何从以NDEF ( 用于 NFC数据交互的标准数据格式 ) 格式接收或发送 NFC 数据,与支持其操作的 Android API
官文 :https://developer.android.com/guide/topics/connectivity/nfc/nfc.html

  • NDEF两种主要用途
  • 基于 Tag调度系统(Tag Dispatch System )从 NFC tag 中读取 NDEF 数据
  • 基于 Android Beam 从一台设备向另一台设备播送 NDEF 信息

Tag调度系统

开启了 NFC 功能的设备,在亮屏状态下,会在允许范围能定时监测 NFC tag。当成功捕捉到 NFC tag 时,由于 NFC 的短距离检测性质,Tag调度系统会立即在最合适的 Activity 中将 tag 封装于 intent 中,不发出允许请求或者寻找将要使用的NFC的 Activity,以防 NFC tag 在距离增加时无法捕捉。

  • 解析 tag 信息及将 tag 发送到指定应用
  • 解析标识 MIME 或 URI 的信息
  • 将 MIME/URI 及其他信息封装到 intent
  • 基于 intent 启动指定 activity, 将 tags分配给应用程序
    详见下文

NFC tag 如何映射 MIME 和 URI

类似计算机网络,NFC 的无线数据交互亦基于协议完成,什么协议官文并未给出定义,但有标准格式报文** NDEF **。 类似网络中的 IP 数据报,报文首部中的特定 bits 用于标识,报文主体搭载数据。
 NDEF 以 NdefRecord 对象的形式被封装于 NdefMessage 中,一个 NdefMessage 可包含多个 NdefRecord,NdefMessage 可视为 NdefRecord 的容器, NdefMessage 类比 IP数据报, 第一个 NdefRecord 类比 IP 数据报首部。

  • 标准数据格式下,NDEF message中的第一个NDEF record数据格式简析
  • 3-bit TNF(Type Name Format), 指明 Variable length type 域的数据类型
  • Variable length type, 指明record (NdefRecord) 的类型
  • Variable length ID, record 唯一标识,不常用
  • Variable length payload, 实际要进行读写的数据

Tag调度系统通过 TNF和 Variable length type解析出 MIME/URI 并进行匹配,如果成功,将其随着 payload 数据一同封装进 ACTION_NDEF_DISCOVERY intent 中。否则,tag 对象含有的技术信息(technologies)及其搭载的数据将被封装到 ACTION_TECH_DISCOVERY intent中。

将 NFC tags 分派给应用程序

当Tag调度系统成功创建封装有 NFC tag 的 intent后,需将 intent传给有相关应用程序,应用程序需定义 filter属性与 intent进行匹配。如果有多个应用程序能够处理此 intent,则有 Activity Chooser进行选择。

  • 优先级从高到低的三个 intent
  • ACTION_NDEF_DISCOVERED, 优先级最高的 intent
  • ACTION_TECH_DISCOVERED, 优先级次之,当无注册 ACTION_NDEF_DISCOVERED,或者无法匹配 MIME/URI 号,或者是一个 没有包含 NDEF数据technolog tag 时,启动此 intent
  • ACTION_TAG_DISCOVERED, 优先级最低,用于前两种情况均无法应用时

流程图如下:

Tag Dispatch System

*Note : 有使用 NDEF message 时,应创建 ACTION_NDEF_DISCOVERY intent *


实战演练, 以读卡器模式为例

Manifest

  • NFC权限请求
<uses-permission android:name="android.permission.NFC" />
  • 最低 Sdk 版本
<uses-sdk android:minSdkVersion="10"/>
  • 特征
 <uses-feature android:name="android.hardware.nfc" android:required="true" />
  • 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_list"/>

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

      <category android:name="android.intent.category.DEFAULT"/>
 </intent-filter>

创建res/xml/nfc_tech_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>
    <tch-list>
        <tech>android.nfc.tech.NdefFormatable</tech>
    </tch-list>
</resources>

MainActivity.java

  • OnCreate()
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent nfcIntent = new Intent(this, getClass());
        mTextView = (TextView)findViewById(R.id.text_view);
        nfcIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
      
        mPendingIntent = PendingIntent.getActivity(this, 0, nfcIntent, 0);
        mAdapter = NfcAdapter.getDefaultAdapter(this);
        if (mAdapter == null){
            Toast.makeText(getApplicationContext(), "此设备不支持 NFC ", Toast.LENGTH_SHORT).show();
            return;
        }
        if(!mAdapter.isEnabled()) {
            Toast.makeText(getApplicationContext(), " NFC 未开启 ", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(getApplicationContext(), " 请将预读卡片紧贴手机背部 ", Toast.LENGTH_SHORT).show();
        }
    }

More Info about PendingIntent http://blog.csdn.net/harvic880925/article/details/42030955

  • OnResume()
@Override
    protected void onResume() {
        super.onResume();
       //enableForegroundDispatch()允许 app前台调度当前Activity,而不通过系统自带的 Activity Choose Dialog 进行选择
        mAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
    }
  • OnNewIntent()
//当 Activity 中有新 intent 创建时调用
 @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        getTagInfo(intent);
        mTextView.setText(mInfo);
    }
  • OnPause()
@Override
    protected void onPause() {
        super.onPause();
        if (mAdapter != null) {
            mAdapter.disableForegroundDispatch(this);
        }
    }
  • MyFuncs
  private void getTagInfo(Intent intent){
        //获取 Tag 对象数据,用于解析出 id 和 所适配的 tech
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        byte[] tagId = tag.getId();
        //将 byte[] 转换为 十六进制字符
        String idStr = FormatUtil.ByteArrayToHexString(tagId);

        mInfo = "TypeID : " + idStr + "\n";
        // 获取 tag 中能于 android nfc 适配的 tech
        for (int i = 0; i < tag.getTechList().length; i++){
            mInfo += "Tech-" + (i + 1) + " : "+ tag.getTechList()[i] + "\n";
        }
        Toast.makeText(getApplicationContext(), idStr, Toast.LENGTH_SHORT).show();
    }

 此例能读取多种类型的 Tag 数据,小编试过银行卡,羊城通公交卡,学生饭卡及身份证。但读取的信息有限,而且无用... 连卡号无法读取,后续基于公交卡适配的 IsoDep tech 查找相关资料,进行如下扩展。

扩展

IsoDep 通过 Byte[] transceive(Byte[] cmdData) 函数进行交互,函数参数是 命令数据,返回结果数据
详见 https://developer.android.com/reference/android/nfc/tech/NfcA.html#transceive(byte[])

public class Yangchengtong {
    public static class SendCmd{
        public static byte[] id = { (byte) 'P', (byte) 'A', (byte) 'Y',
                (byte) '.', (byte) 'A', (byte) 'P', (byte) 'P', (byte) 'Y'};
        public static byte[] balance = {
                (byte) 0x80,
                (byte) 0x5C,
                (byte) 0x00,
                (byte) 0x02,
                (byte) 0x04
              }; //通信后获取的 byte[] 取前四个字节转换成int 再除以 100 得到余额
        public static byte[] trades = {
                (byte) 0x00,
                (byte) 0xB2,
                (byte) 0x01,
                (byte) 0xC5,
                (byte) 0x00
              }; //一次性读取所有交易记录
        }
}
  • MainActivity.java 续
private void getPreciseTagInfo(Intent intent){
        IsoDep isoDep = IsoDep.get((Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
        try{
            isoDep.connect();
            byte[] cardNameBytes = isoDep.transceive(Yangchengtong.SendCmd.id);
            mInfo += divideLine;
            mInfo += "Card Type Bytes: " + FormatUtil.ByteArrayToHexString(cardNameBytes) + "\n";

            byte[] cardBalanceBytes = isoDep.transceive(Yangchengtong.SendCmd.balance);
            mInfo += "Card Balance Bytes: " + FormatUtil.ByteArrayToHexString(cardBalanceBytes) + "\n";

        }catch (IOException ioe){
            Log.e("Error connect isoDep", ioe.toString());
        }finally {
            if(isoDep != null){
                try{
                    isoDep.close();
                }catch (IOException e){
                    Log.e("Error close isoDep", e.toString());
                }
            }
        }
    }

  em... 能成功读出支持 IsoDep IC卡的数据,但经验证,余额不准确。无法找到羊城通与 NFC交互的相关资料,至此暂时冰冻此次尝试。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容