The use of NFC in Android

NFC development is also something we will encounter in development, so we also need to understand.

1. Permission configuration

<!-- NFC -->
<uses-permission android:name="android.permission.NFC" />
<!-- 仅在支持NFC的设备上运行 -->
<uses-feature android:name="android.hardware.nfc" android:required="true" />

2. AndroidManifest.xml related configuration

Configure the following for the corresponding Activity

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.nfc.action.TAG_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.nfc.action.TECH_DISCOVERED" />
    </intent-filter>
    <meta-data
        android:name="android.nfc.action.TECH_DISCOVERED"
        android:resource="@xml/nfc_tech_filter" />
</activity>

Among them, the nfc_tech_filter.xml file is as follows

<resources>
    <!-- 可以处理所有Android支持的NFC类型 -->
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.NfcF</tech>
        <tech>android.nfc.tech.NfcV</tech>
        <tech>android.nfc.tech.IsoDep</tech>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NdefFormatable</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>

3. Different NFC usage scenarios

NFC data format name ISO standard name Actual application
NfcA ISO 14443-3A Access Card
NfcB ISO 14443-3B Second-generation ID card
NfcF JIS 6319-4 Octopus
NfcV ISO 15693 Shenzhen Library Reader Card
IsoDep ISO 14443-4 Beijing Pass, Shenzhen Pass, Xi'an Chang'an Pass, Wuhan Pass, Guangzhou Yangcheng Pass

4. Initialize NFC

Declare an NFC adapter object

private NfcAdapter mNfcAdapter; 

Next is the method of initialization

// 1.初始化NFC适配器
private void initNfc() {
    // 获取系统默认的NFC适配器
    mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    if (mNfcAdapter == null) {
        tv_nfc_result.setText("当前手机不支持NFC");
    } else if (!mNfcAdapter.isEnabled()) {
        tv_nfc_result.setText("请先在系统设置中启用NFC功能");
    } else {
        tv_nfc_result.setText("当前手机支持NFC");
    }
}

Then call the initNfc method in the onCreate method

initNfc();

At the same time we define a text box on the layout and initialize it

5. Enable NFC sensing, disable NFC sensing

    @Override
    protected void onResume() {
        super.onResume();
        if (mNfcAdapter==null || !mNfcAdapter.isEnabled()) {
            return;
        }
        // 探测到NFC卡片后,必须以FLAG_ACTIVITY_SINGLE_TOP方式启动Activity,
        // 或者在AndroidManifest.xml中设置launchMode属性为singleTop或者singleTask,
        // 保证无论NFC标签靠近手机多少次,Activity实例都只有一个。
        Intent intent = new Intent(this, MainActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        // 声明一个NFC卡片探测事件的相应动作
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);
        // 读标签之前先确定标签类型。这里以大多数的NfcA为例
        String[][] techLists = new String[][]{new String[]{NfcA.class.getName()}, {IsoDep.class.getName()}};
        try {
            // 定义一个过滤器(检测到NFC卡片)
            IntentFilter[] filters = new IntentFilter[]{new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED, "*/*")};
            // 为本App启用NFC感应
            mNfcAdapter.enableForegroundDispatch(this, pendingIntent, filters, techLists);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mNfcAdapter==null || !mNfcAdapter.isEnabled()) {
            return;
        }
        // 禁用本App的NFC感应
        mNfcAdapter.disableForegroundDispatch(this);
    }

6. After receiving the induction message and decoding the message

After enabling NFC sensing through the previous step 5, once the APP receives the sensing message, it will call back the Activity's onNewIntent function, so the developer can rewrite this function to process the NFC message content. The card data can be obtained by using the related methods of the MifareClassic class. The following is the method description of the MifareClassic class.

  • get: Get the card object information from the Tag object. This method is static.
  • connect: Link card data.
  • close: Release card data.
  • getType: Get the type of card. TYPE_CLASSIC represents the traditional type, TYPE_PLUS represents the enhanced type, and TYPE_PRO represents the professional type.
  • getSectorCount: Get the number of sectors of the card.
  • getBlockCount: Get the block count of the card.
  • getSize: Get the storage space size of the card, in bytes.

The specific code is as follows

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        String action = intent.getAction(); // 获取到本次启动的action
        if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED) // NDEF类型
                || action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) // 其他类型
                || action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) { // 未知类型
            // 从intent中读取NFC卡片内容
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            // 获取NFC卡片的序列号
            byte[] ids = tag.getId();
            String card_info = String.format("卡片的序列号为: %s",
                    ByteArrayToHexString(ids));
            String result = readGuardCard(tag);
            card_info = String.format("%s\n详细信息如下:\n%s", card_info, result);
            tv_nfc_result.setText(card_info);
        }
    }

    // 读取小区门禁卡信息
    public String readGuardCard(Tag tag) {
        MifareClassic classic = MifareClassic.get(tag);
        for (String tech : tag.getTechList()) {
            Log.d(TAG, "当前设备支持" + tech); //显示设备支持技术
        }
        String info;
        try {
            classic.connect(); // 连接卡片数据
            int type = classic.getType(); //获取TAG的类型
            String typeDesc;
            if (type == MifareClassic.TYPE_CLASSIC) {
                typeDesc = "传统类型";
            } else if (type == MifareClassic.TYPE_PLUS) {
                typeDesc = "增强类型";
            } else if (type == MifareClassic.TYPE_PRO) {
                typeDesc = "专业类型";
            } else {
                typeDesc = "未知类型";
            }
            info = String.format("\t卡片类型:%s\n\t扇区数量:%d\n\t分块个数:%d\n\t存储空间:%d字节",
                    typeDesc, classic.getSectorCount(), classic.getBlockCount(), classic.getSize());
        } catch (Exception e) {
            e.printStackTrace();
            info = e.getMessage();
        } finally { // 无论是否发生异常,都要释放资源
            try {
                classic.close(); // 释放卡片数据
            } catch (Exception e) {
                e.printStackTrace();
                info = e.getMessage();
            }
        }
        return info;
    }

    public static String ByteArrayToHexString(byte[] bytesId) {
        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;
    }

