ข่าวสารเกี่ยวกับผลิตภัณฑ์

กำหนดค่าและแก้ปัญหาเกี่ยวกับกฎ Keep ของ R8

ใช้เวลาอ่าน 7 นาที
ดูโปรไฟล์ของ Ajesh Pai ดูโปรไฟล์ของ Ben Weiss
Ajesh Pai & Ben Weiss

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

การเปิดใช้ R8 เป็นขั้นตอนสำคัญในการเตรียมแอปสำหรับการเผยแพร่ แต่กำหนดให้นักพัฒนาแอปต้องให้คำแนะนำในรูปแบบ "กฎ Keep"

หลังจากอ่านบทความนี้แล้ว โปรดดูวิดีโอ Performance Spotlight Week เกี่ยวกับการเปิดใช้ การแก้ไขข้อบกพร่อง และการแก้ปัญหาเครื่องมือเพิ่มประสิทธิภาพ R8 ใน YouTube

 

 

เหตุผลที่ต้องมีกฎ Keep

ความจำเป็นในการเขียนกฎ Keep เกิดจากความขัดแย้งหลักที่ว่า R8 เป็นเครื่องมือวิเคราะห์แบบคงที่ แต่แอป Android มักอาศัยรูปแบบการดำเนินการแบบไดนามิก เช่น การทำ Reflection หรือการเรียกเข้าและออกจากโค้ดเนทีฟโดยใช้ JNI (Java Native Interface)

R8 สร้างกราฟของโค้ดที่ ใช้แล้ว โดยการวิเคราะห์การเรียกโดยตรง เมื่อมีการเข้าถึงโค้ดในลักษณะแบบไดนามิก การวิเคราะห์แบบคงที่ของ R8 จะคาดการณ์ไม่ได้และจะระบุโค้ดดังกล่าวว่า ไม่ได้ใช้แล้ว และนำออก ซึ่งจะทำให้เกิดข้อขัดข้องขณะรันไทม์

กฎ Keep คือคำสั่งที่ชัดเจนที่ส่งไปยังคอมไพเลอร์ R8 โดยระบุว่า "คลาส เมธอด หรือฟิลด์ที่เฉพาะเจาะจงนี้เป็นจุดเริ่มต้นที่จะเข้าถึงแบบไดนามิกขณะรันไทม์ คุณต้องเก็บไว้ แม้ว่าจะไม่พบการอ้างอิงโดยตรงก็ตาม"

ดูรายละเอียดเพิ่มเติมเกี่ยวกับกฎ Keep ได้ในคู่มืออย่างเป็นทางการ

ตำแหน่งที่เขียนกฎ Keep

กฎ Keep ที่กำหนดเองสำหรับแอปพลิเคชันจะเขียนในไฟล์ข้อความ ตามธรรมเนียมปฏิบัติ ไฟล์นี้จะมีชื่อว่า proguard-rules.pro และอยู่ในรูทของโมดูลแอปหรือไลบรารี จากนั้นไฟล์นี้จะระบุไว้ในประเภทบิลด์ release ของไฟล์ build.gradle.kts ของโมดูล

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

ใช้ไฟล์เริ่มต้นที่ถูกต้อง

เมธอด getDefaultProguardFile จะนำเข้าชุดกฎเริ่มต้นที่ Android SDK มีให้ หากใช้ไฟล์ที่ไม่ถูกต้อง แอปอาจไม่ได้รับการเพิ่มประสิทธิภาพ โปรดใช้ proguard-android-optimize.txt ไฟล์นี้มีกฎ Keep เริ่มต้นสำหรับคอมโพเนนต์ Android มาตรฐาน และ เปิดใช้การเพิ่มประสิทธิภาพโค้ดของ R8 proguard-android.txt ที่ล้าสมัยจะมีเฉพาะกฎ Keep แต่ ไม่ได้ เปิดใช้การเพิ่มประสิทธิภาพของ R8

progaurd.png

เนื่องจากนี่เป็นปัญหาด้านประสิทธิภาพที่ร้ายแรง เราจึงเริ่มเตือนนักพัฒนาแอปเกี่ยวกับการใช้ไฟล์ที่ไม่ถูกต้อง โดยเริ่มใน Android Studio Narwhal 3 Feature Dropและเราจะเลิกให้การสนับสนุนไฟล์ proguard-android.txtที่ล้าสมัยโดยเริ่มจากปลั๊กอิน Android Gradle เวอร์ชัน 9.0 ดังนั้นโปรดอัปเกรดเป็นเวอร์ชันที่เพิ่มประสิทธิภาพแล้ว

วิธีเขียนกฎ Keep

