Search code examples
androidkotlinretrofit2android-jetpack-compose

Background concurrent copying GC when Login


MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        val navController = rememberNavController()
        NavHost(
            navController = navController,
            startDestination = Screen.LoginScreen.route
        ) {
            composable(route = Screen.LoginScreen.route) {
                navBackStackEntry ->
                val factory = HiltViewModelFactory(LocalContext.current, navBackStackEntry)
                val viewModel: LoginViewModel = viewModel(key = "LoginViewModel", factory = factory)
                LoginScreen(
                    viewModel = viewModel,
                    onNavigateToNextScreen = navController::navigate,
                    navController = navController
                )
            }
            composable(route = Screen.HomeScreen.route) {
                navBackStackEntry ->
                val factory = HiltViewModelFactory(LocalContext.current, navBackStackEntry)
                val viewModel: HomeViewModel = viewModel(key = "HomeViewModel", factory = factory)
                HomeScreen(

                )
            }
        }
    }
}

Screen.kt

sealed class Screen (
    val route: String,
){
    object LoginScreen: Screen("loginScreen")
    object HomeScreen: Screen("homeScreen")
}

LoginScreen.kt

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun LoginScreen(
    viewModel: LoginViewModel,
    onNavigateToNextScreen: (String) -> Unit,
    navController: NavController
) {
    val focusManager = LocalFocusManager.current
    val keyboardController = LocalSoftwareKeyboardController.current

    val empno = viewModel.empno.value
    val password = viewModel.password.value
    val loading = viewModel.loading.value
    val user = viewModel.user.value
    val dialogQueue = viewModel.dialogQueue

    var isPasswordVisible by remember {
        mutableStateOf(false)
    }
    val isFormValid by derivedStateOf {
        empno.isNotBlank() && password.isNotBlank()
    }
    
    Scaffold(backgroundColor = Color.White) {
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Top
        ) {
            if (loading && user == null) {
                LoadingRecipeShimmer(imageHeight = IMAGE_HEIGHT.dp)
            }
            else if (!loading && user != null) {
                //Something to do.
                val route = Screen.HomeScreen.route
                onNavigateToNextScreen(route)
            }else {
                Spacer(modifier = Modifier.height(16.dp))
                Image(
                    painter = painterResource(id = R.drawable.image_login),
                    contentDescription = "loginImage",
                    modifier = Modifier
                        .weight(1f)
                        .size(300.dp)
                )
                Card(
                    modifier = Modifier
                        .weight(2f)
                        .padding(8.dp),
                    shape = RoundedCornerShape(32.dp)
                ) {
                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(32.dp),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center
                    ) {
                        Text(
                            text = "CompanyApp",
                            fontWeight = FontWeight.Bold,
                            fontSize = 40.sp
                        )
                        Column(
                            Modifier.fillMaxSize(),
                            horizontalAlignment = Alignment.CenterHorizontally,
                            verticalArrangement = Arrangement.Center
                        ) {
                            Spacer(modifier = Modifier.weight(1f))
                            OutlinedTextField(
                                value = empno,
                                onValueChange = {
                                    viewModel.setEmpno(it)
                                },
                                modifier = Modifier
                                    .fillMaxWidth(),
                                label = { Text(text = "EmployeeNumber") },
                                singleLine = true,
                                keyboardOptions = KeyboardOptions(
                                    keyboardType = KeyboardType.Number,
                                    imeAction = ImeAction.Next
                                ),
                                keyboardActions = KeyboardActions(
                                    onNext = {
                                        focusManager.moveFocus(FocusDirection.Down)
                                    }
                                ),
                                trailingIcon = {
                                    if (empno.isNotBlank()) {
                                        IconButton(
                                            onClick = {
                                                viewModel.setEmpno("")
                                            }) {
                                            Icon(
                                                imageVector = Icons.Filled.Clear,
                                                contentDescription = "")
                                        }
                                    }
                                }
                            )
                            Spacer(modifier = Modifier.height(16.dp))
                            OutlinedTextField(
                                value = password,
                                onValueChange = {
                                    viewModel.setPassword(it)
                                },
                                modifier = Modifier
                                    .fillMaxWidth(),
                                label = { Text(text = "Password") },
                                singleLine = true,
                                keyboardOptions = KeyboardOptions(
                                    keyboardType = KeyboardType.NumberPassword,
                                    imeAction = ImeAction.Done
                                ),
                                keyboardActions = KeyboardActions(
                                    onDone = {
                                        keyboardController?.hide()
                                        viewModel.login()
                                    }
                                ),
                                visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
                                trailingIcon = {
                                    IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) {
                                        Icon(
                                            imageVector = if (isPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
                                            contentDescription = "Password Toggle"
                                        )
                                    }
                                }
                            )
                            Spacer(modifier = Modifier.height(16.dp))
                            Button(
                                onClick = {
                                    keyboardController?.hide()
                                    viewModel.login()
                                },
                                enabled = isFormValid,
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .height(60.dp),
                                shape = RoundedCornerShape(16.dp)
                            ) {
                                Text(text = "Login")
                            }
                            Spacer(modifier = Modifier.weight(1f))
                        }
                    }
                }
            }
        }
    }
}

