小天互连移动端 SDK 接入指南
将小天互连 OA / IM 能力集成到第三方 iOS 与 Android 原生应用
iOS · HtOAFunctions 1.0.7 Android · com.oa8000.htim:main 1.0.11. 概述
移动端 SDK 以整页功能模块的形式提供能力:宿主 App 完成初始化与登录后,调用对应方法即可拉起小天互连的原生页面(消息、通讯录、待办、流程、会议、AI 助手等),无需自行实现界面。
能力清单
| 分类 | 能力 |
|---|---|
| IM | 消息列表、发起聊天、通讯录、未读角标、离线推送 |
| OA | 动态门户 / 工作台、待办、发起申请、信息发布、邮件 |
| 音视频 | 视频会议、音视频通话(需 HTRTC 授权) |
| 其他 | 扫一扫、AI 助手 |
接入主流程
引入 Pod → 配置权限 → 登录 → 设置代理(角标)→ 调用功能入口
引入依赖 → 工程配置 → Application 初始化 → 登录 → 注册监听
→ setHtImRouteListener → initHtImData → 调用功能入口
2. 环境要求
| 项目 | 要求 |
|---|---|
| Xcode | 15 及以上 |
| CocoaPods | 1.11 及以上(Demo 使用 1.16.2 验证) |
| iOS 部署目标 | 13.0 及以上 |
| 调试设备 | 真机(详见下方说明) |
| 私有源 | 需可访问小天互连 CocoaPods 私有源(地址与凭据在对接时提供) |
ios-arm64(真机)与 ios-x86_64-simulator 两个 slice,
不含 arm64 模拟器 slice。Apple Silicon Mac 上的 iOS 模拟器无法链接,需在 post_install 中对模拟器排除 arm64。
| 项目 | 要求 |
|---|---|
| Android Gradle Plugin | 7.4.2(Demo 验证版本) |
| Gradle | 7.5 |
| compileSdk / targetSdk | 33 |
| minSdk | 24(SDK 自身声明 23,宿主可下探到 23) |
| Java | 1.8(sourceCompatibility / targetCompatibility) |
| ABI | arm64-v8a(详见下方说明) |
| 私有源 | 需可访问小天互连 Maven 私有源(地址与凭据在对接时提供) |
arm64-v8a 与 armeabi-v7a 两套 .so。
需要支持 32 位设备时把 armeabi-v7a 一并加入,但不要引入 x86 / x86_64——没有对应 slice,模拟器会在加载 native 库时崩溃。
3. 接入准备
正式接入前,需要向小天互连获取以下信息与文件。带 必需 的项目缺失会导致无法编译或核心功能不可用。
| 项目 | 用途 | 平台 |
|---|---|---|
| 私有源地址与凭据必需 | 拉取 SDK 依赖 | 两端 |
| 服务器地址必需 | 登录时传入 serverUrl | 两端 |
| 测试账号必需 | 联调验证 | 两端 |
| HTRTC appId / appKey | 音视频通话与会议 | 两端 |
| userSdkKey 开放接口 (含 app_id / app_secret) | 免密登录 | 两端 |
| htrtc-2.2.3.aar必需 | 音视频包,不随依赖链传递 | Android |
native 库 .so必需 | 语音识别、证件识别、录音编码 | Android |
| 推送证书配置 | 离线推送(在服务端管理后台配置) | 两端 |
<小天互连私有源地址> 均为占位符,请联系小天互连商务或技术对接人获取真实地址与访问凭据后再替换。
4. 引入 SDK
两端都只需声明一个入口依赖,其余模块由依赖链自动解析引入。
platform :ios, '13.0'
use_frameworks!
# 私有源放最前面,官方源跟在后面
source '<小天互连私有源地址>'
source 'https://cdn.cocoapods.org/'
target 'YourApp' do
pod 'HtOAFunctions', '1.0.7'
end
pod repo update # 首次配置私有源后执行
pod install
open YourApp.xcworkspace # 注意不是 .xcodeproj
'1.0.7' 这样的精确版本号而非 ~>,避免解析到未经验证的新版本。
自动带出的模块
| 模块 | 版本 | 说明 |
|---|---|---|
| HtOAFunctions | 1.0.7 | OA 业务功能库,唯一入口 |
| HTChatProject | 1.0.7 | IM 聊天模块 |
| HtOACore | 1.0.4 | 基础底座 |
| HTAIKit | 1.0.6 | AI 助手,勿降级(1.0.6 修复了空表 NSNull 崩溃) |
| HtCardKit | 1.0.2 | 卡片消息渲染 |
| HtSocket | 1.0.1 | 长连接 |
| HtQRCode | 4.1.0 | 扫码 |
同时会引入的开源三方库:AFNetworking 4.0.1、FMDB 2.7.5、Masonry 1.1.0、MBProgressHUD 1.2.0、MJExtension 3.4.1、 MJRefresh 3.7.5、SDWebImage 5.0.6、Toast 4.0.0、TZImagePickerController 3.8.9、RealReachability 1.3.0、 IQKeyboardManager 6.5.10、PGDatePicker 2.6.9、HTRTC 2.2.0。
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
// 小天互连私有源(地址请向小天互连获取)
maven { url '<小天互连私有源地址>' }
// SDK 依赖的三方仓库,缺一不可
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://jitpack.io' }
maven { url 'https://developer.huawei.com/repo/' } // 华为 HMS Push
maven { url 'https://mvn.getui.com/nexus/content/repositories/releases/' } // 个推
google()
mavenCentral()
// 本地 aar 目录(存放 htrtc 音视频包)
flatDir { dirs 'repositories' }
}
}
Android · app/build.gradle
dependencies {
implementation 'com.oa8000.htim:main:1.0.1'
// htrtc 音视频包由私有源以外的渠道提供,需放入工程根目录 repositories/ 后本地引入
implementation(name: 'htrtc-2.2.3', ext: 'aar')
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
}
flatDir 方式引入的本地包,
这类依赖不会写进发布的 pom,因此不会随 com.oa8000.htim:main 自动传递。
需要小天互连单独提供,放进工程根目录的 repositories/ 文件夹。
自动带出的模块
| artifactId | 版本 | 说明 |
|---|---|---|
| main | 1.0.1 | 门户与统一管理入口,唯一需要声明的依赖 |
| lib-core | 1.0.1 | 网络、存储、权限、扫码等基础底座 |
| common | 1.0.1 | 公共实体与工具 |
| chat | 1.0.1 | IM 聊天与视频会议界面 |
| simsdk | 1.0.1 | IM 长连接 |
| email / trace / information meeting / calendar / hrwork office / car / task file-center / document | 1.0.1 | 邮件、流程、信息发布、会议、日程、人事考勤、办公、用车、任务、文件中心、文档中心 |
随依赖链引入的三方库:OkHttp 3.9.1 / Retrofit 2.4.0 / RxJava 2.2.3 / Glide 4.9.0 / Gson 2.8.5 /
Room 2.5.0-alpha01 / MMKV 1.2.11 / LiveEventBus 1.5.7 / SmartRefreshLayout 1.1.0 / XXPermissions 16.6 /
ImmersionBar 3.0.0 等,以及个推 gtsdk 3.2.14.0 + gtc 3.2.1.0 与华为/小米/OPPO/vivo 厂商推送通道。
NoSuchMethodError。
用 ./gradlew :app:dependencies 排查,必要时用 resolutionStrategy.force 锁回上表版本。
5. 工程配置
SDK 依赖链中包含若干已停止维护的三方库,在 Xcode 15+ 上直接编译会失败。请将下面的 post_install 原样加入 Podfile,
pod install 时会自动完成修补,无需手工改源码。
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
# Toast / Masonry 等老 pod 仍停在 iOS 8/9,动态链接时会去找
# Xcode 14.3 起已移除的 libarclite
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
# 语音识别库无 arm64 模拟器 slice
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
end
end
# ── 必须:修补两个已停止维护的依赖 ──────────────────────────
# AFNetworking (最后更新 2020) 与 RealReachability (2021) 引用了系统私有头
# netinet6/in6.h,Xcode 15+ 会报 "Use of private header from outside its
# module" 而编译失败。两者均无更新版本可升,只能在安装后修补源码。
root = installer.sandbox.root
[
'AFNetworking/AFNetworking/AFHTTPSessionManager.m',
'AFNetworking/AFNetworking/AFNetworkReachabilityManager.m',
'RealReachability/RealReachability/Connection/LocalConnection.m',
].each do |rel|
path = File.join(root, rel)
next unless File.exist?(path)
text = File.read(path)
patched = text.gsub(/^#import <netinet6\/in6\.h>\n/, '')
File.write(path, patched) if patched != text
end
# PingFoundation.h 用到 sa_family_t 却未导入其所在模块
ping = File.join(root, 'RealReachability/RealReachability/Ping/PingFoundation.h')
if File.exist?(ping)
text = File.read(ping)
unless text.include?('_sa_family_t.h')
text = text.sub(/(#import <Foundation\/Foundation\.h>\n)/,
"\\1#import <sys/_types/_sa_family_t.h>\n")
File.write(ping, text)
end
end
end
| 修补项 | 不做会怎样 |
|---|---|
| IPHONEOS_DEPLOYMENT_TARGET | 老 pod 停留在 iOS 8/9,链接时查找 Xcode 14.3 起已移除的 libarclite 而失败 |
| EXCLUDED_ARCHS[simulator] | Apple Silicon 模拟器编译报找不到 arm64 slice |
| netinet6/in6.h | AFNetworking / RealReachability 报 "Use of private header from outside its module" |
| PingFoundation.h | 报 sa_family_t 未声明 |
容器要求
SDK 的所有功能入口都是从当前 keyWindow 的导航栈打开页面,请确保 App 的根视图是 UINavigationController(或其子类):
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
self.rootNaviController = [[UINavigationController alloc]
initWithRootViewController:[[HLoginViewController alloc] init]];
self.window.rootViewController = self.rootNaviController;
[self.window makeKeyAndVisible];
gradle.properties
android.useAndroidX=true
android.enableJetifier=true # SDK 依赖链中仍有 support 库,必须开启
android.nonTransitiveRClass=true
android.injected.testOnly=false # 允许直接安装 debug 包到设备
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
app/build.gradle
android {
compileSdk 33
defaultConfig {
minSdk 24
targetSdk 33
ndk {
// SDK 的 native 库只有 arm64-v8a / armeabi-v7a
abiFilters "arm64-v8a"
}
manifestPlaceholders = [
// 个推 APPID(3.1.2.0 起占位符名为 GETUI_APPID)
GETUI_APPID: "你的个推 AppId",
// 高德地图 key(定位、位置消息、考勤打卡使用)
AMAP_APIKEY: "你的高德 Key",
]
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// SDK 的界面使用 DataBinding,宿主必须一并开启
dataBinding { enabled = true }
buildFeatures { viewBinding true }
configurations {
// 与 SDK 依赖链中的注解库冲突,必须排除
implementation.exclude group: 'com.intellij', module: 'annotations'
}
}
GETUI_APPID 与 AMAP_APIKEY 由 SDK 内部 Manifest 引用,
宿主不声明会导致 manifest 合并失败,构建直接报错。两个 key 需分别在个推、高德开放平台申请,并绑定宿主 App 的包名与签名。
AndroidManifest.xml
SDK 与宿主都声明了 android:theme 与 android:allowBackup,需要用 tools:replace 指定以宿主为准:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MyApp"
android:allowBackup="false"
android:theme="@style/Theme.YourApp"
tools:replace="android:theme, android:allowBackup"
tools:targetApi="33">
...
</application>
</manifest>
native 库
以下 .so 需放入 app/src/main/jniLibs/<abi>/(由小天互连随 SDK 提供):
| 文件 | 用途 |
|---|---|
| libmsc.so | 讯飞语音识别(语音转文字) |
| libmp3lame.so | 语音消息 MP3 编码 |
| libclientalg.so | 识别算法库 |
| libIDCARDDLL.so | 身份证识别 |
| libcloudwalksdk.so | 云从人脸识别 |
混淆
SDK 各模块自带 consumer-rules.pro,宿主开启 minifyEnabled 时只需补充地图相关规则:
-keep class com.baidu.** {*;}
-keep class vi.com.** {*;}
-keep class com.baidu.vi.** {*;}
-dontwarn com.baidu.**
6. 权限配置
SDK 涉及相机、相册、麦克风、定位、日历等能力,需要在 Info.plist 中声明对应用途说明,否则调用时会直接崩溃。
| Key | 用于 |
|---|---|
| NSCameraUsageDescription | 扫一扫、拍照发送、审批附件上传 |
| NSPhotoLibraryUsageDescription | 相册选图发送 |
| NSPhotoLibraryAddUsageDescription | 保存图片到相册 |
| NSMicrophoneUsageDescription | 语音消息、音视频通话与会议 |
| NSLocationWhenInUseUsageDescription | 考勤打卡、发送位置 |
| NSLocationAlwaysAndWhenInUseUsageDescription | 同上(后台定位场景) |
| NSCalendarsUsageDescription | 日程写入系统日历 |
| NSLocalNetworkUsageDescription | 与服务器的本地网络数据交互 |
<key>NSCameraUsageDescription</key>
<string>请点击"好"以允许访问。如不允许,您将不能使用审批上传等功能。</string>
<key>NSMicrophoneUsageDescription</key>
<string>用于发送语音消息、发起通话和会议</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>用于发送位置、考勤打卡</string>
ATS
若服务端为 HTTP 或使用自签证书,需要放开 App Transport Security:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
NSAllowsArbitraryLoads 全量放开会被 App Store 审核问询。生产环境建议服务端启用受信任的 HTTPS 证书,
或改用 NSExceptionDomains 只对特定域名放开。
SDK 各模块的 Manifest 已声明所需权限,合并后会自动进入宿主 App,无需手工重复声明。宿主需要关注的是:这些权限会出现在应用商店的权限清单里,以及 Android 6.0+ 的运行时授权。
| 权限 | 用于 | 运行时授权 |
|---|---|---|
| INTERNET ACCESS_NETWORK_STATE ACCESS_WIFI_STATE | 网络通信、长连接 | 否 |
| CAMERA | 扫一扫、拍照发送、视频通话 | 是 |
| RECORD_AUDIO MODIFY_AUDIO_SETTINGS | 语音消息、音视频通话与会议 | 是 |
| READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE | 附件上传下载、图片选择 | 是 |
| MANAGE_EXTERNAL_STORAGE | 文件中心(Android 11+ 全文件访问) | 是(需跳设置页) |
| ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION | 考勤打卡、位置消息 | 是 |
| POST_NOTIFICATIONS | Android 13+ 通知展示 | 是 |
| READ_PHONE_STATE | 设备唯一标识(登录设备绑定) | 是 |
| SYSTEM_ALERT_WINDOW | 来电悬浮窗 | 是(需跳设置页) |
| CALL_PHONE | 通讯录拨号 | 是 |
| REQUEST_INSTALL_PACKAGES | 应用内升级安装 | 是 |
| BLUETOOTH / BLUETOOTH_CONNECT | 音视频通话音频路由 | Android 12+ 是 |
| VIBRATE / WAKE_LOCK | 消息提醒、保活 | 否 |
READ_SMS / SEND_SMS / MANAGE_EXTERNAL_STORAGE 等敏感权限在国内应用商店上架时需要单独说明用途。
如果宿主 App 用不到相关功能,可在自己的 Manifest 中用
<uses-permission android:name="…" tools:node="remove" /> 移除,但需先与小天互连确认不影响所用功能。
7. 初始化
iOS 端无需单独的初始化步骤,SDK 在登录时一并完成内部初始化。直接进入第 8 节 登录即可。
+[HtClienInitUtil initClientDataWithConfig:]。常规接入无需调用。
#import <HtOAFunctions/HtClienInitUtil.h>
[HtClienInitUtil initClientDataWithConfig:configM]; // 可选
Android 端必须在 Application.onCreate() 中初始化,且早于任何其他 SDK 调用。
初始化工具类、MMKV 存储、换肤框架、加密数据库、音视频 SDK 与前后台切换监听。方法内部有幂等保护,重复调用只生效一次。
| 参数 | 类型 | 说明 |
|---|---|---|
| application必填 | Application | Application 上下文 |
| htImInitConfig可选 | HtImInitConfig | 初始化配置,传 null 时读取 SDK 内置默认值 |
HtImInitConfig 参数
| 参数 | 类型 | 说明 |
|---|---|---|
| htrtcAppId可选 | String | HTRTC 音视频 appId,使用音视频通话 / 会议时必填 |
| htrtcAppKey可选 | String | HTRTC 音视频 appKey,同上 |
| extra可选 | String | 扩展字段 |
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
HtImInitConfig config = new HtImInitConfig();
config.setHtrtcAppId("你的 htrtc appId");
config.setHtrtcAppKey("你的 htrtc appKey");
HtImClientManager.getInstance().initHtImSdk(this, config);
// 服务端为自签证书时放行 HTTPS
try {
HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketClient.getSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketClient.getHostnameVerifier());
} catch (Exception e) {
e.printStackTrace();
}
KLog.init(true); // 开发期打开 SDK 日志,上线前置为 false
}
}
htrtcAppId / htrtcAppKey 为空时 SDK 会跳过音视频初始化并打日志,
此时视频会议与音视频通话不可用,其余功能正常。
释放音视频资源。在 Application.onTerminate() 中调用。
@Override
public void onTerminate() {
super.onTerminate();
HtImClientManager.getInstance().cleanHtImSdk();
}
下拉刷新样式(建议)
SDK 的列表页使用 SmartRefreshLayout,宿主可在 Application 的静态块中统一配置全局头尾样式:
static {
ClassicsFooter.REFRESH_FOOTER_LOADING = "加载中...";
SmartRefreshLayout.setDefaultRefreshHeaderCreator(
(context, layout) -> new ClassicsHeader(context));
SmartRefreshLayout.setDefaultRefreshFooterCreator(
(context, layout) -> new ClassicsFooter(context).setDrawableSize(20));
}
8. 登录
两端都支持账号密码登录与令牌免密登录两种方式,登录是使用其余所有接口的前提。
使用配置模型登录,成功后 SDK 内部会完成长连接建立、本地库初始化与用户信息缓存。
-(void)doHtLoginWithLoginConfigM:(HtLoginCofigModel *)cofigM
callBlock:(HtImClientBlock)callBlock;
HtLoginCofigModel 参数
| 参数 | 类型 | 说明 |
|---|---|---|
| serverUrl必填 | NSString | 服务器地址,如 https://im.example.com |
| loginId必填 | NSString | 登录账号 ID |
| password二选一 | NSString | 登录密码。账号密码登录时使用 |
| userSdkKey二选一 | NSString | 用户身份令牌,由后端签发。用于免密集成 |
| deviceToken可选 | NSString | APNs 设备唯一标识,用于离线推送。见第 13 节 |
| languageType可选 | NSString | 语言:CN 中文(默认)/ EN 英文 |
| htrtcAppId可选 | NSString | HTRTC 音视频 appId,使用视频会议时必填 |
| htrtcAppKey可选 | NSString | HTRTC 音视频 appKey,使用视频会议时必填 |
| extra可选 | NSString | 扩展字段,透传给服务端 |
回调结果码 HtReqCode
| 枚举 | 值 | 含义 |
|---|---|---|
| HtReqCodeSuccess | 1 | 登录成功 |
| HtReqCodeServerIPErr | 0 | 服务器地址错误 / 不可达 |
| HtReqCodeServerErr | -1 | 服务端异常 |
| HtReqCodeLoginErr | -2 | 登录失败(账号或密码错误) |
回调第二个参数 response 为服务端返回的原始内容,失败时可用于展示错误原因。
账号密码登录
HtLoginCofigModel *configM = HtLoginCofigModel.new;
configM.serverUrl = self.serverField.text;
configM.loginId = self.accountField.text;
configM.password = self.pwdField.text;
configM.languageType = @"CN";
[[HtImClientManager shareInstance] doHtLoginWithLoginConfigM:configM
callBlock:^(NSInteger type, id response) {
NSLog(@"登录===%@", response);
if (type == HtReqCodeSuccess) {
HFunctionsViewController *funcVC =
[[HFunctionsViewController alloc] initWithNibName:@"HFunctionsViewController"
bundle:[NSBundle bundleForClass:self.class]];
[self.navigationController pushViewController:funcVC animated:YES];
}
}];
令牌免密登录
HtLoginCofigModel *configM = HtLoginCofigModel.new;
configM.serverUrl = @"https://im.example.com";
configM.loginId = user.account;
configM.userSdkKey = tokenFromYourBackend; // 后端下发,勿硬编码
[[HtImClientManager shareInstance] doHtLoginWithLoginConfigM:configM
callBlock:^(NSInteger type, id response) {
if (type == HtReqCodeSuccess) { /* ... */ }
}];
依次完成服务器初始化 → 登录初始化 → 用户登录 → IM 长连接鉴权。全流程为异步,结果通过回调返回。
HtImLoginConfig 参数
| 参数 | 类型 | 说明 |
|---|---|---|
| serverUrl必填 | String | 服务器地址,如 http://im.example.com:31921 |
| loginId二选一 | String | 登录账号 ID。与 password 成对使用 |
| password二选一 | String | 登录密码 |
| userSdkKey二选一 | String | 用户身份令牌,由后端签发。传了它就不再校验 loginId / password |
| languageType可选 | String | CN 中文(默认)/ EN 英文 |
| vendorDeviceToken可选 | String | 厂商推送 token,见第 13 节 |
| cidDeviceToken可选 | String | 个推 CID,见第 13 节 |
| extra可选 | String | 扩展字段 |
serverUrl 为空 → 回调"服务器地址校验失败";
userSdkKey 为空且 loginId / password 任一为空 → 回调"用户校验失败";
languageType 为空时自动补 CN。
ServerInitCallbackListener 回调
| 回调 / 常量 | 值 | 说明 |
|---|---|---|
| onSuccess() | — | 登录成功,可以进入业务页面 |
| onError(code, error) | — | 失败,error 为可直接展示的中文提示 |
| LOGIN_ERROR | -1 | 登录错误(地址不可达、账号密码错误、服务器初始化失败等) |
| LOGIN_UPDATE_PASSWORD | -2 | 使用初始密码登录被拒,需先修改密码 |
initHtImSdk 就登录会抛
IllegalStateException: The htim sdk is not initialized。
账号密码登录
HtImLoginConfig config = new HtImLoginConfig();
config.setServerUrl(url); // 服务器地址
config.setLoginId(loginId); // 登录账号 Id
config.setPassword(password); // 登录密码
config.setLanguageType("CN"); // "CN"-中文 "EN"-英文
HtImClientManager.getInstance().doHtImLogin(config, new ServerInitCallbackListener() {
@Override
public void onSuccess() {
loadingDialog.dismiss();
startActivity(new Intent(LoginActivity.this, MainActivity.class));
}
@Override
public void onError(int code, String error) {
loadingDialog.dismiss();
ToastUtils.showShort(error);
}
});
令牌免密登录
HtImLoginConfig config = new HtImLoginConfig();
config.setServerUrl("http://im.example.com:31921");
config.setUserSdkKey(tokenFromYourBackend); // 后端下发,勿硬编码
config.setLanguageType("CN");
HtImClientManager.getInstance().doHtImLogin(config, listener);
userSdkKey 的开放接口地址、app_id 与 app_secret 由小天互连在对接时提供。
生产环境推荐使用令牌免密登录,客户端不接触明文密码。
9. 登录后初始化
iOS 端无此步骤。登录成功后直接调用功能入口即可,如需未读角标请设置代理(见第 11 节)。
登录成功后,在承载功能入口的 Activity 中还需要完成两步初始化,否则模块间跳转、会话列表、未读数都不会工作。
注册模块间跳转路由与各类事件观察者:单据选择/查看、文档选择/查看、系统消息跳转、模块跳转,以及未读数、待办数、登录失效、长连接状态的监听。 不调用会导致聊天中发送单据/文档、点击系统消息等功能无响应。
HtImClientManager.getInstance().setHtImRouteListener(this);
加载本地通讯录/群组缓存、注册 IM 长连接客户端、载入会话列表并计算未读总数。 内部通过定时器等待 IM 服务地址就绪后再注册,无需宿主自行重试。
HtImClientManager.getInstance().initHtImData(this);
addHtImChatListener,再 setHtImRouteListener,最后 initHtImData。
监听注册晚于 initHtImData 会漏掉首次未读数回调。
IllegalStateException: Please call the doHtImLogin method first,
因此必须放在登录成功之后。
移除 setHtImRouteListener 注册的全部观察者。宿主销毁 SDK 承载页或退出登录时调用,避免观察者泄漏。
HtImClientManager.getInstance().removeHtImRoutListener();
10. 退出登录
断开长连接、通知服务端登出、清理本地登录态与缓存。
| 平台 | 方法 |
|---|---|
| iOS | -[HtImClientManager doHtLogout](无参数、无回调) |
| Android | doHtImLogout(Activity activity) |
[[HtImClientManager shareInstance] doHtLogout];
[self.navigationController popViewControllerAnimated:YES];
dealloc 中也调用一次,避免页面销毁后长连接与角标轮询继续运行。
HtImClientManager.getInstance().doHtImLogout(MainActivity.this);
finish();
doHtImLogin 即可,不需要重复调用 initHtImSdk。
11. 事件监听与角标
两端都通过回调把消息未读数、待办数推给宿主,由宿主自行决定角标的展示形式。Android 额外提供登录失效与长连接状态回调。
| 事件 | iOS | Android |
|---|---|---|
| 注册方式 | delegate 属性(weak,单个) | addHtImChatListener()(可多个) |
| 消息未读数 | -receiveMsgUnreadNum: | onRecentMsgUnreadNum(int) |
| 待办数 | -receiveTodoUnreadNum: | onRecentToDoNum(int) |
| 登录失效 | — | onLogoutStatus(Map) |
| 长连接状态 | — | onHIMSocketStatus(boolean) |
设置 HtImClientManager.delegate 后,SDK 会在未读数变化时主动回调。两个方法均为 @optional。
delegate 为 weak 属性,请确保持有者存活;建议在登录成功后设置。
@interface HFunctionsViewController () <HtImClientDelegate>
@property (weak, nonatomic) IBOutlet UILabel *msgUnreadLabel;
@property (weak, nonatomic) IBOutlet UILabel *todoUnreadLabel;
@end
- (void)viewDidLoad {
[super viewDidLoad];
[HtImClientManager shareInstance].delegate = self;
}
#pragma mark - HtImClientDelegate
- (void)receiveMsgUnreadNum:(NSInteger)unreadNum {
self.msgUnreadLabel.text = [NSString stringWithFormat:@"%ld", unreadNum];
self.msgUnreadLabel.hidden = (unreadNum <= 0);
}
- (void)receiveTodoUnreadNum:(NSInteger)unreadNum {
self.todoUnreadLabel.text = [NSString stringWithFormat:@"%ld", unreadNum];
self.todoUnreadLabel.hidden = (unreadNum <= 0);
}
[UIApplication sharedApplication].applicationIconBadgeNumber = unreadNum;(需已申请通知权限)。
接口的四个方法都是 default 方法,按需重写即可。支持注册多个监听器。
登录失效状态码
onLogoutStatus 的入参形如
{"type": 1, "message": "您的账号已经在其他设备登录…"},message 可直接展示。
| type | 含义 |
|---|---|
| 1 | 账号在其他设备登录(多端互踢) |
| 9 | 账号被系统强制退出,请联系管理员 |
| -1 | IM token 已过期 |
| -2 | IM 地址已失效 |
| -3 | IM token 已失效 |
| -11 | token 已过期 |
| -12 | 地址已失效 |
注册与反注册
| 方法 | 说明 |
|---|---|
| addHtImChatListener(listener) | 添加监听,内部去重,同一实例不会重复添加 |
| removeHtImChatListener(listener) | 移除指定监听 |
| removeAllHtImChatListener() | 清空全部监听 |
HtImClientManager.getInstance().addHtImChatListener(new HtImChatListener() {
@Override
public void onRecentMsgUnreadNum(int unreadNum) {
TextView tv = findViewById(R.id.chatMsgUnreadNum);
if (tv != null) {
tv.setVisibility(unreadNum > 0 ? View.VISIBLE : View.GONE);
tv.setText(String.valueOf(unreadNum));
}
}
@Override
public void onRecentToDoNum(int num) {
TextView tv = findViewById(R.id.chatMsgToDoNum);
if (tv != null) {
tv.setVisibility(num > 0 ? View.VISIBLE : View.GONE);
tv.setText(String.valueOf(num));
}
}
@Override
public void onLogoutStatus(Map<String, Object> map) {
ToastUtils.showShort(String.valueOf(map.get("message")));
startActivity(new Intent(MainActivity.this, LoginActivity.class));
finish();
}
@Override
public void onHIMSocketStatus(boolean isConnect) {
// 可用于展示"连接中/网络异常"提示条
}
});
onDestroy() 中移除,否则会内存泄漏:
removeAllHtImChatListener()
12. 功能入口
登录成功后即可调用以下方法拉起对应的原生页面。除特别说明外,均为无返回值的页面跳转。
openHtIm* / sendHtImChat 方法在未登录时都会抛
IllegalStateException: Please call the doHtImLogin method first。
打开动态门户(工作台首页),展示服务端配置的门户布局与应用入口。
[[HtImClientManager shareInstance] goHomeView];
HtImClientManager.getInstance().openHtImWorkStation(this);
打开 IM 消息会话列表页,可在其中选择会话进入聊天。
[[HtImClientManager shareInstance] goMsgView];
HtImClientManager.getInstance().openHtImChatMessage(context);
打开组织架构通讯录,支持按部门浏览与搜索人员。
[[HtImClientManager shareInstance] goAddressView];
HtImClientManager.getInstance().openHtImContact(this);
打开选人界面,由用户从通讯录挑选聊天对象后进入会话。
// 选人聊天
[[HtImClientManager shareInstance] goChatView];
// 跳过选人,直接进入与指定对象的会话
// uuid 为聊天对象的用户 ID 或群 ID
[[HtImClientManager shareInstance] goChatViewWith:@"zzc"];
uuid 需与小天互连侧的账号体系一致。若宿主 App 使用自有 ID,需先由服务端完成映射。传入不存在的 uuid 会打开空会话。
// 选人聊天。当前登录用户会被自动置为已选中且不可取消
HtImClientManager.getInstance().sendHtImChat(context);
goChatViewWith: 能力),请联系小天互连确认版本支持情况。
打开二维码扫描页。SDK 内部会处理登录授权、加好友等已知格式的二维码。
扫描结果通过 block 回传,type 为结果码(HtReqCodeSuccess 表示成功),response 为扫描到的内容。
[[HtImClientManager shareInstance] goScanCallBlock:^(NSInteger type, id response) {
if (type == HtReqCodeSuccess) {
NSLog(@"扫码结果:%@", response);
}
}];
Info.plist 中声明 NSCameraUsageDescription,否则打开扫描页会崩溃。
支持从相册选图识别。SDK 内部会先申请相机权限并展示用途说明;扫描结果由 SDK 内部处理,不回传给宿主。
HtImClientManager.getInstance().openHtImScanQr(context);
打开写邮件页面,支持选择收件人、添加附件并发送。
[[HtImClientManager shareInstance] goSendMailView];
HtImClientManager.getInstance().openHtImEmailSend(this);
打开待办事项列表,可查看并处理待审批、待办理的流程。待办数量通过第 11 节的回调获取。
[[HtImClientManager shareInstance] goTodoView];
HtImClientManager.getInstance().openHtImPlatformTodo(this);
打开流程模板选择页,列出服务端配置的可发起流程(请假、报销、用车等),选择后进入表单填写。
[[HtImClientManager shareInstance] goApplyView];
HtImClientManager.getInstance().openHtImTraceTemplateSelect(this);
打开信息发布 / 发送通知页面。发布权限由服务端按账号角色控制。
[[HtImClientManager shareInstance] goSendNotice];
HtImClientManager.getInstance().openHtImInformation(this);
打开视频会议模块,可创建会议、加入会议与查看会议列表。
[[HtImClientManager shareInstance] goVideoMeeting];
HtLoginCofigModel 中配置 htrtcAppId 与 htrtcAppKey,
并已声明 NSCameraUsageDescription 与 NSMicrophoneUsageDescription。
HtImClientManager.getInstance().openHtImChatMeeting(context);
HtImInitConfig 中配置 htrtcAppId 与 htrtcAppKey,
且工程已引入 htrtc-2.2.3.aar。缺任一项,会议页面可打开但无法建立音视频通道。
打开 AI 助手会话页,支持自然语言问答与卡片形式的业务结果展示。
[[HtImClientManager shareInstance] goAIChat];
NSNull 导致的崩溃。
首次调用会先完成 AI SDK 初始化,成功后自动进入聊天页;已初始化过则直接打开。初始化失败时 Toast 提示"AI 初始化失败"。
HtImClientManager.getInstance().openHtImAiAssistant(MainActivity.this);
agentId 为硬编码的联调值。
接入生产环境前请与小天互连确认该模块是否已切换为动态配置,否则 AI 助手在客户环境下无法连通。
13. 离线推送
| iOS | Android | |
|---|---|---|
| 通道 | APNs | 个推 + 华为/小米/OPPO/vivo 厂商通道 |
| 上报字段 | deviceToken | vendorDeviceToken + cidDeviceToken |
| 点击处理 | HtOAPushHandleManager | 个推 GTIntentService 回调 |
① 注册并上报 deviceToken
在 AppDelegate 中申请通知权限并注册 APNs,把拿到的 token 写入登录配置:
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSMutableString *token = [NSMutableString string];
const unsigned char *bytes = deviceToken.bytes;
for (NSUInteger i = 0; i < deviceToken.length; i++) {
[token appendFormat:@"%02x", bytes[i]];
}
self.deviceTokenString = token; // 登录时赋给 configM.deviceToken
}
deviceToken 是 HtLoginCofigModel 的字段,需要在登录前拿到。
若 token 晚于登录返回,可在拿到后重新调用一次登录以完成绑定。
② 交给 SDK 处理推送内容
把 APNs 回调中的 userInfo 原样传入,SDK 会解析并跳转到对应的会话或业务页面。
#import <HtOAFunctions/HtOAPushHandleManager.h>
// 用户点击通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
NSDictionary *userInfo = response.notification.request.content.userInfo;
[[HtOAPushHandleManager new] pushHandleData:userInfo];
completionHandler();
}
③ 服务端配置推送证书
需要把 App 的 APNs 推送证书(或 p8 密钥)配置到小天互连服务端,并确保 Bundle ID 与证书一致。此步骤由服务端同学在管理后台完成。
SDK 采用个推 + 厂商通道双通道方案:个推负责统一调度,厂商通道负责应用被杀后的到达率。
厂商通道的 SDK 已打进 com.oa8000.htim:main,宿主只需完成个推接入与两个 token 的上报。
① 申请并配置个推 AppId
在个推开放平台创建应用(包名需与宿主 App 一致),把 AppId 填入 manifestPlaceholders,并在 Manifest 中声明:
<application ...>
<meta-data
android:name="PUSH_APPID"
android:value="你的个推 AppId" />
<service
android:name=".push.getui.HtGTPushService"
android:exported="false"
android:label="PushService"
android:process=":pushservice" />
<service android:name=".push.getui.HtGTIntentService" />
</application>
<queries>
<!-- 个推适配 Android 11+ -->
<intent>
<action android:name="com.getui.sdk.action" />
</intent>
</queries>
② 实现个推服务类
两个类都必须由宿主 App 自己实现并在 Manifest 中声明,个推才能回调:
public class HtGTPushService extends PushService {
}
public class HtGTIntentService extends GTIntentService {
// 接收 CID —— 这是要上报给 SDK 的推送标识
@Override
public void onReceiveClientId(Context context, String clientid) {
App.cidDeviceToken = clientid;
}
@Override
public void onReceiveMessageData(Context context, GTTransmitMessage msg) {
String data = new String(msg.getPayload());
// 透传消息,按需自行处理
}
@Override public void onReceiveServicePid(Context context, int pid) { }
@Override public void onReceiveOnlineState(Context context, boolean online) { }
@Override public void onReceiveCommandResult(Context context, GTCmdMessage msg) { }
@Override public void onNotificationMessageArrived(Context context, GTNotificationMessage msg) { }
@Override public void onNotificationMessageClicked(Context context, GTNotificationMessage msg) { }
}
③ 登录时上报 token
把两类 token 写入登录配置,SDK 会拼成 vendorDeviceToken;cidDeviceToken 一并提交服务端:
| 字段 | 来源 | 格式示例 |
|---|---|---|
| vendorDeviceToken | 厂商通道 token | OPPO_CN_81d9c923b0f6… |
| cidDeviceToken | 个推 onReceiveClientId 回调 | e7cee28a8ffb5272… |
config.setVendorDeviceToken(vendorToken); // 厂商推送 token
config.setCidDeviceToken(cid); // 个推 CID,非必填
doHtImLogin 完成绑定。
14. 完整集成示例
AppDelegate
#import "AppDelegate.h"
#import "HLoginViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
self.window.backgroundColor = UIColor.whiteColor;
// SDK 的功能页依赖导航栈,根控制器需为 UINavigationController
self.rootNaviController = [[HtNaviViewController alloc]
initWithRootViewController:[[HLoginViewController alloc] init]];
self.window.rootViewController = self.rootNaviController;
[self.window makeKeyAndVisible];
return YES;
}
@end
登录页
#import <HtOAFunctions/HtImClientManager.h>
- (IBAction)goLogin:(UIButton *)sender {
HtLoginCofigModel *configM = HtLoginCofigModel.new;
configM.serverUrl = self.serverField.text;
configM.loginId = self.accountField.text;
configM.password = self.pwdField.text;
[[HtImClientManager shareInstance] doHtLoginWithLoginConfigM:configM
callBlock:^(NSInteger type, id response) {
NSLog(@"登录===%@", response);
if (type == HtReqCodeSuccess) {
HFunctionsViewController *funcVC =
[[HFunctionsViewController alloc] initWithNibName:@"HFunctionsViewController"
bundle:[NSBundle bundleForClass:self.class]];
[self.navigationController pushViewController:funcVC animated:YES];
}
}];
}
功能入口页
#import <HtOAFunctions/HtImClientManager.h>
@interface HFunctionsViewController () <HtImClientDelegate>
@property (weak, nonatomic) IBOutlet UILabel *msgUnreadLabel;
@property (weak, nonatomic) IBOutlet UILabel *todoUnreadLabel;
@end
@implementation HFunctionsViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"功能列表";
self.msgUnreadLabel.backgroundColor = UIColor.redColor;
self.msgUnreadLabel.layer.cornerRadius = 10;
self.msgUnreadLabel.layer.masksToBounds = YES;
self.msgUnreadLabel.text = @"";
[HtImClientManager shareInstance].delegate = self;
}
#pragma mark - 功能入口
- (IBAction)goHome:(id)sender { [[HtImClientManager shareInstance] goHomeView]; }
- (IBAction)goMsg:(id)sender { [[HtImClientManager shareInstance] goMsgView]; }
- (IBAction)goAddress:(id)sender { [[HtImClientManager shareInstance] goAddressView]; }
- (IBAction)goChat:(id)sender { [[HtImClientManager shareInstance] goChatView]; }
- (IBAction)goMail:(id)sender { [[HtImClientManager shareInstance] goSendMailView]; }
- (IBAction)goTodo:(id)sender { [[HtImClientManager shareInstance] goTodoView]; }
- (IBAction)goApply:(id)sender { [[HtImClientManager shareInstance] goApplyView]; }
- (IBAction)goInformation:(id)sender { [[HtImClientManager shareInstance] goSendNotice]; }
- (IBAction)goVideoMeeting:(id)sender{ [[HtImClientManager shareInstance] goVideoMeeting]; }
- (IBAction)goAIChat:(id)sender { [[HtImClientManager shareInstance] goAIChat]; }
- (IBAction)goScan:(id)sender {
[[HtImClientManager shareInstance] goScanCallBlock:^(NSInteger type, id response) {
NSLog(@"扫码结果:%@", response);
}];
}
- (IBAction)goLogout:(id)sender {
[[HtImClientManager shareInstance] doHtLogout];
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - HtImClientDelegate
- (void)receiveMsgUnreadNum:(NSInteger)unreadNum {
self.msgUnreadLabel.text = [NSString stringWithFormat:@"%ld", unreadNum];
self.msgUnreadLabel.hidden = (unreadNum <= 0);
}
- (void)receiveTodoUnreadNum:(NSInteger)unreadNum {
self.todoUnreadLabel.text = [NSString stringWithFormat:@"%ld", unreadNum];
self.todoUnreadLabel.hidden = (unreadNum <= 0);
}
- (void)dealloc {
[[HtImClientManager shareInstance] doHtLogout];
}
@end
Swift 调用
SDK 以 Objective-C framework 形式提供。Podfile 中使用了 use_frameworks!,因此 Swift 工程可直接 import 使用,无需桥接头文件。
import HtOAFunctions
let config = HtLoginCofigModel()
config.serverUrl = "https://im.example.com"
config.loginId = account
config.userSdkKey = tokenFromYourBackend
HtImClientManager.shareInstance().doHtLogin(withLoginConfigM: config) { type, response in
if type == HtReqCodeSuccess.rawValue {
HtImClientManager.shareInstance().goMsgView()
}
}
Application
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
HtImInitConfig config = new HtImInitConfig();
config.setHtrtcAppId("你的 htrtc appId");
config.setHtrtcAppKey("你的 htrtc appKey");
HtImClientManager.getInstance().initHtImSdk(this, config);
try {
HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketClient.getSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketClient.getHostnameVerifier());
} catch (Exception e) {
e.printStackTrace();
}
KLog.init(true);
}
@Override
public void onTerminate() {
super.onTerminate();
HtImClientManager.getInstance().cleanHtImSdk();
}
static {
ClassicsFooter.REFRESH_FOOTER_LOADING = "加载中...";
SmartRefreshLayout.setDefaultRefreshHeaderCreator(
(context, layout) -> new ClassicsHeader(context));
SmartRefreshLayout.setDefaultRefreshFooterCreator(
(context, layout) -> new ClassicsFooter(context).setDrawableSize(20));
}
}
登录页
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
findViewById(R.id.textView).setOnClickListener(view -> {
LoadingDialog loadingDialog = new LoadingDialog(this);
loadingDialog.show("正在登录");
HtImLoginConfig config = new HtImLoginConfig();
config.setServerUrl(sUrl.getText().toString()); // 服务器地址
config.setLoginId(sLoginId.getText().toString()); // 登录账号 Id
config.setPassword(sPassword.getText().toString()); // 登录密码
config.setLanguageType("CN"); // "CN"-中文 "EN"-英文
config.setVendorDeviceToken(vendorToken); // 厂商推送 token
config.setCidDeviceToken(cid); // 个推 CID,非必填
HtImClientManager.getInstance().doHtImLogin(config, new ServerInitCallbackListener() {
@Override
public void onSuccess() {
loadingDialog.dismiss();
startActivity(new Intent(LoginActivity.this, MainActivity.class));
}
@Override
public void onError(int code, String error) {
loadingDialog.dismiss();
ToastUtils.showShort(error);
}
});
});
}
}
功能入口页
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 先注册监听
HtImClientManager.getInstance().addHtImChatListener(new HtImChatListener() {
@Override
public void onRecentMsgUnreadNum(int unreadNum) {
TextView tv = findViewById(R.id.chatMsgUnreadNum);
tv.setVisibility(unreadNum > 0 ? View.VISIBLE : View.GONE);
tv.setText(String.valueOf(unreadNum));
}
@Override
public void onRecentToDoNum(int num) {
TextView tv = findViewById(R.id.chatMsgToDoNum);
tv.setVisibility(num > 0 ? View.VISIBLE : View.GONE);
tv.setText(String.valueOf(num));
}
});
// 2. 再注册路由,最后加载数据
HtImClientManager.getInstance().setHtImRouteListener(this);
HtImClientManager.getInstance().initHtImData(this);
HtImClientManager mgr = HtImClientManager.getInstance();
findViewById(R.id.textView) .setOnClickListener(v -> mgr.openHtImWorkStation(this)); // 工作台
findViewById(R.id.textView2) .setOnClickListener(v -> mgr.openHtImChatMessage(this)); // 消息
findViewById(R.id.textView3) .setOnClickListener(v -> mgr.openHtImContact(this)); // 通讯录
findViewById(R.id.textView_send_chat) .setOnClickListener(v -> mgr.sendHtImChat(this)); // 发起聊天
findViewById(R.id.textView_scan) .setOnClickListener(v -> mgr.openHtImScanQr(this)); // 扫一扫
findViewById(R.id.textView_send_email) .setOnClickListener(v -> mgr.openHtImEmailSend(this)); // 发邮件
findViewById(R.id.textView_todo) .setOnClickListener(v -> mgr.openHtImPlatformTodo(this)); // 待办
findViewById(R.id.textView_trace) .setOnClickListener(v -> mgr.openHtImTraceTemplateSelect(this)); // 发起申请
findViewById(R.id.textView_information) .setOnClickListener(v -> mgr.openHtImInformation(this)); // 信息发布
findViewById(R.id.textView_chat_meeting).setOnClickListener(v -> mgr.openHtImChatMeeting(this)); // 视频会议
findViewById(R.id.textView_ai) .setOnClickListener(v -> mgr.openHtImAiAssistant(this)); // AI 助手
findViewById(R.id.textView4).setOnClickListener(v -> {
mgr.doHtImLogout(MainActivity.this);
finish();
});
}
@Override
protected void onDestroy() {
HtImClientManager.getInstance().removeAllHtImChatListener();
super.onDestroy();
}
}
startActivity。
15. API 对照速查
两端能力一一对应,仅方法命名与调用形式不同。— 表示该平台无对应接口。
生命周期
| 能力 | iOS | Android |
|---|---|---|
| 单例 | [HtImClientManager shareInstance] | HtImClientManager.getInstance() |
| SDK 初始化 | —(登录时完成) | initHtImSdk(Application, HtImInitConfig) |
| 预置配置 | HtClienInitUtil.initClientDataWithConfig: | — |
| 登录 | doHtLoginWithLoginConfigM:callBlock: | doHtImLogin(config, listener) |
| 登录配置类 | HtLoginCofigModel | HtImLoginConfig + HtImInitConfig |
| 结果码 | HtReqCode(成功 = 1) | ServerInitCallbackListener(onSuccess()) |
| 登录后路由注册 | — | setHtImRouteListener(Context) |
| 登录后数据加载 | — | initHtImData(Activity) |
| 退出登录 | doHtLogout | doHtImLogout(Activity) |
| 释放路由监听 | — | removeHtImRoutListener() |
| 释放音视频资源 | — | cleanHtImSdk() |
事件回调
| 能力 | iOS | Android |
|---|---|---|
| 回调协议 | HtImClientDelegate(delegate 属性) | HtImChatListener(addHtImChatListener) |
| 消息未读数 | receiveMsgUnreadNum: | onRecentMsgUnreadNum(int) |
| 待办数 | receiveTodoUnreadNum: | onRecentToDoNum(int) |
| 登录失效 | — | onLogoutStatus(Map) |
| 长连接状态 | — | onHIMSocketStatus(boolean) |
功能入口
| 能力 | iOS | Android |
|---|---|---|
| 动态门户 / 工作台 | goHomeView | openHtImWorkStation(Context) |
| 消息列表 | goMsgView | openHtImChatMessage(Context) |
| 通讯录 | goAddressView | openHtImContact(Context) |
| 发起聊天(选人) | goChatView | sendHtImChat(Context) |
| 直达指定会话 | goChatViewWith: | — |
| 扫一扫 | goScanCallBlock:(有结果回调) | openHtImScanQr(Context)(无回调) |
| 发送邮件 | goSendMailView | openHtImEmailSend(Context) |
| 待办列表 | goTodoView | openHtImPlatformTodo(Context) |
| 发起申请 | goApplyView | openHtImTraceTemplateSelect(Context) |
| 信息发布 | goSendNotice | openHtImInformation(Context) |
| 视频会议 | goVideoMeeting | openHtImChatMeeting(Context) |
| AI 助手 | goAIChat | openHtImAiAssistant(Context) |
登录配置字段
| 字段 | iOS · HtLoginCofigModel | Android |
|---|---|---|
| 服务器地址 | serverUrl | serverUrl |
| 账号 / 密码 | loginId / password | loginId / password |
| 免密令牌 | userSdkKey | userSdkKey |
| 语言 | languageType | languageType |
| 推送标识 | deviceToken(APNs) | vendorDeviceToken + cidDeviceToken |
| 音视频授权 | htrtcAppId / htrtcAppKey(在登录配置中) | htrtcAppId / htrtcAppKey(在 HtImInitConfig 中) |
| 扩展字段 | extra | extra |
htrtcAppId / htrtcAppKey 放在登录配置中,
Android 放在 Application 初始化的 HtImInitConfig 中。跨端移植代码时容易漏配。
16. 常见问题
通用
| 现象 | 原因与处理 |
|---|---|
| 拉取不到 SDK 依赖 | 私有源仍是占位符 <小天互连私有源地址>,或该地址无访问权限。向小天互连获取真实地址与凭据后替换。 |
| 登录提示地址错误 / 不可达 | serverUrl 填写有误或服务器不可达;HTTP / 自签证书场景还需检查 ATS(iOS)或 SSLSocketClient(Android)配置。 |
| 令牌登录失败 | userSdkKey 已过期或与 serverUrl 不匹配。令牌应每次启动时从宿主后端动态获取。 |
| 视频会议进不去 / 没有画面 | htrtcAppId / htrtcAppKey 未配置。注意两端配置位置不同,见第 15 节。 |
| 收不到未读角标 | 回调注册时机早于登录成功,或持有者已释放。 |
| 收不到离线推送 | 登录时未上报推送 token;或服务端未配置推送证书;或包名 / Bundle ID 与证书不匹配。 |
iOS
| 现象 | 原因与处理 |
|---|---|
| 模拟器编译报 找不到 arm64 slice |
SDK 无 arm64 模拟器 slice。请用真机调试,并确认 post_install 中已设置 EXCLUDED_ARCHS[sdk=iphonesimulator*] = 'arm64'。 |
| Use of private header from outside its module |
AFNetworking / RealReachability 引用了 netinet6/in6.h。加上第 5 节的 post_install 后重新 pod install。 |
| 找不到 libarclite | 老 pod 的部署目标停在 iOS 8/9。post_install 中统一抬到 13.0。 |
| 运行时 duplicate class 警告 | 宿主工程重复声明了已打进 xcframework 的三方库(百度地图 / 微信 OpenSDK / iflyMSC)。从 Podfile 中移除这些声明。 |
| 登录回调 type = 0 / -2 | 0 为服务器地址错误或不可达;-2 为账号或密码错误。 |
| 点击功能入口无反应 | 未登录成功,或根控制器不是 UINavigationController,SDK 找不到可用的导航栈。 |
| 扫一扫 / 发语音闪退 | Info.plist 缺少对应的 NS*UsageDescription,见第 6 节。 |
| AI 助手打开后崩溃 | HTAIKit 被降级到 1.0.6 以下。锁回 1.0.6。 |
Android
| 现象 | 原因与处理 |
|---|---|
| Manifest merger failed 缺少 GETUI_APPID / AMAP_APIKEY |
manifestPlaceholders 未配置。见第 5 节,两个 key 都是必填。 |
| Manifest merger failed android:theme / allowBackup 冲突 |
在 <application> 上加 tools:replace="android:theme, android:allowBackup"。 |
| 找不到 htrtc-2.2.3 | 该 aar 不随依赖链传递。向小天互连索取后放入工程根目录 repositories/,并确保 flatDir { dirs 'repositories' } 已配置。 |
| UnsatisfiedLinkError 加载 .so 失败 |
ABI 不匹配。SDK 只有 arm64-v8a / armeabi-v7a,abiFilters 中不要包含 x86 / x86_64;模拟器需使用 arm64 镜像或改用真机。 |
| duplicate class com.intellij.annotations |
加上 implementation.exclude group: 'com.intellij', module: 'annotations'。 |
| SDK 界面报 DataBinding 相关错误 |
宿主未开启 DataBinding。在 android { dataBinding { enabled = true } } 中打开。 |
| IllegalStateException: The htim sdk is not initialized |
登录前未调用 initHtImSdk,或没在 Application.onCreate() 中调用。 |
| IllegalStateException: Please call the doHtImLogin method first |
在登录成功前调用了 setHtImRouteListener / initHtImData 或任意 openHtIm* 方法。 |
| 登录回调"用户校验失败" | userSdkKey 为空,且 loginId / password 未成对填写。 |
| 登录回调 code = -2 | 禁止使用初始密码登录,需先在 Web 端修改密码。 |
| 聊天中发送单据 / 文档无响应 | 未调用 setHtImRouteListener,模块间跳转路由没注册。 |
| 退出 Activity 后内存泄漏 | onDestroy() 中补 removeAllHtImChatListener(),必要时再调 removeHtImRoutListener()。 |