主頁 > 移動端開發 > Nfc 開發記錄總結

Nfc 開發記錄總結

2020-11-18 16:56:11 移動端開發

整個篇幅 上面為代碼,最后面為介紹,如果想直接看我遇到過的坑,請直接拉到最后

目錄

一、先來個NDEF寫入代碼:主要在Activity中的生命周期中呼叫

1??Activity 代碼

2??工具類代碼

3??Manifest中的 activity注冊代碼

4??TECH_DISCOVERED resource nfc_tech_filter

5??工具類

上面為 Nfc 的所有型別的讀取 以及NDEF型別的寫入,沒有其他型別的寫入,暫時沒有加密,后期會考慮新寫一篇來介紹,

二、注意事項

1、Android 如果想要啟動一個應用,并且到達指定界面,需要注意NdefRecord添加的先后順序,因為他會依次執行,type 必須是小寫,否則可能過濾失效,跳往啟動頁

2、IOS 不能主動識別鏈接之外的NdefRecord(除非主動去獲取),所以NdefRecord[]需要添加一個鏈接, iOS跳往外鏈去識別(添加到最后)

3、如果想要給 NdefRecord添加Id 只能自己去New NdefRecord但是又想達成人家代碼的效果



一、先來個NDEF寫入代碼:主要在Activity中的生命周期中呼叫

1??Activity 代碼

package com.jiao.demo.nfc;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.MifareUltralight;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcA;
import android.nfc.tech.NfcB;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.haier.hailicommontlib.mvp.model.utils.LogUtil;
import com.haier.hailicommontlib.mvp.model.utils.ToastUtil;
import com.jiao.demo.nfc.model.NfcUtil;
import com.jiao.demo.nfc.model.StringUtil;

import java.io.IOException;

/**
 * @author: jiaojunfeng
 * @date: 2020/11/6
 * @describe: NFC demo
 */

public class NFCMainActivity extends Activity implements OnClickListener {

