Giới thiệu về Android NDK. Cài đặt bằng Android Studio

Android NDK là một công cụ dành cho SDK Android cho phép Các nhà phát triển ứng dụng Android xây dựng hiệu suất của các phần quan trọng trong ứng dụng của họ bằng mã gốc. Nó được dự định chỉ được sử dụng cùng với SDK Android, vì vậy nếu bạn chưa cài đặt android mới nhất SDK, vui lòng thực hiện việc này trước khi tải xuống NDP. Ngoài ra, bạn nên đọc kỹ nó là gì để hiểu NDT cung cấp những gì và liệu nó có hữu ích cho bạn hay không.

Chọn tải gói phù hợp với máy tính của bạn.

Nền tảng Bưu kiện Kích cỡ Tổng kiểm tra MD5
các cửa sổandroid-ndk-r4b-windows.zip45792835 byte
Mac OS X (intel)android-ndk-r4b-darwin-x86.zip50586041 byte
Linux 32/64-bit (x86)android-ndk-r4b-linux-x86.zip49464776 byte

Thay đổi
Các phần sau đây cung cấp thông tin và ghi chú về các lần phát hành NDC kế tiếp, được xác định bằng số sửa đổi.
Android NDK, Bản sửa đổi 4b Android NDK, Phiên bản 4, b (tháng 6 năm 2010):
Bao gồm các bản sửa lỗi cho một số vấn đề trong NDK để tạo và gỡ lỗi tập lệnh - nếu bạn đang sử dụng R4 NDK, chúng tôi khuyên bạn nên tải xuống R4b NDK. Để có được thông tin chi tiết những thay đổi trong bản phát hành này, hãy đọc tài liệu CHANGES.txt có trong gói NDK đã tải xuống.
Hướng dẫn chung:
Cung cấp một hệ thống xây dựng đơn giản hóa bằng cách sử dụng trình tạo lệnh NDK mới.
Đã thêm hỗ trợ để dễ dàng gỡ lỗi mã máy sản xuất thiết bị được tạo thông qua đội mới NDK-GDB.
Thêm ABI mới dành riêng cho Android cho kiến ​​trúc bộ xử lý dựa trên ARM, armeabi-v7a. Các ABI mới mở rộng các ABI armeabi hiện có có trong bộ hướng dẫn bộ xử lý mở rộng này:
Hướng dẫn phần cứng Thumb-2 Hướng dẫn VFP FPU (VFPv3-D16)
Hỗ trợ bổ sung cho phần mở rộng ARM SIMD (neon) GCC và VFPv3-D32 được nhúng. Hỗ trợ các thiết bị như Verizon Droid của Motorola, Nexus của Google và các thiết bị khác.
Thêm thư viện tĩnh cpufeatures mới (có nguồn) cho phép ứng dụng của bạn khám phá bộ xử lý của thiết bị chủ khi chạy. Đặc biệt, các ứng dụng có thể kiểm tra hỗ trợ ARMv7, cũng như hỗ trợ VFPv3-D32 và NEON, sau đó cung cấp các đường dẫn mã riêng lẻ nếu cần.
Thêm một ứng dụng mẫu, hi-neon, minh họa cách sử dụng thư viện cpufeatures để kiểm tra bộ xử lý và sau đó cung cấp mã được tối ưu hóa bằng cách sử dụng nội tại NEON nếu được bộ xử lý hỗ trợ.
Cho phép bạn tạo mã máy cho một hoặc cả hai bộ hướng dẫn được NDC hỗ trợ. Ví dụ: bạn có thể xây dựng cho kiến ​​​​trúc ARMv5 và ARMv7, đồng thời mọi thứ sẽ được giữ nguyên.

Ứng dụng APK.
Để đảm bảo ứng dụng chỉ khả dụng cho người dùng nếu thiết bị của họ có khả năng chạy chúng, Android Market có các bộ lọc ứng dụng dựa trên thông tin, một bộ hướng dẫn đi kèm với ứng dụng - bạn cần phải thực hiện hành động để thực hiện lọc. Ngoài ra, trong quá trình cài đặt, hệ thống Android còn tự kiểm tra ứng dụng và chỉ cho phép cài đặt tiếp tục khi ứng dụng cung cấp thư viện được biên dịch cho kiến ​​trúc bộ xử lý của thiết bị.
Đã thêm hỗ trợ cho Android 2.2, bao gồm các API ổn định mới để truy cập bộ đệm pixel đối tượng raster từ mã máy.
Android NDK, Phiên bản 3 Android NDK, Phiên bản 3 (tháng 3 năm 2010)
Hướng dẫn chung:
Thêm hỗ trợ thư viện gốc OpenGL ES 2.0.
Thêm một ứng dụng mẫu, hi-gl2, minh họa cách sử dụng OpenGL ES 2.0 và các trình đổ bóng phân đoạn trên cùng.
Chuỗi công cụ thực thi đã được cập nhật cho phiên bản này với GCC 4.4.0, sẽ tạo ra mã máy nhỏ gọn và hiệu quả hơn một chút so với phiên bản trước (4.2.1). NDC cũng vẫn cung cấp các tệp nhị phân 4.2.1 có thể được sử dụng tùy ý để tạo mã máy.
Android NDK, Phiên bản 2 Android NDK, Phiên bản 2 (tháng 9 năm 2009)
Ban đầu được phát hành dưới dạng "Android 1.6 NDK Release 1".
Hướng dẫn chung:
Thêm hỗ trợ thư viện gốc OpenGL ES 1.1.
Thêm một ứng dụng mẫu, San Angeles, giúp tạo đồ họa 3D thông qua API OpenGL ES gốc, đồng thời quản lý vòng đời của một hoạt động bằng đối tượng GLSurfaceView.
Android NDK, Bản sửa đổi 1 Android NDK, Bản sửa đổi 1 (tháng 6 năm 2009)
Ban đầu được phát hành dưới dạng "Android 1.5 NDK Release 1".
Hướng dẫn chung:
Bao gồm hỗ trợ trình biên dịch (CCS) cho các hướng dẫn ARMv5TE, bao gồm cả hướng dẫn Thumb.
Bao gồm các hệ thống tiêu đề cho các API gốc, tài liệu và ứng dụng mẫu ổn định.

Android NDK (Bộ công cụ phát triển bản địa) là bộ công cụ rất phổ biến được sử dụng để phát triển các ứng dụng cho thiêt bị di động. Nhiều ứng dụng trong kho ứng dụng Android Market sử dụng các thành phần được phát triển bằng các ngôn ngữ lập trình khác ngoài Java để đạt được hiệu suất tối đa. Dựa trên điều này, NDK là bộ công cụ giúp các nhà phát triển tạo ra các thành phần cho ứng dụng của họ bằng cách sử dụng các ngôn ngữ lập trình được biên dịch cho nhiều mục đích khác nhau, từ đạt được hiệu suất tối ưu đến đơn giản hóa mã được sử dụng.

Tại sao và mã nhị phân được sử dụng như thế nào?

Chúng ta đều biết rằng quá trình phát triển ứng dụng Android có liên quan chặt chẽ đến việc sử dụng ngôn ngữ lập trình Java và việc sử dụng của ngôn ngữ này lập trình giúp cuộc sống của các nhà phát triển trở nên dễ dàng hơn nhiều vì họ có thể sử dụng mô hình hướng đối tượng tinh tế của Java. Các ứng dụng hoặc thuật toán được triển khai bằng Java được chuyển đổi thành mã byte đặc biệt chạy theo cùng một cách trên tất cả các nền tảng được hỗ trợ. Đồng thời, máy ảo Java hoặc JVM (Máy ảo Java), chịu trách nhiệm biên dịch JIT và thực thi mã byte Java, có sẵn cho hầu hết tất cả các nền tảng hiện có, từ máy tính lớn đến điện thoại di động.

Tuy nhiên, trong trường hợp hệ thống Android, vốn chủ yếu được sử dụng trên điện thoại thông minh và máy tính bảng, yếu tố chính là đạt được hiệu suất ứng dụng tối đa trên thiết bị đã sử dụng phần cứng. Mã nguồn Java, như đã đề cập ở trên, trước tiên được chuyển đổi thành mã byte. Đây chính xác là mã byte thực thi với những khác biệt nhỏ trên các nền tảng có sẵn máy ảo Java. Cuối cùng, toàn bộ ứng dụng chạy trong một máy ảo trên thiết bị Android.

Khi nói đến phát triển ứng dụng Android, yếu tố trên là một nhược điểm nhỏ. Nhưng lập trình bằng Java có thể khá khó khăn do độ phức tạp liên tục của mã và khó hiểu nó. Hơn nữa, việc sử dụng bytecode đa nền tảng và máy ảo gây ra chi phí đáng kể tài nguyên máy tính thiết bị.

Một yếu tố quan trọng khác cần được chú ý là mã đa nền tảng. Nếu chúng ta cần viết một chương trình cho nhiều nền tảng phần cứng, chúng ta có thể sẽ phải viết lại phần lớn bộ điều khiển và mã hiển thị cho từng nền tảng, đây không phải là một giải pháp thông minh. Nhưng tất cả mã liên quan đến bộ điều khiển phải được chuyển sang C và C++, vì hầu hết tất cả đều phải được chuyển sang C và C++. nền tảng di động hỗ trợ họ; Do đó, nếu chúng ta có thể triển khai logic trong các thư viện trong C và C++ và sau đó sử dụng nó trên nhiều nền tảng, chúng ta sẽ có thể giảm thiểu mức phạt về hiệu suất của ứng dụng. Trong những trường hợp như vậy, chúng tôi sẽ sử dụng mã C và C++ cùng với mã Java thông thường hoặc "mã đa nền tảng".

Khi sử dụng ngôn ngữ lập trình được biên dịch, mã nguồn được biên dịch trực tiếp thành mã máy để bộ xử lý trung tâm, chứ không phải là một biểu diễn trung gian như trong ngôn ngữ Java. Bằng cách này, nhà phát triển ứng dụng có thể tạo ứng dụng có hiệu suất tối ưu cho các thiết bị Android khác nhau. Các đoạn mã được biên dịch có thể được cấu trúc trong một thư viện dùng chung duy nhất, các hàm từ đó có thể được gọi từ mã Java. Một thư viện chia sẻ riêng biệt phải được tạo cho từng kiến ​​trúc CPU được hỗ trợ. Hầu hết nó mã nguồn tuy nhiên, nó có thể không thay đổi. Các thư viện dùng chung đã biên dịch phải được thêm vào tệp .apk của ứng dụng của bạn. Với tất cả những gì đã nói, mô hình cơ bản của ứng dụng Android sẽ không thay đổi.

Sử dụng Android NDK

Android NDK là bộ công cụ cho phép bạn triển khai các phần của ứng dụng Android bằng các ngôn ngữ lập trình được biên dịch như C và C++, đồng thời chứa các thư viện để quản lý hoạt động và truy cập các thành phần vật lý của thiết bị, chẳng hạn như các cảm biến khác nhau và màn hình.

Android NDK được tích hợp với các công cụ bộ phát triển phần mềm (Android SDK) cũng như môi trường phát triển tích hợp Studio Android hoặc môi trường phát triển Eclipse ADT kế thừa. Tuy nhiên, NDK không thể được sử dụng một mình.

Cách hoạt động của Android NDK

Trọng tâm của NDK của Android là tập lệnh ndk-build, chịu trách nhiệm tự động duyệt qua các tệp dự án Android (quá trình phát triển từng ứng dụng Android mới sử dụng môi trường phát triển tích hợp như Android Studio hoặc Eclipse bắt đầu bằng việc tạo các tệp dự án mới) và thu thập thông tin về thành phần nào cần được biên dịch. Kịch bản này cũng chịu trách nhiệm tạo ra tập tin nhị phân và sao chép các tệp nhị phân này vào thư mục tệp dự án ứng dụng.

Chúng ta có thể sử dụng từ khóa gốc để cho trình biên dịch biết rằng một đoạn nhất định được triển khai trong mã được biên dịch. Ví dụ:

Số int gốc công khai(int x, int y);

Ngoài ra, trong quá trình xây dựng dự án, các thư viện dùng chung (Thư viện dùng chung gốc, có phần mở rộng .so) và thư viện tĩnh(Thư viện chia sẻ gốc, có phần mở rộng .a), có thể liên kết với các thư viện khác. Giao diện nhị phân ứng dụng (ABI) sử dụng các thư viện dùng chung có phần mở rộng .so để thực thi mã máy trên hệ thống trong khi ứng dụng đang chạy.

Tất cả mã được biên dịch đều được thực thi thông qua một giao diện được gọi là Giao diện gốc Java (JNI), cho phép các thành phần Java và C/C++ được liên kết với nhau.

Để xây dựng dự án bằng tập lệnh ndk-build, chúng ta sẽ phải tạo hai tệp: Android.mk và Application.mk. Cả hai tệp này phải nằm trong thư mục JNI. Tệp Android.mk mô tả mô-đun và tên của nó, cờ xây dựng, thư viện đã sử dụng, tệp mã nguồn phải được biên dịch và tệp Application.mk mô tả các mô-đun nhị phân cần thiết để ứng dụng hoạt động.

Cài đặt và sử dụng Android NDK trên Ubuntu

NDK của Android có định dạng lưu trữ tự giải nén. Vì lý do này, chúng ta sẽ chỉ phải thiết lập bit thực thi và giải nén nó:

$ chmod +x android-ndk-r10c-linux-x86_64.bin $ ./android-ndk-r10c-linux-x86_64.bin

Kết quả là các thành phần NDK sẽ được lưu trong thư mục làm việc hiện tại.

Giải nén thủ công

Vì tệp .bin không gì khác hơn là một kho lưu trữ 7-Zip tự giải nén, chúng ta có thể trích xuất nội dung của nó theo cách thủ công bằng lệnh sau:

$ 7za x -o/path/to/target/directories/android-ndk-r10c-linux-x86_64.bin

Gói thành phần trình lưu trữ 7-Zip có sẵn từ kho lưu trữ chính thức của Ubuntu và có thể được cài đặt, chẳng hạn bằng cách sử dụng lệnh apt-get:

$ sudo apt-get cài đặt p7zip-đầy đủ

Cài đặt bằng Android Studio

Chúng tôi có thể cài đặt Android NDK bằng thành phần Trình quản lý SDK trực tiếp từ Android Studio.

Để thực hiện việc này, sau khi mở dự án, bạn nên vào menu chính của cửa sổ Tools\u003e Android\u003e SDK Manager. Sau này, bạn cần đánh dấu vào các ô bên cạnh tên của các thành phần LLDB, CMake và NDK. Tiếp theo, bạn chỉ cần áp dụng các thay đổi bằng nút thích hợp.

Tạo hoặc nhập dự án có thành phần nhị phân

Sau đó cài đặt Android Studio, chúng tôi có thể tạo một dự án mới với sự hỗ trợ cho các ngôn ngữ lập trình C/C++. Tuy nhiên, nếu chúng ta cần thêm hoặc nhập mã hiện có bằng các ngôn ngữ này vào dự án Android Studio, chúng ta sẽ buộc phải làm theo các bước bên dưới.

Bước đầu tiên là tạo các tệp mã nguồn mới bằng các ngôn ngữ lập trình đã đề cập và thêm chúng vào dự án được mở trong Android Studio. Chúng ta có thể bỏ qua bước này nếu dự án đã có các tệp tương tự hoặc chúng ta cần nhập thư viện được biên dịch sẵn vào đó.

Tập lệnh xây dựng CMake cho phép bạn chỉ dẫn cho hệ thống xây dựng cùng tên cách biên dịch các tệp mã nguồn và xây dựng thư viện nhị phân kết quả. Tệp này cũng được yêu cầu để nhập và liên kết các thư viện NDK hiện có hoặc đi kèm với thư viện của chúng tôi. Chúng ta cũng có thể bỏ qua mà không có bất kỳ hậu quả nào bước này nếu thư viện nhị phân hiện tại của chúng tôi đã đi kèm với tệp tập lệnh xây dựng CMakeLists.txt hoặc nếu nó sử dụng thành phần ndk-build và đi kèm với tệp tập lệnh xây dựng Android.mk.

