一、NFC API
API地址:https://www.android-doc.com/guide/topics/connectivity/nfc/nfc.html
二、简介
NFC的三种模式
读卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、点对点模式(P2P mode)。
(1)读卡器模式
数据在NFC芯片中,可以简单理解成“刷标签”。本质上就是通过支持NFC的手机或其它电子设备从带有NFC芯片的标签、贴纸、名片等媒介中读写信息。通常NFC标签是不需要外部供电的。当支持NFC的外设向NFC读写数据时,它会发送某种磁场,而这个磁场会自动的向NFC标签供电。
(2)仿真卡模式
数据在支持NFC的手机或其它电子设备中,可以简单理解成“刷手机”。本质上就是将支持NFC的手机或其它电子设备当成借记卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息凭证封装成数据包存储在支持NFC的外设中 。
在使用时还需要一个NFC射频器(相当于刷卡器)。将手机靠近NFC射频器,手机就会接收到NFC射频器发过来的信号,在通过一系列复杂的验证后,将IC卡的相应信息传入NFC射频器,最后这些IC卡数据会传入NFC射频器连接的电脑,并进行相应的处理(如电子转帐、开门等操作)。
(3)点对点模式
该模式与蓝牙、红外差不多,用于不同NFC设备之间进行数据交换,不过这个模式已经没有有“刷”的感觉了。其有效距离一般不能超过4厘米,但传输建立速度要比红外和蓝牙技术快很多,传输速度比红外块得多,如过双方都使用Android4.2,NFC会直接利用蓝牙传输。这种技术被称为Android Beam。所以使用Android Beam传输数据的两部设备不再限于4厘米之内。
点对点模式的典型应用是两部支持NFC的手机或平板电脑实现数据的点对点传输,例如,交换图片或同步设备联系人。因此,通过NFC,多个设备如数字相机,计算机,手机之间,都可以快速连接,并交换资料或者服务。
二、NDEF,TECH,TAG 解析顺序
在使用之前,我们要先去了解下NFC 的tag分发系统
如果想让android设备感应到NFC标签,你要保证两点
1:屏幕没有锁住
2:NFC功能已经在设置中打开
当系统检测到一个NFC标签的时候,他会自动去寻找最合适的activity去处理这个intent.
他所发出的这个Intent将会有三种action:
ACTION_NDEF_DISCOVERED:当系统检测到tag中含有NDEF格式的数据时,且系统中有activity声明可以接受包含NDEF数据的Intent的时候,系统会优先发出这个action的intent。
ACTION_TECH_DISCOVERED:当没有任何一个activity声明自己可以响应ACTION_NDEF_DISCOVERED时,系统会尝试发出TECH的intent.即便你的tag中所包含的数据是NDEF的,但是如果这个数据的MIME type或URI不能和任何一个activity所声明的想吻合,系统也一样会尝试发出tech格式的intent,而不是NDEF.
ACTION_TAG_DISCOVERED:当系统发现前两个intent在系统中无人会接受的时候,就只好发这个默认的TAG类型的
3:NFC标签过滤
在activity的intent过滤xml声明中,你可以同时声明过滤这三种action.但是由之前所说,你应该知道系统在发送intent的时候是有优先级的,所以你最好清楚自己最想处理哪个。
1、过滤ACTION_TAG_DISCOVERED:
2、过滤ACTION_NDEF_DISCOVERED:
data的mimeType类型了,这个定义的越准确,intent指向你这个activity的成功率就越高,否则系统可能不会发出你想要的NDEF intent了。
3、过滤ACTION_TECH_DISCOVERED:
你首先需要在你的
一个nfc_tech_filter.xml中可以定义多个
--------------------------------A组
android.nfc.tech.IsoDep android.nfc.tech.NfcA
android.nfc.tech.NfcB android.nfc.tech.NfcF
-----------------------------------------B组
android.nfc.tech.NfcV android.nfc.tech.Ndef
android.nfc.tech.NdefFormatable
android.nfc.tech.MifareClassic
android.nfc.tech.MifareUltralight
过滤器过滤
4、
nfc标签前台分发系统
之所以把他也归类在nfc的过滤里面,主要是因为他跟解析nfc标签到不是那么的紧密,他解决的是接受哪些nfc标准的标签问题。所以更接近nfc的过滤。
什么叫nfc的前台发布系统?就是说当我们已经打开我们的应用的时候,那么通过这个前台发布系统的设置,我们可以让我们已经启动的activity拥有更高的优先级来依据我们在代码中定义的标准来过滤和处理intent,而不是让别的声明了intent filter的activity来干扰,甚至连自己声明在androidManifest中的intent filter都不会来干扰。也就是说foreground Dispatch的优先级大于intent filter。
第一种情况:当你的activity没有启动的时候,去扫描tag,那么系统中所有的intent filter都将一起参与过滤。
第二种情况:当你的actiity启动了,去扫描tag时,那么将直接使用你在foreground dispatch中代码写入的过滤标准。如果这个标准没有命中任何intent,那么系统将使用所有activity声明的intent filter xml来过滤。
三、NFC有关的常见的ISO标准有:
标准 | 说明 |
---|---|
ISO 14443 | RFID卡标准(非接触IC卡),该标准又有很多子标准 |
ISO 7816 | 接触式IC卡标准 |
ISO 15693 | 某种射频卡标准吧,这个没查到资料 |
ISO 18092 | NFC标准 |
四、使用
1、加权限
2、加上文提到的过滤,将activity启动模式android:launchMode="singleInstance"或者android:launchMode="singleTop"
3、封装工具类
/**
* Created by Administrator on 2019/7/26
*
* desc:
*/
public class nfcUtils {
public static NfcAdapter mNfcAdapter;
public static IntentFilter[] mIntentFilter = null;
public static PendingIntent mPendingIntent = null;
public static String[][] mTechList = null;
/**
* 构造函数,用于初始化nfc
*/
public nfcUtils(Activity activity) {
mNfcAdapter = isNfcEnable(activity);
NfcInit(activity);
}
/**
* 判断手机是否具备NFC功能
*
* @param context {@link Context}
* @return {@code true}: 具备 {@code false}: 不具备
*/
public static boolean isNfcExits(Context context) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
return nfcAdapter != null;
}
/**
* 判断手机NFC是否开启
*
* OPPO A37m 发现必须同时开启NFC以及Android Beam才可以使用
* 20180108 发现OPPO单独打开NFC即可读取标签,不清楚是否是系统更新
*
*
* @param context {@link Context}
* @return {@code true}: 已开启 {@code false}: 未开启
*/
public static boolean isNfcEnable(Context context) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
// if (Build.MANUFACTURER.toUpperCase().contains("OPPO")) {
// return nfcAdapter.isEnabled() && isAndroidBeamEnable(context);
// }
return nfcAdapter != null && nfcAdapter.isEnabled();
}
/**
* 判断手机NFC的Android Beam是否开启,在API 16之后才有
*
* @param context {@link Context}
* @return {@code true}: 已开启 {@code false}: 未开启
*/
public static boolean isAndroidBeamEnable(Context context) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && nfcAdapter != null && nfcAdapter.isNdefPushEnabled();
}
/**
* 判断手机是否具备Android Beam
*
* @param context {@link Context}
* @return {@code true}:具备 {@code false}:不具备
*/
public static boolean isAndroidBeamExits(Context context) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && isNfcExits(context);
}
/**
* 跳转至系统NFC设置界面.
*
* @param context {@link Context}
* @return {@code true} 跳转成功
{@code false} 跳转失败
*/
public static boolean intentToNfcSetting(Context context) {
if (isNfcExits(context)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
return toIntent(context, Settings.ACTION_NFC_SETTINGS);
}
}
return false;
}
/**
* 跳转至系统NFC Android Beam设置界面,同页面基本都有NFC开关.
*
* @param context {@link Context}
* @return {@code true} 跳转成功
{@code false} 跳转失败
*/
public static boolean intentToNfcShare(Context context) {
if (isAndroidBeamExits(context)) {
return toIntent(context, Settings.ACTION_NFCSHARING_SETTINGS);
}
return false;
}
/**
* 跳转方法.
* @param context {@link Context}
* @param action 意图
* @return 是否跳转成功 {@code true } 成功
{@code false}失败
*/
private static boolean toIntent(Context context, String action) {
try {
Intent intent = new Intent(action);
context.startActivity(intent);
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
return true;
}
/**
* 初始化nfc设置
*/
public static void NfcInit(Activity activity) {
方法一:
Intent intent = new Intent(activity, activity.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
mPendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
//做一个IntentFilter过滤你想要的action 这里过滤的是ndef
IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
//如果你对action的定义有更高的要求,比如data的要求,你可以使用如下的代码来定义intentFilter
// IntentFilter filter2 = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
// try {
// filter.addDataType("*/*");
// } catch (IntentFilter.MalformedMimeTypeException e) {
// e.printStackTrace();
// }
// mIntentFilter = new IntentFilter[]{filter, filter2};
// mTechList = null;
try {
filter.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
e.printStackTrace();
}
mTechList = new String[][]{{MifareClassic.class.getName()},
{NfcA.class.getName()}};
//生成intentFilter
mIntentFilter = new IntentFilter[]{filter};
方法二:
mPendingIntent = PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
IntentFilter filter2 = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
try {
filter.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
e.printStackTrace();
}
mIntentFilter = new IntentFilter[]{filter, filter2};
mTechList = null;
}
/**
* 读取NFC的数据
*/
public static String readNFCFromTag(Intent intent) throws UnsupportedEncodingException {
Parcelable[] rawArray = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawArray != null) {
NdefMessage mNdefMsg = (NdefMessage) rawArray[0];
NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];
if (mNdefRecord != null) {
String readResult = new String(mNdefRecord.getPayload(), "UTF-8");
return readResult;
}
}
return "";
}
/**
* 往nfc写入数据
*/
public static void writeNFCToTag(String data, Intent intent) throws IOException, FormatException {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
ndef.connect();
NdefRecord ndefRecord = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
ndefRecord = NdefRecord.createTextRecord(null, data);
}
NdefRecord[] records = {ndefRecord};
NdefMessage ndefMessage = new NdefMessage(records);
ndef.writeNdefMessage(ndefMessage);
}
/**
* 读取nfcID
*/
public static String readNFCId(Intent intent) throws UnsupportedEncodingException {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String id = ByteArrayToHexString(tag.getId());
return id;
}
/**
* 将字节数组转换为字符串
*/
private static String ByteArrayToHexString(byte[] inarray) {
int i, j, in;
String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
String out = "";
for (j = 0; j < inarray.length; ++j) {
in = (int) inarray[j] & 0xff;
i = (in >> 4) & 0x0f;
out += hex[i];
i = in & 0x0f;
out += hex[i];
}
return out;
}
}
4、弹框提示
private void showDialog(String title, String content, View.OnClickListener listener) {
dismissDialog();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.dialog_tag_lost, null);
TextView tvTitle = (TextView) view.findViewById(R.id.dialog_title);
TextView tvContent = (TextView) view.findViewById(R.id.dialog_content);
tvTitle.setText(title);
tvContent.setText(content);
Button btnCancel = (Button) view.findViewById(R.id.btn_cancel);
Button btnOk = (Button) view.findViewById(R.id.btn_confirm);
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissDialog();
}
});
btnOk.setOnClickListener(listener);
builder.setView(view);
mDialog = builder.create();
mDialog.setCancelable(false);
mDialog.setCanceledOnTouchOutside(false);
mDialog.show();
}
private void dismissDialog() {
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
mDialog = null;
}
}
5、生命周期
@Override
protected void onDestroy() {
super.onDestroy();
dismissDialog();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent);
}
public void onResume(){
super.onResume();
nfcUtils.mNfcAdapter.enableForegroundDispatch(this, nfcUtils.mPendingIntent, nfcUtils.mIntentFilter, nfcUtils.mTechList);
}
public void onPause(){
super.onPause();
if (nfcUtils.isNfcEnable(this)){
nfcUtils.mNfcAdapter.disableForegroundDispatch(this);
}
}
6、在onCreate()中 nfcUtils nfc = new NFC(this);