[✔️] android JNI学习

934 阅读2分钟

举个栗子

github.com/tidys/learn…

  • build.gradle设置
android {
    defaultConfig {
        ndk {
            moduleName "native-lib" // 指定库的名字
            abiFilters "x86" // 如果不指定平台,AndroidStudio默认会生成所有平台的so
        }
    }
    externalNativeBuild {
        cmake {
            version "3.10.2"
            path "CMakeLists.txt"
        }
    }
}


  • CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp )
find_library(log-lib log )
target_link_libraries(native-lib ${log-lib} )
  • src/main/cpp/native-lib.cpp

可以使用java-h生成对应的头文件,

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_example_learnndk_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++!";
    return env->NewStringUTF(hello.c_str());
}

将APK解压后发现在lib目录的确存在我们编写的c++库

image.png

编译自己的c++库

编译使用jni有两种构建方式,:

  • CMake:Android studio新的构建方式,Project Path需要选择CMakeList.txt文件路径,jni会按照这个脚本来进行编译,具体脚本的编写看下面。
  • ndk-build:老eclipse的构建方式,也就是Android.mk、Appli.mk的形式。

ndk-build

Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := native-lib
LOCAL_SRC_FILES := ../src/main/cpp/native-lib.cpp

# include $(BUILD_SHARED_LIBRARY) # 编译为动态库
# include $(BUILD_STATIC_LIBRARY) # 编译为静态库
  • LOCAL_MODULE :lib的名字
  • LOCAL_SRC_FILES:编译的源代码
  • LOCAL_C_INCLUDES: 头文件目录
  • LOCAL_SHARED_LIBRARIES: 当前模块依赖的动态库模块
  • LOCAL_STATIC_LIBRARIES: 当前模块依赖的静态库模块
  • PREBUILT_STATIC_LIBRARY:用来指定一个预先已经编译好的静态库
  • PREBUILT_SHARED_LIBRARY:用来指定一个预先已经编译好的动态库,与BUILD_SAHRED_LIBRARYBUILD_STATIC_LIBRARY不同,该模块对应的LOCAL_SRC_FILES不能是源文件,而只能是一个已经编译好的的动态库的路径,如foo/libfoo.so
  • LOCAL_LDFLAGS := 静态动态都可以

Application.mk

项目级别的设置

  • APP_STL: c++标准库stl
    APP_STL := c++_shared # 会多出来一个libc++_shared.so
    APP_STL := c++_static 
    
  • APP_CPPFLAGS: 编译器选项
    APP_CPPFLAGS := -std=c++11 # 支持c++11
    

常见错误

ld: error: undefined symbol: std::__ndk1::basic_string<char,

APP_STLApplication.mk中定义,不能在Android.mk中定义,没有用

APP_STL := c++_shared # 动态库使用
APP_STL := c++_static # 静态库使用
APP_STL := gnustl_static # 低版本 静态库使用

项目之间相互引用

  • settings.gradle
include ':Project1' 
project(':Project1').projectDir = new File(settingsDir, '../Project1')
  • build.gradle
dependencies {  
  compile project(":Project1")  
}

import-module

导入外部模块的.mk文件 ,和 include基本一样。

  • include导入的是由我们自己写的.mk,路径是.mk文件的绝对路径
  • import-module导入的是外部库、外部模块提供的.mk,路径是相对于NDK_MODULE_PATH中的路径列表的相对路径。

示例:

$(call import-module,相对路径)

NDK_MODULE_PATH

设置NDK_MODULEPATH的几种方法:

  1. 系统环境变量
  2. 直接将NDK_MODULE_PATH=路径1:路径2 加到 ndk-build命令的参数后面,ndk-build的参数最终会直接传给make
  3. 在import之前加入$(call import-add-path,$(LOCAL_PATH)),cocos2dx选择的是这种方式

Gradle Wrapper

Gradle Wrapper称为Gradle包装器,是对Gradle的一层包装。为什么需要Gradle Wrapper呢?比如在一个开发团队中,如果每进来一个成员,都需要在计算机中安装Gradle,这个时候运行Gradle的环境和版本就会对构建结果带来不确定性。针对这个问题,Gradle提供了一个解决方案,那就是Gradle Wrapper,它是一个脚本,可以在计算机没有安装Gradle的情况下运行Gradle构建,并且能够指定Gradle的版本,开发人员可以快速启动并运行Gradle项目,而不必手动安装,这样就标准化了项目,从而提高了开发效率。AS在新建项目时会自带Gradle Wrapper,这也是我们很少去单独去下载安装Gradle的原因。

执行:

gradle wrapper

这时会在项目根目录中生成如下文件:

├── gradle 
│ └── wrapper 
│ ├── gradle-wrapper.jar 
│ └── gradle-wrapper.properties 
├── gradlew 
└── gradlew.bat
  • gradle-wrapper.jar :包含Gradle运行时的逻辑代码。
  • gradle-wrapper.properties :负责配置包装器运行时行为的属性文件,用来配置使用哪个版本的Gradle等属性。
  • gradlew:Linux平台下,用于执行Gralde命令的包装器脚本。
  • gradlew.bat:Windows平台下,用于执行Gralde命令的包装器脚本。