分享

NDK 应用及扩展 - fanwei51880的日志 - 网易博客

 techres 2011-03-23

 Android NDK 作用主要用来编译本地库(C,C++文件编译后运行于目标CPU的库)Android java 调用,同时,可以用来打包编译动态库和静态库,不能将本地库和java文件打包成APK(这是别的范畴,后面有简单实现这功能的方法).

       本例子主要介绍如何使用NDK编译出动态库(.so)和静态库(.a),并且在编译它们的时候又调用其他的动态库或静态库,并且将其他的库打包成最终的一个库文件.sojava调用最后简单说明其他实现编译so文件的方法,打包soapk里的方法.

NDK内部变量说明请参考<eoe特刊】第七期:NDK>.pdf 文件.

       本例子是在ubuntu9.04上使用android-ndk-1.6_r1-linux-x86android-sdk-linux_x86_16.

 

1.  安装.

a)         Tar –jxvf android-ndk-1.6_r1-linux-x86.tar.bz2 得到android-ndk-1.6_r1文件夹

b)        android-ndk-1.6_r1文件夹放到你要的位置,进入android-ndk-1.6_r1文件夹,此时的pwd 就是NDK的根目录,我定义为$NDK_ROOT,

c)        $NDK_ROOT 执行命令 ./build/host-setup.sh ,能成功安装DNK环境.(如果有问题,请按提示添加系统缺失文件)

2.  编译一个动态库和一个静态库.

a)         $NDK_ROOT 目录下进入apps文件夹,创建jni文件夹和Application.mk文件,其内容如下

APP_PROJECT_PATH := $(call my-dir)

APP_MODULES      := testapi 

testapi  是生产模块的主要名字,

此版本系统默认编译$ APP_PROJECT_PATH jni文件夹下的c,c++文件)

b)        进入jni文件夹,创建add.c,add.h,Android.mk内容分别如下:

                         i.              add.h:

#ifndef ADD_H

#define ADD_H

extern int add(int  x, int  y);

#endif /* ADD_H */

                       ii.              add.c

#include "add.h"

int  add(int  x, int  y){

    return x + y;

}

                      iii.              Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := testapi

LOCAL_SRC_FILES := add.c

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

 

                     iv.              回到$NDK_ROOT目录,命令行输入

make APP=api

编译成功,$NDK_ROOT/apps/api/libs/armeabi得到动态库 libtestapi.so, 3147Btye

                       v.              Android.mk 最后一样修改成

include $(BUILD_STATIC_LIBRARY)

$NDK_ROOT目录,命令行输入

make APP=api

编译成功,$NDK_ROOT/out/apps/api/得到静态库 libtestapi.a, 2572Btye

3.  利用Eclipse创建android java程序通过JNI调用动态库libapp.so,so又调用步骤2.生产的库文件libtestapi.*.

Android APK据我了解JNI只能调用动态库,不能把静态库添加到APK里面.

a)         eclipse在任意位置,创建工程apktest,创建test.jni包里jnitest的类,jnitest.java内容如下

package test.jni;

public class jnitest  {

    public native int appadd(int  x, int  y);

}

b)        然后进入工程的src目录制作JNI头文件

                         i.              javac test/jni/jnitest.java

javah test.jni.jnitest

产生得到test_jni_jnitest.h头文件

c)        在修改jnitest.java成如下形式

package test.jni;

import android.app.Activity;

import android.os.Bundle;

import android.widget.TextView;

public class jnitest extends Activity {

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        TextView  tv = new TextView(this);

        int       x  = 77;

        int       y  = 88;

        System.loadLibrary("apptest");

        int  z = appadd(x, y);

        tv.setText( x + "+" + y + "=" + z );

        setContentView(tv);

    }

    public native int appadd(int  x, int  y);

}

       其中主要参考了apps\two-libs,因为这样简单让我们看起来容易理解.

       没有语法错误后Eclipse在其工程的bin文件夹下自动产生apktest.apk 13.2k

此时,这模拟器运行apk会报错,因为动态链接库libapptest.so没有在apk,也没在/system/lib

d)        回到$NDK_ROOT目录下,进入apps目录执行一下命令

ln -s /home/clay/workspace/apktest/ app

其中/home/clay/workspace/apktest/eclipse创建工程的位置,

