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


}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM