整个篇幅 上面为代码,最后面为介绍,如果想直接看我遇到过的坑,点击目录第二条 。
目录
一、先来个NDEF写入代码:主要在Activity中的生命周期中调用
4⃣️TECH_DISCOVERED resource nfc_tech_filter
上面为 Nfc 的所有类型的读取 以及NDEF类型的写入,没有其他类型的写入,暂时没有加密,后期会考虑新写一篇来介绍。
1、Android 如果想要启动一个应用,并且到达指定界面,需要注意NdefRecord添加的先后顺序,因为他会依次执行,type 必须是小写,否则可能过滤失效,跳往启动页
2、IOS 不能主动识别链接之外的NdefRecord(除非主动去获取),所以NdefRecord[]需要添加一个链接, iOS跳往外链去识别(添加到最后)
3、如果想要给 NdefRecord添加Id 只能自己去New NdefRecord,但是又想达成人家代码的效果,那就复制一下源码,给加个ID
1⃣️、同一个App里的Manifest中Activity设置NFC 过滤,android:pathPrefix最好不要设置一样的
2⃣️、想让其他界面在NFC 启动APP 之后继续响应NFC(监听消息)
5、获取不到Tag,手机响应NFC,但是Activity#onNewIntent并没有响应
1、NFC 打开指定Activity页面(非启动页,因为只用来启动APP,那就不用看这个思路了)
一、先来个NDEF写入代码:主要在Activity中的生命周期中调用
1⃣️Activity 代码
package com.jiao.demo.nfc;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.MifareUltralight;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcA;
import android.nfc.tech.NfcB;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.haier.hailicommontlib.mvp.model.utils.LogUtil;
import com.haier.hailicommontlib.mvp.model.utils.ToastUtil;
import com.jiao.demo.nfc.model.NfcUtil;
import com.jiao.demo.nfc.model.StringUtil;
import java.io.IOException;
/**
* @author: jiaojunfeng
* @date: 2020/11/6
* @describe: NFC demo
*/
public class NFCMainActivity extends Activity implements OnClickListener {
// NFC适配器
private NfcAdapter nfcAdapter = null;
// 传达意图
private PendingIntent pi = null;
// 滤掉组件无法响应和处理的Intent
private IntentFilter tagDetected = null;
private IntentFilter techDetected = null;
private IntentFilter ndefDetected=null;
// 文本控件
private TextView promt = null;
// 是否支持NFC功能的标签
private boolean isNFC_support = false;
// 读、写、删按钮控件
private Button readBtn, writeBtn, deleteBtn;
private Button btRead;
private Button btWrite;
private Button btDelete;
private TextView tvContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_n_f_c_main);
initView();
initNFCData();
}
@Override
protected void onResume() {
super.onResume();
if (!isNFC_support) {
// 如果设备不支持NFC或者NFC功能没开启,就return掉
ToastUtil.showLongToast(this, "NFC功能受限,本设备不支持或未开启");
return;
}
// 开始监听NFC设备是否连接
startNFC_Listener();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(this.getIntent()
.getAction())) {
// 处理该intent
processIntent(this.getIntent());
}
}
@Override
protected void onPause() {
super.onPause();
if (isNFC_support) {
// 当前Activity如果不在手机的最前端,就停止NFC设备连接的监听
stopNFC_Listener();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 当前app正在前端界面运行,这个时候有intent发送过来,那么系统就会调用onNewIntent回调方法,将intent传送过来
// 我们只需要在这里检验这个intent是否是NFC相关的intent,如果是,就调用处理方法
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())||NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
processIntent(intent);
}
}
@Override
public void onClick(View v) {
// 点击读按钮后
if (v.getId() == R.id.bt_read) {
processIntent(getIntent());
// 点击写后写入
} else if (v.getId() == R.id.bt_write) {
try {
String content = NfcUtil.writeNdef(tagFromIntent);
settext(content);
} catch (Exception e) {
settext("错误:" + e.getMessage());
Log.e("myonclick", "写nfc异常", e);
}
} else if (v.getId() == R.id.bt_delete) {
try {
delete(tagFromIntent);
} catch (IOException e) {
settext("错误:" + e.getMessage());
} catch (FormatException e) {
settext("错误:" + e.getMessage());
Log.e("myonclick", "删除nfc异常", e);
}
}
}
private void initNFCData() {
// 初始化设备支持NFC功能
isNFC_support = true;
// 得到默认nfc适配器
nfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
// 提示信息定义
String metaInfo = "";
// 判定设备是否支持NFC或启动NFC
if (nfcAdapter == null) {
metaInfo = "设备不支持NFC!";
Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();
isNFC_support = false;
}
if (!nfcAdapter.isEnabled()) {
metaInfo = "请在系统设置中先启用NFC功能!";
Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();
isNFC_support = false;
}
if (isNFC_support) {
init_NFC();
} else {
promt.setTextColor(Color.RED);
settext(metaInfo);
}
}
private Tag tagFromIntent;
/**
* Parses the NDEF Message from the intent and prints to the TextView
*/
public void processIntent(Intent intent) {
if (!isNFC_support || intent == null) {
ToastUtil.showLongToast(this, "不支持NFC 或数据Intent==NULL");
return;
}
// 取出封装在intent中的TAG
tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tagFromIntent == null) {
Toast.makeText(this, "TAG为NULL", Toast.LENGTH_SHORT).show();
return;
}
promt.setTextColor(Color.BLUE);
String metaInfo = "";
metaInfo += "卡片ID:" + StringUtil.bytesToHexString(tagFromIntent.getId()) + "\n";
Toast.makeText(this, "找到卡片", Toast.LENGTH_SHORT).show();
// Tech List
String[] techList = tagFromIntent.getTechList();
//分析NFC卡的类型: Mifare Classic/UltraLight Info
String CardType = "\n\t" + techList.length + "\n\t";
for (String s : techList) {
LogUtil.I(TAG, "" + s);
if (s.equals(NfcA.class.getName())) {
// 读取TAG
NfcA mfc = NfcA.get(tagFromIntent);
settext(mfc.getTag().toString());
try {
if ("".equals(CardType))
CardType = "MifareClassic卡片类型 \n 不支持NDEF消息 \n";
} catch (Exception e) {
e.printStackTrace();
}
} else if (s.equals(NfcB.class.getName())) {
try {
NfcB nfcB = NfcB.get(tagFromIntent);
nfcB.connect();
} catch (IOException e) {
e.printStackTrace();
}
CardType = "身份证";
} else if (s.equals(MifareUltralight.class.getName())) {
MifareUltralight mifareUlTag = MifareUltralight
.get(tagFromIntent);
String lightType = "";
// Type Info
switch (mifareUlTag.getType()) {
case MifareUltralight.TYPE_ULTRALIGHT:
lightType = "Ultralight";
break;
case MifareUltralight.TYPE_ULTRALIGHT_C:
lightType = "Ultralight C";
break;
}
CardType = lightType + "卡片类型\n";
Ndef ndef = Ndef.get(tagFromIntent);
CardType += "最大数据尺寸:" + ndef.getMaxSize() + "\n";
} else if (s.equals(MifareClassic.class.getName())) {
try {
CardType = NfcUtil.readCard(tagFromIntent, NfcUtil.MIFARECLASSIC_CARD);
} catch (IOException | FormatException e) {
CardType += "MifareClassic 错误" + e.toString();
e.printStackTrace();
}
} else if (s.equals(IsoDep.class.getName())) {
try {
CardType = NfcUtil.readCard(tagFromIntent, NfcUtil.ISO_DEP_CARD);
} catch (IOException | FormatException e) {
CardType += "IsoDep 错误" + e.toString();
e.printStackTrace();
}
} else if (s.equals(Ndef.class.getName())) {
try {
CardType = NfcUtil.readCard(intent, tagFromIntent, NfcUtil.NDEF_CARD);
} catch (IOException | FormatException e) {
CardType += "Ndef 错误" + e.toString();
e.printStackTrace();
}
}
}
metaInfo += CardType;
settext(metaInfo);
}
private String TAG = "NFCMAINac";
// 删除方法
private void delete(Tag tag) throws IOException, FormatException {
if (tag != null) {
//新建一个里面无任何信息的NdefRecord实例
NdefRecord nullNdefRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
new byte[]{}, new byte[]{}, new byte[]{});
NdefRecord[] records = {nullNdefRecord};
NdefMessage message = new NdefMessage(records);
// 解析TAG获取到NDEF实例
Ndef ndef = Ndef.get(tag);
// 打开连接
ndef.connect();
// 写入信息
ndef.writeNdefMessage(message);
// 关闭连接
ndef.close();
settext("删除数据成功");
} else {
settext("设备与nfc卡连接断开,请重新连接...");
}
}
private void startNFC_Listener() {
// 开始监听NFC设备是否连接,如果连接就发pi意图
nfcAdapter.enableForegroundDispatch(this, pi,
new IntentFilter[]{tagDetected,techDetected,ndefDetected}, null);
}
private void stopNFC_Listener() {
// 停止监听NFC设备是否连接
nfcAdapter.disableForegroundDispatch(this);
}
private void init_NFC() {
// 初始化PendingIntent,当有NFC设备连接上的时候,就交给当前Activity处理
pi = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
// 新建IntentFilter,使用的是第二种的过滤机制
tagDetected = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
tagDetected.addCategory("haili.DEFAULT");
}
private void initView() {
btRead = (Button) findViewById(R.id.bt_read);
btWrite = (Button) findViewById(R.id.bt_write);
btDelete = (Button) findViewById(R.id.bt_delete);
tvContent = (TextView) findViewById(R.id.tv_content);
btDelete = (Button) findViewById(R.id.bt_delete_log);
// 控件的绑定
promt = (TextView) findViewById(R.id.tv_content);
readBtn = (Button) findViewById(R.id.bt_read);
writeBtn = (Button) findViewById(R.id.bt_write);
deleteBtn = (Button) findViewById(R.id.bt_delete);
// 给文本控件赋值初始文本
promt.setText("等待RFID标签");
// 监听读、写、删按钮控件
readBtn.setOnClickListener(this);
writeBtn.setOnClickListener(this);
deleteBtn.setOnClickListener(this);
btDelete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
promt.setText("");
}
});
}
public void settext(String message) {
promt.setText(promt.getText() + " \n\t" + message);
}
}
2⃣️工具类代码
package com.jiao.demo.nfc.model;
import android.content.Intent;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcB;
import android.os.Parcelable;
import com.haier.hailicommontlib.mvp.model.utils.LogUtil;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* @author: jjf
* @date: 2020/11/9
* @describe:
*/
public class NfcUtil {
private static String TAG = "NfcUtil";
public static String NULL_DATA = "\n\tNdef is null...\n\t";
public static String DEVICE_CONNENT_ERROR = "\n\t设备与nfc卡连接断开,请重新连接...\n\t";
public static final int NFCA_CARD = 11;
public static final int NFCB_CARD = 12;
public static final int NDEF_CARD = 21;
public static final int MIFARECLASSIC_CARD = 31;
public static final int ISO_DEP_CARD = 41;
public static String readCard(Tag tag, int cartType) throws IOException, FormatException {
switch (cartType) {
case NFCA_CARD:
case NFCB_CARD:
return readNfcB(tag);
case NDEF_CARD:
return readNdefRecordPayload(tag);
case MIFARECLASSIC_CARD:
return readMifareClassic(tag);
case ISO_DEP_CARD:
return IsoDep(tag);
}
return "未发现的芯片类型";
}
/**
* 读取Ndef 信息 根据Id 获取指定NdefRecord 的附加参数
* @param tag
* @param id 指定NdefRecord (消息录入时 设置的Id)
* @return
* @throws IOException
* @throws FormatException
*/
public static String readNdef(Tag tag, String id) throws IOException, FormatException {
//解析Tag获取到NDEF实例
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
return NULL_DATA;
}
//打开连接
if (!ndef.isConnected()) {
ndef.connect();
}
//获取NDEF消息
NdefMessage message = ndef.getNdefMessage();
if (message == null) {
ndef.close();
return "NdefMessage==null0";
}
NdefRecord[] ndefRecord = message.getRecords();
for (NdefRecord ndefRecord1 : ndefRecord) {
byte[] payload = ndefRecord1.getPayload();
if (payload != null) {
String str = new String(payload, StandardCharsets.UTF_8);
System.out.println(" " + str);
}
}
//将消息转换成字节数组
byte[] data = message.toByteArray();
//将字节数组转换成字符串
String str = new String(data, StandardCharsets.UTF_8);
//关闭连接
ndef.close();
System.out.println(" " + str);
return str;
}
// 读取Ndef 参数,所有NdefRecord 附带参数拼接一起返回
public static String readNdefRecordPayload(Tag tag) {
StringBuilder str = new StringBuilder();
Ndef ndef = null;
try {
//解析Tag获取到NDEF实例
if (tag == null) {
return "Tag==null";
}
ndef = Ndef.get(tag);
if (ndef == null) {
return NfcUtil.NULL_DATA;
}
//打开连接
ndef.connect();
//获取NDEF消息
NdefMessage message = ndef.getNdefMessage();
if (message == null) {
ndef.close();
return "NdefMessage==null";
}
NdefRecord[] ndefRecord = message.getRecords();
for (NdefRecord ndefRecord1 : ndefRecord) {
byte[] payload = ndefRecord1.getPayload();
if (payload != null) {
if (str.toString().length() != 0) {
str.append("&");
}
str.append(new String(payload, StandardCharsets.UTF_8));
}
}
//关闭连接
ndef.close();
return str.toString();
} catch (Exception e) {
LogUtil.I("Base", "NfcUtil " + e.toString());
e.printStackTrace();
} finally {
if (ndef != null) {
try {
ndef.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return str.toString();
}
// 读取NfcB
public static String readNfcB(Tag tag) throws IOException, FormatException {
if (tag != null) {
//解析Tag获取到NDEF实例
NfcB ndef = NfcB.get(tag);
if (ndef == null) {
return NULL_DATA;
}
try {
//打开连接
ndef.connect();
} catch (Exception e) {
e.printStackTrace();
}
//获取NfcB消息
byte[] message = ndef.getProtocolInfo();
for (int i = 0; message != null && i < message.length; i++) {
LogUtil.I(TAG, "message[" + i + "]" + message[i]);
}
//将字节数组转换成字符串
String str = new String(message, StandardCharsets.UTF_8);
//关闭连接
ndef.close();
return str;
} else {
return DEVICE_CONNENT_ERROR;
}
}
// Ndef 消息写入方法
public static String writeNdef(Tag tag) throws Exception {
if (tag != null) {
//新建NdefRecord数组,本例中数组只有一个元素
NdefRecord[] records = createNdefRecordByNFCUri("deviceId=123456");
//新建一个NdefMessage实例
NdefMessage message = new NdefMessage(records);
// 解析TAG获取到NDEF实例
Ndef ndef = Ndef.get(tag);
// 打开连接
if (!ndef.isConnected()) {
ndef.connect();
}
// 写入NDEF信息
ndef.writeNdefMessage(message);
// 关闭连接
ndef.close();
return "写入数据成功!";
} else {
return DEVICE_CONNENT_ERROR;
}
}
// Ndef简单文本写入
public static NdefRecord createRecord() {
//组装字符串,准备好你要写入的信息
String msg = "BEGIN:HAHA\n" + "VERSION:1.0\n" + "DEVICE_ID:123456\n"
+ "hl_company\n" + "END:HAHA";
//将字符串转换成字节数组
byte[] textBytes = msg.getBytes();
//将字节数组封装到一个NdefRecord实例中去
NdefRecord textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
"text/*".getBytes(), new byte[]{}, textBytes);
return textRecord;
}
/**
* Ndef 创建消息(录入NFC)
*
* @param content
* @return
*/
public static NdefRecord[] createNdefRecordByNFCUri(String content) {
//消息ID
String id = "hailiWasher01";
//这一段是对应的想要打开的Activity中添加的意图过滤参数pathPrefix 为自定义(前面要有斜杠),其余为固定写法
//<intent-filter>
// <action android:name="android.nfc.action.NDEF_DISCOVERED" />
// <category android:name="android.intent.category.DEFAULT" />
// <data android:scheme="vnd.android.nfc"
// android:host="ext"
// android:pathPrefix="/com.jiao.demo.nfc.nfcmainactivity:nfc"/>
//</intent-filter>
return new NdefRecord[]{
//打开App , ,第一个参数跟第二个参数就是Manifest中的android:pathPrefix,第三个参数是自己添加的参数
createExternal("com.jiao.demo.nfc.nfcmainactivity", id, "nfc", content.getBytes()),
//想要打开的应用包名(如果是组件化项目,包名必须是主项目的包名,也就是说,模块的build.gradle 中顶部代码为 apply plugin: 'com.android.application')
//如果没有安装这个应用,则会跳往应用市场
NdefRecord.createApplicationRecord("com.haier.haierwashertopspeed"),
//打开的链接,这里主要用于IOS 识别
NdefRecord.createUri("https://www.baidu.com" + "?" + content),
};
}
/**
* 重写NdefRecord方法 添加Id
* {@link NdefRecord#createExternal(String, String, byte[])}
*
* @param domain domain-name of issuing organization (自定义的,Activity路径 与 @param type 一起组成参数pathPrefix)
* @param type domain-specific type of data (自定义的,Activity路径 与 @param domain 一起组成参数pathPrefix)
* @param id 消息Id
* @param data 自添加数据 可以用来追加参数
* @return
*/
public static NdefRecord createExternal(String domain, String id, String type, byte[] data) {
if (domain == null) throw new NullPointerException("domain is null");
if (type == null) throw new NullPointerException("type is null");
domain = domain.trim().toLowerCase(Locale.ROOT);
type = type.trim().toLowerCase(Locale.ROOT);
if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
if (type.length() == 0) throw new IllegalArgumentException("type is empty");
byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
byte[] b = new byte[byteDomain.length + 1 + byteType.length];
System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
b[byteDomain.length] = ':';
System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
return new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, b, id == null ? null : id.getBytes(), data);
}
//读取EXTRA_NDEF_MESSAGES内容:
public static String readFromTag(Intent intent) {
Parcelable[] rawArray = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage mNdefMsg = (NdefMessage) rawArray[0];
NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];
try {
if (mNdefRecord != null) {
return new String(mNdefRecord.getPayload(), "UTF-8");
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
/**
* 读取MifareClassic 类型
*
* @param tag
* @return
*/
public static String readMifareClassic(Tag tag) {
//取出封装在intent中的TAG
String CardId = StringUtil.bytesToHexString(tag.getId());
String metaInfo = "";
metaInfo += "卡片ID:" + CardId;
for (String tech : tag.getTechList()) {
System.out.println(tech);
}
boolean auth;
//读取TAG
MifareClassic mfc = MifareClassic.get(tag);
try {
//Enable I/O operations to the tag from this TagTechnology object.
if (!mfc.isConnected()) {
mfc.connect();
}
int type = mfc.getType();//获取TAG的类型
int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数
String typeS = "";
switch (type) {
case MifareClassic.TYPE_CLASSIC:
typeS = "TYPE_CLASSIC";
break;
case MifareClassic.TYPE_PLUS:
typeS = "TYPE_PLUS";
break;
case MifareClassic.TYPE_PRO:
typeS = "TYPE_PRO";
break;
case MifareClassic.TYPE_UNKNOWN:
typeS = "TYPE_UNKNOWN";
break;
}
metaInfo += "\n卡片类型:" + typeS + "\n共" + sectorCount + "个扇区\n共"
+ mfc.getBlockCount() + "个块\n存储空间: " + mfc.getSize() + "B\n";
for (int j = 0; j < sectorCount; j++) {
//Authenticate a sector with key A.
auth = mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_DEFAULT);
int bCount;
int bIndex;
if (auth) {
metaInfo += "Sector " + j + ":验证成功\n";
// 读取扇区中的块
bCount = mfc.getBlockCountInSector(j);
bIndex = mfc.sectorToBlock(j);
for (int i = 0; i < bCount; i++) {
byte[] data = mfc.readBlock(bIndex);
metaInfo += "Block " + bIndex + " : "
+ StringUtil.bytesToHexString(data) + "\n";
bIndex++;
}
} else {
metaInfo += "Sector " + j + ":验证失败\n";
}
}
return metaInfo;
//Toast.makeText(this, metaInfo, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
return "读取错误:" + e.toString();
}
}
/**
* 读取IsoDep
*
* @param tag
* @return
*/
public static String IsoDep(Tag tag) {
IsoDep isoDep = IsoDep.get(tag);
String str = "";
try {
isoDep.connect(); // 连接
if (isoDep.isConnected()) {
LogUtil.D(TAG, "isoDep.isConnected"); // 判断是否连接上
// 1.select PSF (1PAY.SYS.DDF01)
// 选择支付系统文件,它的名字是1PAY.SYS.DDF01。
byte[] DFN_PSE = {(byte) '1', (byte) 'P', (byte) 'A', (byte) 'Y', (byte) '.', (byte) 'S', (byte) 'Y', (byte) 'S', (byte) '.', (byte) 'D', (byte) 'D', (byte) 'F', (byte) '0', (byte) '1',};
byte[] payFile = isoDep.transceive(getSelectCommand(DFN_PSE));
String EN_CODE_TYPE = "GBK";
String payFileStr = new String(payFile, EN_CODE_TYPE);
str += "\n\t" + "支付系统:" + payFileStr;
// 2.选择公交卡应用的名称
byte[] DFN_SRV = {(byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x86, (byte) 0x98, (byte) 0x07, (byte) 0x01,};
byte[] card_name = isoDep.transceive(getSelectCommand(DFN_SRV));
String card_nameStr = new String(card_name, EN_CODE_TYPE);
str += "\n\t" + "公交卡应用的名称:" + card_nameStr;
// 3.读取余额
byte[] ReadMoney = {(byte) 0x80, // CLA Class
(byte) 0x5C, // INS Instruction
(byte) 0x00, // P1 Parameter 1
(byte) 0x02, // P2 Parameter 2
(byte) 0x04, // Le
};
byte[] Money = isoDep.transceive(ReadMoney);
String MoneyStr = new String(card_name, EN_CODE_TYPE);
str += "\n\t" + "余额1:" + MoneyStr;
if (Money != null && Money.length > 4) {
int cash = byteToInt(Money, 4);
float ba = cash / 100.0f;
str += "\n\t" + "余额2:" + ba;
}
// 4.读取所有交易记录
byte[] ReadRecord = {(byte) 0x00, // CLA Class
(byte) 0xB2, // INS Instruction
(byte) 0x01, // P1 Parameter 1
(byte) 0xC5, // P2 Parameter 2
(byte) 0x00, // Le
};
byte[] Records = isoDep.transceive(ReadRecord);
if (Records != null && Records.length > 4) {
int cash = byteToInt(Records, 4);
float ba = cash / 100.0f;
str += "\n\t" + "总消费记录:" + ba;
}
ArrayList<byte[]> ret = parseRecords(Records);
List<String> retList = parseRecordsToStrings(ret);
str = str + "\n\t" + "消费记录" + retList.size() + "条";
if (retList.size() > 0) {
str = str + ",如下:";
}
for (String string : retList) {
LogUtil.D(TAG, "消费记录" + string);
str = str + "\n\t" + string;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isoDep != null) {
try {
isoDep.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return str;
}
/**
* 是否支持NFC
* @param context
* @return
*/
public static boolean isSupportNfc(Context context){
PackageManager pm = context.getPackageManager();
return pm.hasSystemFeature("android.hardware.nfc");
}
public static byte byteToHex(byte arg) {
byte hex = 0;
if (arg >= 48 && arg <= 57) {
hex = (byte) (arg - 48);
} else if (arg >= 65 && arg <= 70) {
hex = (byte) (arg - 55);
} else if (arg >= 97 && arg <= 102) {
hex = (byte) (arg - 87);
}
return hex;
}
private static byte[] getSelectCommand(byte[] aid) {
final ByteBuffer cmd_pse = ByteBuffer.allocate(aid.length + 6);
cmd_pse.put((byte) 0x00) // CLA Class
.put((byte) 0xA4) // INS Instruction
.put((byte) 0x04) // P1 Parameter 1
.put((byte) 0x00) // P2 Parameter 2
.put((byte) aid.length) // Lc
.put(aid).put((byte) 0x00); // Le
return cmd_pse.array();
}
// byteArray转化为int
private static int byteToInt(byte[] b, int n) {
int ret = 0;
for (int i = 0; i < n; i++) {
ret = ret << 8;
ret |= b[i] & 0x00FF;
}
if (ret > 100000 || ret < -100000)
ret -= 0x80000000;
return ret;
}
/**
* 整条Records解析成ArrayList<byte[]>
*
* @param Records
* @return
*/
private static ArrayList<byte[]> parseRecords(byte[] Records) {
int max = Records.length / 23;
LogUtil.D(TAG, "消费记录有" + max + "条");
ArrayList<byte[]> ret = new ArrayList<byte[]>();
for (int i = 0; i < max; i++) {
byte[] aRecord = new byte[23];
for (int j = 23 * i, k = 0; j < 23 * (i + 1); j++, k++) {
aRecord[k] = Records[j];
}
ret.add(aRecord);
}
for (byte[] bs : ret) {
LogUtil.D(TAG, "消费记录有byte[]" + bs); // 有数据。解析正确。
}
return ret;
}
/**
* ArrayList<byte[]>记录分析List<String> 一条记录是23个字节byte[] data,对其解码如下
* data[0]-data[1]:index data[2]-data[4]:over,金额溢出??? data[5]-data[8]:交易金额
* ??代码应该是(5,4) data[9]:如果等于0x06或者0x09,表示刷卡;否则是充值
* data[10]-data[15]:刷卡机或充值机编号
* data[16]-data[22]:日期String.format("%02X%02X.%02X.%02X %02X:%02X:%02X"
* ,data[16], data[17], data[18], data[19], data[20], data[21], data[22]);
*
* @return
*/
private static List<String> parseRecordsToStrings(ArrayList<byte[]>... Records) {
List<String> recordsList = new ArrayList<String>();
for (ArrayList<byte[]> record : Records) {
if (record == null)
continue;
for (byte[] v : record) {
StringBuilder r = new StringBuilder();
int cash = NumberUtil.toInt(v);
char t = (v[9] == TRANS_CSU || v[9] == TRANS_CSU_CPX) ? '-' : '+';
r.append(String.format("%02X%02X.%02X.%02X %02X:%02X ", v[16], v[17], v[18], v[19], v[20], v[21], v[22]));
r.append(" " + t).append(cash / 100.0f);
String aLog = r.toString();
recordsList.add(aLog);
}
}
return recordsList;
}
protected final static byte TRANS_CSU = 6; // 如果等于0x06或者0x09,表示刷卡;否则是充值
protected final static byte TRANS_CSU_CPX = 9; // 如果等于0x06或者0x09,表示刷卡;否则是充值
/**
* NFC 获取到的Device 在intent中传播, 使用此方法获取DeviceId
*
* @param activity
* @return
*/
public static NfcParamenterBeen getDeviceIdFromIntent(Activity activity) {
Intent intent = activity.getIntent();
NfcParamenterBeen nfcParamenterBeen = null;
if (intent != null) {
nfcParamenterBeen = (NfcParamenterBeen) intent.getSerializableExtra(NfcUtil.MODEL_BEEN);
}
return nfcParamenterBeen;
}
/**
* 把DeviceId 放入Intent 中
*
* @param intent
* @param deviceId
* @return
*/
public static Intent putDeviceIdToIntent(Intent intent, String deviceId) {
if (intent != null) {
Bundle bundle = intent.getBundleExtra("bundle");
if (bundle == null) {
bundle = new Bundle();
intent.putExtra("bundle", bundle);
}
bundle.putString(DEVICE_ID, deviceId);
}
return intent;
}
}
3⃣️Manifest中的 activity注册代码
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<activity
android:name="com.jiao.demo.nfc.NFCMainActivity"
android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|fontScale|touchscreen"
android:launchMode="singleInstance"
android:maxAspectRatio="2.4"
android:label="NFC参数修改"
android:screenOrientation="portrait">
<!-- NFC start-->
<!-- NFC 过滤目标 -->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="vnd.android.nfc"
android:host="ext"
android:pathPrefix="/com.jiao.demo.nfc.nfcmainactivity:nfc"/>
</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" />
<!-- NFC start-->
</activity>
4⃣️TECH_DISCOVERED resource nfc_tech_filter
下面是nfc_tech_filter 中的代码, 这个文件的位置为src/main/res/xml/nfc_tech_filter.xml
因为我测试的Nfc 只有三个类型,所以我只写这几个,这也是一层对Nfc的过滤
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- 可以处理所有Android支持的NFC类型 -->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.IsoDep</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.NfcA</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.NfcB</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.NfcF</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.NfcV</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.Ndef</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.NdefFormatable</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.MifareUltralight</tech>-->
<!-- </tech-list>-->
<!-- <tech-list>-->
<!-- <tech>android.nfc.tech.MifareClassic</tech>-->
<!-- </tech-list>-->
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>
5⃣️工具类
public class NumberUtil {
public static int toInt(byte[] bytes){
int number = 0;
for(int i = 0; i < 4 ; i++){
number += bytes[i] << i*8;
}
return number;
}
}
public class StringUtil {
// 字符序列转换为16进制字符串
public static String bytesToHexString(byte[] src) {
return bytesToHexString(src, true);
}
public static String bytesToHexString(byte[] src, boolean isPrefix) {
StringBuilder stringBuilder = new StringBuilder();
if (isPrefix) {
stringBuilder.append("0x");
}
if (src == null || src.length <= 0) {
return null;
}
char[] buffer = new char[2];
for (int i = 0; i < src.length; i++) {
buffer[0] = Character.toUpperCase(Character.forDigit(
(src[i] >>> 4) & 0x0F, 16));
buffer[1] = Character.toUpperCase(Character.forDigit(src[i] & 0x0F,
16));
System.out.println(buffer);
stringBuilder.append(buffer);
}
return stringBuilder.toString();
}
}
上面为 Nfc 的所有类型的读取 以及NDEF类型的写入,没有其他类型的写入,暂时没有加密,后期会考虑新写一篇来介绍。
二、注意事项
1、Android 如果想要启动一个应用,并且到达指定界面,需要注意NdefRecord添加的先后顺序,因为他会依次执行,type 必须是小写,否则可能过滤失效,跳往启动页
2、IOS 不能主动识别链接之外的NdefRecord(除非主动去获取),所以NdefRecord[]需要添加一个链接, iOS跳往外链去识别(添加到最后)
//创建消息(录入NFC) public static NdefRecord[] createNdefRecordByNFCUri(String content) { //这一段是对应的想要打开的Activity中添加的意图过滤参数pathPrefix 为自定义(前面要有斜杠),其余为固定写法 //<intent-filter> // <action android:name="android.nfc.action.NDEF_DISCOVERED" /> // <category android:name="android.intent.category.DEFAULT" /> // <data android:scheme="vnd.android.nfc" // android:host="ext" // android:pathPrefix="/com.jiao.demo.nfc.nfcmainactivity:nfc"/> //</intent-filter> // return new NdefRecord[]{ //去往APP指定的页面 (需要与NdefRecord.createApplicationRecord配合使用), ,第一个参数跟第二个参数就是Manifest中的android:pathPrefix,第三个参数是自定义参数,比如设备Id等信息 NdefRecord.createExternal("com.jiao.demo.nfc.nfcmainactivity", "nfc", content.getBytes()), //想要打开的应用包名(如果是组件化项目,包名必须是主项目的包名,也就是说,模块的build.gradle 中顶部代码为 apply plugin: 'com.android.application') //如果没有安装这个应用,则会跳往应用市场 NdefRecord.createApplicationRecord("com.*.*"), //打开的链接,这里主要用于IOS 识别 NdefRecord.createUri("https://www.baidu.com" + "?" + content), }; }
3、如果想要给 NdefRecord添加Id 只能自己去New NdefRecord,但是又想达成人家代码的效果,那就复制一下源码,给加个ID
比如:
4、同一个App 想响应多个Activity
1⃣️、同一个App里的Manifest中Activity设置NFC 过滤,android:pathPrefix最好不要设置一样的
因为这对用户来说很不友好(一刷Nfc,手机界面弹出一个弹窗,有多个同样图标的选项);
如果真有需求想多个界面去监听,可以看看下面的第三条“应用场景思路”的第一条1、NFC 打开指定Activity页面 ,有异曲同工之妙!
2⃣️、想让其他界面在NFC 启动APP 之后继续响应NFC(监听消息)
并且想做到上面 1⃣️ 那样,则需要让那个在Manifest中添加过滤的Activity 一直保持存活状态;
不然的话,如果APP 第一次是通过NFC响应启动的,关掉这个Activity之后,其他页面则无法继续响应(或许是我漏掉了什么,如有人有什么建议,请在下面评论,谢谢)
但是这样的话有时候于我们的业务会有一定程度的冲突,所以 下面的“三、应用场景思路”中第一条“NFC 打开指定Activity页面”,也可以解决这个问题
五、获取不到Tag,手机响应NFC,但是Activity#onNewIntent并没有响应
这个是意图过滤问题(本篇文章上面Activity之前代码中也有这个问题,已修改)
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); LogUtil.I(TAG,"onNewIntent "+intent.getAction()); // 当前app正在前端界面运行,这个时候有intent发送过来,那么系统就会调用onNewIntent回调方法,将intent传送过来 // 我们只需要在这里检验这个intent是否是NFC相关的intent,如果是,就调用处理方法。如果 if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())||NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { processIntent(intent); } }
NfcAdapter 监听事件添加过滤意图(视实际情况而定,看卡片的tech)
private void startNFC_Listener() { // 开始监听NFC设备是否连接,如果连接就发pi意图 nfcAdapter.enableForegroundDispatch(this, pi, new IntentFilter[]{new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
,new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
,new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)}, null); }
三、应用场景思路
1、NFC 打开指定Activity页面(非启动页,因为只用来启动APP,那就不用看这个思路了)
1⃣️、要求:
第一,无论当前在哪个界面,都可以收到Nfc的消息
第二,不想让App 每次展示Nfc指定的Activity(Manifest中添加过滤意图的Activity),只想在当前页面继续往下走,不影响页面跳转顺序
第三,APP 启动后,不想让Nfc的参数,在多个Activity中传输之后才能到达指定页面或者 指定需要启动的页面;想让这个传输路线变得简单,不需要过多传输
第四,想让Nfc功能对原有界面的侵入性达到最低(我的思路可能并不是最优,但是相对来说实现比较简单的,不需要其他技术配合实现)
2⃣️、实现思路
第一、创建一个单独的NfcActivity,并把它设置为用户不可见的透明状态(设置大小为1px。请看文章 创建一个悬浮窗 Activity(或者无界面Activity))
第二、NfcActivity在单独的栈里面(android:launchMode="singleInstance")这样不会影响原来的栈中activity的进出,并且让NfcActivity 注册的时候,添加NFC启动意图
,也就是说刷Nfc,实际先启动的是NfcActivity。
第三、 除了NfcActivity 需要对NFC 设置接受消息,其他的类,只接受从NfcActivity页面传过来的参数,NFC 的对接工作全部交给NfcActivity
可以写一个接口,有一个接收消息的方法,让相关需要接收消息的Activity实现,然后在NfcActivity中传输数据的时候调用对应Activity的接受方法(适用于需要数据的页面已经创建的情况)可以与第五条一起使用
第四、返回键需要处理(点击返回键不销毁Activity)
第五、在其他需要接受消息的类里调用 下面的方法传输、获取
NfcUtil. putDeviceIdToIntent();
String deviceId = NfcUtil.getDeviceIdFromIntent(this);
3⃣️、代码
Activity
public class NfcActivity extends BaseActivity {
static String TAG = "NfcActivity_";
/**
* {@link Activity#onCreate(Bundle)}
*/
@Override
public void mInitView() {
initNFCData();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
LogUtil.I(TAG, "onNewIntent");
nfcRead(intent);
}
//Nfc卡片发现设备Id
private String deviceId = null;
@Override
protected void onResume() {
super.onResume();
Bundle bundle = null;
if (MainApplication.NFC_OFF_NO && isNFC_support) {
// 开始监听NFC设备是否连接
startNFC_Listener();
Intent intent = getIntent();
LogUtil.I(TAG, "onResume");
nfcRead(intent);
if (!StringUtils.isNullData(deviceId)) {
bundle = new Bundle();
bundle.putString("deviceId", deviceId);
}
}
}
@Override
protected void onPause() {
super.onPause();
stopNFC_Listener();
}
// NFC适配器
private NfcAdapter nfcAdapter = null;
// 传达意图
private PendingIntent pi = null;
// 滤掉组件无法响应和处理的Intent
private IntentFilter tagDetected = null;
// 是否支持NFC功能的标签
private boolean isNFC_support = false;
private void initNFCData() {
if (MainApplication.NFC_OFF_NO) {
// 初始化设备支持NFC功能
isNFC_support = NfcUtil.isSupportNfc(this);
// 得到默认nfc适配器
nfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
// 提示信息定义
String metaInfo = "";
// 判定设备是否支持NFC或启动NFC
if (nfcAdapter == null) {
metaInfo = "设备不支持NFC!";
ToastUtil.showShortToast(this, metaInfo);
isNFC_support = false;
return;
}
if (!nfcAdapter.isEnabled()) {
metaInfo = "请在系统设置中先启用NFC功能!";
ToastUtil.showShortToast(this, metaInfo);
isNFC_support = false;
}
if (isNFC_support) {
init_NFC();
}
}
}
private void init_NFC() {
// 初始化PendingIntent,当有NFC设备连接上的时候,就交给当前Activity处理
pi = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
// 新建IntentFilter,使用的是第二种的过滤机制
tagDetected = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
}
private void startNFC_Listener() {
// 开始监听NFC设备是否连接,如果连接就发pi意图
nfcAdapter.enableForegroundDispatch(this, pi,
new IntentFilter[]{tagDetected}, null);
}
private void stopNFC_Listener() {
// 停止监听NFC设备是否连接
nfcAdapter.disableForegroundDispatch(this);
}
/**
* 从NFC获取数据
* @param intent
*/
public void nfcRead(Intent intent) {
//防止在短时间内重复执行 1.5秒的屏蔽
if (DoubleClickUtil.isFastDoubleClick(1500)) {
return;
}
if (MainApplication.NFC_OFF_NO) {
if (intent != null) {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
Map<String, String> map = NfcUtil.getMessage(intent);
LogUtil.I(TAG, "onNewIntent " + map.get(NfcUtil.CARD_TYPE));
if (map.get(NfcUtil.CARD_TYPE) != null) {
//跳转设备详情页 意图
if (NfcUtil.PARAMETER_GOTO_DEVICE_DETAIL_PAGE.equals(map.get(NfcUtil.CARD_TYPE))) {
deviceId = map.get(NfcUtil.DEVICE_ID);
if (equalsByName ("WasherHomeActivity")!=null) { //洗衣页面存在
//刷新页面?
} else if (equalsByName ("AppHomeActivity")!=null) {//首页面存在
Intent startApp = new Intent(equalsByName ("AppHomeActivity"), AppHomeActivity.class);
Bundle bundle = new Bundle();
bundle.putString("deviceId", deviceId);
startApp.putExtra("bundle", bundle);
startActivity(startApp);
} else if (equalsByName ("SplashActivity")!=null) {//启动页正在展示
// 把值传给启动页
} else {
Intent startApp = new Intent(this, SplashActivity.class);
Bundle bundle = new Bundle();
bundle.putString("deviceId", deviceId);
startApp.putExtra("bundle", bundle);
startActivity(startApp);
}
deviceId = "";
getIntent().putExtra("bundle", new Bundle());
}
}
}
}
}
}
public Bundle getBundleOfDeviceId(String str) {
Bundle bundle = null;
if (!StringUtils.isNullData(str)) {
bundle = new Bundle();
bundle.putString("deviceId", str);
}
return bundle;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
//点击返回键不销毁Activity
moveTaskToBack(true);
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* 从项目中在启动的Activity,通过名称中寻找Activity
*
* @param activityName
* @return
*/
public AppBaseActicity equalsByName(String activityName) {
//MainApplication.baseActivities 是我记录在Application 中的一个集合, 在基类中调用,onCreat时list.add(this). onDestroy时 list.remove(this)
for (int i = 0; i < MainApplication.baseActivities.size(); i++) {
if (!MainApplication.baseActivities.get(i).isFinishing()) {
if (activityName.equals(MainApplication.baseActivities.get(i).getLocalClassName())) {
return MainApplication.baseActivities.get(i);
}
}
}
return null;
}
}
Manifest注册
主要是设置了android:theme="@style/OnePxActivityStyle"
其余部分 点击目录 3⃣️Manifest中的 activity注册代码 查看
OnePxActivityStyle代码
请看文章 创建一个悬浮窗 Activity(或者无界面Activity) ,顶部即是!
文中代码若有短缺,请在下面评论处提醒,谢谢!
至此,待补充。。。