android--使用NDK-build生成so

环境:
linux:Deepin15.4rc
java:openJDK1.8

1、将ndk添加至环境变量

至添加一次即可,即下面语句使用一次之后以后就不用再使用了

echo 'export PATH=~/Desktop/Android/android-sdk-linux/ndk-bundle/:$PATH' >> ~/.bashrc 

echo ‘export PATH=你的具体ndk路径/ndk-bundle/:$PATH’ >> ~/.bashrc
让环境变量生效的方法:

  • 重启
  • 若不重启,要使用之前先用source ~/.bashrc

完成以上操作你就可以随处使用NDK-build这个命令了

2、创建native类声明

建议单独写一个native的java类,理论上MainActivity上写也是可以的,但是每次改动,你都要重新生成。
新建一个native包,里面放native的java类

package com.example.myapplication.natives;
/**
 * Created by hui on 17-4-4.
 */
public class Test {
    static {
        System.loadLibrary("test");
    }
    public native String get();
    public native void set(String str);
}

现在你肯定看到get和set方法红色吧。因为还没生成对应的.h头文件

1、生成对应的class文件

这步必须做,.class文件,在后面生成.h头文件要用到
对着那个Test.java,右键show in file manager,自动打开文件管理器

javac Test.java

生成了Test.class文件了
android--使用NDK-build生成so_第1张图片

2、 生成com_example_myapplication_natives_Test.h文件

这个头文件是自动生成的,你可以看出上面声明包package com.example.myapplication.natives;这句与那个.h文件的命名规则了么?

在文件管理器中,我们回退到这个java目录下
android--使用NDK-build生成so_第2张图片
看到这个包声明的打头目录,就在这里打开命令行

javah com.example.myapplication.natives.Test

格式:javah 包声明.Test
因为javah要从包声明最顶层寻找Test.class,所以要在这个相对路径使用命令

  • Android Studio项目上右键New->Folder->JNI Folder
  • 将.h头文件放进去
  • gradle.properties添加android.useDeprecatedNdk=true

android.useDeprecatedNdk=true这个很重要!!!
此时我们写的Test.java没有红色提示了。
android--使用NDK-build生成so_第3张图片

左边还多了两个红绿箭头。恩,Android Studio找到了.h头文件了。

重点是那个.h的命名方式

3、实现Native方法(函数)

test.cpp和test.c的实现很类似,但是它们对env的操作方式有所不同,因此用C++和C来实现同一个JNI方法,它们的区别主要集中在对env的操作上,其他都是类似的,如下所示。
C++: env->NewStringUTF(“Hello from JNI !”);
C:(*env)->NewStringUTF(env,”Hello from JNI !”);
摘自Android开发艺术探索

这里我们编写c语言

#include "com_example_myapplication_natives_Test.h"
#include 

JNIEXPORT jstring JNICALL Java_com_example_myapplication_natives_Test_get
        (JNIEnv *env, jobject thiz){
    printf("start c get ");
    return (*env)->NewStringUTF(env,"hello from jni");
}
JNIEXPORT void JNICALL Java_com_example_myapplication_natives_Test_set
(JNIEnv * env, jobject thiz, jstring string){
    printf("start c set");
    char * str = (char *)(*env)->GetStringUTFChars(env , string , NULL);
    printf("%s\n",str);
    (*env)->ReleaseStringUTFChars(env , string ,str);
}
  • 要使用printf所以导入stdio.h,要实现.h中的方法,所以导入com_example_myapplication_natives_Test.h
  • 直接复制.h文件中的声明,给声明添加变量名env , thiz (取谐音thiz,clazz等,因为重复了关键字,所以一般用z代替s), string ,接着给个大括号,写逻辑吧。
  • JNIEnv *: 表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法
  • jobject: 表示java对象中的this
  • JNIEXPORT和JNICALL: 它们是JNI中所定义的宏,可以在jni.h这个头文件中找到
    摘自Android开发艺术探索

4、生成对应so

编译前的准备:

1、编写Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c
include $(BUILD_SHARED_LIBRARY)

其中
LOCAL_MODULE表示模块名称
LOCAL_SRC_FILES表示需要参与编译的源文件
除了这两个,其他照搬即可

2、编写Application.mk

APP_ABI := armeabi

用作配置要编译的CPU架构平台的类型

3、生成so库文件

1、直接编译(想想是不可以的,因为没有指定平台(arm、x86等),当扩展知识吧)

PS:如果看不懂我括号写的意思,直接跳过这个3.1就好了,3.2也是生成so的方法

切换到jni文件夹中,命令行使用命令
小插曲:

hui@hui-PC:~/AndroidStudioProjects/MyApplication/myapplication2/src/main/jni$ gcc -shared -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -fPIC test.c -o libtest.so
In file included from com_example_myapplication_natives_Test.h:2:0,
                 from test.c:1:
/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/jni.h:45:20: fatal error: jni_md.h: 没有那个文件或目录
 #include "jni_md.h"
                    ^
compilation terminated.

找不到jni_md.h,文件管理器,一找,jni_md.h在下一级目录的linux文件夹中,所以正确命令是

gcc -shared -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux/ -fPIC test.c -o libtest.so

2、NDK-build

在jni的上一级目录,使用命令

hui@hui-PC:~/AndroidStudioProjects/MyApplication/myapplication2/src/main$ ndk-build 
[armeabi] Compile thumb  : test <= test.c
[armeabi] SharedLibrary  : libtest.so
[armeabi] Install        : libtest.so => libs/armeabi/libtest.so

自动生成了,libs目录

4、使用so

新建一个jniLibs,将libs下的文件夹全部复制到jniLibs
并在build.gradle(记得这是module的,不是project的)下添加如下

android {
...
    sourceSets{
        main{
            //jniLibs.srcDirs = ['src/main/jniLibs']
            jni.srcDirs = []
        }
    }
}

新版的gradle语法变成sourceSets{main{ }}了,不是以前的sourceSets.main{ }了。

其实网上都有,感觉自己写得复杂了。
重点有三个吧。

  • 令native的java类报错消失:添加android.useDeprecatedNdk=true,用于兼容新版dnk
  • 移动so:需要把libs下的全部复制到jniLibs,不能仅仅复制一个so文件,接着libs下面的文件你想删了也行,
  • 让app识别so路径:
android {
...
    sourceSets{
        main{
            //jniLibs.srcDirs = ['src/main/jniLibs']
            jni.srcDirs = []
        }
    }
}

不这么做会提示找不到so,奇怪了,so在jniLibs下面,却要写jni.srcDirs

PS:如果出现奇怪的错误,那么clear一下project

最后简单调用即可,这里简单的在onreate里面调用toast就算了

Toast.makeText(MainActivity.this , "" + new Test().get(), Toast.LENGTH_SHORT).show();

你可能感兴趣的:(android)