android NFC NDEF解析 3

一:解析NDEF
   一直在说ndef格式,这到底是个啥格式?据文档说这是google参考nfc论坛而提出和支持的格式。也就是说nfc的格式和标准肯定有很多,google只不过比较支持其中的这一种NDEF。而如果之后apple也搞nfc的话,我怀疑他肯定就不会去支持google已经支持的ndef了。我浅薄的如此认为,以后再看看吧呵呵
  ndef格式,就是数据被包裹在一个NdefMessage里,而同时一个NdefMessage里面又可以包含多个NdefRecord.当然你也应该还记得,nfc的tag里面是可以不包含Ndef数据的,他也可以包含android.nfc.tech所定义的多种标签。Google推荐开发人员使用ndef格式的数据来处理android相关的nfc格式数据。
   当一个android设备检测到nfc标签包含ndef格式的数据的时候,他就会尝试的去解析数据中的MIME type或者是URI。为了做这个事情,他需要解析NdefMessage中的第一个NdefRecord。第一个NdefRecord中的结构一般如下:
3-bit TNF (Type Name Format)
如何解释variable length type的数据。并依据不同的解析,来确定发送什么样的intent。如果是这里定义的是TNF_WELL_KNOWN则还要参考variable length type来进一步确定。如果是网络地址URL则直接去调用浏览器程序了。
Variable length type
进一步定义record的类型。具体的请参看android文档。不过话在这里多说一句,TNF和TYPE这两个参数非常的重要,直接决定了你NDEF格式的命中程度。定义的越规范,越清楚,同时在androidmanifest文件中写的越正确,就越好,否则系统就会被你发TECH_ACTION
比如:构造的时候写
new NdefRecord(
                NdefRecord.TNF_WELL_KNOWN ,
                NdefRecord.RTD_TEXT,
                new byte[0],
                data.getBytes(Charset.forName("UTF-8")));
androidmanifest文件中写
      <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>     
    </intent-filter>
    这一对就可以正确的过滤一个text/plain格式的ndef
Variable length ID
    不经常使用.....是用来唯一标示一个record的。 
 
Variable length payload
    actual这个才是真正的数据区域。你的读写操作一般应该主要是对这个区域的操作。一个NdefMessage可以有多个NdefRecord所以你不要假设所有的数据都仅仅存储在第一个NdefRecord中。
 
已经知道了Ndef的格式了,那么解析带有Ndef数据的Intent也就简单很多了。底下这个解析的方法,通常放在onResume或者onNewIntent中。
   
    private boolean readFromTag(Intent in){
        Parcelable[] rawArray = in.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        //we just have noly one NdefMessage,如果你不止一个的话,那么你要遍历了。
        NdefMessage mNdefMsg = (NdefMessage)rawArray[0];
        //we just have only one NdefRecord,如果你不止一个record,那么你也要遍历出来所有的record
        NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];
        try {
            if(mNdefRecord != null){
                readResult = new String(mNdefRecord.getPayload(),"UTF-8");
                readJson = new JSONObject(readResult);//我举的例子中用的是json格式,所以这里要把payload中的数据封装成json格式的。
                return true;
            }
        } catch (JSONException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        };
        return false;
    }
 
 
 
二:解析其他tech标签
   现在你已经会解析ndef标签了,但是你也应该知道,大多数nfc标签其实并不是ndef格式的。比如你的公交卡,各个城市的公交卡会支持什么格式,并不一定。至少我理解是这样的。所以你还要会解析各种tech格式的nfc数据。
  如果你还记得我们再tech-list中定义了多少种格式,你就应该知道我们如果要全部解析的话,应该要解析多少种不同的nfc格式了。底下这段代码可以显示出你所扫描的nfc卡到底支持哪几种格式。
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        String[] temp = tag.getTechList();
        for(String s :temp){
          Log.i("xxx","s = "+s);
        }
 
   这是一段最常用的(我个人理解啊)的nfc解析代码,主要是解析MifareClassic格式。貌似大多数nfc标签都会支持这种格式。先普及下关于他的简单常识:不知道各个格式的数据是怎么构成的,你压根就没法解析。
一般来说,给予MifareClassic的射频卡,一般内存大小有3种:
1K: 16个分区(sector),每个分区4个块(block),每个块(block) 16个byte数据
2K: 32个分区,每个分区4个块(block),每个块(block) 16个byte数据
4K:64个分区,每个分区4个块(block),每个块(block) 16个byte数据
 
 对于所有基于MifareClassic的卡来说,每个区最后一个块(block)叫Trailer,16个byte,主要来存放读写该区的key,可以有A,B两个KEY,每个key长6byte,默认的key一般是FF 或 0,最后一个块的内存结构如下:
Block 0  Data 16bytes
Block 1  Data 16 bytes
Block 2  Data 16 bytes
Block 3  Trailer 16 bytes
 
Trailer:
Key A: 6 bytes
Access Conditions: 4 bytes
Key B: 6 bytes 
 
所以在写卡的内存的时候,一般不能写每个sector的最后一个block,除非你有要修改KEY和访问权限的需求。如果KEY A 被你不小心修改掉了,而你不知道修改成什么,那与之对应的那个sector你就没有办法访问了。因为在MifareClassic中,如果你要读取数据,那么必须要有这个数据地址所在的sector的权限,这个权限就是这个sector的trailer的KEY A或KEY B。
 
