iOS SDK

本文介绍如何使用无影云手机iOS SDK。

1. 快速开始

1.1 获取SDKDEMO

获取方式

说明

下载和使用即表示您认可《无影云电脑SDK隐私权政策》

本平台所有文档、SDK、客户端程序仅限于本人或本企业使用,未经阿里云同意不会转发给第三方个人或企业。

集成环境要求

最低支持iOS版本:10.0

由于iOS系统相关接口的开放,外设鼠标和键盘功能,要求iOS最低版本:13.4

SDK集成

  1. SDK解压拷贝到自己的工程中。

  2. 关联 SDK库。

    target --> Build Phases --> Link Binary With Libraries --> + --> Add Other -- Add Files -->选择对应的framework

  3. 设置Embed。

    target --> General --> Frameworks, Libraries, and Embedded Content --> 找到ASPEngineSDK.framework --> Embed Without Signing

SDK权限

工程info.plist配置ASPEngineSDK需要的权限。

1. 录音权限

Privacy - Microphone Usage Description

2. 相机权限

Privacy - Camera Usage Description

SDK签名

SDK库是动态库,真机调试或发布App Store时,需要签名。

  • KEY文件签名

参考demo工程目录下的KEY配置进行签名,默认KEY文件是空的,可在KEY文件里配置一个签名ID,修改方法:

1. 命令修改

在命令窗口,cd工程目录,然后执行下面命令进行修改:

echo "8AEF20DC6E0CBFF4FA852118BCDD6D507F5764ED" > KEY

2. 手动修改

在工程里,找到KEY文件,以文本编辑器打开并修改。

  • 如何获取签名ID

通过以下命令,查找自己本机安装证书的签名ID:

security find-identity -v -p codesigning

若没有本机安装证书,可以登录Apple开发者账号,下载对应证书的授权文件Profiles,下载后,选中文件右键,右键菜单选择显示简介,在预览处会显示此授权文件关联的所有证书信息,找到对应证书的信息,里面SHA-1对应的值就是我们需要签名ID。

  • Signing & Capabilities签名

需清空KEY文件配置的签名ID,然后在工程target里正确配置Signing & Capabilities里的证书即可。

  • 添加签名Shell(模拟器不需要,真机需要)

target --> Build Phases --> + --> New Run Script Phase --> 填写sh内容,参考签名示例 --> 添加KEY文件,在Input Files处,点击+,填写$(SRCROOT)/KEY

注意:此Run Script必现在Embed Frameworks下面。

  • 签名Shell示例

set -e
SHELL_PATH="${PROJECT_DIR}/ASPDemo/Lib/ASPEngineSDK/ASPEngineSDK.framework/link_and_sign.sh"
while read -r line; do
exit 0
done < ${SCRIPT_INPUT_FILE_0}
echo "===> Signing using $line"
/bin/bash -c "${SHELL_PATH}"\ "${TARGET_BUILD_DIR}"\ "${FRAMEWORKS_FOLDER_PATH}"\ "${line}"

工程配置注意事项

关闭Bitcode。

target --> Build Settings --> Build Options --> Enable Bitcode --> 设置No

1.2 对接流程

image

1.3 最佳实践

方案详见无影云手机快速集成最佳实践。 云手机集成的总体方案如下图所示

image

有多种登录方式,获取集成SDK所需要的连接云手机Ticket凭证,流程图为:

image

具体集成代码可以参考生命周期接口的参考代码。

2. 生命周期接口

2.1 初始化实例

+ (instancetype)buildStreamView;

2.2 建立连接

- (void)startWithTicket:(ASPConnTicket*) ticketParam;

ASPConnTicket参数说明:

属性

说明

connTicket

连接鉴权Ticket,通过GetConnectionTicket - 获取应用连接凭证接口获取。

caFilePath

CA文件的绝对路径,用于TLS通信加密

desktopId

请传入应用实例ID(格式:ai-0cc7s3n1iagyq****)。应用实例ID可通过调用云手机的查询实例详细信息接口获取。

useVpc

是否通过企业专网进入

enableTls

是否进行加密传输(建议设为YES)

enableStatistics

是否开启埋点(建议设为YES)

preferRtcTransport

是否使用rtc通道(建议设为YES)

2.3 断开连接

- (int)stop;

2.4 暂停

暂停云端下发流和端侧流渲染

- (void)pause;

2.5 恢复

恢复云端下发流和端侧流渲染

- (void)dispose;

2.6 销毁实例

- (void)dispose;

2.7 回调说明

