
一、前言
在内存优化中,享元模式和对象池均通过复用对象减少内存消耗,但核心机制和应用场景存在差异:
- 享元模式 通过将对象拆分为:
可共享的内部状态
(如字体、颜色等)和不可共享的外部状态
(如坐标、内容),仅保留内部状态并允许外部传入变动信息。 -
对象池:
通过维护一组可复用的对象实例,当客户端请求对象时,优先从池中分配已创建的实例.
在Android中用到享元设计模式或者对象池的有:
Handler
消息机制通过Message.obtain()
方法复用消息对象,避免频繁创建和销毁。Message
实例,减少内存消耗。- 在加载位图时,通过
BitmapFactory.Options.inBitmap
复用已有Bitmap
内存,减少内存占用和垃圾回收压力 - Glide框架通过
BitmapPool
管理已释放的Bitmap
内存,实现对象复用 RecyclerView
通过ViewHolder
缓存视图节点,减少布局时的对象创建。StringBuilder
和String
类通过共享内部字符数组实现字符串拼接的优化。- 线程池通过共享线程实现任务调度,减少线程创建成本
二、享元设计模式详解:
- 享元设计模式核心思想 :通过分离内部状态(可共享)和外部状态(不可共享)来优化内存使用。内部状态存储在享元对象内部(如颜色、纹理),外部状态由客户端维护(如坐标、尺寸)。
可以简单理解:
通用的共享相同的属性,不通用的差异的从外部传入进来,主要减少通用的部分多次占用内存。 - 典型结构:
2.1)抽象享元接口 :定义操作接口,通常包含接收外部不能共享参数的方法:
2.2)具体享元类 :实现共享部分 2.3)享元工厂 :管理对象池(通常用HashMap
缓存实例)
2.4)Kotlin 具体代码实现:如下,Color
作为内部状态共享, 坐标x,y
作为外部状态由调用方传入,因为每次都不一样。通过工厂mutableMapOf
,getOrPut
实现对象复用。
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) }
}
三、享元设计模式和对象池的比较和联系
对象池
和享元设计模式
的关系主要体现在两者均通过对象复用减少内存消耗,但实现方式存在差异:
- 对象池: 通过预先创建一组可复用对象,避免频繁创建销毁对象,适用于资源受限场景(如CPU/内存不足)。其核心在于"空间换时间",通过对象池管理对象生命周期,减少内存分配和回收成本,避免频繁GC导致内存抖动
- 享元模式:侧重于共享细粒度对象(如:文本编辑器中的字符、游戏中的粒子),通过共享内部状态实现高效复用。其核心在于"对象共享",通过享元工厂管理共享对象,客户端通过传递外部状态(如文本内容、粒子属性)实现差异化使用
- 他们的相似点在于:两者均通过复用减少内存消耗,但享元模式更强调内部状态共享,对象池更注重对象生命周期管理。简单地说:例如:在文本编辑场景中,若字符属性(如字体、字号)可共享,则适合用享元模式,若需管理大量临时字符(如光标位置),则更适合对象池
- 对比表格:
特性 | 享元模式 | 对象池 |
---|---|---|
复用目标 | 不可变对象 | 可重用资源(如线程、连接) |
状态管理 | 强制分离内外状态 | 通常整体复用对象 |
典型应用 | 图形渲染、文本处理 | 线程池、数据库连接池 |
四、享元内存优化典型案例
-
案例一、游戏草坪渲染优化 :
问题: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级别左右 -
案例二、Android消息气泡复用
非享元实现 :每次创建都加载资源(高开销)享元实现优化:把加载资源弄成内部共享
kotlinclass BubbleView(private val type: MessageType) { private val background = ResourceLoader.loadBackground(type) // 共享 fun display(content: String) { /* 使用外部状态content */ } }
相同类型气泡共享样式资源
-
案例3:线程池对象池化
实现方式 :通过ConcurrentHashMap
缓存线程池实例,向东配置的线程池让缓存共享起来。 private val executors = ConcurrentHashMap<String, ThreadPoolExecutor>()kotlinfun getExecutor(config: String): ThreadPoolExecutor = executors.getOrPut(config) { ThreadPoolExecutor(...) }
-
案例4:图片资源管理器
优化点:相同URL图片只加载一次,核心思想代码如下:kotlinclass ImageLoader { private val cache = mutableMapOf<String, Bitmap>() fun load(url: String): Bitmap = cache.getOrPut(url) { decodeBitmap(url) } }
五、对象池详解:
- 核心逻辑原理 :通过预创建对象并缓存,将对象生命周期从"创建-销毁"转变为"借出-归还",减少GC频率和内存抖动
借出
:从池中获取可用对象。
归还
:重置对象状态后放回池中。 - 线程安全设计 :使用
ArrayBlockingQueue
或synchronized
保证多线程环境下的原子操作。 - Android中典型的对象池使用有哪些?
3.1)图片加载框架 Glide通过Pools
复用EngineJob
等对象,避免频繁创建/销毁导致的内存抖动。
3.2)数据库连接管理
复用SQLiteDatabase
连接降低打开/关闭开销。
3.3)View复用 RecyclerView的ViewHolder机制本质是对象池的变体实现 - 与享元模式对比:
特性 | 对象池 | 享元模式 |
---|---|---|
目标 | 减少对象创建开销 | 共享不可变状态 |
适用场景 | 数据库连接/线程等 | 纹理/字体等资源 |
状态管理 | 整体复用 | 分离内外状态 |
六、Kotlin 通过对象池优化内存使用案例
- Android内置工具类对象池
kotlin
private val pool = SynchronizedPool<Bitmap>(10) // 容量10的线程安全池
fun getBitmap(): Bitmap = pool.acquire() ?: createNewBitmap()
fun recycleBitmap(bitmap: Bitmap) {
bitmap.recycle()
pool.release(bitmap)
}
- 网络请求管理 复用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) }
}
}
- 自定义实现对象池,优化内存案例:比如一台Android 收银机,在超市频繁使用一整天,结账时,加入购物车商品,商品本身带有,价格,名称等很多参数,这个购物车的商品对象需要创建,里面的参数是从数据库查询或者缓存拿到赋值给到它的,一直不停的创建很多商品对象,直到结算用完它,再重复下一单的购物车里面创建,结算。在内存达到GC临界值时候,就会频繁GC,然后导致内存抖动,一旦产生内存抖动,UI界面随之而来的卡顿,ANR都会随机发生。
我们要通过对象池对它进行优化:
- 通过商品对象池控制商品的创建:整体对象池代码如下:
- 定义通用创建对象
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 享元设计模式详解 和对象池 在内存优化中的几种案例和应用场景 包括:
- 享元设计模式详解
- 享元设计模式和对象池的比较和联系
- 享元内存优化典型案例
- 对象池详解
- Kotlin 通过对象池优化内存使用案例