Task và Backstack trong Android

Android Jun 16, 2021

Mở đầu

Activity là 1 trong những thành phần chính tạo nên ứng dụng Android, việc hiểu rõ và cách sử dụng activity là việc tiên quyết mà lập trình viên Android cần học ngay từ đầu. Bên cạnh Activity thì việc hiểu rõ về Task và Back Stack cũng hết sức quan trọng. Trong bài này chúng ta sẽ đi tìm hiểu về 2 thành phần này nhé.

Giới thiệu về Task và Back Stack

Một task là một tập hợp các activity mà người dùng tương tác khi thực hiện một công việc nhất định. Các activity này được sắp xếp trong một ngăn xếp (stack), được gọi là back stack.

Trong back stack, activity được sắp xếp theo thứ tự mỗi activity được mở.
Ví dụ: Khi ta mở ứng dụng lên với thứ tự các activity được start như sau:

Activity A -> Activity B -> Activity C

hình bên dưới mô tả stack của hoạt động trên

Nếu người dùng nhấn nút điều hướng back, thì Activity C sẽ bị finish và pop ra khỏi stack.

Khi có nhiều ứng dụng chạy đồng thời trong một môi trường đa cửa sổ, được hỗ trợ từ Android 7.0 (API 24) trở lên, hệ thống sẽ quản lý các task riêng biệt cho mỗi cửa sổ, mỗi cửa sổ có thể có nhiều task. Màn hình Home của thiết bị là điểm bắt đầu cho hầu hết các task.

Nếu người dùng mở một ứng dụng bất kì, hệ thống sẽ tạo 1 task mới cho ứng dụng đó, trong trường hợp ứng dụng đã tồn tại trong stack rồi thì task đó sẽ vào foreground và người dùng sẽ nhìn thấy.

Khi Activity hiện tại khởi chạy 1 Activity khác, Activity mới sẽ được push vào stack và chiếm focus của Activity trước đó, Activity trước đó vẫn tồn tại trong stack và xếp ngay sau Activity mới, nó sẽ dừng lại, hệ thống sẽ giữ lại toàn bộ trạng thái hiện tại của nó.

Khi người dùng nhấn nút back, Activity mới bên trên sẽ bị pop ra khỏi top của stack(bị destroy), activity trước đó sẽ quay trở lại cùng với trạng thái trước khi bị mất focus.

Một lưu ý là Stack sẽ không bao giờ được sắp xếp lại, nó chỉ có thể push activity mới vào và pop activity hiện tại ra. Vì vậy nguyên lý hoạt động của stack là LIFO(Last in first out). Hinh bên dưới mô tả ví dụ về hoạt động của backstack:

Một task có thể đi vào background khi mà người dùng bắt đầu một task mới hoặc đi tới màn hình Home, thông qua nút Home. Khi ở trong background, tất cả activity trong stack đều bị dừng lại, nhưng back stack của task vẫn còn nguyên vẹn, task chỉ đơn giản là bị mất focus khi task khác đang chiếm vị trí. Một task có thể quay trở lại foreground và người dùng có thể quay trở lại thời điểm họ đã rời đi.

Giả sử, ta có một task (Task A) với 3 activity trong stack, và task này đang nằm ở foreground. Khi người dùng nhấn nút Home, sau đó bắt đầu một app mới từ app launcher. Khi màn hình Home xuất hiện, Task A đi vào background. Khi ứng dụng được khởi chạy, hệ thống sẽ tạo một task cho ứng dụng đó (Task B) với stack cho

các activity của tiêng nó. Sau khi sử dụng ứng dụng, người dùng quay trở về màn hình Home một lần nữa và chọn ứng dụng đã bắt đầu Task A ban đầu. Khi đó, Task A sẽ đi vào foreground, cả 3 activity trong stack của nó vẫn còn nguyên vẹn và activity ở top của stack sẽ tiếp tục. Tại thời điểm này, người dùng cũng có thể chuyển về Task B một lần nữa bằng cách quay về màn hình Home và chọn ứng dụng tương ứng hoặc sử dụng màn hình Recents để chọn ứng dụng. Đây là ví dụ của multitasking trong Android.

Quản lý Task

Bằng cách đặt tất cả activity được bắt đầu vào cùng một task và trong một stack hoạt động theo kiểu "last in first out" - sẽ hoạt động tốt cho hầu hết các ứng dụng và ta không cần lo lắng về cách các activity được liên kết với task cũng như cách chúng tồn tại trong back stack. Tuy nhiên, ta có thể quyết định thay đổi cách hoạt động bình thường này. Giả sử ta muốn một activity trong ứng dụng bắt đầu một task mới khi nó khởi chạy (thay vì được đặt vào task hiện tại), hay là khi ta khởi chạy một activity, ta muốn đem instance (nếu) đang tồn tại của nó lên top của task (thay vì tạo một instance mới), hoặc là ta muốn back stack sẽ xóa tất cả activity ngoại trừ root activity khi người dùng rời khỏi task. Ta có thể làm tất cả những việc này và nhiều hơn nữa với những thuộc tính của thẻ <activity> trong file manifest hay các flag của intent mà ta truyền vào khi gọi phương thức startActivity(). Các thuộc tính của activity mà ta có thể sử dụng có thể kể tới:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwayRetainTaskState
  • finishOnTaskLaunch

Cùng với đó, các giá trị flag của intent ta có thể sử dụng là:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP Sau đây, ta sẽ đi vào ý nghĩa của từng thuộc tính và flag của intent, để có thể định nghĩa cách activity được liên kết với task và cách chúng được xử lý trong back stack.

Liên quan đến launchMode thì mình có viết hẳn 1 bài riêng giải thích về nó. Các bạn có thể tham khảo tại đây nhé.

