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

รายงานปัญหา ดูซอร์สโค้ด รุ่น Nightly · 8.0 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 ทำการปรับปรุงต่อไปนี้

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

  • การโอนไฟล์แบบเพิ่ม ระบบจะนำทรัพยากร Android, ไฟล์ .dex และไลบรารีแบบเนทีฟออกจาก .apk หลักและจัดเก็บไว้ในไดเรกทอรีแยกต่างหากสำหรับการติดตั้งบนอุปกรณ์เคลื่อนที่ วิธีนี้ช่วยให้คุณอัปเดตโค้ดและทรัพยากร 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 เวอร์ชันเก่าไม่ใช่จุดสนใจของเรา)