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上还会有打印。