IT评测·应用市场-qidao123.com

标题: 安卓Android.nfc读卡 [打印本页]

作者: 笑看天下无敌手    时间: 2024-8-30 15:04
标题: 安卓Android.nfc读卡
利用安卓手机,通过NFC功能可以读取IC卡信息,。
IC卡有很多种卡标准,协议,等等,这些具体就不细讨论。
主要讨论,2种卡,一种是M1卡,别的是CPU卡。
1、 M1卡

M1卡是市面上比较常见的卡,一般的门禁卡。比较便宜。
M1卡的数据存储比较通用的是分扇区存储数据。默认是16个扇区,每个扇区4个数据块。
每个卡片都有一个ID号码(去买很多的卡的时间,可以要求厂商提供给你的每个卡ID是唯一的。)
0扇区的数据,是厂商写死的数据,包含卡面的ID号。
其他扇区的数据,可以自己去写,可以加密写。(密码为12位)
2、 CPU卡

CPU卡是加密卡,CPU卡还分为双界面卡和单界面卡。比较复杂。此次只说明,CPU卡的数据,通常是通过指令来读取数据。
3、android 的NFC功能

当安卓手机的NFC功能感应到 卡片的时间。会读取出该卡片支持的数据传输方式
android.nfc.tech.NfcA

android.nfc.tech.NfcB

android.nfc.tech.MifareClassic

android.nfc.tech.IsoDep

1、 平凡的M1卡,会支持好几种数据传输方式,一般会有NfcA,MifareClassic等。如果支持MifareClassic,那就优先利用这种数据传输方式,获取数据。
2、 CPU卡的数据传输,会支持NfcA,IsoDep等,优先利用IsoDep
3、现在市面上出现了云解身份证,原理是通过NFC读取身份证的加密信息,通过API调用公安接口返回用户的证件信息。身份证通过NFC读取,只能通过NFCB的模式读取数据。
安卓开启NFC功能,肯定先开启权限
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:tools="http://schemas.android.com/tools">
  4.     <application
  5.         android:allowBackup="true"
  6.         android:dataExtractionRules="@xml/data_extraction_rules"
  7.         android:fullBackupContent="@xml/backup_rules"
  8.         android:icon="@mipmap/ic_launcher"
  9.         android:label="@string/app_name"
  10.         android:roundIcon="@mipmap/ic_launcher_round"
  11.         android:supportsRtl="true"
  12.         android:theme="@style/Theme.Smkpda"
  13.         tools:targetApi="31">
  14.         <activity
  15.             android:name=".MainActivity"
  16.             android:exported="true">
  17.             <intent-filter>
  18.                 <action android:name="android.intent.action.MAIN" />
  19.                 <category android:name="android.intent.category.LAUNCHER" />
  20.             </intent-filter>
  21.         </activity>
  22.     </application>
  23.     <!--NFC权限-->
  24.     <uses-permission android:name="android.permission.NFC" />
  25.     <!-- 要求当前设备必须要有NFC芯片 -->
  26.     <uses-feature android:name="android.hardware.nfc" android:required="true" />
  27. </manifest>
