Background Execution

brian
9 min readApr 10, 2019

--

Trước đây mình đã từng có bài viết nói về app life-cycle. Mọi người có thể xem lại tại đây.

Đây là bài viết tiếp nối bài viết trên ❤

Như mọi người cũng đã biết. Nếu user không sử dụng app nữa và muốn để nó xuống background, thì một thời gian ngắn sau đó (do system quyết định) app sẽ bị suspended, tức là k nhận event và xử lý bất kì thứ gì nữa để nhường resources cũng như optimize battery life, và sau đó nếu cần thì OS sẽ terminate app để nhường memory cho các app khác ở foreground.

Nhưng may thay, iOS vẫn cho chúng ta một số cách để làm việc dưới background. Như mình đã nói trong bài viết trước, background là trạng thái app vẫn có thể nhận và xử lý event. Và có một số loại ứng dụng bắt buộc phải dụng được dưới background, ví dụ như mấy ứng dụng phát nhạc (Spotify).

Những xử lý dưới background phụ thuộc hoàn toàn vào iOS để nó tối ưu được việc sử dụng resources và battery. Có 3 categories cơ bản khi làm việc với background:

  • BackgroundTask: hay còn gọi là Finite-Length Tasks , từ cái tên chúng ta cũng có thể hiểu là iOS cho chúng ta một khoản thời gian giới hạn để hoàn thành những thứ chưa thực hiện xong ở foreground.
  • URLSession’s background transfer: đây là một dạng technique đặc biệt của URL Session, khi app bị suspend thì system (iOS) sẽ đảm nhận thực thi của URLSession, và tất nhiên nó sẽ chậm hơn thì bậy giờ priority của session này thấp hơn khi ở foreground
  • Background modes: list một số loại tasks sẽ được built-in support từ iOS.
Target > Capabilities > Background Modes

Trong phạm vi của bài viết mình sẽ đề cập chủ yếu tới 2 technique đầu. Vì Background modes cũng khá đơn giản và nhiều tutorial.

“Always try to avoid doing any background work unless doing so improves the overall user experience. An app might move to the background because the user launched a different app or because the user locked the device and is not using it right now. In both situations, the user is signaling that your app does not need to be doing any meaningful work right now. Continuing to run in such conditions will only drain the device’s battery and might lead the user to force quit your app altogether. So be mindful about the work you do in the background and avoid it when you can.” — Apple

Finite-Length Tasks

