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

Báo cáo vấn đề Xem nguồn Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

Trang này mô tả cách bazel mobile-install giúp quá trình phát triển lặp đi lặp lại cho Android diễn ra nhanh hơn nhiều. Bài viết 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 pháp 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 chóng, 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. Không phải chờ đợi lâu.

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

  • --adb cho Bazel biết nên sử dụng tệp nhị phân adb nào
  • --adb_arg có thể được dùng để thêm đố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 có nhiều thiết bị kết nối với máy trạm của bạn:bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
  • --start_app tự động khởi động ứng dụng

Khi nghi ngờ, 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 đợi vài phút, đôi khi là vài giờ, trước khi bạn nhận được bất kỳ thông tin phản hồi nào về việc các thay đổi của bạn có thực hiện được những gì bạn mong đợi hay không.

Rất tiếc, chuỗi công cụ Android truyền thống để tạo một .apk đòi hỏi 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 bất 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 đi lặp lại cho Android diễn ra nhanh hơn nhiều bằng cách 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 nội bộ của Android mà không cần 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 tạo ứ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ó dex 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 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à một điểm tắc nghẽn lớn.

  • Biên dịch sang mã gốc. Android L ra mắt 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 tại thời điểm như Dalvik. Điều này giúp các ứng dụng chạy nhanh hơn nhiều nhưng thời gian cài đặt sẽ lâu hơn. Đây là một sự đánh đổi hợp lý cho người dùng vì họ thường cài đặt ứng dụng một lần và sử dụng nhiều lần, nhưng điều này 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ỉ chạy tối đa vài lần.

Phương pháp tiếp cận của bazel mobile-install

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

  • Tạo tệp dex phân mảnh. Sau khi tạo mã Java của ứng dụng, Bazel sẽ phân mảnh 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 riêng dx trên các phần đó. dx không được gọi trên những phân đoạn không thay đổi kể từ bản dựng gần đây nhất.

  • Truyền tệp gia tăng. Các 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 trên thiết bị di động riêng biệt. Nhờ đó, 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 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 gốc nhỏ được đưa vào .apk tải tài nguyên Android, mã Java và mã gốc từ thư mục cài đặt di động trên thiết bị, sau đó chuyển quyền kiểm soát cho ứng dụng thực tế. Tất cả những điều này đều diễn ra một cách minh bạch đối với ứng dụng, ngoại trừ một số trường hợp đặc biệt được mô tả bên dưới.

Phân mảnh Dexing

Phân mảnh dexing khá đơn giản: sau khi các tệp .jar được tạo, một công cụ sẽ phân mảnh các tệp này 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 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 đoạn chỉ sắp xếp các tệp .class theo bảng chữ cái, sau đó chia danh sách thành các phần có kích thước bằng nhau, nhưng cách này không hiệu quả: 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), thì tất cả các lớp theo bảng chữ cái sau lớp đó sẽ bị dịch chuyển đi một vị trí, dẫn đến việc phân đoạn lại các mảnh đó. Do đó, chúng tôi quyết định phân đoạn 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 lập chỉ mục 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 duy nhất.

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

Truyề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 ít công sức nhất có thể. Quá trình cài đặt bao gồm các bước sau:

  1. Cài đặt .apk (thường là dùng adb install)
  2. Tải 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 mức tăng trong bước đầu tiên: ứng dụng được cài đặt hoặc không được cài đặt. Hiện tại, Bazel 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 lựa chọn dòng lệnh --incremental vì Bazel không thể xác định trong mọi trường hợp liệu bước này có cần thiết hay không.

Ở bước thứ hai, các tệp của ứng dụng trong bản dựng sẽ được so sánh với một tệp kê khai trên thiết bị. Tệp này liệt kê những tệp ứng dụng có trên thiết bị và tổng kiểm tra 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 tổng kiểm trong tệp kê khai. Bạn có thể ngăn chặn điều này bằng cách tính 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 Stub

Ứng dụng gốc là nơi diễn ra quá trình tải các 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 phụ 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 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 nằm trong .apk. Điều này có nghĩa là các thay đổi đối với những lớp đó sẽ yêu cầu cài đặt lại hoàn toàn.

Việc 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. Thao tác này sẽ kiểm soát khi ứng dụng được khởi động, đồng thời đ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 của trình quản lý) 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 việc 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. Tuy nhiên, bạn không thể làm điều này 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 việc này, ứng dụng gốc sẽ khởi tạo lớp Application thực tế, 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 độ 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ỏ.

Các số liệu 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 phải lúc nào cũng hiệu quả. Các trường hợp sau đây cho thấy nơi mà tính nă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 vẫn sẽ tham chiếu ứng dụng gốc thay vì ứng dụng thực. Có thể đây không phải là lỗi vì bạn không được phép truyền xuống Context 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ẽ đến 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 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 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 gây ra những lỗi này, thì ứng dụng đó cũng sẽ hoạt động với Dalvik (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)