1

我有Bottom Navigation View4 个选项卡,其中一个包含Huawei MapView. 当我从包含 MapView 的选项卡 A 切换到选项卡 B 时,Leak Canary显示与相关的内存泄漏MapView

Fragment仅包含MapView,没有任何额外的视图

class MapFragment : BaseFragment(R.layout.fragment_map), OnMapReadyCallback  {

    private var hMap: HuaweiMap? = null

    companion object {
        private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey"
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initHuaweiMap(savedInstanceState)
    }


    private fun initHuaweiMap(savedInstanceState: Bundle?) {
        var mapViewBundle: Bundle? = null
        if (savedInstanceState != null) {
            mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY)
        }
        map_view?.apply {
            onCreate(mapViewBundle)
            getMapAsync(this@MapFragment)
        }
    }

    override fun onMapReady(map: HuaweiMap?) {
        hMap = map
        hMap?.setMapStyle(MapStyleOptions.loadRawResourceStyle(requireContext(), R.raw.mapstyle_night_hms))
        hMap?.isMyLocationEnabled = false // Enable the my-location overlay.
        hMap?.uiSettings?.isMyLocationButtonEnabled = false // Enable the my-location icon.
        hMap?.uiSettings?.isZoomControlsEnabled = false // Disable zoom-in zoom-out buttons
    }


    override fun onStart() {
        map_view?.onStart()
        super.onStart()
    }

    override fun onStop() {
        map_view?.onStop()
        super.onStop()
    }

    override fun onDestroy() {
        map_view?.onDestroy()
        super.onDestroy()
    }

    override fun onPause() {
        map_view?.onPause()
        super.onPause()
    }

    override fun onResume() {
        map_view?.onResume()
        super.onResume()
    }

    override fun onLowMemory() {
        map_view?.onLowMemory()
        super.onLowMemory()
    }

    override fun onDestroyView() {
        hMap?.clear()
        hMap = null
        map_view?.onDestroy()
        super.onDestroyView()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        var mapViewBundle = outState.getBundle(MAPVIEW_BUNDLE_KEY)
        if (mapViewBundle == null) {
            mapViewBundle = Bundle()
            outState.putBundle(MAPVIEW_BUNDLE_KEY, mapViewBundle)
        }
        map_view?.onSaveInstanceState(mapViewBundle)
    }
}

我的布局文件

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ui.tabs.profile.Mapragment">

    <com.huawei.hms.maps.MapView
        android:id="@+id/map_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:cameraZoom="14"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

我把所有与 MapView 相关的生命周期方法,但仍然导致内存泄漏

华为 MapView 内存泄漏

它说的最后一步;

MapFragment 收到 Fragment#onDestroyView() 回调(应清除对其视图的引用以防止泄漏)

我清除了参考,onDestroyView但仍然得到同样的错误。如何防止这种内存泄漏?

4

2 回答 2

0

问题可能是地图在被破坏后仍在继续检查位置。尝试插入hMap?.setMyLocationEnabled(false) ; 进入onDestroy()以强制它在被销毁后不再跟踪位置。

于 2020-12-18T02:12:19.730 回答
0

建议您使用MapFragment(扩展 Android 原生 Fragment 组件,可用于以最简单的方式将地图添加到应用程序)或 SupportMapFragment,而不是使用您的 Fragment 嵌入华为 MapView。


更新: 请检查以下代码:

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {//1
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
    }
}
package com.huawei.googlemaptankillereproduce

import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction

internal class FragmentController(fragmentManager: FragmentManager, containerId: Int, savedInstanceState: Bundle?) {
    companion object {
        private const val TAG = "FragmentController"

        private const val TAB_COUNT = 3
        private const val TAG_FRAGMENT_1 = "Fragment1"
        private const val TAG_FRAGMENT_2 = "Fragment2"
        private const val TAG_FRAGMENT_3 = "Fragment3"
        private const val STATE_ACTIVE_TAB = "StateActiveTab"

        const val TAB_1 = 0
        const val TAB_2 = 1
        const val TAB_3 = 2
    }

