ติดตั้ง 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 บอกบาเซลว่าจะใช้ไบนารี 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 มีปัญหาบางอย่าง เช่น

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

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

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

การดำเนินการของ bazel mobile-install

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

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

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

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

การชาร์ดดิ้ง Dexing

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 จะยังคงอ้างอิงแอปพลิเคชันสตับแทนที่จะเป็นของจริง ข้อผิดพลาดนี้ไม่ใช่ข้อบกพร่องเนื่องจากคุณไม่ควรดาวน์แคสต์ Context แบบนี้ แต่ดูเหมือนว่าปัญหานี้จะเกิดขึ้นในไม่กี่แอปที่ Google

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

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