android ndk开发(2)——基础编程

0x00 基础知识

  • jni 是java native interface,java调用底层C代码所用到的技术
  • ndk 是Android方便jni编程所提供的工具集合
  • 在windows 环境下,需要安装cygwin工具,方便对c代码进行编译 配置ndk运行环境
    打开cygwin64\etc\profile文件,编辑其中的环境变量,添加ndk位置变量:
1
2
3
4
5
if [ ${CYGWIN_NOWINPATH-addwinpath} = "addwinpath" ] ; then
PATH="/usr/local/bin:/cygdrive/h/software/android_ndk_develop/windows/android-ndk-r10e:/usr/bin${PATH:+:${PATH}}"
else
PATH="/usr/local/bin:/cygdrive/h/software/android_ndk_develop/windows/android-ndk-r10e:/usr/bin"
fi

安装cygwin选择163的源,安装bash和make即可。需要更新也是用setup文件。
参考链接:
http://pielot.org/2010/12/using-cygwin-with-the-android-ndk-on-windows/
http://mirrors.163.com/.help/cygwin.html
http://blog.csdn.net/hu_shengyang/article/details/7828998

0x01 开始编程

1. 创建工程 java部分

  • 新建一个空的Android App程序,添加几个按钮,用于测试调用ndk。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JAVA与C交互 integer"
android:id="@+id/button2"
android:layout_alignTop="@+id/button"
android:layout_toRightOf="@+id/button"
android:layout_toEndOf="@+id/button"
android:layout_marginLeft="60dp"
android:layout_marginStart="60dp"
android:onClick="clickInteger" /> //调用clickInteger函数

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JAVA与C交互 string"
android:id="@+id/button3"
android:layout_below="@+id/button"
android:layout_alignLeft="@+id/button"
android:layout_alignStart="@+id/button"
android:layout_marginTop="72dp"
android:onClick="clickString" /> //调用clickString函数
  • 新建DataProvider类,里面用native关键字声明需要调用C的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class DataProvider {
    /**
    *
    * @param a integer a
    * @param b integer b
    * @return a + b
    */
    public native int add(int a , int b);

    /**
    *
    * @param message
    * @return "hello" + message
    */
    public native String sayHello(String message);

    }
  • 测试C代码调用

1
2
3
4
5
6
7
8
9
public void clickInteger(View view)
{
Toast.makeText(this,dataProvider.add(1, 2) + "" ,Toast.LENGTH_LONG).show();
}

public void clickString(View view)
{
Toast.makeText(this,dataProvider.sayHello("angel~~"),Toast.LENGTH_LONG).show();
}

2. 编写C代码

  • 使用javah 帮助生成C头文件
1
2
cd app\build\intermediates\classes\debug
javah com.example.huanqi.myapplication.DataProvider

便可生成对应的头文件com_example_huanqi_myapplication_DataProvider.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_huanqi_myapplication_DataProvider */

#ifndef _Included_com_example_huanqi_myapplication_DataProvider
#define _Included_com_example_huanqi_myapplication_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_huanqi_myapplication_DataProvider
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_huanqi_myapplication_DataProvider_add
(JNIEnv *, jobject, jint, jint);

/*
* Class: com_example_huanqi_myapplication_DataProvider
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_huanqi_myapplication_DataProvider_sayHello
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
  • 使用jni技术,C代码编程

这里用到了jni.h 头文件进行基础编程和android/log.h文件进行打印日志。这里的C代码需要通过jni中数据转换为java可识别的类型。

data_test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//
// Created by HuAnqi on 2015-09-25.
//


#include <string.h>
#include "com_example_huanqi_myapplication_DataProvider.h"


#include "until.h"

#include "log.h"

/*
* Class: com_example_huanqi_myapplication_DataProvider
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_huanqi_myapplication_DataProvider_add
(JNIEnv * env , jobject object , jint x , jint y)
{

LOGD("x = %d",x);
LOGD("y = %d",y);

return x + y ;

}

/*
* Class: com_example_huanqi_myapplication_DataProvider
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_huanqi_myapplication_DataProvider_sayHello
(JNIEnv * env, jobject object, jstring message)
{
/*
* java String is not supported in C language
* we need to translate the String type
*/

