Android的NFC教程(一)



最近在学习NFC相关的东西,看完相关资料之后感觉云里雾里,不知如何下手,有非常多的困惑,废了九牛二虎之力才摸到一点皮毛。现在写出来与大家共勉,文章后面也有一些疑问,希望大家不吝赐教。

我是做智能卡的,至少在现在的同事是这样子理解的,心里很多次吐槽,哥是做服务器的,对卡不熟,只是做的项目都和智能卡相关,为了避免被人忽悠,多了解了一些智能卡的规范而已,难道这也有错吗,呵呵。

今天的这篇文章我想写一个NFCHelloWorld程序,大家只要按照我这篇文章把项目跑起来,发两条指令就大功告成,其它的以后再慢慢普及。

先说一下测试的工具或者材料,在网上能看到很多NFC的例子写的非常好,第一次看到的时候非常激动,当然也仅限于第一次。为什么呢?原因有两个,一是程序不完整,copy到项目中根本运行不起来,我是小白,你让我怎么办;二是网上的例子除了提到用NFC手机外,对于智能卡语焉不详,让我运行起来也不能测试,非常痛苦,感觉美女都让猪拱了。

那么我们这篇文章中用到的工具和材料是什么呢?

一是Android NFC手机,怎么识别?最简单的一种方法,在NFC手机的“系统设置”里有NFC开关,有这一项的就是NFC手机。

二是一张银行卡,怎么识别?2015年左右(或者2014年开始,不同银行不一样),在银行开户的银行卡大部分都是智能卡,在卡上印有Quickpass的字样,有Quickpass字样的银行卡就是本文的测试卡。


接着说一下程序实现的逻辑和步骤:

  1. 初始化非接触参数。

  2. 再在NFC信号来袭时,获得该对象。

  3. 指定该对象的通信协议。

  4. 打开连接,准备和卡交互。

  5. 指令交互。

  6. 关闭连接。

  7. Activity的生命周期中对NFC做一些配置。也是一些非接参数的初始化工作。

下面是代码:

package com.example.nfcdemo;

import java.io.IOException;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.NfcF;
import android.nfc.tech.NfcV;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.Menu;

import com.example.nfcdemo.util.Utils;

@SuppressLint("NewApi")
public class MainActivity extends Activity {

	final String TAG = "NFCDemo";
	NfcAdapter nfcAdapter = null;
	PendingIntent pendingIntent = null;

	IntentFilter[] FILTERS = null;
	String[][] TECHLISTS = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		try {
			Log.i(TAG, "start....");
			FILTERS = new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED, "*/*") };
			TECHLISTS = new String[][] { { IsoDep.class.getName() }, { NfcV.class.getName() }, { NfcF.class.getName() } };
			// 第一步初始化
			nfcAdapter = NfcAdapter.getDefaultAdapter(this);
			pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

			onNewIntent(getIntent());
			Log.i(TAG, "end....");
		} catch (MalformedMimeTypeException e1) {
			e1.printStackTrace();
		}
	}

	@Override
	protected void onNewIntent(Intent intent) {
		try {
			//第二步,在信号来袭时,获得该对象
			final Parcelable p = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);

			if (p == null) {
				// 打印nfcAdapter状态
				Log.i(TAG, "p==null");
				if (nfcAdapter == null) {
					Log.i(TAG, "nfcAdapter is null");
				} else if (nfcAdapter.isEnabled()) {
					Log.i(TAG, "nfcAdapter is enabled");
				} else {
					Log.i(TAG, "nfcAdapter is disabled");
				}
				return;
			}
			//第三部,指定通信协议  
			final IsoDep isodep = IsoDep.get((Tag) p);
			Log.i(TAG, "connect...");
			//第四步,打开连接,准备写卡
			isodep.connect();
			Log.i(TAG, "connect ok...");
			do {
				//第五步,发送指令 1.选择主安全域;2外部认证
				Log.i(TAG, "send 00A40400...");
				byte[] b = isodep.transceive(Utils.bytesFromHexStringNoBlank("00A40400"));
				Log.i(TAG, "===TEST BY MXW===apdu cmd return:" + Utils.toHexStringNoBlank(b));

				Log.i(TAG, "send 80500000081122334455667788...");
				b = isodep.transceive(Utils.bytesFromHexStringNoBlank("80500000081122334455667788"));
				Log.i(TAG, "===TEST BY MXW===apdu cmd return:" + Utils.toHexStringNoBlank(b));

				break;
			} while (true);

			//第六步,关闭连接 
			Log.i(TAG, "close...");
			isodep.close();
			Log.i(TAG, "close ok...");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	protected void onResume() {
		Log.i(TAG, "===TEST BY MXW===onResume");
		super.onResume();

		// if (nfcAdapter != null)
		// nfcAdapter.enableForegroundDispatch(this, pendingIntent, FILTERS,
		// TECHLISTS);
		
		//第七步,关闭NFC
		if (nfcAdapter != null)
			nfcAdapter.disableForegroundDispatch(this);

	}

	@Override
	protected void onPause() {
		Log.i(TAG, "===TEST BY MXW===onPause");
		super.onPause();

		// if (nfcAdapter != null)
		// nfcAdapter.disableForegroundDispatch(this);
		
		//第七步,启动NFC
		if (nfcAdapter != null)
			nfcAdapter.enableForegroundDispatch(this, pendingIntent, FILTERS, TECHLISTS);
	}

}