复制代码
4、定义一个单例的NfcUtils 工具类

  1. package com.sss.ssspda.common.nfc;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.nfc.NfcAdapter;
  5. import android.nfc.Tag;
  6. import android.util.Log;
  7. import android.widget.Toast;
  8. /**
  9. * Created by Administrator on 2023/11/10.
  10. */
  11. public class NfcUtils {
  12.      private static final String TAG = "NfcUtils";
  13.      private static NfcAdapter mNfcAdapter;
  14.    
  15.    //NFC功能,只能定义为单例模式,避免出现系统奔溃
  16.     private NfcUtils(){}
  17.     private static NfcUtils nfcUtils = null;
  18.     private static boolean isOpen = false;
  19.         /**
  20.          * 获取NFC的单例
  21.          * @return NfcUtils
  22.          */
  23.     public static NfcUtils getInstance(){
  24.         if (nfcUtils == null){
  25.             synchronized (NfcUtils.class){
  26.                 if (nfcUtils == null){
  27.                     nfcUtils = new NfcUtils();
  28.                 }
  29.             }
  30.         }
  31.         return nfcUtils;
  32.     }
  33.     /**
  34.      * 在onStart中检测是否支持nfc功能
  35.      * @param context 当前页面上下文
  36.      */
  37.     public void onStartNfcAdapter(Context context){
  38.         mNfcAdapter = NfcAdapter.getDefaultAdapter(context);//设备的NfcAdapter对象
  39.         if(mNfcAdapter==null){//判断设备是否支持NFC功能
  40.             Toast.makeText(context,"设备不支持NFC功能!",Toast.LENGTH_SHORT).show();
  41.             return;
  42.         }
  43.         if (!mNfcAdapter.isEnabled()){//判断设备NFC功能是否打开
  44.             Toast.makeText(context,"请到系统设置中打开NFC功能!",Toast.LENGTH_SHORT).show();
  45.             return;
  46.         }
  47.         Log.d(TAG,"NFC is start");
  48.     }
  49.     /**
  50.      * 在onResume中开启nfc功能
  51.      * @param activity
  52.      */
  53.     public void onResumeNfcAdapter(final Activity activity){
  54.         if (mNfcAdapter == null  || !mNfcAdapter.isEnabled()) {
  55.             throw new RuntimeException("NFC is not  start");
  56.         }
  57. //            mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,null,null);//打开前台发布系统,使页面优于其它nfc处理.当检测到一个Tag标签就会执行mPendingItent
  58.         if (!isOpen) {
  59.             mNfcAdapter.enableReaderMode(activity, new NfcAdapter.ReaderCallback() {
  60.                         @Override
  61.                         public void onTagDiscovered(final Tag tag) {
  62.                             //ByteArrayToHexString(tag.getId())即cardId
  63.                             if (nfcListener != null)
  64.                                 (activity).runOnUiThread(new Runnable() {
  65.                                     @Override
  66.                                     public void run() {
  67.                                         nfcListener.doing(tag);
  68.                                     }
  69.                                 });
  70.                         }
  71.                     },
  72.                     (NfcAdapter.FLAG_READER_NFC_A |
  73.                             NfcAdapter.FLAG_READER_NFC_B |
  74.                             NfcAdapter.FLAG_READER_NFC_F |
  75.                             NfcAdapter.FLAG_READER_NFC_V |
  76.                             NfcAdapter.FLAG_READER_NFC_BARCODE ),
  77.                     null);
  78.             isOpen = true;
  79.             Log.d(TAG, "Resume");
  80.         }
  81.     }
  82.     /**
  83.      * 在onPause中关闭nfc功能
  84.      * @param activity
  85.      */
  86.     public void onPauseNfcAdapter(Activity activity){
  87.         if(mNfcAdapter!=null && mNfcAdapter.isEnabled()){
  88.             if (isOpen){
  89.                 mNfcAdapter.disableReaderMode(activity);
  90.             }
  91.             isOpen = false;
  92.         }
  93.         Log.d("myNFC","onPause");
  94.     }
  95.     private  NfcListener nfcListener;
  96.     public void setNfcListener(NfcListener listener){
  97.         nfcListener = listener;
  98.     }
  99.     public String ByteArrayToHexString(byte[] inarray) {
  100.         int i, j, in;
  101.         String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
  102.                 "B", "C", "D", "E", "F"};
  103.         String out = "";
  104.         for (j = 0; j < inarray.length; ++j) {
  105.             in = (int) inarray[j] & 0xff;
  106.             i = (in >> 4) & 0x0f;
  107.             out += hex[i];
  108.             i = in & 0x0f;
  109.             out += hex[i];
  110.         }
  111.         return out;
  112.     }
  113. }
复制代码
定义一个处理读卡变乱的接口类

NfcListener
  1. package com.sss.ssspda.common.nfc;
  2. import android.nfc.Tag;
  3. /**
  4.      * 自定义的NFC接口
  5.      */
  6.     public interface NfcListener{
  7.         /**
  8.          * 用于扫到nfc后的后续操作
  9.          */
  10.         void doing(Tag tag);
  11.     }
复制代码
实现获取卡片数据交互

  1. package com.sss.ssspda.common.nfc;
  2. import android.nfc.Tag;
  3. import android.nfc.tech.IsoDep;
  4. import android.nfc.tech.MifareClassic;
  5. import android.nfc.tech.NfcB;
  6. import android.widget.Toast;
  7. import java.io.IOException;
  8. public class NfcReadHander {
  9.     /**
  10.      * 读M1卡的方法扇区,MifareClassic 类型
  11.      * @return
  12.      *
  13.      */
  14.     public static String readMifareTag(Tag tag, MifareClassic mfc){
  15.         //读取TAG
  16.         try {
  17.             String metaInfo = "";
  18.             //操作之前,一点要先连接卡片通讯
  19.             mfc.connect();
  20.             int type = mfc.getType();//获取TAG的类型
  21.             int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数
  22.             String typeS = "";
  23.             switch (type) {
  24.                 case MifareClassic.TYPE_CLASSIC:
  25.                     typeS = "TYPE_CLASSIC";
  26.                     break;
  27.                 case MifareClassic.TYPE_PLUS:
  28.                     typeS = "TYPE_PLUS";
  29.                     break;
  30.                 case MifareClassic.TYPE_PRO:
  31.                     typeS = "TYPE_PRO";
  32.                     break;
  33.                 case MifareClassic.TYPE_UNKNOWN:
  34.                     typeS = "TYPE_UNKNOWN";
  35.                     break;
  36.             }
  37.             metaInfo += "卡片类型:" + typeS + "\n共" + sectorCount + "个扇区\n共"         + mfc.getBlockCount() + "个块\n存储空间: " + mfc.getSize() + "B\n";
  38.             System.out.println(metaInfo);
  39.             int blockIndex;
  40.             for(int i =0;i<16;i++){
  41.                 try{
  42.                     System.out.println(i);
  43.                     metaInfo += "扇区:"+i+",key:";
  44.                     boolean f = false;
  45.       byte[] keybyte = new byte[]{0x01, (byte) 0x11, (byte) 0x18,0x3F,0x12, (byte) 0x11};//样例的一个密码,要自己去获取,卡片商获取
  46.       //每个扇区的数据读取都可能不一样,每个扇区有的有数据,有的是加密数据,有的是空数据
  47.       //没加密扇区都会配置一个默认密码,开发包里面默认带有3种默认密码,
  48.       //这里代码只列出来使用A加密的方法,也有B加密方法authenticateSectorWithKeyB,要根据实际卡片加密方法来设置
  49.                     if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_DEFAULT)){
  50.                         metaInfo += "KEY_DEFAULT";
  51.                         f = true;
  52.                     }else if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_NFC_FORUM)){
  53.                         metaInfo += ".KEY_NFC_FORUM";
  54.                         f = true;
  55.                     }else if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_MIFARE_APPLICATION_DIRECTORY)){
  56.                         metaInfo += ".KEY_MIFARE_APPLICATION_DIRECTORY";
  57.                         f = true;
  58.                     }else if(mfc.authenticateSectorWithKeyA(i,keybyte)){
  59.                     //这里的密码 keybyte,要自己和卡片提供商来获取,有可能是一个批次的卡片的密码都一样,也可能每个卡片的密码都不一样,这里的密码需要自己想办法获取。
  60.                         metaInfo += ByteArrayToHexString(keybyte);
  61.                         f = true;
  62.                     }else{
  63.                     }
  64.                     metaInfo += "\n";
  65.                     if(f){
  66.                     //前面通过扇区界面进入了扇区里面,这里就是计算当前扇区的第一个块的块号码
  67.                         int bnum = mfc.sectorToBlock(i);
  68.                         //通过块号码读取块里面的数据
  69.                         metaInfo += ByteArrayToHexString(mfc.readBlock(bnum))+"\n";
  70.                         //每个扇区有4个块,依次读取
  71.                         metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+1))+"\n";
  72.                         metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+2))+"\n";
  73.                         metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+3))+"\n";
  74.                     }
  75.                 }catch (Exception e){
  76.                     e.printStackTrace();
  77.                 }
  78.             }
  79.             return metaInfo;
  80.         } catch (Exception e) {
  81.             e.printStackTrace();
  82.         } finally {
  83.             if (mfc != null) {
  84.                 try {
  85.                     mfc.close();
  86.                 } catch (IOException e) {
  87.                     e.printStackTrace();
  88.                 }
  89.             }
  90.         }
  91.         return null;
  92.     }
  93.     public static String ByteArrayToHexString(byte[] inarray) {
  94.         int i, j, in;
  95.         String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
  96.                 "B", "C", "D", "E", "F"};
  97.         String out = "";
  98.         for (j = 0; j < inarray.length; ++j) {
  99.             in = (int) inarray[j] & 0xff;
  100.             i = (in >> 4) & 0x0f;
  101.             out += hex[i];
  102.             i = in & 0x0f;
  103.             out += hex[i];
  104.         }
  105.         return out;
  106.     }
  107. //找卡片提供商获取的读卡指令
  108.     private static byte[] sheb = new  byte[]{0x00, (byte) 0x14,0x04,0x00,0x0F,0x13,0x18,0x11,0x1E,0x13,0x18,0x2E,(byte) 0x19,(byte) 0xE1,(byte) 0xB1,(byte) 0xE1,(byte) 0x11
  109.             ,(byte) 0x13,(byte) 0x15,(byte) 0xCF };
  110. //找卡片提供商获取的读卡指令
  111.     private static byte[] JIAOTONG = new  byte[]{0x00, (byte) 0x14, (byte) 0x04, (byte) 0x00, (byte) 0x18, (byte) 0x10, (byte) 0x10, (byte) 0x00
  112.             , (byte) 0x06, (byte) 0x12, (byte) 0x11, (byte) 0x11, (byte) 0x15};
  113.    
  114.    //通过ISODEP读取数据
  115.     public static String readIsoDepTag(Tag tag, IsoDep isodep) {
  116.         String msg = "";
  117.         try{
  118.         //先连接
  119.             isodep.connect();
  120.             if(isodep.isConnected()){
  121.                                                 //判断是否链接成功
  122.             }
  123.             msg += "sheb指令读取的数据:\n";
  124.             msg += transcMsg(isodep,sheb)+"\n";
  125.             msg += "JIAOTONG指令读取的数据:\n";
  126.             msg += transcMsg(isodep,JIAOTONG)+"\n";
  127.          
  128.         }catch (Exception e){
  129.         }
  130.         return msg;
  131.     }
  132.     public static String transcMsg(IsoDep isodep,byte[] b) throws  Exception{
  133.         return ByteArrayToHexString(isodep.transceive(b));
  134.     }
  135. //nfcB类型,身份证就是此种传输方式
  136.     public static String readNfcBTag(Tag tag, NfcB nfcb) {
  137.         String msg = "";
  138.         try{
  139.             nfcb.connect();
  140.             msg += ByteArrayToHexString(nfcb.getApplicationData()) +"\n";
  141.             byte[]  sd = new byte[]{0x05,0x00,0x00};
  142.             msg += ByteArrayToHexString(nfcb.transceive(sd)) +"\n";
  143.             msg += ByteArrayToHexString(nfcb.getApplicationData()) +"\n";
  144.         }catch (Exception e){
  145.             e.printStackTrace();
  146.         }
  147.         return msg;
  148.     }
  149. }
