1
votes

DialogFragment dans CustomView provoque une fuite de mémoire

J'ai une MainActivity qui a un customView (DatePicker) dans sa mise en page. DatePicker customView a un bouton et un CustomDialogFragment. Lorsque le bouton est cliqué sur le DatePicker, il affiche le CustomDialogFragment. L'application fonctionne bien mais leakCanary montre une fuite. Voici le code (Certains codes ont été supprimés par souci de concision)

MainActivity.class

References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.

1437 bytes retained by leaking objects
Signature: 1eb8b5c7c3fd403a9a6851729c4044c8a6ce7cf6
┬───
│ GC Root: System class
│
├─ android.view.inputmethod.InputMethodManager class
│    Leaking: NO (InputMethodManager↓ is not leaking and a class is never leaking)
│    ↓ static InputMethodManager.sInstance
├─ android.view.inputmethod.InputMethodManager instance
│    Leaking: NO (DecorView↓ is not leaking and InputMethodManager is a singleton)
│    ↓ InputMethodManager.mNextServedView
├─ com.android.internal.policy.DecorView instance
│    Leaking: NO (LinearLayout↓ is not leaking and View attached)
│    mContext instance of com.android.internal.policy.DecorContext, wrapping activity com.example.testproject.MainActivity with mDestroyed = false
│    Parent android.view.ViewRootImpl not a android.view.View
│    View#mParent is set
│    View#mAttachInfo is not null (view attached)
│    View.mWindowAttachCount = 1
│    ↓ DecorView.mContentRoot
├─ android.widget.LinearLayout instance
│    Leaking: NO (MainActivity↓ is not leaking and View attached)
│    mContext instance of com.example.testproject.MainActivity with mDestroyed = false
│    View.parent com.android.internal.policy.DecorView attached as well
│    View#mParent is set
│    View#mAttachInfo is not null (view attached)
│    View.mWindowAttachCount = 1
│    ↓ LinearLayout.mContext
├─ com.example.testproject.MainActivity instance
│    Leaking: NO (DatePicker↓ is not leaking and Activity#mDestroyed is false)
│    ↓ MainActivity._$_findViewCache
├─ java.util.HashMap instance
│    Leaking: NO (DatePicker↓ is not leaking)
│    ↓ HashMap.table
├─ java.util.HashMap$Node[] array
│    Leaking: NO (DatePicker↓ is not leaking)
│    ↓ HashMap$Node[].[0]
├─ java.util.HashMap$Node instance
│    Leaking: NO (DatePicker↓ is not leaking)
│    ↓ HashMap$Node.value
├─ com.example.testproject.customViews.DatePicker instance
│    Leaking: NO (View attached)
│    mContext instance of com.example.testproject.MainActivity with mDestroyed = false
│    View.parent androidx.constraintlayout.widget.ConstraintLayout attached as well
│    View#mParent is set
│    View#mAttachInfo is not null (view attached)
│    View.mID = R.id.date_picker
│    View.mWindowAttachCount = 1
│    ↓ DatePicker.calendarDialog
│                 ~~~~~~~~~~~~~~
╰→ com.example.testproject.customViews.CalendarDialog instance
​     Leaking: YES (ObjectWatcher was watching this because com.example.testproject.customViews.CalendarDialog received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
​     key = e176896c-49c6-4b17-a21e-4a6ca7cde260
​     watchDurationMillis = 11213
​     retainedDurationMillis = 6208
​     key = f3a2f22a-c77f-4c8e-a281-d803d110acff
​     watchDurationMillis = 11214
====================================
0 LIBRARY LEAKS

Library Leaks are leaks coming from the Android Framework or Google libraries.
====================================
METADATA

Please include this in bug reports and Stack Overflow questions.

Build.VERSION.SDK_INT: 28
Build.MANUFACTURER: Google
LeakCanary version: 2.2
App process name: com.example.testproject
Analysis duration: 4191 ms
Heap dump file path: /data/user/0/com.example.testproject/files/leakcanary/2020-03-11_10-15-46_729.hprof
Heap dump timestamp: 1583936152876
====================================

activity_main.xml

class CalendarDialog: DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val builder = AlertDialog.Builder(context!!)
        builder.setView(view)
            .setMessage("This is a dummy message")
            .setPositiveButton("OK") { dialog, which -> }
            .setNegativeButton("Cancel") { dialog, which -> }

        return builder.create()
    }
}

