安卓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功能,一定先开启权限

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Smkpda"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <!--NFC权限-->
    <uses-permission android:name="android.permission.NFC" />
    <!-- 要求当前设备必须要有NFC芯片 -->
    <uses-feature android:name="android.hardware.nfc" android:required="true" />
</manifest>
4、定义一个单例的NfcUtils 工具类
java 复制代码
package com.sss.ssspda.common.nfc;
 
import android.app.Activity;
import android.content.Context;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.util.Log;
import android.widget.Toast;
 
/**
 * Created by Administrator on 2023/11/10.
 */
 
public class NfcUtils {
     private static final String TAG = "NfcUtils";
 
     private static NfcAdapter mNfcAdapter;
   
   //NFC功能,只能定义为单例模式,避免出现系统奔溃
    private NfcUtils(){}
 
    private static NfcUtils nfcUtils = null;
 
    private static boolean isOpen = false;
 
        /**
         * 获取NFC的单例
         * @return NfcUtils
         */
    public static NfcUtils getInstance(){
        if (nfcUtils == null){
            synchronized (NfcUtils.class){
                if (nfcUtils == null){
                    nfcUtils = new NfcUtils();
                }
            }
        }
        return nfcUtils;
    }
 
    /**
     * 在onStart中检测是否支持nfc功能
     * @param context 当前页面上下文
     */
    public void onStartNfcAdapter(Context context){
        mNfcAdapter = NfcAdapter.getDefaultAdapter(context);//设备的NfcAdapter对象
        if(mNfcAdapter==null){//判断设备是否支持NFC功能
            Toast.makeText(context,"设备不支持NFC功能!",Toast.LENGTH_SHORT).show();
            return;
        }
        if (!mNfcAdapter.isEnabled()){//判断设备NFC功能是否打开
            Toast.makeText(context,"请到系统设置中打开NFC功能!",Toast.LENGTH_SHORT).show();
            return;
        }
        Log.d(TAG,"NFC is start");
    }
 
    /**
     * 在onResume中开启nfc功能
     * @param activity
     */
    public void onResumeNfcAdapter(final Activity activity){
        if (mNfcAdapter == null  || !mNfcAdapter.isEnabled()) {
            throw new RuntimeException("NFC is not  start");
        }
//            mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,null,null);//打开前台发布系统,使页面优于其它nfc处理.当检测到一个Tag标签就会执行mPendingItent
        if (!isOpen) {
            mNfcAdapter.enableReaderMode(activity, new NfcAdapter.ReaderCallback() {
                        @Override
                        public void onTagDiscovered(final Tag tag) {
                            //ByteArrayToHexString(tag.getId())即cardId
                            if (nfcListener != null)
                                (activity).runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        nfcListener.doing(tag);
                                    }
                                });
                        }
                    },
                    (NfcAdapter.FLAG_READER_NFC_A |
                            NfcAdapter.FLAG_READER_NFC_B |
                            NfcAdapter.FLAG_READER_NFC_F |
                            NfcAdapter.FLAG_READER_NFC_V |
                            NfcAdapter.FLAG_READER_NFC_BARCODE ),
                    null);
            isOpen = true;
            Log.d(TAG, "Resume");
        }
    }
 
    /**
     * 在onPause中关闭nfc功能
     * @param activity
     */
    public void onPauseNfcAdapter(Activity activity){
        if(mNfcAdapter!=null && mNfcAdapter.isEnabled()){
            if (isOpen){
                mNfcAdapter.disableReaderMode(activity);
            }
            isOpen = false;
        }
        Log.d("myNFC","onPause");
    }
 
    private  NfcListener nfcListener;
 
    public void setNfcListener(NfcListener listener){
        nfcListener = listener;
    }

    public String ByteArrayToHexString(byte[] inarray) {
        int i, j, in;
        String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
                "B", "C", "D", "E", "F"};
        String out = "";

        for (j = 0; j < inarray.length; ++j) {
            in = (int) inarray[j] & 0xff;
            i = (in >> 4) & 0x0f;
            out += hex[i];
            i = in & 0x0f;
            out += hex[i];
        }
        return out;
    }
}
定义一个处理读卡事件的接口类

NfcListener

java 复制代码
package com.sss.ssspda.common.nfc;

import android.nfc.Tag;

/**
     * 自定义的NFC接口
     */
    public interface NfcListener{
        /**
         * 用于扫到nfc后的后续操作
         */
        void doing(Tag tag);
    }
实现获取卡片数据交互
java 复制代码
package com.sss.ssspda.common.nfc;

import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NfcB;
import android.widget.Toast;

import java.io.IOException;

public class NfcReadHander {

    /**
     * 读M1卡的方法扇区,MifareClassic 类型
     * @return
     *
     */
    public static String readMifareTag(Tag tag, MifareClassic mfc){
        //读取TAG
        try {
            String metaInfo = "";
            //操作之前,一点要先连接卡片通讯
            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";
            System.out.println(metaInfo);
            int blockIndex;
            for(int i =0;i<16;i++){
                try{
                    System.out.println(i);
                    metaInfo += "扇区:"+i+",key:";
                    boolean f = false;
      byte[] keybyte = new byte[]{0x01, (byte) 0x11, (byte) 0x18,0x3F,0x12, (byte) 0x11};//样例的一个密码,要自己去获取,卡片商获取
      //每个扇区的数据读取都可能不一样,每个扇区有的有数据,有的是加密数据,有的是空数据
      //没加密扇区都会配置一个默认密码,开发包里面默认带有3种默认密码,
      //这里代码只列出来使用A加密的方法,也有B加密方法authenticateSectorWithKeyB,要根据实际卡片加密方法来设置
                    if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_DEFAULT)){
                        metaInfo += "KEY_DEFAULT";
                        f = true;
                    }else if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_NFC_FORUM)){
                        metaInfo += ".KEY_NFC_FORUM";
                        f = true;
                    }else if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_MIFARE_APPLICATION_DIRECTORY)){
                        metaInfo += ".KEY_MIFARE_APPLICATION_DIRECTORY";
                        f = true;
                    }else if(mfc.authenticateSectorWithKeyA(i,keybyte)){
                    //这里的密码 keybyte,要自己和卡片提供商来获取,有可能是一个批次的卡片的密码都一样,也可能每个卡片的密码都不一样,这里的密码需要自己想办法获取。
                        metaInfo += ByteArrayToHexString(keybyte);
                        f = true;
                    }else{

                    }
                    metaInfo += "\n";
                    if(f){
                    //前面通过扇区界面进入了扇区里面,这里就是计算当前扇区的第一个块的块号码
                        int bnum = mfc.sectorToBlock(i);
                        //通过块号码读取块里面的数据
                        metaInfo += ByteArrayToHexString(mfc.readBlock(bnum))+"\n";
                        //每个扇区有4个块,依次读取
                        metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+1))+"\n";
                        metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+2))+"\n";
                        metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+3))+"\n";
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }

            return metaInfo;
        } catch (Exception e) {

            e.printStackTrace();
        } finally {
            if (mfc != null) {
                try {
                    mfc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static String ByteArrayToHexString(byte[] inarray) {
        int i, j, in;
        String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
                "B", "C", "D", "E", "F"};
        String out = "";

        for (j = 0; j < inarray.length; ++j) {
            in = (int) inarray[j] & 0xff;
            i = (in >> 4) & 0x0f;
            out += hex[i];
            i = in & 0x0f;
            out += hex[i];
        }
        return out;
    }

//找卡片提供商获取的读卡指令
    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
            ,(byte) 0x13,(byte) 0x15,(byte) 0xCF };

//找卡片提供商获取的读卡指令
    private static byte[] JIAOTONG = new  byte[]{0x00, (byte) 0x14, (byte) 0x04, (byte) 0x00, (byte) 0x18, (byte) 0x10, (byte) 0x10, (byte) 0x00
            , (byte) 0x06, (byte) 0x12, (byte) 0x11, (byte) 0x11, (byte) 0x15};

   
   //通过ISODEP读取数据 
    public static String readIsoDepTag(Tag tag, IsoDep isodep) {
        String msg = "";
        try{
        //先连接
            isodep.connect();
            if(isodep.isConnected()){
						//判断是否链接成功
            }
            msg += "sheb指令读取的数据:\n";
            msg += transcMsg(isodep,sheb)+"\n";
            msg += "JIAOTONG指令读取的数据:\n";
            msg += transcMsg(isodep,JIAOTONG)+"\n";
          
        }catch (Exception e){

        }
        return msg;
    }

    public static String transcMsg(IsoDep isodep,byte[] b) throws  Exception{
        return ByteArrayToHexString(isodep.transceive(b));
    }


//nfcB类型,身份证就是此种传输方式
    public static String readNfcBTag(Tag tag, NfcB nfcb) {
        String msg = "";
        try{
            nfcb.connect();
            msg += ByteArrayToHexString(nfcb.getApplicationData()) +"\n";
            byte[]  sd = new byte[]{0x05,0x00,0x00};
            msg += ByteArrayToHexString(nfcb.transceive(sd)) +"\n";
            msg += ByteArrayToHexString(nfcb.getApplicationData()) +"\n";
        }catch (Exception e){
            e.printStackTrace();
        }

        return msg;
    }
}
调用主类
java 复制代码
package com.sss.ssspda;

import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NfcA;
import android.nfc.tech.NfcB;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.sss.ssspda.common.nfc.NfcListener;
import com.sss.ssspda.common.nfc.NfcReadHander;
import com.sss.ssspda.common.nfc.NfcUtils;

public class MainActivity extends AppCompatActivity  implements NfcListener {

    private   TextView typetext;

    private   TextView datatext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        typetext = findViewById(R.id.typenfc);
        datatext = findViewById(R.id.data);
        nfcUtils.setNfcListener(this);
        Log.d("myNFC","onPause");
    }

    public void Welcome1(View view) {
        Toast.makeText(this, "按钮点击一下", Toast.LENGTH_SHORT).show();
    }
    public void Welcome2(View view) {

        Toast.makeText(this, "撮了一下", Toast.LENGTH_SHORT).show();
    }

    private NfcUtils nfcUtils = NfcUtils.getInstance();
    private TextView textView;


    @Override
    protected void onStart() {
        super.onStart();
        System.out.println("onStart................................");
        nfcUtils.onStartNfcAdapter(this);       //初始化Nfc对象
    }

    @Override
    protected void onResume() {
        super.onResume();
        System.out.println("onResume................................");
        nfcUtils.onResumeNfcAdapter(this);      //activity激活的时候开始扫描
    }

    @Override
    protected void onPause() {
        super.onPause();
        System.out.println("onPause................................");
        nfcUtils.onPauseNfcAdapter(this);       //activity切换到后台的时候停止扫描
    }

    @Override
    public void doing(Tag tag) {
        String tl[] = tag.getTechList();
        for (String s:tl) {
            System.out.println(s);
        }
        for (String s:tl) {
            System.out.println("type:"+s);
            if(s.equals("android.nfc.tech.MifareClassic")){
                typetext.setText("MifareClassic");
                String data = NfcReadHander.readMifareTag(tag,MifareClassic.get(tag));
                datatext.setText(data);
                break;
            }else  if(s.equals("android.nfc.tech.IsoDep")){
                typetext.setText("IsoDep");
                String data = NfcReadHander.readIsoDepTag(tag, IsoDep.get(tag));
                datatext.setText(data);
                break;
            }else  if(s.equals("android.nfc.tech.NfcB")){
                typetext.setText("NfcB");
                String data = NfcReadHander.readNfcBTag(tag, NfcB.get(tag));
                datatext.setText(data);
                break;
            }
        }System.out.println("onPause................................"+NfcReadHander.ByteArrayToHexString(tag.getId()));
    
}
布局文件
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="900sp"
    android:onClick="Welcome1"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1054dp"
        android:orientation="vertical"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="15dp"
            android:layout_marginRight="15dp"
            android:layout_marginBottom="15dp"
            android:background="#77CCB3"
            android:text="读取类型"
            android:textAlignment="center"
            android:textSize="35sp" />

        <TextView
            android:id="@+id/typenfc"
            android:layout_width="match_parent"
            android:layout_height="77dp"
            android:text="type"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="match_parent"
            android:layout_height="59dp"
            android:background="#6DCFC6"
            android:text="数据"
            android:textAlignment="center"
            android:textSize="35sp" />

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="613dp">

            <TextView
                android:id="@+id/data"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="TextView"
                android:textSize="15sp" />
        </ScrollView>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
相关推荐
皓木.2 分钟前
Mybatis-Plus
java·开发语言
不良人天码星2 分钟前
lombok插件不生效
java·开发语言·intellij-idea
源码哥_博纳软云24 分钟前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
学会沉淀。32 分钟前
Docker学习
java·开发语言·学习
吃着火锅x唱着歌1 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
西猫雷婶1 小时前
python学opencv|读取图像(二十一)使用cv2.circle()绘制圆形进阶
开发语言·python·opencv
kiiila1 小时前
【Qt】对象树(生命周期管理)和字符集(cout打印乱码问题)
开发语言·qt
小_太_阳1 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾2 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
唐 城2 小时前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust