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

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

  们生活中似乎经常能遇到这种尴尬的场景,近距离数据传输功能是用户的一个痛点。现在,只需要接入华为近距离通信服务,通过Nearby Connection便可以轻松实现设备间的数据传输,传输类型支持短文本、流数据和文件数据等类型,可帮助app实现本地多人游戏、实时协作、多屏游戏和离线文件传输等功能。下图是功能演示:

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

  首先需要了解Nearby Connection 开发流程

1. 业务流程

  整体流程可以划分为4个阶段。

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

  • 广播端调用startBroadcasting()启动广播。
  • 发现端调用startScan()启动扫描以发现附近的设备。
  • 由onFound()方法通知扫描结果。

  建立连接阶段:发现端发起连接并启动对称的身份验证流程,双端独立接受或拒绝连接请求。

  • 发现端调用requestConnect()向广播端发起连接请求。
  • 两端由onEstablish()通知连接启动后,均可以调用acceptConnect()接受连接或调用rejectConnect()拒绝连接。
  • 两端由onResult()通知连接结果。仅当两端都接受连接时,连接才能建立。

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

  • 连接建立后,双端均可以调用sendData()发送数据给对端。
  • 接收数据的一端由onReceived()通知接收到数据;两端由onTransferUpdate()通知当前的传输状态。

  断开连接阶段:双端任意一端发起断开连接,通知对端连接断开。

  • 主动断开连接的一端调用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()以获取全部数据。

2.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();
)

3.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实现如下相关功能:

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

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

