如何解决框架布局中的内存泄漏问题?

Posted

技术标签:

【中文标题】如何解决框架布局中的内存泄漏问题?【英文标题】:How to fix memory leak issue in Frame Layout? 【发布时间】:2021-07-06 07:13:12 【问题描述】:

我在我的 android 应用程序中使用了 leakcanary,它检测到 FrameLayout Leaked。但我无法找到或解决此问题,

如何解决此泄漏?参考我的泄漏金丝雀报告。 FrameLayout 泄露

 ┬───
    │ GC Root: System class
    │
    ├─ leakcanary.internal.InternalLeakCanary class
    │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
    │    ↓ static InternalLeakCanary.resumedActivity
    ├─ com.android.zigmaster.MainActivity instance
    │    Leaking: NO (MapViewFragment↓ is not leaking and Activity#mDestroyed
    │    is false)
    │    mApplication instance of com.android.zigmaster.MyApplication
    │    mBase instance of androidx.appcompat.view.ContextThemeWrapper
    │    ↓ ComponentActivity.mActivityResultRegistry
    ├─ androidx.activity.ComponentActivity$2 instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    Anonymous subclass of androidx.activity.result.ActivityResultRegistry
    │    this$0 instance of com.android.zigmaster.MainActivity with mDestroyed =
    │    false
    │    ↓ ActivityResultRegistry.mKeyToCallback
    ├─ java.util.HashMap instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ HashMap.table
    ├─ java.util.HashMap$HashMapEntry[] array
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ HashMap$HashMapEntry[].[4]
    ├─ java.util.HashMap$HashMapEntry instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ HashMap$HashMapEntry.value
    ├─ androidx.activity.result.ActivityResultRegistry$CallbackAndContract instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ ActivityResultRegistry$CallbackAndContract.mCallback
    ├─ androidx.fragment.app.FragmentManager$10 instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    Anonymous class implementing androidx.activity.result.
    │    ActivityResultCallback
    │    ↓ FragmentManager$10.this$0
    ├─ androidx.fragment.app.FragmentManagerImpl instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ FragmentManager.mParent
    ├─ com.android.zigmaster.ui.trips.FragmentTripPlanner instance
    │    Leaking: NO (Fragment#mFragmentManager is not null)
    │    ↓ FragmentTripPlanner.mMap
    │                          ~~~~
    ├─ com.google.android.gms.maps.GoogleMap instance
    │    Leaking: UNKNOWN
    │    Retaining 765.4 kB in 10828 objects
    │    ↓ GoogleMap.zzg
    │                ~~~
    ├─ com.google.android.gms.maps.internal.zzg instance
    │    Leaking: UNKNOWN
    │    Retaining 142 B in 2 objects
    │    ↓ zza.zza
    │          ~~~
    ├─ com.google.maps.api.android.lib6.impl.bn instance
    │    Leaking: UNKNOWN
    │    Retaining 765.0 kB in 10821 objects
    │    A instance of com.android.zigmaster.MyApplication
    │    ↓ bn.w
    │         ~
    ├─ android.widget.FrameLayout instance
    │    Leaking: UNKNOWN
    │    Retaining 2.9 kB in 65 objects
    │    View not part of a window view hierarchy
    │    View.mAttachInfo is null (view detached)
    │    View.mWindowAttachCount = 1
    │    mContext instance of com.android.zigmaster.MyApplication
    │    ↓ View.mParent
    │           ~~~~~~~
    ╰→ android.widget.FrameLayout instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.google.android.
    ​     gms.maps.SupportMapFragment received Fragment#onDestroyView() callback
    ​     (references to its views should be cleared to prevent leaks))
    ​     Retaining 1.9 kB in 48 objects
    ​     key = 265fafb3-b2a3-4462-a4e7-d5cc2afbc6fe
    ​     watchDurationMillis = 57439
    ​     retainedDurationMillis = 52422
    ​     View not part of a window view hierarchy
    ​     View.mAttachInfo is null (view detached)
    ​     View.mWindowAttachCount = 1
    ​     mContext instance of com.android.zigmaster.MainActivity with
    ​     mDestroyed = false
    
   

这是我的 xml 代码: XML:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_
    android:layout_
    tools:context=".ui.trips.FragmentTripPlanner">


    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_
        android:layout_ />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

这是我的片段 片段:

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.gms.maps.CameraUpdate
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng



class MapViewFragment : Fragment() 

    private lateinit var mMap: GoogleMap
    private var userLatitude     = 38.2500486
    private var userLongitude    = -85.7647484
    private lateinit var mapFragment : SupportMapFragment

    private var binding : MapViewFragmentBinding ?=null

    private fun zoomingLocation(): CameraUpdate 
        return CameraUpdateFactory.newLatLngZoom(LatLng(userLatitude, userLongitude), 14f)
    

    private fun configActivityMaps(googleMap: GoogleMap): GoogleMap 
        // set map type
        googleMap.mapType = GoogleMap.MAP_TYPE_NORMAL
        // Enable / Disable zooming controls
        googleMap.uiSettings.isZoomControlsEnabled = false

        // Enable / Disable Compass icon
        googleMap.uiSettings.isCompassEnabled = true
        // Enable / Disable Rotate gesture
        googleMap.uiSettings.isRotateGesturesEnabled = true
        // Enable / Disable zooming functionality
        googleMap.uiSettings.isZoomGesturesEnabled = true

        googleMap.uiSettings.isScrollGesturesEnabled = true
        googleMap.uiSettings.isMapToolbarEnabled = true

        return googleMap
    

    
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View 
        binding = MapViewFragmentBinding.inflate(inflater)

        mapFragment = (childFragmentManager.findFragmentById(R.map.id) as SupportMapFragment?)!!
        mapFragment.getMapAsync  googleMap ->
            mMap = configActivityMaps(googleMap)
            mMap.moveCamera(zoomingLocation())
        

        return binding!!.root
    

    override fun onDestroyView() 
        super.onDestroyView()
        
        mapFragment =null
        binding=null
    

........

Build.VERSION.SDK_INT:25

Build.MANUFACTURER:三星

LeakCanary 版本:2.7

统计:LruCache[maxSize=3000,hits=5750,misses=80700,hitRate=6%] RandomAccess[bytes=4089892,reads=80700,travel=60892211355,range=18101017,size=22 288421]

堆转储原因:用户请求

分析持续时间:29922 毫秒

试过:同样的问题

import androidx.fragment.app.Fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions

class MapsFragment : Fragment() 

    private val callback = OnMapReadyCallback  googleMap ->
        val sydney = LatLng(-34.0, 151.0)
        googleMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        googleMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
    

    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? 
        return inflater.inflate(R.layout.fragment_maps, container, false)
    

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)
        val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
        mapFragment?.getMapAsync(callback)
    


<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_
    android:layout_
    tools:context=".ui.pref.MapsFragment" />

【问题讨论】:

onDestroyView 尝试通过使字段可为空来分配mapFragment = null,并使用WeakReference 作为mMap 字段的类型 mapFragment = null(不工作),请分享我如何使用“WeakReference” 那就是:WeakReference&lt;GoogleMap&gt;,但我认为可空性不是问题。 mapsFragment.onDestroy() - 当我屏幕旋转时仍然泄漏复制 同样的问题,看起来谷歌已经确认这是一个错误。此问题已由 8 位开发人员加注星标,因此您可能需要对此问题发表评论,询问是否有人(包括 Google)有解决方法等待修复。 【参考方案1】:

首先,删除@SuppressLint("RestrictedApi") 并修复你隐藏在那里的内容。

然后尝试将private lateinit var mMap: GoogleMap 设为局部变量... 不需要保留,因为会得到OnMapReadyCallback 的实例。

也许最后尝试调用super.onDestroyView(),因为FrameLayout既不是层次结构的一部分,也不是附加的......而且附加计数是1,所以它一定是以前附加的。您很有可能尝试处置 super.onDestroyView() 已处置的内容,而整个覆盖可能毫无意义。

猜猜看这个android.widget.FrameLayout 到底是什么(后来的 API 提供了一个布局检查器 - 不过在 API 25 上,仍然可以将渲染的布局转储到文件中)。

【讨论】:

尝试了局部变量,删除了受限注释并删除了 onDestroyView - 仍然发生内存泄漏【参考方案2】:

在某些情况下,内存泄漏是不可避免的——例如,在这种情况下使用 GoogleMaps——GoogleMaps 片段总是会发生内存泄漏——这就是它的工作原理。不过,它比以前好多了——在 2013 年之前,Google Maps leaked 整个地图缓存都非常糟糕。

不管怎样,这是一个known 问题,但仍未修复。如果泄漏发生在 Google 提供的库中,我建议不要打扰。通常,它们都是已知的,如果它们是严重的 - 固定的或轻微的 - 很高兴被遗忘。

【讨论】:

【参考方案3】:

尝试使用MapView 而不是MapFragment/SupportMapFragment。 MapView 是 FrameLayout 的扩展,面向不仅是地图,而且其他控件需要显示在设备屏幕上的情况,并且是为现代平台设计的,并且可以以其他方式实现。

另外,如果您只需要为用户显示地图,您可以尝试LiteMode 或Static Maps API。

【讨论】:

以上是关于如何解决框架布局中的内存泄漏问题?的主要内容,如果未能解决你的问题,请参考以下文章

如何解决 SKDatabase 函数中的内存泄漏?

如何解决 xcode(仪器)中的 iPhone 应用程序内存泄漏

如何解决内存泄漏问题?

Android性能优化之利用Rxlifecycle解决RxJava内存泄漏

如何解决内存溢出以及内存泄漏

如何解决此内存泄漏?