การยืนยันลักษณะการทำงานของแอปในรันไทม์ (ART) ของ Android

รันไทม์ของ Android (ART) คือรันไทม์เริ่มต้นสำหรับอุปกรณ์ที่ใช้ Android 5.0 (API ระดับ 21) ขึ้นไป รันไทม์นี้มีฟีเจอร์มากมาย ซึ่งปรับปรุงประสิทธิภาพและความราบรื่นของแพลตฟอร์มและแอป Android ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์ใหม่ของ ART ได้ในขอแนะนำ ART

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

การจัดการปัญหาการเก็บขยะ (GC)

ภายใต้ Dalvik แอปมักพบว่าการเรียก System.gc() เพื่อแจ้งเตือนเกี่ยวกับการเก็บขยะ (GC) ควรเป็น จำเป็นต้องใช้ ART น้อยกว่ามาก โดยเฉพาะอย่างยิ่งหากคุณกำลังเรียกใช้งานที่เก็บข้อมูลขยะ เพื่อป้องกันประเภท GC_FOR_ALLOC หรือเพื่อลดการกระจัดกระจาย คุณตรวจสอบได้ว่ามีการใช้รันไทม์ใดอยู่ โดยโทรไปที่ System.getProperty("java.vm.version") หากมีการใช้ ART ค่าของพร็อพเพอร์ตี้ เท่ากับ "2.0.0" ขึ้นไป

ART ใช้ตัวรวบรวมการคัดลอกพร้อมกัน (CC) ซึ่งจะบีบอัดฮีป Java พร้อมๆ กัน ด้วยเหตุนี้ คุณจึงควรหลีกเลี่ยงการใช้เทคนิค เข้ากันไม่ได้กับ GC ที่กระชับ (เช่น การบันทึกตัวชี้ไปยังออบเจ็กต์ ข้อมูลอินสแตนซ์) ซึ่งสำคัญอย่างยิ่งสำหรับแอปที่ใช้ประโยชน์จาก Java Native Interface (JNI) ดูข้อมูลเพิ่มเติมได้ที่การป้องกันปัญหา JNI

การป้องกันปัญหา JNI

JNI ของ ART ค่อนข้างเข้มงวดกว่าของ Dalvik วิธีนี้เป็นความคิดที่ดีอย่างยิ่ง เพื่อใช้โหมด CheckJNI เพื่อหาปัญหาที่พบบ่อย หากแอปใช้ C/C++ คุณควรอ่านบทความต่อไปนี้

การแก้ไขข้อบกพร่อง Android JNI กับ CheckJNI

กำลังตรวจสอบรหัส JNI เกี่ยวกับปัญหาการเก็บรวบรวมข้อมูลขยะ

ตัวรวบรวมการคัดลอก (CC) พร้อมกันอาจย้ายวัตถุในหน่วยความจำเพื่อบีบอัด หากคุณใช้โค้ด C/C++ โปรดอย่า ดำเนินการที่เข้ากันไม่ได้กับ GC ที่บีบอัด เราได้ปรับปรุง CheckJNI เพื่อระบุปัญหาที่อาจเกิดขึ้น (ตามที่อธิบายไว้ใน JNI การเปลี่ยนแปลงข้อมูลอ้างอิงในท้องถิ่นใน ICS)

สิ่งที่ต้องระวังเป็นพิเศษคือการใช้ Get...ArrayElements() และ Release...ArrayElements() ในรันไทม์ที่มี GC ที่ไม่ได้บีบอัด โดยทั่วไปแล้ว Get...ArrayElements() ฟังก์ชันจะส่งการอ้างอิงไปยังฟังก์ชัน หน่วยความจำจริงที่สำรองออบเจ็กต์อาร์เรย์ หากคุณทำการเปลี่ยนแปลงกับ เอลิเมนต์อาร์เรย์ที่แสดง ออบเจ็กต์อาร์เรย์จะเปลี่ยนแปลง (และอาร์กิวเมนต์ โดยปกติจะไม่สนใจถึง Release...ArrayElements()) อย่างไรก็ตาม หาก กำลังใช้งานการย่อขนาด GC อยู่ ฟังก์ชัน Get...ArrayElements() อาจ ส่งกลับสำเนาของความทรงจำนั้น หากคุณใช้การอ้างอิงในทางที่ผิดเมื่อย่อ GC ขณะใช้งานอาจทำให้หน่วยความจำเสียหายหรือปัญหาอื่นๆ เช่น

  • ถ้าคุณทำการเปลี่ยนแปลงใดๆ กับองค์ประกอบอาร์เรย์ที่แสดงผล คุณต้องเรียกใช้การเรียก ฟังก์ชัน Release...ArrayElements() ที่เหมาะสมเมื่อเสร็จแล้ว เพื่อให้แน่ใจว่าการเปลี่ยนแปลงที่คุณทำได้รับการคัดลอก กลับไปยังหน้าเว็บที่เกี่ยวข้อง อาร์เรย์
  • เมื่อคุณปล่อยองค์ประกอบอาร์เรย์หน่วยความจำ คุณต้องใช้องค์ประกอบ โหมดต่อไปนี้ โดยขึ้นอยู่กับการเปลี่ยนแปลงที่คุณทำ
    • ถ้าคุณไม่ได้ทำการเปลี่ยนแปลงใดๆ กับองค์ประกอบอาร์เรย์ ให้ใช้ โหมด JNI_ABORT ซึ่งจะปล่อยความทรงจำโดยไม่ต้องคัดลอก จะเปลี่ยนกลับไปเป็นออบเจ็กต์อาร์เรย์ที่สำคัญ
    • ถ้าคุณแก้ไขอาร์เรย์ และไม่ต้องการการอ้างอิง เพิ่มเติม ให้ใช้โค้ด 0 (ซึ่งอัปเดตออบเจ็กต์อาร์เรย์และเพิ่มพื้นที่ว่าง สำเนาของความทรงจำนั้น)
    • ถ้าคุณแก้ไขอาร์เรย์ที่ต้องการยืนยัน และต้องการ ในการเก็บสำเนาของอาร์เรย์ ให้ใช้ JNI_COMMIT (ซึ่งจะ ออบเจ็กต์อาร์เรย์ที่สำคัญและเก็บสำเนาไว้)
  • เมื่อโทรหา Release...ArrayElements() ให้คืนสินค้าที่เดิม ตัวชี้ที่ Get...ArrayElements() ส่งคืนในตอนแรก สำหรับ เช่น เพิ่มเคอร์เซอร์เดิม (เพื่อสแกนโค้ด เอลิเมนต์อาร์เรย์ที่แสดงผล) แล้วส่งตัวชี้ที่เพิ่มขึ้นไปยัง Release...ArrayElements() การส่งผ่านตัวชี้ที่แก้ไขนี้สามารถ มีการปล่อยหน่วยความจำที่ไม่ถูกต้อง ทำให้หน่วยความจำเสียหาย

การจัดการข้อผิดพลาด

JNI ของ ART แสดงข้อผิดพลาดในบางกรณีที่ Dalvik ไม่ทำ (ครั้งเดียว คุณสามารถดูกรณีเช่นนี้ได้โดยการทดสอบกับ CheckJNI)

ตัวอย่างเช่น หากมีการเรียก RegisterNatives ด้วยวิธีการที่ ไม่มีอยู่ (อาจเป็นเพราะวิธีการถูกลบออกโดยเครื่องมือ เช่น ProGuard) ART ส่ง NoSuchMethodError ได้อย่างถูกต้องแล้ว

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

ART ยังบันทึกข้อผิดพลาด (ดูได้ใน Logcat) หาก RegisterNatives คือ โดยไม่มีเมธอด:

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

นอกจากนี้ ฟังก์ชัน JNI จะ GetFieldID() และ ตอนนี้ GetStaticFieldID() ส่ง NoSuchFieldError ได้ถูกต้อง แทนที่จะแสดงผลเป็น Null ในทำนองเดียวกัน GetMethodID() และ ตอนนี้ GetStaticMethodID() ส่ง NoSuchMethodError ได้ถูกต้อง ซึ่งอาจทำให้ CheckJNI ทำงานไม่สำเร็จเนื่องจากข้อยกเว้นที่ไม่ได้รับการจัดการ ข้อยกเว้นที่จะเกิดกับผู้โทร Java ของโค้ดเนทีฟ จึงทำให้ โดยเฉพาะกับการทดสอบแอปที่เข้ากันได้กับ ART ด้วยโหมด CheckJNI

ART คาดหวังว่าผู้ใช้เมธอด JNI CallNonvirtual...Method() (เช่น CallNonvirtualVoidMethod()) เพื่อใช้การประกาศของเมธอด ไม่ใช่คลาสย่อยตามที่ JNI กำหนดไว้

การป้องกันปัญหาเกี่ยวกับขนาดสแต็ก

Dalvik มีสแต็กแยกต่างหากสำหรับโค้ดเนทีฟและโค้ด Java ที่มี Java เริ่มต้น ขนาดสแต็ก 32 KB และขนาดสแต็กเริ่มต้นคือ 1 MB ART มีเอกภาพ เพื่อย่านที่ดีขึ้น โดยปกติ สแต็ก ART Thread ควรมีขนาดประมาณเท่ากับสำหรับ Dalvik แต่ถ้าคุณ ตั้งค่าขนาดสแต็ก คุณอาจต้องกลับไปดูค่าเหล่านั้นสําหรับแอปที่ทำงานใน ART

  • ใน Java ให้ตรวจสอบการเรียกใช้ตัวสร้าง Thread ที่ระบุสแต็กที่ชัดเจน ขนาด เช่น คุณจะต้องเพิ่มขนาดหากมี StackOverflowError
  • ใน C/C++ ให้ตรวจสอบการใช้ pthread_attr_setstack() และ pthread_attr_setstacksize() สำหรับชุดข้อความที่เรียกใช้โค้ด Java ด้วย JNI ต่อไปนี้เป็นตัวอย่างของข้อผิดพลาดที่บันทึกไว้เมื่อแอปพยายามโทรหา JNI AttachCurrentThread() เมื่อขนาด pthread มีขนาดเล็กเกินไป:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