复制代码
调用主类

  1. package com.sss.ssspda;
  2. import android.nfc.Tag;
  3. import android.nfc.tech.IsoDep;
  4. import android.nfc.tech.MifareClassic;
  5. import android.nfc.tech.NfcA;
  6. import android.nfc.tech.NfcB;
  7. import android.os.Bundle;
  8. import android.util.Log;
  9. import android.view.View;
  10. import android.widget.TextView;
  11. import android.widget.Toast;
  12. import androidx.activity.EdgeToEdge;
  13. import androidx.appcompat.app.AppCompatActivity;
  14. import androidx.core.graphics.Insets;
  15. import androidx.core.view.ViewCompat;
  16. import androidx.core.view.WindowInsetsCompat;
  17. import com.sss.ssspda.common.nfc.NfcListener;
  18. import com.sss.ssspda.common.nfc.NfcReadHander;
  19. import com.sss.ssspda.common.nfc.NfcUtils;
  20. public class MainActivity extends AppCompatActivity  implements NfcListener {
  21.     private   TextView typetext;
  22.     private   TextView datatext;
  23.     @Override
  24.     protected void onCreate(Bundle savedInstanceState) {
  25.         super.onCreate(savedInstanceState);
  26.         EdgeToEdge.enable(this);
  27.         setContentView(R.layout.activity_main);
  28.         ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
  29.             Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
  30.             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
  31.             return insets;
  32.         });
  33.         typetext = findViewById(R.id.typenfc);
  34.         datatext = findViewById(R.id.data);
  35.         nfcUtils.setNfcListener(this);
  36.         Log.d("myNFC","onPause");
  37.     }
  38.     public void Welcome1(View view) {
  39.         Toast.makeText(this, "按钮点击一下", Toast.LENGTH_SHORT).show();
  40.     }
  41.     public void Welcome2(View view) {
  42.         Toast.makeText(this, "撮了一下", Toast.LENGTH_SHORT).show();
  43.     }
  44.     private NfcUtils nfcUtils = NfcUtils.getInstance();
  45.     private TextView textView;
  46.     @Override
  47.     protected void onStart() {
  48.         super.onStart();
  49.         System.out.println("onStart................................");
  50.         nfcUtils.onStartNfcAdapter(this);       //初始化Nfc对象
  51.     }
  52.     @Override
  53.     protected void onResume() {
  54.         super.onResume();
  55.         System.out.println("onResume................................");
  56.         nfcUtils.onResumeNfcAdapter(this);      //activity激活的时候开始扫描
  57.     }
  58.     @Override
  59.     protected void onPause() {
  60.         super.onPause();
  61.         System.out.println("onPause................................");
  62.         nfcUtils.onPauseNfcAdapter(this);       //activity切换到后台的时候停止扫描
  63.     }
  64.     @Override
  65.     public void doing(Tag tag) {
  66.         String tl[] = tag.getTechList();
  67.         for (String s:tl) {
  68.             System.out.println(s);
  69.         }
  70.         for (String s:tl) {
  71.             System.out.println("type:"+s);
  72.             if(s.equals("android.nfc.tech.MifareClassic")){
  73.                 typetext.setText("MifareClassic");
  74.                 String data = NfcReadHander.readMifareTag(tag,MifareClassic.get(tag));
  75.                 datatext.setText(data);
  76.                 break;
  77.             }else  if(s.equals("android.nfc.tech.IsoDep")){
  78.                 typetext.setText("IsoDep");
  79.                 String data = NfcReadHander.readIsoDepTag(tag, IsoDep.get(tag));
  80.                 datatext.setText(data);
  81.                 break;
  82.             }else  if(s.equals("android.nfc.tech.NfcB")){
  83.                 typetext.setText("NfcB");
  84.                 String data = NfcReadHander.readNfcBTag(tag, NfcB.get(tag));
  85.                 datatext.setText(data);
  86.                 break;
  87.             }
  88.         }System.out.println("onPause................................"+NfcReadHander.ByteArrayToHexString(tag.getId()));
  89.    
  90. }
