ภาพรวมของกระบวนการและชุดข้อความ

เมื่อคอมโพเนนต์ของแอปพลิเคชันเริ่มต้นทำงานและแอปพลิเคชันไม่มีคอมโพเนนต์อื่นทำงานอยู่ ระบบ Android จะเริ่มกระบวนการ Linux ใหม่สำหรับแอปพลิเคชันที่มีเธรดการดำเนินการเดียว โดยค่าเริ่มต้น คอมโพเนนต์ทั้งหมดของแอปพลิเคชันเดียวกันจะทำงานในกระบวนการและเธรดเดียวกัน ซึ่งเรียกว่าเธรด หลัก

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

เอกสารนี้จะอธิบายวิธีการทำงานของกระบวนการและเธรดในแอปพลิเคชัน Android

กระบวนการ

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

รายการ Manifest สำหรับองค์ประกอบคอมโพเนนต์แต่ละประเภท ได้แก่ <activity>, <service>, <receiver>, และ <provider> รองรับแอตทริบิวต์ android:process ที่ระบุกระบวนการที่คอมโพเนนต์ทำงานอยู่ได้ คุณสามารถตั้งค่าแอตทริบิวต์นี้เพื่อให้คอมโพเนนต์แต่ละรายการทำงานในกระบวนการของตัวเอง หรือเพื่อให้คอมโพเนนต์บางรายการใช้กระบวนการร่วมกันในขณะที่คอมโพเนนต์อื่นๆ ไม่ใช้

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

องค์ประกอบ <application> ยังรองรับแอตทริบิวต์ android:process ซึ่งคุณสามารถใช้เพื่อตั้งค่าเริ่มต้นที่ใช้กับคอมโพเนนต์ทั้งหมดได้

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

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

รายละเอียดของวงจรการทำงานของกระบวนการและความสัมพันธ์กับสถานะของแอปพลิเคชันจะอธิบายไว้ใน หัวข้อกระบวนการและวงจรการทำงานของแอป

เธรด

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

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

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

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

จากมุมมองของผู้ใช้ แอปพลิเคชันจะดูเหมือนหยุดตอบสนอง และที่แย่กว่านั้นคือ หากเธรด UI ถูกบล็อกนานกว่า 2-3 วินาที ผู้ใช้จะเห็นกล่องโต้ตอบ "แอปพลิเคชันไม่ ตอบสนอง" (ANR) จากนั้นผู้ใช้อาจตัดสินใจออกจากแอปพลิเคชันหรือถอนการติดตั้งแอปพลิเคชัน

โปรดทราบว่าชุดเครื่องมือ Android UI ไม่ใช่ เธรดที่ปลอดภัย ดังนั้นอย่าจัดการ UI จากเธรดผู้ปฏิบัติงาน ให้จัดการอินเทอร์เฟซผู้ใช้ทั้งหมดจากเธรด UI โมเดลเธรดเดียวของ Android มีกฎ 2 ข้อดังนี้

  1. อย่าบล็อกเธรด UI
  2. อย่าเข้าถึงชุดเครื่องมือ Android UI จากภายนอกเธรด UI

เธรดผู้ปฏิบัติงาน

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

Android มีหลายวิธีในการเข้าถึงเธรด UI จากเธรดอื่นๆ เพื่อช่วยให้คุณปฏิบัติตามกฎเหล่านี้ได้ รายการเมธอดที่ช่วยคุณได้มีดังนี้

ตัวอย่างต่อไปนี้แสดงวิธีลดภาระงานไปยังเธรดเบื้องหลังและอัปเดตเธรด UI เมื่อทำงานเสร็จแล้ว

Kotlin

// Kotlin coroutines implementation.
fun onClick(v: View) {
    // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment).
    lifecycleScope.launch {
        // Run the blocking task on the IO dispatcher.
        val bitmap = withContext(Dispatchers.IO) {
            BitmapFactory.decodeFile("image.png")
        }
        // Back on the main thread, update the UI.
        imageView.setImageBitmap(bitmap)
    }
}

Java

// Java Executor implementation.
// (executorService is assumed to be defined elsewhere).
public void onClick(View v) {
    executorService.execute(() -> {
        // Run the heavy task on a background thread.
        Bitmap bitmap = BitmapFactory.decodeFile("image.png");

        // Update the View on the UI thread.
        imageView.post(() -> imageView.setImageBitmap(bitmap));
    });
}

การใช้งานนี้เป็นเธรดที่ปลอดภัยเนื่องจากระบบจะดำเนินการเบื้องหลังจากเธรดแยกต่างหากในขณะที่ระบบจะจัดการ ImageView จากเธรด UI เสมอ

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

เมธอดที่ปลอดภัยต่อเธรด

ในบางสถานการณ์ ระบบจะเรียกเมธอดที่คุณใช้จากเธรดมากกว่า 1 เธรด ดังนั้นคุณจึงต้องเขียนเมธอดให้ปลอดภัยต่อเธรด

โดยเฉพาะอย่างยิ่งสำหรับเมธอดที่เรียกจากระยะไกลได้ เช่น เมธอดในบริการที่มีผลผูกพัน เมื่อการเรียกใช้เมธอดที่ใช้ใน IBinder มาจากกระบวนการเดียวกันกับที่ IBinder ทำงานอยู่ ระบบจะดำเนินการเมธอดในเธรดของผู้เรียก อย่างไรก็ตาม เมื่อการเรียกใช้มาจากกระบวนการอื่น ระบบจะดำเนินการเมธอดในเธรดที่เลือกจากพูลเธรดที่ระบบดูแลรักษาไว้ในกระบวนการเดียวกันกับ IBinder ระบบจะไม่ดำเนินการในเธรด UI ของกระบวนการ

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

ในทำนองเดียวกัน ผู้ให้บริการเนื้อหาอาจได้รับคำขอข้อมูลที่มาจากกระบวนการอื่นๆ คลาส ContentResolver และ ContentProvider จะซ่อนรายละเอียดเกี่ยวกับวิธีจัดการการสื่อสารระหว่างโปรเซส (IPC) แต่ระบบจะเรียกเมธอด ContentProvider ที่ตอบสนองต่อคำขอเหล่านั้น ได้แก่ เมธอด query(), insert(), delete(), update(), และ getType()—จากพูลเทรดในกระบวนการของ Content Provider ไม่ใช่เธรด UI สำหรับกระบวนการ เนื่องจากระบบอาจเรียกเมธอดเหล่านี้จากเธรดจำนวนเท่าใดก็ได้ในเวลาเดียวกัน คุณจึงต้องใช้เมธอดเหล่านี้ให้ปลอดภัยต่อเธรดด้วย

การสื่อสารระหว่างโปรเซส

Android มีกลไกสำหรับการสื่อสารระหว่างกระบวนการ (IPC) โดยใช้ RPC ซึ่งกิจกรรมหรือคอมโพเนนต์ของแอปพลิเคชันอื่นๆ จะเรียกเมธอด แต่ระบบจะดำเนินการเมธอดจากระยะไกลในกระบวนการอื่น และส่งผลลัพธ์กลับไปยังผู้เรียก ซึ่งเกี่ยวข้องกับการแยกการเรียกเมธอดและข้อมูลที่เกี่ยวข้องออกเป็นระดับที่ระบบปฏิบัติการเข้าใจได้ การส่งข้อมูลจากการเรียกเมธอดจากกระบวนการและพื้นที่ที่อยู่ภายในไปยังกระบวนการและพื้นที่ที่อยู่นอกระบบ และจากนั้นประกอบและดำเนินการเรียกใช้ใหม่

จากนั้นระบบจะส่งค่าที่แสดงผลในทิศทางตรงกันข้าม Android มีโค้ดทั้งหมดเพื่อดำเนินการธุรกรรม IPC เหล่านี้ คุณจึงมุ่งเน้นไปที่การกำหนดและใช้อินเทอร์เฟซการเขียนโปรแกรม RPC ได้

หากต้องการดำเนินการ IPC แอปพลิเคชันของคุณต้องผูกกับบริการโดยใช้ bindService() ดูข้อมูลเพิ่มเติมได้ที่ภาพรวมของบริการ