Tiếp theo, chúng ta cần cho Gradle biết về sự tồn tại của thư viện nhị phân bằng cách chỉ định đường dẫn đến tệp tập lệnh xây dựng CMake hoặc ndk-build. Gradle sử dụng tập lệnh xây dựng được chỉ định để nhập mã nguồn vào dự án Android Studio và đóng gói thư viện nhị phân kết quả (tệp có phần mở rộng .so) vào tệp gói có định dạng APK.

Lưu ý quan trọng: Nếu dự án sử dụng công cụ ndkCompile cũ, chúng ta sẽ phải mở tệp build.poperties và xóa dòng mã sau khỏi nó trước khi định cấu hình Gradle để sử dụng CMake hoặc ndk-build:

Android.useDeprecatedNdk = true

Bây giờ chúng ta có thể xây dựng và chạy ứng dụng của mình bằng cách nhấp vào nút Run. Gradle sẽ coi quy trình CMake hoặc ndk-build là phần phụ thuộc cần xây dựng, xây dựng thư viện nhị phân và đóng gói thành tệp APK.

Sau khi chạy ứng dụng trên thiết bị hoặc trong trình mô phỏng, chúng tôi có thể sử dụng các tính năng của nhiều môi trường phát triển tích hợp khác nhau, chẳng hạn như Android Studio để gỡ lỗi.

Tất cả điều này cho thấy tầm quan trọng của NDK của Android đối với các nhà phát triển ứng dụng trên nền tảng Android. Ví dụ như bộ này thành phần phần mềm cho phép người tạo công cụ trò chơi tối ưu hóa tốt hơn các phiên bản Android cho sản phẩm của họ, mang lại hiệu ứng đồ họa ấn tượng hơn trong khi sử dụng ít tài nguyên hệ thống hơn.

Quá trình tạo một ứng dụng đơn giản dựa trên Android NDK không gặp bất kỳ khó khăn nào. Tuy nhiên, mọi nhà phát triển nên hiểu một điều tâm điểm: Các thành phần phần mềm NDK của Android được thiết kế cho các trường hợp sử dụng cụ thể và không được sử dụng cho bất kỳ hoạt động phát triển ứng dụng nào.

Android NDK vừa có thể hỗ trợ quá trình phát triển ứng dụng vừa gây khó khăn nhất có thể. Không có gì bí mật rằng việc sử dụng mã nhị phân trên Nền tảng Android trong một số trường hợp, nó không cải thiện đáng kể hiệu suất của ứng dụng (mặc dù trong hầu hết các trường hợp, nó cải thiện hiệu suất của ứng dụng), nhưng trong mọi trường hợp, nó làm tăng độ phức tạp của mã. Thông thường, cải thiện hiệu suất ứng dụng đạt được bằng cách chạy mã với các hướng dẫn dành riêng cho CPU. Nhưng nói chung, chỉ nên sử dụng NDK khi hiệu suất ứng dụng là quan trọng tham số quan trọng chứ không phải khi nhà phát triển viết mã bằng C/C++ sẽ thuận tiện hơn.

Tóm lại, cần phải nói rằng không có quy tắc bất di bất dịch nào điều chỉnh trường hợp có thể xảy ra sử dụng NDK, vì vậy bạn phải luôn dựa vào kiến ​​thức, kinh nghiệm và trực giác của mình.

Android NDK là bộ công cụ đáng tin cậy và hiệu quả được thiết kế đặc biệt dành cho Android và các nhà phát triển cần triển khai các phần trong ứng dụng của họ bằng các ngôn ngữ lập trình như C++ hoặc C#.

Tuy nhiên, trước khi sử dụng Android NDK, bạn cần phải là người sành sỏi về các ngôn ngữ mã gốc này và đảm bảo rằng máy tính của bạn đáp ứng tất cả các yêu cầu hệ thống, nếu không thì bạn có thể không được hưởng lợi từ tất cả các tính năng mà bộ công cụ đi kèm.

Nói chung, bạn có thể nhận được vô số tập lệnh C hoặc Java cho ứng dụng hiện tại, nhưng khi sử dụng Android NDK, bạn có thể tăng tốc quá trình phát triển dự án của mình cũng như giữ các thay đổi được đồng bộ hóa giữa các dự án Android và không phải Android.

Là một nhà phát triển nâng cao, khi sử dụng Android NDK, bạn cần cân bằng giữa lợi ích và nhược điểm của nó. Do đó, bạn chỉ nên sử dụng nó nếu nó cần thiết khi phát triển một ứng dụng mới và bạn thực sự cần thành phần này.

Tuy nhiên, bạn không cần phải cho rằng bạn có thể tăng hiệu suất của ứng dụng chỉ vì bạn đang sử dụng mã gốc. Chỉ cần kiểm tra các yêu cầu và xem liệu API khung Android có cung cấp cho bạn không với chức năng chính bạn cần.

Nói như vậy, khi bạn chắc chắn rằng Android NDK là một thành phần mà bạn thực sự cần để chạy và phát triển ứng dụng của mình, bạn có thể giải nén nó và đặt nó vào một thư mục thích hợp. Sau đó, các biến như ‘android_log_print’ và ‘sample_ndk’ sẽ có sẵn trong dự án của bạn.

Ngoài ra, gói NDK còn cung cấp cho bạn các công cụ phù hợp để bạn có thể làm việc hiệu quả với các tập lệnh của mình mà không cần phải xử lý tất cả các chi tiết CPU và ABI.

Cân nhắc rằng Android NDK được dành riêng cho các nhà phát triển Java, nên nó cung cấp cho họ các lớp hữu ích để thông báo mã gốc của họ về bất kỳ lệnh gọi lại nào trong vòng đời hoạt động. Tuy nhiên, phần thú vị nhất của bộ công cụ này là nó cho phép họ nhúng các thư viện gốc vào một tệp gói ứng dụng, tệp này có thể được triển khai trên các thiết bị Android.

