Android开发学习之路--NDK、JNI之初体验

时间:2022-12-11 19:13:12

好久没有更新博客了,最近一直在看一个仿微信项目,然后看源码并自己实现下,相信经过这个项目可以让自己了解一个项目中的代码以及种种需要注意的事项。不知不觉中博客已经快要40w访问量,而且排名也即将突破3000了,在此感谢朋友们的支持和认可。今天趁着有点时间就来完成早就想要完成的jni技术了。

说到jni可能初学者会不知道,其实就是java native interface,也就是java代码需要调用底层的c/c++代码,那么就需要通过jni来实现了,android手机的底层是linux,而linux之上跑的一般都是c/c++代码,而我们app是java代码,虽然一般情况下开发app是不需要了解jni的,但是有些需要高效率的事情,比如音视频编解码,比如3d绘图等就需要用c/c++来实现了,而且这些算法在c/c++上都是非常成熟的。讲了这么多,这里还是简单地来实现下了。

记得以前在windows下用cgwin来编译ndk很不舒服,现在用mac了,用android studio,那就在这个环境下来简单实现一个测试例子了,android studio确实方便。首先我们需要下载ndk,http://www.androiddevtools.cn,不*可以在这里下载。如果可以*,那么就去这里。http://developer.android.com/intl/zh-cn/ndk/downloads/index.html。下载好后,放到自己想要放的目录下,然后执行如下命令:

chmod a+x android-ndk-r10e-darwin-x86_64.bin
./android-ndk-r10e-darwin-x86_64.bin

以上命令其实就是把下载好的包解压缩出来。最后会生产一个android-ndk-r10的文件夹,里面就是一系列ndk需要用的东西了。

    既然已经下载好了ndk,那么接下来就来测试下了。首先是新建工程emJniStudy。编写ndkjniutils,代码如下:

package com.jared.emjnistudy;

/**
* Created by jared on 16/2/28.
*/
public class NdkJniUtils {
static {
System.loadLibrary("emJniLibName"); //defaultConfig.ndk.moduleName
} public native String getCLanguageString();
}

这里的loadLibrary主要是加载.so文件,一般linux下的库文件都是.so结尾的。这里的库名字是emJniLibName。这里后面再了解怎么定义这个名字的。然后有一个native开头表示是jni的接口。这里的函数是获取c的string,也就是c代码中的一串字符串了。

接着我们来根据这个java代码实现c代码的头文件,这里先build下我们的代码,需要有一个class文件才行。

进入当前工程目录:

cd app/build/intermediates/classes/debug

然后通过命令行:

javah -jni com.jared.emjnistudy.NdkJniUtils

其中com.jared.emjnistudy是包名,NdkJniUtils是java代码。然后会在当前目录下生成

com_jared_emjnistudy_NdkJniUtils.h

打开文件可以看到源代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jared_emjnistudy_NdkJniUtils */ #ifndef _Included_com_jared_emjnistudy_NdkJniUtils
#define _Included_com_jared_emjnistudy_NdkJniUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jared_emjnistudy_NdkJniUtils
* Method: getCLanguageString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_jared_emjnistudy_NdkJniUtils_getCLanguageString
(JNIEnv *, jobject); #ifdef __cplusplus
}
#endif
#endif

这里就会生产一个需要c实现的函数接口了。接着在main目录下新建jni目录,然后把这个头文件拷贝到jni目录下,然后新建一个c文件,命名为jnitest.c。编写jnitest.c如下:

#include "com_jared_emjnistudy_NdkJniUtils.h"

JNIEXPORT jstring JNICALL Java_com_jared_emjnistudy_NdkJniUtils_getCLanguageString
(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "This is Jni test!!!");
}

这里只要实现头文件函数即可,也就是return一串字符串。这样库文件和jni接口都准备好了,接着呢我们需要来配置下编译的gradle了。首先是:

vi gradle.properties

配置文件最后添加一行代码如下:

android.useDeprecatedNdk=true

接着修改app目录下的build.gradle:

apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "23.0.2" defaultConfig {
applicationId "com.jared.emjnistudy"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0" ndk {
moduleName "emJniLibName" //生成的so名字
abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库。目前可有可无。
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
}

