12/08/2018, 14:19

OutOfMemoryException trong Android

11-03 10:59:39.199: E/AndroidRuntime(13566): FATAL EXCEPTION: main 11-03 10:59:39.199: E/AndroidRuntime(13566): java.lang.OutOfMemoryError 11-03 10:59:39.199: E/AndroidRuntime(13566): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 11-03 10:59:39.199: E/AndroidRuntime(13566): ...

11-03 10:59:39.199: E/AndroidRuntime(13566): FATAL EXCEPTION: main
11-03 10:59:39.199: E/AndroidRuntime(13566): java.lang.OutOfMemoryError
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:493)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at  android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:299)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:324)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.graphics.drawable.Drawable.createFromPath(Drawable.java:880)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at org.Infoware.childrenbible.GeneralHelper.setImgfromSDCard(GeneralHelper.java:608)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at org.Infoware.childrenbible.CoverPageActivity.onCreate(CoverPageActivity.java:205)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.app.Activity.performCreate(Activity.java:4465)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1920)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.app.ActivityThread.access$600(ActivityThread.java:123)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1147)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.os.Handler.dispatchMessage(Handler.java:99)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.os.Looper.loop(Looper.java:137)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at android.app.ActivityThread.main(ActivityThread.java:4424)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at java.lang.reflect.Method.invokeNative(Native Method)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at  java.lang.reflect.Method.invoke(Method.java:511)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
11-03 10:59:39.199: E/AndroidRuntime(13566):    at dalvik.system.NativeStart.main(Native Method)

OutOfMemoryException hay còn gọi là Memory leak là sự việc xảy ra khi mà vùng nhớ được hệ thống cấp phát cho đối tượng không thể thu hồi lại khi mà đối tượng đó không sử dụng nữa.

Nguồn gốc căn bản của Leak memory là do việc không kiểm soát được reference tới ActivityContext nên sẽ có 3 nguyên tắc như sau :

  1. Chú ý đến vòng đời của Activity để gỡ bỏ reference khi Activity bị GC thu hồi vùng nhớ.
  2. Dùng Context đúng phạm vi của nó.
  3. Hạn chế truyền Activity vào những cái không bao giờ chết hoặc không biết bao giờ chết như Singleton hay Thread. Nếu có thì nên dùng thêm WeakReference.

1. Non static inner class

        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(50000);
                }
            }).start();
        }
    }

Ví dụ như đoạn mã trên. Khi ta chạy chương trình xong và nhấn nut backPress trên thiết bị thì chắc chắn OutOfMemoryException sẽ xảy ra. Lý do đơn giản bởi vì khi ta nhấn BackPress thì OnDestroy sẽ được gọi nhưng MainActivity không được giải phóng vì Thread vẫn còn đang chạy. Do đó nó vẫn đang sử dụng instance MainActivity của bạn và điều này làm cho GC không thể thu hồi được vùng nhớ đã cấp phát cho MainActivity.

Nguyên nhân

Chắc các bạn cũng đã biết non static inner class trong Java sẽ reference đến outer class. Ở đoạn mã này, Thread mà bạn đang dùng có thể dùng thành phần của MainActivity, tức là Thread này đang reference đến MainActivity. Do đó nếu Thread vẫn còn chạy thì MainActivity sẽ không thể hủy. --> OutofMemory thôi.

Cách giải quyết

Sử dụng static inner class thay cho non static inner class để Thread không refer đến MainActivity nữa. Do bây giờ Thread không còn refer đến MainActivity nữa nên nếu bạn muốn dùng ActivityContext thì làm như sau :

    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new Thread(new MyRunnable(MainActivity.this)).start();
        }

    private static class MyRunnable implements Runnable {
            private WeakReference aWR;
            public MyRunnable(Activity activity) {
                aWR = new WeakReference<>(activity);
            }
            @Override
            public void run() {
                SystemClock.sleep(50000);
                Activity act = (Activity) aWR.get();
                if (act != null) {
                // check null vì GC sẽ thu vùng nhớ bất kỳ lúc nào do được đặt trong 1 WeakReference
                    action(act);
                }
            }

            private void action(Activity act) {
                //TODO something
            }
        }

2. Tạo instance của Singleton này không đúng cách

    public class MySingleton {

        private static MySingleton instance;
        private Context context;

        public static MySingleton getInstance(Context context) {
            if (instance == null) {
                instance = new MySingleton(context);
            }
            return instance;
        }

        private MySingleton(final Context context) {
            this.context = context;
        }
    }

Nguyên nhân

Vì Singleton đi cùng với getApplicationContext, nên nếu bạn tạo một instance không đúng cách chắc chắn sẽ tạo ra leak memory ngay. Ví dụ như sau:

MySingleton.getInstance(Activity.this);

Cách giải quyết

Rất đơn giản bạn chỉ cần

public static MySingleton getInstance(Context context) {
        if (instance == null) {
           instance = new MySingleton(context.getApplicationContext());
        }
        return instance;
}

3. Sử dụng Broadcast không đúng cách

Nguyên nhân

Nguyên nhân do bạn unRegister chưa đúng cách.

Cách giải quyết

Nếu bạn register trong onCreate() thì unregister trong onDestroy(), nếu register trong onResume() thì unregister trong onPause()

Chúc mọi người một ngày làm việc vui vẻ !

0