今天接到一个NFC读CPU卡的需求,做个总结。
总结之前,吐槽一下,一定要查一下测试机支不支持NFC功能,我拿乐视S3开发了一上午,以为哪里出了BUG或者权限没加,查了无数资料,下了十几个demo,死活就是连不上NFC,下午换了小米8se来测,发现还是连不上。。。万念俱灰之际,找了同事的小米MIX2来测,发现可以了!!!!!!!!!!
测试之前一定要查一下自己的测试机支不支持NFC功能!!!!!!!!
测试之前一定要查一下自己的测试机支不支持NFC功能!!!!!!!!
测试之前一定要查一下自己的测试机支不支持NFC功能!!!!!!!!
言归正传,上代码
步骤一:Mainfext中添加权限
<uses-permission android:name="android.permission.NFC" />
在activity中添加
<activity
android:name=".NfcActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<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/tag_type" />
</activity>
在res文件夹中新建一个xml文件夹,取名叫:tag_type
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.MifareClassic</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcF</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NdefFormatable</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
</tech-list>
</resources>
主activity中
package com.soullistener.nfcdemo;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.widget.TextView;
import java.io.IOException;
public class NfcActivity extends AppCompatActivity {
private NfcAdapter mNfcAdapter;
private Tag mTag;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNfcAdapter = M1CardUtils.isNfcAble(this);
M1CardUtils.setPendingIntent(PendingIntent.getActivity(this, 0, new Intent(this,
getClass()), 0));
mTag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
TextView textView = findViewById(R.id.tv_content);
textView.setMovementMethod(ScrollingMovementMethod.getInstance());
//M1卡类型
findViewById(R.id.btn_read_m1).setOnClickListener(v -> {
if (M1CardUtils.hasCardType(mTag, this, "MifareClassic")) {
try {
StringBuilder stringBuilder = new StringBuilder();
String[][] m1Content = M1CardUtils.readCard(mTag);
for (int i = 0; i < m1Content.length; i++) {
for (int j = 0; j < m1Content[i].length; j++) {
stringBuilder.append(m1Content[i][j]+"\n");
}
}
textView.setText(stringBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
});
//CPU
findViewById(R.id.btn_read_cpu).setOnClickListener(v->{
if (M1CardUtils.hasCardType(mTag, this, "IsoDep")) {
try {
textView.setText(M1CardUtils.readIsoCard(mTag));
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mNfcAdapter = M1CardUtils.isNfcAble(this);
M1CardUtils.setPendingIntent(PendingIntent.getActivity(this, 0, new Intent(this,
getClass()), 0));
Log.e("onNewIntent","onNewIntent");
mTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
}
@Override
public void onPause() {
super.onPause();
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
@Override
public void onResume() {
super.onResume();
if (mNfcAdapter != null) {
mNfcAdapter.enableForegroundDispatch(this, M1CardUtils.getPendingIntent(),
null, null);
}
}
}
添加两个工具类
package com.soullistener.nfcdemo;
import android.app.Activity;
import android.app.PendingIntent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
public class M1CardUtils {
private static PendingIntent pendingIntent;
public static PendingIntent getPendingIntent(){
return pendingIntent;
}
public static void setPendingIntent(PendingIntent pendingIntent){
M1CardUtils.pendingIntent = pendingIntent;
}
/**
* 判断是否支持NFC
* @return
*/
public static NfcAdapter isNfcAble(Activity mContext){
NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext);
if (mNfcAdapter == null) {
Toast.makeText(mContext, "设备不支持NFC!", Toast.LENGTH_LONG).show();
}else if (!mNfcAdapter.isEnabled()) {
Toast.makeText(mContext, "请在系统设置中先启用NFC功能!", Toast.LENGTH_LONG).show();
}
return mNfcAdapter;
}
/**
* 监测是否支持cardType类型卡
* @param tag
* @param activity
* @param cardType
* @return
*/
public static boolean hasCardType(Tag tag,Activity activity,String cardType){
if (tag == null){
Toast.makeText(activity,"请贴卡",Toast.LENGTH_LONG).show();
return false;
}
String[] techList = tag.getTechList();
boolean hasCardType = false;
for (String tech : techList) {
Log.e("TagTech",tech);
if (tech.contains(cardType)) {
hasCardType = true;
break;
}
}
if (!hasCardType) {
Toast.makeText(activity, "不支持"+cardType+"卡", Toast.LENGTH_LONG).show();
}
return hasCardType;
}
/**
* CPU卡信息读取
* @param tag
* @return
* @throws IOException
*/
public static String readIsoCard(Tag tag) throws IOException {
IsoDep isoDep = IsoDep.get(tag);
if (!isoDep.isConnected()){
isoDep.connect();
}
String result = StringUtil.bytesToHexString(isoDep.transceive(StringUtil.hex2Bytes("00A40000023F00")));
Log.e("readIsoCard",result);
result = StringUtil.bytesToHexString(isoDep.transceive(StringUtil.hex2Bytes("00A40000020005")));
Log.e("readIsoCard",result);
result = StringUtil.bytesToHexString(isoDep.transceive(StringUtil.hex2Bytes("00B0000016")));
Log.e("readIsoCard",result);
isoDep.close();
return result;
}
/**
* M1读取卡片信息
* @return
*/
public static String[][] readCard(Tag tag) throws IOException{
MifareClassic mifareClassic = MifareClassic.get(tag);
try {
mifareClassic.connect();
String[][] metaInfo = new String[16][4];
// 获取TAG中包含的扇区数
int sectorCount = mifareClassic.getSectorCount();
for (int j = 0; j < sectorCount; j++) {
int bCount;//当前扇区的块数
int bIndex;//当前扇区第一块
if (m1Auth(mifareClassic,j)) {
bCount = mifareClassic.getBlockCountInSector(j);
bIndex = mifareClassic.sectorToBlock(j);
for (int i = 0; i < bCount; i++) {
byte[] data = mifareClassic.readBlock(bIndex);
String dataString = bytesToHexString(data);
metaInfo[j][i] = dataString;
Log.e("获取到信息",dataString);
bIndex++;
}
} else {
Log.e("readCard","密码校验失败");
}
}
return metaInfo;
} catch (IOException e){
throw new IOException(e);
} finally {
try {
mifareClassic.close();
}catch (IOException e){
throw new IOException(e);
}
}
}
/**
* 改写数据
* @param block
* @param blockbyte
*/
public static boolean writeBlock(Tag tag, int block, byte[] blockbyte) throws IOException {
MifareClassic mifareClassic = MifareClassic.get(tag);
try {
mifareClassic.connect();
if (m1Auth(mifareClassic,block/4)) {
mifareClassic.writeBlock(block, blockbyte);
Log.e("writeBlock","写入成功");
} else {
Log.e("密码是", "没有找到密码");
return false;
}
} catch (IOException e){
throw new IOException(e);
} finally {
try {
mifareClassic.close();
}catch (IOException e){
throw new IOException(e);
}
}
return true;
}
/**
* 密码校验
* @param mTag
* @param position
* @return
* @throws IOException
*/
public static boolean m1Auth(MifareClassic mTag,int position) throws IOException {
if (mTag.authenticateSectorWithKeyA(position, MifareClassic.KEY_DEFAULT)) {
return true;
} else if (mTag.authenticateSectorWithKeyB(position, MifareClassic.KEY_DEFAULT)) {
return true;
}
return false;
}
private static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
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();
}
}
第二个
package com.soullistener.nfcdemo;
import java.io.UnsupportedEncodingException;
/**
*
* @description 字符串处理工具类
*/
public class StringUtil {
/**
* 判断字符串是否为空
*
* @param value
* @return
*/
public static boolean isEmpty(String value) {
if (value != null && !"".equalsIgnoreCase(value.trim())
&& !"null".equalsIgnoreCase(value.trim())) {
return false;
} else {
return true;
}
}
/**
* 十六进制的ASCII码转字符串
* @param hex
* @return
*/
public static String convertHexToString(String hex){
StringBuilder sb = new StringBuilder();
StringBuilder temp = new StringBuilder();
for( int i=0; i<hex.length()-1; i+=2 ){
String output = hex.substring(i, (i + 2));
int decimal = Integer.parseInt(output, 16);
sb.append((char)decimal);
temp.append(decimal);
}
return sb.toString();
}
/**
* 字节数组转十六进制字符串
* */
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 16进制转ascii
* @param string
* @return
*/
public static String converStringtoAscll(String string){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0;i<string.length();i++){
stringBuilder.append((int)string.charAt(i));
}
return stringBuilder.toString();
}
/**
*
* @param hexString
* @return 将十六进制转换为字节数组
*/
public static String hexStr = "0123456789ABCDEF";
public static byte[] hexStringToBinary(String hexString){
int len = hexString.length()/2;
byte[] bytes = new byte[len];
byte high = 0;
byte low = 0;
for(int i=0;i<len;i++){
high = (byte)((hexStr.indexOf(hexString.charAt(2*i)))<<4);
low = (byte)hexStr.indexOf(hexString.charAt(2*i+1));
bytes[i] = (byte) (high|low);
}
return bytes;
}
/**左补指定元素*/
public static String fillLeft(String value, String element, int count){
StringBuffer sb = new StringBuffer();
for(int i=0;i<count - value.length();i++){
sb.append(element);
}
sb.append(value);
return sb.toString();
}
/**右补指定元素*/
public static String fillRight(String value, String element, int count){
StringBuffer sb = new StringBuffer();
sb.append(value);
for(int i=0;i<count - value.length();i++){
sb.append(element);
}
return sb.toString();
}
//10进制转16进制
public static String IntToHex(int n){
char[] ch = new char[20];
int nIndex = 0;
while ( true ){
int m = n/16;
int k = n%16;
if ( k == 15 )
ch[nIndex] = 'F';
else if ( k == 14 )
ch[nIndex] = 'E';
else if ( k == 13 )
ch[nIndex] = 'D';
else if ( k == 12 )
ch[nIndex] = 'C';
else if ( k == 11 )
ch[nIndex] = 'B';
else if ( k == 10 )
ch[nIndex] = 'A';
else
ch[nIndex] = (char)('0' + k);
nIndex++;
if ( m == 0 )
break;
n = m;
}
StringBuffer sb = new StringBuffer();
sb.append(ch, 0, nIndex);
sb.reverse();
// String strHex = new String("0x");
// strHex += sb.toString();
return sb.toString();
}
// 16进制转10进制
public static int HexToInt(String strHex) {
int nResult = 0;
if (!IsHex(strHex))
return nResult;
String str = strHex.toUpperCase();
if (str.length() > 2) {
if (str.charAt(0) == '0' && str.charAt(1) == 'X') {
str = str.substring(2);
}
}
int nLen = str.length();
for (int i = 0; i < nLen; ++i) {
char ch = str.charAt(nLen - i - 1);
try {
nResult += (GetHex(ch) * GetPower(16, i));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return nResult;
}
// 计算16进制对应的数值
public static int GetHex(char ch) throws Exception {
if (ch >= '0' && ch <= '9')
return (int) (ch - '0');
if (ch >= 'a' && ch <= 'f')
return (int) (ch - 'a' + 10);
if (ch >= 'A' && ch <= 'F')
return (int) (ch - 'A' + 10);
throw new Exception("error param");
}
// 计算幂
public static int GetPower(int nValue, int nCount) throws Exception {
if (nCount < 0)
throw new Exception("nCount can't small than 1!");
if (nCount == 0)
return 1;
int nSum = 1;
for (int i = 0; i < nCount; ++i) {
nSum = nSum * nValue;
}
return nSum;
}
// 判断是否是16进制数
public static boolean IsHex(String strHex) {
int i = 0;
if (strHex.length() > 2) {
if (strHex.charAt(0) == '0'
&& (strHex.charAt(1) == 'X' || strHex.charAt(1) == 'x')) {
i = 2;
}
}
for (; i < strHex.length(); ++i) {
char ch = strHex.charAt(i);
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
|| (ch >= 'a' && ch <= 'f'))
continue;
return false;
}
return true;
}
/**
* 转字符串成十六进制的ASCII
* @param str
* @return
*/
public static String convertStringToHex(String str){
char[] chars = str.toCharArray();
StringBuffer hex = new StringBuffer();
for(int i = 0; i < chars.length; i++){
hex.append(Integer.toHexString((int)chars[i]));
}
return hex.toString();
}
/**
* 十六进制字符串转换成bytes
*
* @param hexStr
* @return
*/
public static byte[] hexStr2Bytes(String hexStr) {
int l = hexStr.length();
if (l % 2 != 0) {
StringBuilder sb = new StringBuilder(hexStr);
sb.insert(hexStr.length() - 1, '0');
hexStr = sb.toString();
}
byte[] b = new byte[hexStr.length() / 2];
int j = 0;
for (int i = 0; i < b.length; i++) {
char c0 = hexStr.charAt(j++);
char c1 = hexStr.charAt(j++);
b[i] = (byte) ((parse(c0) << 4) | parse(c1));
}
return b;
}
private static int parse(char c) {
if (c >= 'a')
return (c - 'a' + 10) & 0x0f;
if (c >= 'A')
return (c - 'A' + 10) & 0x0f;
return (c - '0') & 0x0f;
}
/**解析44域汉字错误信息描述*/
public static String parseErrorMsg(String msg){
String parseStr = null;
try {
parseStr = new String(hexStringToByte(msg.toUpperCase()),"GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return parseStr;
}
public static byte[] hexStringToByte(String hex) {
int len = hex.length() / 2;
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for(int i = 0; i < len; ++i) {
int pos = i * 2;
result[i] = (byte)(toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
private static byte toByte(char c) {
byte b = (byte)"0123456789ABCDEF".indexOf(c);
return b;
}
/**格式姓名*/
public static String formateName(String name){
String nameStr = "";
int length = name.length();
if(length == 2){
nameStr = name.substring(0,1);
nameStr += "*";
}else if(length == 3){
nameStr = name.substring(0,1);
nameStr += "**";
}
return nameStr;
}
/**
* 16进制数字取反
*
* @param hexString
* @return
*/
public static String hexBalanceNo(String hexString) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < hexString.length(); i++) {
stringBuilder.append(Integer.toHexString(~Integer.valueOf(hexString, 16)).substring(7, 8));
}
return stringBuilder.toString();
}
/**
* 16进制数字取反
*
* @param hexString
* @return
*/
public static String hexNo(String hexString) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < hexString.length(); i++) {
String noHex = Integer.toHexString(~Integer.valueOf(String.valueOf(hexString.charAt(i)), 16));
stringBuilder.append(noHex.substring(7, 8));
}
return stringBuilder.toString();
}
public static byte[] intToBytes(int value) {
byte[] src = new byte[4];
src[3] = (byte) ((value >> 24) & 0xFF);
src[2] = (byte) ((value >> 16) & 0xFF);
src[1] = (byte) ((value >> 8) & 0xFF);
src[0] = (byte) (value & 0xFF);
return src;
}
/**
* 十六进制转int 大端模式
*
* @param hex
* @return
*/
public static int hex2Int(String hex) {
byte[] bytes = hex2Bytes(hex);
int len = bytes.length;
int rec = 0;
for (int i = 0; i < len; i++) {
int temp = bytes[i] & 0xff;
int off = (len - 1 - i) * 8;
rec |= (temp << off);
}
return rec;
}
/**
* 十六进制转Byte
*
* @param hexStr
* @return
*/
public static byte[] hex2Bytes(String hexStr) {
int len = hexStr.length();
if (len % 2 != 0) {
throw new RuntimeException("length error");
}
byte[] b = new byte[hexStr.length() / 2];
int bLen = b.length;
int j = 0;
for (int i = 0; i < bLen; i++) {
char c0 = hexStr.charAt(j++);
char c1 = hexStr.charAt(j++);
b[i] = (byte) ((parse(c0) << 4) | parse(c1));
}
return b;
}
}