kotlin 中由于闭包导致内存泄漏?

Jat*_*ani 5 android memory-leaks memory-management textwatcher kotlin

我对 Kotlin 中的内存管理有基本的了解,即局部变量存储在堆栈中并由操作系统管理,对象在堆上创建并由 JVM 管理它们。此外,对象作为引用的副本传递,原始类型作为函数中的副本传递。

在下面的代码中,我创建了一个 TextEditOperation 函数,该函数以编程方式创建 EditText 并使用指令对象设置其属性,并向其添加 TextWatcher,最后将 edittext 视图添加到父相对布局。

public fun TextEditOperation (pInstructionSetIndex: Int, pInstructionIndex: Int): View? 
{
    val instruction: InstructionKernelAndroid
    val context: Context?
    val activity: Activity
    val textedit: EditText
    val parent_view: RelativeLayout

    // fetch the instruction
    instruction = InstructionSetMgr.uAndroidOSInstructionSetList[pInstructionSetIndex][pInstructionIndex]

    // get activity's Context
    context = ProcessStates.GetMainActivityContext ()

    // cast activity's context to activity to use it's method
    activity = context as Activity

    // get parent view
    parent_view = activity.findViewById (instruction.GetParentID ()) as RelativeLayout

    // create view
    textedit = EditText (context)

    // set view properties

    textedit.id = instruction.GetElementID ()
    textedit.hint = instruction.GetHintText ()
    textedit.layoutParams = RelativeLayout.LayoutParams (
        instruction.GetWidth (),
        instruction.GetHeight ()
    )
    textedit.x = instruction.GetX ()
    textedit.y = instruction.GetY ()
    textedit.setBackgroundColor (Color.parseColor (instruction.GetBackgroundColor ()))
    textedit.setTextColor (Color.parseColor (instruction.GetTextColor ()))
    
    // create and add a TextWatcher to the EditText

        val text_watcher: TextWatcher

    text_watcher = object : TextWatcher {
    
        override fun beforeTextChanged (s: CharSequence, start: Int, count: Int, after: Int)
        {
            
        }
    
        override fun onTextChanged (s: CharSequence, start: Int, before: Int, count: Int) 
        {
            TextChanged (pInstructionSetIndex + 1, textedit.length (), textedit.id)
        }
    
        override fun afterTextChanged (s: Editable)
        {
            
        }
    } 
    // add the textwatcher
    textedit.addTextChangedListener (text_watcher)
    
    // add view to parent view
    parent_view.addView (textedit);

    return textedit;  
}
Run Code Online (Sandbox Code Playgroud)

现在,我的理解是,如果 text_watcher 对象不存在,那么对于 line

textedit = EditText(上下文)

EditText(context) 对象将在堆上创建(由 JVM 管理),引用该对象的变量 textedit(由操作系统管理)将在堆栈上创建。因此,一旦函数作用域结束,textedit 变量就会被操作系统从堆栈内存中清除。

但是,现在我在 text_watcher 的 OnTextChanged 函数中使用 textedit 变量:

override fun onTextChanged (s: CharSequence, start: Int, before: Int, count: Int) 
{
     TextChanged (pInstructionSetIndex + 1, textedit.length (), textedit.id)
}
Run Code Online (Sandbox Code Playgroud)

在 TextEditOperation 的函数作用域结束后, onTextChanged 函数仍将被调用。我如何能够使用现在应该已被操作系统删除的 textedit 变量访问 EditText 的长度和 id?

是否是因为 kotlin 中的闭包,比我能够访问外部作用域中存在的变量,因此它无法被操作系统释放,从而导致内存泄漏,如果是,如何修复它,或者还有其他原因吗后面呢?

Sla*_*law 3

免责声明:当针对 JVM 时,本答案重点关注 Kotlin。一般概念应该适用于其他平台,但具体细节可能有所不同。

\n
\n

闭包和变量捕获

\n

闭包使用封闭范围中的变量时,该闭包将在创建闭包时“捕获”该变量的值。

\n

在 Kotlin 中,这是通过向为闭包对象生成的类隐式添加类属性来实现的。该属性在闭包实例化期间使用捕获的变量的值进行初始化。在闭包的范围内,任何时候看起来您正在使用捕获的变量,您实际上正在使用该属性。所有这些都是由编译器在幕后为您完成的。

\n

因此,textedit在您的TextWatcher实现中使用是完全可以的。

\n
    \n
  • 该变量的值是在TextWatcher创建时捕获的,而不是在实现中首次“使用”该变量时捕获的。

    \n
  • \n
  • textedit对实现范围内的所有引用TextWatcher实际上都在引用所述实现的隐藏的、隐式定义的属性。这意味着在局部变量从堆栈中弹出后,不会尝试访问它。

    \n
  • \n
\n

请注意,当闭包引用其封闭类的类属性时,闭包实际上捕获封闭类的实例(即this),而不是直接捕获属性的值。此外,与 Java 不同,Kotlin 能够捕获可变局部变量(即var),这是通过使用隐藏的“包装类”来包装实际值,然后捕获该包装对象来实现的。

\n

概念示例

\n

下面是一些代码,演示了编译器正在执行的操作的概念。这不一定是变量捕获的具体实现方式,但它应该有助于理解这个想法。

\n

如果你有这样的事情:

\n
interface Printer {\n    fun print()\n}\n\nfun createPrinter(): Printer {\n    val message = "Hello, World" // local variable\n    return object : Printer {\n        override fun print() = println(message)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

编译后,就好像你写了以下内容:

\n
interface Printer {\n    fun print()\n}\n\nfun createPrinter() : Printer {\n    val message = "Hello, World" // local variable\n    class AnonymousPrinterImpl(\n        private val message: String // class property\n    ): Printer {\n        // Here, \'message\' is referring to the class property,\n        // not the local variable.\n        override fun print() = println(message)\n    }\n    return AnonymousPrinterImpl(message)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果您想查看编译器实际生成的内容,则可以通过该javap工具检查 Java 字节码。请注意,匿名对象的实现与函数接口或 lambda 表达式不同,但最终结果是相同的:类属性被隐式添加到闭包的类中。

\n
\n

内存管理

\n

在 Kotlin 中,堆内存由垃圾收集器管理。从较高层次来看,垃圾收集器的工作方式是从指定的根对象开始定期遍历当前对象图。任何无法从这些根对象到达的对象都可以进行垃圾回收。在具有垃圾收集器的语言中,当程序不再需要某个对象后仍保持强引用时,就会发生“内存泄漏”。强引用可以防止垃圾收集器收集不需要的对象。

\n

您似乎担心函数返回后TextWatcher错误地将对象保留在内存中。TextEdit但是您正在TextEdit从函数返回对象并将其添加到某个“父视图”中。这两件事都强烈表明TextEdit在函数返回后您的程序仍然需要该对象。

\n

此外,函数返回后对 的唯一强引用TextWatcherTextEdit. 在这种情况下,并不是将TextWatcher保留TextEdit在内存中,而是相反。当TextEdit变得有资格进行垃圾回收时,TextWatcher. 两者之间的循环引用不会改变这一点。\xc2\xa0

\n

简而言之,您的代码中没有内存泄漏;或者至少不是由TextWatcher您向我们展示的内容引起的。

\n