Android NDK开发总结

3 篇文章 0 订阅
2 篇文章 0 订阅

大家都知道在eclipse上进行ndk开发光是编译C代码就很蛋疼,还好android studio的出现改变了现状,然而,在android studio 3.0以前进行ndk开发,也是各种配置总的来说用着很不爽,如今的android studio作了优化升级,不仅仅配置简化了,还直接引入cmake等功能,终于解放了双手。

一、NDK基础介绍

1、环境配置:

  • 导入ndk等工具:Tools->SDK Manager->SDK Tools:选中LLDB、CMake、NDK

  • 设置NDK安装路径:File->Project Structure->Android NDK location 选中默认路径

  • 检查NDK路径:local.properties里面的ndk.dir在sdk路径里面,默认ndk-bundle

2、创建NDK项目:

  • 创建native工程:

创建一个native c++工程:

有些时候可能会是下面这样的,这个时候只需要选中include c++ support就行了

  • native工程目录:原来android studio已经帮我们把所有的配置都设置好了,build.gradle里面的配置如下,这其实不需要我们更改。而cpp文件夹也已经帮我们创建了,里面除了有cpp文件之外,还有一个includes和CMakeLists.txt

3、CMakeLists.txt

该文件其实是一个cmake的脚步。除去注释部分,其实核心的内容并不是很多

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED
             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
              log-lib
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
                       native-lib
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
  • cmake_minimum_required(VERSION 3.4.1):用来设置在编译本地库时我们需要的最小的cmake版本,AndroidStudio自动生成,我们几乎不需要自己管。
  • add_library:用来设置编译生成的本地库的名字为native-libSHARED表示编译生成的是动态链接库(这个概念前面已经提到过了),src/main/cpp/native-lib.cpp表示参与编译的文件的路径,这里面可以写多个文件的路径。如下将两个cpp文件都编译到动态链接库dpc-lib中,注意在java调用native方法之前需要加载的动态库跟这里的名字保持一致 static { System.loadLibrary("dpc-lib");}
     
  • find_library:是用来添加一些我们在编译我们的本地库的时候需要依赖的一些库,由于cmake已经知道系统库的路径,所以我们这里只是指定使用log库,然后给log库起别名为log-lib便于我们后面引用,此处的log库是我们后面调试时需要用来打log日志的库,是NDK为我们提供的。

target_link_libraries:是为了关联我们自己的库和一些第三方库或者系统库,这里把我们把自己的库native-lib库和log库关联起来。

4、includes

这是一个包含很多头文件的库,这些头文件定义了很多c++系统函数或者android 框架层下的一些函数,这样更加方便了我们的ndk开发

例如在android/log.h文件中提供了一些在c++里面打印日志的函数,在c++中我们可以试着利用这些方法来向logcat打印日子:

#include <jni.h>
#include <string>
//引入log.h头文件
#include <android/log.h>           
extern "C" JNIEXPORT jint JNICALL
Java_shen_com_myapplication_MainActivity_printfLog(JNIEnv* env,jobject thiz) 
{
    //参数1是LOG等级,参数二是TAG,参数三表示后面跟字符串
    __android_log_print(ANDROID_LOG_INFO, "SHEN", "%s", "Hello, World");
    return 0;
}

例如在jni.h文件中提供了jni相关的定义和接口:

#ifndef JNI_H_
#define JNI_H_
#include <stdarg.h>
#include <stdint.h>
/* Primitive types that match up with Java equivalents. */
typedef uint8_t  jboolean; /* unsigned 8 bits */
typedef int8_t   jbyte;    /* signed 8 bits */
typedef uint16_t jchar;    /* unsigned 16 bits */
typedef int16_t  jshort;   /* signed 16 bits */
typedef int32_t  jint;     /* signed 32 bits */
typedef int64_t  jlong;    /* signed 64 bits */
typedef float    jfloat;   /* 32-bit IEEE 754 */
typedef double   jdouble;  /* 64-bit IEEE 754 */
/* "cardinal indices and sizes" */
typedef jint     jsize;
struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID;     /* field IDs */
struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */
struct JNIInvokeInterface;
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;  //C++中的JNIEnv是一个结构体类型_JNIEnv 里面定义了各种各样的方法
typedef _JavaVM JavaVM;  
#else
typedef const struct JNINativeInterface* JNIEnv; //C中的JNIEnv是一个JNINativeInterface指针
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;

    jint        (*GetVersion)(JNIEnv *);

    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
                        jsize);
    jclass      (*FindClass)(JNIEnv*, const char*);

    jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);
    jfieldID    (*FromReflectedField)(JNIEnv*, jobject);
