Skip to content

Commit

Permalink
feat(android): clustering impl + fix android, ios memory leak?
Browse files Browse the repository at this point in the history
  • Loading branch information
mym0404 committed Apr 23, 2024
1 parent 02190a0 commit b232e12
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.graphics.PointF
import android.view.Gravity
import android.view.View
import com.airbnb.android.react.maps.SizeReportingShadowNode
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.uimanager.ReactStylesDiffMap
Expand All @@ -16,9 +17,11 @@ import com.mjstudio.reactnativenavermap.event.NaverMapInitializeEvent
import com.mjstudio.reactnativenavermap.event.NaverMapOptionChangeEvent
import com.mjstudio.reactnativenavermap.event.NaverMapTapEvent
import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapClusterKey
import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapClustererHolder
import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapLeafMarkerHolder
import com.mjstudio.reactnativenavermap.overlay.marker.cluster.RNCNaverMapLeafMarkerUpdater
import com.mjstudio.reactnativenavermap.util.CameraAnimationUtil
import com.mjstudio.reactnativenavermap.util.RectUtil
import com.mjstudio.reactnativenavermap.util.debugE
import com.mjstudio.reactnativenavermap.util.getDoubleOrNull
import com.mjstudio.reactnativenavermap.util.getLatLng
import com.mjstudio.reactnativenavermap.util.getLatLngBoundsOrNull
Expand Down Expand Up @@ -47,6 +50,8 @@ import com.naver.maps.map.NaverMap.MapType.Terrain
import com.naver.maps.map.NaverMapOptions
import com.naver.maps.map.clustering.Clusterer
import java.util.Locale
import kotlin.math.max
import kotlin.math.min

