Prevent memory leak in Android

2019-09-10 06:55发布

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

2条回答
仙女界的扛把子
2楼-- · 2019-09-10 07:14

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

android:configChanges="orientation|screenSize"
查看更多
做个烂人
3楼-- · 2019-09-10 07:25

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

查看更多
登录 后发表回答