JNI ipuçları

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 veya JNI_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ızca JNI_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_createyok 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 ve DeleteWeakGlobalRef. (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ü belirleme

    Kullanı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 üzerinde GetObjectRefType çağrısı yaptığınızda JNIGlobalRefType yerine JNILocalRefType 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. JNI RegisterNatives ile açık kayıt yapmadan bu optimizasyonların kullanılması, Android 8-11'de kilitlenmelere neden olabilir.

  • FindClass ClassNotFoundException topunu atıyor

    Android, geriye dönük uyumluluk için ClassNotFoundException yerine NoClassDefFoundError ile FindClass. Bu davranış, Java yansıma API'si ile tutarlıdır Class.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 eski jni.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).

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üm FindClass ç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üyorsa FindClass 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ğrudan loadClass 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:

  1. Veri erişimlerinin çoğu Java veya C/C++ ile yazılmış koddan mı gerçekleşecek?
  2. 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.