Kotlin 享元设计模式详解 和对象池及在内存优化中的几种案例和应用场景

一、前言

在内存优化中,享元模式和对象池均通过复用对象减少内存消耗,但核心机制和应用场景存在差异:

  1. 享元模式 ‌ 通过将对象拆分为‌:可共享的内部状态 (如字体、颜色等)和‌ 不可共享的外部状态(如坐标、内容),仅保留内部状态并允许外部传入变动信息。
  2. 对象池: 通过维护一组可复用的对象实例,当客户端请求对象时,优先从池中分配已创建的实例.

在Android中用到享元设计模式或者对象池的有:

  1. Handler 消息机制通过 Message.obtain() 方法复用消息对象,避免频繁创建和销毁。 Message 实例,减少内存消耗。
  2. 在加载位图时,通过 BitmapFactory.Options.inBitmap 复用已有 Bitmap 内存,减少内存占用和垃圾回收压力
  3. Glide框架通过 BitmapPool 管理已释放的 Bitmap 内存,实现对象复用
  4. RecyclerView 通过 ViewHolder 缓存视图节点,减少布局时的对象创建。
  5. StringBuilderString 类通过共享内部字符数组实现字符串拼接的优化。
  6. 线程池通过共享线程实现任务调度,减少线程创建成本

二、享元设计模式详解:

  1. 享元设计模式核心思想 :通过分离内部状态(可共享)和外部状态(不可共享)来优化内存使用。内部状态存储在享元对象内部(如颜色、纹理),外部状态由客户端维护(如坐标、尺寸)。可以简单理解: 通用的共享相同的属性,不通用的差异的从外部传入进来,主要减少通用的部分多次占用内存。
  2. 典型结构:
    2.1)抽象享元接口 ‌:定义操作接口,通常包含接收外部不能共享参数的方法:
    2.2)具体享元类 ‌:实现共享部分 2.3‌)享元工厂 ‌:管理对象池(通常用HashMap缓存实例)
    2.4)Kotlin 具体代码实现:如下,Color作为内部状态共享, 坐标x,y作为外部状态由调用方传入,因为每次都不一样。通过工厂mutableMapOfgetOrPut实现对象复用。
kotlin 复制代码
interface WXShape { fun draw(x: Int, y: Int) }

class WXCircle(private val color: String) : WXShape {
    override fun draw(x: Int, y: Int) {
        println("绘制$color圆形 at ($x,$y)") 
    }
}

object WXShapeFactory {
    private val cache = mutableMapOf<String, Shape>()
    fun getWXCircle(color: String): WXShape = cache.getOrPut(color) { WXCircle(color) }
}

三、享元设计模式和对象池的比较和联系

对象池享元设计模式的关系主要体现在两者均通过对象复用减少内存消耗,但实现方式存在差异:

  1. 对象池‌: 通过预先创建一组可复用对象,避免频繁创建销毁对象,适用于资源受限场景(如CPU/内存不足)。其核心在于"空间换时间",通过对象池管理对象生命周期,减少内存分配和回收成本,避免频繁GC导致内存抖动
  2. 享元模式‌:侧重于共享细粒度对象(如:文本编辑器中的字符、游戏中的粒子),通过共享内部状态实现高效复用。其核心在于"对象共享",通过享元工厂管理共享对象,客户端通过传递外部状态(如文本内容、粒子属性)实现差异化使用
  3. 他们的相似点在于:两者均通过复用减少内存消耗,但享元模式更强调内部状态共享,对象池更注重对象生命周期管理。简单地说:例如:在文本编辑场景中,若字符属性(如字体、字号)可共享,则适合用享元模式,若需管理大量临时字符(如光标位置),则更适合对象池
  4. 对比表格:
特性 享元模式 对象池
复用目标 不可变对象 可重用资源(如线程、连接)
状态管理 强制分离内外状态 通常整体复用对象
典型应用 图形渲染、文本处理 线程池、数据库连接池

