Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Виды Scope в Kotlin Coroutines
Scope (область видимости) определяет жизненный цикл корутин и управляет их выполнением. Это критическая часть асинхронного программирования в Kotlin.
Основные типы Scope
1. GlobalScope (Глобальная область)
GlobalScope — корутины запускаются на всю жизнь приложения. Живут независимо от компонента.
fun loadUsers() {
// ПЛОХО — корутина живет столько же сколько приложение
GlobalScope.launch {
val users = fetchUsers() // Может выполняться после уничтожения Activity
updateUI(users)
}
}
// Проблемы:
// - Нет контроля над жизненным циклом
// - Утечки памяти
// - Может привести к краху
Когда использовать: Почти НИКОГДА. Это анти-паттерн. Разве что для критических инициализаций приложения.
2. ViewModelScope (ViewModel область)
viewModelScope — корутины привязаны к жизненному циклу ViewModel. Отменяются когда ViewModel уничтожена.
class UserViewModel : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
// ХОРОШО — автоматически отменяется когда ViewModel уничтожена
viewModelScope.launch {
try {
_users.value = userRepository.getUsers()
} catch (e: Exception) {
Log.e("TAG", "Failed to load users", e)
}
}
}
}
// Преимущества:
// - Привязан к ViewModel
// - Автоматическая очистка
// - Нет утечек памяти
Когда использовать: ВСЕГДА для ViewModels. Это best practice.
3. LifecycleScope (Жизненный цикл область)
lifecycleScope — корутины привязаны к жизненному циклу Activity/Fragment.
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// Корутина автоматически отменяется при onDestroy
lifecycleScope.launch {
userRepository.getUsers()
}
}
}
// Для разных состояний:
class DetailFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Запускается только когда View создана
viewLifecycleOwner.lifecycleScope.launch {
// ...
}
// Запускается на каждом STARTED
viewLifecycleOwner.lifecycleScope.launch(Lifecycle.State.STARTED) {
userRepository.getUsers().collect { users ->
updateUI(users)
}
}
}
}
Когда использовать: В Activity/Fragment если нет ViewModel. Лучше использовать viewModelScope.
4. LazyScope (Ленивая область)
CoroutineScope() — создаем свой scope для управления корутинами.
class UserManager {
private val scope = CoroutineScope(Dispatchers.Main + Job())
fun loadUsers() {
scope.launch {
val users = userRepository.getUsers()
updateUI(users)
}
}
fun cleanup() {
scope.cancel() // Отменяем все корутины
}
}
// Для Service:
class MyService : Service() {
private val scope = CoroutineScope(Dispatchers.Main + Job())
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
scope.launch {
// работа
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
scope.cancel() // Отменяем корутины
}
}
Когда использовать: Для Service, Repository, или других компонентов где нет встроенного scope.
5. LaunchedEffect (Композиция область)
LaunchedEffect — специальный scope для Jetpack Compose.
@Composable
fun UserScreen(userId: String) {
var userData by remember { mutableStateOf<User?>(null) }
// Корутина запускается когда composable входит, отменяется когда выходит
LaunchedEffect(userId) {
userData = fetchUser(userId)
}
Text("User: ${userData?.name}")
}
Когда использовать: В Compose компонентах для side-effects.
Сравнение всех Scope
| Scope | Жизненный цикл | Использование | Очистка |
|---|---|---|---|
| GlobalScope | Все приложение | Никогда | Ручная |
| viewModelScope | ViewModel | ViewModel | Автоматическая |
| lifecycleScope | Activity/Fragment | Activity/Fragment | Автоматическая |
| CoroutineScope() | Ручной | Service/Custom | ручная cancel() |
| LaunchedEffect | Composable | Compose | Автоматическая |
Dispatcher — контекст исполнения
viewModelScope.launch {
// Основной поток (Main)
updateUI()
val data = withContext(Dispatchers.IO) {
// IO поток
fetchFromDatabase()
}
val computed = withContext(Dispatchers.Default) {
// Background поток для вычислений
heavyComputation(data)
}
}
// Виды Dispatcher:
class ExampleViewModel : ViewModel() {
// Основной поток
viewModelScope.launch {
// Dispatchers.Main
}
// IO операции
viewModelScope.launch(Dispatchers.IO) {
val data = database.query()
}
// Вычисления
viewModelScope.launch(Dispatchers.Default) {
val result = computeExpensive()
}
// Immediate
viewModelScope.launch(Dispatchers.Unconfined) {
// Быстрые операции
}
}
Практический пример: полный workflow
// ViewModel с правильным scope
class PostViewModel : ViewModel() {
private val _posts = MutableStateFlow<List<Post>>(emptyList())
val posts: StateFlow<List<Post>> = _posts.asStateFlow()
private val _loading = MutableStateFlow(false)
val loading: StateFlow<Boolean> = _loading.asStateFlow()
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error.asStateFlow()
fun loadPosts() {
viewModelScope.launch {
try {
_loading.value = true
// IO операция
val posts = withContext(Dispatchers.IO) {
postRepository.getPosts()
}
_posts.value = posts
_error.value = null
} catch (e: Exception) {
_error.value = e.message
} finally {
_loading.value = false
}
}
}
}
// Activity использует ViewModel
class PostActivity : AppCompatActivity() {
private val viewModel: PostViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_post)
// Используем lifecycleScope только при необходимости
lifecycleScope.launch {
viewModel.posts.collect { posts ->
updateUI(posts)
}
}
viewModel.loadPosts()
}
}
Иерархия Scope
GlobalScope (приложение)
↓
lifeycleScope (Activity/Fragment)
↓
viewModelScope (ViewModel) ← РЕКОМЕНДУЕТСЯ
↓
Custom CoroutineScope (Service/Custom)
Общие ошибки
// ПЛОХО
class UserViewModel : ViewModel() {
fun loadUsers() {
GlobalScope.launch { // Утечка памяти!
val users = repository.getUsers()
}
}
}
// ХОРОШО
class UserViewModel : ViewModel() {
fun loadUsers() {
viewModelScope.launch { // Правильно
val users = repository.getUsers()
}
}
}
// ПЛОХО
class MyService : Service() {
fun startWork() {
lifecycleScope.launch { // lifecycleScope в Service!
// Может быть null
}
}
}
// ХОРОШО
class MyService : Service() {
private val scope = CoroutineScope(Dispatchers.Main + Job())
fun startWork() {
scope.launch {
// работа
}
}
override fun onDestroy() {
scope.cancel()
}
}
Правила использования Scope
- НЕ используй GlobalScope — это анти-паттерн
- Используй viewModelScope в ViewModel — это лучший выбор
- Используй lifecycleScope в Activity/Fragment если нет ViewModel
- Создавай свой scope для долгоживущих объектов
- Всегда отменяй пользовательские scope в cleanup методе
- Выбирай правильный Dispatcher (Main, IO, Default)
Правильный выбор scope — это основа стабильного и безопасного приложения без утечек памяти.