HOME
  Security
   Software
    Hardware
  
FPGA
  CPU
   Android
    Raspberry Pi
  
nLite
  Xcode
   etc.
    ALL
  
LINK
BACK
 

2020/10/30

Android JNI NDKの C言語側で R.rawや Assetのファイルを FileDescriptor経由で直接読み込む方法 Android JNI NDKの C言語側で R.rawや Assetのファイルを FileDescriptor経由で直接読み込む方法

(Android JNIの C言語で FileDescriptor経由でダイレクトに rawリソースを読み込む、メモリ受け渡しやダミーファイル作成が不要)

Tags: [Android開発]




● 2020年も終わりに近づいたので Android JNIをいじります

 前回は 2010年にいじってました。

2010/08/13
Google Android JNI NDKの C/C++言語開発環境の構築のつもり
Google Android JNI NDKの C/C++言語開発環境の構築のつもり

  Android JNI NDKで C言語の Native関数を作って処理速度を高速化


●久しぶりに OpenWnnを Android Studio 4.1でビルド可能な様にいじりました

 そして、OpenWnnで気になる所としては辞書ファイルを JNI NDK側で定義しています。

android / platform / packages / inputmethods / OpenWnn

 辞書ファイルの中身は純粋なバイトデータですが、Androidの APK形式のアプリファイルには実行する CPU(ARMや Intel x86等)の CPU毎の NDK部分のファイルを含める必要があり、その結果、内容的には似て非なる物を複数存在する事になり、それが APKのファイルサイズの肥大化に繋がります。

 1バイトでもケチる性格の私としては、非常に耐え難い事です。
 ※ OpenWnn登場当時は ARM CPUの ARMv5(armeabi)の1種類だけでした

 2020年現在の Androidの NDK JNI Nativeコンパイラでは下記の 4種類の CPUアーキテクチャをサポートしています。(あれ? MIPS君は?息してないの?)
CPUABI名称bit
ARM系armeabi-v7a32bit
ARM系arm64-v8a64bit
Intel系x8632bit
Intel系x86_6464bit

 OpenWnnの場合は、辞書ファイルは日本語と英語の両方で 2.4MB程度となり、2.4MB * 4種類 = 9.6MBが必要になります。

 これを共通の辞書ファイルとして rawや Assetsのリソースファイルとして管理すれば、実行する CPUが何であろうと APKの中には1個だけ含めば良くなるので 2.4MBで済み APKサイズの削減になります。

 逆を言えば 3種類ぶんが不要になるので 2.4MB * 3 = 7.2MBの削減になります。

 と、言う訳で OpenWnnの辞書ファイルを NDK側ではなく rawや Assetsのリソースファイルで管理する方法を模索しました。


● Android App Bundle について aab形式

 Android App Bundle(aab形式)を使うと各端末にとって不要なリソースを省いた APKでインストールされるので結果的にアプリサイズの削減になります。

 aab形式を使う事で上記の問題(JNI側に不要なコードやリソースを複数含む)を解決できますが、私にとってはそれも「許容できない」のです。

Android App Bundle について

Google Play は App Bundle から、デバイス設定ごとに最適化した APK を生成、配信します。
それによって、個々のデバイスでアプリを実行するのに必要なコードとリソースだけがダウンロードされます。
デベロッパー側では、多様なデバイスのサポートを最適化するために複数の APK をビルド、署名、管理する必要がなくなり、
ユーザー側では、よりサイズが小さく、最適化された APK をダウンロードできるようになります。
 重要: 2021 年 8 月より、Google Play での新規アプリの公開は Android App Bundle で行う必要があります。


●あれ? MIPS君は?息してないの?

MIPSアーキテクチャ - Wikipedia

Android Developers - NDK - ガイド - Android ABI

注: これまで NDK は ARMv5(armeabi)、32 ビットおよび 64 ビットの MIPS をサポートしていましたが、こうした ABI のサポートは NDK r17 で削除されました。

Android Developers - NDK - ダウンロード - NDK 変更履歴

古いバージョンの NDK のダウンロード

・ Android NDK、リビジョン r17c(2018 年 6 月)
 ARMv5(armeabi)、MIPS、MIPS64 に対するサポートは終了しました。 これらの ABI をビルドしようとすると、エラーになります。
  android {
      ndkVersion "17.2.4988734"
  }