e)         因为ln –s的关系,app目录几乎等同/home/clay/workspace/apktest/,我用app目录代表/home/clay/workspace/apktest/目录.方便理解

f)         app目录下创建jni文件夹和Application.mk文件,其内容如下

APP_PROJECT_PATH := $(call my-dir)

APP_MODULES      := apptest

  其中apptest 要和javaSystem.loadLibrary("apptest");一致

g)        在上一步的jni文件夹里创建appcall.c Android.mk文件,内容如下

                         i.              Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := apptest

LOCAL_SRC_FILES := callapp.c

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

                       ii.              appcall.c 根据test_jni_jnitest.h

#include <jni.h>

JNIEXPORT jint JNICALL Java_test_jni_jnitest_appadd

  (JNIEnv *env, jobject obj, jint x, jint y){

    return x + y;

}

为检查JNI,我们先直接在libapptest.so实现,待验证后再修改call别的库文件.

h)        回到$NDK_ROOT目录,执行make APP=app

得到  $NDK_ROOT /apps/app/libs/armeabi/libapptest.so 2146Byte

如果这时候, adb push /apps/app/libs/armeabi/libapptest.so /system/lib,然后从模拟菜单里运行刚才apk程序(名字也叫apk,建工程时候定下来的),此时可以运行,而且显示”77+88=165”

i)          此时回到Eclipse 从菜单选择 project->clean,然后eclipse会自动(如果没设auto需手动)重新编译该工程,这时候你会发现工程里libs/armeabi/libapptest.so出现,而且,bin目录下的apk变成了14.7k,ziprar(windows)你会发现你的apk文件里比之前多了lib文件夹,其子文件夹armeabi里有libapptest.so文件.adb shell进入到虚拟机的/system/lib目录,删除之前libapptest.so,可以重启模拟器.

证明系统没有libapptest.so.,运行后边编译的apk文件,这时候apk能正确运行,没有报错,/system/lib目录下仍是没有libapptest.so,这说明:我们这时候的apklibapptest.so打包进去,而且系统能解出libapptest.so并加载.

但是,只是eclipse 通过ADT完成把so打包到apk里的.如果用源代码开发包mm命令方式产生的apk(不是NDK)似乎不行(之后再讨论,或许makefile没定义好). NDK绝对没有做这打包的动作.

4.  利用NDK 打印调试

a)         3.基础上,修改android.mk连接liblog.so成为以下

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := apptest

LOCAL_SRC_FILES := callapp.c

LOCAL_LDLIBS := -llog

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

b)        然后修改callapp.c,添加打印

#include <jni.h>

#include <android/log.h>

#define LOGD(...) __android_log_print(ANDROID_LOG_INFO, "Logtest", __VA_ARGS__);

JNIEXPORT jint JNICALL Java_test_jni_jnitest_appadd

  (JNIEnv *env, jobject obj, jint x, jint y){

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

__android_log_print(ANDROID_LOG_INFO, "Logtest","================");

    return x + y;

}

c)        $NDK_ROOT 下输入 make APP=app,重新编译动态库,eclipse clean rebuilt,运行,eclipselogcat 可以看到以下:

I/Logtest (  375): TEST 77+88=165

I/Logtest (  375): ================

//注意,如果不用NDK,只有SDK开发,可以直接#include <utils/Log.h>后用LOGD(),类似cprintf,,另外,此时即使你include<stdio.h>,cprintf仍是没有打印输出的

5.  为动态库libapptest.so链接别的静态库libXX.a

a)         修改前,libapptest.so 2829Byte,libtestapi.a 2572Byte.

b)        api工程的add.h复制到$NDK_ROOT/apps/app/jni/,修改callapp.c如下:

#include <jni.h>

#include <android/log.h>

#include "add.h"

#define LOGD(...) __android_log_print(ANDROID_LOG_INFO, "Logtest", __VA_ARGS__);

JNIEXPORT jint JNICALL Java_test_jni_jnitest_appadd

  (JNIEnv *env, jobject obj, jint x, jint y){

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

__android_log_print(ANDROID_LOG_INFO, "Logtest","================");

    return add(x,y);

}

c)        修改$NDK_ROOT/apps/app/jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := apptest

LOCAL_SRC_FILES := callapp.c