Có gì mới trong Android NDK Phiên bản 19c:

  • Các nhà phát triển nên bắt đầu thử nghiệm ứng dụng của họ với LLD. AOSP đã chuyển sang sử dụng LLD theo mặc định và NDK sẽ sử dụng nó theo mặc định trong phiên bản tiếp theo. BFD và Gold sẽ bị xóa sau khi LLD đã trải qua chu kỳ phát hành mà không có vấn đề lớn nào chưa được giải quyết (ước tính r21). Kiểm tra LLD trong ứng dụng của bạn bằng cách chuyển -fuse-ld=lld khi liên kết. Lưu ý: lld hiện không hỗ trợ các ký hiệu nén trên Windows. Vấn đề 888. Clang cũng không thể tạo các ký hiệu nén trên Windows, nhưng điều này có thể là một vấn đề khi sử dụng các tạo phẩm được xây dựng từ Darwin hoặc Linux.
  • Cửa hàng Play sẽ yêu cầu hỗ trợ 64 bit khi tải APK lên bắt đầu từ tháng 8 năm 2019. Hãy bắt đầu chuyển ngay bây giờ để tránh những điều bất ngờ khi thời gianđến. Để biết thêm thông tin, hãy xem bài đăng trên blog này.
  • Vấn đề 780: Chuỗi công cụ độc lập hiện không cần thiết. Clang, binutils, sysroot và các phần toolchain khác hiện đã được cài đặt vào $NDK/toolchains/llvm/prebuild/ và Clang sẽ tự động tìm thấy chúng.

Để phát triển ứng dụng cho hệ điều hành Android, Google cung cấp hai gói phát triển: SDK và NDK. Có rất nhiều bài viết, sách và cả những hướng dẫn hay của Google về SDK. Nhưng ngay cả bản thân Google cũng viết rất ít về NDK. Và trong số những cuốn sách đáng giá, tôi chỉ chọn ra một cuốn, Cinar O. - Pro Android C++ with the NDK – 2012.

Bài viết này hướng tới những người chưa quen (hoặc ít quen) với Android NDK và muốn củng cố kiến ​​thức của mình. Tôi sẽ chú ý đến JNI, vì đối với tôi, có vẻ như chúng ta cần bắt đầu với giao diện này. Ngoài ra, cuối cùng chúng ta sẽ xem xét ví dụ nhỏ với hai chức năng ghi và đọc file.

Android NDK là gì?

Android NDK(tự nhiên bộ dụng cụ phát triển) là một bộ công cụ cho phép bạn triển khai một phần ứng dụng của mình bằng các ngôn ngữ như C/C++.

NDK dùng để làm gì?

Google khuyên bạn chỉ nên sử dụng NDK trong những trường hợp rất hiếm. Thường là những trường hợp sau:
  • Bạn cần tăng năng suất (ví dụ: sắp xếp một lượng lớn dữ liệu);
  • Sử dụng thư viện của bên thứ ba. Ví dụ: rất nhiều thứ đã được viết bằng ngôn ngữ C/C++ và bạn chỉ cần sử dụng tài liệu hiện có. Ví dụ về các thư viện như Ffmpeg, OpenCV;
  • Lập trình cấp thấp (ví dụ: mọi thứ vượt xa Dalvik);

JNI là gì?

Giao diện gốc Java– một cơ chế tiêu chuẩn để chạy mã dưới sự điều khiển của máy ảo Java, được viết bằng ngôn ngữ C/C++ hoặc Trình biên dịch mã và được liên kết dưới dạng thư viện động, loại bỏ nhu cầu liên kết tĩnh. Điều này giúp có thể gọi hàm C/C++ từ chương trình Java và ngược lại.

Lợi ích của JNI

Ưu điểm chính so với các sản phẩm tương tự của nó (Giao diện thời gian chạy Java của Netscape hoặc Giao diện gốc thô của Microsoft và Giao diện COM/Java) là JNI ban đầu được phát triển để đảm bảo khả năng tương thích nhị phân, cho khả năng tương thích của các ứng dụng được viết bằng JNI cho bất kỳ ứng dụng ảo nào. Máy Java trên một nền tảng cụ thể (khi nói về JNI, tôi không bị ràng buộc với máy Dalvik, vì JNI được Oracle viết cho JVM, phù hợp với mọi ngôn ngữ Java máy ảo). Do đó, mã được biên dịch trong C/C++ sẽ được thực thi bất kể nền tảng nào. Hơn phiên bản đầu không cho phép thực hiện khả năng tương thích nhị phân.

Khả năng tương thích nhị phân hoặc khả năng tương thích nhị phân là một loại khả năng tương thích chương trình cho phép chương trình hoạt động trong các môi trường khác nhau mà không thay đổi các tệp thực thi của nó.

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

Bảng JNI, được tổ chức giống như bảng các hàm ảo trong C++. VM có thể hoạt động với một số bảng như vậy. Ví dụ, một cái sẽ dùng để gỡ lỗi, cái thứ hai để sử dụng. Con trỏ tới giao diện JNI chỉ hợp lệ trong luồng hiện tại. Điều này có nghĩa là con trỏ không thể đi từ luồng này sang luồng khác. Nhưng các phương thức gốc có thể được gọi từ các luồng khác nhau. Ví dụ:

Jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s) ( const char *str = (*env)->GetStringUTFChars(env, s, 0); (*env)->ReleaseStringUTFChars(env, s, str ); trả về 10 )

  • *env– con trỏ tới giao diện;
  • vật thể– một liên kết đến đối tượng trong đó phương thức gốc được mô tả;
  • tôi và s– các đối số đã được thông qua;
Các kiểu nguyên thủy được sao chép giữa VM và mã gốc, còn các đối tượng được truyền bằng tham chiếu. VM được yêu cầu theo dõi tất cả các liên kết được chuyển vào mã gốc. Tất cả các tham chiếu được chuyển đến mã gốc không thể được giải phóng bởi GC. Nhưng đến lượt mã gốc phải thông báo cho VM rằng nó không còn cần tham chiếu đến các đối tượng được chuyển nữa.

Liên kết địa phương và toàn cầu

JNI chia các tham chiếu thành ba loại: tham chiếu cục bộ, toàn cầu và toàn cầu yếu. Người dân địa phương có giá trị cho đến khi phương thức hoàn thành. Tất cả Đối tượng Java mà hàm JNI trả về là cục bộ. Lập trình viên phải dựa vào chính VM để dọn sạch tất cả các tham chiếu cục bộ. Liên kết cục bộ chỉ có sẵn trong chuỗi nơi chúng được tạo. Tuy nhiên, nếu có nhu cầu, chúng có thể được giải phóng ngay lập tức bằng phương thức DeleteLocalRef của giao diện JNI:

Jclass clazz; clazz = (*env)->FindClass(env, "java/lang/String"); //mã của bạn (*env)->DeleteLocalRef(env, clazz);
Tài liệu tham khảo toàn cầu vẫn còn cho đến khi chúng được phát hành rõ ràng. Để đăng ký một tham chiếu toàn cục, hãy gọi phương thức NewGlobalRef. Nếu tham chiếu toàn cục không còn cần thiết nữa thì nó có thể bị xóa bằng phương thức DeleteGlobalRef:

Jclass localClazz; jclass toàn cầuClazz; localClazz = (*env)->FindClass(env, "java/lang/String"); GlobalClazz = (*env)->NewGlobalRef(env, localClazz); // mã của bạn (*env)->DeleteLocalRef(env, localClazz);

Xử lý lỗi

JNI không kiểm tra các lỗi như NullPointerException, IllegalArgumentException. Nguyên nhân:
  • giảm năng suất;
  • Trong hầu hết các chức năng của thư viện C, rất khó để bảo vệ khỏi lỗi.
JNI cho phép bạn sử dụng Ngoại lệ Java. Hầu hết các hàm JNI đều trả về một mã lỗi chứ không phải chính Ngoại lệ và do đó bạn phải tự xử lý mã đó và trong Java bạn đã đưa ra một Ngoại lệ. Trong JNI, bạn nên kiểm tra mã lỗi của các hàm được gọi và sau đó gọi ExceptionOccurred(), hàm này sẽ trả về một đối tượng lỗi:

Đã xảy ra ngoại lệ Jthrowable(JNIEnv *env);
Ví dụ: một số hàm truy cập mảng JNI không trả về lỗi nhưng có thể đưa ra các ngoại lệ ArrayIndexOutOfBoundsException hoặc ArrayStoreException.

Các kiểu nguyên thủy của JNI

JNI có các kiểu dữ liệu nguyên thủy và tham chiếu riêng.
Loại Java Kiểu gốc Sự miêu tả
boolean jboolean 8 bit không dấu
byte jbyte ký 8 bit
ký tự jchar 16 bit không dấu
ngắn ngắn gọn ký 16 bit
int jint ký 32 bit
dài jlong ký 64 bit
trôi nổi jfloat 32 bit
gấp đôi jdouble 64 bit
trống rỗng trống rỗng không áp dụng

Các loại tham chiếu JNI

UTF-8 đã sửa đổi

JNI sử dụng mã hóa UTF-8 đã sửa đổi để biểu diễn chuỗi. Ngược lại, Java sử dụng UTF-16. UTF-8 chủ yếu được sử dụng trong C vì nó mã hóa \u0000 thành 0xc0 thay vì 0x00 thông thường. Các chuỗi đã sửa đổi được mã hóa sao cho một chuỗi các ký tự chỉ chứa một số khác 0 ký tự ASCII có thể được biểu diễn chỉ bằng một byte.

Các hàm JNI

Giao diện JNI không chỉ chứa bộ dữ liệu riêng mà còn chứa các chức năng riêng. Sẽ mất rất nhiều thời gian để xem xét chúng, vì có hơn một chục trong số chúng. Bạn có thể làm quen với họ trong tài liệu chính thức.

Ví dụ về cách sử dụng hàm JNI

Một ví dụ nhỏ để giúp bạn hiểu tài liệu được đề cập:
#bao gồm //... JavaVM *jvm; JNIEnv *env; JavaVMInitArgs vm_args; Tùy chọn JavaVMOption* = JavaVMOption mới; options.optionString = "-Djava.class.path=/usr/lib/java"; vm_args.version = JNI_VERSION_1_6; vm_args.nOptions = 1; vm_args.options = tùy chọn; vm_args.ignoreUnrecognized = false; JNI_CreateJavaVM(&jvm, &env, &vm_args); tùy chọn xóa; jclass cls = env->FindClass("Main"); jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V"); env->CallStaticVoidMethod(cls, mid, 100); jvm->Tiêu diệtJavaVM();
Chúng ta hãy nhìn vào nó từng dòng một:
  • JavaVM– cung cấp giao diện để gọi các hàm cho phép bạn tạo và hủy JavaVM;
  • JNIEnv– cung cấp hầu hết các chức năng JNI;
  • JavaVMInitArgs– đối số cho JavaVM;
  • Tùy chọn JavaVM– tùy chọn cho JavaVM;