//***各种方法***
}
//_JNIEnv代理了JNINativeInterface 所以JNIEnv在C和C++本质一样,使用方式有所不同
struct _JNIEnv {  
    const struct JNINativeInterface* functions;
    jint GetVersion()
    { return functions->GetVersion(this); }
    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }
    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }
//***各种方法***
}

例如includes里面还提供了linux里面的一些操作,如线程和信号量的使用,示例如下:

#include <android/log.h>
#include <sys/time.h>
#include <pthread.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"SHEN",__VA_ARGS__)
//线程入口函数
void *threadTest(void *junk)
{
    int count =0 ;
    while(true){
        LOGE("thread1-runing");
        sleep(2);
        count++;
        if(count>=10){
            //pthread_exit((void *)&status);
            break;
        }
    }
}
//信号量句柄函数
void signalHandler(int signo)
{
    if(signo == SIGALRM){
        LOGE("signalHandler-signo=%d",signo);
    }
}
//测试信号量
void TestSignal()
{
    signal(SIGALRM, signalHandler);
    struct itimerval new_value, old_value;
    new_value.it_value.tv_sec = 0;
    new_value.it_value.tv_usec = 1;
    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_usec = 180099;
    setitimer(ITIMER_REAL, &new_value, &old_value);
    for(;;);
}
//测试线程创建
void TestPthread(){
    pthread_t t_a;
    pthread_create(&t_a, nullptr,threadTest, nullptr);/*创建进程t_a*/
}

6、生成native的头文件

package test.shen.com.shentest2;
import android.util.Log;
public class JniTest {
 //静态加载c/c++的native库,注意这里的名称必须跟CMakeLists.txt的add_library配置的名称一样
    static {
        System.loadLibrary("ShenTestLib");
    }
    public JniTest(){   }
    public native int add(int i,int j);
    public native String link(String i,String j);
    public native int[] fore(int[] src);
    public native void cToJava();
}

在项目路径/app/src/main/java路径下执行javah命令生成头文件,如下:

#切换到src/main/java路径下
E:\workspace\ShenTest2\app\src\main\java>cd E:\workspace\ShenTest2\app\src\main\java
#执行javah 全类名 命令
E:\workspace\ShenTest2\app\src\main\java>javah test.shen.com.shentest2.JniTest
错误: 编码GBK的不可映射字符
错误: 编码GBK的不可映射字符
#查看是否生成了头文件
E:\workspace\ShenTest2\app\src\main\java>dir
 驱动器 E 中的卷是 新加卷
 卷的序列号是 7C84-9FE1
E:\workspace\ShenTest2\app\src\main\java 的目录
2019/07/21 周日  18:36    <DIR>          .
2019/07/21 周日  18:36    <DIR>          ..
2019/07/12 周五  21:45    <DIR>          test
2019/07/21 周日  18:36             1,173 test_shen_com_shentest2_JniTest.h
               1 个文件          1,173 字节
               3 个目录 14,369,390,592 可用字节

test_shen_com_shentest2_JniTest.h内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_shen_com_shentest2_JniTest */

#ifndef _Included_test_shen_com_shentest2_JniTest
#define _Included_test_shen_com_shentest2_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test_shen_com_shentest2_JniTest
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_test_shen_com_shentest2_JniTest_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     test_shen_com_shentest2_JniTest
 * Method:    link
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_test_shen_com_shentest2_JniTest_link
  (JNIEnv *, jobject, jstring, jstring);

