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 một số tính năng giúp cải thiện hiệu suất cũng như độ mượt mà của nền tảng và ứng dụng Android. Bạn có thể tìm thêm thông tin về các tính năng mới của ART trong bài viết Giới thiệu về ART.

Tuy nhiên, một số kỹ thuật hoạt động trên Dalvik sẽ không hoạt động trên ART. Tài liệu này cho bạn biết những điều cần lưu ý khi di chuyển một ứng dụng hiện có để tương thích với ART. Hầu hết ứng dụng sẽ hoạt động khi chạy bằng ART.

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

Trong Dalvik, các ứng dụng thường thấy cần gọi System.gc() một cách rõ ràng để nhắc thu thập rác (GC). Điều này sẽ ít cần thiết hơn nhiều với ART, đặc biệt là khi bạn gọi thu thập rác để ngăn xảy ra loại GC_FOR_ALLOC hoặc để giảm sự phân mảnh. Bạn có thể xác minh môi trường 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 ART đang được sử dụng, thì giá trị của thuộc tính từ "2.0.0" trở lên.

ART sử dụng trình thu thập Sao chép đồng thời (CC) đồng thời thu gọ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 việc thu gọn GC (chẳng hạn như lưu con trỏ vào dữ liệu thực thể đối tượng). Điều này đặc biệt quan trọng đối với các ứng dụng sử 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 chặn 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. Bạn nên sử dụng chế độ CheckJNI để phát hiện các vấn đề thường gặp. Nếu ứng dụng của bạn sử dụng mã C/C++, bạn nên xem bài viết sau:

Gỡ lỗi JNI Android bằng CheckJNI

