jni之ndk与cmake的最简单的demo

1,155 阅读3分钟

Jni实战

以下所有代码,我都提交到我的github上面了

生成CPP的头文件

首先让我们从最简单的开始

  1. 新建TestJni.java 文件
package com.tele.jni; //放置在这个目录底下

public class TestJni{
	public native static long _get_UID();
}
  1. 然后在根目录执行 javac com/tele/jni/TestJni.java 会生成 com/tele/jni/TestJni.class 文件

  2. 然后再执行 javah -jni -classpath . com.tele.jni.TestJni ,就会在执行目录生成 com_tele_jni_TestJni.h

    注意

    1. 这里不能有 .class 后缀,
    2. 必须用 . 来拼接到最终的class文件,要不然会报下面的错误
    3. 必须在根目录下执行
Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class name: TestJni.class
        at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:129)
        at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:107)
        at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:64)
        at com.sun.tools.javah.JavahTask.run(JavahTask.java:503)
        at com.sun.tools.javah.JavahTask.run(JavahTask.java:329)
        at com.sun.tools.javah.Main.main(Main.java:46)

//or
错误: 找不到 'TestJni' 的类文件。
  1. 移动文件 com_tele_jni_TestJni.hcom/tele/jni/里面,新建 TestJni.cpp
#include "com_tele_jni_TestJni.h"
JNIEXPORT jlong JNICALL Java_com_tele_jni_TestJni__1get_1UID(JNIEnv *, jclass){
	return 100;
}  

至此准备工作做完了,接下来就是编译成Android可以运行的so

这里提供两种方式生成so,

第一种: 通过android.mk 【以下采用这种方式的生成】

这里需要注意,android.mk 在不改变默认行为以前,需要放置在jni目录下,然后再jni同级目录执行ndk-build 就可以。在这里就是在目录 com/tele 这个目录底下执行,会生成 libs obj 两个文件夹

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_SRC_FILES := TestJni.cpp
LOCAL_CFLAGS += -fPIC
LOCAL_CPP_INCLUDES := .
LOCAL_MODULE:= testJni
include $(BUILD_SHARED_LIBRARY)

第二种: 通过cmake

如果看不懂cmake的,可以查看我之前的文章。

cmake_minimum_required(VERSION 3.0 FATAL_ERROR)

project (testJni)

add_library(${PROJECT_NAME} SHARED testJni.cpp)

这里写完,可以需要在工程项目d的 build.gradle里面添加 如下代码

    externalNativeBuild {
        cmake {
            path "src\\main\\java\\com\\tele\\jni\\CMakeLists.txt"
        }
    }

新建Android项目

  1. 使用Androidstudio 新建一个EmptyActivity的项目,

  2. 将我们刚才生成的文件拷贝进去,【当然以后你可以去掉某些文件】

如果你按照笔者说的步骤,到这里,你应该会是这样的目录层级【当然我去掉了AS自动生成的一些文件】

 >tree /f
 com
   ├─tele
   │  ├─jni
   │  │      Android.mk
   │  │      com_tele_jni_TestJni.h
   │  │      TestJni.class
   │  │      TestJni.cpp
   │  │      TestJni.java
   │  │
   │  ├─libs
   │  │  └─armeabi
   │  │          libtestJni.so
   │  │
   │  └─obj
   │      └─local
   │          └─armeabi
   │              │  libstdc++.a
   │              │  libtestJni.so
   │              │
   │              └─objs
   │                  └─testJni
   │                          TestJni.o
   │                          TestJni.o.d
   │
   └─txznet
       └─testjniapp
               MainActivity.java
  1. 需要把自动生成的 /libs/armeabi/libtestJni.so 整个拷贝到工程项目里面的 libs所在的目录

  2. 需要补充代码:

你要装载so库,才可以执行相应的方法:java中采用 System.loadLibrary("testJni");的方法加载,其实内部就是dlopen

//TestJni.java
public class TestJni{
	static {
		System.loadLibrary("testJni");
	}

	public native static long _get_UID();
}
  1. 我们在界面随便拖动一个按钮,设置点击事件,运行。
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void clicktestjni(View view) {
        //点击事件,打印出从cpp中获取的值
        Toast.makeText(this, "hello :"+TestJni._get_UID(), Toast.LENGTH_SHORT).show();
    }
}

运行:报错找不到库。

 Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.txznet.testjniapp-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]] couldn't find "libtestJni.so"
  1. 因为我们还没有在 工程的 build.gradle 中增加库查找的路径
android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}
  1. 连接夜神模拟器。我这里使用了夜神模拟器,你也可以使用别的模拟器,都是可以的。
adb connect 127.0.0.1:62001

就可以运行了。

  1. 运行,你就会发现成功执行了,附图

说在最后

如果是cmake 的话 需要在gradle中增加如下代码:

android {
    compileSdkVersion 28

    defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

如果是ndk编译的话,则需要在gradle中增加如下代码

android {
    defaultConfig {
        ndk {
       	//指定abi的版本,可以不加
            abiFilters "armeabi-v7a" 
        }
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

如果你正在编译我的项目的话,请注意查看 build.gradle 里面是使用cmake 还是使用ndk.

感谢您关注我的公众号: