instanceManagerTemplate function
The Kotlin InstanceManager
.
Implementation
String instanceManagerTemplate(KotlinOptions options) {
return '''
/**
* Maintains instances used to communicate with the corresponding objects in Dart.
*
* Objects stored in this container are represented by an object in Dart that is also stored in
* an InstanceManager with the same identifier.
*
* When an instance is added with an identifier, either can be used to retrieve the other.
*
* Added instances are added as a weak reference and a strong reference. When the strong
* reference is removed with [remove] and the weak reference is deallocated, the
* `finalizationListener.onFinalize` is called with the instance's identifier. However, if the strong
* reference is removed and then the identifier is retrieved with the intention to pass the identifier
* to Dart (e.g. calling [getIdentifierForStrongReference]), the strong reference to the instance
* is recreated. The strong reference will then need to be removed manually again.
*/
@Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate")
class ${kotlinInstanceManagerClassName(options)}(private val finalizationListener: $_finalizationListenerClassName) {
/** Interface for listening when a weak reference of an instance is removed from the manager. */
interface $_finalizationListenerClassName {
fun onFinalize(identifier: Long)
}
private val identifiers = java.util.WeakHashMap<Any, Long>()
private val weakInstances = HashMap<Long, java.lang.ref.WeakReference<Any>>()
private val strongInstances = HashMap<Long, Any>()
private val referenceQueue = java.lang.ref.ReferenceQueue<Any>()
private val weakReferencesToIdentifiers = HashMap<java.lang.ref.WeakReference<Any>, Long>()
private val handler = android.os.Handler(android.os.Looper.getMainLooper())
private var nextIdentifier: Long = minHostCreatedIdentifier
private var hasFinalizationListenerStopped = false
/**
* Modifies the time interval used to define how often this instance removes garbage collected
* weak references to native Android objects that this instance was managing.
*/
var clearFinalizedWeakReferencesInterval: Long = 3000
set(value) {
handler.removeCallbacks { this.releaseAllFinalizedInstances() }
field = value
releaseAllFinalizedInstances()
}
init {
handler.postDelayed(
{ releaseAllFinalizedInstances() },
clearFinalizedWeakReferencesInterval
)
}
companion object {
// Identifiers are locked to a specific range to avoid collisions with objects
// created simultaneously from Dart.
// Host uses identifiers >= 2^16 and Dart is expected to use values n where,
// 0 <= n < 2^16.
private const val minHostCreatedIdentifier: Long = 65536
private const val tag = "${proxyApiClassNamePrefix}InstanceManager"
/**
* Instantiate a new manager with a listener for garbage collected weak
* references.
*
* When the manager is no longer needed, [stopFinalizationListener] must be called.
*/
fun create(finalizationListener: $_finalizationListenerClassName): ${kotlinInstanceManagerClassName(options)} {
return ${kotlinInstanceManagerClassName(options)}(finalizationListener)
}
}
/**
* Removes `identifier` and return its associated strongly referenced instance, if present,
* from the manager.
*/
fun <T> remove(identifier: Long): T? {
logWarningIfFinalizationListenerHasStopped()
return strongInstances.remove(identifier) as T?
}
/**
* Retrieves the identifier paired with an instance, if present, otherwise `null`.
*
*
* If the manager contains a strong reference to `instance`, it will return the identifier
* associated with `instance`. If the manager contains only a weak reference to `instance`, a new
* strong reference to `instance` will be added and will need to be removed again with [remove].
*
*
* If this method returns a nonnull identifier, this method also expects the Dart
* `${kotlinInstanceManagerClassName(options)}` to have, or recreate, a weak reference to the Dart instance the
* identifier is associated with.
*/
fun getIdentifierForStrongReference(instance: Any?): Long? {
logWarningIfFinalizationListenerHasStopped()
val identifier = identifiers[instance]
if (identifier != null) {
strongInstances[identifier] = instance!!
}
return identifier
}
/**
* Adds a new instance that was instantiated from Dart.
*
* The same instance can be added multiple times, but each identifier must be unique. This
* allows two objects that are equivalent (e.g. the `equals` method returns true and their
* hashcodes are equal) to both be added.
*
* [identifier] must be >= 0 and unique.
*/
fun addDartCreatedInstance(instance: Any, identifier: Long) {
logWarningIfFinalizationListenerHasStopped()
addInstance(instance, identifier)
}
/**
* Adds a new unique instance that was instantiated from the host platform.
*
* [identifier] must be >= 0 and unique.
*/
fun addHostCreatedInstance(instance: Any): Long {
logWarningIfFinalizationListenerHasStopped()
require(!containsInstance(instance)) { "Instance of \${instance.javaClass} has already been added." }
val identifier = nextIdentifier++
addInstance(instance, identifier)
return identifier
}
/** Retrieves the instance associated with identifier, if present, otherwise `null`. */
fun <T> getInstance(identifier: Long): T? {
logWarningIfFinalizationListenerHasStopped()
val instance = weakInstances[identifier] as java.lang.ref.WeakReference<T>?
return instance?.get()
}
/** Returns whether this manager contains the given `instance`. */
fun containsInstance(instance: Any?): Boolean {
logWarningIfFinalizationListenerHasStopped()
return identifiers.containsKey(instance)
}
/**
* Stops the periodic run of the [$_finalizationListenerClassName] for instances that have been garbage
* collected.
*
* The InstanceManager can continue to be used, but the [$_finalizationListenerClassName] will no
* longer be called and methods will log a warning.
*/
fun stopFinalizationListener() {
handler.removeCallbacks { this.releaseAllFinalizedInstances() }
hasFinalizationListenerStopped = true
}
/**
* Removes all of the instances from this manager.
*
* The manager will be empty after this call returns.
*/
fun clear() {
identifiers.clear()
weakInstances.clear()
strongInstances.clear()
weakReferencesToIdentifiers.clear()
}
/**
* Whether the [$_finalizationListenerClassName] is still being called for instances that are garbage
* collected.
*
* See [stopFinalizationListener].
*/
fun hasFinalizationListenerStopped(): Boolean {
return hasFinalizationListenerStopped
}
private fun releaseAllFinalizedInstances() {
if (hasFinalizationListenerStopped()) {
return
}
var reference: java.lang.ref.WeakReference<Any>?
while ((referenceQueue.poll() as java.lang.ref.WeakReference<Any>?).also { reference = it } != null) {
val identifier = weakReferencesToIdentifiers.remove(reference)
if (identifier != null) {
weakInstances.remove(identifier)
strongInstances.remove(identifier)
finalizationListener.onFinalize(identifier)
}
}
handler.postDelayed(
{ releaseAllFinalizedInstances() },
clearFinalizedWeakReferencesInterval
)
}
private fun addInstance(instance: Any, identifier: Long) {
require(identifier >= 0) { "Identifier must be >= 0: \$identifier" }
require(!weakInstances.containsKey(identifier)) {
"Identifier has already been added: \$identifier"
}
val weakReference = java.lang.ref.WeakReference(instance, referenceQueue)
identifiers[instance] = identifier
weakInstances[identifier] = weakReference
weakReferencesToIdentifiers[weakReference] = identifier
strongInstances[identifier] = instance
}
private fun logWarningIfFinalizationListenerHasStopped() {
if (hasFinalizationListenerStopped()) {
Log.w(
tag,
"The manager was used after calls to the $_finalizationListenerClassName has been stopped."
)
}
}
}
''';
}