原料:Android 帶NFC功能手機(jī)碟贾、M1卡
參考官方文檔: https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf
怕你們沒(méi)耐心先上demo
gayhub: https://github.com/soulListener/NFCDemo
1.在AndroidManifest中添加權(quán)限控制
<uses-permission android:name="android.permission.NFC"/>
activity中需要添加
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/tag_type" />
需要使用前臺(tái)調(diào)度的話,就需要在<intent-filter>中添加需要過(guò)濾的標(biāo)簽变汪,這里使用自定義過(guò)濾
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
2.開(kāi)始編寫(xiě)代碼
最主要的是在OnNewIntent中添加對(duì)卡片控制的代碼。主要的流程大概是這樣滴:
1.判斷是否支持NFC蚁趁、是否打開(kāi)NFC
2.通過(guò)Intent獲取卡類型裙盾,進(jìn)行類型判斷
3.獲得Adapter對(duì)象、獲得Tag對(duì)象、獲得MifareClassic對(duì)象
4.讀寫(xiě)卡操作分為兩個(gè)步驟:密碼校驗(yàn)番官、讀寫(xiě)卡庐完。只有對(duì)當(dāng)前要讀寫(xiě)的塊數(shù)據(jù)所在的扇區(qū)進(jìn)行密碼校驗(yàn)之后才能進(jìn)行接下來(lái)的讀寫(xiě)操作
/**
* @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","寫(xiě)入成功");
} else {
Log.e("onNewIntent","寫(xiě)入失敗");
}
} 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卡片讀寫(xiě)工具類
*/
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, "設(shè)備不支持NFC!", Toast.LENGTH_LONG).show();
}
if (!mNfcAdapter.isEnabled()) {
Toast.makeText(mContext, "請(qǐng)?jiān)谙到y(tǒng)設(shè)置中先啟用NFC功能徘熔!", Toast.LENGTH_LONG).show();
}
return mNfcAdapter;
}
/**
* 監(jiān)測(cè)是否支持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中包含的扇區(qū)數(shù)
int sectorCount = mifareClassic.getSectorCount();
for (int j = 0; j < sectorCount; j++) {
int bCount;//當(dāng)前扇區(qū)的塊數(shù)
int bIndex;//當(dāng)前扇區(qū)第一塊
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","密碼校驗(yàn)失敗");
}
}
return metaInfo;
} catch (IOException e){
throw new IOException(e);
} finally {
try {
mifareClassic.close();
}catch (IOException e){
throw new IOException(e);
}
}
}
/**
* 改寫(xiě)數(shù)據(jù)
* @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","寫(xiě)入成功");
} else {
Log.e("密碼是", "沒(méi)有找到密碼");
return false;
}
} catch (IOException e){
throw new IOException(e);
} finally {
try {
mifareClassic.close();
}catch (IOException e){
throw new IOException(e);
}
}
return true;
}
/**
* 密碼校驗(yàn)
* @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卡數(shù)據(jù)一個(gè)塊為16字節(jié)门躯,卡數(shù)據(jù)存儲(chǔ)的是16進(jìn)制的byte數(shù)組。讀取的時(shí)候要將16進(jìn)制byte數(shù)組轉(zhuǎn)換為10進(jìn)制的酷师;寫(xiě)卡的時(shí)候要進(jìn)行轉(zhuǎn)換為16進(jìn)制的byte數(shù)組讶凉,而且數(shù)據(jù)必須為16字節(jié)
2.第3塊一般不進(jìn)行數(shù)據(jù)存儲(chǔ)(0、1山孔、2懂讯、3塊)
3.一般來(lái)說(shuō)第0個(gè)扇區(qū)的第0塊為卡商初始化數(shù)據(jù),不能進(jìn)行寫(xiě)操作
4.要關(guān)注Activity的聲明周期台颠。onNewIntent中要進(jìn)行掃描卡片的處理褐望,onResume要禁止前臺(tái)卡片活動(dòng)的調(diào)度處理, onPause要啟用前臺(tái)卡片活動(dòng)的調(diào)度處理蓉媳。
5.要修改密鑰需要先校驗(yàn)密鑰之后修改控制位數(shù)據(jù)譬挚、密鑰數(shù)據(jù)。