การเปลี่ยนแปลงโมเดลวัตถุ

Dalvik อนุญาตให้คลาสย่อยลบล้างเมธอดแพ็กเกจส่วนตัวอย่างไม่ถูกต้อง ART จะออกคำเตือนในกรณีดังกล่าว

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

ถ้าต้องการลบล้างเมธอดของคลาสในแพ็กเกจอื่น ให้ประกาศ เป็น public หรือ protected

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

Class.getSuperclass() == java.lang.Object.class

แทนที่จะดำเนินการต่อจนกว่าเมธอดจะแสดง null

ตอนนี้พร็อกซี InvocationHandler.invoke() จะได้รับ null หากไม่มี แทนอาร์เรย์ที่ว่างเปล่า มีการบันทึกลักษณะการทำงานนี้ไว้ก่อนหน้านี้ แต่ ไม่ได้รับการจัดการอย่างถูกต้องใน Dalvik Mockito เวอร์ชันก่อนหน้ามีปัญหาเกี่ยวกับ ดังนั้นให้ใช้ Mockito เวอร์ชันที่อัปเดตแล้วเมื่อทดสอบกับ ART

การแก้ปัญหาการคอมไพล์ AOT

การคอมไพล์ Java Ahead-Of-Time (AOT) ของ ART ควรใช้งานได้กับ Java มาตรฐานทั้งหมด โค้ด ดำเนินการคอมไพล์โดย ART เครื่องมือ dex2oat หากพบปัญหาที่เกี่ยวข้องกับ dex2oat ณ เวลาที่ติดตั้ง โปรดแจ้งให้เราทราบ (ดูการรายงานปัญหาเกี่ยวกับ) เพื่อให้เราแก้ไขได้อย่างรวดเร็ว ให้มากที่สุด ปัญหา 2 ข้อที่ควรทราบมีดังนี้

  • ART ยืนยันไบต์โค้ดในเวลาติดตั้งได้มากกว่า Dalvik ที่ทำ โค้ดที่เครื่องมือสร้างของ Android สร้างขึ้นจะไม่สามารถใช้งานได้ อย่างไรก็ตาม เครื่องมือหลังการประมวลผล (โดยเฉพาะเครื่องมือที่สร้างความสับสน) อาจสร้าง ไฟล์ที่ไม่ถูกต้องซึ่ง Dalvik ยอมรับแต่ ART ปฏิเสธ เราคือ ทำงานร่วมกับผู้ให้บริการเครื่องมือเพื่อค้นหาและแก้ไขปัญหาดังกล่าว ในหลายกรณี การได้รับ เครื่องมือเวอร์ชันล่าสุดและการสร้างไฟล์ DEX ใหม่สามารถแก้ปัญหาเหล่านี้ได้ ปัญหา
  • ปัญหาทั่วไปบางอย่างที่เครื่องมือตรวจสอบ ART แจ้งว่าไม่เหมาะสม ได้แก่
    • ขั้นตอนการควบคุมไม่ถูกต้อง
    • ไม่สมดุล monitorenter/monitorexit
    • ขนาดรายการประเภทพารามิเตอร์ความยาว 0
  • แอปบางแอปมีทรัพยากร Dependency ในไฟล์ .odex ที่ติดตั้งไว้ รูปแบบใน /system/framework, /data/dalvik-cache หรือ ในไดเรกทอรีเอาต์พุตที่เพิ่มประสิทธิภาพแล้วของ DexClassLoader เหล่านี้ ตอนนี้เป็นไฟล์ ELF ไม่ใช่ไฟล์ DEX ในรูปแบบขยาย ขณะที่ ART พยายาม ให้เป็นไปตามการตั้งชื่อและกฎการล็อกเดียวกันกับ Dalvik แอปไม่ควร เกี่ยวกับรูปแบบไฟล์ รูปแบบดังกล่าวอาจเปลี่ยนแปลงได้โดยไม่ต้องแจ้งให้ทราบ

    หมายเหตุ: ใน Android 8.0 (API ระดับ 26) และ ไดเรกทอรีเอาต์พุตที่เพิ่มประสิทธิภาพ DexClassLoader แล้ว เลิกใช้งานแล้ว สําหรับข้อมูลเพิ่มเติม โปรดดูเอกสารประกอบสําหรับ DexClassLoader() เครื่องมือสร้างขึ้นมา

การรายงานปัญหา

หากพบปัญหาที่ไม่ได้มาจากปัญหา JNI ของแอป โปรดรายงาน ผ่านเครื่องมือติดตามปัญหาโครงการโอเพนซอร์ส Android ที่ https://code.google.com/p/android/issues/list ใส่ "adb bugreport" และลิงก์ไปยังแอปใน Google Play Store (หากมี) หากทำได้ ให้แนบ APK ที่ทำให้เกิดซ้ำ ถึงปัญหา โปรดทราบว่าปัญหา (รวมถึงไฟล์แนบ) เป็นข้อมูลสาธารณะ มองเห็นได้