กฎ Keep ประกอบด้วย 3 ส่วนหลักๆ ดังนี้

  1. ตัวเลือก เช่น -keep หรือ -keepclassmembers
  2. ตัวแก้ไขเพิ่มเติมที่ไม่บังคับ เช่น allowshrinking
  3. ข้อกำหนดคลาส ที่กำหนดโค้ดที่จะจับคู่

ดูไวยากรณ์และตัวอย่างฉบับเต็มได้ในคำแนะนำในการเพิ่มกฎ Keep

รูปแบบที่ไม่ควรใช้ของกฎ Keep

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

ตัวเลือกทั่วโลก

แฟล็กเหล่านี้เป็นตัวสลับทั่วโลกที่ไม่ควร ใช้ในบิลด์ที่เผยแพร่ โดยมีไว้สำหรับการแก้ปัญหาชั่วคราวเพื่อแยกปัญหาเท่านั้น

การใช้ -dontotptimize จะปิดใช้การเพิ่มประสิทธิภาพของ R8 อย่างมีประสิทธิภาพ ซึ่งจะทำให้แอปทำงานช้าลง

เมื่อใช้ -dontobfuscate คุณจะปิดใช้การเปลี่ยนชื่อทั้งหมด และการใช้ -dontshrink จะปิดการนำโค้ดที่ไม่ได้ใช้แล้วออก กฎทั่วโลกทั้ง 2 ข้อนี้จะเพิ่มขนาดแอป

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

กฎ Keep ที่กว้างเกินไป

วิธีที่ง่ายที่สุดในการทำให้ประโยชน์ของ R8 เป็นโมฆะคือการเขียนกฎ Keep ที่กว้างเกินไป กฎ Keep เช่นกฎด้านล่างจะสั่งให้เครื่องมือเพิ่มประสิทธิภาพ R8 ไม่ลดขนาด ไม่ปรับให้ยากต่อการอ่าน (Obfuscate) และไม่เพิ่มประสิทธิภาพคลาส ใดๆ ในแพ็กเกจนี้หรือแพ็กเกจย่อย ใดๆ ของแพ็กเกจ ซึ่งจะทำให้ประโยชน์ของ R8 สำหรับแพ็กเกจทั้งหมดนั้นหายไปโดยสิ้นเชิง ลองเขียนกฎ Keep ที่แคบและเฉพาะเจาะจงแทน

-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

โอเปอเรเตอร์การผกผัน (!)

โอเปอเรเตอร์การผกผัน (!) ดูเหมือนจะเป็นวิธีที่มีประสิทธิภาพในการยกเว้นแพ็กเกจจากกฎ แต่ก็ไม่ได้ง่ายขนาดนั้น ดูตัวอย่างนี้

-keep class !com.example.my_package.** { *; } // USE WITH CAUTION

คุณอาจคิดว่ากฎนี้หมายความว่า "ไม่เก็บคลาสในcom.example.package" แต่จริงๆ แล้วหมายความว่า "เก็บทุกคลาส เมธอด และพร็อพเพอร์ตี้ ในแอปพลิเคชันทั้งหมดที่ไม่ได้อยู่ใน com.example.package" หากคุณรู้สึกประหลาดใจกับความหมายนี้ โปรดตรวจสอบการปฏิเสธใดๆ ในการกำหนดค่า R8

กฎที่ซ้ำซ้อนสำหรับคอมโพเนนต์ Android

ข้อผิดพลาดทั่วไปอีกอย่างหนึ่งคือการเพิ่มกฎ Keep สำหรับ Activities, Services หรือ BroadcastReceivers ของแอปด้วยตนเอง ซึ่งเป็นสิ่งที่ไม่จำเป็น ไฟล์ proguard-android-optimize.txt เริ่มต้นมีกฎที่เกี่ยวข้องสำหรับคอมโพเนนต์ Android มาตรฐานเหล่านี้อยู่แล้วเพื่อให้ทำงานได้ทันที

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

แนวทางปฏิบัติแนะนำสำหรับกฎ Keep

ตอนนี้คุณทราบแล้วว่าไม่ควรทำอะไรบ้าง เรามาพูดถึงแนวทางปฏิบัติแนะนำกัน

เขียนกฎ Keep ที่แคบ

กฎ Keep ที่ดีควรแคบและเฉพาะเจาะจงมากที่สุด โดยควรเก็บเฉพาะสิ่งที่จำเป็นเท่านั้น เพื่อให้ R8 เพิ่มประสิทธิภาพส่วนอื่นๆ ได้

กฎคุณภาพ

 

-keep class com.example.** { ; }

 

ต่ำ: เก็บแพ็กเกจทั้งหมดและแพ็กเกจย่อย

 

-keep class com.example.MyClass { ; }

 

ต่ำ: เก็บทั้งคลาส ซึ่งอาจยังกว้างเกินไป
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
สูง: เก็บเฉพาะเมธอดและพร็อพเพอร์ตี้ที่เกี่ยวข้องจากคลาสที่เฉพาะเจาะจง

