Android Framework 之HelloWorld(二)
HelloWorld功能简单,假设这个led灯(硬件)只有我们一个HelloWorld app会去 操作它,其他app永远不去操作,那么 我们就不使用硬件访问服务(system server)了,直接使用JNI的方式操作底下硬件。
JNI不是Android特有,有兴趣可以查阅java jni的官方接口规范文档。
我们先写java端,接着上次创建的HelloWorld工程,在HelloWorld/app/src/main/java/com/demo/下增加一个文件夹ledctrl,里面创建一个LedCtrl.java文件,用于提供java native接口:
package com.demo.ledctrl;public class LedCtrl { public static native int LedInit(); public static native int LedCtrl(boolean blIsOn); public static native void LedRelease(); static { try { System.loadLibrary("ledctrl"); } catch (Exception e) { e.printStackTrace(); } }}
LedCtrl.java总共就暴露了三个接口:LedInit、LedCtrl和LedRelease,分别用于打开一个led灯资源、控制led灯亮灭以及释放一个led灯资源。而System.loadLibrary("ledctrl");则用于加载一个so动态链接库,说明这个C/C++库名字叫“libledctrl.so”。
下面则是一个最简单的JNI模板:
#include #include #include #include #include #include #include #define PRINT_DEBUG(x,...) __android_log_print(ANDROID_LOG_DEBUG, "MyLog", ("|DBG|<%s:%d>[%s] " x),basename(__FILE__), __LINE__,__FUNCTION__,##__VA_ARGS__)#define PRINT_ERROR(x,...) __android_log_print(ANDROID_LOG_ERROR, "MyLog", ("|ERR|<%s:%d>[%s] " x),basename(__FILE__), __LINE__,__FUNCTION__,##__VA_ARGS__)jint LedInit(JNIEnv *env, jobject cls){PRINT_DEBUG("Entry LedInit ...");return 0;}jint LedCtrl(JNIEnv *env, jobject cls, jboolean blIsOn){PRINT_DEBUG("Entry LedCtrl : blIsOn = %d", blIsOn);return 0;}void LedRelease(JNIEnv *env, jobject cls){PRINT_DEBUG("Entry LedRelease ...");}static const JNINativeMethod methods[] = {{"LedInit", "()I", (void *)LedInit},{"LedCtrl", "(Z)I", (void *)LedCtrl},{"LedRelease", "()V", (void *)LedRelease},};JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved){JNIEnv *env;jclass cls;if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6)) {PRINT_ERROR("GetEnv error!");return JNI_ERR;}cls = (*env)->FindClass(env, "com/demo/ledctrl/LedCtrl");if (cls == NULL) {PRINT_ERROR("FindClass error!");return JNI_ERR;}if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0) {PRINT_ERROR("RegisterNatives error!");return JNI_ERR;}return JNI_VERSION_1_6;}
因为在jni里使用printf是不能在Android Studio的logcat上看到打印的,要使用google封装好的__android_log_print函数才行。我这里进一步封装成PRINT_DEBUG和PRINT_ERROR宏。
值得关注的是cls = (*env)->FindClass(env, "com/demo/ledctrl/LedCtrl");这句话,这里是要根据路径找到对应的java类,而我们的LedCtrl类是定义在com/demo/ledctrl/LedCtrl.java的。
static const JNINativeMethod methods[] = {...};则是声明了对应于java上的native接口,LedInit、LedCtrl和LedRelease,它们没有具体实现硬件相关的代码,只是加入打印而已。
好了,我们要使用gcc编译出libledctrl.so:
aarch64-linux-gcc -fPIC -shared ledctrl.c -o libledctrl.so -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux -nostdlib ../rk3399-android-8.1/prebuilts/ndk/r11/platforms/android-24/arch-arm64/usr/lib/libc.so -I ../rk3399-android-8.1/prebuilts/ndk/r11/platforms/android-24/arch-arm64/usr/include ../rk3399-android-8.1/prebuilts/ndk/r11/platforms/android-24/arch-arm64/usr/lib/liblog.so
这里没有使用Makefile,也没有创建NDK工程,而是直接使用gcc命令进行编译。需要关注的是,要指定正确的库路径和头文件路径。使用-nostdlib编译选项是因为我的开发板nanopc-t4只有/system/lib64/libc.so,而没有标准c库中的libc.so.6。aarch64-linux-gcc是rk3399的交叉编译工具链,在android8.1源码中用于编译uboot、linux内核、framework等相关模块。
编译出来libledctrl.so应该放在AS工程的什么地方呢?在AS创建工程时我们看到有个“libs”的空文件夹,我们可以把libledctrl.so放进该目录,但需要用“arm64-v8a”文件夹包裹起来(即HelloWorld/app/libs/arm64-v8a/下),否则打开app后会出现类似:
....... is not accessible for the namespace "classloader-namespace"之类的错误log。当然了,如果还要依赖其他第三方库的话,还要把库push到/system/lib64/,同时修改/system/etc/public.libraries.txt文件。
最后还要在HelloWorld/app/build.gradle中指定库的路径:
android {...... sourceSets { main { jniLibs.srcDirs = ['libs'] } }......
最后在按键点击事件回调中加入LED的控制的函数调用:
......import com.demo.ledctrl.*;public class MainActivity extends AppCompatActivity { private boolean isLedOn = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LedCtrl m_LedCtrl = new LedCtrl(); m_LedCtrl.LedInit(); final Button button = findViewById(R.id.Led); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Code here executes on main thread after user presses button if(isLedOn == false) { LedCtrl.LedCtrl(false); button.setText("Led off"); Toast.makeText(getApplicationContext(), "Led off", Toast.LENGTH_SHORT).show(); } else { LedCtrl.LedCtrl(true); button.setText("Led on"); Toast.makeText(getApplicationContext(), "Led on", Toast.LENGTH_SHORT).show(); } isLedOn = !isLedOn; } }); }}
OK,这样HelloWorld就完成了C库的调用。点击屏幕上的按钮,在logcat上还会有打印。
更多相关文章
- Android 匿名共享内存C接口分析
- android在build中配置资源路径的方式
- Android PinyinIME 源码笔记 -- 1. 底层服务接口简介
- Android 从硬件到应用:一步一步向上爬 4 -- 使用 JNI 方法调硬件
- windows修改Android AVD路径
- 读取指定路径数据库的方法
- android 调用系统图片浏览器并返回图片路径
- Android App怎样调用 Frameworks Bluetooth接口
- Android Uri转换成真实File路径