射频识别(RFID)技术

将手机化身为读卡器

作者:陈广 日期:2019-11-11


今天光棍节,昨晚12点后京东买书6本,200块钱才优惠20元,而且极有可能在节前涨价,搞不好还多花了钱。以后再也不等到双11再买东西了。话说这段时间一直在弄 Android 的读卡程序,踩坑无数,痛苦无比。M1 卡中的内容早就可以读出来了,心想第一个 Android 程序怎么说都不能做得太难看,遂研究如何让程序变得好看一些。听说 RecyclerView 是高大上的东西,于是研究使用它存放标签数据。说实话 RecyclerView 太难用了,搞了我好几天,最终做出来了,才发现 RecyclerView 的每个 item 都会在滑动时刷新数据,而我只需要一次性将标签中的数据读取并显示。这东西太高端,不合我胃口啊!于是只能推倒重来,幸好没花多少时间,完成任务。

本文教大家如何制作一个使用手机读取 Mifare S50 标签数据的程序。由于使用到 NFC 功能,使用模拟器肯定是不行的了,必须要有一部带 NFC 功能的手机,前段时间我的小米 MIX2 手机重重地摔了一下,屏幕摔坏,居然还能正常使用,质量相当好啊!之后换了手机,这部手机正好可以用来写程序。现在的手机大部分都带 NFC 功能了,用来写程序没有问题。首先要在手机上开 USB 调试功能,各手机的开法不一样,大家上网查吧。

新建并配置项目

我使用的是最新版的 Android Studio 3.5.2 版本进行开发。首先新建一个项目,命名为 MifareClassicReader,由于我的小米手机使用的是 Android 9.0 版本,所以我也使用9.0版本的 API,各位根据自己手机使用的 API 版本来决定使用哪个 API。

配置AndroidManifest.xml文件

首先打开 AndroidManifest.xml 文件,加入如下两个标签:

<uses-permission android:name="android.permission.NFC" />
<uses-feature
    android:name="android.hardware.nfc"
    android:required="true" />

第一个标签<uses-permission>允许增加使用 NFC 功能的权限,第二个标签<uses-feature>表明手机必须具备 NFC 功能,否则无法运行程序。

接下来添加过滤器:

<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" />

<intent-filter>中的android.nfc.action.TECH_DISCOVERED表示识别 TECH 格式的电子标签,当 NFC 读到这种格式的标签时,会自动调用我们的程序。完整的 AndroidManifest.xml 文件代码如下,其中有一个名为.CardInfoActivity的 Activify 之后创建它时才会出现在这里:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.skypan.mifareclassicreader">

    <uses-permission android:name="android.permission.NFC" />
    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".CardInfoActivity" android:screenOrientation="portrait"></activity>
        <activity android:name=".MainActivity" android:screenOrientation="portrait">
            <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.TECH_DISCOVERED" />
            </intent-filter>

            <meta-data
                android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/nfc_tech_filter" />
        </activity>
    </application>

</manifest>

添加资源文件

上述代码中指定了一个资源文件 nfc_tech_filter,用于过滤的标签类型。在 res 文件夹下新建一个名为 xml 的文件夹,并在此新建文件夹下新建一个名为 nfc_tech_filter.xml 的文件,输入代码如下:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>
</resources>

这句代码表明我们只接收 MifareClassic 标签,即 M1 卡。

接下来添加一些资源文件在程序中使用,首先在 res 文件夹下新建一个 drawable-xxhdpi 文件夹,将下面这张图片保存到此文件夹中。

接下来添加一个背景资源文件,在 res 下的 drawable 文件夹下新建一个 bg_shadow.xml 文件,添加如下代码:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 第一层阴影:左偏移2dp,上偏移4dp -->
    <item
        android:left="1dp"
        android:top="2dp">
        <shape>
            <solid android:color="@android:color/darker_gray" />
        </shape>
    </item>
    <!-- 第二层前景::底偏移4dp,右偏移2dp -->
    <item
        android:bottom="2dp"
        android:right="1dp">
        <shape>
            <solid android:color="@color/backColor" />
        </shape>
    </item>
