ติดตั้ง 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 วินาทีกับการต้องรอหลายนาที บางครั้งอาจเป็นชั่วโมง ก่อนที่จะได้รับความคิดเห็นว่าการเปลี่ยนแปลงของคุณทำงานตามที่คาดไว้หรือไม่

น่าเสียดายที่ชุดเครื่องมือ Android แบบดั้งเดิมสำหรับการสร้าง .apk มีขั้นตอนแบบโมโนลิธิกและลำดับต่อเนื่องหลายขั้นตอน และต้องทำตามขั้นตอนทั้งหมดนี้เพื่อสร้างแอป Android ที่ Google การรอ 5 นาทีเพื่อสร้างการเปลี่ยนแปลงบรรทัดเดียวไม่ใช่เรื่องแปลกในโปรเจ็กต์ขนาดใหญ่ เช่น Google Maps

bazel mobile-install ช่วยให้การพัฒนาแบบวนซ้ำสำหรับ Android เร็วขึ้นมากโดยใช้การผสมผสานระหว่างการตัดแต่งการเปลี่ยนแปลง การแบ่งงาน และการจัดการภายในของ Android อย่างชาญฉลาด ทั้งหมดนี้โดยไม่เปลี่ยนโค้ดของแอป

ปัญหาเกี่ยวกับการติดตั้งแอปแบบเดิม

การสร้างแอป Android มีปัญหาบางอย่าง ได้แก่

  • Dexing โดยค่าเริ่มต้น ระบบจะเรียกใช้ "dx" เพียงครั้งเดียวในการบิลด์ และไม่ทราบวิธีนำงานจากการบิลด์ก่อนหน้ากลับมาใช้ใหม่ โดยจะทำการ Dexing ทุกเมธอดอีกครั้ง แม้ว่าจะมีการเปลี่ยนแปลงเพียงเมธอดเดียวก็ตาม

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

  • การคอมไพล์เป็นโค้ดแบบเนทีฟ Android L ได้เปิดตัว ART ซึ่งเป็นรันไทม์ Android ใหม่ที่คอมไพล์แอปแบบล่วงหน้าแทนที่จะคอมไพล์แบบทันทีเหมือน Dalvik ซึ่งทำให้แอปเร็วขึ้นมาก แต่ใช้เวลาติดตั้งนานขึ้น นี่เป็นข้อแลกเปลี่ยนที่ดีสำหรับผู้ใช้ เนื่องจากโดยปกติแล้วผู้ใช้จะติดตั้งแอปเพียงครั้งเดียวและใช้หลายครั้ง แต่ทำให้การพัฒนาช้าลงเนื่องจากมีการติดตั้งแอปหลายครั้งและแต่ละเวอร์ชันจะทำงานไม่กี่ครั้ง

วิธีการของ bazel mobile-install

bazel mobile-install มีการปรับปรุงดังนี้

  • Dexing แบบชาร์ด หลังจากสร้างโค้ด Java ของแอปแล้ว Bazel จะชาร์ดไฟล์คลาสออกเป็นส่วนๆ ที่มีขนาดเท่ากันโดยประมาณ และเรียกใช้ dx แยกกันในแต่ละส่วน ระบบจะไม่เรียกใช้ dx ในชาร์ดที่ไม่มีการเปลี่ยนแปลงตั้งแต่การสร้างครั้งล่าสุด

  • การโอนไฟล์แบบเพิ่มทีละน้อย ระบบจะนำทรัพยากร Android, ไฟล์ .dex และไลบรารีแบบเนทีฟออกจาก .apk หลักและจัดเก็บไว้ในไดเรกทอรี mobile-install แยกกัน ซึ่งช่วยให้สามารถอัปเดตโค้ดและทรัพยากร Android ได้อย่างอิสระโดยไม่ต้องติดตั้งแอปทั้งหมดอีกครั้ง ดังนั้นการโอนไฟล์จึงใช้เวลาน้อยลง และระบบจะคอมไพล์ไฟล์ .dex ที่มีการเปลี่ยนแปลงอีกครั้งในอุปกรณ์

  • การโหลดบางส่วนของแอปจากภายนอก .apk ระบบจะใส่แอปพลิเคชัน Stub ขนาดเล็กไว้ใน .apk ซึ่งจะโหลดทรัพยากร Android, โค้ด Java และโค้ดแบบเนทีฟจากไดเรกทอรี mobile-install ในอุปกรณ์ จากนั้นจะโอนการควบคุมไปยังแอปจริง ทั้งหมดนี้จะโปร่งใสสำหรับแอป ยกเว้นในบางกรณีที่อธิบายไว้ด้านล่าง

