用“存储空间管理”来优化用户体验

使用存储空间管理来优化用户体验

应用管理详情

用户在感觉手机空间被APP占用较大时候,会查看APP的应用占用磁盘详情,可以在设置的应用管理找到当前APP然后查看。

  • 应用大小:安装APK解压后占用磁盘空间
  • 缓存:指data/data/{package-name}/cachesdcard/Android/{package-name}/cache占用磁盘大小
  • 用户数据:指data/data/{package-name}/sdcard/Android/{package-name}/减去缓存磁盘占用后的大小,可能不同厂商定制后的计算有所不同。

管理储存空间

app安装后要恢复到安装时候的状态有2种方式,卸载重新安装或者打开设置找到APP的应用管理-> 清除数据 -> 清除全部数据/清除缓存。而在日常使用中发现有个特别的情况,在TG和bilibili的应用在选择清除数据时候没有清除全部数据的选项,而是管理空间(在piexl7Pro 上还是clear data)点击后会打开APP自己的一个缓存管理定制页面,在这里可以自由的清理一些开发者允许的数据。

定制存储空间管理

其实在google官方文档中有专门对这个功能的介绍,只是平常开发中并没有产品对此有特别需求,我们就很少知道了。 developer.android.com/training/da...

让我们跟着文档学习下如何定制一个存储空间管理的页面吧。

  1. 创建并在Manifest中注册一个普通Activity或者是你APP中已经存在的管理和缓存清理的Activity,不需要添加任何特殊属性。当您的 activity 将 android:exported 设置为 false 时,仍然可以调用。

    xml 复制代码
    <application
     ...
     android:name=".app.DemoApplication"
     android:theme="@style/AppTheme">
     ...
     <activity
         android:name=".screen.ManageSpaceActivity"
         android:exported="false"/>
  2. 在application的标签中添加android:manageSpaceActivity并声明上面的Activity路径

    xml 复制代码
    <application
         android:name=".app.DemoApplication"
         ...
         android:manageSpaceActivity=".screen.ManageSpaceActivity"
         android:theme="@style/AppTheme">
  3. 安装APP在设置中找到应用详情,点击清理数据 -> 管理空间