到此这篇关于如何用HMS Nearby Service给自己的App添加近距离数据传输功能的文章就介绍到这了,更多相关HMS Nearby Service App数据传输内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • ajax用json实现数据传输

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C.C++.C#.Java.JavaScript.Perl.Python等).这些特性使JSON成为理想的数据交换语言. 易于人阅读和编写,同时也易于机器解析和生成(一般用于提升网络传输速率). json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,

  • Nginx服务器对数据传输速度限制的基本配置方法讲解

    注意: nginx 1.1.8 之后的版本的语法改为limit_conn_zone $binary_remote_addr zone=NAME:10m; NAME 就是 zone 的名字详情请看这里 http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html 限制连接数: 要限制连接,必须先有一个容器对连接进行计数,在http段加入如下代码: "zone=" 给它一个名字,可以随便叫,这个名字要跟下面的 limit_con

  • 如何使用json在前后台进行数据传输实例介绍

    上一篇博客写到用javascript生成多组文本,可以让数据的输入不受显示,现在我们需要把这些输入写入数据库,这里就用到json传入. 首先,我们来写一下后台如何生成要传输的数据 [html] 复制代码 代码如下: function generateDtb() { //写入 var txtName = document.getElementById("txtName").value; //创建数组 var dtb = new Array(); //通过循环把数据写入到数组并返回 for

  • Vuejs 用$emit与$on来进行兄弟组件之间的数据传输通信

    最近在学习vue组件鸡组件之前通信问题,正好看到,以此来留作笔记. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Vue2-单一事件管理组件通信</title> <script src="vue.js"></script> <script type=

  • 详解Android——蓝牙技术 带你实现终端间数据传输

    蓝牙技术在智能硬件方面有很多用武之地,今天我就为大家分享一下蓝牙在Android系统下的使用方法技巧,并实现一下两个终端间数据的传输. 蓝牙(Bluetooth)是一种短距离的无线通信技术标准,蓝牙协议分为4层,即核心协议层.电缆替代协议层.电话控制协议层和采纳的其它协议层. 这4种协议中最重要的是核心协议.蓝牙的核心协议包括基带.链路管理.逻辑链路控制和适应协议四部分.其中链路管理(LMP)负责蓝牙组件间连接的建立.逻辑链路控制与适应协议(L2CAP)位于基带协议层上,属于数据链路层,是一个为

  • Java使用TCP实现数据传输实例详解

    Java使用TCP实现数据传输实例详解 TCP所提供服务的主要特点: 1.面向连接的传输: 2.端到端的通信: 3.高可靠性,确保传输数据的正确性,不出现丢失或乱序: 4.全双工方式传输: 5.采用字节流方式,即以字节为单位传输字节序列: 6.紧急数据传送功能. TCP传输需要建立客户端和服务器端,即Socket和Server Socket , 建立连接后,通过Socket中的IO流进行数据的传输 .传输结束后关闭Socket. 客户端和服务器端是两个独立的应用程序. 以下是实现基本的TCP数据

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

      当你给朋友发送手机资料时,过了很久进度条却动也不动:当你想发送大文件给同事时,仅一个文件就用光了你所有流量:当你跟朋友乘坐飞机时想一起玩游戏时,却因没有网络无奈放弃.   们生活中似乎经常能遇到这种尴尬的场景,近距离数据传输功能是用户的一个痛点.现在,只需要接入华为近距离通信服务,通过Nearby Connection便可以轻松实现设备间的数据传输,传输类型支持短文本.流数据和文件数据等类型,可帮助app实现本地多人游戏.实时协作.多屏游戏和离线文件传输等功能.下图是功能演示:   如果你对

  • 如何用C#在PC上查找连接蓝牙设备并实现数据传输

    目录 概述 代码实现 完整代码 概述 在PC端用.NET开发一个蓝牙下载的程序.实现在PC上查找周围的蓝牙设备(主要是手机),并将PC上的文件通过蓝牙传输到手机上.目前我采用的是OpenNETCF.Net.Bluetooth,能够正常发现周围的蓝牙设备,手机也能够正常进行蓝牙连接并接收文件. 代码实现 1.查找周围蓝牙设备 BluetoothClient bc = new BluetoothClient(); BluetoothDeviceInfo[] bdi; bdi = bc.Discove

  • 前端axios取消请求总结详解

    目录 应用场景 如何取消请求 项目中用法示例 批量取消请求 切换路由时,取消请求 取消请求的实现原理 应用场景 取消请求在前端有时候会用到,以下是两个工作中可能会用到的场景 tab切换时刷新某个列表数据,如果他们共用一个变量存储数据列表,当请求有延时,可能会导致两个tab数据错乱: 导出文件或下载文件时,中途取消 . 如何取消请求 取消http请求,axios文档里提供了两种用法: 第一种:使用 CancelToken const { CancelToken, isCanCel } = axio

  • angularJS Provider、factory、service详解及实例代码

    factory 用 Factory 就是创建一个对象,为它添加属性,然后把这个对象返回出来.你把 service 传进 controller 之后,在 controller 里这个对象里的属性就可以通过 factory 使用了. app.controller('myFactoryCtrl', function($scope, myFactory){ $scope.artist = myFactory.getArtis(); }); app.factory('myFactory', functio

  • SSH框架网上商城项目第2战之基本增删查改、Service和Action的抽取

    上一节<SSH框架网上商城项目第1战之整合Struts2.Hibernate4.3和Spring4.2>我们搭建好了Struts2.Hibernate和Spring的开发环境,并成功将它们整合在一起.这节主要完成一些基本的增删改查以及Service.Dao和Action的抽取. 1. Service层的抽取         上一节中,我们在service层简单写了save和update方法,这里我们开始完善该部分的代码,然后对service层的代码进行抽取. 1.1 完善CategorySer

  • 不使用web服务(Service)实现文本框自动完成扩展

    以前写Ajax 的AutoCompleteExtender功能,都需要写WCF Service或是Web Service数据源.但一个系统中,很多文本框都想使用AutoComplete的功能.我们不可能写很多的Service,比如一些较小的数据.我们是否有一个可替换的方法呢?这是肯定的.下面的演示,Insus.NET以不用写Service来实现文本框的AutoCompete extender功能. 首先从数据库获取数,你可以写SQL语句,或是写存储过程,以下是获取域用户信息,仿Outlook输入

  • Android中Service(后台服务)详解

    1.概念: (1).Service可以说是一个在后台运行的Activity.它不是一个单独的进程,它只需要应用告诉它要在后台做什么就可以了. (2).它要是实现和用户的交互的话需要通过通知栏或者是通过发送广播,UI去接收显示. (3).它的应用十分广泛,尤其是在框架层,应用更多的是对系统服务的调用. 2.作用: (1).它用于处理一些不干扰用户使用的后台操作.如下载,网络获取.播放音乐,他可以通过INTENT来开启,同时也可以绑定到宿主对象(调用者例如ACTIVITY上)来使用. (2).如果说

  • Android Service的启动过程分析

    Android Service的启动过程分析 刚开始学习Service的时候以为它是一个线程的封装,也可以执行耗时操作.其实不然,Service是运行在主线程的.直接执行耗时操作是会阻塞主线程的.长时间就直接ANR了. 我们知道Service可以执行一些后台任务,是后台任务不是耗时的任务,后台和耗时是有区别的喔. 这样就很容易想到音乐播放器,天气预报这些应用是要用到Service的.当然如果要在Service中执行耗时操作的话,开个线程就可以了. 关于Service的运行状态有两种,启动状态和绑

  • Android Service启动过程完整分析

    刚开始学习Service的时候以为它是一个线程的封装,也可以执行耗时操作.其实不然,Service是运行在主线程的.直接执行耗时操作是会阻塞主线程的.长时间就直接ANR了. 我们知道Service可以执行一些后台任务,是后台任务不是耗时的任务,后台和耗时是有区别的喔. 这样就很容易想到音乐播放器,天气预报这些应用是要用到Service的.当然如果要在Service中执行耗时操作的话,开个线程就可以了. 关于Service的运行状态有两种,启动状态和绑定状态,两种状态可以一起. 启动一个Servi

  • 浅谈Android中Service的注册方式及使用

    Service通常总是称之为"后台服务",其中"后台"一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件: 1.并不依赖于用户可视的UI界面(当然,这一条其实也不是绝对的,如前台Service就是与Notification界面结合使用的): 2.具有较长时间的运行特性. 1.Service AndroidManifest.xml 声明 一般而言,从Service的启动方式上

随机推荐