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

วิธีแก้ไข android.os.NetworkOnMainThreadException ใน Android

NetworkOnMainThreadException เกิดขึ้นเมื่อแอปพยายามทำ Network Operation (เช่น ดึงข้อมูลจากเว็บ) บน Main Thread (UI Thread) ซึ่ง Android ห้ามทำเช่นนี้ตั้งแต่ API Level 11 (Honeycomb) เป็นต้นไป เพื่อป้องกัน UI Freeze

วิธีแก้: ใช้ AsyncTask (Deprecated) หรือ Thread/Executor/RxJava/Coroutines

ขั้นตอนที่ 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"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="8dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textLoad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:textStyle="bold|italic" />

    <TextView
        android:id="@+id/textMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textStyle="bold" />

</LinearLayout>

ขั้นตอนที่ 3: MainActivity.java (ใช้ AsyncTask - แบบเก่า)

import androidx.appcompat.app.AppCompatActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

public class MainActivity extends AppCompatActivity {
    TextView textLoad, textMessage;
    final String strMessage = "https://sites.google.com/site/androidersite/text.txt";

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

        textLoad = findViewById(R.id.textLoad);
        textMessage = findViewById(R.id.textMessage);
        textLoad.setText("Loading...");
        
        new MyTask().execute();
    }

    private class MyTask extends AsyncTask<Void, Void, Void> {
        String result;

        @Override
        protected Void doInBackground(Void... voids) {
            // ⚠️ Network Operation ต้องอยู่ใน doInBackground (Background Thread)
            try {
                URL url = new URL(strMessage);
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(url.openStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    stringBuilder.append(line);
                }
                reader.close();
                result = stringBuilder.toString();
            } catch (IOException e) {
                e.printStackTrace();
                result = "Error: " + e.getMessage();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            // ✅ อัปเดต UI ได้ที่นี่ (Main Thread)
            textMessage.setText(result);
            textLoad.setText("Finished");
            super.onPostExecute(aVoid);
        }
    }
}

ขั้นตอนที่ 4: AndroidManifest.xml (ต้องมี Internet Permission)

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

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

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

⚠️ AsyncTask ถูก Deprecated แล้ว (API 30+) - ใช้วิธีใหม่แทน

ทางเลือกที่ 1: Kotlin Coroutines (แนะนำ)

// build.gradle
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"

// ใน Activity
lifecycleScope.launch(Dispatchers.IO) {
    try {
        val url = URL("https://example.com/data.txt")
        val result = url.readText()
        withContext(Dispatchers.Main) {
            textMessage.text = result
            textLoad.text = "Finished"
        }
    } catch (e: Exception) {
        withContext(Dispatchers.Main) {
            textMessage.text = "Error: ${e.message}"
        }
    }
}

ทางเลือกที่ 2: Java Thread + Handler

new Thread(() -> {
    try {
        URL url = new URL(strMessage);
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(url.openStream()));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        reader.close();
        String result = sb.toString();
        
        // กลับมาที่ Main Thread
        runOnUiThread(() -> {
            textMessage.setText(result);
            textLoad.setText("Finished");
        });
    } catch (IOException e) {
        e.printStackTrace();
        runOnUiThread(() -> textMessage.setText("Error: " + e.getMessage()));
    }
}).start();

ทางเลือกที่ 3: OkHttp (Library ยอดนิยม)

// build.gradle
implementation "com.squareup.okhttp3:okhttp:4.11.0"

// ใช้งาน
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("https://example.com/data.txt")
    .build();

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        runOnUiThread(() -> textMessage.setText("Error: " + e.getMessage()));
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        String result = response.body().string();
        runOnUiThread(() -> {
            textMessage.setText(result);
            textLoad.setText("Finished");
        });
    }
});

สรุป

  • ❌ ห้ามทำ Network บน Main Thread
  • ✅ ใช้ Background Thread (Coroutine, Thread, RxJava, WorkManager)
  • ✅ อัปเดต UI ต้องกลับมาที่ Main Thread (runOnUiThread, withContext(Dispatchers.Main), onPostExecute)
  • ✅ อย่าลืม Permission android.permission.INTERNET

การทดสอบ

รันแอป → จะแสดง "Loading..." → หลังโหลดเสร็จจะแสดงข้อความจาก URL และ "Finished"