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

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

การพัฒนาแบบวนซ้ำอย่างรวดเร็วสำหรับ 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 ตำแหน่ง ซึ่งส่งผลให้ต้องทำการแปลง DEX ของ 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 ระบบจะถือว่าต้องอัปโหลดทุกไฟล์

โปรดทราบว่าคุณสามารถหลอกอัลกอริทึมการติดตั้งแบบเพิ่มทีละรายการได้โดย การเปลี่ยนไฟล์ในอุปกรณ์ แต่จะไม่สามารถเปลี่ยนผลรวมตรวจสอบในไฟล์ 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 เวอร์ชันเก่าไม่ใช่สิ่งที่เรา มุ่งเน้นโดยตรง)