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

วิธีสร้างไฟล์ txt ใน External Storage พร้อม Runtime Permission ใน Android

ภาพรวม

ตัวอย่างนี้สาธิตวิธีสร้างและเขียนไฟล์ข้อความ (txt) ลงใน External Storage พร้อมการขอ Runtime Permission สำหรับ Android 6.0+

ขั้นตอนที่ 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">

    <EditText
        android:id="@+id/enterText"
        android:hint="Please enter text here"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <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.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
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.EditText;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

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

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

        final EditText enterText = findViewById(R.id.enterText);
        save = findViewById(R.id.save);

        save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String text = enterText.getText().toString().trim();
                if (text.isEmpty()) {
                    enterText.setError("Please enter text");
                    return;
                }

                String state = Environment.getExternalStorageState();
                if (!Environment.MEDIA_MOUNTED.equals(state)) {
                    Toast.makeText(MainActivity.this, "External Storage ไม่พร้อมใช้งาน", Toast.LENGTH_SHORT).show();
                    return;
                }

                if (Build.VERSION.SDK_INT >= 23) {
                    if (checkPermission()) {
                        writeTextFile(text);
                    } else {
                        requestPermission();
                    }
                } else {
                    writeTextFile(text);
                }
            }
        });
    }

    private void writeTextFile(String text) {
        File sdcard = Environment.getExternalStorageDirectory();
        File dir = new File(sdcard.getAbsolutePath() + "/text/");
        
        // สร้างโฟลเดอร์ถ้ายังไม่มี
        if (!dir.exists()) {
            boolean created = dir.mkdirs();
            if (!created) {
                Toast.makeText(this, "สร้างโฟลเดอร์ไม่สำเร็จ", Toast.LENGTH_SHORT).show();
                return;
            }
        }

        File file = new File(dir, "sample.txt");
        
        try (FileOutputStream os = new FileOutputStream(file, true)) { // true = append mode
            os.write(text.getBytes("UTF-8"));
            os.write("\n".getBytes()); // เพิ่มบรรทัดใหม่
            Toast.makeText(this, "บันทึกข้อความสำเร็จ", Toast.LENGTH_SHORT).show();
            Log.d("path", file.getAbsolutePath());
        } catch (IOException 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, String[] permissions, 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");
                // ต้องดึงข้อความจาก EditText อีกครั้งเพราะอาจถูก Clear ไปแล้ว
                EditText enterText = findViewById(R.id.enterText);
                String text = enterText.getText().toString().trim();
                if (!text.isEmpty()) {
                    writeTextFile(text);
                }
            } else {
                Log.e("value", "Permission Denied");
                Toast.makeText(this, "ไม่ได้รับอนุญาต ไม่สามารถสร้างไฟล์ได้", Toast.LENGTH_SHORT).show();
            }
        }
    }
}
การปรับปรุงจากโค้ดต้นฉบับ:
  • ใช้ try-with-resources สำหรับ FileOutputStream (ปิดอัตโนมัติ)
  • เพิ่ม UTF-8 encoding รองรับภาษาไทย
  • ใช้ append mode (true) เพื่อไม่ทับข้อมูลเก่า
  • ใช้ dir.mkdirs() แทน mkdir() สร้างโฟลเดอร์ย่อยได้
  • ลดโค้ดซ้ำด้วย method แยก

ขั้นตอนที่ 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>

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

วิธีสร้างไฟล์ txt ใน External Storage พร้อม Runtime Permission ใน Android

วิธีสร้างไฟล์ txt ใน External Storage พร้อม Runtime Permission ใน Android

วิธีสร้างไฟล์ txt ใน External Storage พร้อม Runtime Permission ใน Android

ข้อมูลสำคัญสำหรับ Android 10+ (Scoped Storage)

การเปลี่ยนแปลงใน Android 10 (API 29) และ 11 (API 30+)

Versionผลกระทบแนวทางแก้ไข
Android 10Scoped Storage เริ่มต้น แต่ยังอนุญาต legacy modeเพิ่ม android:requestLegacyExternalStorage="true" ใน <application>
Android 11+Scoped Storage บังคับ ไม่เข้าถึงไฟล์โดยตรงได้ใช้ MediaStore API, SAF, หรือ getExternalFilesDir()

วิธีที่แนะนำสำหรับแอปใหม่ (Android 10+)

1. ใช้ App-specific Directory (ไม่ต้อง Permission)
File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "sample.txt");
// เส้นทาง: /storage/emulated/0/Android/data/com.example.app/files/Documents/sample.txt
2. ใช้ MediaStore API (สำหรับไฟล์สื่อ/เอกสารสาธารณะ)
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, "sample.txt");
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS);

Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
try (OutputStream os = getContentResolver().openOutputStream(uri)) {
    os.write(text.getBytes("UTF-8"));
}
3. ใช้ Storage Access Framework (SAF)
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TITLE, "sample.txt");
startActivityForResult(intent, CREATE_FILE_REQUEST);

สรุป

การสร้างไฟล์ txt ใน External Storage ต้องขอ WRITE_EXTERNAL_STORAGE Permission สำหรับ Android 6.0-9.0 แต่สำหรับ Android 10+ ควรเปลี่ยนไปใช้ App-specific Directory (getExternalFilesDir()) ซึ่งไม่ต้อง Permission หรือใช้ MediaStore/SAF สำหรับไฟล์สาธารณะ