การพัฒนาแบบวนซ้ำอย่างรวดเร็วสำหรับ Android
หน้านี้จะอธิบายวิธีที่ bazel mobile-install ช่วยให้การพัฒนาแบบวนซ้ำสำหรับ Android เร็วขึ้นมาก โดยจะอธิบายถึงประโยชน์ของแนวทางนี้เมื่อเทียบกับ
ความท้าทายของวิธีการติดตั้งแอปแบบเดิม
สรุป
หากต้องการติดตั้งการเปลี่ยนแปลงเล็กๆ น้อยๆ ในแอป Android อย่างรวดเร็ว ให้ทำดังนี้
- ค้นหา
android_binaryของแอปที่ต้องการติดตั้ง - ปิดใช้ Proguard โดยนำแอตทริบิวต์
proguard_specsออก - ตั้งค่าแอตทริบิวต์
multidexเป็นnative - ตั้งค่าแอตทริบิวต์
dex_shardsเป็น10 - เชื่อมต่ออุปกรณ์ที่ใช้ ART (ไม่ใช่ Dalvik) ผ่าน USB และเปิดใช้การ แก้ไขข้อบกพร่อง USB ในอุปกรณ์
- เรียกใช้
bazel mobile-install :your_targetการเริ่มต้นแอปจะช้ากว่าปกติเล็กน้อย - แก้ไขโค้ดหรือทรัพยากร Android
- เรียกใช้
bazel mobile-install --incremental :your_target - เพลิดเพลินกับการไม่ต้องรอนาน
ตัวเลือกบรรทัดคำสั่งบางอย่างสำหรับ Bazel ที่อาจเป็นประโยชน์มีดังนี้
--adbบอก Bazel ว่าจะใช้ไบนารี adb ใด--adb_argใช้เพื่อเพิ่มอาร์กิวเมนต์พิเศษลงในบรรทัดคำสั่งของadbได้ การใช้งานที่มีประโยชน์อย่างหนึ่งคือการเลือกอุปกรณ์ที่ต้องการติดตั้ง หากคุณมีอุปกรณ์หลายเครื่องที่เชื่อมต่อกับเวิร์กสเตชันbazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target--start_appจะเริ่มแอปโดยอัตโนมัติ
หากไม่แน่ใจ ให้ดูตัวอย่าง หรือติดต่อเรา
บทนำ
คุณสมบัติที่สำคัญที่สุดอย่างหนึ่งของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์คือความเร็ว ความแตกต่างระหว่างการเปลี่ยนโค้ดและดูโค้ดทำงานภายใน 1 วินาทีกับการต้องรอหลายนาทีหรือบางครั้งอาจเป็นชั่วโมงก่อนที่จะได้รับความคิดเห็นว่าการเปลี่ยนแปลงของคุณทำงานได้ตามที่คาดไว้หรือไม่นั้นมีมาก
น่าเสียดายที่ Toolchain แบบเดิมของ Android สำหรับการสร้าง .apk นั้นมี ขั้นตอนแบบ Monolithic ตามลำดับหลายขั้นตอน และต้องทำทั้งหมดนี้เพื่อ สร้างแอป Android ที่ Google การรอ 5 นาทีเพื่อสร้างการเปลี่ยนแปลงบรรทัดเดียว ไม่ใช่เรื่องแปลกในโปรเจ็กต์ขนาดใหญ่ เช่น Google Maps
bazel mobile-install ช่วยให้การพัฒนาแบบวนซ้ำสำหรับ Android เร็วขึ้นมากโดย
ใช้การผสมผสานระหว่างการตัดทอนการเปลี่ยนแปลง การแบ่งงาน และการจัดการที่ชาญฉลาดของ
ส่วนประกอบภายในของ Android โดยไม่ต้องเปลี่ยนโค้ดของแอป
ปัญหาเกี่ยวกับการติดตั้งแอปแบบเดิม
การสร้างแอป Android มีปัญหาบางอย่าง เช่น
Dexing โดยค่าเริ่มต้น "dx" จะเรียกใช้เพียงครั้งเดียวในการสร้าง และไม่ทราบวิธีนำงานจากการสร้างครั้งก่อนมาใช้ซ้ำ ซึ่งจะแปลงทุกเมธอดเป็น Dex อีกครั้ง แม้ว่าจะมีการเปลี่ยนแปลงเพียงเมธอดเดียวก็ตาม
การอัปโหลดข้อมูลไปยังอุปกรณ์ adb ไม่ได้ใช้แบนด์วิดท์ทั้งหมดของการเชื่อมต่อ USB 2.0 และแอปขนาดใหญ่อาจใช้เวลาในการอัปโหลดนานมาก ระบบจะอัปโหลดแอปทั้งแอปแม้ว่าจะมีการเปลี่ยนแปลงเพียงเล็กน้อย เช่น ทรัพยากรหรือเมธอดเดียว ซึ่งอาจเป็นจุดคอขวดที่สำคัญ
การคอมไพล์เป็นโค้ดที่มาพร้อมเครื่อง Android L ได้เปิดตัว ART ซึ่งเป็นรันไทม์ใหม่ของ Android ที่คอมไพล์แอปแบบล่วงหน้าแทนที่จะคอมไพล์แบบทันทีเหมือน Dalvik ซึ่งจะทำให้แอปทำงานได้เร็วขึ้นมาก แต่จะใช้เวลาในการติดตั้งนานขึ้น ซึ่งเป็นข้อแลกเปลี่ยนที่ดีสำหรับผู้ใช้ เนื่องจากโดยปกติแล้วผู้ใช้จะติดตั้งแอปเพียงครั้งเดียวและใช้งานหลายครั้ง แต่จะทำให้การพัฒนาช้าลงในกรณีที่แอปมีการติดตั้งหลายครั้งและแต่ละเวอร์ชันจะทำงานไม่กี่ครั้ง
แนวทางของ bazel mobile-install
bazel mobile-install ได้ทำการปรับปรุงดังต่อไปนี้
การ Dex แบบ Shard หลังจากสร้างโค้ด Java ของแอปแล้ว Bazel จะแบ่งไฟล์คลาส ออกเป็นส่วนๆ ที่มีขนาดใกล้เคียงกัน และเรียกใช้
dxแยกกันdxจะไม่เรียกใช้ใน Shard ที่ไม่มีการเปลี่ยนแปลงนับตั้งแต่การสร้างครั้งล่าสุดการโอนไฟล์แบบเพิ่ม ระบบจะนำทรัพยากร Android, ไฟล์ .dex และไลบรารีแบบเนทีฟ ออกจาก .apk หลักและจัดเก็บไว้ในไดเรกทอรีการติดตั้งบนอุปกรณ์เคลื่อนที่แยกต่างหาก ซึ่งช่วยให้คุณอัปเดตโค้ดและทรัพยากร Android ได้อย่างอิสระโดยไม่ต้องติดตั้งแอปทั้งแอปใหม่ ดังนั้น การโอนไฟล์จึงใช้เวลาน้อยลง และระบบจะคอมไพล์ไฟล์ .dex ที่มีการเปลี่ยนแปลงอีกครั้งในอุปกรณ์เท่านั้น
โหลดส่วนต่างๆ ของแอปจากภายนอก .apk ระบบจะใส่แอปพลิเคชัน Stub ขนาดเล็ก ลงใน .apk ซึ่งจะโหลดทรัพยากร Android, โค้ด Java และโค้ดแบบเนทีฟ จากไดเรกทอรีการติดตั้งบนอุปกรณ์เคลื่อนที่ในอุปกรณ์ จากนั้นจะโอนการควบคุมไปยัง แอปจริง ทั้งหมดนี้จะโปร่งใสต่อแอป ยกเว้นในกรณีที่พบได้ยาก บางกรณีที่อธิบายไว้ด้านล่าง
การจัดทำดัชนีแบบ Sharded
การแบ่ง DEX ออกเป็นส่วนๆ นั้นค่อนข้างตรงไปตรงมา เมื่อสร้างไฟล์ .jar แล้ว เครื่องมือจะ
แบ่งไฟล์เหล่านั้นออกเป็นไฟล์ .jar แยกกันซึ่งมีขนาดใกล้เคียงกัน จากนั้นจะเรียกใช้
dx ในไฟล์ที่มีการเปลี่ยนแปลงนับตั้งแต่การสร้างครั้งก่อน ตรรกะที่กำหนดว่าควรใช้ Shard ใดกับ Dex ไม่ได้เจาะจงสำหรับ Android แต่ใช้เพียงอัลกอริทึมการตัดแต่งการเปลี่ยนแปลงทั่วไปของ Bazel
อัลกอริทึมการแยกส่วนเวอร์ชันแรกเพียงแค่จัดเรียงไฟล์ .class ตามลำดับตัวอักษร จากนั้นตัดรายการออกเป็นส่วนๆ ที่มีขนาดเท่ากัน แต่ปรากฏว่าวิธีนี้ไม่เหมาะสม เนื่องจากหากมีการเพิ่มหรือนำคลาสออก (แม้จะเป็นคลาสที่ซ้อนกันหรือคลาสที่ไม่ระบุชื่อ) ก็จะทำให้คลาสทั้งหมดที่อยู่หลังคลาสนั้นตามลำดับตัวอักษรเลื่อนไป 1 ตำแหน่ง ซึ่งส่งผลให้ต้องทำการ Dex ชาร์ดเหล่านั้นอีกครั้ง จึงตัดสินใจที่จะแยกแพ็กเกจ Java แทนที่จะแยกคลาสแต่ละคลาส แน่นอนว่าการดำเนินการนี้ยังคงส่งผลให้มีการจัดทำดัชนีหลายๆ ชาร์ดหากมีการเพิ่มหรือนำแพ็กเกจใหม่ออก แต่การดำเนินการนี้จะเกิดขึ้นไม่บ่อยเท่ากับการเพิ่มหรือนำคลาสเดียวออก
ไฟล์ BUILD จะควบคุมจำนวน Shard (โดยใช้แอตทริบิวต์ android_binary.dex_shards) ในอุดมคติแล้ว Bazel จะ
กำหนดจำนวน Shard ที่ดีที่สุดโดยอัตโนมัติ แต่ปัจจุบัน Bazel ต้องทราบ
ชุดการดำเนินการ (เช่น คำสั่งที่จะดำเนินการในระหว่างการสร้าง) ก่อน
ที่จะดำเนินการใดๆ จึงไม่สามารถกำหนดจำนวน Shard ที่เหมาะสมได้
เนื่องจากไม่ทราบว่าในท้ายที่สุดแล้วจะมีคลาส Java กี่คลาสใน
แอป โดยทั่วไปแล้ว ยิ่งมี Shard มากเท่าใด การสร้างและการ
ติดตั้งก็จะยิ่งเร็วขึ้น แต่การเริ่มต้นแอปจะช้าลงเนื่องจากลิงก์เกอร์แบบไดนามิก
ต้องทำงานมากขึ้น โดยปกติแล้ว จำนวนที่เหมาะสมจะอยู่ระหว่าง 10 ถึง 50 ชิ้น
การโอนไฟล์แบบเพิ่ม
หลังจากสร้างแอปแล้ว ขั้นตอนถัดไปคือการติดตั้งแอป ซึ่งควรทำโดยใช้ ความพยายามน้อยที่สุด การติดตั้งประกอบด้วยขั้นตอนต่อไปนี้
- การติดตั้ง .apk (โดยปกติจะใช้
adb install) - การอัปโหลดไฟล์ .dex, ทรัพยากร Android และไลบรารีเนทีฟไปยัง ไดเรกทอรีการติดตั้งบนอุปกรณ์เคลื่อนที่
ขั้นตอนแรกไม่ได้เพิ่มขึ้นมากนัก เนื่องจากแอปจะติดตั้งหรือไม่ติดตั้ง
ก็ได้ ปัจจุบัน Bazel อาศัยผู้ใช้ในการระบุว่าควรทำขั้นตอนนี้หรือไม่
ผ่าน--incrementalตัวเลือกบรรทัดคำสั่ง เนื่องจากไม่สามารถระบุได้ใน
ทุกกรณีว่าจำเป็นหรือไม่
ในขั้นตอนที่ 2 ระบบจะเปรียบเทียบไฟล์ของแอปจากบิลด์กับไฟล์ Manifest ในอุปกรณ์ที่แสดงรายการไฟล์ของแอปที่อยู่ในอุปกรณ์และผลรวมตรวจสอบ ระบบจะอัปโหลดไฟล์ใหม่ไปยังอุปกรณ์ อัปเดตไฟล์ที่มีการเปลี่ยนแปลง และลบไฟล์ที่นำออกไปจากอุปกรณ์ หากไม่มีไฟล์ Manifest ระบบจะถือว่าต้องอัปโหลดทุกไฟล์
โปรดทราบว่าคุณสามารถหลอกอัลกอริทึมการติดตั้งแบบเพิ่มทีละรายการได้โดยการเปลี่ยนไฟล์ในอุปกรณ์ แต่จะไม่สามารถเปลี่ยนผลรวมตรวจสอบในไฟล์ Manifest ได้ เราสามารถป้องกันปัญหานี้ได้โดยการคำนวณผลรวมตรวจสอบของไฟล์ในอุปกรณ์ แต่เราเห็นว่าการเพิ่มเวลาในการติดตั้งไม่คุ้มค่า
แอปพลิเคชัน Stub
แอปพลิเคชัน Stub คือที่ที่เกิดการโหลดไฟล์ DEX, โค้ดที่มาพร้อมเครื่อง และ
ทรัพยากร Android จากไดเรกทอรี mobile-install ในอุปกรณ์
การโหลดจริงจะดำเนินการโดยการสร้างคลาสย่อยของ BaseDexClassLoader และเป็นเทคนิคที่มีเอกสารประกอบค่อนข้างดี
ซึ่งจะเกิดขึ้นก่อนที่จะโหลดคลาสใดๆ ของแอป เพื่อให้คลาสแอปพลิเคชันใดๆ ที่อยู่ใน APK สามารถวางไว้ในไดเรกทอรี mobile-install ในอุปกรณ์เพื่อให้สามารถอัปเดตได้โดยไม่ต้องใช้ adb install
การดำเนินการนี้ต้องเกิดขึ้นก่อนที่จะโหลดคลาสใดๆ ของแอป เพื่อให้ไม่ต้องมีคลาสแอปพลิเคชันใน .apk ซึ่งหมายความว่าการเปลี่ยนแปลงคลาสเหล่านั้นจะต้องมีการติดตั้ง ใหม่ทั้งหมด
โดยการแทนที่คลาส Application ที่ระบุใน
AndroidManifest.xml ด้วย
แอปพลิเคชัน Stub ซึ่งจะ
ควบคุมเมื่อเริ่มแอป และปรับแต่งตัวโหลดคลาสและ
เครื่องมือจัดการทรัพยากรอย่างเหมาะสมในเวลาที่เร็วที่สุด (ตัวสร้าง) โดยใช้
การสะท้อน Java ในส่วนภายในของเฟรมเวิร์ก Android
อีกสิ่งหนึ่งที่แอปพลิเคชัน Stub ทำคือการคัดลอกไลบรารีเนทีฟ
ที่ติดตั้งโดย mobile-install ไปยังตำแหน่งอื่น การดำเนินการนี้จำเป็นเนื่องจาก
โปรแกรมลิงก์แบบไดนามิกต้องตั้งค่า X บิตในไฟล์ ซึ่งทำไม่ได้
ในตำแหน่งใดก็ตามที่ adb ที่ไม่ใช่รูทเข้าถึงได้
เมื่อดำเนินการทั้งหมดนี้เสร็จแล้ว แอปพลิเคชัน Stub จะสร้างอินสแตนซ์ของคลาส Application จริง โดยเปลี่ยนการอ้างอิงทั้งหมดไปยังตัวแอปพลิเคชันจริงภายในเฟรมเวิร์ก Android
ผลลัพธ์
ประสิทธิภาพ
โดยทั่วไปแล้ว bazel mobile-install จะช่วยให้สร้าง
และติดตั้งแอปขนาดใหญ่ได้เร็วขึ้น 4-10 เท่าหลังจากมีการเปลี่ยนแปลงเล็กน้อย
ตัวเลขต่อไปนี้คำนวณขึ้นสำหรับผลิตภัณฑ์บางอย่างของ Google
ซึ่งแน่นอนว่าขึ้นอยู่กับลักษณะของการเปลี่ยนแปลง การคอมไพล์ใหม่หลังจากเปลี่ยนไลบรารีพื้นฐานจะใช้เวลานานกว่า
ข้อจำกัด
แต่กลเม็ดที่แอปพลิเคชัน Stub ใช้ไม่ได้ผลในทุกกรณี กรณีต่อไปนี้แสดงให้เห็นว่าฟีเจอร์นี้ทำงานไม่เป็นไปตามที่คาดไว้
เมื่อแคสต์
Contextไปยังชั้นเรียนApplicationในContentProvider#onCreate()เมธอดนี้จะเรียกใช้ในระหว่างการเริ่มต้นแอปพลิเคชัน ก่อนที่เราจะมีโอกาสแทนที่อินสแตนซ์ของคลาสApplicationดังนั้นContentProviderจะยังอ้างอิงแอปพลิเคชัน Stub แทนแอปพลิเคชันจริง อาจกล่าวได้ว่านี่ไม่ใช่ข้อบกพร่องเนื่องจากคุณไม่ควรลดระดับContextในลักษณะนี้ แต่ดูเหมือนว่าปัญหานี้จะเกิดขึ้นในแอปบางแอปของ Googleทรัพยากรที่ติดตั้งโดย
bazel mobile-installจะใช้ได้ภายในแอปเท่านั้น หากแอปอื่นๆ เข้าถึงทรัพยากรผ่านPackageManager#getApplicationResources()ทรัพยากรเหล่านี้จะมาจาก การติดตั้งครั้งล่าสุดที่ไม่ใช่การติดตั้งแบบเพิ่มอุปกรณ์ที่ไม่ได้ใช้ ART แม้ว่าแอปพลิเคชัน Stub จะทำงานได้ดีใน Froyo และเวอร์ชันที่ใหม่กว่า แต่ Dalvik มีข้อบกพร่องที่ทำให้ระบบคิดว่าแอปไม่ถูกต้องหากมีการกระจายโค้ดในไฟล์ .dex หลายไฟล์ในบางกรณี เช่น เมื่อใช้คำอธิบายประกอบ Java ในลักษณะเฉพาะ ตราบใดที่แอปของคุณไม่ทำให้เกิดข้อบกพร่องเหล่านี้ แอปก็ควรจะทำงานกับ Dalvik ได้ด้วย (โปรดทราบว่าเราไม่ได้มุ่งเน้นที่การรองรับ Android เวอร์ชันเก่าโดยเฉพาะ)