因为公司需求最近研究了大量的NFC读写M1卡的资料,自己撸了一个相对完整DEMO,让大家一起学习学习。由于之前一直是使用复旦方案的读卡器进行读写卡的,不了解基本原理的请移步:
现在我们使用手机自带的NFC标准来进行读写M1卡,毕竟现在手机都这么先进了还插着读卡器来进行读写M1卡实在是过不去,过于Low逼了。至于什么是NFC标准,基于什么原理请各位自行百度吧,我懒得凑字数了,直接开始吧!
首先我们先封装一个关于NFC的工具类,其中包括初始化NFC、检查NFC是否打开、初始化NFC设置、读取NFCID(UID)等等方法,上代码:
public class NfcUtils {
//nfc
public static NfcAdapter mNfcAdapter;
public static IntentFilter[] mIntentFilter = null;
public static PendingIntent mPendingIntent = null;
public static String[][] mTechList = null;
/**
* 构造函数,用于初始化nfc
*/
public NfcUtils(Activity activity) {
mNfcAdapter = NfcCheck(activity);
NfcInit(activity);
}
/**
* 检查NFC是否打开
*/
public static NfcAdapter NfcCheck(Activity activity) {
NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (mNfcAdapter == null) {
return null;
} else {
if (!mNfcAdapter.isEnabled()) {
Intent setNfc = new Intent(Settings.ACTION_NFC_SETTINGS);
activity.startActivity(setNfc);
}
}
return mNfcAdapter;
}
/**
* 初始化nfc设置
*/
public static void NfcInit(Activity activity) {
mPendingIntent = PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
IntentFilter filter2 = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
try {
filter.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
e.printStackTrace();
}
mIntentFilter = new IntentFilter[]{filter, filter2};
mTechList = null;
}
/**
* 读取NFC的数据
*/
public static String readNFCFromTag(Intent intent) throws UnsupportedEncodingException {
Parcelable[] rawArray = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawArray != null) {
NdefMessage mNdefMsg = (NdefMessage) rawArray[0];
NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];
if (mNdefRecord != null) {
String readResult = new String(mNdefRecord.getPayload(), "UTF-8");
return readResult;
}
}
return "数据为空";
}
/**
* 往nfc写入数据
*/
public static void writeNFCToTag(String data, Intent intent) throws IOException, FormatException {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
ndef.connect();
NdefRecord ndefRecord = NdefRecord.createTextRecord(null, data);
NdefRecord[] records = {ndefRecord};
NdefMessage ndefMessage = new NdefMessage(records);
ndef.writeNdefMessage(ndefMessage);
}
/**
* 读取nfcID
*/
public static String readNFCId(Intent intent) throws UnsupportedEncodingException {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String id = ByteArrayToHexString(tag.getId());
return id;
}
/**
* 将字节数组转换为字符串
*/
private static 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;
}
}
上面代码有不懂的可以看一下官方文档里面有详细的介绍NFCAPI,写完工具类后我们可以通过在任意你想实现NFC功能的Activity类里面重写onNewIntent这个方法。因为刷卡后会实例化一个新的Intent ,通过这个方法我们可以收集到该实例的Intent的Action是不是一个NFCAction。如果是,我们可以通过getParcelableExtra这个方法获取Intent对应的NFC的Tag数据。接着我们通过MifareClassic类里的get方法创建一个MifareClassic的对象,基于MifareClassic协议进行无握手式的连接M1卡。这个是射频技术,具体原理百度。连接后我们就可以对M1卡基于MifareClassic协议进行读写,代码如下:
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)){
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
MifareClassic mifareClassic = MifareClassic.get(tag);
try {
mifareClassic.connect();
//获取扇区数量
int count = mifareClassic.getSectorCount();
Log.e("onNewIntent:","扇区数量 ===" + count);
//用于判断时候有内容读取出来
for (int i = 0;i < count;i++){
boolean isOpen = mifareClassic.authenticateSectorWithKeyA(i,MifareClassic.KEY_DEFAULT);
if (isOpen){
//获取扇区里面块的数量
int bCount = mifareClassic.getBlockCountInSector(i);
Log.e("onNewIntent:","扇区里面块的数量 ===" + bCount);
//获取扇区第一个块对应芯片存储器的位置
//存储器的位置为第一扇区为0后面叠加4直到60为止
int bIndex = mifareClassic.sectorToBlock(i);
for (int j = 0; j < bCount; j++){
Log.e("onNewIntent:","存储器的位置 ===" + bIndex + "当前块 === "+ (bIndex+j));
byte[] data = mifareClassic.readBlock(bIndex+j);//进行了读卡
msgBuffer.append("块"+(bIndex+j)+"数据:").append(StringTool.byteHexToSting(data)).append("\r\n");
handler.sendEmptyMessage(0);
Log.e("数据","第"+(bIndex+j)+"块" + StringTool.byteHexToSting(data));
//修改KeyA和KeyB
if ((bIndex+j)==(4*i+3)){
//将所有扇区的最后一个Block修改为111111111111ff078069111111111111
mifareClassic.writeBlock(bIndex+j,new byte[]{(byte) 0x11, (byte) 0x11, (byte) 0x11, (byte) 0x11, (byte) 0x11, (byte) 0x11, (byte) 0xff, 0x07, (byte) 0x80, (byte) 0x69,(byte) 0x11, (byte) 0x11, (byte) 0x11, (byte) 0x11, (byte) 0x11, (byte) 0x11});
Log.e("onNewIntent:",(bIndex+j)+"块加密成功");
}
}
}else {
msgBuffer.append("第"+(i+1)+"扇区"+"验证新卡密码失败\r\n");
handler.sendEmptyMessage(0);
Log.e("失败 ","验证密码");
}
通过上面的操作基本是可以进行刷卡读写操作了,但是这还没有结束,我们还需要在这个Activity的生命周期对NFC进行开启和关闭前台调度系统。除此之外我们还要在这个Activity创建之时先检查我们的设备是否支持NFC功能,做到尽可能的完善,代码如下:
@Override
protected void onResume() {
super.onResume();
//开启前台调度系统
if (mAdapter != null) {
mAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
}
@Override
protected void onPause() {
super.onPause();
//关闭前台调度系统
mAdapter.disableForegroundDispatch(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgBuffer = new StringBuffer();
mAdapter = NfcAdapter.getDefaultAdapter(this);
mPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
mFilters = new IntentFilter[]{ndef,};
mTechLists = new String[][]{{IsoDep.class.getName()}, {NfcA.class.getName()},};
Log.d(" mTechLists", NfcF.class.getName() + mTechLists.length);
if (mAdapter == null) {
Toast.makeText(this, "设备不支持NFC!", Toast.LENGTH_LONG).show();
msgBuffer.append("\r\n").append("设备不支持NFC!");
handler.sendEmptyMessage(0);
return;
}else {
msgBuffer.append("\r\n").append("设备支持NFC!");
handler.sendEmptyMessage(0);
}
if (!mAdapter.isEnabled()) {
Toast.makeText(this, "请在系统设置中先启用NFC功能!", Toast.LENGTH_LONG).show();
return;
}
}
这样我们的NFC读写卡功能就能实现了,如果有什么地方错误的话欢迎大家指正,互相探讨,这个领域好像很少人做。希望更多人一起来探讨。
原创:https://blog.csdn.net/weixin_40600325/article/details/88824910