การพัฒนาแบบทำซ้ำอย่างรวดเร็วสำหรับ Android
หน้านี้จะอธิบายวิธีที่ bazel mobile-install
ทำให้การพัฒนาแบบวนซ้ำ
สำหรับ Android เร็วขึ้นมาก โดยจะอธิบายประโยชน์ของวิธีนี้เทียบกับความท้าทายของวิธีติดตั้งแอปแบบดั้งเดิม
สรุป
หากต้องการติดตั้งการเปลี่ยนแปลงเล็กๆ น้อยๆ ในแอป Android อย่างรวดเร็ว ให้ทำดังนี้
- ค้นหากฎ
android_binary
ของแอปที่ต้องการติดตั้ง - ปิดใช้ Proguard โดยนำแอตทริบิวต์
proguard_specs
ออก - ตั้งค่าแอตทริบิวต์
multidex
เป็นnative
- ตั้งค่าแอตทริบิวต์
dex_shards
เป็น10
- เชื่อมต่ออุปกรณ์ที่ใช้ ART (ไม่ใช่ Dalvik) ผ่าน USB และเปิดใช้การแก้ไขข้อบกพร่อง USB บนอุปกรณ์
- เรียกใช้
bazel mobile-install :your_target
การเริ่มต้นแอปจะช้ากว่าปกติเล็กน้อย - แก้ไขโค้ดหรือทรัพยากรของ Android
- เรียกใช้
bazel mobile-install --incremental :your_target
- ไม่ต้องรอนาน
ตัวเลือกบรรทัดคำสั่งไปยัง 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 ชาร์ด
การโอนไฟล์ที่เพิ่มขึ้น
หลังจากสร้างแอปแล้ว ขั้นตอนถัดไปคือการติดตั้งแอปโดยพยายามดำเนินการน้อยที่สุดเท่าที่จะเป็นไปได้ การติดตั้งมีขั้นตอนดังต่อไปนี้
- การติดตั้ง .apk (โดยปกติจะใช้
adb install
) - การอัปโหลดไฟล์ .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 เวอร์ชันเก่ายังไม่ใช่ประเด็นที่เราให้ความสำคัญ)