Dexing แบบชาร์ด

Dexing แบบชาร์ดค่อนข้างตรงไปตรงมา เมื่อสร้างไฟล์ .jar แล้ว a เครื่องมือ จะชาร์ดไฟล์เหล่านั้นออกเป็นไฟล์ .jar แยกกันที่มีขนาดเท่ากันโดยประมาณ จากนั้นจะเรียกใช้ dx ในไฟล์ที่มีการเปลี่ยนแปลงตั้งแต่การสร้างครั้งก่อน ตรรกะที่กำหนดว่าชาร์ดใดที่จะทำการ Dexing ไม่ได้เฉพาะเจาะจงสำหรับ Android แต่ใช้เพียงอัลกอริทึมการตัดแต่งการเปลี่ยนแปลงทั่วไปของ Bazel

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

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

การโอนไฟล์แบบเพิ่มทีละน้อย

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

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

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

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

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

แอปพลิเคชัน 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 ใช้ไม่ได้ผลในทุกกรณี กรณีต่อไปนี้แสดงให้เห็นว่าแอปพลิเคชัน Stub ไม่ทำงานตามที่คาดไว้

  • เมื่อมีการแคสต์ Context เป็นคลาส Application ใน ContentProvider#onCreate() ระบบจะเรียกใช้เมธอดนี้ระหว่างการเริ่มต้นแอปพลิเคชันก่อนที่เราจะมีโอกาสแทนที่อินสแตนซ์ของคลาส Application ดังนั้น ContentProvider จะยังคงอ้างอิงแอปพลิเคชัน Stub แทนที่จะอ้างอิงแอปพลิเคชันจริง อาจกล่าวได้ว่านี่ไม่ใช่ข้อบกพร่องเนื่องจากคุณไม่ควรแคสต์ Context ลงแบบนี้ แต่ดูเหมือนว่าเหตุการณ์นี้จะเกิดขึ้นในแอปบางแอปที่ Google

  • ทรัพยากรที่ติดตั้งโดย bazel mobile-install จะใช้ได้จากภายในแอปเท่านั้น หากแอปอื่นๆ เข้าถึงทรัพยากรผ่าน PackageManager#getApplicationResources() ทรัพยากรเหล่านี้จะมาจากการติดตั้งครั้งล่าสุดแบบไม่เพิ่มทีละน้อย

  • อุปกรณ์ที่ไม่ได้ใช้ ART แม้ว่าแอปพลิเคชัน Stub จะทำงานได้ดีใน Froyo และเวอร์ชันที่ใหม่กว่า แต่ Dalvik มีข้อบกพร่องที่ทำให้คิดว่าแอปไม่ถูกต้องหากโค้ดของแอปมีการกระจายอยู่ในไฟล์ .dex หลายไฟล์ในบางกรณี เช่น เมื่อมีการใช้คำอธิบายประกอบ Java ในลักษณะที่ เฉพาะเจาะจง ตราบใดที่แอปของคุณไม่ทำให้เกิดข้อบกพร่องเหล่านี้ แอปก็ควรทำงานร่วมกับ Dalvik ได้เช่นกัน (อย่างไรก็ตาม โปรดทราบว่าการรองรับ Android เวอร์ชันเก่าไม่ใช่เป้าหมายหลักของเรา)