Android--NFC讀寫

一、NFC  API

API地址:https://www.android-doc.com/guide/topics/connectivity/nfc/nfc.html

二、簡介

NFC的三種模式

讀卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、點對點模式(P2P mode)。

(1)讀卡器模式

數據在NFC芯片中,可以簡單理解成“刷標籤”。本質上就是通過支持NFC的手機或其它電子設備從帶有NFC芯片的標籤、貼紙、名片等媒介中讀寫信息。通常NFC標籤是不需要外部供電的。當支持NFC的外設向NFC讀寫數據時,它會發送某種磁場,而這個磁場會自動的向NFC標籤供電。

(2)仿真卡模式

數據在支持NFC的手機或其它電子設備中,可以簡單理解成“刷手機”。本質上就是將支持NFC的手機或其它電子設備當成借記卡、公交卡、門禁卡等IC卡使用。基本原理是將相應IC卡中的信息憑證封裝成數據包存儲在支持NFC的外設中 。

在使用時還需要一個NFC射頻器(相當於刷卡器)。將手機靠近NFC射頻器,手機就會接收到NFC射頻器發過來的信號,在通過一系列複雜的驗證後,將IC卡的相應信息傳入NFC射頻器,最後這些IC卡數據會傳入NFC射頻器連接的電腦,並進行相應的處理(如電子轉帳、開門等操作)。

(3)點對點模式

該模式與藍牙、紅外差不多,用於不同NFC設備之間進行數據交換,不過這個模式已經沒有有“刷”的感覺了。其有效距離一般不能超過4釐米,但傳輸建立速度要比紅外和藍牙技術快很多,傳輸速度比紅外塊得多,如過雙方都使用Android4.2,NFC會直接利用藍牙傳輸。這種技術被稱爲Android Beam。所以使用Android Beam傳輸數據的兩部設備不再限於4釐米之內。

點對點模式的典型應用是兩部支持NFC的手機或平板電腦實現數據的點對點傳輸,例如,交換圖片或同步設備聯繫人。因此,通過NFC,多個設備如數字相機,計算機,手機之間,都可以快速連接,並交換資料或者服務。

二、NDEF,TECH,TAG 解析順序

在使用之前,我們要先去了解下NFC 的tag分發系統

如果想讓android設備感應到NFC標籤,你要保證兩點
1:屏幕沒有鎖住
2:NFC功能已經在設置中打開
當系統檢測到一個NFC標籤的時候,他會自動去尋找最合適的activity去處理這個intent.
他所發出的這個Intent將會有三種action:
ACTION_NDEF_DISCOVERED:當系統檢測到tag中含有NDEF格式的數據時,且系統中有activity聲明可以接受包含NDEF數據的Intent的時候,系統會優先發出這個action的intent。
ACTION_TECH_DISCOVERED:當沒有任何一個activity聲明自己可以響應ACTION_NDEF_DISCOVERED時,系統會嘗試發出TECH的intent.即便你的tag中所包含的數據是NDEF的,但是如果這個數據的MIME type或URI不能和任何一個activity所聲明的想吻合,系統也一樣會嘗試發出tech格式的intent,而不是NDEF.
ACTION_TAG_DISCOVERED:當系統發現前兩個intent在系統中無人會接受的時候,就只好發這個默認的TAG類型的

3:NFC標籤過濾
在activity的intent過濾xml聲明中,你可以同時聲明過濾這三種action.但是由之前所說,你應該知道系統在發送intent的時候是有優先級的,所以你最好清楚自己最想處理哪個。
1、過濾ACTION_TAG_DISCOVERED:

<intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

2、過濾ACTION_NDEF_DISCOVERED:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

data的mimeType類型了,這個定義的越準確,intent指向你這個activity的成功率就越高,否則系統可能不會發出你想要的NDEF intent了。

3、過濾ACTION_TECH_DISCOVERED:
你首先需要在你的<project-path>/res/xml下面創建一個過濾規則文件。名字任取,比如可以叫做nfc_tech_filter.xml。這個裏面定義的是nfc實現的各種標準,每一個nfc卡都會符合多個不同的標準,個人理解爲這些標準有些相互之間也是兼容的。你可以在檢測到nfc標籤後使用getTechList()方法來查看你所檢測的tag到底支持哪些nfc標準。
一個nfc_tech_filter.xml中可以定義多個<tech-list>結構組。每一組代表我聲明我只接受同時滿足這些標準的nfc標籤。比如A組表示,只有同時滿足IsoDep,NfcA,NfcB,NfcF這四個標準的nfc標籤的intent才能進入。A與B組之間的關係就是隻要滿足其中一個就可以了。換句話說,你的nfc標籤技術,滿足A的聲明也可以,滿足B的聲明也可以。

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
        <tech-list> --------------------------------A組
        <tech>android.nfc.tech.IsoDep</tech> <tech>android.nfc.tech.NfcA</tech>      
        <tech>android.nfc.tech.NfcB</tech> <tech>android.nfc.tech.NfcF</tech>
        </tech-list>
        <tech-list>-----------------------------------------B組
        <tech>android.nfc.tech.NfcV</tech> <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NdefFormatable</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
        </tech-list>
  </resources>

