diff --git a/README.md b/README.md index 4269c743386ba32932a5f0847877b9dd46823d70..16c4dcef42a3bc67e29e39b7386e35d44bb8c6f3 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ ## Android-SDK +### 版本更新: + +- #### 24.3.0 + + - SDK版本更新内容为: + - 连接信息字段增加**必选**字段**client_mode**,字段内容为**direct**或**management**,direct代表调用请求华为云CPH服务提供的[获取连接信息](https://support.huaweicloud.com/api-cph/cph_api_0564.html)接口的方式获取连接信息,云侧CAE校验该接口返回字段;management代表连接信息为自研的服务端生成,云侧CAE通过配置接口来校验,即原有校验逻辑。 + - 新增可选字段region_id,内容为当前连接云手机所在region的id,用于direct模式下校验连接信息时下载公钥,配置region_id时,公钥下载流程更快更稳定。 + - App版本更新内容为:通过登陆华为云账号,获取账号token,请求华为云CPH服务提供的[获取连接信息](https://support.huaweicloud.com/api-cph/cph_api_0564.html)接口,获取到指定云手机的连接信息,通过当前版本SDK发送连接信息到云机,即可成功连接云手机。云手机镜像需要为发布时间为2024-04-29之后的镜像,可在官网文档查看[镜像发布动态](https://support.huaweicloud.com/wtsnew-cph/cph_wtsnew_0004.html)。 + ### 1. 方案简介 华为云手机端云协同方案,致力于高效、便捷的接入并使用华为云手机,涵盖信令交互、接入鉴权、音视频流传输、解码渲染、触控采集等模块,使得客户构建云游戏、个人云手机等业务场景变的简单。客户在云手机内部署 CloudAppEngine,真机侧集成 Android 和 H5 的 SDK,RemoteServer 实现相关 RestApi 接口,进一步结合业务场景需要来丰富相关模块,实现完善的接入方案。 @@ -12,9 +21,9 @@ 1. SDK->RemoteServer: 客户端 SDK 向后端 server 请求一个空闲云机实例 2. RemoteServer->SDK: 后端 server 按需求找到一个空闲云机实例信息返回给客户端 SDK -3. SDK->cloudAppEngine: 客户端 SDK 通过云机实例信息向 cloudAppEngine 建链、认证,发心跳、控制信令、触控指令等 -4. cloudAppEngine->SDK: cloudAppEngine 向客户端 SDK 发信令、发送音视频流等 -5. cloudAppEngine->RemoteServer: cloudAppEngine 从后端 server 获取解密秘钥、上报事件等 +3. SDK->cloudAppEngine: 客户端 SDK 通过云机实例信息向云机服务进程 cloudAppEngine 建链、认证,发心跳、控制信令、触控指令等 +4. cloudAppEngine->SDK: 云机服务进程 cloudAppEngine 向客户端 SDK 发信令、发送音视频流等 +5. cloudAppEngine->RemoteServer: 云机服务进程 cloudAppEngine 验签、上报事件等 6. RemoteServer->cloudAppEngine: 后端 server 响应 cloudAppEngine 的请求 ### 3.工程构建 @@ -74,6 +83,8 @@ │    │   └── arm64-v8a │    ├── libopus <用于音频数据解码> │    │   └── include +│    ├── mtrans <用于数据传输> +│    │   └── include │    └── openssl <用于 tls 安全通信> │    └── include │    └── openssl @@ -190,18 +201,25 @@ implementation 'org.bouncycastle:bcprov-jdk15to18:1.71' @param {Map} params:启动相关配置,具体如下: - | 参数 | 是否必选 | 参数类型 | 描述 | - | ------------------ | -------- | -------- | -------------- | - | ip | 是 | String | 云手机IP地址 | - | port | 是 | String | 云手机端口信息 | - | session_id | 是 | String | 会话id | - | ticket | 是 | String | 随机数 | - | aes_key | 是 | String | 对称秘钥 | - | auth_ts | 是 | String | 验签时间戳 | - | background_timeout | 是 | String | home时长 | - | available_playtime | 是 | String | 试玩时长 | - | touch_timeout | 否 | String | 无触控时长 | - | user_id | 否 | String | 用户id | + | 参数 | 是否必选 | 参数类型 | 描述 | + | ------------------ | -------- | -------- | -------------------- | + | ip | 是 | String | 云手机IP地址 | + | port | 是 | String | 云手机端口信息 | + | session_id | 是 | String | 会话id | + | ticket | 是 | String | 随机数 | + | aes_key | 是 | String | 对称秘钥 | + | auth_ts | 是 | String | 验签时间戳 | + | background_timeout | 是 | String | home时长 | + | available_playtime | 是 | String | 试玩时长 | + | touch_timeout | 否 | String | 无触控时长 | + | user_id | 否 | String | 用户id | + | client_mode | 是 | String | direct/management | + | region_id | 否 | String | 云手机所在region的id | + + 其中client_mode为24.3.0版本新增字段,字段为direct或management: + + - direct代表调用请求华为云CPH服务提供的[获取连接信息](https://support.huaweicloud.com/api-cph/cph_api_0564.html)接口的方式获取连接信息直接连接,连接信息包含access_ip、access_port、session_id、timestamp、ticket,分别对应上表的ip、port、session_id、auth_ts、ticket字段,上表的其他字段中aeskey配置为该云手机的phoneId,client_mode配置为direct ,region_id设置为云手机所在region的id,其余字段按需配置即可; + - management代表由调度管理系统下发给端侧连接信息,端侧SDK发送给云侧CAE,CAE通过请求调度管理平台接口鉴权、上报心跳、上报事件,即为原有调度接入逻辑。 - 调用示例 @@ -241,7 +259,7 @@ implementation 'org.bouncycastle:bcprov-jdk15to18:1.71' | bitrate | String | 码率 | 1Mbit/s到10Mbit/s | | virtual_width | String | 虚拟宽 | 240到4096,且为8的倍数 | | virtual_height | String | 虚拟高 | 240到4096,且为8的倍数 | - | auto_quality | String | 画质自动调整开关 | 1:启用,0:关闭 | + | auto_quality | String | 画质自动调整开关 | 1:启用,0:关闭 | - 调用示例 diff --git a/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java b/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java index 1bf1e204970850590928affc7568b9ca5f6df340..b369e65997c142059991c0a70117cb47d57fbcdc 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java @@ -2,6 +2,8 @@ package com.huawei.cloudapp.ui.fragment.home; import static android.content.Context.MODE_PRIVATE; import static android.view.View.GONE; +import static android.widget.AbsListView.OnScrollListener.SCROLL_STATE_IDLE; +import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_DRAGGING; import static com.huawei.cloudapp.utils.CasConstantsUtil.COMMON; import static com.huawei.cloudapp.utils.CasConstantsUtil.EMPTY_STRING; import static com.huawei.cloudapp.utils.CasConstantsUtil.IAM_USERNAME; @@ -115,6 +117,7 @@ public class PhoneListFragment extends CloudPhoneFragment implements IHandleData private CasCommonDialog mDialog; private int offset = 0; private RecyclerView mRecyclerView; + private int mPosition = 0; private SmartRefreshLayout mRefreshLayout; private int mViewType = 1; private ImageView mViewTypeIV; @@ -327,6 +330,25 @@ public class PhoneListFragment extends CloudPhoneFragment implements IHandleData child.setScaleY(scale); } } + + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == SCROLL_STATE_IDLE || newState == SCROLL_STATE_DRAGGING) { + RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; + int firstVisiblePosition = linearLayoutManager.findFirstVisibleItemPosition(); + int lastVisiblePosition = linearLayoutManager.findLastVisibleItemPosition(); + if (firstVisiblePosition == recyclerView.getChildCount() - 1 && + lastVisiblePosition == recyclerView.getChildCount()) { + mPosition = lastVisiblePosition; + } else { + mPosition = (firstVisiblePosition + lastVisiblePosition) / 2; + } + } + } + } }); } mPhoneListAdapter.addChildClickViewIds(R.id.tv_item_reset, R.id.tv_item_restart, @@ -335,7 +357,10 @@ public class PhoneListFragment extends CloudPhoneFragment implements IHandleData mPhoneListAdapter.setOnItemChildClickListener(new OnItemChildClickListener() { @Override public void onItemChildClick(@NonNull BaseQuickAdapter adapter, @NonNull View view, int position) { - Log.e(TAG, "onItemChildClick: "); + if (mViewType != 0 && position != mPosition) { + Log.e(TAG, "Click another view, ignore."); + return; + } Phone phone = mPhoneListAdapter.getItem(position).getPhone(); if (view.getId() == R.id.tv_item_reset) { showPhoneActionDialog(false, phone.getPhoneName(), @@ -391,20 +416,7 @@ public class PhoneListFragment extends CloudPhoneFragment implements IHandleData if (mPhoneListAdapter.getData().isEmpty()) { return; } - int recyclerViewCenterY = mRecyclerView.getHeight() / 2; - int minDistance = Integer.MAX_VALUE; - int centerChildIndex = 0; - for (int i = 0; i < mLayoutManager.getChildCount(); i++) { - View childView = mLayoutManager.getChildAt(i); - int childCenterY = childView.getTop() + childView.getHeight() / 2; - int distance = Math.abs(childCenterY - recyclerViewCenterY); - if (distance < minDistance) { - minDistance = distance; - centerChildIndex = i; - } - } - int position = mLayoutManager.getPosition(mLayoutManager.getChildAt(centerChildIndex)); - startCloudPhoneActivity(mPhoneListAdapter.getItem(position)); + startCloudPhoneActivity(mPhoneListAdapter.getItem(mPosition)); } }); mEnterCloudPhoneButton.bringToFront();