Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit e0fbdd52 authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

Maps: Add manual reference counting / garbage collection for Bitmaps

Fixes memory leaks, especially visible with markers on React Native Map component
parent f4950c18
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ import com.mapbox.mapboxsdk.style.layers.PropertyFactory
import com.mapbox.mapboxsdk.style.layers.SymbolLayer
import com.mapbox.mapboxsdk.utils.ColorUtils

open class BitmapDescriptorImpl(private val id: String, internal val size: FloatArray) {
open class BitmapDescriptorImpl(val id: String, internal val size: FloatArray) {
    open fun applyTo(options: SymbolOptions, anchor: FloatArray, dpiFactor: Float): SymbolOptions {
        return options.withIconImage(id).withIconAnchor(ICON_ANCHOR_TOP_LEFT).withIconOffset(arrayOf(-anchor[0] * size[0] / dpiFactor, -anchor[1] * size[1] / dpiFactor))
    }
@@ -44,6 +44,14 @@ open class BitmapDescriptorImpl(private val id: String, internal val size: Float
            PropertyFactory.iconImage(id)
        )
    }

    protected fun finalize() {
        BitmapDescriptorFactoryImpl.disposeDescriptor(id)
    }

    override fun toString(): String {
        return "[BitmapDescriptor $id]"
    }
}