LOCAL_STATIC_LIBRARIES := libtestapi

LOCAL_LDLIBS := -llog

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

d)        $NDK_ROOT/out/api/libtestapi.a复制到$NDK_ROOT/out/apps/app/,然后make APP=app,编译成功.$NDK_ROOT/apps/app/libs/armeabi/libapptest.so 3012Byte

如果把a文件放到jni文件里编译,会提示找不到No rule to make target `out/apps/app//libtestapi.a,我认为,LOCAL_STATIC_LIBRARIES, LOCAL_SHARED_LIBRARIES默认也是要自己编译出来

e)         Eclipse 把工程clean rebuilt,run, 显示和打印都是正确的表是libtestapi.a的确是编进到libapptest.so文件里了

6.  为动态库libapptest.so连接别的动态库libXX.so

a)         4修改后,libapptest.so 2829Byte,libtestapi.so 3147Byte.(之前的记录)

b)        维持第5修改的c文件,

c)        修改$NDK_ROOT/apps/app/jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := apptest

LOCAL_SRC_FILES := callapp.c

LOCAL_SHARED_LIBRARIES := libtestapi

LOCAL_LDLIBS := -llog

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)      

d)        同理,$NDK_ROOT, cp out/apps/api/libtestapi.so out/apps/app/,make APP=app –B可以通过但是此时,eclipse cleanrebuilt,run 出现错误.因为动态库在编译的时候只做连接,并不包含到最终目标,这和静态库不一样.同时,你也会发现$NDK_ROOT/apps/app/libs/armeabi/下只有libapptest.so,没有libtestapi.so.请注意,这时候编译得到到libapptest.so 2913Byte,和之前的不调用add()函数几乎差不多.

e)         这时候,我们把libtestapi.so 复制到$NDK_ROOT/apps/app/libs/armeabi/,eclipse clean&rebuilt,run,结果仍是出错.虽然bin目录下的apktest.apk 的包libs/armeabi/里面有libtestapi.solibapptest.so

f)         如果这时候,libtestapi.so 复制到$NDK_ROOT/apps/app/asset,eclipse clean&rebuilt,run,结果仍是出错.虽然bin目录下的apktest.apk包的

g)        即使我在jnitest.java,添加了      System.loadLibrary("apptest");  System.loadLibrary("testapi");重复ef,仍旧失败

h)        如果这时候,我们用adb remount(让模拟器系统可以改写),adb push out/apps/app/libtestapi.so /system/lib,之后,运行eclipse修改的apk,发现可以正常正确的运行.

由此,我认为NDK能编译,但是,不能把so打包,同时也不能把编译中间的so文件(libtestapi.so)打包,但是NKD 却能以某种方式(我不知道)告诉APK打包的程序,所以eclipse 能把ibs/armeabi/libapptest.solibtestapi.so打包,但是系统跑起来却不能自动加载(能加载libapptest.so,不能加载libtestapi.so)

7.  为动态库libapptest.so连接别的动态库libXX.so,方法二.

a)         4修改后,libapptest.so 2829Byte,libtestapi.so 3147Byte.(之前的记录)

b)        维持第5修改的c文件,

c)        adb shell 进入到/system/lib里删除第6加进去的libtestapi.so

d)        修改$NDK_ROOT/apps/app/jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := apptest

LOCAL_SRC_FILES := callapp.c

LOCAL_LDLIBS :=  -ldl -llog

LOCAL_PRELINK_MODULE := false

#LOCAL_ALLOW_UNDEFINED_SYMBOLS = true

include $(BUILD_SHARED_LIBRARY)

e)         这时候make APP=app,提示找add的原型,这时候 ,我们把LOCAL_ALLOW_UNDEFINED_SYMBOLS = true打开,这时候,能编译通过得到2.82klibapptest.so,但是却出现正确的,,其实是刚好有一个有个lib 的函数是add,如果我们把我们add.cadd.h里的add函数改成,add22,重新编译apiapp,运行就出错了.但是,这时候需要我们手工把libtestapi.so放到/system/lib,仍旧不能运行,所以怀疑,flag 是从全部的so里搜索(改成add22,重复之前所以步骤都是可以得到按之前的说明的结果)

f)         果这时候我们只打开把Android.mk 修改成如下

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := apptest

LOCAL_SRC_FILES := callapp.c

LOCAL_LDLIBS := -ltestapi -ldl -llog

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

LOCAL_LDLIBS := -ltestapi -ldl –llog//有时候可以分成几行来写,但分开写,有时候编译不通过.(I don’t know why)

这是仿照我们之前添加log打印的方式一样,把我们的动态库当成NDK的库,所以,我把libtestapi.so放到NDK, cp apps/api/libs/armeabi/libtestapi.so build/platforms/android-4/arch-arm/usr/lib/,

在这之前,我们make APP=app -B 提示找不到libtestapi.so,libtestapi.so放到NDKlib,编译可以通过.如果没通过,需要把libtestapi.somode变成777

这时候,eclipse重新编译工程运行,提示错误,但是,我们用adb libtestapi.so push 弄到/system/lib,重新运行,ok

 

 

关于NDK 的到此差不多告一段落.

由于android makefile的一大堆变量 刚开始就把人吓坏了.至今也没理解多少,这一切都是不断在网上查,自己摸索,不断看他的列子和其他人的建议,尝试的结果,其中反反复复好多次.

======================================================================

其实通过JNI的方法来实现还有两种方式,这些都是我同事rock帮忙弄的,借花献佛,简单介绍一下.

1.       linux 命令方式,

a)         gcc xxx.c -shared -o libXXX.so,生成 动态链接库.

b)        gcc xxx2.c -L . -l XXX -o APP,xxx2c文件链接动态库libXXX.so,形成APP 可执行文件//注意,这个文件时在linux下运行的,host

c)        因为我的目标板(target board)arm核心的,所以使用/usr/local/arm/4.3.2/bin/ 交叉编译器,模仿以上两步(自己写makefile),注意头文件时arm 的头文件就可以了,这样soAPP 可以在arm板上run,可以使用gdb等工具调试.

2.       Android原代码SDK 开发.需要有完整的code,并且会找到系统的编译器和头文件,把编译出来的中间so文件放到特定位置.详细参考<Android平台怎样使用第三方动态库>

So文件放的位置主要根据编译时候提示在哪里找不到,就放到哪里(因为android 默认要完全有原代码开发,所以它默认我们的apk用的so需要有原文件,vendor下的例外)

3.       另外,幸运,我们目标是ARM核心,有以上两步和NDK得到so文件几乎可以混用.SDK开发得到的so文件和NDK得到的so文件,分别push我目标板子和模拟器,都可以跑起来.而且三种方法得到的so,apk都可以在我的ARM目标板可以跑起来.并且,NDK得到的libapptest.so可以调用已经push /system/lib arm编译器编译出来的libtestapi.so文件.

但是,arm编译器编出来的libtestapi.so 不能放到NDKSDK 里混合来编译出新的libapptest.so,提示” libthunder_api.so has EABI version 0, but target out/target/product/generic/obj/SHARED_LIBRARIES/libthunder_app_intermediates/LINKED/libthunder_app.so has EABI version 4”,也就是说,NDKSDK里会检测版本.幸运的是我用的NDK SDK都是1.6,arm工具链编出来没有版本(EABI version 0)

另外,才傻傻发现,SDK里已经有NDK

 

 

另外,如果我们用SDK 开发,我们使用mm 得到的APK里是没有打包so文件的,这时候测试,只能手工把so文件push到系统里.而且,你会发现,如打包了so文件的apk文件用adb install 以后,/system/lib目录,是找不到这so文件的.我曾经在/data/app/data/system目录里见过,但是现在仍是没发现,不知道为什么?如果有谁知道请分享一下.

虽然mm命令方式生成的apk里没so文件,但是整个android 系统built通过以后,建成img文件的时候,so文件是会被放到/system/lib目录下的,例如lib3G等等.

我在网上查到,ant的开发方式可以把so打包到APK,但是SDK开发用mm目前我没有试出来,曾经看过别人写的android.mk 里把so当成source文件去编译APK,但是我用NDK的时候却不成功,但是根据linux c的感觉,so当成source(或中间文件一起编译打包理论是可以的..

======================================================================

这些事熬了半个通宵写了一部分边印证得出来的,平时上班的时间也尝试了很久很久,所以如此艰难弄了这说不定bug很多的半成品,虚荣一下,留个名.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多