用Compose + MVI 快速实现存储空间管理页面

  1. 使用Compose编写UI,并且提供一个按钮去清理空间, PieChart源码查看后面github link

    kotlin 复制代码
     class ManageSpaceActivity : AppCompatActivity() {
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContent {
                 MVIHiltTheme {
                     ManageSpaceScreen()
                 }
             }
         }
     }
    
    @OptIn(ExperimentalMaterial3Api::class)
    @Preview
    @Composable
    fun ManageSpaceScreen() {
        val scaffoldState = remember { SnackbarHostState() }
        val viewModel = viewModel<ManageSpaceViewModel>()
        SideEffect {
            viewModel.sendAction(ManageSpaceAction.LoadSpaceData)
        }
        val lifecycleOwner = LocalLifecycleOwner.current
        LaunchedEffect(viewModel.effect) {
            lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.effect.collect {
                    if (it is ManageSpaceEffect.ShowToast) {
                        scaffoldState.showSnackbar(it.content)
                    }
                }
            }
        }
        Scaffold(modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(scaffoldState) }, floatingActionButton = {
            if (viewModel.state.value is ManageSpaceState.StorageSpace) {
                FloatingActionButton(onClick = {
                    scaffoldState.currentSnackbarData?.dismiss()
                    viewModel.sendAction(ManageSpaceAction.ClearCache)
                }) {
                    Icon(Icons.Outlined.Delete, contentDescription = "")
                }
            }
        }) {
            when (val spaceState = viewModel.state.value) {
                ManageSpaceState.Loading -> CircularProgressIndicator()
                is ManageSpaceState.StorageSpace -> Box(
                    modifier = Modifier
                        .padding(it)
                        .fillMaxSize(), contentAlignment = Alignment.Center
                ) {
                    val point = listOf(
                        spaceState.apkBytes.toFloat(), spaceState.dataBytes.toFloat(), spaceState.cacheBytes.toFloat()
                    )
                    val labels = listOf(
                        "应用大小:${spaceState.apkSize}", "用户数据:${spaceState.dataSize}", "缓存:${spaceState.cacheSize}"
                    )
                    val color = listOf(Color.Magenta, Color.Green, Color.Gray)
                    PieChart(color, point, labels)
                }
            }
        }
    }
  2. 定义MVI中的model和Intent

    kotlin 复制代码
    sealed class ManageSpaceAction {
        object LoadSpaceData : ManageSpaceAction()
        object ClearCache : ManageSpaceAction()
    }
    
    sealed class ManageSpaceState {
        object Loading : ManageSpaceState()
        data class StorageSpace(
            val apkBytes: Long,
            val dataBytes: Long,
            val cacheBytes: Long,
            val apkSize: String,
            val dataSize: String,
            val cacheSize: String,
        ) : ManageSpaceState()
    }
    
    sealed class ManageSpaceEffect {
        data class ShowToast(val content: String) : ManageSpaceEffect()
    }
  3. 给ViewModel添加一个repository来获取数据,并且管理M和I

    kotlin 复制代码
    class ManageSpaceViewModel constructor(
        val context: Application,
    ) : AndroidViewModel(context) {
        private val repository: ManageSpaceRepository = ManageSpaceRepository(context)
    
        private val _viewState: MutableState<ManageSpaceState> = mutableStateOf(ManageSpaceState.Loading)
        val state: State<ManageSpaceState> = _viewState
    
        private val _effect = MutableSharedFlow<ManageSpaceEffect>()
        val effect: SharedFlow<ManageSpaceEffect> by lazy { _effect.asSharedFlow() }
    
        fun sendAction(action: ManageSpaceAction) {
            when (action) {
                ManageSpaceAction.LoadSpaceData -> {
                    viewModelScope.launch {
                        withContext(Dispatchers.IO) {
                            repository.getAppSize()
                        }.onSuccess {
                            _viewState.value = it
                        }.onFailure {
                            _effect.tryEmit(ManageSpaceEffect.ShowToast("free space load error"))
                        }
                    }
                }
    
                ManageSpaceAction.ClearCache -> {
                    viewModelScope.launch {
                        _viewState.value = (ManageSpaceState.Loading)
                        withContext(Dispatchers.IO) {
                            repository.clearCache()
                            repository.getAppSize()
                        }.onSuccess {
                            _viewState.value = it
                        }.onFailure {
                            _effect.tryEmit(ManageSpaceEffect.ShowToast("space clear error"))
                        }
                    }
                }
            }
        }
    }
  4. repository通过StorageStatsManager来获取APP的所有信息和清理缓存

    kotlin 复制代码
    class ManageSpaceRepository(private val context: Context) {
        private val filesDir: File
            get() = context.filesDir
    
        fun getAppSize() = kotlin.runCatching {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val storageStatsManager = context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager
                val uid = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA).uid
                val storageStats = storageStatsManager.queryStatsForUid(StorageManager.UUID_DEFAULT, uid)
                ManageSpaceState.StorageSpace(
                    dataBytes = storageStats.dataBytes,
                    cacheBytes = storageStats.cacheBytes,
                    apkBytes = storageStats.appBytes,
                    dataSize = Formatter.formatFileSize(context, storageStats.dataBytes),
                    cacheSize = Formatter.formatFileSize(context, storageStats.cacheBytes),
                    apkSize = Formatter.formatFileSize(context, storageStats.appBytes),
                )
            } else {
                TODO()
            }
        }
    
        fun clearCache() = kotlin.runCatching {
            val time = measureTimeMillis {
                filesDir.deleteRecursively()
            }
            println(2500 - time)
        }
    }
  5. 运行查看下

Github

github.com/forJrking/M...

相关推荐
Kapaseker13 小时前
详解 Compose background 的重组陷阱
android·kotlin
黄林晴14 小时前
Kotlin 2.3.20-RC2 来了!JPA 开发者狂喜,6 大更新一文速览
android·kotlin
糖猫猫cc1 天前
Kite:填充处理器
kotlin·orm·kite
Kapaseker2 天前
一杯美式深入理解 data class
android·kotlin
alexhilton4 天前
端侧RAG实战指南
android·kotlin·android jetpack
Kapaseker5 天前
2026年,我们还该不该学编程?
android·kotlin
Kapaseker6 天前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
BoomHe6 天前
Now in Android 架构模式全面分析
android·android jetpack
Kapaseker7 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
FunnySaltyFish7 天前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack