JNI, Java Yerel Arayüzüdü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 edinmek için bkz. Kotlin ve Android.
Bu konu hakkında henüz bilginiz yoksa, Java Yerel Arayüzü Spesifikasyonu JNI'nin işleyiş şekli ve sunulan özellikler hakkında fikir edinebilirsiniz. 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 alınması gereken birkaç boyut vardır. JNI çözümünüz şu yönergeleri izlemeye ç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. Veri toplama ve düzenleme işlerinde içerik miktarını en aza verileri ve bunların ne sıklıkta Marshall olması gerektiğini ele alacağız.
- 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, JNI arayüzünüzün bakımını kolaylaştıracaktır. Genellikle eşzamansız işlemleri Kullanıcı arayüzü, eşzamansız güncellemeyi kullanıcı arayüzüyle aynı dilde tutarak güncellenir. Örneğin, JNI aracılığıyla Java kodunda UI iş parçacığından bir C++ işlevi çağırmak iki iş parçacığı (Java programlama dilindeki) arasında geri arama yapmak için 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 tarafından dokunulması gereken veya dokunulması gereken ileti dizisi 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.
- Arayüz kodunuzu kolayca tanımlanmış az sayıda C++ ve Java kaynağında tutun şubelere giderek yeniden düzenleme yapabilirsiniz. JNI otomatik oluşturma yöntemini kullanmayı düşünün kitaplığını gerektiği şekilde ayarlayın.
JavaVM ve JNIEnv
JNI, iki temel veri yapısını tanımlar: "JavaVM" ve "JNIEnv" yer alır. Bunların ikisi de aslında fonksiyon tablolarına işaretçiler içeren araçlar sunar. (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, ileti dizileri arasında bir JNIEnv öğesini 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 iş parçacıkları, çekirdek tarafından planlanan Linux iş parçacıklarıdır. Genelde
yönetilen koddan başlatıldı (Thread.start()
kullanılarak),
ancak başka bir yerde oluşturulup JavaVM
öğesine de eklenebilir. Örneğin,
örnek, pthread_create()
veya std::thread
ile başlayan bir ileti dizisi
AttachCurrentThread()
veya
AttachCurrentThreadAsDaemon()
işlev. İleti dizisi
ekli değilse JNIEnv içermez ve JNI çağrıları yapamaz.
Bir ileti dizisi oluşturmak için genellikle Thread.start()
öğesini kullanmak
çağrısı da yapabilirsiniz. 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. Eğer atık toplama işlemi devam ediyor veya hata ayıklayıcı bir askıya alma işlemi gerçekleştirdi isteğinde bulunmak için Android, bir sonraki JNI çağrısında ileti dizisini duraklatır.
JNI aracılığıyla eklenen ileti dizileri aranmalıdır
DetachCurrentThread()
sonra çıkmadan önce kontrol edin.
Bunu doğrudan kodlamak size zor geliyorsa Android 2.0 (Eclair) ve sonraki sürümlerde
bir yıkıcı tanımlamak için pthread_key_create()
kullanabilir
iş parçacığı çıkmadan önce çağrılacak bir işlev ve
DetachCurrentThread()
adlı kişiyi oradan arayın. (
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 istiyorsanız şunları yapın:
-
FindClass
ile sınıfın sınıf nesnesi referansını alma -
GetFieldID
içeren alanın alan kimliğini alın - Alanın içeriğini aşağıdaki gibi uygun bir metinle alın:
GetIntField
.
Benzer şekilde, bir yöntemi çağırmak için ilk olarak bir sınıf nesnesi referansı ve 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ı bulmak için birkaç dize gerekebilir. ancak karşılaştırmaları yaptıktan sonra, alanı almak veya yöntemi çağırmak için gerçek çağrıyı ç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 kaldırılana kadar geçerli olacaktır. 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 otomatik olarak yeniden önbelleğe almak istiyorsanız Sınıfın yüklemesi kaldırılıp yeniden yüklenirse ilk kullanıma hazırlamanın doğru yöntemi kimlikler, aşağıdakine benzer bir kod parçasını uygun sınıfa 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ın yüklemesi kaldırılırsa ve
ve ardından yeniden yüklenirse işlem 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
.
(Çalışma zamanı, JNI uzantısı uzatıldığında çoğu referans hatalı kullanımı hakkında sizi uyarır:
kontrollerin etkin olduğundan emin olun.)
Yerel olmayan referanslar almanın tek yolu, işlevleri kullanmaktır
NewGlobalRef
ve NewWeakGlobalRef
.
Bir referansı daha uzun süre saklamak istiyorsanız
"global" bir referans noktası olarak kabul edilir. NewGlobalRef
işlevi,
yerel başvuruyu bağımsız değişken olarak kullanı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 verilen 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 işaret edip etmediğini görmek için
IsSameObject
işlevini kullanmanız gerekir. Hiçbir zaman karşılaştırma
yerel kodda ==
içeren referanslar.
Bunun sonuçlarından biri de
Nesne referanslarının sabit veya benzersiz olduğu varsayılmamalıdır
yerel kodda. Bir nesneyi temsil eden değer farklı olabilir
arasında geçiş yapılmasını sağlar ve iki tanesinin
farklı nesneler, ardışık aramalarda aynı değere sahip olabilir. 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ğan dışı bir vakadan ayrı olarak bahsedilmesi gerekir. Yerel bir
AttachCurrentThread
ile iş parçacığınız varsa, çalıştırdığınız kod
İş 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.
Genel referansları kullanırken dikkatli olun. Global referanslar kaçınılmaz olabilir ancak bu tür referanslar zordur ve teşhis edilmesi zor bellek (yanlış) davranışlara neden olabilir. Diğer her şey eşit olduğunda daha az global referansa sahip çözümler muhtemelen daha iyi olacaktır.
UTF-8 ve UTF-16 dizeleri
Java programlama dili UTF-16 kullanır. JNI, kolaylık sağlamak amacıyla Ayrıca değiştirilmiş UTF-8. İ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, standart libc dizesi işlevleriyle kullanıma uygundur. Bunun olumsuz tarafı ise JNI'ye göndermeli ve düzgün çalışmasını beklemelidir.
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. İlgili içeriği oluşturmak için kullanılan
dize işlevleri jchar*
veya jbyte*
değerini döndürür.
yerel referanslar yerine temel verilere işaret eden C stilindeki öğelerdir. Onlar
Release
çağrılana kadar geçerliliği garanti edilir, yani
yerel yöntem geri döndüğünde serbest bırakılır.
NewStringUTF'e 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
hesabına aktarılacak.
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 cihazlarda UTF-16 dizeleriyle çalışmak genellikle daha hızlıydı.
GetStringChars
içinde bir kopya gerekli değildir, ancak
GetStringUTFChars
için ayırma ve UTF-8'e dönüştürülmesi gerekiyordu.
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'nin kötü amaçlı yazılımın neden olduğu
bile olsa kopya oluşturmadan String
verilerine işaretçi sağlayabilir
(GetStringCritical
için). 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 dilinde tanımlanmış gibi okunup yazılabilir.
Arayüzü herhangi bir kısıtlama yapmadan olabildiğince verimli hale getirmek için
Get<PrimitiveType>ArrayElements
çağrı ailesi, çalışma zamanının gerçek öğelere işaret eden bir işaretçi döndürmesine veya
bir miktar bellek ayırın ve bir kopyasını oluşturun. Her iki durumda da, ham işaretçi
ilgili 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ının 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 nadiren
faydalı olur.
Release
çağrısı, aşağıdakileri yapabilecek bir mode
bağımsız değişkeni alır:
üç değerden birine sahip olmalıdı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 tekrar kopyalanır. Kopyayı içeren arabellek serbest bırakılmaz.
JNI_ABORT
- Gerçek: Dizi nesnesinin sabitlemesi kaldırıldı. Daha önce yazma işlemleri iptal edilmez.
- Kopyala: Kopyayı içeren arabellek serbest bırakılır; yapılan değişiklikler kaybolur.
isCopy
işaretini kontrol etmenin bir nedeni de
JNI_COMMIT
ile Release
numaralı telefonu aramanız gerekiyor
bir dizide değişiklik yaptıktan sonra (ör. gerçek zamanlı olarak
ve dizenin içeriğini kullanan bir kod yürütürse,
şunu yapabilir:
işlemsiz kaydı atlayabilirsiniz. İşareti kontrol etmenin başka bir olası nedeni de
JNI_ABORT
için etkili bir çözüm olabilir. Örneğin, ekip arkadaşlarınızın
parçayı diğer fonksiyonlara iletebilir, diziyi yerinde değiştirebilir ve
sonra değişiklikleri silin. JNI'nin web sitesi için yeni bir kopya oluşturduğunu
başka bir "düzenlenebilir" dosyası oluşturmanız kopyala. 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 çağrıları
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, diziyi alır, ilk len
baytı kopyalar
öğelerini çıkarır ve ardından diziyi serbest bırakır. Seçtiğiniz düzenlemenin
uygulanması durumunda Get
çağrısı, diziyi sabitler veya kopyalar
içerik.
Kod, verileri kopyalar (belki ikinci bir kez) ve ardından Release
yöntemini çağırır; bu örnekte
JNI_ABORT
, üçüncü kopya ihtimalinin olmamasını sağlar.
Aynı şeyi daha basit bir şekilde başarabilirsiniz:
env->GetByteArrayRegion(array, 0, len, buffer);
Bunun birkaç avantajı vardır:
- 2 yerine bir JNI çağrısı gerektirir ve böylece ek 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 bu istisnayı fark etmesi beklenir (işlevin döndürdüğü değer aracılığıyla
ExceptionCheck
veya ExceptionOccurred
) ve geri döndüğünüzde
veya istisnayı temizleyip işleyin.
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 olabilir ancak genellikle daha basit bir yöntem
yardımcı olur. Örneğin, NewString
NULL olmayan bir değer içeriyorsa istisna olup olmadığını kontrol etmeniz gerekmez. Ancak,
bir yöntem çağırırsınız (CallObjectMethod
gibi bir işlev kullanarak),
her zaman istisna olup olmadığını kontrol etmeniz gerekir çünkü döndürülen değer
geçerli olur.
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ı yalnızca
geçerli ileti dizisinde bir istisna işaretçisi ayarla. Yönetilen hizmet uygulamasına döndüğünde
istisna olarak not alınır ve uygun şekilde işlenir.
Yerel kod "yakalayabilir" bir istisna için ExceptionCheck
veya
ExceptionOccurred
ve bunu şununla temizleyin:
ExceptionClear
. 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 CheckJNI adında bir mod sunar. Bu modda JavaVM ve JNIEnv işlevi tablo işaretçileri, standart uygulamaya çağrılmadan önce genişletilmiş bir dizi kontrol gerçekleştiren işlev tablolarına geçirilir.
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ı: bir JNI çağrısına sınıf adının "Java/lang/String" stili dışındaki her şeyi iletme.
- Kritik çağrılar: "kritik" alma ile karşılık gelen sürümü arasında bir JNI çağrısı yapma
- Direct ByteBuffers: Hatalı bağımsız değişkenleri
NewDirectByteBuffer
öğesine iletme. - İ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ı yapılırken yanlış jmethodID türünün kullanılması: yanlış döndürme 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ılıyor. - 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: Bir JNI çağrısına geçersiz bir Değiştirilmiş UTF-8 bayt sırası iletme.
(Yöntemlerin ve alanların erişilebilirliği hâlâ kontrol edilmemiştir: 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 etkindir.
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, çalışan uygulamaları etkilemez ancak o andan itibaren başlatılan tüm uygulamalarda CheckJNI etkin olur. (Özelliği başka bir değerle değiştirin veya yeniden başlatmak CheckJNI tekrar devre dışı bırakılacaktır.) Bu durumda, bir uygulama tekrar başlatıldığında logcat çıkışınızda şuna benzer bir şey görürsünüz:
D Late-enabling CheckJNI
Ayrıca uygulamanızın manifest dosyasındaki android:debuggable
özelliğini şu şekilde de ayarlayabilirsiniz:
CheckJNI'yı yalnızca uygulamanız için açın. Android derleme araçlarının bu işlemi
bazı derleme türleri vardır.
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öntemleri olan yalnızca bir sınıfınız varsa
System.loadLibrary
öğesinin bu sınıf için statik başlatıcıda yer alması gerekir. Aksi takdirde
kitaplığın her zaman yüklü olduğunu bildiğiniz için Application
adlı kişiden çağrı yapmak istiyorsunuz.
ve her zaman erken yüklenir.
Çalışma zamanı, yerel yöntemlerinizi iki şekilde bulabilir. Aşağıdaki yöntemlerden birini kullanarak
bunları RegisterNatives
ile kaydedebilir veya çalışma zamanının bunları dinamik olarak
dlsym
ile. RegisterNatives
avantajı, önceden ödeme yapmanızdır.
emin olmak için bir kontrol listesi de mevcuttur. Ek olarak, ortak bir dil kullanımına gerek duymadan
JNI_OnLoad
dışındaki tüm verileri dışa aktararak. Çalışma zamanının, en değerli verilerinizi keşfetmesini
daha az kod yazılmasıdır.
RegisterNatives
uygulamasını kullanmak için:
- Bir
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
işlevi sağlayın. JNI_OnLoad
içinde tüm yerel yöntemleriniziRegisterNatives
kullanarak kaydedin.- Yalnızca
JNI_OnLoad
için-fvisibility=hidden
ile oluşturun 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"); }
Aşağıdaki gibi görünmesi gerekir: JNI_OnLoad
C++ dilinde yazılmış:
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; }
Bunun yerine "Keşif" özelliğini kullanmak için kullanıyorsanız, bu yöntemleri belirli bir şekilde adlandırmanız gerekir (bkz. JNI spesifikasyonu bakın). Bu, bir yöntem imzası yanlışsa, bunu denemeden önce yöntem ilk kez çağrıldığında.
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ı. Diğer
bağlamlardayken FindClass
, sayfanın en üstündeki yöntemle ilişkili sınıf yükleyiciyi kullanır.
Java yığını veya yoksa (çağrı az önce ekli bir yerel ileti dizisinden geldiği için)
"sistem"i kullanır sınıf yükleyicisi. Sistem sınıfı yükleyici, uygulamanızın
o yüzden FindClass
ile kendi sınıflarınızı arayamazsınız.
bağlam. Bu nedenle JNI_OnLoad
, sınıfları aramak ve önbelleğe almak için kullanışlı bir yer haline gelir: bir kez
geçerli bir jclass
genel referansınız var
ekteki herhangi bir ileti 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 kullanabilir (parametrelerde veya döndürülen değerlerde ya da örtülü this
olarak) ve
ek açıklaması, JNI geçiş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, çok hızlı bir yöntem olan bir diğeri ise yavaş durumları ele alır. Ö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, aşağıdakileri kullanmıyor Java bayt kodları veya sınıf dosyaları, böylece ikili sınıf verilerini iletme çalışmaz.
Eski Android sürümleriyle geriye dönük uyumluluk için şunları yapmanız gerekebilir: şunlara dikkat edin:
- Yerel işlevler için dinamik arama
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
pthread_key_create
kullanmak mümkün değildi "iş parçacığının, ilk önce çıkış" onay kutusunu işaretleyin. (Çalışma zamanı ayrıca bir pthread anahtar yıkıcı işlevi kullanır. bu yüzden önce hangisinin çağrıldığını görmek bir yarış olur.) - 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. Tekliflerinizi otomatikleştirmek ve optimize etmek için Android platform sürümü sabit değerlerini test edin.
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) sürümünden itibaren zayıf global referanslar diğer JNI referansları gibi kullanılır.
- Yerel referanslar
Android 4.0 (Ice Cream Sandwich) sürümüne kadar, yerel referanslar yardımcı oluyor. 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'dan ö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'dan 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ırayla görebilirsiniz. Uygulama, işaretleyici olarak işaretlediğinde, referansınızın bu türde yaptıkları incelemedir. Bu demek oluyor ki, örneğin, dünya genelinde gerçekleştirilen bir jclass'deGetObjectRefType
adlı kişiyi aradınız. statik yerel yöntem, yerineJNILocalRefType
JNIGlobalRefType
.@FastNative
ve@CriticalNative
Bu optimizasyon ek açıklamaları Android 7'ye kadar yok sayıldı. ABI
@CriticalNative
uyuşmazlığı, yanlış bağımsız değişkene yol açar ve muhtemelen çöküyor.@FastNative
ve@CriticalNative
yöntemleri Android 8-10 ve sonraki sürümlerde uygulanmamıştır ve Android 11'de bilinen hataları içeriyor. Bu optimizasyonları JNIRegisterNatives
açık kaynaklı kayıt, Android 8-11'de kilitlenmelere yol açabilir.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 ifade, kitaplık bulunamadı. İç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.
"Kitaplık bulunamadı" hatasıyla karşılaşmanızın yaygın nedenleri istisnalar:
- 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
başarısızlık sınıfı şu şekilde görünüyor:
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ının bir eşleştirme yöntemi bulmaya çalıştığı ancak başarısız oldu. Bunun yaygın nedenlerinden bazıları şunlardır:
- Kitaplık yüklenmiyor. Logcat çıkışında şunu kontrol edin: yaklaşık 10 dakika sürer.
- Yöntem, bir ad veya imza uyuşmazlığı nedeniyle bulunamıyor. Bu
genellikle aşağıdakilerden 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. Gönderdiğiniz belgenin
kayıt çağrısı günlük dosyasındaki imzayla eşleşir.
'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 başlıklarını otomatik olarak oluşturmak için javah
kullanmak işe yarayabilir
bazı sorunları
önlemeye çalışır.
SSS: FindClass
sınıfımı neden bulamadı?
(Bu tavsiyenin çoğu, yöntem bulunamaması durumunda da aynı derecede geçerlidir
GetMethodID
, GetStaticMethodID
veya alanlarla
GetFieldID
veya GetStaticFieldID
ile.)
Sınıf adı dizesinin doğru biçimde olduğundan emin olun. JNI dersi
adlar paket adıyla başlar ve eğik çizgiyle ayrılır.
java/lang/String
gibi. 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ğrı yığınını inceler,
Bu, aşağıdaki gibi görünecektir:
Foo.myfunc(Native Method) Foo.main(Foo.java:10)
En üstteki yöntem Foo.myfunc
. FindClass
.
Foo
ile ilişkili ClassLoader
nesnesini bulur
kullanır.
Bu işlem genellikle istediğinizi yapar. 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.
Bu ileti dizisinden FindClass
adlı kişiyi ararsanız
JavaVM, "system" içinde başlatılır ilişkilendirilmiş olan yerine sınıf yükleyici
olduğu için, 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.- Sınıfın bir örneğini gereken işlevlere aktarın
Class bağımsız değişkenini almak için yerel yönteminizi tanımlayarak
ardından
Foo.class
akıyor. - 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 tür ve temel yaklaşımları öğrendiniz.
Verileri bir byte[]
içinde depolayabilirsiniz. Bu şekilde çok hızlı
yönetilen koddan erişme Ancak yerel tarafta ise
verilere kopyalanmayacakları garanti edilmez. İçinde
bazı uygulamalar, GetByteArrayElements
ve
GetPrimitiveArrayCritical
, gerçek işaretçileri
yönetilen yığında ham veri oluşturur, ancak diğerlerinde ise bir tampon ayırır
oluşturup verileri kopyalayın.
Alternatif olarak, veriler doğrudan bir bayt arabelleğinde depolanır. 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.
Hangi seçeneğin kullanılacağı iki faktöre bağlıdır:
- Veri erişimlerinin çoğu Java'da yazılan koddan mı yapılacak C/C++ arasında mı?
- Veriler sonunda bir sistem API'sine aktarılıyorsa,
içinde olması gerekir. (Örneğin, veriler sonunda bir
doğrudan bir bayt üzerinde işlem yapan, bayt[] alan fonksiyon
ByteBuffer
mantıklı 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.