Task Affinity

Affinity sẽ chỉ định task mà activity sẽ được đưa vào khi chạy. Theo mặc định, tất cả các activity của cùng một ứng dụng sẽ có cùng affinity. Như vậy, các activity của cùng một ứng dụng sẽ chạy vào cùng một task. Tuy nhiên, ta có thể thay đổi điều này. Activity từ những ứng dụng khác nhau có thể chia sẻ một affinity, hoặc activity của cùng một ứng dụng có thể có affinity khác nhau. Để khai báo affinity của một activity, ta sử dụng thuộc tính taskAffinity của thẻ <activity> trong file manifest. Thuộc tính này nhận vào giá trị là một string, phải là duy nhất từ package name mặc định được khai báo trong thẻ <manifest>. Task Affinity sẽ được sử dụng trong hai trường hợp:

  • Khi intent được sử dụng để khởi chạy activity chứa flag FLAG_ACTIVITY_NEW_TASK. Một activity, theo mặc định sẽ được chạy vào trong cùng một task với activity gọi phương thức startActivity(). Nó sẽ được push vào cùng back stack với activity gọi nó bắt đầu. Tuy nhiên, nếu intent dùng để chạy activity chứa flag FLAG_ACTIVITY_NEW_TASK, hệ thống sẽ tìm kiếm một task khác để đưa activity mới vào. Thường thường, nó sẽ là một task mới. Tuy nhiên, nó không bắt buộc phải vậy. Nếu có một task đã tồn tại có cùng affinity với activity đang chuẩn bị được khởi chạy, activity sẽ được đưa vào task đó thay vì tạo một task mới. Trong trường hợp một task mới được bắt đầu, nếu người dùng nhấn nút Home để rời khỏi nó, thì chúng ta cần có một cách nào đó để người dùng có thể quay trở lại task đó. Một vài trường hợp (như là notification manager) luôn bắt đầu một activity trong một external task, vậy nên chúng luôn đưa flag FLAG_ACTIVITY_NEW_TASK vào trong intent được truyền vào hàm onStartActivity. Nếu ta có một activity có thể được gọi bởi một thực thể bên ngoài và sử dụng flag này, hãy quan tâm tới cách người dùng có thể quay trở lại task sau khi nó đã bắt đầu, như là một launcher icon chẳng hạn.
  • Khi một activity có thuộc tính allowTaskReparenting được set là true. Trong trường hợp này, activity có thể di chuyển từ task nó được bắt đầu sang task nó được gán giá trị affinity, khi task đó đi vào foreground. Ví dụ, giả sử ta có một activity thông báo thời tiết trong một thành phố được chọn được coi là một phần của một ứng dụng du lịch. Nó có cùng affinity với những activity khác ở trong ứng dụng, và nó có thuộc tính allowTaskReparenting được set là true. Khi một activity nào đó khởi động activity thông báo thời tiết, nó sẽ đi vào cùng task với activity gọi nó bắt đầu. Tuy nhiên, khi task của ứng dụng du lịch đi vào foreground, activity đó sẽ được gán lại cho task của nó và được hiển thị trong nó.

Dọn dẹp Back Stack

Nếu người dùng rời khỏi task trong một thời gian dài, hệ thống sẽ dọn dẹp các activity trong task ngoại trừ root activity. Khi người dùng quay trở lại task, chỉ có root activity được khôi phục. Hệ thống hoạt động theo cách này bởi có thể sau một khoảng thời gian dài, người dùng quay trở lại ứng dụng để bắt đầu một công việc mới thay vì tiếp tục công việc cũ trước đó. Có một số thuộc tính ta có thể sử dụng để thay đổi điều này:

  • alwaysRetainTaskState: Nếu thuộc thính này được set là true trong root activity của task, tất cả activity trong stack của task đó sẽ luôn được giữ lại.
  • clearTaskOnLaunch: Nếu thuộc tính này được set là true trong root activity của task, bất kỳ khi nào người dùng rời khỏi task và quay trở lại, chỉ còn root activity được giữ lại, kể cả khi người dùng chỉ rời đi trong một thời gian ngắn. Có thể nói, thuộc tính này hoàn toàn ngược lại với alwaysRetainTaskState
  • finishOnTaskLaunch: Thuộc tính này tương tự như clearTaskOnLaunch, nhưng nó chỉ xảy ra trên một activity đơn lẻ, không phải trên toàn bộ task. Nó cũng có thể khiến bất kỳ activity nào bị hủy bỏ, kể cả là root activity. Khi nó được set là true, khi người dùng rời khỏi task, activity được set thuộc tính này sẽ không còn tồn tại.

Tạo một Task

Ta có thể thiết đặt cho một activity như là điểm bắt đầu của một task bằng cách sử dụng thẻ <intent-filter> với "android.intent.action.MAIN" như là hành động được chỉ định và "android.intent.category.LAUNCHER" như là category được chỉ định. Một intent filter kiểu này sẽ tạo một icon và một label cho activity để hiển thị trong app launcher, cho phép người dùng khởi chạy activity cũng như quay trở lại task mà nó tạo ra sau khi rời khỏi task. khả năng cho phép người dùng quay trở lại task sau khi rời đi bằng activity launcher là rất quan trọng. Vì lý do này, hai launch mode làm cho activity luôn tạo task mới là singleTask và singleInstance chỉ nên được dùng khi activity có một ACTION_MAIN và CATEGORY_LAUNCHER filter.

Trên đây là bài viết của mình về Task và Backstack trong android. Cảm ơn các bạn đã đọc, hẹn mọi người vào những bài viết theo nhé.

nguồn tham khảo: https://developer.android.com/guide/components/activities/tasks-and-back-stack

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.