四、享元内存优化典型案例

  1. 案例一、游戏草坪渲染优化

    问题:1万个草块对象占用1GB内存(每个含100KB纹理)
    解决方案‌:如下:草块位置,外部状态不共享,草块矢量属性vector:外部状态也不共享,草块纹理texture:属性,内部状态,1万个草块对象纹理,不可能1万个不一样的,去重后可能就10来种纹理,可共享。 data class GrassBlock( val position: Pair<Float,Float>, // 外部状态 val vector: Triple<Float,Float,Float>,// 外部状态 val texture: Texture // 内部状态(共享) ) 通过共享10种纹理对象,内存降至10MB级别左右

  2. 案例二、Android消息气泡复用
    非享元实现 ‌:每次创建都加载资源(高开销)

    享元实现优化‌:把加载资源弄成内部共享

    kotlin 复制代码
    class BubbleView(private val type: MessageType) {
        private val background = ResourceLoader.loadBackground(type) // 共享
        fun display(content: String) { 
            /* 使用外部状态content */ 
        }
    }

    相同类型气泡共享样式资源

  3. 案例3:线程池对象池化
    实现方式 ‌:通过ConcurrentHashMap缓存线程池实例,向东配置的线程池让缓存共享起来。 private val executors = ConcurrentHashMap<String, ThreadPoolExecutor>()

    kotlin 复制代码
    fun getExecutor(config: String): ThreadPoolExecutor = executors.getOrPut(config) { 
        ThreadPoolExecutor(...) 
    }
  4. 案例4:图片资源管理器
    优化点‌:相同URL图片只加载一次,核心思想代码如下:

    kotlin 复制代码
      class ImageLoader {
        private val cache = mutableMapOf<String, Bitmap>()
        fun load(url: String): Bitmap = cache.getOrPut(url) { decodeBitmap(url) }
    }

五、对象池详解:

  1. 核心逻辑原理 :通过预创建对象并缓存,将对象生命周期从"创建-销毁"转变为"借出-归还",减少GC频率和内存抖动
    借出 :从池中获取可用对象。
    归还:重置对象状态后放回池中。
  2. 线程安全设计 :使用ArrayBlockingQueuesynchronized保证多线程环境下的原子操作。
  3. Android中典型的对象池使用有哪些?
    3.1)图片加载框架 ‌ Glide通过Pools复用EngineJob等对象,避免频繁创建/销毁导致的内存抖动。
    3.2)数据库连接管理
    复用SQLiteDatabase连接降低打开/关闭开销。
    3.3)View复用‌ RecyclerView的ViewHolder机制本质是对象池的变体实现
  4. 与享元模式对比
特性 对象池 享元模式
目标 减少对象创建开销 共享不可变状态
适用场景 数据库连接/线程等 纹理/字体等资源
状态管理 整体复用 分离内外状态

六、Kotlin 通过对象池优化内存使用案例

  1. Android内置工具类对象池
kotlin 复制代码
private val pool = SynchronizedPool<Bitmap>(10) // 容量10的线程安全池
fun getBitmap(): Bitmap = pool.acquire() ?: createNewBitmap()
fun recycleBitmap(bitmap: Bitmap) {
    bitmap.recycle()
    pool.release(bitmap)
}
  1. 网络请求管理‌ 复用OkHttp的Call对象,避免重复创建连接
kotlin 复制代码
object RequestPool {
    private val pool = ArrayBlockingQueue<OkHttpCall>(5)
    fun execute(request: Request): Response {
        val call = pool.poll() ?: OkHttpClient().newCall(request)
        return call.execute().also { pool.put(call) }
    }
}
  1. 自定义实现对象池,优化内存案例:比如一台Android 收银机,在超市频繁使用一整天,结账时,加入购物车商品,商品本身带有,价格,名称等很多参数,这个购物车的商品对象需要创建,里面的参数是从数据库查询或者缓存拿到赋值给到它的,一直不停的创建很多商品对象,直到结算用完它,再重复下一单的购物车里面创建,结算。在内存达到GC临界值时候,就会频繁GC,然后导致内存抖动,一旦产生内存抖动,UI界面随之而来的卡顿,ANR都会随机发生。

我们要通过对象池对它进行优化:

  1. 通过商品对象池控制商品的创建:整体对象池代码如下
  • 定义通用创建对象
kotlin 复制代码
interface Creator<T> {
    fun create(): T
}
  • 实现创建商品对象