Đây cũng là một technique khá thú vị của iOS :D Trước hết, nói sơ qua về những gì chạy dưới OS từ lúc user bấm nút home đến khi app xuống background thật sự:

  • applicationWillResignActive(_:) sẽ được gọi trước tiên để thống báo là app sẽ từ active -> inactive. Cụ thể hơn thì xem lại bài viết trước của mình :)) Nói nôm na là iOS cho cơ hội để chúng ta làm những công việc như lưu lại data trên memory (nếu cần) để tránh mất khi app có thể bị suspended -> terminated by system. Chúng ta có một notification tương tự sẽ được fire ngay trước khi method này được gọi là willResignActiveNotification
  • Tiếp theo đó là applicationDidEnterBackground(_:) đây là event mà iOS cho chúng ta biết là app của chúng ta giờ đang ở background. Một lần nữa iOS lại cho chúng ta cơ hội để do cleanup tránh mất mát, clean up UI để snapshot hiển thị trong switcher (cái này quan trọng nhất là với mấy app phim pỏn), cũng như stop một số services có thể làm app bị ternimated, ví dụ như OpenGL ES. Có thể tham khảo thêm phần best practice ở dưới. Và khoảng thời gian mà iOS cho chúng ta đó là … Vâng khoảng 5s :( 5s thì làm được gì :’( … À và tương tự sẽ có một notificaion được post didEnterBackgroundNotification

Best practice: Being a Responsible Background App

Một lưu ý nhỏ là nếu mà các bạn làm gì quá thời gian iOS cho phép (ý là không return appDidEnterBackground), khoảng 5s đó, thì app sẽ bị terminated ngay lập tức :( Vậy nên cứ return càng sớm càng tốt.

The background execution sequence

Snapshot sẽ được chụp ngay sau khi appDidEnterBackground return.

Những hạn chế trên sẽ được giải quyết phần nào đó nhờ vào một thứ gọi là BackgroundTask. Được tạo ra thông qua methods của UIApplication: beginBackgroundTaskWithName:expirationHandler: hoặc beginBackgroundTaskWithExpirationHandler: Hiểu một cách đơn giản là chúng ta sẽ xin OS thêm thời gian để hoàn thành những tasks còn lại. Sau khi hoàn thành chúng ta phải gọi endBackgroundTask: để thông báo cho system rằng: “Hey! We're done. Do your job”. Nếu sau khoảng thời gian mà system cho thêm chúng ta vẫn k gọi endBackgroundTask thì system có thể sẽ terminate app chúng ta.

Trong method begin có một tham số là expirationHandler. Sẽ được gọi khi thời gian sắp hết, chúng ta có thể gọi end ở đây.

Đây là một implementation khá phổ biến.

Hành động này sẽ được lặp đi lặp lại nhiều lần nên chúng ta nên tạo ra 1 protocol cho nó rồi sau đó nếu cần sài thì conform to protocol là xong.

Khi cần start một task nào mà bạn nghĩ là sẽ cần thêm thời gian thì cứ gọi registerBackgroundTask() , khi xong thì gọi endBackgroundTask() . Không cần phải chờ tới khi app xuống background mới registerBackgroundTask. Có thể gọi lúc app còn trên foreground.

Khoảng thời gian được cấp thêm là một khoảng không nhất định, có nghĩa là có thể khác nhau vì nó được quyết bởi system, cho nên phải tính toán trước khi sử dụng. Nên sử dụng để clean-up một số thứ chứ không nên thực hiện những tác vụ dài như download file.

À còn nữa!! Các bạn nên phân biệt rõ Background Task với Background Fetch, Background Fetch là một feature được cung cấp trong Background Modes nhận support từ framework. Đại khái là system quyết định thỉnh thoảng sẽ wake up app của chúng ta dậy, cho khoảng thời gian ngắn để để update content, ví dụ như mấy ứng dụng có news feed thì lâu lâu được wake cái cho fetch ít data :D (mình sẽ đính kèm link tutorial bên dưới).

URLSession Background Transfer

Đây là một trong những feature mà mình thấy khá hay của iOS, hay cụ thể hơn là của URLSession, một networking API, được cung cấp sẵn trong Foundation Framework.

Như chúng ta cũng đã biết thì để khởi tạo URLSession thì chúng ta cần một configuration object để define behavior cũng như các policies của URLSession — URLSessionConfiguration. Thì để sử dụng được URLSession background transfer thì ta cũng sẽ phải tạo ra một background configuration. Chức năng của background transfer là khi app của chúng ta xuống background, bị suspended hay terminated bởi system (user force quit thì thua), hoặc thậm chí crashed thì những URL session tasks của chúng ta vẫn được thực thi tiếp tục. Đúng với cái tên background transfer, khi app của chúng ta không thể tiếp tục thì sẽ delegate lại cho system để làm tiếp công việc còn lại.

Để làm được những thứ như vậy, thì iOS sẽ control tất cả những công việc đó ở một process khác, cũng khá dễ để hiểu tại sao nó có thể tiếp tục chạy khi app bị terminated. Khi app còn trên foreground thì system sẽ chịu trách nhiệm report lại status của các session tasks lại cho app của chúng ta, chúng ta sẽ nhận được các delegate messages như bình thường. Khi app của chúng ta không còn thực thi được nữa (suspended or terminated) thì những tasks đó sẽ vẫn tiếp tục chạy bình thường. Khi một task hoàn thành system sẽ wake app của chúng ta dậy để thông báo là task đã xog và cần xử lý :D

Khi sử dụng background session thì chúng ta chỉ được return data thông qua delegate. Nếu setup để nhận qua callback….

Đây là cái giá phải trả :))

Nói nghe lằng nhằng vậy những để setup cũng chỉ có một vài bước:

  • Khởi tạo một URLSessionConfiguration với background(withIdentifier identifier: String) -> URLSessionConfiguration cái identifier này cũng khá là quan trọng :3 mình sẽ nói tại sao ở dưới.
let configuration = URLSessionConfiguration.background(withIdentifier: IDENTIFIER)configuration.sessionSendsLaunchEvents = trueconfiguration.isDiscretionary = true
  • Tiếp theo là khởi tạo URLSession với configuration đó và implement các delegate messages. Sao khi config URLSession xong, việc của chúng ta là sử dụng session để tạo ra các download/upload task nhưng thông thường, những việc còn lại là của system.
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
  • Như đã biết thì khi app chúng ta không thể excute, thì system sẽ thỉnh thoảng wake app up để thông báo là task đã hoàn thành. Chúng ta cần implement: func application(_ application: UIApplication,
    handleEventsForBackgroundURLSession identifier: String,
    completionHandler: @escaping () -> Void)
    của AppDelegate. Có 2 thứ chúng ta cần lưu tâm trong method này là: identifiercompletionHandler. Chúng ta cần dụng identifier này để tạo lại URLSessionConfiguration và URLSession như cái chúng ta sử dụng trước đó, system sẽ reconnect các tasks với session mới và report thông qua session's delegate. Chúng ta cần invoke completionHandler để thông báo cho system biết là chúng ta đã hoàn thành công việc. Ví dụ: sau khi download xog chúng ta cần lưu file xuống disk hoặc update UI, sau xog tất cả những đó thì phải invoke completionHandler để báo cho system là chúng ta đã xong. System sẽ snapshot lại current state của app để hiển thị trong app switcher… À thêm nữa, thật ra chúng ta chỉ cần tạo lại URLSession trong trường hợp app bị ternimated, nếu app chỉ bị suspended thì không cần, vì session với identifier đó vẫn còn trong memory.

Có một delegate của URLSession cần chú ý đó là func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) đây là nơi nói cho chúng ta biết là cái delegate messages đã được gọi hết, và đây là nơi chúng ta nên gọi completionHandler.

