NFC

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是在很短的范围和时间,如果让用户选择的话,那就有可能需要移动设备,这样将会打断这个扫描过程。

你应该开发你只处理需要处理的tagsActivity,以防止让用户选择使用哪个Activity来处理的情况。

Android提供两个系统来帮助你正确的识别一个NFC tag是否是你的Activity想要处理的:

1.Intent发布系统

2.前台Activity发布系统。

Intent发布系统检查所有Activitiesintent filters,找出那些定义了可以处理此tagActivity,如果有多个Activity都配置了处理同一个tag Intent,那么将使用Activity选择器来让用户选择使用哪个Activity。用户选择之后,将使用选择的Activity来处理此Intent.

前台发布系统允许一个Activity覆盖掉Intent发布系统而首先处理此tag Intent,这要求你将要处理Tag IntentActivity运行在前台,这样当一个NFC tag被扫描到,系统先检测前台的Activity是否支持处理此Intent,如果支持,即将此Intent传给此Activity,如果不支持,则转到Intent发布系统。

 

以前台前台发布系统为例,需要编写如下代码:

1. 定义变量

private NfcAdaptermAdapter;

    private String[][] techList;

    private IntentFilter[] intentFilters;

    private PendingIntent pendingIntent;

    private Tag tag;

 

2. 添加下列代码到ActivityonCreate() 方法里:

//获取nfc适配器

        mAdapter =NfcAdapter.getDefaultAdapter(this);

        //定义程序可以兼容的nfc协议,例子为nfcanfcv

        //Intent filters里声明你想要处理的Intent,一个tag被检测到时先检查前台发布系统,

        //如果前台Activity符合Intent filter的要求,那么前台的Activity的将处理此Intent

        //如果不符合,前台发布系统将Intent转到Intent发布系统。如果指定了nullIntent 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);

        }

    }

 

定义了这些方法以后,运行程序,在不锁屏的情况下,使用NFCVNFCANFC卡靠近的手机的时候OnNewIntent就会被触发。Tag就可以被获取到,可以使用获取到的TAG来查询该卡的一些详细信息和数据。

 

NFC (二)读写MifareClassic协议的NFC卡

先了解一下定义了这些方法以后,运行程序,在不锁屏的情况下,使用NFCVNFCANFC卡靠近的手机的时候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 协议的,我们可以通过NfcAandroid的相关类来处理给予MifareClassic RFID卡。

 

一般来说,给予MifareClassic的射频卡,一般内存大小有3种:

1K: 16个分区(sector),每个分区4个块(block),每个块(block) 16byte数据

2K: 32个分区,每个分区4个块(block),每个块(block) 16byte数据

4K64个分区,每个分区4个块(block),每个块(block) 16byte数据

 

对于所有基于MifareClassic的卡来说,每个区最后一个块叫Trailer16byte主要来存放读写该区的key,可以有AB两个KEY,每个key6byte,默认的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的权限,这个权限就是这个sectortrailerkeyAKEY 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)

转载▼

标签:

android

nfc

ndef

tech

mifareclassic

分类:技术之菜

 

在上一篇中的最后写到了如何过滤和接收一个nfc intent。那么这里要写如何解析已经收到的nfc intent

 

一:解析NDEFNFC Data Exchange Format):NFC数据转换格式

   一直在说ndef格式,这到底是个啥格式?据文档说这是google参考nfc论坛而提出和支持的格式。也就是说nfc的格式和标准肯定有很多,google只不过比较支持其中的这一种NDEF。而如果之后apple也搞nfc的话,我怀疑他肯定就不会去支持google已经支持的ndef了。我浅薄的如此认为,以后再看看吧呵呵

NFC的NDEF格式

  ndef格式,就是数据被包裹在一个NdefMessage里,而同时一个NdefMessage里面又可以包含多个NdefRecord.当然你也应该还记得,nfctag里面是可以不包含Ndef数据的,他也可以包含android.nfc.tech所定义的多种标签。Google推荐开发人员使用ndef格式的数据来处理android相关的nfc格式数据。

NdefMessage

NdefNdefMessage

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文档。不过话在这里多说一句,TNFTYPE这两个参数非常的重要,直接决定了你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) 16byte数据

2K: 32个分区,每个分区4个块(block),每个块(block) 16byte数据

4K64个分区,每个分区4个块(block),每个块(block) 16byte数据

 

 对于所有基于MifareClassic的卡来说,每个区最后一个块(block)叫Trailer16byte,主要来存放读写该区的key,可以有AB两个KEY,每个key6byte,默认的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的权限,这个权限就是这个sectortrailerKEY AKEY 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中本来就只有4block
                   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();
        }
    }

 

Android NFC 开发实例

标签: androidtypesactionlayoutstringbuffer

2012-04-06 08:56 67134人阅读 评论(60) 收藏举报

本文章已收录于:

Android知识库

分类:

Android应用开发系列教程(126)

作者同类文章X

Android应用开发技巧(138)

作者同类文章X

版权声明:本文为博主原创文章,未经博主允许不得转载。

作者:裘德超

使用硬件: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. 

运行结果:

猜你喜欢

转载自blog.csdn.net/briup_qiuqiu/article/details/50911322
NFC