kotlin 复制代码
class ProductBeanCreator : Creator<ProductBean> {
    override fun create() = ProductBean()
}
  • 完全自定义通用对象池,可配置对象池的数量最大值:maxSize,如果首次需要,池子当前对象数量mPoolSize:为0,就新建,一直新建,当用完结束了,需要释放,释放一个,就存入池子内部,直到池子内部存到maxSize个,下一次需要用对象,调用obtain(),就从池子里面拿去,相当与 ,当把里面借完了就新建,用完后再释放,相当于 。当UI界面生命周期结束后 需要调用 池子的 recycle() 回收,完整代码如下,这样只要在收银这个界面,池子里面就一直有假设10个商品对象,这10个商品对象一直占着内存,绝大多数情况下满足每一单的使用,如果偶尔一单超过10个商品,会再次创建,超出10个用完就会释放,池子里面的10个不会释放,如果不这样做,每次都创建,虽然是在栈中创建的对象,但分配到内存堆中,栈结束,堆内存就释放,深入理解Android 内存分配和GC机制便会明白, 栈结束了,断了堆里面和栈的关系,堆里面的对象进入待检测GC时间段,时间一久,当堆积到GC临界点时候,这时候可能里面对象不止10个,可能商品对象都几十个或者上百个,千个,就会GC,就会存在这边在使用一边在创建,GC那边在回收,这两个行为就一直持续着,那么UI卡顿,ANR一系列问题就来了。
kotlin 复制代码
class ObjectPools<T> constructor(private val maxSize: Int, private var creator: Creator<T>) {
    private var mLock: Any = Any()
    private var mPoolSize = 0

    private var pool: Array<Any?> = arrayOfNulls<Any?>(maxSize)

    fun obtain(): T {
        synchronized(mLock) {
            return if (mPoolSize > 0) {
                val lastPooledIndex = mPoolSize - 1
                if (lastPooledIndex < pool.size) {
                    val instance = pool[lastPooledIndex] as T
                    pool[lastPooledIndex] = null
                    if (instance != null) {
                        mPoolSize--
                        WLog.e(this, "拿到复用的: ${instance!!::class.simpleName}")
                        instance
                    } else {
                        create()
                    }
                } else {
                    create()
                }
            } else {
                create()
            }
        }
    }

    private fun create(): T {
        val obj = creator.create()
        WLog.e(this, "新建: create ${obj!!::class.java.simpleName}")
        return obj
    }

    fun release(instance: T): Boolean {
        synchronized(mLock) {
            if (isInPool(instance)) {
                return true
            }
            if (mPoolSize < maxSize) {
                pool[mPoolSize] = instance
                mPoolSize++
                return true
            }
            return false
        }
    }

    private fun isInPool(instance: T): Boolean {
        for (i in 0 until mPoolSize) {
            if (pool[i] === instance) {
                return true
            }
        }
        return false
    }

    fun recycle() {
        synchronized(mLock) {
            pool.takeIf { it.isNotEmpty() }?.fill(null)
        }
    }
}
  • 商品对象使用的地方, b.recycle() 方法具体内容是对象里面所有属性,回归初始化默认值,如果属性是对象,就置为 null
scss 复制代码
//商品池子大小设置为10,
val productPools by lazy { ObjectPools(10, ProductBeanCreator()) }

//创建对象的复方调用  相当于 借
productPools.obtain()
//释放商品对象  相当于 还,不还,下次只要超过池子最大值时候,就会一直新创建
productPools.release(b.recycle())

七、总结

本文重点介绍了Kotlin 享元设计模式详解 和对象池 在内存优化中的几种案例和应用场景 包括:

  1. 享元设计模式详解
  2. 享元设计模式和对象池的比较和联系
  3. 享元内存优化典型案例
  4. 对象池详解
  5. Kotlin 通过对象池优化内存使用案例

感谢阅读:

欢迎用你发财的小手 关注,点赞、收藏

这里你会学到不一样的东西

相关推荐
城东米粉儿7 分钟前
android StrictMode 笔记
android
Zender Han10 分钟前
Flutter Android 启动页 & App 图标替换(不使用任何插件的完整实践)
android·flutter·ios
去哪儿技术沙龙12 分钟前
Qunar酒店搜索排序模型的演进
前端·架构·操作系统
童无极22 分钟前
Android 弹幕君APP开发实战01
android
赛恩斯37 分钟前
kotlin 为什么可以在没有kotlin 环境的安卓系统上运行的
android·开发语言·kotlin
于山巅相见39 分钟前
【3588】Android动态隐藏导航栏
android·导航栏·状态栏·android11
乡野码圣40 分钟前
【RK3588 Android12】开发效率提升技巧
android·嵌入式硬件
eybk1 小时前
Beeware生成安卓apk取得系统tts语音朗读例子
android
zhangphil2 小时前
Android图像显示,CPU的Skia与GPU的Vulkan高性能渲染系统
android