Android开发学习之路--NDK、JNI之初体验
好久没有更新博客了,最近一直在看一个仿微信项目,然后看源码并自己实现下,相信经过这个项目可以让自己了解一个项目中的代码以及种种需要注意的事项。不知不觉中博客已经快要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);
}
}
然后我们运行下代码看下效果如下:
从上图可以看到我们需要的内容,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文件。
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();
}
}
运行看下效果:
既然已经会了简单的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之初体验的更多相关文章
- Android开发学习之路--数据持久化之初体验
上班第一天,虽然工作上处于酱油模式,但是学习上依旧不能拉下,接着学习android开发吧,这里学习数据持久化的 知识. 其实数据持久化就是数据可以保存起来,一般我们保存数据都是以文件,或者数据库的形式 ...
- Android开发学习之路--Broadcast Receiver之初体验
学习了Activity组件后,这里再学习下另一个组件Broadcast Receiver组件.这里学习下自定义的Broadcast Receiver.通过按键自己发送广播,然后自己接收广播.新建MyB ...
- Android开发学习之路--百度地图之初体验
手机都有gps和网络,通过gps或者网络可以定位到自己,然后通过百度,腾讯啊之类的地图可以显示我们的地理位置.这里学习下百度地图的使用.首先就是要申请开发者了,这个详细就不多讲了.http://dev ...
- Android开发学习之路--Content Provider之初体验
天气说变就变,马上又变冷了,还好空气不错,阳光也不错,早起上班的车上的人也不多,公司来的同事和昨天一样一样的,可能明天会多一些吧,那就再来学习android吧.学了两个android的组件,这里学习下 ...
- Android开发学习之路--网络编程之初体验
一般手机都是需要上网的,一般我们的浏览器就是个webview.这里简单实现下下功能,先编写Android的layout布局: <?xml version="1.0" enco ...
- Web开发学习之路--Springmvc+Hibernate之初体验
本来想继续学习android的,可是用到了android和服务器交互,需要实现个login的功能,苦于没有这么个环境,那就只能自己来搭建了.既然已经基本上可以玩web了,那么接下来使用web开源的框架 ...
- Android开发学习之路--网络编程之xml、json
一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载 ...
- Android开发学习之路--Android Studio cmake编译ffmpeg
最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...
- Android开发学习之路--Android系统架构初探
环境搭建好了,最简单的app也运行过了,那么app到底是怎么运行在手机上的,手机又到底怎么能运行这些应用,一堆的电子元器件最后可以运行这么美妙的界面,在此还是需要好好研究研究.这里从芯片及硬件模块-& ...
随机推荐
- bzoj4830 hnoi2017 抛硬币
题目描述 小 A 和小 B 是一对好朋友,他们经常一起愉快的玩耍.最近小 B 沉迷于**师手游,天天刷本,根本无心搞学习.但是已经入坑了几个月,却一次都没有抽到 SSR,让他非常怀疑人生.勤勉的小 A ...
- 【Codeforces Round #430 (Div. 2) A C D三个题】
·不论难度,A,C,D自己都有收获! [A. Kirill And The Game] ·全是英文题,述大意: 给出两组区间端点:l,r,x,y和一个k.(都是正整数,保证区间不为空),询问是否 ...
- C++多态?
以前看资料只是理解多态是"一个接口,多种调用" ,但是没有理解其真正意思,不明白具体咋么实现. 不过看了这位博主的博客后对多态有了一些理解,链接:https://www.cnblo ...
- Java Servlet 笔记4
Servlet 客户端 HTTP 请求 当浏览器请求网页时,它会向 Web 服务器发送特定信息,这些信息不能被直接读取,因为这些信息是作为 HTTP 请求的头的一部分进行传输的. 读取 HTTP 头的 ...
- VC++ 6.0中添加库文件和头文件
附加头文件包含 VC6.0中: VC6.0默认include包含路径:Tools>Options>Directories>Include files. 对于特定项目的头文件包含,在& ...
- 数据结构之B树、B+树(一)
B-树 什么是B-树? B树是一种查找树,我们知道,这一类树(比如二叉搜索树,红黑树等等)最初生成的目的都是为了解决某种系统中,查找效率低的问题.B树也是如此,它最初启发于二叉搜索树,二叉搜索树的特点 ...
- Python作业之工资管理
作业之工资管理 工资管理实现要求: 工资管理系统 Alex 100000 Rain 80000 Egon 50000 Yuan 30000 -----以上是info.txt文件----- 实现效果: ...
- Map value类型不同的写法
Map value类型不同的写法 Map<String, Object> accountMap=new HashMap<String, Object>(); int userI ...
- typeAliases别名
<configuration> <typeAliases> <!-- 通过package, 可以直接指定package的名字, mybatis会自动扫描你指定包下面的ja ...
- mysql数据库索引类型和原理
索引初识: 最普通的情况,是为出现在where子句的字段建一个索引.为方便讲述,我们先建立一个如下的表. CREATE TABLE mytable ( id serial primary key, c ...