class ColorBitmapDescriptorImpl(id: String, size: FloatArray, val hue: Float) : BitmapDescriptorImpl(id, size) {
+76 −53
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() {
    private var mapResources: Resources? = null
    private val maps = hashSetOf<MapboxMap>()
    private val bitmaps = hashMapOf<String, Bitmap>()
    private val refCount = hashMapOf<String, Int>()

    fun initialize(mapResources: Resources?, resources: Resources?) {
        BitmapDescriptorFactoryImpl.mapResources = mapResources ?: resources
@@ -53,7 +54,6 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() {

    fun unregisterMap(map: MapboxMap?) {
        maps.remove(map)
        // TODO: cleanup bitmaps?
    }

    fun put(style: Style.Builder) {
@@ -68,17 +68,53 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() {
        bitmaps[id]?.let { floatArrayOf(it.width.toFloat(), it.height.toFloat()) }
            ?: floatArrayOf(0f, 0f)

    private fun registerBitmap(id: String, bitmapCreator: () -> Bitmap?) {
        val bitmap: Bitmap = synchronized(bitmaps) {
            if (bitmaps.contains(id)) return
    fun disposeDescriptor(id: String) {
        synchronized(refCount) {
            if (refCount.containsKey(id)) {
                val old = refCount[id]!!
                if (old > 1) {
                    refCount[id] = old - 1;
                    return
                }
            }
        }
        unregisterBitmap(id)
    }

    private fun unregisterBitmap(id: String) {
        synchronized(bitmaps) {
            if (!bitmaps.containsKey(id)) return
            bitmaps.remove(id)
        }

        for (map in maps) {
            map.getStyle {
                try {
                    runOnMainLooper {
                        it.removeImage(id)
                    }
                } catch (e: Exception) {
                    Log.w(TAG, e)
                }
            }
        }

        refCount.remove(id)
    }

    private fun registerBitmap(id: String, descriptorCreator: (id: String, size: FloatArray) -> BitmapDescriptorImpl = { id, size -> BitmapDescriptorImpl(id, size) }, bitmapCreator: () -> Bitmap?): IObjectWrapper {
        val bitmap: Bitmap? = synchronized(bitmaps) {
            if (bitmaps.contains(id)) return@synchronized null
            val bitmap = bitmapCreator()
            if (bitmap == null) {
                Log.w(TAG, "Failed to register bitmap $id, creator returned null")
                return
                return@synchronized null
            }
            bitmaps[id] = bitmap
            bitmap
        }

        if (bitmap != null) {
            for (map in maps) {
                map.getStyle {
                    runOnMainLooper {
@@ -88,9 +124,14 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() {
            }
        }

    override fun fromResource(resourceId: Int): IObjectWrapper? {
        val id = "resource-$resourceId"
        registerBitmap(id) {
        synchronized(refCount) {
            refCount[id] = (refCount[id] ?: 0) + 1
        }

        return ObjectWrapper.wrap(descriptorCreator(id, bitmapSize(id)))
    }

    override fun fromResource(resourceId: Int): IObjectWrapper = registerBitmap("resource-$resourceId") {
        val bitmap = BitmapFactory.decodeResource(resources, resourceId)
        if (bitmap == null) {
            try {
@@ -101,25 +142,17 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() {
        }
        bitmap
    }
        return ObjectWrapper.wrap(BitmapDescriptorImpl(id, bitmapSize(id)))
    }

    override fun fromAsset(assetName: String): IObjectWrapper? {
        val id = "asset-$assetName"
        registerBitmap(id) { resources?.assets?.open(assetName)?.let { BitmapFactory.decodeStream(it) } }
        return ObjectWrapper.wrap(BitmapDescriptorImpl(id, bitmapSize(id)))
    override fun fromAsset(assetName: String): IObjectWrapper = registerBitmap("asset-$assetName") {
        resources?.assets?.open(assetName)?.let { BitmapFactory.decodeStream(it) }
    }

    override fun fromFile(fileName: String): IObjectWrapper? {
        val id = "file-$fileName"
        registerBitmap(id) { BitmapFactory.decodeFile(fileName) }
        return ObjectWrapper.wrap(BitmapDescriptorImpl(id, bitmapSize(id)))
    override fun fromFile(fileName: String): IObjectWrapper = registerBitmap("file-$fileName") {
        BitmapFactory.decodeFile(fileName)
    }

    override fun defaultMarker(): IObjectWrapper? {
        val id = "marker"
        registerBitmap(id) { BitmapFactory.decodeResource(mapResources, R.drawable.maps_default_marker) }
        return ObjectWrapper.wrap(BitmapDescriptorImpl(id, bitmapSize(id)))
    override fun defaultMarker(): IObjectWrapper = registerBitmap("marker") {
        BitmapFactory.decodeResource(mapResources, R.drawable.maps_default_marker)
    }

    private fun adjustHue(cm: ColorMatrix, value: Float) {
@@ -142,8 +175,7 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() {
    }

    override fun defaultMarkerWithHue(hue: Float): IObjectWrapper? {
        val id = "marker-${hue.toInt()}"
        registerBitmap(id) {
        return registerBitmap("marker-${hue.toInt()}", { id, size -> ColorBitmapDescriptorImpl(id, size, hue) }) {
            val bitmap = BitmapFactory.decodeResource(mapResources, R.drawable.maps_default_marker).copy(Bitmap.Config.ARGB_8888, true)
            val paint = Paint()
            val matrix = ColorMatrix()
@@ -155,20 +187,11 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() {
            canvas.drawBitmap(bitmap, 0f, 0f, paint)
            bitmap
        }
        return ObjectWrapper.wrap(ColorBitmapDescriptorImpl(id, bitmapSize(id), hue))
    }

    override fun fromBitmap(bitmap: Bitmap): IObjectWrapper? {
        val id = "bitmap-${bitmap.hashCode()}"
        registerBitmap(id) { bitmap }
        return ObjectWrapper.wrap(BitmapDescriptorImpl(id, bitmapSize(id)))
    }
    override fun fromBitmap(bitmap: Bitmap): IObjectWrapper = registerBitmap("bitmap-${bitmap.hashCode()}") { bitmap }

    override fun fromPath(absolutePath: String): IObjectWrapper? {
        val id = "path-$absolutePath"
        registerBitmap(id) { BitmapFactory.decodeFile(absolutePath) }
        return ObjectWrapper.wrap(BitmapDescriptorImpl(id, bitmapSize(id)))
    }
    override fun fromPath(absolutePath: String): IObjectWrapper = registerBitmap("path-$absolutePath") { BitmapFactory.decodeFile(absolutePath) }

    override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean =
        if (super.onTransact(code, data, reply, flags)) {