ติดตั้ง 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 เวอร์ชันเก่า)