class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec<RNCNaverMapViewWrapper>() {
override fun getName(): String {
Expand All @@ -59,14 +64,17 @@ class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec<RNCNaverMapViewWrapper
private var isFirstCameraMoving = true
private var lastClustersPropKey = "NOT_SET"

private val clustererRecord = mutableMapOf<String, Clusterer<RNCNaverMapClusterKey>>()
private val clustererHolders = mutableMapOf<String, RNCNaverMapClustererHolder>()

private lateinit var reactAppContext: ReactApplicationContext

override fun createViewInstance(
reactTag: Int,
reactContext: ThemedReactContext,
initialProps: ReactStylesDiffMap?,
stateWrapper: StateWrapper?,
): RNCNaverMapViewWrapper {
reactAppContext = reactContext.reactApplicationContext
initialMapOptions =
NaverMapOptions().apply {
useTextureView(
Expand All @@ -86,9 +94,10 @@ class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec<RNCNaverMapViewWrapper
}

override fun onDropViewInstance(view: RNCNaverMapViewWrapper) {
super.onDropViewInstance(view)
view.onDropViewInstance()
view.reactContext.removeLifecycleEventListener(view)
clustererHolders.forEach { (_, u) -> u.onDetach() }
super.onDropViewInstance(view)
}

override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
Expand Down Expand Up @@ -519,10 +528,10 @@ class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec<RNCNaverMapViewWrapper
lastClustersPropKey = propKey

// remove all at now
clustererRecord.forEach { (_, clusterer) ->
clusterer.map = null
clustererHolders.forEach { (_, clusterer) ->
clusterer.onDetach()
}
clustererRecord.clear()
clustererHolders.clear()

value.getArray("clusters")?.toArrayList()?.filterIsInstance<Map<String, Any?>>()?.forEach {
val clustererKey = it["key"] as? String
Expand All @@ -532,62 +541,46 @@ class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec<RNCNaverMapViewWrapper
val animate = it["animate"] as? Boolean
val markers = (it["markers"] as? ArrayList<*>)?.filterIsInstance<Map<String, *>>() ?: listOf()

debugE(clustererKey, screenDistance, minZoom, maxZoom, animate, markers)

val clusterer =
Clusterer.Builder<RNCNaverMapClusterKey>().apply {
// if (minZoom != null) {
// this.minZoom(minZoom.toInt())
// }
// if (maxZoom != null) {
// this.maxZoom(maxZoom.toInt())
// }
// if (animate != null) {
// this.animate(animate)
// }
}.build()

val keys =
Clusterer.Builder<RNCNaverMapClusterKey>().also { cluster ->
if (screenDistance != null) {
cluster.screenDistance(screenDistance)
}
if (minZoom != null) {
cluster.minZoom(max(minZoom.toInt(), 1))
}
if (maxZoom != null) {
cluster.maxZoom(min(maxZoom.toInt(), 20))
}
if (animate != null) {
cluster.animate(animate)
}
}.leafMarkerUpdater(RNCNaverMapLeafMarkerUpdater()).build()

val keyPairs =
markers.associate { marker ->
val identifier = marker["identifier"] as String
val latitude = marker["latitude"] as Double
val longitude = marker["longitude"] as Double
val image = marker["image"] as? Map<*, *>

RNCNaverMapClusterKey(identifier, LatLng(latitude, longitude)) to null
RNCNaverMapClusterKey(
identifier,
LatLng(latitude, longitude),
image,
RNCNaverMapLeafMarkerHolder(identifier, reactAppContext),
) to null
}
debugE(keys)

clusterer.addAll(keys)
clusterer.addAll(keyPairs)
clusterer.map = map
clustererRecord[clustererKey!!] = clusterer

// val clusterer =
// Clusterer.Builder<RNCNaverMapClusterKey>()
// .build()
// .apply {
// val keyTagMap =
// buildMap(5000) {
// val south = MapConstants.EXTENT_KOREA.southLatitude
// val west = MapConstants.EXTENT_KOREA.westLongitude
// val height = MapConstants.EXTENT_KOREA.northLatitude - south
// val width = MapConstants.EXTENT_KOREA.eastLongitude - west
//
// repeat(5000) { i ->
// put(
// RNCNaverMapClusterKey(
// i.toString(),
// LatLng(height * Math.random() + south, width * Math.random() + west),
// ),
// null,
// )
// }
// }
//
// addAll(keyTagMap)
// this.map = map
// }
// clustererRecord[clustererKey!!] = clusterer
clustererHolders[clustererKey!!] =
RNCNaverMapClustererHolder(
clustererKey,
clusterer,
reactAppContext,
keyPairs.map { pair -> pair.key.holder },
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import android.view.View
import androidx.core.view.children
import com.airbnb.android.react.maps.TrackableView
import com.airbnb.android.react.maps.ViewChangesTracker
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder
import com.facebook.drawee.view.DraweeHolder
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.ThemedReactContext
import com.mjstudio.reactnativenavermap.event.NaverMapOverlayTapEvent
import com.mjstudio.reactnativenavermap.overlay.RNCNaverMapOverlay
import com.mjstudio.reactnativenavermap.util.ImageRequestCanceller
import com.mjstudio.reactnativenavermap.util.createDraweeHierarchy
import com.mjstudio.reactnativenavermap.util.emitEvent
import com.mjstudio.reactnativenavermap.util.getOverlayImage
import com.naver.maps.map.NaverMap
Expand All @@ -27,20 +26,17 @@ import kotlin.math.max
@SuppressLint("ViewConstructor")
class RNCNaverMapMarker(val reactContext: ThemedReactContext) :
RNCNaverMapOverlay<Marker>(reactContext), TrackableView {
private var imageHolder: DraweeHolder<GenericDraweeHierarchy>? = null
private val imageHolder: DraweeHolder<GenericDraweeHierarchy>? by lazy {
DraweeHolder.create(createDraweeHierarchy(resources), reactContext)?.apply {
onAttach()
}
}
private var customView: View? = null
private var customViewBitmap: Bitmap? = null
private var lastImage: ReadableMap? = null
private var imageRequestCanceller: ImageRequestCanceller? = null
private var isImageSetFromSubview = false

init {
imageHolder =
DraweeHolder.create(createDraweeHierarchy(), context)?.apply {
onAttach()
}
}

override fun onDetachedFromWindow() {
imageRequestCanceller?.invoke()
super.onDetachedFromWindow()
Expand Down Expand Up @@ -148,7 +144,7 @@ class RNCNaverMapMarker(val reactContext: ThemedReactContext) :
if (isImageSetFromSubview) return
imageRequestCanceller?.invoke()
imageRequestCanceller =
getOverlayImage(imageHolder!!, context, image) {
getOverlayImage(imageHolder!!, context, image?.toHashMap()) {
setOverlayImage(it)
}
}
Expand All @@ -157,11 +153,4 @@ class RNCNaverMapMarker(val reactContext: ThemedReactContext) :
overlay.icon =
image ?: OverlayImage.fromBitmap(Bitmap.createBitmap(0, 0, Bitmap.Config.ARGB_8888))
}

private fun createDraweeHierarchy(): GenericDraweeHierarchy {
return GenericDraweeHierarchyBuilder(resources)
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setFadeDuration(0)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,13 @@ package com.mjstudio.reactnativenavermap.overlay.marker.cluster
import com.naver.maps.geometry.LatLng
import com.naver.maps.map.clustering.ClusteringKey

data class RNCNaverMapClusterKey(val identifier: String, private val latlng: LatLng, val image: Map<*, *>? = null) : ClusteringKey {
internal data class RNCNaverMapClusterKey(
val identifier: String,
val latlng: LatLng,
val image: Map<*, *>? = null,
val holder: RNCNaverMapLeafMarkerHolder,
) : ClusteringKey {
override fun getPosition(): LatLng {
return latlng
}
}
//
// class RNCNaverMapClusterKey(val id: Int, private val position: LatLng) : ClusteringKey {
// override fun getPosition() = position
//
// override fun equals(other: Any?): Boolean {
// if (this === other) return true
// if (other == null || javaClass != other.javaClass) return false
// val itemKey = other as RNCNaverMapClusterKey
// return id == itemKey.id
// }
//
// override fun hashCode() = id
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.mjstudio.reactnativenavermap.overlay.marker.cluster

import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.view.DraweeHolder
import com.facebook.react.bridge.ReactApplicationContext
import com.mjstudio.reactnativenavermap.util.createDraweeHierarchy
import com.naver.maps.map.clustering.Clusterer

internal data class RNCNaverMapClustererHolder internal constructor(
val identifier: String,
val clusterer: Clusterer<RNCNaverMapClusterKey>,
val context: ReactApplicationContext,
val markers: List<RNCNaverMapLeafMarkerHolder>,
) {
private val imageHolder: DraweeHolder<GenericDraweeHierarchy> by lazy {
DraweeHolder.create(createDraweeHierarchy(context.resources), context).apply {
onAttach()
}
}

fun onDetach() {
markers.forEach { it.onDetach() }
clusterer.map = null
imageHolder.onDetach()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mjstudio.reactnativenavermap.overlay.marker.cluster

import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.view.DraweeHolder
import com.facebook.react.bridge.ReactApplicationContext
import com.mjstudio.reactnativenavermap.util.createDraweeHierarchy

internal data class RNCNaverMapLeafMarkerHolder(
val identifier: String,
val context: ReactApplicationContext,
) {
val imageHolder: DraweeHolder<GenericDraweeHierarchy> by lazy {
DraweeHolder.create(createDraweeHierarchy(context.resources), context).apply {
onAttach()
}
}

fun onDetach() {
imageHolder.onDetach()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mjstudio.reactnativenavermap.overlay.marker.cluster

import com.mjstudio.reactnativenavermap.util.ImageRequestCanceller
import com.mjstudio.reactnativenavermap.util.getOverlayImage
import com.naver.maps.map.clustering.DefaultLeafMarkerUpdater
import com.naver.maps.map.clustering.LeafMarkerInfo
import com.naver.maps.map.overlay.Marker
import com.naver.maps.map.util.MarkerIcons

internal class RNCNaverMapLeafMarkerUpdater : DefaultLeafMarkerUpdater() {
private var imageRequestCanceller: ImageRequestCanceller? = null

override fun updateLeafMarker(
info: LeafMarkerInfo,
marker: Marker,
) {
super.updateLeafMarker(info, marker)

imageRequestCanceller?.invoke()
(info.key as? RNCNaverMapClusterKey)?.let { (id, _, image, holder) ->
imageRequestCanceller =
getOverlayImage(holder.imageHolder, holder.context, image) {
marker.icon = it ?: MarkerIcons.GREEN
}
}
}
}
Loading

0 comments on commit b232e12

Please sign in to comment.