Android NFC 开发实例

来源:互联网 发布:js的继承 编辑:程序博客网 时间:2024/05/22 19:26

作者:裘德超


使用硬件:Google Nexus S,北京大学学生卡。(ps:笔者本想使用公交一卡通进行测试,发现手机不能正确识别)

手机操作系统:Android ICS 4.04。

开发时,笔者从Google Play Store上下载了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 plaincopy
  1. <span style="font-size:16px;"><?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="org.reno"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.     <uses-permission android:name="android.permission.NFC" />  
  7.     <uses-sdk android:minSdkVersion="14" />  
  8.     <uses-feature android:name="android.hardware.nfc" android:required="true" />  
  9.     <application  
  10.         android:icon="@drawable/ic_launcher"  
  11.         android:label="@string/app_name" >  
  12.         <activity  
  13.             android:name="org.reno.Beam"  
  14.             android:label="@string/app_name"  
  15.             android:launchMode="singleTop" >  
  16.             <intent-filter>  
  17.                 <action android:name="android.intent.action.MAIN" />  
  18.   
  19.                 <category android:name="android.intent.category.LAUNCHER" />  
  20.             </intent-filter>  
  21.             <intent-filter>  
  22.                 <action android:name="android.nfc.action.TECH_DISCOVERED" />  
  23.             </intent-filter>  
  24.             <meta-data  
  25.                 android:name="android.nfc.action.TECH_DISCOVERED"  
  26.                 android:resource="@xml/nfc_tech_filter" />  
  27.         </activity>  
  28.     </application>  
  29. </manifest>  
  30. </span>  


res/xml/nfc_tech_filter.xml:


<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

    <tech-list>

       <tech>android.nfc.tech.MifareClassic</tech>

    </tech-list>

</resources>


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

<uses-feature android: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 plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <ScrollView  
  8.         android:id="@+id/scrollView"  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="fill_parent"  
  11.         android:background="@android:drawable/edit_text" >  
  12.   
  13.         <TextView  
  14.             android:id="@+id/promt"  
  15.             android:layout_width="fill_parent"  
  16.             android:layout_height="wrap_content"  
  17.             android:scrollbars="vertical"  
  18.             android:singleLine="false"  
  19.             android:text="@string/info" />  
  20.     </ScrollView>  
  21.   
  22. </LinearLayout>  

定义了Activity的布局:只有一个带有滚动条的TextView用于显示从TAG中读取的信息。

res/values/strings.xml


[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <string name="app_name">NFC测试</string>  
  4.     <string name="info">扫描中。。。</string>  
  5. </resources>  

src/org/reno/Beam.java

[java] view plaincopy
  1. package org.reno;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.nfc.NfcAdapter;  
  6. import android.nfc.Tag;  
  7. import android.nfc.tech.MifareClassic;  
  8. import android.os.Bundle;  
  9. import android.widget.TextView;  
  10.   
  11. public class Beam extends Activity {  
  12.     NfcAdapter nfcAdapter;  
  13.     TextView promt;  
  14.     @Override  
  15.     public void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.main);  
  18.         promt = (TextView) findViewById(R.id.promt);  
  19.         // 获取默认的NFC控制器  
  20.         nfcAdapter = NfcAdapter.getDefaultAdapter(this);  
  21.         if (nfcAdapter == null) {  
  22.             promt.setText("设备不支持NFC!");  
  23.             finish();  
  24.             return;  
  25.         }  
  26.         if (!nfcAdapter.isEnabled()) {  
  27.             promt.setText("请在系统设置中先启用NFC功能!");  
  28.             finish();  
  29.             return;  
  30.         }  
  31.     }  
  32.   
  33.     @Override  
  34.     protected void onResume() {  
  35.         super.onResume();  
  36.         //得到是否检测到ACTION_TECH_DISCOVERED触发  
  37.         if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) {  
  38.             //处理该intent  
  39.             processIntent(getIntent());  
  40.         }  
  41.     }  
  42.     //字符序列转换为16进制字符串  
  43.     private String bytesToHexString(byte[] src) {  
  44.         StringBuilder stringBuilder = new StringBuilder("0x");  
  45.         if (src == null || src.length <= 0) {  
  46.             return null;  
  47.         }  
  48.         char[] buffer = new char[2];  
  49.         for (int i = 0; i < src.length; i++) {  
  50.             buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F16);  
  51.             buffer[1] = Character.forDigit(src[i] & 0x0F16);  
  52.             System.out.println(buffer);  
  53.             stringBuilder.append(buffer);  
  54.         }  
  55.         return stringBuilder.toString();  
  56.     }  
  57.   
  58.     /** 
  59.      * Parses the NDEF Message from the intent and prints to the TextView 
  60.      */  
  61.     private void processIntent(Intent intent) {  
  62.         //取出封装在intent中的TAG  
  63.         Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);  
  64.         for (String tech : tagFromIntent.getTechList()) {  
  65.             System.out.println(tech);  
  66.         }  
  67.         boolean auth = false;  
  68.         //读取TAG  
  69.         MifareClassic mfc = MifareClassic.get(tagFromIntent);  
  70.         try {  
  71.             String metaInfo = "";  
  72.             //Enable I/O operations to the tag from this TagTechnology object.  
  73.             mfc.connect();  
  74.             int type = mfc.getType();//获取TAG的类型  
  75.             int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数  
  76.             String typeS = "";  
  77.             switch (type) {  
  78.             case MifareClassic.TYPE_CLASSIC:  
  79.                 typeS = "TYPE_CLASSIC";  
  80.                 break;  
  81.             case MifareClassic.TYPE_PLUS:  
  82.                 typeS = "TYPE_PLUS";  
  83.                 break;  
  84.             case MifareClassic.TYPE_PRO:  
  85.                 typeS = "TYPE_PRO";  
  86.                 break;  
  87.             case MifareClassic.TYPE_UNKNOWN:  
  88.                 typeS = "TYPE_UNKNOWN";  
  89.                 break;  
  90.             }  
  91.             metaInfo += "卡片类型:" + typeS + "\n共" + sectorCount + "个扇区\n共"  
  92.                     + mfc.getBlockCount() + "个块\n存储空间: " + mfc.getSize() + "B\n";  
  93.             for (int j = 0; j < sectorCount; j++) {  
  94.                 //Authenticate a sector with key A.  
  95.                 auth = mfc.authenticateSectorWithKeyA(j,  
  96.                         MifareClassic.KEY_DEFAULT);  
  97.                 int bCount;  
  98.                 int bIndex;  
  99.                 if (auth) {  
  100.                     metaInfo += "Sector " + j + ":验证成功\n";  
  101.                     // 读取扇区中的块  
  102.                     bCount = mfc.getBlockCountInSector(j);  
  103.                     bIndex = mfc.sectorToBlock(j);  
  104.                     for (int i = 0; i < bCount; i++) {  
  105.                         byte[] data = mfc.readBlock(bIndex);  
  106.                         metaInfo += "Block " + bIndex + " : "  
  107.                                 + bytesToHexString(data) + "\n";  
  108.                         bIndex++;  
  109.                     }  
  110.                 } else {  
  111.                     metaInfo += "Sector " + j + ":验证失败\n";  
  112.                 }  
  113.             }  
  114.             promt.setText(metaInfo);  
  115.         } catch (Exception e) {  
  116.             e.printStackTrace();  
  117.         }  
  118.     }  
  119. }  


