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

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

Trang này mô tả cách bazel mobile-install giúp phát triển lặp cho Android nhanh hơn nhiều. Trang này mô tả những 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 các thay đổi nhỏ cho một ứng dụng Android rất nhanh, hãy làm như sau:

  1. Tìm quy tắc android_binary của ứng dụng mà 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 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. Bạn sẽ 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 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 việc 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ị 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 so với việc phải chờ đợ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 các thay đổi của bạn có thực hiện đúng 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 bao gồm nhiều bước tuần tự, nguyên khối và bạn phải thực hiện tất cả các bước này để tạo một ứng dụng Android. Tại Google, việc chờ 5 phút để tạo một thay đổi một dòng không phải là điều hiếm gặp đối với các dự án lớn hơn như Google Maps.

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

Các vấn đề khi cài đặt ứng dụng truyền thống

Việc tạo một ứ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: công cụ này tạo tệp dex cho mọi phương thức một lần nữa, ngay cả khi chỉ có một phương thức được thay đổi.

  • 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 được tải lên, ngay cả khi chỉ có các phần nhỏ thay đổi, ví dụ: một tài nguyên hoặc một phương thức duy nhất, vì vậy, đây có thể là một nút thắt cổ chai lớn.

  • Biên dịch sang mã gốc. Android L giới thiệu ART, một thời gian chạy Android mới, biên dịch các ứng dụng trước thời gian thay vì biên dịch chúng ngay khi cần như Dalvik. Điều này giúp các ứng dụng nhanh hơn nhiều với chi phí là thời gian cài đặt lâu hơn. Đây là một sự đánh đổi tốt 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 quá trình 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 chạy tối đa một vài lần.

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

bazel mobile-install thực hiện các cải tiến sau:

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

  • Chuyển tệp gia tăng. Tài nguyên Android, tệp .dex và thư viện gốc bị xoá khỏi tệp .apk chính và được lưu trữ trong một thư mục mobile-install riêng. Điều này giúp bạn có thể 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 mất ít thời gian hơn và chỉ các 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 gốc 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 mobile-install 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 trong suốt đối với ứng dụng, ngoại trừ một vài trường hợp đặc biệt được mô tả bên dưới.

Tạo tệp dex được phân chia

Việc tạo tệp dex được phân chia khá đơn giản: sau khi tạo các tệp .jar, một công cụ sẽ phân chia 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 những tệp đã thay đổi kể từ bản dựng trước. Logic xác định phân đoạn nào cần tạo tệp dex không dành riêng cho Android: logic này chỉ sử dụng thuật toán cắt tỉa thay đổi chung của Bazel.

Phiên bản đầu tiên của thuật toán phân chia chỉ sắp xếp các tệp .class theo 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 tỏ là không tối ưu: nếu một lớp được thêm hoặc xoá (ngay cả lớp lồng nhau hoặc lớp ẩn danh một), thì tất cả các lớp theo bảng chữ cái sau đó sẽ bị dịch chuyển đi một, dẫn đến việc tạo tệp dex cho các phân đoạn đó một lần nữa. Do đó, người ta quyết định phân chia các gói Java thay vì các lớp riêng lẻ. Tất nhiên, điều này vẫn dẫn đến việc tạo tệp dex cho nhiều phân đoạn 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 duy nhất.

Số lượng phân đoạn được kiểm soát bởi tệp BUILD (sử dụng thuộc tính android_binary.dex_shards). Trong một thế giới lý tưởng, Bazel sẽ tự động xác định số lượng phân đoạn 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 tạo bản 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 phân đoạn 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 nhiều phân đoạn thì bản dựng và quá trình cài đặt sẽ càng nhanh, nhưng quá trình khởi động ứng dụng sẽ càng chậm, vì trình liên kết động phải thực hiện nhiều công việc hơn. Điểm tối ưu thường nằm trong khoảng từ 10 đến 50 phân đoạn.

Chuyển tệp gia tăng

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

  1. Cài đặt tệp .apk (thường là bằ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 mobile-install

Không có nhiều tính năng gia tăng trong bước đầu tiên: ứng dụng được cài đặt hoặc không. 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 thông qua tuỳ chọn dòng lệnh --incremental hay không vì không thể xác định trong mọi trường hợp liệu có cần thiết hay không.

Trong bước thứ hai, các tệp của ứng dụng từ 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 có trên thiết bị và tổng kiểm tra của chúng. Mọi tệp mới đều được tải lên thiết bị, mọi tệp đã thay đổi đều được cập nhật và mọi tệp đã bị xoá đều 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 tổng kiểm tra của tệp đó trong tệp kê khai. Bạn có thể bảo vệ chống lại điều này bằng cách tính toán tổng kiểm tra của các tệp trên thiết bị, nhưng điều này được coi là không đáng để tăng thời gian cài đặt.

Ứng dụng gốc

Ứng dụng gốc là nơi xảy ra quá trình tải 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 tạo lớp con BaseDexClassLoader và là một kỹ thuật được ghi lại khá đầy đủ. Điều này xảy 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 tệp 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.

Điều này cần xảy 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 có trong tệp .apk, nghĩa là các thay đổi đối với những lớp đó sẽ yêu cầu cài đặt lại toàn bộ.

Điều này được thực hiện bằng cách thay thế lớp Application được chỉ định trong AndroidManifest.xml bằng ứng dụng gốc. Ứng dụng này sẽ kiểm soát khi ứng dụng được khởi động và điều chỉnh trình tải lớp và trình quản lý tài nguyên một cách thích hợp vào 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 các thành phần bên trong của khung Android.

Một điều khác mà ứng dụng gốc thực hiện là sao chép các thư viện gốc do mobile-install cài đặt 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 cho bất kỳ vị trí nào mà adb không phải là gốc có thể truy cập.

Sau khi hoàn tất tất cả những điều này, ứng dụng gốc sẽ tạo thực thể cho lớp thực tế Application, thay đổi tất cả các tham chiếu đến chính nó 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 độ tạo và cài đặt các ứng dụng lớn từ 4 đến 10 lần sau một thay đổi nhỏ.

Các số sau đây được tính toán 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 một 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 gốc thực hiện không hoạt động trong mọi trường hợp. Các trường hợp sau đây nêu bật những trường hợp mà ứng dụng này không hoạt động như mong đợi:

  • Khi Context được truyền đến 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 gốc 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 truyền Context xuống như thế này, nhưng điều này dường như xảy ra trong một số ứng dụng tại Google.

  • Các tài nguyên do bazel mobile-install cài đặt chỉ có 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ần cài đặt không gia tăng gần đây nhất.

  • Các thiết bị không chạy ART. Mặc dù ứng dụng gốc hoạt động tốt trên Froyo trở lên, nhưng Dalvik có một lỗi khiến ứng dụng này nghĩ 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, ví dụ: khi chú thích Java được sử dụng theo một cách cụ thể. Miễn là ứng dụng của bạn không kích hoạt các lỗi này, thì ứng dụng đó cũng sẽ hoạt động với Dalvik, too (tuy nhiên, xin lưu ý rằng việc hỗ trợ các phiên bản Android cũ không phải là trọng tâm của chúng tôi )