So we can read the corresponding content of the card.

The complete code is as follows:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/tv_nfc_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>

MainActivity.java

public class MainActivity extends WaterPermissionActivity {

    private NfcAdapter mNfcAdapter; // 声明一个NFC适配器对象
    private TextView tv_nfc_result;

    @Override
    protected MvcBaseModel getModelImp() {
        return null;
    }

    @Override
    protected int getContentLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initWidget() {
        tv_nfc_result = findViewById(R.id.tv_nfc_result);
        initNfc();
    }

    // 1.初始化NFC适配器
    private void initNfc() {
        // 获取系统默认的NFC适配器
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (mNfcAdapter == null) {
            tv_nfc_result.setText("当前手机不支持NFC");
        } else if (!mNfcAdapter.isEnabled()) {
            tv_nfc_result.setText("请先在系统设置中启用NFC功能");
        } else {
            tv_nfc_result.setText("当前手机支持NFC");
        }
    }

    //2.
    @Override
    protected void onResume() {
        super.onResume();
        if (mNfcAdapter==null || !mNfcAdapter.isEnabled()) {
            return;
        }
        // 探测到NFC卡片后,必须以FLAG_ACTIVITY_SINGLE_TOP方式启动Activity,
        // 或者在AndroidManifest.xml中设置launchMode属性为singleTop或者singleTask,
        // 保证无论NFC标签靠近手机多少次,Activity实例都只有一个。
        Intent intent = new Intent(this, MainActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        // 声明一个NFC卡片探测事件的相应动作
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);
        // 读标签之前先确定标签类型。这里以大多数的NfcA为例
        String[][] techLists = new String[][]{new String[]{NfcA.class.getName()}, {IsoDep.class.getName()}};
        try {
            // 定义一个过滤器(检测到NFC卡片)
            IntentFilter[] filters = new IntentFilter[]{new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED, "*/*")};
            // 为本App启用NFC感应
            mNfcAdapter.enableForegroundDispatch(this, pendingIntent, filters, techLists);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mNfcAdapter==null || !mNfcAdapter.isEnabled()) {
            return;
        }
        // 禁用本App的NFC感应
        mNfcAdapter.disableForegroundDispatch(this);
    }

    //3.
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        String action = intent.getAction(); // 获取到本次启动的action
        if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED) // NDEF类型
                || action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) // 其他类型
                || action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) { // 未知类型
            // 从intent中读取NFC卡片内容
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            // 获取NFC卡片的序列号
            byte[] ids = tag.getId();
            String card_info = String.format("卡片的序列号为: %s",
                    ByteArrayToHexString(ids));
            String result = readGuardCard(tag);
            card_info = String.format("%s\n详细信息如下:\n%s", card_info, result);
            tv_nfc_result.setText(card_info);
        }
    }

    // 读取小区门禁卡信息
    public String readGuardCard(Tag tag) {
        MifareClassic classic = MifareClassic.get(tag);
        for (String tech : tag.getTechList()) {
            Log.d(TAG, "当前设备支持" + tech); //显示设备支持技术
        }
        String info;
        try {
            classic.connect(); // 连接卡片数据
            int type = classic.getType(); //获取TAG的类型
            String typeDesc;
            if (type == MifareClassic.TYPE_CLASSIC) {
                typeDesc = "传统类型";
            } else if (type == MifareClassic.TYPE_PLUS) {
                typeDesc = "增强类型";
            } else if (type == MifareClassic.TYPE_PRO) {
                typeDesc = "专业类型";
            } else {
                typeDesc = "未知类型";
            }
            info = String.format("\t卡片类型:%s\n\t扇区数量:%d\n\t分块个数:%d\n\t存储空间:%d字节",
                    typeDesc, classic.getSectorCount(), classic.getBlockCount(), classic.getSize());
        } catch (Exception e) {
            e.printStackTrace();
            info = e.getMessage();
        } finally { // 无论是否发生异常,都要释放资源
            try {
                classic.close(); // 释放卡片数据
            } catch (Exception e) {
                e.printStackTrace();
                info = e.getMessage();
            }
        }
        return info;
    }

    public static String ByteArrayToHexString(byte[] bytesId) {
        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;
    }
}

 

Guess you like

Origin blog.csdn.net/weixin_38322371/article/details/115345813