Android NFC 近场通讯开发全解

来源:互联网 发布:淘宝网卖家中心 编辑:程序博客网 时间:2024/04/28 01:59
  • 由于开发的项目需求,涉及到了android NFC通讯
    的开发,由于没有接触过,也没有资料,所以自己一个人在网上找资料,但是网上的资料并不丰富,很多东西都需要自己慢慢研究试探,经过一段时间的摸索,终于实现了所有的功能需求,花费的时间比较长久,好心塞啊,可能是因为NFC手机普及率比较低吧,所以网上资料比较缺乏,所以我觉得得自己写个博客来,以供后来者可以学习领悟,快速开发符合自己需求的应用场景。“share your knowledge to the world!”.
    NFC设备的普及率很低,并且Android 3.0 开始系统才支持,而且现在NFC功能的设备并不多,这样的应用也并不多。
  • 设备: 需要一部Andriod带NFC的设备,至少一张标签纸 。 NFC:近场通信Near Field Communication

首先,了解NFC标签纸的类型有分A类,B类,F类 所以你需要知道你的应用支持什么类型的标签,设置你需要过滤的标签。
我们创建一个NFCActivity.java ,也就是处理我们的逻辑类。在AndroidMinifest.xml文件中,你需要声明NFCActivity 使用singleTask

    <activity        android:name=".NFCActivity"        android:launchMode="singleTask"        android:screenOrientation="portrait" >

重点1:声明Action,顺序相关

如果是有内容的标签,则优先获得android.nfc.action.NDEF_DISCOVERED 这个Action动作

    <!-- NDEF_DISCOVERED 优先级最高的Action -->    <intent-filter>        <action android:name="android.nfc.action.NDEF_DISCOVERED" />        <category android:name="android.intent.category.DEFAULT" />        <data android:mimeType="text/plain" />    </intent-filter>

如果未获得上面的Action,就会获取android.nfc.action.TECH_DISCOVERED 这个Action动作

    <!-- TECH_DISCOVERED 优先级第二的Action -->    <intent-filter>        <action android:name="android.nfc.action.TECH_DISCOVERED" />        <category android:name="android.intent.category.DEFAULT" />    </intent-filter>

如果上面两个都没有,就是这个优先级最低的,也是没有任何Action的情况下最终会获取的android.nfc.action.TAG_DISCOVERED 这个Action 动作。

    <!-- TAG_DISCOVERED 优先级最低的最终处理 Action -->    <intent-filter>        <action android:name="android.nfc.action.TAG_DISCOVERED" />        <category android:name="android.intent.category.DEFAULT" />    </intent-filter>

最后需要声明你需要过滤的标签类型

    <!-- 技术标签过滤的便签卡类型 android:resource="@xml/nfc_tech_filter" -->     <meta-data           android:name="android.nfc.action.TECH_DISCOVERED"           android:resource="@xml/nfc_tech_filter" />

@xml/nfc_tech_filter,在res下面建立一个XML文件夹,然后在文件夹下面建立
Nfc_tech_filter.xml文件,名字随便取,但是最好的见名知意的名称。

重点2:Nfc_tech_filter.xml 文件过滤

    <tech-list>        <tech>android.nfc.tech.Ndef</tech>    </tech-list>

这个是一个组,组中的Ndef是代表需要捕获F类型标签,即F类型的才是符合条件的
如果要同时支持F类型和A类型的标签,则是与关系,可以写成:

    <tech-list>        <tech>android.nfc.tech.NdeA</tech>        <tech>android.nfc.tech.Ndef</tech>    </tech-list>

如果我需要支持A类型,或者是F类型,则是:

    <tech-list>        <tech>android.nfc.tech.NdeA</tech>    </tech-list>    <tech-list>        <tech>android.nfc.tech.Ndef</tech>    </tech-list>

或者关系。

然后是声明权限:

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

重点3:Intent发布系统

  • minifasts.xml中声明的配置,都会在系统内部注册,然后系统匹配哪些应用符合Action,然后检测到这样的Action时候就会在桌面弹出让用户选择的应用列表,这就是系统的Intent发布系统,幸运的是,我们可以在代码中截获系统的Intent发布系统,这样我们就可以指定特定的Action,跳转之我们指定的应用的页面,这样就不需要系统弹出应用选择的列表了,即我们在代码中接管了系统的Intent发布系统。

