Khái niệm

Trước khi bắt đầu

Hướng dẫn này giả định rằng bạn đã quen thuộc với các khái niệm vốn có trong chương trình gốc và trong quá trình phát triển Android.

Giới thiệu

Phần này đưa ra nội dung giải thích tổng thể về cách thức hoạt động của NDK. Android NDK là một bộ công cụ cho phép bạn nhúng C hoặc C++ (“mã gốc”) vào ứng dụng Android. Khả năng sử dụng mã gốc trong ứng dụng Android có thể đặc biệt hữu ích cho các nhà phát triển muốn thực hiện một hoặc nhiều việc sau:

  • Chuyển ứng dụng giữa các nền tảng.
  • Sử dụng lại thư viện hiện có hoặc cung cấp thư viện riêng cho phép sử dụng lại.
  • Tăng hiệu suất trong một số trường hợp nhất định, đặc biệt các trường hợp nặng về điện toán như trò chơi.

Cách thức hoạt động

Phần này giới thiệu các thành phần chính dùng để xây dựng ứng dụng gốc dành cho Android, đồng thời mô tả quy trình xây dựng và đóng gói ứng dụng.

Các thành phần chính

Khi xây dựng ứng dụng, bạn nắm rõ về các thành phần sau:

  • Thư viện chia sẻ gốc: NDK xây dựng các thư viện này hoặc các tệp .so qua mã nguồn C/C++ của bạn.

  • Thư viện tĩnh gốc: NDK cũng có thể xây dựng các thư viện tĩnh hoặc các tệp .a mà bạn có thể liên kết với các thư viện khác.

  • Giao diện gốc của Java (Java Native Interface – JNI): JNI là giao diện mà qua đó các thành phần Java và C++ giao tiếp với nhau. Hướng dẫn này giả định rằng bạn đã có kiến thức về JNI. Bạn có thể tham khảo nội dung Thông số kỹ thuật của giao diện gốc Java để biết thêm thông tin.

  • Giao diện nhị phân của ứng dụng (Application Binary Interface – ABI): ABI xác định chính xác cách mã máy của ứng dụng tương tác với hệ thống khi chạy. NDK xây dựng các tệp .so dựa trên những định nghĩa này. Mỗi ABI lại có kiến trúc riêng: NDK cung cấp tính năng hỗ trợ ABI cho ARM 32-bit, AArch64, x86 và x86-64. Để biết thêm thông tin, hãy xem nội dung ABI Android.

  • Tệp kê khai: Nếu đang viết một ứng dụng không có thành phần Java, bạn phải khai báo lớp NativeActivity trong tệp kê khai. Hãy xem nội dung Sử dụng giao diện native_activity.h để biết thêm thông tin về cách thực hiện việc này.

Quy trình

Sau đây là quy trình chung về việc phát triển một ứng dụng gốc dành cho Android:

  1. Thiết kế ứng dụng của bạn, quyết định phần nào cần triển khai trong Java và phần nào cần triển khai dưới dạng mã gốc.

  2. Tạo một Dự án ứng dụng Android như bạn thực hiện với mọi dự án Android khác.

  3. Nếu bạn đang viết một ứng dụng chỉ dùng mã gốc, hãy khai báo lớp nativeActivity trong AndroidManifest.xml. Để biết thêm thông tin, hãy xem nội dung Hoạt động gốc và ứng dụng gốc.

  4. Tạo một tệp Android.mk mô tả thư viện gốc, bao gồm cả tên, các cờ, thư viện liên kết và tệp nguồn được biên dịch trong thư mục "JNI".

  5. Nếu muốn, bạn có thể tạo một tệp Application.mk định cấu hình ABI mục tiêu, chuỗi công cụ, chế độ phát hành/gỡ lỗi và STL. Đối với mọi giá trị bạn không chỉ định trong số này, giá trị mặc định sau đây sẽ được sử dụng, lần lượt như sau:

    • ABI: tất cả ABI không còn được dùng nữa
    • Chế độ: Phát hành
    • STL: hệ thống
  6. Đặt nguồn mã gốc vào thư mục jni của dự án.

  7. Sử dụng ndk-build để biên dịch thư viện gốc (.so, .a).

  8. Xây dựng thành phần Java, tạo tệp thực thi .dex.

  9. Đóng gói mọi thứ vào một tệp APK, chứa .so, .dex và các tệp khác cần thiết để ứng dụng của bạn chạy được.

Hoạt động gốc và ứng dụng gốc

SDK Android cung cấp một lớp trợ giúp (NativeActivity) cho phép bạn viết một hoạt động hoàn toàn bằng mã gốc. NativeActivity xử lý thông tin giao tiếp giữa khung Android và mã gốc, vì vậy bạn không phải tạo lớp con hoặc gọi phương thức. Bạn chỉ cần khai báo rằng ứng dụng của bạn là ứng dụng gốc trong tệp AndroidManifest.xml rồi bắt đầu tạo ứng dụng gốc.

Ứng dụng Android sử dụng NativeActivity vẫn sẽ chạy trong máy ảo của riêng ứng dụng đó và chạy trong hộp cát qua các ứng dụng khác. Do đó, bạn vẫn có thể truy cập API khung Android thông qua JNI. Trong một số trường hợp (chẳng hạn như đối với các cảm biến, sự kiện đầu vào và nội dung), NDK cung cấp giao diện gốc mà bạn có thể sử dụng thay vì phải gọi trên JNI. Để biết thêm thông tin về tính năng hỗ trợ đó, hãy xem nội dung API gốc.

