八、新的连接 API——安卓波束和 WIFI 直连
安卓冰淇淋三明治–安卓波束采用了设备的 NFC 硬件,而 Wi-Fi Direct 则引入了新的连接 API,允许设备无需使用无线接入点即可相互连接。本章将教我们安卓波束和无线直接接口的用法。
本章涵盖的主题如下:
- 安卓波束
- 轴承数据
- 通过无线网络直接共享数据
安卓波束
有 NFC 硬件的设备可以通过 一起点击共享数据。这可以在安卓波束功能的帮助下完成。它类似于蓝牙,因为我们可以在蓝牙连接中无缝发现和配对。当设备彼此靠近时(不超过几厘米),设备就会连接。用户可以使用 Android Beam 功能共享图片、视频、联系人等。
发送索引消息
在这一节中,我们将实现一个简单的 Android Beam 应用。当两个设备被点击在一起时,该应用将向另一个设备发送图像。安卓冰淇淋三明治推出了三种发送短信的方法。这些方法如下:
- setndefpusmessage():该方法以一个 NdefMessage 为参数,当设备被连在一起点击时,自动发送到另一个设备 。这通常在消息是静态的并且没有变化时使用。
- setndefpusmessagecallback():这个 方法是用来创建动态的 NdefMessages 的。当两个设备连在一起时,调用
createNdefMessage()
方法。 - setOnNdefPushCompleteCallback():这个 方法设置一个回调,当安卓 Beam 成功时调用。
我们将在示例应用中使用第二种方法。
我们的示例应用的用户界面将包含一个用于显示文本消息的 TextView
组件和一个用于显示从另一个设备发送的接收图像的 ImageView
组件。布局 XML 代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text=""
/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true"
android:layout_marginTop="14dp"
/>
</RelativeLayout>
现在,我们将逐步实现示例应用的Activity
类。Activity
类的代码用 onCreate()
的方法如下:
public class Chapter9Activity extends Activity implements
CreateNdefMessageCallback
{
NfcAdapter mNfcAdapter;
TextView mInfoText;
ImageView imageView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imageView = (ImageView) findViewById(R.id.imageView);
mInfoText = (TextView) findViewById(R.id.textView);
// Check for available NFC Adapter
mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
if (mNfcAdapter == null)
{
mInfoText = (TextView) findViewById(R.id.textView);
mInfoText.setText("NFC is not available on this device.");
finish();
return;
}
// Register callback to set NDEF message
mNfcAdapter.setNdefPushMessageCallback(this, this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
在这段代码中可以看到,我们可以检查设备是否提供了NfcAdapter
。如果是的话,我们得到一个NfcAdapter
的实例。然后,我们调用 setNdefPushMessageCallback()
方法,使用NfcAdapter
实例设置回调。我们发送Activity
类作为回调参数,因为Activity
类实现了CreateNdefMessageCallback
。
为了实现CreateNdefMessageCallback
,我们应该覆盖createNdefMessage()
方法,如下代码块所示:
@Override
public NdefMessage createNdefMessage(NfcEvent arg0) {
Bitmap icon = BitmapFactory.decodeResource(this.getResources(),
R.drawable.ic_launcher);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
icon.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
NdefMessage msg = new NdefMessage(new NdefRecord[] {
createMimeRecord("application/com.chapter9", byteArray)
, NdefRecord.createApplicationRecord("com.chapter9")
});
return msg;
}
public NdefRecord createMimeRecord(String mimeType, byte[] payload) {
byte[] mimeBytes = mimeType.getBytes(Charset.forName("US-ASCII"));
NdefRecord mimeRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
mimeBytes, new byte[0], payload);
return mimeRecord;
}
正如您在这段代码中看到的,我们得到了一个可绘制的,将其转换为位图,然后转换为字节数组。然后我们用两个NdefRecords
创建一个NdefMessage
。第一条记录包含 mime 类型和字节数组。第一个记录是由createMimeRecord()
法创造的。第二条记录包含安卓应用记录 ( AAR )。安卓应用记录随安卓冰淇淋三明治推出。该记录包含应用的包名,并增加了扫描 NFC 标签时应用将启动的确定性。也就是说,系统首先尝试将意图过滤器和 AAR 匹配在一起以启动活动。如果它们不匹配,则启动与 AAR 匹配的活动。
当活动由安卓波束事件启动时,我们需要处理安卓波束发送的消息。我们在Activity
类的 onResume()
方法中处理这个消息,如下代码块所示:
@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);
}
void processIntent(Intent intent) {
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
byte[] bytes = msg.getRecords()[0].getPayload();
Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
imageView.setImageBitmap(bmp);
}
在这段代码中可以看到,我们首先检查意图是否为ACTION_NDEF_DISCOVERED
。这意味着Activity
课程因安卓波束而开始。如果它是由于安卓波束启动的,我们用processIntent()
方法处理意图。我们首先从意图中得到NdefMessage
。然后我们获取第一条记录,并使用BitmapFactory
将第一条记录中的字节数组转换为位图。请记住,第二个记录是 AAR,我们对它不做任何事情。最后,我们设置 ImageView
组件的位图。
申请的AndroidManifest.xml
文件应如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.chapter9"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.NFC"/>
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".Chapter9Activity"
android:label="@string/title_activity_chapter9" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/com.chapter9" />
</intent-filter>
</activity>
</application>
</manifest>
正如您在这段代码中看到的,我们需要在AndroidManifest.xml
文件中将最小 SDK 设置为 API Level 14 或更高,因为这些 API 在 API Level 14 或更高版本中可用。此外,我们需要设置使用 NFC 的权限。我们还在AndroidManifest.xml
中设置了uses
功能。该功能被设置为不需要。这意味着我们的应用可以用于不支持 NFC 的设备。最后,我们用application/com.chapter9
的mimeType
为android.nfc.action.NDEF_DISCOVERED
创建一个意图过滤器。
当设备使用我们的示例应用发送图像时,屏幕将如下所示:
无线直拨
在传统的无线网络中,设备通过无线接入点相互连接。借助 Wi-Fi Direct ,设备无需无线接入点即可相互连接。类似蓝牙,但是速度更快,Wi-Fi Direct 的范围更长。安卓冰淇淋三明治引入了新的无线直接应用编程接口,允许我们使用安卓设备的无线直接属性。
帮助我们寻找和联系同伴的主要班级是 WifiP2pManager
班。我们将在查找和连接对等点时使用以下Listener
类:
WifiP2pManager.ActionListener
WifiP2pManager.ChannelListener
WifiP2pManager.ConnectionInfoListener
WifiP2pManager.PeerListListener
最后,以下意图将帮助我们实现无线直接连接:
WIFI_P2P_CONNECTION_CHANGED_ACTION
WIFI_P2P_PEERS_CHANGED_ACTION
WIFI_P2P_STATE_CHANGED_ACTION
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
在本节中,我们将学习如何在示例应用中使用这些新的无线直接应用接口。
Wi-Fi 直接应用示例
为了使用 Wi-Fi Direct API,我们需要在AndroidManifest.xml
中将最低 SDK 版本设置为 API 等级 14 或更高。此外,我们需要一些许可来使用无线直接接口。AndroidManifest.xml
文件应如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.chapter9"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".Chapter9Activity"
android:label="@string/title_activity_chapter9" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
我们需要的第一个类是扩展BroadcastReceiver
并处理我们之前在 onReceive()
方法中列出的意图的类。这个类的构造函数应该如下:
package com.chapter9;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.NetworkInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
import android.widget.Toast;
public class Chapter9WiFiDirectBroadcastReceiver extends BroadcastReceiver {
private WifiP2pManager manager;
private Channel channel;
private Chapter9Activity activity;
public Chapter9WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel
channel,
Chapter9Activity activity) {
super();
this.manager = manager;
this.channel = channel;
this.activity = activity;
}
}
正如您在这段代码中所看到的,我们将Channel
、WifiP2pManager
和Activity
类作为参数传递给了构造函数,因为我们稍后将在onReceive()
方法中需要它们。我们需要覆盖BroadcastReceiver
的 onReceive()
方法,如下面的代码块所示:
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
// Wifi Direct mode is enabled
Toast.makeText(activity, "wifi direct is enabled",Toast.LENGTH_LONG).show();
} else {
// Wifi Direct mode is disabled
Toast.makeText(activity, "wifi direct is disabled",Toast.LENGTH_LONG).show();
}
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action))
{
// request peers from the wifi p2p manager
if (manager != null) {
manager.requestPeers(channel, (PeerListListener) activity);
}
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
if (manager == null) {
return;
}
NetworkInfo networkInfo = (NetworkInfo) intent
.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
// request connection info
manager.requestConnectionInfo(channel, activity);
} else {
// It's a disconnect
}
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
}
}
在这种方法中,我们处理接收到的意图。首先,我们检查意图是否为WIFI_P2P_STATE_CHANGED_ACTION
。当启用或禁用无线直接连接时,会收到此意图。我们从意向接收无线直接状态,并根据无线直接状态采取行动。
其次,我们检查意图是否为 WIFI_P2P_PEERS_CHANGED_ACTION
。这个意图是在调用WifiP2pManager
类的 discoverPeers()
方法时收到的。当我们收到WIFI_P2P_PEERS_CHANGED_ACTION
意向时,我们从Wifi2P2pManager
类的requestPeers()
方法中获得同行列表。
接下来,我们检查接收到的意图是否为WIFI_P2P_CONNECTION_CHANGED_ACTION
。当无线网络连接改变时,接收到该意图。当我们收到WIFI_P2P_CONNECTION_CHANGED_ACTION
意向时,我们处理连接或断开。我们首先从意图中得到NetworkInfo
来理解是否存在连接或断开。如果是连接,我们称之为WifiP2pManager
的 requestConnectionInfo()
法进行连接。
最后,我们检查意图是否为 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
。当设备细节发生变化时,我们会收到此意向。我们不会为此做任何事。
对于这个应用,我们有一个简单的用户界面;带有两个按钮的布局。第一个按钮是查找,第二个按钮是连接对等点。布局的 XML 代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/buttonFind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="find" />
<Button
android:id="@+id/buttonConnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="connect" />
</LinearLayout>
用户界面如下图所示:
最后,我们需要实现这个应用的Activity
类。Activity
类的代码应该如下:
package com.chapter9;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pDeviceList;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.ActionListener;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.net.wifi.p2p.WifiP2pManager.ChannelListener;
import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener;
import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class Chapter9Activity extends Activity implements
ChannelListener,OnClickListener,PeerListListener,ConnectionInfoListener {
private WifiP2pManager manager;
private final IntentFilter intentFilter = new IntentFilter();
private Channel channel;
private BroadcastReceiver receiver = null;
private Button buttonFind;
private Button buttonConnect;
private WifiP2pDevice device;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
channel = manager.initialize(this, getMainLooper(), null);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
receiver = new Chapter9WiFiDirectBroadcastReceiver(manager, channel, this);
registerReceiver(receiver, intentFilter);
this.buttonConnect = (Button) this.findViewById(R.id.buttonConnect);
this.buttonConnect.setOnClickListener(this);
this.buttonFind = (Button)this.findViewById(R.id.buttonFind);
this.buttonFind.setOnClickListener(this);
}
}
目前尚未完成实施。我们将逐步添加必要的方法。
在这段代码中可以看到,我们的Activity
类实现了各种Listeners
来处理 Wi-Fi Direct 事件。ConnectionInfoListener
用于连接信息可用时的回拨。 PeerListListener
用于对等列表可用时的回拨。ChannelListener
用于通道丢失时的回调。
我们创建一个意图过滤器,并添加我们将在扩展BroadcastReceiver
的类的onReceive()
方法中检查的意图。
我们通过调用 initialize()
方法来初始化 WifiP2pManager
类。这将向无线网络注册我们的应用。
-
我们需要覆盖
onChannelDisconnected()
方法,因为我们实现了ChannelListener
,如下面的代码块所示:java @Override public void onChannelDisconnected() { //handle the channel lost event }
-
We need to implement the
onPeersAvailable()
method because we implementedPeerListListener
, as shown in the following code block:```java @Override public void onPeersAvailable(WifiP2pDeviceList peerList) {
for (WifiP2pDevice device : peerList.getDeviceList()) { this.device = device; break; }
} ```
我们用这种方法得到可用的
peerList
。我们得到第一个装置,打破for
循环。我们需要连接设备。 -
We need to implement the
onConnectionInfoAvailable()
method because we implementedConnectionInfoListener
, as shown in the following code block:```java @Override public void onConnectionInfoAvailable(WifiP2pInfo info) { String infoname = info.groupOwnerAddress.toString();
} ```
这是我们获取连接信息、连接并向对等方发送数据的地方。例如,可以在这里执行传输文件的
AsyncTask
。 -
我们需要对按钮执行
onClick()
方法:```java @Override public void onClick(View v) { if(v == buttonConnect) { connect(this.device); } else if(v == buttonFind) { find(); }
} ```
find()
和 connect()
的方法如下:
public void connect(WifiP2pDevice device)
{
WifiP2pConfig config = new WifiP2pConfig();
if(device != null)
{
config.deviceAddress = device.deviceAddress;
manager.connect(channel, config, new ActionListener() {
@Override
public void onSuccess() {
//success
}
@Override
public void onFailure(int reason) {
//fail
}
});
}
else
{
Toast.makeText(Chapter9Activity.this, "Couldn't connect, device is not found",
Toast.LENGTH_SHORT).show();
}
}
public void find()
{
manager.discoverPeers(channel, new WifiP2pManager.ActionListener()
{
@Override
public void onSuccess() {
Toast.makeText(Chapter9Activity.this, "Finding Peers",
Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(int reasonCode)
{
Toast.makeText(Chapter9Activity.this, "Couldnt find peers ",
Toast.LENGTH_SHORT).show();
}
});
}
当点击查找按钮时,我们调用WifiP2pManager
的 discoverPeers()
方法来发现可用的对等体。你会记得,调用 discoverPeers()
方法会导致BroadcastReceiver
收到WIFI_P2P_PEERS_CHANGED_ACTION
意图。然后我们将在BroadcastReceiver
中请求对等列表。
点击连接按钮,我们使用设备信息调用WifiP2pManager
的 connect()
方法。这将启动与指定设备的对等连接。
介绍无线直接应用接口的示例应用就是用这些方法完成的。
总结
在这一章中,我们首先学习了 Android 的 Android Beam 特性。借助此功能,设备可以使用 NFC 硬件发送数据。我们实现了一个示例 Android Beam 应用,并学习了如何使用 Android Beam APIs。其次,我们学习了什么是 Wi-Fi Direct,以及如何使用 Wi-Fi Direct API。
版权属于:月萌API www.moonapi.com,转载请注明出处