如何使用 AFNetworking 3.0 在后台下载大文件并在会话完成所有任务时显示本地通知

Posted

技术标签:

【中文标题】如何使用 AFNetworking 3.0 在后台下载大文件并在会话完成所有任务时显示本地通知【英文标题】:How to download large file in background using AFNetworking 3.0 and present local notification when session completes all the tasks 【发布时间】:2016-02-11 15:06:57 【问题描述】:

我正在尝试使用 AFNetworking 3.0 在我的应用程序中下载大型视频文件。 我找不到任何关于如何使用 AFNetworking 在后台下载文件的具体文档。

我知道 AFNetworking 使用 AFURLSessionManagerAFHTTPSessionManager,它们本身实现了 NSURLSession 的所有委托。

到目前为止,我所做的是,创建了一个 backgroundSessionConfugaration 并使用它开始下载一个文件。

self.manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:bgDownloadSessionIdentifier]];

[self.manager downloadTaskWithRequest:request progress:nil destination:nil completionHandler:nil];

下载开始时,我将应用程序置于后台。到目前为止我发现应用程序在后台运行直到 20 分钟并且下载成功。但是 appDelegate 中的 application:handleEventsForBackgroundURLSession 没有被调用,所以我的 setDidFinishEventsForBackgroundURLSessionBlock 也没有被调用。

我只想在调用 setDidFinishEventsForBackgroundURLSessionBlock 时显示通知。

当我使用它初始化管理器时。 (而不是 backgroundSessionConfiguration

self.manager = [AFHTTPSessionManager manager];

在后台运行 20 分钟后,应用仍然可以下载整个文件。

我的问题是为什么应用程序运行到 20 分钟并完成下载。理想情况下,ios 应该在 10 分钟后终止应用程序。还有为什么 application:handleEventsForBackgroundURLSession 在会话完成后没有被调用。

我已经提到了这个AFNetworking 2.0 and background transfers,但它似乎不适合我。

Background Modes 来自功能的 ON 或 OFF 不会影响我的应用程序中的任何内容。

请帮助我或建议我在这里做错了什么。

我已在此处上传示例代码。 https://www.dropbox.com/s/umwicuta2qzd3k1/RSNetworkKitExample.zip?dl=0

AppDelegate.m

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler 

  NSLog(@"===== background session completed in AppDelegate ===== ");

  if([identifier isEqualToString:bgDownloadSessionIdentifier])
      [RSDownloadManager sharedManager].backgroundSessionCompletionHandler = completionHandler;
  

================================================ =====

ViewController.m


- (IBAction)downloadVideoInBackground:(id)sender 

  NSString *videoURL = @"http://videos1.djmazadownload.com/mobile/mobile_videos/Tum%20Saath%20Ho%20(Tamasha)%20(DJMaza.Info).mp4";

 [RSDownloadManager sharedManager].backgroundSessionCompletionHandler = ^

    NSLog(@"===== background session completed in ViewController ===== ");

    UILocalNotification* localNotification = [[UILocalNotification alloc] init];
    localNotification.alertBody = @"Download Complete!";
    localNotification.alertAction = @"Background Transfer Download!";

    //On sound
    localNotification.soundName = UILocalNotificationDefaultSoundName;

    //increase the badge number of application plus 1
    localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication] applicationIconBadgeNumber] + 1;

    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];

;

  [[RSDownloadManager sharedManager] downloadInBackgroundWithURL:videoURL downloadProgress:^(NSNumber *progress) 

     NSLog(@"Video download progress %.2f", [progress floatValue]);

   success:^(NSURLResponse *response, NSURL *filePath) 

     NSLog(@"Video download completed at Path %@", filePath);

   andFailure:^(NSError *error) 

     NSLog(@"Error in video download %@", error.description);
  ];


================================================ =====

RSDownloadManager.h


#import <Foundation/Foundation.h>
#import "AFHTTPSessionManager.h"

static NSString *bgDownloadSessionIdentifier = @"com.RSNetworkKit.bgDownloadSessionIdentifier";

@interface RSDownloadManager : NSObject

@property (nonatomic, strong) AFHTTPSessionManager *manager;

@property (nonatomic, copy) void (^backgroundSessionCompletionHandler)(void);

+(instancetype)sharedManager;

-(NSURLSessionDownloadTask *)downloadInBackgroundWithURL:(NSString *)urlString downloadProgress:(void (^)(NSNumber *progress))progressBlock success:(void (^)(NSURLResponse *response, NSURL *filePath))completionBlock andFailure:(void (^)(NSError *error))failureBlock;

@end

================================================ =====

RSDownloadManager.m


@implementation RSDownloadManager

#pragma mark - Singleton instance
+(instancetype)sharedManager

   static RSDownloadManager *_downloadManager = nil;
   static dispatch_once_t token;

   dispatch_once(&token, ^

     if (!_downloadManager) 
        _downloadManager = [[self alloc] init];
     
  );
   return _downloadManager;


#pragma mark - Init with Session Configuration

-(void)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 
   self.manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];

   //self.manager = [AFHTTPSessionManager manager];



#pragma mark- Handle background session completion

- (void)configureBackgroundSessionCompletion 
  typeof(self) __weak weakSelf = self;

  [self.manager setDidFinishEventsForBackgroundURLSessionBlock:^(NSURLSession *session) 

     if (weakSelf.backgroundSessionCompletionHandler) 
         weakSelf.backgroundSessionCompletionHandler();
         weakSelf.backgroundSessionCompletionHandler = nil;
     
  ];



#pragma mark- download in background request Method

-(NSURLSessionDownloadTask *)downloadInBackgroundWithURL:(NSString *)urlString downloadProgress:(void (^)(NSNumber *))progressBlock success:(void (^)(NSURLResponse *, NSURL *))completionBlock andFailure:(void (^)(NSError *))failureBlock 

  /* initialise session manager with background configuration */

  if(!self.manager)
     [self initWithSessionConfiguration:[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:bgDownloadSessionIdentifier]];
  

  [self configureBackgroundSessionCompletion];

  /* Create a request from the url */
  NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];

  NSURLSessionDownloadTask *downloadTask = [self.manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) 

     if(progressBlock) 
         progressBlock ([NSNumber numberWithDouble:downloadProgress.fractionCompleted]);
     

  destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) 

     NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
     return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];

  completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) 

    if(error) 
        if(failureBlock) 
            failureBlock (error);
        
    
    else 
        if(completionBlock) 
            completionBlock (response, filePath);
        
    

 ];

  [downloadTask resume];

  return downloadTask;
 

【问题讨论】:

【参考方案1】:

这个委托方法有时不会被调用,

如果任务完成时应用程序已经在运行。 应用程序被主页按钮终止。 如果您无法启动具有相同标识符的后台 NSURLSession。

注意:这在模拟器上表现不同,所以请在真机上检查。

【讨论】:

好的,我会在真实设备上检查。谢谢! 我可以向您推荐apple doc,以获取有关此问题的完整信息。我认为这对于阅读此链接以做出好的设计非常有用 不仅可以检查真实设备,还可以检查未通过 Xcode 运行应用程序的设备。连接到调试器会改变应用的生命周期行为。【参考方案2】:

为什么你没有在你的 downloadInBackgroundWithURL:downloadProgress: 功能中将本地通知块替换为成功块? AppDelegate 中的方法确实没有被调用。 你需要注册你的通知

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    // Override point for customization after application launch.

    if ([UIApplication instancesRespondToSelector:@selector(registerUserNotificationSettings:)])
         [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
    

    return YES;

对于本地通知,请添加开火日期和时区:

    UILocalNotification* localNotification = [[UILocalNotification alloc] init];
    localNotification.alertBody = @"Download Complete!";
    localNotification.alertAction = @"Background Transfer Download!";
    [localNotification setFireDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    [localNotification setTimeZone:[NSTimeZone  defaultTimeZone]];
    //On sound
    localNotification.soundName = UILocalNotificationDefaultSoundName;

    //increase the badge number of application plus 1
    localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication] applicationIconBadgeNumber] + 1;

    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];

现在可以了。 干杯。

【讨论】:

@user3820674:它也不起作用。它不是本地通知的问题,也不是打印 NSLog。我遵循了这个code.tutsplus.com/tutorials/… 和这个appcoda.com/background-transfer-service-ios7 教程。在 successBlock 中显示通知不是一个好方法。还有其他一些任务,比如打开 pdf,或者将 image 设置为 imageView,我们应该在 successBlock 中完成。 我使用了 presentLocalNotificationNow。所以不需要设置 fireDate 和 TimeZone。

以上是关于如何使用 AFNetworking 3.0 在后台下载大文件并在会话完成所有任务时显示本地通知的主要内容,如果未能解决你的问题,请参考以下文章

AFNetworking 3.0 后台上传不起作用

我们如何使用 AFNetworking 3.0 记录请求

我如何在 AFNetworking 3.0 中获取 allHTTPHeaderFields

如何使用 AFNetworking 3.0 在后台下载大文件并在会话完成所有任务时显示本地通知

Afnetworking 3.0 迁移:如何使用 xml 帖子正文发布

在 AFNetworking 3.0 中使用自定义 HTTP 标头