原料:Android 带NFC功能手机、M1卡
参考官方文档: https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf
怕你们没耐心先上demo
gayhub: https://github.com/soulListener/NFCDemo
1.在AndroidManifest中添加权限控制
activity中需要添加
需要使用前台调度的话,就需要在
2.开始编写代码
最主要的是在OnNewIntent中添加对卡片控制的代码。主要的流程大概是这样滴:
1.判断是否支持NFC、是否打开NFC
2.通过Intent获取卡类型,进行类型判断
3.获得Adapter对象、获得Tag对象、获得MifareClassic对象
4.读写卡操作分为两个步骤:密码校验、读写卡。只有对当前要读写的块数据所在的扇区进行密码校验之后才能进行接下来的读写操作
/**
* @author kuan
* Created on 2019/2/25.
* @description
*/
public class NfcActivity extends AppCompatActivity {
private NfcAdapter mNfcAdapter;
@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));
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mNfcAdapter = M1CardUtils.isNfcAble(this);
M1CardUtils.setPendingIntent(PendingIntent.getActivity(this, 0, new Intent(this,
getClass()), 0));
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
M1CardUtils.isMifareClassic(tag,this);
try {
if (M1CardUtils.writeBlock(tag, 25,"9966332211445566".getBytes())){
Log.e("onNewIntent","写入成功");
} else {
Log.e("onNewIntent","写入失败");
}
} catch (IOException e) {
e.printStackTrace();
}
try {
M1CardUtils.readCard(tag);
} catch (IOException e) {
e.printStackTrace();
}
}
@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);
}
}
}
下面是抽离的工具类
/**
* @author kuan
* Created on 2019/2/26.
* @description MifareClassic卡片读写工具类
*/
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();
}
if (!mNfcAdapter.isEnabled()) {
Toast.makeText(mContext, "请在系统设置中先启用NFC功能!", Toast.LENGTH_LONG).show();
}
return mNfcAdapter;
}
/**
* 监测是否支持MifareClassic
* @param tag
* @param activity
* @return
*/
public static boolean isMifareClassic(Tag tag,Activity activity){
String[] techList = tag.getTechList();
boolean haveMifareUltralight = false;
for (String tech : techList) {
if (tech.contains("MifareClassic")) {
haveMifareUltralight = true;
break;
}
}
if (!haveMifareUltralight) {
Toast.makeText(activity, "不支持MifareClassic", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
/**
* 读取卡片信息
* @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();
}
}
遇坑指南
1.鄙人所用的M1卡数据一个块为16字节,卡数据存储的是16进制的byte数组。读取的时候要将16进制byte数组转换为10进制的;写卡的时候要进行转换为16进制的byte数组,而且数据必须为16字节
2.第3块一般不进行数据存储(0、1、2、3块)
3.一般来说第0个扇区的第0块为卡商初始化数据,不能进行写操作
4.要关注Activity的声明周期。onNewIntent中要进行扫描卡片的处理,onResume要禁止前台卡片活动的调度处理, onPause要启用前台卡片活动的调度处理。
5.要修改密钥需要先校验密钥之后修改控制位数据、密钥数据。