DatePicker.class

class DatePicker : FrameLayout {
    var calendarDialog: CalendarDialog? = null
    init {
        View.inflate(context, R.layout.date_picker, this)
        open_calendar.setOnClickListener {
            calendarDialog?.show((context as MainActivity).supportFragmentManager.beginTransaction(), "Calendar")
        }
    }
}

CalendarDialog.class

  <androidx.constraintlayout.widget.ConstraintLayout>
    <com.example.testproject.customViews.DatePicker
        android:id="@+id/date_picker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </com.example.testproject.customViews.DatePicker>
 </androidx.constraintlayout.widget.ConstraintLayout>

RÉSULTAT DE L'ANALYSE HEAP ===================================== 1 FUITES D'APPLICATIONS

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        date_picker.calendarDialog = getCalendarDialog()
    }

    private fun getCalendarDialog(): CalendarDialog {
        return CalendarDialog()
    }
}

Voici les choses que j'ai essayées jusqu'à présent sans succès.

  1. J'ai essayé d'initialiser CalendarDialog à différents endroits.
  2. Créez un écouteur dans la classe CalendarDialog et annulez l'instance 'calendarDialog' lorsque la boîte de dialogue est fermée. Et quelques autres ...


2 commentaires

Il n'y a pas de code incorrect dans les classes ci-dessus. Pourriez-vous fournir le code complet des classes?


Je n'ai plus de code. C'est tout.


4 Réponses :


0
votes

Lorsque le fragment CalendarDialog est ignoré, ce fragment est censé être récupéré. Cependant, ici, nous pouvons voir que la mise en page DatePicker en garde une référence, l'empêchant d'être récupérée. DatePicker est toujours attaché, il est donc logique qu'il existe toujours, mais il doit définir son champ DatePicker.calendarDialog sur null lorsque la boîte de dialogue est fermée.


1 commentaires

J'ai pensé la même chose.J'ai ajouté un callBack à CalendarDialog et défini l'instance de CalendarDialog dans DatePicker sur null lorsque rejeté est appelé. Cela n'a pas aidé.



1
votes

Vous pouvez placer CalendarDialog dans un WeakReference, de cette façon, vous n'avez pas à le définir explicitement sur null via un rappel. Le garbage collector le nettoiera automatiquement et vous pourrez éviter la fuite de mémoire.

Pour plus d'informations sur WeakReference: https://developer.android.com / reference / java / lang / ref / WeakReference


0 commentaires

0
votes

Faites de CalenderDialog la fonction utilitaire

class DatePicker(context: Context, attr: AttributeSet) : FrameLayout(context, attr) {

  var calendarDialog: CalendarDialog? = null

  init {
    View.inflate(context, R.layout.date_picker, this)
    open_calendar.setOnClickListener {
        calendarDialog?.onCreateDialog(context)?.show()
    }
  }

  override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    calendarDialog = null
    open_calendar.setOnClickListener(null)
  }
}

et

class CalendarDialog {

  fun onCreateDialog(context: Context): Dialog {
    val builder = AlertDialog.Builder(context)
    builder
        .setMessage("This is a dummy message")
        .setPositiveButton("OK") { dialog, which -> }
        .setNegativeButton("Cancel") { dialog, which -> }

    return builder.create()
  }
}

L'activité n'a aucune idée que le fragment de dialogue existe dans votre cas.


0 commentaires

0
votes

J'ai vécu une expérience similaire auparavant. Tout ce que j'ai fait a été de changer supportFragmentManager avec childFragmentManager . Je suggère donc de changer ceci:

calendarDialog?.show((context as MainActivity).childFragmentManager.beginTransaction(), "Calendar")

en ceci:

calendarDialog?.show((context as MainActivity).supportFragmentManager.beginTransaction(), "Calendar")

Recherchez également tout autre supportFragmentManager (s'il y en a).


0 commentaires