Android JNI学习笔记1(Android Studio)

Android系统上有两种开发包:SDK和NDK。

Android SDK (software development kit) 提供了让我们编写的java代码成功运行在Android平台上所需的一系列工具和API。
Android NDK(Native Development Kit)是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。

一、添加NDK编译路径
在项目根目录下local.properties中添加编译NDK的路径:

ndk.dir=/mnt/sda1/android-sdk-linux/ndk-bundle

也可以通过点击项目右键-选择 Open Module Settings,然后指定Android NDK location。
这里写图片描述

之所以要配置这个目录,目的是让我们开发的项目在使用gradle编译时能够找到NDK相关编译路径

二、JNI编程
1、so文件的引入

AndroidStudio目录src/main/jniLibs/是AndroidStudio默认提供的ndk目录,用来存放已经编译好的.so文件。另外,我们也可以通过gradle配置来指定.so文件的存放目录。

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

这里写图片描述

假如我们在src/main/jniLibs/目录下引入了libhellojni.so文件,在导入了这个.so文件之后,下面需要做的就是在相应的java类文件中,加载这个静态库。

package com.example.hellojni;

class HelloJni {
     public native String stringFromJNI();

     static {
         System.loadLibrary("hellojni");
     }
}

加载了这个静态库之后,我们就可以在我们的Android项目中调用stringFromJNI方法了。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    HelloJni jni = new HelloJni();
    Log.d("MainActivity", jni.stringFromJNI());
}

因为,NDK下的C/C++函数和Java桥接的函数命名是有约束的,规则如下:

Java_PackageName_ClassName_MethodName

也就是说so中的方法名称跟我们的项目是有对应关系的,在编译so的时候,应该事前约定好项目名称、使用的类名称和方法名称。例如,我们是在项目com.example.hellojni的HelloJni类中使用stringFromJNI本地方法,所以,so中文件中对应的该方法的名称必须为:

jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello World!");
}

否则,就会出现找不到该方法的错误。

所以一般情况下,我们不会直接使用第三方so文件,除非该so文件是特意为该项目所写的,因为so文件的函数命名跟所使用项目的耦合性太强,一般情况下,第三方库会将so文件进行包装,然后提供给我们使用,也就是说它会将本地方法的调用封装在一个jar文件里面,这样so的本地方法只需要跟它的jar库保持一致,而我们只需要调用jar文件所提供的接口就可以了,这样就充分的降低了耦合性,我们的项目跟它的so文件没有任何的关系。

2、编译自己的so文件

AndroidStudio默认的源码存放目录是:src/main/jni,如果你没发现此目录,那么你可以手动创建一个,把所有的C/C++源码放在此文件下,当然并非必须要放在此目录下,你可以自定义目录,然后在build.gradle中做一个资源路径指定即可:

android{
    sourceSets.main {
        jni.srcDir 'src/main/otherDir' 
    }
}

上面我们知道so中的本地方法它依赖于所使用的项目和类。所以一般的做法是:
(1)在需要使用的类中声明native本地方法。

package com.example.hellojni;

class HelloJni {
     public native String stringFromJNI();
}

(2)编译该文件,我们就得到一个class文件,进入项目目录,执行下面命令。

javah -jni com.example.hellojni.HelloJni

这样我们就得到了一个.h的头文件,这个头文件中,我们就可以看到本地方法stringFromJNI的声明。

#ifndef _Included_com_example_hellojni_HelloJni
#define _Included_com_example_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

(3)在src/main/jni目录下面创建C/C++实现文件。

#include <jni.h>

#include "com_example_hellojni_HelloJni.h"

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
  (JNIEnv *env, jobject obj) {
    return (*env)->NewStringUTF(env, "Hello World!");
}

(4)编译生成so文件

第一种方式:手工编译

在我们的C/C++文件所在目录,还要创建如下两个文件:

Android.mk
Applicatoin.mk (非必要)

Android.mk

Android.mk文件用来指定源码编译的配置信息,例如工作目录,编译模块的名称,参与编译的文件等,大致内容如下:

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

LOCAL_PATH:设置工作目录,而my-dir则会返回Android.mk文件所在的目录。
CLEAR——VARS:清除几乎所有以LOCAL——PATH开头的变量(不包括LOCAL_PATH)。
LOCAL_MODULE:用来设置模块的名称。
LOCAL_SRC_FILES:用来指定参与模块编译的C/C++源文件名。
BUILD_SHARED_LIBRARY:作用是指定生成的静态库或者共享库在运行时依赖的共享库模块列表。

Application.mk
这个文件用来配置编译平台相关内容,我们最常用的估计只是APP_ABI字段,它用来指定我们需要基于哪些CPU架构的.so文件,当然你可以配置多个平台:

APP_ABI := armeabi armeabi-v7a x86 mips

如果不创建Application.mk文件,那么手动编译的.so文件只有armeabi平台一个版本,其他平台的不会被编译。

假设我们配置好了Android.mk文件,那么接下来我们就可以执行如下命令来生成.so文件了,我们假设开发NDK的目录为默认目录:

cd src/main/jni/
ndk-build

如果顺利,那么你将会看到,在src/main/目录下会多了一个libs目录,这是NDK使用命令编译.so文件的生成的默认目录,而AndroidSutdio默认加载NDK的目录是jniLibs,那么你有两种解决方式:

配置build.gradle资源目录
使用 ndk-build NDK_LIBS_OUT=../jniLibs 指定具体的输出目录

第二种方式:gradle配置,自动编译

app module目录下的build.gradle中设置库文件名(生成的so文件名)。找到gradle文件的defaultConfig这项,在里面添加如下内容:

android.ndk {
        // 模块名称
        moduleName = "hellojni" //生成的so名字

        // 指定编译平台,更多平台信息 参见https://developer.android.com/ndk/guides/abis.html#sa
        abiFilters "armeabi", "armeabi-v7a", "x86"
        /*
         * Other ndk flags configurable here are
         * cppFlags.add("-fno-rtti")
         * cppFlags.add("-fno-exceptions")
         * ldLibs.addAll(["android", "log"])
         * stl       = "system"
         */
    }

配置完成之后,我们直接运行项目就可以了,编译过程gradle已经帮我处理了。运行项目之后,进入项目app/build/intermediates/ndk/debug下面可以看到生成的so文件,并且我们也可以看到一个Android.mk文件。所以其实原理是一样的,只是一个是手动完成的一个是gradle自动完成的。

使用gradle的好处是,自动编译生成apk文件,并且把相关的.so文件打包到apk安装包中。

(5)在System.loadLibrary(“hellojni”)加载这个so文件。

class HelloJni {
     public native String stringFromJNI();

     static {
         System.loadLibrary("hellojni");
     }
}   

(6)完成上面操作之后,我们就可以直接调用HelloJni中的stringFromJNI本地方法了。

参考文章:

使用 AndroidStudio 进行 NDK 开发(一)

Android Studio Jar、so、library项目依赖

NDK-JNI实战教程(一) 在Android Studio运行第一个NDK程序

欢迎关注微信公众号:DroidMind
精品内容独家发布平台


呈现与博客不一样的技术干货

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值