/*
 * Class:     test_shen_com_shentest2_JniTest
 * Method:    fore
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_test_shen_com_shentest2_JniTest_fore
  (JNIEnv *, jobject, jintArray);

/*
 * Class:     test_shen_com_shentest2_JniTest
 * Method:    cToJava
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_test_shen_com_shentest2_JniTest_cToJava
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

7、Jni的基本使用

jni的使用方式跟普通的c/c++的使用方式稍有不同,主要用来进行java与c/c++之间的数据转换,函数定义格式:

JNIEXPORT 函数返回值  JNICALL   函数名(包名+方法名)

#include "JniTest.h"
#include <string>
#include <android/log.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"SHEN",__VA_ARGS__)
using std::string;
#ifdef __cplusplus
char* _JString2CStr(JNIEnv* env, jstring jstr) {
    char* rtn = nullptr;
    jclass  cstr = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("UTF-8");
    jmethodID mid = env->GetMethodID(cstr,"getBytes","(Ljava/lang/String;)[B");
    jbyteArray bytes = (jbyteArray)env->CallObjectMethod(jstr,mid,strencode);
    jsize  size = env->GetArrayLength(bytes);
    jbyte* bytej = env->GetByteArrayElements(bytes,JNI_FALSE);
    if(size > 0) {
        rtn = (char*)malloc(size+1); //"\0"
        memset(rtn,'\0',size);
        memcpy(rtn, bytej, size);
    }
    env->ReleaseByteArrayElements(bytes,bytej,0);
    return rtn;
}
#else
char* _JString2CStr(JNIEnv* env, jstring jstr) {
    char* rtn = NULL;
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");
    jstring strencode = (*env)->NewStringUTF(env,"GB2312");
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
    jsize alen = (*env)->GetArrayLength(env, barr);
    jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
    if(alen > 0) {
        rtn = (char*)malloc(alen+1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen]=0;
    }
    (*env)->ReleaseByteArrayElements(env, barr, ba,0);
    return rtn;
}
#endif
//实现字符串的拼接
JNIEXPORT jstring JNICALL Java_test_shen_com_shentest2_JniTest_link
        (JNIEnv *env, jobject thiz, jstring s1, jstring s2){
    char* c1 = _JString2CStr(env,s1);
    char* c2 = _JString2CStr(env,s2);
    char* cs = strcat(c1,c2);
    return env->NewStringUTF(cs);
}
//实现遍历数组
JNIEXPORT jintArray JNICALL Java_test_shen_com_shentest2_JniTest_fore
        (JNIEnv *env, jobject thiz, jintArray array){
    jsize size = env->GetArrayLength(array);  //通过env来获取数组的大小
    jint* array0 = env->GetIntArrayElements(array,JNI_FALSE);//通过env来得到数组的首地址(指针)
    for(int i=0;i<size;i++)  array0[i] = array0[i] + i;
    env->ReleaseIntArrayElements(array,array0,0); //jni的数组在不使用的时候通过env进行释放
    return array;
}
//回调Java有参数有返回值的方法
JNIEXPORT void JNICALL Java_test_shen_com_shentest2_JniTest_cToJava
        (JNIEnv *env, jobject thiz){
    jclass clasz = env->GetObjectClass(thiz);
    //得到需要调用的方法
    jmethodID methodID2 = env->GetMethodID(clasz,"printfJavaLog","(Ljava/lang/String;Ljava/lang/String;)Z");
    //第一种调用方式 将参数组装成jvalue指针
    jvalue * values = new jvalue[2];
    values[0].l = env->NewStringUTF("SHEN");
    values[1].l = env->NewStringUTF("dingpc");
    jboolean reslut = env->CallBooleanMethodA(thiz,methodID2,values);
    //第二种调用方式 直接传递参数
    reslut = env->CallBooleanMethod(thiz,methodID2,
             env->NewStringUTF("SHEN"),env->NewStringUTF("dingpc"));
    //错误调用方式 这里会导致程序崩溃,因此参数不匹配,被调用的java方法的参数是String,对应jni的参数就应该是jstring,jstring继承于jobject,这里"SHEN"类型是char*,因此会抛出jni的异常
    //reslut = env->CallBooleanMethod(thiz,methodID2,"SHEN","dingpc"); //该方式参数不对,这里参数应该是object,SHEN并不是一个object
    if(reslut)
        LOGE("reslut = true"); //打印logo
    else
        LOGE("reslut = faluse");
}

8、生成方法签名

jni回调java的方法,需要首先获取一个java变量,thiz是java调用者本身,如果要获取其他java的类需要通过env的FindClass方法,除此之外还需要通过方法签名来获取对应方法的id,方法签名可以使用javap来生成,步骤如下:

  • Build -> Rebuild Project 进行重新编译生成对应的class文件
  • Terminal切换到classes路径

  • 找到对应的class文件执行javap -s  xxx.class
#切换到class文件对应目录
E:\workspace\ShenTest2\app\src\main\java>cd E:\workspace\ShenTest2\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\test\shen\com\shentest2
#执行javap -s 被执行的class文件
E:\workspace\ShenTest2\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\test\shen\com\shentest2>javap -s JniTest.class
Compiled from "JniTest.java"
public class test.shen.com.shentest2.JniTest {
  public test.shen.com.shentest2.JniTest();
    descriptor: ()V

  public native int add(int, int);
    descriptor: (II)I

  public native java.lang.String link(java.lang.String, java.lang.String);
    descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

  public native int[] fore(int[]);
    descriptor: ([I)[I

  public native void cToJava();
    descriptor: ()V

  public boolean printfJavaLog(java.lang.String, java.lang.String);
    descriptor: (Ljava/lang/String;Ljava/lang/String;)Z

  public void printfJavaLog(java.lang.String);
    descriptor: (Ljava/lang/String;)V

  public void printfJavaLog();
    descriptor: ()V

  public void printfJavaValue(int);
    descriptor: (I)V

  static {};
    descriptor: ()V
}

二、NDK高级用法

上面介绍了NDK的基本用法, 但是在很多时候我们需要去封装多个动态库或者静态库(或者需要链接其他预构建库),把第三方so或者a文件编译到我们jni中好让我们编写的jni代码随意调用。下面就以ffmpeg来进行实践。

1、准备预构建库

创建一个ndk基本工程(可以参考第一章),但是在最新版的android studio(3.4.2)中创建出来的工程有所不一样,其CMakeLists.txt文件的路径被放在了cpp目录下(以前版本改文件与build.gradle同路径),这个先不纠结,我们先看看工程目录。

  • 在cpp下创建了第三方库的目录ffmpeg
  • 将编译好的ffmpeg的include目录拷贝进来作为native-lib.cpp链接的头文件路径
  • 在ffmpeg中创建src来存放不同平台架构的动态库文件,当然名字可以任意取,关键是后面指定路径的时候要正确
  • 暂时只创建了armeabi-v7a架构,为了兼容所有平台,你可以自己定义所有架构的目录,这里的路径名称最好不要随便取

2、配置CMakeLists.txt

ffmpeg的头文件路径

${CMAKE_SOURCE_DIR}/ffmpeg/include/ 

ffmpeg的动态库路径:

${CMAKE_SOURCE_DIR}/ffmpeg/src/${ANDROID_ABI}/xxx.so

其中ANDROID_ABI就是芯片架构,所以上面的src里面的路径建议按照ABI来命名。CMAKE_SOURCE_DIR表示CMakeLists.txt文件的当前路径,在最新版的studio里面该文件被放在cpp目录下,这也是为什么我把ffmpeg的根目录放在cpp下面,就是为了能够用改环境变量。网上很多资料将ffmpeg的动态库放在app/libs或者app/src/main/jniLibs,当然都可以,但是唯一要注意的就是CMakeLists.txt的配置要正确,否则就会出现各种ninja Error:.,needed by ...,missing and no known rule to make it错误。配置如下:

cmake_minimum_required(VERSION 3.4.1)
#设置添加动态库native-lib
add_library( # Sets the name of the library.
        native-lib
        SHARED
        native-lib.cpp)
#设置添加我们需要调用的第一个预构建库
add_library(
        ffmpeg_avcodec  #为第一个so文件指定一个名字
        SHARED          #对应的文件是动态库(可以为静态库)
        IMPORTED        #标志库文件已经存在只需要导入
)
set_target_properties(  #配合add_library使用,为其指定库文件路径
        ffmpeg_avcodec  #上面的名字
        PROPERTIES IMPORTED_LOCATION  #标志下面的参数为改库的绝对路径
        ${CMAKE_SOURCE_DIR}/ffmpeg/src/${ANDROID_ABI}/libavcodec-57.so
)
#设置添加我们需要调用的第二个预构建库 这个不能批量添加,有N个so就需要N条命令
add_library(
        ffmpeg_avdevice
        SHARED
        IMPORTED
)
set_target_properties(
        ffmpeg_avdevice
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/ffmpeg/src/${ANDROID_ABI}/libavdevice-57.so
)
#指定预构建库头文件的路径${CMAKE_SOURCE_DIR}表示cmake文件的当前路径,一定要看好这个路径
include_directories(
        ${CMAKE_SOURCE_DIR}/ffmpeg/include/
)
#以前的不用改
find_library( # Sets the name of the path variable.
        log-lib
        log)
#链接所需要的所有库,注意参数是上面add_library里面指定的名字而不是so的路径
target_link_libraries( # Specifies the target library.
        native-lib
        ffmpeg_avcodec
        ffmpeg_avdevice
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

关于最新版本的cmake用法,可以参考官方文档https://developer.android.google.cn/studio/projects/add-native-code?hl=zh-cn

3、配置build.gradle

这个其实没有多少改动,值得注意的是CMakeLists.txt文件路径,有些版本可能在app目录下:

有些版本的CMakeLists.txt在cpp目录下,这里的配置为:

默认情况下,Gradle 会针对 NDK 支持的 ABI 将您的原生库构建到单独的 .so 文件中,并将其全部打包到您的 APK 中。如果您希望 Gradle 仅构建和打包原生库的特定 ABI 配置,您可以在模块级 build.gradle 文件中使用 ndk.abiFilters标志指定这些配置如下,我这里因为只准备了armeabi-v7a的动态库,所以需要过滤一下,否则就会出现找不到x86平台下的动态库。

除此之外,还需要为Gradle指定so文件路径,让gradle能够正确打包进去,这里的so的路径为src/main/cpp/ffmpeg/src进行如下配置:

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.1"
    defaultConfig {
        applicationId "com.shen.ndktest"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
                abiFilters "armeabi-v7a"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
    sourceSets.main {
        jniLibs.srcDirs = ['src/main/cpp/ffmpeg/src']
        jni.srcDirs = []
    }
}

4、在jni中调用ffmpeg的函数

在native-lib.cpp文件里面,编写代码调用avcodec_register_all函数,编译在手机运行OK

//native-lib.cpp
#include <jni.h>
#include <string>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>   //包含ffmpeg头文件
#ifdef __cplusplus
}
#endif

extern "C" JNIEXPORT jstring JNICALL
Java_com_shen_ndktest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    avcodec_register_all();   //调用ffmpeg注册函数
    return env->NewStringUTF(hello.c_str());
}

5、所踩的坑

1)、ninja: error:.., needed by ..., missing and no known rule to make it

如上图,改问题一般原因是error: 'ffmpeg/src/armeabi-v7a/libavdevice-57.so',文件没有找到,但是该文件已经在项目工程中了,所以应该从CMakeLists.txt配置的路径着手排查,检查so文件的路径是否配置正确,我这里因为没有加上CMAKE_SOURCE_DIR进行定位绝对路径,所以报出没有找到so文件。在后面的ANDROID_ABI路径不匹配的时候也会报出相同问题。

2)、ninja: error:.., needed by ..., missing and no known rule to make it

如上图,这里也是报出ninja:error...needed by ....missing and no known rule to make it,但是这里的cmakelists.txt的配置并没有什么问题,这里报出E:/workspace/NdkTest/app/src/main/cpp/ffmpeg/src/x86_64/libavcodec-57.so,会发现gradle居然主动的去加载x86_64下面的动态库,因为我并没有准备这些架构的so文件,因此需要在gradle进行平台过滤。

3)、java.lang.UnsatisfiedLinkError: dlopen failed: library "libavcodec-57.so" not found

如上图,在手机上运行,程序直接崩溃,错误提示链接libavcodec-57.so的时候失败,我最开始有怀疑是我编译出来的动态库有问题还是我的ndk有问题,最后在csdn搜索,发现原来是需要在build.gradle里面配置指定so文件的路径,我其实感到很奇怪,尼玛CMakeLists.txt不是已经指定好了吗,没办法通过soruceSets.main进行设置,我这里的路径是src/main/cpp/ffmpeg/src,这里注意的是,路径一定要精确带ABI上一级目录,目录不匹配改错误依旧

4)、[1/2] Building CXX object CMakeFiles/native-lib.dir/native-lib.cpp.o

如上图,编译的时候直接报错avcodec_register_all函数没有定义,什么鬼情况,难道动态库没有链接进去,但是检查各项配置都没有什么问题,最后不知道是在那篇文章里面发现,ffmpeg是采用的c语言编写,jni的cpp文件在调用ffmpeg的api的时候需要进行编译语言环境匹配,尼玛,恍然大悟,果断加上extern "C"

#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
}
#endif

5)、Error configuring CMake server (G:\SDT\Android\sdk\cmake\3.10.2.4988404\bin)

如上图,这个问题很奇怪,在网上搜索了很多出现这种问题的,有些说是ndk路径多了空格,有些说是编译工具版本不支持,先不管了,从错误信息上看cmake文件的57行进行连接的时候有问题,才发现avcodec放在native-lib前面,个人认为可能是avcodec属于导入进去的,应该依赖native-lib什么的,所以应该吧native-lib放在前面,如下

target_link_libraries( # Specifies the target library.
        native-lib   #注意native-lib应该放在最前面
        avcodec
        ${log-lib})

6)、fatal error: 'libavcodec/avcodec.h' file not found

如上图,虽然native-lib.cpp能够找到对应的头文件,但是ffmpeg/include/libavcodec/avcodec.h找不到同目录下的头文件,不知道怎么回事,头文件的目录已经在cmake文件里面配置了,按理说应该studio能够自动引入呢,最后搞不定,只有按照库里面头文件的路径来放置头文件路径了,因此如下将ffmpeg的头文件放置在cpp目录下,并更改对应的配置:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诸神黄昏EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值