編輯:關於Android編程
首先說一下我的開發環境,硬件環境開發板使用的是全志的CQA83T板子,Android開發是windows下的eclipse。關於Android下控制led,主要有兩大部分,一是Android程序,二是Linux驅動開發。Android部分的開發肯定要使用Android ndk,jni編程,通過jni來調用Linux下的C函數從而控制led設備。關於ndk的安裝,和簡單使用我在另外的博客裡面已經寫了,有興趣的可以自己看看。這篇博客住要是講一下Android部分的開發,這裡默認led驅動正常。
先看一下我的工程目錄,如下圖:
紅色是我們工程的所有文件,後面打對勾是我們需要編輯的文件。
第一步:java部分文件編輯,這部分主要三個文件,布局文件activity_main.xml、邏輯控制文件MainActivity.java、led控制封裝類A83TLED.java三個文件。
(1)布局文件activity_main.xml
布局文件沒什麼可講的,就是一列按鈕,文件內容如下:
實際顯示的布局圖如下:
雖然說沒什麼講的,還是說一下每個按鈕的作用吧。
device_open:設備打開按鈕,在Linux下一切皆文件,想要操作led,首先要打開led設備。
led1_on:打開1號led
led1_off:關閉1號led
led2_on:打開2號led
led2_off:關閉2號led
beep_on:打開蜂鳴器,這裡說一下,蜂鳴器的控制和led一樣,開關信號,單引腳,即是只需要控制引腳的輸出電平是0或1即可。所以驅動和led寫在一起。
beep_off:關閉蜂鳴器
device_close:關閉設備。
(2)看一下A83TLED.java這個文件。這個文件主要是對本地方進行聲明,封裝了幾個控制函數,以及加載本地庫。
文件內容如下:
package com.coban.a83tled; import java.io.IOException; import android.util.Log; public class A83TLed { private static final String TAG = "A83TLed"; private int ret; public A83TLed(String path) throws SecurityException, IOException{ ret = open_led_device(path); if (ret < -1) { Log.e(TAG, "native open returns null"); throw new IOException(); } } //打開設備 public void open_led(){ ret = open_led_device("/dev/led"); if (ret < -1) { Log.e(TAG, "native open returns null"); } } //打開led public void on_led(int num){ ioctl_led(num,1); } //關閉led public void off_led(int num){ ioctl_led(num,0); } //關閉設備 public void close_led(){ close_led_device(); } // JNI聲明本地函數 private native static int open_led_device(String path); private native void ioctl_led(int i, int j); private native void close_led_device();
//加載本地庫 static{ System.loadLibrary("a83tled"); } }
文件很簡單,首先是類的構造,類的方法,JNI本地函數聲明,加載本地庫。在類聲明時就打開了設備,當然也提供了打開設備的方法open_led(),既然提供了打開設備
那對應的也提供了關閉設備函數close_led(),另外提供了led的打開on_led()和關閉off_led()方法。這些方法都是調用了jni函數,而jni又調用Linux函數。這個文件就是這樣了。
(3)下面來看一下控制邏輯文件MainActivity.java文件。這個文件主要是提供控制邏輯,直接面向使用者。
文件內容如下:
package com.coban.a83tled; import java.io.IOException; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private Button led1On = null; private Button led1Off = null; private Button led2On = null; private Button led2Off = null; private Button beepOn = null; private Button beepOff = null; private Button deviceOpen = null; private Button deviceClose = null; private A83TLed a83tLed = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { a83tLed = new A83TLed("/dev/led"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } initLayout(); btnListener(); } //初始化界面 private void initLayout(){ led1On = (Button)this.findViewById(R.id.led1_on_btn); led1Off = (Button)this.findViewById(R.id.led1_off_btn); led2On = (Button)this.findViewById(R.id.led2_on_btn); led2Off = (Button)this.findViewById(R.id.led2_off_btn); beepOn = (Button)this.findViewById(R.id.beep_on_btn); beepOff = (Button)this.findViewById(R.id.beep_off_btn); deviceOpen = (Button)this.findViewById(R.id.device_open_btn); deviceClose = (Button)this.findViewById(R.id.device_close_btn); } //按鈕監聽 private void btnListener(){ led1On.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.on_led(1); } }); led1Off.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.off_led(1); } }); led2On.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.on_led(2); } }); led2Off.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.off_led(2); } }); beepOn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.on_led(3); } }); beepOff.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.off_led(3); } }); deviceOpen.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.open_led(); } }); deviceClose.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub a83tLed.close_led(); } }); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); a83tLed.close_led(); } }
這樣java文件就編寫完了。
第二步:生成jni頭文件
上面一步,把java文件的編輯完成了,然後我們要生成頭文件了。
(1)在工程目錄下新建jni文件夾。
(2)使用dos窗口進入工程目錄下面,使用javah命令生成自動生成頭文件。具體命令如下
javah -classpath src -d jni com.coban.a83tled.A83TLed
然後你會在jni文件夾下找到生成的頭文件com_coban_a83tled_A83TLed.h
有關操作我在另一篇博客裡面都有詳細說明,http://blog.csdn.net/dingfengen/article/details/51604877
那我們來看一下頭文件:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include/* Header for class com_coban_a83tled_A83TLed */ #ifndef _Included_com_coban_a83tled_A83TLed #define _Included_com_coban_a83tled_A83TLed #ifdef __cplusplus extern "C" { #endif /* * Class: com_coban_a83tled_A83TLed * Method: open_led_device * Signature: (Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_coban_a83tled_A83TLed_open_1led_1device (JNIEnv *, jclass, jstring); /* * Class: com_coban_a83tled_A83TLed * Method: ioctl_led * Signature: (II)V */ JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_ioctl_1led (JNIEnv *, jobject, jint, jint); /* * Class: com_coban_a83tled_A83TLed * Method: close_led_device * Signature: ()V */ JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_close_1led_1device (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
主要包含的就是本地函數聲明。
第三步:本地C或C++文件的編輯。這裡主要是倆文件,一個是a83tled.c,另一個是Android.mk。最後生成的so庫的全名是liba83tled.so,這個名字和c文件名保持一致。
以及前面java裡加載的庫名,也是這個文件名。這一點我曾經出過錯誤,在此也提醒一下大家。首先在jni文件夾下創建a83tled.c和Android.mk文件。
先看a83tled.c這個文件的內容:
#include#include #include #include #include #include #include #include "android/log.h" static const char *TAG = "a83tled"; static int fd = -1; #define IOCTL_SET_ON 1 #define IOCTL_SETOFF 0 #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) JNIEXPORT jint JNICALL Java_com_coban_a83tled_A83TLed_open_1led_1device (JNIEnv *env, jclass thiz, jstring path) { jboolean iscopy; const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy); LOGD("Opening %s",path_utf); fd = open("/dev/led",O_WRONLY,0777); LOGD("open fd = %d",fd); (*env)->ReleaseStringUTFChars(env, path, path_utf); if(fd < -1) { LOGE("Cannot open port"); /* TODO: throw an exception */ return NULL; } return fd; } JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_ioctl_1led (JNIEnv *env, jobject thiz, jint i, jint j) { if(fd > -1) { if(1 == j) { ioctl(fd,IOCTL_SET_ON,i); } else { ioctl(fd,IOCTL_SETOFF,i); } } } JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_close_1led_1device(JNIEnv *env, jobject thiz) { if(fd > -1) { close(fd); } }
說了。
下面再看一下Android.mk文件,其內容如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) TARGET_PLATFORM := android-3 LOCAL_MODULE := a83tled LOCAL_SRC_FILES := a83tled.c LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)
這個Androd.mk文件不長,下面我們來簡單解釋下:
LOCAL_PATH := $(call my-dir)
一個Android.mk 文件首先必須定義好LOCAL_PATH變量。它用於在開發樹中查找源文件。在這個例子中,宏函數’my-dir’, 由編譯系統提供,用於返回當前路徑(即
包含Android.mk file文件的目錄)。
include $( CLEAR_VARS)
CLEAR_VARS由編譯系統提供,指定讓GNU MAKEFILE清除許多LOCAL_XXX變量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES,
等等...),除LOCAL_PATH 。這是必要的,因為所有的編譯控制文件都在同一個GNU MAKE執行環境中,所有的變量都是全局的。
TARGET_PLATFORM := android-3
TARGET_PLATFORM是Android.mk 解析的時候,目標 Android 平台的名字,其中,android-3 -> Official Android 1.5 system images
android-4 -> Official Android 1.6 system images, android-5 -> Official Android 2.0 system images
LOCAL_MODULE := a83tled
編譯的目標對象,LOCAL_MODULE變量必須定義,以標識你在Android.mk文件中描述的每個模塊。名稱必須是唯一的,而且不包含任何空格。注意:編譯
系統會自動產生合適的前綴和後綴,換句話說,一個被命名為'a83tled'的共享庫模塊,將會生成'liba83tled.so'文件。
重要注意事項:如果你把庫命名為‘liba83tled’,編譯系統將不會添加任何的lib前綴,也會生成 'liba83tled.so',這是為了支持來源於Android平台的源代碼的Android.mk文件,如果你確實需要這麼做的話。
LOCAL_SRC_FILES := a83tled.c
LOCAL_SRC_FILES變量必須包含將要編譯打包進模塊中的C或C++源代碼文件。注意,你不用在這裡列出頭文件和包含文件,因為編譯系統將會自動為你找出依賴型的文件;
僅僅列出直接傳遞給編譯器的源代碼文件就好。
注意,默認的C++源碼文件的擴展名是’.cpp’. 指定一個不同的擴展名也是可能的,只要定義LOCAL_DEFAULT_CPP_EXTENSION變量,不要忘記開始的小圓點(也就是’.cxx’,而不是’cxx’)
LOCAL_LDLIBS := -llogLOCAL_LDLIBS: 編譯模塊時要使用的附加的鏈接器選項。這對於使用‘-l’前綴傳遞指定庫的名字是有用的。
include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY表示編譯生成共享庫,是編譯系統提供的變量,指向一個GNU Makefile腳本,負責收集自從上次調用'include $(CLEAR_VARS)'以來,定義在LOCAL_XXX變量中的所有信息,
並且決定編譯什麼,如何正確地去做。還有 BUILD_STATIC_LIBRARY變量表示生成靜態庫:lib$(LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可執行文件。
第四步,就是編譯了。首先:再工程目錄中,右鍵點擊工程名 --> Android tools --> Add Native Support 來添加ndk支持,會出現如下界面:
然後是編譯,點擊工具欄裡的錘子圖標,如下圖:
如果沒有錯誤,下載到開發板測試。如下圖,
我的這裡沒有錯誤。
簡單總結一下,這裡我比他們給的例程裡面多了一個打開設備函數,在java層。這個程序的執行過程是這樣的,界面按鈕(xml文件) --> 邏輯控制層(MainActivity.java) --> java實際調用層(A83TLed.java) --> jni層(a83tled.c) --> linux應用層 --> linux底層驅動。
這裡參考了如下連接:
http://blog.csdn.net/ly131420/article/details/9619269
http://www.cnblogs.com/devinzhang/archive/2012/02/29/2373729.html
特此感謝。
關於自定義View,相信多數開發者都已經非常熟悉了,網絡上的例子也非常多,各種炫酷吊炸天的自定義View也層出不窮。本文只是一個初級學習教程,對於初學者有參考價值。下面正
Android移植開發 Android移植開發的最終目的是為了開發Android嵌入式產品,從開發者的角度來看,這種類型的開發以具有硬件系統為前提,在硬件
管理Activity(Fragment、dialogFragment)的生命周期需要在build.gradle中加入compile 'com.trello:rxl
Binder概述一句話概括進程通信:進程間的數據傳遞。Binder是Anroid系統裡最重要的進程通信方式,很多文章會直接用代碼、原理類的文字進行描述,對於接觸Andro