要點:
以下示例是:
1:入參和讀取到的返回結果:都采用Android與js交互的方式處理
2:讀取卡片等的 某一扇區(該扇區帶有秘鑰)的信息並解析,扇區和秘鑰都可以在前端手動入參
3:如果想要獲取所有扇區的信息,可以放開 processIntent 方法中的一些注釋(我保留了獲取所有扇區信息的代碼)稍加修改測試即可。
//主干代碼
package com.xxxx.xxx; import android.annotation.SuppressLint; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.MifareClassic; import android.os.Build; import android.os.Bundle; import android.webkit.JavascriptInterface; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import com.greencloud.gcsunmi.utils.ICallbackUtil; import com.greencloud.gcsunmi.utils.ToHexStringUtils; public class MainActivity extends Activity { WebView mWebView; NfcAdapter nfcAdapter; PendingIntent mPendingIntent; //讀卡 扇區+秘鑰 String Sector = ""; String Key = ""; @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); mWebView.getSettings().setDefaultTextEncodingName("utf-8");// 設置編碼 mWebView.getSettings().setJavaScriptEnabled(true);// 支持js mWebView.setWebChromeClient(new WebChromeClient()); mWebView.setWebViewClient(new WebViewClientDemo());//添加一個頁面相應監聽類 mWebView.getSettings().setDomStorageEnabled(true);//支持HTML5中的一些控件標簽 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { mWebView.getSettings().setMediaPlaybackRequiresUserGesture(false);//支持前端音頻自動播放 } // 載入包含js的html mWebView.loadData("", "text/html", null); mWebView.loadUrl("file:///android_asset/www/index.html"); //nfc功能初始化 nfccheck(); mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); } /** * 注冊 */ class WebViewClientDemo extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 當打開新鏈接時,使用當前的 WebView,不會使用系統其他瀏覽器 view.loadUrl(url); return true; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); /** * 注冊JavascriptInterface,其中"lee"的名字隨便取,如果你用"lee",那么在html中只要用 lee.方法名() * 即可調用MyJavascriptInterface里的同名方法,參數也要一致 */ mWebView.addJavascriptInterface(new JsObject(), "lee"); } } public void initViews() { mWebView = (WebView) findViewById(R.id.wv_view); } /** * js調用的安卓方法 */ class JsObject { ICallbackUtil callback = new ICallbackUtil(); //讀卡接口 //sector扇區 key秘鑰 @JavascriptInterface public void funSunmiReadCard(final String sector, final String key) { Sector = sector; Key = key; } } /** * 安卓調用js的方法 輸出結果 * * @param me */ //讀卡 public void forJSrecard(final String me) { mWebView.post(new Runnable() { @Override public void run() { mWebView.loadUrl("javascript:recardresult(" + me + ")"); } }); } /** * 相關服務配置: */ private void nfccheck() { // 獲取默認的NFC控制器 nfcAdapter = NfcAdapter.getDefaultAdapter(MainActivity.this); if (nfcAdapter == null) { forJSrecard("'設備不支持NFC!'"); finish(); return; } if (!nfcAdapter.isEnabled()) { forJSrecard("'請在系統設置中先啟用NFC功能!'"); finish(); return; } } //啟動前台調度系統 @Override protected void onResume() { super.onResume(); nfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); } //關閉前台調度系統 @Override protected void onPause() { super.onPause(); if (nfcAdapter != null) { nfcAdapter.disableForegroundDispatch(this); } } //onNewIntent回調方法 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); // 當前app正在前端界面運行,這個時候有intent發送過來,那么系統就會調用onNewIntent回調方法,將intent傳送過來 // 我們只需要在這里檢驗這個intent是否是NFC相關的intent,如果是,就調用處理方法 if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { if (!Key.equals("") && Key != null) { processIntent(intent, Integer.valueOf(Sector), Key); } } } /** * 讀卡 * int sectorIndex 指定扇區 * String authKey 對應的秘鑰 * //注意,如果扇區傳0,秘鑰只傳字符串"ID",說明只是想獲取物理卡號,返回給前端物理卡號即可 */ private void processIntent(Intent intent, int sectorIndex, String authKey) { //取出封裝在intent中的TAG Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); byte[] bytesId = tagFromIntent.getId();// 獲取id數組 String idinfo = ToHexStringUtils.ByteArrayToHexString(bytesId); for (String tech : tagFromIntent.getTechList()) { } boolean auth = false; //如果密鑰只傳了"ID",返回給前端會員卡的物理ID即可 if (authKey != null && authKey.equals("ID")) { forJSrecard("'" + idinfo + "'"); } else { //如果密鑰只傳了"NID",秘鑰讀卡,后台秘鑰寫死即可 //讀取TAG MifareClassic mfc = MifareClassic.get(tagFromIntent); try { String metaInfo = ""; String msg = ""; 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 += "卡片類型:" + typeS + "\n共" + sectorCount + "個扇區\n共" // + mfc.getBlockCount() + "個塊\n存儲空間: " + mfc.getSize() + "B\n"+"Id標簽是"+info+"\n+"+"\n"; // for (int j = 0; j < sectorCount; j++) { //解析扇區秘鑰信息 auth = mfc.authenticateSectorWithKeyA(sectorIndex, ToHexStringUtils.hexStringToByte("568G789F334")); int bCount; int bIndex; if (auth) { // metaInfo += "Sector " + j + ":驗證成功\n"; // 讀取扇區中的塊 bCount = mfc.getBlockCountInSector(sectorIndex); bIndex = mfc.sectorToBlock(sectorIndex); for (int i = 0; i < 3; i++) { byte[] data = mfc.readBlock(bIndex); metaInfo += ToHexStringUtils.toStringHex2(ToHexStringUtils.bytesToHexString(data)); bIndex++; } } else { // metaInfo += "sector:" + sectorIndex + ":驗證失敗\n"; metaInfo += ""; } // } forJSrecard("'" + metaInfo + "'"); } catch (Exception e) { e.printStackTrace(); } } } }
AndroidMainfest.xml文件配置
<uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc" android:required="true" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.INTERNET"/>
<application android:allowBackup="true" android:icon="@mipmap/logo_cy" android:label="@string/app_name" android:supportsRtl="true" android:theme="@android:style/Theme.Translucent.NoTitleBar"> <activity android:name=".MainActivity" android:hardwareAccelerated="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.nfc.action.TAG_DISCOVERED" /> <data android:mimeType="text/plain" /> </intent-filter> </activity> </application>
JS前端交互代碼
<body> <a id="sn">讀卡</a> <div id="snback"></div> <script type="text/javascript"> document.querySelector('#sn').addEventListener('click', function () { javascript: lee.funSunmiReadCard(sector,key); return false; }, false); function recardresult(str) { document.querySelector("#snback").innerHTML = str } getidresult("ddd") </script> </body>
Android如果要讀取前端代碼,需要在與java目錄,同等級目錄下新增 assets 文件夾(注意,文件夾命名必須是這個名稱,安卓規定的)
Mainactivity頁面的代碼
// 載入包含js的html mWebView.loadData("", "text/html", null); mWebView.loadUrl("file:///android_asset/www/index.html");
就是為了讀取js等前端頁面(這里我又在assets文件夾下新增了一個www的文件夾)
工具類
import java.io.UnsupportedEncodingException; public class ToHexStringUtils { public static String ByteArrayToHexString(byte[] bytesId) { //Byte數組轉換為16進制字符串 // TODO 自動生成的方法存根 int i, j, in; String[] hex = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; String output = ""; for (j = 0; j < bytesId.length; ++j) { in = bytesId[j] & 0xff; i = (in >> 4) & 0x0f; output += hex[i]; i = in & 0x0f; output += hex[i]; } return output; } //字符序列轉換為16進制字符串 public static String bytesToHexString(byte[] src) { StringBuilder stringBuilder = new StringBuilder("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.forDigit((src[i] >>> 4) & 0x0F, 16); buffer[1] = Character.forDigit(src[i] & 0x0F, 16); stringBuilder.append(buffer); } return stringBuilder.toString(); } /** * 將16進制數字解碼成字符串,適用於所有字符(包括中文) */ public static String toStringHex2(String bytes) { byte[] baKeyword = new byte[bytes.length() / 2]; for (int i = 0; i < baKeyword.length; i++) { try { baKeyword[i] = (byte) (0xff & Integer.parseInt(bytes.substring( i * 2, i * 2 + 2), 16)); } catch (Exception e) { e.printStackTrace(); } } try { bytes = new String(baKeyword, "UTF-8");// UTF-16le:Not } catch (Exception e1) { e1.printStackTrace(); } return bytes; } public static byte[] hexStringToByte(String hex) { int len = (hex.length() / 2); byte[] result = new byte[len]; char[] achar = hex.toCharArray(); for (int i = 0; i < len; i++) { int pos = i * 2; result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1])); } return result; } private static int toByte(char c) { byte b = (byte) "0123456789ABCDEF".indexOf(c); return b; } public static String reEncoding(String text) { String str = null; try { str = new String(text.getBytes(), "utf-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } return str; } }