这就是解析MifareClassic的代码了
    private void readTechPayloadMC(Tag tag){
        MifareClassic mc = MifareClassic.get(tag);//通过intent拿到EXTRA_TAG并转化成MirareClassic格式。
        int bCount = 0 ;
        int bIndex = 0 ;
        try {
            mc.connect();
            int sectorCount = mc.getSectorCount();//获得sector总数
            Log.i("liyufei3","sectorCount = "+sectorCount);
            for(int i =0;i<sectorCount;i++){
                //尝试去获得每个sector的认证,只有认证通过才能访问
                auth = mc.authenticateSectorWithKeyA(i, MifareClassic.KEY_DEFAULT);
                if(auth){
                    //这句其实不是必须的,因为每个sector中本来就只有4个block
                    bCount = mc.getBlockCountInSector(i);
                   
                    //all blocks are consecutively numbered ,0-64,so we can not use number of cycles to be index.
                    //we just can get the first block in iTH sector
//我们可以得到每一个sector中的第一个block的编号
                    bIndex = mc.sectorToBlock(i);
                    for(int j =0;j<bCount;j++){//循环四次拿出一个sector中所有的block
//每次循环bIndex会去++,然后可以得出每一个block的数据。这些数据是字节码,所以你还有一个翻译的工作要做。
                        byte[] data = mc.readBlock(bIndex);           
                       String s = readByteArray(data);
                        bIndex++;
                    }
                }else{
                    Log.i("xxx","   auth = false !!!  in sectorCount = "+i);
                   
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }


二:将数据写入NFC 标签
   往可读写的nfc标签中写tag相比读什么的要简单一点。当然这主要是因为,我在这里只讲如何写ndef数据。简单概括一下就是自己构造一个或多个NdefRecord然后将他们封装到一个NdefMessage中。然后将这个message写入就可以了。
 
    private void writeNdefTag(Intent in){
        Tag tag = in.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        Ndef ndef = Ndef.get(tag);
        try {
//这一句别丢了,读nfc标签的时候不需要这句,因为那时数据直接就在intent中。
            ndef.connect(); 
//构造一个合适的NdefMessage。你可以看到代码里用了NdefRecord数组,只不过这个数组里只有一个record
            NdefMessage ndefMsg = new NdefMessage(new NdefRecord[]{createRecord()});
            ndef.writeNdefMessage(ndefMsg);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (FormatException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
创建一个Record的举例,注释掉的是举例写一个url进去,到时候一扫描就会直接调用浏览器程序。没有注释的是尝试调用一个本地应用,只要一扫描就会直接启动应用
    private NdefRecord createRecord(){
//               return new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI,
//                       "http://www.sohu.com".getBytes(Charset.forName("US-ASCII")),
//                       new byte[0], new byte[0]);
                        
               return new NdefRecord(
                       NdefRecord.TNF_MIME_MEDIA ,
                       "application/com.android.TestNfc".getBytes(Charset.forName("US-ASCII")),
                       new byte[0],  "com.android.yufeimusic".getBytes(Charset.forName("US-ASCII")));
    }
 
 
二:Android Beam
    再来讲讲android 4.0之后的新特性,android beam。如果你和你的朋友都恰好有一部android NFC手机,而且都是4.0的系统,那你们就可以玩一下了。把nfc功能打开,然后打开联系人界面,然后将两个手机背靠背放在一起,手指轻轻一点。你的联系人数据就会唰的一下飞到对方手机里去了。这个非常有意思,而且实现也很简单。
主要是在代码中的onCreate中要设置回调。       
 
 mNfcAdapter.setNdefPushMessageCallback(this, this);
其中第一个this就是CreateNdefMessageCallback 回调函数。他中间你要实现的也就是下面的createNdefMessage
第二个this,貌似指的是由哪一个activity来接受系统回调...好吧...没做实验我就不多说了怕误导自己
以下的代码全部为android文档中的示例代码,比较简单,也就是创建了一个字符串NdefMessage然后传过去。到时候,另外一个设备收到Intent后如何解析,你可以参看之前的过滤和解析博文
    public NdefMessage createNdefMessage(NfcEvent event) {
        Time time = new Time();
        time.setToNow();
        String text = ("Beam me up!\n\n" +
                "Beam Time: " + time.format("%H:%M:%S"));
        NdefMessage msg = new NdefMessage(
                new NdefRecord[] { createMimeRecord(
                        "application/com.example.android.beam", text.getBytes())
        
//          ,NdefRecord.createApplicationRecord("com.example.android.beam")
        });
        return msg;
    }
public NdefRecord createMimeRecord(String mimeType, byte[] payload) { byte[] mimeBytes = mimeType.getBytes(Charset.forName("US-ASCII")); NdefRecord mimeRecord = new NdefRecord( NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload); return mimeRecord; }
 
当然系统中有关android beam的不会只有这一个回调。比如还有
mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
这个回调主要是用来当你的NdefMessage发送到对方成功之后,你要干什么的一个回调函数。
比如你可以向用户主线程发一个handler或者自己弹出个toast都是可以的。   
@Override
    public void onNdefPushComplete(NfcEvent arg0) {
        // A handler is needed to send messages to the activity when this
        // callback occurs, because it happens from a binder thread
        mHandler.obtainMessage(MESSAGE_SENT).sendToTarget();
    }


原文:http://blog.sina.com.cn/s/blog_67d95f4001011vdg.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值