2024-04-06

【Kotlin】查詢 Android 可用外部儲存空間

參考資料 ----


Android app,可儲存 app 自身專屬(用) 檔案於 內部儲存空間外部儲存空間,內部儲存空間很小,只適合存放小容量的檔案,若不是有特殊需求,官方建議存於外部儲存空間。

雖然是使用外部儲存空間,但因為僅供 app 自身專用,所以並不須向系統請求權限。

 
class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        // 查詢磁碟區狀態
        val sExternalStorage = Environment.getExternalStorageState()
        Log.d(TAG, "可用外部空間媒體資訊: $sExternalStorage")    // mounted: 可讀/寫
        // 狀態有: MEDIA_UNKNOWN, MEDIA_REMOVED, MEDIA_UNMOUNTED, MEDIA_CHECKING, MEDIA_NOFS, MEDIA_MOUNTED, MEDIA_MOUNTED_READ_ONLY, MEDIA_SHARED, MEDIA_BAD_REMOVAL, MEDIA_UNMOUNTABLE
  
        // 以前的 Android 因為 ROM 小, 多半會有 SD 卡插槽
        // 現在 ROM 變大了, 也漸漸不提供 SD 卡插槽了, 並模擬出一塊虛擬 ROM
        val externalStorageVolumes: Array<out File> = ContextCompat.getExternalFilesDirs(applicationContext, null)
        Log.d(TAG, "可用外部空間媒體: ${externalStorageVolumes.size} 個")    // 2 個, 1 個實體 SD 卡, 1 個虛擬 ROM
  
        for(xx in externalStorageVolumes.indices) {
            val primaryExternalStorage = externalStorageVolumes[xx]
            Log.d(TAG, "可用外部空間 $xx: $primaryExternalStorage")
            // 可用外部空間 0: /storage/emulated/0/Android/data/[您的 app 完整 package 名]/files, 虛擬 ROM 通常會是第 1 個
            // 可用外部空間 1: /storage/9E1E-5F1E/Android/data/[您的 app 完整 package 名]/files, 實體 SD 卡
        }
  
        if(Environment.getExternalStorageState()==Environment.MEDIA_MOUNTED) {
            // 有外部儲存空間媒體, 且可 讀/寫
            // 官方強烈建議, 若沒有特殊需要, 優先使用第 1 個可用外部空間
            val mFilePath = externalStorageVolumes[0]
            Log.d(TAG, "mFilePath: $mFilePath")    //   /storage/emulated/0/Android/data/[您的 app 完整 package 名]/files
            val mExtdir = File(getExternalFilesDir(null), "Chkspace")

            // 查詢可用空間
            val mScope = CoroutineScope(Job() + Dispatchers.IO)
            mScope.launch {
                var mBytes: Long = 0
                // 1. 由於在 Android8(Oreo) 推出新的 API26,
                // storageManager.getUuidForPath 不能在主執行緒呼叫, 因此要在 coroutine 內執行
                // 2. 舊的 API 在未來會被棄用, 所以要分開處理
                if(Build.VERSION.SDK_INT>=26) {
                    val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
                    val uuid = storageManager.getUuidForPath(externalStorageVolumes[0])
                    try {
                        mBytes = storageManager.getAllocatableBytes(uuid)
                    } catch(err: IOException) {
                        err.printStackTrace()
                    }
                } else {
                    val path = Environment.getDataDirectory()
                    val stat = StatFs(path.path)
                    val blockSize: Long = stat.blockSizeLong
                    val availableBlocks: Long = stat.availableBlocksLong
                    mBytes = availableBlocks * blockSize
                }
                val formatter = DecimalFormat("#,###")
                Log.d(TAG, "可用剩餘外部儲存空間 = ${formatter.format(mBytes)} ")

                if(mBytes>=1024) {
                    val mKB = (mBytes/1024) // 換算為 kb
                    if(mKB>=1024) {
                        val mmMB = (mKB/1024) // 換算為 mb
                        if(mmMB>=1024) {
                            val mGB = (mmMB/1024) // 換算為 gb
                            Log.d(TAG, "可用剩餘外部儲存空間 = $mGB gb")
                        } else
                            Log.d(TAG, "可用剩餘外部儲存空間 = $mmMB mb")
                    } else {
                        Log.d(TAG, "可用剩餘外部儲存空間 = $mKB kb")
                    }
                } else {
                    Log.d(TAG, "可用剩餘外部儲存空間 = $mBytes bytes")
                }
            }
        }
    }
}
 


沒有留言:

張貼留言