Sửa lỗi rò rỉ bộ nhớ trong Android - OutOfMemoryError
Việc rò rỉ bộ nhớ trong Android là khá dễ xảy ra. Developer có thể không nhận ra đã để bộ nhớ bị rò rỉ mỗi ngày. Cho đến khi nhảy ra một ngoại lệ như thế này... java . lang . OutOfMemoryError : Failed to allocate a 4308492 byte allocation with 467872 free bytes and 456 KB until OOM at ...
Việc rò rỉ bộ nhớ trong Android là khá dễ xảy ra. Developer có thể không nhận ra đã để bộ nhớ bị rò rỉ mỗi ngày. Cho đến khi nhảy ra một ngoại lệ như thế này...
java.lang.OutOfMemoryError: Failed to allocate a 4308492 byte allocation with 467872 free bytes and 456KB until OOM at dalvik.system.VMRuntime.newNonMovableArray(Native Method) at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609) at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444) at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:988) at android.content.res.Resources.loadDrawableForCookie(Resources.java:2580) at android.content.res.Resources.loadDrawable(Resources.java:2487) at android.content.res.Resources.getDrawable(Resources.java:814) at android.content.res.Resources.getDrawable(Resources.java:767) at com.nostra13.universalimageloader.core.DisplayImageOptions.getImageOnLoading(DisplayImageOptions.java:134)
Exception này có phải nói rằng bitmap quá lớn đối với hệ thống Android?
Thật không may, khi nhận được một OutOfMemoryError, thì nó thường có nghĩa là, 9/10 đã xảy ra rò rỉ bộ nhớ. Ấn tượng đầu tiên khi đọc đoạn Stack trace trên, nghĩ là bitmap quá lớn, như vậy là nhận định sai lầm.
Rò rỉ bộ nhớ là gì?
A failure in a program to release discarded memory, causing impaired performance or failure.
Tức là: một lỗi trong chương trình khi giải phóng bộ nhớ không dùng tới, làm ảnh hưởng đến hiệu năng hệ thống hoặc xảy ra lỗi.
Rò rỉ bộ nhớ xảy ra trong Android?
Rò rỉ bộ nhớ trong Android thực sự khá dễ dàng để xảy ra. Vấn đề lớn nhất là các đối tượng Context.
Mỗi ứng dụng có một đối tượng Context toàn cục (getApplicationContext()). Mọi Activity là một lớp con của Context, lưu giữ thông tin liên quan đến Activity hiện tại. Rò rỉ bộ nhớ thường xuyên được liên kết với một Activity bị rò rỉ. Chẳng hạn, một vài đối tượng TextView được khai báo static sẽ giữ tham chiếu tới Activity, dẫn tới Activity này sẽ không được giải phóng khi không còn sử dụng, bộ nhớ bị chiếm dụng không cần thiết.
Một dấu hiệu cảnh báo lớn cho thấy bộ nhớ được sử dụng ngày càng tăng trong ứng dụng, như mô tả trong Androids Memory Monitor:
Androids Memory Monitor của ứng dụng xảy ra rò rỉ bộ nhớ
Androids Memory Monitor sau khi sửa lỗi rò rỉ bộ nhớ
Trong đồ thị đầu tiên, ta có thế thấy ứng dụng sẽ không bao giờ có thể lấy lại một số bộ nhớ đã sử dụng. Nó được sử dụng lên đến khoảng 300MB trước thời điểm xảy ra OutOfMemoryError. Biểu đồ thứ 2 cho thấy rằng ứng dụng có thể thu dọn rác, lấy lại một số bộ nhớ đã cung cấp và sử dụng bộ nhớ khá thích hợp.
Làm thế nào để tránh rò rỉ bộ nhớ?
- Tránh truyền thêm đối tượng Context vào Activity hoặc Fragment.
- KHÔNG BAO GIỜ tạo hoặc lưu một đối tượng Context hoặc View trong một biến static. Đây là dấu hiệu đầu tiên của rò rỉ bộ nhớ.
private static TextView textView; //DO NOT DO THIS private static Context context; //DO NOT DO THIS
- Luôn luôn unregister các listener trong phương thức onPauser() hoặc onDestroy(). Nó bao gồm các listener của Android, đến những thứ như Location service hay các service quản lý hiển thị và các listener được tuỳ chỉnh riêng.
- Không lưu trữ các tham chiếu mạnh (strong refenrence) tới các activity trong các AsyncTask hoặc các background thread. Bởi vì các Activity có thể bị đóng lại, nhưng AsyncTask vẫn tiếp tục thực hiện và giữ tham chiếu tới Activity.
- Sử dụng Context của ứng dụng (getApplicationContext()) thay vì Context từ một Activity nếu có thể.
- Cố gắng tránh sử dụng các non-static inner class. Lưu trữ một tham chiếu tới một cái gì đó như Activity hay View bên trong lớp này có thể dẫn đến rò rỉ bộ nhớ. Sử dụng WeakReference nếu cần tham chiếu tới chúng.
Làm thế nào để sửa lỗi rò rỉ bộ nhớ?
Sửa lỗi rò rỉ bộ nhớ sẽ phải thực hiện một số hoạt động, rất nhiều lần thử và lỗi. Rò rỉ bộ nhớ có thể rất khó để theo dõi. May mắn là có một vài công cụ trợ giúp xác định một rò rỉ có thể.
- Mở Android Studio, bật sang tab Android Monitor.
- Chạy ứng dụng và lựa chọn nó từ danh sách các ứng dụng.
- Thao tác một số hành động trong ứng dụng.
- Theo dõi quá trình ứng dụng sử dụng bộ nhớ cho đến khi xảy ra OutOfMemoryException.
- Click tab Memory trong Android Monitor.
- Xuất hiện biểu đồ bắt đầu được vẽ. Khi sẵn sàng thì click “Initiate GC” (biểu tượng xe tải rác màu cam).
- Click “Dump Java Heap” và chờ đợi một vài giây. Hành động này sẽ tạo một tập tin .hprof để phân tích sử dụng bộ nhớ.
- Cài đặt MAT để xem phân tích file .hprof
- Chạy lệnh sau để chuyển đổi từ file .hprof sang file MAT.
./hprof-conv path/file.hprof exitPath/heap-converted.hprof
- Mở file trong MAT và lựa chọn “Leak Suspects Report".
- Click vào thanh 3 màu xanh trên cùng “Create a histogram from an arbitrary set of objects”, sẽ thấy danh sách các đối tượng được chiếm bộ nhớ.
- Lọc các đối tượng bằng tên class.
- Nhìn hình trên thấy rằng có 9 đối tượng VideoDetailActivity, rõ ràng là không đúng khi chỉ nên có 1 đối tượng. Để tìm những thứ giữ tham chiếu tới VideoDetailActivity, click chuột phải vào item và chọn “Merge Paths to Shortest GC Root”, sau đó click vào “exclude all phantom/weak/soft etc. references”.
- Từ thông tin dưới đây, có thể thấy một DisplayListener đã được register nhưng không bao giờ được unregistered.
Vì vậy, rò rỉ bộ nhớ được giải quyết bằng cách gọi unregistered cho listener đã được register.
DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); displayManager.unregisterDisplayListener(listener);
Không phải tất cả các rò rỉ bộ nhớ được tìm ra dễ dàng, một số sẽ khó khăn hơn. Có nhiều công cụ khác trợ giúp cho việc tìm rò rỉ bộ nhớ, có thể xem qua tại: http://developer.android.com/intl/vi/tools/performance/comparison.html