本文旨在一步一步记录一个 JNI 实例的诞生过程及在这个过程当中我遇到的坑 & 解决方案。做为一个之前没有接触过 JNI 开发的新手,如下步骤中不少内容都看不懂,因此也不打算在本文中详细介绍,不过会在之后的博文中慢慢记录。java
新建一个 Android 工程,我姑且将该工程命名为 FirstJni。
新建一个 Java 文件,在该文件中加载 JNI 的库和定义须要经过 native 实现的方法,以下:express
public class JniMethod { static { /** * 类加载时便加载库文件,文件名与实现所需的库函数的 C 代码文件名相同 */ System.loadLibrary("JniMethod"); } // native 方法 public static native String getNativeString(String s); }
同时我将 MainActivity 修改以下,使得点击 button 时,显示经过 native 方法获取的字符串,从而能够测试 native 方法是否成功调用。apache
public class MainActivity extends AppCompatActivity { private TextView mView; private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mView = (TextView) findViewById(R.id.tv); mButton = (Button)findViewById(R.id.btn); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mView.setText(getNativeString("Native string")); } }); } }
个人项目结构如图所示:app
进入刚刚写的 JniMethod 所在的目录,使用 javac 命令编译该类,获得该类的 .class 文件,由于后面须要经过该 .class 文件生成 .h 头文件,如:less
进入项目根目录,使用 javah 命令生成与 JniMethod.class 文件相对应的 .h 头文件,如:ide
注意:函数
接下来能够在项目根目录下看到一个头文件,名为 “com_lilacouyang_test_JniMethod”,你能够继续使用该名称,但我为了简便,重命名为“JniMethod”了。学习
为了方便,新建一个 jni 目录,保存全部与 JNI 相关的文件。将刚刚生成的 JniMethod.h 移到该目录中,而后建立一个 JniMethod.c 的文件,实现定义在 JniMethod.h 头文件中方法。测试
头文件:ui
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_lilacouyang_firstjni_JniMethod */ #ifndef _Included_com_lilacouyang_firstjni_JniMethod #define _Included_com_lilacouyang_firstjni_JniMethod #ifdef __cplusplus extern "C" { #endif /* * Class: com_lilacouyang_firstjni_JniMethod * Method: getNativeString * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_lilacouyang_firstjni_JniMethod_getNativeString (JNIEnv *, jclass, jstring); #ifdef __cplusplus } #endif #endif
c 文件示例:
/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include <string.h> #include <jni.h> /* This is a trivial JNI example where we use a native method * to return a new VM String. See the corresponding Java source * file located at: * * E:\Lilac-Applications\Test\app\src\main\java\com\lilacouyang\firstjni\JniMethod.java */ jstring Java_com_lilacouyang_firstjni_JniMethod_getNativeString( JNIEnv* env, jobject thiz ) { #if defined(__arm__) #if defined(__ARM_ARCH_7A__) #if defined(__ARM_NEON__) #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a/NEON (hard-float)" #else #define ABI "armeabi-v7a/NEON" #endif #else #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a (hard-float)" #else #define ABI "armeabi-v7a" #endif #endif #else #define ABI "armeabi" #endif #elif defined(__i386__) #define ABI "x86" #elif defined(__x86_64__) #define ABI "x86_64" #elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ #define ABI "mips64" #elif defined(__mips__) #define ABI "mips" #elif defined(__aarch64__) #define ABI "arm64-v8a" #else #define ABI "unknown" #endif return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI "."); }
建立 Android.mk 和 application.mk 文件,即 Makefile 文件。
注意: Android.mk 和 application.mk 文件须要保存在根目录下的jni文件夹中,否则
可能出现Android NDK: Your APP_BUILD_SCRIPT points to an unknown file
的错误。
Android.mk
# Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := JniMethod LOCAL_SRC_FILES := JniMethod.c include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := all
进入到 Android.mk 所在的目录,运行 ndk-build 命令,获得动态库 .so 文件。
ndk-build
能够看到会在与 jni 目录同级的目录下生成包含不一样平台的 .so 文件的 libs 目录和 obj 目录,而后将 libs 目录中的内容复制到 Android 工程的 libs 目录中,运行程序便可。
通过如上六个步骤,一个简单的 JNI demo 程序就写好了,不过里面还包含了许多的知识点须要学习,如 Makefile 文件的内容是什么意思,该怎么写, C++ 代码如何实现等等。
error: base operand of '->' has non-pointer type 'JNIEnv {aka _JNIEnv}' return (*env)->NewStringUTF(env, "Hello from JNI on 2018-08-29! Compiled with ABI " ABI ".");
解决方案:
1)将C++编译器改成C语言编译器;
2)将C变量改成C++变量,如将(*env)->NewStringUTF(env, "Hello from JNI on 2018-08-29! Compiled with ABI " ABI ".");
改成return env->NewStringUTF("Hello from JNI on 2018-08-29! Compiled with ABI " ABI ".");
参见: # error: base operand of ‘->’ has non-pointer type ‘JNIEnv’