这里在defaultConfig中添加了ndk。其中moduleName就是上面java代码中load的名字,emJniLibName。这里制定了三种abi体系结构下的so库,所谓体系结构就是linux下需要编译不同芯片需要不同的交叉编译工具链。因为不同的芯片,比如是pc,那么需要用gcc编译才可以在pc上跑程序,如果是arm的就需要用arm提供的交叉编译工具才可以跑。

一切准备就绪,那么最后我们在Activity中需要显示c代码中的这句字符串,修改layout代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.jared.emjnistudy.MainActivity"> <TextView
android:id="@+id/hello"
android:text="Hello World!"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

修改MainActivity代码:

package com.jared.emjnistudy;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView; public class MainActivity extends AppCompatActivity { private TextView helloJni;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); helloJni = (TextView)findViewById(R.id.hello);
NdkJniUtils jniUtils = new NdkJniUtils();
String text = jniUtils.getCLanguageString();
helloJni.setText(text);
}
}

然后我们运行下代码看下效果如下:

Android开发学习之路--NDK、JNI之初体验

从上图可以看到我们需要的内容,ndk编译,jni实现ok了。之后的话很多东西都可以在c中来完成了。是不是很简单,比起以前的cgwin不知道要方便多少。这里看下jni的c代码到底是怎么编译的呢? 进入目录

cd app/build/intermediates/ndk/debug

接着我们我们看下一个Android.mk。如果看过android源码就会看过很多的Android.mk,其实这个就类似于linux下的Makefile,也就是编译代码用的,就是编译.so库的脚本。看下代码如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) LOCAL_MODULE := emJniLibName
LOCAL_LDFLAGS := -Wl,--build-id
LOCAL_SRC_FILES := \
/Users/jared/Documents/workspace/android/emJniStudy/app/src/main/jni/jnitest.c \ LOCAL_C_INCLUDES += /Users/jared/Documents/workspace/android/emJniStudy/app/src/main/jni
LOCAL_C_INCLUDES += /Users/jared/Documents/workspace/android/emJniStudy/app/src/debug/jni include $(BUILD_SHARED_LIBRARY)

这里编译后的库名字就是emJniLibName,需要进行编译的源代码是jnitest.c了。

然后我们看下编译生产的project下的.so文件。

Android开发学习之路--NDK、JNI之初体验

logcat打印信息如下,已经加载成功了。

02-28 00:36:30.220 1266-1266/com.jared.emjnistudy D/dalvikvm: Trying to load lib /data/app-lib/com.jared.emjnistudy-1/libemJniLibName.so 0xb1da7598
02-28 00:36:30.220 1266-1266/com.jared.emjnistudy D/dalvikvm: Added shared lib /data/app-lib/com.jared.emjnistudy-1/libemJniLibName.so 0xb1da7598
02-28

基本上jni的简单使用已经ok了。接着我们继续来实现个a+b。修改NdkJniUtils代码如下:

package com.jared.emjnistudy;

/**
* Created by jared on 16/2/28.
*/
public class NdkJniUtils {
static {
System.loadLibrary("emJniLibName"); //defaultConfig.ndk.moduleName
} public native String getCLanguageString(); public native int calAAndB(int a, int b);
}

这里添加了calAAndB方法。然后重新生成头文件。如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jared_emjnistudy_NdkJniUtils */ #ifndef _Included_com_jared_emjnistudy_NdkJniUtils
#define _Included_com_jared_emjnistudy_NdkJniUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jared_emjnistudy_NdkJniUtils
* Method: getCLanguageString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_jared_emjnistudy_NdkJniUtils_getCLanguageString
(JNIEnv *, jobject); /*
* Class: com_jared_emjnistudy_NdkJniUtils
* Method: calAAndB
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_jared_emjnistudy_NdkJniUtils_calAAndB
(JNIEnv *, jobject, jint, jint); #ifdef __cplusplus
}
#endif
#endif

接着修改c代码如下:

#include "com_jared_emjnistudy_NdkJniUtils.h"

JNIEXPORT jstring JNICALL Java_com_jared_emjnistudy_NdkJniUtils_getCLanguageString
(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "This is Jni test!!!");
} JNIEXPORT jint JNICALL Java_com_jared_emjnistudy_NdkJniUtils_calAAndB
(JNIEnv *env, jobject obj, jint a, jint b) {
return (a+b);
}

最后我们在MainActivity中添加一个代码:

package com.jared.emjnistudy;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast; public class MainActivity extends AppCompatActivity { private TextView helloJni;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); helloJni = (TextView)findViewById(R.id.hello);
NdkJniUtils jniUtils = new NdkJniUtils();
String text = jniUtils.getCLanguageString();
helloJni.setText(text); String res = String.valueOf(jniUtils.calAAndB(10, 30));
Toast.makeText(getApplicationContext(), res, Toast.LENGTH_LONG).show();
}
}

运行看下效果:

Android开发学习之路--NDK、JNI之初体验
    很明显10+30等于40,最后返回了我们要的结果。

既然已经会了简单的jni调用了,但是发现了一个问题,No JNI_OnLoad found。貌似少了onload函数,记得以前研究android源码的时候,onload函数是需要的,还有一大推函数调用的函数指针,还有注册函数。

02-28 02:58:38.366 20431-20431/com.jared.emjnistudy D/dalvikvm: No JNI_OnLoad found in /data/app-lib/com.jared.emjnistudy-2/libemJniLibName.so 0xb1d9fc90, skipping init

那么接下来我们就来小研究下,简单地实现下这个小模版了。修改.h和.c代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jared_emjnistudy_NdkJniUtils */ #ifndef _Included_com_jared_emjnistudy_NdkJniUtils
#define _Included_com_jared_emjnistudy_NdkJniUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jared_emjnistudy_NdkJniUtils
* Method: getCLanguageString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL getCLanguageString
(JNIEnv *, jobject); /*
* Class: com_jared_emjnistudy_NdkJniUtils
* Method: calAAndB
* Signature: (II)I
*/
JNIEXPORT jint JNICALL calAAndB
(JNIEnv *, jobject, jint, jint); #ifdef __cplusplus
}
#endif
#endif

这里把函数名都简化了,待会儿就知道为什么了,接着是.c代码:

#include <string.h>
#include <assert.h>
#include "com_jared_emjnistudy_NdkJniUtils.h" JNIEXPORT jstring JNICALL getCLanguageString
(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "This is Jni test!!!");
} JNIEXPORT jint JNICALL calAAndB
(JNIEnv *env, jobject obj, jint a, jint b) {
return (a+b);
} //参数映射表
static JNINativeMethod methods[] = {
{"getCLanguageString", "()Ljava/lang/String;", (void*)getCLanguageString},
{"calAAndB", "(II)I", (void*)calAAndB},
}; //自定义函数,为某一个类注册本地方法,调运JNI注册方法
static int registerNativeMethods(JNIEnv* env , const char* className , JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
} if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
} return JNI_TRUE;
} static int registerNatives(JNIEnv* env)
{
const char* kClassName = "com/jared/emjnistudy/NdkJniUtils";//指定要注册的类
return registerNativeMethods(env, kClassName, methods, sizeof(methods) / sizeof(methods[0]));
} JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL); //动态注册,自定义函数
if (!registerNatives(env)) {
return -1;
} return JNI_VERSION_1_4;
}

这里可以发现JNI_Onload函数,当启动程序的时候,会加载库文件,就会调用这个函数,之前简单的例子没有这个函数,所以就有log打印信息了。接着在onload函数中,我们注册了nativemethods。这里注册了刚刚实现的两个方法。

可以看出methods数组中第一个和第三个参数比较好理解,那么第二个参数呢?其实第二个参数可以参考头文件,一模一样拉过来就好了。其中的意思就是()里的表示函数的参数,()表示没有参数,(II)表示两个参数,都是int。后面跟的Ljava/lang/String表示返回值是String类型的,I表示的是int类型。

基本上这个模板就非常地清晰了。我们之后就可以基于这个模版来写了。至于更多的知识,以后用到了再学习了。当然,运行结果也是我们所需要的。

Android开发学习之路--NDK、JNI之初体验的更多相关文章

  1. Android开发学习之路--数据持久化之初体验

    上班第一天,虽然工作上处于酱油模式,但是学习上依旧不能拉下,接着学习android开发吧,这里学习数据持久化的 知识. 其实数据持久化就是数据可以保存起来,一般我们保存数据都是以文件,或者数据库的形式 ...

  2. Android开发学习之路--Broadcast Receiver之初体验

    学习了Activity组件后,这里再学习下另一个组件Broadcast Receiver组件.这里学习下自定义的Broadcast Receiver.通过按键自己发送广播,然后自己接收广播.新建MyB ...

  3. Android开发学习之路--百度地图之初体验

    手机都有gps和网络,通过gps或者网络可以定位到自己,然后通过百度,腾讯啊之类的地图可以显示我们的地理位置.这里学习下百度地图的使用.首先就是要申请开发者了,这个详细就不多讲了.http://dev ...

  4. Android开发学习之路--Content Provider之初体验

    天气说变就变,马上又变冷了,还好空气不错,阳光也不错,早起上班的车上的人也不多,公司来的同事和昨天一样一样的,可能明天会多一些吧,那就再来学习android吧.学了两个android的组件,这里学习下 ...

  5. Android开发学习之路--网络编程之初体验

    一般手机都是需要上网的,一般我们的浏览器就是个webview.这里简单实现下下功能,先编写Android的layout布局: <?xml version="1.0" enco ...

  6. Web开发学习之路--Springmvc&plus;Hibernate之初体验

    本来想继续学习android的,可是用到了android和服务器交互,需要实现个login的功能,苦于没有这么个环境,那就只能自己来搭建了.既然已经基本上可以玩web了,那么接下来使用web开源的框架 ...

  7. Android开发学习之路--网络编程之xml、json

    一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载 ...

  8. Android开发学习之路--Android Studio cmake编译ffmpeg

      最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...

  9. Android开发学习之路--Android系统架构初探

    环境搭建好了,最简单的app也运行过了,那么app到底是怎么运行在手机上的,手机又到底怎么能运行这些应用,一堆的电子元器件最后可以运行这么美妙的界面,在此还是需要好好研究研究.这里从芯片及硬件模块-& ...

随机推荐

  1. WPF中通过代码设置控件的坐标

    用WPF做贪吃蛇小游戏时,发现了一个问题: 贪吃蛇的移动,我是通过不断刷新Rectangle来实现(贪吃蛇的身体由一组Rectangle组成),因此需要不断调整Rectangle的坐标,但是WPF中没 ...

  2. SpringMVC 2&period;5&period;6 noMapping

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  3. C&num; DevExpress&lowbar;gridControl 行号行样式

    #region 行号 /// <summary> /// 行号 /// </summary> /// <param name="sender"> ...

  4. 极客DIY:使用树莓派制作一架四轴无人机

    如果你想DIY一台属于自己的无人机,那么接下来可以阅读这篇文章,阅读完毕之后也许对你会有启发. 这个项目主要用到的零件主要来自Erle Robotics(一个使用Linux系统的开源四轴飞行器项目). ...

  5. 如何在Java客户端调用RESTful服务

    在这个例子中,我们将看到如何使用java.net包实用工具,创建一个访问REST服务RESTful的客户端.当然这不是创建一个RESTful客户端最简单的方法,因为你必须自己读取服务器端的响应,以及J ...

  6. Kinetic使用注意点--image

    new Image(config) 参数: config:包含所有配置项的对象. { image: "图片对象", crop: "图片裁剪对象", fill: ...

  7. 命令行界面下的用户和组管理之useradd和passwd命令的使用

    命令行界面下的用户和组的管理之useradd和passwd命令的使用 useradd [-c comment] [-d dir] [-e expire] [-g group] [-G group1,g ...

  8. &lbrack;Oracle&rsqb; 浅谈Sequence

    Oracle的Sequence是一种数据库对象,它可以生成有序数字,主要用于主键的自动生成.如果没有Sequence,主键的自动生成必须得在代码逻辑里实现,大致过程是:获取当前主键值,新主键值=当前主 ...

  9. ostringstream的使用方法

    ostringstream的使用方法 [本文来自]http://www.builder.com.cn/2003/0304/83250.shtml http://www.cppblog.com/alan ...

  10. 1st&lowbar;homework&lowbar;SE--四则运算题目生成器

    0x00 Code 查询源代码及README请点此 0x01 需求分析 实现一个自动生成小学四则运算题目的命令行程序. 0x02 功能设计 主要功能为: 接受用户输入以便知道要出多少道题目python ...