11package com.github.donglua.fastinflater
22
3+ import android.app.Activity
34import android.content.Context
5+ import android.content.ContextWrapper
46import android.os.Handler
57import android.os.Looper
68import android.util.Xml
@@ -31,10 +33,9 @@ class ViewPool {
3133 }
3234
3335 /* *
34- * Pool key 用 Long 表示。
35- * - 默认(factoryIsolation = false):key = layoutId(直接放低 32 位),不访问 Context
36- * - 开启 factory 隔离时:高 32 位 layoutId,低 32 位 factory hash
37- * 用 Long 而非 data class 是为了避免每次 obtain/recycle 都分配对象。
36+ * Pool key 用 Long 表示,避免每次 obtain/recycle 分配对象。
37+ * - 高 32 位:layoutId
38+ * - 低 32 位:隔离 hash(hostHash xor factoryHash,两者都关时为 0)
3839 */
3940 private val pool = ConcurrentHashMap <Long , ConcurrentLinkedDeque <View >>()
4041 private val policies = ConcurrentHashMap <Int , ViewRecyclePolicy >()
@@ -59,6 +60,20 @@ class ViewPool {
5960 @Volatile
6061 private var factoryIsolation = false
6162
63+ /* *
64+ * 开启/关闭 host(Activity)隔离。默认关闭。
65+ *
66+ * 开启后 pool key 包含 Activity identityHashCode,
67+ * 不同 Activity 的 View 不会串池,避免 context/theme 不一致导致的 UI 异常。
68+ *
69+ * 注意:开启后 Application context 预热的 View 进入独立桶(hostHash=0),
70+ * 不会被 Activity context 的 obtain 命中。请改用 Activity context 做 warmUp。
71+ *
72+ * 关闭时所有 context 共享同一个桶,适合全局 theme 一致的项目(绝大多数情况)。
73+ */
74+ @Volatile
75+ private var hostIsolation = false
76+
6277 private var defaultMaxPoolSize = 4
6378
6479 fun setMaxPoolSize (size : Int ) {
@@ -94,6 +109,47 @@ class ViewPool {
94109
95110 fun isFactoryIsolationEnabled (): Boolean = factoryIsolation
96111
112+ /* *
113+ * 开启/关闭 host(Activity)隔离。默认关闭。
114+ *
115+ * 开启后不同 Activity 的 View 不会串池,Application context 预热的 View 进入共享桶。
116+ * 关闭后所有 context 共享同一个桶,适合全局 theme 一致的项目(绝大多数情况)。
117+ *
118+ * 切换时会清空池。
119+ */
120+ fun setHostIsolation (enabled : Boolean ) {
121+ if (hostIsolation == enabled) return
122+ hostIsolation = enabled
123+ clear()
124+ }
125+
126+ fun isHostIsolationEnabled (): Boolean = hostIsolation
127+
128+ /* *
129+ * 清除指定 Activity 关联的所有池条目。
130+ * 在 Activity.onDestroy 时调用,避免池中 View 持有已销毁 Activity 的 context。
131+ *
132+ * - hostIsolation 关闭:退化为 [clear](无法按 host 区分)。
133+ * - hostIsolation + factoryIsolation 同时开启:低 32 位是 hostHash xor factoryHash,
134+ * 无法仅凭 activityHash 精确匹配,退化为 [clear] 避免泄漏。
135+ * - 仅 hostIsolation 开启:精确删除低 32 位等于 activityHash 的条目。
136+ */
137+ fun clearForHost (activity : Activity ) {
138+ if (! hostIsolation || factoryIsolation) {
139+ clear()
140+ return
141+ }
142+ poolGeneration.incrementAndGet()
143+ val activityHash = System .identityHashCode(activity)
144+ val mask = 0xFFFFFFFFL
145+ pool.keys.removeIf { key ->
146+ (key and mask).toInt() == activityHash
147+ }
148+ warmingCounts.keys.removeIf { key ->
149+ (key and mask).toInt() == activityHash
150+ }
151+ }
152+
97153 /* *
98154 * 控制某个布局是否允许进入 FastInflater 池。
99155 *
@@ -122,30 +178,51 @@ class ViewPool {
122178 *
123179 * 池中的 View 在 [recycle] 时已经被清理过(默认 [ViewCleaner.clean] 或 [ViewRecyclePolicy.onRecycle]),
124180 * View 处于 detached 状态、不会被外部修改,所以这里不再重复清理整棵 View 树。
181+ *
182+ * 并发安全:用有界尝试(snapshot size)替代无界 while 循环,避免并发 offer 导致的活锁。
183+ * 不兼容的 View 暂存本地列表,最后统一放回队尾,保留给后续兼容的 parent 使用。
125184 */
126185 fun obtain (@LayoutRes layoutId : Int , context : Context , parent : ViewGroup ? = null): View ? {
127186 if (isPoolingDisabled(layoutId)) return null
128187 val key = keyFor(layoutId, context)
188+ return pollCompatible(key, layoutId, context, parent)
189+ }
190+
191+ /* *
192+ * 从指定 deque 中 poll 出第一个与 parent 兼容的 View。
193+ * 最多尝试 deque 当前 size 次(快照),不兼容的 View 暂存后统一放回队尾。
194+ */
195+ private fun pollCompatible (
196+ key : Long ,
197+ @LayoutRes layoutId : Int ,
198+ context : Context ,
199+ parent : ViewGroup ?
200+ ): View ? {
129201 val deque = pool[key] ? : return null
130- while (true ) {
131- val view = deque.poll() ? : return null
132- if (! ensureAttachableToParent(layoutId, context, parent, view)) {
133- deque.offerLast(view)
134- val remaining = (deque.size - 1 ).coerceAtLeast(0 )
135- repeat(remaining) {
136- val next = deque.poll() ? : return null
137- if (ensureAttachableToParent(layoutId, context, parent, next)) {
138- policies[layoutId]?.onObtain(next)
139- return next
140- }
141- deque.offerLast(next)
142- }
143- return null
202+ val maxAttempts = deque.size
203+ if (maxAttempts == 0 ) return null
204+
205+ val rejected = ArrayList <View >(4 )
206+ var found: View ? = null
207+
208+ for (i in 0 until maxAttempts) {
209+ val view = deque.poll() ? : break
210+ if (ensureAttachableToParent(layoutId, context, parent, view)) {
211+ found = view
212+ break
144213 }
145- // 只跑用户自定义的 onObtain 钩子;默认情况下 obtain 是纯 poll
146- policies[layoutId]?.onObtain(view)
147- return view
214+ rejected.add(view)
215+ }
216+
217+ // 不兼容的 View 放回队尾,保留给后续兼容的 parent 使用
218+ for (view in rejected) {
219+ deque.offerLast(view)
220+ }
221+
222+ if (found != null ) {
223+ policies[layoutId]?.onObtain(found)
148224 }
225+ return found
149226 }
150227
151228 /* *
@@ -377,12 +454,14 @@ class ViewPool {
377454 }
378455
379456 private fun clearLayout (@LayoutRes layoutId : Int ) {
380- if (! factoryIsolation) {
381- val key = layoutId.toLong()
457+ if (! hostIsolation && ! factoryIsolation) {
458+ // 无隔离模式:key = layoutId << 32,直接移除
459+ val key = layoutId.toLong() shl 32
382460 pool.remove(key)
383461 warmingCounts.remove(key)
384462 return
385463 }
464+ // 有隔离模式:高 32 位匹配 layoutId 的所有 key 都要移除
386465 pool.keys.removeIf { key ->
387466 (key ushr 32 ).toInt() == layoutId
388467 }
@@ -428,16 +507,47 @@ class ViewPool {
428507 }
429508
430509 /* *
431- * 计算 pool key。默认仅基于 layoutId,零额外开销、不访问 Context。
432- * 开启 factoryIsolation 时,把 LayoutInflater.factory2 的 class hash 编入低 32 位。
510+ * 计算 pool key。
511+ * - 高 32 位始终为 layoutId
512+ * - 低 32 位根据隔离模式组合 hostHash 和 factoryHash
513+ * - hostIsolation=true: 包含 Activity identityHashCode(Application context 为 0)
514+ * - factoryIsolation=true: 包含 LayoutInflater.factory2 的 class hashCode
515+ * - 两者都关: 低 32 位为 0,等价于旧行为
433516 */
434517 private fun keyFor (@LayoutRes layoutId : Int , context : Context ): Long {
435- if (! factoryIsolation) {
436- return layoutId.toLong()
518+ val high = layoutId.toLong() shl 32
519+ if (! hostIsolation && ! factoryIsolation) {
520+ return high
521+ }
522+ var low = 0
523+ if (hostIsolation) {
524+ low = hostHash(context)
525+ }
526+ if (factoryIsolation) {
527+ low = low xor factoryHash(context)
528+ }
529+ return high or (low.toLong() and 0xFFFFFFFFL )
530+ }
531+
532+ /* *
533+ * 返回 context 所属 Activity 的 identityHashCode,用于 host 隔离。
534+ * 非 Activity context(Application / Service)返回 0,进入共享桶。
535+ */
536+ private fun hostHash (context : Context ): Int {
537+ val activity = unwrapActivity(context) ? : return 0
538+ return System .identityHashCode(activity)
539+ }
540+
541+ /* *
542+ * 沿 ContextWrapper 链向上查找 Activity。
543+ */
544+ private fun unwrapActivity (context : Context ): Activity ? {
545+ var ctx: Context ? = context
546+ while (ctx != null ) {
547+ if (ctx is Activity ) return ctx
548+ ctx = if (ctx is ContextWrapper ) ctx.baseContext else null
437549 }
438- // 高 32 位 layoutId,低 32 位 factory hash
439- val hash = factoryHash(context)
440- return (layoutId.toLong() shl 32 ) or (hash.toLong() and 0xFFFFFFFFL )
550+ return null
441551 }
442552
443553 private fun factoryHash (context : Context ): Int {
@@ -489,6 +599,12 @@ class ViewPool {
489599 }
490600
491601 private fun canEnterPool (@LayoutRes layoutId : Int , view : View ): Boolean {
602+ // 仍 attached 的 view 不能入池:下一次 obtain + parent.addView 会抛 IllegalStateException
603+ // 如果业务确认安全(已经 detach 或者 parent 即将丢弃),可以注册 ViewRecyclePolicy 覆盖默认行为
604+ if (view.parent != null ) {
605+ val policy = policies[layoutId] ? : return false
606+ return policy.canRecycle(view)
607+ }
492608 return policies[layoutId]?.canRecycle(view) ? : true
493609 }
494610
0 commit comments