1、第一个 JNI 程序
package com.open.firstjni;
import android.os.Bundle;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
// 第一步:应用程序启动时加载 "native-lib" 库
static {
System.loadLibrary("native-lib");
}
// 第二步:由 "native-lib" 库实现的方法,它与此应用程序打包在一起
public native String stringFromJNI();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 第三步:调用 JNI 方法示例
TextView textView = (TextView) findViewById(R.id.sample_text);
textView.setText(stringFromJNI());
}
}
package com.open.firstjni
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib")
}
}
external fun stringFromJNI(): String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.sample_text).text = stringFromJNI()
}
}
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_open_firstjni_MainActivity_stringFromJNI(JNIEnv *env, jobject ) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
2、Java 调用 C/C++
JNIEnv
JavaVM
第一步:在 Java 层定义 native 关键字函数
第二步:在 C/C++ 层创建 Java_packname_classname_methodname 函数
## 示例如:第一个 JNI 程序所示
- 代码示例如:第一个 JNI 程序所示
- (2)Java 调用 C/C++ 方式二:
RegisterNative
映射表对应如下:
typedef struct{
const char* name;
const char* signature;
void* fnPtr;
}JNINativeMethod;
jint JNI_OnLoad(JavaVM *vm, void *reserved)
jint JNI_UnLoad(JavaVM *vm, void *reserved)
package com.open.firstjni
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib")
}
}
external fun stringFromJNI2(): String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.sample_text).text = stringFromJNI2()
}
}
#include <jni.h>
#include <string>
#define JNI_CLASS_PATH "com/open/firstjni/MainActivity"
extern "C"
JNIEXPORT jstring JNICALL
stringFromJNI2(JNIEnv *env, jobject thiz) {
return env->NewStringUTF("1111");
}
static JNINativeMethod g_method[] = {
{"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2},
};
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
jclass clazz = env->FindClass(JNI_CLASS_PATH);
env->RegisterNatives(clazz, g_method, sizeof(g_method) / sizeof(g_method[0]));
return JNI_VERSION_1_6;
}
3、JNI 中的 signature
- Java 与 C/C++ 相互调用时,表示函数参数的描述符
- 输入参数放在括号()内,输出参数放在括号()外
- 多个参数之间顺序存放,并用 “;” 分割
- 原始类型的 signature
Java 类型 | 符号 | Java 类型 | 符号
—|--- |
boolean | Z | byte | B
char | C | short | S
int | I | long | L
float | F | double | D
void | V - Java 对象参数:“L包路径/类名”
- Java 数组:"["
- 示例:
示例一:([Student;)[LStudent;
=》Student() Xxx(Student[] st)
示例二:([Ljava/lang/String;)[Ljava/lang/Object;
=》Object[] Xxx(String[] s)
4、C 调用 Java
FindClass
GetMethodID
GetFieldID
NewObject
Call<TYPE> Method/[G/S]et<Type>Field
<!-- Java 层代码 -->
package com.open.firstjni
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.sample_text).text = stringFromJNI()
}
external fun stringFromJNI(): String
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
package com.open.firstjni;
public class Student {
private int year;
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
<!-- C/C++ 层代码 -->
#include <jni.h>
#include <string>
#define STUDENT_CLASS_PATH "com/open/firstjni/Student"
extern "C" JNIEXPORT jstring JNICALL
Java_com_open_firstjni_MainActivity_stringFromJNI(JNIEnv *env, jobject ) {
jclass clazz = env->FindClass(STUDENT_CLASS_PATH);
jmethodID method_init_year = env->GetMethodID(clazz, "<init>", "()V");
jmethodID method_set_year = env->GetMethodID(clazz, "setYear", "(I)V");
jmethodID method_get_year = env->GetMethodID(clazz, "getYear", "()I");
jobject obj = env->NewObject(clazz, method_init_year);
env->CallVoidMethod(obj, method_set_year, 18);
int year = env->CallIntMethod(obj, method_get_year);
char tmp[256];
sprintf(tmp, "%d", year);
std::string hello = "Hello from C/C++, year = ";
hello.append(tmp);
return env->NewStringUTF(hello.c_str());
}
显示:Hello from C/C++, year = 18
5、交叉编译
通俗理解就是,就是在某个系统下编译生成另外一个系统的库,即为交叉编译。
举例:
在 Mac 系统下编译成功 Android 系统下使用的 FFmpeg 库。
- 以 NDK 为 android-ndk-r21e 示例:
- (1)生成交叉编译工具链方式:
build/tools/make-standalone-toolchain.sh
--toolchain=arm-linux-androideabi-4.9
--platform=android-21
--install-dir=../toolchain
cd android-ndk-r21e 目录
build/tools/make-standalone-toolchain.sh --toolchain=arm-linux-androideabi-4.9 --platform=android-21 --install-dir=../toolchain
export NDK=/Users/Xxx/Library/Android/ndk/android-ndk-r20b
export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin
- 编写编译脚本(build_ffmpeg_android.sh):
// 第一步:下载并解压 FFmpeg 源码
// 第二步:进入 FFmpeg 源码目录
cd ffmpeg
// 第三步:创建并编写 build_ffmpeg_android.sh shell 脚本
make clean
set -e
archbit=32
if [ $archbit -eq 64 ];then
ARCH=aarch64
CPU=armv8-a
API=21
PLATFORM=aarch64
ANDROID=android
CFLAGS=""
LDFLAGS=""
echo "Compiling FFmpeg build for 64bit and $CPU"
else
ARCH=arm
CPU=armv7-a
API=16
PLATFORM=armv7a
ANDROID=androideabi
CFLAGS="-mfloat-abi=softfp -march=$CPU"
LDFLAGS="-Wl,--fix-cortex-a8"
echo "Compiling FFmpeg build for 32bit and $CPU"
fi
export NDK=/Users/Xxx/Library/Android/ndk/android-ndk-r20b
export TOOLCHAIN=/Users/Xxx/Library/Android/ndk/toolchain/bin
export SYSROOT=$NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot
export CROSS_PREFIX=$TOOLCHAIN/$ARCH-linux-$ANDROID-
export CC=$TOOLCHAIN/$PLATFORM-linux-$ANDROID$API-clang
export CXX=$TOOLCHAIN/$PLATFORM-linux-$ANDROID$API-clang++
export PREFIX=$(pwd)/android-build/$CPU
function build_android {
./configure \
--prefix=$PREFIX \
--cross-prefix=$CROSS_PREFIX \
--target-os=android \
--arch=$ARCH \
--cpu=$CPU \
--cc=$CC \
--cxx=$CXX \
--nm=$TOOLCHAIN/$ARCH-linux-$ANDROID-nm \
--strip=$TOOLCHAIN/$ARCH-linux-$ANDROID-strip \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="$CFLAGS" \
--extra-ldflags="$LDFLAGS" \
--extra-ldexeflags=-pie \
--enable-runtime-cpudetect \
--disable-static \
--enable-shared \
--disable-ffprobe \
--disable-ffplay \
--disable-ffmpeg \
--disable-debug \
--disable-doc \
--enable-avfilter \
--enable-swresample \
--enable-decoders \
$ADDITIONAL_CONFIGURE_FLAG
make -j8
make install
echo "The Compilation of FFmpeg for $CPU is completed and generated in $PREFIX directory"
}
build_android
6、Android 下的 FFmpeg 播放器
- github项目地址:https://github.com/xing-tang/FFmpeg-master
7、Linux 系统为什么无法编译出 FFplay?
- Linux 系统必须有显示系统
- 必须安装了 SDL 库
- 需设置了 DISPLAY 环境变量
- 你的 Linux 系统必须是桌面版,而不能是服务版,这样它才会有显示系统,建议使用 Ubuntu系统
- 安装 SDL 库, apt install libsdl2-dev
- 在 ~/.bashrc 中设备 DISPLAY=0.0 环境变量
8、错误分析
- 编译时提示 ERROR: libfdk_aac not found 这是什么原因?
- 这是由于你使用的 FFmpeg 在编译时没有加 libfdk_aac 库。使用源码方式编译 FFmpeg,在执行 configure 时将 –enable-libfdk-aac 选项加上,并且在编译之前先将 libfdk-aac 库装上。