如何用HMS-Nearby-Service给自己的App添加近距离数据传输功能

11次阅读

共计 9308 个字符,预计需要花费 24 分钟才能阅读完成。

  当你给敌人发送手机材料时,过了很久进度条却动也不动;当你想发送大文件给共事时,仅一个文件就用光了你所有流量;当你跟敌人乘坐飞机时想一起玩游戏时,却因没有网络无奈放弃。

  们生存中仿佛常常能遇到这种难堪的场景,近距离数据传输性能是用户的一个痛点。当初,只须要接入华为近距离通信服务,通过 Nearby Connection 便能够轻松实现设施间的数据传输,传输类型反对短文本、流数据和文件数据等类型,可帮忙 app 实现本地多人游戏、实时合作、多屏游戏和离线文件传输等性能。下图是性能演示:

  如果你对实现形式感兴趣,能够在 Github 上下载源码:
  https://github.com/HMS-Core/hms-nearby-demo/tree/master/NearbyConnection

  首先须要理解 Nearby Connection 开发流程

1. 业务流程

  整体流程能够划分为 4 个阶段。

   播送扫描阶段 :播送端启动播送,发现端启动扫描以发现播送端。

  1. 播送端调用 startBroadcasting() 启动播送。
  2. 发现端调用 startScan() 启动扫描以发现左近的设施。
  3. 由 onFound() 办法告诉扫描后果。

   建设连贯阶段 :发现端发动连贯并启动对称的身份验证流程,双端独立承受或回绝连贯申请。

  1. 发现端调用 requestConnect() 向播送端发动连贯申请。
  2. 两端由 onEstablish() 告诉连贯启动后,均能够调用 acceptConnect() 承受连贯或调用 rejectConnect() 回绝连贯。
  3. 两端由 onResult() 告诉连贯后果。仅当两端都承受连贯时,连贯能力建设。

   传输数据阶段 :建设连贯后,双端进行数据交换。

  1. 连贯建设后,双端均能够调用 sendData() 发送数据给对端。
  2. 接收数据的一端由 onReceived() 告诉接管到数据;两端由 onTransferUpdate() 告诉以后的传输状态。

   断开连接阶段 :双端任意一端发动断开连接,告诉对端连贯断开。

  1. 被动断开连接的一端调用 disconnect() 断开连接,对端由 onDisconnected() 告诉连贯断开。

2. 开发步骤

2.1 开发筹备

  如果你以前没有集成华为挪动服务的教训,那么须要先配置 AppGallery Connect,开明近距离通信服务并集成 HMS SDK。相干步骤请参考官网文档。

2.2 申明零碎权限

  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.3 抉择策略

  Nearby Discovery 反对 3 种不同的连贯策略:POLICY_MESH,POLICY_STAR 和 POLICY_P2P。能够依据利用场景优选策略。

  策略抉择并创立 BroadcastOption 对象的示例代码如下:

Policy policy = Policy.POLICY_STAR;  
BroadcastOption broadcastOption = new BroadcastOption.Builder().setPolicy (policy).build();

2.4 播送和扫描

  一旦授予利用所需的权限,并为利用抉择一个策略,就能够开始播送和扫描以发现左近的设施。

2.4.1 启动播送

  播送端以选定的 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<Void>() {
                        @Override
                        public void onSuccess(Void aVoid) {/* We are broadcasting. */}
                    })
            .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(Exception e) {/* Fail to start broadcasting. */}
                    });
}

  参数 connectCallback 是一个连贯监听回调类实例,用于告诉连贯状态信息。无关 ConnectCallback 类的详细信息及示例代码,参见确认连贯章节。

2.4.2 启动扫描

  发现端以选定的 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<Void>() {
                        @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);
                }
            };

2.4.3 进行播送

  当须要进行播送时,调用 stopBroadcasting()。进行播送后,播送端不能够接管来自发现端的连贯申请。

2.4.4 进行扫描

  当须要进行扫描时,调用 stopScan()。进行扫描后,发现端仍能够向已发现的设施申请连贯。一种常见的做法是:一旦发现须要连贯的设施,就调用 stopScan() 进行扫描。

2.5 建设连贯

2.5.1 申请连贯

  当左近的设施被发现,发现端能够调用 requestConnect() 发动连贯。示例代码如下:

private void doStartConnect(String name, String endpointId) throws RemoteException {Nearby.getDiscoveryEngine(getApplicationContext())
            .requestConnect(name, endpointId, connectCallback)
            .addOnSuccessListener(new OnSuccessListener<Void>() {
                        @Override
                        public void onSuccess(Void aVoid) {/* Request success. */}
                    })
            .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(Exception e) {/* Fail to request connect. */}
                    });
}

  当然,依据须要,能够向用户展现发现的设施列表,并容许他们抉择连贯哪些设施。

2.5.2 确认连贯

  发现端发动连贯后,通过回调 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. */}  
        };

  此示例显示了一种单方主动承受连贯的确认连贯形式。依据须要,能够应用其余的确认连贯形式。

2.5.3 验证连贯

  应用程序能够提供一种让用户确认连贯到指定设施的办法,例如:通过验证 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();}

2.6 传输数据

  设施间建设连贯后,能够应用该连贯传输 Data 对象。Data 对象的类型包含字节序列、文件和流。通过调用 sendData() 办法发送数据,通过 DataCallback 类实例的 onReceived() 办法接收数据。

2.6.1 数据类型

  1. 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() 以获取全副数据。

  1. 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();
)
  1. 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 HashMap<Long, Data> incomingData = 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);
    } 
}

2.6.2 进度更新

  DataCallBack 回调类 onTransferUpdate() 办法提供数据发送或接管的进度更新,基于此能够向用户显示传输进度,例如:进度条。

2.6.3 勾销传输

  如果须要在接管或发送过程中勾销传输,调用 TransferEngine 类实例办法 cancelDataTransfer()。

2.7 断开连接

  如果须要断开与对端的连贯,调用 DiscoveryEngine 类实例办法 disconnect()。一旦调用此接口,将不能从此 endpoint 收发数据。

结后语

  基于 Nearby Connection, 能够帮忙你的 APP 实现如下相干性能:

  1. 本地多人游戏:自组网,提供低延时、稳固牢靠的传输体验。
  2. 离线文件传输:无需流量,可达 80MB/ S 的传输速度。

  更具体的开发指南参考华为开发者联盟官网:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/introduction-0000001050040566


原文链接:https://developer.huawei.com/consumer/cn/forum/topicview?tid=0201296701616220024&fid=18
原作者:赵照

正文完
 0