本篇博客接上一篇的:[Freescale IMX6 Android: 基于TQIMX6 给Toolbox添加LED控制程序](http://blog.csdn.net/sy373466062/article/details/50252123),上一篇是直接将控制程序放到了Toolbox,本篇则是通过JNI调用C库来完成LED的控制,即APP--> C --> HW。
## APK的编写(源码见末尾)
### 编写主要需要注意下面个事项与步骤
1. 更改xml在layout中添加控件
2. Checkbox与Button的添加及其click与checked事件的handler
3. Toast的使用
4. API版本问题
5. 文档的查询
UI的layout如下:
![](https://box.kancloud.cn/2016-05-05_572afc9aaaf32.jpg)
然后添加checkbox与button的callback。
### 添加调用C语言native方法的java库
直接在App下的java中添加一个package,然后添加一个class,如下图的libledctrl中的HWLedCtrl:
![](https://box.kancloud.cn/2016-05-05_572afc9ac9c16.jpg)
图一
其中的内容也很简单,就是建立一个static代码块,同时将native函数声明为static函数,这样子因为static代码块是存在class中的,不需要new一个对象来操作,其中HWLedCtrl.java的内容如上图:
~~~
package libledctrl;
/**
* Created by hexiongjun on 12/11/15.
*/
public class HWLedCtrl {
public static native int ledctrl(int which, int status);
public static native int ledopen();
public static native void ledclose();
static {
try {
System.loadLibrary("ledctrl");
} catch (Exception e) {
e.printStackTrace();
}
}
}
~~~
可以看到这里面加载了libledctrl.so这个库,这个就是下面的内容。
有了native方法与class之后,我们就可以在APP更改checkbox与button的时候更改led状态了:
![](https://box.kancloud.cn/2016-05-05_572afc9aeee5e.jpg)
这里面首先是Open,然后是进行亮灭状态切换。
### C库的制作
在前面博客中的C语言代码基础上,我们去掉main函数,将其改成符合JNI接口的C源码:
~~~
#include "jni.h"
//#include "JNIHelp.h"
//#include "android_runtime/AndroidRuntime.h"
//#include <utils/misc.h>
//#include <utils/Log.h>
//#include <hardware_legacy/led.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define ALOGI printf
#define LED_NUM 3
int leds_fd[LED_NUM];
char path_buff[255];
static jint ledctrl(JNIEnv *env, jobject clazz, jint which, jint status)
{
int ret = -1;
if(status == 1) {
ret = write(leds_fd[which], "255", 3);
} else {
ret = write(leds_fd[which], "0", 1);
}
if(ret < 0){
return -1;
}
ALOGI("Native ctrl fd = [%d]\n", which);
return 0;
}
static jint ledopen(JNIEnv *env, jobject clazz)
{
int i = 0;
printf("Native Open\n");
for(i=0; i<LED_NUM; i++){
sprintf(path_buff, "/sys/class/leds/led%d/brightness", i);
printf("path:%s\n",path_buff);
leds_fd[i] = open(path_buff, O_RDWR);
if(leds_fd[i] < 0){
ALOGI("led%d: %s, open failed\n", i, path_buff);
return -1;
} else {
ALOGI("led%d: %s, open success\n", i, path_buff);
}
}
return 0;
}
static void ledclose(JNIEnv *env, jobject clazz)
{
int i = 0;
for(i=0; i< LED_NUM; i++){
close(leds_fd[i]);
}
}
static JNINativeMethod method_table[] = {
{ "ledctrl", "(II)I", (void*)ledctrl },
{ "ledclose", "()V", (void*)ledclose },
{ "ledopen", "()I", (void*)ledopen }
};
#if 0
int register_android_server_LedService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "libledctrl",
method_table, NELEM(method_table));
}
#endif
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
printf("Enter the JNI Onload\n");
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
printf("Version Error in JNI OnLoad\n");
return JNI_ERR; /* JNI version not supported */
}
// Package/ClassName
// The package is define in the java first line
cls = (*env)->FindClass(env, "libledctrl/HWLedCtrl");
if (cls == NULL) {
return JNI_ERR;
}
/* 2. map java hello <-->c c_hello */
if ((*env)->RegisterNatives(env, cls, method_table, sizeof(method_table)/sizeof(JNINativeMethod)) < 0)
return JNI_ERR;
return JNI_VERSION_1_4;
}
~~~
需要注意的是:
~~~
cls = (*env)->FindClass(env, "libledctrl/HWLedCtrl");
~~~
这里面需要填写正确的来调用的class,这个路径按照Package/Class来写。
然后进行编译链接成动态库,要使用Android提供的ToolChain需要先lunch target,免去写长长的路径Prefix:
~~~
source build/ensetup.sh
lunch sabresd_6dq-eng
~~~
第一步是compile,将C源码(ledctrl_jni.c)编译成object(ledctrl_jni.o)文件:
~~~
arm-linux-androideabi-gcc -I bionic/libc/bionic -I libnativehelper/include/nativehelper -isystem system/core/include -isystem hardware/libhardware/include -isystem hardware/libhardware_legacy/include -isystem hardware/ril/include -isystem libnativehelper/include -isystem frameworks/native/include -isystem frameworks/native/opengl/include -isystem frameworks/av/include -isystem frameworks/base/include -isystem external/skia/include -isystem out/target/product/sabresd_6dq/obj/include -isystem bionic/libc/arch-arm/include -isystem bionic/libc/include -isystem bionic/libstdc++/include -isystem bionic/libc/kernel/common -isystem bionic/libc/kernel/arch-arm -isystem bionic/libm/include -isystem bionic/libm/include/arm -isystem bionic/libthread_db/include -fno-exceptions -Wno-multichar -msoft-float -fpic -fPIE -ffunction-sections -fdata-sections -funwind-tables -fstack-protector -Wa,--noexecstack -Werror=format-security -D_FORTIFY_SOURCE=1 -fno-short-enums -march=armv7-a -mfloat-abi=softfp -mfpu=neon -include build/core/combo/include/arch/linux-arm/AndroidConfig.h -I build/core/combo/include/arch/linux-arm/ -Wno-unused-but-set-variable -fno-builtin-sin -fno-strict-volatile-bitfields -Wno-psabi -mthumb-interwork -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith -Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point -DNDEBUG -g -Wstrict-aliasing=2 -fgcse-after-reload -frerun-cse-after-loop -frename-registers -DNDEBUG -UDEBUG -mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -c ledctrl_jni.c -o ledctrl_jni.o
~~~
第二步则是完成链接ledctrl_jni.o --> libledctrl.so:
~~~
arm-linux-androideabi-g++ -nostdlib -Bdynamic -fPIE -pie -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc -Lout/target/product/sabresd_6dq/obj/lib -Wl,-rpath-link=out/target/product/sabresd_6dq/obj/lib ledctrl_jni.o -Wl,--whole-archive -Wl,--no-whole-archive -lc -lstdc++ -lm -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--icf=safe -Wl,--fix-cortex-a8 -Wl,--no-undefined -shared -fPIC -o libledctrl.so
~~~
### 链接时候需要的注意
需要注意的是链接的时候需要写明-lc,即要链接libc,否则在加载的时候后出现问题,例如没有加入的话运行的时候会出现下面的提示:
![](https://box.kancloud.cn/2016-05-05_572afc9b1ae26.jpg)
这里提示的是找不到符号__sprintf_chk,而这个符号我们可以确定其来源于libc,操作命令如下:
~~~
readelf --syms libc.so
~~~
结果中可以看到这个函数的定义:
~~~
File: out/target/product/sabresd_6dq/system/lib/libc.so
Symbol table '.dynsym' contains 1243 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000c9d8 88 FUNC GLOBAL DEFAULT 8 __init_tls
289: 000138bd 30 FUNC GLOBAL DEFAULT 8 __sprintf_chk
~~~
这个是查找一个symbol的方法。
### 编译用的ToolChain的注意
另外,看到很多人直接使用了arm-linux-gnueabi-gcc来编译链接,这样子做并不合理,因为我们是需要运行在Android上面,而Android系统编译使用的是arm-linux-androideabi-gcc来编译的,所以最后Android系统打包的libc等各种库都是用这个toolchain里面的。否则容易出现问题。
添加Clib库到APK项目中
在app/libs中添加armeabi目录,并将libledctrl.so拷贝进来,然后project的structure如下:
![](https://box.kancloud.cn/2016-05-05_572afc9b3b00d.jpg)
添加完成后build project,并generate APK,然后我们可以解压APK看到lib库已经打包进去了:
![](https://box.kancloud.cn/2016-05-05_572afc9b58813.jpg)
然后就可以运行查看效果了。
## class名字的一致性
前面提到C代码中需要查找(FindFlass)对应来调用其的java class,如果名字不对应,那么就会提示找不到对应的class,例如下面在C中要查找的是LedCtrl class,自然找不到:
![](https://box.kancloud.cn/2016-05-05_572afc9b7cb80.jpg)
## native函数名字的一致性
如果java中native的函数声明与实际的C中的函数不一致也会报错,而且要注意函数的返回类型与传入的参数类型,例如下面这个就是不匹配的结果:
![](https://box.kancloud.cn/2016-05-05_572afc9b9bff7.jpg)
## APK调试方法
### 查看so是否加载
这个可以通过adb中的log信息来查看,同时也可以查看加载器是否加载了so,例如我们可以去cat /proc/PROCESS/maps文件查看,也可以使用pmap命令来查看:
~~~
root@sabresd_6dq:/ # pmap 9196 | grep led
415a7000 16K r--s /data/app/com.hexiongjun.led-1.apk
65c6f000 16K r--s /data/app/com.hexiongjun.led-1.apk
6644b000 156K r--s /data/app/com.hexiongjun.led-1.apk
664a7000 4K r-xp /data/app-lib/com.hexiongjun.led-1/libledctrl.so
664a8000 4K r--p /data/app-lib/com.hexiongjun.led-1/libledctrl.so
664a9000 4K rw-p /data/app-lib/com.hexiongjun.led-1/libledctrl.so
~~~
我们可以看到后面so被加载了。
## logcat查看某个进程的log
一条命令,后面的30585就是process id:
~~~
logcat -v process | grep 30585
~~~
## 代码位置
[https://github.com/tonyho/ApkJNI01](https://github.com/tonyho/ApkJNI01.git)
- 前言
- Freescale IMX6 Android (1): 使用HDMI作为Android显示输出的配置
- Freescale IMX6 Android (2): Android NFS启动问题汇总
- Freescale IMX6 Android (3): 手动制作Android启动用SD卡 省去MFGTOOLS烧写
- Freescale IMX6 Android (5): APP通过JNI控制LED
- Freescale IMX6 Android (4): 基于TQIMX6 给Toolbox添加LED控制程序
- Freescale IMX6 Android (6): 向ServerManager中添加Service
- Freescale IMX6 Android (7): Android启动动画死循环 Home界面不出来与pid XXX exit 可能的原因汇总