ติดตั้ง bazel ผ่านมือถือ

การพัฒนาแบบวนซ้ำอย่างรวดเร็วสำหรับ Android

หน้านี้จะอธิบายวิธีที่ bazel mobile-install ช่วยให้การพัฒนาแบบวนซ้ำสำหรับ Android เร็วขึ้นมาก โดยจะอธิบายถึงประโยชน์ของแนวทางนี้เมื่อเทียบกับ ความท้าทายของวิธีการติดตั้งแอปแบบเดิม

สรุป

หากต้องการติดตั้งการเปลี่ยนแปลงเล็กๆ น้อยๆ ในแอป Android อย่างรวดเร็ว ให้ทำดังนี้

  1. ค้นหาandroid_binaryของแอปที่ต้องการติดตั้ง
  2. ปิดใช้ Proguard โดยนำแอตทริบิวต์ proguard_specs ออก
  3. ตั้งค่าแอตทริบิวต์ multidex เป็น native
  4. ตั้งค่าแอตทริบิวต์ dex_shards เป็น 10
  5. เชื่อมต่ออุปกรณ์ที่ใช้ ART (ไม่ใช่ Dalvik) ผ่าน USB และเปิดใช้การแก้ไขข้อบกพร่อง USB ในอุปกรณ์
  6. เรียกใช้ bazel mobile-install :your_target การเริ่มต้นแอปจะช้ากว่าปกติเล็กน้อย
  7. แก้ไขโค้ดหรือทรัพยากร Android
  8. เรียกใช้ bazel mobile-install --incremental :your_target
  9. คุณจึงไม่ต้องรอนาน

ตัวเลือกบรรทัดคำสั่งบางอย่างสำหรับ Bazel ที่อาจเป็นประโยชน์มีดังนี้

  • --adb บอก Bazel ว่าจะใช้ไบนารี adb ใด
  • --adb_arg ใช้เพื่อเพิ่มอาร์กิวเมนต์พิเศษลงในบรรทัดคำสั่งของ adb ได้ การใช้งานที่มีประโยชน์อย่างหนึ่งคือการเลือกอุปกรณ์ที่ต้องการติดตั้ง หากมีอุปกรณ์หลายเครื่องเชื่อมต่อกับเวิร์กสเตชัน bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
  • --start_app จะเริ่มแอปโดยอัตโนมัติ

หากไม่แน่ใจ ให้ดูตัวอย่าง หรือติดต่อเรา

บทนำ

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

น่าเสียดายที่ Toolchain แบบเดิมของ Android สำหรับการสร้าง .apk นั้นมีขั้นตอนแบบลำดับที่ซับซ้อนหลายขั้นตอน และต้องทำทั้งหมดนี้เพื่อสร้างแอป 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 และโค้ดแบบเนทีฟจากไดเรกทอรีการติดตั้งบนอุปกรณ์เคลื่อนที่ จากนั้นจะโอนการควบคุมไปยังแอปจริง ทั้งหมดนี้จะโปร่งใสต่อแอป ยกเว้นในกรณีที่พบได้ยากบางกรณีที่อธิบายไว้ด้านล่าง

การจัดทำดัชนีแบบชาร์ด

การแบ่ง DEX ออกเป็นส่วนๆ นั้นค่อนข้างตรงไปตรงมา เมื่อสร้างไฟล์ .jar แล้ว เครื่องมือจะ แบ่งไฟล์เหล่านั้นออกเป็นไฟล์ .jar แยกกันซึ่งมีขนาดใกล้เคียงกัน จากนั้นจะเรียกใช้ dx ในไฟล์ที่มีการเปลี่ยนแปลงนับตั้งแต่การสร้างครั้งก่อน ตรรกะที่กำหนดว่าควรใช้ Dex กับ Shard ใดไม่ได้เจาะจงสำหรับ Android แต่ใช้อัลกอริทึมการตัดแต่งการเปลี่ยนแปลงทั่วไปของ Bazel

