Android NFC(一) M1卡讀寫(xiě)

原料: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ù)。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酪呻,一起剝皮案震驚了整個(gè)濱河市减宣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌玩荠,老刑警劉巖漆腌,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異阶冈,居然都是意外死亡闷尿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門女坑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)填具,“玉大人,你說(shuō)我怎么就攤上這事匆骗±途埃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵碉就,是天一觀的道長(zhǎng)盟广。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瓮钥,這世上最難降的妖魔是什么筋量? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任烹吵,我火速辦了婚禮,結(jié)果婚禮上桨武,老公的妹妹穿的比我還像新娘肋拔。我一直安慰自己,他們只是感情好呀酸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布只损。 她就那樣靜靜地躺著,像睡著了一般七咧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叮叹,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天艾栋,我揣著相機(jī)與錄音,去河邊找鬼蛉顽。 笑死蝗砾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的携冤。 我是一名探鬼主播悼粮,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼曾棕!你這毒婦竟也來(lái)了扣猫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤翘地,失蹤者是張志新(化名)和其女友劉穎申尤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衙耕,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昧穿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了橙喘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片时鸵。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖厅瞎,靈堂內(nèi)的尸體忽然破棺而出饰潜,到底是詐尸還是另有隱情,我是刑警寧澤磁奖,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布囊拜,位于F島的核電站,受9級(jí)特大地震影響比搭,放射性物質(zhì)發(fā)生泄漏冠跷。R本人自食惡果不足惜南誊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜜托。 院中可真熱鬧抄囚,春花似錦、人聲如沸橄务。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜂挪。三九已至重挑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棠涮,已是汗流浹背谬哀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留严肪,地道東北人史煎。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驳糯,于是被迫代替她去往敵國(guó)和親篇梭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355