复制代码
结构文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"
  4.     xmlns:tools="http://schemas.android.com/tools"
  5.     android:id="@+id/main"
  6.     android:layout_width="match_parent"
  7.     android:layout_height="900sp"
  8.     android:onClick="Welcome1"
  9.     tools:context=".MainActivity">
  10.     <LinearLayout
  11.         android:layout_width="match_parent"
  12.         android:layout_height="1054dp"
  13.         android:orientation="vertical"
  14.         app:layout_constraintTop_toTopOf="parent">
  15.         <TextView
  16.             android:id="@+id/textView1"
  17.             android:layout_width="match_parent"
  18.             android:layout_height="wrap_content"
  19.             android:layout_marginLeft="15dp"
  20.             android:layout_marginTop="15dp"
  21.             android:layout_marginRight="15dp"
  22.             android:layout_marginBottom="15dp"
  23.             android:background="#77CCB3"
  24.             android:text="读取类型"
  25.             android:textAlignment="center"
  26.             android:textSize="35sp" />
  27.         <TextView
  28.             android:id="@+id/typenfc"
  29.             android:layout_width="match_parent"
  30.             android:layout_height="77dp"
  31.             android:text="type"
  32.             android:textSize="25sp" />
  33.         <TextView
  34.             android:id="@+id/textView2"
  35.             android:layout_width="match_parent"
  36.             android:layout_height="59dp"
  37.             android:background="#6DCFC6"
  38.             android:text="数据"
  39.             android:textAlignment="center"
  40.             android:textSize="35sp" />
  41.         <ScrollView
  42.             android:layout_width="match_parent"
  43.             android:layout_height="613dp">
  44.             <TextView
  45.                 android:id="@+id/data"
  46.                 android:layout_width="match_parent"
  47.                 android:layout_height="match_parent"
  48.                 android:text="TextView"
  49.                 android:textSize="15sp" />
  50.         </ScrollView>
  51.     </LinearLayout>
  52. </androidx.constraintlayout.widget.ConstraintLayout>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4