首页 > 代码库 > Android之NFC

Android之NFC

来源:http://blog.csdn.net/bear_huangzhen/article/details/46333421

NFC简介:

Near Field Communication 近场通信,是一种数据传输技术。

与wifi、蓝牙、红外线等数据传输技术的一个主要差异就是有效距离一般不能超过4cm。

 

NFC支持3种工作模式:

1.读卡器模式;

2.仿真卡模式;

3.点对点模式;

 

1.读卡器模式:

通过NFC设备(支持NFC的Android手机)从带有NFC芯片的标签、贴纸、报纸、明信片等媒介读取信息,或将数据写到这些媒介中。

 

2.仿真卡模式:

是将支持NFC的手机或其他电子设备当成借记卡、信用卡、公交卡、门禁卡等IC卡使用;基本原理是将相应的IC卡中的信息(支付凭证)封装成数据包存储在支持NFC的手机中,在使用时还需要一个NFC射频器(相当于刷传统IC卡时使用的刷卡器),将手机靠近NFC射频器,手机就会收到NFC射频器发过来的信号,在通过一系列复杂的验证后,将IC卡的相应信息传入NFC射频器,最后这些IC卡数据会传入NFC射频器连接的计算机,并进行相应的处理(如电子转账、开门等操作)。

 

3.点对点模式:

与蓝牙、红外差不多,可以用于不同的NFC设备之间进行数据交换,只是NFC的点对点模式有效距离更短,不能超过4cm;但是如果两个设备使用的都是Android4.2及以上版本,NFC会直接利用蓝牙传输,这种技术被称为android Beam,所以Android Beam传输数据的两部设备不局限于4cm之内。

 

基础知识:

1.Android SDK API主要支持NFC论坛标准(Forum Standard),这种标准被称为NDEF(NFC Data Exchange Format,NFC数据交换格式);

 

2.Android SDK API支持如下三种NDEF数据的操作:

a.从NFC标签读取NDEF格式的数据;

b.向NFC标签写入NDEF格式的数据;

c.通过Android Beam技术将NDEF数据发送到另一部NFC设备;

 

3.在一个NFC设备读取NFC标签或另一个NFC设备中的数据之前会在0.1秒的时间之内建立NFC连接,然后数据会自动从被读取一端流向读取数据的一端;数据接收端会根据具体的数据格式和标签类型调用相应的Activity(这种行为也称为Tag Dispatch),这些Activity都需要定义Intent Filter,这些Intent Filter中就会指定不同的过滤机制,分为三个级别,也称为NFC的三重过滤机制。

 

4.NDEF_DISCOVERED:

只过滤固定格式的NDEF数据。例如:纯文本、指定协议(http、ftp、smb等)的URI等;

 

  TECH_DISCOVERED:

当ACTION_NDEF_DISCOVERED指定的过滤机制无法匹配Tag时,就会使用这种过滤机制进行匹配,这种过滤机制并不是通过Tag中的数据格式进行匹配的,而是根据Tag支持的数据存储格式进行匹配,因此这种过滤机制的范围更广;

 

  TAG_DISCOVERED:

如果将NFC过滤机制看成if...else if...else语句的话,那么这种过滤机制就相当于else部分,当前面两种过滤机制都匹配失败后,系统就会利用这种过滤机制来处理,这种过滤机制用来处理未识别的Tag(数据格式不对,而且Tag支持的格式也不匹配)。

 

5.Android系统会依次匹配NDEF_DISCOVERED、TECH_DISCOVERED和TAG_DISCOVERED;如果通过三重过滤机制仍然无法匹配Tag,则什么都不做;通常在成功匹配Tag后,Android设备会发出比较清脆的声音,而未成功匹配Tag,就会发出比较沉闷的声音。

 

此过程的处理流程如下图所示:

技术分享

6.在manifest文件中需要设置的部分有:

设置权限:

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

 

限制Android版本:

android:minSdkVersion="14"

 

限制安装的设备:

<uses-feature  android:name="android.hardware.nfc"  android:required="true" />

 

设置Activity的Intent Filter,比如设置为三种过滤机制的一种:

<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>

 

接下来,我们来第一个例子,这个例子是属于读卡器模式,从NFC芯片中读取和写入数据。

它的manifest文件内容如下:

 

[html] view plain copy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.r8c.nfc_demo"  
  4.     android:versionCode="110"  
  5.     android:versionName="1.1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="15"  
  9.         android:targetSdkVersion="17" />  
  10.     <!-- NFC权限声明 -->  
  11.     <uses-permission android:name="android.permission.NFC" />  
  12.   
  13.     <uses-feature  
  14.         android:name="android.hardware.nfc"  
  15.         android:required="true" />  
  16.   
  17.     <application  
  18.         android:allowBackup="true"  
  19.         android:icon="@drawable/ic_launcher"  
  20.         android:label="@string/app_name"  
  21.         android:theme="@style/AppTheme" >  
  22.   
  23.         <activity  
  24.             android:name="com.r8c.nfc_demo.NfcDemoActivity"  
  25.             android:configChanges="orientation|keyboardHidden|screenSize"  
  26.             android:label="@string/app_name"   
  27.             android:launchMode="singleTask">  
  28.             <intent-filter>  
  29.                 <action android:name="android.intent.action.MAIN" />  
  30.   
  31.                 <category android:name="android.intent.category.LAUNCHER" />  
  32.             </intent-filter>  
  33.             <!-- TECH_DISCOVERED类型的nfc -->  
  34.             <intent-filter>  
  35.                 <action android:name="android.nfc.action.TECH_DISCOVERED" />  
  36.             </intent-filter>  
  37.             <!-- 后设资源  调用自己建立的文件夹xml中的文件 -->  
  38.             <meta-data  
  39.                 android:name="android.nfc.action.TECH_DISCOVERED"  
  40.                 android:resource="@xml/nfc_tech_filter" />  
  41.         </activity>  
  42.     </application>  
  43.   
  44. </manifest>  

 

 

它的Activity的内容如下,包括读取、写入、删除三大功能:(其中删除功能是通过写入空值来实现的)

 