//云手机连接相关回调
@property (nonatomic, weak) id<ASPEngineDelegate> engineDelegate;
//分辨率发生变化回调
@property (nonatomic, weak) id<ASPEngineResolutionUpdateDelegate> resolutionUpdateDelegate;
//鼠标光标相关回调
@property (nonatomic, weak) id<ASPEngineCursorDelegate> cursorDelegate;
//云手机方向发生变化回调
@property (nonatomic, weak) id<ASPEngineOrientationUpdateDelegate> orientationUpdateDelegate;
//埋点相关回调
@property (nonatomic, weak) id<ASPEngineStatisticsDelegate> statisticsDelegate;
//输入法相关回调 云手机暂不支持
@property (nonatomic, weak) id<ASPEngineIMEDelegate> imeDelegate;
//LOG相关回调,delegate建议使用单例
+ (void)setASPEngineLogDelegate:(id<ASPEngineLogDelegate>)delegate;
+ (void)unsetASPEngineLogDelegate;

云手机连接相关回调ASPEngineDelegate

接口

描述

onConnectionSuccess:(int)connectId

连接云手机成功回调,返回连接的标识

onConnectionFailureWithErrCode:(int)errCode errMsg:(NSString*)errMsg

连接云手机失败回调,返回错误码和错误信息

onEngineErrorWithErrCode:(int)errCode errMsg:(NSString*)errMsg

SDK内部发生异常回调,返回错误码和错误信息

onDisconnected:(int)reason

云手机连接被断开,返回被断开原因

onFirstFrameRendered:(long)timeCostMS

云手机显示第一帧画面回调,返回耗时情况

onReconnect:(int)errorCode

云手机连接发生重连动作,返回导致重连的错误码

onPolicyUpdate:(NSString *)policy

云手机策略回调,返回策略配置

onUpdateNetworkQos:(AspNetworkQoS)qos

网络服务质量情况

onSessionSuccess

云手机连接会话创建成功回调

分辨率发生变化回调ASPEngineResolutionUpdateDelegate

接口

描述

onResolutionUpdateWithOldWidth:(int)oldWidth oldHeight:(int)oldHeight width:(int)width height:(int)height

分辨率发生变化回调,返回旧分辨率和新分辨率

onMonitorsDpiConfig:(int)dpi maxSupportDpi:(int)maxSupportDpi

dpi配置和支持的最大dpi,云手机暂不支持

鼠标光标相关回调ASPEngineCursorDelegate

接口

描述

onCursorBitmapUpdateWithHotX:(int)hotX hotY:(int)hotY

width:(int)width height:(int)height rgba:(char*)rgba

鼠标光标数据回调,返回位置、大小和数据

onCursorReset

鼠标光标发生重置

onCursorHide

鼠标光标被隐藏

onCursorMoveWithX:(int)x y:(int)y

鼠标光标移动位置

埋点相关回调ASPEngineStatisticsDelegate

接口

描述

onStatisticsInfoUpdate:(ASPStatisticsInfo *)info

云手机性能数据回调,返回性能数据对象

LOG相关回调,ASPEngineLogDelegate建议使用单例

接口

描述

onLogMessage:(NSString*)msg

tag:(NSString*)tag

level:(AspLogLevel)level

SDK日志回调,返回日志信息、tag和级别

3. 业务接口

接口

说明

- (BOOL)enableStatistics:(BOOL) enabled enableGuestInfo:(BOOL)enableGuestInfo

是否开启埋点和获取镜像cpu使用率。

- (BOOL)enableMouseMode:(BOOL)enabled

是否开启鼠标模式

@property (nonatomic, assign) BOOL enableDump;

是否开启dump,此功能会消耗性能,只用于开发调试,禁止上线。

- (BOOL)sendKeyboardEvent: (ASPKeyEvent) event;

发送键盘按键事件。

// leftButton是鼠标左键还是右键,鼠标点击位置 x:0 y:0

- (BOOL)simulateMouseClick:(BOOL)leftButton;

- (BOOL)simulateMouseClick:(BOOL)leftButton x:(float)x y:(float)y;

模拟鼠标点击。

- (void)hideCursor;

- (void)showCursor;

隐藏和现实鼠标光标

- (void)setVideoProfileWithWidth:(int)width height:(int)height fps:(int)fps;

设置分辨率。fps暂不支持

@property (nonatomic, assign) BOOL mute;

设置是否静音。

@property (nonatomic, assign) ASPScaleType scaleType;

画面填充模式,ASPScaleType参考本文档枚举5.1 ASPScaleType

