・2020/10/30
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++言語開発環境の構築のつもり
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君は?息してないの?)
CPU | ABI名称 | bit |
ARM系 | armeabi-v7a | 32bit |
ARM系 | arm64-v8a | 64bit |
Intel系 | x86 | 32bit |
Intel系 | x86_64 | 64bit |
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 8 | Java 1.8 |
2022年 | API 33 - | Android 13(Tiramisu) | OpenJDK 8 | Java 1.8 |
2021年 | API 31 - | Android 12(S) | OpenJDK 8 | Java 1.8 |
2020年 | API 30 - | Android 11(R) | OpenJDK 8 | Java 1.8 |
2019年 | API 29 - | Android 10(Q) | OpenJDK 8 | Java 1.8 |
2018年 | API 28 - | Android 9(Pie) | OpenJDK 8 | Java 1.8 |
2017年 | API 27 - | Android 8.1(Oreo) | OpenJDK 8 | Java 1.8 |
2017年 | API 26 | Android 8.0(Oreo) | OpenJDK 8 | Java 1.8 |
~ 壁 ~ | | ~~~ 以下は旧バージョン扱い ~~~ | | |
2016年 | API 24 - 25 | Android 7.x(Nougat) | OpenJDK 8 | Java 1.8 |
~ 壁 ~ | | ~~~ 以下は Java 8+ API desugarが必要 ~~~ | | |
2015年 | API 21 - 23 | Android 6.0.x(Marshmallow) | OpenJDK 7 | Java 1.7 |
2014年 | API 21 - 23 | Android 5.x(Lollipop) | OpenJDK 7 | Java 1.7 |
2012年 | API 16 - 20 | Android 4.1.x(Jelly Bean) - Android 4.4.x(KitKat) | Java JDK 6 | Java 1.6 |
~ 壁 ~ | | ~~~ 以下は過去の遺物で切捨て ~~~ | | |
2010年 | API 9 - 15 | Android 2.3.x(Gingerbread) - Android 4.0.x(ICS、Ice Cream Sandwich) | Java JDK 6 | Java 1.6 |
~ 壁 ~ | | ~~~ 以下は黒歴史 ~~~ | | |
2009年 | API 3 - 8 | Android 1.5(Cupcake) - Android 2.2.x(Froyo) | Java JDK 5 | Java 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を使う
CPU | ABI名称 | bit | OpenWnnのバイナリサイズ |
ARM系 | armeabi | 32bit | 50708 bytes |
ARM系 | armeabi-v7a | 32bit | 46612 bytes |
ARM系 | arm64-v8a | 64bit | 67344 bytes |
Intel系 | x86 | 32bit | 67028 bytes |
Intel系 | x86_64 | 64bit | 71776 bytes |
MIPS系 | mips | 32bit | 139972 bytes |
MIPS系 | mips64 | 64bit | 83528 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年経ってデータ バインディングの俺的カンニング帳を作る
Android開発 データ バインディング 虎の巻 Android Studio
Androidのアプリ開発で AAR形式のライブラリを Androidプロジェクトに組み込む方法
Androidプロジェクトに AAR/JAR形式のライブラリを組み込む方法のバリエーション
Kotlin大嫌い人間が Kotlin言語を必死に勉強する
行末にセミコロン;の無い言語は大嫌い
2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!
Androidの開発で AACって何? DataBinding、LiveData、ViewModel、LifecycleObserverについて解説
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/