Android開發(fā)實(shí)現(xiàn)NFC刷卡讀取的兩種方式
場景:NFC是目前Android手機(jī)一個(gè)主流的配置硬件項(xiàng),本文主要講解一下Android開發(fā)中,NFC刷卡的兩種實(shí)現(xiàn)方式以及相關(guān)方法源碼解析。
①:Manifest注冊方式:這種方式主要是在Manifest文件對應(yīng)的activity下,配置過濾器,以響應(yīng)不同類型NFC Action。使用這種方式,在刷卡時(shí),如果手機(jī)中有多個(gè)應(yīng)用都存在該NFC實(shí)現(xiàn)方案,系統(tǒng)會彈出能響應(yīng)NFC事件的應(yīng)用列表供用戶選擇,用戶需要點(diǎn)擊目標(biāo)應(yīng)用來響應(yīng)本次NFC刷卡事件。目前我公司這邊項(xiàng)目中使用了該邏輯,比較簡便,這里先貼一下該方式的實(shí)現(xiàn)邏輯。
Manifest配置:
<!--權(quán)限要加,這是一個(gè)普通權(quán)限,不需要動態(tài)申請,但是在小米手機(jī)里需要動態(tài)申請-->
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
<application>
...
<activity
android:name=".NfcActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent">
<!--透明主題,把刷卡變成一個(gè)無感知的過程-->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<!--使用這個(gè)過濾器 這里其實(shí)還要用 meta-data 配置一下標(biāo)簽過濾,-->
<!--我項(xiàng)目中是 NDEF_DISCOVERED 這個(gè)TECH_DISCOVERED形同虛設(shè)-->
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech" />
</activity>
</application>
nfc_tech.xml:這個(gè)文件就是TECH_DISCOVERED需要配置的,其中,tech-list之間是邏輯或關(guān)系,tech之間是邏輯與關(guān)系,與方案②中的techLists原理以及用途是類似的。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
<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>
</resources>
NfcActivity:
public class NfcActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc);
initData();
}
/**
* 初始化數(shù)據(jù)
*/
private void initData() {
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
if (null == adapter) {
Toast.makeText(this, "不支持NFC功能", Toast.LENGTH_SHORT).show();
} else if (!adapter.isEnabled()) {
Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
// 根據(jù)包名打開對應(yīng)的設(shè)置界面
startActivity(intent);
}
//我項(xiàng)目中是拿了NFC卡的tag中的id數(shù)據(jù),這根據(jù)具體情況來;
// 可以在NfcAdapter源碼中查看,具體能拿到哪些數(shù)據(jù)
Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
String id = bytesToHex(tag.getId());
//TODO 目前我這邊項(xiàng)目中,拿到數(shù)據(jù)后,通過EventBus分發(fā)到對應(yīng)的activity,當(dāng)然也能使用其他分發(fā)響應(yīng)方式,
//關(guān)閉動畫,畢竟對用戶來說,刷卡應(yīng)當(dāng)是一個(gè)無感知的過程
overridePendingTransition(0, 0);
finish();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initData();
}
/**
* 2轉(zhuǎn)10
* @param src
* @return
*/
private static String bytesToTenNum(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[1] = Character.toUpperCase(Character.forDigit(
(src[i] >>> 4) & 0x0F, 16));
buffer[0] = Character.toUpperCase(Character.forDigit(src[i] & 0x0F,
16));
stringBuilder.append(buffer);
}
stringBuilder.reverse();
BigInteger bigi = new BigInteger(stringBuilder.toString(), 16);
return bigi.toString();
}
/**
* 2轉(zhuǎn)16
* @param src
* @return
*/
private static String bytesToHex(byte[] src){
StringBuffer sb = new StringBuffer();
if (src == null || src.length <= 0) {
return null;
}
String sTemp;
for (int i = 0; i < src.length; i++) {
sTemp = Integer.toHexString(0xFF & src[i]);
if (sTemp.length() < 2){
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}
②:前臺響應(yīng)機(jī)制:這種方式與第一種的區(qū)別如下:方法一中,NFC事件由系統(tǒng)分發(fā),需要選擇應(yīng)用去響應(yīng)事件;而方法二,直接使用前臺activity來捕獲NFC事件進(jìn)行響應(yīng),并且優(yōu)先級高于方案一。
下面對該方案進(jìn)行解析,直接懟上代碼。這里我新建了一個(gè)NfcTestActivity進(jìn)行測試,布局文件就補(bǔ)貼了,隨便丟一個(gè)就行。
NfcTestActivity:
/**
* @author Flash
* 創(chuàng)建時(shí)間:2021-07-30 11:14
*/
public class NfcTestActivity extends AppCompatActivity {
NfcAdapter mNfcAdapter;
PendingIntent pIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_test);
initNfc();
Log.i("FlashTestNFC", "onCreate");
}
@Override
protected void onStop() {
super.onStop();
Log.i("FlashTestNFC", "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i("FlashTestNFC", "onDestroy");
}
/**
* 初始化
*/
private void initNfc(){
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
pIntent = PendingIntent.getActivity(this, 0,
//在Manifest里或者這里設(shè)置當(dāng)前activity啟動模式,否則每次向陽NFC事件,activity會重復(fù)創(chuàng)建
// 當(dāng)然也要按照具體情況來,你設(shè)置成singleTask也不是不行,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
0);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//這里必須setIntent,set NFC事件響應(yīng)后的intent才能拿到數(shù)據(jù)
setIntent(intent);
Log.i("FlashTestNFC", "onNewIntent");
Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
//TODO 獲取數(shù)據(jù)進(jìn)行下一步處理
Log.i("FlashTestNFC--Tag", bytesToHex(tag.getId()));
}
@Override
protected void onResume() {
super.onResume();
Log.i("FlashTestNFC", "onResume");
if (mNfcAdapter != null) {
//添加intent-filter
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
IntentFilter tag = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
IntentFilter tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
IntentFilter[] filters = new IntentFilter[]{ndef, tag, tech};
//添加 ACTION_TECH_DISCOVERED 情況下所能讀取的NFC格式,這里列的比較全,實(shí)際我這里是沒有用到的,因?yàn)闇y試的卡是NDEF的
String[][] techList = new String[][]{
new String[]{
"android.nfc.tech.Ndef",
"android.nfc.tech.NfcA",
"android.nfc.tech.NfcB",
"android.nfc.tech.NfcF",
"android.nfc.tech.NfcV",
"android.nfc.tech.NdefFormatable",
"android.nfc.tech.MifareClassic",
"android.nfc.tech.MifareUltralight",
"android.nfc.tech.NfcBarcode"
}
};
mNfcAdapter.enableForegroundDispatch(this, pIntent, filters, techList);
}
}
@Override
protected void onPause() {
super.onPause();
Log.i("FlashTestNFC", "onPause");
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
/**
* 2進(jìn)制to 16進(jìn)制
* @param src
* @return
*/
private static String bytesToHex(byte[] src){
StringBuffer sb = new StringBuffer();
if (src == null || src.length <= 0) {
return null;
}
String sTemp;
for (int i = 0; i < src.length; i++) {
sTemp = Integer.toHexString(0xFF & src[i]);
if (sTemp.length() < 2){
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}
解析:主要其實(shí)就是NfcAdapter.enableForegroundDispatch(),開啟前臺響應(yīng);在onNewIntent中獲取系統(tǒng)傳遞過來的數(shù)據(jù),并解析;在前臺activity停止時(shí),使用NfcAdapter.disableForegroundDispatch()關(guān)閉響應(yīng)。下圖是該activity在設(shè)置啟動模式為singleTop或singleTask情況下,刷卡后該activity生命周期變化:

enableForegroundDispatch源碼注釋解析,這里大致翻譯一下:
- 將發(fā)現(xiàn)的tag(可以理解為NFC刷卡事件)優(yōu)先分配給應(yīng)用程序的前臺activity;
- 如果給該方法提供了任何IntentFilters,那么會優(yōu)先去匹配ACTION_NDEF_DISCOVERED和ACTION_TAG_DISCOVERED。由于ACTION_TECH_DISCOVERED依賴于 IntentFilter 匹配之外的元數(shù)據(jù),使用改IntentFilter要通過單獨(dú)傳入techLists來處理的。techLists中的每個(gè)第一級條目下的配置必須全部匹配才行。如果任何一級下的內(nèi)容都匹配,則分派將通過給定的 PendingIntent 進(jìn)行路由。(這三句話我解釋一下:techLists參數(shù)是一個(gè)二維數(shù)組,可以設(shè)置很多級,每一級下是第二級,在第二級中放置相關(guān)匹配項(xiàng);看我方法②中對techLists數(shù)組的構(gòu)建方式就能明白)。換句話說,第一級內(nèi)容是邏輯或關(guān)系,第二級內(nèi)容是邏輯與關(guān)系。
- 如果IntentFilters和techLists都傳了null,那么會默認(rèn)匹配ACTION_TAG_DISCOVERED
- 這個(gè)方法必須在主線程調(diào)用,并且activity必須處于前臺的情況下。同時(shí),在activity調(diào)用enableForegroundDispatch方法后,必須在onPause時(shí)調(diào)用disableForegroundDispatch進(jìn)行關(guān)閉。
- Manifest文件中要聲明NFC權(quán)限。

總結(jié):大概19年8-9月份的時(shí)候,那會兒剛開始實(shí)習(xí)不久,當(dāng)時(shí)手頭負(fù)責(zé)的項(xiàng)目就涉及到NFC刷卡,使用了方案①中的方式。在開發(fā)過程中,調(diào)試機(jī)為自己的華為Mate 20手機(jī),每一次我打開刷卡頁面進(jìn)行刷卡時(shí),都會默認(rèn)跳轉(zhuǎn)到微信的NFC事件響應(yīng)頁面,這叫一個(gè)頭大;后來直接找到微信NFC開關(guān),將其關(guān)閉后才不影響調(diào)試。好在線上手持機(jī)設(shè)備都是不讓用戶安裝其他應(yīng)用的。當(dāng)時(shí)還很奇怪,微信到底咋就能強(qiáng)占這NFC響應(yīng),現(xiàn)在我終于找到了答案并進(jìn)行了一定深度的挖掘。
對于這兩種方案,我更加偏向于方案②,因?yàn)榻换ド夏軌蝮w驗(yàn)更好,使用方案①用戶可能還會有一個(gè)選擇的過程。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android應(yīng)用程序簽名步驟及相關(guān)知識介紹
本文主要介紹Android應(yīng)用程序簽名相關(guān)的理論知識,包括:什么是簽名、為什么要給應(yīng)用程序簽名、如何給應(yīng)用程序簽名等,感興趣的朋友可以參考下哈2013-04-04
Flutter封裝組動畫混合動畫AnimatedGroup示例詳解
這篇文章主要為大家介紹了Flutter封裝組動畫混合動畫AnimatedGroup示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Android自定義驗(yàn)證碼輸入框的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Android自定義驗(yàn)證碼輸入框的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
Android多媒體應(yīng)用使用MediaPlayer播放音頻
這篇文章主要為大家詳細(xì)介紹了Android多媒體應(yīng)用使用MediaPlayer播放音頻,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android實(shí)現(xiàn)單頁顯示3個(gè)Item的ViewPager炫酷切換效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)單頁顯示3個(gè)Item的ViewPager炫酷切換效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10