“In iOS, when a background transfer completes or requires credentials, if your app is no longer running, your app is automatically relaunched in the background, and the app’s UIApplicationDelegate is sent an application(_:handleEventsForBackgroundURLSession:completionHandler:)message. This call contains the identifier of the session that caused your app to be launched. You should then store that completion handler before creating a background configuration object with the same identifier, and creating a session with that configuration. The newly created session is automatically reassociated with ongoing background activity.

When your app later receives a urlSessionDidFinishEvents(forBackgroundURLSession:) message, this indicates that all messages previously enqueued for this session have been delivered, and that it is now safe to invoke the previously stored completion handler or to begin any internal updates that may result in invoking the completion handler.” — Apple

Tuỳ theo mục đích sử dụng của từng loại app mà chúng ta có thể áp dụng các technique nói trên, trong thực tế chúng ta có thể kết hợp cả 2 để tạo ra những ứng dụng thật vui vẻ :D 

Còn lại một số background modes được support sẵn, thì thật ra để sử dụng và setup cũng khá đơn giản, vì chúng đc integrated trực tiếp với những framework và libs chúng ta sử dụng hằng ngày, cũng như hàng tá tutorial ngoài kìa, nếu sau này có thời gian thì mình sẽ brief sơ qua những thứ này.

Một lần nữa, chân thành cảm ơn những người đã đọc hết bài viết này :D nhìn ngắn vậy thôi chứ mỗi ngày mình viết một ít thành ra cũng hết mấy ngày. Vẫn như những lần trước, nếu thấy có gì không đúng thì cứ góp ý với mình dưới phần commnet ❤ Much love.

Tham khảo:

--

--

brian
brian

Written by brian

a software engineer who does software engineering

No responses yet