文章创建时间:2021-07-02,最后修改时间:2021-07-02
1、创建工程
首先使用Android Studio创建一个Empty Project,包名自定义,这里我设为com.howie.ndkdemo 。
创建成功后,该包名对应的目录下,有一个MainActivity.java。在此目录下,新建一个class,类名自定义,这里我定义的类名为NDKTools 。
因此,在我的~/AndroidStudioProjects/NDKDemo/app/src/main/java目录结构下,tree为
>>> tree ./
./
└── com
└── howie
└── ndkdemo
├── MainActivity.java
└── NDKTools.java
到这里,简单地先罗列下目前的code
首先是资源文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gtfn"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/hello" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.howie.ndkdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.hello);
button = findViewById(R.id.button);
}
@Override
protected void onResume() {
super.onResume();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = NDKTools.getTextFromNDK();
Log.i("NDK Demo ","text=" + text);
textView.setText(text);
}
});
}
}
NDKTools.java
package com.howie.ndkdemo;
public class NDKTools {
public static native String getTextFromNDK();
}
2、生成JNI头文件
进入到 ~/AndroidStudioProjects/NDKDemo/app/src/main/java目录下,生成头文件。
这里会用到javah
>>> javah
用法:
javah [options] <classes>
其中, [options] 包括:
-o <file> 输出文件 (只能使用 -d 或 -o 之一)
-d <dir> 输出目录
-v -verbose 启用详细输出
-h --help -? 输出此消息
-version 输出版本信息
-jni 生成 JNI 样式的标头文件 (默认值)
-force 始终写入输出文件
-classpath <path> 从中加载类的路径
-cp <path> 从中加载类的路径
-bootclasspath <path> 从中加载引导类的路径
<classes> 是使用其全限定名称指定的
(例如, java.lang.Object)。
javah -classpath . -d ./../jni -jni com.howie.ndkdemo.NDKTools
-d 指定生成头文件的路径;
-jni可以不加;
-classpath一定要加上,指定加载类的路径,否则会报下面的错误:
>>> javah -d ./../jni -jni com.howie.ndkdemo.NDKTools
错误: 找不到 'com.howie.ndkdemo.NDKTools' 的类文件。
生成的com_howie_ndkdemo_NDKTools.h在~/AndroidStudioProjects/NDKDemo/app/src/main/jni目录下,
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_howie_ndkdemo_NDKTools */
#ifndef _Included_com_howie_ndkdemo_NDKTools
#define _Included_com_howie_ndkdemo_NDKTools
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_howie_ndkdemo_NDKTools
* Method: getTextFromNDK
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_howie_ndkdemo_NDKTools_getTextFromNDK
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
3、创建CPP/C文件
在~/AndroidStudioProjects/NDKDemo/app/src/main/jni目录下,创建对应的CPP/C文件,这里我创建了com_howie_ndkdemo_NDKTools.cpp文件。
//
// Created by mi on 21-7-2.
//
#include "com_howie_ndkdemo_NDKTools.h"
JNIEXPORT jstring JNICALL Java_com_howie_ndkdemo_NDKTools_getTextFromNDK
(JNIEnv *env, jclass cla) {
return env -> NewStringUTF ("Hello, Howie!");
}
4、关联编译
这时候如果我们编译app并安装到手机中运行,会报下面的错误
2021-07-02 10:19:52.584 27619-27619/com.howie.ndkdemo E/m.howie.ndkdem: No implementation found for java.lang.String com.howie.ndkdemo.NDKTools.getTextFromNDK() (tried Java_com_howie_ndkdemo_NDKTools_getTextFromNDK and Java_com_howie_ndkdemo_NDKTools_getTextFromNDK__)
2021-07-02 10:19:52.584 27619-27619/com.howie.ndkdemo D/AndroidRuntime: Shutting down VM
2021-07-02 10:19:52.585 27619-27619/com.howie.ndkdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.howie.ndkdemo, PID: 27619
java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.howie.ndkdemo.NDKTools.getTextFromNDK() (tried Java_com_howie_ndkdemo_NDKTools_getTextFromNDK and Java_com_howie_ndkdemo_NDKTools_getTextFromNDK__)
at com.howie.ndkdemo.NDKTools.getTextFromNDK(Native Method)
at com.howie.ndkdemo.MainActivity$1.onClick(MainActivity.java:30)
at android.view.View.performClick(View.java:7185)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
at android.view.View.performClickInternal(View.java:7162)
at android.view.View.access$3500(View.java:819)
at android.view.View$PerformClick.run(View.java:27678)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7562)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
2021-07-02 10:19:52.613 27619-27619/com.howie.ndkdemo I/Process: Sending signal. PID: 27619 SIG: 9
意思是没找到JNI的实现。
到这里,可以理解,我们在jni下新增的头文件已经CPP文件,没有与NDKTools关联起来。
我们写代码知道增加的JNI实现在哪,但是机器不知道,我们需要关联编译。
添加的JNI,我们将它编译成的动态库命名为:hello-howie-jni,那么在NDKTools里面我们需要加载这个动态库,才能找到jni。
package com.howie.ndkdemo;
public class NDKTools {
static {
// 加载动态库
System.loadLibrary("hello-howie-jni");
}
public static native String getTextFromNDK();
}
关联编译的方式,有两种,一种CMake编译,一种NDK编译。
(1) CMake编译
添加方式,可以参照google的指导https://developer.android.com/studio/projects/configure-cmake?hl=zh-cn,
首先在项目的app目录下新增一个CMakeLists.txt,上述命名必须保持一样。
add_library( # Specifies the name of the library.
hello-howie-jni
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/jni/com_howie_ndkdemo_NDKTools.cpp )
右键点击想要关联到原生库的模块(例如 app 模块),并从菜单中选择 Link C++ Project with Gradle
。从下拉菜单中,选择 CMake。将刚才添加的CMakeLists.txt加入到 Project Path,然后点击OK确认。这时,在app目录下的build.gradle中就会出现下面的编译信息externalNativeBuild:
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.howie.ndkdemo"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
cmake {
path file('CMakeLists.txt')
}
}
}
如果不通过Link C++ Project with Gradle方式添加,在app下的build.gradle添加
externalNativeBuild也是可以的。
到这里,已经可以了,点击Make Project,等编译通过后,再点击Run app,这时候就可以了。
当点击按钮,主界面的Hello, World!就会变成Hello, Howie!
(2) NDK编译
在jni目录下,创建Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-howie-jni
LOCAL_SRC_FILES := com_howie_ndkdemo_NDKTools.cpp
include $(BUILD_SHARED_LIBRARY)
然后配置gradle,可以用Link C++ Project with Gradle方式添加,也可以在build.gradle直接添加。
externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
}
5、Demo下载
https://download.csdn.net/download/hihan_5/19991157
6、 下一篇
Android JNI开发(2) Java <==> Native
参考文章:
[1] https://www.jianshu.com/p/b4431ac22ec2
[2] https://juejin.cn/post/6844903696476700680#heading-2
[3] https://developer.android.com/studio/projects/add-native-code?hl=zh-cn