lol*_*f64 28 lambda android memory-leaks kotlin
在阅读了关于内存泄漏的这篇文章之后,我想知道在Kotlin Android项目中使用lambdas是否安全.确实,lambda语法让我更容易编程,但内存泄漏怎么样?
作为问题的一个例子,我从我的一个项目中获取了一段代码,在那里我构建了一个AlertDialog.此代码位于项目的MainActivity类中.
fun deleteItemOnConfirmation(id: Long) : Unit {
val item = explorerAdapter.getItemAt(id.toInt())
val stringId = if (item.isDirectory) R.string.about_to_delete_folder else R.string.about_to_delete_file
val dialog = AlertDialog.Builder(this).
setMessage(String.format(getString(stringId), item.name)).setPositiveButton(
R.string.ok, {dialog: DialogInterface, id: Int ->
val success = if (item.isDirectory) ExplorerFileManager.deleteFolderRecursively(item.name)
else ExplorerFileManager.deleteFile(item.name)
if (success) {
explorerAdapter.deleteItem(item)
explorerRecyclerView.invalidate()
}
else Toast.makeText(this@MainActivity, R.string.file_deletion_error, Toast.LENGTH_SHORT).show()
}).setNegativeButton(
R.string.cancel, {dialog: DialogInterface, id: Int ->
dialog.cancel()
})
dialog.show()
}
Run Code Online (Sandbox Code Playgroud)
我的问题很简单:为正负按钮设置的两个lambdas可以导致内存泄漏吗?(我的意思是,kotlin lambdas只是转换为Java匿名函数吗?)
编辑:也许我在这个Jetbrains话题中得到了答案.
Sta*_*ots 27
编辑(2017年2月19日):我收到Mike Hearn关于此问题的非常全面的回复:
就像在Java中一样,Kotlin中发生的事情在不同情况下会有所不同.
- 如果将lambda传递给内联函数并且没有标记为noinline,那么整个事情就会消失,并且不会创建其他类或对象.
- 如果lambda没有捕获,那么它将作为一个单独的类发出,其实例一次又一次地被重用(一个类+一个对象分配).
- 如果lambda捕获,则每次使用lambda时都会创建一个新对象.
因此它与Java的行为类似,除了内联案例甚至更便宜.这种编码lambdas的有效方法是Kotlin中的函数式编程比Java更具吸引力的一个原因.
编辑(2017年2月17日):我在Kotlin讨论中发布了有关此主题的问题.也许Kotlin工程师会带来一些新东西.
是kotlin lambdas简单地转换为Java匿名函数?
我自己也在问这个问题(这里有一个简单的修正:这些被称为匿名类,而不是函数).Koltin文档中没有明确的答案.他们只是说出那
使用高阶函数会产生某些运行时惩罚:每个函数都是一个对象,它捕获一个闭包,即在函数体中访问的那些变量.
它们在函数体中访问的变量意味着什么有点令人困惑.是否还计算了对封闭类实例的引用?
我已经看到了你在问题中引用的主题,但它似乎已经过时了.我发现多了最新信息在这里:
Lambda表达式或匿名函数保留封闭类的隐式引用
所以,不幸的是,似乎Kotlin的lambdas与Java的Anonymous Inner Classes有同样的问题.
从Java 规格:
类O的直接内部类C的实例i与O的实例相关联,称为i的直接封闭实例.在创建对象时确定对象的直接封闭实例(如果有)
这意味着匿名类将始终具有对封闭类的实例的隐式引用.由于引用是隐含的,因此无法摆脱它.
看看这个简单的例子
public class YourActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(new Runnable() {
// the inner class will keep the implicit reference to the outer activity
@Override
public void run() {
// long-running task
}
}).start();
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,在这种情况下,在执行长时间运行的任务之前会出现内存泄漏.一种解决方法是使用静态嵌套类.
由于Kotlin's 非内联 lambda拥有对封闭类实例的引用,因此它们具有与内存泄漏类似的问题.
句法:
声明SAM(单个抽象方法)接口
interface Runnable { void run(); }
Run Code Online (Sandbox Code Playgroud)将此接口用作lambda的类型
public void canTakeLambda(Runnable r) { ... }
Run Code Online (Sandbox Code Playgroud)通过你的lambda
canTakeLambda(() -> System.out.println("Do work in lambda..."));
Run Code Online (Sandbox Code Playgroud)内存泄漏问题:如规范中所述:
对此的引用 - 包括通过非限定字段引用或方法调用的隐式引用 - 基本上是对最终局部变量的引用.包含此类引用的Lambda主体捕获相应的实例.在其他情况下,对象不保留对此的引用.
简单地说,如果你不使用封闭类中的任何字段/方法,就没有隐式引用,this就像匿名类一样.
来自文档
Lambda表达式通过将它们转换为匿名内部类来向后移植.这包括优化对无状态lambda表达式使用单例实例以避免重复的对象分配.
我想,这是不言自明的.
句法:
声明类似于Kotlin,在Swift lambdas中称为闭包:
func someFunctionThatTakesAClosure(closure: (String) -> Void) {}
Run Code Online (Sandbox Code Playgroud)通过关闭
someFunctionThatTakesAClosure { print($0) }
Run Code Online (Sandbox Code Playgroud)
在这里,$0请参考闭包的第一个String参数.这与itKotlin 相对应.注:与科特林,在斯威夫特我们也可以参考其他参数一样$1,$2等等.
内存泄漏问题:
在Swift中,就像在Java 8中一样,闭包只有在访问实例的属性时才会捕获self(this在Java和Kotlin中)的强引用,例如self.someProperty,或者如果闭包调用实例上的方法,例如self.someMethod().
开发人员也可以轻松指定他们只想捕获弱引用:
someFunctionThatTakesAClosure { [weak self] in print($0) }
Run Code Online (Sandbox Code Playgroud)
我希望在Kotlin也有可能:)
aga*_*aga 10
当因为不再需要而应该删除的某个对象无法删除时会发生内存泄漏,因为具有较长生命周期的对象具有对此对象的引用.最简单的例子是存储参考Activity的static变量(我从Java的角度讲,但它在科特林类似):用户点击"返回"按钮后Activity不需要了,但将保持但是在内存中 - 因为某些静态变量仍然指向此活动.
现在,在您的示例中,您没有将自己分配Activity给某个static变量,没有object涉及Kotlin 可以防止您Activity被垃圾收集 - 您的代码中涉及的所有对象的生命周期大致相同,这意味着没有内存泄漏.
PS我已经刷新了Kotlin对lambda的实现的记忆:在负按钮单击处理程序的情况下,你没有引用外部作用域,因此编译器将创建单击的侦听器实例,它将被重用于所有点击此按钮.对于正向按钮单击侦听器,您将引用外部作用域(this@MainActivity),因此在这种情况下,Kotlin将在每次创建对话框时创建匿名类的新实例(此实例将引用外部类,MainActivity),所以行为与在Java中编写此代码完全相同.
Gra*_*rks 10
这是一个(潜在的)泄漏的简单示例,其中闭包/块捕获this:
class SomeClass {
fun doWork() {
doWorkAsync { onResult() } // leaks, this.onResult() captures `this`
}
fun onResult() { /* some work */ }
}
Run Code Online (Sandbox Code Playgroud)
您需要使用 WeakReference。
fun doWork() {
val weakThis = WeakReference(this)
doWorkAsync { weakThis?.get()?.onResult() } // no capture, no leak!
}
Run Code Online (Sandbox Code Playgroud)
如果 Kotlin 从 Swift 复制 [弱自我] 语法糖的想法,那就太好了。
| 归档时间: |
|
| 查看次数: |
7810 次 |
| 最近记录: |