上一小節,介紹了界面設計怎麼和具體功能函數聯系在一起。下面将介紹在andriod系統中怎麼通路C程式
在java中是無法調用C函數的,但是我們要操作硬體又不得不去調用C函數,看起來這是個十分沖突的問題。其實不然,
例如:在winds作業系統中,我們沒有辦法執行linux程式,但是通過虛拟機可以運作ubuntu執行linux系統程式。
同理,在andriod系統中,我們也可以建立一個虛拟機,去調用C函數,實作對底層的控制
協定制定
根據我們目的,要實作對LED燈的控制,我們至少需要兩個C函數open,ioctrl。在C檔案中,我們把這個兩個函數實作,然後通過java虛拟機進行注冊,轉化為對應的方法。為了友善,我們使用一一對應的關系,即在java軟體程式中調用ioctl,實質是調用C程式的ioctl,當然open,close也是一一對應。
制定一個簡單的協定,ioctl傳入兩個參數,第一個指定LED(可傳遞參數1或者2),第二個參數指定LED的狀态(0代表OFF,1代表ON)下面我們就開始向java虛拟機注冊函數
注冊本地C函數
我們先建立一個hardcontrol.c檔案,這個檔案主要是把C函數(主要為對硬體的操作函數)注冊轉化為java方法,這樣andriod系統就可以通過調用java方法,執行對應的C函數,實作對硬體的操作。
首先實作函數
該函數會在APP軟體程式中調用System.loadLibrary時進而被調用,那麼System.loadLibrary的作用是什麼呢?從名字上了解即可,就是系統加載library,至于他什麼時候加載,怎麼加載,在後面進行講解。下面我們看看這個函數需要做什麼
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "com/thisway/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;
return JNI_VERSION_1_4;
}
其中的核心函數為 ( ∗ e n v ) − > R e g i s t e r N a t i v e s ( ) \color{red}{(*env)->RegisterNatives()} (∗env)−>RegisterNatives(),它實作了C函數到java方法的轉化,這個函數需要是個參數:
JNIEnv *env: JNI的環境變量
jclass cls: 注冊C程式後,生成的類相關資訊儲存結構體
JNINativeMethod **methods: 需要注冊的C函數結構體數組
jint number:注冊函數的數目
既然要實作RegisterNatives函數,那麼我們就需要傳遞他所需要的四個參數
JNIEnv *env
JNIEnv *env;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4))
其中JNI_VERSION_1_4為JNI的版本号,這裡我們暫時填寫為JNI_VERSION_1_4
jclass cls
jclass cls;
cls=(*env)>FindClass(env,"com/example/administrator//hardlibrary/HardControl");
注 意 \color{red}{注意} 注意:這裡的路徑比較重要,要生成一個class,這個路徑表示這個class屬于哪個包,在其他的class想導入生成的class必須以這個路徑為基準,這個後續會再次提到。
JNINativeMethod **methods
static const JNINativeMethod methods[] = {
{"ledOpen", "()I", (void *)ledOpen},
{"ledClose", "()V", (void *)ledClose},
{"ledCtrl", "(II)I", (void *)ledCtrl},
};
這個參數,為一個二級指針,我們可以一次注冊多個函數,右邊的"ledOpen",“ledClose”,“ledCtrl"代表的是java中的方法,注冊之後我們就可以調用HardControl類中"ledOpen”,“ledClose”,“ledCtrl”。實質是調用右邊與之對應的C函數,然後我們就需要實作這三個函數,具體代碼在最後有貼出
由于核心驅動,還沒有移植,我們暫時隻做簡單資訊列印,其中
"()I"代表方法沒有參數,傳回值為int型
"()V"代表方法沒有參數,也沒有傳回值
"(II)I"代表方法有兩個int型參數和一個int型的傳回值,具體了解可另行百度
動态C庫編譯
通過上面的介紹,我們已經寫出了JNI本地函數,現在我們需要把hardcontrol.c編譯成動态C庫,然後加載到APP中。編譯指令如下
sudo aarch64-linux-gnu-gcc hardcontrol.c -fPIC -shared -o libhardcontrol.so -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-8-openjdk-amd64/include/linux/ -I /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/include/ -nostdlib /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/libc.so /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/liblog.so
指令看起來有點吓人,不過沒有辦法,
如果不加上
-I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/
就會出現找不到jni.h的錯誤提示;
如果不加上
-I /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/include/
就會出現找不到log.h的錯誤提示;
如果不加上
-nostdlib /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/libc.so
就會出現library “libc.so.6” not found 的錯誤提示,系統中預設沒有libc.so.6庫,是以可以直接指定使用libc.so進行代替;
如果不加上
/home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/liblog.so
使用列印接口需要使用到liblog.so否則就會出錯,該liblog.so可以在android源碼中查找得到;
APP加載C庫
通過前面的操作,編譯生成libhardcontrol.so我們把這個C庫拷貝到AS(AandriodStudio)工程目錄app-> libs-> arm64-v8a(該檔案自行建立)下,然後打開AS,在工程界面app-> jniLibs下可以看到arm64-v8a檔案夾中的libhardcontrol.so,這樣我們的這個C庫,就加載到了工程中,但是加載過後我們并沒有使用。
前面提到過
這裡要填寫"com/example/administrator/HardControl"的原因是,我們要把對硬體操作的C函數注冊成一個類,這個類就是HardControl,屬于包com.example.administrator,現在我們在這個包中建立文夾(工程目錄app->src-> main-> java-> example-> administrator)hardlibrary,并且在其中建立檔案HardControl.java,然後打開AS軟體,編寫該檔案代碼如下:
package com.example.administrator.hardlibrary;
public class HardControl {
public static native int ledCtrl(int which,int status);
public static native int ledOpen();
public static native void ledClose();
static {
try {
System.loadLibrary("hardcontrol");
} catch (Exception e) {
e.printStackTrace();
}
}
}
這裡我們定義了一個public class HardControl,其中定義了三個靜态本地函數ledCtrl(int which,int status),ledOpen(),ledClose()。其中有靜态代碼塊,塊中的 System.loadLibrary(“hardcontrol”)加載了我們之前編譯生成的C庫,前面提到過,在加載C庫的時候,會執行
這樣,就會注冊C函數為HardControl中的方法,在APP軟體程式中,需要控制LED時,隻需要導入import com.example.administrator.hardlibrary.*,我們就可以調用HardControl中的方法了。
APP軟體補充
在上一節中,我們的APP軟體隻隻是進行了簡單的操作,點選界面button和checkbox隻做了簡單的列印資訊,現在我們讓他通過
class HardControl 進而調用C函數,打開MainActivity.java檔案(app-> java中),在class MyButtonListener implements View.OnClickListener中,添加HardControl.ledCtrl函數,具體怎麼添加,請看本部落格末尾的代碼清單。
注 意 \color{red}{注意} 注意:Gradle Scripts->build.gradle(Modeule:app)腳本檔案中,android 代碼段添加
splits {
abi {
enable true
reset()
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
universalApk true //generate an additional APK that contains all the ABIs
}
}
目的是讓其把libhardcontrol.so加載到APK之中,在我們的GEC-3399開發闆上正常運作。
代碼清單
hardcontrol.c
#include <jni.h> /* /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <android/log.h> /* liblog */
#if 0
typedef struct {
char *name; /* Java閲岃皟鐢ㄧ殑鍑芥暟鍚?*/
char *signature; /* JNI瀛楁鎻忚堪绗? 鐢ㄦ潵琛ㄧずJava閲岃皟鐢ㄧ殑鍑芥暟鐨勫弬鏁闆拰杩斿洖鍊肩被鍨?*/
void *fnPtr; /* C璇█瀹炵幇鐨勬湰鍦闆嚱鏁?*/
} JNINativeMethod;
#endif
jint ledOpen(JNIEnv *env, jobject cls)
{
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen");
return 0;
}
void ledClose(JNIEnv *env, jobject cls)
{
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledClose");
}
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
int ret;
//int ret = ioctl(fd, status, which);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl : %d, %d, %d", which, status, ret);
return ret;
}
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;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "com/example/administrator/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;
return JNI_VERSION_1_4;
}
HardControl.java
package com.example.administrator.hardlibrary;
public class HardControl {
public static native int ledCtrl(int which,int status);
public static native int ledOpen();
public static native void ledClose();
static {
try {
System.loadLibrary("hardcontrol");
} catch (Exception e) {
e.printStackTrace();
}
}
}
回顧總結
該小節,主要介紹了,andriod軟體如何加載,通過JNI調用C函數,以及生成動态C庫的編譯過程
下小節将介紹,核心驅動的實作
源碼下載下傳
[系統移植相關源碼]
(https://github.com/944284742/android7.1Transplant.git)
[AndriodStudioAPP]
(https://github.com/944284742/andriod7.1APP.git)