一、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);