Xác minh hành vi của ứng dụng trên thời gian chạy Android (ART)

Android Runtime (ART) là môi trường thời gian chạy mặc định cho các thiết bị chạy Android 5.0 (API cấp 21) trở lên. Môi trường thời gian chạy này cung cấp nhiều tính năng giúp cải thiện hiệu suất và độ mượt của nền tảng Android cũng như các ứng dụng. Bạn có thể tìm thêm thông tin về các tính năng mới của ART trong phần Giới thiệu ART.

Tuy nhiên, một số kỹ thuật hoạt động trên Dalvik không hoạt động trên ART. Chiến dịch này cho bạn biết về những điều cần lưu ý khi di chuyển tương thích với ART. Hầu hết các ứng dụng sẽ chỉ hoạt động khi chạy cùng (theo giờ ART)

Giải quyết các vấn đề về thu gom rác (GC)

Trong Dalvik, các ứng dụng thường thấy hữu ích khi gọi một cách rõ ràng System.gc() để nhắc thu thập rác (GC). Thông tin này phải là ít cần thiết hơn nhiều với ART, đặc biệt là khi bạn đang gọi hoạt động thu gom rác để ngăn loại GC_FOR_ALLOC hoặc giảm sự phân mảnh. Bạn có thể xác minh thời gian chạy nào đang được sử dụng bằng cách gọi System.getProperty("java.vm.version"). Nếu đang sử dụng ART, giá trị của thuộc tính này từ "2.0.0" trở lên.

ART sử dụng trình thu thập đồng thời sao chép (CC) để đồng thời nén vùng nhớ khối xếp Java. Do đó, bạn nên tránh sử dụng các kỹ thuật không tương thích với tính năng nén GC (chẳng hạn như lưu con trỏ đến đối tượng dữ liệu thực thể). Điều này đặc biệt quan trọng đối với những ứng dụng tận dụng Giao diện gốc Java (JNI). Để biết thêm thông tin, hãy xem bài viết Ngăn ngừa các vấn đề về JNI.

Ngăn chặn các vấn đề về JNI

JNI của ART có phần nghiêm ngặt hơn so với JNI của Dalvik. Đó là một ý tưởng đặc biệt hay để sử dụng chế độ CheckJNI nhằm phát hiện các vấn đề thường gặp. Nếu ứng dụng của bạn sử dụng C/C++ , bạn nên tham khảo bài viết sau:

Gỡ lỗi JNI Android với CheckJNI

Kiểm tra mã JNI để tìm các sự cố thu thập rác

Trình thu thập Sao chép đồng thời (CC) có thể di chuyển các đối tượng trong bộ nhớ để nén. Nếu bạn sử dụng mã C/C++, đừng thực hiện các thao tác không tương thích với việc nén GC. Chúng tôi đã nâng cao CheckJNI để xác định một số vấn đề tiềm ẩn (như mô tả trong JNI Các thay đổi về tệp đối chiếu cục bộ trong ICS).

