วิธีแก้ไข 'android.os.NetworkOnMainThreadException'?


2394

ฉันพบข้อผิดพลาดขณะเรียกใช้โครงการ Android ของฉันสำหรับ RssReader

รหัส:

URL url = new URL(urlToRssFeed);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
XMLReader xmlreader = parser.getXMLReader();
RssHandler theRSSHandler = new RssHandler();
xmlreader.setContentHandler(theRSSHandler);
InputSource is = new InputSource(url.openStream());
xmlreader.parse(is);
return theRSSHandler.getFeed();

และมันแสดงให้เห็นข้อผิดพลาดด้านล่าง:

android.os.NetworkOnMainThreadException

ฉันจะแก้ไขปัญหานี้ได้อย่างไร


131
อ่านโพสต์บล็อกนี้ใน NetworkOnMainThreadException สำหรับข้อมูลเพิ่มเติม มันอธิบายว่าทำไมสิ่งนี้เกิดขึ้นกับ Android 3.0 ขึ้นไป
Adrian Monk

6
ก่อนอื่นให้อ่านเกี่ยวกับคำขอเครือข่ายใน Android จากนั้นฉันจะแนะนำให้ศึกษา "Volley"
Anuj Sharma

3
มีห้องสมุดทางเลือกมากมายที่แก้ปัญหานี้ได้ หลายรายการอยู่ที่ด้านล่างของหน้านี้ หากคุณได้มากขึ้นเราพาพวกเขา :)
Snicolas

คุณต้องเรียกใช้กิจกรรมอินเทอร์เน็ตบนเธรดแยกจากเธรดหลัก (UI)
Naveed Ahmad

"เนื่องจากข้อผิดพลาดใน Android รุ่นก่อนหน้านี้ระบบไม่ได้ตั้งค่าสถานะการเขียนไปยังซ็อกเก็ต TCP บนเธรดหลักเป็นการละเมิดโหมดเข้มงวด Android 7.0 แก้ไขข้อผิดพลาดนี้แอปที่แสดงพฤติกรรมนี้โยน android.os NetworkOnMainThreadException." - ดังนั้นพวกเราบางคนยังไม่เคยเจอสิ่งนี้มาก่อน developer.android.com/about/versions/nougat/…
Jay

คำตอบ:


2547

ข้อยกเว้นนี้จะเกิดขึ้นเมื่อแอปพลิเคชันพยายามดำเนินการเครือข่ายในเธรดหลัก เรียกใช้รหัสของคุณในAsyncTask:

class RetrieveFeedTask extends AsyncTask<String, Void, RSSFeed> {

    private Exception exception;

    protected RSSFeed doInBackground(String... urls) {
        try {
            URL url = new URL(urls[0]);
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();
            XMLReader xmlreader = parser.getXMLReader();
            RssHandler theRSSHandler = new RssHandler();
            xmlreader.setContentHandler(theRSSHandler);
            InputSource is = new InputSource(url.openStream());
            xmlreader.parse(is);

            return theRSSHandler.getFeed();
        } catch (Exception e) {
            this.exception = e;

            return null;
        } finally {
            is.close();
        }
    }

    protected void onPostExecute(RSSFeed feed) {
        // TODO: check this.exception
        // TODO: do something with the feed
    }
}

วิธีดำเนินการงาน:

ในMainActivity.javaไฟล์คุณสามารถเพิ่มบรรทัดนี้ภายในoncreate()วิธีการของคุณ

new RetrieveFeedTask().execute(urlToRssFeed);

อย่าลืมเพิ่มลงในAndroidManifest.xmlไฟล์:

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

37
ฉันคิดว่ามันควรค่าแก่การสังเกตที่นี่ว่าข้อมูลโค้ดข้างต้นควรเป็น subclass (คลาสภายใน) โดยเฉพาะอย่างยิ่งส่วนตัว ด้วยวิธีนี้เมื่อ AsyncTask เสร็จสิ้นคุณยังสามารถจัดการอวัยวะภายในของชั้นเรียนของคุณได้
dyslexicanaboko

4
ที่จริงฉันทำสิ่งเดียวกับที่คุณได้กล่าวถึงข้างต้น แต่ m เผชิญข้อผิดพลาดนี้ java.lang.RuntimeException: ไม่สามารถสร้างตัวจัดการภายในเธรดที่ไม่ได้เรียก Looper.prepare ()
Dhruv Tyagi

68
นี่เป็นคำตอบที่ผิด ฉันเจอสิ่งนี้ตลอดเวลาในรหัสประชาชนและมันน่ารำคาญที่ต้องแก้ไขมันตลอดเวลา AsyncTask ไม่ควรใช้สำหรับกิจกรรมเครือข่ายเพราะมันเชื่อมโยงกับกิจกรรม แต่ไม่ใช่วงจรชีวิตของกิจกรรม การหมุนอุปกรณ์ด้วยภารกิจนี้กำลังทำงานอยู่จะทำให้เกิดข้อยกเว้นและทำให้แอปของคุณเสียหาย ใช้ IntentService ที่ปล่อยข้อมูลในฐานข้อมูล sqlite แทน
Brill Pappin

5
ข้อควรระวัง AsyncTask มักจะใช้สำหรับการดำเนินงานเครือข่ายต่อกิจกรรมเมื่อมันไม่ควรจะเป็น วงจรชีวิตของมันไม่สอดคล้องกับกิจกรรม สำหรับการดึงข้อมูลคุณควรใช้ IntentService และฐานข้อมูลที่อยู่ด้านหลังมุมมอง
Brill Pappin

1
@BrillPappin, FWIW, คู่มือผู้พัฒนา Android นี้ใช้AsyncTaskและดูแลการเปลี่ยนแปลงการกำหนดค่า
HeyJude

677

คุณควรเรียกใช้การดำเนินงานเครือข่ายบนเธรดหรือเป็นแบบอะซิงโครนัสเกือบตลอดเวลา

แต่เป็นไปได้ที่จะลบข้อ จำกัด นี้และคุณจะแทนที่พฤติกรรมเริ่มต้นหากคุณยินดีที่จะยอมรับผลที่ตามมา

เพิ่ม:

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();

StrictMode.setThreadPolicy(policy); 

ในชั้นเรียนของคุณ

และ

เพิ่มการอนุญาตนี้ในไฟล์ android manifest.xml:    

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

ผลกระทบ:

แอปของคุณ (ในพื้นที่ของการเชื่อมต่ออินเทอร์เน็ตที่ไม่แน่นอน) จะไม่ตอบสนองและล็อคผู้ใช้จะรับรู้ถึงความเชื่องช้าและต้องทำการฆ่าแบบบังคับและคุณต้องเสี่ยงต่อผู้จัดการกิจกรรมที่ฆ่าแอปของคุณและบอกผู้ใช้ว่าแอปหยุด

Android มีเคล็ดลับที่ดีเกี่ยวกับวิธีปฏิบัติในการเขียนโปรแกรมที่ดีเพื่อออกแบบเพื่อตอบสนอง: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html


456
นี่เป็นความคิดที่แย่มาก การแก้ปัญหาคือการหลีกเลี่ยงเครือข่าย IO บนเธรดหลัก (ตามคำตอบที่ยอมรับแสดง)
MByD

74
ด้วยวิธีนี้คุณซ่อนปัญหาที่แท้จริงของคุณ
Alex

28
@TwistedUmbrella AsyncTask ไม่ได้เพิ่มหน้าของรหัสมันเพิ่ม 6 บรรทัด (การประกาศคลาสการแทนที่คำอธิบายประกอบdoInBackgroundการประกาศการประกาศวงเล็บปิด 2 ตัวและการเรียกไปยังexecute()) ในทางกลับกันแม้แต่การดึงข้อมูลเพียงครั้งเดียวจากเว็บไซต์ตามที่คุณพูดถึงจะทำให้เกิดความล่าช้าอย่างมากในการตอบสนองของ UI อย่าขี้เกียจ
Zoltán

19
ฉันคิดว่านี่เป็นโซลูชั่นที่สมบูรณ์แบบหากคุณกำลังมองหารันโค้ดตัวอย่างเพื่อดูว่ามีบางอย่างทำงานได้หรือไม่ก่อนที่จะใช้ AsyncTask ที่เหมาะสม นั่นเป็นเหตุผลที่ฉันตอบโต้คำตอบนี้ถึงแม้ว่าอย่างที่คนอื่น ๆ ทุกคนบอกว่าไม่ควรทำในแอพโปรดักชั่น แต่เป็นการแก้ปัญหาอย่างรวดเร็วสำหรับการทดสอบ dev
hooked82

94
upvoted คำตอบนี้ถูกต้องและสำหรับโปรแกรมเมอร์จำนวนมากที่ไม่ไร้เดียงสาหรือโง่เขลา แต่ผู้ที่ต้องการซิงโครนัส (เช่น: ต้องบล็อกแอป ) การโทรนี่คือสิ่งที่จำเป็นจริงๆ ฉันมีความสุขมากกว่าที่ Android โยนข้อยกเว้นตามค่าเริ่มต้น (IMHO มันเป็นสิ่งที่ "มีประโยชน์" มากที่ต้องทำ!) - แต่ฉันก็มีความสุขที่จะพูดว่า "ขอบคุณ แต่ - นี่คือสิ่งที่ฉันตั้งใจ" และจริง ๆ มัน.
Adam

428

Threadฉันจะแก้ไขปัญหานี้โดยใช้ใหม่

Thread thread = new Thread(new Runnable() {

    @Override
    public void run() {
        try  {
            //Your code goes here
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

thread.start(); 

7
แทนที่จะสร้างเธรดใหม่ทุกครั้งที่คุณต้องการดำเนินการเครือข่ายคุณสามารถใช้เซอร์วิสตัวจัดการเธรดเดียวได้เช่นกัน
Alex Lockwood

66
เรียบง่าย แต่อันตราย Runnable ที่ไม่ระบุชื่อมีการอ้างอิงโดยนัยถึงคลาสที่ปิดล้อม (เช่นกิจกรรมหรือส่วนของคุณ) ป้องกันไม่ให้ถูกรวบรวมขยะจนกว่าเธรดจะเสร็จสมบูรณ์ อย่างน้อยคุณควรตั้งค่าลำดับความสำคัญเป็น Process.BACKGROUND มิฉะนั้นเธรดนี้จะทำงานในระดับความสำคัญเดียวกับเธรดหลัก / ui, โต้แย้งกับวงจรชีวิตและอัตราเฟรม ui (ระวังคำเตือนในบันทึกจากผู้ออกแบบท่าเต้น)
Stevie

1
@Stevie วิธีการตั้งค่าลำดับความสำคัญ? runnble และ executorService นั้นไม่มีเมธอด setter เช่นนี้
JK

1
@JK ระบุ ExecutorService ของคุณด้วย ThreadFactory ที่กำหนดเองและเรียกใช้ Thread.setPriority บนเธรดก่อนส่งคืน
Stevie

1
"การใช้เธรดใน Android โดยตรงนั้นเป็นสิ่งที่ท้อแท้มันทำให้เกิดปัญหามากกว่าที่มันจะแก้ได้" สนใจที่จะอธิบายอย่างละเอียดไหม AsyncTask อันที่จริงกำลังถูกคัดค้านอย่างแน่นอนสำหรับ ... techyourchance.com/asynctask-deprecated
Fran Marzoa

169

คำตอบที่ได้รับการยอมรับมีความสำคัญน้อยลง ไม่แนะนำให้ใช้ AsyncTask สำหรับเครือข่ายเว้นแต่คุณจะรู้ว่าคุณกำลังทำอะไรอยู่ ข้อเสียบางประการ ได้แก่ :

  • AsyncTask ถูกสร้างขึ้นเป็นคลาสภายในที่ไม่คงที่มีการอ้างอิงโดยนัยไปยังวัตถุกิจกรรมที่ล้อมรอบบริบทและลำดับชั้นมุมมองทั้งหมดที่สร้างโดยกิจกรรมนั้น การอ้างอิงนี้ป้องกันกิจกรรมจากการรวบรวมขยะจนกว่าการทำงานพื้นหลังของ AsyncTask จะเสร็จสมบูรณ์ หากการเชื่อมต่อของผู้ใช้ช้าและ / หรือการดาวน์โหลดมีขนาดใหญ่การรั่วไหลของหน่วยความจำระยะสั้นเหล่านี้อาจเป็นปัญหาได้ตัวอย่างเช่นหากการวางแนวนั้นเปลี่ยนไปหลายครั้ง (และคุณไม่ได้ยกเลิกงานที่เรียกใช้) หรือผู้ใช้ นำทางออกจากกิจกรรม
  • AsyncTask มีลักษณะการดำเนินการที่แตกต่างกันขึ้นอยู่กับแพลตฟอร์มที่รันบน: ก่อนหน้า API ระดับ 4 AsyncTasks จะดำเนินการตามลำดับบนเธรดพื้นหลังเดียว จาก API ระดับ 4 ถึงระดับ API 10 AsyncTasks จะดำเนินการในกลุ่มมากถึง 128 เธรด จาก API ระดับ 11 เป็นต้นไป AsyncTask จะดำเนินการตามลำดับบนเธรดพื้นหลังเดียว (เว้นแต่คุณจะใช้executeOnExecutorวิธีโอเวอร์โหลดและจัดหาตัวเลือกสำรอง) รหัสที่ใช้งานได้ดีเมื่อรันอย่างต่อเนื่องบน ICS อาจแตกหักเมื่อดำเนินการพร้อมกันใน Gingerbread พร้อมกันบอกว่าถ้าคุณมีการพึ่งพาคำสั่งของการดำเนินการโดยไม่ตั้งใจ

หากคุณต้องการหลีกเลี่ยงการรั่วไหลของหน่วยความจำระยะสั้นมีคุณสมบัติการประมวลผลที่กำหนดไว้อย่างชัดเจนในทุกแพลตฟอร์มและมีฐานในการสร้างการจัดการเครือข่ายที่แข็งแกร่งจริง ๆ คุณอาจต้องพิจารณา:

  1. การใช้ห้องสมุดที่ทำงานได้ดีสำหรับคุณ - มีการเปรียบเทียบ libs ระบบเครือข่ายในคำถามนี้หรือ
  2. ใช้ a ServiceหรือIntentServiceแทนอาจใช้ a PendingIntentเพื่อส่งคืนผลลัพธ์ผ่านonActivityResultวิธีการของกิจกรรม

วิธีการ IntentService

ลงข้าง:

  • มีโค้ดและความซับซ้อนมากกว่าAsyncTaskแต่ไม่มากเท่าที่คุณคิด
  • จะจัดคิวคำขอและเรียกใช้บนเธรดพื้นหลังเดียว คุณสามารถควบคุมสิ่งนี้ได้อย่างง่ายดายโดยแทนที่IntentServiceด้วยการServiceใช้งานที่เทียบเท่ากันหรืออาจจะเป็นแบบนี้ก็ได้
  • อืมตอนนี้ฉันไม่สามารถนึกถึงคนอื่นได้

ขึ้นด้าน:

  • หลีกเลี่ยงปัญหาการรั่วไหลของหน่วยความจำระยะสั้น
  • หากกิจกรรมของคุณรีสตาร์ทในขณะที่การดำเนินงานเครือข่ายอยู่ในระหว่างการบินก็ยังสามารถรับผลการดาวน์โหลดผ่านonActivityResultวิธีการของมันได้
  • แพลตฟอร์มที่ดีกว่า AsyncTask ในการสร้างและใช้รหัสเครือข่ายที่ทนทานอีกครั้ง ตัวอย่าง: หากคุณจำเป็นต้องทำการอัปโหลดที่สำคัญคุณสามารถทำได้จากAsyncTaskในActivityแต่ถ้าบริบทผู้ใช้สลับออกจากแอพที่จะใช้โทรศัพท์ระบบอาจฆ่าแอพก่อนที่การอัปโหลดจะเสร็จสมบูรณ์ มันเป็นโอกาสน้อยที่Serviceจะฆ่าแอพลิเคชันที่มีที่ใช้งาน
  • หากคุณใช้รุ่นพร้อมกันของคุณเองIntentService(เช่นเดียวที่ฉันเชื่อมโยงด้านบน) Executorคุณสามารถควบคุมระดับของการทำงานพร้อมกันผ่านทางที่

สรุปการนำไปปฏิบัติ

คุณสามารถใช้IntentServiceเพื่อดำเนินการดาวน์โหลดในเธรดพื้นหลังเดียวได้อย่างง่ายดาย

ขั้นตอนที่ 1: สร้างIntentServiceเพื่อดำเนินการดาวน์โหลด คุณสามารถบอกได้ว่าจะดาวน์โหลดอะไรผ่านทางสิ่งIntentพิเศษและส่งผ่านPendingIntentเพื่อใช้ในการส่งคืนผลลัพธ์ไปที่Activity:

import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Intent;
import android.util.Log;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

public class DownloadIntentService extends IntentService {

    private static final String TAG = DownloadIntentService.class.getSimpleName();

    public static final String PENDING_RESULT_EXTRA = "pending_result";
    public static final String URL_EXTRA = "url";
    public static final String RSS_RESULT_EXTRA = "url";

    public static final int RESULT_CODE = 0;
    public static final int INVALID_URL_CODE = 1;
    public static final int ERROR_CODE = 2;

    private IllustrativeRSSParser parser;

    public DownloadIntentService() {
        super(TAG);

        // make one and re-use, in the case where more than one intent is queued
        parser = new IllustrativeRSSParser();
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        PendingIntent reply = intent.getParcelableExtra(PENDING_RESULT_EXTRA);
        InputStream in = null;
        try {
            try {
                URL url = new URL(intent.getStringExtra(URL_EXTRA));
                IllustrativeRSS rss = parser.parse(in = url.openStream());

                Intent result = new Intent();
                result.putExtra(RSS_RESULT_EXTRA, rss);

                reply.send(this, RESULT_CODE, result);
            } catch (MalformedURLException exc) {
                reply.send(INVALID_URL_CODE);
            } catch (Exception exc) {
                // could do better by treating the different sax/xml exceptions individually
                reply.send(ERROR_CODE);
            }
        } catch (PendingIntent.CanceledException exc) {
            Log.i(TAG, "reply cancelled", exc);
        }
    }
}

ขั้นตอนที่ 2: ลงทะเบียนบริการในไฟล์ Manifest:

<service
        android:name=".DownloadIntentService"
        android:exported="false"/>

ขั้นตอนที่ 3: เรียกใช้บริการจากกิจกรรมผ่านวัตถุ PendingResult ซึ่งบริการจะใช้เพื่อส่งคืนผลลัพธ์:

PendingIntent pendingResult = createPendingResult(
    RSS_DOWNLOAD_REQUEST_CODE, new Intent(), 0);
Intent intent = new Intent(getApplicationContext(), DownloadIntentService.class);
intent.putExtra(DownloadIntentService.URL_EXTRA, URL);
intent.putExtra(DownloadIntentService.PENDING_RESULT_EXTRA, pendingResult);
startService(intent);

ขั้นตอนที่ 4: จัดการผลลัพธ์ใน onActivityResult:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == RSS_DOWNLOAD_REQUEST_CODE) {
        switch (resultCode) {
            case DownloadIntentService.INVALID_URL_CODE:
                handleInvalidURL();
                break;
            case DownloadIntentService.ERROR_CODE:
                handleError(data);
                break;
            case DownloadIntentService.RESULT_CODE:
                handleRSS(data);
                break;
        }
        handleRSS(data);
    }
    super.onActivityResult(requestCode, resultCode, data);
}

โครงการ Github ที่มีการทำงานที่สมบูรณ์โครงการ Android ที่สตูดิโอ / Gradle สามารถใช้ได้ที่นี่


IntentService เป็นวิธีที่ถูกต้องในการทำเช่นนี้ไม่ใช่ถอนรากเพราะ AsyncTask เป็นวิธีที่ไม่ต้องทำ
Brill Pappin

3
@ BrillPappin ฉันเกือบทั้งหมดเห็นด้วยและมีคำพูดอีกครั้งเพื่อเน้นข้อเสียของ AsyncTask (ผมยังคิดว่ามีขนาดเล็กมากจำนวนของกรณีที่ - ถ้าคุณรู้ว่าจริงๆสิ่งที่คุณกำลังทำ - มันอาจจะตกลงที่จะใช้ AsyncTask แต่คำตอบที่ได้รับการยอมรับไม่ได้ชี้ให้เห็นข้อบกพร่องใด ๆ และเป็นวิธีที่นิยมเกินไปสำหรับการที่ดี ของ Android)
Stevie

1
คุณต้องการจริงๆIllustrativeRSSหรือ ถ้าคุณไม่ได้ทำงานกับเนื้อหา RSS
Cullub

ในความคิดของฉัน Google ควรเปลี่ยนการใช้งานที่ไม่ดีของพวกเขาในการเก็บขยะมากกว่าที่จะทำให้ Berdon อยู่ด้านโปรแกรมเมอร์ นี่เป็นความรับผิดชอบของระบบปฏิบัติการ หากโปรแกรมเมอร์ใช้ IntentService หรือ Service เพื่อทำงานดังกล่าวเนื่องจากปัญหาพื้นฐานในการใช้งาน Android หลังจากผ่านไปหลายปี Google จะกล่าวว่า IntentService นั้นเป็นแนวปฏิบัติที่ไม่ดีเช่นกันและแนะนำอย่างอื่น และเรื่องนี้ยังดำเนินต่อไป ... ดังนั้นนักพัฒนาของ Google ควรแก้ปัญหาการจัดการหน่วยความจำไม่ดีของ Android ไม่ใช่โปรแกรมเมอร์
saeed khalafinejad

ฉันแค่จะบอกว่ามัน: AsyncTaskคำตอบนี้ดูเหมือนว่ามากขึ้นของวิธีการที่จะมีส่วนร่วมโครงการกว่าเป็นทางเลือกที่ดีกว่าที่จะ ข้อผิดพลาดนี้มีขึ้นเพื่อหยุดยั้งผู้พัฒนาไม่ให้ UI ล้มเหลวโดยไม่จำเป็นต้องชักจูงพวกเขาให้เสี่ยงต่อความปลอดภัยด้วยการใช้ intents / services
รถเข็นที่ถูกทิ้งร้าง

144

คุณไม่สามารถดำเนินการเครือข่ายI / Oในหัวข้อ UI บนรังผึ้ง ในทางเทคนิคแล้วมันเป็นไปได้สำหรับ Android เวอร์ชันก่อนหน้า แต่มันเป็นความคิดที่แย่มากเพราะมันจะทำให้แอปของคุณหยุดตอบสนองและอาจส่งผลให้ระบบปฏิบัติการฆ่าแอปของคุณเพราะประพฤติผิด คุณจะต้องเรียกใช้กระบวนการพื้นหลังหรือใช้ AsyncTask เพื่อทำธุรกรรมเครือข่ายของคุณในเธรดพื้นหลัง

มีบทความเกี่ยวกับPainless Threadingบนเว็บไซต์นักพัฒนาซอฟต์แวร์ Android ซึ่งเป็นการแนะนำที่ดีเกี่ยวกับเรื่องนี้และมันจะช่วยให้คุณได้คำตอบที่ลึกซึ้งยิ่งขึ้นกว่าที่สามารถให้ได้ที่นี่


76
  1. ห้ามใช้เข้มงวดโหมด (เฉพาะในโหมดแก้ไขข้อบกพร่อง)
  2. อย่าเปลี่ยนรุ่น SDK
  3. อย่าใช้เธรดแยกต่างหาก

ใช้บริการหรือ AsyncTask

ดูเพิ่มเติมคำถาม Stack Overflow:

android.os.NetworkOnMainThreadException ส่งอีเมลจาก Android


8
บางทีอาจเป็นการเน้นย้ำถึงประเด็นที่ว่าถ้าคุณใช้บริการคุณจะต้องสร้างเธรดแยกต่างหาก - การเรียกกลับของเซอร์วิสที่รันบนเธรดหลัก ในทางกลับกัน IntentService จะเรียกใช้เมธอด onHandleIntent บนเธรดพื้นหลัง
Stevie

คุณไม่ควรใช้ AsyncTask สำหรับการดำเนินการที่ต้องใช้เวลานาน! หลักเกณฑ์ระบุสูงสุด 2 ถึง 3 วินาที
Dage

76

ดำเนินการกับเครือข่ายในเธรดอื่น

ตัวอย่างเช่น:

new Thread(new Runnable(){
    @Override
    public void run() {
        // Do network action in this function
    }
}).start();

และเพิ่มลงใน AndroidManifest.xml

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

5
แต่เราจะทราบได้อย่างไรเมื่อเธรดเสร็จสิ้นในสิ่งนี้เพื่อให้เราสามารถดำเนินงานชุดถัดไปในเธรด UI ได้อย่างไร AsyncTask ให้ความสะดวกในการทำเช่นนั้น มีวิธีการทำเช่นเดียวกันโดยใช้เธรดที่รันได้หรือไม่?
Piyush Soni

3
มันจะประมวลผลโค้ดของคุณทีละขั้นตอนดังนั้นในตอนท้ายของโค้ดจะเสร็จสิ้นคุณต้องใช้ตัวจัดการกลับไปที่เธรด UI
henry4343

1
คุณสามารถใช้งาน async หรือบริการความตั้งใจเพราะมันทำงานบนเธรดคนงาน
Chetan Chaudhari

64

คุณปิดใช้งานโหมดเข้มงวดโดยใช้รหัสต่อไปนี้:

if (android.os.Build.VERSION.SDK_INT > 9) {
    StrictMode.ThreadPolicy policy = 
        new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);
}

ไม่แนะนำ : ใช้AsyncTaskส่วนต่อประสาน

รหัสเต็มสำหรับทั้งสองวิธี


2
ใช่ข้อผิดพลาด ANR จะมา หมายความว่าแอปไม่ตอบสนองภายใน 5 วินาที
มูฮัมหมัดมูบาชิร์

11
นี่เป็นคำตอบที่ไม่ดีจริงๆ คุณไม่ควรเปลี่ยนนโยบายของเธรด แต่เพื่อเขียนโค้ดที่ดีกว่า: อย่าดำเนินการเครือข่ายในเธรดหลัก!
shkschneider

@Sandeep คุณและผู้ดูอื่น ๆ ควรอ่านด้วย stackoverflow.com/a/18335031/3470479
Prakhar1001

54

การดำเนินการบนเครือข่ายไม่สามารถรันบนเธรดหลักได้ คุณต้องใช้งานเครือข่ายทั้งหมดในเธรดลูกหรือใช้ AsyncTask

นี่คือวิธีที่คุณรันงานในเธรดลูก:

new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            // Your implementation goes here
        } 
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}).start();