過濾器過濾

  <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" />

4、

nfc標籤前臺分發系統
之所以把他也歸類在nfc的過濾裏面,主要是因爲他跟解析nfc標籤到不是那麼的緊密,他解決的是接受哪些nfc標準的標籤問題。所以更接近nfc的過濾。
什麼叫nfc的前臺發佈系統?就是說當我們已經打開我們的應用的時候,那麼通過這個前臺發佈系統的設置,我們可以讓我們已經啓動的activity擁有更高的優先級來依據我們在代碼中定義的標準來過濾和處理intent,而不是讓別的聲明瞭intent filter的activity來干擾,甚至連自己聲明在androidManifest中的intent filter都不會來干擾。也就是說foreground Dispatch的優先級大於intent filter。
第一種情況:當你的activity沒有啓動的時候,去掃描tag,那麼系統中所有的intent filter都將一起參與過濾。
第二種情況:當你的actiity啓動了,去掃描tag時,那麼將直接使用你在foreground dispatch中代碼寫入的過濾標準。如果這個標準沒有命中任何intent,那麼系統將使用所有activity聲明的intent filter xml來過濾。

三、NFC有關的常見的ISO標準有:

標準 說明
ISO 14443 RFID卡標準(非接觸IC卡),該標準又有很多子標準
ISO 7816 接觸式IC卡標準
ISO 15693 某種射頻卡標準吧,這個沒查到資料
ISO 18092 NFC標準

 四、使用

1、加權限

<uses-permission android:name="android.permission.NFC" />

2、加上文提到的過濾,將activity啓動模式android:launchMode="singleInstance"或者android:launchMode="singleTop"

3、封裝工具類

	/**
 * Created by Administrator on 2019/7/26
 * <p>
 * desc:
 */
public class nfcUtils {
	public static NfcAdapter mNfcAdapter;
    public static IntentFilter[] mIntentFilter = null;
    public static PendingIntent mPendingIntent = null;
    public static String[][] mTechList = null;


	/**
     * 構造函數,用於初始化nfc
     */
    public nfcUtils(Activity activity) {
        mNfcAdapter = isNfcEnable(activity);
        NfcInit(activity);
    }
    /**
     * 判斷手機是否具備NFC功能
     *
     * @param context {@link Context}
     * @return {@code true}: 具備 {@code false}: 不具備
     */
    public static boolean isNfcExits(Context context) {
        NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
        return nfcAdapter != null;
    }

    /**
     * 判斷手機NFC是否開啓
     * <p>
     *     OPPO A37m 發現必須同時開啓NFC以及Android Beam纔可以使用
     *     20180108 發現OPPO單獨打開NFC即可讀取標籤,不清楚是否是系統更新
     * </p>
     *
     * @param context {@link Context}
     * @return {@code true}: 已開啓 {@code false}: 未開啓
     */
    public static boolean isNfcEnable(Context context) {
        NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
//        if (Build.MANUFACTURER.toUpperCase().contains("OPPO")) {
//            return nfcAdapter.isEnabled() && isAndroidBeamEnable(context);
//        }
        return nfcAdapter != null && nfcAdapter.isEnabled();
    }

    /**
     * 判斷手機NFC的Android Beam是否開啓,在API 16之後纔有
     *
     * @param context {@link Context}
     * @return {@code true}: 已開啓 {@code false}: 未開啓
     */
    public static boolean isAndroidBeamEnable(Context context) {
        NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && nfcAdapter != null && nfcAdapter.isNdefPushEnabled();
    }

    /**
     * 判斷手機是否具備Android Beam
     *
     * @param context {@link Context}
     * @return {@code true}:具備 {@code false}:不具備
     */
    public static boolean isAndroidBeamExits(Context context) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && isNfcExits(context);
    }

    /**
     * 跳轉至系統NFC設置界面.
     *
     * @param context {@link Context}
     * @return {@code true} 跳轉成功 <br> {@code false} 跳轉失敗
     */
    public static boolean intentToNfcSetting(Context context) {
        if (isNfcExits(context)) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                return toIntent(context, Settings.ACTION_NFC_SETTINGS);
            }
        }
        return false;

    }

    /**
     * 跳轉至系統NFC Android Beam設置界面,同頁面基本都有NFC開關.
     *
     * @param context {@link Context}
     * @return {@code true} 跳轉成功 <br> {@code false} 跳轉失敗
     */
    public static boolean intentToNfcShare(Context context) {
        if (isAndroidBeamExits(context)) {
            return toIntent(context, Settings.ACTION_NFCSHARING_SETTINGS);
        }
        return false;
    }

    /**
     * 跳轉方法.
     * @param context {@link Context}
     * @param action 意圖
     * @return 是否跳轉成功 {@code true } 成功<br>{@code false}失敗
     */
    private static boolean toIntent(Context context, String action) {
        try {
            Intent intent = new Intent(action);
            context.startActivity(intent);
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 初始化nfc設置
     */
    public static void NfcInit(Activity activity) {
		方法一:
        Intent intent = new Intent(activity, activity.getClass());
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        mPendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
        //做一個IntentFilter過濾你想要的action 這裏過濾的是ndef
        IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
        //如果你對action的定義有更高的要求,比如data的要求,你可以使用如下的代碼來定義intentFilter
        //        IntentFilter filter2 = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
        //        try {
        //            filter.addDataType("*/*");
        //        } catch (IntentFilter.MalformedMimeTypeException e) {
        //            e.printStackTrace();
        //        }
        //        mIntentFilter = new IntentFilter[]{filter, filter2};
        //        mTechList = null;
        try {
            filter.addDataType("*/*");
        } catch (IntentFilter.MalformedMimeTypeException e) {
            e.printStackTrace();
        }
        mTechList = new String[][]{{MifareClassic.class.getName()},
                {NfcA.class.getName()}};
        //生成intentFilter
        mIntentFilter = new IntentFilter[]{filter};
		方法二:
		mPendingIntent = PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
        IntentFilter filter2 = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
        try {
            filter.addDataType("*/*");
        } catch (IntentFilter.MalformedMimeTypeException e) {
            e.printStackTrace();
        }
        mIntentFilter = new IntentFilter[]{filter, filter2};
        mTechList = null;
    }


    /**
     * 讀取NFC的數據
     */
    public static String readNFCFromTag(Intent intent) throws UnsupportedEncodingException {
        Parcelable[] rawArray = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (rawArray != null) {
            NdefMessage mNdefMsg = (NdefMessage) rawArray[0];
            NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];
            if (mNdefRecord != null) {
                String readResult = new String(mNdefRecord.getPayload(), "UTF-8");
                return readResult;
            }
        }
        return "";
    }


    /**
     * 往nfc寫入數據
     */
    public static void writeNFCToTag(String data, Intent intent) throws IOException, FormatException {
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        Ndef ndef = Ndef.get(tag);
        ndef.connect();
        NdefRecord ndefRecord = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            ndefRecord = NdefRecord.createTextRecord(null, data);
        }
        NdefRecord[] records = {ndefRecord};
        NdefMessage ndefMessage = new NdefMessage(records);
        ndef.writeNdefMessage(ndefMessage);
    }

    /**
     * 讀取nfcID
     */
    public static String readNFCId(Intent intent) throws UnsupportedEncodingException {
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        String id = ByteArrayToHexString(tag.getId());
        return id;
    }

    /**
     * 將字節數組轉換爲字符串
     */
    private static String ByteArrayToHexString(byte[] inarray) {
        int i, j, in;
        String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
        String out = "";

        for (j = 0; j < inarray.length; ++j) {
            in = (int) inarray[j] & 0xff;
            i = (in >> 4) & 0x0f;
            out += hex[i];
            i = in & 0x0f;
            out += hex[i];
        }
        return out;
    }
}

4、彈框提示

private void showDialog(String title, String content, View.OnClickListener listener) {
        dismissDialog();
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.dialog_tag_lost, null);
        TextView tvTitle = (TextView) view.findViewById(R.id.dialog_title);
        TextView tvContent = (TextView) view.findViewById(R.id.dialog_content);
        tvTitle.setText(title);
        tvContent.setText(content);
        Button btnCancel = (Button) view.findViewById(R.id.btn_cancel);
        Button btnOk = (Button) view.findViewById(R.id.btn_confirm);
        btnCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissDialog();
            }
        });
        btnOk.setOnClickListener(listener);

        builder.setView(view);
        mDialog = builder.create();
        mDialog.setCancelable(false);
        mDialog.setCanceledOnTouchOutside(false);
        mDialog.show();
    }

    private void dismissDialog() {
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
            mDialog = null;
        }
    }

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:id="@+id/dialog_title"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:textSize="18sp"
        android:gravity="center"
        android:textColor="#76C36E"/>

    <TextView
        android:id="@+id/dialog_content"
        android:layout_width="match_parent"
        android:layout_height="70dp"
        android:textSize="16sp"
        android:gravity="center"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_cancel"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消"
            android:textStyle="bold"
            android:textColor="#76C36E"
            android:background="@null"/>

        <Button
            android:id="@+id/btn_confirm"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="確定"
            android:textStyle="bold"
            android:textColor="#76C36E"
            android:background="@null"/>
    </LinearLayout>
</LinearLayout>

5、生命週期

@Override
    protected void onDestroy() {
        super.onDestroy();
        dismissDialog();
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        processIntent(intent);
    }
    public void onResume(){
        super.onResume();
        nfcUtils.mNfcAdapter.enableForegroundDispatch(this, nfcUtils.mPendingIntent, nfcUtils.mIntentFilter, nfcUtils.mTechList);
    }

    public void onPause(){
        super.onPause();
        if (nfcUtils.isNfcEnable(this)){
            nfcUtils.mNfcAdapter.disableForegroundDispatch(this);
        }
    }

 6、在onCreate()中    nfcUtils nfc = new NFC(this);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章