前几天老大把一张卡拿给我说要我把用NFC技术读出它的uid来,写成一个demo给他,我第一反应就是一脸闷逼,可是卡已经交给我了,只能照办不误了!在我不长的开发生涯中是从来没有接触过NFC的,所以在接到任务后,我也是急忙的恶补一波相关知识,最后也算是完成任务了吧,但对于NFC还只是入了门而已,我今天要说的也就是常见的NFC标签读取,给大家领个路而已,互相学习。我先给大家说一下NFC(Near Field Communication)的工作模式 :NFC支持如下3种工作模式:读卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、点对点模式(P2P mode)。下来分别看一下这三种模式:
数据在NFC芯片中,可以简单理解成“刷标签”。本质上就是通过支持NFC的手机或其它电子设备从带有NFC芯片的标签、贴纸、名片等媒介中读写信息。通常NFC标签是不需要外部供电的。当支持NFC的外设向NFC读写数据时,它会发送某种磁场,而这个磁场会自动的向NFC标签供电。
数据在支持NFC的手机或其它电子设备中,可以简单理解成“刷手机”。本质上就是将支持NFC的手机或其它电子设备当成借记卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息凭证封装成数据包存储在支持NFC的外设中 。
在使用时还需要一个NFC射频器(相当于刷卡器)。将手机靠近NFC射频器,手机就会接收到NFC射频器发过来的信号,在通过一系列复杂的验证后,将IC卡的相应信息传入NFC射频器,最后这些IC卡数据会传入NFC射频器连接的电脑,并进行相应的处理(如电子转帐、开门等操作)。
该模式与蓝牙、红外差不多,用于不同NFC设备之间进行数据交换,不过这个模式已经没有有“刷”的感觉了。其有效距离一般不能超过4厘米,但传输建立速度要比红外和蓝牙技术快很多,传输速度比红外块得多,如过双方都使用Android4.2,NFC会直接利用蓝牙传输。这种技术被称为Android Bean。所以使用Android Bean传输数据的两部设备不再限于4厘米之内。
点对点模式的典型应用是两部支持NFC的手机或平板电脑实现数据的点对点传输,例如,交换图片或同步设备联系人。因此,通过NFC,多个设备如数字相机,计算机,手机之间,都可以快速连接,并交换资料或者服务。
NFC全称为Near Field Communication意思是近场通讯,作为和蓝牙,红外线一起成为当前的主流的数据传输技术我们来看看他们之间的区别:
对比项 | NFC | 蓝牙 | 红外 |
---|---|---|---|
网络类型 | 点对点 | 单点对多点 | 点对点 |
有效距离 | <=0.1m | <=10m,最新的蓝牙4.0有效距离可达100m | 一般在1m以内,热技术连接,不稳定 |
传输速度 | 最大424kbps | 最大24Mbps | 慢速115.2kbps,快速4Mbps |
建立时间 | <0.1s | 6s | 0.5s |
安全性 | 安全,硬件实现 | 安全,软件实现 | 不安全,使用IRFM时除外 |
通信模式 | 主动-主动/被动 | 主动-主动 | 主动-主动 |
成本 | 低 | 中 | 低 |
我们读取id就是利用的读卡器模式,当把卡片靠近手机的NFC天线的时候,NFC会识别到卡,然后把卡对象装到intent里面,并发送广播NfcAdapter.ACTION_TECH_DISCOVERED,应用程序接到这个广播之后,通过intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)来获取到卡对象,然后就可以对卡进行读写。下面我就直接上代码了。
首先类似蓝牙通讯,NFC开发也是相应的权限的:
<uses-permission android:name="android.permission.NFC" />
<!-- 要求当前设备必须要有NFC芯片 -->
<uses-feature android:name="android.hardware.nfc" android:required="true" />
然后你要进行NFC操作的activity这么注册,注意启动模式务必要设置成singleTop或singleTask ,至于广播过滤大家看自己需要添加
<activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</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" />
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
nfc_tech_filter.xml是过滤器,过滤哪些卡片(由于我们需要读取的是MifareClassic卡,所以以外的过滤)
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcC</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcF</tech>
</tech-list>
</resources>
我先给大家看一下界面布局吧,很简单就是读取之后用textview显示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#151414"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_centerInParent="true"
>
<TextView
android:layout_width="wrap_content"
android:text="读取到的卡UID: "
android:textColor="#fff"
android:layout_height="wrap_content" />
<TextView
android:textColor="#fff"
android:id="@+id/tv_uid"
android:text=" "
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</RelativeLayout>
接下来看下activity代码:
public class MainActivity extends AppCompatActivity {
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
private TextView tvUid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvUid = (TextView) findViewById(R.id.tv_uid);
//获取NfcAdapter实例
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
//获取通知
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
if (nfcAdapter == null) {
Toast.makeText(MainActivity.this,"设备不支持NFC",Toast.LENGTH_LONG).show();
return;
}
if (nfcAdapter!=null&&!nfcAdapter.isEnabled()) {
Toast.makeText(MainActivity.this,"请在系统设置中先启用NFC功能",Toast.LENGTH_LONG).show();
return;
}
//因为启动模式是singleTop,于是会调用onNewIntent方法
onNewIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
resolveIntent(intent);
}
void resolveIntent(Intent intent) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
processTag(intent);
}
}
private 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;
}
private String flipHexStr(String s){
StringBuilder result = new StringBuilder();
for (int i = 0; i <=s.length()-2; i=i+2) {
result.append(new StringBuilder(s.substring(i,i+2)));
}
return result.toString();
}
public void processTag(Intent intent) {//处理tag
//获取到卡对象
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//获取卡id这里即uid
byte[] aa = tagFromIntent.getId();
String str = ByteArrayToHexString(aa);
str = flipHexStr(str);
tvUid.setText(str);
}
@Override
protected void onPause() {
super.onPause();
if (nfcAdapter != null)
//设置程序不优先处理
nfcAdapter.disableForegroundDispatch(this);
}
@Override
protected void onResume() {
super.onResume();
if (nfcAdapter != null)
//设置程序优先处理
nfcAdapter.enableForegroundDispatch(this, pendingIntent,
null, null);
}
}
这里我给大家说几个要注意的地方,首先为啥要采用onNewIntent方法,我不知道在这之前,各位有没有理解过这个方法,这里我简单说下,大家都知道activity有四种启动模式singleTop,standard,singleTask,singleInstance,当你从activityA跳转到activityB,然后从B又跳回来并带数据,这个时候如果启动模式为singleTask,A是不会再执行onCreate方法了,那么我们怎么取数据呢,这个时候onNewIntent就起作用了,这个intent才是你跳转过来的新Intent,这里同样,因为NFC检测到对象时,会在系统startActivity,那么目标activity已经是启动了,所以我们需要在onNewIntent方法中接受tag对象,同时activity启动模式设为singleTop或singleTask也为了避免重复创建实例!还有一点要注意的是,我们知道不在前台的activity在系统内存告急,或者时间太久都有被杀死的可能,没办法这是底层的一种策略,就是前台可见的东西,优先级永远大于不可见的,所以为了避免这种血案,我们应该在onCreate方法和onNewIntent方法中都执行getIntent操作,这样就万无一失了!
以上代码很容易理解了,好了,那么读取NFC标签就这些了!