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

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

应用管理详情

用户在感觉手机空间被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...

相关推荐
帅次7 小时前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
夏非夏2 天前
Kotlin jetpack MVP
android·kotlin
zhangphil2 天前
Kotlin约束泛型参数必须继承自某个父类
kotlin
ch_kexin2 天前
Android kotlin integer-array 存放图片资源ID
android·开发语言·kotlin
IAM四十二2 天前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
jiay23 天前
Kotlin-面向对象之构造函数、实例化和初始化
android·开发语言·kotlin
我怀里的猫3 天前
glide ModelLoader的Key错误使用 可能造成的内存泄漏
android·kotlin·glide
陟彼高冈yu3 天前
第10天:Fragments(碎片)使用-补充材料——‘MainActivity.kt‘解读
android·kotlin·android studio
姑苏风3 天前
《Kotlin实战》-第11章:DSL构建
android·开发语言·kotlin
Wgllss4 天前
那些大厂架构师是怎样封装网络请求的?
android·架构·android jetpack