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

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

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

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

การแยก Dex

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ผลลัพธ์

ประสิทธิภาพ

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

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

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

ข้อจำกัด

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

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

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

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