    private val mFragmentManager = fragmentManager
    private val mContainerId = containerId
    private var mSelectedTab = -1

    init {
        if (savedInstanceState != null) {
            mSelectedTab = savedInstanceState.getInt(STATE_ACTIVE_TAB)
        }
        else {
            activateTab(TAB_2)
        }
    }

    fun onSaveInstanceState(outState: Bundle) {
        outState.putInt(STATE_ACTIVE_TAB, mSelectedTab)
    }

    fun activateTab(tab: Int) {
        if (tab != mSelectedTab) {
            val fragmentTransaction = mFragmentManager.beginTransaction()

            if (mSelectedTab != -1) {
                hideFragment(mSelectedTab, fragmentTransaction)
            }

            mSelectedTab = tab
            showFragment(tab, fragmentTransaction)

            fragmentTransaction.commitAllowingStateLoss()
        }
    }

    private fun hideFragment(view: Int, ft: FragmentTransaction) {
        val fragment = getExistingFragment(view)
        if (fragment != null) {
            ft.detach(fragment)
        }
    }

    private fun showFragment(tab: Int, ft: FragmentTransaction) {
        val fragment = getExistingFragment(tab)
        if (fragment != null) {
            ft.attach(fragment)
        }
        else {
            ft.add(mContainerId, getNewFragment(tab)!!, getTag(tab))
        }
    }

    private fun getExistingFragment(view: Int): Fragment? {
        return mFragmentManager.findFragmentByTag(getTag(view))
    }

    private fun getNewFragment(tab: Int): Fragment? {
        return when (tab) {
            TAB_1 -> MapFragment()
            TAB_2 -> MapFragment()
            TAB_3 -> MapFragment()
            else -> null
        }
    }

    private fun getTag(tab: Int): String {
        return when (tab) {
            TAB_1 -> TAG_FRAGMENT_1
            TAB_2 -> TAG_FRAGMENT_2
            TAB_3 -> TAG_FRAGMENT_3
            else -> ""
        }
    }
}
package com.huawei.googlemaptankillereproduce

import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomnavigation.BottomNavigationView

class MainActivity : AppCompatActivity() {

    private lateinit var mFragmentController: FragmentController

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

        mFragmentController = FragmentController(supportFragmentManager, R.id.fragment_container, savedInstanceState)

        val bottomBar = findViewById<BottomNavigationView>(R.id.bottom_navigation)
        bottomBar.selectedItemId = R.id.bottom_bar_2
        bottomBar.setOnNavigationItemSelectedListener { item: MenuItem ->
            when (item.itemId) {
                R.id.bottom_bar_1 -> mFragmentController.activateTab(FragmentController.TAB_1)
                R.id.bottom_bar_2 -> mFragmentController.activateTab(FragmentController.TAB_2)
                R.id.bottom_bar_3 -> mFragmentController.activateTab(FragmentController.TAB_3)
            }
            true
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        mFragmentController.onSaveInstanceState(outState)
    }

}
package com.huawei.googlemaptankillereproduce

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.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.huawei.hms.maps.HuaweiMap
import com.huawei.hms.maps.SupportMapFragment

class MapFragment : Fragment(),  com.huawei.hms.maps.OnMapReadyCallback {

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

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

    }


    override fun onMapReady(p0: HuaweiMap?) {
//        TODO("Not yet implemented")
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:contentInsetLeft="0dp"
            app:contentInsetStart="0dp" />

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="0dp"
        android:paddingTop="0dp"
        android:paddingRight="0dp"
        android:paddingBottom="56dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:layout_gravity="bottom"
        android:background="@android:color/white"
        app:menu="@menu/bottombar_menu" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

片段映射.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView

        android:id="@+id/map"
        android:name="com.huawei.hms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>
dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation "androidx.fragment:fragment:1.2.5"

    implementation 'com.huawei.hms:maps:5.0.5.301'

    implementation 'com.squareup.leakcanary:leakcanary-android:1.5'
}
于 2020-12-04T03:12:05.903 回答