อัลกอริทึมการแบ่งข้อมูลเวอร์ชันแรกเพียงแค่จัดเรียงไฟล์ .class ตามลำดับตัวอักษร แล้วตัดรายการออกเป็นส่วนๆ ที่มีขนาดเท่ากัน แต่ปรากฏว่าวิธีนี้ไม่เหมาะสม หากมีการเพิ่มหรือนำคลาสออก (แม้จะเป็นคลาสที่ซ้อนกันหรือคลาสที่ไม่ระบุชื่อ) ก็จะทำให้คลาสทั้งหมดที่อยู่หลังคลาสนั้นตามลำดับตัวอักษรเลื่อนไป 1 ตำแหน่ง ส่งผลให้ต้องทำการ Dexing Shard เหล่านั้นอีกครั้ง จึงตัดสินใจที่จะแยกแพ็กเกจ Java แทนที่จะแยกคลาสแต่ละคลาส แน่นอนว่าการดำเนินการนี้ยังคงส่งผลให้มีการจัดทำดัชนีหลายๆ ชิ้นหากมีการเพิ่มหรือนำแพ็กเกจใหม่ออก แต่การดำเนินการนี้จะเกิดขึ้นไม่บ่อยเท่ากับการเพิ่มหรือนำคลาสเดียวออก

ไฟล์ BUILD จะควบคุมจำนวน Shard (โดยใช้แอตทริบิวต์ android_binary.dex_shards) ในโลกที่สมบูรณ์แบบ Bazel จะ กำหนดจำนวน Shard ที่ดีที่สุดโดยอัตโนมัติ แต่ปัจจุบัน Bazel ต้องทราบ ชุดการดำเนินการ (เช่น คำสั่งที่จะดำเนินการระหว่างการสร้าง) ก่อน ที่จะดำเนินการใดๆ จึงไม่สามารถกำหนดจำนวน Shard ที่เหมาะสมได้ เนื่องจากไม่ทราบว่าในท้ายที่สุดจะมีคลาส Java กี่คลาสใน แอป โดยทั่วไปแล้ว ยิ่งมี Shard มากเท่าใด การสร้างและการ ติดตั้งก็จะยิ่งเร็วขึ้น แต่การเริ่มต้นแอปจะช้าลงเนื่องจากลิงก์เกอร์แบบไดนามิก ต้องทำงานมากขึ้น โดยปกติแล้ว จำนวนที่เหมาะสมจะอยู่ระหว่าง 10 ถึง 50 ชิ้น

การโอนไฟล์แบบเพิ่ม

หลังจากสร้างแอปแล้ว ขั้นตอนถัดไปคือการติดตั้งแอป ซึ่งควรทำด้วย ความพยายามน้อยที่สุด การติดตั้งประกอบด้วยขั้นตอนต่อไปนี้

  1. การติดตั้ง .apk (โดยปกติจะใช้ adb install)
  2. การอัปโหลดไฟล์ .dex, ทรัพยากร Android และไลบรารีเนทีฟไปยัง ไดเรกทอรีการติดตั้งบนอุปกรณ์เคลื่อนที่

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

ในขั้นตอนที่ 2 ระบบจะเปรียบเทียบไฟล์ของแอปจากบิลด์กับไฟล์ Manifest ในอุปกรณ์ที่แสดงรายการไฟล์ของแอปที่อยู่ในอุปกรณ์และ Checksum ของไฟล์เหล่านั้น ระบบจะอัปโหลดไฟล์ใหม่ไปยังอุปกรณ์ อัปเดตไฟล์ที่มีการเปลี่ยนแปลง และลบไฟล์ที่นำออกไปจากอุปกรณ์ หากไม่มีไฟล์ Manifest ระบบจะถือว่าต้องอัปโหลดทุกไฟล์

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

แอปพลิเคชัน Stub

แอปพลิเคชัน Stub คือที่ที่เกิดการโหลดไฟล์ DEX, โค้ดที่มาพร้อมเครื่อง และ ทรัพยากร Android จากไดเรกทอรี mobile-install ในอุปกรณ์

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

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

โดยการแทนที่Applicationคลาสที่ระบุใน AndroidManifest.xmlด้วย แอปพลิเคชัน Stub ซึ่งจะ ควบคุมเมื่อเริ่มแอป และปรับแต่ง Class Loader และ Resource Manager อย่างเหมาะสมในเวลาที่เร็วที่สุด (ตัวสร้าง) โดยใช้ การสะท้อน 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 เวอร์ชันเก่าโดยเฉพาะ)