Anonymous Runnable ไม่ใช่วิธีที่ดีที่สุดเนื่องจากมีการอ้างอิงโดยนัยถึงคลาสที่ปิดล้อมและป้องกันไม่ให้ GC ed จนกว่าเธรดจะเสร็จสมบูรณ์! นอกจากนี้เธรดนี้จะทำงานที่ระดับความสำคัญเดียวกันกับเธรดหลัก / สหรัฐอเมริกาโดยใช้เมธอดวงจรชีวิตและอัตราเฟรม UI!
Yousha Aleayoub

49

ใส่รหัสของคุณภายใน:

new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            // Your implementation
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}).start();

หรือ:

class DemoTask extends AsyncTask<Void, Void, Void> {

    protected Void doInBackground(Void... arg0) {
        //Your implementation
    }

    protected void onPostExecute(Void result) {
        // TODO: do something with the feed
    }
}

อันดับที่สองจะดีกว่าครั้งแรกสำหรับ api ที่สูงกว่า 11
Rohit Goswami

46

สิ่งนี้เกิดขึ้นใน Android 3.0 ขึ้นไป ตั้งแต่ Android 3.0 ขึ้นไปพวกมันถูก จำกัด การใช้งานเครือข่าย (ฟังก์ชั่นที่เข้าถึงอินเทอร์เน็ต) ไม่ให้ทำงานในเธรดหลัก / UI thread (สิ่งที่เกิดจากการสร้างและวิธีการดำเนินการต่อในกิจกรรม)

นี่คือเพื่อสนับสนุนให้ใช้หัวข้อแยกต่างหากสำหรับการดำเนินงานเครือข่าย ดูAsyncTaskสำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการดำเนินการกิจกรรมเครือข่ายอย่างถูกวิธี


46

การใช้คำอธิบายประกอบ Androidเป็นตัวเลือก มันจะช่วยให้คุณสามารถเรียกใช้เมธอดใด ๆ ในเธรดพื้นหลัง:

// normal method
private void normal() {
    doSomething(); // do something in background
}

@Background
protected void doSomething() 
    // run your networking code here
}

โปรดทราบว่าแม้ว่าจะให้ประโยชน์ของความเรียบง่ายและความสามารถในการอ่าน แต่ก็มีข้อเสีย


