NFC是一套短距离的无线通信,通常距离是4厘米或更短。NFC工作频率是13.56M Hz,传输速率是106kbit/s 到848kbit/s.NFC总是在一个发起者和一个被动目标之间发生。发起者发出近场无线电波,这个近场可以给被动目标供电。这些被动的目标包括不需要电源的标签,卡,也可以是有电源的设备。
与其他无线通信技术比较,例如蓝牙和WiFi, NFC提供更低贷款和距离,并且低成本,不需要供电,不需要实现匹配,整个通信过程仅仅是短短的靠近一秒就能完成。
一个带有NFC支持的android设备通常是一个发起者。也可以作为NFC的读写设备。他将检测NFC tags并且打开一个Activity来处理. Android 2.3.3还有支持有限的P2P。
Tags分很多种,其中简单的只提供读写段,有的只能读。复杂的tags可以支持一些运算,加密来控制对tags里数据段的读写。甚至一些tags上有简单的操作系统,允许一些复杂的交互和可以执行一些代码。
本文的代码例子是基于API10的。
要在Android手机中使用NFC,必须在AndroidManifest.xml中如下配置:
<uses-feature android:name="android.hardware.nfc"
android:required="true" />
<uses-permissionandroid:name="android.permission.NFC" />
Tag发布系统
当android设备扫描到一个NFC tag,通用的行为是自动找最合适的Activity会处理这个tag Intent而不需要用户来选择哪个Activity来处理。因为设备扫描NFCtags是在很短的范围和时间,如果让用户选择的话,那就有可能需要移动设备,这样将会打断这个扫描过程。
你应该开发你只处理需要处理的tags的Activity,以防止让用户选择使用哪个Activity来处理的情况。
Android提供两个系统来帮助你正确的识别一个NFC tag是否是你的Activity想要处理的:
1.Intent发布系统
2.前台Activity发布系统。
Intent发布系统检查所有Activities的intent filters,找出那些定义了可以处理此tag的Activity,如果有多个Activity都配置了处理同一个tag Intent,那么将使用Activity选择器来让用户选择使用哪个Activity。用户选择之后,将使用选择的Activity来处理此Intent.
前台发布系统允许一个Activity覆盖掉Intent发布系统而首先处理此tag Intent,这要求你将要处理Tag Intent的Activity运行在前台,这样当一个NFC tag被扫描到,系统先检测前台的Activity是否支持处理此Intent,如果支持,即将此Intent传给此Activity,如果不支持,则转到Intent发布系统。
以前台前台发布系统为例,需要编写如下代码:
1. 定义变量
private NfcAdaptermAdapter;
private String[][] techList;
private IntentFilter[] intentFilters;
private PendingIntent pendingIntent;
private Tag tag;
2. 添加下列代码到Activity的onCreate() 方法里:
//获取nfc适配器
mAdapter =NfcAdapter.getDefaultAdapter(this);
//在Intent filters里声明你想要处理的Intent,一个tag被检测到时先检查前台发布系统,
//如果前台Activity符合Intent filter的要求,那么前台的Activity的将处理此Intent。
//如果不符合,前台发布系统将Intent转到Intent发布系统。如果指定了null的Intent filters,
//当任意tag被检测到时,你将收到TAG_DISCOVERED intent。因此请注意你应该只处理你想要的Intent。
techList = new String[][] {
new String[] { android.nfc.tech.NfcV.class.getName() },
new String[] { android.nfc.tech.NfcA.class.getName() } };
intentFilters = new IntentFilter[]{ new IntentFilter(
NfcAdapter.ACTION_TECH_DISCOVERED), };
//创建一个 PendingIntent 对象, 这样Android系统就能在一个tag被检测到时定位到这个对象
pendingIntent =PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
3. 在onNewIntent方法中:
public void onNewIntent(Intent intent) {
tag =intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
return;
}
4. 在OnPause方法中:
@Override
protected void onPause() {
super.onPause();
mAdapter.disableForegroundDispatch(this);
}
4. 在OnResume方法中:
@Override
protected void onResume() {
super.onResume();
//使用前台发布系统
mAdapter.enableForegroundDispatch(this,pendingIntent, intentFilters,
techList);
}
}
定义了这些方法以后,运行程序,在不锁屏的情况下,使用NFCV或NFCA的NFC卡靠近的手机的时候OnNewIntent就会被触发。Tag就可以被获取到,可以使用获取到的TAG来查询该卡的一些详细信息和数据。
先了解一下定义了这些方法以后,运行程序,在不锁屏的情况下,使用NFCV或NFCA的NFC卡靠近的手机的时候OnNewIntent就会被触发。Tag就可以被获取到,可以使用获取到的TAG来查询该卡的一些详细信息和数据。
协议
在android sdk 的文档中,描述道 “all MifareClassic I/O operations will be supported, and MIFARE_CLASSIC NDEF tags will also be supported. In either case, NfcA will also be enumerated on the tag, because all MIFAREClassic tags are also NfcA.” 所以说NFCA协议是兼容MifareClassic 协议的,我们可以通过NfcA在android的相关类来处理给予MifareClassic 的RFID卡。
一般来说,给予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的卡来说,每个区最后一个块叫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的keyA或KEY B。
读数据的例子:
//tag 就是在上一篇中onNewIntent中获取的tag
MifareClassicmc = MifareClassic.get(tag);
short startAddress = 0;
short endAddress = 5;
byte[] data = new byte[(endAddress - startAddress + 1 ) *ByteCountPerBlock];
try {
mc.connect();
for (short i =startAddress; i <= endAddress; i++ ,time++)
{
boolean auth = false;
short sectorAddress = getSectorAddress(i);
auth =mc.authenticateSectorWithKeyA(sectorAddress, MifareClassic.KEY_DEFAULT);
if (auth){
//the last block of the sector is usedfor KeyA and KeyB cannot be overwritted
short readAddress = (short)(sectorAddress== 0 ? i : i + sectorAddress);
byte[] response =mc.readBlock(readAddress);
CombineByteArray(data,response, time * ByteCountPerBlock);
}
else{
throw newNfcException(NfcErrorCode.TemporaryError,
"AuthorizationError.");
}
}
mc.close();
}
catch (NfcException ne) {
throw ne;
}
catch (IOException e) {
throw newNfcException(NfcErrorCode.TemporaryError,
"Get response, what itis not successfully.", e);
}
finally
{
try {
mc.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
写数据的例子:
//tag 就是在上一篇中onNewIntent中获取的tag
MifareClassicmc = MifareClassic.get(tag);
try {
mc.connect();
boolean auth = false;
short sectorAddress = 0
auth =mc.authenticateSectorWithKeyA(sectorAddress,
MifareClassic.KEY_DEFAULT);
if (auth) {
//the last block of the sector is usedfor KeyA and KeyB cannot be overwritted
mc.writeBlock(readAddress,dataTemp);
mc.close();
}
}finally
{
try {
mc.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
完整的代码示例在这里下载
android NFC学习笔记(二)
(2012-09-3012:30:17)
标签: |
分类:技术之菜 |
在上一篇中的最后写到了如何过滤和接收一个nfc intent。那么这里要写如何解析已经收到的nfc intent。
一:解析NDEF(NFC Data Exchange Format):NFC数据转换格式
一直在说ndef格式,这到底是个啥格式?据文档说这是google参考nfc论坛而提出和支持的格式。也就是说nfc的格式和标准肯定有很多,google只不过比较支持其中的这一种NDEF。而如果之后apple也搞nfc的话,我怀疑他肯定就不会去支持google已经支持的ndef了。我浅薄的如此认为,以后再看看吧呵呵
NFC的NDEF格式 |
ndef格式,就是数据被包裹在一个NdefMessage里,而同时一个NdefMessage里面又可以包含多个NdefRecord.当然你也应该还记得,nfc的tag里面是可以不包含Ndef数据的,他也可以包含android.nfc.tech所定义的多种标签。Google推荐开发人员使用ndef格式的数据来处理android相关的nfc格式数据。
NdefMessage |
Ndef:NdefMessage
NdefRecord |
当一个android设备检测到nfc标签包含ndef格式的数据的时候,他就会尝试的去解析数据中的MIME type或者是URI。为了做这个事情,他需要解析NdefMessage中的第一个NdefRecord。第一个NdefRecord中的结构一般如下:
3-bit TNF (Type Name Format)
如何解释variablelength type的数据。并依据不同的解析,来确定发送什么样的intent。如果是这里定义的是TNF_WELL_KNOWN则还要参考variablelength type来进一步确定。如果是网络地址URL则直接去调用浏览器程序了。
Variablelength type
进一步定义record的类型。具体的请参看android文档。不过话在这里多说一句,TNF和TYPE这两个参数非常的重要,直接决定了你NDEF格式的命中程度。定义的越规范,越清楚,同时在androidmanifest文件中写的越正确,就越好,否则系统就会被你发TECH_ACTION
比如:构造的时候写
newNdefRecord(
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"/>
<dataandroid: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) {
// TODOAuto-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: 6bytes
AccessConditions: 4 bytes
Key B: 6bytes
所以在写卡的内存的时候,一般不能写每个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();
intsectorCount = 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 ofcycles 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-generatedcatch block
e.printStackTrace();
}
}
标签: androidtypesactionlayoutstringbuffer
2012-04-06 08:56 67134人阅读 评论(60) 收藏举报
本文章已收录于:
分类:
Android应用开发系列教程(126)
Android应用开发技巧(138)
版权声明:本文为博主原创文章,未经博主允许不得转载。
作者:裘德超
使用硬件:Google NexusS,北京大学学生卡。(ps:笔者本想使用公交一卡通进行测试,发现手机不能正确识别)
手机操作系统:Android ICS 4.04。
开发时,笔者从Google PlayStore上下载了NFC TagInfo软件进行对比学习。所以我们可以使用任意一张能被TagInfo软件正确识别的卡做测试。
在Android NFC 应用中,Android手机通常是作为通信中的发起者,也就是作为各种NFC卡的读写器。Android对NFC的支持主要在 android.nfc 和android.nfc.tech 两个包中。
android.nfc 包中主要类如下:
NfcManager 可以用来管理Android设备中指出的所有NFCAdapter,但由于大部分Android设备只支持一个NFC Adapter,所以一般直接调用getDefaultAapater来获取手机中的Adapter。
NfcAdapter 相当于一个NFC适配器,类似于电脑装了网络适配器才能上网,手机装了NfcAdapter才能发起NFC通信。
NDEF: NFC Data Exchange Format,即NFC数据交换格式。
NdefMessage 和NdefRecord NDEF 为NFC forum 定义的数据格式。
Tag 代表一个被动式Tag对象,可以代表一个标签,卡片等。当Android设备检测到一个Tag时,会创建一个Tag对象,将其放在Intent对象,然后发送到相应的Activity。
android.nfc.tech 中则定义了可以对Tag进行的读写操作的类,这些类按照其使用的技术类型可以分成不同的类如:NfcA, NfcB, NfcF,以及MifareClassic 等。其中MifareClassic比较常见。
在本次实例中,笔者使用北京大学学生卡进行数据读取测试,学生卡的TAG类型为MifareClassic。
AndroidManifest.xml:
[html] view plain copy print?
<span style="font-size: 16px;"><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.reno"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.NFC" />
<uses-sdk android:minSdkVersion="14" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="org.reno.Beam"
android:label="@string/app_name"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</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_filter" />
</activity>
</application>
</manifest>
</span>
<?xml version="1.0"encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.reno"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.NFC"/>
<uses-sdk android:minSdkVersion="14" />
<uses-feature android:name="android.hardware.nfc"android:required="true" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="org.reno.Beam"
android:label="@string/app_name"
android:launchMode="singleTop" >
<intent-filter>
<actionandroid:name="android.intent.action.MAIN" />
<categoryandroid:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<actionandroid:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity>
</application>
</manifest>
res/xml/nfc_tech_filter.xml:
<resourcesxmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.MifareClassic</tech>
</tech-list>
</resources>
<uses-permissionandroid:name="android.permission.NFC" />
<uses-featureandroid:name="android.hardware.nfc" android:required="true"/>
表示会使用到硬件的NFC功能。并且当用户在Google Play Store中搜索时,只有带有NFC功能的手机才能够搜索到本应用。
当手机开启了NFC,并且检测到一个TAG后,TAG分发系统会自动创建一个封装了NFC TAG信息的intent。
如果多于一个应用程序能够处理这个intent的话,那么手机就会弹出一个框,让用户选择处理该TAG的Activity。TAG分发系统定义了3中intent。按优先级从高到低排列为:
NDEF_DISCOVERED, TECH_DISCOVERED, TAG_DISCOVERED
当Android设备检测到有NFC Tag靠近时,会根据Action申明的顺序给对应的Activity 发送含NFC消息的 Intent。
此处我们使用的intent-filter的Action类型为TECH_DISCOVERED从而可以处理所有类型为ACTION_TECH_DISCOVERED并且使用的技术为nfc_tech_filter.xml文件中定义的类型的TAG。
详情可查看http://developer.android.com/guide/topics/nfc/nfc.html说明。下图为当手机检测到一个TAG时,启用Activity的匹配过程。
res/layout/main.xml
[html] view plain copy print?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ScrollView
android:id="@+id/scrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:drawable/edit_text" >
<TextView
android:id="@+id/promt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:singleLine="false"
android:text="@string/info" />
</ScrollView>
</LinearLayout>
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ScrollView
android:id="@+id/scrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:drawable/edit_text" >
<TextView
android:id="@+id/promt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:singleLine="false"
android:text="@string/info" />
</ScrollView>
</LinearLayout>
定义了Activity的布局:只有一个带有滚动条的TextView用于显示从TAG中读取的信息。
res/values/strings.xml
[html] view plain copy print?
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NFC测试</string>
<string name="info">扫描中。。。</string>
</resources>
<?xml version="1.0"encoding="utf-8"?>
<resources>
<string name="app_name">NFC测试</string>
<string name="info">扫描中。。。</string>
</resources>
src/org/reno/Beam.java
[java] view plain copy print?
package org.reno;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.os.Bundle;
import android.widget.TextView;
public class Beam extends Activity {
NfcAdapter nfcAdapter;
TextView promt;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
promt = (TextView) findViewById(R.id.promt);
// 获取默认的NFC控制器
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
promt.setText("设备不支持NFC!");
finish();
return;
}
if (!nfcAdapter.isEnabled()) {
promt.setText("请在系统设置中先启用NFC功能!");
finish();
return;
}
}
@Override
protected void onResume() {
super.onResume();
//得到是否检测到ACTION_TECH_DISCOVERED触发
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) {
//处理该intent
processIntent(getIntent());
}
}
//字符序列转换为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();
}
/**
* Parses the NDEF Message from the intent and prints to the TextView
*/
private void processIntent(Intent intent) {
//取出封装在intent中的TAG
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
for (String tech : tagFromIntent.getTechList()) {
System.out.println(tech);
}
boolean auth = false;
//读取TAG
MifareClassic mfc = MifareClassic.get(tagFromIntent);
try {
String metaInfo = "";
//Enable I/O operations to the tag from this TagTechnology object.
mfc.connect();
int type = mfc.getType();//获取TAG的类型
int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数
String typeS = "";
switch (type) {
case MifareClassic.TYPE_CLASSIC:
typeS = "TYPE_CLASSIC";
break;
case MifareClassic.TYPE_PLUS:
typeS = "TYPE_PLUS";
break;
case MifareClassic.TYPE_PRO:
typeS = "TYPE_PRO";
break;
case MifareClassic.TYPE_UNKNOWN:
typeS = "TYPE_UNKNOWN";
break;
}
metaInfo += "卡片类型:" + typeS + "\n共" + sectorCount + "个扇区\n共"
+ mfc.getBlockCount() + "个块\n存储空间: " + mfc.getSize() + "B\n";
for (int j = 0; j < sectorCount; j++) {
//Authenticate a sector with key A.
auth = mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_DEFAULT);
int bCount;
int bIndex;
if (auth) {
metaInfo += "Sector " + j + ":验证成功\n";
// 读取扇区中的块
bCount = mfc.getBlockCountInSector(j);
bIndex = mfc.sectorToBlock(j);
for (int i = 0; i < bCount; i++) {
byte[] data = mfc.readBlock(bIndex);
metaInfo += "Block " + bIndex + " : "
+ bytesToHexString(data) + "\n";
bIndex++;
}
} else {
metaInfo += "Sector " + j + ":验证失败\n";
}
}
promt.setText(metaInfo);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package org.reno;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.os.Bundle;
import android.widget.TextView;
public class Beam extends Activity {
NfcAdapternfcAdapter;
TextViewpromt;
@Override
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
promt= (TextView) findViewById(R.id.promt);
//获取默认的NFC控制器
nfcAdapter= NfcAdapter.getDefaultAdapter(this);
if(nfcAdapter == null) {
promt.setText("设备不支持NFC!");
finish();
return;
}
if(!nfcAdapter.isEnabled()) {
promt.setText("请在系统设置中先启用NFC功能!");
finish();
return;
}
}
@Override
protectedvoid onResume() {
super.onResume();
//得到是否检测到ACTION_TECH_DISCOVERED触发
if(NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) {
//处理该intent
processIntent(getIntent());
}
}
//字符序列转换为16进制字符串
privateString bytesToHexString(byte[] src) {
StringBuilderstringBuilder = new StringBuilder("0x");
if(src == null || src.length <= 0) {
returnnull;
}
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);
}
returnstringBuilder.toString();
}
/**
* Parses the NDEF Message from the intent andprints to the TextView
*/
privatevoid processIntent(Intent intent) {
//取出封装在intent中的TAG
TagtagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
for(String tech : tagFromIntent.getTechList()) {
System.out.println(tech);
}
booleanauth = false;
//读取TAG
MifareClassicmfc = MifareClassic.get(tagFromIntent);
try{
StringmetaInfo = "";
//EnableI/O operations to the tag from this TagTechnology object.
mfc.connect();
inttype = mfc.getType();//获取TAG的类型
intsectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数
StringtypeS = "";
switch(type) {
caseMifareClassic.TYPE_CLASSIC:
typeS= "TYPE_CLASSIC";
break;
caseMifareClassic.TYPE_PLUS:
typeS= "TYPE_PLUS";
break;
caseMifareClassic.TYPE_PRO:
typeS= "TYPE_PRO";
break;
caseMifareClassic.TYPE_UNKNOWN:
typeS= "TYPE_UNKNOWN";
break;
}
metaInfo+= "卡片类型:" +typeS + "\n共" +sectorCount + "个扇区\n共"
+mfc.getBlockCount() + "个块\n存储空间: " +mfc.getSize() + "B\n";
for(int j = 0; j < sectorCount; j++) {
//Authenticatea sector with key A.
auth= mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_DEFAULT);
intbCount;
intbIndex;
if(auth) {
metaInfo+= "Sector " + j + ":验证成功\n";
//读取扇区中的块
bCount= mfc.getBlockCountInSector(j);
bIndex= mfc.sectorToBlock(j);
for(int i = 0; i < bCount; i++) {
byte[]data = mfc.readBlock(bIndex);
metaInfo+= "Block " + bIndex + " : "
+bytesToHexString(data) + "\n";
bIndex++;
}
}else {
metaInfo+= "Sector " + j + ":验证失败\n";
}
}
promt.setText(metaInfo);
}catch (Exception e) {
e.printStackTrace();
}
}
}
关于MifareClassic卡的背景介绍:数据分为16个区(Sector) ,每个区有4个块(Block) ,每个块可以存放16字节的数据。
每个区最后一个块称为Trailer ,主要用来存放读写该区Block数据的Key ,可以有A,B两个Key,每个Key 长度为6个字节,缺省的Key值一般为全FF或是0. 由 MifareClassic.KEY_DEFAULT 定义。
因此读写Mifare Tag 首先需要有正确的Key值(起到保护的作用),如果鉴权成功
然后才可以读写该区数据。
执行效果:
Android NFC开发教程: Mifare Tag读写示例
2013-01-23 14:42 佚名 eoeAndroid 我要评论(0) 字号:T | T
本文介绍Mifare Tag的具体规格,和如何利用AndroidSDK中的NFC包来读写Mifare Tag中的数据。
AD: 51CTO网+ 首届中国APP创新评选大赛火热招募中……
本例针对常用的Mifare Tag具体说明。
MifareTag 可以有1K ,2K, 4K,其内存分区大同小异,下图给出了1K字节容量的Tag的内存分布:
数据分为16个区(Sector) ,每个区有4个块(Block) ,每个块可以存放16字节的数据,其大小为16 X 4 X 16 =1024 bytes。
每个区最后一个块称为Trailer ,主要用来存放读写该区Block数据的Key ,可以有A,B两个Key,每个Key 长度为6个字节,缺省的Key值一般为全FF或是0. 由 MifareClassic.KEY_DEFAULT 定义。
因此读写Mifare Tag 首先需要有正确的Key值(起到保护的作用),如果鉴权成功:
1. auth = mfc.authenticateSectorWithKeyA(j, MifareClassic.KEY_DEFAULT);
然后才可以读写该区数据。
本例定义几个Mifare相关的类 MifareClassCard ,MifareSector, MifareBlock 和MifareKey 以方便读写Mifare Tag.
Android 系统来检测到NFC Tag, 将其封装成Tag类,存放到Intent的NfcAdapter.EXTRA_TAGExtra 数据包中,可以使用MifareClassic.get(Tag) 获取对象的 MifareClassic类。
1. Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
2. // 4) Get an instance of the Mifare classic card from this TAG
3. // intent MifareClassic mfc = MifareClassic.get(tagFromIntent);
下面为读取Mifare card 的主要代码:
1. // 1) Parse the intent and get the action that triggered this intent
2. String action = intent.getAction();
3. // 2) Check if it was triggered by a tag discovered interruption.
4. if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
5. // 3) Get an instance of the TAG from the NfcAdapter
6. Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
7. // 4) Get an instance of the Mifare classic card from this TAG
8. // intent
9. MifareClassic mfc = MifareClassic.get(tagFromIntent);
10. MifareClassCard mifareClassCard=null;
11.
12. try { // 5.1) Connect to card
13. mfc.connect();
14. boolean auth = false;
15. // 5.2) and get the number of sectors this card has..and loop
16. // thru these sectors
17. int secCount = mfc.getSectorCount();
18. mifareClassCard= new MifareClassCard(secCount);
19. int bCount = 0;
20. int bIndex = 0;
21. for (int j = 0; j < secCount; j++) {
22. MifareSector mifareSector = new MifareSector();
23. mifareSector.sectorIndex = j;
24. // 6.1) authenticate the sector
25. auth = mfc.authenticateSectorWithKeyA(j,
26. MifareClassic.KEY_DEFAULT);
27. mifareSector.authorized = auth;
28. if (auth) {
29. // 6.2) In each sector - get the block count
30. bCount = mfc.getBlockCountInSector(j);
31. bCount =Math.min(bCount, MifareSector.BLOCKCOUNT);
32. bIndex = mfc.sectorToBlock(j);
33. for (int i = 0; i < bCount; i++) {
34.
35. // 6.3) Read the block
36. byte []data = mfc.readBlock(bIndex);
37. MifareBlock mifareBlock = new MifareBlock(data);
38. mifareBlock.blockIndex = bIndex;
39. // 7) Convert the data into a string from Hex
40. // format.
41.
42. bIndex++;
43. mifareSector.blocks<i> = mifareBlock;
44.
45. }
46. mifareClassCard.setSector(mifareSector.sectorIndex,
47. mifareSector);
48. } else { // Authentication failed - Handle it
49.
50. }
51. }
52. ArrayList<String> blockData=new ArrayList<String>();
53. int blockIndex=0;
54. for(int i=0;i<secCount;i++){
55.
56. MifareSector mifareSector=mifareClassCard.getSector(i);
57. for(int j=0;j<MifareSector.BLOCKCOUNT;j++){
58. MifareBlock mifareBlock=mifareSector.blocks[j];
59. byte []data=mifareBlock.getData();
60. blockData.add("Block "+ blockIndex++ +" : "+
61. Converter.getHexString(data, data.length));
62. }
63. }
64. String []contents=new String[blockData.size()];
65. blockData.toArray(contents);
66. setListAdapter(new ArrayAdapter<String>(this,
67. android.R.layout.simple_list_item_1, contents));
68. getListView().setTextFilterEnabled(true);
69.
70. } catch (IOException e) {
71. Log.e(TAG, e.getLocalizedMessage());
72. showAlert(3);
73. }finally{
74.
75. if(mifareClassCard!=null){
76. mifareClassCard.debugPrint();
77. }
78. }
79. }
运行结果: