Shiply Android 远程资源 SDK 集成
一、隐私安全说明
Shiply 资源发布 Android SDK
版本:1.8.22-RC04
更新时间:2025年7月22日
SDK介绍:为移动开发者提供专业的资源分发能力,帮助开发者动态下载远程资源文件。
服务提供方:深圳市腾讯计算机系统有限公司
接入指引:《Shiply资源发布SDK接入指引》
隐私保护规则:《Shiply资源发布SDK个人信息保护规则》
合规使用指南:[《Shiply资源SDK合规使用指南》](/document/remote-resource/compliance/
前置概念:接入前请先阅读 通用概念,了解各平台共用的
appId/appKey/userId/deviceId/systemId/target/资源加载策略 等核心概念,避免跨平台混淆。
二、接入SDK
1. 在项目根目录下的 build.gradle 中配置 Maven 仓库
allprojects {
repositories {
maven { url "https://maven.cnb.cool/tencent-tds/shiply-public/-/packages/" }
}
}
2. 在主 module 的 build.gradle 中添加运行时 SDK
dependencies {
implementation "com.tencent.shiply:reshub-sdk:1.8.22-RC04" // ResHub核心SDK
implementation "com.tencent.shiply:reshub-net:1.8.22-RC04" // 可选,默认网络下载实现
implementation "com.tencent.shiply:reshub-report:1.8.22-RC04" // 可选,默认上报SDK
implementation "com.tencent.shiply:reshub-patch-sdk:1.8.22-RC04" // 可选,差量更新SDK
}
3. 初始化 SDK,建议在 Application.onCreate 方法中初始化
// 构造初始化参数 ,建议将 ResHubParams.multiProcessMode 设置为true
ResHubParams params = new ResHubParams(
"1.0.0", // 应用版本号
"your_user_id" // 用户ID
);
// 初始化 SDK
ResHubCenter.INSTANCE.init(
getApplication(),
params,
new ResHubDefaultDownloadImpl(), // 默认下载器实现
new HttpsURLConnectionNetwork(this), // 默认网络请求实现
new ResHubDefaultReportImpl() // 默认数据上报实现
);
// 注入差量包处理器
List<AbsProcessor> processorList = new ArrayList<>();
processorList.add(new TryPatchProcessor());
ResHubCenter.INSTANCE.injectProcessor(processorList);
// 设置日志代理,建议参考AbsLog实现,对接到业务方自己的日志接口
ResHubCenter.INSTANCE.setLogDelegate(new SystemLog());
// 获取 SDK 实例
IResHub reshub = ResHubCenter.INSTANCE.getResHub("${appId}", "${appKey}",
TargetType.AndroidApp, ResHubCenter.ENV_ONLINE);
ResHubParams各参数字段说明:
data class ResHubParams @JvmOverloads constructor (
/**
* 宿主应用版本号
*
* 格式规范参考:https://semver.org/lang/zh-CN/
*/
val appVersion: String,
/**
* 设备标识,业务方根据自己的策略注入(推荐使用 qimei36)
*
* 注意: 取值不能为空
*/
val deviceId: String,
/**
* 是否是debug测试包,为true时可拉取到未发布的资源
*/
val isRdmTest: Boolean = false,
/**
* 存放内置资源的根目录
*/
val localPresetPath: String = ResHubCenter.LOCAL_PRESET_PATH_DEFAULT,
/**
* onComplete回调是否在主线程进程
*
* (默认是,方便使用方进行UI相关处理)
*/
val completeCallbackOnMainThread: Boolean = true,
/**
* onProgress回调是否要求在主线程进行
*
* (默认为否,该回调可能频率较高)
*/
val progressCallbackOnMainThread: Boolean = false,
/**
* 存储路径额外前缀
*
* 用于隔离存储的文件路径,一般不用改
* (应用举例:比如想隔离32和64位的资源,可以添加个 64bit 的路径)
*/
val configStoreSuffix: String = ResHubCenter.CONFIG_STORE_SUFFIX_DEFAULT,
/**
* 自定义配置请求规则参数,影响配置下发的规则匹配
*/
val variantMap: Map<String, String> = mapOf(),
/**
* 全量配置自动更新策略
*
* 取值参考@RDeliverySetting.UpdateStrategy, 可设置为多个值(对值进行或运算)
*
* 常用可选策略:
* 启动时拉取全量配置: UpdateStrategy.START_UP
* 按照固定时间间隔,周期拉取全量配置: UpdateStrategy.PERIODIC
* App切换后台超过30秒,再切回前台时拉取全量配置: UpdateStrategy.HOT_RELOAD
* 断网重连时拉取全量配置: UpdateStrategy.NETWORK_RECONNECT
*
* 默认为: 启动时/定时3小时后更新
*/
val configUpdateStrategy: Int = defaultConfigUpdateStrategy,
/**
* 全量配置定时自动更新时的时间间隔,单位为秒
*
* 注意: 更新策略包括 RDeliverySetting.UpdateStrategy.PERIODIC 才有效
* 默认10800秒(3小时)
*/
val configUpdateInterval: Int = defaultConfigUpdateInterval,
/**
* 是否启用ResHub多进程模式
*
* 开启后:
* 1. 默认会使用MMKV(的多进程模式)来保存配置,不同进程同一个ResHub的资源和配置保证是同步的。
* 2. 不同进程对同一个ResHub的同一资源的文件操作(下载/解压/解密/Patch)会使用文件锁保证互斥。
*
* 注意: ResHub的多进程模式默认依赖"MMKV"(来完成多进程配置同步),所以:
* 1. 如果开启了多进程模式
* 必须在初始化ResHubCenter之前,确保已经自行调用了"MMKV.initialize()"以完成它的全局初始化。
* 2. 如果未开启多进程模式(默认不开启),则无需关心。
* ResHub默认会使用SharedPreferences存储配置。(见DefaultConfigStorageDelegateImpl)
*/
val multiProcessMode: Boolean = false,
/**
* 是否是64位设备;注入该参数后,才可以在平台上使用系统内置的'是否64位'判断条件
*/
val is64Bit: Boolean? = null,
/**
* 是否要记录配置key最近一次发送远程网络请求时的时间戳,回包成功时记录
*/
val enableRecordLastRequestTime: Boolean? = null,
/**
* 是否懒加载内部的配置信息,用来优化性能,默认不开启
*/
val enableLazyLoadInnerConfig: Boolean = false,
/**
* 当pullTarget为[TargetType.AndroidApp]时,是否要额外拉取项目级别的配置信息
*/
val fetchProjectWhenAppOnly: Boolean = false,
/**
* 是否跳过内置资源复制到磁盘后文件完整性校验操作,默认不跳过
*/
val skipIntegrityCheckAfterPresetResCopy: Boolean = false
)
/**
* 默认的全量配置自动更新策略
*/
val defaultConfigUpdateStrategy =
// 启动时拉取全量配置
RDeliverySetting.UpdateStrategy.START_UP.value or
// 按照固定时间间隔,周期拉取全量配置
RDeliverySetting.UpdateStrategy.PERIODIC.value
/**
* 默认的全量配置定时自动更新时间间隔: 10800秒(3小时)
*
* 更新策略包括 RDeliverySetting.UpdateStrategy.PERIODIC 才有效
*/
const val defaultConfigUpdateInterval = 10800
三、使用SDK
1. 异步获取远端资源
// 拉取资源(并锁定特定版本)
reshub.load("your_res_key", callback)
// 拉取最新资源
reshub.loadLatest("your_res_key", callback)
// 批量拉取资源(并锁定特定版本)
reshub.batchLoad(setOf("your_res_key1", "your_res_key2"), batchCallback)
// 批量拉取最新资源
reshub.batchLoadLatest(setOf("your_res_key1", "your_res_key2"), batchCallback)
2. 同步获取本地资源
// 获取本地资源(并锁定特定版本)
IRes res = reshub.get("testResId1")
// 获取本地最新资源
IRes res = reshub.getLatest("testResId1")
// 同步指定 taskId
IRes res = reshub.getSpecific("testResId1", taskId, true)
3. 指定任务版本加载
// 加载指定任务的资源
reshub.loadSpecific("my_res", taskId, callback)
IRes 主要只读属性
| 属性 | 类型 | 说明 |
|---|---|---|
getResId() | String | 资源 ID |
getVersion() | Long | 版本号 |
getLocalPath() | String | 本地路径 |
getSize() | Long | 文件大小 |
getMd5() | String? | 文件 MD5 |
getDownloadUrl() | String? | 下载地址 |
getFileExtra() | String? | 文件附加信息 |
getResType() | String | 资源类型 |
getDescription() | String? | 资源描述 |
getTaskId() | String? | 任务 ID |
isLoadFromPresetAssets() | Boolean | 是否从预置资源加载 |
getSubRes(name) | ISubRes? | 获取子资源 |
getDiffPkgSize(baseResVersion) | Long | 差量包大小 |
四、QA
4.1 锁定版本是什么意思?
调用load/get接口都会锁定版本,锁定是指同一个进程生命周期内多次调用load返回的版本号不会变化,即使远端发布了更高的版本。
4.2 下载后的文件如何保持原来的后缀?
sdk默认会给下载的文件添加.res后缀,也支持设置使用原文件后缀:在初始化的时候设置ResHubCenter.downloadStorageDelegate = OriginDownloadStorageDelegateImpl()。
4.3 如果资源有更新,本地之前下载的产物是否会被删除?
sdk更新新版本资源后,会将旧版本资源标记为待删除,下次进程启动时再执行删除。
4.4 ResHubParams.isRdmTest 有什么作用?
表示当前是debug包,与前端资源发布任务体验号码设置中的内部测试包规则配合使用。
4.5 反复调用ResHubCenter.getResHub是否会返回多个实例?
实例是根据 appid+env隔离的,appid 和 env不变时,返回的是同一个实例。
4.6 如果一个资源key下的所有任务都被删除,端上loadLatest回调返回的是什么?
回调中isSuccess是false, result会返回本地缓存的制品。
4.7 loadLatest回调返回的isSuccess是false时,怎么区分是请求失败了才false,还是因为资源下的任务都删除了才false?
可以通过error: IResLoadError 区分,errCode是1003,errMsg是「config result empty」时表示请求成功,但是任务不匹配。
errCode错误码:
const val SUCCESS = 0
// 配置拉取相关
const val FETCH_CONFIG_BAD_RESULT_ERROR = 1003 // 接口返回错误(如ret不为0),对应的errMsg包括:「config not found」:表示远端不存在对应的资源key;「config result empty」:表示远端资源key存在,但是任务不匹配;「SocketTimeoutException」: 表示网络异常导致配置拉取失败;
const val FETCH_CONFIG_INVALID_RESULT_ERROR = 1005 // 接口返回数据校验失败
const val FETCH_CONFIG_PARAMETER_ERROR = 1006 // 接口入参错误
const val FETCH_CONFIG_EMPTY_RESOURCE_ERROR = 1007 // 接口返回资源为空
const val FETCH_CONFIG_REDIS_ERROR = 1008 // 接口返回访问Redis资源失败
const val FETCH_CONFIG_INVALID_RESID_ERROR = 1009 // 请求的资源ID不合法
const val FETCH_CONFIG_CDN_OVERLOAD_ERROR = 1010 // CDN高负载状态,放弃下载
// 完整资源下载相关
const val DOWNLOAD_HTTP_ERROR = 2001 // 全量文件下载HTTP错误
const val DOWNLOAD_NETWORK_ERROR = 2002 // 全量文件下载其他网络错误
const val DOWNLOAD_NET_LIMIT_ERROR = 2003 // 全量文件下载网络状态限制
const val DOWNLOAD_NO_ENOUGH_SPACE_ERROR = 2004 // 全量文件下载空间不足
const val DOWNLOAD_FILE_OPERATE_ERROR = 2005 // 全量文件下载文件操作错误,文件(创建/移动/重命名)失败
const val DOWNLOAD_MD5_CHECK_ERROR = 2006 // 全量文件MD5校验失败
const val DOWNLOAD_DECRYPT_ERROR = 2007 // 全量文件解密失败
// 按照压缩方式下发的资源下载相关
const val COMPRESSED_DOWNLOAD_HTTP_ERROR = 2101 // 按照压缩方式下发的资源下载HTTP错误
const val COMPRESSED_DOWNLOAD_NETWORK_ERROR = 2102 // 按照压缩方式下发的资源下载其他网络错误
const val COMPRESSED_DOWNLOAD_NET_LIMIT_ERROR = 2103 // 按照压缩方式下发的资源下载网络状态限制
const val COMPRESSED_DOWNLOAD_NO_ENOUGH_SPACE_ERROR = 2104 // 按照压缩方式下发的资源下载空间不足
const val COMPRESSED_DOWNLOAD_FILE_OPERATE_ERROR = 2105 // 按照压缩方式下发的资源下载文件操作错误,文件(创建/移动/重命名)失败
const val COMPRESSED_DOWNLOAD_MD5_CHECK_ERROR = 2106 // 按照压缩方式下发的资源MD5校验失败
const val COMPRESSED_DOWNLOAD_DECRYPT_ERROR = 2107 // 按照压缩方式下发的资源解密失败
// 解压相关
const val UNZIP_ERROR = 3001 // 解压失败
const val UNZIP_MD5_CHECK_ERROR = 3002 // 解压后文件校验失败
const val COMP_ORIG_FILE_UNZIP_ERROR = 3101 // 以压缩方式下发的资源解压失败
const val COMP_ORIG_FILE_UNZIP_MD5_CHECK_ERROR = 3102 // 以压缩方式下发的资源解压后文件校验失败
// 配置更新相关
const val UPDATE_CONFIG_ERROR = 4001 // 更新本地资源配置时发生异常
const val READ_LOCAL_RES_ERROR = 4002 // 读取到的本地资源是关闭状态
// 差量下载相关
const val PATCH_HTTP_ERROR = 5001 // 差量文件下载HTTP错误
const val PATCH_NETWORK_ERROR = 5002 // 差量文件下载其他网络错误
const val PATCH_NET_LIMIT_ERROR = 5003 // 差量文件下载网络状态限制
const val PATCH_NO_ENOUGH_SPACE_ERROR = 5004 // 差量文件下载空间不足
const val PATCH_FILE_OPERATE_ERROR = 5005 // 差量文件下载文件操作错误
const val PATCH_MD5_CHECK_ERROR = 5006 // 差量文件MD5校验失败
const val PATCH_APPLY_ERROR = 5007 // 差量文件Patch失败
const val PATCH_TOTAL_MD5_CHECK_ERROR = 5008 // Patch后文件MD5校验失败
五、LocalResStatus 资源状态枚举
| 枚举值 | 序号 | 含义 | 处理建议 |
|---|---|---|---|
| GOOD | 0 | 本地有最新配置和文件,正常 | 直接使用 localPath |
| NEED_DOWNLOAD | 1 | 本地没有配置和文件,待下载 | 调用 load 系列方法 |
| NEED_UPDATE | 2 | 本地有旧的配置和文件,待更新 | 调用 loadLatest 系列方法 |
| DISABLED | 3 | 资源被禁用(关闭/不满足版本/超最大版本等) | 回退到内置资源或降级 |
| FILE_INVALID | 4 | 资源文件异常(MD5 校验失败/文件被删除等) | 删除后重新下载 |
| NOT_EXIST | 5 | 没找到该资源 | 检查 resId 是否正确 |
| LOCAL_ONLY | 6 | 仅本地资源有效,远程配置不正常 | 使用本地资源,检查远程配置 |
| UNKNOWN | 7 | 未知不正常状态(如未启用 RDelivery) | 检查初始化配置 |
六、注意事项
- 入口是 ResHubCenter:不要直接 new
ResHub(它是 internal)。用ResHubCenter.getResHub。 - validateResFile:MD5 校验,大资源建议异步。
- forceRequestRemoteConfig:
loadLatest可强制请求远程配置。 - 预置资源:
ResHubParams支持预置资源配置,首次启动可用内置资源降级。 - 依赖 RDelivery:资源配置经 RDelivery 通道拉取,RDelivery 初始化参数影响资源。
- systemId 注意:ResHub 内部使用
BizSystemID.RES_HUB("10010"),不同于配置 SDK 的默认"10001"。不需要手动设置。 - 监听器单槽 vs 多槽:ResHub 的
setRemoteLoadInterceptor/setResRefreshListener只能同时持有一个实例(clear清除后换人)。对比 RDelivery 的addDataChangeListener/removeDataChangeListener支持多个监听器共存——两个 SDK 的设计模式不同。
IResRefreshListener 接口
interface IResRefreshListener {
fun onResFirstLoaded(resId: String, res: IRes) // 本进程首次加载
fun onResRefreshed(resId: String, res: IRes) // 版本或配置更新
}
IRemoteLoadInterceptor 接口
interface IRemoteLoadInterceptor {
fun isRemoteServerBusy(): Boolean
fun shouldInterceptPreload(resId: String): Boolean
fun onInterceptPreload(resId: String, preloadAction: Runnable, cancelAction: Runnable)
}
线程说明
ResHubParams.completeCallbackOnMainThread默认 true——onComplete回调在主线程。ResHubParams.progressCallbackOnMainThread默认 false——onProgress回调在后台线程。loadLatest()支持逐次覆盖:callbackOnMainThread: Boolean? = null(null = 使用全局设置)。IResRefreshListener回调无线程保证。- 重要:两个 SDK 均无
@MainThread/@WorkerThread注解。
资源管理
// 按 resId 删除所有已下载版本
resHub.deleteRes("my_res")
// 按 IRes 对象删除单个版本
resHub.deleteRes(res)
// 删除所有资源,重置本地配置
resHub.deleteAll()
// 取消下载
resHub.cancelDownloading("my_res")
// 磁盘清理
resHub.cleanupDiskStorage() // 清理残留文件
resHub.cleanupDiskStorage(tryDeleteOriginFiles = true) { result -> } // 含原始压缩包
resHub.cleanupDiskStorage("my_res", tryDeleteOriginFiles = true) // 清理指定资源
// 延迟删除
resHub.deleteResWithDelayOption("my_res", pendingDeleteResFile = true)
resHub.deleteSpecificTaskRes("my_res", taskId)
取消下载依赖注入的下载器(
IRDownload)实现IRDownloadTask并支持 cancel。下载器不支持则无效果。
资源状态查询
// 查询资源本地状态
val status: LocalResStatus = resHub.getStatus("my_res")
// 获取已拉取的最新资源配置(仅本地缓存,不触发网络)
val config: IRes? = resHub.getFetchedResConfig("my_res")
getFetchedResConfig 仅从 RDelivery 本地缓存读取,不经过网络。返回的 IRes 中 localPath 为空。
配置拉取
// 全量拉取(每次发网络请求,成本高)
resHub.requestFullRemoteResConfig(listener)
// 按 key 拉取多个
resHub.requestMultiRemoteResConfig(listOf("r1", "r2"), listener)
// 异步拉取单个资源配置(仅配置,不下载资源)
resHub.fetchResConfig("my_res", callback)
// 批量拉取资源配置
resHub.batchFetchResConfig(setOf("r1", "r2"), batchCallback)
回调与监听
// 资源刷新监听
resHub.setResRefreshListener(listener) // IResRefreshListener
resHub.clearResRefreshListener() // 移除刷新监听
// 远程加载拦截
resHub.setRemoteLoadInterceptor(interceptor) // IRemoteLoadInterceptor
resHub.clearRemoteLoadInterceptor() // 移除拦截器
// 全量配置更新监听(无 remove,传 null 清除)
resHub.setFullResConfigUpdateListener(listener)
resHub.setFullResConfigUpdateListener(null)
其他 API
val rDelivery = resHub.getRDeliveryInstance() // 底层 RDelivery 实例
val lastTime = resHub.getLastRequestServerTime("my_res") // 最近一次远程请求时间戳(需在 ResHubParams 中启用 enableRecordLastRequestTime,未记录返回 -1)
val lastFullTime = resHub.getLastFullRequestServerTime() // 最近一次全量请求时间戳
val tags = resHub.getHitSubTaskTags() // 命中子任务标签
val valid = resHub.isResFileValid(res) // 文件 MD5 校验
resHub.updateCustomProperties("key", "value") // 更新自定义属性