    // NFC配接器
    private NfcAdapter nfcAdapter = null;
    // 傳達意圖
    private PendingIntent pi = null;
    // 濾掉組件無法回應和處理的Intent
    private IntentFilter tagDetected = null;
    // 文本控制元件
    private TextView promt = null;
    // 是否支持NFC功能的標簽
    private boolean isNFC_support = false;
    // 讀、寫、刪按鈕控制元件
    private Button readBtn, writeBtn, deleteBtn;
    private Button btRead;
    private Button btWrite;
    private Button btDelete;
    private TextView tvContent;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_n_f_c_main);

        initView();

        initNFCData();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (!isNFC_support) {
            // 如果設備不支持NFC或者NFC功能沒開啟,就return掉
            ToastUtil.showLongToast(this, "NFC功能受限,本設備不支持或未開啟");
            return;
        }
        // 開始監聽NFC設備是否連接
        startNFC_Listener();

        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(this.getIntent()
                .getAction())) {
            // 處理該intent
            processIntent(this.getIntent());
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (isNFC_support) {
            // 當前Activity如果不在手機的最前端,就停止NFC設備連接的監聽
            stopNFC_Listener();
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 當前app正在前端界面運行,這個時候有intent發送過來,那么系統就會呼叫onNewIntent回呼方法,將intent傳送過來
        // 我們只需要在這里檢驗這個intent是否是NFC相關的intent,如果是,就呼叫處理方法
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
            processIntent(intent);
        }
    }

    @Override
    public void onClick(View v) {
        // 點擊讀按鈕后
        if (v.getId() == R.id.bt_read) {
            processIntent(getIntent());
            // 點擊寫后寫入
        } else if (v.getId() == R.id.bt_write) {
            try {
                String content = NfcUtil.writeNdef(tagFromIntent);
                settext(content);
            } catch (Exception e) {
                settext("錯誤:" + e.getMessage());
                Log.e("myonclick", "寫nfc例外", e);
            }
        } else if (v.getId() == R.id.bt_delete) {
            try {
                delete(tagFromIntent);
            } catch (IOException e) {
                settext("錯誤:" + e.getMessage());
            } catch (FormatException e) {
                settext("錯誤:" + e.getMessage());
                Log.e("myonclick", "洗掉nfc例外", e);
            }
        }
    }


    private void initNFCData() {
        // 初始化設備支持NFC功能
        isNFC_support = true;
        // 得到默認nfc配接器
        nfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
        // 提示資訊定義
        String metaInfo = "";
        // 判定設備是否支持NFC或啟動NFC
        if (nfcAdapter == null) {
            metaInfo = "設備不支持NFC!";
            Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();
            isNFC_support = false;
        }
        if (!nfcAdapter.isEnabled()) {
            metaInfo = "請在系統設定中先啟用NFC功能!";
            Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();
            isNFC_support = false;
        }

        if (isNFC_support) {
            init_NFC();
        } else {
            promt.setTextColor(Color.RED);
            settext(metaInfo);
        }
    }


    private Tag tagFromIntent;

    /**
     * Parses the NDEF Message from the intent and prints to the TextView
     */
    public void processIntent(Intent intent) {
        if (!isNFC_support || intent == null) {
            ToastUtil.showLongToast(this, "不支持NFC 或資料Intent==NULL");
            return;
        }
        // 取出封裝在intent中的TAG
        tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        if (tagFromIntent == null) {
            Toast.makeText(this, "TAG為NULL", Toast.LENGTH_SHORT).show();
            return;
        }
        promt.setTextColor(Color.BLUE);
        String metaInfo = "";
        metaInfo += "卡片ID:" + StringUtil.bytesToHexString(tagFromIntent.getId()) + "\n";
        Toast.makeText(this, "找到卡片", Toast.LENGTH_SHORT).show();

        // Tech List
        String[] techList = tagFromIntent.getTechList();

        //分析NFC卡的型別: Mifare Classic/UltraLight Info
        String CardType = "\n\t" + techList.length + "\n\t";
        for (String s : techList) {
            LogUtil.I(TAG, "" + s);
            if (s.equals(NfcA.class.getName())) {
                // 讀取TAG
                NfcA mfc = NfcA.get(tagFromIntent);
                settext(mfc.getTag().toString());
                try {
                    if ("".equals(CardType))
                        CardType = "MifareClassic卡片型別 \n 不支持NDEF訊息 \n";
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else if (s.equals(NfcB.class.getName())) {
                try {
                    NfcB nfcB = NfcB.get(tagFromIntent);
                    nfcB.connect();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                CardType = "身份證";
            } else if (s.equals(MifareUltralight.class.getName())) {
                MifareUltralight mifareUlTag = MifareUltralight
                        .get(tagFromIntent);
                String lightType = "";
                // Type Info
                switch (mifareUlTag.getType()) {
                    case MifareUltralight.TYPE_ULTRALIGHT:
                        lightType = "Ultralight";
                        break;
                    case MifareUltralight.TYPE_ULTRALIGHT_C:
                        lightType = "Ultralight C";
                        break;
                }
                CardType = lightType + "卡片型別\n";

                Ndef ndef = Ndef.get(tagFromIntent);
                CardType += "最大資料尺寸:" + ndef.getMaxSize() + "\n";

            } else if (s.equals(MifareClassic.class.getName())) {
                try {
                    CardType = NfcUtil.readCard(tagFromIntent, NfcUtil.MIFARECLASSIC_CARD);
                } catch (IOException | FormatException e) {
                    CardType += "MifareClassic 錯誤" + e.toString();
                    e.printStackTrace();
                }
            } else if (s.equals(IsoDep.class.getName())) {
                try {
                    CardType = NfcUtil.readCard(tagFromIntent, NfcUtil.ISO_DEP_CARD);
                } catch (IOException | FormatException e) {
                    CardType += "IsoDep 錯誤" + e.toString();
                    e.printStackTrace();
                }
            } else if (s.equals(Ndef.class.getName())) {
                try {
                    CardType = NfcUtil.readCard(intent, tagFromIntent, NfcUtil.NDEF_CARD);

                } catch (IOException | FormatException e) {
                    CardType += "Ndef 錯誤" + e.toString();
                    e.printStackTrace();
                }

            }
        }
        metaInfo += CardType;
        settext(metaInfo);
    }


    private String TAG = "NFCMAINac";


    // 洗掉方法
    private void delete(Tag tag) throws IOException, FormatException {
        if (tag != null) {
            //新建一個里面無任何資訊的NdefRecord實體
            NdefRecord nullNdefRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
                    new byte[]{}, new byte[]{}, new byte[]{});
            NdefRecord[] records = {nullNdefRecord};
            NdefMessage message = new NdefMessage(records);
            // 決議TAG獲取到NDEF實體
            Ndef ndef = Ndef.get(tag);
            // 打開連接
            ndef.connect();
            // 寫入資訊
            ndef.writeNdefMessage(message);
            // 關閉連接
            ndef.close();
            settext("洗掉資料成功");

        } else {
            settext("設備與nfc卡連接斷開,請重新連接...");
        }
    }

    private void startNFC_Listener() {
        // 開始監聽NFC設備是否連接,如果連接就發pi意圖
        nfcAdapter.enableForegroundDispatch(this, pi,
                new IntentFilter[]{tagDetected}, null);
    }

    private void stopNFC_Listener() {
        // 停止監聽NFC設備是否連接
        nfcAdapter.disableForegroundDispatch(this);
    }

    private void init_NFC() {
        // 初始化PendingIntent,當有NFC設備連接上的時候,就交給當前Activity處理
        pi = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        // 新建IntentFilter,使用的是第二種的過濾機制
        tagDetected = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
        tagDetected.addCategory("haili.DEFAULT");
    }

    private void initView() {
        btRead = (Button) findViewById(R.id.bt_read);
        btWrite = (Button) findViewById(R.id.bt_write);
        btDelete = (Button) findViewById(R.id.bt_delete);
        tvContent = (TextView) findViewById(R.id.tv_content);
        btDelete = (Button) findViewById(R.id.bt_delete_log);
        // 控制元件的系結
        promt = (TextView) findViewById(R.id.tv_content);
        readBtn = (Button) findViewById(R.id.bt_read);
        writeBtn = (Button) findViewById(R.id.bt_write);
        deleteBtn = (Button) findViewById(R.id.bt_delete);
        // 給文本控制元件賦值初始文本
        promt.setText("等待RFID標簽");
        // 監聽讀、寫、刪按鈕控制元件
        readBtn.setOnClickListener(this);
        writeBtn.setOnClickListener(this);
        deleteBtn.setOnClickListener(this);
        btDelete.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                promt.setText("");
            }
        });
    }

    public void settext(String message) {
        promt.setText(promt.getText() + " \n\t" + message);
    }
}

