Android NFC开发之读取NDEF格式Uri数据
首先在AndroidManifest.xml文件中添加如下配置
<!-- SDK版本至少为14 -->
<uses-sdk android:minSdkVersion="14"/>
<!-- 添加NFC权限 -->
<uses-permission android:name="android.permission.NFC" />
<!-- 要求当前设备必须要有NFC芯片 -->
<uses-feature android:name="android.hardware.nfc" android:required="true" />
创建一个NFC处理的基类
public class BaseNfcActivity extends AppCompatActivity {
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
@Override
protected void onStart() {
super.onStart();
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
// 用于感应到NFC时启动该Activity
// 这里建议将处理NFC的子类的launchMode设置成singleTop模式,这样感应到标签时就会回调onNewIntent,而不会重复打开页面
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);
}
/**
* 获得焦点,按钮可以点击
*/
@Override
public void onResume() {
super.onResume();
// 设置当该页面处于前台时,NFC标签会直接交给该页面处理
if (mNfcAdapter != null) {
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
}
/**
* 暂停Activity,界面获取焦点,按钮可以点击
*/
@Override
public void onPause() {
super.onPause();
// 当页面不可见时,NFC标签不交给当前页面处理
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
}
再创建一个Uri前缀查询解析的类
public class UriPrefixMap {
public static final Map<Byte, String> URI_PREFIX_MAP = new HashMap<Byte, String>();
static {
URI_PREFIX_MAP.put((byte) 0x00, "");
URI_PREFIX_MAP.put((byte) 0x01, "http://www.");
URI_PREFIX_MAP.put((byte) 0x02, "https://www.");
URI_PREFIX_MAP.put((byte) 0x03, "http://");
URI_PREFIX_MAP.put((byte) 0x04, "https://");
URI_PREFIX_MAP.put((byte) 0x05, "tel:");
URI_PREFIX_MAP.put((byte) 0x06, "mailto:");
URI_PREFIX_MAP.put((byte) 0x07, "ftp://anonymous:anonymous@");
URI_PREFIX_MAP.put((byte) 0x08, "ftp://ftp.");
URI_PREFIX_MAP.put((byte) 0x09, "ftps://");
URI_PREFIX_MAP.put((byte) 0x0A, "sftp://");
URI_PREFIX_MAP.put((byte) 0x0B, "smb://");
URI_PREFIX_MAP.put((byte) 0x0C, "nfs://");
URI_PREFIX_MAP.put((byte) 0x0D, "ftp://");
URI_PREFIX_MAP.put((byte) 0x0E, "dav://");
URI_PREFIX_MAP.put((byte) 0x0F, "news:");
URI_PREFIX_MAP.put((byte) 0x10, "telnet://");
URI_PREFIX_MAP.put((byte) 0x11, "imap:");
URI_PREFIX_MAP.put((byte) 0x12, "rtsp://");
URI_PREFIX_MAP.put((byte) 0x13, "urn:");
URI_PREFIX_MAP.put((byte) 0x14, "pop:");
URI_PREFIX_MAP.put((byte) 0x15, "sip:");
URI_PREFIX_MAP.put((byte) 0x16, "sips:");
URI_PREFIX_MAP.put((byte) 0x17, "tftp:");
URI_PREFIX_MAP.put((byte) 0x18, "btspp://");
URI_PREFIX_MAP.put((byte) 0x19, "btl2cap://");
URI_PREFIX_MAP.put((byte) 0x1A, "btgoep://");
URI_PREFIX_MAP.put((byte) 0x1B, "tcpobex://");
URI_PREFIX_MAP.put((byte) 0x1C, "irdaobex://");
URI_PREFIX_MAP.put((byte) 0x1D, "file://");
URI_PREFIX_MAP.put((byte) 0x1E, "urn:epc:id:");
URI_PREFIX_MAP.put((byte) 0x1F, "urn:epc:tag:");
URI_PREFIX_MAP.put((byte) 0x20, "urn:epc:pat:");
URI_PREFIX_MAP.put((byte) 0x21, "urn:epc:raw:");
URI_PREFIX_MAP.put((byte) 0x22, "urn:epc:");
URI_PREFIX_MAP.put((byte) 0x23, "urn:nfc:");
}
}
然后创建一个NFC读标的类ReadTagUriActivity,继承BaseNfcActivity
public class ReadTagUriActivity extends BaseNfcActivity {
private TextView mContent;
private String content;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_read_tag_uri);
mContent = findViewById(R.id.content);
}
// 感应到NFC标签会回调该方法
@Override
public void onNewIntent(Intent intent) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
content = "type:" + ndef.getType() + "\nmax size:" + ndef.getMaxSize() + "bytes";
readNfcTag(intent);
mContent.setText(content);
}
private void readNfcTag(Intent intent) {
if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
Toast.makeText(this, "非NDEF格式标签", Toast.LENGTH_SHORT).show();
return;
}
Parcelable[] extra = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (extra == null) {
return;
}
if (extra.length == 0) {
return;
}
NdefMessage message = (NdefMessage) extra[0];
int contentSize = message.toByteArray().length;
NdefRecord record = message.getRecords()[0];
Uri uri = parseUri(record);
content += "\ncontent:" + uri.toString();
content += "\ncontentSize:" + contentSize + " bytes";
}
public static Uri parseUri(NdefRecord record) {
short tnf = record.getTnf();
if (tnf == NdefRecord.TNF_WELL_KNOWN) {
return parseWellKnownUri(record);
} else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) {
return parseAbsoluteUri(record);
}
return null;
}
/**
* 未知类型的uri,直接把byte[]转成字符串
* @param record
* @return
*/
private static Uri parseAbsoluteUri(NdefRecord record) {
byte[] payload = record.getPayload();
Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8")));
return uri;
}
/**
* 解析已知类型的uri
* @param record
* @return
*/
private static Uri parseWellKnownUri(NdefRecord record) {
if (!Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
return null;
}
byte[] payload = record.getPayload();
// payload第一个字节为Uri识别码,根据Uri识别码查询出对应的Uri前缀
String prefix = UriPrefixMap.URI_PREFIX_MAP.get(payload[0]);
// 将Uri前缀转成byte数组
byte[] prefixBytes = prefix.getBytes(Charset.forName("UTF-8"));
// 创建一个新的byte数组,重新组合Uri前缀和Uri内容
byte[] fullUri = new byte[prefixBytes.length + payload.length - 1];
// 先组装Uri前缀
System.arraycopy(prefixBytes, 0, fullUri, 0, prefixBytes.length);
// 再组装Uri内容
System.arraycopy(payload, 1, fullUri, prefixBytes.length, payload.length - 1);
Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8")));
return uri;
}
}
布局文件很简单,就一个TextView显示读取结果
<?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">
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
最后记得把ReadTagUriActivity的启动模式设置成singleTop
<activity
android:name=".ReadTagUriActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
测试
- 首先准备一台支持NFC功能的手机,打开我们写好的应用
- 准备一张写好Uri数据的NFC标签,靠近手机NFC读取区域(一般在背部)
- 读取成功后会在应用中显示读取结果