從內(nèi)核驅(qū)動到android app
該文不得作為商業(yè)用途,僅為學(xué)習(xí)積累所用,轉(zhuǎn)載請注明出處:http://blog.csdn.net/callon_h/article/details/51909169
了解android驅(qū)動框架:
1.方法1——jni調(diào)用底層驅(qū)動
在android框架中寫入c/c++直接調(diào)用底層linux驅(qū)動,并向上提供jni接口給應(yīng)用程序:
優(yōu)點:簡單易行;
缺點:主要在于驅(qū)動程序,由于在linux中需要遵循GPL協(xié)議,需要開源,而許多廠商的一些代碼不希望開源。
2.方法2——增加硬件抽象層
將驅(qū)動程序一分為二,一部分開源在內(nèi)核中,一部分不開源在android框架中:
led android驅(qū)動:
從這里我們將看到整個應(yīng)用框架層到底層驅(qū)動的走向。首先,無論是哪種方法,我們都需要實現(xiàn)一個linux驅(qū)動以供上層訪問led資源。
1.linux驅(qū)動:
linux的字符設(shè)備驅(qū)動編寫:
#include#include#include#include#include#include#include#includestatic?struct?cdev?dev; static?dev_t?dev_num; #define?GPM4CON?0x110002E0 #define?GPM4DAT?0X110002E4 #define?LED_ON?_IOW('G',0,int) #define?LED_OFF?_IOW('G',1,int) static?unsigned?int?*led_con?=?NULL; static?unsigned?int?*led_dat?=?NULL; static?struct?class?*led_class?=?NULL; static?long?led_ioctl(struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg) { switch(cmd) { case?LED_ON: { writel(readl(led_dat)&?~(0x1<<arg),?led_dat); break; } case?LED_OFF: { writel(readl(led_dat)|?(0x1<<arg),?led_dat); break; } default: { return?-EINVAL; break; } } return?0; } static?struct?file_operations?led_fops?=?{ .owner?=?THIS_MODULE, .unlocked_ioctl?=?led_ioctl, }; static?void?hw_init()//GPM4_0-3 { //1.2.1?映射地址 led_con?=?ioremap(GPM4CON,4); led_dat?=?ioremap(GPM4DAT,4); //1.2.2?設(shè)置為輸出狀態(tài) writel((readl(led_con)&?~0xffff)?|?0x1111,?led_con); //1.2.3?設(shè)置為高電平 writel(readl(led_dat)|0xf,?led_dat); } static?int?led_init() { ??//1.1?cdev字符設(shè)備初始化 //1.1.1?分配cdev結(jié)構(gòu)(靜態(tài)分配)? //1.1.2?初始化cdev結(jié)構(gòu) alloc_chrdev_region(&dev_num,0,1,"callon_led"); cdev_init(&dev,?&led_fops); dev.owner?=?THIS_MODULE; //1.1.3?注冊cdev結(jié)構(gòu)? cdev_add(&dev,dev_num,1); //1.2?硬件初始化 hw_init(); //1.3?創(chuàng)建設(shè)備文件 //1.3.1?創(chuàng)建類 led_class?=?class_create(THIS_MODULE,"callon_led"); //1.3.2?創(chuàng)建設(shè)備 device_create(led_class,NULL,dev_num,NULL,"%s","callon_led"); printk("init?led?device?is?OK!n"); return?0; } static?void?led_exit() { device_destroy(led_class,dev_num); class_destroy(led_class); iounmap(led_dat); iounmap(led_con); cdev_del(&dev); unregister_chrdev_region(dev_num,1); } module_init(led_init); module_exit(led_exit);
2 實現(xiàn)第一種方法:
首先,說明一下為什么使用jni:
1.基于對代碼的保護,java相對容易被反編譯,而c/c++庫反匯編難度較大;
2.可以方便地使用現(xiàn)存的開源庫;
3.提高執(zhí)行效率;
4.java在某些文件操作方面,找不到相關(guān)的API,如此處的ioctl.
其次,為什么使用ndk?
ndk提供了一系列的工具,能夠幫助開發(fā)者快速開發(fā)c/c++的動態(tài)庫,并能自動將.so動態(tài)庫和java一起打包成apk.
第一種方法的設(shè)計思路如下所示:
在第一步中想要使用javah命令自動產(chǎn)生頭文件需要先編寫app程序,但是此時的app程序并不需要很完善,只需要提出一個你想要的native接口就好(但是你的android程序必須要Rebuild正確才會正確產(chǎn)生頭文件):
package?com.led.ndk.example.callon.ndk_led; import?android.app.Activity; import?android.os.Bundle; import?android.view.View; import?android.widget.CheckBox; public?class?MainActivity?extends?Activity?{ ????private?CheckBox[]?Led?=?new?CheckBox[4]; ????@Override ????protected?void?onCreate(Bundle?savedInstanceState)?{ ????????super.onCreate(savedInstanceState); ????????setContentView(R.layout.activity_main); ????????Led[0]?=?(CheckBox)findViewById(R.id.checkbox_led1); ????????Led[1]?=?(CheckBox)findViewById(R.id.checkbox_led2); ????????Led[2]?=?(CheckBox)findViewById(R.id.checkbox_led3); ????????Led[3]?=?(CheckBox)findViewById(R.id.checkbox_led4); ????} ????private?void?SendCmd(View?view) ????{ ????????for(int?i?=1;?i<5;?i++) ????????{ ????????????if(Led[i].isChecked()) ????????????{ ????????????????cmdLeds(1,?i); ????????????} ????????????else ????????????{ ????????????????cmdLeds(0,?i); ????????????} ????????} ????} ????public?native?void?cmdLeds(int?cmd,?int?arg); ???? }
根據(jù)如上簡單的app,使用命令:
javah -d jni -classpath 你的sdk目錄/platforms/你的平臺名/android.jar:你的應(yīng)用程序/app/build/intermediates/classes/debug/ 你的包名.主類名
如:
javah?-d?jni?-classpath?/opt/AndroidSDK/platforms/android-23/android.jar:/home/callon/Downloads/callon_ndk_led/ndk_led/app/build/intermediates/classes/debug/?com.led.ndk.example.callon.ndk_led.MainActivity
此時自動產(chǎn)生出jni文件夾,其中包含頭文件com_led_ndk_example_callon_ndk_led_MainActivity.h,頭文件比較重要的:
JNIEXPORT?void?JNICALL?Java_com_led_ndk_example_callon_ndk_1led_MainActivity_cmdLeds ??(JNIEnv?*,?jobject,?jint,?jint);
這也就是我們編寫源文件需要實現(xiàn)的方法名,下面直接編寫源文件:
#include?"com_led_ndk_example_callon_ndk_led_MainActivity.h" #include#include#include#include#include#include#include#define?LED_ON?_IOW('G',0,int) #define?LED_OFF?_IOW('G',1,int) JNIEXPORT?void?JNICALL?Java_com_led_ndk_example_callon_ndk_1led_MainActivity_cmdLeds ??(JNIEnv?*?env,?jobject?thiz,?jint?cmd,?jint?arg) { int?fd; int?temp_cmd; fd?=?open("/dev/callon_led",O_WRONLY); if(cmd?==?1) temp_cmd?=?LED_ON; else temp_cmd?=?LED_OFF; ioctl(fd,?temp_cmd,?arg); close(fd); ?? }
就是一些對內(nèi)核驅(qū)動文件的操作,然后編寫Android.mk即makefile:
LOCAL_PATH?:=?$(call?my-dir) include?$(CLEAR_VARS) LOCAL_MODULE?:=?callon_ndk_led LOCAL_SRC_FILES?:=?ndk_led.c include?$(BUILD_SHARED_LIBRARY)
此時,在你的jni文件夾應(yīng)該有這三個文件了,退到j(luò)ni文件夾之外,使用命令ndk-build即可:
callon@ubuntu:~/Downloads/callon_ndk_led/jni$?ls Android.mk??com_led_ndk_example_callon_ndk_led_MainActivity.h??ndk_led.c callon@ubuntu:~/Downloads/callon_ndk_led/jni$?cd?.. callon@ubuntu:~/Downloads/callon_ndk_led$?ndk-build? [armeabi]?Compile?thumb??:?callon_ndk_led??libs/armeabi/libcallon_ndk_led.so callon@ubuntu:~/Downloads/callon_ndk_led$
最后一步,完善app:
首先,用Project形式來看我們的app,并在app->src->main下創(chuàng)建一個目錄"jniLibs";
然后,將我們.so所在的armeabi目錄拷貝到j(luò)niLibs目錄下;
最后在我們的app代碼的最后加上:
static ????{ ????????System.loadLibrary("callon_ndk_led"); ????}
然后Rebuild Project即可!
3 實現(xiàn)第二種方法:
1.HAL程序編寫:
首先在 ?安卓源代碼根目錄/hardware/libhardware/modules/下創(chuàng)建自己的hal代碼存放路徑,如led。
最終編寫的文件為:安卓源代碼根目錄/hardware/libhardware/modules/led/led_hal.c和Android.mk,安卓源代碼根目錄/hardware/libhardware/include/hardware/led.h。
#include#include#include#include#include#include#include#include#include#include#define?LOG_TAG?"callon_led" static?int?fd; static?int?led_close(struct?hw_device_t?*device) { struct?led_device_t*?led?=?(struct?led_device_t*)device; free(led); close(fd); return?0; } int?led_on(struct?led_device_t*?dev,int?arg) { ioctl(fd,LED_ON,arg); return?0; } int?led_off(struct?led_device_t*?dev,int?arg) { ioctl(fd,LED_OFF,arg); return?0; } static?struct?led_device_t?led_dev?=?{ .led_device?=?{ .tag?=?HARDWARE_DEVICE_TAG, .close?=?led_close, }, .set_on?=?led_on, .set_off?=?led_off, }; static?int?open_led(const?struct?hw_module_t*?module,?char?const*?name, ????????struct?hw_device_t**?device) { *device?=?&led_dev; fd?=?open("/dev/callon_led",O_RDWR); if(fd?<?0) { ALOGD(LOG_TAG,?"open?device?fail!"); return?-1; } return?0; } static?struct?hw_module_methods_t?led_methods?=?{ ????.open?=??open_led, }; struct?hw_module_t?HAL_MODULE_INFO_SYM?=?{ ????.tag?=?HARDWARE_MODULE_TAG, ????.id?=?"led", ????.methods?=?&led_methods, };
led.h
#ifndef?_HARDWARE_LED_H #define?_HARDWARE_LED_H #include#define?LED_ON?_IOW('G',0,int) #define?LED_OFF?_IOW('G',1,int) struct?led_device_t?{ struct?hw_device_t?led_device; int?(*set_on)(struct?led_device_t*?dev,int?arg);//means?led_number int?(*set_off)(struct?led_device_t*?dev,int?arg); }; #endif??//?_HARDWARE_LED_H
Android.mk
LOCAL_PATH?:=?$(call?my-dir) include?$(CLEAR_VARS) LOCAL_MODULE?:=?led.default #?HAL?module?implementation?stored?in #?hw/.default.so LOCAL_MODULE_RELATIVE_PATH?:=?hw LOCAL_C_INCLUDES?:=?hardware/libhardware LOCAL_SRC_FILES?:=?led_hal.c LOCAL_SHARED_LIBRARIES?:=?liblog LOCAL_MODULE_TAGS?:=?eng include?$(BUILD_SHARED_LIBRARY)
編譯則在 ?安卓源代碼根目錄下使用
$?.?setenv $?lunch $?mmm?hardware/libhardware/modules/led/
看到
target?SharedLib:?led.default?(out/target/product/tiny4412/obj/SHARED_LIBRARIES/led.default_intermediates/LINKED/led.default.so) target?Symbolic:?led.default?(out/target/product/tiny4412/symbols/system/lib/hw/led.default.so) Export?includes?file:?hardware/libhardware/modules/led/Android.mk?--?out/target/product/tiny4412/obj/SHARED_LIBRARIES/led.default_intermediates/export_includes target?Strip:?led.default?(out/target/product/tiny4412/obj/lib/led.default.so) Install:?out/target/product/tiny4412/system/lib/hw/led.default.so make:?Leaving?directory?`/opt/Tiny4412/Android/android-5.0.2' ####?make?completed?successfully?(1?seconds)?####
即可,最后產(chǎn)生的就在out/target/product/tiny4412/system/lib/hw/led.default.so了。
我們將system.img重做,這里通過一個腳本./gen-img.sh完成。
2.硬件服務(wù)編寫:
首先,Service Manager為了解決訪問沖突而存在的,在有Service Manager的基礎(chǔ)之上,我們的底層需要先編寫硬件服務(wù),注冊到Service Manager,我們的app才能通過Service Manager獲取到服務(wù),操作底層。
由于涉及到許多目錄操作,細化操作后為:
1.創(chuàng)建 ILedService.aidl
仿照 frameworks/base/core/java/android/os/IVibratorService.aidl?
package?android.os; /**?{@hide}?*/ interface?ILedService { ????int?LedOpen(); ????int?LedOn(int?arg); ????int?LedOff(int?arg); }
2.修改frameworks/base/Android.mk 增加
core/java/android/os/ILedService.aidl?
3.自動生成ILedService.java
mmm frameworks/base/編譯自動生成out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/ILedService.java
4.創(chuàng)建 LedService.java 實現(xiàn)接口函數(shù)
仿照frameworks/base/services/core/java/com/android/server/VibratorService.java?
package?com.android.server; import?android.util.Slog; import?android.os.ILedService; public?class?LedService?extends?ILedService.Stub{ ????private?static?final?String?TAG?=?"LedService"; ???? public?LedService()? { Slog.d(TAG,"LedService"); } ???? public?int?LedOpen()?throws?android.os.RemoteException { return?native_LedOpen(); } ???? public?int?LedOn(int?arg)?throws?android.os.RemoteException { return?native_LedOn(arg); } public?int?LedOff(int?arg)?throws?android.os.RemoteException { return?native_LedOff(arg); }?????? ???????? public?static?native?int?native_LedOpen(); public?static?native?int?native_LedOn(int?arg); public?static?native?int?native_LedOff(int?arg); }
5.將服務(wù)注冊到Service Manager當(dāng)中
修改frameworks/base/services/java/com/android/server/SystemServer.java
參考vibrator修改的地方
LedService?led?=?null;
Slog.i(TAG,?"Led?Service"); ????????????led?=?new?LedService(); ????????????ServiceManager.addService("led",?led);
6.實現(xiàn)com_android_server_LedService.cpp
為什么需要它?因為我們的hal代碼并沒有提供jni的接口(這里提供了一種新的方法來提供native方法,之前是自動產(chǎn)生頭文件然后來實現(xiàn)的)。
根據(jù)frameworks/base/services/core/jni/com_android_server_VibratorService.cpp
/* ?*?Copyright?(C)?2009?The?Android?Open?Source?Project ?* ?*?Licensed?under?the?Apache?License,?Version?2.0?(the?"License"); ?*?you?may?not?use?this?file?except?in?compliance?with?the?License. ?*?You?may?obtain?a?copy?of?the?License?at ?* ?*??????http://www.apache.org/licenses/LICENSE-2.0 ?* ?*?Unless?required?by?applicable?law?or?agreed?to?in?writing,?software ?*?distributed?under?the?License?is?distributed?on?an?"AS?IS"?BASIS, ?*?WITHOUT?WARRANTIES?OR?CONDITIONS?OF?ANY?KIND,?either?express?or?implied. ?*?See?the?License?for?the?specific?language?governing?permissions?and ?*?limitations?under?the?License. ?*/ #define?LOG_TAG?"LedService" #include?"jni.h" #include?"JNIHelp.h" #include?"android_runtime/AndroidRuntime.h" #include#include#include#includestruct?led_device_t?*led_dev; namespace?android { static?jint?LedOpen(JNIEnv?*env,?jobject?clazz) { hw_module_t?*module; hw_device_t?*device; hw_get_module("led",(hw_module_t?const?**)&module); module->methods->open(module,?NULL,?&device); led_dev?=?(struct?led_device_t*)device; return?0; } static?jint?LedOn(JNIEnv?*env,?jobject?clazz,?int?arg) { led_dev->set_on(led_dev,arg); return?0; } static?jint?LedOff(JNIEnv?*env,?jobject?clazz,?int?arg) { ???? led_dev->set_off(led_dev,arg); return?0; } static?JNINativeMethod?method_table[]?=?{ ????{?"native_LedOpen",?"()I",?(void*)LedOpen?}, ????{?"native_LedOn",?"(I)I",?(void*)LedOn?}, ????{?"native_LedOff",?"(I)I",?(void*)LedOff} }; int?register_android_server_LedService(JNIEnv?*env) { ????return?jniRegisterNativeMethods(env,?"com/android/server/LedService", ????????????method_table,?NELEM(method_table)); } };
7.注冊native接口
在frameworks/base/services/core/jni/onload.cpp中添加
int?register_android_server_LedService(JNIEnv*?env);
register_android_server_LedService(env);
8.修改Android.mk
在frameworks/base/services/core/jni/Android.mk中加入自己寫的com_android_server_LedService.cpp
$(LOCAL_REL_DIR)/com_android_server_LedService.cpp?
9.mmm frameworks/base/services/編譯
Copying:?out/target/common/obj/JAVA_LIBRARIES/services_intermediates/classes.dex target?Jar:?services?(out/target/common/obj/JAVA_LIBRARIES/services_intermediates/javalib.jar) Install:?out/target/product/tiny4412/system/framework/services.jar make:?Leaving?directory?`/opt/Tiny4412/Android/android-5.0.2' ####?make?completed?successfully?(50?seconds)?####
其中碰到一陣錯誤,非常無語:
make:?Entering?directory?`/opt/Tiny4412/Android/android-5.0.2' make:?***?No?rule?to?make?target?`frameworks/base/services/core/.java',?needed?by?`out/target/common/obj/JAVA_LIBRARIES/services.core_intermediates/classes-full-debug.jar'.??Stop. make:?Leaving?directory?`/opt/Tiny4412/Android/android-5.0.2' ####?make?failed?to?build?some?targets?(1?seconds)?####
直接不往下編譯,直接報錯,非常難找錯誤,最后發(fā)現(xiàn)由于LedService.java的文件名多了個空格。出現(xiàn)這種錯誤一定要耐心,一定是某個小地方出錯的。
最后繼續(xù)使用./gen-img.sh完成system.img的編譯,燒寫進開發(fā)板,為寫應(yīng)用程序做準(zhǔn)備。
3.應(yīng)用程序設(shè)計:
package?com.led.hal.example.callon.callonhalled; import?android.os.RemoteException; import?android.support.v7.app.AppCompatActivity; import?android.os.Bundle; import?android.view.View; import?android.widget.CheckBox; import?android.os.ILedService; import?android.os.ServiceManager; public?class?MainActivity?extends?AppCompatActivity?{ ????private?CheckBox[]?Led?=?new?CheckBox[4]; ????private?ILedService?iLedService?=?null; ????@Override ????protected?void?onCreate(Bundle?savedInstanceState)?{ ????????super.onCreate(savedInstanceState); ????????setContentView(R.layout.activity_main); ????????Led[0]?=?(CheckBox)findViewById(R.id.checkbox_led1); ????????Led[1]?=?(CheckBox)findViewById(R.id.checkbox_led2); ????????Led[2]?=?(CheckBox)findViewById(R.id.checkbox_led3); ????????Led[3]?=?(CheckBox)findViewById(R.id.checkbox_led4); ????????iLedService?=?ILedService.Stub.asInterface(ServiceManager.getService("led")); ????????try?{ ????????????iLedService.LedOpen(); ????????}catch(RemoteException?e){ ????????????e.printStackTrace(); ????????} ????} ????private?void?SendCmd(View?view) ????{ ????????for(int?i?=1;?i<5;?i++) ????????{ ????????????if(Led[i].isChecked())?{ ????????????????try?{ ????????????????????iLedService.LedOn(i); ????????????????}catch?(RemoteException?e){ ????????????????????e.printStackTrace(); ????????????????} ????????????} ????????????else?{ ????????????????try?{ ????????????????????iLedService.LedOff(i); ????????????????}catch?(RemoteException?e){ ????????????????????e.printStackTrace(); ????????????????} ????????????} ????????} ????} }
程序其實很簡單,但是涉及到android 中隱藏類的使用,我們的ILedService和ServiceManager其實都是隱藏類,你編譯不過去的,添加相應(yīng)的jar包才可以,參考
http://blog.csdn.net/wukunting/article/details/5788196
首先拷貝out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar到工程下
然后AndroidStudio中File->Project Structure點擊其中的'+'->選擇Import .JAR/.AAR Package->選擇classes.jar->Finish
繼續(xù)在Project Structure下選擇app->Dependencies->選擇'+'->Module Dependency->選擇'classes'即可。
這時候整個源碼都好了,但是下載速度會很慢,改善基于frameworks設(shè)計的app下載和運行速度的方案:
1.修改build.gradle(Module:app)
apply?plugin:?'com.android.application' android?{ ????compileSdkVersion?23 ????buildToolsVersion?"23.0.2" ????defaultConfig?{ ????????applicationId?"com.led.hal.example.callon.callonhalled" ????????minSdkVersion?21 ????????targetSdkVersion?23 ????????versionCode?1 ????????versionName?"1.0" ????????multiDexEnabled?true ????} ????dexOptions?{ ????????javaMaxHeapSize?"4g" ????} ????buildTypes?{ ????????release?{ ????????????minifyEnabled?false ????????????proguardFiles?getDefaultProguardFile('proguard-android.txt'),?'proguard-rules.pro' ????????} ????} } dependencies?{ ????compile?fileTree(dir:?'libs',?include:?['*.jar']) ????testCompile?'junit:junit:4.12' ????compile?'com.android.support:appcompat-v7:23.2.0' ????compile?project(':classes') ????compile?'com.android.support:multidex:1.0.0' }
2.修改AndroidManifest.xml
在application下增加
android:name="android.support.multidex.MultiDexApplication"
最后下載運行吧!