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

วิธีเขียนไฟล์รูปภาพลง External Storage พร้อม Runtime Permission ใน Android

ภาพรวม

ตัวอย่างนี้สาธิตวิธีบันทึกรูปภาพลง External Storage (หน่วยความจำภายนอก) พร้อมการขอ Runtime Permission สำหรับ Android 6.0 (API 23) ขึ้นไป

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

Android Studio → File → New Project → กรอกรายละเอียด

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

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

    <Button
        android:id="@+id/save"
        android:text="Save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

ขั้นตอนที่ 3: เขียน MainActivity.java

package com.example.andy.myapplication;

import android.annotation.TargetApi;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;

public class MainActivity extends AppCompatActivity {
    private static final int PERMISSION_REQUEST_CODE = 100;
    Button save;
    Bitmap bitmap;

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

        // โหลดรูปจาก Drawable
        Drawable drawable = getResources().getDrawable(R.drawable.mario);
        bitmap = ((BitmapDrawable) drawable).getBitmap();

        save = findViewById(R.id.save);
        save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String state = Environment.getExternalStorageState();
                
                // ตรวจสอบว่า External Storage พร้อมใช้งาน
                if (Environment.MEDIA_MOUNTED.equals(state)) {
                    if (Build.VERSION.SDK_INT >= 23) {
                        // Android 6.0+ ต้องขอ Runtime Permission
                        if (checkPermission()) {
                            saveImageToExternalStorage();
                        } else {
                            requestPermission();
                        }
                    } else {
                        // Android 5.1 ลงมา ไม่ต้องขอ Permission
                        saveImageToExternalStorage();
                    }
                } else {
                    Toast.makeText(this, "External Storage ไม่พร้อมใช้งาน", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private void saveImageToExternalStorage() {
        String path = Environment.getExternalStorageDirectory().toString();
        File file = new File(path, "UniqueFileName.jpg");
        
        if (!file.exists()) {
            Log.d("path", file.toString());
            try {
                FileOutputStream fos = new FileOutputStream(file);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                fos.flush();
                fos.close();
                Toast.makeText(this, "บันทึกรูปภาพสำเร็จ", Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this, "บันทึกล้มเหลว: " + e.getMessage(), Toast.LENGTH_SHORT).show();
            }
        }
    }

    private boolean checkPermission() {
        int result = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
        return result == PackageManager.PERMISSION_GRANTED;
    }

    private void requestPermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            Toast.makeText(this, "ต้องการสิทธิ์เขียน External Storage เพื่อบันทึกไฟล์ กรุณาอนุญาตใน Settings", Toast.LENGTH_LONG).show();
        } else {
            ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.e("value", "Permission Granted, Now you can use local drive");
                saveImageToExternalStorage();
            } else {
                Log.e("value", "Permission Denied, You cannot use local drive");
                Toast.makeText(this, "ไม่ได้รับอนุญาต ไม่สามารถบันทึกไฟล์ได้", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

ขั้นตอนที่ 4: กำหนดสิทธิ์ใน AndroidManifest.xml

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

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <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">
        <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>

การทดสอบและผลลัพธ์

วิธีเขียนไฟล์รูปภาพลง External Storage พร้อม Runtime Permission ใน Android

วิธีเขียนไฟล์รูปภาพลง External Storage พร้อม Runtime Permission ใน Android

ข้อมูลสำคัญเกี่ยวกับ External Storage และ Permission

การเปลี่ยนแปลงตาม Android Version

Android VersionAPI Levelการจัดการ Permission
Android 5.1 และก่อน< 23Permission ประกาศใน Manifest พอ
Android 6.0 - 9.023 - 28ต้องขอ Runtime Permission
Android 10 (Q)29Scoped Storage เริ่มใช้งาน จำกัดการเข้าถึงไฟล์
Android 11+>= 30Scoped Storage บังคับ ใช้ MediaStore API แทน

⚠️ หมายเหตุสำคัญสำหรับ Android 10+ (API 29+)

Scoped Storage: ตั้งแต่ Android 10 แอปไม่สามารถเข้าถึงไฟล์ใน External Storage โดยตรงได้แล้ว ควรใช้:
  • MediaStore API สำหรับรูปภาพ วิดีโอ เสียง
  • Storage Access Framework (SAF) สำหรับไฟล์ทั่วไป
  • getExternalFilesDir() สำหรับไฟล์ส่วนตัวของแอป (ไม่ต้อง Permission)

โฟลเดอร์ที่แนะนำสำหรับแอป

// ไฟล์ส่วนตัวของแอป (ไม่ต้อง Permission, ถูกลบเมื่อ Uninstall)
File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "image.jpg");

// ไฟล์สาธารณะ (ต้อง Permission, คงอยู่เมื่อ Uninstall) - Deprecated ใน API 29+
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "image.jpg");

สรุป

การเขียนไฟล์ลง External Storage ต้องคำนึงถึง Android Version และ Permission Model ที่เปลี่ยนแปลงไป ควรย้ายไปใช้ MediaStore API หรือ Scoped Storage สำหรับแอปใหม่ๆ