</layer-list>

更改在 res 的 values 文件夹下的 colors.xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
    <color name="gridLineColor">#BBBBBB</color>
    <color name="backColor">#AEB8AE</color>
    <color name="textViewColor">#FFFFFF</color>
    <color name="textColor">#555555</color>
</resources>

在 values 文件夹下新建一个名为 dimens.xml 的文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="dividerHeight">1dp</dimen>
    <dimen name="textSizeContent">20sp</dimen>
    <dimen name="textSizeSector">20sp</dimen>
    <dimen name="textSizeBlock">20sp</dimen>
    <dimen name="blockAreaWidth">30dp</dimen>
    <dimen name="sectorAreaWidth">50dp</dimen>
</resources>

为去除窗体标题栏,更改 values 文件夹下的 styles.xml 文件如下:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="windowNoTitle">true</item>
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

主窗体

新建一个名为 CardInfoActivity 的空白 Activity。此 Activity 用于显示 M1 标签内存中的数据,界面文件代码如下:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".CardInfoActivity">

        <TextView
            android:id="@+id/tv_main"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text="发现标签:UID:"
            android:textSize="16sp" />

        <LinearLayout
            android:id="@+id/llayout_info"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"></LinearLayout>

    </LinearLayout>
</ScrollView>

接下来更改主界面 activity_main.xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="@color/colorPrimary"
        android:gravity="center_vertical"
        android:paddingLeft="10dp"
        android:text="Mifare S50 标签读取程序"
        android:textSize="18sp" />

    <ImageView
        android:id="@+id/img_rfid"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:src="@drawable/nfc" />

    <TextView
        android:id="@+id/tv_tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="20dp"
        android:text="请将 Mifare S50 电子标签置于手机背部摄像头附近"
        android:textSize="22sp" />

    <TextView
        android:id="@+id/tv_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="20dp"
        android:text=""
        android:textSize="20sp" />

</LinearLayout>

然后更改主窗体代码文件:

package com.skypan.mifareclassicreader;