Windows 32 ビット  android-ndk-r17c-windows-x86.zip  608358310  5bb25bf13fa494ee6c3433474c7aa90009f9f6a9
Windows 64 ビット  android-ndk-r17c-windows-x86_64.zip  650626501  3e3b8d1650f9d297d130be2b342db956003f5992

・ Android NDK、リビジョン r16b(2017 年 12 月) 16.1.4479499
 ARMv5(armeabi)、MIPS、MIPS64 をビルドする場合に必要。(2020年 10月の Android Studio 4.1でも NDK r16bをサポートしている)
  android {
      ndkVersion "16.1.4479499"
  }
Windows 32 ビット  android-ndk-r16b-windows-x86.zip  656720029  becaf3d445a4877ca1a9300a62f0934a4838c7fa
Windows 64 ビット  android-ndk-r16b-windows-x86_64.zip  723301086  f3f1909ed1052e98dda2c79d11c22f3da28daf25


●アプリを 64 ビット要件に対応させましょう 2019年2月12日火曜日

 Google Play で公開する際に 64 ビット版の提供が必須

アプリを 64 ビット要件に対応させましょう 2019年2月12日火曜日

・ 64 ビット要件: デベロッパーにとっての意味
2019 年 8 月 1 日以降:
 ネイティブ コードを含むすべての新規アプリとアプリのアップデートを Google Play で公開する際に、32 ビット版に加えて 64 ビット版の提供が必須になります。

2021 年 8 月 1 日以降:
 Google Play は、64 ビット対応端末に対する 64 ビット版のないアプリの提供を停止します。
 つまり、64 ビット対応端末は、Play Store で 64 ビット版のないアプリを利用できなくなります。


● R.rawや Assetsのリソースファイルの内容を JNI側で処理する方法

 って、ここで Assetの綴りを確認する為に公式で検索したらこんなのが出てきたんだけど?

Android Developers - NDK - Reference - Asset

Asset
#include <asset_manager.h>
#include <asset_manager_jni.h>

オーディオ コンテンツ
アセット:
オーディオ ファイルを assets/ フォルダに格納することで、Android ネイティブ アセット マネージャー API から直接アクセスできるようになります。
これらの API の詳細については、ヘッダー ファイルの
android/asset_manager.h と android/asset_manager_jni.h
をご覧ください。

 ま、問題は Android 1.5 API 3 Cupcakeでも使えるかどうかだけれど。。。
 NDKの AssetManagerは Android OS 2.3以降 Gingerbread API 9以降で対応

 私のアプリは API 3~30までの幅広い APIレベルで動く事を「無駄」に特徴にしています。(単なる無意味な自己満足)

 スーパー エコ システム。

 エコと言えば当然 エコエコアザラク。黒井ミサたん可愛いですよね。
エコエコアザラク - Wikipedia

 そう言えば大昔、電力会社の「エコアイス」のマスコットキャラクター 「エココ」ちゃんと言うのも居ましたね。(正式名称はアイスちゃん)


● Java Native Interface 仕様

Javaネイティブ・インタフェース仕様: 4 - JNI関数
JNI インタフェースの関数とポインタ


● NDK JNI Native側の envを使った関数の呼び出しの記述方法

 envを使った関数(メソッド)の呼び出しの記述方法が C言語と C++言語で異なります。
● NDK JNI側 (C言語)
  clazz = (*env)->FindClass(env, "java/io/FileDescriptor");
  filePath = ( *env )->GetStringUTFChars( env, filePathJ, 0 )

● NDK JNI側 (C++言語)
  clazz = env->FindClass("java/io/FileDescriptor");
  filePath = env->GetStringUTFChars( filePathJ, 0 )


●その1 R.rawや Assetsのリソースファイルの内容を JNI Native側で処理する方法

 その1 ダミーファイルを作成してファイル名を受け渡す方法
 ・Java側で R.rawや Assetsの FileDescriptorを取得して
 ・Java側でファイルの内容を読み込み
 ・NDK側の fopenで読み込める様に Java側でファイルを作成して
 ・Javaから NDKにファイル名情報(String)を渡して
 ・NDK側の fopenでファイルを読み込む方法

 デメリット
 ・ダミーファイルを生成するので端末のストレージを消費する(無駄)

 なお、mallocでメモリを確保する為にファイルサイズを取得していますが、下記の  fstat() を使用してバイナリファイルのサイズを取得しています。
FIO19-C. ファイルサイズの計算に fseek() および ftell() を使用しない

    private String copyRawResourceFile(@RawRes int rawId, @NonNull String fileName) {
        try {
            File rawDir = getDir("raw", Context.MODE_PRIVATE);
            File rawFile = new File(rawDir, fileName);
            final String filepath = rawFile.getAbsolutePath();
            if (rawFile.exists()) {
                return filepath;
            }

            InputStream is = getResources().openRawResource(rawId);
            OutputStream os = new FileOutputStream(rawFile);

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();

            return filepath;

        } catch (Exception e) {

            return null;
        }
    }

    String filepath;
    // /data/user/0/jp.hoge.hoge/app_app/fileName.dat
    long ret = NativeImplJni.hogehoge( filepath );


● NativeImplJni.java
public class NativeImplJni {
    public static final native long hogehoge( String filePath );
}

● NDK JNI側 (C言語)
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

JNIEXPORT jlong JNICALL hogehoge(JNIEnv *env, jobject obj, jstring filePathJ )
{
    const char* filePath;

    if( filePathJ == NULL ||
        ( filePath = ( *env )->GetStringUTFChars( env, filePathJ, 0 ) ) == NULL ) {
        return 0L;
    }

    FILE* fOpenHandle = (void *) fopen(filePath, "rb");
    if (fOpenHandle == NULL) {
        return 0L;
    }

    // FIO19-C. ファイルサイズの計算に fseek() および ftell() を使用しない
    // https://www.jpcert.or.jp/sc-rules/c-fio19-c.html
    struct stat stbuf;
    if (stat(dicLibPath, &stbuf) == -1) {
        fclose(fOpenHandle);
        return 0L;
    }

    unsigned char*      fOpenBuffer;

    file_size = stbuf.st_size;
    fOpenBuffer = (void *) malloc(file_size);
    if (fOpenBuffer == NULL) {
        fclose(fOpenHandle);
        return 0L;
    }

    long read_size = fread(fOpenBuffer, file_size, 1, fOpenHandle);
    if (read_size == file_size) {
        fclose(fOpenHandle);
        return 0L;
    }

    fclose(fOpenHandle);

...

    fOpenBufferをいじくる処理

...

    // 終了時に freeで解放する
    free(fOpenBuffer);

    return 1L;


●その2 R.rawや Assetsのリソースファイルの内容を JNI Native側で処理する方法

 その2 ファイルの中身を受け渡す方法
 ・Java側で R.rawや Assetsの FileDescriptorを取得して
 ・Java側でファイルの内容を読み込み
 ・Javaから NDKにファイルの内容(byte配列、byte[])を渡して
 ・NDK側で byte配列を処理する方法

 デメリット
 ・一時的に読み込む内容と同じサイズのメモリを Java側と JNI Native側とで必要とする(無駄)

● Java側

    byte[] buffer = null;

    AssetFileDescriptor afd;
    afd = getContext().getResources().openRawResourceFd(R.raw.hogehoge_resource);

    int length = (int)afd.getLength();
    buffer = new byte[length];

    FileInputStream fis = afd.createInputStream()
    fis.read(buffer, 0, length);
    fis.close();

    hogehogeJNI( buffer );


● NDK JNI側 (C言語)
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void Java_jp_ne_neko_freewing_HogeHogeApp_HogeClass_hogehogeJNI( JNIEnv* env,
    jobject thiz,
    jbyteArray bufferJ
    )
{
    unsigned long length;
    length = ( *env )->GetArrayLength( env, bufferJ );

    jbyte* buffer = ( *env )->GetByteArrayElements(env, bufferJ, 0);

...

    bufferをいじくる処理

    int pos = 123;
    jbyte b = buffer[pos]

...

    unsigned char*      fOpenBuffer;
    fOpenBuffer = (void *) malloc(length);

    memcpy(fOpenBuffer, buffer, length);

    // 終了時に ReleaseByteArrayElementsで解放する
    (*env)->ReleaseByteArrayElements(env, bufferJ, buffer, 0);

...

    fOpenBufferをいじくる処理

...

    // 終了時に freeで解放する
    free(fOpenBuffer);

    return;


●その3A R.rawや Assetsのリソースファイルの内容を JNI Native側で処理する方法

 この方法は PinyinIMEを参考にしました(まるパク)。
android / platform / packages / inputmethods / PinyinIME

 Android OS 1.5 Cupcake API 3でも動きます!

 その3A FileDescriptorを受け渡す方法
 ・Java側で R.rawや Assetsの FileDescriptorを取得して
 ・Javaから NDKに FileDescriptorを渡して
 ・NDK側で FileDescriptorを取得して fdopenでファイル(中身)を読み込む方法

 ※ NDK側で Java FileDescriptorクラスの private変数 descriptorをリフレクション Reflectionで取得しています。
 ※ Android実装の FileDescriptor.javaは private int descriptor = -1;

 デメリット
 ・リフレクションなので将来の Androidバージョンで使えなくなる可能性

● Java側

    AssetFileDescriptor afd;
    afd = getContext().getResources().openRawResourceFd(R.raw.hogehoge_resource);
    long ret = NativeImplJni.hogehoge( afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength() );

 ※ FileDescriptor単体だとファイルサイズが不明なので afd.getLength()でファイルサイズ情報も JNI Native側に受け渡す
 ※ afd.getStartOffset()も必要!

● NativeImplJni.java
public class NativeImplJni {
    public static final native long hogehoge( FileDescriptor fd, long startOffset, long length );
}

● R.raw.hogehoge_resource
/res/raw/hogehoge_resource.mp3
 等のファイル名で格納しておく(非圧縮!!)
 ※ /res/raw/hogehoge_resource.datと、拡張子が .datの場合は、なぜか読み込みでエラーになるので注意!!

● NDK JNI側 (C言語)
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static struct file_descriptor_offsets_t
{
    jclass mClass;
    jfieldID mDescriptor;
} gFileDescriptorOffsets;

JNIEXPORT jlong JNICALL hogehoge(JNIEnv *env, jobject obj, jobject dicLibFdJ, jlong startOffsetJ, jlong lengthJ )
{

    // Java Reflection
    jclass clazz;
    clazz = (*env)->FindClass(env, "java/io/FileDescriptor");
    // LOG_FATAL_IF(clazz == NULL, "Unable to find Java class java.io.FileDescriptor");
    gFileDescriptorOffsets.mClass = (jclass) (*env)->NewGlobalRef(env, clazz);
    gFileDescriptorOffsets.mDescriptor = (*env)->GetFieldID(env, clazz, "descriptor", "I");
    // LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
    //              "Unable to find descriptor field in java.io.FileDescriptor");


    unsigned char*      fOpenBuffer;

    long file_size = lengthJ;
    long start_offset = startOffsetJ;

    jint fd = ( *env )->GetIntField( env, dicLibFdJ, gFileDescriptorOffsets.mDescriptor );
    int newfd = dup(fd);

    FILE *fOpenHandle = fdopen(newfd, "rb");
    if (NULL == fOpenHandle) {
        close(newfd);
        return 0L;
    }

    if (-1 == fseek(fOpenHandle, start_offset, SEEK_SET)) {
        fclose(fOpenHandle);
        close(newfd);
        return 0L;
    }

    fOpenBuffer = (void *) malloc(file_size);
    if (fOpenBuffer == NULL) {
        fclose(fOpenHandle);
        close(newfd);
        return 0L;
    }

    long read_size = fread(fOpenBuffer, file_size, 1, fOpenHandle);
    if (read_size == file_size) {
        fclose(fOpenHandle);
        close(newfd);
        return 0L;
    }

    fclose(fOpenHandle);
    close(newfd);

...

    fOpenBufferをいじくる処理

...

    // 終了時に freeで解放する
    free(fOpenBuffer);

    return 1L;


●その3B R.rawや Assetsのリソースファイルの内容を JNI Native側で処理する方法

 Android OS 1.5 Cupcake API 3でも動きます!

 その3B FileDescriptorを受け渡す方法
 ・Java側で R.rawや Assetsの FileDescriptorを取得して
 ・Java側でリフレクションで FileDescriptorから fdopenで必要な descriptorを取得して
 ・Javaから NDKに descriptorを渡して
 ・NDK側で fdopenでファイル(中身)を読み込む方法

 ※ Java側で Java FileDescriptorクラスの private変数 descriptorをリフレクション Reflectionで取得しています。
 ※ Androidの FileDescriptor.javaの実装は private int descriptor = -1;
 ※ Android以外の FileDescriptor.javaの実装は private int fd; だったり、private long handle; だったりする

 デメリット
 ・リフレクションなので将来の Androidバージョンで使えなくなる可能性

 ※ FileDescriptorクラスの private変数、Javaから獲るか? Nativeから獲るか?(「打ち上げ花火、下から見るか?横から見るか?」風に読んでね)

● Java側
import java.io.FileDescriptor;
import java.lang.reflect.Field;

    // Java Reflection
    private static int getFileDescriptorDescriptor(FileDescriptor fd) throws NoSuchFieldException, IllegalAccessException {
        Field field = FileDescriptor.class.getDeclaredField("descriptor");
        field.setAccessible(true);
        int value = (Integer) field.get(fd);
        field.setAccessible(false);
        return value;
    }

    AssetFileDescriptor afd;
    afd = getContext().getResources().openRawResourceFd(R.raw.hogehoge_resource);
    final int descriptor = getFileDescriptorDescriptor(afd.getFileDescriptor());
    long ret = NativeImplJni.hogehoge( descriptor, afd.getStartOffset(), afd.getLength() );

● NDK JNI側 (C言語)
JNIEXPORT jlong JNICALL Java_jp_co_omronsoft_openwnn_OpenWnnDictionaryImplJni_createWnnWork
  (JNIEnv *env, jobject obj, jint descriptorJ, jlong startOffsetJ, jlong lengthJ )
{
 読み込み部分は「その3A」と同じ
}


●その4 R.rawや Assetsのリソースファイルの内容を JNI Native側で処理する方法

 その4 Android公式の File Descriptor NDK関数を使用する
 ・Java側で R.rawや Assetsの FileDescriptorを取得して
 ・Javaから NDKに FileDescriptorを渡して
 ・NDK側で File Descriptorクラスでファイル(中身)を読み込む方法

 ※ R.rawにも対応

 デメリット
 ・Android公式なので特に無し(最も効率が良い)
 ・Android OS 2.3以降 Gingerbread API 9以降で対応

Android Developers - NDK - Reference - File Descriptor

/ luni / src / main / native / java_io_FileDescriptor.c
 (実質的に「その3A」と同じ)
 /*
 * These are JNI field IDs for the stuff we're interested in.  They're
 * computed when the class is loaded.
 */
static struct {
    jfieldID    descriptor;       /* int */
    jmethodID   constructorInt;
    jmethodID   setFD;
    jclass      clazz;
} gCachedFields;

static inline int getFd(JNIEnv* env, jobject obj)
{
    return (*env)->GetIntField(env, obj, gCachedFields.descriptor);
}

static void nativeClassInit(JNIEnv* env, jclass clazz)
{
    gCachedFields.clazz = (*env)->NewGlobalRef(env, clazz);
    gCachedFields.descriptor =
        (*env)->GetFieldID(env, clazz, "descriptor", "I");
    if(gCachedFields.descriptor == NULL) {
        jniThrowException(env, "java/lang/NoSuchFieldError", "FileDescriptor");
        return;
    }
    gCachedFields.constructorInt =
        (*env)->GetMethodID(env, clazz, "<init>", "()V");
    if(gCachedFields.constructorInt == NULL) {
        jniThrowException(env, "java/lang/NoSuchMethodError", "<init>()V");
        return;
    }
}


●その5 Assetsのリソースファイルの内容を JNI Native側で処理する方法

NDK のサンプル - ネイティブ オーディオの実装

 その5 Android公式の Assets NDK関数を使用する
 ・Java側で Assetsの AssetManagerを取得して
 ・Javaから NDKに AssetManagerを渡して
 ・NDK側で AssetManagerと Assetのファイル名を指定してファイル(中身)を読み込む方法

 デメリット
 ・Android公式なので特に無し(最も効率が良い)
 ・R.rawには非対応
 ・Android OS 2.3以降 Gingerbread API 9以降で対応

ndk-samples/native-audio/app/src/main/cpp/native-audio-jni.c
● Java側

AssetManager assetManager = getAssets();
createAssetAudioPlayer(assetManager, "background.mp3");

● Assetsファイル

/assets/background.mp3

● NDK JNI側 (C言語)
 // for native asset manager
#include <sys/types.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>

 // for assert
#include <assert.h>

 // create asset audio player
JNIEXPORT jboolean JNICALL
Java_com_example_nativeaudio_NativeAudio_createAssetAudioPlayer(
        JNIEnv* env, jclass clazz,
        jobject assetManager, jstring filename)
{
    SLresult result;

    // convert Java string to UTF-8
    const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL);
    assert(NULL != utf8);

    // use asset manager to open asset by filename
    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
    assert(NULL != mgr);
    AAsset* asset = AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN);

    // release the Java string and UTF-8
    (*env)->ReleaseStringUTFChars(env, filename, utf8);

    // the asset might not be found
    if (NULL == asset) {
        return JNI_FALSE;
    }

    // これ使う?
    // open asset as file descriptor
    off_t start, length;
    int fd = AAsset_openFileDescriptor(asset, &start, &length);
    assert(0 <= fd);
    AAsset_close(asset);

    // これ使う?
    SLDataLocator_AndroidFD loc_fd = { SL_DATALOCATOR_ANDROIDFD, fd, start, length };
    SLDataFormat_MIME format_mime = { SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED };
    SLDataSource audioSrc = { &loc_fd, &format_mime };

...

    // こっち使う? ← こっちの方法で動作を確認しました
    AAsset_read、AAsset_getBuffer、AAsset_getLength

    off_t length = AAsset_getLength(asset);

    unsigned char*      fOpenBuffer;
    long file_size = length;
    fOpenBuffer = (void *) malloc(file_size);

    int ret = AAsset_read(asset, fOpenBuffer, file_size);
    assert(0 <= ret);
    AAsset_close(asset);

    fOpenBufferをいじくる処理

    // 終了時に freeで解放する
    free(fOpenBuffer);

● CMakeLists.txt

find_library(android-lib android)

target_link_libraries(
            hogehogejni
            ${android-lib}
            )
● android/asset_manager.h

 /**
 * Open an asset.
 *
 * The object returned here should be freed by calling AAsset_close().
 */
AAsset* AAssetManager_open(AAssetManager* mgr, const char* filename, int mode);

 /**
 * Close the asset, freeing all associated resources.
 */
void AAsset_close(AAsset* asset);

 /**
 * Attempt to read 'count' bytes of data from the current offset.
 *
 * Returns the number of bytes read, zero on EOF, or < 0 on error.
 */
int AAsset_read(AAsset* asset, void* buf, size_t count);

 /**
 * Get a pointer to a buffer holding the entire contents of the assset.
 *
 * Returns NULL on failure.
 */
const void* AAsset_getBuffer(AAsset* asset);

 /**
 * Report the total size of the asset data.
 */
off_t AAsset_getLength(AAsset* asset);

 /**
 * Report the total size of the asset data. Reports the size using a 64-bit
 * number insted of 32-bit as AAsset_getLength.
 */
off64_t AAsset_getLength64(AAsset* asset);


● AAsset_openFileDescriptorで 0未満のエラーが返ってくる場合

 ※ AAsset_openFileDescriptorは非圧縮で格納されるファイル形式のみ動作可能(mp3, jpg, png等)

解決方法:中身が .txt等でも「わざと」拡張子を .mp3にする。
● android/asset_manager.h

 /**
 * Open a new file descriptor that can be used to read the asset data.
 *
 * Returns < 0 if direct fd access is not possible (for example, if the asset is compressed).
 */
int AAsset_openFileDescriptor(AAsset* asset, off_t* outStart, off_t* outLength);

● off_tの定義
● bits/types.h

typedef unsigned long int __dev_t;
typedef unsigned int __uid_t;
typedef unsigned int __gid_t;
typedef unsigned long int __ino_t;
typedef unsigned long int __ino64_t;
typedef unsigned int __mode_t;
typedef unsigned long int __nlink_t;
typedef long int __off_t;
typedef long int __off64_t;

● sys/types.h

 /* This historical accident means that we had a 32-bit off_t on 32-bit architectures. */
 /* See https://android.googlesource.com/platform/bionic/+/master/docs/32-bit-abi.md */
#if defined(__USE_FILE_OFFSET64) || defined(__LP64__)
typedef int64_t off_t;
typedef off_t loff_t;
typedef loff_t off64_t;
#else
typedef __kernel_off_t off_t;
typedef __kernel_loff_t loff_t;
typedef loff_t off64_t;
#endif


● Assetや rawのファイルを FileDescriptorで読み込むと 50 4B 03 04 0A 00 00 08のバイナリになる場合

 Start Offsetで SEEKが必要!
 fseek(fd, start_offset, SEEK_SET);

 50 4B 03 04 0A 00 00 08は ZIPファイルの先頭の識別子(つまり APKファイルの先頭)
 P K


● Android OS 2.3 API 9以降で Java JDK 1.6になったのか

旧バージョンのサポート

2023年API 34 -Android 14(Upside Down Cake)OpenJDK 8Java 1.8
2022年API 33 -Android 13(Tiramisu)OpenJDK 8Java 1.8
2021年API 31 -Android 12(S)OpenJDK 8Java 1.8
2020年API 30 -Android 11(R)OpenJDK 8Java 1.8
2019年API 29 -Android 10(Q)OpenJDK 8Java 1.8
2018年API 28 -Android 9(Pie)OpenJDK 8Java 1.8
2017年API 27 -Android 8.1(Oreo)OpenJDK 8Java 1.8
2017年API 26Android 8.0(Oreo)OpenJDK 8Java 1.8
~ 壁 ~~~~ 以下は旧バージョン扱い ~~~
2016年API 24 - 25Android 7.x(Nougat)OpenJDK 8Java 1.8
~ 壁 ~~~~ 以下は Java 8+ API desugarが必要 ~~~
2015年API 21 - 23Android 6.0.x(Marshmallow)OpenJDK 7Java 1.7
2014年API 21 - 23Android 5.x(Lollipop)OpenJDK 7Java 1.7
2012年API 16 - 20Android 4.1.x(Jelly Bean) - Android 4.4.x(KitKat)Java JDK 6Java 1.6
~ 壁 ~~~~ 以下は過去の遺物で切捨て ~~~
2010年API 9 - 15Android 2.3.x(Gingerbread) - Android 4.0.x(ICS、Ice Cream Sandwich)Java JDK 6Java 1.6
~ 壁 ~~~~ 以下は黒歴史 ~~~
2009年API 3 - 8Android 1.5(Cupcake) - Android 2.2.x(Froyo)Java JDK 5Java 1.5

・ Java 1.6の場合
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_6
        targetCompatibility JavaVersion.VERSION_1_6
    }

・ Java 1.5の場合(2020年 まだ使える)
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_5
        targetCompatibility JavaVersion.VERSION_1_5
    }