LoginViewModel.kt

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val login: Login,
    private val connectivityManager: ConnectivityManager,
    private val state: SavedStateHandle
) : ViewModel() {

    val empno = mutableStateOf("")
    val password = mutableStateOf("")
    val user: MutableState<User?> = mutableStateOf(null)

    val loading = mutableStateOf(false)
    val onLoad: MutableState<Boolean> = mutableStateOf(false)
    val dialogQueue = DialogQueue()

    fun setEmpno(empno: String) {
        this.empno.value = empno
    }
    fun setPassword(password: String) {
        this.password.value = password
    }

    init {
    }


    fun login() {
        val auth = Auth(store_code = "2001", empno = empno.value, password = password.value)
        login.execute(auth).onEach { dataState ->
            loading.value = dataState.loading

            dataState.data?.let { data ->
                user.value = data
            }

            dataState.error?.let { error ->
                Log.e(TAG, "getRecipe: ${error}")
                dialogQueue.appendErrorMessage("An Error Occurred", error)
            }
        }.launchIn(viewModelScope)
    }
}

HomeScreen.kt

@Composable
fun HomeScreen(

) {
    Card(
        modifier = Modifier.fillMaxSize()
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text(
                text = "HomeScreen",
                fontWeight = FontWeight.Bold,
                fontSize = 40.sp
            )
        }
    }
}

Press login button to try log in and receive user information, it's made to move on to the next screen.(HomeScreen) However, when log in and go to the next HomeScreen, the screen flashes and outputs the following message.

Background concurrent copying GC freed 821063(20MB) AllocSpace objects, 4(1176KB) LOS objects, 49% free, 19MB/38MB, paused 77us total 100.203ms
2021-11-01 11:51:04.493 32596-4299/com.woorimart.projectapp I/rt.woorimartap: Background concurrent copying GC freed 783275(19MB) AllocSpace objects, 3(960KB) LOS objects, 49% free, 21MB/43MB, paused 42us total 107.347ms
2021-11-01 11:51:12.030 32596-4299/com.woorimart.projectapp I/rt.woorimartap: Background concurrent copying GC freed 905429(22MB) AllocSpace objects, 3(768KB) LOS objects, 49% free, 23MB/46MB, paused 43us total 112.243ms
2021-11-01 11:51:15.313 32596-4299/com.woorimart.projectapp I/rt.woorimartap: Background concurrent copying GC freed 951843(23MB) AllocSpace objects, 3(1184KB) LOS objects, 48% free, 25MB/49MB, paused 38us total 114.812ms
2021-11-01 11:51:22.273 32596-4299/com.woorimart.projectapp I/rt.woorimartap: Background concurrent copying GC freed 1030020(25MB) AllocSpace objects, 2(872KB) LOS objects, 47% free, 26MB/50MB, paused 45us total 101.114ms
2021-11-01 11:51:36.202 32596-4299/com.woorimart.projectapp I/rt.woorimartap: Background concurrent copying GC freed 1035054(25MB) AllocSpace objects, 2(1008KB) LOS objects, 44% free, 30MB/54MB, paused 42us total 126.748ms
2021-11-01 11:51:38.349 32596-4299/com.woorimart.projectapp I/rt.woorimartap: Background concurrent copying GC freed 903031(22MB) AllocSpace objects, 3(3596KB) LOS objects, 41% free, 33MB/57MB, paused 40us total 127.925ms
2021-11-01 11:51:41.070 32596-4299/com.woorimart.projectapp I/rt.woorimartap: Background concurrent copying GC freed 975005(24MB) AllocSpace objects, 3(1584KB) LOS objects, 40% free, 35MB/59MB, paused 46us total 106.787ms

I looked it up on the Internet and found that it was if gson did not match, but when I printed the value in Log.d, the value was printed normally. If there is anything I need to add before moving on to the next screen, please let me know.


Edit. I've done some experiments. When I put the code of HomeScreen into LoginScreen and change the screen, freezing does not occur.

Like this,

...
            else if (!loading && user != null) {
                //Something to do.
                //navController.navigate(Screen.HomeScreen.route)
                Card(
                    modifier = Modifier.fillMaxSize()
                ) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center
                    ) {
                        Text(
                            text = "HomeScreen",
                            fontWeight = FontWeight.Bold,
                            fontSize = 40.sp
                        )
                    }
                }
            }else {
...

There seems to be a problem with Main Activity's code, but I can't guess what's wrong. Please let me know if you know.


Solution

  • That log is relatively normal, and likely has nothing to do with a flash. Java and Kotlin are both garbage collected languages. At intervals, the system will decide to free up any memory that's no longer being used. Switching between Activities is a common time for this, as you have a sudden need for memory for new objects (especially the new view hierarchy), and if the previous Activity is being finished it may be able to free most of it. These messages are just telling you the GC ran and what the status of it.

    So if you're seeing problems, its probably unrelated to these logs.

    If you want a further breakdown just for curiosity,

    Background concurrent copying GC freed 783275(19MB) AllocSpace objects, 3(960KB) LOS objects, 49% free, 21MB/43MB, paused 42us total 107.347ms
    

    The concurrent copying GC is a form of GC that Android started using with 8. Before that it used an older Mark and Sweep algorithm.

    It freed 783275 objects, making up 19 MB of space. Which is actually a huge number, and makes me curious why so many existed. It freed 3 objects in the Large Object Space (a pool of memory used for large objects). After it finished, your heap was 49% free (21 out of 43 MB). The operation took 107ms, and your app needed to be paused for 42 microseconds while some of the data was moved around.

    Other than the huge number of objects, that's all pretty standard. And the number of objects probably isn't concerning either, modern programming techniques use a lot of small objects.