2016就这样静悄悄的过去了,最近也是颓废的不行,都没怎么充实自己了,每天练练车,打打游戏和麻将,书都基本没翻个几页,家里完全没有状态。2017,加油吧,为了事业和家庭,Fighting!
关于接触到NFC这东西,是我2年前导师项目中认识到的,当时自己也是刚学Android,由于项目需要,所以自己当时承担了这方面的工作,由此也开启了我Android生涯。想当初我也是翻遍了网上的所有资料,基本上没有一个是完完全全写好的,能直接拿来用的,当时能读出一个标签的id也是乐的不行,所以我当时在想,要是我弄好了,就分享下经验,顺便共享下代码。但是为什么现在才写呢,因为当时做完后也没有继续做这个了,导致这个事情也是一拖再拖,直到最近导师又需要这个,又拿出来把代码改了改,才想起以前说要记录的这事。
NFC(Near Field Communication)近场通信,属于RFID中的一种,它是一种无源、低频、近距离的通信方式。通过扫描方提供的电能,利用电磁感应原理来驱动标签与扫描方进行通信。是一种快速,且安全的通信方式。现在广泛运用于公交卡、地铁票、门禁卡、校园卡等场景。现在应用比较广泛的是MifareClassic 1K卡,属于NfcA型标签,NFC标签共有4种类型的标签,具体类型和用途不一,这里就不说了。而当时我项目中用到的就是NfcA型卡,对NfcA卡进行数据读取和写入。至于NFC标签内部数据存放格式,读写的过程以及标签扇区(sector)和块(block)的概念,也就不说了,相信你可以通过其他途径可以获取到,这里我就只介绍NFC读写的代码了。
完成后整个功能界面如下,由于主要是测试的玩,所以界面什么的就不用太在意了,功能实现就好了。
整个应用分为3大功能模块:读取标签内存,将内存数据转为Ascii码,写入数据。
后面的是sector和block是写入数据时,自己要将数据写入的扇区和块,data是要写入的数据。
首先你得拥有NFC标签,相信这个不难,校园卡,地铁票,公交卡什么的,都可以的。其次你得有读写的设备,作为Android开发者,相信你也不会专门买个读写的设备,不然就和Android联系不上了,我们要做的是开发一个简易版的NFC标签读写软件,所以你得有一个NFC读写的手机或Pad。好了,准备好了之后就可以测试玩一玩了。你也可以先从应用市场上下载NFC的应用,来测试你的设备是否支持NFC功能或者你的标签有没有问题。
1).新建Android项目,在res文件夹下新建一个xml的文件夹,里面放的是Android支持的NFC类型的配置数据。nfc_tech_filter.xml如下:
android.nfc.tech.IsoDep
android.nfc.tech.NfcA
android.nfc.tech.NfcB
android.nfc.tech.NfcF
android.nfc.tech.NfcV
android.nfc.tech.Ndef
android.nfc.tech.NdefFormatable
android.nfc.tech.MifareUltralight
android.nfc.tech.MifareClassic
2)在AndroidManifest中进行权限声明:
3)布局界面代码如下activity_nfc_main:
4)主界面 NfcRW.java
package com.example.nfc_read_write;
import java.io.IOException;
import java.util.logging.Logger;
import com.example.nfc_test3.R;
import android.app.Activity;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
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.MifareClassic;
import android.nfc.tech.NfcA;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
public class NfcRW extends Activity {
TextView tv1, tv2, tv3;
EditText etSector, etBlock, etData;
// private NfcAdapter nfcAdapter;
private PendingIntent mPendingIntent;
private IntentFilter[] mFilters;
private String[][] mTechLists;
private int mCount = 0;
String info = "";
private int bIndex;
private int bCount;
private int BlockData;
private String BlockInfo;
private RadioButton mRead, mWriteData, mChange;
private byte[] b3;
byte[] code=MifareClassic.KEY_NFC_FORUM;//读写标签中每个块的密码
private byte[] data3, b0;
private String temp = "";
private NfcAdapter mNfcAdapter;
private Context mContext;
int block[] = { 4, 5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 24,
25, 26, 28, 29, 30, 32, 33, 34, 36, 37, 38, 40, 41, 42, 44, 45, 46,
48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 62 };
// private StringBuilder metaInfo=new StringBuilder();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_main);
mContext = this;
checkNFCFunction(); // NFC Check
init();
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
// Setup an intent filter for all MIME based dispatches
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
mFilters = new IntentFilter[] { ndef, };
// 根据标签类型设置
mTechLists = new String[][] { new String[] { NfcA.class.getName() } };
}
private void checkNFCFunction() {
// TODO Auto-generated method stub
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
// check the NFC adapter first
if (mNfcAdapter == null) {
// mTextView.setText("NFC apdater is not available");
Dialog dialog = null;
CustomDialog.Builder customBuilder = new CustomDialog.Builder(
mContext);
customBuilder
.setTitle("很遗憾")
.setMessage("没发现NFC设备,请确认您的设备支持NFC功能!")
.setIcon(R.drawable.dialog_icon2)
.setPositiveButton("是",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
finish();
}
});
dialog = customBuilder.create();
dialog.setCancelable(false);// back
dialog.setCanceledOnTouchOutside(false);
SetDialogWidth(dialog).show();
return;
} else {
if (!mNfcAdapter.isEnabled()) {
Dialog dialog = null;
CustomDialog.Builder customBuilder = new CustomDialog.Builder(
mContext);
customBuilder
.setTitle("提示")
.setMessage("请确认NFC功能是否开启!")
.setIcon(R.drawable.dialog_icon2)
.setPositiveButton("现在去开启......",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
Intent setnfc = new Intent(
Settings.ACTION_NFC_SETTINGS);
startActivity(setnfc);
}
});
dialog = customBuilder.create();
dialog.setCancelable(false);// back
dialog.setCanceledOnTouchOutside(false);
SetDialogWidth(dialog).show();
return;
}
}
}
private Dialog SetDialogWidth(Dialog dialog) {
// TODO 自动生成的方法存根
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int screenWidth = dm.widthPixels;
int screenHeight = dm.heightPixels;
WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
if (screenWidth > screenHeight) {
params.width = (int) (((float) screenHeight) * 0.875);
} else {
params.width = (int) (((float) screenWidth) * 0.875);
}
dialog.getWindow().setAttributes(params);
return dialog;
}
private void init() {
// TODO 自动生成的方法存根
tv1 = (TextView) findViewById(R.id.tv1);
tv2 = (TextView) findViewById(R.id.tv2);
tv3 = (TextView) findViewById(R.id.tv3);
etSector = (EditText) findViewById(R.id.etSector);
etBlock = (EditText) findViewById(R.id.etBlock);
etData = (EditText) findViewById(R.id.etData);
mRead = (RadioButton) findViewById(R.id.rb_read);
mWriteData = (RadioButton) findViewById(R.id.rb_write);
mChange = (RadioButton) findViewById(R.id.rb_Change);
}
@Override
protected void onResume() {
// TODO 自动生成的方法存根
super.onResume();
enableForegroundDispatch();
// mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters,
// mTechLists);
}
private void enableForegroundDispatch() {
// TODO 自动生成的方法存根
if (mNfcAdapter != null) {
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent,
mFilters, mTechLists);
}
}
@Override
protected void onNewIntent(Intent intent) {
// TODO 自动生成的方法存根
super.onNewIntent(intent);
tv1.setText("发现新的 Tag: " + ++mCount + "\n");// mCount 计数
String intentActionStr = intent.getAction();// 获取到本次启动的action
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intentActionStr)// NDEF类型
|| NfcAdapter.ACTION_TECH_DISCOVERED.equals(intentActionStr)// 其他类型
|| NfcAdapter.ACTION_TAG_DISCOVERED.equals(intentActionStr)) {// 未知类型
// 在intent中读取Tag id
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] bytesId = tag.getId();// 获取id数组
info += ByteArrayChange.ByteArrayToHexString(bytesId) + "\n";
tv2.setText("标签UID: " + "\n" + info);
// 读取存储信息
if (mRead.isChecked()) {
// mChange=false;
tv3.setText("读取成功! " + readTag(tag));
// readNfcVTag(tag);
etSector.setText("");
etBlock.setText("");
etData.setText("");
}
// 写数据
if (mWriteData.isChecked()) {
//writeTag(tag);
String str= etData.getText().toString();
writeTag(tag,str);
}
// 转换为ASCll
if (mChange.isChecked()) {
tv3.setText(change(tag));
Toast.makeText(getBaseContext(), "转换成功", 1).show();
etSector.setText("");
etBlock.setText("");
etData.setText("");
}
}
}
// 写数据
public void writeTag(Tag tag, String str) {
MifareClassic mfc = MifareClassic.get(tag);
try {
if (mfc != null) {
mfc.connect();
} else {
Toast.makeText(mContext, "写入失败", 1).show();
return;
}
Log.i("write", "----connect-------------");
boolean CodeAuth = false;
byte[] b1 = str.getBytes();
if (b1.length <= 720) {
//System.out.println("------b1.length:" + b1.length);
int num = b1.length / 16;
System.out.println("num= " + num);
int next = b1.length / 48 + 1;
System.out.println("扇区next的值为" + next);
b0 = new byte[16];
if (!(b1.length % 16 == 0)) {
for (int i = 1, j = 1; i <= num; i++) {
CodeAuth = mfc.authenticateSectorWithKeyA(j, code);
System.arraycopy(b1, 16 * (i - 1), b0, 0, 16);
mfc.writeBlock(block[i - 1], b0);
if (i % 3 == 0) {
j++;
}
}
//Log.d("下一个模块", "测试");
CodeAuth = mfc.authenticateSectorWithKeyA(next,// 非常重要------
code);
//Log.d("获取第5块的密码", "---成功-------");
byte[] b2 = { 0 };
b0 = new byte[16];
System.arraycopy(b1, 16 * num, b0, 0, b1.length % 16);
System.arraycopy(b2, 0, b0, b1.length % 16, b2.length);
mfc.writeBlock(block[num], b0);
mfc.close();
Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();
return;
} else {
for (int i = 1, j = 1; i <= num; i++) {
if (i % 3 == 0) {
j++;
System.out.println("扇区j的值为:" + j);
}
CodeAuth = mfc.authenticateSectorWithKeyA(j,// 非常重要---------
code);
System.arraycopy(b1, 16 * (i - 1), b0, 0, 16);
mfc.writeBlock(block[i - 1], b0);
str += ByteArrayChange.ByteArrayToHexString(b0);
System.out.println("Block" + i + ": " + str);
}
mfc.close();
Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();
return;
}
} else {
Toast.makeText(getBaseContext(), "字符过长,内存不足", 1).show();
return;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
mfc.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// //读取数据
public String readTag(Tag tag) {
MifareClassic mfc = MifareClassic.get(tag);
for (String tech : tag.getTechList()) {
System.out.println(tech);// 显示设备支持技术
}
boolean auth = false;
// 读取TAG
try {
// metaInfo.delete(0, metaInfo.length());//清空StringBuilder;
StringBuilder metaInfo = new StringBuilder();
// 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.append(" 卡片类型:" + 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_NFC_FORUM);// 逐个获取密码
/*
* byte[]
* codeByte_Default=MifareClassic.KEY_DEFAULT;//FFFFFFFFFFFF
* byte[]
* codeByte_Directory=MifareClassic.KEY_MIFARE_APPLICATION_DIRECTORY
* ;//A0A1A2A3A4A5 byte[]
* codeByte_Forum=MifareClassic.KEY_NFC_FORUM;//D3F7D3F7D3F7
*/if (auth) {
metaInfo.append("Sector " + j + ":验证成功\n");
// 读取扇区中的块
bCount = mfc.getBlockCountInSector(j);
bIndex = mfc.sectorToBlock(j);
for (int i = 0; i < bCount; i++) {
byte[] data = mfc.readBlock(bIndex);
metaInfo.append("Block " + bIndex + " : "
+ ByteArrayChange.ByteArrayToHexString(data)
+ "\n");
bIndex++;
}
} else {
metaInfo.append("Sector " + j + ":验证失败\n");
}
}
return metaInfo.toString();
} catch (Exception e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
e.printStackTrace();
} finally {
if (mfc != null) {
try {
mfc.close();
} catch (IOException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG)
.show();
}
}
}
return null;
}
// 转换Hex为字符串
public String change(Tag tag) {
MifareClassic mfc = MifareClassic.get(tag);
Log.d("----------", "change----------");
boolean auth = false;
// 读取TAG
String ChangeInfo = "";
String Ascll = "";
// Enable I/O operations to the tag from this TagTechnology object.
try {
mfc.connect();
int sectorCount = mfc.getSectorCount();// 获取TAG中包含的扇区数
for (int j = 1; j < sectorCount; j++) {
// Authenticate a sector with key A.
auth = mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_NFC_FORUM);
if (auth) {
Log.i("change 的auth验证成功", "开始读取模块信息");
byte[] data0 = mfc.readBlock(4 * j);
byte[] data1 = mfc.readBlock(4 * j + 1);
byte[] data2 = mfc.readBlock(4 * j + 2);
data3 = new byte[data0.length + data1.length + data2.length];
System.arraycopy(data0, 0, data3, 0, data0.length);
System.arraycopy(data1, 0, data3, data0.length,
data1.length);
System.arraycopy(data2, 0, data3, data0.length
+ data1.length, data2.length);
ChangeInfo = ByteArrayChange.ByteArrayToHexString(data3);
temp = "扇区" + (j) + "里的内容为:"
+ ToStringHex.decode(ChangeInfo) + "\n";
}
Ascll += temp;
}
return Ascll;
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
Toast.makeText(getBaseContext(), "转换失败", 1).show();
} finally {
if (mfc != null) {
try {
mfc.close();
} catch (IOException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG)
.show();
}
}
}
return "";
}
public int StringToInt(String s) {
if (!(TextUtils.isEmpty(s)) || s.length() > 0) {
BlockData = Integer.parseInt(s);
} else {
Toast.makeText(NfcRW.this, "Block输入有误", 1).show();
}
System.out.println(BlockData);
return BlockData;
}
@Override
public void onPause() {
super.onPause();
disableForegroundDispatch();
}
private void disableForegroundDispatch() {
// TODO 自动生成的方法存根
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
}
5)由于标签中的数据都是byte型,需要转为16进制Ascii码数据。
ByteArrayChange.java
package com.example.nfc_read_write;
public class ByteArrayChange {
//转换法1 格式为0xabcd1234 字母小写
/* public static String ByteArrayToHexString(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();
}*/
//转换法2 格式为 ABCD1234 字母大写
public static String ByteArrayToHexString(byte[] bytesId) { //Byte数组转换为16进制字符串
// TODO 自动生成的方法存根
int i, j, in;
String[] hex = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
};
String output = "";
for (j = 0; j < bytesId.length; ++j) {
in = bytesId[j] & 0xff;
i = (in >> 4) & 0x0f;
output += hex[i];
i = in & 0x0f;
output += hex[i];
}
return output;
}
}
6)在进行读取16进制数据时,我们将其内容转换(翻译)为我们比较熟悉的中文
ToStringHex.java
package com.example.nfc_read_write;
import java.io.ByteArrayOutputStream;
public class ToStringHex {
// 将字符串编码成16进制数字,适用于所有字符(包括中文)
private static String hexString = "0123456789ABCDEF";
/**
* 将字符串编码成16进制数字,适用于所有字符(包括中文)
*/
public static String encode(String str) {
// 根据默认编码获取字节数组
byte[] bytes = str.getBytes();
StringBuilder sb = new StringBuilder(bytes.length * 2);
// 将字节数组中每个字节拆解成2位16进制整数
for (int i = 0; i < bytes.length; i++) {
sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4));
sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0));
}
return sb.toString();
}
/** 将16进制数字解码成字符串,适用于所有字符(包括中文)
*
* @param bytes
* @return
*/
public static String decode(String bytes) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(
bytes.length() / 2);
// 将每2位16进制整数组装成一个字节
for (int i = 0; i < bytes.length(); i += 2)
baos.write((hexString.indexOf(bytes.charAt(i)) << 4 | hexString
.indexOf(bytes.charAt(i + 1))));
return new String(baos.toByteArray());
}
// 转化十六进制编码为ASCll字符串
public static String toStringHex(String s) {
byte[] baKeyword = new byte[s.length() / 2]; // ------------------
for (int i = 0; i < baKeyword.length; i++) {
try {
baKeyword[i] = (byte) (0xff & Integer.parseInt(
s.substring(i * 2, i * 2 + 2), 16));
} catch (Exception e) {
e.printStackTrace();
}
}
try {
s = new String(baKeyword, "utf-8");// UTF-16le:Not
} catch (Exception e1) {
e1.printStackTrace();
}
return s;
}
}
项目源码地址,点我下载。
PS:现已更新,可以下载了,好像现在CSDN上传文件时,必须说要下载积分,以前不要积分的全部不能下载了,现在更新了。2017-08-12
注:关于NFC的Android 软件,大家可以去应用市场下载 NFC玩家 和 Mifare Classic Tool(MCT) 这2个app,然后对比下,基本原理差不多的。