工具类的代码:

package com.example.nfcdemo.util;

public class Utils {
	
    private static final char[] HEX_DIGITS =
    {
	'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
    };
    private static int fromDigit(char ch) {
        if (ch >= '0' && ch <= '9')
            return ch - '0';
        if (ch >= 'A' && ch <= 'F')
            return ch - 'A' + 10;
        if (ch >= 'a' && ch <= 'f')
            return ch - 'a' + 10;
        throw new IllegalArgumentException("invalid hex digit '" + ch + "'");
    }
    
   
    
    public static String toHexStringNoBlank( byte[] ba ) {
        int length = ba.length;
        char[] buf = new char[length * 2];
        for (int i = 0, j = 0, k; i < length; ) {
            k = ba[i++];
            buf[j++] = HEX_DIGITS[(k >> 4) & 0x0F];
            buf[j++] = HEX_DIGITS[ k       & 0x0F];
        }
        return new String(buf, 0, buf.length);
    }
    
      
    public static byte[] bytesFromHexStringNoBlank(String hex) throws NumberFormatException  {
        if (hex.length() == 0) return null;
        String myhex = hex;
        int len = myhex.length();
        if ((len % 2) != 0) throw new NumberFormatException();
        byte[] buf = new byte[len / 2];
        int i = 0, j = 0;
        while (i < len) {
            try {
                buf[j++] = (byte) ((fromDigit(myhex.charAt(i++)) << 4) |
                        fromDigit(myhex.charAt(i++)));
            } catch (IllegalArgumentException e) {
                throw new NumberFormatException();
            }
        }
        return buf;
    }
    
    
    
}

下面是项目的配置文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.nfcdemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.NFC" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <uses-permission android:name="org.simalliance.openmobileapi.SMARTCARD" />
    <uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <!-- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> -->

    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.nfcdemo.MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
</span>

把上面的代码Copy到项目中在NFC的手机上运行后,把银行卡贴在手机的NFC感应区,就能在LogCat的后台日志看到指令的下发和响应的接收。

以上的代码有两个问题,其中一个是疑问:

一是项目的配置文件申请的权限有点多,有一些没有用到,大家可以自己删减。

二是代码中的第七步,在onResumeonPause中对NFC的设置和很多教程相反。通过对日志的跟踪发现,本文例子在NFC手机上运行时,当有NFC信号时onPauseonNewIntentonResume三个方法依次执行,如果其他教程中的说明进行设置,则第二步中获得的对象为null,导致后续程序不能执行。

第二个问题也是我的疑问,目前还在探究当中,希望大家不吝指教。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值