import androidx.appcompat.app.AppCompatActivity;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    NfcAdapter nfcAdapter;
    private PendingIntent pi;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        pi = PendingIntent.getActivity(this, 0,
                new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        if (nfcAdapter == null) {
            Toast.makeText(this, "对不起,您的设备不支持NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
        if (!nfcAdapter.isEnabled()) {
            Toast.makeText(this, "请在系统设置中开启NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 当前app正在前端界面运行,这个时候有intent发送过来,那么系统就会调用onNewIntent回调方法,将intent传送过来
        // 我们只需要在这里检验这个intent是否是NFC相关的intent,如果是,就调用处理方法
        if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
            Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            //打开CardInfoActivity窗体,并将tag一起传递过去
            Intent cardInfoIntent = new Intent(MainActivity.this, CardInfoActivity.class);
            cardInfoIntent.putExtra("tag",tagFromIntent);
            startActivity(cardInfoIntent);
        }
    }

    //页面获取焦点
    @Override
    protected void onResume() {
        super.onResume();
        nfcAdapter.enableForegroundDispatch(this, pi, null, null);
    }

    //页面失去焦点
    @Override
    protected void onPause() {
        super.onPause();
        if (nfcAdapter != null) {
            nfcAdapter.disableForegroundDispatch(this);//关闭前台发布系统
        }
    }
}

标签信息窗体

接下来,创建单个扇区数据布局文件,在 Layout 文件夹下新建一个 sector_info_item.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="291dp"
    android:layout_marginTop="5dp"
    android:background="@drawable/bg_shadow"
    android:orientation="horizontal"
    android:paddingBottom="2dp">

    <TextView
        android:id="@+id/tv_sector_num"
        android:layout_width="@dimen/sectorAreaWidth"
        android:layout_height="match_parent"
        android:layout_marginRight="1dp"
        android:background="@color/textViewColor"
        android:gravity="center"
        android:text="第\n00\n扇\n区"
        android:textColor="@color/textColor"
        android:textSize="@dimen/textSizeSector" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/gridLineColor"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="1dp"
            android:layout_weight="1"
            android:background="@color/gridLineColor"
            android:orientation="horizontal">

            <TextView
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:layout_marginRight="1dp"
                android:background="@color/textViewColor"
                android:gravity="center"
                android:text="0"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginRight="1dp"
                android:layout_weight="1"
                android:background="@color/textViewColor"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/tv_data_00"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />

                <TextView
                    android:id="@+id/tv_data_01"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />
            </LinearLayout>

            <TextView
                android:id="@+id/tv_block_0"
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:background="@color/textViewColor"
                android:gravity="center"
                android:text="0"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="1dp"
            android:layout_weight="1"
            android:background="@color/gridLineColor"
            android:orientation="horizontal">

            <TextView
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:layout_marginRight="1dp"
                android:background="@color/textViewColor"
                android:gravity="center"
                android:text="1"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginRight="1dp"
                android:layout_weight="1"
                android:background="@color/textViewColor"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/tv_data_10"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />

                <TextView
                    android:id="@+id/tv_data_11"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />
            </LinearLayout>

            <TextView
                android:id="@+id/tv_block_1"
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:background="#FFF"
                android:gravity="center"
                android:text="0"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="1dp"
            android:layout_weight="1"
            android:background="@color/gridLineColor"
            android:orientation="horizontal">

            <TextView
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:layout_marginRight="1dp"
                android:background="@color/textViewColor"
                android:gravity="center"
                android:text="2"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginRight="1dp"
                android:layout_weight="1"
                android:background="@color/textViewColor"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/tv_data_20"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />

                <TextView
                    android:id="@+id/tv_data_21"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />
            </LinearLayout>

            <TextView
                android:id="@+id/tv_block_2"
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:background="#FFF"
                android:gravity="center"
                android:text="0"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@color/gridLineColor"
            android:orientation="horizontal">

            <TextView
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:layout_marginRight="1dp"
                android:background="@color/textViewColor"
                android:gravity="center"
                android:text="3"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginRight="1dp"
                android:layout_weight="1"
                android:background="@color/textViewColor"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/tv_data_30"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />

                <TextView
                    android:id="@+id/tv_data_31"
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:gravity="center"
                    android:text="00 00 00 00 00 00 00 00"
                    android:textColor="@color/textColor"
                    android:textSize="@dimen/textSizeContent" />
            </LinearLayout>

            <TextView
                android:id="@+id/tv_block_3"
                android:layout_width="@dimen/blockAreaWidth"
                android:layout_height="72dp"
                android:background="#FFF"
                android:gravity="center"
                android:text="0"
                android:textColor="@color/textColor"
                android:textSize="@dimen/textSizeBlock" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

以上布局文件显示的图形如下所示:

显示的东西不多,代码很多。原先是想使用代码动态生成这个界面,但 dp 转像素以及写代码太过麻烦,后面发现可以写好布局文件,之后插到代码中,这样也挺方便。就是在获取每个 TextView 的句柄时麻烦一些。另外,S70 标签每个扇区的块数不一定是4个,这时此布局就没用了。不管了,反正 S70 标签我这也没有,就仅限于使用 S50 吧。

最后,终于可以写 CardInfoActivity 的代码了。

package com.skypan.mifareclassicreader;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

public class CardInfoActivity extends AppCompatActivity {

    TextView tv_main;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_card_info);

        Intent intent = getIntent();
        Tag tag = intent.getParcelableExtra("tag"); //获取主窗体传过来的tag
        String CardId = ByteArrayToHexString(tag.getId()); //取出tag的UID

        tv_main = findViewById(R.id.tv_main);
        tv_main.setText("标签 UID:" + CardId);

        processIntent(tag);
    }

    private void processIntent(Tag tag) {
        //找到显示标签数据信息的根布局,以便动态将sector_info_item布局加入
        LinearLayout ll_root = findViewById(R.id.llayout_info);
        LayoutInflater flater = LayoutInflater.from(this);

        MifareClassic mfc = MifareClassic.get(tag);
        try {
            mfc.connect();
            int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数
            for (int i = 0; i < sectorCount; i++) {
                //从sector_info_item.xml文件中取出布局
                View view = flater.inflate(R.layout.sector_info_item, null, false);
                //写入扇区号
                TextView tv_sector = view.findViewById(R.id.tv_sector_num);
                tv_sector.setText("第\n" + i + "\n扇\n区");
                if (mfc.authenticateSectorWithKeyA(i, MifareClassic.KEY_DEFAULT)) { //证实密钥成功
                    //写入扇区0号块数据及总块号
                    int blockNum = i * 4;
                    TextView tv = view.findViewById(R.id.tv_block_0);
                    tv.setText(String.valueOf(blockNum));
                    byte[] data = mfc.readBlock(blockNum);
                    String dataStr = ByteArrayToHexString(data);
                    tv = view.findViewById(R.id.tv_data_00);
                    tv.setText(dataStr.substring(0, 23));
                    tv = view.findViewById(R.id.tv_data_01);
                    tv.setText(dataStr.substring(24, 47));
                    //写入扇区1号块数据及总块号
                    blockNum = i * 4 + 1;
                    tv = view.findViewById(R.id.tv_block_1);
                    tv.setText(String.valueOf(blockNum));
                    data = mfc.readBlock(blockNum);
                    dataStr = ByteArrayToHexString(data);
                    tv = view.findViewById(R.id.tv_data_10);
                    tv.setText(dataStr.substring(0, 23));
                    tv = view.findViewById(R.id.tv_data_11);
                    tv.setText(dataStr.substring(24, 47));
                    //写入扇区2号块数据及总块号
                    blockNum = i * 4 + 2;
                    tv = view.findViewById(R.id.tv_block_2);
                    tv.setText(String.valueOf(blockNum));
                    data = mfc.readBlock(blockNum);
                    dataStr = ByteArrayToHexString(data);
                    tv = view.findViewById(R.id.tv_data_20);
                    tv.setText(dataStr.substring(0, 23));
                    tv = view.findViewById(R.id.tv_data_21);
                    tv.setText(dataStr.substring(24, 47));
                    //写入扇区2号块数据及总块号
                    blockNum = i * 4 + 3;
                    tv = view.findViewById(R.id.tv_block_3);
                    tv.setText(String.valueOf(blockNum));
                    data = mfc.readBlock(blockNum);
                    dataStr = ByteArrayToHexString(data);
                    tv = view.findViewById(R.id.tv_data_30);
                    tv.setText(dataStr.substring(0, 23));
                    tv = view.findViewById(R.id.tv_data_31);
                    tv.setText(dataStr.substring(24, 47));
                } else { //证实密钥失败
                    TextView tv_data30 = view.findViewById(R.id.tv_data_30);
                    tv_data30.setText("证实失败!");
                }
                //将布局文件加入LinearLayout
                ll_root.addView(view);
            }
            mfc.close();
        } catch (Exception e) {
            tv_main.setText(e.getMessage());
        }
    }
    //将字节数组以十六进制形式显示
    private 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;
    }
}

运行程序

将程序编译到手机中,首先会显示如下界面:

然后将一张14443标签放到手机后面的摄像头附近,会自动跳转到新窗体并显示标签内的数据信息,如下图所示:

关掉程序后,只要手机背部靠近14443标签,就会自动弹出程序界面,让标签再次离开并靠近,就可以读出数据。

至此,我的第一个 Android 程序就写完了。挺辛苦,但总算是完成了,庆贺一下。文章写得很简单,没办法,现阶段是把程序给做出来,全部做完了再详细写书。

;

© 2018 - IOT小分队文章发布系统 v0.3