2??工具類代碼

package com.jiao.demo.nfc.model;

import android.content.Intent;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcB;
import android.os.Parcelable;

import com.haier.hailicommontlib.mvp.model.utils.LogUtil;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * @author: jjf
 * @date: 2020/11/9
 * @describe:
 */
public class NfcUtil {
    private static String TAG = "NfcUtil";
    public static String NULL_DATA = "\n\tNdef is null...\n\t";
    public static String DEVICE_CONNENT_ERROR = "\n\t設備與nfc卡連接斷開,請重新連接...\n\t";
    public static final int NFCA_CARD = 11;
    public static final int NFCB_CARD = 12;
    public static final int NDEF_CARD = 21;
    public static final int MIFARECLASSIC_CARD = 31;
    public static final int ISO_DEP_CARD = 41;
 

    public static String readCard(Tag tag, int cartType) throws IOException, FormatException {
        switch (cartType) {
            case NFCA_CARD:

            case NFCB_CARD:
                return readNfcB(tag);
            case NDEF_CARD:
                return readNdefRecordPayload(tag);
            case MIFARECLASSIC_CARD:
                return readMifareClassic(tag);
            case ISO_DEP_CARD:
                return IsoDep(tag);
        }
        return "未發現的芯片型別";
    }

    /**
     * 讀取Ndef 資訊  根據Id 獲取指定NdefRecord 的附加引數
     * @param tag
     * @param id  指定NdefRecord (訊息錄入時 設定的Id)
     * @return
     * @throws IOException
     * @throws FormatException
     */
    public static String readNdef(Tag tag, String id) throws IOException, FormatException {

        //決議Tag獲取到NDEF實體


        Ndef ndef = Ndef.get(tag);
        if (ndef == null) {
            return NULL_DATA;
        }
        //打開連接
        if (!ndef.isConnected()) {
            ndef.connect();
        }

        //獲取NDEF訊息
        NdefMessage message = ndef.getNdefMessage();
        if (message == null) {
            ndef.close();
            return "NdefMessage==null0";
        }
        NdefRecord[] ndefRecord = message.getRecords();
        for (NdefRecord ndefRecord1 : ndefRecord) {
            byte[] payload = ndefRecord1.getPayload();
            if (payload != null) {
                String str = new String(payload, StandardCharsets.UTF_8);
                System.out.println(" " + str);
            }
        }
        //將訊息轉換成位元組陣列
        byte[] data = message.toByteArray();
        //將位元組陣列轉換成字串
        String str = new String(data, StandardCharsets.UTF_8);
        //關閉連接
        ndef.close();
        System.out.println(" " + str);
        return str;

    }

    // 讀取Ndef 引數,所有NdefRecord 附帶引數拼接一起回傳
    public static String readNdefRecordPayload(Tag tag) {
        StringBuilder str = new StringBuilder();
        Ndef ndef = null;
        try {
            //決議Tag獲取到NDEF實體

            if (tag == null) {
                return "Tag==null";
            }
            ndef = Ndef.get(tag);
            if (ndef == null) {
                return NfcUtil.NULL_DATA;
            }
            //打開連接
            ndef.connect();
            //獲取NDEF訊息
            NdefMessage message = ndef.getNdefMessage();
            if (message == null) {
                ndef.close();
                return "NdefMessage==null";
            }
            NdefRecord[] ndefRecord = message.getRecords();

            for (NdefRecord ndefRecord1 : ndefRecord) {
                byte[] payload = ndefRecord1.getPayload();
                if (payload != null) {
                    if (str.toString().length() != 0) {
                        str.append("&");
                    }
                    str.append(new String(payload, StandardCharsets.UTF_8));

                }
            }
            //關閉連接
            ndef.close();
            return str.toString();

        } catch (Exception e) {
            LogUtil.I("Base", "NfcUtil  " + e.toString());
            e.printStackTrace();
        } finally {
            if (ndef != null) {
                try {
                    ndef.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return str.toString();
    }

    // 讀取NfcB
    public static String readNfcB(Tag tag) throws IOException, FormatException {
        if (tag != null) {
            //決議Tag獲取到NDEF實體
            NfcB ndef = NfcB.get(tag);
            if (ndef == null) {
                return NULL_DATA;
            }
            try {
                //打開連接
                ndef.connect();
            } catch (Exception e) {
                e.printStackTrace();
            }
            //獲取NfcB訊息
            byte[] message = ndef.getProtocolInfo();

            for (int i = 0; message != null && i < message.length; i++) {
                LogUtil.I(TAG, "message[" + i + "]" + message[i]);
            }
            //將位元組陣列轉換成字串
            String str = new String(message, StandardCharsets.UTF_8);
            //關閉連接
            ndef.close();
            return str;
        } else {
            return DEVICE_CONNENT_ERROR;
        }
    }

    // Ndef 訊息寫入方法
    public static String writeNdef(Tag tag) throws Exception {
        if (tag != null) {
            //新建NdefRecord陣列,本例中陣列只有一個元素
            NdefRecord[] records = createNdefRecordByNFCUri("deviceId=123456");
            //新建一個NdefMessage實體
            NdefMessage message = new NdefMessage(records);
            // 決議TAG獲取到NDEF實體
            Ndef ndef = Ndef.get(tag);
            // 打開連接
            if (!ndef.isConnected()) {
                ndef.connect();
            }

            // 寫入NDEF資訊
            ndef.writeNdefMessage(message);
            // 關閉連接
            ndef.close();
            return "寫入資料成功!";
        } else {
            return DEVICE_CONNENT_ERROR;
        }
    }

    // Ndef簡單文本寫入
    public static NdefRecord createRecord() {
        //組裝字串,準備好你要寫入的資訊
        String msg = "BEGIN:HAHA\n" + "VERSION:1.0\n" + "DEVICE_ID:123456\n"
                + "hl_company\n" + "END:HAHA";
        //將字串轉換成位元組陣列
        byte[] textBytes = msg.getBytes();
        //將位元組陣列封裝到一個NdefRecord實體中去
        NdefRecord textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
                "text/*".getBytes(), new byte[]{}, textBytes);
        return textRecord;
    }


    /**
     * Ndef 創建訊息(錄入NFC)
     *
     * @param content
     * @return
     */
    public static NdefRecord[] createNdefRecordByNFCUri(String content) {
        //訊息ID
        String id = "hailiWasher01";
        //這一段是對應的想要打開的Activity中添加的意圖過濾引數pathPrefix 為自定義(前面要有斜杠),其余為固定寫法
        //<intent-filter>
        //    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        //    <category android:name="android.intent.category.DEFAULT" />
        //    <data android:scheme="vnd.android.nfc"
        //        android:host="ext"
        //        android:pathPrefix="/com.jiao.demo.nfc.nfcmainactivity:nfc"/>
        //</intent-filter>
        return new NdefRecord[]{
                //打開App , ,第一個引數跟第二個引數就是Manifest中的android:pathPrefix,第三個引數是自己添加的引數
                createExternal("com.jiao.demo.nfc.nfcmainactivity", id, "nfc", content.getBytes()),
                //想要打開的應用包名(如果是組件化專案,包名必須是主專案的包名,也就是說,模塊的build.gradle 中頂部代碼為 apply plugin: 'com.android.application')
                //如果沒有安裝這個應用,則會跳往應用市場
                NdefRecord.createApplicationRecord("com.haier.haierwashertopspeed"),
                //打開的鏈接,這里主要用于IOS 識別
                NdefRecord.createUri("https://www.baidu.com" + "?" + content),
        };
    }

    /**
     * 重寫NdefRecord方法 添加Id
     * {@link NdefRecord#createExternal(String, String, byte[])}
     *
     * @param domain domain-name of issuing organization (自定義的,Activity路徑 與 @param type   一起組成引數pathPrefix)
     * @param type   domain-specific type of data (自定義的,Activity路徑 與 @param domain   一起組成引數pathPrefix)
     * @param id     訊息Id
     * @param data   自添加資料 可以用來追加引數
     * @return
     */
    public static NdefRecord createExternal(String domain, String id, String type, byte[] data) {
        if (domain == null) throw new NullPointerException("domain is null");
        if (type == null) throw new NullPointerException("type is null");

        domain = domain.trim().toLowerCase(Locale.ROOT);
        type = type.trim().toLowerCase(Locale.ROOT);

        if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
        if (type.length() == 0) throw new IllegalArgumentException("type is empty");

        byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
        byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
        byte[] b = new byte[byteDomain.length + 1 + byteType.length];
        System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
        b[byteDomain.length] = ':';
        System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);

        return new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, b, id == null ? null : id.getBytes(), data);
    }


    //讀取EXTRA_NDEF_MESSAGES內容:
    public static String readFromTag(Intent intent) {
        Parcelable[] rawArray = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

        NdefMessage mNdefMsg = (NdefMessage) rawArray[0];
        NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];
        try {
            if (mNdefRecord != null) {
                return new String(mNdefRecord.getPayload(), "UTF-8");
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 讀取MifareClassic 型別
     *
     * @param tag
     * @return
     */
    public static String readMifareClassic(Tag tag) {
        //取出封裝在intent中的TAG

        String CardId = StringUtil.bytesToHexString(tag.getId());
        String metaInfo = "";
        metaInfo += "卡片ID:" + CardId;
        for (String tech : tag.getTechList()) {
            System.out.println(tech);
        }
        boolean auth;
        //讀取TAG
        MifareClassic mfc = MifareClassic.get(tag);
        try {
            //Enable I/O operations to the tag from this TagTechnology object.
            if (!mfc.isConnected()) {
                mfc.connect();
            }
            int type = mfc.getType();//獲取TAG的型別
            int sectorCount = mfc.getSectorCount();//獲取TAG中包含的扇區數
            String typeS = "";
            switch (type) {
                case MifareClassic.TYPE_CLASSIC:
                    typeS = "TYPE_CLASSIC";
                    break;
                case MifareClassic.TYPE_PLUS:
                    typeS = "TYPE_PLUS";
                    break;
                case MifareClassic.TYPE_PRO:
                    typeS = "TYPE_PRO";
                    break;
                case MifareClassic.TYPE_UNKNOWN:
                    typeS = "TYPE_UNKNOWN";
                    break;
            }
            metaInfo += "\n卡片型別:" + typeS + "\n共" + sectorCount + "個扇區\n共"
                    + mfc.getBlockCount() + "個塊\n存盤空間: " + mfc.getSize() + "B\n";
            for (int j = 0; j < sectorCount; j++) {
                //Authenticate a sector with key A.
                auth = mfc.authenticateSectorWithKeyA(j,
                        MifareClassic.KEY_DEFAULT);
                int bCount;
                int bIndex;
                if (auth) {
                    metaInfo += "Sector " + j + ":驗證成功\n";
                    // 讀取扇區中的塊
                    bCount = mfc.getBlockCountInSector(j);
                    bIndex = mfc.sectorToBlock(j);
                    for (int i = 0; i < bCount; i++) {
                        byte[] data = mfc.readBlock(bIndex);
                        metaInfo += "Block " + bIndex + " : "
                                + StringUtil.bytesToHexString(data) + "\n";
                        bIndex++;
                    }
                } else {
                    metaInfo += "Sector " + j + ":驗證失敗\n";
                }
            }
            return metaInfo;
            //Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
            return "讀取錯誤:" + e.toString();
        }

    }

    /**
     * 讀取IsoDep
     *
     * @param tag
     * @return
     */
    public static String IsoDep(Tag tag) {
        IsoDep isoDep = IsoDep.get(tag);
        String str = "";
        try {
            isoDep.connect(); // 連接
            if (isoDep.isConnected()) {
                LogUtil.D(TAG, "isoDep.isConnected"); // 判斷是否連接上
                // 1.select PSF (1PAY.SYS.DDF01)
                // 選擇支付系統檔案,它的名字是1PAY.SYS.DDF01,
                byte[] DFN_PSE = {(byte) '1', (byte) 'P', (byte) 'A', (byte) 'Y', (byte) '.', (byte) 'S', (byte) 'Y', (byte) 'S', (byte) '.', (byte) 'D', (byte) 'D', (byte) 'F', (byte) '0', (byte) '1',};
                byte[] payFile = isoDep.transceive(getSelectCommand(DFN_PSE));
                String EN_CODE_TYPE = "GBK";
                String payFileStr = new String(payFile, EN_CODE_TYPE);
                str += "\n\t" + "支付系統:" + payFileStr;

                // 2.選擇公交卡應用的名稱
                byte[] DFN_SRV = {(byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x86, (byte) 0x98, (byte) 0x07, (byte) 0x01,};
                byte[] card_name = isoDep.transceive(getSelectCommand(DFN_SRV));
                String card_nameStr = new String(card_name, EN_CODE_TYPE);
                str += "\n\t" + "公交卡應用的名稱:" + card_nameStr;
                // 3.讀取余額
                byte[] ReadMoney = {(byte) 0x80, // CLA Class
                        (byte) 0x5C, // INS Instruction
                        (byte) 0x00, // P1 Parameter 1
                        (byte) 0x02, // P2 Parameter 2
                        (byte) 0x04, // Le
                };
                byte[] Money = isoDep.transceive(ReadMoney);
                String MoneyStr = new String(card_name, EN_CODE_TYPE);
                str += "\n\t" + "余額1:" + MoneyStr;
                if (Money != null && Money.length > 4) {
                    int cash = byteToInt(Money, 4);
                    float ba = cash / 100.0f;

                    str += "\n\t" + "余額2:" + ba;
                }
                // 4.讀取所有交易記錄
                byte[] ReadRecord = {(byte) 0x00, // CLA Class
                        (byte) 0xB2, // INS Instruction
                        (byte) 0x01, // P1 Parameter 1
                        (byte) 0xC5, // P2 Parameter 2
                        (byte) 0x00, // Le
                };
                byte[] Records = isoDep.transceive(ReadRecord);
                if (Records != null && Records.length > 4) {
                    int cash = byteToInt(Records, 4);
                    float ba = cash / 100.0f;

                    str += "\n\t" + "總消費記錄:" + ba;
                }


                ArrayList<byte[]> ret = parseRecords(Records);
                List<String> retList = parseRecordsToStrings(ret);

                str = str + "\n\t" + "消費記錄" + retList.size() + "條";
                if (retList.size() > 0) {
                    str = str + ",如下:";
                }
                for (String string : retList) {
                    LogUtil.D(TAG, "消費記錄" + string);
                    str = str + "\n\t" + string;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (isoDep != null) {
                try {
                    isoDep.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return str;
    }


    public static byte byteToHex(byte arg) {
        byte hex = 0;
        if (arg >= 48 && arg <= 57) {
            hex = (byte) (arg - 48);
        } else if (arg >= 65 && arg <= 70) {
            hex = (byte) (arg - 55);
        } else if (arg >= 97 && arg <= 102) {
            hex = (byte) (arg - 87);
        }
        return hex;
    }

    private static byte[] getSelectCommand(byte[] aid) {
        final ByteBuffer cmd_pse = ByteBuffer.allocate(aid.length + 6);
        cmd_pse.put((byte) 0x00) // CLA Class
                .put((byte) 0xA4) // INS Instruction
                .put((byte) 0x04) // P1 Parameter 1
                .put((byte) 0x00) // P2 Parameter 2
                .put((byte) aid.length) // Lc
                .put(aid).put((byte) 0x00); // Le
        return cmd_pse.array();
    }


    // byteArray轉化為int
    private static int byteToInt(byte[] b, int n) {
        int ret = 0;
        for (int i = 0; i < n; i++) {
            ret = ret << 8;
            ret |= b[i] & 0x00FF;
        }
        if (ret > 100000 || ret < -100000)
            ret -= 0x80000000;
        return ret;
    }

    /**
     * 整條Records決議成ArrayList<byte[]>
     *
     * @param Records
     * @return
     */
    private static ArrayList<byte[]> parseRecords(byte[] Records) {
        int max = Records.length / 23;
        LogUtil.D(TAG, "消費記錄有" + max + "條");
        ArrayList<byte[]> ret = new ArrayList<byte[]>();
        for (int i = 0; i < max; i++) {
            byte[] aRecord = new byte[23];
            for (int j = 23 * i, k = 0; j < 23 * (i + 1); j++, k++) {
                aRecord[k] = Records[j];
            }
            ret.add(aRecord);
        }
        for (byte[] bs : ret) {
            LogUtil.D(TAG, "消費記錄有byte[]" + bs); // 有資料,決議正確,
        }
        return ret;
    }

    /**
     * ArrayList<byte[]>記錄分析List<String> 一條記錄是23個位元組byte[] data,對其解碼如下
     * data[0]-data[1]:index data[2]-data[4]:over,金額溢位??? data[5]-data[8]:交易金額
     * ??代碼應該是(5,4) data[9]:如果等于0x06或者0x09,表示刷卡;否則是充值
     * data[10]-data[15]:刷卡機或充值機編號
     * data[16]-data[22]:日期String.format("%02X%02X.%02X.%02X %02X:%02X:%02X"
     * ,data[16], data[17], data[18], data[19], data[20], data[21], data[22]);
     *
     * @return
     */
    private static List<String> parseRecordsToStrings(ArrayList<byte[]>... Records) {
        List<String> recordsList = new ArrayList<String>();
        for (ArrayList<byte[]> record : Records) {
            if (record == null)
                continue;
            for (byte[] v : record) {
                StringBuilder r = new StringBuilder();
                int cash = NumberUtil.toInt(v);
                char t = (v[9] == TRANS_CSU || v[9] == TRANS_CSU_CPX) ? '-' : '+';
                r.append(String.format("%02X%02X.%02X.%02X %02X:%02X ", v[16], v[17], v[18], v[19], v[20], v[21], v[22]));
                r.append("   " + t).append(cash / 100.0f);
                String aLog = r.toString();
                recordsList.add(aLog);
            }
        }
        return recordsList;
    }

    protected final static byte TRANS_CSU = 6; // 如果等于0x06或者0x09,表示刷卡;否則是充值
    protected final static byte TRANS_CSU_CPX = 9; // 如果等于0x06或者0x09,表示刷卡;否則是充值
}

3??Manifest中的 activity注冊代碼

  <activity
            android:name="com.jiao.demo.nfc.NFCMainActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|fontScale|touchscreen"
            android:launchMode="singleInstance"
            android:maxAspectRatio="2.4"
            android:label="NFC引數修改"
            android:screenOrientation="portrait">

            <!--   NFC  start-->
            <!-- NFC 過濾目標 -->
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="vnd.android.nfc"
                    android:host="ext"
                    android:pathPrefix="/com.jiao.demo.nfc.nfcmainactivity:nfc"/>
            </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/nfc_tech_filter" />
            <!--   NFC  start-->
        </activity>

4??TECH_DISCOVERED resource nfc_tech_filter

下面是nfc_tech_filter 中的代碼, 這個檔案的位置為src/main/res/xml/nfc_tech_filter.xml

因為我測驗的Nfc 只有三個型別,所以我只寫這幾個,這也是一層對Nfc的過濾

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <!-- 可以處理所有Android支持的NFC型別 -->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.IsoDep</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.NfcA</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.NfcB</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.NfcF</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.NfcV</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.Ndef</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.NdefFormatable</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.MifareUltralight</tech>-->
    <!--    </tech-list>-->
    <!--    <tech-list>-->
    <!--        <tech>android.nfc.tech.MifareClassic</tech>-->
    <!--    </tech-list>-->
    <tech-list>
        <tech>android.nfc.tech.MifareUltralight</tech>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
</resources>

5??工具類

public class NumberUtil {
    public static int toInt(byte[] bytes){
        int number = 0;
        for(int i = 0; i < 4 ; i++){
            number += bytes[i] << i*8;
        }
        return number;
    }
}


public class StringUtil {

    // 字符序列轉換為16進制字串
    public static String bytesToHexString(byte[] src) {
        return bytesToHexString(src, true);
    }

    public static String bytesToHexString(byte[] src, boolean isPrefix) {
        StringBuilder stringBuilder = new StringBuilder();
        if (isPrefix) {
            stringBuilder.append("0x");
        }
        if (src == null || src.length <= 0) {
            return null;
        }
        char[] buffer = new char[2];
        for (int i = 0; i < src.length; i++) {
            buffer[0] = Character.toUpperCase(Character.forDigit(
                    (src[i] >>> 4) & 0x0F, 16));
            buffer[1] = Character.toUpperCase(Character.forDigit(src[i] & 0x0F,
                    16));
            System.out.println(buffer);
            stringBuilder.append(buffer);
        }
        return stringBuilder.toString();
    }


}

上面為 Nfc 的所有型別的讀取 以及NDEF型別的寫入,沒有其他型別的寫入,暫時沒有加密,后期會考慮新寫一篇來介紹,

二、注意事項

1、Android 如果想要啟動一個應用,并且到達指定界面,需要注意NdefRecord添加的先后順序,因為他會依次執行,type 必須是小寫,否則可能過濾失效,跳往啟動頁

2、IOS 不能主動識別鏈接之外的NdefRecord(除非主動去獲取),所以NdefRecord[]需要添加一個鏈接, iOS跳往外鏈去識別(添加到最后)

//創建訊息(錄入NFC)
public static NdefRecord[] createNdefRecordByNFCUri(String content) {
    //這一段是對應的想要打開的Activity中添加的意圖過濾引數pathPrefix 為自定義(前面要有斜杠),其余為固定寫法
    //<intent-filter>
    //    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    //    <category android:name="android.intent.category.DEFAULT" />
    //    <data android:scheme="vnd.android.nfc"
    //        android:host="ext"
    //        android:pathPrefix="/com.jiao.demo.nfc.nfcmainactivity:nfc"/>
    //</intent-filter>
    //
    return new NdefRecord[]{
            //打開App , ,第一個引數跟第二個引數就是Manifest中的android:pathPrefix,第三個引數是自己添加的引數
            NdefRecord.createExternal("com.jiao.demo.nfc.nfcmainactivity", "nfc", content.getBytes()),
            //想要打開的應用包名(如果是組件化專案,包名必須是主專案的包名,也就是說,模塊的build.gradle 中頂部代碼為 apply plugin: 'com.android.application')
            //如果沒有安裝這個應用,則會跳往應用市場
            NdefRecord.createApplicationRecord("com.*.*"),
            //打開的鏈接,這里主要用于IOS 識別
            NdefRecord.createUri("https://www.baidu.com" + "?" + content),
    };
}

3、如果想要給 NdefRecord添加Id 只能自己去New NdefRecord但是又想達成人家代碼的效果,那就復制一下原始碼,給加個ID 比如

原始碼修改原始碼

至此,待補充,,,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/224294.html

標籤:其他

上一篇:Android WMS筆記(1)

下一篇:Android的應用資源-顏色狀態串列資源

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more