ImageKnifePro 源码解读(十):网络可靠性——CRC32 校验、备用 URL 与动态 DNS
网络图片加载有三个常见的失败场景:CDN 返回了损坏的图片数据、主域名不可达需要切换备用域名、DNS 解析被劫持或延迟过高。ImageKnifePro 在 C++ 层针对这三个场景分别实现了 CRC32 数据校验、fallbackUrls 多域名自动重试、以及静态/动态两种自定义 DNS 机制。三者的触发点都在 用户通过 CRC32 计算使用的是 zlib 库的 校验失败后有两个去向:如果还有未尝试的 fallbackUrl,继续重试下一个备用地址;如果所有地址都失败了,交由下一个拦截器处理。 重试逻辑在同步和异步两条路径上的行为有区别。同步路径下( 低版本设备不支持分离时,使用 分离后,下载完成的回调 取消请求时需要特别小心竞态。 静态 DNS 通过 转换过程中需要分配内存并拷贝字符串——host 通过 配置路径有两条:单请求级别通过 动态 DNS 通过回调函数实现,允许应用在运行时动态解析域名。ArkTS 层设置一个返回 C++ 层的 关键设计是超时机制。 动态 DNS 设置后会覆盖所有静态 DNS 配置。 网络请求的配置在 HTTP Headers:优先使用请求级别的 请求完成后, 以上就是本篇内容的所有了~有什么问题欢迎在评论区提出 项目地址:ImageKnifeProLoadInterceptor 的加载链路上,与 Detach 异步分离机制紧密配合。一、CRC32 校验——下载数据的完整性验证
ImageKnifeOption.crc32 设置一个期望的 CRC32 值。下载完成后,IsImageCrc32Match 函数对图片二进制数据计算 CRC32 并与期望值比较。bool IsImageCrc32Match(std::shared_ptr<ImageKnifeTaskInternal> taskInternal,
std::shared_ptr<ImageKnifeOption> option)
{
u_int32_t expectedCrc = option->crc32;
if (expectedCrc == 0) {
return true; // 未设置 crc32,直接跳过
}
uLong crc = crc32(0L, Z_NULL, 0);
auto data = taskInternal->product.imageBuffer;
auto len = taskInternal->product.imageLength;
if (data != nullptr && len > 0) {
crc = crc32(crc, reinterpret_cast<const Bytef*>(data.get()), len);
}
if (expectedCrc == crc) {
return true;
}
// 校验失败,清空下载数据
taskInternal->product.imageBuffer = nullptr;
taskInternal->product.imageLength = 0;
taskInternal->EchoError("CRC32 Check Failed");
return false;
}crc32 函数。校验的调用时机有两个。同步路径下(未分离),在 LoadInterceptor::Process 的 RetryFallbackUrls 中,每次 Resolve 成功后立即校验;异步路径下(已分离),在 LoadInterceptor::OnComplete 中,下载回调完成后校验。二、RetryFallbackUrls——多域名自动重试
fallbackUrls 是一个字符串数组,用户在 ImageKnifeOption 中配置备用下载地址。当主地址下载失败或 CRC32 校验不通过时,框架自动尝试备用地址。bool RetryFallbackUrls(std::shared_ptr<ImageKnifeTask> task, LoadInterceptor *downloadInterceptor)
{
// index初始为-1,即没有使用过备用地址
for (int i = taskInternal->fallbackUrlIndex; i < (int)option->fallbackUrls.size(); i++) {
if (request->IsDestroy() || taskInternal->IsFatalErrorHappened()) {
return false;
}
if (taskInternal->fallbackUrlIndex > -1) {
taskInternal->SetFallbackUrl(option->fallbackUrls[i]);
}
taskInternal->fallbackUrlIndex++;
bool result = downloadInterceptor->Resolve(task);
if (taskInternal->IsDetached()) {
return true;
}
if (result && IsImageCrc32Match(taskInternal, option)) {
return true;
}
}
taskInternal->ResetFallbackUrl();
return false;
}fallbackUrlIndex 初始值为 -1,表示首次下载使用原始 URL。每次失败后 index 递增,取 fallbackUrls[i] 作为新的下载地址。整个循环中,每次 Resolve 之前都检查请求是否已取消或出现致命错误,支持中途中断。Process 方法),RetryFallbackUrls 作为回调传入 Interceptor::Process,替代默认的 Resolve 函数。异步路径下(OnComplete 回调),下载分离后回来发现失败,需要构造新的 ImageKnifeTaskInternal 对象来进行重试——因为已分离的 task 不能标记为未分离状态。三、Detach 交互——异步分离与加载链回归
LoadInterceptor 的 Detach 机制允许网络下载异步化而不阻塞当前工作线程。DownloadInterceptorDefault 在调用 HMS_Rcp_Fetch 发起异步下载后,如果设备支持分离(API > 13),就调用 Detach(task) 把 task 标记为已分离状态。if (IsDownloadDetachEnabled()) {
Detach(task);
} else {
// 低版本ROM不支持分离,用promise同步等待
bool res = data->waitDownload.get_future().get();
delete data;
return res;
}std::promise/future 把异步下载转为同步阻塞——回调中 set_value,调用处 get 等待。ResponseCallback 在 RCP 线程中触发。回调中调用 OnComplete,这个方法承担了重新回归加载链的职责:执行 CRC32 校验、进行 fallbackUrl 重试、如果全部失败则交由下一个拦截器。最终无论成功还是失败,都通过 FinishLoadChain 把结果推回 ImageKnifeDispatcher 的主流程。Cancel 方法先获取 task 的 cancelMutex_,标记 rcpRequestCanceled = true 并取出 rcpRequest 指针,然后释放锁后再调用 HMS_Rcp_CancelRequest。LoadImageFromUrl 中在 fetch 完成后也需要获取同一把锁来记录 request 并检查是否已被取消。两把锁(sessionLock_ 保护全局 session、cancelMutex_ 保护单个 task)分开操作,避免死锁。四、DnsOption 静态 DNS——编译时 host-to-IP 映射
DnsOption 类设置。用户创建 StaticDnsRuleItem 列表,每项包含 host、port 和 ip 地址数组:struct StaticDnsRuleItem {
std::string host;
uint16_t port = 80;
std::vector<std::string> ipAddresses;
};DnsOptionInternal 继承 DnsOption,在构造函数中调用 CovertRcpStaticDnsRule 把 vector<StaticDnsRuleItem> 转换为 RCP 需要的 Rcp_StaticDnsRule 链表结构。每个 Rcp_StaticDnsRule 节点包含一个 Rcp_StaticDnsRuleItem(host、port 和 ip 地址链表),通过 next 指针串联。memcpy_s 拷贝到固定长度的 char[RCP_HOST_MAX_LEN] 数组,ip 地址拷贝到 char[RCP_IP_MAX_LEN] 数组。析构函数负责按链表顺序释放所有动态分配的节点和 ip 地址。ImageKnifeOption.dnsOption 设置;全局级别通过 ImageKnife.setGlobalDnsOption(dnsOption) 设置。DownloadInterceptorDefault::ConfigDns 中的优先级为:全局动态 DNS > 请求级别静态 DNS > 全局静态 DNS。五、DnsDynamic 异步动态 DNS——运行时 host 解析
Promise<Array<string>> 的回调函数:ImageKnife.getInstance().setGlobalDynamicDns(
(host: string, port: number) => Promise<Array<string>>,
timeout // 超时时间,默认 50ms
);DnsDynamic 类包装了这个回调。当 RCP 需要解析域名时,调用 DnsDynamic::Execute:Rcp_IpAddress *DnsDynamic::Execute(const char *host, uint16_t port)
{
auto future = callback_(host, port);
auto status = future.wait_for(std::chrono::milliseconds(timeout_));
if (status != std::future_status::ready) {
IMAGE_KNIFE_LOG(LOG_WARN, "Dynamic Dns Callback Reach Timeout");
return nullptr;
}
std::vector<std::string> result = future.get();
// 将 vector<string> 转换为 Rcp_IpAddress 链表
Rcp_IpAddress *head = nullptr;
Rcp_IpAddress *point = nullptr;
for (auto &ipAddress : result) {
Rcp_IpAddress *address = (Rcp_IpAddress *)malloc(sizeof(Rcp_IpAddress));
memcpy_s(address->ipAddress, RCP_IP_MAX_LEN, ipAddress.c_str(), ipAddress.size() + 1);
// ... 链表串联
}
return head;
}wait_for 等待指定毫秒数,如果回调没有在超时时间内返回结果,直接返回 nullptr 让 RCP 走默认的 DNS 解析。默认超时 50ms,这个值考虑了用户自定义 DNS 解析通常只是查本地缓存或内网 DNS 服务器的场景。如果用户的回调涉及外部网络请求,需要适当调大超时值。Rcp_IpAddress 链表的内存由 RCP 框架负责释放——这与 DnsOptionInternal 中静态规则由 ImageKnifePro 自己释放不同。ConfigDns 方法中,如果全局动态 DNS 规则不为空,直接使用并 return,不再检查静态规则。六、DownloadInterceptorDefault 的整体请求配置
ConfigHttpRequest 中完成。除了 DNS,还包括以下几项。ImageKnifeOption.httpHeaders,如果为空则使用全局 headers。Session 池:DownloadInterceptorDefault 维护一个 stack<Rcp_Session *> 做连接池复用,GetRcpSession 取出、RecycleSession 回收。超时配置:connectTimeout 和 readTimeout 分别设置连接超时和传输超时。CA 证书:通过 httpOption.caPath 设置自定义证书路径。下载进度:如果设置了 progressListener,通过 RCP 的 onDownloadProgress 回调实时报告下载进度百分比。ResponseCallback 检查 HTTP 状态码。状态码不是 200 时记录错误并销毁 response。成功时用 shared_ptr 包装 response body 的 buffer,设置自定义删除器来确保 response->destroyResponse 被正确调用——避免手动释放下载数据 buffer 导致的内存错误。auto deleter = [response](void *) {
response->destroyResponse(response);
};
task->product.imageBuffer = std::shared_ptr<uint8_t[]>(
(uint8_t *)response->body.buffer, deleter);