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

รายงานปัญหา ดูซอร์สโค้ด รุ่น Nightly · 8.1 · 8.0 · 7.6 · 7.5 · 7.4

การพัฒนาแบบทำซ้ำอย่างรวดเร็วสำหรับ 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 เริ่มแอปโดยอัตโนมัติ

หากมีข้อสงสัย ให้ดูตัวอย่างหรือติดต่อเรา

บทนำ

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

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

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

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

การสร้างแอป Android มีปัญหาบางอย่าง ซึ่งรวมถึง

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

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

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

แนวทางของ bazel mobile-install

bazel mobile-install ทำการปรับปรุงต่อไปนี้

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

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

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

การแยก Dex

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

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

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

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

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

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

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

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

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

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

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

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

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

ซึ่งทำได้โดยแทนที่คลาส Application ที่ระบุใน AndroidManifest.xml ด้วยแอปพลิเคชันจำลอง การดำเนินการนี้จะควบคุมเมื่อแอปเริ่มต้น และปรับแต่งตัวโหลดคลาสและเครื่องมือจัดการทรัพยากรอย่างเหมาะสมตั้งแต่เนิ่นๆ (ตัวสร้างคอนสตรัคเตอร์) โดยใช้การสะท้อนของ Java ในข้อมูลภายในของเฟรมเวิร์ก Android

อีกสิ่งที่แอปพลิเคชันสตับทําคือการคัดลอกไลบรารีเนทีฟที่ติดตั้งโดยการติดตั้งบนอุปกรณ์เคลื่อนที่ไปยังตําแหน่งอื่น ซึ่งจำเป็นต้องทำเนื่องจากโปรแกรมลิงก์แบบไดนามิกต้องตั้งค่าบิต X ในไฟล์ ซึ่งไม่สามารถดำเนินการสำหรับตำแหน่งที่เข้าถึงได้โดยใช้ adb ที่ไม่ใช่รูท

เมื่อดำเนินการทั้งหมดเสร็จแล้ว แอปพลิเคชันจำลองจะสร้างอินสแตนซ์ของคลาส Application จริง ซึ่งจะเปลี่ยนการอ้างอิงทั้งหมดไปยังแอปพลิเคชันจริงภายในเฟรมเวิร์ก Android

ผลลัพธ์

ประสิทธิภาพ

โดยทั่วไป bazel mobile-install จะทำให้การสร้างและการติดตั้งแอปขนาดใหญ่เร็วขึ้น 4-10 เท่าหลังจากการเปลี่ยนแปลงเล็กน้อย

ตัวเลขต่อไปนี้คำนวณจากผลิตภัณฑ์ของ Google บางรายการ

แน่นอนว่าเวลาที่ใช้จะขึ้นอยู่กับลักษณะของการเปลี่ยนแปลง การคอมไพล์อีกครั้งหลังจากการเปลี่ยนแปลงไลบรารีพื้นฐานจะใช้เวลานานกว่า

ข้อจำกัด

เทคนิคที่แอปพลิเคชันจำลองใช้อาจใช้ไม่ได้ในบางกรณี กรณีต่อไปนี้จะไฮไลต์กรณีที่ฟีเจอร์ไม่ทำงานตามที่ควรจะเป็น

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

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

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