前言
想象一下,当你回到家中,客厅里自动播放起你今天在某家咖啡厅听到并Mark到手机备忘录里的歌曲;智能配电表通过手机提醒你该交电费了,而你只需要说一句“充五毛钱的”就完成了缴费;晚上,浴室里的热水器检测到你在房间里的活动,根据你的作息习惯开始自动加热;而你又一次在车库里因为忘了停车位置而迷茫时,不远处的爱车正闪烁着卡姿兰大眼睛呼唤着你…..所有这一切,通过接入HMS Nearby Service的近距离设备间数据传输服务,你都可以轻松获取。
应用场景
HMS Nearby Service的近距离设备间数据传输服务,无需连接Internet,通过蓝牙、WIFI等方式,发现并建立与其他设备的直接通信通道,支持无缝的近距离互动,例如本地多人游戏、实时协作、多屏游戏和离线文件传输等。开发者不需要关心底层蓝牙、wifi的通信原理,只需要接入HMS Nearby Service便可以轻松实现设备间的数据传输,传输类型支持短文本、流数据和文件数据等类型。
下文将为您介绍Nearby Connection 开发流程。
1. 开发准备
如果您已经是华为的开发者,可以省略此步骤。如果您以前没有集成华为移动服务的经验,那么需要先配置AppGallery Connect,开通近距离通信服务并集成HMS SDK。相关步骤请参考参考华为开发者联盟:https://developer.huawei.com/consumer/cn/doc/development/HMS-Guides/ml-process-4
2. 代码开发
2.1 声明系统权限
Nearby Connection开发场景需要使用Nearby Discovery API和Nearby Transfer API,您的应用必须根据所使用的策略声明适当的权限。例如:使用POLICY_STAR策略开发文件传输的应用,需要添加特定的权限到AndroidManifest.xml:
<!-- Required for Nearby Discovery and Nearby Transfer -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Required for FILE payloads -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
由于ACCESS_FINE_LOCATION,WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE 是危险的系统权限,因此,您必须动态的申请这些权限。如果权限不足,近距离通信服务(Nearby Service)将会拒绝您的应用开启广播或者开启发现。
2.2 获取Connection客户端实例
Nearby Service Connection通过DiscoveryEngine和TransferEngine这两个类的实例提供广播、扫描、连接、传输等功能,因此首先需要获取这两个客户端实例。
DiscoveryEngine mDiscoveryEngine = Nearby.getDiscoveryEngine(context);
TransferEngine mTransferEngine = Nearby.getTransferEngine(context);
2.3 开始广播
DiscoveryEngine客户端实例提供了startBroadcasting接口用于提供广播功能。在调用该接口时,需要指定设备名称、应用的serviceId、连接回调类实例、广播策略作为该接口参数。
public void doStartBroadcast(View view) throws RemoteException {
BroadcastOption.Builder advBuilder = new BroadcastOption.Builder();
advBuilder.setPolicy(Policy.POLICY_STAR);
mDiscoveryEngine.startBroadcasting(myNameStr, myServiceId, mConnCb, advBuilder.build());
}
其中,连接回调类实例mConnCb中的方法会在建立连接的过程中被回调执行。示例代码中,onEstablish方法指示广播端在接收到扫描端发起连接请求时,通过调用acceptConnect接口接受连接请求。同时示例代码中,通过调用stopBroadcasting/stopScan接口停止广播/扫描,以提高连接效率。onResult方法中,可以获取连接结果,并根据业务需要对连接结果进行处理。示例代码中对已连接设备的EndpointId进行了保存。onDisconnected方法中,可以添加断开连接时的业务处理逻辑。
private ConnectCallback mConnCb =
new ConnectCallback() {
@Override
public void onEstablish(String endpointId, ConnectInfo connectionInfo) {
mTransferEngine = Nearby.getTransferEngine(getApplicationContext());
mEndpointId = endpointId;
mDiscoveryEngine.acceptConnect(endpointId, mDataCb);
ToastUtil.showShortToastTop("Let's chat!");
sendBtn.setEnabled(true);
msgEt.setEnabled(true);
connectBtn.setEnabled(false);
connectTaskResult = StatusCode.STATUS_SUCCESS;
if (myNameStr.compareTo(friendNameStr) > 0) {
mDiscoveryEngine.stopScan();
} else {
mDiscoveryEngine.stopBroadcasting();
}
}
@Override
public void onResult(String endpointId, ConnectResult resolution) {
mEndpointId = endpointId;
}
@Override
public void onDisconnected(String endpointId) {
ToastUtil.showShortToastTop("Disconnect.");
connectTaskResult = StatusCode.STATUS_NOT_CONNECTED;
sendBtn.setEnabled(false);
connectBtn.setEnabled(true);
msgEt.setEnabled(false);
myNameEt.setEnabled(true);
friendNameEt.setEnabled(true);
}
};
2.4 开始扫描
DiscoveryEngine客户端实例提供了startScan接口用于提供扫描功能。在调用该接口时,需要指定应用的serviceId、扫描回调类实例、扫描策略作为该接口参数。其中serviceId和扫描策略需要与广播端的serviceId和广播策略保持一致。
public void doStartScan(View view) throws RemoteException {
ScanOption.Builder discBuilder = new ScanOption.Builder();
discBuilder.setPolicy(Policy.POLICY_STAR);
mDiscoveryEngine.startScan(myServiceId, mDiscCb, discBuilder.build());
}
其中,扫描回调类实例mDiscCb中的方法会在扫描过程中被回调执行。示例代码中,onFound方法指示扫描端在扫描到广播端设备时,通过调用requestConnect接口请求与广播端建立连接。onLost方法中,可以添加设备Lost后的业务处理逻辑。
private ScanEndpointCallback mDiscCb =
new ScanEndpointCallback() {
@Override
public void onFound(String endpointId, ScanEndpointInfo discoveryEndpointInfo) {
mEndpointId = endpointId;
mDiscoveryEngine.requestConnect(myNameStr, mEndpointId, mConnCb);
}
@Override
public void onLost(String endpointId) {
Log.d(TAG, "Nearby Connection Demo app: Lost endpoint: " + endpointId);
}
};
2.5 请求连接
前文2.3中已经提到了请求连接接口的使用。请求连接接口requestConnect在调用时,需要指定本地设备名称、对端设备的EndpointId以及连接回调类实例mConnCb作为参数。实例代码中,扫描端和广播端共用已同一个连接回调类实例。开发者也可以根据业务需要,分别定义扫描端和广播端的处理逻辑。
2.6 接受连接
前文2.2中已经提到了接受连接接口的使用。接受连接接口acceptConnect在调用时,需要指定对端设备的EndpointId、数据传输回调类实例作为参数。
其中,数据传输回调类实例mDataCb会在数据传输过程中被执行。开发者可以根据业务需要,在onReceived方法中对接收到的数据进行处理。在onTransferUpdate方法中,开发者可以从TransferStateUpdate实例中获取已传输数据量、数据传输总量等信息,并添加相关业务处理逻辑。
private DataCallback mDataCb =
new DataCallback() {
@Override
public void onReceived(String endpointId, Data data) {
receiveMessage(data);
}
@Override
public void onTransferUpdate(String endpointId, TransferStateUpdate update) {
}
};
2.7 发送数据
TransferEngine客户端实例提供了sendData接口以提供数据传输能力。调用该接口时,需要指定对端设备的EndpointId、待传输数据作为参数。示例代码中的待传输数据为通过Data. fromFile接口获取到的文件类型数据。开发者也可以通过Data. fromStream、Data. fromBytes接口获取其他类型的数据进行传输。
File fileToSend = new File(getApplicationContext().getFilesDir(), "fileSample.txt");
try {
Data data = Data.fromFile(fileToSend);
mTransferEngine.sendData(mEndpointId, data);
} catch (FileNotFoundException e) {
/* Exception handle. */
}
您可以在Github上获取更详细的源码:https://github.com/HMS-Core/hms-nearby-demo/tree/master/NearbyConnection
更详细的开发指南参考华为开发者联盟官网
https://developer.huawei.com/consumer/cn/hms/huawei-nearbyservice