ใช้บรรพบุรุษร่วมกัน

แทนที่จะเขียนกฎ Keep แยกกันสำหรับโมเดลข้อมูลที่แตกต่างกันหลายรายการ ให้เขียนกฎเดียวที่กำหนดเป้าหมายเป็นคลาสพื้นฐานหรืออินเทอร์เฟซร่วมกัน กฎด้านล่างจะบอกให้ R8 เก็บสมาชิกของคลาสที่ใช้กฎนี้และปรับขนาดได้สูง

# Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

ใช้คำอธิบายประกอบเพื่อกำหนดเป้าหมายคลาสหลายรายการ

สร้างคำอธิบายประกอบที่กำหนดเอง (เช่น @Serialize) และใช้เพื่อ "ติดแท็ก" คลาสที่ต้องเก็บฟิลด์ไว้ นี่เป็นอีกรูปแบบที่ชัดเจน ประกาศได้ และปรับขนาดได้สูง คุณยังสร้างกฎ Keep สำหรับคำอธิบายประกอบที่มีอยู่แล้วจากเฟรมเวิร์กที่คุณใช้อยู่ได้ด้วย

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

เลือกตัวเลือก Keep ที่เหมาะสม

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

ตัวเลือก Keepสิ่งที่ตัวเลือกนี้ทำ
-keepป้องกันไม่ให้ระบบนำคลาส และสมาชิกที่ระบุไว้ในการประกาศ ออกหรือเปลี่ยนชื่อ
-keepclassmembersป้องกันไม่ให้ระบบนำ สมาชิกที่ระบุ ออกหรือเปลี่ยนชื่อ แต่จะอนุญาตให้นำคลาสออกได้เฉพาะในคลาสที่ไม่ได้นำออกด้วยวิธีอื่น
-keepclasseswithmembersเป็นการรวมกันระหว่างการเก็บคลาส และ สมาชิกของคลาส เฉพาะในกรณีที่ สมาชิกที่ระบุทั้งหมดมีอยู่

ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือก Keep ได้ในเอกสารประกอบสำหรับตัวเลือก Keep

อนุญาตการเพิ่มประสิทธิภาพด้วยตัวแก้ไข

ตัวแก้ไข เช่น allowshrinking และ allowobfuscation จะผ่อนปรนกฎ -keep ที่กว้าง ทำให้ R8 มีอำนาจในการเพิ่มประสิทธิภาพอีกครั้ง ตัวอย่างเช่น หากไลบรารีเวอร์ชันเก่าบังคับให้คุณใช้ -keep กับทั้งคลาส คุณอาจเรียกคืนการเพิ่มประสิทธิภาพบางส่วนได้โดยอนุญาตการลดขนาดและการปรับให้ยากต่อการอ่าน (Obfuscation)

# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

เพิ่มตัวเลือกทั่วโลกเพื่อการเพิ่มประสิทธิภาพเพิ่มเติม

นอกเหนือจากกฎ Keep แล้ว คุณยังเพิ่มแฟล็กทั่วโลกลงในไฟล์การกำหนดค่า R8 เพื่อกระตุ้นให้มีการเพิ่มประสิทธิภาพมากยิ่งขึ้นได้ด้วย

-repackageclasses เป็นตัวเลือกที่มีประสิทธิภาพซึ่งสั่งให้ R8 ย้ายคลาสที่ปรับให้ยากต่อการอ่าน (Obfuscate) ทั้งหมดไปยังแพ็กเกจเดียว ซึ่งจะช่วยประหยัดพื้นที่ในไฟล์ DEX ได้อย่างมากด้วยการนำสตริงชื่อแพ็กเกจที่ซ้ำซ้อนออก

-allowaccessmodification อนุญาตให้ R8 ขยายการเข้าถึง (เช่น จาก private เป็น public) เพื่อเปิดใช้การอินไลน์ที่เข้มงวดมากขึ้น ตอนนี้ตัวเลือกนี้เปิดใช้โดยค่าเริ่มต้นเมื่อใช้ proguard-android-optimize.txt.

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

และเพื่อให้ชัดเจนยิ่งขึ้น ในปลั๊กอิน Android Gradle เวอร์ชัน 9.0 เราจะเริ่มละเว้นแฟล็กการเพิ่มประสิทธิภาพทั่วโลกจากไลบรารีทั้งหมด

แนวทางปฏิบัติแนะนำสำหรับไลบรารี

แอป Android ทุกแอปต้องอาศัยไลบรารีไม่ทางใดก็ทางหนึ่ง ดังนั้นเรามาพูดถึงแนวทางปฏิบัติแนะนำสำหรับไลบรารีกัน

สำหรับนักพัฒนาแอปไลบรารี

