Android NDK orqali nativ kutubxonalar yaratish
Loyihani qo'llab quvvatlash uchub buyerga bosing
BYD uchun O’zbek tilidagi yordamchini integratsiya qilish vaqtida kutilmagan vazifa tushdi, ya’ni shared kutubxonalar qilish. Android ham linux ustiga qurilganligi bois chuxnya masala deb o’ylagandim lekin java sababli linux va kutubxona arxitekturasida o’zgarishlar ham qilishga ehtiyoj bo’lgan ekan.
Menga yuklatilgan vazifada kutubxonani Cda yozish topshirildi, har holda bunday masalalarda ancha qulay til (c++ yanada). Lekin java uchun odatiy name linker berish metodlari boshqacha ekan. Aniqroq aytganda boshqa tillar ffi orqali nativ kutubxonaga murojaat qilsa, javada bu ish jni orqali amalga oshirilarkan. Menda boshlanishida ba’zi bir tushunmovchiliklar bo’ldi, shu sababli oz bo’lsada sizlar bilan bu haqida ulashay dedim.
Kichik nativ kutubxona
Ishni odatiy C da nativ kutubxonada yozishdan boshlasak bu keyinchalik ndk uchun qo’shimcha qilishga yordam beradi.
Demak bizda odatiy kutubxona kodi:
//mylib.c
#include <string.h>
int add(int a, int b) {
return a + b;
}
int getlen( char * str ) {
return strlen( str );
}
char * genStr() {
return "Test string!";
}
Kutubxonani kompiliyatsiya qilish:
gcc -fPIC -shared -o libmylib.so mylib.c
Kutubxonadan foydalanish:
//app.c
#include <stdio.h>
int add(int a, int b);
int getlen( char * str );
char * genStr();
int main() {
int result = add(2, 2);
printf( "Result: %d\n", result );
printf( "Length: %d\n", getlen( "Hello" ) );
printf( "String: %s\n", genStr() );
return 0;
}
Kutubxonadan foydalanish uchun app.c faylini kompiliyatsiya qilish:
gcc -L. -lmylib -o app app.c
app faylini ishga tushirish:
Binary faylimiz mylib.so faylini topa olishi uchun avvalo LD_LIBRARY_PATH
orqali kutubxona turgan papkani ko’rsatish lozim. Yoki kutubxonani /usr/lib
papkasiga ko’chirish orqali ham muammoni hal etish mumkin.
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
./app #ishga tushirish
Kutubxonadan php orqali foydalanib ko’rish:
<?php
$ffi = FFI::cdef("
int add(int a, int b);
int getlen(char* str);
char* genStr();
", "./libmylib.so");
$result = $ffi->add(2, 2);
echo "add(2, 2) = $result\n";
$len = $ffi->getlen("Hello");
echo "getlen('Hello') = $len\n";
$str = $ffi->genStr();
echo "genStr() = " . FFI::string($str) . "\n";
FFI::free($str);
?>
JNI va NDK
Yuqoridagi misolda men kutubxonani x86_64 arxitekturada kompiliyatsiya qilib ko’rganman, o’z navbatida esa bu arm uchun ishlamaydi. Menimcha arm orqali kompiliyatsiya qilish imkoni hammada ham bo’lmasligi mumkin, aynan shunday vaziyatda ndk bizga bu vazifani bajarib undan tashaqari jni interfeysi uchun ulab beradi ham.
ndk o’rnatish
Ndk o’rnatish uchun android studio yoki boshqa narsalarni o’rnatishni o’ylab o’tirmang (bu juda og’ir jarayon). Eng yaxshi usuli google adnroidni o’zidan ndkning cli versiyasini yuklab olish va uni sozlash:
wget https://dl.google.com/android/repository/android-ndk-r26c-linux.zip
unzip android-ndk-r26c-linux.zip -d ~/android-ndk
NDK kerakli papkaga yuklangach undan foydalanish uchun linux path va ANDROID_NDK_HOME o’zgaruvchilarini e’lon qilish kerak. Bu uchun ~/.bashrc faylining oxiriga quyidagi qatorlarni qo’shamiz:
export ANDROID_NDK_HOME=~/android-ndk
export PATH=$PATH:$ANDROID_NDK_HOME
jni uchun kutuxbona kodi:
JNI interterfeysidan foydalanish uchun c kodimizga jni.h
header faylini import qilish va funksiya nomlairniyam jni uchun moslash talab qilinadi.
Bunda har bir funksiya quyidagi prefikslar bilan boshlanishi mumkin:
JNIEXPORT jitype JNICALL Java_appid_Class_function
- jitype - qaytarish turi
- appid - ilova idenfikatori
- Class - java interfeysi uchun class nomi
- function - funksiya nomi
//mylib.c
#include <jni.h>
#include <string.h>
JNIEXPORT jint JNICALL Java_com_example_myapp_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
return a + b;
}
JNIEXPORT jint JNICALL Java_com_example_myapp_MainActivity_getlen(JNIEnv *env, jobject thiz, jstring str) {
const char *cStr = (*env)->GetStringUTFChars(env, str, NULL);
jint len = strlen(cStr);
(*env)->ReleaseStringUTFChars(env, str, cStr);
return len;
}
JNIEXPORT jstring JNICALL Java_com_example_myapp_MainActivity_genStr(JNIEnv *env, jobject thiz) {
return (*env)->NewStringUTF(env, "Test string!");
}
NDK ham xuddi gcc kabi makefayldan foydalanish imkoniga ega. Bunda .mk fayllar sodda Makefilega qaraganda ko’proq kompleks yechimlar berib ndk ishini kengaytirib bera oladi.
Demak bizdagi nativ kutubxona uchun Android.mk
faylimiz:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c
include $(BUILD_SHARED_LIBRARY)
Application.mk
bu ndk-build uchun sozlamalar berishga foydalanadigan fayl hisoblanadi. Masalan qaysi arxitekturaga kompiliyatsiya qilish, c/c++ sozlamalari qanday bo’lishi kabi holatlar uchun yechim bera oladi.
APP_ABI := all
Bizning holatda APP_ABI
( Application binary interface ) all ko’rsatilgan, bu bir vaqtda barcha qo’llay oladigan arxitekturalar uchun kompiliyatsiya qilishni ko’rsatadi.
Arch | ABI |
---|---|
32-bit ARMv7 | APP_ABI := armeabi-v7a |
64-bit ARMv8 (AArch64) | APP_ABI := arm64-v8a |
x86 | APP_ABI := x86 |
x86-64 | APP_ABI := x86_64 |
Yuqoridagi fayllar hosil qilingach terminal orqali ndk-build
buyrug’ini beradigan bo’lsak bizda java orqali foydalanish mumkin bo’lgan libtest.so
fayli hosil bo’ladi.
Kutubxonada foydalanish uchun esa javadan quyidagicha murojaat qilishimiz kerak (Xatolik bo’lsa uzr, men java bo’yicha mutaxassis emasman)
package com.example.myapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("test"); // lib prefiksisiz
}
public native int add(int a, int b);
public native int getlen(String str);
public native String genStr();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int sum = add(3, 4);
int length = getlen("Hello JNI");
String message = genStr();
}
}