После многих попыток решить эту ошибку, я действительно из идей, что делать, я понимаю, из чего он исходит, из кеша не хватает памяти.
Итак, после прочтения какого-то решения, лучшим из них, действительно улучшающим производительность и обеспечивающим выживание приложения более 20 секунд, было следующее:
https://stackoverflow.com/a/36903036/9753681
У меня более 30 тыс. Кластеров на карте с текстом значка, что каждый кластер является представлением.
Так какой идентификатор я унаследовал IconGenerator
и создать свой собственный кэш с LruCache
,
и по границам камеры я удаляю сам кластер из ClusterManger
и само растровое изображение из кэша и сохраняю саму модель кластера только в другом кэше, чтобы обеспечить, если камера перемещается и кластер не находится в кэше точечные рисунки будут предоставлены кэшем модели удаления.
Кроме того, когда камера останавливается, я рисую кластер.
Но все равно он не работает, и все равно выдает ту же ошибку:
Could not allocate dup blob fd
Вот мой код:
CustomClusterMarker:
class CustomClusterMarker(private var context: Context,
private var map: GoogleMap,
private var clusterManager: ClusterManager<ClusterMarkerBase>,
clusters: List<ClusterMarkerBase>) :
DefaultClusterRenderer<ClusterMarkerBase>(context, map, clusterManager) {
private val TAG: String = CustomClusterMarker::class.simpleName.toString()
private var mIconGenerator: CachedIconGenerator
private var mClusterIconGenerator: CachedIconGenerator
private var bounds: LatLngBounds? = null
private var isNotMove = true
init {
mDimension = context.resources.getDimension(R.dimen.custom_profile_image).toInt()
mIconGenerator = CachedIconGenerator(context)
mClusterIconGenerator = CachedIconGenerator(context)
mIconGenerator.setBackground(ColorDrawable(Color.TRANSPARENT))
mClusterIconGenerator.setBackground(ColorDrawable(Color.TRANSPARENT))
}
override fun onBeforeClusterItemRendered(item: ClusterMarkerBase?, markerOptions: MarkerOptions?) {
//For single item
if (item == null && markerOptions == null)
return
// // Prepare View
val itemCluster = LayoutInflater.from(context).inflate(R.layout.view_marker, null)
val imgItem = itemCluster.findViewById<ImageView>(R.id.image)
val imgStatus = itemCluster.findViewById<ImageView>(R.id.status_icon)
val tvCount = itemCluster.findViewById<TextView>(R.id.txt_counter)
val txtDescription = itemCluster.findViewById<TextView>(R.id.description_marker)
imgItem.setImageResource(item!!.mIcon)
txtDescription.text = item.title
if (tvCount.visibility == View.VISIBLE)
tvCount.visibility = View.GONE
if (imgStatus.visibility == View.GONE)
imgStatus.visibility = View.VISIBLE
if (item.isTech) {
imgStatus.setImageResource(getIconOnCall(item.onCall, imgStatus))
} else {
imgStatus.setImageResource(getIconOpenCall(item.onCall, imgStatus))
}
//Start cleaning cache before set the icon
if (bounds != null) {
if (!bounds!!.contains(item.position)) {
Completable.fromAction({
mIconGenerator.removeIcon(item.mId)
mIconGenerator.saveBeforeRemoveCluster(item)
clusterManager.removeItem(item)
}).doOnError {
Log.e(TAG, "Show error single remove: " + it.toString())
}
}
}
if (bounds != null) {
//Adding the cluster that were delete depends by the bounds
mIconGenerator.getRemovesClusters(bounds!!)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
clusterManager.addItems(it)
}) {
Log.e(TAG, "Show add single error: " + it.toString())
}
}
if (isNotMove) {
//Convert view to Bitmap and set it on the map with the marker option
try {
mIconGenerator.setContentView(itemCluster)
val descriptorFactory = BitmapDescriptorFactory
.fromBitmap(mIconGenerator.makeIcon(item.mId))
markerOptions!!.icon(descriptorFactory)
} catch (e: Exception) {
Log.e(TAG, "Show single exeption: " + e.toString())
}
}
}
override fun onBeforeClusterRendered(cluster: Cluster<ClusterMarkerBase>?, markerOptions: MarkerOptions?) {
//Check for cluster and mark option is Null
//For group only!!!!
if (cluster == null || markerOptions == null)
return
// // Inflate View
val itemCluster = LayoutInflater.from(context).inflate(R.layout.view_marker, null)
val tvCount = itemCluster.findViewById<TextView>(R.id.txt_counter)
val imgItem = itemCluster.findViewById<ImageView>(R.id.image)
val imgStatus = itemCluster.findViewById<ImageView>(R.id.status_icon)
if (imgStatus.visibility == View.VISIBLE)
imgStatus.visibility = View.GONE
if (tvCount.visibility == View.GONE)
tvCount.visibility = View.VISIBLE
if (cluster.items.isNotEmpty())
imgItem.setImageResource(cluster.items.first().mIcon)
tvCount.text = (cluster.size - 1).toString()
//Convert view to Bitmap and set it on the map with the marker option
//Start cleaning cache before set the icon
if (bounds != null) {
if (!bounds!!.contains(cluster.position)) {
Completable.fromAction({
mClusterIconGenerator.removeIcon(cluster.toString())
mClusterIconGenerator.saveBeforeRemoveCluster(cluster.items)
for (item in cluster.items) {
clusterManager.removeItem(item)
}
}).doOnError {
Log.e(TAG, "Show group error remove: " + it.toString())
}
}
}
//Adding all clusters that were delete depends by the bounds
if (bounds != null) {
mClusterIconGenerator.getRemovesClusters(bounds!!)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
clusterManager.addItems(it)
}) {
Log.e(TAG, "Show group error add: " + it.toString())
}
}
if (isNotMove) {
try {
mClusterIconGenerator.setContentView(itemCluster)
val descriptorFactory = BitmapDescriptorFactory
.fromBitmap(mClusterIconGenerator.makeIcon(cluster.toString()))
markerOptions.icon(descriptorFactory)
} catch (e: Exception) {
Log.e(TAG,"Show gourp exeption: "+e.toString())
}
}
}
fun setBounds(bounds: LatLngBounds) {
this.bounds = bounds
}
fun setOnCameraState(isNotMove: Boolean) {
this.isNotMove = isNotMove
}
override fun shouldRenderAsCluster(cluster: Cluster<ClusterMarkerBase>?): Boolean {
return super.shouldRenderAsCluster(cluster)
}
private fun getIconOnCall(isOnCall: Boolean, image: ImageView): Int {
return if (isOnCall) {
image.visibility = View.GONE
0
} else {
image.visibility = View.VISIBLE
R.drawable.free
}
}
private fun getIconOpenCall(onCall: Boolean, image: ImageView): Int {
return if (onCall) {
image.visibility = View.VISIBLE
R.drawable.open_call
} else {
image.visibility = View.GONE
0
}
}
Мой собственный кеш:
CachedIconGenerator:
class CachedIconGenerator(context: Context) : IconGenerator(context) {
private val mBitmapsCache: LruCache<String, Bitmap>
private val mModelClusterCache: LruCache<String, ClusterMarkerBase>
private var bounds: LatLngBounds? = null
private val TAG = CachedIconGenerator::class.simpleName
init {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
// Use 1/8th of the available memory for this memory cache.
val cacheSize = maxMemory / 8
mBitmapsCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.byteCount / 1024
}
}
// val sizeModel = maxMemory
mModelClusterCache = LruCache(cacheSize)
}
fun saveBeforeRemoveCluster(cluster: ClusterMarkerBase) {
mModelClusterCache.put(cluster.mId, cluster)
}
fun saveBeforeRemoveCluster(clusters: Collection<ClusterMarkerBase>) {
for (item in clusters) {
mModelClusterCache.put(item.mId, item)
}
}
fun setBounds(bounds: LatLngBounds) {
this.bounds = bounds
}
fun removeIcon(id: String) {
if (mBitmapsCache[id] != null)
mBitmapsCache.remove(id)
}
fun isIconExsit(id: String): Boolean {
return mBitmapsCache[id] != null
}
fun makeIcon(id: String): Bitmap? {
return if (mBitmapsCache[id] == null) {
val bitmap = super.makeIcon()
mBitmapsCache.put(id, bitmap)
bitmap
} else
mBitmapsCache[id]
}
fun getRemovesClusters(bounds: LatLngBounds): Observable<ArrayList<ClusterMarkerBase>> {
val list = ArrayList<ClusterMarkerBase>()
for (item in mModelClusterCache.snapshot().values) {
if (bounds.contains(item.position))
list.add(item)
}
return Observable.just(list)
}
fun isCacheEmtpty(): Boolean {
return mBitmapsCache.size() == 0
}
}