หากไลบรารีของคุณใช้การทำ Reflection หรือ JNI คุณมีหน้าที่รับผิดชอบในการจัดหากฎ Keep ที่จำเป็นแก่ผู้ใช้ กฎเหล่านี้จะอยู่ในไฟล์ consumer-rules.pro ซึ่งจากนั้นจะรวมอยู่ในไฟล์ AAR ของไลบรารีโดยอัตโนมัติ

android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

สำหรับผู้ใช้ไลบรารี

กรองกฎ Keep ที่มีปัญหาออก

หากคุณต้องใช้ไลบรารีที่มีกฎ Keep ที่มีปัญหา คุณสามารถกรองกฎเหล่านั้นออกในไฟล์ build.gradle.kts โดยเริ่มจาก AGP 9.0 ซึ่งจะบอกให้ R8 ละเว้นกฎที่มาจากทรัพยากร Dependency ที่เฉพาะเจาะจง

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

กฎ Keep ที่ดีที่สุดคือกฎ Keep ที่ไม่มีอยู่

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีเลือกไลบรารีได้ที่เลือกไลบรารีอย่างชาญฉลาด

การแก้ข้อบกพร่องและการแก้ปัญหาการกำหนดค่า R8

เมื่อ R8 นำโค้ดที่ควรเก็บไว้หรือ APK มีขนาดใหญ่กว่าที่คาดไว้ ให้ใช้เครื่องมือต่อไปนี้เพื่อวินิจฉัยปัญหา

ค้นหากฎ Keep ที่ซ้ำกันและกฎ Keep ทั่วโลก

เนื่องจาก R8 รวมกฎจากแหล่งที่มาหลายสิบแหล่ง จึงอาจยากที่จะทราบว่าชุดกฎ "สุดท้าย" คืออะไร การเพิ่มแฟล็กนี้ลงในไฟล์ proguard-rules.pro จะสร้างรายงานที่สมบูรณ์

# Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

คุณสามารถค้นหาไฟล์นี้เพื่อค้นหากฎที่ซ้ำซ้อนหรือติดตามกฎที่มีปัญหา (เช่น -dontoptimize) กลับไปยังไลบรารีที่เฉพาะเจาะจงซึ่งมีกฎนั้นอยู่

ถาม R8 ว่า "เหตุใดคุณจึงเก็บสิ่งนี้ไว้"

หากคลาสที่คุณคาดว่าจะนำออกยังคงอยู่ในแอป R8 จะบอกเหตุผลให้คุณทราบ เพียงเพิ่มกฎนี้

# Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

ระหว่างการบิลด์ R8 จะพิมพ์เชนการอ้างอิงที่แน่นอนซึ่งทำให้ R8 เก็บคลาสนั้นไว้ ซึ่งจะช่วยให้คุณติดตามการอ้างอิงและปรับกฎได้

ดูคำแนะนำฉบับเต็มได้ในส่วนแก้ปัญหา R8

ขั้นตอนถัดไป

R8 เป็นเครื่องมือที่มีประสิทธิภาพในการเพิ่มประสิทธิภาพของแอป Android ประสิทธิภาพของเครื่องมือนี้ขึ้นอยู่กับความเข้าใจที่ถูกต้องเกี่ยวกับการทำงานของเครื่องมือในฐานะเครื่องมือวิเคราะห์แบบคงที่

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

ขณะที่ติดตาม Performance Spotlight Week โปรดดูวิดีโอ Spotlight Week ของวันนี้ใน YouTube และทำภารกิจ R8 ต่อ ใช้ #optimizationEnabled หากมีคำถามเกี่ยวกับการเปิดใช้หรือการแก้ปัญหา R8 เราพร้อมช่วยเหลือคุณ

ถึงเวลาที่คุณจะได้เห็นสิทธิประโยชน์ด้วยตัวคุณเองแล้ว

เราขอท้าให้คุณเปิดใช้ R8 โหมดเต็มรูปแบบสำหรับแอปของคุณ วันนี้.

  1. ทำตามคำแนะนำสำหรับนักพัฒนาแอปเพื่อเริ่มต้นใช้งาน: เปิดใช้การเพิ่มประสิทธิภาพแอป
  2. ตรวจสอบว่าคุณยังใช้ proguard-android.txt อยู่หรือไม่ และแทนที่ด้วย proguard-android-optimize.txt
  3. จากนั้นวัดผลกระทบ อย่าเพียงแค่ รู้สึก ถึงความแตกต่าง แต่ให้ ตรวจสอบ ความแตกต่างนั้น วัดผลการเพิ่มประสิทธิภาพโดยปรับโค้ดจาก แอปตัวอย่าง Macrobenchmark ใน GitHub เพื่อวัดเวลาเริ่มต้นก่อนและหลังการเพิ่มประสิทธิภาพ

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

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

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

เขียนโดย
อ่านต่อ