พื้นฐานของ Android: ใช้งานรหัสในเธรด UI


450

ในมุมมองของการเรียกใช้รหัสในเธรด UI มีความแตกต่างระหว่าง:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

หรือ

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

และ

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}

เพื่อชี้แจงคำถามของฉัน: ฉันคิดว่ารหัสเหล่านั้นถูกเรียกมาจากเธรดบริการซึ่งโดยปกติแล้วจะเป็นผู้ฟัง ฉันก็ควรจะมีงานหนักที่จะทำให้สำเร็จทั้งในฟังก์ชั่น doInBackground () ของ AsynkTask หรือในงานใหม่ (... ) เรียกว่าก่อนหน้าตัวอย่างสองอันแรก อย่างไรก็ตาม onPostExecute () ของ AsyncTask กำลังถูกวางที่ส่วนท้ายของคิวเหตุการณ์ใช่ไหม
Luky

คำตอบ:


288

ไม่มีสิ่งใดที่เหมือนกันอย่างแน่นอนแม้ว่าพวกเขาจะมีผลกระทบสุทธิเหมือนกัน

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

หนึ่งในสามสมมติว่าคุณสร้างและดำเนินตัวอย่างของBackgroundTaskจะเสียมากเวลาโลภด้ายออกจากสระว่ายน้ำด้ายในการดำเนินการเริ่มต้นไม่มี-op ที่สุดก่อนที่จะทำในสิ่งที่จะมีจำนวนdoInBackground() post()นี่คือประสิทธิภาพที่น้อยที่สุดของทั้งสามคน ใช้AsyncTaskถ้าคุณจะมีงานที่ต้องทำในหัวข้อพื้นหลังไม่เพียง onPostExecute()แต่สำหรับการใช้งานของ


27
นอกจากนี้โปรดทราบว่าAsyncTask.execute()คุณต้องโทรจากเธรด UI ต่อไปซึ่งทำให้ตัวเลือกนี้ไร้ประโยชน์สำหรับกรณีการใช้งานของการเรียกใช้โค้ดบนเธรด UI จากเธรดพื้นหลังเว้นแต่ว่าคุณจะย้ายพื้นหลังทั้งหมดของคุณทำงานdoInBackground()และใช้AsyncTaskอย่างถูกต้อง
kabuko

@kabuko ฉันจะตรวจสอบว่าฉันโทรAsyncTaskจากเธรด UI ได้อย่างไร
Neil Galiaskarov

@NeilGaliaskarov นี่เป็นตัวเลือกที่ดี: stackoverflow.com/a/7897562/1839500
Dick Lucas

1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
แบน geoengineering

1
@NeilGaliaskarov สำหรับรุ่นที่มากกว่าหรือเท่ากับการใช้ MLooper.getMainLooper().isCurrentThread
Balu Sangem

252

ฉันชอบอันนี้จากคำวิจารณ์ HPPมันสามารถใช้ได้ทุกที่โดยไม่มีพารามิเตอร์:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

2
มันมีประสิทธิภาพแค่ไหน? มันเหมือนกับตัวเลือกอื่น ๆ หรือไม่?
EmmanuelMess

59

มีวิธีที่สี่คือการใช้ Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

56
คุณควรระวังสิ่งนี้ เพราะถ้าคุณสร้างตัวจัดการในเธรด UI ที่ไม่ใช่คุณจะโพสต์ข้อความไปยังเธรด UI ที่ไม่ใช่ ตัวจัดการตามข้อความประกาศเริ่มต้นไปยังเธรดที่สร้างขึ้น
lujop

108
เพื่อดำเนินการในเธรด UI หลักทำnew Handler(Looper.getMainLooper()).post(r)ซึ่งเป็นวิธีที่ต้องการเนื่องจากLooper.getMainLooper()ใช้การเรียกแบบสแตติกถึงหลักในขณะที่postOnUiThread()ต้องมีอินสแตนซ์ของMainActivityในขอบเขต
HPP

1
@HPP ฉันไม่ทราบวิธีการนี้จะเป็นวิธีที่ยอดเยี่ยมเมื่อคุณไม่มีกิจกรรมหรือไม่มีมุมมอง ใช้งานได้ดี! ขอบคุณมากมาก ๆ !
Sulfkain

@lujop เดียวกันเป็นกรณีที่มีวิธีการโทรกลับ OnPreExecute ของ AsyncTask
Sreekanth Karumanaghat

18

คำตอบของ Pomber นั้นเป็นที่ยอมรับ แต่ฉันไม่ใช่แฟนตัวยงของการสร้างวัตถุใหม่ซ้ำ ๆ ทางออกที่ดีที่สุดคือวิธีที่พยายามลดความจำเป็นของหน่วยความจำ ใช่มีการรวบรวมขยะอัตโนมัติ แต่การอนุรักษ์หน่วยความจำในอุปกรณ์มือถือนั้นอยู่ในขอบเขตของแนวปฏิบัติที่ดีที่สุด รหัสด้านล่างอัปเดต TextView ในบริการ

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