6
@Gavriel มันสร้างรายการที่ซ้ำซ้อนของทุกสิ่งที่คุณใส่คำอธิบายประกอบไม่ว่าจะเป็นวิธีการกิจกรรมส่วนย่อยซิงเกิล ฯลฯ ดังนั้นจึงมีโค้ดมากเป็นสองเท่าและใช้เวลาในการรวบรวมนานกว่า มันอาจมีปัญหาบางอย่างเนื่องจากข้อบกพร่องในห้องสมุด การดีบักและค้นหาข้อผิดพลาดจะกลายเป็นเรื่องยากมากขึ้น
Oleksiy

43

ข้อผิดพลาดที่เกิดจากการดำเนินการการดำเนินงานระยะยาวในหัวข้อหลักคุณสามารถแก้ไขปัญหาโดยใช้AsynTaskหรือกระทู้ คุณสามารถเช็คเอาต์ไลบรารีนี้AsyncHTTPClientเพื่อการจัดการที่ดีขึ้น

AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {

    @Override
    public void onStart() {
        // Called before a request is started
    }

    @Override
    public void onSuccess(int statusCode, Header[] headers, byte[] response) {
        // Called when response HTTP status is "200 OK"
    }

    @Override
    public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
        // Called when response HTTP status is "4XX" (for example, 401, 403, 404)
    }

    @Override
    public void onRetry(int retryNo) {
        // Called when request is retried
    }
});

42

คุณไม่ควรใช้เวลานานในเธรดหลัก (เธรด UI) เช่นการดำเนินการเครือข่ายใด ๆ การดำเนินการของไฟล์ I / O หรือฐานข้อมูล SQLite ดังนั้นสำหรับการดำเนินการชนิดนี้คุณควรสร้างเธรดผู้ปฏิบัติงาน แต่ปัญหาคือคุณไม่สามารถดำเนินการกับ UI ที่เกี่ยวข้องโดยตรงจากเธรดผู้ปฏิบัติงานของคุณได้โดยตรง เพื่อที่คุณจะต้องใช้และผ่านการHandlerMessage

เพื่อให้ง่ายต่อสิ่งเหล่านี้, Android มีวิธีการต่างๆเช่นAsyncTask, AsyncTaskLoader,CursorLoaderIntentServiceหรือ เพื่อให้คุณสามารถใช้สิ่งเหล่านี้ตามความต้องการของคุณ


40

คำตอบยอดนิยมของ spektomทำงานได้อย่างสมบูรณ์แบบ

หากคุณกำลังเขียนAsyncTaskแบบอินไลน์และไม่ขยายเป็นชั้นเรียนและด้านบนของสิ่งนี้หากมีความต้องการที่จะได้รับการตอบสนองจากการAsyncTaskอย่างใดอย่างหนึ่งสามารถใช้get()วิธีการดังต่อไปนี้

RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();

(จากตัวอย่างของเขา)


5
ใช้get()เป็นความคิดที่ไม่ดี ... มันทำให้ AsyncTask "ซิงค์" อีกครั้ง
Selvin

มีวิธีที่ดีกว่านี้อีกไหม? @Selvin
sivag1

2
ฉันคิดว่าคุณสามารถให้ข้อมูลเธรดหลักเกี่ยวกับผลลัพธ์ได้ตัวอย่างเช่นส่งการออกอากาศไปที่เธรดหลักรวมถึงผลลัพธ์
Chine Gary

32

นี่เป็นการโยนเฉพาะสำหรับแอปพลิเคชันที่กำหนดเป้าหมายHoneycomb SDK หรือสูงกว่า แอปพลิเคชันที่กำหนดเป้าหมายเวอร์ชัน SDK ก่อนหน้าได้รับอนุญาตให้ทำเครือข่ายในเธรดเหตุการณ์ลูปหลักของพวกเขา

ข้อผิดพลาดคือคำเตือน SDK!


28

สำหรับฉันมันเป็นแบบนี้:

<uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="10" />

อุปกรณ์ที่ฉันกำลังทดสอบแอพของฉันคือ 4.1.2 ซึ่งเป็น SDK เวอร์ชัน 16!

ตรวจสอบให้แน่ใจว่าเวอร์ชันเป้าหมายเหมือนกับไลบรารี Android เป้าหมายของคุณ หากคุณไม่แน่ใจว่าไลบรารีเป้าหมายของคุณคืออะไรให้คลิกขวาที่ Project ของคุณ -> Build Path -> Androidและควรเป็นที่ถูกเลือก

รวมถึงสิทธิ์อื่น ๆ ที่ถูกต้องในการเข้าถึงอินเทอร์เน็ต:

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

11
ให้ฉันอธิบายสิ่งที่คุณกำลังทำที่นี่: NetworkOnMainThreadExceptionเป็นผู้พิทักษ์ที่บอกคุณ: อย่ายิงด้วยเท้าของคุณเอง ... ทางออกของคุณคือ: กลับไปที่อดีตเมื่อไม่มีผู้พิทักษ์ - ตอนนี้ฉันสามารถยิงที่ฉันได้ เท้าได้อย่างอิสระ
Selvin

1
ฉันใช้วิธีนี้เช่นกันและไม่มีปัญหาใด ๆ บางครั้งผู้ปกครองจุกจิกเกินไป
FractalBob

25

ใช้สิ่งนี้ในกิจกรรมของคุณ

    btnsub.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub

                    //Initialize soap request + add parameters
                    SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME1);

                    //Use this to add parameters
                    request.addProperty("pincode", txtpincode.getText().toString());
                    request.addProperty("bg", bloodgroup.getSelectedItem().toString());

                    //Declare the version of the SOAP request
                    SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);

                    envelope.setOutputSoapObject(request);
                    envelope.dotNet = true;

                    try {
                        HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

                        //this is the actual part that will call the webservice
                        androidHttpTransport.call(SOAP_ACTION1, envelope);

                        // Get the SoapResult from the envelope body.
                        SoapObject result = (SoapObject) envelope.getResponse();
                        Log.e("result data", "data" + result);
                        SoapObject root = (SoapObject) result.getProperty(0);
                        // SoapObject s_deals = (SoapObject) root.getProperty(0);
                        // SoapObject s_deals_1 = (SoapObject) s_deals.getProperty(0);
                        //

                        System.out.println("********Count : " + root.getPropertyCount());

                        value = new ArrayList<Detailinfo>();

                        for (int i = 0; i < root.getPropertyCount(); i++) {
                            SoapObject s_deals = (SoapObject) root.getProperty(i);
                            Detailinfo info = new Detailinfo();

                            info.setFirstName(s_deals.getProperty("Firstname").toString());
                            info.setLastName(s_deals.getProperty("Lastname").toString());
                            info.setDOB(s_deals.getProperty("DOB").toString());
                            info.setGender(s_deals.getProperty("Gender").toString());
                            info.setAddress(s_deals.getProperty("Address").toString());
                            info.setCity(s_deals.getProperty("City").toString());
                            info.setState(s_deals.getProperty("State").toString());
                            info.setPinecode(s_deals.getProperty("Pinecode").toString());
                            info.setMobile(s_deals.getProperty("Mobile").toString());
                            info.setEmail(s_deals.getProperty("Email").toString());
                            info.setBloodgroup(s_deals.getProperty("Bloodgroup").toString());
                            info.setAdddate(s_deals.getProperty("Adddate").toString());
                            info.setWaight(s_deals.getProperty("waight").toString());
                            value.add(info);
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    Intent intent = new Intent(getApplicationContext(), ComposeMail.class);
                    //intent.putParcelableArrayListExtra("valuesList", value);

                    startActivity(intent);
                }
            }).start();
        }
    });

23

เพียงเพื่อสะกดสิ่งที่ชัดเจน:

เธรดหลักนั้นเป็นเธรด UI

ดังนั้นการบอกว่าคุณไม่สามารถทำการดำเนินการเครือข่ายในเธรดหลักหมายความว่าคุณไม่สามารถทำการดำเนินการเครือข่ายในเธรด UI ได้ซึ่งหมายความว่าคุณไม่สามารถทำการดำเนินการเครือข่ายใน*runOnUiThread(new Runnable() { ... }*บล็อกภายในเธรดอื่นได้เช่นกัน

(ฉันเพิ่งจะมีรอยขีดข่วนหัวนานพยายามคิดว่าทำไมฉันถึงได้รับข้อผิดพลาดที่อื่นนอกเหนือจากหัวข้อหลักของฉันนี่คือเหตุผลที่หัวข้อนี้ช่วยและหวังว่าความคิดเห็นนี้จะช่วยคนอื่น)


22

ข้อยกเว้นนี้เกิดขึ้นเนื่องจากการงานหนักใด ๆ ดำเนินการในหัวข้อหลักว่าการปฏิบัติงานจะใช้เวลาเวลามากเกินไป

เพื่อหลีกเลี่ยงปัญหานี้เราสามารถจัดการได้โดยใช้เธรดหรือตัวประมวลผล

Executors.newSingleThreadExecutor().submit(new Runnable() {
    @Override
    public void run() {
        // You can perform your task here.
    }
});

18

มีคำตอบที่ยอดเยี่ยมมากมายเกี่ยวกับคำถามนี้อยู่แล้ว แต่มีห้องสมุดดีๆมากมายออกมาตั้งแต่คำตอบเหล่านั้นถูกโพสต์ สิ่งนี้มีไว้เพื่อเป็นแนวทางสำหรับมือใหม่

ฉันจะครอบคลุมกรณีการใช้งานหลายอย่างสำหรับประสิทธิภาพการดำเนินงานของเครือข่ายและวิธีการแก้ปัญหาหรือสองสำหรับแต่ละ

ReST ผ่าน HTTP

โดยทั่วไป Json สามารถเป็น XML หรืออย่างอื่น

การเข้าถึง API แบบเต็ม

สมมติว่าคุณกำลังเขียนแอพที่ช่วยให้ผู้ใช้ติดตามราคาหุ้นอัตราดอกเบี้ยและอัตราแลกเปลี่ยน currecy คุณพบ Json API ที่มีลักษณะดังนี้:

http://api.example.com/stocks                       //ResponseWrapper<String> object containing a list of Srings with ticker symbols
http://api.example.com/stocks/$symbol               //Stock object
http://api.example.com/stocks/$symbol/prices        //PriceHistory<Stock> object
http://api.example.com/currencies                   //ResponseWrapper<String> object containing a list of currency abbreviation
http://api.example.com/currencies/$currency         //Currency object
http://api.example.com/currencies/$id1/values/$id2  //PriceHistory<Currency> object comparing the prices of the first currency (id1) to the second (id2)

ชุดติดตั้งเพิ่มจาก Square

นี่เป็นตัวเลือกที่ยอดเยี่ยมสำหรับ API ที่มีจุดปลายหลายจุดและอนุญาตให้คุณประกาศจุดสิ้นสุดของ ReST แทนที่จะต้องเขียนโค้ดแยกต่างหากเช่นเดียวกับไลบรารีอื่น ๆ เช่น ion หรือ Volley (เว็บไซต์: http://square.github.io/retrofit/ )

คุณใช้กับการเงิน API ได้อย่างไร?

build.gradle

เพิ่มบรรทัดเหล่านี้ในโมดูลระดับ buid.gradle ของคุณ:

implementation 'com.squareup.retrofit2:retrofit:2.3.0' //retrofit library, current as of September 21, 2017
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' //gson serialization and deserialization support for retrofit, version must match retrofit version

FinancesApi.java

public interface FinancesApi {
    @GET("stocks")
    Call<ResponseWrapper<String>> listStocks();
    @GET("stocks/{symbol}")
    Call<Stock> getStock(@Path("symbol")String tickerSymbol);
    @GET("stocks/{symbol}/prices")
    Call<PriceHistory<Stock>> getPriceHistory(@Path("symbol")String tickerSymbol);

    @GET("currencies")
    Call<ResponseWrapper<String>> listCurrencies();
    @GET("currencies/{symbol}")
    Call<Currency> getCurrency(@Path("symbol")String currencySymbol);
    @GET("currencies/{symbol}/values/{compare_symbol}")
    Call<PriceHistory<Currency>> getComparativeHistory(@Path("symbol")String currency, @Path("compare_symbol")String currencyToPriceAgainst);
}

FinancesApiBuilder

public class FinancesApiBuilder {
    public static FinancesApi build(String baseUrl){
        return new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                    .create(FinancesApi.class);
    }
}

ตัวอย่างข้อมูลทางการเงิน

FinancesApi api = FinancesApiBuilder.build("http://api.example.com/"); //trailing '/' required for predictable behavior
api.getStock("INTC").enqueue(new Callback<Stock>(){
    @Override
    public void onResponse(Call<Stock> stockCall, Response<Stock> stockResponse){
        Stock stock = stockCall.body();
        //do something with the stock
    }
    @Override
    public void onResponse(Call<Stock> stockCall, Throwable t){
        //something bad happened
    }
}

หาก API ของคุณต้องการคีย์ API หรือส่วนหัวอื่น ๆ เช่นโทเค็นของผู้ใช้เป็นต้น Retrofit ทำให้เป็นเรื่องง่าย (ดูคำตอบที่ยอดเยี่ยมสำหรับรายละเอียด: https://stackoverflow.com/a/42899766/1024412 )

การเข้าถึง ReST API หนึ่งครั้ง

สมมติว่าคุณกำลังสร้างแอพ "mood weather" ที่ค้นหาตำแหน่ง GPS ของผู้ใช้และตรวจสอบอุณหภูมิปัจจุบันในพื้นที่นั้นและบอกอารมณ์ แอพประเภทนี้ไม่จำเป็นต้องประกาศจุดสิ้นสุดของ API มันแค่ต้องสามารถเข้าถึงจุดปลาย API ได้

ไอออน

นี่เป็นห้องสมุดที่ยอดเยี่ยมสำหรับการเข้าถึงประเภทนี้

โปรดอ่านคำตอบที่ยอดเยี่ยมของ msysmilu ( https://stackoverflow.com/a/28559884/1024412 )

โหลดภาพผ่าน HTTP

การระดมยิง

วอลเล่ย์ยังสามารถใช้สำหรับ ReST APIs ได้อีกด้วย แต่เนื่องจากจำเป็นต้องมีการติดตั้งที่ซับซ้อนมากขึ้นฉันต้องการใช้ Retrofit จาก Square ดังกล่าวข้างต้น ( http://square.github.io/retrofit/ )

สมมติว่าคุณกำลังสร้างแอปเครือข่ายสังคมและต้องการโหลดรูปโปรไฟล์ของเพื่อน

build.gradle

เพิ่มบรรทัดนี้ในโมดูลระดับ buid.gradle ของคุณ:

implementation 'com.android.volley:volley:1.0.0'

ImageFetch.java

วอลเล่ย์ต้องการการติดตั้งมากกว่า Retrofit คุณจะต้องสร้างคลาสเช่นนี้เพื่อตั้งค่า RequestQueue, ImageLoader และ ImageCache แต่ก็ไม่ได้เลวร้ายเกินไป:

public class ImageFetch {
    private static ImageLoader imageLoader = null;
    private static RequestQueue imageQueue = null;

    public static ImageLoader getImageLoader(Context ctx){
        if(imageLoader == null){
            if(imageQueue == null){
                imageQueue = Volley.newRequestQueue(ctx.getApplicationContext());
            }
            imageLoader = new ImageLoader(imageQueue, new ImageLoader.ImageCache() {
                Map<String, Bitmap> cache = new HashMap<String, Bitmap>();
                @Override
                public Bitmap getBitmap(String url) {
                    return cache.get(url);
                }
                @Override
                public void putBitmap(String url, Bitmap bitmap) {
                    cache.put(url, bitmap);
                }
            });
        }
        return imageLoader;
    }
}

user_view_dialog.xml

เพิ่มสิ่งต่อไปนี้ในไฟล์เลย์เอาต์ XML ของคุณเพื่อเพิ่มรูปภาพ:

<com.android.volley.toolbox.NetworkImageView
    android:id="@+id/profile_picture"
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    app:srcCompat="@android:drawable/spinner_background"/>

UserViewDialog.java

เพิ่มรหัสต่อไปนี้ไปยังวิธีการ onCreate (Fragment, Activity) หรือ Constructor (Dialog):

NetworkImageView profilePicture = view.findViewById(R.id.profile_picture);
profilePicture.setImageUrl("http://example.com/users/images/profile.jpg", ImageFetch.getImageLoader(getContext());

ปิกัสโซ

อีกห้องสมุดที่ยอดเยี่ยมจากสแควร์ โปรดดูเว็บไซต์สำหรับตัวอย่างที่ดี: http://square.github.io/picasso/


16

ในคำง่าย ๆ

อย่าทำงานบนเครือข่ายในส่วนของ UI

ตัวอย่างเช่นถ้าคุณทำคำร้องขอ HTTP นั่นเป็นการกระทำของเครือข่าย

สารละลาย:

  1. คุณต้องสร้างกระทู้ใหม่
  2. หรือใช้คลาส AsyncTask

วิธีการ:

ใส่ผลงานทั้งหมดของคุณไว้ข้างใน

  1. run() วิธีการของด้ายใหม่
  2. หรือ doInBackground()วิธีการของคลาส AsyncTask

แต่:

เมื่อคุณได้รับบางสิ่งจากการตอบกลับของเครือข่ายและต้องการแสดงในมุมมองของคุณ (เช่นข้อความตอบกลับการแสดงผลใน TextView) คุณจะต้องกลับไปที่เธรดUI

ViewRootImpl$CalledFromWrongThreadExceptionหากคุณไม่ได้ทำมันคุณจะได้รับ

ทำอย่างไร?

  1. ในขณะที่ใช้ AsyncTask ให้ปรับปรุงมุมมองจากonPostExecute()วิธีการ
  2. หรือrunOnUiThread()วิธีการโทรและปรับปรุงมุมมองภายในrun()วิธีการ

12

คุณสามารถย้ายส่วนหนึ่งของรหัสของคุณไปยังเธรดอื่นเพื่อลดการโหลดmain threadและหลีกเลี่ยงการรับANR , NetworkOnMainThreadException , IllegalStateException (เช่นไม่สามารถเข้าถึงฐานข้อมูลบนเธรดหลักเนื่องจากอาจล็อค UI เป็นเวลานาน)

มีวิธีการบางอย่างที่คุณควรเลือกขึ้นอยู่กับสถานการณ์

Java Threadหรือ Android HandlerThread

เธรด Java ใช้ครั้งเดียวเท่านั้นและจะตายหลังจากเรียกใช้เมธอดการรัน

HandlerThread เป็นคลาสที่มีประโยชน์สำหรับการเริ่มหัวข้อใหม่ที่มี looper

AsyncTask

AsyncTaskได้รับการออกแบบให้เป็นผู้ช่วยที่อยู่รอบ ๆเธรดและตัวจัดการและไม่ได้เป็นกรอบเธรดทั่วไป AsyncTasks ควรใช้อย่างเหมาะสมสำหรับการดำเนินการสั้น ๆ (ไม่เกินสองสามวินาที) หากคุณต้องการให้เธรดทำงานต่อเนื่องเป็นเวลานานขอแนะนำอย่างยิ่งให้คุณใช้ API ต่างๆที่จัดทำโดยแพ็คเกจ java.util.concurrent เช่นผู้ปฏิบัติการ , ThreadPoolExecutorและFutureTask

การใช้งานเธรดพูลThreadPoolExecutor , ScheduledThreadPoolExecutor ...

คลาส ThreadPoolExecutor ที่ใช้ ExecutorService ซึ่งให้การควบคุมที่ดีในเธรดพูล (เช่นขนาดพูลหลักขนาดพูลสูงสุดให้เวลามีชีวิต ฯลฯ )

ScheduledThreadPoolExecutor - คลาสที่ขยาย ThreadPoolExecutor มันสามารถกำหนดเวลางานหลังจากที่ได้รับล่าช้าหรือเป็นระยะ

FutureTask

FutureTask ทำการประมวลผลแบบอะซิงโครนัสอย่างไรก็ตามถ้าผลลัพธ์ยังไม่พร้อมหรือการประมวลผลไม่เสร็จการเรียกใช้ get () จะถูกบล็อกเธรด

AsyncTaskLoaders

AsyncTaskLoaders ตามที่พวกเขาแก้ปัญหาจำนวนมากที่มีอยู่ใน AsyncTask

IntentService

นี่คือตัวเลือกการลดขนาดสำหรับการประมวลผลที่รันบน Android เป็นเวลานานตัวอย่างที่ดีคือการอัปโหลดหรือดาวน์โหลดไฟล์ขนาดใหญ่ การอัปโหลดและดาวน์โหลดอาจดำเนินต่อไปแม้ว่าผู้ใช้จะออกจากแอปและคุณไม่ต้องการปิดกั้นผู้ใช้ไม่ให้สามารถใช้แอปได้ในขณะที่งานเหล่านี้กำลังดำเนินอยู่

JobScheduler

ได้อย่างมีประสิทธิภาพคุณต้องสร้างบริการและสร้างงานโดยใช้ JobInfo.Builder ที่ระบุเกณฑ์ของคุณเมื่อต้องการเรียกใช้บริการ

RxJava

ไลบรารีสำหรับการเขียนโปรแกรมแบบอะซิงโครนัสและอิงเหตุการณ์โดยใช้ลำดับที่สังเกตได้

Coroutines (Kotlin)

ส่วนสำคัญของมันคือมันทำให้โค้ดแบบอะซิงโครนัสมีลักษณะคล้ายกับซิงโครนัส

อ่านเพิ่มเติมที่นี่ , ที่นี่ , ที่นี่ , ที่นี่


ทำงานให้ฉัน ... ฉันใช้ AsyncTask อย่างกว้างขวาง แต่เมื่องานหนึ่งกำลังทำงานอีกงานหนึ่งจะรอ ฉันต้องการแก้ไขปัญหานี้ ตอนนี้ทำงานร่วมกับ executeonexecutor เรามาดูกันว่ามันจะทำงานอย่างไรกับอุปกรณ์หน่วยความจำเหลือน้อย
Suraj Shingade

โปรดดูวิธีการ: asyncTask.executeOnExecutor (AsyncTask.THREAD_POOL_EXECUTOR, params); เพื่อเรียกใช้งานของคุณพร้อมกัน
yoAlex5

10

แม้ว่าข้างบนจะมีกลุ่มโซลูชันขนาดใหญ่ แต่ก็ไม่มีใครพูดถึงcom.koushikdutta.ion: https://github.com/koush/ion

นอกจากนี้ยังใช้แบบไม่ซิงค์และใช้งานง่ายมาก :

Ion.with(context)
.load("http://example.com/thing.json")
.asJsonObject()
.setCallback(new FutureCallback<JsonObject>() {
   @Override
    public void onCompleted(Exception e, JsonObject result) {
        // do stuff with the result or error
    }
});

10

มีการอธิบายโซลูชันใหม่ThreadและAsyncTaskแล้ว

AsyncTaskควรใช้สำหรับการดำเนินงานในระยะสั้น ปกติThreadไม่เหมาะสำหรับ Android

ดูที่โซลูชันทางเลือกโดยใช้HandlerThreadและHandler

HandlerThread

คลาสที่มีประโยชน์สำหรับการเริ่มเธรดใหม่ที่มี looper Looper สามารถใช้สร้างคลาสตัวจัดการได้ โปรดทราบว่าstart()จะต้องยังคงถูกเรียก

handler:

ตัวจัดการช่วยให้คุณสามารถส่งและประมวลผลข้อความและวัตถุที่เรียกใช้ที่เชื่อมโยงกับ MessageQueue ของเธรด แต่ละอินสแตนซ์ของตัวจัดการเกี่ยวข้องกับเธรดเดี่ยวและคิวข้อความของเธรดนั้น เมื่อคุณสร้างตัวจัดการใหม่มันจะถูกผูกไว้กับคิวเธรด / ข้อความของเธรดที่สร้างมัน - จากจุดนั้นเป็นต้นไปมันจะส่งข้อความและ runnables ไปยังคิวข้อความนั้นและดำเนินการเมื่อพวกเขาออกมาจากข้อความ คิว.

สารละลาย:

  1. สร้าง HandlerThread

  2. สอบถามstart()เกี่ยวกับHandlerThread

  3. สร้างHandlerโดยเริ่มLooperจากHanlerThread

  4. ฝังรหัสการดำเนินการเครือข่ายของคุณในRunnableวัตถุ

  5. ส่งRunnableงานไปที่Handler

ตัวอย่างโค้ดซึ่งอยู่ NetworkOnMainThreadException

HandlerThread handlerThread = new HandlerThread("URLConnection");
handlerThread.start();
handler mainHandler = new Handler(handlerThread.getLooper());

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            Log.d("Ravi", "Before IO call");
            URL page = new URL("http://www.google.com");
            StringBuffer text = new StringBuffer();
            HttpURLConnection conn = (HttpURLConnection) page.openConnection();
            conn.connect();
            InputStreamReader in = new InputStreamReader((InputStream) conn.getContent());
            BufferedReader buff = new BufferedReader(in);
            String line;
            while ( (line =  buff.readLine()) != null) {
                text.append(line + "\n");
            }
            Log.d("Ravi", "After IO call");
            Log.d("Ravi",text.toString());

        }catch( Exception err){
            err.printStackTrace();
        }
    }
};
mainHandler.post(myRunnable);

ข้อดีของการใช้วิธีนี้:

  1. การสร้างใหม่Thread/AsyncTaskสำหรับการดำเนินการแต่ละเครือข่ายมีราคาแพง Thread/AsyncTaskจะถูกทำลายและสร้างขึ้นใหม่สำหรับการดำเนินงานเครือข่ายต่อไป แต่ด้วยHandlerและHandlerThreadวิธีการที่คุณสามารถส่งดำเนินงานเครือข่ายจำนวนมาก (เป็นงาน Runnable) เดียวโดยใช้HandlerThreadHandler

8

RxAndroidเป็นอีกทางเลือกที่ดีกว่าสำหรับปัญหานี้และช่วยให้เราไม่ต้องวุ่นวายกับการสร้างหัวข้อแล้วโพสต์ผลลัพธ์บน Android UI thread เราเพียงแค่ต้องระบุเธรดที่จะต้องเรียกใช้งานและทุกอย่างได้รับการจัดการภายใน

Observable<List<String>> musicShowsObservable = Observable.fromCallable(new Callable<List<String>>() { 

  @Override 
  public List<String> call() { 
    return mRestClient.getFavoriteMusicShows(); 
  }
});

mMusicShowSubscription = musicShowsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<String>>() {

    @Override 
    public void onCompleted() { }

    @Override 
    public void onError(Throwable e) { }

    @Override 
    public void onNext(List<String> musicShows){
        listMusicShows(musicShows);
    }
});
  1. โดย specifiying (Schedulers.io()), RxAndroid จะทำงานgetFavoriteMusicShows() บนเธรดอื่น

  2. โดยการใช้AndroidSchedulers.mainThread()เราต้องการสังเกต Observable นี้ในเธรด UI นั่นคือเราต้องการให้onNext()callback ของเราถูกเรียกบนเธรด UI


8

เธรดหลักคือเธรด UI และคุณไม่สามารถทำการดำเนินการในเธรดหลักซึ่งอาจบล็อกการโต้ตอบผู้ใช้ คุณสามารถแก้ปัญหานี้ได้สองวิธี:

บังคับให้ทำงานในเธรดหลักเช่นนี้

StrictMode.ThreadPolicy threadPolicy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(threadPolicy);

หรือสร้างตัวจัดการอย่างง่ายและอัปเดตเธรดหลักหากคุณต้องการ

Runnable runnable;
Handler newHandler;

newHandler = new Handler();
runnable = new Runnable() {
    @Override
    public void run() {
         try {
            //update UI
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
};
newHandler.post(runnable);

และเพื่อหยุดการใช้เธรด:

newHandler.removeCallbacks(runnable);

สำหรับข้อมูลเพิ่มเติมตรวจสอบสิ่งนี้: เธรดที่ไม่เจ็บปวด


ขอบคุณ. เวอร์ชัน 1 ช่วยเมื่อเพิ่มเป็น Action แรกใน onCreate
Ingo

7

วิธีนี้ใช้ได้ผล เพิ่งทำให้คำตอบของ Dr.Luiji ง่ายขึ้นเล็กน้อย

new Thread() {
    @Override
    public void run() {
        try {
            //Your code goes here
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}.start();

7

บน Android การทำงานของเครือข่ายไม่สามารถรันบนเธรดหลักได้ คุณสามารถใช้ Thread, AsyncTask (งานระยะสั้น), บริการ (งานระยะยาว) เพื่อดำเนินการเครือข่าย


7

การเข้าถึงทรัพยากรเครือข่ายจากเธรดหลัก (UI) ทำให้เกิดข้อยกเว้นนี้ ใช้เธรดแยกต่างหากหรือ AsyncTask สำหรับเข้าถึงทรัพยากรเครือข่ายเพื่อหลีกเลี่ยงปัญหานี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.