Android上的NFC開發(fā)


title: Android上的NFC開發(fā)
date: 2017-09-02 16:34:33
tags: NFC


摘要:
最近了解了一下NFC相關(guān)開發(fā),做一下記錄哦


正文:

NFC開發(fā)

NFC是Near Field Communication縮寫,即近距離無線通訊技術(shù)。NFC提供了一種簡單、觸控式的解決方案,可以讓消費(fèi)者簡單直觀地交換信息、訪問內(nèi)容與服務(wù)。NFC技術(shù)允許電子設(shè)備之間進(jìn)行非接觸式點(diǎn)對(duì)點(diǎn)數(shù)據(jù)傳輸,在十厘米(3.9英吋)內(nèi),交換數(shù)據(jù),其傳輸速度有106Kbit/秒、212Kbit/秒或者424Kbit/秒三種。

工作模式

卡模式

例如QuickPay。

數(shù)據(jù)在支持NFC的手機(jī)或其它電子設(shè)備中,可以簡單理解成“刷手機(jī)”。本質(zhì)上就是將支持NFC的手機(jī)或其它電子設(shè)備當(dāng)成借記卡、公交卡、門禁卡等IC卡使用。基本原理是將相應(yīng)IC卡中的信息憑證封裝成數(shù)據(jù)包存儲(chǔ)在支持NFC的外設(shè)中 。在使用時(shí)還需要一個(gè)NFC射頻器(相當(dāng)于刷卡器)。將手機(jī)靠近NFC射頻器,手機(jī)就會(huì)接收到NFC射頻器發(fā)過來的信號(hào),在通過一系列復(fù)雜的驗(yàn)證后,將IC卡的相應(yīng)信息傳入NFC射頻器,最后這些IC卡數(shù)據(jù)會(huì)傳入NFC射頻器連接的電腦,并進(jìn)行相應(yīng)的處理(如電子轉(zhuǎn)帳、開門等操作)。

虛擬卡模式(Virtual Card Mode)

這些芯片的內(nèi)部實(shí)際上運(yùn)行一個(gè)微型JAVA虛擬機(jī),,一個(gè)卡(手機(jī))可以裝多個(gè)applet,它有自己的證書,上層協(xié)議是卡片與芯片內(nèi)置的Applet進(jìn)行加密交互。通過OAT(空中發(fā)卡)業(yè)務(wù)可以實(shí)現(xiàn)把服務(wù)器中的Applet二進(jìn)制文件下載到手機(jī)芯片中(是不是有點(diǎn)像HotFix技術(shù)?),俗稱“卡包”。

主機(jī)卡模式(Host Card Mode)

Google提供HCE的方法,在Android開發(fā)中集成HostAptuService 服務(wù),就可以實(shí)現(xiàn)卡模擬。

CardReader < -- >NFCAdapter <----> HostApduService <----> BackendCloudServer

讀卡器模式

數(shù)據(jù)在NFC芯片中,可以簡單理解成“刷標(biāo)簽”。本質(zhì)上就是通過支持NFC的手機(jī)或其它電子設(shè)備從帶有NFC芯片的標(biāo)簽、貼紙、名片等媒介中讀寫信息。通常NFC標(biāo)簽是不需要外部供電的。當(dāng)支持NFC的外設(shè)向NFC讀寫數(shù)據(jù)時(shí),它會(huì)發(fā)送某種磁場,而這個(gè)磁場會(huì)自動(dòng)的向NFC標(biāo)簽供電。

sample:公交卡充值查詢

點(diǎn)對(duì)點(diǎn)模式

該模式與藍(lán)牙、紅外差不多,用于不同NFC設(shè)備之間進(jìn)行數(shù)據(jù)交換,不過這個(gè)模式已經(jīng)沒有有“刷”的感覺了。其有效距離一般不能超過4厘米,但傳輸建立速度要比紅外和藍(lán)牙技術(shù)快很多,傳輸速度比紅外塊得多,如過雙方都使用Android4.2,NFC會(huì)直接利用藍(lán)牙傳輸。這種技術(shù)被稱為AndroidBeam。所以使用androidBeam傳輸數(shù)據(jù)的兩部設(shè)備不再限于4厘米之內(nèi)。

<img src="http://upload-images.jianshu.io/upload_images/5443336-a02d3bde35fa7d5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"/>

NFC的傳輸層協(xié)議棧

JAVA中 byte 是有符號(hào)一字節(jié)的,而char是編碼過的兩個(gè)字節(jié)的;C中byte(也就是#define byte (unsigned char))是無符號(hào)一個(gè)字節(jié)的,而char是有符號(hào)一字節(jié)的; 為了方便,我們?nèi)渴褂胋yte與16進(jìn)制進(jìn)行表示

APDU的數(shù)據(jù)結(jié)構(gòu),本質(zhì)是一種編碼。
發(fā)送的數(shù)據(jù)報(bào)

NFC讀卡器的Android開發(fā)

配置權(quán)限與feature

<!--Use NFC feature and Permissions-->
<uses-permission android:name="android.permission.NFC"/>
<uses-feature
 android:name="android.hardware.nfc"
 android:required="true"/>
 
注意配置Activity的模式為singleTop,配置Activity不可轉(zhuǎn)變屏幕(防止Intent丟失,支付寶也是這樣做的),配置NFCTech過濾器,配置Intent接收器。

<activity
android:name=".NfcReaderActivity"
android:launchMode="singleTop"
android:alwaysRetainTaskState="true"
android:label="@string/title_activity_nfcscanner">

  \<!--nfc filter-->
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />

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

</activity>

配置NFC卡片技術(shù)過濾器,每個(gè)卡對(duì)應(yīng)一個(gè) <tech-list>

/<!--file nfc_tech_filter.xml -->
<resources>

/<!--重慶一卡通-->
<tech-list>
<!--ISO 14443-4-->
<tech>android.nfc.tech.IsoDep</tech>
<!--ISO 14443-3A-->
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
</resources>

獲取Intent

在Activity 中針對(duì)收到的Intent進(jìn)行處理,Oncreate進(jìn)入的情況。

@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfcscanner);
handleIntent(getIntent());
}

