登录时后台并发复制GC
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")
}
登录Screen.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)
}
}
首页Screen.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
)
}
}
}
按登录按钮尝试登录并接收用户信息,然后转到下一个屏幕。(HomeScreen)
但是,当登录并转到下一个主屏幕时,屏幕闪烁并输出以下消息。
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
在网上查了下,发现是if gson不匹配,但是打印[=43=中的值时,打印值正常。
如果在进入下一个屏幕之前我需要添加任何内容,请告诉我。
编辑。
我做了一些实验。
当我把HomeScreen的代码放到LoginScreen里面换屏的时候,没有卡顿。
像这样,
...
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 {
...
Main Activity 的代码似乎有问题,但我猜不出哪里出了问题。知道的请告诉我
该日志比较正常,应该与闪退无关。 Java 和 Kotlin 都是垃圾回收语言。每隔一段时间,系统将决定释放不再使用的所有内存。在 Activity 之间切换是一个常见的时间,因为你突然需要新对象的内存(尤其是新的视图层次结构),如果之前的 Activity 正在完成,它可能会释放大部分.这些消息只是告诉您 GC 运行 及其状态。
因此,如果您发现问题,可能与这些日志无关。
如果你只是出于好奇想要进一步细分,
Background concurrent copying GC freed 783275(19MB) AllocSpace objects, 3(960KB) LOS objects, 49% free, 21MB/43MB, paused 42us total 107.347ms
并发复制 GC 是 Android 从 8 开始使用的一种 GC 形式。在此之前,它使用较旧的标记和清除算法。
它释放了 783275 个对象,占 space 的 19 MB。这实际上是一个巨大的数字,让我很好奇为什么会有这么多。它释放了 Large Object Space(用于大型对象的内存池)中的 3 个对象。完成后,您的堆有 49% 空闲(43 MB 中的 21 个)。该操作耗时 107 毫秒,您的应用需要暂停 42 微秒,同时移动部分数据。
除了物件数量多以外,其他都很一般。对象的数量可能也不是问题,现代编程技术使用很多小对象。
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")
}
登录Screen.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)
}
}
首页Screen.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
)
}
}
}
按登录按钮尝试登录并接收用户信息,然后转到下一个屏幕。(HomeScreen) 但是,当登录并转到下一个主屏幕时,屏幕闪烁并输出以下消息。
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
在网上查了下,发现是if gson不匹配,但是打印[=43=中的值时,打印值正常。 如果在进入下一个屏幕之前我需要添加任何内容,请告诉我。
编辑。 我做了一些实验。 当我把HomeScreen的代码放到LoginScreen里面换屏的时候,没有卡顿。
像这样,
...
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 {
...
Main Activity 的代码似乎有问题,但我猜不出哪里出了问题。知道的请告诉我
该日志比较正常,应该与闪退无关。 Java 和 Kotlin 都是垃圾回收语言。每隔一段时间,系统将决定释放不再使用的所有内存。在 Activity 之间切换是一个常见的时间,因为你突然需要新对象的内存(尤其是新的视图层次结构),如果之前的 Activity 正在完成,它可能会释放大部分.这些消息只是告诉您 GC 运行 及其状态。
因此,如果您发现问题,可能与这些日志无关。
如果你只是出于好奇想要进一步细分,
Background concurrent copying GC freed 783275(19MB) AllocSpace objects, 3(960KB) LOS objects, 49% free, 21MB/43MB, paused 42us total 107.347ms
并发复制 GC 是 Android 从 8 开始使用的一种 GC 形式。在此之前,它使用较旧的标记和清除算法。
它释放了 783275 个对象,占 space 的 19 MB。这实际上是一个巨大的数字,让我很好奇为什么会有这么多。它释放了 Large Object Space(用于大型对象的内存池)中的 3 个对象。完成后,您的堆有 49% 空闲(43 MB 中的 21 个)。该操作耗时 107 毫秒,您的应用需要暂停 42 微秒,同时移动部分数据。
除了物件数量多以外,其他都很一般。对象的数量可能也不是问题,现代编程技术使用很多小对象。