Android Thread: Looper, Handler, HandlerThread, Executor (P2)
Tiếp nối bài viết về Android Thread Phần 1, ở bài viết này mình sẽ đi sâu hơn chi tiết hơn về các khái niệm cơ bản khi làm việc với Thread trong Android.
Nhắc lại kiến thức: Thread là gì?
Thread là một đơn vị được sử dụng để thực thi một đoạn mã của chương trình. Hoặc bạn cũng có thể hiểu Thread là môi trường để thực thi 1 đoạn mã của chương trình.
Trong Android để thao tác với Thread bạn có nhiều sự lựa chọn, trong bài viết này mình sẽ giới thiệu lần lượt chúng.
Trong thực tế lập trình Android hiện đại không còn quá nhiều người sử dụng Thread trực tiếp cho các xử lý của họ. Tuy nhiên đây là một mảng kiến thức mà mỗi lập trình viên đều nên nắm để có thể hiểu cách chương trình thực thi và viết ra những dòng code tốt hơn.
Thread
Trong Android, Thread được chia làm 2 loại chính: Main Thread và Background Thread. Main Thread được dùng cho việc xử lý các cập nhật, sự kiện của giao diện người dùng; ở mặt khác Background Thread là đại diện được chọn cho các xử lý logic, tính toán nặng. Khi sử dụng Background Thread để xử lý các logic nặng, ta tránh được tình trạng app lag, giật, đơ giúp gia tăng trải nghiệm người dùng.
Lưu ý rằng trong Android các Logic cập nhật giao diện, xử lý Input chỉ được chạy trên Main Thread. Chính vì vậy ta cần có những xử lý phù hợp để chuyển đổi việc thực thi trên Main Thread sang Background Thread và ngược lại phù hợp để chương trình được tối ưu.
Hãy xem đoạn code bên dưới để biết cách sử dụng Thread cho việc xử lý tính toán và cập nhật giao diện trên Main Thread (UI Thread).
class MainActivity : AppCompatActivity {
private lateinit var textViewCount: TextView
// default functions
fun handleLargeProcess() {
// chuyển logic sang Background Thread để thực thi
Thread {
var count = 0
while (count < 100000) {
count += 1; // giả lập tính toán nặng
}
runOnUiThread {
// chuyển việc cập nhật giao diện lên runOnUiThread
textViewCount.text = "$count"
}
}.start() // thực thi Thread đã tạo
}
}
runOnUiThread
là hàm mặc định của Activity cung cấp phương thức để thực thi một logic trên Main Thread ở bất kỳ đâu từ Activity.
Looper
Đặt vấn đề: Mỗi Thread trong chương trình như một đường ống không có quay đầu. Các Logic được đặt vào trong Thread khi thực thi xong sẽ làm cho Thread đi thẳng đến trạng thái Terminated. Điều này gây ra sự lãng phí trong chương trình vì ta vẫn có thể tận dụng lại Thread đó để thực thi tiếp cho các Logic khác.
Android nhận ra vấn đề này và họ đã tạo ra lớp Looper. Trong chương trình Android, Looper đúng như tên gọi, nó là một vòng lặp mà khi đặt vào trong Thread sẽ khiến Thread đó bị … bất tử (just kidding). Looper được đặt vào trong Thread để giúp Thread duy trì được trạng thái Running của nó. Đồng thời vòng lặp này chứa một MessageQueue, MessageQueue này là một hang đợi giúp xử lý các khối logic một các có thứ tự bên trong Thread.
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
} // Đây là cách mà Looper duy trì trạng thái running của Thread
Tham khảo tại android.os.Looper, android.os.MessageQueue
Handler
Vậy Handler là gì?
Trong thiết kế của Android, Looper là đối tượng đảm nhiệm vai trò duy trì vòng lặp (trạng thái running) cho Thread, còn Handler được tạo ra với mục đích giúp chúng ta tương tác với Looper một cách có quy củ, hiệu quả. Ta không thể gọi Looper của một Thread nào đó trực tiếp và bắt hắn thực thi một logic. Tất cả phải thông qua Handler.
Một số hỗ trợ mà Handler trong Android cung cấp
- Thực thi một logic bất kỳ trên Thread của Looper
- Thực thi một logic bất kỳ trên Thread của Looper tại một thời điểm trong tương lai
- Thực thi một logic bất kỳ trên Thread của Looper sau một khoảng thời gian định trước
- Gửi một message đến Looper
Viết đến đây thì mình lại nghĩ cái Handler này giờ chả còn ai dùng nữa viết làm gì. Thôi các bạn tự tìm hiểu tiếp nha. Không thì chỉ cần biết đến đây để sau này đi phỏng vấn người ta hỏi biết cách trả lời là được rồi 😀
HandlerThread
Đây là một Wrapper của bộ 3: Thread, Looper, Handler. Các lập trình viên của Google tạo ra HandlerThread với mục đích giúp chúng ta dễ dàng thao tác và tái sử dụng Background Thread hơn. Chỉ vậy thôi.
Về cơ bản HandlerThread là một Thread nhưng được gắn liền với Looper, Handler nên nó vẫn tồn tại sau khi tạo để bạn tái sử dụng được nhiều lần. Một lưu ý nhỏ là hãy nhớ terminate HandlerThread khi không còn nhu cầu sử dụng nữa.
// Chọn 1 trong 2 hàm này để kill Handler Thread khi không còn nhu cầu sử dụng nữa là được.
handlerThread.quit()
handlerThread.quitSafely()
Executor
Well, chào mừng bạn đến với kỷ nguyên của tái sử dụng.
Sử dụng Executor cũng là sử dụng Thread, nhưng là sử dụng tổ hợp nhiều (>= 1 Thread) được đóng gói lại chung trong một cấu trúc dữ liệu (thường là dạng List, hoặc Queue) để sử dụng cho một mục đích nhất định.
Về cơ bản Executor không khác gì HandlerThread nhưng ở quy mô lớn hơn, cách tiếp cận khác hơn. Nếu HandlerThread là sử dụng 1 Thread để đưa các executable code vào trong một MessageQueue để thực thi tuần tự thì Executor cung cấp cho chúng ta đa dạng các giải pháp hơn.
Có thể nhắc đến ThreadPoolExecutor là một Implementation của Executor, nó cung cấp cho ta một Pool có thể chứa nhiều Thread bên trong nó. Điều này đồng nghĩa với việc tại 1 thời điểm ThreadPoolExecutor có thể thực thi cùng lúc nhiều hơn 1 task, ThreadPoolExecutor cũng cung cấp giải pháp tương tự như HandlerThread là cancel một scheduled task. Tuy nhiên vẫn có thể có một số hạn chế nhất định: ThreadPoolExecutor không cho phép chúng ta schedule một task theo thời gian như HandlerThread (thật ra mình thấy cái này không quan trọng, vì nó là thiết kế thôi ta có thể tự thêm nếu thấy cần thiết) : )
Thôi không add example code đâu. Thời buổi này còn ai dùng Executor trong Android nữa. Chuyển qua RxJava với Kotlin Coroutines hết rồi nha!