Android原生NFC功能开发:附 js与安卓交互方式
要点:
以下示例是:
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; } }