返回

应用移至SD卡:安卓存储难题及解决之道

Android

移动应用至SD卡:疑难解答

设备存储空间的不足经常困扰用户,将应用移动到SD卡成为一个常见的需求。然而,这个过程并不总是像预期那样简单直接。本文将深入探讨移动应用至SD卡涉及的问题,并提供有效的解决方案。

问题根源:权限与兼容性

应用能否被移动到SD卡,核心问题在于Android系统如何处理安装包位置以及相关权限。早期Android版本通常允许用户直接移动大部分应用至SD卡,但随着系统演进,安全性考虑占据了主导地位。未经授权的访问外部存储可能会引发隐私风险。

部分设备制造商的定制系统也可能影响应用的移动策略。这意味着即使你的应用支持移动到SD卡,也未必能在所有设备上实现。

权限不足是限制应用移动至SD卡的直接原因。应用如果需要操作外部存储(即SD卡),则必须在 AndroidManifest.xml 中声明对应的权限:WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE。但如问题中所述,直接申请这些权限可能会使用户产生疑虑,并增加应用在应用商店被标记为“需要敏感权限”的风险。

解决方案:合理使用存储权限

方法一:仅限用户手动移动

一个常见的误解是应用必须声明外部存储读写权限才能被用户手动移动到SD卡。事实上,系统允许用户通过"设置">“应用”>“存储” 来将部分应用迁移到SD卡,只要应用本身支持且系统允许,即使应用未声明上述存储权限。这种方法无需额外代码或权限申请,它依赖于用户的主动操作,较为稳妥。

方法二:声明更精细的存储权限

对于应用确实需要操作SD卡上的文件(比如,导出备份文件,保存用户下载的内容),可以考虑使用 MANAGE_EXTERNAL_STORAGE 或者更细粒度的存储权限,如 Android 13 引入的 "photos and videos", “audio” 权限,以此获得更细致的权限控制,并向用户透明的告知应用的意图。 使用这些权限可以让用户对授权请求更加信任,并可能提升应用的接受度。

声明 MANAGE_EXTERNAL_STORAGE 权限需谨慎。此权限拥有广泛的文件访问权限,用户对其通常较为敏感,因此应用只有在绝对需要这种权限的情况下才能申请。

使用方式示例 (AndroidManifest.xml):

<manifest ...>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
            tools:ignore="ScopedStorage"/>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>

    ...
</manifest>

代码请求权限示例(Kotlin):

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.activity.result.contract.ActivityResultContracts

class MainActivity : AppCompatActivity(){
    private val requestPermissionLauncher =
            registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()){
        isGranted :Map<String,Boolean> ->
           val allPermissionsGranted = isGranted.values.all{it}
             if(allPermissionsGranted) {
                  //Permissions Granted, do work here
            } else {
               //permission denied, tell the user.
            }
            }


       fun requestRequiredPermissions(){
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val permissionsToRequest = arrayOf(
             Manifest.permission.READ_MEDIA_IMAGES,
            Manifest.permission.READ_MEDIA_AUDIO,
              Manifest.permission.READ_MEDIA_VIDEO)
          requestPermissionLauncher.launch(permissionsToRequest)
          }else {

             if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED ) {

                  ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
                    ,REQUEST_PERMISSION_CODE)
                } else {

               }

            }


        }


      //For  MANAGE_EXTERNAL_STORAGE check.
    fun checkStoragePermission(): Boolean{

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

          return Environment.isExternalStorageManager()

        } else{

           return   ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
              && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;


        }
    }



        fun  openManageExternalStoragePermissionDialog(){
       if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
            startActivity(Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
                    .setData(Uri.parse("package:${application.packageName}")))

        }


}

操作步骤
1. 修改 AndroidManifest.xml 文件, 添加相应的权限声明。
2. 在应用的逻辑中,通过检查权限授予状态,并在必要时请求权限。
3. 对于Android 11 以上版本(SDK 30+), 考虑使用更为灵活的 “Scoped Storage” 机制,并使用 Media Store APIs,避免直接的文件路径访问。
4. 若要请求MANAGE_EXTERNAL_STORAGE 权限,请在确认用户需要此功能时引导用户打开系统设置授权页面。

额外的安全建议

  • 最小化权限需求 : 始终请求最小化权限集, 只获取应用运行所必需的权限。 避免申请可能被用户视为侵入的权限。
  • 用户解释 : 当应用需要存储权限时,应清楚解释原因,使整个过程对用户更加透明。
  • 安全的文件操作 : 对从SD卡读取或写入的文件执行适当的安全检查, 确保没有潜在的安全风险。避免应用尝试读写其他应用创建的文件。

合理地使用权限管理,能够保证用户的数据安全,提高应用的整体质量。根据用户实际需求,谨慎的选择,并结合透明的用户沟通,是处理这类问题的最佳方式。