Unity中如何暂停、恢复、取消OnDemandResourcesRequest?ODR续传求助
我之前在Unity iOS项目里也碰到过一模一样的ODR续传坑——Unity自带的OnDemandResourcesRequest只提供了基础的下载、取消能力,完全没考虑后台强杀后的续传场景。要解决这个问题,就得借助Objective-C的NSProgress类来实现暂停、恢复和进度追踪,通过Unity与OC的桥接层来打通能力。下面是我验证过的完整解决方案:
问题根源拆解
Unity对苹果ODR的封装(OnDemandResources类)是基于NSBundleResourceRequest,但C#层没有暴露NSProgress的管控API。当应用被后台强杀时,原有的请求实例直接销毁,重启后无法关联到未完成的系统级下载任务,导致进度卡住。而NSProgress本身支持暂停/恢复,且系统会保留未完成的下载任务状态,只要重新关联就能续传。
完整实现步骤
1. 编写Objective-C原生插件
先创建OC类封装ODR的管控逻辑,暴露方法给Unity调用:
ODRManager.h
#import <Foundation/Foundation.h> @interface ODRManager : NSObject + (instancetype)sharedInstance; - (void)startDownloadWithTags:(NSArray<NSString*>*)tags completion:(void(^)(BOOL success, NSError* error))completion; - (void)pauseDownload; - (void)resumeDownload; - (void)cancelDownload; - (float)getDownloadProgress; @end
ODRManager.m
#import "ODRManager.h" #import <Unity/UnityInterface.h> @interface ODRManager () @property (nonatomic, strong) NSBundleResourceRequest *resourceRequest; @property (nonatomic, strong) NSProgress *downloadProgress; @end @implementation ODRManager + (instancetype)sharedInstance { static ODRManager *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[ODRManager alloc] init]; }); return instance; } - (void)startDownloadWithTags:(NSArray<NSString*>*)tags completion:(void(^)(BOOL success, NSError* error))completion { // 优先恢复未完成的请求 if (_resourceRequest && _resourceRequest.state != NSBundleResourceRequestStateFinished) { [self resumeDownload]; return; } _resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags]; _downloadProgress = _resourceRequest.progress; // KVO监听进度变化,回调到Unity [_downloadProgress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:nil]; __weak typeof(self) weakSelf = self; [_resourceRequest beginAccessingResourcesWithCompletionHandler:^(NSError * _Nullable error) { if (error) { completion(NO, error); [weakSelf cleanupObserver]; } else { completion(YES, nil); [weakSelf cleanupObserver]; } }]; } - (void)pauseDownload { if (_resourceRequest && _resourceRequest.state == NSBundleResourceRequestStateLoading) { _downloadProgress.paused = YES; } } - (void)resumeDownload { if (_resourceRequest && _resourceRequest.state == NSBundleResourceRequestStateLoading) { _downloadProgress.paused = NO; } } - (void)cancelDownload { if (_resourceRequest) { [_resourceRequest cancel]; [self cleanupObserver]; _resourceRequest = nil; _downloadProgress = nil; } } - (float)getDownloadProgress { return _downloadProgress ? _downloadProgress.fractionCompleted : 0.0f; } - (void)cleanupObserver { if (_downloadProgress) { [_downloadProgress removeObserver:self forKeyPath:@"fractionCompleted"]; } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"fractionCompleted"] && object == _downloadProgress) { float progress = [change[NSKeyValueChangeNewKey] floatValue]; // 回调到Unity的ODRController脚本 UnitySendMessage("ODRController", "OnODRProgressUpdate", [NSString stringWithFormat:@"%.2f", progress].UTF8String); } } @end
2. Unity C#层调用与管控脚本
创建一个单例脚本ODRController,负责调用OC插件、处理前后台切换和续传逻辑:
using UnityEngine; using System.Runtime.InteropServices; public class ODRController : MonoBehaviour { // 导入OC插件方法 [DllImport("__Internal")] private static extern void _ODRStartDownload(string[] tags, int tagCount); [DllImport("__Internal")] private static extern void _ODRPauseDownload(); [DllImport("__Internal")] private static extern void _ODRResumeDownload(); [DllImport("__Internal")] private static extern void _ODRCancelDownload(); [DllImport("__Internal")] private static extern float _ODRGetProgress(); public static ODRController Instance; private void Awake() { Instance = this; DontDestroyOnLoad(gameObject); } /// <summary> /// 启动ODR下载(自动续传未完成任务) /// </summary> public void StartODRDownload(string[] tags) { #if UNITY_IOS && !UNITY_EDITOR SaveDownloadTags(tags); _ODRStartDownload(tags, tags.Length); #endif } public void PauseODR() { #if UNITY_IOS && !UNITY_EDITOR _ODRPauseDownload(); #endif } public void ResumeODR() { #if UNITY_IOS && !UNITY_EDITOR _ODRResumeDownload(); #endif } public void CancelODR() { #if UNITY_IOS && !UNITY_EDITOR _ODRCancelDownload(); PlayerPrefs.DeleteKey("ODR_SavedTags"); #endif } public float GetODRProgress() { #if UNITY_IOS && !UNITY_EDITOR return _ODRGetProgress(); #else return 0f; #endif } /// <summary> /// OC回调的进度更新方法(方法名必须和UnitySendMessage一致) /// </summary> public void OnODRProgressUpdate(string progressStr) { if (float.TryParse(progressStr, out float progress)) { Debug.Log($"ODR Download Progress: {progress:P2}"); // 这里更新UI进度条等逻辑 } } /// <summary> /// 处理应用前后台切换 /// </summary> private void OnApplicationPause(bool pauseStatus) { if (pauseStatus) { // 后台主动暂停,避免被系统强杀导致任务丢失 PauseODR(); } else { // 前台恢复下载 ResumeODR(); } } /// <summary> /// 应用重启后自动续传 /// </summary> private void Start() { string[] savedTags = LoadSavedTags(); if (savedTags != null && savedTags.Length > 0) { StartODRDownload(savedTags); } } // 保存下载标签到本地,用于重启续传 private void SaveDownloadTags(string[] tags) { PlayerPrefs.SetString("ODR_SavedTags", string.Join(",", tags)); PlayerPrefs.Save(); } private string[] LoadSavedTags() { return PlayerPrefs.HasKey("ODR_SavedTags") ? PlayerPrefs.GetString("ODR_SavedTags").Split(',') : null; } }
核心管控逻辑说明
- 暂停/恢复:通过
NSProgress的paused属性控制,应用进入后台时主动暂停,回到前台恢复,避免后台被系统终止导致任务状态丢失。 - 续传实现:重启应用时,读取本地存储的下载标签重新发起请求,OC层会自动关联系统中未完成的下载任务,实现续传。
- 进度追踪:用KVO监听
NSProgress的进度变化,通过UnitySendMessage回调到C#层,也可以主动调用GetODRProgress查询实时进度。
测试注意事项
- 确保Xcode项目中已正确配置ODR标签和资源分组,TestFlight包开启ODR功能。
- 测试后台强杀场景时,不要用Xcode终止进程,要在iPhone上手动划掉应用后重新打开。
- 注意清理KVO监听,避免内存泄漏,OC层在下载完成或取消时已处理。
内容的提问来源于stack exchange,提问作者Jitendra Gochhayat
相关产品推荐
相关产品推荐

