Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีตรวจสอบพื้นที่ว่าง Internal/External Storage ใน Android

ภาพรวม

บทความนี้สาธิตวิธีตรวจสอบพื้นที่จัดเก็บที่ว่างอยู่ (Free Space) ของ Internal Storage และ External Storage ใน Android โดยใช้ StatFs (Legacy) และ StorageManager/StorageStatsManager (Modern API) พร้อมจัดการ Permission และ Scoped Storage

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

สร้างโปรเจกต์ Android ใหม่ใน Android Studio

ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/tvInternalStorage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textStyle="bold"
        android:text="Internal Storage: Calculating..." />

    <TextView
        android:id="@+id/tvExternalStorage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="16sp"
        android:textStyle="bold"
        android:text="External Storage: Calculating..." />

    <TextView
        android:id="@+id/tvAppSpecific"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="16sp"
        android:textStyle="bold"
        android:text="App Specific: Calculating..." />

    <Button
        android:id="@+id/btnRefresh"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:layout_gravity="center"
        android:text="🔄 Refresh" />

</LinearLayout>

ขั้นตอนที่ 3: MainActivity.java - รองรับทุก API Level

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.text.format.Formatter;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private TextView tvInternalStorage, tvExternalStorage, tvAppSpecific;
    private Button btnRefresh;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvInternalStorage = findViewById(R.id.tvInternalStorage);
        tvExternalStorage = findViewById(R.id.tvExternalStorage);
        tvAppSpecific = findViewById(R.id.tvAppSpecific);
        btnRefresh = findViewById(R.id.btnRefresh);

        btnRefresh.setOnClickListener(v -> checkStorageSpace());
        checkStorageSpace();
    }

    private void checkStorageSpace() {
        // 1. Internal Storage (App Data Directory)
        File internalDir = getFilesDir(); // /data/user/0/package/files
        String internalInfo = getStorageInfo(internalDir, "Internal (App)");
        tvInternalStorage.setText(internalInfo);

        // 2. External Storage (Public/Shared) - ต้อง Permission
        if (hasStoragePermission()) {
            File externalDir = Environment.getExternalStorageDirectory(); // /storage/emulated/0
            String externalInfo = getStorageInfo(externalDir, "External (Shared)");
            tvExternalStorage.setText(externalInfo);
        } else {
            tvExternalStorage.setText("External Storage: ต้องการ Permission");
            requestStoragePermission();
        }

        // 3. App-Specific External Storage (ไม่ต้อง Permission API 19+)
        File appExternalDir = getExternalFilesDir(null); // /storage/emulated/0/Android/data/package/files
        if (appExternalDir != null) {
            String appExternalInfo = getStorageInfo(appExternalDir, "External (App Specific)");
            tvAppSpecific.setText(appExternalInfo);
        } else {
            tvAppSpecific.setText("External (App Specific): ไม่พร้อมใช้งาน");
        }
    }

    private String getStorageInfo(File path, String label) {
        try {
            // ใช้ StatFs (ทำงานได้ทุก API)
            StatFs statFs = new StatFs(path.getPath());
            
            long blockSize, availableBlocks, totalBlocks;
            
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { // API 18+
                blockSize = statFs.getBlockSizeLong();
                availableBlocks = statFs.getAvailableBlocksLong();
                totalBlocks = statFs.getBlockCountLong();
            } else {
                // API เก่า
                blockSize = statFs.getBlockSize();
                availableBlocks = statFs.getAvailableBlocks();
                totalBlocks = statFs.getBlockCount();
            }

            long availableBytes = blockSize * availableBlocks;
            long totalBytes = blockSize * totalBlocks;
            long usedBytes = totalBytes - availableBytes;

            return String.format("%s\n  Free: %s / Total: %s (Used: %s)",
                label,
                Formatter.formatFileSize(this, availableBytes),
                Formatter.formatFileSize(this, totalBytes),
                Formatter.formatFileSize(this, usedBytes)
            );

        } catch (Exception e) {
            Log.e("StorageInfo", "Error getting storage info for " + path, e);
            return label + ": Error - " + e.getMessage();
        }
    }

    // ==================== Modern API (API 24+) ====================
    
    /**
     * ใช้ StorageManager สำหรับข้อมูลรายละเอียดมากขึ้น (API 24+)
     */
    private void checkStorageManager() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            StorageManager storageManager = getSystemService(StorageManager.class);
            if (storageManager != null) {
                List volumes = storageManager.getStorageVolumes();
                for (StorageVolume volume : volumes) {
                    // volume.getDescription(), volume.getPath(), volume.isPrimary(), volume.isRemovable()
                    // volume.createOpenDocumentTreeIntent() - สำหรับ Scoped Storage
                }
            }
        }
    }

    // ==================== Permission Handling ====================
    
    private boolean hasStoragePermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13+ (API 33)
            // ไม่ต้อง READ_EXTERNAL_STORAGE สำหรับ Media (Photo/Video/Audio)
            // แต่ต้องการสำหรับไฟล์อื่นๆ
            return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_GRANTED;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11+ (API 30)
            // Scoped Storage - ไม่ต้อง Permission สำหรับ App-Specific Directory
            // แต่ต้องการ MANAGE_EXTERNAL_STORAGE สำหรับ All Files Access
            return Environment.isExternalStorageManager() 
                || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
        } else {
            // Android 10 และก่อน
            return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
        }
    }

    private void requestStoragePermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            requestPermissions(new String[]{
                Manifest.permission.READ_MEDIA_IMAGES,
                Manifest.permission.READ_MEDIA_VIDEO,
                Manifest.permission.READ_MEDIA_AUDIO
            }, 1001);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // Android 11+ ใช้ MANAGE_EXTERNAL_STORAGE (Special Permission)
            try {
                Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.setData(android.net.Uri.parse("package:" + getPackageName()));
                startActivityForResult(intent, 1002);
            } catch (Exception e) {
                Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivityForResult(intent, 1002);
            }
        } else {
            requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1001);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1001) {
            checkStorageSpace();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1002) {
            checkStorageSpace();
        }
    }
}