警告: [options] ソース値1.5は廃止されていて、今後のリリースで削除される予定です
警告: [options] ターゲット値1.5は廃止されていて、今後のリリースで削除される予定です
警告: [options] 廃止されたオプションについての警告を表示しないようにするには、-Xlint:オプションを使用します。


● Android JNI Native側でビルドする CPUタイプを明示的に指定する方法

 ABI = Application Binary Interface

Android Developers - NDK - Guides - Android ABIs

● app/build.gradle
android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a', 'x86_64'
        }
    }
}

全部入りの場合 NDK r16bを使う
CPUABI名称bitOpenWnnのバイナリサイズ
ARM系armeabi32bit50708 bytes
ARM系armeabi-v7a32bit46612 bytes
ARM系arm64-v8a64bit67344 bytes
Intel系x8632bit67028 bytes
Intel系x86_6464bit71776 bytes
MIPS系mips32bit139972 bytes
MIPS系mips6464bit83528 bytes
● app/build.gradle
全部入りの場合 NDK r16bを使う
android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
        }
    }
}


● Intel x86系の Androidでも ARMバイナリの JNI Nativeを動かす事が可能

 Houdini Binary Translator

 Houdini is an ARM binary translator developed by Intel .
 It can translate the ARM binary code into the x86 instruction set that can run in an x86 CPU .

https://github.com/AospPlus/android_vendor_intel_houdini

 と言う事はパフォーマンスを重視しなければ ARMバイナリだけで良い?(MIPS君はバイバイ)
● app/build.gradle
android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
        }
    }
}


● Android JNI Native側でログ出力 android.util.Log機能を便利に使う方法

undefined reference to __android_log_write

● NDK JNI側 (C言語)
#include <android/log.h>

#define TAG         "HogeHogeJni"
#define LOGV(...)   __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
#define LOGD(...)   __android_log_print(ANDROID_LOG_DEBUG,   TAG, __VA_ARGS__)
#define LOGI(...)   __android_log_print(ANDROID_LOG_INFO,    TAG, __VA_ARGS__)
#define LOGW(...)   __android_log_print(ANDROID_LOG_WARN,    TAG, __VA_ARGS__)
#define LOGE(...)   __android_log_print(ANDROID_LOG_ERROR,   TAG, __VA_ARGS__)
#define LOGF(...)   __android_log_print(ANDROID_LOG_FATAL,   TAG, __VA_ARGS__)

    long file_size = 123456L;
    LOGD("file_size %08lx", file_size);

● CMakeLists.txt

find_library(log-lib log)

target_link_libraries(
            hogehogejni
            ${log-lib}
            )
● android/log.h

 /**
 * Android log priority values, in increasing order of priority.
 */
typedef enum android_LogPriority {
  /** For internal use only.  */
  ANDROID_LOG_UNKNOWN = 0,

  /** The default priority, for internal use only.  */
  ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */

  /** Verbose logging. Should typically be disabled for a release apk. */
  ANDROID_LOG_VERBOSE,

  /** Debug logging. Should typically be disabled for a release apk. */
  ANDROID_LOG_DEBUG,

  /** Informational logging. Should typically be disabled for a release apk. */
  ANDROID_LOG_INFO,

  /** Warning logging. For use with recoverable failures. */
  ANDROID_LOG_WARN,

  /** Error logging. For use with unrecoverable failures. */
  ANDROID_LOG_ERROR,

  /** Fatal logging. For use when aborting. */
  ANDROID_LOG_FATAL,

  /** For internal use only.  */
  ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */

} android_LogPriority;


● OpenWnnの内部辞書解析

OpenWnnの内部辞書解析

k-utsubo / openwnn
 openwnnのCPP辞書を作成する。

Android > OpenWnn > 辞書インターフェースの解析


● OpenWnnの辞書ファイルのバイトの並びを 16バイト単位でアライメントする

 コンパイラ指定 -align、-malign-double

Understanding x86 vs ARM Memory Alignment on Android

__attribute__((aligned (16)))
static NJ_UINT8 dic_01_data[] __attribute__ ((aligned (16))) = {
    0x4e, 0x4a, 0x44, 0x43, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x32, 0xf7,
    ...
    };

static NJ_UINT8 dic_02_data[] __attribute__ ((aligned (16))) = {
    0x4e, 0x4a, 0x44, 0x43, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x37, 0x99,
    ...
    };

NJ_UINT8* con_data[ ] __attribute__ ((aligned (16))) = {
        con_01_data
    };

NJ_UINT32 dic_size[ ] __attribute__ ((aligned (16))) = {
        78665, 79851, 280537, 7323, 81907, 783246, 5785, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };

    ...
-malign-double
  -malign-double          Align doubles to two words in structs (x86 only)


● ndk-build

 ndk-build スクリプトを使用する物凄く古いビルド方法。

ndk-build



Tags: [Android開発]

●関連するコンテンツ(この記事を読んだ人は、次の記事も読んでいます)

AACの記事から1年経ってデータ バインディングの俺的カンニング帳を作る
AACの記事から1年経ってデータ バインディングの俺的カンニング帳を作る

  Android開発 データ バインディング 虎の巻 Android Studio

Androidのアプリ開発で AAR形式のライブラリを Androidプロジェクトに組み込む方法
Androidのアプリ開発で AAR形式のライブラリを Androidプロジェクトに組み込む方法

  Androidプロジェクトに AAR/JAR形式のライブラリを組み込む方法のバリエーション

Kotlin大嫌い人間が Kotlin言語を必死に勉強する
Kotlin大嫌い人間が Kotlin言語を必死に勉強する

  行末にセミコロン;の無い言語は大嫌い

2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!
2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!

  Androidの開発で AACって何? DataBinding、LiveData、ViewModel、LifecycleObserverについて解説

Androidアプリ作成に必須の多端末に対応するデザイン方法について解説する dipを極める
Androidアプリ作成に必須の多端末に対応するデザイン方法について解説する dipを極める

  Androidの開発で dipって何?密度非依存ピクセル?Density-Independent Pixels?って何者?




[HOME] | [BACK]
リンクフリー(連絡不要、ただしトップページ以外は Web構成の変更で移動する場合があります)
Copyright (c) 2020 FREE WING,Y.Sakamoto
Powered by 猫屋敷工房 & HTML Generator

http://www.neko.ne.jp/~freewing/android/android_ndk_jni_open_file_by_filedescriptor/