記事ディレクトリ
- 第9章JNI
- 1. JNIおよびNDKの概要
- 2.特定の使用
- (1)NDK統合開発プロセス
- 1. Android NDK環境を構成する
- 2. Android StudioプロジェクトをNDKに関連付けます
- 3. JNIクラス宣言のネイティブメソッドを作成する
- 4. .hファイルを生成する
- 5.ローカルコードファイルを作成する
- 6. Android.mkファイルとApplication.mkファイルを作成する
- 7.上記のファイルをコンパイルし、.soライブラリファイルを生成して、プロジェクトファイルに配置します。
- 8. Android StudioプロジェクトのJNIを介してsoファイルを呼び出す
- (2)NDK開発
- 3.実際のシーン
第9章JNI
1. JNIおよびNDKの概要
1. JNI(契約)
(1)定義
Javaネイティブインターフェース、つまりJavaネイティブインターフェースは、ブリッジ、プロトコルに相当します。
つまり、JavaコードでJava、C、C ++コードを呼び出すか、Javaコードを呼び出すためにCおよびC ++コードを呼び出します(相互に呼び出す)
Androidシステムアーキテクチャの上位層(フレームワーク)レイヤー+アプリケーションレイヤー)JAVAはJNIを介してボトムレイヤー(Linuxカーネルレイヤー)Cを
呼び出します; JNIはJavaがネイティブ言語を呼び出す機能で、Javaに属し、Androidと直接の関係はありません
(2)機能
実際の使用では、Javaはネイティブコードと対話する必要がありますが、Javaにはクロスプラットフォーム機能があるため、Javaはネイティブコードと対話する能力が弱いです。JNI機能を使用して、Javaとネイティブコード間の相互作用を強化できます。
2. NDK(ツール)
(1)定義
Native Development KitはAndroid用のツール開発キットであり、NDKはAndroidに属し、Javaと直接の関係はありません
(2)機能
CおよびC ++の動的ライブラリをすばやく開発し、そのようにアプリケーションをAPKとして自動的にパッケージ化します。JNIを使用すると、NDKを介してAndroidのローカルコード(C、C ++など)と対話できます。Androidによって
開発された関数は、ローカルコード(C / C ++)実装
(3)特徴
- 高い操作効率:高性能を必要とする要求の厳しい関数の開発では、C / C ++の方が効率的です。たとえば、ローカルコード(C / C ++)を使用してアルゴリズムを実行すると、アルゴリズムの実行効率を大幅に向上できます。
- 高度なコードセキュリティ:javaは半解釈言語であり、変更後のソースコードの取得は簡単ですが、ローカルコード(C / C ++)はシステムのセキュリティを向上させることができません
- 優れた機能拡張性:他の開発言語のオープンソースライブラリを簡単に使用でき、Javaオープンソースライブラリに加えて、(C / C ++)オープンソースライブラリも使用できます。
- 簡単なコードの再利用と移植性
- ネイティブコード(C / C ++)で開発されたコードは、Androidだけでなく、他のタイプのプラットフォームでも使用できます。
- .soおよび.apkをパッケージ化するためのツールを提供します(JNIは.soファイルのみをファイルシステムの特定の場所に配置します)
- NDKが提供するライブラリは制限されており、アルゴリズムの効率性とデリケートな問題に対処するためにのみ使用されます
- 特定のCPUプラットフォームの動的ライブラリを生成するためのクロスコンパイラを提供します
3. JNIとNDKの関係
JNIは実装の目的(Javaがローカル言語と対話するためのインターフェース/プロトコル)、NDKはAndroidにJNIを実装するためのツール(Androidツール開発キット)
2.特定の使用
(1)NDK統合開発プロセス
1. Android NDK環境を構成する
2. Android StudioプロジェクトをNDKに関連付けます
a。Gradleのlocal.propertiesに設定を追加します
ndk.dir=/Users/Carson_Ho/Library/Android/sdk/ndk-bundle
b。gradleのgradle.propertiesに設定を追加します
#兼容老的Ndk
android.useDeprecatedNdk=true
c。Gradleのbuild.gradleにndkノードを追加します
ndk {
//.so文件 Linux下动态链接库(同windows下dll文件),二进制文件,多用于NDK开发.用户拿到动态库和头文件说明,就可以使用动态库中function
moduleName "hello_jni"//对应本地代码文件,生成.so文件:lib+moduleName.so
//abiFilters "x86","armeabi", "armeabi-v7a"//CPU类型
}
3. JNIクラス宣言のネイティブメソッドを作成する
パッケージcom.sdu.chy.chytest.ndkTest
/**
* Java调用对应的C代码
*/
public class JNI {
//加载JNI生成so库
static {
System.loadLibrary("hello_jni");
}
//定义Native方法,调用C代码对应方法
public native String sayHello();
}
4. .hファイルを生成する
(1)javacはパッケージディレクトリに.classファイルを生成します
ndkTest danding$ javac JNI.java
(2)外部(java)ディレクトリーのjavahの下にjavaファイルを生成します
java danding$ javah -jni com.sdu.chy.chytest.ndkTest.JNI
(3).hファイルをjniフォルダーに移動する
5.ローカルコードファイルを作成する
Androidプロジェクトで呼び出す必要があるローカルコードhello.c
#include<stdio.h>
#include<stdlib.h>
#include<jni.h>
//类名:Java类型+本地类型 对应关系
//C函数命名格式:Java_全类名_方法名
//JNIEnv*:代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。
//jobject:代表native方法的实例(调用者),这里是JNI.ini
JNIEXPORT jstring JNICALL Java_com_sdu_chy_chytest_ndkTest_JNI_sayHello(JNIEnv* env,jobject jobj){
char* text = "I am from C";
return (*env)->NewStringUTF(env,text);
}
注:
- ローカルコードがC ++(.cppまたは.cc)の場合、extern "C" {}を使用してローカルメソッドを囲みます。
- JNIEXPORT jstring JNICEXおよびJNICALLのJNICALLを保存できません
- メソッド名についてJava_scut_carson_1ho_ndk_1demo_MainActivity_getFromJNI
format = Java パッケージ名_クラス名_
Java はメソッド名を呼び出す必要があるJavaは大文字にする必要があるパッケージ名では、パッケージ名で_に変更するには_を_1に変更します。私のパッケージ名がscut.carson_ho.ndk_demoの場合、scut_carson_1ho_ndk_1demoに変更する必要があります - 最後に、作成したtest.cppファイルをプロジェクトファイルディレクトリのsrc / main / jniフォルダーに配置します。jniフォルダーがない場合は、手動で作成します。
- JNI型とJava型の間の対応の概要
6. Android.mkファイルとApplication.mkファイルを作成する
6.1)Android.mkファイルを作成する
役割:
作業ディレクトリ、コンパイルされたモジュールの名前、コンパイルに関係するファイルなど、ソースコードのコンパイルに関する構成情報を指定します。
使用:
Android.mk(src / main / jni)
LOCAL_PATH := $(call my-dir)
// 设置工作目录,而my-dir则会返回Android.mk文件所在的目录
include $(CLEAR_VARS)
// 清除几乎所有以LOCAL——PATH开头的变量(不包括LOCAL_PATH)
LOCAL_MODULE := hello_jni
// 设置模块的名称,即编译出来.so文件名
// 注,要和上述步骤中build.gradle中NDK节点设置的名字相同
LOCAL_SRC_FILES := hello.c \
// 指定参与模块编译的C/C++源文件名
include $(BUILD_SHARED_LIBRARY)
// 指定生成的静态库或者共享库在运行时依赖的共享库模块列表。
6.2)Application.mkファイルを作成する
役割:コンパイルプラットフォームの関連コンテンツを構成します
使用:
Application.mk(src / main / jni)
APP_MODULES := hello_jni
APP_ABI := all
// 最常用的APP_ABI字段:指定需要基于哪些CPU平台的.so文件
// 常见的平台有armeabi x86 mips,其中移动设备主要是armeabi平台
// 默认情况下,Android平台会生成所有平台的.so文件,即同APP_ABI := armeabi x86 mips
// 指定CPU平台类型后,就只会生成该平台的.so文件,即上述语句只会生成armeabi平台的.so文件
7.上記のファイルをコンパイルし、.soライブラリファイルを生成して、プロジェクトファイルに配置します。
7.1)build.gradle設定を変更する
sourceSets {
main {
jni.srcDirs = []
jniLibs.srcDirs = ['libs','src/main/libs']
//生成.so文件位置'src/main/libs'
}
}
7.2)JNIクラスのndk-build
コンパイルが成功すると、src / main /にlibs&objフォルダーがさらに2つあります。ここで、.soライブラリファイルはlibsの下に保存されます。
8. Android StudioプロジェクトのJNIを介してsoファイルを呼び出す
/Users/danding/Documents/chy_workspace/app/src/main/java/com/sdu/chy/chytest/ndkTest/JniTestActivity.java
public class JniTestActivity extends AppCompatActivity {
private TextView JniTextView;
private Button JniScheduleBtn;
private JniClickListener jniClickListener = new JniClickListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jni_test);
initView();
}
public void initView(){
JniTextView = (TextView)findViewById(R.id.jni_text_view);
JniScheduleBtn = (Button) findViewById(R.id.jni_btn_schedule);
JniScheduleBtn.setOnClickListener(jniClickListener);
}
public class JniClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.jni_btn_schedule:
JniTextView.setText(new JNI().sayHello());//调用
break;
}
}
}
}
ボタンをクリックしてCメソッドを呼び出す
(2)NDK開発
1. JavaがC関数を呼び出す
1.1)JNI関数の登録
1.静的登録
最初にJavaからローカルメソッドの宣言を取得し、次にJNIを介して宣言メソッドを実装します。
静的登録は、関数名に従ってJava関数とJNI関数の間の関連付けをトラバースすることであり、JNIレイヤー関数の名前は特定の形式に従う必要があります。
ステップ1:最初にJavaコードでネイティブ関数を宣言する
public class JniDemo1{
static {
System.loadLibrary("samplelib_jni");
}
private native void nativeMethod();
}
ステップ2:javahを介してネイティブ関数.hファイルを生成する
javah -d ./jni/ -classpath /Users/YOUR_NAME/Library/Android/sdk/platforms/android-21/android.jar:../../build/intermediates/classes/debug/ com.gebilaolitou.jnidemo.JniDemo1
次に、これらのネイティブ関数の宣言を含むJNI.hファイルを取得
しますファイル名と関数名を確認してください。実際、JNIメソッド名の仕様が出て
きます。戻り値+ Javaプレフィックス+フルパスクラス名+メソッド名+パラメーター1JNIEnv +パラメーター2jobject +その他のパラメーター
ステップ3:メソッドを.hファイルに実装するコードを記述します。
2.動的登録
最初にJNIオーバーロードJNI_OnLoad()を介してローカルメソッドを実装し、次にJavaでローカルメソッドを直接呼び出します。
特定のメソッド命名形式に従わずに、RegisterNativesメソッドを使用して、C / C ++のメソッドをJavaのネイティブメソッドにマップします。
1.2)JNIからC関数を呼び出す
ステップ1:soライブラリーをロードする
public class JniDemo1{
static {
System.loadLibrary("samplelib_jni");
}
}
ステップ2:JNIでの実装
jint JNI_OnLoad(JavaVM* vm, void* reserved)
ステップ3:この関数にネイティブメソッドを動的に登録する
#include <jni.h>
#include "Log4Android.h"
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
static const char *className = "com/gebilaolitou/jnidemo/JNIDemo2";
static void sayHello(JNIEnv *env, jobject, jlong handle) {
LOGI("JNI", "native: say hello ###");
}
static JNINativeMethod gJni_Methods_table[] = {
{"sayHello", "(J)V", (void*)sayHello},
};
static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGI("JNI","Registering %s natives\n", className);
clazz = (env)->FindClass( className);
if (clazz == NULL) {
LOGE("JNI","Native registration unable to find class '%s'\n", className);
return -1;
}
int result = 0;
if ((env)->RegisterNatives(clazz, gJni_Methods_table, numMethods) < 0) {
LOGE("JNI","RegisterNatives failed for '%s'\n", className);
result = -1;
}
(env)->DeleteLocalRef(clazz);
return result;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGI("JNI", "enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif
注:
1)JNINativeMethod
定義
JNIでは、Java仮想マシンに登録する関数マッピングテーブルを提供できるため、JVMは関数マッピングテーブルを使用して対応する関数を呼び出すことができます。これにより、呼び出される関数を関数名で見つける必要がなくなります。JavaとJNIはjni.h中で定義されているJNINativeMethod構造を通じて接触を確立するための
構造
typedef struct {
const char* name; //Java中函数名
const char* signature; //Java中参数和返回值
void* fnPtr; //指向C函数的函数指针
} JNINativeMethod;
バインディング
jniRegisterNativeMethodsでは、RegisterNatives関数を呼び出すことにより、登録関数のJavaクラス、登録関数の配列、および登録関数の数が一緒に登録されます。
2)JNIの署名
原因:
パラメータタイプと戻り値タイプの組み合わせ。この関数のシグネチャ情報とこの関数の関数名があれば、Javaレイヤーで対応する関数を順番に見つけることができます。(Java関数のオーバーロードが対応する実装メソッドを見つけないようにする)
仕様
(パラメーター1のタイプラベル、パラメーター2のタイプラベル、パラメーター3のタイプラベル...)戻り値のタイプラベル
2. CコールバックJavaメソッド
(1)Classオブジェクトを取得する
C / C ++でJavaのクラスを呼び出すことができるようにするために、jni.hのヘッダーファイルは、JavaのClassクラスを表すjclassタイプを明確に定義しています。JNIEnvには、jclassを取得するための3つの関数があります。
1.jclass jcl_string=env->FindClass("java/lang/String");//通过类的名称
2.jclass GetObjectClass(jobject obj);//通过对象实例来获取jclass,相当于Java中的getClass()函数
3.jclass getSuperClass(jclass obj);//通过jclass可以获取其父类的jclass对象
(2)属性メソッドの取得
したがって、C / C ++でJavaレイヤーのプロパティとメソッドを取得するために、JNIはjfield。とjmethodIDの2つのタイプをjni.hヘッダーファイルに定義して、それぞれJava側のプロパティとメソッドを表します。Javaで特定のプロパティ\メソッドにアクセスまたは設定する場合、まずローカルコードでJavaクラスのプロパティを表すjfieldID \ jmethodIDを取得する必要があります。その後、ローカルコードでJavaプロパティを操作できます。
メソッド:通常はJNIEnvを使用します。動作させるために
GetFieldID / GetMethodIDメソッドは:プロパティを取得/方法
GetStaticFieldID / GetStaticMethodID:静的プロパティを取得/静的メソッド
の特定の実装
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
(3)オブジェクトを作成し、オブジェクトを介して応答メソッドを呼び出す
jobject NewObject(jclass clazz, jmethodID methodID, ...)
3.実際のシーン
Java言語で処理できないタスクがある場合、開発者はJNIテクノロジーを使用して完了することができます。一般的に、次の状況ではJNIテクノロジーを使用
する必要があります。1.開発中に、Java言語でサポートされておらず、オペレーティングシステムプラットフォームの特性に依存する一部の関数を呼び出す必要があります。たとえば、現在のUnixシステムの関数を呼び出す必要がありますが、Javaはこの関数をサポートしていないため、JNIテクノロジーを使用して実行する必要があります。
2.開発中、以前のJava以外の言語を統合するために開発されたシステムがあります。たとえば、初期のCまたはC ++言語で開発されたいくつかの関数またはシステムを開発し、これらの関数を現在のシステムまたは新しいバージョンに統合する必要があります。
3.開発時には、プログラムの実行時間を節約するために、いくつかの低レベルまたは中レベル言語を使用する必要があります。たとえば、時間を節約するアプリケーションを作成するには、アセンブリ言語を使用してから、Java言語を使用して、JNIテクノロジを通じてこの低レベル言語アプリケーションを呼び出す必要があります。
例:
Meitu Xiuxiu画像の処理:javaを使用して画像ファイルを取得し、Cを使用してカラーマトリックス(RGBA)で画像を処理します。