存档

文章标签 ‘android’

Android NDK Usage

2013年5月21日 没有评论

Sometimes we have to write some C/C++ code to deal with something that java can not do in android development. Such as interacting with hardware, or reusing some codec library written in C/C++.

Because of above reasons, we need a tool or a method which helps us call C/C++ code in android(java) code.

The NDK(Native Development Kit) is the tool which fits our requirements.

This page simply describes how to use C/C++ code in android project.

Installing the NDK

Just download the Android NDK from here, and unzip into a anyplace you like, e.g. /opt

Load the library and declare method

In order to use the functions written with C/C++, we need to load the library which contains those functions, and declare those functions.

For example, suppose the function we want to call is sayHello(), which is contained in library libjdemo.so.

Below code show the details:

package com.example.jdemo;
 
// A simple JNI interface wrapper
public class JNIWrapper {
    static {
// Load libjdemo.so, must be called in static block
        System.loadLibrary("jdemo");
    }
 
// Declare the C function we want to call
public static native String sayHello();
}

Implement C/C++ code

This step, we are readying to implement the C function declared above.

Create header file

We can use “javah” utility to create C/C++ header file.

$ mkdir jni # this directory is used to place C/C++ source files
$ cd bin/classes && javah com.example.jdemo.JNIWrapper # create header file, note the arguments passed to "javah"
$ cd - && mv bin/classes/com_example_jdemo_JNIWrapper.h jni/demo.h # move the header file to jni directory, for convinent, rename filename to anyone you want

Implement the C function

Suppose we use demo.c as the C source file, below is its contents:

#include <stdio.h>
#include <time.h>
#include "demo.h"
 
/* Return the time information */
JNIEXPORT jstring JNICALL Java_com_example_jdemo_JNIWrapper_sayHello
(JNIEnv *je, jclass jc)
{
    char buf[256];
    time_t tm = time(NULL);
    char *ct = ctime(&tm);
 
    snprintf(buf, sizeof(buf), "Hello, the time is: %s\n", ct);
    return (*je)->NewStringUTF(je, buf);
}

Build library

For building the source file to library using NDK, we need a Makefile named Android.mk.

LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
 
LOCAL_MODULE := jdemo # so the library will be named libjdemo.so
LOCAL_SRC_FILES := demo.c # the source files
 
include $(BUILD_SHARED_LIBRARY)

then compile source using ndk-build

$ /opt/android-ndk-r8d/ndk-build 
Compile thumb  : jdemo <= demo.c
SharedLibrary  : libjdemo.so
Install        : libjdemo.so => libs/armeabi/libjdemo.so

others

That’s all

分类: android 标签:

Port glib to android

2013年4月1日 25 条评论

glib]] 是 linux 下非常基础的库, 大部分 linux 下的软件 都依赖于它, 比如 gstreamer, gtk 等等. 由于最近我在准备 hack spice, 准备把它 port 到 android 上, 而 libspice 又依赖于 glib, 所以需要把 glib 移植到 android 上.

所幸几年前就 hack 过大量程序到 ARM 和 blackfin 平台上, 所以过程还算顺利.

下载相应的文件

需要用到的源代码有以下几个:

libiconv
1.14, 从 这里 下载
gettext
0.18.2, 从 这里 下载
libffi
3.0.12, 从 这里 下载
glib
2.34.3, 从 这里 下载

由于有的版本(比如 libiconv-1.14, 旧的 glib)暂时不支持自动探测 android host, 所以需要对探测的脚本做一些修改, 这里使用最简单的方法, 使用 gnu 网站上最新的 探测脚本替换不支持的脚本, 先把脚本下载下来, 需要是替换

$  wget -O /tmp/config.sub "git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD"
$  wget -O /tmp/config.guess "git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD"

编译环境