@Override protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}

private void handleIntent(Intent intent)    {
//獲取到Intent的Action,注意多打Log
Log.d(TAG, "handleIntent: " + intent.getAction());
if (!intent.getAction().equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
Log.d(TAG, "handleIntent: no valid action");
return;
}
//獲取Tag對(duì)象
tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//獲取卡ID,這個(gè)ID一般沒什么用,有可能是卡自動(dòng)生成的
Log.d(TAG, "Id:" + Util.byteArraytoHexString(tag.getId()));
//NFC卡片所支持的技術(shù)標(biāo)準(zhǔn)
Log.d(TAG, "TechList:" + Arrays.toString(tag.getTechList()));
}

I/O處理

我們通過Tag獲取到I/O對(duì)象,通過字節(jié)流進(jìn)行處理,這里的例子按照如下的標(biāo)準(zhǔn)進(jìn)行解析

ISO-DEP (ISO 14443-4) 注意它與ISO-7816也是兼容的

目前最完善的代碼如下,基于RxAndroid進(jìn)行異步處理,可以處理所有的異常,注意這里的subscriber是一個(gè)RxAndroid的回掉接口,它處理成功,下一個(gè),異常這三個(gè)回掉

public final Observable<ResponseAPDU> getResponseAPDUObservable(final Tag tag,
final byte... bytes) {

return Observable.create(new Observable.OnSubscribe<ResponseAPDU>() {
@WorkerThread @Override public void call(Subscriber<? super ResponseAPDU> subscriber) {
  if (tag == null) {
    subscriber.onError(new NullPointerException(
        "Tag is null,try again to turn on NFC and keep your card close to your phone!"));
    return;
  }
  if (bytes == null || bytes.length == 0) {
    subscriber.onError(
        new NullPointerException("apdu is null or empty, cheak your command!"));
    return;
  }
  IsoDep iso = IsoDep.get(tag);
  byte[] result_all;
  //NfcA iso = NfcA.get(tag);
  if (iso == null) {
    subscriber.onError(new NullPointerException("Tech was not enumerated in NfcTechList"));
    return;
  }
  iso.setTimeout(5000);//ms
  try {
    if (!iso.isConnected()) {
      iso.connect();
    } else {
      iso.close();
      iso.connect();
    }
    result_all = iso.transceive(bytes);
    subscriber.onNext(ResponseAPDU.createFromPdu(result_all));
    subscriber.onCompleted();
  } catch (IOException e) {
    subscriber.onError(e);
  } finally {
    if (iso != null) {
      try {
        iso.close();
      } catch (IOException ignored) {
        subscriber.onError(ignored);
      }
    }
  }
}
});
}

在UI線程下,進(jìn)行如下的調(diào)用

//RxAndroid的回調(diào)
Subscriber subscriber = new Subscriber<ResponseAPDU>() {
@Override public void onCompleted() {
Log.d(TAG, "onCompleted:");
}

@Override public void onError(Throwable e) {
    mTextView_response.setText(e.getMessage());
}

@Override public void onNext(ResponseAPDU responseAPDU) {
Log.d(TAG, "onNext:" + responseAPDU.toString());
mTextView_response.setText(responseAPDU.toString());
}
    };

getResponseAPDUObservable(tag, data)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);

這里還有很大的優(yōu)化空間,比如加入 keep-alive 機(jī)制,避免每讀寫一次都重新打開關(guān)閉流,當(dāng)然這個(gè)需要與場景進(jìn)行適配

封裝成高級(jí)對(duì)象(DTO)

通過以上步驟,我們能夠得到一個(gè)簡單的基于字節(jié)流的NFC讀取器,但是更加進(jìn)一步的開發(fā)(比如查詢命令,SDK等)需要卡廠商提供內(nèi)部公開的SDK,均有私鑰的,就不能具體寫了。有興趣的可以去網(wǎng)上搜索PBOC2.0,網(wǎng)上有泄露的代碼,貌似是GPL協(xié)議,各位慎重學(xué)一下。這個(gè)開發(fā)一點(diǎn)都不難,封裝好I/O后,寫改刪查即可,主要難點(diǎn)在能夠與公交公司談攏(人家說不定看不上你,當(dāng)然這個(gè)活不是程序員干的),以及談攏后與對(duì)方開發(fā)進(jìn)行聯(lián)調(diào)。

NFC卡模擬

從12年開始,隨著O2O的發(fā)展,國外的品牌手機(jī)都開始加入了對(duì)SE芯片的支持,中華酷聯(lián)也開始加入了對(duì)SE卡的支持。

是否支持SE

AndroidSE的通信架構(gòu)

開發(fā)卡模擬

Java card Oracle

cardwerk ISO 7816-4: Interindustry Commands for Interchange

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容