转载:https://i-blog.csdnimg.cn/blog_migrate/ce81c3f35768c6df111bd36e7406ede3.png
因为PC与手机上的处理器是不一样的,所以不能直接使用电脑上的gcc和对应的库文件进行编译,而是使用对应的sdk,也就是 NDK进行开发。
NDK + Android SDK + Eclipse + ADT 安装
这个就不用多说了, 网上教程一找一大把。 先安装好Android SDK, 然后是Eclipse,在里面配置好Android SDK, 然后下载安装ADT。 NDK的安装的话,下载解压,然后把对应的路径添加到系统路径里面就OK了。 具体的说,就是在~/.bashrc 里面添加如下一句:
- export PATH=$PATH:you/ndk/install/dir
- . ~/.bashrc
- viking@own:~/documents/project/csdn$ ndk-build
- Android NDK: Could not find application project directory !
- Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
- /home/viking/bin/android/android-ndk-r8c/build/core/build-local.mk:130: *** Android NDK: Aborting . Stop.
Eclipse 项目创建
创建一个Android Application Project,下面是命名页面,后面的一路默认。
接下来,我要做的就是启动程序的时候显示一个对话框,对话框上的文本是调用一个native函数 getMsg() 得到的。具体的操作过程的话,和前一篇差不多,只不过,不是使用gcc编译,而是使用 NDK 里面的工具进行编译。 OK, let's go!!!
对话框显示
so easy, 直接修改 MainActivity.java 中的 Oncreate 函数,修改如下:
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- // create a alert dialog and show it
- AlertDialog alertDialog = new AlertDialog.Builder(this).create();
- alertDialog.setTitle("title");
- alertDialog.setMessage("msg");
- alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- }
- });
- alertDialog.show();
- }
那么,接下来要做的就很明显啦,就是修改这个对话框的message, 用来测试 jni 函数调用。
JNI函数登台
和第一篇一样,先声明这个native函数,修改Mainctivity.java 如下:
- public class MainActivity extends Activity {
- private native String getMsg();
- static {
- System.loadLibrary("JniTest");
- }
- /* some other things */
- alertDialog.setTitle("title");
- alertDialog.setMessage( getMsg() );
- /* some other things */
修改的地方有三处,添加了 getMsg() 的声明以及对它的调用,另外就是加载动态链接库 libJniTest。
这个时候整个程序是不能运行的,因为没有 getMsg 这个函数的实现。接下来的工作就是实现这个函数啦。
原始方法
最原始的方法,就是按照第一篇中说到的来操作,切换到 ManActivity 这个类的目录下面,然后调用 javah 就OK了。不过,
注意这个类名不是简单的 MainActivity,而是 com.viking.JniTest.MainActivity, 所以,切换目录到 bin/classes 下面,输入:
- viking@own:~/documents/project/workspace/JniTest/bin/classes$ javah com.viking.JniTest.MainActivity
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_viking_JniTest_MainActivity */
- #ifndef _Included_com_viking_JniTest_MainActivity
- #define _Included_com_viking_JniTest_MainActivity
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_viking_JniTest_MainActivity
- * Method: getMsg
- * Signature: ()Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_com_viking_JniTest_MainActivity_getMsg
- (JNIEnv *, jobject);
- #ifdef __cplusplus
- }
- #endif
- #endif
JNI 函数实现
就我现在所知道的,NDK 的默认编辑路径是在 jni 目录下面,好像有方法可以修改, 不过,先按照默认得来。在项目根目录下面添加一个 jni目录,将上面得到的 com_viking_JniTest_MainActivity.h 移到这个目录下面, 并添加一个 jniTest.c 文件,用来实现这个函数。内容如下:
- #include <jni.h>
- #include "com_viking_JniTest_MainActivity.h"
- JNIEXPORT jstring JNICALL Java_com_viking_JniTest_MainActivity_getMsg
- (JNIEnv * env, jobject obj)
- {
- return (*env)->NewStringUTF(env, "Hello, JNI for Android!");
- }
此外,ndk 使用编辑配置文件为 Android.mk, 类似于 Makefile 文件,也许要手动创建并放在jni目录下面,内容如下:
- LOCAL_PATH := $(call my-dir)
- LOCAL_MODULE := JniTest
- LOCAL_SRC_FILES := jniTest.c
- include $(BUILD_SHARED_LIBRARY)
注意,这里定义的变量名称一定不能出错,我之前就是因为把 LOCAL_SRC_FILES 写成了 LOCAL_SRC, 结果编译也不报错,但是总是出现 java.lang.UnstatisfiedLinkError, 倒腾了半天才发现,伤心。。。
最后,在jni目录下面,输入
- viking@own:~/documents/project/workspace/JniTest/jni$ ndk-build
- /home/viking/bin/android/android-ndk-r8c/build/core/add-application.mk:128: Android NDK: WARNING: APP_PLATFORM android-14 is larger than android:minSdkVersion 8 in /home/viking/documents/project/workspace/JniTest/AndroidManifest.xml
- Compile thumb : JniTest <= jniTest.c
- SharedLibrary : libJniTest.so
- Install : libJniTest.so => libs/armeabi/libJniTest.so
说明一切OK!!
Eclipse配置
前面虽然已经搞定了最简单的测试,但是,总是需要在终端里面输入命令,然后回到eclipse里面修改代码,很不爽,实际上可以直接在Eclipse完成所有的一切,而且,可以像java代码一样实现自动编译(前提是自动编译已经打开)。接下来一一搞定。
头文件的自动编译
每次我们修改了MainActivity.java 之后,可能都需要重新编译得到 native code对应的头文件,下面就来设置实现。
Project -> Properties -> Builder 菜单, 选择 New, 然后选择 Program, 就可以看到如下对话框:
首先是Main页面
- Location: 这个就是指编译程序的路径,我们要是用javah来生成头文件,所有是 /usr/bin/javah
- Work Directory: 工作目录,相当与终端下面切换到这个目录然后执行编译命令
- Arguments: 编译参数
- -classpath,指的是类所在的路径,注意不要写错了。
- com.viking.JniTest, 这个是类名
- -d, 结果输出目录,实际上,如果上面的Work Directory 设置成 jni目录的话,这个可以省略掉,不过,加上是最好的啦
接下来是 Refresh 页面,这个页面设置的就是是否在完成编译以后刷新项目,以及怎么刷新(整个项目,指定的目录)。刷新的效果有很多,一个就是如果生成了新的文件,那么项目资源浏览器(也就是Package Explorer选项卡)里面被刷新的目录会更新, 另一个就是可能会 触发其他Builder 的动作, 比如,我们生成了头文件,那么很可能意味着需要重新编译 so 文件,如果 so 对应的Builder设置合理的话(也就是Builde Options页面设置,下面会讲到), 那么就会自动调用。 现在,我们选择第一个,也就是刷新整个工作目录。
最后,也是比较关键的一个页面,就是Build Options 页面,这个页面决定了什么时候调用我们所定义的Builder, 把During Auto builds选上,然后修改那个 Specify working set of relevant resources, 点击那个 Specify Resoures按钮。 这个就是说,指定项目中的某些内容,一旦这些内容发生改变,那么就调用当前的 Builder。 显然,对于NDK Header Builder 来说,一旦我们修改了 MainActivity.java, 那么很有可能需要重新生成 so 对应的头文件, 所以把 MainActivity.java 选上。另外,为了避免删掉了那个头文件,导致后面的so编译失败,把它也选上,这样只要我们删掉了这个头文件,就会自动调用 NDK Header Builder来生成它。 然后点击OK。
现在,我们不妨测试一下这个Builder,先删除 jni下面的头文件,然后修改一下 MainActivity.java, Ctrl+S保存, 接下来,看 Console标签页,如下:
说明这个Build已经自动调用了。再说一句,前提是自动编译已经打开。
so 文件的自动编译
这里的设置和前面大同小异啦
设置好 ndk-builder 的路径。
设置Refresh为刷新整个工作目录。
然后修改Build Options, During Auto builds 勾上,Specify Resources里面把输入文件 jni目录和输出文件 libs/armeabi/libJniTest.so 选上。
最后,如果两个Builder之间有依赖关系,比如这里的NDK Builder必须在 NDK Header Builder生成头文件之后方可以正确工作,所以要把 NDK Header Builder 放在 NDK Builder前面,至此一切OK。
当然,有利就有弊,比如我们刚开始生成头文件,但是 Android.mk 和 c文件还没有创建,那么这个时候的 NDK Buidler也会自动调用,当然,编译是会报错的。
接下来,可能是关于第三方库的使用。