NFC开发也是我们开发中会遇到的,所以我们也有必要了解一下。
1.权限配置
<!-- NFC -->
<uses-permission android:name="android.permission.NFC" />
<!-- 仅在支持NFC的设备上运行 -->
<uses-feature android:name="android.hardware.nfc" android:required="true" />
2.AndroidManifest.xml相关配置
为对应的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>
其中,nfc_tech_filter.xml文件如下
<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.不同NFC使用场景
NFC数据格式名称 | ISO标准名称 | 实际应用场合 |
NfcA | ISO 14443-3A | 门禁卡 |
NfcB | ISO 14443-3B | 二代身份证 |
NfcF | JIS 6319-4 | 香港八达通 |
NfcV | ISO 15693 | 深圳图书馆读者证 |
IsoDep | ISO 14443-4 | 北京一卡通、深圳通、西安长安通、武汉通、广州羊城通 |
4.初始化NFC
声明一个NFC适配器对象
private NfcAdapter mNfcAdapter;
接着是初始化的方法
// 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");
}
}
然后在onCreate方法中调用initNfc方法
initNfc();
同时我们在布局上定义一个文本框,并初始化它
5.启用NFC感应,禁用NFC感应
@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.接收到感应消息后并对消息解码
通过前面的第5步启用NFC感应后,一旦APP接收到感应消息,就会回调Activity的onNewIntent函数,因此开发者可以重写该函数来处理NFC的消息内容。利用MifareClassic类的相关方法即可获取卡片数据。下面是MifareClassic类的方法说明。
- get:从Tag对象中获取卡片对象的信息。该方法为静态方法。
- connect:链接卡片数据。
- close:释放卡片数据。
- getType:获取卡片的类型。TYPE_CLASSIC表示传统类型,TYPE_PLUS表示增强类型,TYPE_PRO表示专业类型。
- getSectorCount:获取卡片的扇区数量。
- getBlockCount:获取卡片的分块个数。
- getSize:获取卡片的存储空间大小,单位字节。
具体的代码如下
@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;
}
这样我们就可以读取出卡片的相应内容。
完整代码如下:
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;
}
}