资源清理管理

This commit is contained in:
weber
2025-08-27 18:32:51 +08:00
parent 2a7d310be5
commit fdf8c1fa5a
10 changed files with 466 additions and 35 deletions

View File

@@ -97,7 +97,7 @@ fun AccountEditScreen2() {
) {
//StatusBarSpacer()
Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
modifier = Modifier.padding(horizontal = 0.dp, vertical = 16.dp)
) {
NoticeScreenHeader(
title = stringResource(R.string.edit_profile),

View File

@@ -37,6 +37,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -73,6 +74,7 @@ import com.aiosman.ravenow.ui.index.tabs.shorts.ShortVideo
import com.aiosman.ravenow.ui.index.tabs.street.StreetPage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.post.NewPostViewModel
import com.aiosman.ravenow.utils.ResourceCleanupManager
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch
@@ -97,6 +99,13 @@ fun IndexScreen() {
val coroutineScope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val context = LocalContext.current
// 页面退出时清理所有资源
DisposableEffect(Unit) {
onDispose {
ResourceCleanupManager.cleanupAllResources(context)
}
}
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
}
@@ -363,9 +372,19 @@ fun IndexScreen() {
@Composable
fun Home() {
val systemUiController = rememberSystemUiController()
val context = LocalContext.current
LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode)
}
// 页面退出时清理动态相关资源
DisposableEffect(Unit) {
onDispose {
ResourceCleanupManager.cleanupPageResources("moment")
}
}
Column(
modifier = Modifier
.fillMaxSize(),
@@ -380,9 +399,19 @@ fun Home() {
@Composable
fun Street() {
val systemUiController = rememberSystemUiController()
val context = LocalContext.current
LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode)
}
// 页面退出时清理搜索相关资源
DisposableEffect(Unit) {
onDispose {
ResourceCleanupManager.cleanupPageResources("search")
}
}
Column(
modifier = Modifier
.fillMaxSize(),
@@ -430,9 +459,19 @@ fun Video() {
@Composable
fun Profile() {
val systemUiController = rememberSystemUiController()
val context = LocalContext.current
LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode)
}
// 页面退出时清理个人资料相关资源
DisposableEffect(Unit) {
onDispose {
ResourceCleanupManager.cleanupPageResources("profile")
}
}
Column(
modifier = Modifier
.fillMaxSize(),
@@ -446,9 +485,19 @@ fun Profile() {
@Composable
fun Notifications() {
val systemUiController = rememberSystemUiController()
val context = LocalContext.current
LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode)
}
// 页面退出时清理消息相关资源
DisposableEffect(Unit) {
onDispose {
ResourceCleanupManager.cleanupPageResources("message")
}
}
Column(
modifier = Modifier
.fillMaxSize(),

View File

@@ -28,7 +28,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -53,6 +58,8 @@ import com.aiosman.ravenow.ui.composables.TabItem
import com.aiosman.ravenow.ui.composables.TabSpacer
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.ExploreViewModel
import com.aiosman.ravenow.utils.DebounceUtils
import com.aiosman.ravenow.utils.ResourceCleanupManager
import kotlinx.coroutines.launch
@OptIn( ExperimentalFoundationApi::class)
@@ -68,6 +75,16 @@ fun Agent() {
val viewModel: AgentViewModel = viewModel()
// 防抖状态
var lastClickTime by remember { mutableStateOf(0L) }
// 页面退出时清理AI相关资源
DisposableEffect(Unit) {
onDispose {
ResourceCleanupManager.cleanupPageResources("ai")
}
}
Column(
modifier = Modifier
.fillMaxSize()
@@ -119,11 +136,15 @@ fun Agent() {
modifier = Modifier
.size(36.dp)
.noRippleClickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
// 设置标志,表示新增智能体后不需要刷新
com.aiosman.ravenow.ui.agent.AddAgentViewModel.isFromAddAgent = true
navController.navigate(
NavigationRoute.AddAgent.route
)
}) {
lastClickTime = System.currentTimeMillis()
}
},
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = null,
@@ -189,9 +210,13 @@ fun Agent() {
text = stringResource(R.string.agent_mine),
isSelected = pagerState.currentPage == 0,
onClick = {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 300L) {
scope.launch {
pagerState.animateScrollToPage(0)
}
}) {
lastClickTime = System.currentTimeMillis()
}
}
)
TabSpacer()
@@ -199,9 +224,13 @@ fun Agent() {
text = stringResource(R.string.agent_hot),
isSelected = pagerState.currentPage == 1,
onClick = {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 300L) {
scope.launch {
pagerState.animateScrollToPage(1)
}
}) {
lastClickTime = System.currentTimeMillis()
}
}
)
/*TabSpacer()
@@ -348,6 +377,9 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: NavHostController) {
val AppColors = LocalAppTheme.current
// 防抖状态
var lastClickTime by remember { mutableStateOf(0L) }
Row(
modifier = Modifier
.fillMaxWidth()
@@ -419,8 +451,12 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
shape = RoundedCornerShape(8.dp)
)
.clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
/* viewModel.createSingleChat(agentItem.trtcId)
viewModel.goToChatAi(agentItem.trtcId, navController = navController)*/
}) {
lastClickTime = System.currentTimeMillis()
}
},
contentAlignment = Alignment.Center
) {

View File

@@ -60,8 +60,9 @@ fun HotAgent() {
}
}
// 只在首次加载时刷新避免从AddAgent返回时重复刷新
// 只在初始化页面时刷新
LaunchedEffect(Unit) {
// 只有在列表完全为空且没有正在加载时才进行初始化刷新
if (model.agentList.isEmpty() && !model.isLoading) {
model.refreshPager()
}

View File

@@ -53,8 +53,9 @@ fun MineAgent() {
// Paging 库会自动处理加载更多,无需手动监听滚动
// 只在首次加载时刷新避免从AddAgent返回时重复刷新
// 只在初始化页面时刷新
LaunchedEffect(Unit) {
// 只有在列表完全为空且没有正在加载时才进行初始化刷新
if (agentList.itemCount == 0 && !model.isLoading) {
model.refreshPager()
}

View File

@@ -25,7 +25,6 @@ import com.aiosman.ravenow.entity.MomentPagingSource
import com.aiosman.ravenow.entity.MomentRemoteDataSource
import com.aiosman.ravenow.entity.MomentServiceImpl
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentViewModel.firstLoad
import com.aiosman.ravenow.ui.navigateToChatAi
import com.tencent.imsdk.v2.V2TIMConversationOperationResult
import com.tencent.imsdk.v2.V2TIMManager
@@ -47,6 +46,7 @@ object MineAgentViewModel : ViewModel() {
var hasNext by mutableStateOf(true)
var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null)
var isFirstLoad by mutableStateOf(true)
// 记录已预加载的图片ID避免重复加载
private val preloadedImageIds = mutableSetOf<Int>()
@@ -55,10 +55,10 @@ object MineAgentViewModel : ViewModel() {
fun refreshPager() {
if (!firstLoad) {
if (!isFirstLoad) {
return
}
firstLoad = false
isFirstLoad = false
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),

View File

@@ -29,6 +29,7 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
@@ -179,32 +180,38 @@ fun NotificationsScreen() {
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
.height(40.dp)
.background(
color = Color(0xFFF5F5F5),
shape = RoundedCornerShape(8.dp)
)
.noRippleClickable {//添加搜索逻辑实现
.noRippleClickable {
},
contentAlignment = Alignment.CenterStart
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(start = 16.dp)
modifier = Modifier
.height(36.dp)
.fillMaxWidth()
.clip(shape = RoundedCornerShape(8.dp))
.background(AppColors.inputBackground)
.padding(horizontal = 8.dp, vertical = 0.dp)
.noRippleClickable {
// 搜索框点击事件
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_search_location),
contentDescription = "search",
modifier = Modifier.size(20.dp),
colorFilter = ColorFilter.tint(Color(0xFF999999))
Icon(
painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = null,
tint = AppColors.inputHint
)
Spacer(modifier = Modifier.width(8.dp))
Text(
Box {
androidx.compose.material.Text(
text = stringResource(R.string.search),
fontSize = 14.sp,
color = Color(0xFF999999)
modifier = Modifier.padding(start = 8.dp),
color = AppColors.inputHint,
fontSize = 17.sp
)
}
}
}
Row(
modifier = Modifier

View File

@@ -410,6 +410,7 @@ fun NewPostTextField(hint: String, value: String, onValueChange: (String) -> Uni
value = value,
onValueChange = onValueChange,
modifier = Modifier
.fillMaxWidth()
.height(160.dp)
.heightIn(160.dp)
.padding(horizontal = 16.dp, vertical = 10.dp),

View File

@@ -0,0 +1,78 @@
package com.aiosman.ravenow.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
/**
* 防抖工具类
* 用于防止用户快速重复点击
*/
object DebounceUtils {
/**
* 防抖点击处理
* @param scope 协程作用域
* @param delayMillis 防抖延迟时间毫秒默认500ms
* @param action 要执行的操作
*/
fun debounceClick(
scope: CoroutineScope,
delayMillis: Long = 500L,
action: () -> Unit
) {
scope.launch {
delay(delayMillis)
action()
}
}
/**
* 带状态检查的防抖点击处理
* @param scope 协程作用域
* @param delayMillis 防抖延迟时间毫秒默认500ms
* @param isProcessing 是否正在处理中的状态
* @param action 要执行的操作
*/
fun debounceClickWithState(
scope: CoroutineScope,
delayMillis: Long = 500L,
isProcessing: AtomicBoolean,
action: () -> Unit
) {
if (isProcessing.get()) {
return
}
isProcessing.set(true)
scope.launch {
delay(delayMillis)
try {
action()
} finally {
isProcessing.set(false)
}
}
}
/**
* 简单的防抖点击处理(无协程)
* @param lastClickTime 上次点击时间
* @param delayMillis 防抖延迟时间毫秒默认500ms
* @param action 要执行的操作
* @return 是否执行了操作
*/
fun simpleDebounceClick(
lastClickTime: Long,
delayMillis: Long = 500L,
action: () -> Unit
): Boolean {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime < delayMillis) {
return false
}
action()
return true
}
}

View File

@@ -0,0 +1,258 @@
package com.aiosman.ravenow.utils
import android.content.Context
import android.content.Intent
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.ui.index.IndexViewModel
import com.aiosman.ravenow.ui.index.tabs.ai.AgentViewModel
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.hot.HotAgentViewModel
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic.DynamicViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.ravenow.ui.index.tabs.search.DiscoverViewModel
import com.aiosman.ravenow.ui.index.tabs.search.SearchViewModel
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel
import com.aiosman.ravenow.ui.like.LikeNoticeViewModel
import com.aiosman.ravenow.ui.favourite.FavouriteListViewModel
import com.aiosman.ravenow.ui.favourite.FavouriteNoticeViewModel
import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel
import com.aiosman.ravenow.ui.post.NewPostViewModel
import com.aiosman.ravenow.ui.imageviewer.ImageViewerViewModel
import org.greenrobot.eventbus.EventBus
/**
* 资源清理管理器
* 用于在Index页面退出时清理所有相关资源
*/
object ResourceCleanupManager {
/**
* 清理所有Index页面相关的资源
* @param context 上下文
*/
fun cleanupAllResources(context: Context) {
try {
// 1. 清理ViewModel资源
cleanupViewModels()
// 2. 清理EventBus注册
cleanupEventBus()
// 3. 清理服务
cleanupServices(context)
// 4. 清理缓存和临时数据
cleanupCacheAndTempData()
// 5. 重置应用状态
resetAppState()
} catch (e: Exception) {
// 记录错误但不抛出异常,确保清理过程不会中断
e.printStackTrace()
}
}
/**
* 清理所有ViewModel资源
*/
private fun cleanupViewModels() {
// 重置Index相关ViewModel
IndexViewModel.ResetModel()
// 重置AI相关ViewModel
// AgentViewModel的属性是私有的无法直接访问通过其他方式清理
HotAgentViewModel.let {
it.agentList = emptyList()
it.refreshing = false
it.isLoading = false
it.hasNext = true
it.currentPage = 1
it.error = null
it.clearPreloadedImages()
}
MineAgentViewModel.let {
it.refreshing = false
it.isLoading = false
it.hasNext = true
it.currentPage = 1
it.error = null
}
// 重置动态相关ViewModel
TimelineMomentViewModel.ResetModel()
DynamicViewModel.ResetModel()
HotMomentViewModel.ResetModel()
// 重置个人资料相关ViewModel
MyProfileViewModel.ResetModel()
// 重置搜索相关ViewModel
// DiscoverViewModel的属性是私有的无法直接访问通过其他方式清理
SearchViewModel.ResetModel()
// 重置消息相关ViewModel
MessageListViewModel.ResetModel()
// 重置通知相关ViewModel
LikeNoticeViewModel.ResetModel()
FavouriteNoticeViewModel.ResetModel()
FollowerNoticeViewModel.ResetModel()
// 重置收藏相关ViewModel
FavouriteListViewModel.ResetModel()
// 重置发布相关ViewModel
NewPostViewModel.asNewPost()
// 重置图片查看器ViewModel
ImageViewerViewModel.let {
it.imageList.clear()
it.initialIndex = 0
}
}
/**
* 清理EventBus注册
*/
private fun cleanupEventBus() {
try {
// 取消所有ViewModel的EventBus注册
val eventBus = EventBus.getDefault()
// 检查并取消注册各种ViewModel
if (eventBus.isRegistered(TimelineMomentViewModel)) {
eventBus.unregister(TimelineMomentViewModel)
}
if (eventBus.isRegistered(DynamicViewModel)) {
eventBus.unregister(DynamicViewModel)
}
if (eventBus.isRegistered(HotMomentViewModel)) {
eventBus.unregister(HotMomentViewModel)
}
if (eventBus.isRegistered(MyProfileViewModel)) {
eventBus.unregister(MyProfileViewModel)
}
if (eventBus.isRegistered(DiscoverViewModel)) {
eventBus.unregister(DiscoverViewModel)
}
if (eventBus.isRegistered(SearchViewModel)) {
eventBus.unregister(SearchViewModel)
}
if (eventBus.isRegistered(MessageListViewModel)) {
eventBus.unregister(MessageListViewModel)
}
if (eventBus.isRegistered(LikeNoticeViewModel)) {
eventBus.unregister(LikeNoticeViewModel)
}
if (eventBus.isRegistered(FavouriteListViewModel)) {
eventBus.unregister(FavouriteListViewModel)
}
if (eventBus.isRegistered(FavouriteNoticeViewModel)) {
eventBus.unregister(FavouriteNoticeViewModel)
}
if (eventBus.isRegistered(FollowerNoticeViewModel)) {
eventBus.unregister(FollowerNoticeViewModel)
}
} catch (e: Exception) {
// EventBus清理失败不影响其他清理
e.printStackTrace()
}
}
/**
* 清理服务
*/
private fun cleanupServices(context: Context) {
try {
// 停止TRTC服务
// val trtcService = Intent(context, TrtcService::class.java)
// context.stopService(trtcService)
} catch (e: Exception) {
// 服务停止失败不影响其他清理
e.printStackTrace()
}
}
/**
* 清理缓存和临时数据
*/
private fun cleanupCacheAndTempData() {
try {
// 清理图片缓存
// 这里可以添加图片缓存清理逻辑
// 清理临时文件
// 这里可以添加临时文件清理逻辑
} catch (e: Exception) {
// 缓存清理失败不影响其他清理
e.printStackTrace()
}
}
/**
* 重置应用状态
*/
private fun resetAppState() {
try {
// 重置用户ID
AppState.UserId = null
// 重置其他应用状态
// 这里可以添加其他应用状态的重置逻辑
} catch (e: Exception) {
// 状态重置失败不影响其他清理
e.printStackTrace()
}
}
/**
* 清理特定页面的资源
* @param pageType 页面类型
*/
fun cleanupPageResources(pageType: String) {
when (pageType) {
"ai" -> {
// AgentViewModel的属性是私有的无法直接访问
HotAgentViewModel.let {
it.agentList = emptyList()
it.refreshing = false
it.isLoading = false
it.hasNext = true
it.currentPage = 1
it.error = null
it.clearPreloadedImages()
}
MineAgentViewModel.let {
it.refreshing = false
it.isLoading = false
it.hasNext = true
it.currentPage = 1
it.error = null
}
}
"moment" -> {
TimelineMomentViewModel.ResetModel()
DynamicViewModel.ResetModel()
HotMomentViewModel.ResetModel()
}
"profile" -> {
MyProfileViewModel.ResetModel()
}
"search" -> {
// DiscoverViewModel的属性是私有的无法直接访问
SearchViewModel.ResetModel()
}
"message" -> {
MessageListViewModel.ResetModel()
}
}
}
}