มันสามารถใช้ได้จากทุกที่เช่นนี้

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);

7
+1 สำหรับการกล่าวถึง GC และการสร้างวัตถุ The best solutions are always the ones that try to mitigate memory hogแต่ผมไม่จำเป็นต้องเห็นด้วยกับ มีเกณฑ์อื่น ๆ อีกมากมายสำหรับการมีและนี้มีกลิ่นเล็กน้อยbest premature optimizationนั่นคือถ้าคุณไม่ทราบว่าคุณกำลังเรียกมันว่ามีจำนวนวัตถุที่สร้างเป็นปัญหา (เมื่อเทียบกับวิธีอื่น ๆ ที่แอพของคุณอาจสร้างขยะ) วิธีbestการเขียนสิ่งที่ง่ายที่สุด (เข้าใจง่ายที่สุดคือเข้าใจได้ง่ายกว่า) ) โค้ดและไปยังภารกิจอื่น
ToolmakerSteve

2
BTW textViewUpdaterHandlerจะเป็นชื่อที่ดีกว่าเช่นuiHandlerหรือmainHandlerโดยทั่วไปจะมีประโยชน์สำหรับการโพสต์ไปยังเธรด UI หลัก มันไม่ได้เชื่อมโยงกับคลาส TextViewUpdater ของคุณเลย ฉันจะย้ายมันออกจากส่วนที่เหลือของรหัสนั้นและทำให้ชัดเจนว่าสามารถนำไปใช้ที่อื่น ... ส่วนที่เหลือของรหัสนั้นน่าสงสัยเพราะเพื่อหลีกเลี่ยงการสร้างวัตถุหนึ่งแบบไดนามิกคุณทำลายสิ่งที่อาจเป็นสายเดียวใน สองขั้นตอนsetTextและpostที่ต้องพึ่งพาวัตถุระยะยาวที่คุณใช้เป็นชั่วคราว ความซับซ้อนที่ไม่จำเป็นและไม่ปลอดภัยต่อเธรด บำรุงรักษาไม่ง่าย
ToolmakerSteve

หากคุณจริงๆมีสถานการณ์ที่นี้จะเรียกว่าหลายครั้งเพื่อให้มันมีค่าแคชuiHandlerและtextViewUpdaterแล้วปรับปรุงการเรียนของคุณโดยการเปลี่ยนไปpublic void setText(String txt, Handler uiHandler)และการเพิ่มวิธีเส้นแล้วโทรสามารถทำได้ในขั้นตอนที่หนึ่ง:uiHandler.post(this); textViewUpdater.setText("Hello", uiHandler);จากนั้นในอนาคตหากจำเป็นต้องมีเธรดที่ปลอดภัยเมธอดสามารถห่อคำสั่งไว้ในการล็อกuiHandlerและผู้โทรยังคงไม่เปลี่ยนแปลง
ToolmakerSteve

ฉันค่อนข้างมั่นใจว่าคุณสามารถเรียกใช้ Runnable เพียงครั้งเดียว ซึ่งทำลายความคิดของคุณอย่างแน่นอนซึ่งเป็นสิ่งที่ดีโดยรวม
Nativ

@Nativ Nope สามารถรันได้หลายครั้ง ด้ายไม่สามารถ
Zbyszek

8

ในฐานะของ Android P คุณสามารถใช้getMainExecutor():

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

จากเอกสารนักพัฒนา Android :

ส่งคืน Executor ที่จะรันภารกิจที่จัดคิวบนเธรดหลักที่เชื่อมโยงกับบริบทนี้ นี่คือเธรดที่ใช้เพื่อส่งการเรียกไปยังคอมโพเนนต์ของแอปพลิเคชัน (กิจกรรมบริการ ฯลฯ )

จากคอมมอนส์บล็อก :

คุณสามารถเรียกใช้ getMainExecutor () บน Context เพื่อรับ Executor ที่จะเรียกใช้งานบนเธรดแอปพลิเคชันหลัก มีวิธีอื่น ๆ ในการทำสิ่งนี้ให้สำเร็จโดยใช้ Looper และการใช้งาน Executor แบบกำหนดเอง แต่วิธีนี้ง่ายกว่า


7

หากคุณจำเป็นต้องใช้ในส่วนคุณควรใช้

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

แทน

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

เนื่องจากจะมีข้อยกเว้นตัวชี้โมฆะในบางสถานการณ์เช่นบางส่วนของเพจเจอร์


1

สวัสดีพวกนี้เป็นคำถามพื้นฐานที่ฉันบอกไป

ใช้ตัวจัดการ

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

มีเหตุผลใดบ้างที่คุณเลือกใช้ตัวจัดการวัตถุประสงค์ทั่วไปผ่าน runOnUiThread?
ThePartyTurtle

สิ่งนี้จะให้java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()ถ้าไม่ได้เรียกจากเธรด UI
Roc Boronat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.