扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
这篇文章将为大家详细讲解有关如何用HMS Nearby Service给自己的App添加近距离数据传输功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
超过10余年行业经验,技术领先,服务至上的经营模式,全靠网络和口碑获得客户,为自己降低成本,也就是为客户降低成本。到目前业务范围包括了:网站设计、网站建设,成都网站推广,成都网站优化,整体网络托管,成都微信小程序,微信开发,成都App定制开发,同时也可以让客户的网站和网络营销和我们一样获得订单和生意!
当你给朋友发送手机资料时,过了很久进度条却动也不动;当你想发送大文件给同事时,仅一个文件就用光了你所有流量;当你跟朋友乘坐飞机时想一起玩游戏时,却因没有网络无奈放弃。
我们生活中似乎经常能遇到这种尴尬的场景,近距离数据传输功能是用户的一个痛点。现在,只需要接入华为近距离通信服务,通过Nearby Connection便可以轻松实现设备间的数据传输,传输类型支持短文本、流数据和文件数据等类型,可帮助app实现本地多人游戏、实时协作、多屏游戏和离线文件传输等功能。下图是功能演示:
如果你对实现方式感兴趣,可以在Github上下载源码:
https://github.com/HMS-Core/hms-nearby-demo/tree/master/NearbyConnection
首先需要了解Nearby Connection 开发流程
整体流程可以划分为4个阶段。
广播扫描阶段:广播端启动广播,发现端启动扫描以发现广播端。
广播端调用startBroadcasting()启动广播。
发现端调用startScan()启动扫描以发现附近的设备。
由onFound()方法通知扫描结果。
建立连接阶段:发现端发起连接并启动对称的身份验证流程,双端独立接受或拒绝连接请求。
发现端调用requestConnect()向广播端发起连接请求。
两端由onEstablish()通知连接启动后,均可以调用acceptConnect()接受连接或调用rejectConnect()拒绝连接。
两端由onResult()通知连接结果。仅当两端都接受连接时,连接才能建立。
传输数据阶段:建立连接后,双端进行数据交换。
连接建立后,双端均可以调用sendData()发送数据给对端。
接收数据的一端由onReceived()通知接收到数据;两端由onTransferUpdate()通知当前的传输状态。
断开连接阶段:双端任意一端发起断开连接,通知对端连接断开。
主动断开连接的一端调用disconnect()断开连接,对端由onDisconnected()通知连接断开。
如果你以前没有集成华为移动服务的经验,那么需要先配置AppGallery Connect,开通近距离通信服务并集成HMS SDK。相关步骤请参考官方文档。
Nearby Connection开发场景需要使用Nearby Discovery API和Nearby Transfer API,你的应用必须根据所使用的策略声明适当的权限。例如:使用POLICY_STAR策略开发文件传输的应用,需要添加特定的权限到AndroidManifest.xml:
由于ACCESS_FINE_LOCATION,WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE 是危险的系统权限,因此,必须动态的申请这些权限。如果权限不足,近距离通信服务(Nearby Service)将会拒绝应用开启广播或者开启发现。
Nearby Discovery支持3种不同的连接策略:POLICY_MESH,POLICY_STAR和POLICY_P2P。可以根据应用场景优选策略。
策略选择并创建BroadcastOption对象的示例代码如下:
Policy policy = Policy.POLICY_STAR; BroadcastOption broadcastOption = new BroadcastOption.Builder().setPolicy (policy).build();
一旦授予应用所需的权限,并为应用选择一个策略,就可以开始广播和扫描以发现附近的设备。
广播端以选定的policy和serviceId为参数,调用startBroadcasting()启动广播。其中serviceId应该唯一标识的应用。建议使用应用的包名作为serviceId(例如:com.huawei.example.myapp)。示例代码如下:
private void doStartBroadcasting() { Policy policy = Policy.POLICY_STAR; BroadcastOption broadcastOption = new BroadcastOption.Builder().setPolicy(policy).build(); Nearby.getDiscoveryEngine(getApplicationContext()) .startBroadcasting(name, serviceId, connectCallback, broadcastOption) .addOnSuccessListener( new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { /* We are broadcasting. */ } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { /* Fail to start broadcasting. */ } }); }
参数connectCallback是一个连接监听回调类实例,用于通知连接状态信息。有关ConnectCallback类的详细信息及示例代码,参见确认连接章节。
发现端以选定的policy和serviceId为参数,调用startScan()启动扫描以发现附近的设备。示例代码如下:
private void doStartScan() { Policy policy = Policy.POLICY_STAR; ScanOption scanOption = new ScanOption.Builder().setPolicy(policy).build(); Nearby.getDiscoveryEngine(getApplicationContext()) .startScan(serviceId, scanEndpointCallback, scanOption) .addOnSuccessListener( new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { /* Start scan success. */ } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { /* Fail to start scan. */ } }); }
参数scanEndpointCallback是一个扫描监听回调类实例,通知发现端扫描结果,发现设备或者已发现设备丢失。
private ScanEndpointCallback scanEndpointCallback = 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); } };
当需要停止广播时,调用stopBroadcasting()。停止广播后,广播端不可以接收来自发现端的连接请求。
当需要停止扫描时,调用stopScan()。停止扫描后,发现端仍可以向已发现的设备请求连接。一种常见的做法是:一旦发现需要连接的设备,就调用stopScan()停止扫描。
当附近的设备被发现,发现端可以调用requestConnect()发起连接。示例代码如下:
private void doStartConnect(String name, String endpointId) throws RemoteException { Nearby.getDiscoveryEngine(getApplicationContext()) .requestConnect(name, endpointId, connectCallback) .addOnSuccessListener( new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { /* Request success. */ } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { /* Fail to request connect. */ } }); }
当然,根据需要,可以向用户展示发现的设备列表,并允许他们选择连接哪些设备。
发现端发起连接后,通过回调connectCallback的onEstablish()方法将连接建立事件通知给双方。双方必须通过调用acceptConnect()接受连接或者通过调用rejectConnect()拒绝连接。仅当双方都接受连接时,连接才会建立成功。如果一方或双方都选择拒绝,则连接失败。无论哪种方式,连接结果都会通过onResult()方法通知。示例代码如下:
private final ConnectCallback connectCallback = new ConnectCallback() { @Override public void onEstablish(String endpointId, ConnectInfo connectInfo) { /* Accept the connection request without notifying user. */ Nearby.getDiscoveryEngine(getApplicationContext()) .acceptConnect(endpointId, dataCallback); } @Override public void onResult(String endpointId, ConnectResult result) { switch (result.getStatus().getStatusCode()) { case StatusCode.STATUS_SUCCESS: /* The connection was established successfully, we can exchange data. */ break; case StatusCode.STATUS_CONNECT_REJECTED: /* The Connection was rejected. */ break; default: /* other unknown status code. */ } } @Override public void onDisconnected(String endpointId) { /* The connection was disconneted. */ } };
此示例显示了一种双方自动接受连接的确认连接方式。根据需要,可以使用其他的确认连接方式。
应用程序可以提供一种让用户确认连接到指定设备的方法,例如:通过验证token(token可以是一个短随机字符串或者数字)。通常这涉及在两个设备上显示token并要求用户手动输入或者确认,类似于蓝牙配对对话框。
下面演示一种通过弹窗确认配对码的方式验证连接。示例代码如下:
@Override public void onEstablish(String endpointId, ConnectInfo connectInfo) { AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext()); builder.setTitle(connectInfo.getEndpointName() + " request connection") .setMessage("Please confirm the match code is: " + connectInfo.getAuthCode()) .setPositiveButton( "Accept", (DialogInterface dialog, int which) -> /* Accept the connection. */ Nearby.getDiscoveryEngine(getApplicationContext()) .acceptConnect(endpointId, dataCallback)) .setNegativeButton( "Reject", (DialogInterface dialog, int which) -> /* Reject the connection. */ Nearby.getDiscoveryEngine(getApplicationContext()) .rejectConnect(endpointId)) .setIcon(android.R.drawable.ic_dialog_alert); AlertDialog alert = builder.create(); alert.show(); }
设备间建立连接后,可以使用该连接传输Data对象。Data对象的类型包括字节序列、文件和流。通过调用sendData()方法发送数据,通过DataCallback类实例的onReceived()方法接收数据。
BYTES
通过调用Data.fromBytes()创建Data.Type.BYTES类型的Data对象。
发送BYTES类型的数据,示例代码如下:
Data bytesData = Data.fromBytes(new byte[] {0xA, 0xA, 0xA, 0xA, 0xA}); Nearby.getTransferEngine(getApplicationContext()).sendData(toEndpointId, bytesData);
注意:BYTES类型数据的长度大小不能超过32KB。
接收BYTES类型的数据,示例代码如下:
static class BytesDataReceiver extends DataCallback { @Override public void onReceived(String endpointId, Data data) { /* BYTES data is sent as a single block, so we can get complete data. */ if (data.getType() == Data.Type.BYTES) { byte[] receivedBytes = data.asBytes(); } } @Override public void onTransferUpdate(String endpointId, TransferStateUpdate update) { /* We will receive TRANSFER_STATE_SUCCESS update after onReceived() called. */ } }
注意:BYTES与FILE和STREAM类型不同,BYTES是以单个数据块发送的,因此接收端不用等待BYTES类型的状态更新为TRANSFER_STATE_SUCCESS,当onReceived()被调用时候,你就可以调用data.asBytes()以获取全部数据。
FILE
通过调用Data.fromFile()创建Data.Type.FILE类型的Data对象。
发送FILE类型数据的示例代码如下:
File fileToSend = new File(getApplicationContext().getFilesDir(), "fileSample.txt"); try { Data fileData = Data.fromFile(fileToSend); Nearby.getTransferEngine(getApplicationContext()) .sendData(endpointList, fileData); } catch (FileNotFoundException e) { /* Exception handle. */ }
一种更高效方法是使用ParcelFileDescriptor创建FILE类型,可以最大程度地减少文件的复制。示例代码如下:
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r"); Data fileData = Data.fromFile(pfd);
接收设备收到文件后,文件保存在Download目录,并且将以fileData.getId()转化后的字符串命名。传输完成后,可以获取FILE对象。示例代码如下:
/* We can get the received file in the Download folder. */ File payloadFile = fileData.asFile().asJavaFile(); )
STREAM
通过调用Data.fromStream()创建Data.Type.STREAM类型的Data对象。发送流的示例代码如下:
URL url = new URL("https://developers.huawei.com"); Data streamData = Data.fromStream(url.openStream()); Nearby.getTransferEngine(getApplicationContext()).sendData(toEndpointId, streamData);
接收端,当onTransferUpdate()回调成功时,可以调用streamData.asStream().asInputStream()或者 streamData.asStream().asParcelFileDescriptor()获取流对象。示例代码如下:
static class StreamDataReceiver extends DataCallback { private final HashMapincomingData = new HashMap<>(); @Override public void onTransferUpdate(String endpointId, TransferStateUpdate update) { if (update.getStatus() == TransferStateUpdate.Status.TRANSFER_STATE_SUCCESS) { Data data = incomingData.get(update.getDataId()); InputStream inputStream = data.asStream().asInputStream(); /* Further processing... */ } } @Override public void onReceived(String endpointId, Data data) { incomingData.put(data.getId(), data); } }
DataCallBack回调类onTransferUpdate()方法提供数据发送或接收的进度更新,基于此可以向用户显示传输进度,例如:进度条。
如果需要在接收或发送过程中取消传输,调用TransferEngine类实例方法cancelDataTransfer()。
如果需要断开与对端的连接,调用DiscoveryEngine类实例方法disconnect()。一旦调用此接口,将不能从此endpoint收发数据。
关于如何用HMS Nearby Service给自己的App添加近距离数据传输功能就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流