[Android Performance] Memory leak – Phần 2: Những lỗi cần tránh khi lập trình
Ở bài viết trước chúng ta đã tìm hiểu về nguyên nhân và tác hại của Memory Leak trong các ứng dụng java nói chung và android nói riêng. Ở bài viết này mình sẽ tiếp tục chủ đề Memory Leak bằng việc giới thiệu tới các bạn một số lỗi cần tránh trong quá trình lập trình để không dẫn tới Memory Leak. Những lỗi lập trình này được mình tập hợp dựa trên kinh nghiệm trong quá trình làm việc cộng với tham khảo thêm từ những lập trình viên kinh nghiệm cũng như các trang công nghệ nổi tiếng vì vậy độ chính xác là khá cao. Và mình cũng khuyên các bạn nên lưu ý để không bị các lỗi không mong muốn trong tương lai, cũng như ông cha ta đã dạy “phòng bệnh hơn chữa bệnh” đó thôi :))
Để tránh mất thời gian của các bạn, mình sẽ không giới thiệu lại về Memory Leak nữa mà đi thẳng luôn vào vấn đề chính, còn nếu bạn nào chưa biết Memory Leak là gì thì hãy xem lại bài viết trước của mình [Android Performance] Memory leak – Phần 1: Nguyên nhân và tác hại
1. Không unregister listener, receiver, service
Một trong các lỗi gây ra Memory Leak mà mình thường gặp nhất đó chính là không unregister listener, receiver hoặc service. Đó là bởi vì các listener (hoặc receiver, service) thường sẽ giữ 1 tham chiếu đến đối tượng của bạn và nếu như bạn quên không unregister thì GC vẫn sẽ hiểu là đối tượng của bạn còn được sử dụng nên sẽ không giải phóng đối tượng của bạn. Điều này càng nguy hiểm hơn khi các đối tượng không được giải phóng lại thường là các activity (hoặc context), mà như các bạn đã biết các acitivity trong Android thường có rất nhiều view và các đối tượng khác bên trong nên việc leak Activity sẽ làm bộ nhớ của ứng dụng của bạn tăng nhanh 1 cách chóng mặt.
Một ví dụ đơn giản cho trường hợp này chính là việc không unregister ContentObserver sẽ gây ra Memory Leak đối với Context (Acitivity) đã register ContentObserver đó.
Chú ý: Với các listener được register (hoặc set) cho các đối tượng là View hoặc ViewGroup (như OnClickListener chẳng hạn) thì bạn sẽ không cần phải unregister do Android đã mặc định unregister hộ bạn khi đối tượng View hoặc ViewGroup bị giải phóng.
2. Sử dụng static không đúng cách
Một lỗi khác cũng thường xuyên gặp phải đó là việc sử dụng các biến static không đúng cách. Chắc hẳn nhiều bạn đều đã từng sử dụng các biến static trong java nói chung và android nói riêng, và chắc rằng các bạn cũng đã biết rằng các biến static này sẽ được giữ lại cho tới khi ứng dụng của bạn bị kill (bởi bạn hoặc hệ thống). Cũng chính nhờ vào khả năng này mà biến static được khá nhiều bạn ưu thích nhưng điều đó cũng làm cho ứng dụng của bạn rất dễ bị Memory Leak nếu như bạn quá lạm dụng các biến static hoặc sử dụng các biến static không đúng lúc và đúng đối tượng.
Để làm rõ hơn, chúng ta cùng xét 1 ví dụ như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static Drawable mBackgroundDrawable; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView mLabel = new TextView(this); mLabel.setText("Leaks are bad"); if (mBackgroundDrawable == null) { mBackgroundDrawable = getDrawable(R.drawable.large_bitmap); } mLabel.setBackgroundDrawable(mBackgroundDrawable); setContentView(mLabel); } |
Các bạn có thể thấy biến static mBackgroundDrawble có kiểu là Drawable sẽ chỉ được load lần đầu tiên khi biến này còn có giá trị null và sau đó sẽ được sử dụng để set là background cho 1 TextView là mLabel. Nhìn sơ qua thì đoạn code này rất bình thường và thậm chí còn giúp bạn đỡ phải load lại Drawable ở những lần start Activity sau.
Nhưng chắc bạn không thể ngờ là đoạn code trên lại có thể gây ra Memory Leak phải không. Đó là bởi vì mỗi khi Drawable được truyền vào View, nó sẽ có 1 tham chiếu tới View mà ở đây là TextView mLable và mỗi 1 View trong Activity đều có 1 tham chiếu (dạng strong reference) tới Activity chứa nó. Điều này làm dẫn tới 1 vấn đề là khi Activity bị destroy (sau khi thực hiện onDestroy) thì đáng lẽ bộ nhớ của Acvitiy phải bị GC thu hồi nhưng do biến mBackgroundDrawable là dạng static không bị thu hồi và vẫn giữ tham chiếu đến TextView nên TextView cũng không bị thu hồi, hậu quả kéo theo là Activity cũng không bị thu hồi do vẫn còn tham chiếu tới từ TextView. Và như vậy Memory Leak lại xảy ra đối với Activity.
Như các bạn thấy đó việc sử dụng biến static không đúng cách có thể dẫn tới Memory Leak dễ dàng đến như thế nào. Vì vậy hãy thận trọng khi sử dụng các biến static nhé. Và một lời khuyên của mình là tốt nhất hãy chỉ sử dụng các biến static với các kiểu nguyên thủy, hoặc nếu sử dụng với kiểu tham chiếu thì nên giải phóng các refrence của từ nó đến các đối tượng khác càng sớm càng tốt.
3. Không dùng static với các inner class
Hẳn các bạn sẽ thắc mắc tại sao ở mục trước mình khuyên các bạn nên cẩn thận khi dùng các biến static mà ở mục này mình lại nói rằng việc dùng các inner class không phải là static lại gây ra lỗi phải không nào. Đó là bởi vì khác với các biến, các non-static inner class sẽ luôn có 1 tham chiếu ẩn danh tới outer class của nó. Điều này rất dễ gây ra Memory Leak nếu các bạn quản lý không tốt các đối tượng của các inner class này. Nhưng các static inner class thì lại không có các tham chiếu ẩn này vì vậy bạn có thể thoái mái sử dụng mà không hề lo lắng về vấn đề Memory Leak.
4. Không close Cursor
Trong rất nhiều trường hợp việc không gọi close() Cursor sau khi thực hiện xong việc đọc dữ liệu ra từ Cursor sẽ gây ra Memory Leak, đặc biệt là với các database tự tạo. Vì vậy lời khuyên của mình đó là hãy nhớ close Cursor mỗi khi sử dụng xong, đừng tiếc gì 1 dòng code mà làm ảnh hưởng tới hiệu năng của cả ứng dụng.
Chú ý: Thực ra tất cả các đối tượng của các class có implement interface Closable đều cần gọi close() để tránh Memory Leak.
5. Không sử dụng satic Handler
Các bạn hãy thử xem đoạn code dưới đây:
1 2 3 4 5 6 7 8 |
class MyActivity extends Activity{ Handler myHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; } |
Các bạn có thấy điều gì khác lạ không, chắc hẳn nhiều bạn sẽ nói là không. Nhưng thực ra đoạn code trên lại có thể là nguyên nhân gây ra Memory Leak đấy. Bởi vì sao ư, đó là vì trong trường hợp này myHandler sẽ sử dụng chung looper với class bao bọc nó là MyActivity, và nếu khi MyActivity bị destroy mà myHandler vẫn chưa được giải phóng thì GC cũng sẽ không thể thu hồi bộ nhớ của MyActivity.
Tới đây chắc nhiều bạn cũng thắc mắc là nên sử dụng Handler như thế nào. Theo mình tốt nhất là nên dùng static Handler bởi như vậy thì Handler sẽ dùng chung looper với Application nên không gây ra leak Activity nữa, hoặc một cách nữa là truyền vào Handler khi cần khởi tạo giá trị cho nó thay vì khởi tạo trực tiếp giá trị ngay trong class bao bọc.
6. Không Recycle Bitmap
Đối với các máy sử dụng Android 3.0 trở xuống thì Bitmap sẽ không tự giải phóng mà các bạn phải gọi recycle() trực tiếp để giải phóng các Bitmap. Nhưng kể từ Android 3.0 trở đi Android đã có cơ chết tự động giải phóng Bitmap nên bạn cũng không cần lo lắng nhiều về vấn đề này. Tuy nhiên mình vẫn khuyên các bạn là nên gọi tường minh recycle() cho Bitmap sớm nhất có thể để lấy lại được bộ nhớ một cách nhanh nhất từ các Bitmap không còn sử dụng này.
Vâng như vậy là mình đã giới thiệu tới các bạn một số lỗi thường gặp khi lặp trình có khả năng gây ra Memory Leak. Mình sẽ cố gắng cập nhật thêm các trường hợp khác nếu có thể vì vậy hãy theo dõi xem blog của mình để cập nhật nhé.
- Google+
- Wordpress