Beaming NDEF Messages to Other Devices
Android Beam allows simple peer-to-peer data exchange between two Android-powered devices. The application that wants to beam data to another device must be in the foreground and the device receiving the data must not be locked. When the beaming device comes in close enough contact with a receiving device, the beaming device displays the "Touch to Beam" UI. The user can then choose whether or not to beam the message to the receiving device.
安卓Beam允许在两台Android支持设备进行点对点数据交换,应用想要beam数据给另一台设备(必须在前端),收到数据的设备必须不能被锁定,当beam设备与接受设备足够接近时,beam设备展示了“Touch to Beam”的界面,用户可以选择是否发送信息给接受设备。
Note: Foreground NDEF pushing was available at API level 10, which provides similar functionality to Android Beam. These APIs have since been deprecated, but are available to support older devices. See enableForegroundNdefPush() for more information.
注意:前台NDEF推送在API10提供了和安卓Beam相似的功能,这些API已经被弃用了,但是仍然支持老的设备,可以从enableForegroundNdefPush()查看更多信息
You can enable Android Beam for your application by calling one of the two methods:
你能够打开应用的安卓Beam功能通过调用如下两个方法之一:
setNdefPushMessage(): Accepts an NdefMessage to set as the message to beam. Automatically beams the message when two devices are in close enough proximity.
setNdefPushMessageCallback(): Accepts a callback that contains a createNdefMessage() which is called when a device is in range to beam data to. The callback lets you create the NDEF message only when necessary.
An activity can only push one NDEF message at a time, so setNdefPushMessageCallback() takes precedence over setNdefPushMessage() if both are set. To use Android Beam, the following general guidelines must be met:
一个Activity只能推送一个NDEF信息在一个时刻,所以setNdefPushMessageCallback() 优先于setNdefPushMessage() 如果都设置的话,为了使用android Beam,如下一般原则必须满足:
The activity that is beaming the data must be in the foreground. Both devices must have their screens unlocked.
activity 正在推送时都必须在前台,而且两个设备的屏幕处于解锁状态
You must encapsulate the data that you are beaming in an NdefMessage object.
你必须封装成NdefMessage对象来beam
The NFC device that is receiving the beamed data must support the com.android.npp NDEF push protocol or NFC Forum's SNEP (Simple NDEF Exchange Protocol). The com.android.npp protocol is required for devices on API level 9 (Android 2.3) to API level 13 (Android 3.2). com.android.npp and SNEP are both required on API level 14 (Android 4.0) and later.
NFC设备收到beam过来的数据必须com.android.npp的NDEF推送协议或者Forum's SNEP协议,com.android.npp协议要求设备在API level 9 (Android 2.3) to API level 13 (Android 3.2),SNEP要求两台设备都在Android 4.0以上。
Note: If your activity enables Android Beam and is in the foreground, the standard intent dispatch system is disabled. However, if your activity also enables foreground dispatching, then it can still scan tags that match the intent filters set in the foreground dispatching.
注意:如果你的Activity打开了Android Beam而且处于前台,则标准的intent分发系统是不可用的,如果你的activity还打开了前台调度,它仍然能够扫描通过前台调度系统匹配到的标签。
To enable Android Beam:
1.Create an NdefMessage that contains the NdefRecords that you want to push onto the other device.
创建一个包含了你想要推送给其他设备的NdefRecords的NdefMessage。
2.Call setNdefPushMessage() with a NdefMessage or call setNdefPushMessageCallback passing in a NfcAdapter.CreateNdefMessageCallback object in the onCreate() method of your activity. These methods require at least one activity that you want to enable with Android Beam, along with an optional list of other activities to activate.
In general, you normally use setNdefPushMessage() if your Activity only needs to push the same NDEF message at all times, when two devices are in range to communicate. You use setNdefPushMessageCallback when your application cares about the current context of the application and wants to push an NDEF message depending on what the user is doing in your application.
调用有一个NdefMessage的setNdefPushMessage() ,或者通过NfcAdapter.CreateNdefMessageCallback对象在调用setNdefPushMessageCallback回调在onCreate() 方法在你的Activity,这些方法要求至少一个Activity打开了Android Beam,伴随着其他活动来激活的可选列表。
一般,你总是需要推送NDEF message时调用setNdefPushMessage()在两个设备在范围内通信。如果使用setNdefPushMessageCallback回调则关心当前应用的上下文和你根据用户的操作想要推送的NDEF数据在你的应用。
The following sample shows how a simple activity calls NfcAdapter.CreateNdefMessageCallback in the onCreate() method of an activity (see AndroidBeamDemo for the complete sample). This example also has methods to help you create a MIME record:
下面简单的展示了一个简单的Activity调用NfcAdapter.CreateNdefMessageCallback在onCreate() ,完整的例子查看see AndroidBeamDemo for the complete sample:
package com.example.android.beam; import android.app.Activity; import android.content.Intent; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.nfc.NfcAdapter.CreateNdefMessageCallback; import android.nfc.NfcEvent; import android.os.Bundle; import android.os.Parcelable; import android.widget.TextView; import android.widget.Toast; import java.nio.charset.Charset; public class Beam extends Activity implements CreateNdefMessageCallback { NfcAdapter mNfcAdapter; TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView textView = (TextView) findViewById(R.id.textView); // Check for available NFC Adapter mNfcAdapter = NfcAdapter.getDefaultAdapter(this); if (mNfcAdapter == null) { Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show(); finish(); return; } // Register callback mNfcAdapter.setNdefPushMessageCallback(this, this); } @Override public NdefMessage createNdefMessage(NfcEvent event) { String text = ("Beam me up, Android!\n\n" + "Beam Time: " + System.currentTimeMillis()); NdefMessage msg = new NdefMessage( new NdefRecord[] { createMime( "application/vnd.com.example.android.beam", text.getBytes()) /** * The Android Application Record (AAR) is commented out. When a device * receives a push with an AAR in it, the application specified in the AAR * is guaranteed to run. The AAR overrides the tag dispatch system. * You can add it back in to guarantee that this * activity starts when receiving a beamed message. For now, this code * uses the tag dispatch system. */ //,NdefRecord.createApplicationRecord("com.example.android.beam") }); return msg; } @Override public void onResume() { super.onResume(); // Check to see that the Activity started due to an Android Beam if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { processIntent(getIntent()); } } @Override public void onNewIntent(Intent intent) { // onResume gets called after this to handle the intent setIntent(intent); } /** * Parses the NDEF Message from the intent and prints to the TextView */ void processIntent(Intent intent) { textView = (TextView) findViewById(R.id.textView); Parcelable[] rawMsgs = intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES); // only one message sent during the beam NdefMessage msg = (NdefMessage) rawMsgs[0]; // record 0 contains the MIME type, record 1 is the AAR, if present textView.setText(new String(msg.getRecords()[0].getPayload())); } }Note that this code comments out an AAR, which you can remove. If you enable the AAR, the application specified in the AAR always receives the Android Beam message. If the application is not present, Google Play is started to download the application. Therefore, the following intent filter is not technically necessary for Android 4.0 devices or later if the AAR is used:
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="application/vnd.com.example.android.beam"/> </intent-filter>With this intent filter, the com.example.android.beam application now can be started when it scans an NFC tag or receives an Android Beam with an AAR of type com.example.android.beam, or when an NDEF formatted message contains a MIME record of type application/vnd.com.example.android.beam.
Android Beam
Android Beam:两部NFC设备靠近时,通过触摸一部NFC设备的屏幕,将数据推向另一部NFC设备。
可以传输文本(NDEF格式);也可传输文件(图像、文本文件等)。
文件数据量较大,会自动开启两个设备的蓝牙;若无蓝牙则选用其他方式,如WiFi。
消息点到点传输的实现
静态方法:在onCreat方法中调用如下方法:
NfcAdapter.setNdefPushMessage(NdefMessage message,Activity activity,Activity … activitys)
可以指定多个窗口,但一般指定一个窗口,不用指定后面的第3个参数
该方法直接推送一个NdefMessage对象,在推送之前要先创建该NdefMessage对象
动态方法:在onCreat方法中调用如下方法:
NfcAdapter.setNdefPushMessageCallback(CreateNdefMessageCallback callback,Activity activity,Activity … activitys)
该方法为一个或多个窗口指定一个回调函数,当两个NFC设备靠近并建立连接后,调用该回调方法(CreateNdefMessageCallback接口中包含这个方法)。
该方法原型:public NdefMessage createNdefMessage(NfcEvent event),NfcEvent对象封装了NFC相关信息。
该方法的返回值为用于推送的NdefMessage对象。
Uri
Uri:Uniform Resource Identifier,通用资源标识符,Android上可用的每种资源:HTML文档、图像、视频片段等。根据Uri可以找到某个资源文件。如:ContentProvider共享数据,生成数据的Uri格式共程序调用。
Uri由三部分组成:命名机制(scheme)+存放资源的主机名(authority)+资源自身的名称(path)
例如:所有联系人Uri:content://contacts/people
某个联系人Uri:content://contacts/people/5
文件点到点传输的实现
静态方法:在onCreat方法中调用如下方法:
NfcAdapter.setBeamPushUris(Uri[] uris,Activity activity)
可以直接推送1个多个Uri标示的文件,Uri的scheme必须是file或content,如:file:///sdcard/test.jpg
动态方法:在onCreat方法中调用如下方法:
NfcAdapter.setBeamPushUrisCallback(CreateBeamUrisCallback callback,Activity)
该方法为窗口指定一个回调函数,当两个NFC设备靠近并建立连接后,调用该回调方法(CreateBeamUrisCallback接口中包含这个方法)。
该方法原型:public Uri[] createBeamUris(NfcEvent event),NfcEvent对象封装了NFC相关信息。
该方法的返回值为1个或多个Uri,即1个或多个文件。
消息点到点传输的实现例子代码:
资源文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <EditText android:id="@+id/edittext_beam_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入要传输的文本"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:text="请将设备靠近其他NFC设备,别忘了触摸屏幕" android:textSize="16sp" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:src="@drawable/read_nfc_tag" /> </LinearLayout>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mobile.android.android.beam" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.NFC" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".AndroidBeamMainActivity" android:label="Android Beam" android:launchMode="singleTop" android:screenOrientation="portrait" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
package mobile.android.android.beam; import java.nio.charset.Charset; import java.util.Locale; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.nfc.NfcAdapter.CreateNdefMessageCallback; import android.nfc.NfcAdapter.OnNdefPushCompleteCallback; import android.nfc.NfcEvent; import android.os.Bundle; import android.os.Parcelable; import android.util.Log; import android.widget.EditText; import android.widget.Toast; //需要实现CreatNdefMessageCallback接口,OnNdefPushCompleteCallback接口 public class AndroidBeamMainActivity extends Activity implements CreateNdefMessageCallback,OnNdefPushCompleteCallback { private EditText mBeamText; private NfcAdapter mNfcAdapter; private PendingIntent mPendingIntent; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_android_beam); mBeamText = (EditText) findViewById(R.id.edittext_beam_text); mNfcAdapter = mNfcAdapter.getDefaultAdapter(this); mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); //指定推送消息的回调对象 mNfcAdapter.setNdefPushMessageCallback(this, this); //指定推送完成后的回调对象 mNfcAdapter.setOnNdefPushCompleteCallback(this, this); } //当推送消息方完成消息推送后调用该方法(屏幕缩小方) @Override public void onNdefPushComplete(NfcEvent event) { //Toast.makeText(this, "消息传输完成", Toast.LENGTH_LONG).show(); Log.d("message", "complete"); } //实现回调方法 @Override public NdefMessage createNdefMessage(NfcEvent event) { //获得文本框中输入的字符 String text = mBeamText.getText().toString().trim(); //如果未输入字符,则使用以下文本传送 if ("".equals(text)) { text = "Android Beam Test"; } //创建一个封装文本数据的NdefMessage对象,creatTextRecord方法用于创建封装文本的NdefRecord对象 NdefMessage msg = new NdefMessage(new NdefRecord[] { createTextRecord(text) }); return msg; } @Override public void onResume() { super.onResume(); if (mNfcAdapter != null) mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); } @Override public void onPause() { super.onPause(); if (mNfcAdapter != null) mNfcAdapter.disableForegroundDispatch(this); } @Override public void onNewIntent(Intent intent) { //接收到数据后进行处理 processIntent(intent); } public 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); 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); NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); return record; } //处理接收到的数据,将数据解析后并Toast出来 void processIntent(Intent intent) { Parcelable[] rawMsgs = intent .getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage msg = (NdefMessage) rawMsgs[0]; String text = TextRecord.parse(msg.getRecords()[0]).getText(); Toast.makeText(this, text, Toast.LENGTH_LONG).show(); } }
package mobile.android.android.beam; import java.io.UnsupportedEncodingException; import java.util.Arrays; import android.nfc.NdefRecord; public class TextRecord { private final String mText; private TextRecord(String text) { mText = text; } public String getText() { return mText; } // 将纯文本内容从NdefRecord对象中解析出来 public static TextRecord parse(NdefRecord record) { if(record.getTnf() != NdefRecord.TNF_WELL_KNOWN) return null; if(!Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) return null; try { byte[] payload = record.getPayload(); /* * payload[0] contains the "Status Byte Encodings" field, per the * NFC Forum "Text Record Type Definition" section 3.2.1. * * bit7 is the Text Encoding Field. * * if (Bit_7 == 0): The text is encoded in UTF-8 if (Bit_7 == 1): * The text is encoded in UTF16 * * Bit_6 is reserved for future use and must be set to zero. * * Bits 5 to 0 are the length of the IANA language code. */ String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16"; int languageCodeLength = payload[0] & 0x3f; String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); String text = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); return new TextRecord(text); } catch (UnsupportedEncodingException e) { // should never happen unless we get a malformed tag. throw new IllegalArgumentException(e); } } }
资源文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".NFCFileActivity" > </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mobile.android.nfcfile" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="17" /> <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".NFCFileActivity" android:label="@string/app_name" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
package mobile.android.nfcfile; import java.io.FileOutputStream; import java.io.InputStream; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; import android.nfc.NfcAdapter; import android.nfc.NfcAdapter.CreateBeamUrisCallback; import android.nfc.NfcEvent; import android.os.Bundle; //需要实现CreatBeamUrisCallback接口 public class NFCFileActivity extends Activity implements CreateBeamUrisCallback { private NfcAdapter mNfcAdapter; private PendingIntent mPendingIntent; private final String targetFilename = "/sdcard/temp_icon.png"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_nfcfile); mNfcAdapter = mNfcAdapter.getDefaultAdapter(this); mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); //指定触发NFC Beam传输的回调对象 mNfcAdapter.setBeamPushUrisCallback(this, this); try { //将assets目录中的icon.png文件复制到SD卡的根目录,文件名由targetFilename指定 InputStream is = getResources().getAssets().open("icon.png"); FileOutputStream fos = new FileOutputStream(targetFilename); byte[] buffer = new byte[10000]; int n = is.read(buffer); fos.write(buffer, 0, n); fos.close(); is.close(); } catch (Exception e) { } } //当开始传输文件时调用的方法,并返回的一个或多个Uri @Override public Uri[] createBeamUris(NfcEvent event) { Uri[] uris = new Uri[1]; //该Uri指向要传输的文件 Uri uri = Uri.parse("file://" + targetFilename); uris[0] = uri; return uris; } }