这里 根据操作系统的版本下载相应的 toolchain版本, 我下载的是 android-ndk-r8d-linux-x86.tar.bz2, 解压到合适的目录, 然后进入该目录, 安装 toolchain, (在这里我将 toolchain 安装在 ${HOME}/Develop/android-toolchain, 使用 gcc-4.7, android-14 的 platform

$ tar xf android-ndk-r8d-linux-x86.tar.bz2
$ cd android-ndk-r8d
$ build/tools/make-standalone-toolchain.sh --platform=android-14 \
--toolchain=arm-linux-androideabi-4.7 \
--install-dir=${HOME}/Develop/android-toolchain

在 bash 的配置文件里面配置 toolchain 的相关路径

$ echo "export SYSROOT=${HOME}/Develop/android-toolchain/sysroot" &gt;&gt; ${HOME}/.bashrc
$ echo 'export PATH=${PATH}:${HOME}/Develop/android-toolchain/bin' &gt;&gt; ${HOME}/.bashrc
$ source ${HOME}/.bashrc

compile libiconv

libiconv 目前的最新版本是 1.14, 还不支持 android, 需要做一些 hack

将上面下载的 config.sub 和 config.guest 文件替换原有的文件, 因为目前还不支持 自动探测 android host.

$ cp /tmp/config.sub build-aux/config.sub
$ cp /tmp/config.sub libcharset/build-aux/config.sub
$ cp /tmp/config.guess build-aux/config.guess
$ cp /tmp/config.guess libcharset/build-aux/config.guess

另外, 由于 libiconv 自带的 stdint.h 和 android toolchain 的冲突, 导致使用 time_t 等几个结构体的时候会出现类型未申明的情况, 比如:

In file included from /root/Develop/android-toolchain/sysroot/usr/include/sys/time.h:33:0,
                 from /root/Develop/android-toolchain/sysroot/usr/include/time.h:32,
                 from ./time.h:40,
                 from ./stdint.h:518,
                 from /root/Develop/android-toolchain/bin/../lib/gcc/arm-linux-androideabi/4.7/include-fixed/sys/types.h:43,
                 from ./fcntl.h:46,
                 from careadlinkat.h:23,
                 from areadlink.c:27:
/root/Develop/android-toolchain/sysroot/usr/include/linux/time.h:20:2: error: unknown type name 'time_t'
/root/Develop/android-toolchain/sysroot/usr/include/linux/time.h:26:2: error: unknown type name 'time_t'
/root/Develop/android-toolchain/sysroot/usr/include/linux/time.h:27:2: error: unknown type name 'suseconds_t'

在 configure 中预定义 gl_cv_header_working_stdint_h=yes 可以避开这个问题.

以下是编译的具体指令:

$ gl_cv_header_working_stdint_h=yes ./configure --prefix="${SYSROOT}/usr" --host=arm-linux-androideabi CFLAGS="--sysroot $SYSROOT" --enable-static
$ make -j5
$ make install

现在, 应该可以在 ${SYSROOT}/usr/lib/ 下看到相关的库已经安装了

$ ls ${SYSROOT}/usr/lib/libiconv*

compile gettext

android toolchai 的 passwd 结构体没有 pw_gecos 成员. 会报以下错误:

msginit.c: In function 'get_user_fullname':
msginit.c:1084:21: error: 'struct passwd' has no member named 'pw_gecos'

以下的小 patch 简单的避免这个问题(其实就是简单粗暴地给 fullname 赋值, 避免访问 pwd->pw_gecos):

--- msginit.c   2012-12-04 14:28:58.000000000 +0800
+++ msginit.c.new   2013-04-01 11:59:54.054980294 +0800
@@ -1081,7 +1081,11 @@
       char *result;

       /* Return the pw_gecos field, up to the first comma (if any).  */
+#ifndef __ANDROID__
       fullname = pwd->pw_gecos;
+#else
+fullname = "android";
+#endif
       fullname_end = strchr (fullname, ',');
       if (fullname_end == NULL)
         fullname_end = fullname + strlen (fullname);

以下是编译的具体指令:

$ ./configure --prefix="${SYSROOT}/usr" --host=arm-linux-androideabi CFLAGS="--sysroot $SYSROOT" --enable-static --disable-java --disable-native-java
$ make -j5
$ make install

libffi

编译没什么问题, 一次通过

$ ./configure --prefix="${SYSROOT}/usr" --host=arm-linux-androideabi CFLAGS="--sysroot $SYSROOT" --enable-static
$ make -j5
$ make install

compile glib

glib 的编译稍微麻烦一点.

这里我选择相对新一点的 2.34.3 版本, 这个版本的探测脚本已经支持 android host 了.

另外, 在编译前的配置时, ARM 上的编译器会在检查一些特性时失败, 采用下面的方法 避免这类失败: 详情请参考源码目录下的 docs/reference/glib/html/glib-cross-compiling.html

新建一个文件 android.cache, 写入以下内容.

# file android.cache
ac_cv_type_long_long=yes
glib_cv_stack_grows=no
glib_cv_uscore=no
ac_cv_func_posix_getpwuid_r=no
ac_cv_func_posix_getgrgid_r=no

配置编译的指令(test 模块编译不过去, 没关系, 我永不到, disable 之):

$ ./configure --prefix="${SYSROOT}/usr" --host=arm-linux-androideabi CFLAGS="--sysroot $SYSROOT" --enable-static --cache-file=android.cache --disable-modular-tests

编译的时候会出现各种各样的错误:

gstrfuncs.c: In function 'g_ascii_strtod':
gstrfuncs.c:718:30: error: 'struct lconv' has no member named 'decimal_point'
gstrfuncs.c: In function 'g_ascii_formatd':
gstrfuncs.c:942:30: error: 'struct lconv' has no member named 'decimal_point'

gutils.c:840:8: error: 'struct passwd' has no member named 'pw_gecos'
gutils.c:840:25: error: 'struct passwd' has no member named 'pw_gecos'
gutils.c:846:35: error: 'struct passwd' has no member named 'pw_gecos'
gutils.c:749:12: warning: unused variable 'logname' [-Wunused-variable]
gutils.c:748:10: warning: unused variable 'error' [-Wunused-variable]

glocalfileinfo.c:1097:21: error: 'struct passwd' has no member named 'pw_gecos'

gresolver.c:1133:14: error: 'T_TXT' undeclared (first use in this function)
gresolver.c:1133:14: note: each undeclared identifier is reported only once for each function it appears in
gresolver.c:1135:14: error: 'T_SOA' undeclared (first use in this function)
gresolver.c:1137:14: error: 'T_NS' undeclared (first use in this function)
gresolver.c:1139:14: error: 'T_MX' undeclared (first use in this function)

同样, 简单粗暴的 hack.(由于 patch 比较打, 我放在后面的 gist 上).

不同版本可能会遇到各种各样不同的编译错误, 这里需要具体问题具体分析, 比如 某些结构体, 宏没有申明的, 可以手动添加, 一些测试模块编译不过去, 可以把测试 模块去掉, 例如上面的 configure 时的–disable-modular-tests 参数.

测试

使用 glib 获取外网 IP 的 test 代码, JNI 和 NDK 的使用请看我其它的 android NDK 开发指南(抱歉还没有).

// file GetIPAddr.java
package org.mathslinux.glib_demo;
public class GetIPAddr {
    static {
        System.loadLibrary("ipaddr");
    }
 
     public static native String getAddr();
}
 
//  MainActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_main);
    }
 
    public void onClick(View v) {
        EditText text = (EditText) this.findViewById(R.id.editText1);
        text.setText(GetIPAddr.getAddr());
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        this.getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
}
/* file ip.c */
#include 
#include 
#include 
#include "ip.h"
static gchar *get_addr()
{
    char url[128] = {0};
 
    g_type_init();
    GSocketClient *client = g_socket_client_new();
    snprintf(url, sizeof(url), "%s:%s%s", "http", "//", "cip.cc");
    GSocketConnection *c = g_socket_client_connect_to_uri(
        client, url, 80, NULL, NULL);
    if (c) {
        GSocket *socket = NULL;
        g_object_get(c, "socket", &amp;socket, NULL);
        if (socket) {
            char buf[2048] = {0};
            char *str = g_malloc0(1024);
            char *p, *q;
            char *send = "GET / HTTP/1.1\r\nUser-Agent: curl/7.29.0\r\nHost: cip.cc\r\nAccept: */*\r\n\r\n";
            g_socket_send(socket, send, strlen(send) + 1, NULL, NULL);
            g_socket_receive(socket, buf, sizeof(buf), NULL, NULL);
            p = strstr(buf, "IP");
            q = strstr(p, "\n");
            if (p &amp;&amp; q) {
                char ip[32] = {0};
                snprintf(ip, q - p + 2, "%s", p);
                strcat(str, ip);
            }
 
            p = strstr(buf, "地址");
            q = strstr(p, "\n");
            if (p &amp;&amp; q) {
                char addr[1024] = {0};
                snprintf(addr, q - p + 2, "%s", p);
                strcat(str, addr);
            }
            g_socket_close(socket, NULL);
            return str;
        }
    }
    g_object_unref(client);
    return NULL;
}
 
JNIEXPORT jstring JNICALL Java_org_mathslinux_glib_1demo_GetIPAddr_getAddr
(JNIEnv *je, jclass jc)
{
    char *addr = get_addr();
    return (*je)-&gt;NewStringUTF(je, addr);
}

秀一张截图:
glib_on_android

Others

glib 的 patch 我贴在 gist 上 请访问: 我的 gist

分类: android 标签: