← Назад к вопросам

Какие знаешь виды Scope?

2.0 Middle🔥 91 комментариев
#Kotlin основы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Виды 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Все приложениеНикогдаРучная
viewModelScopeViewModelViewModelАвтоматическая
lifecycleScopeActivity/FragmentActivity/FragmentАвтоматическая
CoroutineScope()РучнойService/Customручная cancel()
LaunchedEffectComposableComposeАвтоматическая

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

  1. НЕ используй GlobalScope — это анти-паттерн
  2. Используй viewModelScope в ViewModel — это лучший выбор
  3. Используй lifecycleScope в Activity/Fragment если нет ViewModel
  4. Создавай свой scope для долгоживущих объектов
  5. Всегда отменяй пользовательские scope в cleanup методе
  6. Выбирай правильный Dispatcher (Main, IO, Default)

Правильный выбор scope — это основа стабильного и безопасного приложения без утечек памяти.