2016-11-30

Retained Fragment, headless Fragment là gì, setRetainInstance(true)?

Thực ra mình cũng định nhắc tới headless Fragment từ lâu. Cũng vài năm rồi, dạo gần đây có đụng lại Android, cần phải giải thích, có search 1 vòng nhưng vẫn không thấy một bài viết tiếng Việt nào có sẵn nên note luôn ở đây. Post này không bàn chi tiết về code, hay cụ thể trường hợp sử dụng headless Fragment nào chỉ focus chung vào đặc điểm của headless Fragment và một vài case thông dụng.

Headless Fragment là gì?

Đầu tiên là tên gọi headless Fragment? Nhiều developer khác gọi là viewless Fragment, UI-less Fragment, retained Fragment không có view hay nhiều khi còn gọi là retained "worker" Fragment lý do sẽ đề cập sau là Fragment này thông thường được sử dụng để manage một worker hay background operation.

Tất nhiên cái tên này làm liên tưởng nhiều đến kỵ sĩ không đầu headless horseman/knight.

Có nhiều developer thông tin rằng tên gọi này bắt nguồn từ tutorial của Lars Vogel tại
Multi-pane development in Android with Fragments - Tutorial#Headless Fragments.

Vậy headless Fragment cụ thể là gì? Mặc dù tên là headless Fragment nhưng tính năng đáng nói nhất lại là "retained" qua configuration change.

setRetainInstance(true)

Mặc định Fragment sẽ bị hủy (destroyed) và tạo lại (recreated) cùng với parent Activity. Việc sử dụng Fragment#setRetainInstance(true) sẽ giúp Fragment được giữ lại retained khi Activity được tạo lại (recreation).

LƯU Ý: điều này chỉ áp dụng với các Fragment KHÔNG NẰM TRONG BACK STACK.

Trạng thái của Fragment sẽ được retained (duy trì, giữ lại) khi configuration change. Điều này "retained" có nghĩa là Fragment sẽ không bị destroyed khi configuration changes. Cũng có nghĩa là Fragment sẽ được giữ lại ngay cả khi configuration change làm cho Activity bị destroyed.

Tất nhiên cần nói rõ thêm. Giống như Activity, Fragment vẫn có thể bị destroyed bởi hệ thống khi tài nguyên bộ nhớ xuống thấp. Khi đó dù có thiết lập "retained" hay không thì hệ thống cũng sẽ destroy các Fragment này khi rời khỏi Activity. Trong trường hợp rời khỏi Activity (ví dụ nhấn nút home), Fragment có thể có hoặc không bị hủy. Nhưng khi rời khởi Activity bằng nút back (việc đó sẽ gọi finish() và sẽ destroy Activity), tất cả các Fragment cũng sẽ bị destroy.

Xem thêm Understanding Fragment's setRetainInstance(boolean) trên Stackoverflow,
Handling configuration changes with Fragments của Alex Lockwood

UI-less + setRetainInstance(true)

Headless Fragment là Fragment không có/không định nghĩa UI hay có nghĩa không inflate một View nào. Tài liệu chính thức của Adroid nói về headless Fragment là Fragment without UI tại Fragments guide/Adding a fragment without a UI. Tuy nhiên trong đoạn này có một chỗ ko rõ ràng là 

"This adds the fragment, but, because it's not associated with a view in the activity layout, it does not receive a call to onCreateView(). So you don't need to implement that method".

Chính xác thì Android có gọi Fragment#onCreateView() nhưng kết quả trả về là null. Tức là thay vì không override Fragment#onCreateView() thì vẫn có thể implement trả về null.

Ví dụ đi kèm SDK là /APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java có thể xem online tại FragmentRetainInstance.

Nhưng chỉ headless không thì cũng không có ý nghĩa gì đặc biệt: đáng nói ở đây thông thường là kết hợp headless Fragment và setRetainInstance(true) còn gọi là retained headless Fragment.

Thực tế thì headless Fragment thường sẽ đi kèm với "retained" hay cũng sẽ là retained headless Fragment nên retained sẽ "vô tình" bị bỏ đi. Vì Fragment không có UI chỉ dùng với mục đích manage Object nào đó thường là background operation.

UI + setRetainInstance(true)

Việc Fragment có UI và sử dụng setRetainInstance(true) vẫn không có vấn đề gì dù trong trường hợp retained này có thể xảy ra một temporary memory leak.

Fragment có UI và developer cần thao tác với các UI. Việc thao tác thực hiện thông qua reference tới các UI/View dùng findViewById chẳng hạn. Với retained Fragment, các reference cũng được "retained" kéo theo View cũng sẽ được "retained". Do đó có thể gây temporary memory leak vì View internal giữ Context của Activity. Giả sử configuration change từ "portrait" sang "landscape". Fragment giữ Context của "portrait" Activity và 1 text box (View) cũng giữ Context của "portrait" Activity. Khi configuration change hoàn thành reference tới Activity sẽ được update lại, ví dụ trong onViewCreated, text box sẽ reference tới với "landscape" Activity. Tức trước khi việc này xảy ra, text box vẫn giữ reference của "portrait" Activity. Việc này có thể ngăn cản "portrait" Activity cũ được thu hồi bởi GC. Hiển nhiên, nếu không cẩn thận, ví dụ giữ một reference tới chính activity trong Fragment thì memory leak thật sự có thể xảy ra.

Đó là lý do "retained" mới là tính năng quan trọng hơn là headless. Headless chỉ là tính năng phụ kéo theo khi Fragment không cần xử lý UI.

Usage

Headless Fragment rất hữu dụng khi nó là "worker" Fragment khi nó giữ các object như Thread, AsyncTask, Socket, etc ...

Ví dụ thông thường là retain một AsyncTask qua configuration change sử dụng headless Fragment. Việc này sẽ bảo đảm rằng progress sẽ cập nhật và kết quả sẽ trả về cho currently displayed Activity instance (Activity đang hiển thị) và bảo đảm sẽ không bị leak leak AsyncTask khi configuration change.

Khi Activity tạo lại thì Fragment sẽ được hệ thống xác định theo ID hoặc tag (nếu không chỉ định cả hai hệ thống sẽ lấy theo ID của container view) để find/restore Fragment. Headless Fragment sẽ được thêm programmatically tức là thêm bằng code nên trong trường hợp của headless fragment unique identifier là tag. Đơn giản trong onCreate của Activity nếu không tìm thấy "retained" Fragment với findFragmentByTag thì  thực hiện add mới Fragment và start background operation. Reference tới Activity sẽ được update thông qua onAttach và onDetach.

Kết quả là headless Fragment có thể dùng để retain Presenter (MVP) do Presenter thường dùng long running background operation. Cách này cũng được Mosby 2 sử dụng.

Headless Fragment cũng có thể được sử dụng để tổ chức việc Ask runtime permission theo như bài viết trên Medium Use headless Fragment for Android M run-time permissions and to check network connectivity.

No comments:

Post a Comment