Một khía cạnh cụ thể cần lưu ý là việc sử dụng Get...ArrayElements()Release...ArrayElements() . Trong thời gian chạy với GC không nén, Hàm Get...ArrayElements() thường trả về một tham chiếu đến bộ nhớ thực sao lưu đối tượng mảng. Nếu bạn thực hiện thay đổi đối với một trong trả về các phần tử mảng, đối tượng mảng đó tự thay đổi (và các đối số đến Release...ArrayElements() thường bị bỏ qua). Tuy nhiên, nếu thu gọn GC đang được sử dụng, các hàm Get...ArrayElements() có thể trả về một bản sao của bộ nhớ. Nếu bạn lạm dụng tệp tham chiếu khi nén GC là trong quá trình sử dụng, điều này có thể dẫn đến hỏng bộ nhớ hoặc các vấn đề khác. Ví dụ:

  • Nếu bạn thực hiện bất kỳ thay đổi nào đối với các phần tử mảng được trả về, bạn phải gọi hàm hàm Release...ArrayElements() thích hợp khi bạn hoàn tất, để đảm bảo những thay đổi bạn đã thực hiện được sao chép chính xác trở lại nền tảng mảng.
  • Khi bạn huỷ bỏ các phần tử mảng bộ nhớ, bạn phải sử dụng , tuỳ thuộc vào những thay đổi bạn đã thực hiện:
    • Nếu bạn không thực hiện bất kỳ thay đổi nào đối với các phần tử mảng, hãy sử dụng Chế độ JNI_ABORT giải phóng bộ nhớ mà không cần sao chép thay đổi trở lại đối tượng mảng cơ bản.
    • Nếu bạn đã thay đổi mảng và không cần tham chiếu bất kỳ khác, hãy sử dụng mã 0 (cập nhật đối tượng mảng và giải phóng bản sao của kỷ niệm đó).
    • Nếu bạn thực hiện thay đổi đối với mảng mà bạn muốn cam kết và muốn để giữ bản sao của mảng, hãy sử dụng JNI_COMMIT (nội dung cập nhật đối tượng mảng cơ bản và giữ lại bản sao).
  • Khi bạn gọi Release...ArrayElements(), hãy trả về cùng một kết quả con trỏ mà ban đầu Get...ArrayElements() trả về. Cho ví dụ: việc tăng con trỏ ban đầu (để quét qua phần tử mảng trả về), sau đó chuyển con trỏ tăng dần đến Release...ArrayElements(). Việc truyền con trỏ đã sửa đổi này có thể khiến bộ nhớ được giải phóng không chính xác, dẫn đến hỏng bộ nhớ.

Xử lý lỗi

JNI của ART sẽ gửi lỗi trong một số trường hợp mà Dalvik thì không. (Một lần xin nhắc lại, bạn có thể phát hiện nhiều trường hợp như vậy bằng cách kiểm thử bằng CheckJNI.)

Ví dụ: nếu RegisterNatives được gọi bằng một phương thức không tồn tại (có thể vì phương thức này đã bị một công cụ như xoá bỏ ProGuard), ART hiện đang gửi NoSuchMethodError đúng cách:

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

ART cũng ghi lại lỗi (hiển thị trong logcat) nếu RegisterNatives là được gọi mà không có phương thức nào:

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

Ngoài ra, JNI hàm GetFieldID()GetStaticFieldID() hiện đang gửi NoSuchFieldError đúng cách thay vì chỉ trả về giá trị rỗng. Tương tự, GetMethodID()GetStaticMethodID() hiện đang gửi NoSuchMethodError đúng cách. Điều này có thể dẫn đến lỗi CheckJNI do các ngoại lệ chưa được xử lý hoặc ngoại lệ được gửi cho phương thức gọi Java của mã gốc. Điều này giúp đặc biệt quan trọng khi kiểm thử các ứng dụng tương thích với ART bằng chế độ CheckJNI.

ART dự kiến người dùng của các phương thức CallNonvirtual...Method() JNI (chẳng hạn như CallNonvirtualVoidMethod()) để sử dụng phần khai báo của phương thức lớp, không phải lớp con, theo yêu cầu của quy cách JNI.

Tránh các vấn đề về kích thước ngăn xếp

Dalvik có các ngăn xếp riêng cho mã gốc và mã Java, với Java mặc định kích thước ngăn xếp là 32KB và kích thước ngăn xếp gốc mặc định là 1MB. ART có một giao diện người dùng hợp nhất ngăn xếp để có vị trí địa phương tốt hơn. Thông thường, ngăn xếp ART Thread kích thước phải gần bằng với Dalvik. Tuy nhiên, nếu bạn rõ ràng đặt kích thước ngăn xếp, bạn có thể cần phải xem lại các giá trị đó cho các ứng dụng đang chạy trong (theo giờ ART)

  • Trong Java, hãy xem lại các lệnh gọi đến hàm khởi tạo Thread chỉ định một ngăn xếp rõ ràng kích thước. Ví dụ: bạn sẽ cần tăng kích thước nếu StackOverflowError xảy ra.
  • Trong C/C++, hãy xem lại cách sử dụng pthread_attr_setstack()pthread_attr_setstacksize() cho các luồng cũng chạy mã Java qua JNI. Dưới đây là ví dụ về lỗi được ghi lại khi một ứng dụng cố gắng gọi JNI AttachCurrentThread() khi kích thước của pthread quá nhỏ:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Thay đổi về mô hình đối tượng

Các lớp con Dalvik được phép ghi đè các phương thức riêng tư của gói. ART sẽ đưa ra cảnh báo trong những trường hợp sau:

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

Nếu bạn có ý định ghi đè phương thức của một lớp trong một gói khác, hãy khai báo dưới dạng public hoặc protected.

Object hiện có các trường riêng tư. Ứng dụng suy ngẫm về các trường trong hệ phân cấp lớp của mình nên cẩn thận để không cố gắng xem xét trường Object. Ví dụ: nếu bạn đang lặp lại một lớp như một phần của khung chuyển đổi tuần tự, hãy dừng khi

Class.getSuperclass() == java.lang.Object.class

thay vì tiếp tục cho đến khi phương thức này trả về null.

Proxy InvocationHandler.invoke() hiện nhận được null nếu không có đối số thay vì một mảng trống. Hành vi này đã được ghi nhận trước đây nhưng không được xử lý chính xác trong Dalvik. Các phiên bản trước của Mockito gặp khó khăn điều này, vì vậy, hãy sử dụng phiên bản Mockito đã cập nhật khi thử nghiệm bằng ART.

Khắc phục sự cố biên dịch AOT

Hoạt động biên dịch Java trước khi sử dụng (AOT) của ART phải hoạt động được với mọi Java tiêu chuẩn . Quá trình biên dịch do ART thực hiện Công cụ dex2oat; nếu bạn gặp phải bất kỳ vấn đề nào liên quan đến dex2oat khi cài đặt, hãy cho chúng tôi biết (xem Báo cáo sự cố) để chúng tôi có thể khắc phục chúng nhanh nhất nhất có thể. Một số vấn đề cần lưu ý:

  • ART thực hiện quy trình xác minh mã byte chặt chẽ hơn tại thời điểm cài đặt so với Dalvik. Mã do các công cụ xây dựng của Android tạo ra sẽ không có vấn đề gì. Tuy nhiên, một số các công cụ hậu xử lý (đặc biệt là các công cụ thực hiện làm rối mã nguồn) có thể các tệp không hợp lệ được Dalvik dung lượng nhưng bị ART từ chối. Chúng tôi đã làm việc với các nhà cung cấp công cụ để tìm và khắc phục các vấn đề như vậy. Trong nhiều trường hợp, việc các phiên bản công cụ mới nhất cũng như việc tạo lại tệp DEX vấn đề.
  • Một số vấn đề điển hình được trình xác minh ART gắn cờ bao gồm:
    • luồng kiểm soát không hợp lệ
    • không cân bằng monitorenter/monitorexit
    • Kích thước danh sách loại thông số có độ dài 0
  • Một số ứng dụng có phần phụ thuộc trên tệp .odex đã cài đặt ở định dạng /system/framework, /data/dalvik-cache hoặc trong thư mục đầu ra được tối ưu hoá của DexClassLoader. Các các tệp này hiện là tệp ELF chứ không phải là dạng tệp DEX mở rộng. Trong khi ART cố gắng để tuân thủ cùng các quy tắc đặt tên và khoá như Dalvik, các ứng dụng không được phụ thuộc về định dạng tệp; định dạng có thể thay đổi mà không cần thông báo.

    Lưu ý: Trong Android 8.0 (API cấp 26) và cao hơn, thư mục đầu ra được tối ưu hoá DexClassLoader không được dùng nữa. Để biết thêm thông tin, hãy xem tài liệu dành cho DexClassLoader() hàm khởi tạo.

Sự cố về báo cáo

Nếu bạn gặp những vấn đề không liên quan đến vấn đề về JNI của ứng dụng, hãy báo cáo chúng thông qua Công cụ theo dõi lỗi của dự án nguồn mở Android tại https://code.google.com/p/android/issues/list. Thêm "adb bugreport" và một đường liên kết đến ứng dụng trong Google Cửa hàng Play nếu có. Hoặc nếu có thể, hãy đính kèm một tệp APK có khả năng tái tạo vấn đề. Xin lưu ý rằng các vấn đề (bao gồm cả tệp đính kèm) sẽ được trình bày công khai hiển thị.