Some ways resolve memory leaks in Fragment Android

Fragment là một phần của một Activity để cho phép thiết kế activity có tính mô-đun. Chính về thế chúng ta thường xuyên dùng tới Fragment, nhưng quá trình sử dụng này gặp nhiều vấn đề về Memory leaks. Chắc hẳn sẽ có bạn đặt ra câu hỏi Memory Leak là gì ? Nó diễn ra khi nào ? ...bla...bla..

1. Memory leak là gì?

Khi bạn code xong 1 ứng dụng hay một chức năng nào đó, qúa trình khởi chạy đầu tiên thấy được kết quả như ý muốn và nghĩ rằng tới đây công việc đã có thể tươi đẹp được rồi. Sau đó bạn cho người khác dùng hoặc QA test lại nhưng lần này dùng trong thời gian lâu hơn, thì tự nhiên... TẠCH!! App bị treo hoặc crash 😦 😦 Khi bạn cắm máy vào debug phát hiện lỗi OutOfMemoryException chẳng hạn - lúc này bạn đang gặp vấn đề về thiếu hụt bộ nhớ rồi. Hiểu đơn giản, leak memory xảy ra khi hệ thống không thể thu hồi vùng nhớ đã cấp phát cho đối tượng khi đối tượng không còn được sử dụng nữa. Dễ hình dung hơn mình ví dụ 1 đoạn code ngắn như sau :


public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(20000);
            }
        }).start();
    }
}

Khi bạn khởi chạy App lên sau đó bấm phím Back, lúc này App sẽ vào trạng thái onDestroy() và GC sẽ thu hồi vùng nhớ của MainActivity nhưng quá trình này không hoàn thành được vì MainActivity không được giải phóng. Vì khi đó vẫn còn 1 thread khác đang chạy và đang reference tới MainActivity. Đó là một trường hợp để xảy ra lỗi memory leak. Vậy nếu chúng ta làm việc với Fragment thì có bị lỗi này không ? Và khắc phục như nào ? Trả lời: Không chỉ có mà rất nhiều. Câu trả lời rõ ràng và chi tiết hơn chúng ta cùng theo dõi nội dung phía dưới nhé! Mình sẽ đề cập một số trường hợp và cách khắc phục đi kèm.

2. Retain Fragment

Đây là trường hợp mà chúng ta hay gặp phải, đó là khi chúng ta savedInstanceState cho View để quá trình sau khi hiển thị lại Fragment thì View sẽ không phải tham chiếu tới id nữa và tăng tốc việc hiển thị view y hệt như trạng thái trước đó. Khi đó View sẽ giữ lại reference tới Activity context, vì thế mà nó cũng giữ cả reference tới Context đó. Điều này sẽ diễn ra tốt đẹp khi mà Context đó vẫn ở trạng thái đang tồn tại, kết quả View sau khi bạn open lại Fragment rất mượt. Nhưng trong trường hợp restart Activity thì sao ? Thường xuyên diễn ra với những ứng dụng cho phép xoay màn hình. Lúc này Activity sẽ tạo ra 1 Context mới và GC muốn dọn đi Context cũ ( chính các Views kia đang sử dụng), bạn biết đấy quá trình này tất nhiên không được diễn ra tốt đẹp rồi. => Memory leak Ví dụ :

public class LeakyFragment extends Fragment {

    private View mLeak; // retained

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mLeak = inflater.inflate(R.layout.whatever, container, false);
        return mLeak;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        // not cleaning up.
    }
}

- Giải pháp: Chúng ta sẽ phải clear hết UI trong onDestroyView để đảm bảo rằng không có View nào được reference tới Context cũ nữa. Khi mà Activity bị restart thì Fragment cũng sẽ tạo mới UI và điều quan trọng là GC thực hiện công việc của mình mà không gặp trở ngại nào.

3. Fragments replaced and put in the back stack

UserA -> ViewProfileFragment(1) StoryViewFragment (1)-> ViewProfileFragment(2) StoryViewFragment(2) -> ViewProfileFragment(3)

Khi nhìn vào sơ đồ tuần tự trên bạn sẽ thấy có những lúc mình replaced một fragment sang 1 fragment khác và thao tác này User thực hiện nhiều lần và kèm theo đó nếu bạn để ý kỹ hơn vào đoạn code thực hiện công việc này sẽ lại bất ngờ rằng việc new instance Fragment diễn ra quá nhiều lần. Đặt câu hỏi : Việc đó có ảnh hưởng gì không ? Chúng ta tiếp tục tìm câu trả lời nha.. Hãy xem Fragment có liên kết như thế nào với Activity qua đoạn trích dẫn từ Android Developer document

A fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle. For example, when the activity is paused, so are all fragments in it, and when the activity is destroyed, so are all fragments. However, while an activity is running (it is in the resumed lifecycle state), you can manipulate each fragment independently.

Dù cho bạn có lường trước được sự ảnh hưởng của việc khởi tạo - update UI rồi và đã handle việc clear tại onPause() trong Fragment thì nó cũng không được khởi chạy, trừ khi bạn xử lý nó trong Activity cha. Như vậy rõ ràng chúng ta đang bị lỗi memory leak ở đây rồi. 😦

  • Giải pháp:

Chúng ta không thực hiện được trong onPause() của Fragment nhưng có cách khác. Hãy thực hiện @Override method setUserVisibleHint tại đây bạn vừa thực hiện việc cài đặt UI khi Fragment được khởi tạo và khi chuyển sang Fragment khác thì bạn hãy remove tất cả những UI được khởi tạo đi. Bạn quan sát ví dụ code dưới đây để hình dung rõ hơn nhé :

public class LeakFragment extends Fragment {
  @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
      //you are visible to user now - so set whatever you need 
      initResources();
    }
    else { 
     //you are no longer visible to the user so cleanup whatever you need
     cleanupResources();
    }
  }
}

Cho thấy rằng giải pháp này thật hữu hiệu khi bạn replaced nhiều Fragment mà lỗi lo về memory leak không còn nữa.

4. Tổng kết

Trên đây là những nội dung mình đã cung cấp về Memory leak và những trường hợp hay gặp phải với Fragment - Cách khắc phục. Quá trình phát triển dự án chắc chắn bạn còn gặp phải nhiều trường hợp bị ẩn đi và phải mất công đào sâu hơn về nó với mong muốn tạo ra ứng dụng tối ưu tốt hơn. Hy vọng với những thông tin trên đây sẽ đem lại sự cải thiện hoặc giải đáp một số vấn đề mà các bạn đang tìm kiếm.


All Rights Reserved