译者:陈广 日期:2020-6-26
在上一篇文章中,我们已经安装了 ST25 SDK,今天可以使用这个开发包来实现一些较为深入的功能。首先实现读取 UID 以及内存容量的功能。本文在上篇文章写的程序的基础上继续。
首先更改主窗体,添加几个显示数据的 TextView。打开 activity_main.xml 文件,更改代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/tagNameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="标签名称"
android:textSize="15dp"
android:textStyle="bold"
android:typeface="serif"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_tagName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:textSize="13dp"
android:typeface="serif"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/uidLayout"
android:layout_below="@+id/tagNameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="UID"
android:textSize="15dp"
android:textStyle="bold"
android:typeface="serif"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_uid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:textSize="13dp"
android:typeface="serif"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/tagMemSizeLayout"
android:layout_below="@+id/uidLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="内存尺寸"
android:textSize="15dp"
android:textStyle="bold"
android:typeface="serif"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_tagMemSize"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:textSize="13dp"
android:typeface="serif"
android:layout_weight="1"/>
</LinearLayout>
</RelativeLayout>
接下来更改 MainActivity.java 文件中onTagDiscoveryCompleted
方法的代码:
@Override
public void onTagDiscoveryCompleted(NFCTag nfcTag, TagHelper.ProductID productId, STException e) {
TextView tvTagName = findViewById(R.id.tv_tagName);
TextView tvUID = findViewById(R.id.tv_uid);
TextView tvMemSize = findViewById(R.id.tv_tagMemSize);
if (nfcTag != null) {
tvTagName.setText(nfcTag.getName()); //获取标签名称
try {
tvUID.setText(nfcTag.getUidString()); //获取标签UID
} catch (STException ex) {
tvUID.setText(ex.getMessage());
}
try { //获取标签内存尺寸
tvMemSize.setText(Integer.toString(nfcTag.getMemSizeInBytes()));
} catch (STException ex) {
tvMemSize.setText(ex.getMessage());
}
} else {
tvTagName.setText("");
tvUID.setText("");
tvMemSize.setText("");
Toast.makeText(this, "标签发现失败", Toast.LENGTH_LONG).show();
}
}
运行效果如下图所示:
看来我之前使用单片机读 UID 时显示的字节顺序正好反了。此开发包只能读取 NFC 标签的内存尺寸,在读普通 15693 内存尺寸时会出错。当然,在正式写程序时,我们应当限制标签类型,只让 ST25DV 标签触发事件。
接下来演示如何读写数据。估计 ST25 SDK 的读写操作在后台是使用线程实现的,所以不能在 UI 线程中调用,所以写起来比较麻烦。Android 在多线程中访问 UI 的操作非常麻烦,远没有 C# 这么便捷。这一块我暂时还不想深入研究,索性抄现成的代码再改改的能力还是有的,花点时间还是写完了 Demo。
首先将 activity_main.xml 的代码更改如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/tagNameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="标签名称"
android:textSize="15dp"
android:textStyle="bold"
android:typeface="serif"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_tagName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:textSize="13dp"
android:typeface="serif"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/uidLayout"
android:layout_below="@+id/tagNameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="UID"
android:textSize="15dp"
android:textStyle="bold"
android:typeface="serif"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_uid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:textSize="13dp"
android:typeface="serif"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/tagMemSizeLayout"
android:layout_below="@+id/uidLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="内存尺寸"
android:textSize="15dp"
android:textStyle="bold"
android:typeface="serif"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_tagMemSize"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:textSize="13dp"
android:typeface="serif"
android:layout_weight="1"/>
</LinearLayout>
<Button
android:id="@+id/btn_readBytes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读取前10个字节数据"
android:layout_below="@+id/tagMemSizeLayout"
android:layout_margin="10dp" />
<TextView
android:id="@+id/tv_tagData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/btn_readBytes"
android:text=""
android:textSize="13dp"
android:typeface="serif"/>
<Button
android:id="@+id/btn_writeBytes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="写入10字节数据"
android:layout_below="@+id/tv_tagData"
android:layout_margin="10dp" />
<Button
android:id="@+id/btn_writeNdefMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="写入NDEF数据"
android:layout_below="@+id/btn_writeBytes"
android:layout_margin="10dp" />
</RelativeLayout>
这里,我们添加了三个按钮和一个 TextView,按钮分别用于读取10个字节,写入10个字节,写入 NDEF 消息;TextView 用于显示读出来的 10 个字节的数据。
更改 MainActivity.java 如下:
package com.example.st25;
import androidx.appcompat.app.AppCompatActivity;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.st.st25sdk.NFCTag;
import com.st.st25sdk.STException;
import com.st.st25sdk.TagHelper;
import com.st.st25sdk.ndef.NDEFMsg;
import com.st.st25sdk.ndef.UriRecord;
import java.util.Arrays;
import static com.st.st25sdk.ndef.UriRecord.NdefUriIdCode.NDEF_RTD_URI_ID_HTTP_WWW;
public class MainActivity extends AppCompatActivity implements TagDiscovery.onTagDiscoveryCompletedListener {
static final String TAG = "MainActivity";
private NfcAdapter nfcAdapter;
// Last tag taped
private NFCTag mNfcTag;
TextView tvData;
byte[] tagDatas;
enum Action {
WRITE_NDEF_MESSAGE,
READ_BYTES,
WRITE_BYTES
};
enum ActionStatus {
ACTION_SUCCESSFUL,
ACTION_FAILED,
TAG_NOT_IN_THE_FIELD
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
//写入NDEF消息按钮
Button writeNdefMessageButton = (Button) findViewById(R.id.btn_writeNdefMessage);
writeNdefMessageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mNfcTag != null) {
executeAsynchronousAction(Action.WRITE_NDEF_MESSAGE);
}
}
});
//读取字节按钮
Button readBytesButton = (Button) findViewById(R.id.btn_readBytes);
readBytesButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mNfcTag != null) {
executeAsynchronousAction(Action.READ_BYTES);
}
}
});
//写入字节按钮
Button writeBytesButton = (Button) findViewById(R.id.btn_writeBytes);
writeBytesButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mNfcTag != null) {
executeAsynchronousAction(Action.WRITE_BYTES);
}
}
});
}
@Override //页面失去焦点
protected void onPause() {
super.onPause();
if (nfcAdapter != null) {
//当前应用程序不在前台时,NFC事件将不再发送到当前应用程序。
nfcAdapter.disableForegroundDispatch(this);
}
}
@Override //页面获取焦点
protected void onResume() {
super.onResume();
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;
}
PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
//将 NFC 事件发送至当前应用程序
nfcAdapter.enableForegroundDispatch(this, pi, null, null);
Intent intent = getIntent();
String action = intent.getAction();
Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (nfcTag != null) {
new TagDiscovery(this).execute(nfcTag);
}
}
@Override //当前app正在前端界面运行,这个时候有intent发送过来,那么系统就会调用onNewIntent回调方法
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onTagDiscoveryCompleted(NFCTag nfcTag, TagHelper.ProductID productId, STException e) {
TextView tvTagName = findViewById(R.id.tv_tagName);
TextView tvUID = findViewById(R.id.tv_uid);
TextView tvMemSize = findViewById(R.id.tv_tagMemSize);
tvData = findViewById(R.id.tv_tagData);
if (nfcTag != null) {
mNfcTag = nfcTag;
tvTagName.setText(nfcTag.getName()); //获取标签名称
try {
tvUID.setText(nfcTag.getUidString()); //获取标签UID
} catch (STException ex) {
tvUID.setText(ex.getMessage());
}
//获取标签内存尺寸
try {
tvMemSize.setText(Integer.toString(nfcTag.getMemSizeInBytes()));
} catch (STException ex) {
tvMemSize.setText(ex.getMessage());
}
tvData.setText("");
} else {
tvTagName.setText("");
tvUID.setText("");
tvMemSize.setText("");
tvData.setText("");
Toast.makeText(this, "标签发现失败", Toast.LENGTH_LONG).show();
}
}
private void executeAsynchronousAction(Action action) {
Log.d(TAG, "开始后台操作" + action);
new myAsyncTask(action).execute();
}
//异步操作类
private class myAsyncTask extends AsyncTask<Void, Void, ActionStatus> {
Action mAction;
int memSizeInBytes;
public myAsyncTask(Action action) {
mAction = action;
}
@Override //开始异步操作
protected ActionStatus doInBackground(Void... param) {
ActionStatus result;
try {
switch (mAction) {
case WRITE_NDEF_MESSAGE:
NDEFMsg ndefMsg = new NDEFMsg();// 创建一个 NDEF 消息
UriRecord uriRecord = new UriRecord(NDEF_RTD_URI_ID_HTTP_WWW, "iotxfd.cn");
ndefMsg.addRecord(uriRecord);
mNfcTag.writeNdefMessage(ndefMsg); //写入 NDEF 消息
result = ActionStatus.ACTION_SUCCESSFUL;
break;
case READ_BYTES:
tagDatas = mNfcTag.readBytes(0, 10); //读取从0地址开始的10个字节
result = ActionStatus.ACTION_SUCCESSFUL;
break;
case WRITE_BYTES:
byte[] wdatas = {61, 62, 63, 64, 65, 66, 67, 68, 69, 70};
mNfcTag.writeBytes(0, wdatas); //从0地址开始写入10个字节
result = ActionStatus.ACTION_SUCCESSFUL;
break;
default:
result = ActionStatus.ACTION_FAILED;
break;
}
} catch (STException e) {
switch (e.getError()) {
case TAG_NOT_IN_THE_FIELD:
result = ActionStatus.TAG_NOT_IN_THE_FIELD;
break;
default:
e.printStackTrace();
result = ActionStatus.ACTION_FAILED;
break;
}
}
return result;
}
@Override //异步操作结束后,显示结果,UI的访问应放在这里进行
protected void onPostExecute(ActionStatus actionStatus) {
switch (actionStatus) {
case ACTION_SUCCESSFUL:
switch (mAction) {
case WRITE_NDEF_MESSAGE:
Toast.makeText(MainActivity.this, "成功写入NDEF消息", Toast.LENGTH_LONG).show();
break;
case READ_BYTES:
tvData.setText(Arrays.toString(tagDatas));
break;
case WRITE_BYTES:
Toast.makeText(MainActivity.this, "成功写入10个字节", Toast.LENGTH_LONG).show();
}
break;
case ACTION_FAILED:
Toast.makeText(MainActivity.this, "操作失败", Toast.LENGTH_LONG).show();
break;
case TAG_NOT_IN_THE_FIELD:
Toast.makeText(MainActivity.this, "标签不在感应区域!", Toast.LENGTH_LONG).show();
break;
}
return;
}
}
}
运行效果:
先操作读取和写入字节,这个不会有什么问题。关键是写入 NDEF 数据,将此数据写入淘宝购买的普通 NFC 标签后,退出程序,然后再次贴近 NFC 标签,则会自动打开指定网站。但是如果写入的是 ST25DV64K,虽然可以成功写入,但无法自动打开网站。我估计如果要写入 NDEF 消息,需要先进行 NDEF 格式化,淘宝买来的标签是预先格式化好的,因为出厂时,里面的数据并不是 0。这块之后再深入研究。
;