JNI ipuçları

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 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: 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öntemlerinizi RegisterNatives 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 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) 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ü 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ı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'de GetObjectRefType adlı kişiyi aradınız. statik yerel yöntem, yerine JNILocalRefType 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ı JNI RegisterNatives açık kaynaklı kayıt, Android 8-11'de kilitlenmelere yol açabilir.

  • 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 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 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. 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).

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ü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.
  • 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ğ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 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:

  1. Veri erişimlerinin çoğu Java'da yazılan koddan mı yapılacak C/C++ arasında mı?
  2. 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.