Phương thức JNI_CreateJavaVM() khởi tạo JavaVM và trả về một con trỏ tới nó. Phương thức JNI_DestroyJavaVM() sẽ dỡ bỏ JavaVM đã tạo.

Dòng

Tất cả các luồng trong Linux đều được quản lý bởi kernel, nhưng chúng có thể được gắn vào JavaVM bằng cách sử dụng các hàm AttachCurrentThread và AttachCurrentThreadAsDaemon. Cho đến khi chuỗi được đính kèm, nó không có quyền truy cập vào JNIEnv. Quan trọng, Android không tạm dừng các chuỗi do JNI tạo, ngay cả khi GC được kích hoạt. Nhưng trước khi luồng kết thúc, nó phải gọi phương thức DetachCurrentThread để tách khỏi JavaVM.

Những bước đầu tiên

Cấu trúc dự án của bạn sẽ trông như thế này:

Như chúng ta có thể thấy trong Hình 3, tất cả mã gốc đều nằm trong thư mục jni. Sau khi xây dựng dự án, bốn thư mục sẽ được tạo trong thư mục libs cho từng kiến ​​trúc bộ xử lý, trong đó thư viện gốc của bạn sẽ được đặt (số lượng thư mục tùy thuộc vào số lượng kiến ​​trúc đã chọn).

Để tạo một dự án gốc, bạn cần tạo một dự án Android thông thường và làm theo các bước sau:

  • Trong thư mục gốc của dự án, bạn cần tạo một thư mục jni để đặt các nguồn mã gốc;
  • Tạo tệp Android.mk sẽ xây dựng dự án;
  • Tạo tệp Application.mk mô tả chi tiết lắp ráp. Nó không phải là điều kiện tiên quyết nhưng cho phép bạn tùy chỉnh linh hoạt việc lắp ráp;
  • Tạo tệp ndk-build sẽ khởi chạy quá trình xây dựng (cũng là tùy chọn).

Android.mk

Như đã đề cập ở trên, đây là một tệp tạo tệp để xây dựng một dự án gốc. Android.mk cho phép bạn nhóm mã của mình thành các mô-đun. Các mô-đun có thể là thư viện tĩnh (chỉ chúng sẽ được sao chép vào dự án của bạn, trong thư mục libs), thư viện dùng chung (thư viện dùng chung), độc lập tập tin thực thi(thực thi độc lập).

Ví dụ cấu hình tối thiểu:
LOCAL_PATH:= $(gọi my-dir) bao gồm $(CLEAR_VARS) LOCAL_MODULE:= NDKBBắt đầu LOCAL_SRC_FILES:= ndkBegining.c bao gồm $(BUILD_SHARED_LIBRARY)
Chúng ta hãy xem xét nó một cách chi tiết:

  • L OCAL_PATH:= $(gọi my-dir)– hàm gọi my-dir trả về đường dẫn của thư mục chứa tệp được gọi;
  • bao gồm $(CLEAR_VARS)– xóa các biến đã được sử dụng trước đó ngoại trừ LOCAL_PATH. Điều này là cần thiết vì tất cả các biến đều mang tính toàn cục, vì quá trình xây dựng diễn ra trong ngữ cảnh của một GNU Make;
  • LOCAL_MODULE– tên của mô-đun đầu ra. Trong ví dụ của chúng tôi, tên thư viện đầu ra được đặt thành NDKBegining, nhưng sau khi xây dựng, các thư viện có tên libNDKBegining sẽ được tạo trong thư mục libs. Android thêm tiền tố lib vào tên, nhưng trong mã java khi kết nối, bạn phải chỉ định tên của thư viện không có tiền tố (nghĩa là các tên phải khớp với tên được cài đặt trong tệp tạo tệp);
  • LOCAL_SRC_FILES– liệt kê các tập tin nguồn mà từ đó tập hợp sẽ được tạo ra;
  • bao gồm $(BUILD_SHARED_LIBRARY)– cho biết loại mô-đun đầu ra.
Bạn có thể xác định các biến của riêng mình trong Android.mk, nhưng chúng không được có cú pháp sau: LOCAL_, PRIVATE_, NDK_, APP_, my-dir. Google khuyên bạn nên đặt tên biến như MY_. Ví dụ:
MY_SOURCE:= NDKBegining.c Để truy cập một biến: $(MY_SOURCE) Các biến cũng có thể được nối, ví dụ: LOCAL_SRC_FILES += $(MY_SOURCE)

Ứng dụng.mk

Makefile này xác định một số biến sẽ giúp việc xây dựng trở nên linh hoạt hơn:
  • APP_OPTIM– một biến bổ sung được đặt để phát hành hoặc gỡ lỗi. Được sử dụng để tối ưu hóa khi lắp ráp các mô-đun. Bạn có thể gỡ lỗi bằng cách phát hành hoặc gỡ lỗi, nhưng việc gỡ lỗi cung cấp thêm thông tin để gỡ lỗi;
  • APP_BUILD_SCRIPT– trỏ tới một đường dẫn thay thế tới Android.mk;
  • APP_ABI– có lẽ là một trong những biến số quan trọng nhất. Nó cho biết các mô-đun sẽ được lắp ráp theo kiến ​​trúc bộ xử lý nào. Mặc định là armeabi, tương ứng với kiến ​​trúc ARMv5TE. Ví dụ: để hỗ trợ ARMv7, bạn nên sử dụng armeabi-v7a, cho IA-32 - x86, cho MIPS - mips hoặc nếu bạn cần hỗ trợ tất cả các kiến ​​​​trúc, thì giá trị sẽ như sau: APP_ABI:= armeabi armeabi-v7a x86 mips. Nếu bạn đang sử dụng ndk phiên bản 7 trở lên thì bạn không thể liệt kê tất cả các kiến ​​trúc mà phải đặt APP_ABI:= tất cả.
  • APP_PLATFORM– mục tiêu nền tảng;
  • APP_STL– Android sử dụng thư viện thời gian chạy libstdc++.so, thư viện này đã bị loại bỏ và không phải tất cả chức năng C++ đều có sẵn cho nhà phát triển. Tuy nhiên, biến APP_STL cho phép đưa hỗ trợ mở rộng vào bản dựng;
  • NDK_TOOLCHAIN_VERSION– cho phép bạn chọn phiên bản trình biên dịch gcc (mặc định 4.6);

XÂY DỰNG NDK

Ndk-build là trình bao bọc cho GNU Make. Sau phiên bản 4, cờ cho ndk-build đã được giới thiệu:
  • lau dọn– xóa tất cả các tệp nhị phân được tạo;
  • NDK_DEBUG=1– tạo mã gỡ lỗi;
  • NDK_LOG=1– hiển thị nhật ký tin nhắn (được sử dụng để gỡ lỗi);
  • NDK_HOST_32BIT=1– Android có các công cụ hỗ trợ các phiên bản tiện ích 64-bit (ví dụ NDK_PATH\toolchains\mipsel-linux-android-4.8\prebuild\windows-x86_64, v.v.);
  • NDK_APPLICATION_MK- đường dẫn đến Application.mk được chỉ định.
Trong phiên bản 5 của NDK, cờ đã được giới thiệu: NDK_DEBUG. Nếu nó được đặt thành 1 thì phiên bản gỡ lỗi sẽ được tạo. Nếu cờ không được đặt thì ndk-build theo mặc định sẽ kiểm tra xem thuộc tính android:debuggable="true" có được đặt trong AndroidManifest.xml hay không. Nếu bạn đang sử dụng ndk cao hơn phiên bản 8 thì Google không khuyên bạn nên sử dụng thuộc tính android:debuggable trong AndroidManifest.xml (vì nếu bạn sử dụng "ant debug" hoặc xây dựng phiên bản gỡ lỗi bằng plugin ADT thì họ sẽ tự động thêm NDK_DEBUG =1 lá cờ).

Theo mặc định, hỗ trợ cho 64 được cài đặt phiên bản bit các tiện ích, nhưng bạn chỉ có thể buộc biên dịch trong 32 bằng cách đặt cờ NDK_HOST_32BIT=1. Google vẫn khuyến nghị sử dụng các tiện ích 64-bit để cải thiện hiệu suất của các chương trình lớn.

Làm thế nào để lắp ráp một dự án?

Nó từng là một nỗi đau. Cần phải cài đặt plugin CDT, tải xuống trình biên dịch cygwin hoặc mingw. Tải xuống Android NDK. Kết nối tất cả điều này trong cài đặt Eclipse. Và thật đáng tiếc là mọi chuyện lại không thành công. Lần đầu tiên tôi biết đến Android NDK, tôi đã mất 3 ngày để thiết lập nó (và vấn đề hóa ra là trong cygwin, tôi phải cấp quyền 777 cho thư mục dự án).

Bây giờ mọi thứ đơn giản hơn nhiều với điều này. Theo liên kết này. Tải xuống Gói ADT Eclipse, gói này đã chứa mọi thứ bạn cần để lắp ráp.

Gọi các phương thức gốc từ mã Java

Để sử dụng mã gốc từ Java, trước tiên bạn cần xác định các phương thức gốc trong lớp Java. Ví dụ:
Chuỗi gốc GetStringFromFile(String path) ném IOException; Native void NativeWriteByteArrayToFile(String path, byte b) ném IOException;
Trước phương thức này phải có từ dành riêng “bản địa”. Bằng cách này, trình biên dịch biết rằng đây là điểm vào JNI. Chúng ta cần triển khai các phương thức này trong tệp C/C++. Google cũng khuyên bạn nên bắt đầu đặt tên phương thức bằng từ localX, trong đó X là tên thật của phương thức. Nhưng trước khi triển khai các phương pháp này một cách thủ công, bạn nên tạo một tệp tiêu đề. Việc này có thể được thực hiện thủ công nhưng bạn có thể sử dụng tiện ích javah có trong jdk. Nhưng chúng ta hãy đi xa hơn và sẽ không sử dụng nó thông qua bảng điều khiển mà sẽ thực hiện nó bằng các công cụ Eclipse tiêu chuẩn.

Bây giờ bạn có thể khởi chạy. Thư mục bin/classes sẽ chứa các tệp tiêu đề của bạn.

Tiếp theo, chúng tôi sao chép các tệp này vào thư mục jni của dự án gốc của chúng tôi. Đang gọi danh mục dự án và chọn Công cụ Android - Thêm Thư viện gốc. Điều này sẽ cho phép chúng ta sử dụng các hàm jni.h. Sau đó, bạn có thể tạo một tệp cpp (đôi khi Eclipse tạo nó theo mặc định) và viết nội dung của các phương thức đã được mô tả trong tệp tiêu đề.

Tôi không thêm ví dụ về mã vào bài viết để không kéo dài nó. Bạn có thể xem/tải xuống một ví dụ từ github.

Thẻ: Thêm thẻ