کوروتین های Kotlin یک API را ارائه می دهند که به شما امکان می دهد کدهای ناهمزمان بنویسید. با کوروتینهای Kotlin، میتوانید یک CoroutineScope
تعریف کنید، که به شما کمک میکند تا زمان اجرا شدن کوروتینهایتان را مدیریت کنید. هر عملیات ناهمزمان در محدوده خاصی اجرا می شود.
مؤلفههای مربوط به چرخه حیات، پشتیبانی درجه یک از برنامههای روتین برای حوزههای منطقی در برنامه شما به همراه یک لایه قابلیت همکاری با LiveData
را ارائه میکنند. این مبحث نحوه استفاده مؤثر از کوروتین ها با مؤلفه های آگاه از چرخه حیات را توضیح می دهد.
وابستگی های KTX را اضافه کنید
حوزههای کوروتین داخلی که در این مبحث توضیح داده شده است، در پسوندهای KTX برای هر جزء مربوطه موجود است. هنگام استفاده از این محدوده ها حتماً وابستگی های مناسب را اضافه کنید.
- برای
ViewModelScope
، ازandroidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0
یا بالاتر استفاده کنید. - برای
LifecycleScope
، ازandroidx.lifecycle:lifecycle-runtime-ktx:2.4.0
یا بالاتر استفاده کنید. - برای
liveData
، ازandroidx.lifecycle:lifecycle-livedata-ktx:2.4.0
یا بالاتر استفاده کنید.
حوزه های کاری آگاه از چرخه زندگی
مؤلفههای مربوط به چرخه حیات، حوزههای داخلی زیر را تعریف میکنند که میتوانید در برنامه خود استفاده کنید.
ViewModelScope
یک ViewModelScope
برای هر ViewModel
در برنامه شما تعریف شده است. اگر ViewModel
پاک شود، هر برنامهای که در این محدوده راهاندازی شود، بهطور خودکار لغو میشود. Coroutine ها در اینجا برای زمانی مفید هستند که کاری دارید که فقط در صورت فعال بودن ViewModel
باید انجام دهید. به عنوان مثال، اگر مقداری داده را برای یک طرح بندی محاسبه می کنید، باید کار را به ViewModel
محدود کنید تا اگر ViewModel
پاک شود، کار به طور خودکار لغو شود تا از مصرف منابع جلوگیری شود.
همانطور که در مثال زیر نشان داده شده است، می توانید از طریق ویژگی viewModelScope
ViewModel به CoroutineScope
یک ViewModel
دسترسی داشته باشید:
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
LifecycleScope
یک LifecycleScope
برای هر شیء Lifecycle
تعریف شده است. هر برنامهای که در این محدوده راهاندازی میشود، زمانی که Lifecycle
از بین میرود، لغو میشود. شما می توانید از طریق lifecycle.coroutineScope
یا lifecycleOwner.lifecycleScope
به CoroutineScope
از Lifecycle
دسترسی داشته باشید.
مثال زیر نحوه استفاده از lifecycleOwner.lifecycleScope
را برای ایجاد متن از پیش محاسبه شده به صورت ناهمزمان نشان می دهد:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
روتینهای آگاه از چرخه حیات قابل راهاندازی مجدد
حتی اگر lifecycleScope
روش مناسبی برای لغو خودکار عملیات طولانی مدت در هنگام DESTROYED
Lifecycle
ارائه میکند، ممکن است موارد دیگری داشته باشید که بخواهید اجرای یک بلوک کد را زمانی که Lifecycle
در وضعیت خاصی است شروع کنید و زمانی که آن را لغو کنید، آن را لغو کنید. در حالت دیگری است برای مثال، ممکن است بخواهید زمانی که Lifecycle
STARTED
، جریانی را جمعآوری کنید و پس از STOPPED
آن، مجموعه را لغو کنید. این رویکرد انتشارات جریان را فقط زمانی پردازش می کند که رابط کاربری روی صفحه نمایش قابل مشاهده باشد، منابع را صرفه جویی می کند و احتمالاً از خرابی برنامه جلوگیری می کند.
برای این موارد، Lifecycle
و LifecycleOwner
API suspend repeatOnLifecycle
را ارائه می دهند که دقیقاً این کار را انجام می دهد. مثال زیر حاوی یک بلوک کد است که هر بار که Lifecycle
مرتبط حداقل در حالت STARTED
باشد اجرا می شود و زمانی که Lifecycle
STOPPED
شود لغو می شود:
class MyFragment : Fragment() {
val viewModel: MyViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Create a new coroutine in the lifecycleScope
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// This happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
viewModel.someDataFlow.collect {
// Process item
}
}
}
}
}
مجموعه جریان آگاه از چرخه حیات
اگر فقط نیاز دارید که مجموعهای از چرخه حیات را روی یک جریان واحد انجام دهید، میتوانید از متد Flow.flowWithLifecycle()
برای سادهسازی کد خود استفاده کنید:
viewLifecycleOwner.lifecycleScope.launch {
exampleProvider.exampleFlow()
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
با این حال، اگر نیاز دارید که مجموعهای از چرخه حیات را روی جریانهای متعدد به صورت موازی انجام دهید، باید هر جریان را در کوروتینهای مختلف جمعآوری کنید. در این صورت، استفاده مستقیم از repeatOnLifecycle()
کارآمدتر است:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Because collect is a suspend function, if you want to
// collect multiple flows in parallel, you need to do so in
// different coroutines.
launch {
flow1.collect { /* Process the value. */ }
}
launch {
flow2.collect { /* Process the value. */ }
}
}
}
برنامه های متداول مربوط به چرخه زندگی را به حالت تعلیق درآورید
حتی اگر CoroutineScope
راه مناسبی برای لغو خودکار عملیات طولانی مدت ارائه می دهد، ممکن است موارد دیگری داشته باشید که بخواهید اجرای یک بلوک کد را به حالت تعلیق در آورید، مگر اینکه Lifecycle
در وضعیت خاصی باشد. برای مثال، برای اجرای FragmentTransaction
، باید منتظر بمانید تا Lifecycle
حداقل STARTED
شود. برای این موارد، Lifecycle
روشهای اضافی را ارائه میکند: lifecycle.whenCreated
، lifecycle.whenStarted
و lifecycle.whenResumed
. اگر Lifecycle
حداقل در حداقل حالت مورد نظر نباشد، هرگونه اجرا در داخل این بلوک ها به حالت تعلیق در می آید.
مثال زیر حاوی یک بلوک کد است که فقط زمانی اجرا می شود که Lifecycle
مرتبط حداقل در حالت STARTED
باشد:
class MyFragment: Fragment {
init { // Notice that we can safely launch in the constructor of the Fragment.
lifecycleScope.launch {
whenStarted {
// The block inside will run only when Lifecycle is at least STARTED.
// It will start executing when fragment is started and
// can call other suspend methods.
loadingView.visibility = View.VISIBLE
val canAccess = withContext(Dispatchers.IO) {
checkUserAccess()
}
// When checkUserAccess returns, the next line is automatically
// suspended if the Lifecycle is not *at least* STARTED.
// We could safely run fragment transactions because we know the
// code won't run unless the lifecycle is at least STARTED.
loadingView.visibility = View.GONE
if (canAccess == false) {
findNavController().popBackStack()
} else {
showContent()
}
}
// This line runs only after the whenStarted block above has completed.
}
}
}
اگر Lifecycle
در حالی که یک کوروتین فعال است از طریق یکی از روشهای when
از بین برود، کوروتین به طور خودکار لغو میشود. در مثال زیر، بلوک finally
پس از DESTROYED
حالت Lifecycle
اجرا میشود:
class MyFragment: Fragment {
init {
lifecycleScope.launchWhenStarted {
try {
// Call some suspend functions.
} finally {
// This line might execute after Lifecycle is DESTROYED.
if (lifecycle.state >= STARTED) {
// Here, since we've checked, it is safe to run any
// Fragment transactions.
}
}
}
}
}
از کوروتین ها با LiveData استفاده کنید
هنگام استفاده از LiveData
، ممکن است لازم باشد مقادیر را به صورت ناهمزمان محاسبه کنید. به عنوان مثال، ممکن است بخواهید تنظیمات برگزیده یک کاربر را بازیابی کنید و آنها را به UI خود ارائه دهید. در این موارد، می توانید از تابع سازنده liveData
برای فراخوانی یک تابع suspend
استفاده کنید و نتیجه را به عنوان یک شی LiveData
ارائه کنید.
در مثال زیر، loadUser()
یک تابع suspend است که در جای دیگری اعلام شده است. از تابع سازنده liveData
برای فراخوانی loadUser()
به صورت ناهمزمان استفاده کنید و سپس از emit()
برای انتشار نتیجه استفاده کنید:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
بلوک سازنده liveData
به عنوان یک همزمانی اولیه ساختاریافته بین کوروتین ها و LiveData
عمل می کند. بلوک کد با فعال شدن LiveData
شروع به اجرا میکند و پس از غیرفعال شدن LiveData
پس از یک مهلت زمانی قابل تنظیم، بهطور خودکار لغو میشود. اگر قبل از تکمیل لغو شود، اگر LiveData
دوباره فعال شود دوباره راه اندازی می شود. اگر در اجرای قبلی با موفقیت کامل شد، راه اندازی مجدد نمی شود. توجه داشته باشید که فقط در صورت لغو خودکار، راه اندازی مجدد می شود. اگر بلوک به هر دلیل دیگری لغو شود (مثلاً پرتاب CancellationException
)، دوباره راه اندازی نمی شود.
همچنین می توانید چندین مقدار را از بلوک منتشر کنید. هر فراخوانی emit()
اجرای بلوک را تا زمانی که مقدار LiveData
روی رشته اصلی تنظیم شود به حالت تعلیق در می آورد.
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
همانطور که در مثال زیر نشان داده شده است، می توانید liveData
با Transformations
ترکیب کنید:
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
میتوانید با فراخوانی emitSource()
هر زمان که میخواهید مقدار جدیدی منتشر کنید، چندین مقدار از LiveData
منتشر کنید. توجه داشته باشید که هر فراخوانی برای emit()
یا emitSource()
منبع اضافه شده قبلی را حذف می کند.
class UserDao: Dao {
@Query("SELECT * FROM User WHERE id = :id")
fun getUser(id: String): LiveData<User>
}
class MyRepository {
fun getUser(id: String) = liveData<User> {
val disposable = emitSource(
userDao.getUser(id).map {
Result.loading(it)
}
)
try {
val user = webservice.fetchUser(id)
// Stop the previous emission to avoid dispatching the updated user
// as `loading`.
disposable.dispose()
// Update the database.
userDao.insert(user)
// Re-establish the emission with success type.
emitSource(
userDao.getUser(id).map {
Result.success(it)
}
)
} catch(exception: IOException) {
// Any call to `emit` disposes the previous one automatically so we don't
// need to dispose it here as we didn't get an updated value.
emitSource(
userDao.getUser(id).map {
Result.error(exception, it)
}
)
}
}
}
برای اطلاعات بیشتر در رابطه با کوروتین ها، به لینک های زیر مراجعه کنید:
منابع اضافی
برای کسب اطلاعات بیشتر در مورد استفاده از کوروتین ها با مؤلفه های مربوط به چرخه حیات، به منابع اضافی زیر مراجعه کنید.
نمونه ها
وبلاگ ها
- Coroutines در اندروید: الگوهای برنامه
- برنامه های ساده در اندروید: viewModelScope
- آزمایش دو انتشار متوالی LiveData در روال
برای شما توصیه می شود
- توجه: متن پیوند زمانی که جاوا اسکریپت خاموش است نمایش داده می شود
- نمای کلی LiveData
- مدیریت چرخه زندگی با اجزای مربوط به چرخه حیات
- بارگیری و نمایش داده های صفحه بندی شده