最近开发App涉及到许多图像处理操作,使用的OpenCV开源库,考虑到性能问题,决定使用JNI层开发。之前一直停留在应用层,并没有JNI层开发的经验,心里总感觉没谱,趁这次机会好好理理,权当纪录。
工具 工欲善其事,必先利其器。开发Android的话,首推Android Studio ,强大的智能提示和优秀的Gradle构建系统,能最大程度提高你的生产力;另外,既然涉及到JNI层开发,还需要下载NDK工具集 。
在早期版本,NDK和SDK是分别打包的;最新Android Studio 1.5,NDK和SDK捆绑,可以直接在Android Studio内安装。
JNI开发概览 开发方式 最开始Android Studio并不支持JNI开发,当时开发者只能选择Eclipse。传统的JNI开发是写好C/C++,最后通过编写Makefile编译脚本,生成so文件供应用层调用。 自从Android Studio 1.3版本加入JNI开发支持后,可以结合Gradle插件进行JNI开发,具体的编译任务通过Gradle插件完成,不需要额外编写编译脚本,并且支持JNI层调试 。 这里,我们通过后一种方式进行说明。具体的官方示例代码可以移步Github 。
具体步骤 新建Android工程
全部选择默认设置,一路Next。
修改Gradle相关配置 Andriod Studio新建工程的Gradle配置是不支持JNI开发的,需要我们手动进行调整。 找到./gradle/wrapper/gradle-wrapper.properties
,修改Gradle版本为2.5。目前,只有2.5版本支持JNI模块开发,可以参考Experimental Plugin User Guide 。
1 2 3 4 5 6 #Wed Oct 21 11:34:03 PDT 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
修改./build.gradle
文件,将依赖模块修改为gradle-experimental
。
1 2 3 dependencies { classpath 'com.android.tools.build:gradle-experimental:0.2.0' }
修改./app/build.gradle
文件,引入model 和JNI 配置。
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 apply plugin: 'com.android.model.application' model { android { compileSdkVersion 23 buildToolsVersion = "23.0.2" defaultConfig.with { applicationId = "com.aaron.nativeapp" minSdkVersion.apiLevel = 11 targetSdkVersion.apiLevel = 23 } } compileOptions.with { sourceCompatibility=JavaVersion.VERSION_1_7 targetCompatibility=JavaVersion.VERSION_1_7 } /* * native build settings */ android.ndk { moduleName = "native" /* * Other ndk flags configurable here are * cppFlags += "-fno-rtti" * cppFlags += "-fno-exceptions" * ldLibs = ["android", "log"] * stl = "system" */ } android.buildTypes { release { minifyEnabled = false proguardFiles += file('proguard-rules.txt') } } android.productFlavors { // for detailed abiFilter descriptions, refer to "Supported ABIs" @ // https://developer.android.com/ndk/guides/abis.html#sa create("arm") { ndk.abiFilters += "armeabi" } create("arm7") { ndk.abiFilters += "armeabi-v7a" } create("arm8") { ndk.abiFilters += "arm64-v8a" } create("x86") { ndk.abiFilters += "x86" } create("x86-64") { ndk.abiFilters += "x86_64" } create("mips") { ndk.abiFilters += "mips" } create("mips-64") { ndk.abiFilters += "mips64" } // To include all cpu architectures, leaves abiFilters empty create("all") } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) // testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.0' compile 'com.android.support:design:23.1.0' }
这个有几个坑列一下:
Gradle版本不对 解决办法:重新设置Gradle的安装目录。
应用层(Java层) 新建一个包含native方法的class。
1 2 3 4 5 6 7 8 9 10 package com.aaron.nativeapp; /** * Created by Aaron on 15/11/11. */ public class NativeCore { public static native String nativeEcho(String param); }
nativeEcho(String param)
方法很简单,就是在输入参数的基础上加上Echo字符串作为结尾。例如:Hi,JNI —> Hi,JNI Echo 。
创建好class后,Rebuild Project
将会在./app/build/intermediates/classes/all/debug/com/aaron/nativeapp
目录生成相应的.class
文件。
JNI层 打开Terminal,在./app/build/intermediates/classes/all/debug
目录执行命令:
1 $ javah -jni com.aaron.nativeapp.NativeCore
生成class对应的头文件com_aaron_nativeapp_NativeCore.h
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_aaron_nativeapp_NativeCore */ #ifndef _Included_com_aaron_nativeapp_NativeCore #define _Included_com_aaron_nativeapp_NativeCore #ifdef __cplusplus extern "C" { #endif /* * Class: com_aaron_nativeapp_NativeCore * Method: nativeEcho * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_aaron_nativeapp_NativeCore_nativeEcho (JNIEnv *, jclass, jstring); #ifdef __cplusplus } #endif #endif
在.app/src/main
新建一个目录jni
,将头文件剪切到该目录下。
现在可以开始着手写C代码了。代码内容很简单,读取从应用层传来的字符串,并在结尾处添加Echo
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // // Created by username on 15/11/11. // #include <string.h> #include "com_aaron_nativeapp_NativeCore.h" /* * Class: com_aaron_nativeapp_NativeCore * Method: nativeEcho * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_aaron_nativeapp_NativeCore_nativeEcho (JNIEnv *env, jclass thiz, jstring jParam) { const char *c_param = env->GetStringUTFChars(jParam, JNI_FALSE); char result[100]; strcpy(result, c_param); strcat(result, " Echo"); env->ReleaseStringUTFChars(jParam, c_param); return env->NewStringUTF(result); }
加载静态库 最后一步,在NativeCore中添加静态代码段,加在本地库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.aaron.nativeapp; /** * Created by Aaron on 15/11/11. */ public class NativeCore { static { System.loadLibrary("native"); } public static native String nativeEcho(String param); }
这里静态库的名称就是在./app/build.gradle
中android.ndk
定义的moduleName
。
调用 现在,所有的准备工作都完成了,让我们来调用NativeCore
看看结果。修改MainActivity
代码:
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 50 51 52 53 54 55 56 57 58 59 package com.aaron.nativeapp; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; public class MainActivity extends AppCompatActivity { TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mTextView = (TextView) findViewById(R.id.tv_content); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); mTextView.setText(NativeCore.nativeEcho("Hi, JNI")); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
编译运行。点击右下角Fab按钮,显示JNI返回结果。
JNI调试
相对传统编写.mk
进行JNI开发的方式,使用gradle-experimental
插件开发的另一个好处就是支持JNI调试,选择运行选项为app-native
:
在JNI层中打断点,点击Debug
就可以进行调试了。附一张效果图,还是挺方便的。
可能会出现unable to attach
错误:
1 2 3 4 5 DEVICE SHELL COMMAND: cat /data/local/tmp/start_lldb_server.sh | run-as com.aaron.nativeapp sh -c 'cat > /data/data/com.aaron.nativeapp/lldb/bin/start_lldb_server.sh; chmod 700 /data/data/com.aaron.nativeapp/lldb/bin/start_lldb_server.sh' Starting LLDB server: run-as com.aaron.nativeapp /data/data/com.aaron.nativeapp/lldb/bin/start_lldb_server.sh /data/data/com.aaron.nativeapp/lldb /data/data/com.aaron.nativeapp/lldb/tmp/platform-1447213097528.sock "lldb process:gdb-remote packets" Now Launching Native Debug Session Failed to attach native debugger: unable to attach unable to attach
多试几次就好了,小米1S亲测可用。