Kiểm tra mã JNI để phát hiện các vấn đề về thu gom 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 thu gọn GC. Chúng tôi đã cải thiện CheckJNI để xác định một số vấn đề tiềm ẩn (như mô tả trong bài viết Các thay đổi về tham chiếu cục bộ của JNI trong ICS).

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

  • Nếu 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 Release...ArrayElements() thích hợp sau khi hoàn tất để đảm bảo những thay đổi đã thực hiện được sao chép chính xác trở lại đối tượng mảng cơ bản.
  • Khi giải phóng các phần tử mảng bộ nhớ, bạn phải sử dụng chế độ thích hợp, 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. Chế độ này sẽ giải phóng bộ nhớ mà không cần sao chép các thay đổi trở lại đối tượng mảng cơ bản.
    • Nếu bạn đã thực hiện các thay đổi đối với mảng và không cần tham chiếu nữa, 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 bộ nhớ).
    • Nếu bạn đã thực hiện các thay đổi đối với mảng mà bạn muốn xác nhận và muốn giữ lại bản sao của mảng đó, hãy sử dụng JNI_COMMIT (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 con trỏ do Get...ArrayElements() trả về ban đầu. Ví dụ: việc tăng con trỏ ban đầu (để quét qua các phần tử mảng được trả về) là không an toàn, sau đó truyề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ớ không được giải phóng, dẫn đến lỗi bộ nhớ.

Xử lý lỗi

JNI của ART gửi lỗi trong một số trường hợp Dalvik không không. (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ể do phương thức này đã bị một công cụ như ProGuard xoá), thì ART hiện sẽ 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 đượ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, các hàm JNI GetFieldID()GetStaticFieldID() hiện đã gửi NoSuchFieldError đúng cách thay vì chỉ trả về giá trị rỗng. Tương tự, GetMethodID()GetStaticMethodID() hiện đã 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 đến các phương thức gọi Java của mã gốc. Điều này đặ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 yêu cầu người dùng của các phương thức CallNonvirtual...Method() JNI (chẳng hạn như CallNonvirtualVoidMethod()) sử dụng lớp khai báo của phương thức chứ không phải lớp con, theo yêu cầu của bản đặc tả về JNI.

Ngăn chặn 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 kích thước ngăn xếp Java mặc định là 32 KB và kích thước ngăn xếp gốc mặc định là 1 MB. ART có một ngăn xếp hợp nhất để có vị trí tốt hơn. Thông thường, kích thước ngăn xếp ART Thread phải gần bằng kích thước của Dalvik. Tuy nhiên, nếu đã thiết lập rõ ràng kích thước ngăn xếp, bạn có thể cần phải truy cập lại các giá trị đó đối với các ứng dụng chạy trong ART.

  • Trong Java, hãy xem lại các lệnh gọi đến hàm khởi tạo Thread chỉ định kích thước ngăn xếp rõ ràng. 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 việc 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 pthread quá nhỏ:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Các thay đổi về mô hình đối tượng

Các lớp con Dalvik được cho phép không chính xác để ghi đè các phương thức riêng tư của gói. ART sẽ đưa ra cảnh báo trong các 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 định ghi đè phương thức của một lớp trong một gói khác, hãy khai báo phương thức đó là public hoặc protected.

Object hiện có các trường riêng tư. Các ứng dụng phản ánh các trường trong hệ phân cấp lớp nên cẩn thận để không cố gắng xem các trường của Object. Ví dụ: nếu bạn đang lặp lại một hệ phân cấp 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 null nếu không có đối số thay vì một mảng trống. Hành vi này đã được ghi lại trước đây nhưng không được xử lý chính xác trong Dalvik. Các phiên bản Mockito cũ gặp khó khăn về điều này, vì vậy, hãy sử dụng phiên bản Mockito đã cập nhật khi kiểm thử bằng ART.

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

Tính năng biên dịch Java trước thời gian (AOT) của ART sẽ hoạt động trên tất cả mã Java tiêu chuẩn. Quá trình biên dịch được thực hiện bằng công cụ dex2oat của ART. Nếu bạn gặp bất kỳ vấn đề nào liên quan đến dex2oat tại thời điểm cài đặt, hãy cho chúng tôi biết (xem phần Báo cáo vấn đề) để chúng tôi có thể khắc phục sự cố nhanh 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ẽ được chấp nhận. Tuy nhiên, một số công cụ xử lý hậu kỳ (đặc biệt là các công cụ thực hiện việc làm rối mã nguồn) có thể tạo ra các tệp không hợp lệ mà Dalvik cho phép nhưng bị ART từ chối. Chúng tôi đã và đang làm việc với các nhà cung cấp công cụ để tìm và khắc phục những vấn đề như vậy. Trong nhiều trường hợp, việc lấy các phiên bản mới nhất của các công cụ và tạo lại tệp DEX có thể khắc phục những vấn đề này.
  • Sau đây là một số vấn đề thường gặp mà trình xác minh ART gắn cờ:
    • luồng điều khiển không hợp lệ
    • không cân bằng monitorenter/monitorexit
    • Quy mô danh sách loại thông số có độ dài 0
  • Một số ứng dụng có các phần phụ thuộc trên định dạng tệp .odex đã cài đặt trong /system/framework, /data/dalvik-cache hoặc trong thư mục đầu ra được tối ưu hoá của DexClassLoader. Các tệp này hiện là tệp ELF và không phải là dạng tệp DEX mở rộng. Mặc dù ART cố gắng tuân theo các quy tắc đặt tên và khoá giống như Dalvik, nhưng các ứng dụng không được phụ thuộc vào định dạng tệp; định dạng có thể thay đổi mà không cần thông báo trước.

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

Báo cáo sự cố

Nếu bạn gặp phải bất kỳ vấn đề nào không phải do vấn đề về JNI ứng dụng, hãy báo cáo các vấn đề đó qua Trình theo dõi lỗi của dự án nguồn mở Android tại https://code.google.com/p/android/issue/list. Đưa vào "adb bugreport" và một đường liên kết đến ứng dụng trong Cửa hàng Google Play nếu có. Nếu có thể, hãy đính kèm một tệp APK 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ẽ hiển thị công khai.