上次记录NFC知识时,还处在研究状态,现在项目的第一阶段开发已经完成。上篇Android之NFC开发,简单介绍了一些知识,也是对未知信息的研究,总要了解一点来龙去脉,省的心发慌。这篇文章总结自己的项目中遇到的问题,和实现基本的NFC读写操作,可以满足一般性的开发需求。

1、首先:在AndroidManifest.xml配置文件里增加NFC权限

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

2.NFC感应操作由NFC适配器来进行处理的:

编写在Activity中onCreate代码:

private NfcAdapter mNfcAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
        if (mNfcAdapter == null) {
            Toast.makeText(this, "该设备不支持nfc", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
        if (!mNfcAdapter.isEnabled()) {
            startActivity(new Intent("android.settings.NFC_SETTINGS"));
            Toast.makeText(this, "设备未开启nfc", Toast.LENGTH_SHORT).show();
        }
    }

3.在Activity重写onNewIntent()方法:

//最主要的方法,监听到NFC信息
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())
                || NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) 
                || NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {

            iTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            tagInfo = "";

            Parcelable[] data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

            if (data != null) {
                try {
                    for (int i = 0; i < data.length; i++) {
                        NdefRecord[] recs = ((NdefMessage) data[i]).getRecords();
                        for (int j = 0; j < recs.length; j++) {
                            if ((recs[j].getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(recs[j].getType(), NdefRecord.RTD_TEXT))
                                    || (recs[j].getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(recs[j].getType(), NdefRecord.RTD_URI))) {
                                /*
                                 *读取普通的文本(即NdefRecord.RTD_TEXT)
                                 *或者是网址(即NdefRecord.RTD_URI)
                                 */
                                byte[] payload = recs[j].getPayload();
                                String textEncoding = "UTF-16";
                                if ((payload[0] & 0200) == 0) {
                                    textEncoding = "UTF-8";
                                }

                                int langCodeLen = payload[0] & 0077;

                                String s = new String(payload, langCodeLen, payload.length - langCodeLen, textEncoding);
                                if (!"".equals(s) && !"null".equals(s)) {
                                    tagInfo += s;
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    Log.e("TagDispatch", e.toString());
                }
                Toast.makeText(this, "检测到卡,并成功存储信息!", Toast.LENGTH_LONG).show();
            }
        }
    }

4.重写其他方法:

@Override
    protected void onPause() {
        super.onPause();
        mNfcAdapter.disableForegroundDispatch(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
                getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        IntentFilter[] intentFilters = new IntentFilter[]{};
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null);
    }

5.写入NFC卡信息:

/**
     * 将普通的文本字符串写入标签中
     *
     * @param messageText 要写入标签的文本字符串
     * @param tag         要写入的标签
     * @return
     */
    boolean writeTextTag(String messageText, Tag tag) {

        //创建NdefMessage对象和NdefRecord对象
        NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createTextRecord(messageText)});
        int size = ndefMessage.toByteArray().length;

        try {
            Ndef ndef = Ndef.get(tag);
            if (ndef != null) {
                //允许对标签进行IO操作
                ndef.connect();
                if (!ndef.isWritable()) {
                    Toast.makeText(this, "NFC Tag是只读的!", Toast.LENGTH_LONG).show();
                    return false;

                }
                if (ndef.getMaxSize() < size) {
                    Toast.makeText(this, "NFC Tag的空间不足!", Toast.LENGTH_LONG).show();
                    return false;
                }

                //向标签写入数据
                ndef.writeNdefMessage(ndefMessage);
                Toast.makeText(this, "已成功写入数据!", Toast.LENGTH_LONG).show();
                return true;

            } else {
                //获取可以格式化和向标签写入数据NdefFormatable对象
                NdefFormatable format = NdefFormatable.get(tag);
                //向非NDEF格式或未格式化的标签写入NDEF格式数据
                if (format != null) {
                    try {
                        //允许对标签进行IO操作
                        format.connect();
                        format.format(ndefMessage);
                        Toast.makeText(this, "已成功写入数据!", Toast.LENGTH_LONG).show();
                        return true;

                    } catch (Exception e) {
                        Toast.makeText(this, "写入NDEF格式数据失败!", Toast.LENGTH_LONG).show();
                        return false;
                    }
                } else {
                    Toast.makeText(this, "NFC标签不支持NDEF格式!", Toast.LENGTH_LONG).show();
                    return false;

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } catch (FormatException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 创建一个NdefRecord对象,用于向标签写入普通的文本数据
     *
     * @param text
     * @return
     */
    public NdefRecord createTextRecord(String text) {
        //生成语言编码的字节数组,中文编码
        byte[] langBytes = Locale.CHINA.getLanguage().getBytes(Charset.forName("US-ASCII"));

        //将要写入的文本以UTF_8格式进行编码
        Charset utfEncoding = Charset.forName("UTF-8");

        //由于已经确定文本的格式编码为UTF_8,所以直接将payload的第1个字节的第7位设为0
        byte[] textBytes = text.getBytes(utfEncoding);
        int utfBit = 0;

        //定义和初始化状态字节
        char status = (char) (utfBit + langBytes.length);

        //创建存储payload的字节数组
        byte[] data = new byte[1 + langBytes.length + textBytes.length];

        //设置状态字节
        data[0] = (byte) status;

        //设置语言编码
        System.arraycopy(langBytes, 0, data, 1, langBytes.length);

        //设置实际要写入的文本
        System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);

        //根据前面设置的payload创建NdefRecord对象
        NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);

        return record;
    }

6.加密

以上代码直接拷贝就可以读写了,但是这是针对没有密码并且是ACTION_NDEF_DISCOVERED类型的数据。面对ACTION_TECH_DISCOVERED类型数据,上面的读取的方法就不可用了。

以下是读写ACTION_TECH_DISCOVERED类型数据,并且带有密码的代码,一般的初始密码是:

//默认为:(12个F或0)
FFFFFFFFFFFF
//或者
000000000000
//Nfc卡的密码
    private byte[] key = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())
                || NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) || NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {

            Tag iTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            MifareClassic mfc = MifareClassic.get(iTag);
            try {
                if (null != mfc) {
                    //链接NFC
                    mfc.connect();
                    //获取TAG中包含的扇区数
                    int sectorCount = mfc.getSectorCount();
                    for (int j = 0; j < sectorCount; j++) {

                        //使用KeyA验证扇区
                        auth = mfc.authenticateSectorWithKeyA(j, key);
                        //验证通过
                        if (auth) {
                            //读取第二块内容
                            byte[] data = mfc.readBlock(2);

                            //转换为十六进制的字符串
                            String s = StringUtil.bytesToHexString(data);
                            if (null != s && !"".equals(s) && !"null".equals(s)&&j == 0) {
                                tagInfo = s;
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if(!TextUtils.isEmpty(tagInfo)){
            }
        }
    }

7.对于尚未打开APP进行读写的处理,需要注意。