Android NFC开发

  要开发NFC,首先确定设备需要支持NFC功能

申请权限

  在清单文件中声明所需要的权限,该权限并不需要动态申请

1
2
3
4
<!-- NFC权限 -->
<uses-permission android:name="android.permission.NFC"/>
<!-- 声明需要android.hardware.nfc硬件模块的支持 -->
<uses-feature android:name="android.hardware.nfc" android:required="true"/>

配置NFC

  给对应的Activity添加NFC配置,该配置有三种类型:NDEF、TECH、TAG,从左到右过滤优先级依次降低,对应的Intent过滤规则如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<activity
android:name=".activity.TeacherLogin"
android:exported="false"
android:screenOrientation="landscape">
<!-- NDEF -->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
</intent-filter>
<!-- TECH (注意这里的mita标签中引用了一个xml配置文件) -->
<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" />
<!-- TAG -->
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

  注意activity的meta-data中引用了一个xml配置文件,这个配置文件用于指定程序支持的nfc卡的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>

  不同NFC使用场景

NFC数据格式名称ISO标准名称实际应用场合
NfcAISO 14443-3A门禁卡
NfcBISO 14443-3B二代身份证
NfcFJIS 6319-4香港八达通
NfcVISO 15693深圳图书馆读者证
IsoDepISO 14443-4北京一卡通、深圳通、西安长安通、武汉通、广州羊城通

NFC读取数据

初始化NFC适配器

  首先声明一个NFC适配器,和一个延迟意图

1
2
private NfcAdapter nfcadapter; // 适配器
private PendingIntent pendingIntent; // 延迟意图

  获取到nfc适配器实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void initNFC() {
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null){
ToastUtils.show("设备不支持NFC", this);
}
else if (!nfcAdapter.isEnabled()){
ToastUtils.show("请先打开NFC功能", this);
}
else {
ToastUtils.show("NFC功能正常", this);

// 初始化延迟意图
Intent intent = new Intent(this, getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}

检测NFC读取

  在页面的onResume中读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
protected void onResume(){
super.onResume();

if (nfcAdapter == null || !nfcAdapter.isEnabled()){
return;
}

// 确定要过滤的类型
IntentFilter filter1 = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); // NDEF
IntentFilter filter2 = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED); // TECH
IntentFilter filter3 = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); // TAG
try {
filter1.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
e.printStackTrace();
}

// 意图过滤器
IntentFilter[] intentFilters = {filter1, filter2, filter3};

// 确定要读的卡类型
String[][] techList = new String[][]{
new String[]{NfcA.class.getName()},
new String[]{IsoDep.class.getName()}
};

// 如果不过滤任何意图,将intentFilters替换为null即可
// 如果不限制卡的类型,将techList替换为null即可
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, techList);
}

读取NFC数据

  onResume()收到被调用后,将会调用newIntent()读取NFC收到的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
Log.d(TAG, "onNewIntent: action = " + action);

// 判断意图是否符合要求
if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED) // NDEF
|| action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) // TECH
|| action.equals(NfcAdapter.ACTION_TAG_DISCOVERED) // TAG
) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] tagId = tag.getId();
// 把读取到的数据转换为16进制的字符串
String hexString = byteArrayToHexString(tagId);
Log.d(TAG, "onNewIntent: 读取数据 = " + hexString);
}
}

/**
* 字节流转换为16进制字符串
* @param bytes 字节流数据
* @return 转换后的16进制字符串
*/
public static String byteArrayToHexString(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
if (bytes == null || bytes.length <= 0) {
return null;
}
for (byte b : bytes) {
String hv = Integer.toHexString(b & 0xFF);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}

  如此,整个过程完成,hexString就是NFC读取的数据了

禁止读取NFC数据

  由于当前页面接管了NFC数据读取,当应用进入后台后,我希望它不再读取数据,所以在onPause()方法中添加禁止读取

1
2
3
4
5
6
7
8
@Override
public void onPause(){
super.onPause();
if (nfcAdapter == null || !nfcAdapter.isEnabled()){
return;
}
nfcAdapter.disableForegroundDispatch(this);
}

其他扩展

关于uses-feature

  用于指定Android是否需要某个硬件功能或者软件功能的支持

  格式

1
2
3
4
<uses-feature
android:name="string"
android:required=["true" | "false"]
android:glEsVersion="integer" />

  释义

属性释义
android:name以描述符字符串的形式指定应用使用的单个硬件或软件功能,硬件功能和软件功能部分已列出有效的属性值参考下方uses-featurename
android:required表示应用是否需要 android:name 中所指定功能,为true时即是规定当设备不具有该指定功能时,应用无法正常工作,或设计为无法正常工作;为false则意味着如果设备具有该功能,应用会在必要时优先使用该功能,但应用设计为不使用该指定功能也可正常工作
android:glEsVersion应用需要的 OpenGL ES 版本。高 16 位表示主版本号,低 16 位表示次版本号

uses-featurename

name释义
android.hardware.audio.low_latency应用使用设备的低延迟时间音频管道,该管道可以减少处理声音输入或输出时的滞后和延迟。
android.hardware.audio.output应用使用设备的扬声器、音频耳机插孔、蓝牙流式传输能力或类似机制传输声音。
android.hardware.audio.pro应用使用设备的高端音频功能和性能能力。
android.hardware.microphone应用使用设备的麦克风记录音频。
android.hardware.bluetooth应用使用设备的蓝牙功能,通常用于与其他支持蓝牙的设备进行通信。
android.hardware.bluetooth_le应用使用设备的低功耗蓝牙无线电功能。

  更多参考: https://developer.android.google.cn/guide/topics/manifest/uses-feature-element