Утечка Huawei MapView во фрагменте

У меня есть Bottom Navigation View с 4 вкладками, и одна из них содержит Huawei MapView. Когда я переключаюсь с вкладки A, содержащей MapView, на вкладку 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, но все равно вызываю утечку памяти

Утечка памяти Huawei MapView

Последний шаг он говорит;

MapFragment получил обратный вызов Fragment#onDestroyView() (ссылки на его представления должны быть очищены, чтобы предотвратить утечку)

Я очищаю ссылки на onDestroyView, но все равно получаю ту же ошибку. Как я могу предотвратить эту утечку памяти?


person ysfcyln    schedule 03.12.2020    source источник


Ответы (2)


Рекомендуется использовать MapFragment (расширяет собственный компонент Fragment Android и может использоваться для добавления карты в приложение самым простым способом) или SupportMapFragment вместо используя свой фрагмент для встраивания Huawei 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>

fragment_map.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'
}
person shirley    schedule 04.12.2020
comment
Замените MapView на SupportMapFragment, но эта утечка все еще существует. возможно ли быть связанным SDK? Потому что там написано com.huawei.hms.maps.SupportMapFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks) and View detached and has parent - person ysfcyln; 04.12.2020
comment
@ysfcyln Пожалуйста, попробуйте Map 5.0.5.301 сначала посмотреть, можно ли это исправить. - person shirley; 04.12.2020
comment
возможно, утечка связана с onMapReady обратным вызовом, потому что каждый раз, когда я меняю вкладку onMapReady, обратный вызов срабатывает несколько раз. Например, первый запуск onMapReady вызывается, когда все в порядке. Затем измените вкладку и снова откройте вкладку карты, на этот раз onMapReady вызывается два раза и даже больше, если я меняю вкладку и снова открываю вкладку карты, на этот раз вызывается три раза. Старые ссылки на слушателей все еще существуют - person ysfcyln; 04.12.2020
comment
Вы можете добавить кеш к фрагменту. Вы вводите фрагмент и загружаете карту только в первый раз, а в остальных не нужно ее подгружать и просто использовать кеш. Пожалуйста, проверьте код, который я обновил еще раз. :) @ysfcyln - person shirley; 07.12.2020
comment
Спасибо @shirley. Хранение глобального экземпляра фрагмента карты поддержки и проверка нуля перед getMapAsync() решением ошибки стека слушателя, но утечка все еще существует :'( - person ysfcyln; 09.12.2020
comment
Пожалуйста, проверьте переключатели и фрагмент. Я добавил свой в ответ. @ysfcyln - person shirley; 09.12.2020

Проблема может заключаться в том, что карта продолжает проверять местоположение после уничтожения. Попробуйте вставить hMap?.setMyLocationEnabled(false); в onDestroy(), чтобы он больше не отслеживал местоположение после уничтожения.

person Zinna    schedule 18.12.2020