最近在学习NFC相关的东西,看完相关资料之后感觉云里雾里,不知如何下手,有非常多的困惑,废了九牛二虎之力才摸到一点皮毛。现在写出来与大家共勉,文章后面也有一些疑问,希望大家不吝赐教。
我是做智能卡的,至少在现在的同事是这样子理解的,心里很多次吐槽,哥是做服务器的,对卡不熟,只是做的项目都和智能卡相关,为了避免被人忽悠,多了解了一些智能卡的规范而已,难道这也有错吗,呵呵。
今天的这篇文章我想写一个NFC的HelloWorld程序,大家只要按照我这篇文章把项目跑起来,发两条指令就大功告成,其它的以后再慢慢普及。
先说一下测试的工具或者材料,在网上能看到很多NFC的例子写的非常好,第一次看到的时候非常激动,当然也仅限于第一次。为什么呢?原因有两个,一是程序不完整,copy到项目中根本运行不起来,我是小白,你让我怎么办;二是网上的例子除了提到用NFC手机外,对于智能卡语焉不详,让我运行起来也不能测试,非常痛苦,感觉美女都让猪拱了。
那么我们这篇文章中用到的工具和材料是什么呢?
一是Android NFC手机,怎么识别?最简单的一种方法,在NFC手机的“系统设置”里有NFC开关,有这一项的就是NFC手机。
二是一张银行卡,怎么识别?2015年左右(或者2014年开始,不同银行不一样),在银行开户的银行卡大部分都是智能卡,在卡上印有Quickpass的字样,有Quickpass字样的银行卡就是本文的测试卡。
接着说一下程序实现的逻辑和步骤:
-
初始化非接触参数。
-
再在NFC信号来袭时,获得该对象。
-
指定该对象的通信协议。
-
打开连接,准备和卡交互。
-
指令交互。
-
关闭连接。
-
在Activity的生命周期中对NFC做一些配置。也是一些非接参数的初始化工作。
下面是代码:
package com.example.nfcdemo;
import java.io.IOException;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.NfcF;
import android.nfc.tech.NfcV;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.Menu;
import com.example.nfcdemo.util.Utils;
@SuppressLint("NewApi")
public class MainActivity extends Activity {
final String TAG = "NFCDemo";
NfcAdapter nfcAdapter = null;
PendingIntent pendingIntent = null;
IntentFilter[] FILTERS = null;
String[][] TECHLISTS = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Log.i(TAG, "start....");
FILTERS = new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED, "*/*") };
TECHLISTS = new String[][] { { IsoDep.class.getName() }, { NfcV.class.getName() }, { NfcF.class.getName() } };
// 第一步初始化
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
onNewIntent(getIntent());
Log.i(TAG, "end....");
} catch (MalformedMimeTypeException e1) {
e1.printStackTrace();
}
}
@Override
protected void onNewIntent(Intent intent) {
try {
//第二步,在信号来袭时,获得该对象
final Parcelable p = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (p == null) {
// 打印nfcAdapter状态
Log.i(TAG, "p==null");
if (nfcAdapter == null) {
Log.i(TAG, "nfcAdapter is null");
} else if (nfcAdapter.isEnabled()) {
Log.i(TAG, "nfcAdapter is enabled");
} else {
Log.i(TAG, "nfcAdapter is disabled");
}
return;
}
//第三部,指定通信协议
final IsoDep isodep = IsoDep.get((Tag) p);
Log.i(TAG, "connect...");
//第四步,打开连接,准备写卡
isodep.connect();
Log.i(TAG, "connect ok...");
do {
//第五步,发送指令 1.选择主安全域;2外部认证
Log.i(TAG, "send 00A40400...");
byte[] b = isodep.transceive(Utils.bytesFromHexStringNoBlank("00A40400"));
Log.i(TAG, "===TEST BY MXW===apdu cmd return:" + Utils.toHexStringNoBlank(b));
Log.i(TAG, "send 80500000081122334455667788...");
b = isodep.transceive(Utils.bytesFromHexStringNoBlank("80500000081122334455667788"));
Log.i(TAG, "===TEST BY MXW===apdu cmd return:" + Utils.toHexStringNoBlank(b));
break;
} while (true);
//第六步,关闭连接
Log.i(TAG, "close...");
isodep.close();
Log.i(TAG, "close ok...");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onResume() {
Log.i(TAG, "===TEST BY MXW===onResume");
super.onResume();
// if (nfcAdapter != null)
// nfcAdapter.enableForegroundDispatch(this, pendingIntent, FILTERS,
// TECHLISTS);
//第七步,关闭NFC
if (nfcAdapter != null)
nfcAdapter.disableForegroundDispatch(this);
}
@Override
protected void onPause() {
Log.i(TAG, "===TEST BY MXW===onPause");
super.onPause();
// if (nfcAdapter != null)
// nfcAdapter.disableForegroundDispatch(this);
//第七步,启动NFC
if (nfcAdapter != null)
nfcAdapter.enableForegroundDispatch(this, pendingIntent, FILTERS, TECHLISTS);
}
}
工具类的代码:
package com.example.nfcdemo.util;
public class Utils {
private static final char[] HEX_DIGITS =
{
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};
private static int fromDigit(char ch) {
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
throw new IllegalArgumentException("invalid hex digit '" + ch + "'");
}
public static String toHexStringNoBlank( byte[] ba ) {
int length = ba.length;
char[] buf = new char[length * 2];
for (int i = 0, j = 0, k; i < length; ) {
k = ba[i++];
buf[j++] = HEX_DIGITS[(k >> 4) & 0x0F];
buf[j++] = HEX_DIGITS[ k & 0x0F];
}
return new String(buf, 0, buf.length);
}
public static byte[] bytesFromHexStringNoBlank(String hex) throws NumberFormatException {
if (hex.length() == 0) return null;
String myhex = hex;
int len = myhex.length();
if ((len % 2) != 0) throw new NumberFormatException();
byte[] buf = new byte[len / 2];
int i = 0, j = 0;
while (i < len) {
try {
buf[j++] = (byte) ((fromDigit(myhex.charAt(i++)) << 4) |
fromDigit(myhex.charAt(i++)));
} catch (IllegalArgumentException e) {
throw new NumberFormatException();
}
}
return buf;
}
}
下面是项目的配置文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.nfcdemo"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="org.simalliance.openmobileapi.SMARTCARD" />
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> -->
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.nfcdemo.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
</span>
把上面的代码Copy到项目中在NFC的手机上运行后,把银行卡贴在手机的NFC感应区,就能在LogCat的后台日志看到指令的下发和响应的接收。
以上的代码有两个问题,其中一个是疑问:
一是项目的配置文件申请的权限有点多,有一些没有用到,大家可以自己删减。
二是代码中的第七步,在onResume和onPause中对NFC的设置和很多教程相反。通过对日志的跟踪发现,本文例子在NFC手机上运行时,当有NFC信号时onPause、onNewIntent、onResume三个方法依次执行,如果其他教程中的说明进行设置,则第二步中获得的对象为null,导致后续程序不能执行。
第二个问题也是我的疑问,目前还在探究当中,希望大家不吝指教。