char *msg = Jstring2CStr(env, message);
LOGD("message = %s" , msg);
strcat(msg , "Hello world");
LOGD("new message = %s", msg);
return (*env) -> NewStringUTF(env,msg);

}

log.h 公用log打印函数

1
2
3
4
5
#include <android/log.h>

#define LOG_TAG "System.out.c" // 定义log的标签
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) //宏定义日志打印,等级为 info
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) //宏定义日志打印,等级为 debug

until.h 公用数据转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// Created by HuAnqi on 2015-09-25.
//
#include <stdio.h>
#include <jni.h>
#ifndef MYAPPLICATION_UNTIL_H
#define MYAPPLICATION_UNTIL_H

#endif //MYAPPLICATION_UNTIL_H
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT char* Jstring2CStr(JNIEnv* env, jstring jstr);
#ifdef __cplusplus
}
#endif

until.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//
// Created by HuAnqi on 2015-09-25.
//

#include "until.h"
#include <malloc.h>

/**
* 返回值 char* 这个代表char数组的首地址
* Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String"); //String
jstring strencode = (*env)->NewStringUTF(env,"GB2312"); // 得到一个java字符串 "GB2312"
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env,barr); // byte数组的长度
jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
if(alen > 0)
{
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env,barr,ba,0); //Release memory
return rtn;
}

3. 编译C代码

  • 编写Android.mk 交叉编译

Android.mk

1
2
3
4
5
6
7
8
9
10
11
#交叉编译 linux  makefile的语法子集
LOCAL_PATH := $(call my-dir) #get "Android.mk" local path
include $(CLEAR_VARS) #init varibles

LOCAL_MODULE := datatest # output filename libHello.so
LOCAL_SRC_FILES := data_test.c until.c # the file to compiler

# add dependency libs
LOCAL_LDLIBS += -llog # add liblog.so

include $(BUILD_SHARED_LIBRARY)

将C代码编译为可以在arm平台上运行的库文件。这里需要使用cygwin,cd到jni目录中,输入ndk-build命令即可。

1
2
3
4
$ ndk-build
[armeabi] Compile thumb : datatest <= data_test.c
[armeabi] SharedLibrary : libdatatest.so
[armeabi] Install : libdatatest.so => libs/armeabi/libdatatest.so
  • 将so文件放在指定的工程目录下

android studio 对于ndk的支持还只是实验阶段,所以需要在gradle.properties文件中添加:

1
android.useDeprecatedNdk=true

然后将so文件放在src/main/jniLibs/armeabi目录下,即可被项目找到。

0x02 运行调试

  • 用Genymotion调试运行

在运行时,记得在使用的地方加载运行库:

1
2
3
4
static {
//load modules Hello
System.loadLibrary("datatest");
}

然后点击项目运行,可以测试运行得到:

  • adb shell查看调试

配置好sdk路径之后,可以使用adb对设备进行调试,权限是root。

1
2
3
4
5
6
7
8
9
10
adb devices

List of devices attached
192.168.56.101:5555 device

adb shell
root@vbox86p:/ # ls
acct
cache
...
  • 编辑log窗口,查看日志

像eclipse一样,在输出的日志中,进行过滤操作。这里配置标签为System.out.c

0x03 git版本控制

1
2
3
4
5
git init
git add README.md
git commit -m “first commit”
git remote add origin git@github.com:angelwhu/android_ndk.git
git push -u origin master

可能需要登录github,先创建android ndk仓库。

  • 接着配置Android Studio Git 插件

http://blog.csdn.net/hello0370/article/details/41899207

文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2015/10/13/android-ndk-development-2-basic-programming/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog