在Android NFC开发-理论篇中,我们了解了在Android中开发NFC的一些理论知识,这篇我们继续应用我们上一篇学到的知识,实现对NDEF格式标签和MifareClassic格式标签的读写操作。

基本操作

配置AndroidMenifest.xml:

<!--API level 9只包含有限的tag支持,包括: .通过ACTION_TAG_DISCOVERED来发布Tag信息 .只有通过EXTRA_NDEF_MESSAGES扩展来访问NDEF消息 .其他的tag属性和I/O操作都不支持 所以你可能想要用API level 10来实现对tag的广泛的读写支持。-->    <uses-sdk android:minSdkVersion="10"/>    <!--NFC权限-->    <uses-permission android:name="android.permission.NFC" />    <!-- 要求当前设备必须要有NFC芯片 -->    <uses-feature android:name="android.hardware.nfc" android:required="true" />

获取设备默认的NfcAdapter对象,判断该设备是否支持NFC功能,若支持,判断此功能是否打开,并且创建一个PendingIntent对象,用于当NFC标签被检测到时,启动我们处理NFC标签的Activity

@Override    protected void onStart() {        super.onStart();        mNfcAdapter= NfcAdapter.getDefaultAdapter(this);//设备的NfcAdapter对象        if(mNfcAdapter==null){//判断设备是否支持NFC功能            Toast.makeText(this,"设备不支持NFC功能!",Toast.LENGTH_SHORT);            finish();            return;        }        if (!mNfcAdapter.isEnabled()){//判断设备NFC功能是否打开            Toast.makeText(this,"请到系统设置中打开NFC功能!",Toast.LENGTH_SHORT);            finish();            return;        }        mPendingIntent=PendingIntent.getActivity(this,0,new Intent(this,getClass()),0);//创建PendingIntent对象,当检测到一个Tag标签就会执行此Intent    }

在OnNewIntent()方法中,获取到Tag对象

@Override    protected void onNewIntent(Intent intent) {        super.onNewIntent(intent);        mTag=intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);//获取到Tag标签对象        String[] techList=mTag.getTechList();        System.out.println("标签支持的tachnology类型:");        for (String tech:techList){            System.out.println(tech);        }    }

为了更好的处理NFC标签,我们需要在Activity获取焦点时(onResume),在主线程中启动前台发布系统,并且在Activity失去焦点时,关闭前台发布系统

//页面获取焦点@Override    protected void onResume() {        super.onResume();        if (mNfcAdapter!=null){          mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,null,null);//打开前台发布系统,使页面优于其它nfc处理.当检测到一个Tag标签就会执行mPendingItent        }    }//页面失去焦点@Override    protected void onPause() {        super.onPause();        if(mNfcAdapter!=null){            mNfcAdapter.disableForegroundDispatch(this);//关闭前台发布系统        }    }

以上所有操作,都是对一个NFC标签的基本操作,我们封装在一个BaseNfcActivity中,对不同格式标签读写的Activity都继承BaseNfcActivity。

NDEF格式标签读写

我们可以通过Tag对象的getTechList()获取到标签的技术类型,只有支持NDEF格式的标签才可以进行NDEF格式标签的读写操作。

读写NDEF格式标签主要涉及到两个类:

  • NdefMessage:描述NDEF格式的信息,实际上我们写入NFC标签的就是NdefMessage对象。
  • NdefRecord:描述NDEF信息的一个信息段,一个NdefMessage可能包含一个或者多个NdefRecord。

获取Ndef对象

Ndef ndef=Ndef.get(mTag);//获取ndef对象

创建NdefRecord,Android为我们提供了创建NdefRecord的方法,是我们可以轻松创建一个NdefRecord对象

  • NdefRecord.createApplicationRecord(String packageName)
  • NdefRecord.createUri(Uri uri)
  • NdefRecord.createUri(String uriString)
  • NdefRecord.createTextRecord(String languageCode, String text)

遗憾的是NdefRecord.createTextRecord(String languageCode, String text)最小兼容sdk版本是21,对于需要兼容更小版本的应用来说就需要我们自己来实现这个方法。

不管什么格式的数据本质上都是由一些字节组成的。对于NDEF文本格式来说,这些数据的第1个字节描述了数据的状态,然后若干个字节描述文本的语言编码,最后剩余字节表示文本数据。这些数据格式由NFC Forum的相关规范定义,可以通过 http://members.nfc-forum.org/specs/spec_dashboard 下载相关的规范。

NDEF的文本数据规范:

偏移量 长度(bytes) 描述
0 1 状态字节,见下表(状态字节编码格式)
1 n ISO/IANA语言编码。例如”en-US”,”fr-CA”。编码格式是US-ASCII,长度(n)由状态字节的后6位指定。
n+1 m 文本数据。编码格式是UTF-8或UTF-16。编码格式由状态字节的前3位指定。

状态字节编码格式:

字节位(0是最低位,7是最高位) 含义
7 0:文本编码为UTF-8,1:文本编码为UTF-16
6 必须设为0
5..0 语言编码的长度(占用的字节个数)

创建文本NdefRecord

/** * 创建NDEF文本数据 * @param text * @return */    public static NdefRecord createTextRecord(String text) {        byte[] langBytes = Locale.CHINA.getLanguage().getBytes(Charset.forName("US-ASCII"));        Charset utfEncoding = Charset.forName("UTF-8");        //将文本转换为UTF-8格式        byte[] textBytes = text.getBytes(utfEncoding);        //设置状态字节编码最高位数为0        int utfBit = 0;        //定义状态字节        char status = (char) (utfBit + langBytes.length);        byte[] data = new byte[1 + langBytes.length + textBytes.length];        //设置第一个状态字节,先将状态码转换成字节        data[0] = (byte) status;        //设置语言编码,使用数组拷贝方法,从0开始拷贝到data中,拷贝到data的1到langBytes.length的位置        System.arraycopy(langBytes, 0, data, 1, langBytes.length);        //设置文本字节,使用数组拷贝方法,从0开始拷贝到data中,拷贝到data的1 + langBytes.length        //到textBytes.length的位置        System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);        //通过字节传入NdefRecord对象        //NdefRecord.RTD_TEXT:传入类型 读写        NdefRecord ndefRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,                NdefRecord.RTD_TEXT, new byte[0], data);        return ndefRecord;    }

创建NdefMessage,并且写入Ndef标签

//往Ndef标签中写数据    private void writeNdef(){        if (mTag==null){            Toast.makeText(this,"不能识别的标签类型!",Toast.LENGTH_SHORT);            finish();            return;        }        Ndef ndef=Ndef.get(mTag);//获取ndef对象        if (!ndef.isWritable()){            Toast.makeText(this,"该标签不能写入数据!",Toast.LENGTH_SHORT);            return;        }        NdefRecord ndefRecord=createTextRecord(writeEdt.getText().toString());//创建一个NdefRecord对象        NdefMessage ndefMessage=new NdefMessage(new NdefRecord[]{ndefRecord});//根据NdefRecord数组,创建一个NdefMessage对象        int size=ndefMessage.getByteArrayLength();        if (ndef.getMaxSize()<size){            Toast.makeText(this,"标签容量不足!",Toast.LENGTH_SHORT);            return;        }        try {            ndef.connect();//连接            ndef.writeNdefMessage(ndefMessage);//写数据            Toast.makeText(this,"数据写入成功!",Toast.LENGTH_SHORT);        } catch (IOException e) {            e.printStackTrace();        } catch (FormatException e) {            e.printStackTrace();        }finally {            try {                ndef.close();//关闭连接            } catch (IOException e) {                e.printStackTrace();            }        }    }

读Ndef文本数据

//读取Ndef标签中数据    private void readNdef(){        if (mTag==null){            Toast.makeText(this,"不能识别的标签类型!",Toast.LENGTH_SHORT);            finish();            return;        }        Ndef ndef=Ndef.get(mTag);//获取ndef对象        try {            ndef.connect();//连接            NdefMessage ndefMessage=ndef.getNdefMessage();//获取NdefMessage对象            if (ndefMessage!=null)               readEdt.setText(parseTextRecord(ndefMessage.getRecords()[0]));            Toast.makeText(this,"数据读取成功!",Toast.LENGTH_SHORT);        } catch (IOException e) {            e.printStackTrace();        } catch (FormatException e) {            e.printStackTrace();        }finally {            try {                ndef.close();//关闭链接            } catch (IOException e) {                e.printStackTrace();            }        }    }    /** * 解析NDEF文本数据,从第三个字节开始,后面的文本数据 * @param ndefRecord * @return */    public static String parseTextRecord(NdefRecord ndefRecord) {        /** * 判断数据是否为NDEF格式 */        //判断TNF        if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) {            return null;        }        //判断可变的长度的类型        if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {            return null;        }        try {            //获得字节数组,然后进行分析            byte[] payload = ndefRecord.getPayload();            //下面开始NDEF文本数据第一个字节,状态字节            //判断文本是基于UTF-8还是UTF-16的,取第一个字节"位与"上16进制的80,16进制的80也就是最高位是1,            //其他位都是0,所以进行"位与"运算后就会保留最高位            String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16";            //3f最高两位是0,第六位是1,所以进行"位与"运算后获得第六位            int languageCodeLength = payload[0] & 0x3f;            //下面开始NDEF文本数据第二个字节,语言编码            //获得语言编码            String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");            //下面开始NDEF文本数据后面的字节,解析出文本            String textRecord = new String(payload, languageCodeLength + 1,                    payload.length - languageCodeLength - 1, textEncoding);            return textRecord;        } catch (Exception e) {            throw new IllegalArgumentException();        }    }

MifareClassic格式标签读写

MifareClassic格式标签数据结构

第一扇区的第一块一般用于制造商占用块

0-15个扇区:一个扇区对应4个块,所以总共有64个块,序号分别为0-63,第一个扇区对应:0-3块,第二个扇区对应:4-7块…

每个扇区的最后一个块用来存放密码或控制位,其余为数据块,一个块占用16个字节,keyA占用6字节,控制位占用4字节,keyB占用6字节。

MifareClassic标签读写常用api:

  • get():根据Tag对象来获得MifareClassic对象;
  • Connect():允许对MifareClassic标签进行IO操作;
  • getType():获得MifareClassic标签的具体类型:TYPE_CLASSIC,TYPE_PLUA,TYPE_PRO,TYPE_UNKNOWN;
  • getSectorCount():获得标签总共有的扇区数量;
  • getBlockCount():获得标签总共有的的块数量;
  • getSize():获得标签的容量:SIZE_1K,SIZE_2K,SIZE_4K,SIZE_MINI
  • authenticateSectorWithKeyA(int SectorIndex,byte[] Key):验证当前扇区的KeyA密码,返回值为ture或false。 常用KeyA:默认出厂密码:KEY_DEFAULT,各种用途的供货商必须配合该技术的MAD:KEY_MIFARE_APPLICATION_DIRECTORY
    被格式化成NDEF格式的密码:KEY_NFC_FORUM
  • getBlockCountInSector(int):获得当前扇区的所包含块的数量;
  • sectorToBlock(int):当前扇区的第1块的块号;
  • writeBlock(int,data):将数据data写入当前块;注意:data必须刚好是16Byte,末尾不能用0填充,应该用空格
  • readBlock(int):读取当前块的数据。
  • close():禁止对标签的IO操作,释放资源。

写MifareClassic格式标签数据

//写块    private void writeBlock(){        if (mTag==null){            Toast.makeText(this,"无法识别的标签!",Toast.LENGTH_SHORT);            finish();            return;        }        if (!haveMifareClissic){            Toast.makeText(this,"不支持MifareClassic",Toast.LENGTH_SHORT);           finish();            return;        }        MifareClassic mfc=MifareClassic.get(mTag);        try {            mfc.connect();//打开连接            boolean auth;            int sector=Integer.parseInt(sectorNum.getText().toString().trim());//写入的扇区            int block=Integer.parseInt(blockNum.getText().toString().trim());//写入的块区            auth=mfc.authenticateSectorWithKeyA(sector,MifareClassic.KEY_DEFAULT);//keyA验证扇区            if (auth){                mfc.writeBlock(block,"0123456789012345".getBytes());//写入数据                Toast.makeText(this,"写入成功!",Toast.LENGTH_SHORT);            }        } catch (IOException e) {            e.printStackTrace();        }finally {            try {                mfc.close();//关闭连接            } catch (IOException e) {                e.printStackTrace();            }        }    }

读MifareClassic格式标签数据

//读取块    private void readBlock(){        if (mTag==null){            Toast.makeText(this,"无法识别的标签!",Toast.LENGTH_SHORT);            finish();            return;        }        if (!haveMifareClissic){            Toast.makeText(this,"不支持MifareClassic",Toast.LENGTH_SHORT);            finish();            return;        }        MifareClassic mfc=MifareClassic.get(mTag);        try {            mfc.connect();//打开连接            boolean auth;            int sector=Integer.parseInt(sectorNum.getText().toString().trim());//写入的扇区            int block=Integer.parseInt(blockNum.getText().toString().trim());//写入的块区            auth=mfc.authenticateSectorWithKeyA(sector,MifareClassic.KEY_DEFAULT);//keyA验证扇区            if (auth){                readData.setText(bytesToHexString(mfc.readBlock(block)));            }        } catch (IOException e) {            e.printStackTrace();        }finally {            try {                mfc.close();//关闭连接            } catch (IOException e) {                e.printStackTrace();            }        }    }    //字符序列转换为16进制字符串    private String bytesToHexString(byte[] src) {        StringBuilder stringBuilder = new StringBuilder("0x");        if (src == null || src.length <= 0) {            return null;        }        char[] buffer = new char[2];        for (int i = 0; i < src.length; i++) {            buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F, 16);            buffer[1] = Character.forDigit(src[i] & 0x0F, 16);            System.out.println(buffer);            stringBuilder.append(buffer);        }        return stringBuilder.toString();    }

Demo

更多相关文章

  1. Android(安卓)SharedPreferences用法及程序共享机制
  2. Firemonkey扩展增强:Android(安卓)浏览器支持Input file标签上传
  3. Android知识梳理之Sqlite数据库的使用和优化
  4. 【笔记】【从Android(安卓)Guide温习Android(安卓)三】意图 (Int
  5. android settings--简述获取部分系统属性
  6. Android之SQLite数据库使用
  7. Android进阶笔记10:Android(安卓)万能适配器
  8. sqlite3加密方案sqlcipher,及sqlcipher使用指南
  9. Android(安卓)- SQLite

随机推荐

  1. Android公共库——图片缓存 网络缓存 下
  2. strut2服务器与android交互数据
  3. android调用输入软键盘回车键跟删除键
  4. Android(安卓)项目导入后真机运行提示:W/d
  5. 安卓selector使用方法
  6. Android属性之build.prop,及property_get/
  7. Android 在 LinearLayout 添加分割线 div
  8. 如何给你的Android 安装文件(APK)瘦身
  9. Android(安卓)给 app默认权限(不弹窗申请
  10. Android 动态logo bootanimation.zip 制