关于MifareClassic卡的背景介绍:数据分为16个区(Sector) ,每个区有4个块(Block) ,每个块可以存放16字节的数据。

每个区最后一个块称为Trailer ,主要用来存放读写该区Block数据的Key ,可以有A,B两个Key,每个Key 长度为6个字节,缺省的Key值一般为全FF或是0. 由 MifareClassic.KEY_DEFAULT 定义。

因此读写Mifare Tag 首先需要有正确的Key值(起到保护的作用),如果鉴权成功

然后才可以读写该区数据。

执行效果:








参考联接:

http://developer.android.com/guide/topics/nfc/nfc.html

http://developer.android.com/reference/android/nfc/tech/MifareClassic.html

http://www.imobilebbs.com/wordpress/?p=2822


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 胳膊疼就和抻筋是的怎么办 四岁胳膊扭了肿了怎么办 干燥综合症使皮肤变黑色了怎么办 刚生的小孩脖子上有淋巴结怎么办 地包天矫正期间嘴歪了怎么办 5个月的宝宝歪胫怎么办 三个多月的宝宝有点斜颈怎么办 5月宝宝有点斜颈偏向左侧怎么办 外斜术后变周期性内斜怎么办 右腰劳损休息一个多月还痛怎么办 宝宝鼻子不通气用嘴呼吸怎么办 窦性心律过速怎么办st-t改变 跑步机跑带上撒油跑起来滑怎么办 娃儿上嘴唇里面破了怎么办图片 支付宝添加好友点忽略了怎么办 如果综合评价考了两间大学怎么办 国二报名时民族错了怎么办 屁股凹陷下去一块已经萎缩了怎么办 我家孩子上高中了英语不好怎么办 驼背脖子向前倾怎么办用背背佳 屁股大又肥是骨盆前倾怎么办 手术40多天引起小腿变细了怎么办 老师教育学生学生跑出教室你怎么办 有矛盾舍友发朋友圈攻击我怎么办 飞机杯送的润滑油很快用完了怎么办 人笨记性不好内向反应迟钝该怎么办 洗衣机滋生了大量的霉菌在怎么办? 爸妈给兄弟买的房子贵很多怎么办 输了十几万 不敢跟爸妈讲怎么办 赌博欠2万不敢和爸妈讲怎么办 学校凳子坏了丢了 现在要查怎么办 我想写字可就是写的不好看怎么办 我的字写的很丑怎么办 大母指腱鞘炎好了之后又犯了怎么办 被蚊子咬了好大一个包怎么办 欠我钱的人跑了怎么办 下面旁边破了沙的疼怎么办 协助民警执法时质疑辅警身份怎么办 跑步每次腿落地小腿骨头疼怎么办 两年义务兵完了想继续当兵怎么办 保研联系老师说名额已满怎么办