代码给你解密:华为如何用分布式软总线串联起一个新生态
“万物互联”似乎是这些年被提的最多的一个概念,在我们的理解中万物互联似乎就是将生活中的每个实物都接入到网络中。不过因为“万物互联”概念中,“造物”的厂商千千万,各自接入网络的方式又五花八门。所以在绝大部分情况下,即便万物真能接入网络,体验也是割裂的。
华为的“万物互联”实现方式有些另辟蹊径的意思:分布式技术通过“分布式软总线”,将硬件资源融合为硬件池,不仅实现硬件互联,而且让硬件资源可以相互利用。典型比如WPS与华为终端分布式技术联手打造的分布式文件,可以手机中的文档,可以在PC上直接查看、编辑、保存,提升跨终端工作效率。
5月27日,华为在线上举办了一场HDD华为终端分布式生态技术交流会,除了华为自己针对这种分布式生态的技术讲解和发展现状,也拉来了不少第三方开发者讲述接入生态的价值和过程。我们也期望通过这篇文章,面向开发者谈谈华为生态究竟是怎么回事,以及要加入生态,会不会特别麻烦。
分布式技术能做到什么?
我们借用一个具体的例子来看看,分布式技术究竟有哪些能力。科大讯飞的办公本应该是比较早接入到华为分布式生态中的一类产品。主要解决的问题是跨设备传输文件,就是在手机和办公本之间做笔记、网文、图书等的分享。
而科大讯飞在智能办公本中,期望解决的问题是:
- 办公本不能拍照(受限于电子墨水屏的刷新率),那么利用手机的摄像头,就能直接将笔记、教案拍摄存储在办公本里;
- 在手机上看到的各种网文、图书可以方便地在办公本上观看;
- 当然,其中还有一些数据安全的考量,比如说用户不希望把重要的笔记同步到互联网上。
而在双方合作后,华为终端分布式技术Share Kit便能够解决这些问题,其采用华为私有传输协议,实现一键分享和更多的数据互通。在科大讯飞的办公本上,其特性至少包括了快速发现设备;PAKE密钥交换做到数据安全交换;快速信道能力协商,根据业务调整物理通路来实现高速传输。
在具体开发的Share Kit集成过程里,科大讯飞办公本涉及到集成发送和接收功能,除了最上层Launcher集成Share Kit 接口,还需要在HAL(硬件抽象层)和Framework层做一些适配。科大讯飞方面则表示,针对Framework和HAL层,华为有提供代码修改流程图,可以非常轻松地搞定;而且即便集成阶段遇到问题,华为也提供服务支持,可以共同解决问题。
对于很多硬件厂商来说,如果只需实现发送功能,那么整个开发过程会更简单,只需上层Launcher集成Share Kit 接口即可。
分布式生态能力还在扩充
除了Share Kit外, CaaS Kit是另外一个典型的分布式Kit,比如接入了CaaS Kit的无人机产品,可以直接在无人机APP应用内拨打畅连视频通话,无人机镜头拍摄的画面可直接作为视频来源显示在对方的屏幕上;Drift运动相机利用DeviceVirtualization Kit能力,可以在运动的时候发起畅连通话并将镜头一键转移到Drift镜头,让对方体验第一运动视角带来的紧张刺激;极米投影仪在集成Cast+ Kit后,为用户带来流畅、高清的家庭娱乐投屏体验。
在理解了华为的分布式生态究竟是什么,以及能做什么以后。接下来我们以其中的DeviceVirtualization Kit和Cast+ Kit为例,简单谈谈要将这些能力接入到自己的应用或设备中,具体过程和易用性如何,毕竟易用性是吸引开发者的一大重点。
从0 开始接入终端分布式
在前期准备阶段,开发者需要在线提交申请,华为会把相应的SDK提供给开发者。开发可以选择华为的DevEco Studio——这是一个全流程覆盖的IDE。当前的beta版DevEco Studio需要在开发者联盟做申请;也可以选择Android Studio。
通过DevEco Studio的Kit Manager简单勾选Kit、添加依赖库之后,就进入开发阶段了。
(1)DeviceVirtualization Kit
DeviceVirtualization Kit(以下简称DV Kit)能够将附近的设备或组件转换为手机的虚拟组件,将其能力作为手机的能力来使用。比如说外部的摄像头、音箱、显示器、话筒,甚至如心率传感器这类设备组件,令其成为手机的眼睛、嘴巴、耳朵等等。
这里从应用开发者的角度来谈一谈接入过程。有关前期在开发者联盟的注册、获取签名证书指纹以及向华为方的接口权限申请这里不再赘述。这里着重说接入过程。首先是声明虚拟外设的使用权限,以及声明该应用调用DV Kit对应接口需要的权限,比如摄像头、音频、身体传感器的权限等。申请不同的Android权限,在AndroidMenifest.xml文件中添加相应权限,例如:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="com.huawei.permission.DISTRIBUTED_VIRTUALDEVICE"/>
这里申请的是虚拟摄像头、虚拟麦克风对应的Android权限;最后一行是在应用需要使用外部的虚拟分布式外设时,所需申请的权限。
DV Kit开发基本方法是,首先创建基础DV Kit对象,并连接后端服务进行初始化;通过该对象获取VirtualDeviceManager服务。通过VirtualDeviceManager服务可以发现当前手机能够控制的虚拟设备。比如前文中的例子,VirtualDeviceManager服务发现Drift运动相机,并返回运动相机当前支持的是Camera(摄像头)、Speaker(扬声器)能力。
按照上图思路,首先初始化连接DV Kit服务,服务初始化结果通过onConnect回调返回。连接成功后,调用getKitService获取VirtualDeviceManager服务实例,用于控制虚拟设备:
//获取DvKit对象并连接DvKit服务
DvKit.getInstance().connect(getApplicationContext(), new IDvKitConnectCallback() {
//服务连接成功后的回调通知
@Override
public void onConnect(int result) {
addLog("msdp service connect");
mVirtualDeviceManager = (VirtualDeviceManager) DvKit.getInstance().getKitService(VIRTUAL_DEVICE_CLASS);
mVirtualDeviceManager.subscribe(EnumSet.of(VIRTUALDEVICE), observer);
}
//服务断开后的回调通知
@Override
public void onDisconnect() {
addLog("msdp service disconnect");
}
});
接下来就是设备发现,如前文所述连接成功,获取到VirtualDeviceManager服务,应用就能调用VirtualDeviceManager服务的startDiscovery接口用于发现周围的可用设备。发现的设备会通过IDiscoveryCallback回调的onFound接口返回:
//开始发现设备
mVirtualDeviceManager.startDiscovery(new IDiscoveryCallback() {
//设备发现时的回调接口
@Override
public void onFound(VirtualDevice device, int state) {
if (device == null) {
addLog("onDevice callback but device is null");
} else {
HwLog.d(TAG, "onDevice Found: " + Util.hideSensitiveInfo(device.getDeviceId()) + " Name: "
+ device.getDeviceName() + " Type:" + device.getDeviceType());
if (!mVirtualDeviceMap.containsKey(device.getDeviceId())) {
addLog("onDevice Found: " + device.getDeviceId() + " Name: " + device.getDeviceName() + " Type:"
+ device.getDeviceType());
mVirtualDeviceMap.put(device.getDeviceId(), device);
handler.sendMessage(handler.obtainMessage(DEVICE_ADD, device));
}
}
}
//发现状态变更的回调通知
@Override
public void onState(int state) {
}
});
在发现虚拟设备之后,应用就可以调用虚拟设备的getDeviceCapability()接口获取设备支持能力,按需选择具体的能力。具体是调用enableVirtualDevice来使能所需使能的设备和能力,支持同时传入多个能力。应用使能的结果可以通过subscribe接口传入的回调对象来获得:
mVirtualDeviceManager.enableVirtualDevice(deviceId, EnumSet.of(CAMARA), null);
//调用subscribe时传入的observer对象
private IVirtualDeviceObserver observer = new IVirtualDeviceObserver() {
//虚拟设备状态变化时的回调通知
@Override
public void onDeviceStateChange(VirtualDevice virtualDevice, int returncode) {
}
//虚拟设备能力状态变化时的回调通知
@Override
public void onDeviceCapabilityStateChange(VirtualDevice virtualDevice, Capability capability, int returncode) {
if (returncode == EventType.EVENT_DEVICE_CAPABILITY_ENABLE) {
//当设备能力使能成功时,应用处理使能成功流程
onEnable(virtualDevice, capability);
} else if (returncode == EventType.EVENT_DEVICE_CAPABILITY_DISABLE) {
//当设备能力去使能成功时,应用处理去使能成功流程
onDisable(virtualDevice, capability);
} else {
//当虚拟设备能力状态异常时,应用应处理异常流程
(virtualDevice, capability, returncode);
}
}
};
这里是以虚拟Camera能力为例,在虚拟Camera能力接入后,应用可以通过getData接口来获取虚拟设备(比如Drift运动相机)的虚拟Camera id。应用随后就能和传统获取手机的本地前后置摄像头一样,来获取虚拟Camera的属性信息(getCameraCharacteristics),以及打开虚拟Camera(openCamera),示例如下:
//通过虚拟设备的getData接口获取设备虚拟Camera的ID
String cameraId = device.getData(Constants.ANDROID_CAMERAID_FRONT);
//使用CameraManager的getCameraCharacteristics接口获取虚拟Camera的属性信息
CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
//使用CameraManager的openCamera接口打开虚拟Camera
manager.openCamera(cameraId, mStateCallback, null)
“去使能”是通过调用disableVirtualDevice接口来实现的,比如去使能摄像头能力:
mVirtualDeviceManager.disableVirtualDevice(deviceId, EnumSet.of(CAMERA));
整体DV Kit提供服务的断连,释放底层资源:
DvKit.getInstance().disConnect();
另外各种应用的具体实现也各有不同,例如有些可能需要使用虚拟Display,有些则需要虚拟Sensor(如Drift Life应用),或者使用本地消息通知等。这在华为开发者文档中都有比较详细的介绍,这里不再一一列举。
整个过程还是比较清晰和便捷的,对于一般开发者而言,代码难度也不高;其流程对于App开发的整体业务存在的影响实际上都是比较小的,这是融入分布式生态比较便利的体现。
(2)Cast+ Kit
在日常生活中,我们经常需要投屏玩游戏、看电影等方式实现跨屏幕协同。而一些第三方厂商,如极米就有多款投影仪集成了华为终端分布式技术Cast+ Kit。从现场演示来看,令人印象比较深刻的就是低延迟,这对于投屏玩游戏还是相对重要的。
Cast+ Kit在设备侧需要与华为进行合作协议签署后获取。对于设备侧而言,开发过程首先也是申请权限,包括允许访问网络连接、获取当前WiFi接入状态和热点信息、获取设备当前WiFi频率信息等。
上面这张图给出了简略的调用流程。其大步骤分成了:首先做变量声明和实例获取:
private PlayerClient mPlayerClient;
private ProjectionDevice mProjectionDevice;
//获取PlayerClient实例
mPlayerClient = PlayerClient.getInstance();
启动服务并注册监听:实现IEventListener.Stub()
private IEventListener mCallback = new IEventListener.Stub() {
//上报连接状态。
public boolean onEvent(int eventId, String type) {
//根据连接状态进行对应的配置及逻辑处理。
…
return true;
}
//上报显示相关事件。
public boolean onDisplayEvent(int eventId, DisplayInfo displayInfo) {
//根据连接状态进行对应的配置及逻辑处理。
…
return true;
}
};
启动服务:
mPlayerClient.init(context);
注册回调接口:
mPlayerClient.registerCallback(mCallback);
随后进行投屏业务相关设置,设置鉴权模式信息:
AuthInfo authInfo = null;
if (needPassword) {
//密码模式,设置6位密码(需要支持混合密码的能力)
authInfo = new AuthInfo(AuthInfo.AUTH_MODE_PWD);
authInfo.setAuthCode(password);
} else {
//PIN码模式
authInfo = new AuthInfo(AuthInfo.AUTH_MODE_GENERIC);
}
boolean isAuthModeSuccessfullySet = mPlayerClient.setAuthMode(authInfo);
If (isAuthModeSuccessfullySet) {
//更新本地密码 or 更新UI
}
设置大屏端设备的投屏能力,首先构造HiSightCapability对象:
HiSightCapability capability = new HiSightCapability(1920, 1080, 1920, 1080);
设置投屏显示帧率,默认为30fps:
capability.setVideoFps(30);
根据平台配置低时延策略:
capability.setMediaCodecConfigureFlag(2)
设置投屏能力:
mPlayerClient.setCapability(capability);
根据选用芯片的不同,可选用HiSightCapability提供的不同方法,设定平台的解码优化参数:
HiSightCapability.setMediaCodecConfigureFlag(int flag)
HiSightCapability. setMediaFormatInteger(String name, int value)
HiSightCapability. setMediaFormatFloat(String name, float value)
HiSightCapability. setMediaFormatLong(String name, long value)
HiSightCapability. setMediaFormatString(String name, String value)
配置大屏端设备信息,包括大屏端设备名称、设备类型:
private DeviceInfo mDeviceInfo = new DeviceInfo(mTvDeviceName, DeviceInfo. TYPE_TV);
并设置设备可被周围的设备发现:
mPlayerClient.setDiscoverable(true, mDeviceInfo);
在大屏设备首次收到移动端连接请求后,会上报EVENT_ID_PIN_CODE_SHOW信息,并提供对端设备信息。在首次连接成功后,再次通过PIN码模式发起连接请求时,会跳出PIN码鉴权步骤:
if (displayInfo != null) {
//设备连接需要的PIN码
String pinCode = displayInfo.getPinCode();
//按照UX规范开发PIN码界面以展示PIN码
showPinCode(pinCode, mProjectionDevice.getDeviceName());
//设置允许手机连接(用于不弹框让用户选择的场景)
mPlayerClient.setConnectRequestChooseResult(new ConnectRequestChoice(
Constant.CONNECT_REQ_CHOICE_ALWAYS, mProjectionDevice));
}
在手机正确输入PIN码鉴权通过后,大屏端应用会上报EVENT_ID_CONNECT_REQ信息,另外也包含移动端设备信息:
if (displayInfo != null) {
//获取请求连接的移动端设备信息
mProjectionDevice = displayInfo.getProjectionDevice();
}
在EVENT_ID_CONNECT_REQ之后,会顺序上报EVENT_ID_DEVICE_CONNECTED和EVENT_ID_PAUSED消息。PAUSED消息下,大屏端可以设置Surface并开始播放投屏视频流,为加快起播速度,也可提前(如EVENT_ID_CONNECT_REQ消息下)启动投屏Acitivity和Surface的创建。
mProjectionDevice为Event_ID_CONNECT_REQ消息中从DisplayInfo中获取的对端设备信息,可通过其获取DeviceId,再通过DeviceID构造TrackControl对象做投屏使用。投屏成功,大屏端将上报EVENT_ID_CASTING消息,标识当前正在投屏。
具体实现上,首先设计应用投屏界面XML布局:
<com.huawei.castpluskit.HiSightSurfaceView
android:id="@+id/HiSightSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
另外应用层面,要求保证投屏过程中不能录屏、截屏、录音:
mHiView.setSecure(true);
再添加SurfaceHolder的回调,在surfaceCreated监听中设置投屏控件
SurfaceHolder surfaceHolder = mHiView.getHolder();
surfaceHolder.addCallback(mSurfaceHolderCallback);
private SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
isSurfaceReady = true;
if(isReceivePaused) {
mPlayerClient.setHiSightSurface(mHiView.getHolder().getSurface());
}
}
}
最后开始投屏:
mPlayerClient.play(new TrackControl(mProjectionDevice.getDeviceId()));
断开连接:
mPlayerClient.disconnectDevice(mProjectionDevice);
对绝大部分开发者而言,这都是一个相当简单快速的集成过程。从这些开发者的代码不难发现,华为在Kit以及系统层面,为简化开发难度,还是花了不少心思的。而对用户来说,投屏质量和效率都是能够得到保证的,用户不需要安装app,也没有复杂操作;而且延迟各方面的体验也都在同类的无线投屏方案中显得比较靠谱——这些也是开发者不需要关心的。
从DevEco Studio看华为的开发生态
针对开发流程,最后再谈谈目前仍处在beta阶段的DevEco Studio工具。如前文所述这就是个来自华为的IDE,整体是为华为的全场景智慧化战略设想所做的。面向的当然包括了应用开发和设备开发,内部集成了华为的分布式能力。
完整的DevEco Studio是基于Intellij开源代码,加上华为的开放能力支持。另外华为也基于Android Studio做了DevEco Toolkit插件,作为另一种形态提供给开发者使用。
DevEco Studio的某些特性,本身也是在为分布式生态构建提供便利,这是在分布式能力接入本身就比较简单的情况下,所做的一些加分项。比如说:
前文就提到的,DevEco Studio针对这些分布式能力有集中管理和呈现的方案,开发者可以直接查看、管理这些Kit,并且在有需要时通过勾选就将某个Kit融入到开发中;甚至还能进行Kit的一键升级操作。这很大程度上提供了开发的便利性。
另外,所谓的“拖拽式生成API代码”,即调用API功能的代码样例可以直接拖动到代码编辑区。若样例代码依赖某些特殊的包,则在拖动操作后,会自动引入依赖包、自动生成头部import。这些都还是颇具特色的。
在开发周期的调测方面,华为提供了远程真机,大概有5000多个华为机型,不同的系统版本、屏幕分辨率等可做调测。与此同时,华为另外还提供了云测和DFX诊断服务 、“非侵入式”的数据分析。
不言而喻,降低开发难度——不管是完善IDE的体验,还是降低接入Kit的技术难度——本质上都是为了吸引更多的开发者加入到这个生态中来。现阶段是华为1+8+N生态的扩展期,华为期望构建起的是以智能手机为中心,将所有周边智能设备通过分布式能力串联起来的生态。这是华为在万物互联方面的庞大视野,也是华为对万物互联样貌的理解。
这样的布局,显然打破了智能手机自身生态的局限,也并不局限在单纯的智能家居或者某种具体使用场景,描绘的是一幅令IoT真正构成统一生态的图景。现如今华为侧打造了这一生态的基础或平台,并正逐步补足和完善;而要让生态真正活跃、丰富起来,仍然要靠三方开发者的共同努力,这是值得期待的。