- (void)enableDesktopMode:(BOOL)enabled;

设置是否以桌面模式运行,设置为enabled后,会将所有Touch消息转换为Mouse事件向服务端发送。云手机建议设置为NO。

- (void)enableDesktopGesture:(BOOL)enabled;

是否使用端侧手势缩放和平移。云手机建议设置为NO。

@property (nonatomic, assign) BOOL enableTouchFeel;

是否开启触感反馈,默认NO。

LyncChannel

发送adb命令通道。实现参考demoDemoLyncChannel。

DataChannel

端侧与镜像接发自定义数据通道。实现参考demoDemoEDSAgentChannel。

LyncChannel使用示例:
//默认命令通道LyncChannel name为static NSString *LYNC_CHANNEL_NAME = @"lync_adb_shell";
//添加LyncChannelself.lync = [[DemoLyncChannel alloc] initWithParameter:LYNC_CHANNEL_NAME];
[self.streamView addLyncChannel:self.lync];
//移除LyncChannel
[self.streamView removeLyncChannel:self.lync];
//LyncChannel实现@interface DemoLyncChannel : BaseLyncChannel@property (nonatomic, strong) NSString *lslaID;

@end@implementation DemoLyncChannel

- (void)onConnectStateChanged:(BOOL)connected {
    NSLog(@"[DemoLyncChannel] onConnectStateChanged %d", connected);
    if (connected) {
        //TEST send cmdself.lslaID = [DemoToolBox getID];
        NSString *cmdData = [DemoToolBox getJsonDataWithId:self.lslaID cmd:@"ls -la"];
        NSLog(@"[DemoLyncChannel] cmdData : %@", cmdData);
        LyncErrorCode code = [self sendString:cmdData];
        NSLog(@"[DemoLyncChannel] cmdData code: %ld", code);
    }
}
- (void)onReceiveStringData:(NSString * _Nonnull)buf {
    NSLog(@"[DemoLyncChannel] onReceiveStringData %@", buf);
    NSDictionary *dic = [DemoToolBox convertStringToJSON:buf];
    NSString *ID = [dic objectForKey:@"id"];
    if (ID != NULL && ID.length && [self.lslaID isEqualToString:ID]) {
        NSLog(@"[DemoLyncChannel] onReceiveStringData get ls -la result");
    }
}
- (void)onReceiveRawData:(NSData * _Nonnull)buf {
    NSString *string = [[NSString alloc] initWithData:buf encoding:NSUTF8StringEncoding];
    NSLog(@"[DemoLyncChannel] onReceiveRawData %@", string);
}
@end
DataChannel使用示例:
//默认DataChannel name为static NSString *DATA_CHANNEL_NAME = @"wy_vdagent_default_dc";
//添加DataChannelself.esdAgent = [[DemoEDSAgentChannel alloc] initWithParameter:DATA_CHANNEL_NAME];
[self.streamView addDataChannel:self.esdAgent];
//移除DataChannel
[self.streamView removeDataChannel:self.esdAgent];
//DataChannel实现@interface DemoEDSAgentChannel : BaseEDSAgentChannel@end@implementation DemoEDSAgentChannel

- (void)onConnectStateChanged:(ASPDCConnectState)state {
    NSLog(@"[DemoEDSAgentChannel] onConnectStateChanged %ld", state);
    if (state == OPEN) {
        // to send data
    }
}
- (void)onReceiveData:(NSData * _Nonnull)buf {
    NSString *string = [[NSString alloc] initWithData:buf encoding:NSUTF8StringEncoding];
    NSLog(@"[DemoEDSAgentChannel] onConnectStateChanged %@", string);
}
@end

消息通道使用datachannel,datachannel名称为wy_vdagent_default_dc。

消息大小限制500KB。

基体消息格式如下:

DataChannel 端侧发送消息给镜像 client --> guest

action:insertcontact/insertsms/sendcontactcvf

  • insertcontact是插入联系人

参数name是联系人名称,必填。

参数phonenumber是电话号码,必填。

  • insertsms是插入短信

参数phonenumber是电话号码, 必填。

参数textbody是短信内容,必填。

参数canInsertdb是否可以插入云手机短信数据库,选填,默认0, 不插入, 1为插入。

示例:let jsonarray = [{'action': 'insertcontact','importdata':[{ 'name': '**', 'phonenumber': '**' }, { 'name': '**', 'phonenumber': '**' }]},{'action': 'insertsms','importdata':[{'phonenumber':'**','textbody':'**,"canInsertdb":0/1}]}]

  • sendcontactvcf是通过vcf插入联系人

参数datavcf文件数据流,必填,若文件过大,需拆包,一个包最大1MB。

参数allsizevcf文件数据总大小,必填。

说明

参数timestamp是时间戳,必填,一个vcf文件数据下的所有包对应一个当前时间戳,通过时间戳保证包来源于一个vcf文件。

示例:{'action': 'sendcontactvcf',importdata:{data:"", "allsize":文件数据总大小, "timestamp":时间戳}}

public void processByteArrayInChunks(byte[] data) {
    int chunkSize = 512 * 1024; // 500KB in bytesint length = data.length;
    long timestamp = System.currentTimeMillis();
    for (int i = 0; i < length; i += chunkSize) {
        int end = Math.min(i + chunkSize, length);
        byte[] chunk = Arrays.copyOfRange(data, i, end);
        String str = new String(chunk, StandardCharsets.UTF_8);
        /**
          {'action': 'sendcontactvcf','importdata':{"data":str, "allsize":length, "timestamp":timestamp}}
        */
    }
}
String recvData = "";
int recvSize = 0;
long recvTimestamp = 0;
public void onMessageReceive(byte[] message) {
  String action = getActioin(meesage);
  String data = getData(message);
  int length = data.length;
  int allSize = getAllSize(message);
  long timestamp = getTimestamp(message);
  if (timestamp != recvTimestamp) {
    clearRecv();
  }
  if (action.equals("sendcontactvcf")) {
    recvSize += length;
    recvData = recvData + data;
    if (recvSize >= allSize) {
      // recvData 写vcf文件 插入联系人
      insertContact(vcfFilePath);
      sendack()
      clearRecv();
    }
  }
}

public void clearRecv() {
  recvData = ""
  recvSize = 0;
  recvTimestamp = 0;
}

DataChannel 镜像发送消息给端侧 guest --> client

action:openeditsms/openphone/rotation

  • openeditsms打开编辑短信页面

参数importdata.phonenumber是电话号码,选填。

参数textbody是短信内容,选填。

示例:{'action': 'openeditsms',importdata:{"phonenumber":"***", "textbody":"***"}} 

  • openphone打开电话拨号页面

参数importdata.phonenumber是电话号码,选填,有电话号,则打开拨打指定联系人页面,没有电话号码,则打开电话拨号页面。

示例:{'action': 'openphone','importdata':{"phonenumber":"***"}} 或 {'action': 'openphone'}

DataChannel 回执消息

action:ack

  • ack是指不管是端侧发送消息给镜像,还是镜像发送消息给端侧,收到消息侧,根据业务需要,可回执ack消息,表明消息处理的情况

参数source是接收到消息的action,必填。

参数code是状态码,必填值为0表示未知,1 执行source,2 执行成功,3 执行失败, 4 不支持执行。

示例:{'action': 'ack','importdata':{"source":"***", "code": *,"id":*}}

4. 参数详细说明

4.1 ASPConnTicket

建立连接的配置参数。

属性

说明

connTicket

连接鉴权Ticket,通过GetConnectionTicket - 获取应用连接凭证接口获取。

caFilePath

CA文件的绝对路径,用于TLS通信加密

desktopId

请传入应用实例ID(格式:ai-0cc7s3n1iagyq****)。应用实例ID可通过调用云手机的查询实例详细信息接口获取。

useVpc

是否通过企业专网进入

enableTls

是否进行加密传输(建议设为YES)

enableStatistics

是否开启埋点(建议设为YES)

preferRtcTransport

是否使用rtc通道(建议设为YES)

4.2 ASPStatisticsInfo

性能数据

接口

类型

说明

mReceiveFps

int

接收到的帧率

mRenderFps

int

渲染帧率

mDownstreamBandwithMBPerSecond

double

下行带宽

mUpstreamBandwithMBPerSecond

double

上行带宽

mP2pFullLinkageLatencyMS

long

端到端全链路时延,已废弃

mNetworkLatencyMS

long

网络rtt时延

mPingGatewayRtt

long

ping rtt时延

mLostRate

double

丢包率

mServerRenderLatencyMS

long

云侧渲染延迟

mServerEncoderLatencyMS

long

云侧编码延迟

mServerTotalLatencyMS

long

云侧总延迟

accumulateBandwidth

long

总带宽

mGuestCpuUsage

long

镜像CPU使用率

mStreamType

String

流协议类型

infoDic

NSDictionary

性能数据Map

5. 枚举类型

5.1 ASPScaleType

流化图像内容缩放处理类型。

名称

含义

ASPScaleTypeFill

总是将流化图像拉伸至与StreamView相同大小。当StreamView的长宽比例与流化图像长宽比例不相等时。采用该策略可能导致图像有明显的变形 

ASPScaleTypeFit

StreamView的渲染区域进行调整,使得StreamView总是能以相同的长宽比例渲染流化图像内容。采用该策略时,流化图像可能无法填满整个StreamView

6. 错误代码

错误码

错误消息(%s表示云手机或云应用)

定义模块

原因

2~26主要是网络相关问题

2

连接%s失败

ASP SDK

无效MAGIC

3

连接%s失败

ASP SDK

数据有误

4

客户端与服务端版本不匹配

ASP SDK

版本不匹配

5

连接需要 TLS

ASP SDK

需要TLS

6

连接不需要 TLS

ASP SDK

不需要TLS而实际使用了TLS

7

您没有权限连接当前%s

ASP SDK

权限问题

8

ASP SDK

迁移过程中client ID无效

9

连接%s失败

ASP SDK

channel不存在

20

连接ASP服务器失败。

ASP SDK

channel 连接错误

21

TLS认证出错了

ASP SDK

TLS 认证错误

22

连接%s失败

ASP SDK

channel link 错误

23

连接%s失败

ASP SDK

连接认证错误

24

连接%s失败

ASP SDK

连接IO错误

25

连接%s失败

ASP SDK

Ticket校验失败。用户连接被断开后,若使用同一个Ticket再次请求建连,也将触发此错误。

26

ASP SDK

xquic 握手失败

连接中断开或遇到某些错误的情况

2000

获取%s数据超时,与服务端断开连接

ASP SDK

正常断开

2001

%s已与服务端断开连接。可能是因为%s进程已被强制终止。

ASP SDK

一般是端上应用被终止进程了,如Android应用用户通过按下Home键终止

2002

已有用户从其他终端连接当前%s。请稍后重试。

ASP SDK

其他人抢占了端

2003

%s正在关机或重启,一般由管理员操作,请稍后重试。

ASP SDK

云手机被关机或重启,一般是管理员操作

2004

当前用户连接被断开。

ASP SDK

客户端发起断流,或服务端发起踢人或断流

2005

%s已超时断开连接,因为已达到管理员设置的使用时长限制。

ASP SDK

管理员设置使用时长被关

2010

连接%s失败

ASP SDK

Vdagent连接失败

2011

连接参数传递错误。

ASP SDK

连接server参数传递错误

2027

拉流模式已切换。

ASP SDK

拉流模式由抢占模式切换为协同模式,或由协同模式切换为抢占模式

2100

剪切板权限,禁止从%s到本地

ASP SDK

剪贴板权限,禁止从VM到本地

2101

剪切板权限,禁止从本地到%s

ASP SDK

剪贴板权限,禁止从本地到 VM

2200

%s正在尝试重连...

ASP SDK

因为网络问题断开了,ASP SDK正在重连

2201

您的设备出现网络异常,导致%s已断开连接。

ASP SDK

因为网络问题断开了,ASP SDK因为镜像原因不支持重连,应用侧开始重连

2202

%s重连超时,请检查设备网络后重试。

ASP SDK

ASP SDK重连超时

端侧逻辑错误

5100

%s连接ASP Server超时,请稍后重试。

应用侧

端侧在一段时间内未接收到connected事件

5102

获取%s数据超时,请稍后重试。

应用侧

端侧在一段时间内接收到了connected但未接收到display事件

5004

客户端出现错误,请重新打开

应用侧

传入端侧启动参数有误,一般出现在开发阶段

5200

客户端重连超时,请稍后重试。

应用侧

7. 常见问题

如何重启云手机

调用管控重启API 重启实例 进行重启,调用重启API后,端侧连接的云手机会断开,重启完成后,端侧再连接云手机。

常用ADB命令

功能

命令

返回键

input keyevent KEYCODE_BACK

Home

input keyevent KEYCODE_HOME

切换键

input keyevent KEYCODE_APP_SWITCH

静音

input keyevent 164

音量增大

input keyevent KEYCODE_VOLUME_UP

音量减小

input keyevent KEYCODE_VOLUME_DOWN

隐藏导航栏

setprop persist.wy.hasnavibar false; killall com.android.systemui

显示导航栏

setprop persist.wy.hasnavibar true; killall com.android.systemui

截图

screencap -p /sdcard/Download/abc.png