Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KeyguardManager Memory Leak

I'm using this code to DismissKeyguard after activity finished LeakCanary displays below message. how to prevent these leak.

keyguardManager = (KeyguardManager) getSystemService(Activity.KEYGUARD_SERVICE);

if(Build.VERSION.SDK_INT >= 27) {

    setShowWhenLocked(true);
    setTurnScreenOn(true);

    if (keyguardManager != null) {
        keyguardManager.requestDismissKeyguard(this, null);
    }
}

LeakCanary showing

GC Root: Global variable in native code
    │
    ├─ android.app.KeyguardManager$1 instance
    │    Leaking: UNKNOWN
    │    Anonymous subclass of com.android.internal.policy.IKeyguardDismissCallback$Stub
    │    ↓ KeyguardManager$1.val$activity
    │                        ~~~~~~~~~~~~
    ╰→ com.example.myapplication.MainActivity instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
    ​     

like image 985
SIMZ Avatar asked Sep 02 '25 04:09

SIMZ


1 Answers

The leaktrace indicates that there is a global variable in native code that keeps a reference to an instance of KeyguardManager$1 which implements com.android.internal.policy.IKeyguardDismissCallback$Stub, and KeyguardManager$1 itself holds a reference to the activity.

The sources for the implementation of KeyguardManager#requestDismissKeyguard can be found here: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/KeyguardManager.java#574 :

ActivityTaskManager.getService().dismissKeyguard(
        activity.getActivityToken(), new IKeyguardDismissCallback.Stub() {
    @Override
    public void onDismissError() throws RemoteException {
        if (callback != null && !activity.isDestroyed()) {
            activity.mHandler.post(callback::onDismissError);
        }
    }
    @Override
    public void onDismissSucceeded() throws RemoteException {
        if (callback != null && !activity.isDestroyed()) {
            activity.mHandler.post(callback::onDismissSucceeded);
        }
    }
    @Override
    public void onDismissCancelled() throws RemoteException {
        if (callback != null && !activity.isDestroyed()) {
            activity.mHandler.post(callback::onDismissCancelled);
        }
    }
}, message);

That's an Inter Process Call to the ActivityTaskManager service process. The stubs that is set to receive a result when the ActivityTaskManager service process calls back holds the reference to the activity. Unfortunately, native stubs tend to be held in memory longer than expected because they're dependent on the GC running in the other process.

This is clearly a bug in the Android framework. You should file a bug to the Android framework and provide a sample app that reproduces it.

like image 139
Pierre-Yves Ricau Avatar answered Sep 04 '25 18:09

Pierre-Yves Ricau