前言
最近公司要实现一个效果,提供几张白卡,通过NFC刷卡来启动我们的应用,找了不少办法:
第一种是直接读取卡id,然后进入应用,但这种方法有个弊端,就是如果有其他监听NFC的应用,会弹出选择框,让你选择使用哪个应用来接收刷卡事件。
第二种是将包名写入到卡中,这次确实不会再弹出选择框了,但是却无法读取到卡内的数据。
第三种就是下面的方法,既可以直接跳转到应用,还可以读取卡的数据。
实现效果
先来看一下效果图:
这是未写入数据时,刷NFC会跳转到写卡界面
这是写入成功后,刷卡会直接跳转到读卡界面,读取卡ID并显示。
代码实现
先定义写卡界面:NfcWriteActivity
public class NfcWriteActivity extends AppCompatActivity {
private static final String TAG = "NfcWriteActivity";
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_write);
if (mNfcAdapter == null) {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
}
mPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, NfcWriteActivity.class), 0);
doSomethingWithIntent(getIntent());
}
@Override
protected void onResume() {
super.onResume();
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
@Override
protected void onPause() {
super.onPause();
mNfcAdapter.disableForegroundDispatch(this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
doSomethingWithIntent(intent);
}
private void doSomethingWithIntent(Intent intent) {
final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag == null) {
return;
}
writeNFC(tag);
}
private void writeNFC(Tag tag) {
// 这里是将数据写入NFC卡中
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord.createExternal("com.bxy.nfc", "nfc", "nfc".getBytes()),NdefRecord.createApplicationRecord("com.bxy.nfc")});
int size = ndefMessage.toByteArray().length;
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
if (!ndef.isWritable()) {
return;
}
if (ndef.getMaxSize() < size) {
return;
}
try {
ndef.writeNdefMessage(ndefMessage);
Toast.makeText(this, "写入成功", Toast.LENGTH_LONG).show();
} catch (FormatException e) {
e.printStackTrace();
}
} else {
NdefFormatable format = NdefFormatable.get(tag);
format.connect();
format.format(ndefMessage);
if (format.isConnected()) {
format.close();
}
Toast.makeText(this, "写入成功", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "写入失败", Toast.LENGTH_LONG).show();
}
}
}
读卡界面:NfcReadActivity
public class NfcReadActivity extends AppCompatActivity {
TextView nfcContentTv;
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_read);
nfcContentTv = findViewById(R.id.tv_nfc_read_content);
if (nfcAdapter == null) {
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
}
pendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, NfcReadActivity.class), 0);
disposeIntent(getIntent());
}
@Override
protected void onResume() {
super.onResume();
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
@Override
protected void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
disposeIntent(intent);
}
private void disposeIntent(Intent intent){
String cardId = getCardId(intent);
if (cardId != null) {
nfcContentTv.setText(String.format("NFC ID:%s", cardId));
} else {
Toast.makeText(this, "未读取到卡ID", Toast.LENGTH_SHORT).show();
}
}
private String getCardId(Intent intent) {
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] bytesId = tagFromIntent.getId();// 获取id数组
return byteArrayToHexString(bytesId);
}
private static String byteArrayToHexString(byte[] bytesId) { //Byte数组转换为16进制字符串
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;
}
}
这里只是把NFC卡的ID拿了出来。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bxy.nfc">
<!-- 一定要注意声明权限 -->
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.NfcReadActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<!-- 这里要匹配之前写入卡里的数据 -->
<data
android:host="ext"
android:pathPrefix="/com.bxy.nfc:nfc"
android:scheme="vnd.android.nfc" />
</intent-filter>
</activity>
<activity
android:name=".activity.NfcWriteActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</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/techs" />
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
techs.xml
<?xml version="1.0" encoding="utf-8"?>
<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.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.MifareClassic</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
如果希望只写入一次,可以在写入成功后调用
Ndef.makeReadOnly()
或
format.formatReadOnly()
如果有什么建议或问题可以在下面留言,大家一起讨论。