获取PendingIntent

    pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,         getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_CANCEL_CURRENT);

获取intentFilter
可以添加你想要的Action和mimeType:

    IntentFilter ndef = new IntentFilter();    ndef.addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);    ndef.addAction(NfcAdapter.ACTION_TECH_DISCOVERED);    try { //        ndef.addDataType("text/plain");    } catch (IntentFilter.MalformedMimeTypeException e) {        throw new RuntimeException("fail", e);    }

添加卡类型:

    intentFiltersArray = new IntentFilter[]{ndef};    techListsArray = new String[][]{new String[]{        Ndef.class.getName(), NdefFormatable.class.getName(),        NfcA.class.getName(), MifareClassic.class.getName(),        MifareUltralight.class.getName(), NfcB.class.getName(),        IsoDep.class.getName(), NfcF.class.getName(), NfcV.class.getName(),        NfcBarcode.class.getName()}};

onNewIntent方法中

     @Override    protected void onNewIntent(Intent intent) {        super.onNewIntent(intent);        setIntent(intent);    }

重点4,获取Intent前台发布系统

        /**          * 获取Intent TAG前台分发系统权限          */        private void getForgroundDispatch() {            if (nfcAdapter != null) {                nfcAdapter.enableForegroundDispatch(NFCActivity.this, pendingIntent, null, null);// 获取前台发布系统                hasforgrounddispatch = true;            }        }            nfcAdapter.enableForegroundDispatch(NFCActivity.this, pendingIntent, null, null);
  • 由于我的Demo业务需求,首次通过系统Intent发布系统在应用列表中选择了我的应用,然后应用获取Intent发布系统,由于获取Intent发布系统第三个参数为null,即不指定Action,这个时候Action将会是NfcAdapter.ACTION_TAG_DISCOVERED,优先级最低的。
    详细看系统API介绍:
   public void enableForegroundDispatch (Activity activity,   PendingIntent intent, IntentFilter[] filters, String[][] techLists)      Added in API level 10 Enable foreground dispatch to the given   Activity. This will give give priority to the foreground activity   when dispatching a discovered Tag to an application. If any   IntentFilters are provided to this method they are used to match   dispatch Intents for both the ACTION_NDEF_DISCOVERED and   ACTION_TAG_DISCOVERED. Since ACTION_TECH_DISCOVERED relies on meta   data outside of the IntentFilter matching for that dispatch Intent is   handled by passing in the tech lists separately. Each first level   entry in the tech list represents an array of technologies that must   all be present to match. If any of the first level sets match then   the dispatch is routed through the given PendingIntent. In other   words, the second level is ANDed together and the first level entries   are ORed together. If you pass null for both the filters and   techLists parameters that acts a wild card and will cause the   foreground activity to receive all tags via the ACTION_TAG_DISCOVERED   intent. This method must be called from the main thread, and only   when the activity is in the foreground (resumed). Also, activities   must call disableForegroundDispatch(Activity) before the completion   of their onPause() callback to disable foreground dispatch after it   has been enabled. Requires the NFC permission. 

上述大概意思是:

至少api10 以上的系统,即Android3.0 为Activity获取前台发布系统 你需要给出一些Activity去匹配Tag的属性值
你给予的任何的IntentFilter必须是匹配ACTION_NDEF_DISCOVEREDACTION_TAG_DISCOVERED.的,ACTION_TECH_DISCOVERED是依赖于meta-data的列表说明,每一个tech list 代表一些技术的匹配,大概就是说匹配Tag标签就是通过tech list的配置来的 。
重要的是这句话:If you pass null for both the filters and techLists parameters that acts a wild card and will cause the foreground activity to receive all tags via the ACTION_TAG_DISCOVERED intent.
即第三个参数 IntentFilter[] filters你设置为null的时候,标签就会获取优先级最低的ACTION_TAG_DISCOVERED这个Action动作
 这个方法在主线程中调用,你必须要在onPause()方法中取消捕获的Intent分发系统

    nfcAdapter.disableForegroundDispatch(this);

核心是在OnResume中根据不同的Action去分发不同的逻辑,详细看Demo简单的逻辑处理

    @Override    protected void onResume() {    .......    ....    }

重点5 :擦除标签内容

  • 这个除标签功能,在网上很难找到介绍方法,当初我也想过,写入空信息就擦除了内容,然后自己试了试,没有试对,然后继续找资料,在GitHub找,然后找到了javaNFC项目,找到了方法,Java项目API跟AndroidAPI大同小异,方法介绍(“format NFC tag is just write a empty Record”) 呵呵呵呵呵呵....
    傻眼了,当初自己思路是对的,原来是empty Record 构造错了,心塞啊,又多花了一天时间找资料,(Google找资料才是王道,百度low了) 擦除数据的核心代码:
   // TODO,格式化NFC标签,即写入空信息即可   NdefRecord emptyrecord = new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null);   NdefRecord[] emptyArray = {emptyrecord};   NdefMessage emptyMsg = new NdefMessage(emptyArray);   startWriteMsg(getIntent(), emptyMsg);

下面是一些核心操作,没有什么难点的:

    /**      * 创建一个封装要写入的文本的NdefRecord对象      *      * @param text      * @return      */    public NdefRecord createTextRecord(String text) {        // 生成语言编码的字节数组,中文编码        byte[] langBytes = Locale.US.getLanguage().getBytes(Charset.forName("US-ASCII"));        // 将要写入的文本以UTF_8格式进行编码        Charset utfEncoding = Charset.forName("UTF-8");        // 由于已经确定文本的格式编码为UTF_8,所以直接将payload的第1个字节的第7位设为0        byte[] textBytes = text.getBytes(utfEncoding);        int utfBit = 0;        // 定义和初始化状态字节        char status = (char) (utfBit + langBytes.length);        // 创建存储payload的字节数组        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);        // 根据前面设置的payload创建NdefRecord对象        NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);        return record;    }     /**       * 开始写入信息       * @param intent       * @param ndefMessage       */    private void startWriteMsg(Intent intent, NdefMessage ndefMessage) {        final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);        try {            Ndef ndef = Ndef.get(tag);            if (ndef != null) {                ndef.connect();                if (!ndef.isWritable()) {                    Tools.showTip(NFCActivity.this, "标签是只读!");                    NFCActivity.this.onBackPressed();                    NFCActivity.this.finish();                    return;                }                    int size = ndefMessage.toByteArray().length;                    if (ndef.getMaxSize() < size) {                    Tools.showTip(NFCActivity.this, "空间不足!");                    NFCActivity.this.onBackPressed();                    NFCActivity.this.finish();                    return;                }                ndef.writeNdefMessage(ndefMessage);                    Tools.showTip(NFCActivity.this, "写入成功");// 写入成功                    // TODO 写入成功,操作UI                    return;            } else {                // 获取可以格式化和向标签写入数据NdefFormatable对象                NdefFormatable format = NdefFormatable.get(tag);                // 向非NDEF格式或未格式化的标签写入NDEF格式数据                if (format != null) {                    try {                        // 允许对标签进行IO操作                        format.connect();                        format.format(ndefMessage);                        Tools.showTip(NFCActivity.this, "写入成功");// 写入成功                            // TODO 写入成功,操作UI                            return;                    } catch (Exception e) {                        Tools.showTip(NFCActivity.this, "写入失败");                        // TODO 写入失败 操作UI                        return;                    }                } else {                    Tools.showTip(NFCActivity.this, "NFC标签不支持NDEF格式");                    // TODO NFC标签不支持NDEF格式! 操作UI                    return;                    }                }                } catch (Exception e) {                e.printStackTrace();            }        }

尾声

  • 到此,NFC功能完成,涉及到了空标签如何识别,如何在触发标签,在应用前台,非前台如何触发逻辑处理,如何修改标签内容,如何擦除标签内容,基本上所有的业务逻辑都囊括了,满足了基本的开发需求,如果你涉及到更加复杂的需求开发,这个Demo也能很好的帮助你理解NFC逻辑跳转,甚至可以直接搬过来使用,直接套上你的业务逻辑。当然,由于能力有限,可能有很多纰漏,望大神指点。
  • 比如这个单是NFC的逻辑跳转就看上去代码比较混乱,再加上你的业务逻辑,可能就没有那么清晰了,如果你有更好的方法,把NFC技术逻辑分离出去,然后再嵌入你的业务逻辑,可能就更加完美了,做到技术跟业务分离的。代码的可维护性就更高了。
  • Demo中使用了别的应用的UI元素。看起来比较不协调。
NFCDemo附件下载链接:http://download.csdn.net/detail/u011661372/8951595
0 0
原创粉丝点击