Bất kể có đang phát triển hoạt động gốc hay không, bạn nên tạo dự án bằng các công cụ xây dựng Android truyền thống. Phương pháp này sẽ giúp đảm bảo việc tạo và đóng gói ứng dụng Android với cấu trúc chính xác.

Android NDK cung cấp cho bạn hai lựa chọn để triển khai hoạt động gốc:

  • Tiêu đề native_activity.h khai báo phiên bản gốc của lớp nativeActivity. Tiêu đề này chứa giao diện gọi lại và các cấu trúc dữ liệu cần thiết để tạo hoạt động gốc. Vì luồng chính của ứng dụng của bạn xử lý các lệnh gọi lại, nên các phương thức triển khai lệnh gọi lại của bạn đều không được chặn. Nếu các phương thức triển khai này chặn, bạn có thể gặp lỗi ANR (Ứng dụng không phản hồi) vì luồng chính không phản hồi cho đến khi lệnh gọi lại trở về.
  • Tệp android_native_app_glue.h khai báo một thư viện trình trợ giúp tĩnh được xây dựng trên giao diện native_activity.h. Thư viện này tạo ra một luồng khác để xử lý các lệnh gọi lại hoặc sự kiện đầu vào trong vòng lặp sự kiện. Việc di chuyển các sự kiện này sang một luồng riêng biệt sẽ ngăn mọi lệnh gọi lại chặn luồng chính của bạn.

Nguồn <ndk_root>/sources/android/native_app_glue/android_native_app_glue.c cũng có sẵn, cho phép bạn sửa đổi phương thức triển khai.

Để biết thêm thông tin về cách sử dụng thư viện tĩnh này, hãy kiểm tra mẫu ứng dụng hoạt động gốc và tài liệu tương ứng. Bạn cũng có thể đọc thêm trong các ghi chú ở tệp <ndk_root>/sources/android/native_app_glue/android_native_app_glue.h.

Sử dụng giao diện native_activity.h

Cách triển khai hoạt động gốc bằng giao diện native_activity.h:

  1. Tạo thư mục jni/ trong thư mục gốc của dự án. Thư mục này lưu trữ tất cả mã gốc của bạn.

  2. Khai báo hoạt động gốc trong tệp AndroidManifest.xml.

    Vì ứng dụng của bạn không có mã Java, hãy đặt android:hasCode thành false.

    <application android:label="@string/app_name" android:hasCode="false">
    

    Bạn phải thiết lập thuộc tính android:name của thẻ hoạt động thành nativeActivity.

    <activity android:name="android.app.NativeActivity"
              android:label="@string/app_name">
    

    Thuộc tính android:value của thẻ meta-data sẽ chỉ định tên của thư viện chia sẻ chứa điểm truy cập vào ứng dụng (chẳng hạn như C/C++main), bỏ qua tiền tố lib và hậu tố .so của tên thư viện.

    <manifest>
      <application>
        <activity>
          <meta-data android:name="android.app.lib_name"
                     android:value="native-activity" />
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
      </application>
    </manifest>
    
  3. Tạo một tệp dành cho hoạt động gốc của bạn roofi triển khai hàm có tên trong biến AnativeActivity_onCreate. Ứng dụng gọi hàm này khi hoạt động gốc bắt đầu. Hàm này (tương tự như hàm main trong C/C++) nhận một con trỏ đến một cấu trúc ANativeActivity có chứa các con trỏ hàm đến nhiều phương thức triển khai lệnh gọi lại mà bạn cần viết. Thiết lập các con trỏ hàm gọi lại hiện hành trong ANativeActivity->callbacks thành phương thức triển khai của các lệnh gọi lại.

  4. Thiết lập trường ANativeActivity->instance thành địa chỉ của mọi thực thể của dữ liệu cụ thể mà bạn muốn sử dụng.

  5. Triển khai mọi việc khác mà bạn muốn hoạt động của mình thực hiện khi khởi động.

  6. Triển khai các lệnh gọi lại còn lại mà bạn đã thiết lập trong ANativeActivity->callbacks. Để biết thêm thông tin về thời điểm gọi lệnh gọi lại, hãy xem nội dung Quản lý vòng đời hoạt động.

  7. Phát triển phần còn lại của ứng dụng.

  8. Tạo Android.mk file trong thư mục jni/ của dự án để mô tả cho hệ thống xây dựng về mô-đun gốc của bạn. Để biết thêm thông tin, hãy xem nội dung Android.mk.

  9. Sau khi bạn có tệp Android.mk, hãy biên dịch mã gốc bằng lệnh ndk-build.

    cd <path>/<to>/<project>
    $NDK/ndk-build
    
  10. Tạo bản dựng và cài đặt dự án Android của bạn như bình thường. Nếu mã gốc của bạn nằm trong thư mục jni/, thì tập lệnh bản dựng sẽ tự động đóng gói (các) tệp .so được tạo qua mã gốc đó vào tệp APK.

Mã nguồn mẫu khác

Để tải các mẫu NDK xuống, hãy xem nội dung Mẫu NDK.