[java] view plain copy
 
  1. import java.io.IOException;  
  2. import java.io.UnsupportedEncodingException;  
  3. import java.nio.charset.Charset;  
  4.   
  5. import android.media.AudioManager;  
  6. import android.media.MediaPlayer;  
  7. import android.media.RingtoneManager;  
  8. import android.net.Uri;  
  9. import android.nfc.FormatException;  
  10. import android.nfc.NdefMessage;  
  11. import android.nfc.NdefRecord;  
  12. import android.nfc.NfcAdapter;  
  13. import android.nfc.Tag;  
  14. import android.nfc.tech.MifareUltralight;  
  15. import android.nfc.tech.Ndef;  
  16. import android.nfc.tech.NfcA;  
  17. import android.os.Bundle;  
  18. import android.app.Activity;  
  19. import android.app.PendingIntent;  
  20. import android.content.Context;  
  21. import android.content.Intent;  
  22. import android.content.IntentFilter;  
  23. import android.graphics.Color;  
  24. import android.util.Log;  
  25. import android.view.Menu;  
  26. import android.view.View;  
  27. import android.view.View.OnClickListener;  
  28. import android.widget.Button;  
  29. import android.widget.TextView;  
  30. import android.widget.Toast;  
  31.   
  32. public class NfcDemoActivity extends Activity implements OnClickListener {  
  33.   
  34.     // NFC适配器  
  35.     private NfcAdapter nfcAdapter = null;  
  36.     // 传达意图  
  37.     private PendingIntent pi = null;  
  38.     // 滤掉组件无法响应和处理的Intent  
  39.     private IntentFilter tagDetected = null;  
  40.     // 文本控件  
  41.     private TextView promt = null;  
  42.     // 是否支持NFC功能的标签  
  43.     private boolean isNFC_support = false;  
  44.     // 读、写、删按钮控件  
  45.     private Button readBtn, writeBtn, deleteBtn;  
  46.   
  47.     @Override  
  48.     protected void onCreate(Bundle savedInstanceState) {  
  49.         super.onCreate(savedInstanceState);  
  50.         setContentView(R.layout.activity_nfc_demo);  
  51.         setupViews();  
  52.         initNFCData();  
  53.     }  
  54.   
  55.     @Override  
  56.     protected void onResume() {  
  57.         super.onResume();  
  58.         if (isNFC_support == false) {  
  59.             // 如果设备不支持NFC或者NFC功能没开启,就return掉  
  60.             return;  
  61.         }  
  62.         // 开始监听NFC设备是否连接  
  63.         startNFC_Listener();  
  64.   
  65.         if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(this.getIntent()  
  66.                 .getAction())) {  
  67.             // 注意这个if中的代码几乎不会进来,因为刚刚在上一行代码开启了监听NFC连接,下一行代码马上就收到了NFC连接的intent,这种几率很小  
  68.             // 处理该intent  
  69.             processIntent(this.getIntent());  
  70.         }  
  71.     }  
  72.   
  73.     @Override  
  74.     protected void onPause() {  
  75.         super.onPause();  
  76.         if (isNFC_support == true) {  
  77.             // 当前Activity如果不在手机的最前端,就停止NFC设备连接的监听  
  78.             stopNFC_Listener();  
  79.         }  
  80.     }  
  81.   
  82.     @Override  
  83.     protected void onNewIntent(Intent intent) {  
  84.         super.onNewIntent(intent);  
  85.         // 当前app正在前端界面运行,这个时候有intent发送过来,那么系统就会调用onNewIntent回调方法,将intent传送过来  
  86.         // 我们只需要在这里检验这个intent是否是NFC相关的intent,如果是,就调用处理方法  
  87.         if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {  
  88.             processIntent(intent);  
  89.         }  
  90.     }  
  91.   
  92.     @Override  
  93.     public void onClick(View v) {  
  94.   
  95.         // 点击读按钮后  
  96.         if (v.getId() == R.id.read_btn) {  
  97.             try {  
  98.                 String content = read(tagFromIntent);  
  99.                 if (content != null && !content.equals("")) {  
  100.                     promt.setText(promt.getText() + "nfc标签内容:\n" + content  
  101.                             + "\n");  
  102.                 } else {  
  103.                     promt.setText(promt.getText() + "nfc标签内容:\n" + "内容为空\n");  
  104.                 }  
  105.             } catch (IOException e) {  
  106.                 promt.setText(promt.getText() + "错误:" + e.getMessage() + "\n");  
  107.                 Log.e("myonclick", "读取nfc异常", e);  
  108.             } catch (FormatException e) {  
  109.                 promt.setText(promt.getText() + "错误:" + e.getMessage() + "\n");  
  110.                 Log.e("myonclick", "读取nfc异常", e);  
  111.             }  
  112.             // 点击写后写入  
  113.         } else if (v.getId() == R.id.write_btn) {  
  114.             try {  
  115.                 write(tagFromIntent);  
  116.             } catch (IOException e) {  
  117.                 promt.setText(promt.getText() + "错误:" + e.getMessage() + "\n");  
  118.                 Log.e("myonclick", "写nfc异常", e);  
  119.             } catch (FormatException e) {  
  120.                 promt.setText(promt.getText() + "错误:" + e.getMessage() + "\n");  
  121.                 Log.e("myonclick", "写nfc异常", e);  
  122.             }  
  123.         } else if (v.getId() == R.id.delete_btn) {  
  124.             try {  
  125.                 delete(tagFromIntent);  
  126.             } catch (IOException e) {  
  127.                 promt.setText(promt.getText() + "错误:" + e.getMessage() + "\n");  
  128.                 Log.e("myonclick", "删除nfc异常", e);  
  129.             } catch (FormatException e) {  
  130.                 promt.setText(promt.getText() + "错误:" + e.getMessage() + "\n");  
  131.                 Log.e("myonclick", "删除nfc异常", e);  
  132.             }  
  133.         }  
  134.     }  
  135.   
  136.     private void setupViews() {  
  137.         // 控件的绑定  
  138.         promt = (TextView) findViewById(R.id.promt);  
  139.         readBtn = (Button) findViewById(R.id.read_btn);  
  140.         writeBtn = (Button) findViewById(R.id.write_btn);  
  141.         deleteBtn = (Button) findViewById(R.id.delete_btn);  
  142.         // 给文本控件赋值初始文本  
  143.         promt.setText("等待RFID标签");  
  144.         // 监听读、写、删按钮控件  
  145.         readBtn.setOnClickListener(this);  
  146.         writeBtn.setOnClickListener(this);  
  147.         deleteBtn.setOnClickListener(this);  
  148.     }  
  149.   
  150.     private void initNFCData() {  
  151.         // 初始化设备支持NFC功能  
  152.         isNFC_support = true;  
  153.         // 得到默认nfc适配器  
  154.         nfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());  
  155.         // 提示信息定义  
  156.         String metaInfo = "";  
  157.         // 判定设备是否支持NFC或启动NFC  
  158.         if (nfcAdapter == null) {  
  159.             metaInfo = "设备不支持NFC!";  
  160.             Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();  
  161.             isNFC_support = false;  
  162.         }  
  163.         if (!nfcAdapter.isEnabled()) {  
  164.             metaInfo = "请在系统设置中先启用NFC功能!";  
  165.             Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();  
  166.             isNFC_support = false;  
  167.         }  
  168.   
  169.         if (isNFC_support == true) {  
  170.             init_NFC();  
  171.         } else {  
  172.             promt.setTextColor(Color.RED);  
  173.             promt.setText(metaInfo);  
  174.         }  
  175.     }  
  176.   
  177.     @Override  
  178.     public boolean onCreateOptionsMenu(Menu menu) {  
  179.         // Inflate the menu; this adds items to the action bar if it is present.  
  180.         getMenuInflater().inflate(R.menu.nfc_demo, menu);  
  181.         return true;  
  182.     }  
  183.   
  184.     // 字符序列转换为16进制字符串  
  185.     private String bytesToHexString(byte[] src) {  
  186.         return bytesToHexString(src, true);  
  187.     }  
  188.   
  189.     private String bytesToHexString(byte[] src, boolean isPrefix) {  
  190.         StringBuilder stringBuilder = new StringBuilder();  
  191.         if (isPrefix == true) {  
  192.             stringBuilder.append("0x");  
  193.         }  
  194.         if (src == null || src.length <= 0) {  
  195.             return null;  
  196.         }  
  197.         char[] buffer = new char[2];  
  198.         for (int i = 0; i < src.length; i++) {  
  199.             buffer[0] = Character.toUpperCase(Character.forDigit(  
  200.                     (src[i] >>> 4) & 0x0F, 16));  
  201.             buffer[1] = Character.toUpperCase(Character.forDigit(src[i] & 0x0F,  
  202.                     16));  
  203.             System.out.println(buffer);  
  204.             stringBuilder.append(buffer);  
  205.         }  
  206.         return stringBuilder.toString();  
  207.     }  
  208.   
  209.     private Tag tagFromIntent;  
  210.   
  211.     /** 
  212.      * Parses the NDEF Message from the intent and prints to the TextView 
  213.      */  
  214.     public void processIntent(Intent intent) {  
  215.         if (isNFC_support == false)  
  216.             return;  
  217.   
  218.         // 取出封装在intent中的TAG  
  219.         tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);  
  220.   
  221.         promt.setTextColor(Color.BLUE);  
  222.         String metaInfo = "";  
  223.         metaInfo += "卡片ID:" + bytesToHexString(tagFromIntent.getId()) + "\n";  
  224.         Toast.makeText(this, "找到卡片", Toast.LENGTH_SHORT).show();  
  225.   
  226.         // Tech List  
  227.         String prefix = "android.nfc.tech.";  
  228.         String[] techList = tagFromIntent.getTechList();  
  229.   
  230.         //分析NFC卡的类型: Mifare Classic/UltraLight Info  
  231.         String CardType = "";  
  232.         for (int i = 0; i < techList.length; i++) {  
  233.             if (techList[i].equals(NfcA.class.getName())) {  
  234.                 // 读取TAG  
  235.                 NfcA mfc = NfcA.get(tagFromIntent);  
  236.                 try {  
  237.                     if ("".equals(CardType))  
  238.                         CardType = "MifareClassic卡片类型 \n 不支持NDEF消息 \n";  
  239.                 } catch (Exception e) {  
  240.                     e.printStackTrace();  
  241.                 }  
  242.             } else if (techList[i].equals(MifareUltralight.class.getName())) {  
  243.                 MifareUltralight mifareUlTag = MifareUltralight  
  244.                         .get(tagFromIntent);  
  245.                 String lightType = "";  
  246.                 // Type Info  
  247.                 switch (mifareUlTag.getType()) {  
  248.                 case MifareUltralight.TYPE_ULTRALIGHT:  
  249.                     lightType = "Ultralight";  
  250.                     break;  
  251.                 case MifareUltralight.TYPE_ULTRALIGHT_C:  
  252.                     lightType = "Ultralight C";  
  253.                     break;  
  254.                 }  
  255.                 CardType = lightType + "卡片类型\n";  
  256.   
  257.                 Ndef ndef = Ndef.get(tagFromIntent);  
  258.                 CardType += "最大数据尺寸:" + ndef.getMaxSize() + "\n";  
  259.   
  260.             }  
  261.         }  
  262.         metaInfo += CardType;  
  263.         promt.setText(metaInfo);  
  264.     }  
  265.   
  266.     // 读取方法  
  267.     private String read(Tag tag) throws IOException, FormatException {  
  268.         if (tag != null) {  
  269.             //解析Tag获取到NDEF实例  
  270.             Ndef ndef = Ndef.get(tag);  
  271.             //打开连接  
  272.             ndef.connect();  
  273.             //获取NDEF消息  
  274.             NdefMessage message = ndef.getNdefMessage();  
  275.             //将消息转换成字节数组  
  276.             byte[] data = message.toByteArray();  
  277.             //将字节数组转换成字符串  
  278.             String str = new String(data, Charset.forName("UTF-8"));  
  279.             //关闭连接  
  280.             ndef.close();  
  281.             return str;  
  282.         } else {  
  283.             Toast.makeText(NfcDemoActivity.this, "设备与nfc卡连接断开,请重新连接...",  
  284.                     Toast.LENGTH_SHORT).show();  
  285.         }  
  286.         return null;  
  287.     }  
  288.   
  289.     // 写入方法  
  290.     private void write(Tag tag) throws IOException, FormatException {  
  291.         if (tag != null) {  
  292.             //新建NdefRecord数组,本例中数组只有一个元素  
  293.             NdefRecord[] records = { createRecord() };  
  294.             //新建一个NdefMessage实例  
  295.             NdefMessage message = new NdefMessage(records);  
  296.             // 解析TAG获取到NDEF实例  
  297.             Ndef ndef = Ndef.get(tag);  
  298.             // 打开连接  
  299.             ndef.connect();  
  300.             // 写入NDEF信息  
  301.             ndef.writeNdefMessage(message);  
  302.             // 关闭连接  
  303.             ndef.close();  
  304.             promt.setText(promt.getText() + "写入数据成功!" + "\n");  
  305.         } else {  
  306.             Toast.makeText(NfcDemoActivity.this, "设备与nfc卡连接断开,请重新连接...",  
  307.                     Toast.LENGTH_SHORT).show();  
  308.         }  
  309.     }  
  310.   
  311.     // 删除方法  
  312.     private void delete(Tag tag) throws IOException, FormatException {  
  313.         if (tag != null) {  
  314.             //新建一个里面无任何信息的NdefRecord实例  
  315.             NdefRecord nullNdefRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,  
  316.                     new byte[] {}, new byte[] {}, new byte[] {});  
  317.             NdefRecord[] records = { nullNdefRecord };  
  318.             NdefMessage message = new NdefMessage(records);  
  319.             // 解析TAG获取到NDEF实例  
  320.             Ndef ndef = Ndef.get(tag);  
  321.             // 打开连接  
  322.             ndef.connect();  
  323.             // 写入信息  
  324.             ndef.writeNdefMessage(message);  
  325.             // 关闭连接  
  326.             ndef.close();  
  327.             promt.setText(promt.getText() + "删除数据成功!" + "\n");  
  328.         } else {  
  329.             Toast.makeText(NfcDemoActivity.this, "设备与nfc卡连接断开,请重新连接...",  
  330.                     Toast.LENGTH_SHORT).show();  
  331.         }  
  332.     }  
  333.       
  334.     //返回一个NdefRecord实例  
  335.     private NdefRecord createRecord() throws UnsupportedEncodingException {  
  336.         //组装字符串,准备好你要写入的信息  
  337.         String msg = "BEGIN:VCARD\n" + "VERSION:2.1\n" + "中国湖北省武汉市\n"  
  338.                 + "武汉大学计算机学院\n" + "END:VCARD";  
  339.         //将字符串转换成字节数组  
  340.         byte[] textBytes = msg.getBytes();  
  341.         //将字节数组封装到一个NdefRecord实例中去  
  342.         NdefRecord textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,  
  343.                 "text/x-vCard".getBytes(), new byte[] {}, textBytes);  
  344.         return textRecord;  
  345.     }  
  346.   
  347.     private MediaPlayer ring() throws Exception, IOException {  
  348.         // TODO Auto-generated method stub  
  349.         Uri alert = RingtoneManager  
  350.                 .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);  
  351.         MediaPlayer player = new MediaPlayer();  
  352.         player.setDataSource(this, alert);  
  353.         final AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);  
  354.         if (audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) != 0) {  
  355.             player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);  
  356.             player.setLooping(false);  
  357.             player.prepare();  
  358.             player.start();  
  359.         }  
  360.         return player;  
  361.     }  
  362.   
  363.     private void startNFC_Listener() {  
  364.         // 开始监听NFC设备是否连接,如果连接就发pi意图  
  365.         nfcAdapter.enableForegroundDispatch(this, pi,  
  366.                 new IntentFilter[] { tagDetected }, null);  
  367.     }  
  368.   
  369.     private void stopNFC_Listener() {  
  370.         // 停止监听NFC设备是否连接  
  371.         nfcAdapter.disableForegroundDispatch(this);  
  372.     }  
  373.   
  374.     private void init_NFC() {  
  375.         // 初始化PendingIntent,当有NFC设备连接上的时候,就交给当前Activity处理  
  376.         pi = PendingIntent.getActivity(this, 0, new Intent(this, getClass())  
  377.                 .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);  
  378.         // 新建IntentFilter,使用的是第二种的过滤机制  
  379.         tagDetected = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);  
  380.         tagDetected.addCategory(Intent.CATEGORY_DEFAULT);  
  381.     }  
  382.   
  383. }  

 

下面是该示例的完整源码下载链接:

Android NFC Demo1

Android之NFC