开发者社区> 凌浩雨> 正文

Android NFC(近程通信)使用

简介: 添加权限 1. 读写文本 activity_main.xml MainActivity.java public class MainActivity extends AppCompatActivity { ...
+关注继续查看

添加权限

    <uses-permission android:name="android.permission.NFC" />

1. 读写文本

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入要写入的文本"/>

    <TextView
        android:hint="卡中内容"
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    /**写入内容的文本框*/
    private EditText mEditText;
    /**显示NFC卡中的内容*/
    private TextView mTextView;

    /**NFC相关*/
    /**NFC适配器*/
    private NfcAdapter mNfcAdapter;
    /**延时意图*/
    private PendingIntent mPendingIntent;
    /**检测的卡类型*/
    private static String[][] sTechArr = null;
    /**意图过滤器*/
    private static IntentFilter[] sFilters = null;
    static {
        try {
            sTechArr = new String[][]{
                    {IsoDep.class.getName()}, {NfcV.class.getName()}, {NfcF.class.getName()},
                    {MifareUltralight.class.getName()}, {NfcA.class.getName()}
            };
            // 如果这里不设置过滤器,就必须要在AndroidManifest.xml中为MainActivity设置过滤器
            // 这里的ACTION在NfcAdapter中有四个选中,个人认为是在设置响应优先级
            /**
             *
             <intent-filter>
                 <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="text/plain" />
             </intent-filter>
             */
            sFilters = new IntentFilter[]{
                    new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED, "*/*")
            };
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initNfc();
        // 判断当前手机是否支持NFC
        if (null == mNfcAdapter) {
            // 如果为null,则为不支持。
            Toast.makeText(this, "当前设备不支持NFC功能!", Toast.LENGTH_SHORT).show();
            return;
        }
    }

    /**
     * 初始化NFC
     */
    private void initNfc() {
        // 1. 初始化Nfc适配器
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        // 2. 初始化延时意图
        mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 3. 允许后台唤醒--此处是指应用处于不可见的状态时,将卡片贴近手机时,唤醒当前App。
        // 如果sFilters为null,则为不过滤任何意图;如果sTechArr为null时,则为接收所有类型的卡片。
        mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, sFilters, sTechArr);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 4. 禁止后台唤醒
        mNfcAdapter.disableForegroundDispatch(this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 5. 重写OnIntent方法, 读取到的数据存储在intent中
        // 获取到意图中的Tag数据
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//        TAG: Tech [android.nfc.tech.NfcV, android.nfc.tech.Ndef]
        Log.e(TAG, "onNewIntent: " + tag);
        // 此处判断输入框mEditText的内容是否为空,如果为空,则为读取数据;如果不为空,则为写入数据
        if (TextUtils.isEmpty(mEditText.getText().toString())) {
            // 数据为空,读取数据
            readData(tag);
        } else {
            // 数据不为空,写入数据
            writeData(tag);
        }

    }

    /**
     * 读取数据
     * @param tag Tag
     */
    private void readData(Tag tag) {
        // 从Tag中获取Ndef信息
        Ndef ndef = Ndef.get(tag);
        try {
            // 连接
            ndef.connect();
//            readData: type: android.ndef.unknown, size: 250, message: NdefMessage [NdefRecord tnf=1 type=54 payload=027A68E4BDA0E5A5BD]
            Log.e(TAG, "readData: type: " + ndef.getType() + ", size: " + ndef.getMaxSize() + ", message: " + ndef.getNdefMessage().toString());
            // 获取NdefMessage消息
            NdefMessage ndefMessage = ndef.getNdefMessage();
            // 获取NdefRecord记录
            //ndefMessage.getRecords();
            // 遍历数组
            StringBuilder stringBuilder = new StringBuilder();
            // 文本
            String text;
            for (NdefRecord ndefRecord : ndefMessage.getRecords()) {
                text = parseNdefRecord(ndefRecord);
                if (!TextUtils.isEmpty(text)) {
                    stringBuilder.append(text);
                }
            }
            // 设置文本
            mTextView.setText(stringBuilder.toString());
        } catch (IOException | FormatException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析NdefRecord数据
     * @param ndefRecord NdefRecord记录
     */
    private String parseNdefRecord(NdefRecord ndefRecord) {
        // 判断是否为文本格式数据
        if (NdefRecord.TNF_WELL_KNOWN != ndefRecord.getTnf()) {
            return "";
        }
        if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
            return "";
        }

        try {
            // 获得字节流
            byte[] payload = ndefRecord.getPayload();
            // 获得编码格式
            String textEncoding = ((payload[0] & 0x80) == 0) ? "utf-8" : "utf-16";
            // 获得语言编码长度
            int languageCodeLength = payload[0] & 0x3f;
            // 语言编码
            String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
            // 获取文本
            return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength -1, textEncoding);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 写数据
     * @param tag Tag
     */
    private void writeData(Tag tag) {
        // 创建一个NdefMessage消息
        NdefMessage ndefMessage = new NdefMessage(createTextRecord(mEditText.getText().toString()));
        // 写入Tag
        if (writeTag(ndefMessage, tag)) {
            mEditText.setText("");
            Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "写入失败", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 将NdefMessage消息写入Tag
     * @param ndefMessage Ndef消息
     * @param tag Tag
     */
    private boolean writeTag(NdefMessage ndefMessage, Tag tag) {
        try {
            // 获取Ndef
            Ndef ndef = Ndef.get(tag);
            // 连接
            ndef.connect();
            // 写入数据
            ndef.writeNdefMessage(ndefMessage);
            return true;
        } catch (IOException | FormatException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 创建文本信息
     * @param text 要写入的文本信息
     */
    private NdefRecord createTextRecord(String text) {
        // 设置语言
        byte[] langBytes = Locale.CHINA.getLanguage().getBytes(Charset.forName("US-ASCII"));
        // 设置编码
        Charset utfEncoding = Charset.forName("UTF-8");
        // 要写入的二进制数据
        byte[] textBytes = text.getBytes(utfEncoding);
        int utfBit = 0;
        // 将语言长度转化对应的字符
        char status = (char)(utfBit + langBytes.length);
        // 创建一个大小为 1(即语言长度转化对应的字符) + 语言字节长度 + 文本字节长度的字节数组
        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, langBytes.length + 1, textBytes.length);
        // 创建一个NdefRecord记录
        return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
    }

    /**
     * 初始化布局
     */
    private void initView() {
        mEditText = (EditText) this.findViewById(R.id.editText);
        mTextView = (TextView) this.findViewById(R.id.textView);
    }
}

2. NFC读写Uri

对NFC的配置信息相同,此处只是更改读取与写入的操作。

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 5. 重写OnIntent方法, 读取到的数据存储在intent中
        // 获取到意图中的Tag数据
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//        TAG: Tech [android.nfc.tech.NfcV, android.nfc.tech.Ndef]
        Log.e(TAG, "onNewIntent: " + tag);
        // 此处判断输入框mEditText的内容是否为空,如果为空,则为读取数据;如果不为空,则为写入数据
        if (TextUtils.isEmpty(mEditText.getText().toString())) {
            // 数据为空,读取数据
            readData(tag);
        } else {
            // 数据不为空,写入数据
            writeData(tag);
        }

    }

    /**
     * 写入Uri
     * http://www.baidu.com/
     * @param tag
     */
    private void writeData(Tag tag) {
        // 创建一个Uri
        Uri uri = Uri.parse(mEditText.getText().toString());
        // 创建一个NdefMessage格式数据
        NdefMessage ndefMessage = new NdefMessage(NdefRecord.createUri(uri));
        if (writeTag(ndefMessage, tag)) {
            Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();
            mEditText.setText("");
        }
    }

    /**
     * 写入数据
     * @param ndefMessage Ndef格式数据
     * @param tag Tag
     * @return true,写入成功;false,写入失败
     */
    private boolean writeTag(NdefMessage ndefMessage, Tag tag) {
        // 计算写入数据的长度
        int size = ndefMessage.toByteArray().length;
        try {
            // 获取Ndef
            Ndef ndef = Ndef.get(tag);
            if (null != ndef) {
                // 连接NFC卡片
                ndef.connect();
                // 判断NFC芯片是否可写
                if (!ndef.isWritable()) {
                    Toast.makeText(this, "卡片不可写", Toast.LENGTH_SHORT).show();
                    return false;
                }
                // 判断要写入的数据是否大于NFC芯片最大字节数
                if (ndef.getMaxSize() < size) {
                    Toast.makeText(this, "写入数据过大", Toast.LENGTH_SHORT).show();
                    return false;
                }
                ndef.writeNdefMessage(ndefMessage);
                return true;
            } else {
                // 获取一个格式化工具
                NdefFormatable formatable = NdefFormatable.get(tag);
                // 判断是否为空
                if (null != formatable){
                    // 连接
                    formatable.connect();
                    // 格式化并写入数据
                    formatable.format(ndefMessage);
                    Toast.makeText(this, "格式化成功,并成功写入数据", Toast.LENGTH_SHORT).show();
                }else {
                    Toast.makeText(this, "无法格式化", Toast.LENGTH_SHORT).show();
                }
            }
        } catch (IOException | FormatException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 读取数据
     * @param tag Tag
     */
    private void readData(Tag tag) {
        try {
            // 获取Ndef
            Ndef ndef = Ndef.get(tag);
            // 连接
            ndef.connect();
            Log.e(TAG, "readData: type: " + ndef.getType() + ", size: " + ndef.getMaxSize()
                    + ", message: " + ndef.getNdefMessage().toString());
            // 获取Ndef格式数据
            NdefMessage ndefMessage = ndef.getNdefMessage();
            // 获取信息中的记录
            NdefRecord[] records = ndefMessage.getRecords();
            // 判断是否有数据
            if (null != records && records.length > 0) {
                StringBuilder stringBuilder = new StringBuilder();
                // 迭代
                for (NdefRecord ndefRecord : records) {
                    Uri uri = ndefRecord.toUri();
                    if (null != uri) {
                        stringBuilder.append(uri.toString());
                    }
                }
                String string = stringBuilder.toString();
                mTextView.setText(string);
                if (string.startsWith("http://")) {
                    // 跳转网页
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(stringBuilder.toString())));
                }
            }
            // 断开连接
            ndef.close();
        } catch (IOException | FormatException e) {
            e.printStackTrace();
        }
    }

3. 读写非Ndef格式数据


    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 5. 重写OnIntent方法, 读取到的数据存储在intent中
        // 获取到意图中的Tag数据
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        // 可打印出当前NFC芯片支持的NFC类型
//        onNewIntent: TAG: Tech [android.nfc.tech.NfcA, android.nfc.tech.MifareUltralight, android.nfc.tech.NdefFormatable]
        Log.e(TAG, "onNewIntent: " + tag);
        // 此处判断输入框mEditText的内容是否为空,如果为空,则为读取数据;如果不为空,则为写入数据
        if (TextUtils.isEmpty(mEditText.getText().toString())) {
            // 数据为空,读取数据
            readData(tag);
        } else {
            // 数据不为空,写入数据
            writeData(tag);
        }
    }

    /**
     * 写数据
     * @param tag Tag
     */
    private void writeData(Tag tag) {
        // 获取到MifareUltralight
        MifareUltralight ultralight = MifareUltralight.get(tag);
        try {
            // 连接
            ultralight.connect();
            // Write 1 page (4 bytes).
            // 往每一页写数据,最多写四个字节
            ultralight.writePage(4,"中国".getBytes(Charset.forName("gb2312")));
            ultralight.writePage(5,"美国".getBytes(Charset.forName("gb2312")));
            ultralight.writePage(6,"英国".getBytes(Charset.forName("gb2312")));
            ultralight.writePage(7,"法国".getBytes(Charset.forName("gb2312")));
            Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();
            mEditText.setText("");
            // 关闭连接
            ultralight.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 读数据
     * @param tag Tag
     */
    private void readData(Tag tag) {
        // 获取连接
        MifareUltralight ultralight = MifareUltralight.get(tag);
        try {
            // 连接
            ultralight.connect();
            byte[] bytes = ultralight.readPages(4);
            mTextView.setText(new String(bytes, Charset.forName("gb2312")));
            // 断开连接
            ultralight.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

4. AndroidBeam打开其他手机应用

/**
 * 测试方法, 将两部手机靠近,AndroidBeam接触时,两部手机界面同时缩小居中,轻触处理数据的手机一方,即可完成发送,
 * 放松数据期间手机不能离开,离开之后即会断开连接,终止发送。
 */
public class MainActivity extends AppCompatActivity implements
        NfcAdapter.CreateNdefMessageCallback, NfcAdapter.OnNdefPushCompleteCallback{
    private static final String TAG = "MainActivity";
    private NfcAdapter mNfcAdapter;
    private PendingIntent mPendingIntent;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);

        // 设置发送消息的回调
        mNfcAdapter.setNdefPushMessageCallback(this, this);
        // 设置发送完成回调
        mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Toast.makeText(this, "NFC靠近", Toast.LENGTH_SHORT).show();
    }

    /**
     * 当有NFC靠近时调用该方法
     */
    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        // 将"com.mazaiting.nfc2"更换为另一部手机中已存在的应用包名
        return new NdefMessage(NdefRecord.createApplicationRecord("com.mazaiting.nfc2"));
    }

    /**
     * 数据发送完成时调用方法
     */
    @Override
    public void onNdefPushComplete(NfcEvent event) {
        Toast.makeText(this, "发送完成", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,
                null,null);
    }

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

AndroidBeam接触时如下图:


img_fa9f34666239789f0b4d0af9bd14b0ad.png
图1.png

5. AndroidBeam传送大文件

1). 添加权限,一定不能忘记

    <uses-permission android:name="android.permission.NFC"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2). 在assets文件夹放一张图片或者文件,或者直接指定手机中的文件地址。

public class MainActivity extends AppCompatActivity implements
        NfcAdapter.CreateBeamUrisCallback, NfcAdapter.OnNdefPushCompleteCallback {
    private static final String TAG = "MainActivity";
    private NfcAdapter mNfcAdapter;
    private PendingIntent mPendingIntent;

    private final String targetFilename = "/sdcard/image.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        mPendingIntent = PendingIntent.getActivity(this,0,
                new Intent(this,getClass()),0);
        try{
            InputStream is = getResources().getAssets().open("image.jpg");
            FileOutputStream fos = new FileOutputStream(targetFilename);
            byte[] buffer = new byte[1024];
            int len = 0;
            while((len = is.read(buffer))!=-1){
                fos.write(buffer,0,len);
            }
            fos.close();
            is.close();
        }catch (Exception e){
            e.printStackTrace();
        }
        // 设置回调
        mNfcAdapter.setBeamPushUrisCallback(this, this);
        mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
    }

    @Override
    public Uri[] createBeamUris(NfcEvent event) {
        Uri[] uris = new Uri[1];
        Uri uri =  Uri.parse("file://" + targetFilename);
        uris[0]=uri;
        return uris;
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Toast.makeText(this, "接触", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,
                null,null);
    }

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

    @Override
    public void onNdefPushComplete(NfcEvent event) {
        // 另一部手机接收的文件位置--/storage/emulated/0/beam/传输的文件名
        Log.e(TAG, "onNdefPushComplete: ");
    }
}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
《Android NFC开发实战详解》——导读
本节书摘来自异步社区《Android NFC开发实战详解》一书中的目录,作者 赵波,更多章节内容可以访问云栖社区“异步社区”公众号查看
1601 0
Android官方开发文档Training系列课程中文版:通过NFC共享文件之从其它设备接收文件
原文地址:http://android.xsoftlab.net/training/beam-files/receive-files.html Android Beam文件传输系统会将文件拷贝到接收设备的指定目录中。
869 0
Android官方开发文档Training系列课程中文版:通过NFC共享文件之发送文件到另一台设备
原文地址:http://android.xsoftlab.net/training/beam-files/index.html 导言 Android允许你通过Android Beam文件传输特性在两台设备之间传送大文件。
777 0
+关注
凌浩雨
毕业于贵州大学大数据与信息工程学院,目前是一名移动端工程师,就职于北京乾元大通信息技术有限责任公司。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Android组件化实现
立即下载
蚂蚁聚宝Android秒级编译—— Freeline
立即下载
Android插件化:从入门到放弃
立即下载