Android'de Kotlin'den Yerel C Kodu Nasıl Çağrılır?
Kısa cevap: Kotlin'de external metotları tanımlayın ve .so dosyasını System.loadLibrary ile yükleyin; C tarafında ise JNI imzalarına (Java_paket_Sınıf_metod) göre fonksiyonları uygulayın veya RegisterNatives ile kaydedin; NDK/CMake ile derleyin ve hedef ABI'lerde test edin. Aşağıda örnekler ve ipuçlarıyla adım adım anlatım bulacaksınız.
Proje Kurulumu ve Derleme Hızlı Başlangıcı
- SDK Manager üzerinden Android NDK (r26+) sürümünü kurun.
- app modülünde externalNativeBuild (CMake) ve ABI filtrelerini ekleyin:
android {
defaultConfig {
ndk {
abiFilters += listOf("arm64-v8a","armeabi-v7a")
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
}
}
}
- src/main/cpp/CMakeLists.txt dosyasını oluşturun:
cmake_minimum_required(VERSION 3.22.1)
add_library(native-lib SHARED native-lib.c)
find_library(log-lib log)
target_link_libraries(native-lib ${log-lib})
- C kodunu src/main/cpp/native-lib.c dosyasına yazın ve ./gradlew assembleDebug komutuyla derleyin. Sonuç olarak libnative-lib.so dosyası app/build/outputs klasöründe oluşacaktır.
JNI, her çağrı için ek yük getirir (~20–50 µs). Yerel kodu sık yapılan küçük çağrılar için değil, ağır hesaplamalar (büyük bloklar, dizi işleme) için kullanın.
Kotlin'de Metot Tanımlama ve C Tarafında Uygulama
Kotlin'de external metotlara sahip bir sınıf oluşturun ve kütüphaneyi yükleyin:
class NativeBridge {
external fun stringFromJNI(): String
external fun computeSum(a: Int, b: Int): Int
companion object {
init { System.loadLibrary("native-lib") }
}
}
Activity içinden çağırma:
val sum = NativeBridge().computeSum(5,10) // 15
C tarafında JNI imzasına göre fonksiyonları uygulayın (paketi kendi paketinizle değiştirin):
#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_example_myapp_NativeBridge_stringFromJNI(JNIEnv *env, jobject thiz) {
return (*env)->NewStringUTF(env, "Hello from C!");
}
JNIEXPORT jint JNICALL
Java_com_example_myapp_NativeBridge_computeSum(JNIEnv *env, jobject thiz, jint a, jint b) {
return a + b;
}
Alternatif olarak: Rastgele isimlerle metotlar uygulayıp JNI_OnLoad içinde RegisterNatives kullanabilirsiniz; bu yaklaşım obfuscation veya karmaşık paket adlarında kullanışlıdır.
Veri Aktarımı, Bellek Güvenliği ve Çoklu İş Parçacığı
- İlkel türler doğrudan aktarılır. Diziler için Get/Release (GetIntArrayElements / ReleaseIntArrayElements) veya hızlı erişim için GetPrimitiveArrayCritical kullanın.
- Dizeler için: GetStringUTFChars ve ReleaseStringUTFChars.
- Her zaman istisnaları kontrol edin: if ((env)->ExceptionCheck(env)) { / işlem */ }.
- Yerel tablo taşmasını önlemek için uzun döngülerde DeleteLocalRef ile yerel referansları serbest bırakın.
- İş parçacıkları: Yerel bir iş parçacığı JNI çağrısı yapacaksa, önce (*vm)->AttachCurrentThread(vm, &env, NULL) çağırın ve tamamlandığında DetachCurrentThread ile ayırın.
Büyük arabellekler ve sık veri alışverişi için native heap ayırmak ve işaretçileri jlong olarak aktarmak tercih edilir; bu, kopyalama ek yükünü azaltır.
Hata Ayıklama ve Profil Oluşturma
- Android Studio'da yerel kodda kesme noktaları için LLDB kullanın.
- Çökmeleri kaynak kodlarla eşleştirmek için ndk-stack veya tombstone yığın izini kullanın.
- Android Profiler, CPU izlemesinde yerel fonksiyonların çalışma süresini gösterir.
- Gerçek cihazda hedef ABI ile test edin; emülatör farklı davranabilir.
Yaklaşımların Karşılaştırılması (Özet)
| Yaklaşım | Ek Yük | Karmaşıklık | Ne Zaman Kullanılır |
|---|---|---|---|
| JNI (C/C++) | Büyük bloklar için düşük | Yüksek | OpenCV, kriptografi, DSP |
| Kotlin/Native | Daha az JVM bağımlılığı | Orta | Çoklu platform desteği |
| JNA | Yüksek | Düşük | Hızlı prototipleme |
Sık Yapılan Hatalar
- Yanlış fonksiyon imzası/adı veya hatalı System.loadLibrary yolu nedeniyle UnsatisfiedLinkError.
- Döngü içinde sık nesne oluştururken yerel referans sızıntıları.
- Derleme sırasında yanlış ABI seçimi — .so dosyası cihazda yüklenmez.
- Yerel iş parçacığında AttachCurrentThread eksikliği — JNI çağrısında çökme.
SSS
- Doğru JNI imzasını nasıl bulurum? — En kolay yol: Java/Kotlin stub'larını derleyip javap -s kullanmak veya javah benzeri araçlarla başlık dosyası üretmek; alternatif olarak RegisterNatives kullanabilirsiniz.
- C mi yoksa C++ mı yazmalıyım? — C++, sarmalayıcılar ve kütüphaneler için daha kullanışlıdır; C daha basit ve evrenseldir. Android'de genellikle C++ (NDK) ve C ABI için extern "C" kullanımı yaygındır.
- Ek yükleri nasıl minimize ederim? — JNI çağrı sayısını azaltın, büyük arabellekler aktarın, GetPrimitiveArrayCritical ve native heap kullanın.
Bu adımlarla, Android'deki Kotlin uygulamanıza C kodunu bir saat içinde entegre edip optimize edebilir ve hızla profil oluşturma ile performans iyileştirme aşamasına geçebilirsiniz.