开发环境:Android stdio 3.5.3
开发板:tiny4412开发板
软件版本:Android5.0.2 + kernel3.0.86
下面将在AS ide中实现一个简单的APP操作界面,总共使用了四个checkbox和一个button,四个checkbox分别用来单独控制四个LED的亮灭,button按钮同时控制四个led的亮灭。下面就是实际的layout界面图,比较简单,不过多介绍。
如下所示在四个checkbox中均设定了click函数为onCheckBoxClick,在这个函数中根据具体的checkbox调用native的ledctrl方法来打开或者关闭对应的led灯。
activity_main.xml
MainActivity.java
public void onCheckBoxClick(View view) {
boolean checked = ((CheckBox) view).isChecked();
switch(view.getId()) {
case R.id.checkBox:
if (checked) {
// Put some meat on the sandwich
**HardControl.ledCtrl(1, 1);**
Toast.makeText(MainActivity.this, "LED1 on", Toast.LENGTH_SHORT).show();
}
else {
// Remove the meat
**HardControl.ledCtrl(1, 0);**
Toast.makeText(MainActivity.this, "LED1 off", Toast.LENGTH_SHORT).show();
}
break;
case R.id.checkBox2:
if (checked) {
// Put some meat on the sandwich
HardControl.ledCtrl(2, 1);
Toast.makeText(MainActivity.this, "LED2 on", Toast.LENGTH_SHORT).show();
}
else {
// Remove the meat
HardControl.ledCtrl(2, 0);
Toast.makeText(MainActivity.this, "LED2 off", Toast.LENGTH_SHORT).show();
}
break;
.......
在button的setOnClickListener函数中同样是调用 HardControl.ledCtrl方法来控制四个led灯了。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LED1 = (CheckBox) findViewById(R.id.checkBox);
LED2 = (CheckBox) findViewById(R.id.checkBox2);
LED3 = (CheckBox) findViewById(R.id.checkBox3);
LED4 = (CheckBox) findViewById(R.id.checkBox4);
Butt_ctrl = (Button) findViewById(R.id.button);
**HardControl hardControl = new HardControl();
HardControl.ledOpen();**
Butt_ctrl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ledall_status) {
HardControl.ledCtrl(1, 1);
HardControl.ledCtrl(2, 1);
HardControl.ledCtrl(3, 1);
HardControl.ledCtrl(0, 1);
Butt_ctrl.setText("All On");
LED1.setChecked(true);
LED2.setChecked(true);
LED3.setChecked(true);
LED4.setChecked(true);
Toast.makeText(MainActivity.this, "打开所有LED", Toast.LENGTH_SHORT).show();
}
else {
HardControl.ledCtrl(1, 0);
HardControl.ledCtrl(2, 0);
HardControl.ledCtrl(3, 0);
HardControl.ledCtrl(0, 0);
Butt_ctrl.setText("All Off");
LED1.setChecked(false);
LED2.setChecked(false);
LED3.setChecked(false);
LED4.setChecked(false);
Toast.makeText(MainActivity.this, "关闭所有LED", Toast.LENGTH_SHORT).show();
}
ledall_status = !ledall_status;
}
});
从上述代码可以看到内容很少,点亮关闭led灯的关键之处就是在onCreate方法中调用了 HardControl.ledCtrl这个方法了,下面我们具体来看看这个方法是如何实现的。
package com.example.hardlibrary;
public class HardControl {
public static native int ledCtrl(int which, int status);
public static native int ledOpen();
//public static native int ledClose();
static {
try {
System.loadLibrary("hardcontrol");
} catch (Exception e) {
e.printStackTrace();
}
}
}
从上图可以看到HardControl 方法也非常简单,只有两三个native方法,然后就去通过loadLibrary 调用我们的libhardcontrol.so动态库。这里需要注意的是我们自己的动态库如何添加到AS工程里面,下图是AS的工程层级图,我们的动态库是放在app\src\main\libs\armeabi目录下,然后还要设置build.gradle文件,添加如下sourceSets 和ndk内容工程才能找到我们的库文件。
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.tiny_demo"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
**sourceSets {
main {
jniLibs.srcDirs = ['app/libs']
jniLibs.srcDirs = ['src/main/libs']
}
}
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi'
}**
}
至此,app端的内容已经介绍完毕了,app通过checkbox和button响应点击事件来通过native方法调用实现功能。
上面用到的libhardcontrol.so就这这一小节的重点了,先看源码,对着源码分析介绍
#include /* /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ */
#include
#include
#include
#include
#include
#include
#include /* liblog */
//__android_log_print(ANDROID_LOG_DEBUG, "JNIDemo", "native add ...");
static jint fd;
jint ledOpen(JNIEnv *env, jobject cls)
{
fd = open("/dev/leds", O_RDWR);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen :");
if (fd >= 0)
return 0;
else
return -1;
}
void ledClose(JNIEnv *env, jobject cls)
{
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledClose ...");
// close(fd);
}
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
int ret = ioctl(fd, status, which);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl : %d, %d", which, status);
return ret;
// return 0;
}
static const JNINativeMethod methods[] = {
{"ledOpen", "()I", (void *)ledOpen},
//{"ledClose", "()V", (void *)ledClose},
{"ledCtrl", "(II)I", (void *)ledCtrl},
};
/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
__android_log_print(ANDROID_LOG_DEBUG, "TNT", "JNI_OnLoad start ...");
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "com/example/hardlibrary/HardControl");
if (cls == NULL) {
return JNI_ERR;
}
/* 2. map java hello <-->c c_hello */
if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)
return JNI_ERR;
__android_log_print(ANDROID_LOG_DEBUG, "TNT", "JNI_OnLoad end ...");
return JNI_VERSION_1_4;
}
app侧的 System.loadLibrary(“hardcontrol”);方法会调用到我们的JNI_OnLoad函数,先通过GetEnv获得JAVA的环境,然后通过FindClass来找到对应的我们java实现的HardControl类,然后通过RegisterNatives注册和HardControl方法中同名的函数,这样app侧调用native方法我们c语言中对应的同名函数就会得到调用,就可以通过Linux标准的系统调用接口open、ioctl来操作我们具体的硬件了。下面是通过交叉编译工具链来编译动态库的指令
arm-linux-gcc -fPIC -shared hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -nostdlib -I /home/tangtao/work/tiny_4412/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/include /home/tangtao/work/tiny_4412/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/liblog.so
小结:从上述流程可以看到APP通过JNI调用我们实现的c函数可以很轻松的完成对硬件的访问,但是这仅仅是个demo,实际上Android系统中对硬件的访问并不是这么简单,因为直接通过jni调用硬件这种方法存在很大的局限性,如果我们多个app都会访问到同一个硬件就会造成硬件响应混乱的局面,无法统一管理,这是不可接受的,后续在介绍Android系统是如何完成app端到对硬件的访问的,可以预想上述的步骤肯定是必不可少的,只是Android系统更加规范的设计了整个流程,下回分解,好好学习,天天向上!