0

I'm having some troubles with memory leaks on Android.
I do have a simple application with a relatively picture as background and it works fine, until I change the orientation.

The following is a graph showing the memory leak (allocated memory over time). Every spike corresponds to an orientation change, and every spike is +20M of allocated memory
Memory leak over time

At ~ 30 seconds the app crashes with an obvious "OutOfMemory" error:

Throwing OutOfMemoryError "Failed to allocate a 17469452 byte allocation with 804912 free bytes and 786KB until OOM"`

The layout is just a simple RelativeLayout with a jpeg image as background sized ~ 430k

I do have implemented also an onDestroy() anti-memory leak solution (as suggested here):

@Override
protected void onDestroy()
{
    unbindDrawables(view);
    view = null;
    System.gc();
    super.onDestroy();
}

private void unbindDrawables(View view) {
    if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
    }
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
        }
        ((ViewGroup) view).removeAllViews();
    }
}

But it really don't change anything.

Is there anything I can do to solve the issue? I do need the image to be that big because the device running the app has a big screen.

-- Edit --
Adding onCreate() code as requested, please do note that the problem is present even without calling initMainView() and even by having an empty onCreate() (with super only), even tough the memory leak is inferior (only 0.8M) but still present.
I'm running Android 6.0.1 on a Pixel C, if that might help.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    System.gc();
    w = getWindow();
    w.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    dpm = (DevicePolicyManager) this.getSystemService(Context.DEVICE_POLICY_SERVICE);
    deviceAdmin = new ComponentName(this, AdmRcvr.class);

    if(!dpm.isDeviceOwnerApp(getPackageName())) {
        setContentView(R.layout.deviceownertutorial);
        final TextView tvTutorial = (TextView) w.findViewById(R.id.tutorialtv);
        tvTutorial.setText(Html.fromHtml(getString(R.string.deviceownertutorial)));

        final TextView tvDeviceOwnerError = (TextView) w.findViewById(R.id.tverrordeviceadmin);

        Button checkDeviceAdmin = (Button) w.findViewById(R.id.cdevadmin);
        checkDeviceAdmin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (dpm.isDeviceOwnerApp(getPackageName())) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            setContentView(R.layout.startview);
                            initMainView();
                        }
                    });
                } else {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            tvDeviceOwnerError.setText("App is not the device admin");
                            tvDeviceOwnerError.setVisibility(View.VISIBLE);
                        }
                    });
                }
            }
        });
    }
    else
    {
        setContentView(R.layout.startview);
        initMainView();
    }
}


public void initMainView(){
    // We're device owners!
    View mDecorView = w.getDecorView();
    mDecorView.setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    mDecorView = null;
    view = findViewById(R.id.sv_relativelayout);
    initReader();
    initBitmapCache();
    enableTimer();
    currentStep = 1;
}

--- EDIT 2 ---

In the following images you could see the memory allocation in two situation: with all my app garbage (like the 430kB image) [a] and without (a clean onCreate()) [b].
Please do note that the memory leak is still present in both situation, as stated above, even tough it isn't that big (but there is!).

Memory leak in case [a] Memory leak, case [a]

Memory leak in case [b] Memory leak, case [b]

The app is compiled against SDK v.23 and with buildToolsVersion 23.0.2. The dependencies are the following:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.2.1'
    compile 'com.android.support:design:23.2.1'
}

And the app/libs is the following:

libs
└── acssmc-1.1.2.jar  

--- EDIT 3 ---
When testing with a newly created application the memory is managed as it should, you could see an example on my GitHub repo

Denys Vitali
  • 530
  • 6
  • 19
  • 1
    Do you use a plain Activity or Fragments within an Activity? Could you show your other lifecycle methods as well (espescially onCreate)? – Robin Vinzenz Mar 23 '16 at 13:15
  • 1
    It'd be helpful if more code is shown. Also what you are doing in onDestroy() doesn't really do anything unless something outside of your Activity/static members of the Activity is referencing your views, which wouldn't be solved by your unbindDrawables() anyway. – Kai Mar 23 '16 at 14:14
  • Added code as requested, hope it helps – Denys Vitali Mar 23 '16 at 15:31
  • what is the ComponentName? I see you pass "this" (which is the activity) to it. – Tin Tran Mar 24 '16 at 07:43
  • The ComponentName is used to set the DevicePolicyManager.[setLockTaskPackages()](http://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#setLockTaskPackages(android.content.ComponentName,%20java.lang.String[])) then, and an application context must be passed to the new ComponentName. You spotted a memory leak, which now I've corrected with getApplicationContext(). But unfortunately isn't the one which compromise the app. – Denys Vitali Mar 24 '16 at 08:57

2 Answers2

0

You could try to avoid reload your activity with this in your manifest under your activity:

android:configChanges="orientation|screenSize"
Miguel Benitez
  • 2,322
  • 10
  • 22
  • I know, but that is the worst solution ever, as stated by Google and here on SO, [check this](http://stackoverflow.com/a/5336057/2560279) – Denys Vitali Mar 23 '16 at 15:14
  • This doesn't solve the problem, it just avoids it for a little bit longer, because eventually the activity will likely load again, which will leak memory, minimizing the frequency of leaked memory rather than fixing the leak is like bailing water out of a sinking ship, you need to plug the hole. – Trevor Hart Mar 03 '17 at 20:02
0

I've managed to solve the problem.
It was all because of a damn timer which was setting a tick event on every screen orientation change.
To fix the issue I simply binded the timer to a variable, and called timer.cancel() in onSaveInstance()

Now my memory leaks are gone (as shown below):
no more memory leaks

Denys Vitali
  • 530
  • 6
  • 19