ขั้นตอนที่ 4: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <!-- Permissions ตาม Android Version -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    <!-- Android 13+ (API 33+) -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    
    <!-- Android 11+ (API 30+) All Files Access -->
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" 
        tools:ignore="ScopedStorage" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:requestLegacyExternalStorage="true"> <!-- Android 10 Opt-out -->

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

สรุป Storage Types และการเข้าถึง

Storage TypePathPermissionScoped StorageUninstall Behavior
Internal (App)/data/user/0/pkg/filesไม่ต้องไม่เกี่ยวข้องลบหมด
Internal Cache/data/user/0/pkg/cacheไม่ต้องไม่เกี่ยวข้องลบหมด (อาจถูกระบบลบก่อน)
External App-Specific/sdcard/Android/data/pkg/filesไม่ต้อง (API 19+)ยกเว้นลบหมด
External Cache/sdcard/Android/data/pkg/cacheไม่ต้อง (API 19+)ยกเว้นลบหมด (อาจถูกลบก่อน)
External Public (Shared)/sdcard/Download, DCIM, etc.ต้อง Permissionบังคับ (API 29+)คงอยู่

Best Practices (Modern Android)

  • Scoped Storage (API 29+): ใช้ App-Specific Directory (getExternalFilesDir()) ไม่ต้อง Permission
  • MediaStore API: สำหรับรูป/วิดีโอ/เสียง ใช้ MediaStore แทน File API โดยตรง
  • Storage Access Framework (SAF): ใช้ ACTION_OPEN_DOCUMENT_TREE ให้ User เลือกโฟลเดอร์
  • MANAGE_EXTERNAL_STORAGE: ขอได้เฉพาะแอปที่จำเป็นจริง (File Manager, Antivirus, Backup) - Google Play จำกัดการใช้
  • Formatter.formatFileSize(): ใช้สำหรับแปลง Bytes เป็น Human-readable (KB, MB, GB)