cài đặt trên thiết bị di động bazel

Báo cáo vấn đề Xem nguồn Nightly · 7.4 .

Phát triển lặp lại nhanh cho Android

Trang này mô tả cách bazel mobile-install giúp việc phát triển lặp lại cho Android nhanh hơn nhiều. Tài liệu này mô tả các lợi ích của phương pháp này so với những thách thức của phương thức cài đặt ứng dụng truyền thống.

Tóm tắt

Để cài đặt nhanh các thay đổi nhỏ đối với ứng dụng Android, hãy làm như sau:

  1. Tìm quy tắc android_binary của ứng dụng bạn muốn cài đặt.
  2. Tắt Proguard bằng cách xoá thuộc tính proguard_specs.
  3. Đặt thuộc tính multidex thành native.
  4. Đặt thuộc tính dex_shards thành 10.
  5. Kết nối thiết bị chạy ART (không phải Dalvik) qua USB và bật tính năng gỡ lỗi qua USB trên thiết bị đó.
  6. Chạy bazel mobile-install :your_target. Quá trình khởi động ứng dụng sẽ chậm hơn một chút so với bình thường.
  7. Chỉnh sửa mã hoặc tài nguyên Android.
  8. Chạy bazel mobile-install --incremental :your_target.
  9. Tận hưởng việc không phải chờ đợi lâu.

Một số tuỳ chọn dòng lệnh cho Bazel có thể hữu ích:

  • --adb cho Bazel biết tệp nhị phân adb nào cần sử dụng
  • Bạn có thể dùng --adb_arg để thêm các đối số bổ sung vào dòng lệnh của adb. Một ứng dụng hữu ích của tính năng này là chọn thiết bị mà bạn muốn cài đặt nếu bạn có nhiều thiết bị được kết nối với máy trạm: bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
  • --start_app tự động khởi động ứng dụng

Khi không chắc chắn, hãy xem ví dụ hoặc liên hệ với chúng tôi.

Giới thiệu

Một trong những thuộc tính quan trọng nhất của chuỗi công cụ dành cho nhà phát triển là tốc độ: có sự khác biệt rất lớn giữa việc thay đổi mã và xem mã chạy trong vòng một giây với việc phải đợi vài phút, đôi khi là vài giờ trước khi bạn nhận được phản hồi về việc liệu các thay đổi của bạn có mang lại kết quả như mong đợi hay không.

Rất tiếc, chuỗi công cụ Android truyền thống để tạo tệp .apk đòi hỏi nhiều bước tuần tự, nguyên khối và tất cả các bước này đều phải được thực hiện để tạo ứng dụng Android. Tại Google, việc chờ 5 phút để tạo một thay đổi dòng đơn là điều không bình thường đối với các dự án lớn hơn như Google Maps.

bazel mobile-install giúp quá trình phát triển lặp lại cho Android nhanh hơn nhiều bằng cách sử dụng kết hợp việc cắt giảm thay đổi, phân đoạn công việc và thao tác thông minh đối với các thành phần nội bộ của Android, tất cả đều không làm thay đổi bất kỳ mã nào của ứng dụng.

Vấn đề khi cài đặt ứng dụng theo cách truyền thống

Việc xây dựng ứng dụng Android có một số vấn đề, bao gồm:

  • Tạo tệp dex. Theo mặc định, "dx" được gọi chính xác một lần trong bản dựng và không biết cách sử dụng lại công việc từ các bản dựng trước đó: nó sẽ tạo lại tệp dex cho mọi phương thức, mặc dù chỉ một phương thức được thay đổi.

  • Đang tải dữ liệu lên thiết bị. adb không sử dụng toàn bộ băng thông của kết nối USB 2.0 và các ứng dụng lớn hơn có thể mất nhiều thời gian để tải lên. Toàn bộ ứng dụng sẽ được tải lên, ngay cả khi chỉ có một số phần nhỏ thay đổi, chẳng hạn như một tài nguyên hoặc một phương thức duy nhất. Vì vậy, đây có thể là nút thắt cổ chai chính.

  • Biên dịch thành mã gốc. Android L đã ra mắt ART, một môi trường thời gian chạy Android mới, biên dịch ứng dụng trước khi thực thi thay vì biên dịch ứng dụng đúng thời điểm như Dalvik. Điều này giúp ứng dụng nhanh hơn nhiều nhưng lại cần thời gian cài đặt lâu hơn. Đây là một sự đánh đổi phù hợp cho người dùng vì họ thường cài đặt một ứng dụng một lần và sử dụng nhiều lần, nhưng dẫn đến việc phát triển chậm hơn khi một ứng dụng được cài đặt nhiều lần và mỗi phiên bản được chạy nhiều nhất là một vài lần.

Phương pháp của bazel mobile-install

bazel mobile-install cải tiến những điểm sau:

  • Tạo tệp dex phân đoạn. Sau khi tạo mã Java của ứng dụng, Bazel sẽ phân đoạn các tệp lớp thành các phần có kích thước gần như bằng nhau và gọi riêng dx trên các phần đó. dx không được gọi trên các mảnh không thay đổi kể từ bản dựng gần đây nhất.

  • Truyền tệp tăng dần. Tài nguyên Android, tệp .dex và thư viện gốc sẽ bị xoá khỏi tệp .apk chính và được lưu trữ trong một thư mục cài đặt dành cho thiết bị di động riêng biệt. Điều này cho phép bạn cập nhật mã và tài nguyên Android một cách độc lập mà không cần cài đặt lại toàn bộ ứng dụng. Do đó, việc chuyển tệp sẽ mất ít thời gian hơn và chỉ những tệp .dex đã thay đổi mới được biên dịch lại trên thiết bị.

  • Tải các phần của ứng dụng từ bên ngoài tệp .apk. Một ứng dụng mô phỏng nhỏ được đưa vào tệp .apk để tải tài nguyên Android, mã Java và mã gốc từ thư mục cài đặt dành cho thiết bị di động trên thiết bị, sau đó chuyển quyền kiểm soát sang ứng dụng thực tế. Tất cả đều minh bạch với ứng dụng, ngoại trừ một số trường hợp hiếm gặp được mô tả bên dưới.

Tạo tệp dex phân đoạn

Việc tạo tệp dex phân đoạn khá đơn giản: sau khi tạo các tệp .jar, một công cụ sẽ phân đoạn các tệp đó thành các tệp .jar riêng biệt có kích thước gần bằng nhau, sau đó gọi dx trên các tệp đã thay đổi kể từ bản dựng trước. Logic xác định mảnh nào sẽ được dex không dành riêng cho Android: logic này chỉ sử dụng thuật toán cắt giảm thay đổi chung của Bazel.

Phiên bản đầu tiên của thuật toán phân đoạn chỉ sắp xếp các tệp .class theo thứ tự bảng chữ cái, sau đó cắt danh sách thành các phần có kích thước bằng nhau, nhưng điều này đã chứng minh là không tối ưu: nếu một lớp được thêm hoặc xoá (thậm chí là một lớp lồng nhau hoặc một lớp ẩn danh), thì lớp đó sẽ khiến tất cả các lớp theo thứ tự bảng chữ cái sau đó dịch chuyển một lớp, dẫn đến việc tạo lại các mảnh đó. Do đó, hệ thống đã quyết định phân đoạn các gói Java thay vì từng lớp riêng lẻ. Tất nhiên, việc này vẫn dẫn đến việc tạo tệp dex cho nhiều mảnh nếu một gói mới được thêm hoặc xoá, nhưng điều đó ít xảy ra hơn nhiều so với việc thêm hoặc xoá một lớp.

Số lượng phân mảnh được kiểm soát bởi tệp BUILD (sử dụng thuộc tính android_binary.dex_shards). Trong trường hợp lý tưởng, Bazel sẽ tự động xác định số lượng mảnh tốt nhất, nhưng Bazel hiện phải biết tập hợp các hành động (ví dụ: các lệnh sẽ được thực thi trong quá trình xây dựng) trước khi thực thi bất kỳ hành động nào trong số đó, vì vậy, Bazel không thể xác định số lượng mảnh tối ưu vì không biết cuối cùng sẽ có bao nhiêu lớp Java trong ứng dụng. Nói chung, càng có nhiều mảnh thì quá trình xây dựng và cài đặt càng nhanh, nhưng quá trình khởi động ứng dụng càng chậm, vì trình liên kết động phải làm nhiều việc hơn. Điểm ngọt thường là từ 10 đến 50 phân đoạn.

Truyền tệp tăng dần

Sau khi tạo ứng dụng, bước tiếp theo là cài đặt ứng dụng đó, tốt nhất là ít tốn công sức nhất có thể. Quá trình cài đặt bao gồm các bước sau:

  1. Đang cài đặt .apk (thường sử dụng adb install)
  2. Tải các tệp .dex, tài nguyên Android và thư viện gốc lên thư mục cài đặt trên thiết bị di động

Không có nhiều điểm tăng dần trong bước đầu tiên: ứng dụng đã được cài đặt hay chưa. Bazel hiện dựa vào người dùng để cho biết liệu có nên thực hiện bước này hay không thông qua tuỳ chọn dòng lệnh --incremental vì không thể xác định trong mọi trường hợp nếu cần.

Ở bước thứ hai, các tệp của ứng dụng trong bản dựng được so sánh với một tệp kê khai trên thiết bị liệt kê những tệp ứng dụng nào có trên thiết bị và tổng kiểm của các tệp đó. Mọi tệp mới sẽ được tải lên thiết bị, mọi tệp đã thay đổi sẽ được cập nhật và mọi tệp đã bị xoá sẽ bị xoá khỏi thiết bị. Nếu không có tệp kê khai, thì hệ thống sẽ giả định rằng mọi tệp đều cần được tải lên.

Xin lưu ý rằng bạn có thể đánh lừa thuật toán cài đặt gia tăng bằng cách thay đổi một tệp trên thiết bị, nhưng không thay đổi giá trị tổng kiểm của tệp trong tệp kê khai. Bạn có thể bảo vệ vấn đề này bằng cách tính toán tổng kiểm của các tệp trên thiết bị, nhưng việc này được coi là không đáng để tăng thời gian cài đặt.

Ứng dụng Stub

Ứng dụng mô phỏng là nơi diễn ra phép thuật để tải các tệp dex, mã gốc và tài nguyên Android từ thư mục mobile-install trên thiết bị.

Quá trình tải thực tế được triển khai bằng cách phân lớp con BaseDexClassLoader và là một kỹ thuật được ghi nhận hợp lý. Quá trình này diễn ra trước khi bất kỳ lớp nào của ứng dụng được tải, để mọi lớp ứng dụng có trong apk đều có thể được đặt trong thư mục mobile-install trên thiết bị để có thể cập nhật mà không cần adb install.

Việc này cần diễn ra trước khi bất kỳ lớp nào của ứng dụng được tải, để không có lớp ứng dụng nào cần phải có trong tệp .apk, tức là các thay đổi đối với các lớp đó sẽ yêu cầu cài đặt lại toàn bộ.

Bạn có thể thực hiện việc này bằng cách thay thế lớp Application được chỉ định trong AndroidManifest.xml bằng ứng dụng mã giả lập. Phương thức này sẽ kiểm soát khi ứng dụng khởi động và điều chỉnh trình tải lớp cũng như trình quản lý tài nguyên một cách thích hợp ngay từ thời điểm sớm nhất (hàm khởi tạo) bằng cách sử dụng tính năng phản chiếu Java trên nội bộ của khung Android.

Một chức năng khác của ứng dụng mã giả lập là sao chép các thư viện gốc được cài đặt bằng lượt cài đặt trên thiết bị di động sang một vị trí khác. Điều này là cần thiết vì trình liên kết động cần đặt bit X trên các tệp, điều này không thể thực hiện được đối với bất kỳ vị trí nào mà adb không phải là thư mục gốc có thể truy cập.

Sau khi hoàn tất những việc này, ứng dụng giả lập sẽ tạo thực thể cho lớp Application thực tế, thay đổi mọi tham chiếu đến chính ứng dụng đó thành ứng dụng thực tế trong khung Android.

Kết quả

Hiệu suất

Nhìn chung, bazel mobile-install giúp tăng tốc độ xây dựng và cài đặt các ứng dụng lớn từ 4 đến 10 lần sau một thay đổi nhỏ.

Chúng tôi đã tính toán các con số sau đây cho một số sản phẩm của Google:

Tất nhiên, điều này phụ thuộc vào bản chất của thay đổi: việc biên dịch lại sau khi thay đổi thư viện cơ sở sẽ mất nhiều thời gian hơn.

Các điểm hạn chế

Các thủ thuật mà ứng dụng giả lập thực hiện không hoạt động trong mọi trường hợp. Sau đây là các trường hợp nêu bật những trường hợp không hoạt động như mong đợi:

  • Khi Context được truyền vào lớp Application trong ContentProvider#onCreate(). Phương thức này được gọi trong quá trình khởi động ứng dụng trước khi chúng ta có cơ hội thay thế thực thể của lớp Application, do đó, ContentProvider sẽ vẫn tham chiếu đến ứng dụng mô phỏng thay vì ứng dụng thực. Có thể nói, đây không phải là lỗi vì bạn không được phép hạ cấp Context như vậy, nhưng điều này dường như xảy ra trong một số ứng dụng của Google.

  • Các tài nguyên do bazel mobile-install cài đặt chỉ có sẵn trong ứng dụng. Nếu các ứng dụng khác truy cập vào tài nguyên thông qua PackageManager#getApplicationResources(), thì các tài nguyên này sẽ là từ lượt cài đặt không tăng dần gần đây nhất.

  • Các thiết bị hiện không chạy ART. Mặc dù ứng dụng mã giả lập hoạt động tốt trên Froyo trở lên, nhưng Dalvik có lỗi khiến ứng dụng cho rằng ứng dụng không chính xác nếu mã của ứng dụng được phân phối trên nhiều tệp .dex trong một số trường hợp nhất định, chẳng hạn như khi sử dụng chú giải Java theo cách cụ thể. Miễn là ứng dụng của bạn không vướng vào các lỗi này, ứng dụng cũng sẽ hoạt động với Dalvik (tuy nhiên, hãy lưu ý rằng việc hỗ trợ các phiên bản Android cũ không chính xác là trọng tâm của chúng tôi)