Android之NDK开发初探
总的来说ANDROID的NDK远不及其应用开发的SDK完善(虽然经过一番不算复杂的折腾发现NDK用起来很方便),而且它本身也不推荐使用这种做法,至少目前也不将此作为重点。但是某些中间层面系统测试(主要如多媒体和OpenGL ES的测试和演示等)必须通过本地代码实现,因此NDK应当是必由之路。
最近尝试了一下,目前将JNI部分基本理顺(而后续则需要链接相关的ANDROID本地库,如OpenCore系统)。
网上这方面相关介绍也有不少,但是多不太完整,此处权作工作记录。
1 NDK使用
1.1 配置NDK
本处讨论在Windows下使用cygwin处理NDK的Windows版本。Linux下的使用方法基本一致。根据unix系系统的规范,所有讨论中涉及名称的字符串均大小写敏感。这里仅NDK的配置和C代码编译须用cygwin,此后的ANDROID调试等均可使用普通的命令行操作。
参考链接:http://developer.android.com/sdk/ndk/1.5_r1/index.html
收到NDK后首先在NDK的主目录(其中包含apps, build, docs, …文件夹)下,输入命令:
build/host-setup.sh
用来配置NDK工具(例如编译器的使用,目标平台等),最终生成out/host/config-host。由于out必须在主目录中,因此上述命令须在主目录中输入。
1.2 编译本地源码
本地源码(主要如C文件)均放在sources下。NDK提供了两个示例,放在sources/sample目录下。
编译只需要在主目录中输入命令:
make APP=<名称>
对于上述示例,分别为hello-jni和two-libs。
1. sources文件夹配置
由于NDK已将MAKE生成系统建立妥善,所以只需要在sources中建立包含源文件的文件夹。
由于NDK的配置是以sources目录作为源文件工程的根节点,因此如果要将源文件工程放在更深的目录,例如sources/package1/proj1,那么就需要在中间的目录中加入一个Android.mk文件,用以转到更深的目录其内容示例可见sources/samples/Android.mk。
上述文件夹proj1名称建议以源文件模块的名称命名。
在源文件工程文件夹中需要有至少一个Android.mk文件用以定义源文件编译信息。可以参照sources/samples中的两个工程中的示例。其中LOCAL_MODULE变量必须定义成指定源文件工程(模块)的名称。
2. C源程序JNI入口
C源程序的入口遵照JNI规范:
Java_<PackageName>_<JAVA类名>_<FuncName>
其中包名称和Android的JAVA类所属包需要保持一致,只是“.”用“_”替换;JAVA类即是包含这个(实例)方法的类;FuncName则是呈现在JAVA中使用的方法名称。
3. apps文件夹配置
在apps文件夹中创建一个ADNROID工程文件夹,名称为APP工程(JAVA)名称,在其中新建一个Application.mk的配置文件,参照两个示例工程设置。主要设置两个变量:
APP_PROJECT_PATH,这个是ANDROID工程路径和相应指定库生成目录(复制而来,名称为“lib源文件模块名”),一般设置成$(call
my-dir)/project,即当前目录下project中,而库生成目录就是project/libs。
APP_MODULES则是这个ANDROID将包含的上述源文件工程,填入涉及的一个或多个源文件工程名称。
最后在主目录中用make APP=<APP工程名称>
1.3 创建工程
NDK两个例程已经含有完整的ANDROID例程,可以在Eclipse中直接导入打开。
如果新建一个工程,只需要仿照ANDROID工程的一般过程开始,由于本地库so处于工程目录下,Eclipse会自动将其包含在工程中,并最终一并链入apk。
1.4 关于JAVA本地(Native)接口JNI
一些参考文档:
1. http://java.sun.com/docs/books/jni/
2. http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/functions.html
3. http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/method.html
有几个注意点:
1. 不能在本地代码中跨线程使用JNI量,而目前又暂没找到联入正确JNI环境量和对象或者JVM的方法,因此只能在调用线程中使用回调,意味着设计需要让回调发生在JAVA调用者线程中。
2. CallXXXMethodX(env, obj, methodid, va_arg)中,va_arg必须输入指针(对象的指针如jstring *,原子的指针如int *)。
3. 对于跨线程的UI操作侧需要用runOnUiThread。
2 ANDROID工具使用
2.1 虚拟设备创建(AVD)
使用android命令
创建:
android create avd –n <虚拟设备名称> -t <目标ID>
在通常情况下接受默认选项(不建立hardware profile)
删除:
android delete avd –n <虚拟设备名称>
列印:
android list
2.2 (在命令行)运行虚拟机
命令:
emulator –avd <虚拟设备名称>
2.3 ADB常用命令
参考:http://oxen.javaeye.com/blog/142373
安装程序:
adb install <待安装APK文件本地位置>
运行命令SHELL:
adb shell ,进入SHELL,可以操作访问设备文件系统
adb shell ,直接执行命令(SHELL中可以执行logcat)
复制文件:
adb push <本地源> <设备文件系统绝对地址> ,复制入文件
adb pull <设备文件系统绝对地址> <本地位置> ,复制出文件
adb devices ,查看运行的模拟器/设备状态
【示例程序】
一个简单的在屏幕上间歇打印的程序。
本地C代码
(仅用于示例,不保证正确性和安全性)
#include <jni.h> #include <stdio.h> #include <string.h> #include <malloc.h> #include <pthread.h> #include <unistd.h> typedef struct { JNIEnv* env; jobject thiz; jclass cls; } UpdateTextContext; static int gRunnerRunning = 0; static int update_text(UpdateTextContext *context, char *buf) { JNIEnv* env = context->env; jobject thiz = context->thiz; jclass cls = context->cls; jmethodID mid = (*env)->GetMethodID(env, cls, "appendText", "(Ljava/lang/String;)V"); if (mid == NULL) return -1; jstring s = (*env)->NewStringUTF(env, buf); (*env)->CallVoidMethodV(env, thiz, mid, &s); return 0; } void Java_com_eden_sample_Sample_initTextGenerator(JNIEnv* env, jobject thiz) { gRunnerRunning = 0; } void Java_com_eden_sample_Sample_runTextGenerator(JNIEnv* env, jobject thiz) { char buf[64]; int counter = 0; UpdateTextContext context; context.env = env; context.thiz = thiz; context.cls = (*env)->GetObjectClass(env, thiz); gRunnerRunning = 1; while (gRunnerRunning) { sprintf(buf, "sample counting: %d/n", counter); int r = update_text(&context, buf); if (r != 0) break; counter++; sleep(1); /* sleep for one second */ } gRunnerRunning = 0; } void Java_com_eden_sample_Sample_stopTextGenerator(JNIEnv* env, jobject thiz) { gRunnerRunning = 0; } void Java_com_eden_sample_Sample_stopTextGenerator(JNIEnv* env, jobject thiz) { gRunnerRunning = 0; }
JAVA程序
(JAVA本不十分熟悉,权当c#写了)
package com.vendor.sample; /* package that must keep in accordance with the native code */ import android.app.Activity; import android.os.Bundle; import android.widget.TextView; import java.util.*; public class Sample extends Activity { enum UpdateType { Modify, Append } private class UpdateTextRunner implements Runnable { public UpdateTextRunner(String s, UpdateType type) { mS = s; mType = type; } public void run() { if (mType == UpdateType.Modify) { mLines.clear(); mLines.add(mS); mTV.setText(mS); } else { mLines.add(mS); /* intended to display no more than `mMaxLineCount' * lines and scroll, however this is not * always the case, consider if mS is broken * into several lines */ while (mLines.size() > mMaxLineCount) mLines.remove(0); StringBuilder sb = new StringBuilder(); for (int i = 0; i < mLines.size(); i++) sb.append(mLines.get(i)); mTV.setText(sb.toString()); } } private String mS; private UpdateType mType; } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mTV = new TextView(this); mTV.setText( "initial text" ); setContentView(mTV); initTextGenerator(); /* The following thread object simply contains * an overriden run method which invokes * runTextGenerator on this Sample object */ mThread = new TextUpdatorThread(this); mThread.start(); } @Override public void onDestroy() { stopTextGenerator(); try { mThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } super.onDestroy(); } public void modifyText(String s) { this.runOnUiThread(new UpdateTextRunner(s, UpdateType.Modify)); } public void appendText(String s) { this.runOnUiThread(new UpdateTextRunner(s, UpdateType.Append)); } public native void initTextGenerator(); public native void stopTextGenerator(); public native void runTextGenerator(); private TextView mTV; private ArrayList mLines = new ArrayList(); private int mMaxLineCount = 20; private TextUpdatorThread mThread; static { System.loadLibrary("sample"); // the corresponding C library is libsample.so } }