英文:
Launch coroutine from click event in fragment
问题
Proper way to launch a coroutine from a click event in a fragment is not to use GlobalScope.launch
. Fragment's shorter lifecycle suggests avoiding it to prevent potential memory issues. Instead, launch the coroutine directly from the click event handler since no parent functions are involved.
英文:
What is the proper way to launch a coroutine from a click event that is defined in a fragment? From my understanding, GlobalScope.launch
is used if you want to launch a coroutine that is supposed to remain in memory for the entire lifecycle of the app. But since a fragment usually has a shorter lifecycle than the app, GlobalScope.launch
probably isn't the proper way. I assume that if I used GlobalScope.launch
, it might keep the fragment from being garbage collected?
I really only need to launch the coroutine from a click event handler, which means that there is no parent functions that I would be calling from.
答案1
得分: 37
You need a job
to handle the coroutine cancelation to prevent leaks:
//inside Fragment
需要一个job
来处理协程取消以防止泄漏:
//在Fragment内部
val job = Job()
val uiScope = CoroutineScope(Dispatchers.Main + job)
//late in the button click
//在按钮点击事件后
button.setOnClickListener{
uiScope.launch(Dispatchers.IO){
//asyncOperation
//异步操作
withContext(Dispatchers.Main){
//ui operation
//UI操作
}
}
}
//later in onDestroy:
//稍后在onDestroy中:
override fun onDestroy(){
job.cancel()
super.onDestroy()
}
You can also use LifecycleScope extension from Google:
你还可以使用Google的生命周期范围扩展:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
Edit, in case you would re-fire another operation. Just use the same scope:
如果您需要重新启动另一个操作,请使用相同的作用域:
//let's assume you have a second button
//假设您有第二个按钮
button2.setOnClickListener{
uiScope.launch(Dispatchers.IO){
//perform second operation
//执行第二个操作
}
}
英文:
You need a job
to handle the coroutine cancelation to prevent leaks:
//inside Fragment
val job = Job()
val uiScope = CoroutineScope(Dispatchers.Main + job)
//late in the button click
button.setOnClickListener{
uiScope.launch(Dispatchers.IO){
//asyncOperation
withContext(Dispatchers.Main){
//ui operation
}
}
}
//later in on destroy:
override fun onDestroy(){
job.cancel()
super.onDestroy()
}
You can also use LifecycleScope extension from Google:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
Edit, in case you would re-fire another operation. Just use the same scope:
//let's assume you have a second button
button2.setOnClickListener{
uiScope.launch(Dispatchers.IO){
//perform second operation
}
}
答案2
得分: 2
GlobalScope.launch
用于启动一个协程,该协程应该在应用程序的整个生命周期内保持在内存中。
这并不一定如此,它可以用于与用户可以导航离开的应用程序的活动或阶段无关的任何协程。例如,您可以启动一个任务,向服务器发送单向消息。它可能很快就会完成,无论用户以后做什么,都希望它完成。
如果我使用 GlobalScope.launch
,它是否会阻止片段被垃圾回收?
只有当协程保留对片段或其一部分的引用,并且只有当它有可能运行很长时间时才会如此。
具体来说,在点击事件中通常要执行的操作是执行涉及后端(即网络)的某些操作,然后更新UI。显然,这可能需要很长时间(特别是在网络不佳的情况下),并且它保留对稍后要操作的UI元素的引用。这应该绑定到片段的生命周期。
从片段中的点击事件启动协程的正确方法是什么?
最简单的方法如下,遵循官方文档:
class MyFragment : Fragment, CoroutineScope by MainScope {
override fun onDestroy() {
cancel() // extension on CoroutineScope
}
... 剩下的片段代码 ...
}
这捕获了以前必须手动编写的惯用法(如本答案中所见)。
英文:
> GlobalScope.launch
is used if you want to launch a coroutine that is supposed to remain in memory for the entire lifecycle of the app.
This isn't necessarily so, it could be used for any coroutine that isn't coupled to an activity or phase of the app that the user could navigate away from. For example, you might launch a task that sends a one-way message to your server. It will probably finish quite soon, and you want it to finish whatever the user does later on.
> I assume that if I used GlobalScope.launch
, it might keep the fragment from being garbage collected?
Only if the coroutine retains a reference to the fragment or a part of it, and only if it has the potential to run for a long time.
Specifically, the typical thing you do in an on-click event is perform some action that involves your back end (i.e., networking) and then updates the UI. Clearly this can take a long time (especially in case of bad network) and it retains a reference to the UI element it's going to touch later on. This should be bound to the lifecycle of the fragment.
>What is the proper way to launch a coroutine from a click event that is defined in a fragment?
The easiest way is like this, following the official documentation:
class MyFragment : Fragment, CoroutineScope by MainScope {
override fun onDestroy() {
cancel() // extension on CoroutineScope
}
... rest of your fragment code ...
}
This captures the idiom that you previously had to write by hand (as seen in the other answer here).
答案3
得分: 1
class JobFragment : Fragment() {
private val job = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + job)
private var button: Button? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 为此片段充气布局
return inflater.inflate(R.layout.fragment_job, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button = view.findViewById(R.id.button)
button?.setOnClickListener {
uiScope.launch(Dispatchers.IO) {
val fun1 = async { followers1() }
val fun2 = async { followers2() }
Log.e("Test1", "${fun1.await()}")
Log.e("Test2", "${fun2.await()}")
withContext(Dispatchers.Main) {
// UI 操作
}
}
}
}
private fun followers1() {
println("Followers Testing...")
}
private fun followers2() {
println("Followers Testing...")
}
override fun onDestroy() {
job.cancel()
super.onDestroy()
}
}
<details>
<summary>英文:</summary>
class JobFragment : Fragment() {
private val job = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + job)
private var button : Button?= null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_job, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button = view.findViewById(R.id.button)
button?.setOnClickListener{
uiScope.launch(Dispatchers.IO){
val fun1 = async { followers1() }
val fun2 = async { followers2() }
Log.e("Test1", "${fun1.await()}", )
Log.e("Test2", "${fun2.await()}", )
withContext(Dispatchers.Main){
//ui operation
}
}
}
}
private fun followers1() {
println("Followers Testing...")
}
private fun followers2() {
println("Followers Testing...")
}
override fun onDestroy(){
job.cancel()
super.onDestroy()
}
}
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/IquxM.png
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论