JNI, Java Native Interface'tır. Bu, Android'in derlediği bayt kodunun bir yöntemini tanımlar. yerel kodla etkileşimde bulunmak için yönetilen kod (Java veya Kotlin programlama dillerinde yazılmış) (C/C++ dilinde yazılmıştır). JNI satıcıdan bağımsızdır, dinamik paylaşılandan kod yükleme desteğine sahiptir zaman zaman kullanışsız olsa da makul ölçüde verimlidir.
Not: Android, Kotlin'i ART uyumlu bayt koduna derlediği için Bu sayfadaki talimatları, Java programlama diline benzer şekilde JNI mimarisi ve bununla ilişkili maliyetler açısından Kotlin ve Java programlama dillerini test ettik. Daha fazla bilgi için Kotlin ve Android başlıklı makaleyi inceleyin.
JNI hakkında bilginiz yoksa JNI'nin işleyiş şeklini ve sunulan özellikleri öğrenmek için Java Native Interface Specification'i okuyun. Biraz arayüzün bazı özellikleri farklı cihazlarda hemen Bu nedenle, sonraki birkaç bölümü kullanışlı bulabilirsiniz.
Global JNI referanslarına göz atmak ve genel JNI referanslarının nerede oluşturulup silindiğini görmek için şunu kullanın: Bellek Profili'ndeki JNI yığın görünümü Android Studio 3.2 ve sonraki sürümlerde kullanılabilir.
Genel ipuçları
JNI katmanının kapladığı alanı en aza indirmeye çalışın. Burada dikkate almanız gereken birkaç boyut vardır. JNI çözümünüz şu yönergelere uymaya çalışmalıdır (önem sırasına göre aşağıda listelenmiştir), örneğin en önemlisiyle başlayın):
- JNI katmanı genelinde kaynakların sıralanmasını en aza indirin. Karşınızda JNI katmanının önemsiz maliyetleri vardır. Düzenlemeniz gereken veri miktarını ve veri düzenleme sıklığını en aza indiren bir arayüz tasarlamaya çalışın.
- Yönetilen programlamada yazılmış kodlar arasında eşzamansız iletişimi önleme C++ dilinde yazılmış dil ve kod yeterli olacaktır. Bu sayede JNI arayüzünüzün bakımı daha kolay olur. Asenkron güncellemeyi kullanıcı arayüzüyle aynı dilde tutarak genellikle asenkron kullanıcı arayüzü güncellemelerini basitleştirebilirsiniz. Örneğin, JNI aracılığıyla Java kodunda UI iş parçacığından bir C++ işlevi çağırmak iki iş parçacığı ve Java programlama dilindeki iki iş parçacığı arasında engelleyen bir C++ çağrısı yapma ve ardından engelleme çağrısı yapıldığında kullanıcı arayüzü iş parçacığını bilgilendirme belirir.
- JNI'nin dokunması veya kendisine dokunulması gereken iş parçacıklarının sayısını en aza indirin. İş parçacığı havuzlarını hem Java hem de C++ dillerinde kullanmanız gerekiyorsa JNI çalışan iş parçacıkları yerine havuz sahipleri arasındaki iletişimi sağlar.
- Gelecekte yeniden yapılandırmayı kolaylaştırmak için arayüz kodunuzu az sayıda kolayca tanımlanabilen C++ ve Java kaynak konumunda tutun. JNI otomatik oluşturma yöntemini kullanmayı düşünün kitaplığını gerektiği şekilde ayarlayın.
JavaVM ve JNIEnv
JNI, "JavaVM" ve "JNIEnv" olmak üzere iki temel veri yapısı tanımlar. Bunların her ikisi de temelde işlev tablolarına işaret eden işaretçilerdir. (C++ sürümünde, her JNI işlevi için bir fonksiyon tablosuna ve üye işlevine tabloyu kullanabilirsiniz.) JavaVM, "çağrı arayüzünü" sağlar fonksiyonları, Böylece JavaVM'yi oluşturup kaldırabilirsiniz. Teoride işlem başına birden çok JavaVM'niz olabilir. ancak Android sadece birine izin veriyor.
JNIEnv, JNI işlevlerinin çoğunu sağlar. Yerel işlevlerinizin tümü
@CriticalNative
yöntemleri hariç ilk bağımsız değişken
Daha hızlı yerel çağrılar başlıklı makaleyi inceleyin.
JNIEnv, iş parçacığı yerel depolaması için kullanılır. Bu nedenle, bir JNIEnv'i iş parçacıkları arasında paylaşamazsınız.
Bir kod parçasının JNIEnv'sini almanın başka yolu yoksa
JavaScript'i açın ve iş parçacığının JNIEnv'ini bulmak için GetEnv
komutunu kullanın. (Reklamverenin bulunduğunu varsayarsak aşağıda AttachCurrentThread
bölümüne bakın.)
JNIEnv ve JavaVM'nin C beyanları, C++
beyanları. "jni.h"
dahil etme dosyası farklı typedefs özellikleri sağlar
C veya C++'a dahil olmasına bağlı olarak değiştirebilirsiniz. Bu nedenle, zaman çizelgesine uyarak
her iki dil tarafından da dahil edilen başlık dosyalarına JNIEnv bağımsız değişkenleri dahil edilmelidir. (Başka bir deyişle:
başlık dosyası için #ifdef __cplusplus
gerekiyorsa ek bir işlem yapmanız gerekebilir.
bu başlık JNIEnv'e işaret etmektedir.)
Mesaj dizileri
Tüm mesaj dizileri, çekirdek tarafından planlanan Linux mesaj dizileridir. Genellikle yönetilen koddan (Thread.start()
kullanılarak) başlatılırlar ancak başka bir yerde oluşturulup JavaVM
'a da eklenebilirler. Örneğin, pthread_create()
veya std::thread
ile başlayan bir ileti dizisi, AttachCurrentThread()
veya AttachCurrentThreadAsDaemon()
işlevleri kullanılarak eklenebilir. İleti dizisi
ekli değilse JNIEnv içermez ve JNI çağrıları yapamaz.
Java kodunu çağırması gereken tüm iş parçacıklarını oluşturmak için genellikle Thread.start()
kullanmanızı öneririz. Bunu yapmak, elinizde yeterli yığın alanına sahip olmanızı sağlar.
doğru ThreadGroup
içinde ve aynı ClassLoader
kodunu girin. Java'da hata ayıklama için ileti dizisinin adını
yerel kod (ör. bir pthread_t
kullanıyorsanız pthread_setname_np()
thread_t
ve varsa std::thread::native_handle()
std::thread
ve pthread_t
istiyorum).
Yerel olarak oluşturulmuş bir ileti dizisi eklemek java.lang.Thread
oluşturulacak ve "ana" öğeye eklenecek ThreadGroup
,
görünür hale getirir. AttachCurrentThread()
aranıyor
herhangi bir işlem yapılamaz.
Android, yerel kod yürüten iş parçacıklarını askıya almaz. Çöp toplama işlemi devam ediyorsa veya hata ayıklayıcı bir askıya alma isteği gönderdiyse Android, bir sonraki JNI çağrısında iş parçacığı duraklatılır.
JNI aracılığıyla eklenen ileti dizileri aranmalıdır
DetachCurrentThread()
sonra çıkmadan önce kontrol edin.
Bunu doğrudan kodlamak zorsa Android 2.0 (Eclair) ve sonraki sürümlerde pthread_key_create()
kullanarak iş parçacığı çıkmadan önce çağrılacak bir yıkıcı işlevi tanımlayabilir ve oradan DetachCurrentThread()
'ı çağırabilirsiniz. (
JNIEnv öğesini depolamak için pthread_setspecific()
ile birlikte
pixel-local-storage; ele geçirilmeden önce yıkıcınıza geçer.
argüman.)
jclass, jmethodID ve jfieldID
Bir nesnenin alanına yerel koddan erişmek isterseniz aşağıdakileri yaparsınız:
-
FindClass
ile sınıfın sınıf nesnesi referansını alma -
GetFieldID
ile alanın alan kimliğini alma - Alanın içeriğini aşağıdaki gibi uygun bir metinle alın:
GetIntField
.
Benzer şekilde, bir yöntemi çağırmak için önce bir sınıf nesnesi referansı, ardından bir yöntem kimliği alırsınız. Kimlikler genellikle dahili çalışma zamanı veri yapılarına işaretçiler oluşturur. Bunları aramak için birkaç dize karşılaştırması gerekebilir ancak bu bilgilere sahip olduğunuzda alanı almak veya yöntemi çağırmak için yapılan gerçek çağrı çok hızlıdır.
Performans önemliyse değerlere bir kez bakmak ve sonuçları önbelleğe almak faydalı olur yerel kodunuzda da kullanabilirsiniz. İşlem başına bir JavaVM sınırı olduğu için bu verilerin statik bir yerel yapıda depolanmasını sağlar.
Sınıf referansları, alan kimlikleri ve yöntem kimlikleri, sınıf yüklenene kadar geçerlidir. Sınıflar
yalnızca ClassLoader ile ilişkili tüm sınıflar atık toplanabiliyorsa kaldırılır;
Android'de imkansız olmayacak ancak nadir bir durum. Bununla birlikte,
jclass
bir sınıf referansıdır ve bir çağrıyla korunması gerekir
NewGlobalRef
adresine (sonraki bölüme bakın).
Bir sınıf yüklendiğinde kimlikleri önbelleğe almak ve sınıf yüklenip yeniden yüklenirse kimlikleri otomatik olarak yeniden önbelleğe almak istiyorsanız kimlikleri başlatmanın doğru yolu, uygun sınıfa aşağıdaki gibi bir kod parçası eklemektir:
Kotlin
companion object { /* * We use a static class initializer to allow the native code to cache some * field offsets. This native function looks up and caches interesting * class/field/method IDs. Throws on failure. */ private external fun nativeInit() init { nativeInit() } }
Java
/* * We use a class initializer to allow the native code to cache some * field offsets. This native function looks up and caches interesting * class/field/method IDs. Throws on failure. */ private static native void nativeInit(); static { nativeInit(); }
C/C++ kodunuzda, kimlik aramalarını gerçekleştiren bir nativeClassInit
yöntemi oluşturun. Kod
, sınıf ilk kullanıma hazırlandığında bir kez yürütülür. Sınıf daha sonra kaldırılıp yeniden yüklenirse tekrar yürütülür.
Yerel ve genel referanslar
Her bağımsız değişken yerel bir yönteme iletildi ve neredeyse her nesne döndürüldü JNI işlevi "yerel başvurudur". Bu, aracın geçerli iş parçacığındaki geçerli yerel yöntemin süresini gösterir. Nesnenin kendisi yerel yöntemden sonra da hayatta kalmaya devam etse bile referans geçerli olmaz.
Bu, aşağıdakiler dahil jobject
alanının tüm alt sınıfları için geçerlidir
jclass
, jstring
ve jarray
.
(Genişletilmiş JNI kontrolleri etkinleştirildiğinde çalışma zamanı, referansların yanlış kullanımıyla ilgili çoğu durum hakkında sizi uyarır.)
Yerel olmayan referanslar almanın tek yolu NewGlobalRef
ve NewWeakGlobalRef
işlevleridir.
Bir referansı daha uzun süre saklamak istiyorsanız
"global" bir referans noktası olarak kabul edilir. NewGlobalRef
işlevi, yerel referansı bağımsız değişken olarak alır ve genel bir referans döndürür.
Siz arayana kadar genel referansın geçerli olacağı garanti edilir
DeleteGlobalRef
Bu kalıp, döndürülen jclass önbelleğe alındığında yaygın olarak kullanılır
kaynak: FindClass
, ör.:
jclass localClass = env->FindClass("MyClass"); jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
Tüm JNI yöntemleri bağımsız değişken olarak hem yerel hem de genel referansları kabul eder.
Aynı nesneye yapılan referansların farklı değerlere sahip olması mümkündür.
Örneğin, art arda yapılan aramaların ve
Aynı nesne üzerindeki NewGlobalRef
farklı olabilir.
İki referansın aynı nesneye atıfta bulunup bulunmadığını görmek için IsSameObject
işlevini kullanmanız gerekir. Referansları yerel kodda ==
ile asla karşılaştırmayın.
Bunun sonuçlarından biri,
Nesne referanslarının sabit veya benzersiz olduğu varsayılmamalıdır
yerel kodda. Bir nesneyi temsil eden değer, bir yöntemin çağrılmasından diğerine farklı olabilir ve iki farklı nesnenin art arda yapılan çağrılarda aynı değere sahip olması mümkündür. Kullanma
Anahtar olarak jobject
değerleri.
Programcıların "aşırı miktarda tahsis etmemesi" gerekir. yerel referanslar. Pratikte bu,
çok sayıda yerel referans oluşturuyorsanız (örneğin, bir dizi diziyi incelerken)
bunları manuel olarak açmak istiyorsanız
DeleteLocalRef
kullanın. İlgili içeriği oluşturmak için kullanılan
yalnızca
16 yerel referans sunar. Dolayısıyla, bundan daha fazlasına ihtiyacınız olursa gerektiğinde silebilirsiniz.
Daha fazla yer ayırtmak için PushLocalFrame
/EnsureLocalCapacity
.
jfieldID
ve jmethodID
öğelerinin opak olduğuna dikkat edin
nesne referansları değil, türlerdir ve
NewGlobalRef
. Ham veriler
GetStringUTFChars
gibi işlevler tarafından döndürülen işaretçiler
ve GetByteArrayElements
birer nesne değildir. (
ve eşleşen Sürüm çağrısına kadar geçerlidir.)
Olağandışı bir durumdan ayrı olarak bahsetmek isteriz. AttachCurrentThread
ile yerel bir iş parçacığı eklerseniz çalıştırdığınız kod, iş parçacığı ayrılana kadar yerel referansları hiçbir zaman otomatik olarak serbest bırakmaz. Herhangi bir yerel
referansın manuel olarak silinmesi gerekir. Genel olarak, tüm yerel
bir döngü içinde yerel referanslar oluşturan kodun muhtemelen bazı manuel
silme.
Global referansları kullanırken dikkatli olun. Evrensel referanslar bazen kaçınılmaz olabilir ancak bu referansların hata ayıklama işlemi zordur ve teşhis edilmesi zor bellek (yanlış) davranışlarına neden olabilir. Diğer tüm koşullar aynı olduğunda, daha az genel referans içeren bir çözüm muhtemelen daha iyidir.
UTF-8 ve UTF-16 dizeleri
Java programlama dili UTF-16 kullanır. JNI, kolaylık sağlamak için Değiştirilmiş UTF-8 ile de çalışan yöntemler sağlar. İlgili içeriği oluşturmak için kullanılan Değiştirilmiş kodlama, \u0000 değerini 0x00 yerine 0xc0 0x80 olarak kodladığından C kodu için yararlıdır. Bunun iyi tarafı, C stili sıfır sonlandırılmış dizelere sahip olabileceğiniz için standart libc dizesi işlevleriyle kullanıma uygundur. Bunun dezavantajı, JNI'ye rastgele UTF-8 verileri ileterek bunların düzgün çalışmasını bekleyememenizdir.
String
öğesinin UTF-16 temsilini almak için GetStringChars
öğesini kullanın.
UTF-16 dizelerinin sıfır sonlandırmalı olmadığını ve \u0000 dizelerine izin verildiğini unutmayın.
Bu nedenle, jchar
işaretçisinin yanı sıra dize uzunluğunu da dikkate almanız gerekir.
Get
dizeleri için Release
işlemi yapmayı unutmayın. Dize işlevleri, yerel referanslar yerine ilkel verilere C stili işaretçiler olan jchar*
veya jbyte*
döndürür. Release
çağrılana kadar geçerli oldukları garanti edilir. Yani yerel yöntem döndürüldüğünde serbest bırakılmazlar.
NewStringUTF'ye iletilen veriler Değiştirilmiş UTF-8 biçiminde olmalıdır. CEVAP
yaygın hatalardan biri dosyadan veya ağ akışından karakter verilerinin okunmasıdır
ve filtrelemeden NewStringUTF
öğesine aktarmak için kullanılır.
Verilerin geçerli MUTF-8 (veya uyumlu bir alt küme olan 7-bit ASCII) olduğunu bilmiyorsanız
geçersiz karakterleri çıkarmanız veya uygun Değiştirilmiş UTF-8 biçimine dönüştürmeniz gerekir.
Aksi takdirde, UTF-16 dönüşümü büyük olasılıkla beklenmedik sonuçlar doğurabilir.
Emülatörler için varsayılan olarak etkin olan CheckJNI, dizeleri tarar.
ve geçersiz giriş alırsa sanal makineyi iptal eder.
Android 8'den önce, Android GetStringChars
içinde kopyalama gerektirmediğinden, UTF-16 dizeleriyle çalışmak genellikle daha hızlıydı. GetStringUTFChars
ise UTF-8'e ayırma ve dönüştürme gerektiriyordu.
Android 8, String
gösterimini karakter başına 8 bit kullanacak şekilde değiştirdi
(hafızadan tasarruf etmek için) tecrübe edinmiş ve
taşıma
yardımcı olur. Bu özellikler, ART'ın GetStringCritical
için bile kopya oluşturmadan String
verilerine işaretçi sağlayabileceği durumların sayısını büyük ölçüde azaltır. Ancak, dizelerin çoğu kod tarafından işlenirse
kısa olduğunda, çoğu durumda dağıtımdan ve dağıtımdan kaçınmak
yığından ayrılmış bir arabellek ve GetStringRegion
veya
GetStringUTFRegion
. Örnek:
constexpr size_t kStackBufferSize = 64u; jchar stack_buffer[kStackBufferSize]; std::unique_ptr<jchar[]> heap_buffer; jchar* buffer = stack_buffer; jsize length = env->GetStringLength(str); if (length > kStackBufferSize) { heap_buffer.reset(new jchar[length]); buffer = heap_buffer.get(); } env->GetStringRegion(str, 0, length, buffer); process_data(buffer, length);
Temel diziler
JNI, dizi nesnelerinin içeriğine erişmek için işlevler sağlar. Nesne dizilerine her defasında tek bir girişten erişilmesi gerekirken, temel öğeler, doğrudan C ile belirtilmiş gibi okunabilir ve yazılabilir.
Get<PrimitiveType>ArrayElements
çağrı ailesi, sanal makine uygulamasını kısıtlamadan arayüzü mümkün olduğunca verimli hale getirmek için çalışma zamanında gerçek öğelere işaretçi döndürmesine veya biraz bellek ayırıp kopya oluşturmasına olanak tanır. Her iki durumda da, ham işaretçi
İlgili Release
çağrısına kadar geçerli olacağı garanti edilir
yayınlanır (veriler kopyalanmazsa dizi nesnesinin
sabitlenir ve yığının sıkıştırılması işleminin bir parçası olarak konumu değiştirilemez).
Get
her dizi için Release
yapmanız gerekir. Ayrıca, Get
çağrısı başarısız olursa, kodunuzun NULL (boşluk) Release
işlemini denemediğinden emin olmalısınız.
devam ettirin.
Verilerin kopyalanıp kopyalanmadığını,
isCopy
bağımsız değişkeni için NULL olmayan işaretçi. Bu durum nadiren faydalıdır.
Release
çağrısı, üç değerden birine sahip olabilecek bir mode
bağımsız değişkeni alır. Çalışma zamanı tarafından gerçekleştirilen işlemler,
verinin bir işaretçisini mi yoksa bir kopyasını mı döndürdüğünü?
0
- Gerçek: Dizi nesnesinin sabitlemesi kaldırıldı.
- Kopyala: Veriler tekrar kopyalanır. Kopyayı içeren arabellek serbest bırakılır.
JNI_COMMIT
- Gerçek: Hiçbir şey yapmaz.
- Kopyala: Veriler geri kopyalanır. Kopyayı içeren arabellek serbest bırakılmaz.
JNI_ABORT
- Gerçek: dizi nesnesi sabitlenmemiş. Daha önce yazma işlemleri iptal edilmez.
- Kopyala: Kopyanın bulunduğu arabellek serbest bırakılır ve bu arabellekteki değişiklikler kaybolur.
isCopy
işaretini kontrol etmenin bir nedeni, bir dizi üzerinde değişiklik yaptıktan sonra Release
işlevini JNI_COMMIT
ile çağırmanız gerekip gerekmediğini bilmektir. Değişiklik yapma ve dizinin içeriğini kullanan kodu yürütme arasında geçiş yapıyorsanız işlemsiz taahhüt işlemini atlayabilirsiniz. İşareti kontrol etmenin başka bir olası nedeni de
JNI_ABORT
için etkili bir çözüm olabilir. Örneğin, bir dizi almak, yerinde değiştirmek, parçaları diğer işlevlere iletmek ve ardından değişiklikleri silmek isteyebilirsiniz. JNI'nin sizin için yeni bir kopya oluşturduğunu biliyorsanız başka bir "düzenlenebilir" kopya oluşturmanız gerekmez. JNI testi geçerse
kendi kopyanızı oluşturmanız gerekir.
Aşağıdaki durumlarda Release
çağrısını atlayabileceğinizi varsaymak yaygın bir hatadır (örnek kodda tekrarlanmıştır)
*isCopy
yanlış. Böyle bir durum söz konusu değildir. Kopya arabelleği yoksa
orijinal bellek sabitlenmelidir ve bu işlem
karar veriyor.
Ayrıca JNI_COMMIT
işaretinin diziyi serbest bırakmadığını unutmayın.
ve Release
adlı kişiyi farklı bir işaretle tekrar çağırmanız gerekir
kazanacaksınız.
Bölge aramaları
Get<Type>ArrayElements
gibi aramalara alternatif bir yöntem vardır
ve GetStringChars
verileri içeri veya dışarı kopyalamaktır. Aşağıdakileri göz önünde bulundurun:
jbyte* data = env->GetByteArrayElements(array, NULL); if (data != NULL) { memcpy(buffer, data, len); env->ReleaseByteArrayElements(array, data, JNI_ABORT); }
Bu işlev, diziyi alır, ilk len
bayt öğesini kopyalar ve ardından diziyi serbest bırakır. Uygulamaya bağlı olarak Get
çağrısı, dizi içeriğini sabitler veya kopyalar.
Kod, verileri kopyalar (belki ikinci kez) ve ardından Release
işlevini çağırır. Bu durumda JNI_ABORT
, üçüncü bir kopya oluşturulmamasını sağlar.
Aynı işlemi daha basit bir şekilde gerçekleştirebilirsiniz:
env->GetByteArrayRegion(array, 0, len, buffer);
Bunun birkaç avantajı vardır:
- 2 yerine bir JNI çağrısı gerektirir ve bu da yükü azaltır.
- Sabitleme veya ekstra veri kopyaları gerektirmez.
- Unutma riski olmadan programcı hatası riskini azaltır
işlevini çağırın.
Release
Benzer şekilde, Set<Type>ArrayRegion
çağrısını da kullanabilirsiniz
veya GetStringRegion
ya da
GetStringUTFRegion
String
.
İstisnalar
Bir istisna beklemedeyken çoğu JNI işlevini çağırmamalısınız.
Kodunuzun istisnayı (işlevin döndürdüğü değer, ExceptionCheck
veya ExceptionOccurred
aracılığıyla) fark etmesi ve döndürmesi ya da istisnayı temizleyip ele alması beklenir.
Bir istisna dışında yalnızca JNI işlevlerini çağırmanıza izin verilir bekleyen:
DeleteGlobalRef
DeleteLocalRef
DeleteWeakGlobalRef
ExceptionCheck
ExceptionClear
ExceptionDescribe
ExceptionOccurred
MonitorExit
PopLocalFrame
PushLocalFrame
Release<PrimitiveType>ArrayElements
ReleasePrimitiveArrayCritical
ReleaseStringChars
ReleaseStringCritical
ReleaseStringUTFChars
Birçok JNI çağrısı istisna oluşturabilir ancak genellikle hatayı kontrol etmenin daha basit bir yolunu sunar. Örneğin, NewString
NULL olmayan bir değer içeriyorsa istisna olup olmadığını kontrol etmeniz gerekmez. Ancak bir yöntemi çağırırsanız (CallObjectMethod
gibi bir işlev kullanarak) istisna atılmışsa döndürülen değer geçerli olmayacağından her zaman istisna olup olmadığını kontrol etmeniz gerekir.
Yönetilen kod tarafından oluşturulan istisnaların, yerel yığını geri döndürmediğini unutmayın.
çerçeveler. (Android'de genellikle önerilmeyen C++ istisnaları,
C++ kodundan yönetilen koda JNI geçiş sınırında atılır.)
JNI Throw
ve ThrowNew
talimatları, mevcut iş parçacığında yalnızca bir istisna işaretçisi ayarlar. Yönetilen hizmet uygulamasına döndüğünüzde
yoksa istisna not edilir ve uygun şekilde işlenir.
Yerel kod, ExceptionCheck
veya ExceptionOccurred
'ü çağırarak bir istisnayı "yakalayabilir" ve ExceptionClear
ile temizleyebilir. Her zamanki gibi
istisnaları işleme alınmadan silmek sorunlara yol açabilir.
Throwable
nesnesini işlemek için yerleşik işlev yok
gerekir, bu nedenle (diyelim) istisna dizesini almak istiyorsanız
Throwable
sınıfını bulmak için yöntem kimliğini arayın.
getMessage "()Ljava/lang/String;"
komutunu çağırır ve sonuç
NULL olmayan bir şey almak için GetStringUTFChars
kullanın
printf(3)
veya eşdeğeri bir cihaza aktarabilirsiniz.
Genişletilmiş kontrol
JNI çok az hata kontrolü yapar. Hatalar genellikle kilitlenmeye neden olur. Android ayrıca, JavaVM ve JNIEnv işlev tablosu işaretçilerinin standart uygulamayı çağırmadan önce genişletilmiş bir dizi kontrol gerçekleştiren işlev tablolarına geçirildiği CheckJNI adlı bir mod da sunar.
Ek kontroller şunlardır:
- Diziler: Negatif boyutlu bir dizi ayırmaya çalışma.
- Kötü işaretçiler: JNI çağrısına kötü bir jarray/jclass/jobject/jstring iletmek veya null olmayan bir bağımsız değişkenle bir JNI çağrısına NULL işaretçisi iletmek.
- Sınıf adları: JNI çağrısına "java/lang/String" sınıf adı stilinden başka bir şey aktarılması.
- Kritik çağrılar: "Kritik" bir alma işlemi ile ilgili yayın arasında JNI çağrısı yapma.
- Doğrudan ByteBuffer'lar:
NewDirectByteBuffer
bağımsız değişkenine hatalı bağımsız değişkenler aktarılıyor. - İstisnalar: Bekleyen bir istisna varken JNI araması yapmak.
- JNIEnv*s: Yanlış iş parçacığından bir JNIEnv* kullanma.
- jfieldID'ler: NULL jfieldID kullanma veya bir alanı yanlış türde bir değere ayarlamak için jfieldID kullanma (örneğin bir String alanına bir StringBuilder değeri atamaya çalışmak) veya bir örnek alanını ayarlamak üzere statik alan için jfieldID kullanmak ya da bir sınıftan jfieldID'yi başka bir sınıfın örnekleriyle birlikte kullanmak.
- jmethodIDs:
Call*Method
JNI çağrısı yaparken yanlış jmethodID türü kullanılıyor: yanlış dönüş türü, statik/statik olmayan uyuşmazlık, "this" için yanlış tür (statik olmayan çağrılar için) veya yanlış sınıf (statik çağrılar için). - Referanslar: Yanlış referans türünde
DeleteGlobalRef
/DeleteLocalRef
kullanılması. - Yayınlama modları: kötü amaçlı bir yayınlama modunu serbest bırakma çağrısına iletme (
0
,JNI_ABORT
veyaJNI_COMMIT
dışında bir mod). - Tür güvenliği: Yerel yönteminizden uyumsuz bir tür döndürme (örneğin, bir Dize döndürmesi için bildirilen yöntemden bir StringBuilder türü döndürme).
- UTF-8: JNI çağrısına geçersiz bir Değiştirilmiş UTF-8 bayt dizisi iletilmesi.
(Yöntemlerin ve alanların erişilebilirliği hâlâ kontrol edilmez: erişim kısıtlamaları yerel kod için geçerli değildir.)
CheckJNI'yı etkinleştirmenin birkaç yolu vardır.
Emülatör kullanıyorsanız CheckJNI varsayılan olarak açıktır.
Root erişimli cihazınız varsa çalışma zamanını CheckJNI etkinken yeniden başlatmak için aşağıdaki komut dizisini kullanabilirsiniz:
adb shell stop adb shell setprop dalvik.vm.checkjni true adb shell start
Her iki durumda da, çalışma zamanı başladığında logcat çıkışınızda aşağıdakine benzer bir kod görürsünüz:
D AndroidRuntime: CheckJNI is ON
Normal bir cihazınız varsa aşağıdaki komutu kullanabilirsiniz:
adb shell setprop debug.checkjni 1
Bu durum, zaten çalışan uygulamaları etkilemez ancak bu tarihten itibaren başlatılan tüm uygulamalarda CheckJNI etkinleştirilir. (Özelliği başka bir değerle değiştirin veya yalnızca yeniden başlatmak CheckJNI tekrar devre dışı bırakılacaktır.) Bu durumda, bir uygulama bir sonraki sefer başlatıldığında logcat çıkışınızda aşağıdakine benzer bir ifade görürsünüz:
D Late-enabling CheckJNI
CheckJNI'yi yalnızca uygulamanız için etkinleştirmek üzere uygulamanızın manifest dosyasında android:debuggable
özelliğini de ayarlayabilirsiniz. Android derleme araçlarının bunu belirli derleme türleri için otomatik olarak yapacağını unutmayın.
Yerel kitaplık
Paylaşılan kitaplıklardan yerel kodu standart
System.loadLibrary
.
Pratikte, Android'in eski sürümlerinin PackageManager'da kuruluma ve yerel kitaplıklar güncellemesinin güvenilir olmaması gerekir. ReLinker projesi, bu ve diğer yerel kitaplık yükleme sorunları için geçici çözümler sunar.
Statik bir sınıftan System.loadLibrary
(veya ReLinker.loadLibrary
) öğesini çağır
Başlatıcı. Bağımsız değişken "dekore edilmemiş" kütüphane adı,
libfubar.so
yüklemek için "fubar"
yılında geçersiniz.
Yerel yöntemlere sahip yalnızca bir sınıfınız varsa System.loadLibrary
çağrısının, söz konusu sınıfın statik başlatıcısında olması mantıklıdır. Aksi takdirde, kitaplığın her zaman ve her zaman erken yüklendiğini bilmek için aramayı Application
'ten yapmak isteyebilirsiniz.
Çalışma zamanında doğal yöntemlerinizin bulunmasının iki yolu vardır. Bunları RegisterNatives
ile açıkça kaydedebilir veya dlsym
ile çalışma zamanında dinamik olarak aranmasına izin verebilirsiniz. RegisterNatives
'ün avantajları, sembollerin varlığını önceden kontrol edebilmeniz ve RegisterNatives
dışında hiçbir şeyi dışa aktarmadığınız için daha küçük ve daha hızlı paylaşılan kitaplıklara sahip olabilmenizdir. Çalışma zamanının işlevlerinizi keşfetmesine izin vermenin avantajı, daha az kod yazmanız gerekmesidir.
RegisterNatives
uygulamasını kullanmak için:
- Bir
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
işlevi sağlayın. JNI_OnLoad
dosyanızda,RegisterNatives
kullanarak tüm doğal yöntemlerinizi kaydedin.- Bir sürüm komut dosyası ile oluşturun (tercih edilir) veya
-fvisibility=hidden
. Böylece yalnızcaJNI_OnLoad
kitaplığınızdan dışa aktarıldı. Bu yöntem daha hızlı ve daha küçük kod üretir ve potansiyel uygulamanıza yüklenen diğer kitaplıklarla çakışmalar (ancak, daha az yararlı yığın izlemeler oluşturur) uygulamanız yerel kodda kilitleniyorsa).
Statik başlatıcı şu şekilde görünmelidir:
Kotlin
companion object { init { System.loadLibrary("fubar") } }
Java
static { System.loadLibrary("fubar"); }
C++'da yazılmış JNI_OnLoad
işlevi şöyle görünmelidir:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // Find your class. JNI_OnLoad is called from the correct class loader context for this to work. jclass c = env->FindClass("com/example/app/package/MyClass"); if (c == nullptr) return JNI_ERR; // Register your class' native methods. static const JNINativeMethod methods[] = { {"nativeFoo", "()V", reinterpret_cast<void*>(nativeFoo)}, {"nativeBar", "(Ljava/lang/String;I)Z", reinterpret_cast<void*>(nativeBar)}, }; int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod)); if (rc != JNI_OK) return rc; return JNI_VERSION_1_6; }
Yerel yöntemlerin "keşfini" kullanmak için bunları belirli bir şekilde adlandırmanız gerekir (ayrıntılar için JNI spesifikasyonuna bakın). Bu, bir yöntemin imzası yanlışsa yöntem ilk kez çağrılana kadar bunu fark etmeyeceğiniz anlamına gelir.
JNI_OnLoad
kullanılarak yapılan FindClass
çağrıları, şu kapsamdaki sınıfları çözümleyecektir:
paylaşılan kitaplığı yüklemek için kullanılan sınıf yükleyici bağlamı. FindClass
, diğer bağlamlardan çağrıldığında Java yığınının en üstündeki yöntemle ilişkili sınıf yükleyiciyi kullanır. Böyle bir sınıf yükleyici yoksa (çağrı, yeni eklenmiş bir yerel iş parçacığından geldiği için) "sistem" sınıf yükleyicisini kullanır. Sistem sınıf yükleyicisi, uygulamanızın sınıflarını bilmez. Bu nedenle, bu bağlamda FindClass
ile kendi sınıflarınızı arayamazsınız. Bu, JNI_OnLoad
'ü sınıfları aramak ve önbelleğe almak için kullanışlı bir yer haline getirir: Geçerli bir jclass
global referansı aldıktan sonra bunu ekteki herhangi bir mesaj dizisinden kullanabilirsiniz.
@FastNative
ve @CriticalNative
ile daha hızlı yerel çağrılar
Yerel yöntemlere
@FastNative
veya
@CriticalNative
(ikisini birden değil) kullanabilirsiniz. Ancak bu ek açıklamalar
kullanımdan önce dikkatli bir şekilde değerlendirilmesi gereken belirli davranış değişikliklerine neden olabilir.
aşağıda bu değişikliklerden kısaca bahsedin. Ayrıntılar için lütfen dokümanları inceleyin.
@CriticalNative
ek açıklaması yalnızca yönetilen nesneleri (parametrelerde veya döndürülen değerlerde ya da örtük this
olarak) kullanmayan yerel yöntemlere uygulanabilir ve bu ek açıklama, JNI geçiş ABI'sini değiştirir. Yerel uygulama,
İşlev imzasından JNIEnv
ve jclass
parametreleri.
@FastNative
veya @CriticalNative
yöntemi yürütülürken çöp
koleksiyonu, önemli çalışmalar için ileti dizisini askıya alamaz ve bunlar engellenebilir. Bunları kullanmayın
ek açıklamalarına yer verir.
Özellikle kod, önemli G/Ç işlemleri gerçekleştirmemeli veya
uzun süre saklanabileceğidir.
Bu ek açıklamalar,
Android 8
ve CTS tarafından test edilmiş herkese açık hale geldi.
Android 14'te API. Bu optimizasyonlar muhtemelen Android 8-13 cihazlarda da çalışır (ancak
güçlü CTS garantileri yoktur), ancak yerel yöntemlerin dinamik araması yalnızca
Android 12 ve sonraki sürümler, JNI RegisterNatives
'ye açık kayıt yapılması kesinlikle gerekir
Android 8-11 sürümlerinde kullanılabilir. Bu ek açıklamalar Android 7'de, ABI uyuşmazlığının
@CriticalNative
için hatalı bağımsız değişken sıralamalarına ve büyük olasılıkla kilitlenmelere yol açabilir.
Bu ek açıklamaların gerekli olduğu, performans açısından kritik öneme sahip yöntemler için
yöntemleriRegisterNatives
ada dayalı "keşif" yerel yöntemlerdir. Uygulama başlatma konusunda optimum performans elde etmek için
dahil etmek için @FastNative
veya @CriticalNative
yöntemlerini çağıran
referans profili oluşturun. Android 12'den beri
derlenmiş yönetilen bir yöntemden @CriticalNative
yerel yöntemine yapılan çağrı neredeyse
tüm bağımsız değişkenler kayıtlara sığdığı sürece (örneğin,
arm64'te 8 integral ve 8 kayan nokta bağımsız değişkeni).
Bazen yerel bir yöntemi ikiye bölmek tercih edilebilir. Bu yöntemlerden biri başarısız olabilecek çok hızlı bir yöntem, diğeri ise yavaş durumları ele alan bir yöntem olabilir. Örnek:
Kotlin
fun writeInt(nativeHandle: Long, value: Int) { // A fast buffered write with a `@CriticalNative` method should succeed most of the time. if (!nativeTryBufferedWriteInt(nativeHandle, value)) { // If the buffered write failed, we need to use the slow path that can perform // significant I/O and can even throw an `IOException`. nativeWriteInt(nativeHandle, value) } } @CriticalNative external fun nativeTryBufferedWriteInt(nativeHandle: Long, value: Int): Boolean external fun nativeWriteInt(nativeHandle: Long, value: Int)
Java
void writeInt(long nativeHandle, int value) { // A fast buffered write with a `@CriticalNative` method should succeed most of the time. if (!nativeTryBufferedWriteInt(nativeHandle, value)) { // If the buffered write failed, we need to use the slow path that can perform // significant I/O and can even throw an `IOException`. nativeWriteInt(nativeHandle, value); } } @CriticalNative static native boolean nativeTryBufferedWriteInt(long nativeHandle, int value); static native void nativeWriteInt(long nativeHandle, int value);
64 bit ile ilgili dikkat edilmesi gereken noktalar
64 bit işaretçiler kullanan mimarileri desteklemek içinlong
Java alanında yerel bir yapıya işaretçi kaydederken int
.
Desteklenmeyen özellikler/geriye dönük uyumluluk
Aşağıdaki istisna dışında tüm JNI 1.6 özellikleri desteklenir:
DefineClass
uygulanmadı. Android, Java bytecode'ları veya sınıf dosyalarını kullanmadığından, ikili sınıf verileri iletme işlemi çalışmaz.
Eski Android sürümleriyle geriye dönük uyumluluk için şunları yapmanız gerekebilir: şunları unutmayın:
- Yerli işlevlerin dinamik olarak aranması
Android 2.0 (Eclair) sürümüne kadar, "$" doğru şekilde yazılmadı "_00024" olarak dönüştürüldü en iyi uygulamaları görelim. Çalışıyor Bu nedenle, açık kayıt kullanılmasını veya doğal yöntemleri öğreneceğiz.
- İleti dizilerini çıkarma
Android 2.0 (Eclair) sürümüne kadar, "çıkmadan önce iş parçacığının ayrılması gerekir" kontrolünü önlemek için
pthread_key_create
yok edicisi işlevi kullanılamıyordu. (Çalışma zamanı ayrıca bir pthread anahtar yıkıcı işlevi kullanır. bu yüzden ilk önce hangisinin çağrıldığını görmek bir yarıştı.) - Zayıf global referanslar
Android 2.2'ye (Froyo) kadar, zayıf global referanslar uygulanmadı. Eski sürümler, kullanım girişimlerini sert bir şekilde reddeder. Desteği test etmek için Android platform sürümü sabitlerini kullanabilirsiniz.
Android 4.0 (Ice Cream Sandwich) sürümüne kadar, zayıf global referanslar yalnızca
NewLocalRef
,NewGlobalRef
veDeleteWeakGlobalRef
. (Bu spesifikasyon, kullanıcıları ve programcıların zayıf küresel kişilere atıfta bulunmalarını dikkate almamanızı öneririz. Bu hiç sınırlayıcı olmamalıdır.)Android 4.0 (Ice Cream Sandwich) ve sonraki sürümlerde, zayıf genel referanslar diğer tüm JNI referansları gibi kullanılabilir.
- Yerel referanslar
Android 4.0 (Ice Cream Sandwich) sürümüne kadar yerel referanslar aslında doğrudan işaretçilerdi. Ice Cream Sandwich dolaylı yol ekledi için gerekli olsa da çöp toplama konusunda JNI hataları eski sürümlerde algılanamıyor. Görüntüleyin Daha ayrıntılı bilgi için ICS'deki JNI Yerel Referans Değişiklikleri başlıklı makaleyi inceleyin.
Android 8.0'den önceki Android sürümlerinde yerel referansların sayısı, sürüme özgü bir sınırla sınırlandırılmıştır. Android 8.0 sürümünden itibaren Android, sınırsız yerel referansları destekler.
GetObjectRefType
ile referans türü belirlemeKullanımın sonucunda, Android 4.0 (Ice Cream Sandwich) sürümüne kadar (yukarıya bakın), uygulamak imkansızdı
GetObjectRefType
doğru. Bunun yerine, var olan zayıf global dünya tablosuna, argümanlara, yerel halka tablosunu, geneller tablosunu da bu sıraya göre sıralayın. Uygulama, işaretleyici olarak işaretlediğinde, referansınızın bu türde yaptıkları incelemedir. Örneğin, statik doğal yönteminize dolaylı bir bağımsız değişken olarak iletilen jclass ile aynı olan bir global jclass üzerindeGetObjectRefType
çağrısı yaptığınızdaJNIGlobalRefType
yerineJNILocalRefType
değerini alırdınız.@FastNative
ve@CriticalNative
Android 7'ye kadar bu optimizasyon ek açıklamaları yoksayılıyordu. ABI
@CriticalNative
uyuşmazlığı, yanlış bağımsız değişkene yol açar ve muhtemelen çöküyor.@FastNative
ve@CriticalNative
yöntemleri için yerel işlevlerin dinamik araması, Android 8-10'da uygulanmadı ve Android 11'de bilinen hatalar içeriyor. JNIRegisterNatives
ile açık kayıt yapmadan bu optimizasyonların kullanılması, Android 8-11'de kilitlenmelere neden olabilir.FindClass
ClassNotFoundException
topunu atıyorAndroid, geriye dönük uyumluluk için
ClassNotFoundException
yerineNoClassDefFoundError
ileFindClass
. Bu davranış, Java yansıma API'si ile tutarlıdırClass.forName(name)
SSS: Neden UnsatisfiedLinkError
alıyorum?
Yerel kod üzerinde çalışırken aşağıdaki gibi bir hata görmek normaldir:
java.lang.UnsatisfiedLinkError: Library foo not found
Bazı durumlarda bu, kitaplığın bulunamadığı anlamına gelir. İçinde
kitaplığın mevcut olduğu ancak dlopen(3)
tarafından açılamadığı durumlar ve
hatanın ayrıntılarını, istisnanın ayrıntı mesajında bulabilirsiniz.
"Kütüphane bulunamadı" istisnalarıyla karşılaşmanızın yaygın nedenleri:
- Kitaplık yok veya uygulama tarafından erişilemiyor. Tekliflerinizi otomatikleştirmek ve optimize etmek için
Var olup olmadığını kontrol etmek için
adb shell ls -l <path>
ve izinler. - Kitaplık NDK ile oluşturulmamıştır. Bu da şunlarla sonuçlanabilir: cihazda bulunmayan işlevlere veya kitaplıklara olan bağımlılıkları.
UnsatisfiedLinkError
hataları sınıfının bir diğer örneği şu şekildedir:
java.lang.UnsatisfiedLinkError: myfunc at Foo.myfunc(Native Method) at Foo.main(Foo.java:10)
Logcat'te şunları görürsünüz:
W/dalvikvm( 880): No implementation found for native LFoo;.myfunc ()V
Bu, çalışma zamanında eşleşen bir yöntem bulmaya çalışıldığı ancak bu işlemin başarısız olduğu anlamına gelir. Bunun yaygın nedenlerinden bazıları şunlardır:
- Kitaplık yüklenmiyor. Logcat çıkışında şunu kontrol edin: yaklaşık 75 gün.
- Ad veya imza uyuşmazlığı nedeniyle yöntem bulunamıyor. Bu durum genellikle şu nedenlerden kaynaklanır:
- Geç yöntem araması için, C++ işlevlerini bildirememe
extern "C"
ile uygun şekilde görünürlük (JNIEXPORT
). Dondurma'dan önce Sandwich, JNIEXPORT makrosu hatalıydı ve bu nedenle eskijni.h
çalışmaz.arm-eabi-nm
kullanabilirsiniz simgeleri kitaplıkta göründükleri şekilde görmek için; bakıyorlarsa karışık (_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass
gibi bir şey)Java_Foo_myfunc
yerine) veya simge türü küçük "t" 'T' harfi yerine kullanıyorsanız beyanı düzenleyin. - Açık kayıt için,
yöntem imzası da dahildir. Kayıt çağrısına ilettiğiniz değerin, günlük dosyasında bulunan imzayla eşleştiğinden emin olun.
'B'yi unutmayın
byte
ve "Z"boolean
. İmzalardaki sınıf adı bileşenleri "L" ile başlar, ";" ile biter. "/" kullanın yerine "$" kullanın ve paket/sınıf adlarını ayırın ayırmak için iç sınıf adlarının (Ljava/util/Map$Entry;
, örneğin).
- Geç yöntem araması için, C++ işlevlerini bildirememe
JNI üstbilgilerini otomatik olarak oluşturmak için javah
kullanmak bazı sorunların önlenmesine yardımcı olabilir.
SSS: FindClass
sınıfımı neden bulamadı?
(Bu tavsiyelerin çoğu, GetMethodID
veya GetStaticMethodID
içeren yöntemler ya da GetFieldID
veya GetStaticFieldID
içeren alanlar bulunamadığında da geçerlidir.)
Sınıf adı dizesinin doğru biçimde olduğundan emin olun. JNI sınıf adları paket adıyla başlar ve java/lang/String
gibi eğik çizgilerle ayrılır. Bir dizi sınıfını arıyorsanız
uygun sayıda köşeli parantezle ve
sınıfı 'L' ile de sarmalamalıdır ";", yani 2 boyutlu bir dizi
String
, [Ljava/lang/String;
olacaktı.
Dahili bir sınıf arıyorsanız "$" kullanın tıklayın. Genel olarak
.class dosyasında javap
kullanmak,
dahili adı.
Kod küçültmeyi etkinleştirirseniz hangi kodun tutulacağını yapılandırın. Yapılandırılıyor doğru saklama kuralları önemlidir çünkü kod daraltıcı, aksi takdirde sınıfları, yöntemleri veya yalnızca JNI'den kullanılan alanlar olabilir.
Sınıf adı düzgün görünüyorsa bir sınıf yükleyiciyle karşılaşıyor olabilirsiniz
. FindClass
, sınıf aramasını şurada başlatmak istiyor:
kodunuzla ilişkili sınıf yükleyicisi. Aşağıdaki gibi görünen çağrı yığınını inceler:
Foo.myfunc(Native Method) Foo.main(Foo.java:10)
En üstteki yöntem Foo.myfunc
'tür. FindClass
, Foo
sınıfıyla ilişkili ClassLoader
nesnesini bulur ve kullanır.
Bu işlem genellikle istediğiniz sonucu verir. Aşağıdaki koşullar söz konusu olduğunda
kendiniz bir ileti dizisi oluşturabilirsiniz (örneğin pthread_create
numaralı telefonu arayarak)
AttachCurrentThread
ile ekleyerek). Şimdi orada
uygulamanızdaki yığın çerçeveleri olmamalıdır.
FindClass
işlevini bu iş parçacığında çağırırsanız JavaVM, uygulamanızla ilişkili olan yerine "sistem" sınıf yükleyicisinde başlatılır. Bu nedenle, uygulamaya özgü sınıfları bulma girişimleri başarısız olur.
Bu sorunu çözmenin birkaç yolu vardır:
FindClass
aramanızı şu sürede bir kez yapın:JNI_OnLoad
ve sınıf referanslarını daha sonrası için önbelleğe alma pek de iyi olmadığını unutmayın. Yürütme işlemi kapsamında yapılan tümFindClass
çağrılarıJNI_OnLoad
, şununla ilişkili sınıf yükleyiciyi kullanacak:System.loadLibrary
adlı fonksiyon (bir özel bir kurala tabidir). Uygulama kodunuz kitaplığı yüklüyorsaFindClass
doğru sınıf yükleyiciyi kullanacaktır.- Yerel yönteminizi bir sınıf bağımsız değişkeni alacak şekilde tanımlayıp
Foo.class
değerini ileterek sınıfın bir örneğini ihtiyacı olan işlevlere aktarın. - Bir yerde
ClassLoader
nesnesinin referansını önbelleğe alın elinizin altında bulunabilir ve doğrudanloadClass
araması yapabilirsiniz. Bu, biraz çaba sarf ettim.
SSS: Ham verileri yerel kodla nasıl paylaşabilirim?
Büyük ölçekli bir kitleye erişmeniz gereken hem yönetilen hem de yerel koddan alınan ham veri tamponu oluşturur. Yaygın örnekler bit eşlemlerin veya ses örneklerinin işlenmesini içerir. İki temel yaklaşım vardır.
Verileri byte[]
içinde saklayabilirsiniz. Bu sayede yönetilen koddan çok hızlı erişim elde edebilirsiniz. Ancak yerel tarafta, verileri kopyalamak zorunda kalmadan erişebileceğiniz garanti edilmez. Bazı uygulamalarda GetByteArrayElements
ve GetPrimitiveArrayCritical
, yönetilen yığıntaki ham verilere ait gerçek işaretçileri döndürür ancak bazılarında yerel yığınta bir arabellek ayırır ve verileri buraya kopyalar.
Alternatif olarak verileri doğrudan bayt arabelleğinde saklayabilirsiniz. Bu
java.nio.ByteBuffer.allocateDirect
ile oluşturulabilir veya
JNI NewDirectByteBuffer
işlevi. Normalden farklı
depolama alanı, yönetilen yığına ayrılmaz ve depolama alanı
her zaman doğrudan yerel koddan erişilebilir (adresi alın)
GetDirectBufferAddress
ile birlikte). Doğrudan bağlı olarak
yönetilen koddan verilere erişilerek bayt arabellek erişimi uygulanır
çok yavaş olabilir.
Hangisini kullanacağınız iki faktöre bağlıdır:
- Veri erişimlerinin çoğu Java veya C/C++ ile yazılmış koddan mı gerçekleşecek?
- Veriler sonunda bir sistem API'sine iletiliyorsa hangi biçimde olmalıdır? (Örneğin, veriler sonunda bir byte[] alan bir işleve iletiliyorsa doğrudan
ByteBuffer
işlemeyi yapmak akıllıca olmayabilir.)
Net bir kazanan yoksa doğrudan bir bayt arabelleği kullanın. Kendileri için destek ve performans artışı gelecek sürümlerde artacaktır.