一,简介
1.1 NFC是继wifi和蓝牙之后,又一种新的近距离无线传输技术,这种局限性比较大,设备必须装有NFC硬件才能实现数据传输,而且传输距离非常短,4cm之内才能传输,真实场景可能触碰才能传输。
1.2 NFC优缺点
优点:传输速度快;连接方便,触碰就能实现通信;安全性高
缺点:传输距离非常短;传输数据量小;并不是全部普及,部分手机不支持
1.3 NFC,蓝牙,红外对比
对比项 | NFC | 蓝牙 | 红外 |
网络类型 | 点对点 | 单点对多点 | 点对点 |
有效距离 | <=0.1m | <=10m,最新的蓝牙4.0有效距离可达100m | 一般在1m以内,热技术连接,不稳定 |
传输速度 | 最大424kbps | 最大24Mbps | 慢速115.2kbp5,快速4Mbps |
连接时间 | <0.1s | 6s | 0.5s |
安全性 | 安全,硬件实现 | 安全,软件实现 | 个安全,使用IRFM时除外 |
通信模式 | 主动-主动/被动 | 主动-主动 | 主动-主动 |
成本 | 低 | 中 | 低 |
1.4 NFC通信模式
读卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、点对点模式(P2P mode)。
读卡器模式
数据在NFC芯片中,可以简单理解成“刷标签”。本质上就是通过支持NFC的手机或其它电子设备从带有NFC芯片的标签、贴纸、名片等媒介中读写信息。通常NFC标签是不需要外部供电的。当支持NFC的外设向NFC读写数据时,它会发送某种磁场,而这个磁场会自动的向NFC标签供电。
仿真卡模式
数据在支持NFC的手机或其它电子设备中,可以简单理解成“刷手机”。本质上就是将支持NFC的手机或其它电子设备当成借记卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息凭证封装成数据包存储在支持NFC的外设中 。
在使用时还需要一个NFC射频器(相当于刷卡器)。将手机靠近NFC射频器,手机就会接收到NFC射频器发过来的信号,在通过一系列复杂的验证后,将IC卡的相应信息传入NFC射频器,最后这些IC卡数据会传入NFC射频器连接的电脑,并进行相应的处理(如电子转帐、开门等操作)。
点对点模式
该模式与蓝牙、红外差不多,用于不同NFC设备之间进行数据交换,不过这个模式已经没有有“刷”的感觉了。其有效距离一般不能超过4厘米,但传输建立速度要比红外和蓝牙技术快很多,传输速度比红外块得多,如过双方都使用Android4.2,NFC会直接利用蓝牙传输。这种技术被称为Android Beam。所以使用Android Beam传输数据的两部设备不再限于4厘米之内。
点对点模式的典型应用是两部支持NFC的手机或平板电脑实现数据的点对点传输,例如,交换图片或同步设备联系人。因此,通过NFC,多个设备如数字相机,计算机,手机之间,都可以快速连接,并交换资料或者服务。
二,NFC通信步骤
2.1 AndroidManifest.xml添加NFC使用权限,不用动态请求
<!--NFC 相关权限-->
<!--描述所需硬件特性-->
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<uses-permission android:name="android.permission.NFC" />
2.2 添加NFC标签过滤,两种方式
方式一:AndroidManifest.xml的Activity标签添加xml配置
res目录下 新建xml文件夹,再新建nfc_tech_filter.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- 可以处理所有Android支持的NFC类型 -->
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcF</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NdefFormatable</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareClassic</tech>
</tech-list>
</resources>
activity标签配置该xml
<activity
android:name=".MainActivity"
android:exported="true">
<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>
方式二:初始化NFC时候动态配置标签
/**
* 初始化nfc设置
*/
public static void NfcInit(Activity activity) {
Intent intent = new Intent(activity, activity.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
mPendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
//做一个IntentFilter过滤你想要的action 这里过滤的是ndef
IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
filter.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
e.printStackTrace();
}
mTechList = new String[][]{{MifareClassic.class.getName()},
{NfcA.class.getName()}};
//生成intentFilter
mIntentFilter = new IntentFilter[]{filter};
}
2.3 创建NFC适配器
NFC所有的操作都是通过NfcAdapter完成的,创建NfcAdapter后,同时新建一个等待的PendingIntent,这个主要用于出发NFC打开该页面
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
@Override
protected void onStart() {
super.onStart();
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
}
2.4 在onResume中开启前台调度,来初始化NFC
//在onResume中开启前台调度
@Override
protected void onResume() {
super.onResume();
//设定intentfilter和tech-list。如果两个都为null就代表优先接收任何形式的TAG action。也就是说系统会主动发TAG intent。
if (mNfcAdapter != null) {
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); //启动 }
}
}
2.5 在onNewIntent中处理由NFC设备传递过来的intent
//在onNewIntent中处理由NFC设备传递过来的intent
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent);
}
2.6 处理读取过来的数据,包括卡ID(通常为设备序列号,16进制形式09:D1:E6:6B)
然后NFC卡其它信息不一定有,NFC卡标签这些信息可能是没有的,但序列号是一定有的,即NFC卡的唯一标识
//这块的processIntent() 就是处理卡中数据的方法
public void processIntent(Intent intent) {
try {
tvContent.setText("");
// 检测卡的id
String id = readNFCId(intent);
// NfcUtils中获取卡中数据的方法
String result = readNFCFromTag(intent);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("卡ID十六进制:" + id).append("\r\n");
stringBuilder.append("卡ID十进制:" + hexToDec(id)).append("\r\n");
stringBuilder.append("信息:").append("\r\n");
stringBuilder.append(result).append("\r\n");
tvContent.setText(stringBuilder);
// 往卡中写数据
//String data = "this.is.write";
//writeNFCToTag(data, intent);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读取nfcID
*/
public String readNFCId(Intent intent) throws UnsupportedEncodingException {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String id = ByteArrayToHexString(tag.getId());
return id;
}
/**
* 读取NFC的数据
*/
public String readNFCFromTag(Intent intent) throws UnsupportedEncodingException {
Parcelable[] rawArray = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
StringBuilder stringBuilder = new StringBuilder();
if (rawArray != null) {
for (int i = 0; i < rawArray.length; i++) {
NdefMessage mNdefMsg = (NdefMessage) rawArray[i];
for (int j = 0; j < mNdefMsg.getRecords().length; i++) {
NdefRecord mNdefRecord = mNdefMsg.getRecords()[j];
if (mNdefRecord != null) {
String readResult = new String(mNdefRecord.getPayload(), "UTF-8");
stringBuilder.append(readResult).append("\r\n");
}
}
}
}
return stringBuilder.toString();
}
/**
* 十六进制转10进制
* @param s
* @return
*/
public static int hexToDec(String s) {
String s1 = s.toUpperCase(); // 全转大写
char[] chars = s1.toCharArray(); // 转成 char 数组
Stack<Character> stack = new Stack<>();
for (int i = 0; i < chars.length; i++) {
stack.push(chars[i]); // 放入栈中,倒序遍历
}
int sum = 0; // 定义总和
int size = stack.size(); // 要先赋值给 size ,不然 stack.pop() 之后 size 会变
for (int i = 0; i < size; i++) {
Character pop = stack.pop();
if (String.valueOf(pop).matches("[A-F]")) { // 如果是 A-F
sum += (Math.pow(16, i) * ((pop - 55))); // A的ASCII码为 65,取偏移量
} else { // 如果是纯数字
sum += Math.pow(16, i) * Integer.parseInt(String.valueOf(pop));
}
}
return sum;
}
/**
* 将字节数组转换为字符串
*/
private String ByteArrayToHexString(byte[] inarray) {
int i, j, in;
String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
String out = "";
for (j = 0; j < inarray.length; ++j) {
in = (int) inarray[j] & 0xff;
i = (in >> 4) & 0x0f;
out += hex[i];
i = in & 0x0f;
out += hex[i];
}
return out;
}
2.7 往NFC写入数据
/**
* 往nfc写入数据
*/
public static void writeNFCToTag(String data, Intent intent){
try {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
ndef.connect();
NdefRecord ndefRecord = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
ndefRecord = NdefRecord.createTextRecord(null, data);
}
NdefRecord[] records = {ndefRecord};
NdefMessage ndefMessage = new NdefMessage(records);
ndef.writeNdefMessage(ndefMessage);
}catch (Exception e){
}
}
// 往卡中写数据
String data = "this.is.write";
writeNFCToTag(data, intent);
2.8 取消NfcAdapter调度
@Override
protected void onPause() {
super.onPause();
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
2.9 销毁NfcAdapter
@Override
protected void onDestroy() {
super.onDestroy();
mNfcAdapter = null;
}
2.10 总结
NFC适合卡片类刷卡数据传输场景,比如门禁卡,支付卡,地铁卡等,快捷方便
三 NFC读卡器通信
3.1 一些手机是不支持NFC的,那么可以选择备选方案,读卡器usb接口插入手机使用。
3.2 但读卡器种类也有很多种,有些是支持键盘模式的,有些是支持usb通信模式的,还有些是支持串口,wifi通信模式。下面主要说下键盘模式,和usb模式通信
3.3 键盘模式比较简单,简单键盘输入事件就可以,大部分扫码枪,读卡器是支持这种模式的,蛀牙源码
private StringBuffer KeyStringBuffer;
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int unicodeChar = event.getUnicodeChar();
String number = String.valueOf((char) unicodeChar);
if (KeyStringBuffer == null) {
KeyStringBuffer = new StringBuffer();
}
KeyStringBuffer.append(number);
if (KeyStringBuffer != null && KeyStringBuffer.toString().length() >= 8) {
//都取到的数据
String resultString = KeyStringBuffer.toString();
KeyStringBuffer = null;
Log.e("EEE", "dispatchKeyEvent: " + resultString);
.........自己业务
}
}
return super.dispatchKeyEvent(event);
}
3.4 USB模式,通过转接器连接手机,usb连接后可能通过读卡器内部协议来进行usb通信,所以需要读卡器厂的协议指令。下面是通用usb通信:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
private UsbManager manager;
private UsbDevice device;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void openUsbClick(View view) {
initUsbManager();
}
/**
* 初始化UsbManager
*/
private void initUsbManager() {
//获取UsbManager
manager = (UsbManager) getSystemService(Context.USB_SERVICE);
//查找设备
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while (deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();
Log.e(TAG, "onCreate: " + device.getDeviceName());
requestPermission(device);
}
}
/**
* 请求权限
*/
private void requestPermission(UsbDevice device) {
//请求权限
PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);
manager.requestPermission(device, permissionIntent);
}
/**
* 权限回调
*/
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
//call method to set up device communication
openDevice();
}
} else {
Log.d(TAG, "permission denied for device " + device);
}
}
}
}
};
/**
* 打开设备,接口数据
*/
private static int TIMEOUT = 100;
private boolean forceClaim = true;
private UsbDeviceConnection connection;
private UsbInterface usbInterface;
private UsbEndpoint usbEndpoint;
private void openDevice() {
if (connection == null) {
//打开设备
connection = manager.openDevice(device);
// 配置设备接口
usbInterface = device.getInterface(0);
connection.claimInterface(usbInterface, forceClaim);
// 获取设备端点
usbEndpoint= usbInterface.getEndpoint(0);
readData();
}
//发送数据
//byte[] dataToSend = "aa".getBytes(); // 需要发送的数据
//int bytesSent = connection.bulkTransfer(usbEndpoint, dataToSend, dataToSend.length, TIMEOUT);
}
//读取数据
boolean isReading = true;
private void readData(){
new Thread(new Runnable(){
@Override
public void run() {
while (isReading){
// 接收数据
byte[] buffer = new byte[1024];
int byteCount = connection.bulkTransfer(usbEndpoint, buffer, buffer.length, 1000);
if (byteCount > 0) {
// 读取到有效数据
String data = new String(buffer, 0, byteCount);
// 处理数据
Log.e(TAG, "data: "+data );
}
}
}
}).start();
}
/**
* 关闭设备
*/
public void closeConnect() {
if (connection != null) {
connection.releaseInterface(usbInterface);
connection.close();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
closeConnect();
}
}