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;
    }


}

 

posted @ 2021-05-06 11:16  寒水易兮萧萧风  阅读(905)  评论(0编辑  收藏  举报