基于Android操作系統的框架層和利用層,介紹了View的繪制、觸摸事件的傳遞流程,分析了View與用戶交互時被回調的相干框架層代碼和利用層代碼,研究了Android利用中事件處理的相干重要機制。通過具體代碼詳細剖析了在Android系統下用戶和View交互進程中折射出的回調機制,回調方法在系統框架的詳細履行進程,和基于回調機制的經典事件處理模型。
1 引言
Android是1種基于Linux的自由及開放源代碼的操作系統,目前基于Android平臺的利用日益廣泛。Android利用程序大多基于圖形用戶界面,因此,需要大量處理界面元素的顯示和對相應控件的點擊事件。在Android中,界面元素的組成控件都是繼承自View類,View的點擊事件常常連接著軟件功能的各個模塊,也最能體現用戶在使用Android系統時的人機交互。但是,安卓利用開發者常常只站在操作系統的利用層進行開發,這類開發是淺層次的,沒有從系統或引擎的框架源碼進行分析,因此研究用戶與View交互進程中的回調和具體進程有益于加深對Android系統框架層的認識,通過這個視角打破原本的思惟方式,更快地解決Bug或編寫出質量更高的軟件。在Android系統中存在很多工作機制,本文研究的View的點擊事件實際上體現了Android中觸摸事件的傳遞機制和回調機制。而對核心的回調,首先討論回調的概念、從理論上分析回調在Android中的基本模型,然后從Android系統框架層的源碼上分析觸摸事件具體如何被分發,具體回調方法是如何被調用的,最后回歸到利用層,構建物理世界和虛擬世界的聯系,由下而上總結和歸納View與用戶在交互進程中的經典事件處理模型。
2 回調函數和Android系統中的回調機制
軟件中相互聯系的模塊之間常常會產生調用,從調用方式上可分為3類:同步調用、異步調用和回調。同步調用和異步調用都是1種單向調用模式,而回調是1種雙向調用模式,被調用方在方法被調用時也會調用對方的方法。回調函數就是1個通過函數指針調用的函數。回調函數其實不是由該函數的實現方法直接去調用,而是把它的函數指針作為參數傳遞給另外一個函數,在特定的事件或條件產生時由另外1方通過這個指針來調用,用于對該事件或條件進行響應。
2.1 Java中的回調函數
Java中不允許直接操作指針,而是通過接口或內部類來實現的。Java方法回調是分離功能定義和功能實現的1種手段,這不但是1種松耦合的設計思想,也是1種接口和對象的組合。開發者可以根據自己不同的需求實現接口,而方法的具體調用則由操作系統來完成。在Java中實現回調也有固定的模式,步驟以下:
a.定義接口Callback ,包括回調方法 callback();
b. 在1個類Caller 中聲明1個Callback接口對象 mCallback;
c. 在程序中賦予 Caller類的接口成員(mCallback)1個具體實現了Callback接口的內部類對象。 interface Callback(){
void callback() ;
}
ClassCaller{
CallbackmCallback;
PublicCaller(Callback cb){ // 傳入實例化以后的內部類對象cb
mCallback=cb;
}
if(根據實際情況回調){
mCallback.callback();
}
}
系統通過Caller類的mCallback成員回調callback()方法完成業務需要的邏輯。Callback并沒有通過繼承實際類用實際類對象來完成回調,而是通過實現接口用接口成員來完成,這是由于在Java語言中接口本身的設計和抽象類相似,可以對不同的業務需求構成多樣的接口實現,有益于程序構成較好的多態性,同時對開發者來講實現接口比繼承實際類完成回調更加便捷并且更符合邏輯性,因此在Java程序中的回調常常是依照上述模式,通過傳遞已實現了抽象接口的對象援用來完成方法回調。
2.2: Android系統利用層中的回調機制
在Android系統利用層中,回調機制是非常普遍和重要的。在操作系統層面,回調機制在通訊、消息傳遞、事件處理等方面也有著較多的利用,以下以Button按鈕的onClick方法為例介紹View被點擊時的框架模型和相干方法的回調。
a.定義接口,接口內部就是系統要回調的方法,參數是行將產生回調的Button,這個接口常被叫做事件監聽器,它相當因而View的1個屬性。
public interface OnClickListener{
public void onClick(Buttonb);
}
b.定義Button類,這里代表View類
public class Button{
//申明Button類的接口(監聽器)成員
OnClickListener listener;
public void click(){
listener.onClick(this);
}
//set函數用于接收外部傳入的被實例化了的監聽器實體對象,實際上是1種面向對象的語言多態性的體現
public void setOnClickListener(OnClickListenerlistener) {
this.listener = listener;
}}
c.定義測試類,將上述監聽器實體對象以匿名內部類的情勢傳入Button類
public class Activity{
public Activity(){
}
public static void main(String[]args) {
Button b = new Button();//實例化Button類
b.setOnClickListener(new OnClickListener(){
@Override
public void onClick(Buttonb) {//onClick中的內容是對接口的不同實現
System.out.println("clicked");
}
});
b.click(); //在主函數中進行回調的摹擬測試
}
}
Android事件監聽器是包括在View類的1個接口,包括1個單獨的回調方法onClick(button b),如上,接口被具體實現并表現為1種內部類的情勢,onClick方法中的具體內容將在按鈕被點擊時由Android系統框架進行回調。因此,若需對點擊事件做處理,應當首先定義1個OnClickListener 接口(事件監聽器),然后通過set()或構造方法將已具體實現了該接口的實際對象或匿名對象賦予Button類的接口成員,最后操作系統通過該接口成員在適合的情形下回調onClick()的具體實現內容。在main()函數中,setOnClicklistener()就是這樣1個set()函數用來接收已實例化監聽器接口的匿名類對象并將之賦予之前申明的接口成員;而另外1個click()方法,實際上是1個摹擬用戶點擊時狀態環境的1個方法,表示用戶點擊這個按鈕時的環境。此方法在主程序中被主動調用,但該方法實際上應當是屏幕捕捉到1個用戶點擊的動作以后轉換成某1坐標A,如果這個坐標A處于該View所在的范圍,那末才調用click方法,也就是說系統先是獲得點擊信號,如果在View的范圍就會觸發click事件,進而回調Button接口成員(事件監聽器)的onClick方法,而這也完全符合實際中Android利用程序與用戶交互時View的回調。
從回調本身來看,onClick()被調用后終究會在外部履行實際的接口(監聽器)實現代碼,而onClick()回調卻在Android系統內部實現,這很好地體現了Android系統本身設計的層次性,一樣體現了接口和對象的組合在實際開發中的作用。在實際的Android操作系統中,接口是系統框架提供的,接口的實現是上層的開發者來實現的,通過回調這類方式就能夠到達接口統1而實現不同。系統在不同的狀態“回調”實現類中的具體方法,以此到達接口和實現的分離,對象和接口的組合。
在Android系統中,回調不單單體現在View與用戶交互的時候,在其他場景(消息傳遞、事件處理等)中也有廣泛的利用,比如Activity生命周期中的onCreate()、onStop()等方法,其中的具體實現也需要開發者根據實際情況來書寫,這些方法被封裝在1個類似的接口中,一樣在系統運行時系統會根據某1狀態值回調相應的生命周期中的方法。Android中很多回調在設計上都是相似的,都是通過在主類里面封裝1個接口然后在上層實現這個接口,接著系統底層在適合的時候回調接口里面已實現的方法,這樣上層和底層就被很好的分開。
3 Android系統框架層中回調函數的具體調用流程
圖1:View樹圖
Android系統中回調機制的概念和在利用層中摹擬的回調進程在前面被討論,下面將從源代碼的角度深入探訪在Android系統框架層中,View點擊時觸發的方法是如何在框架源碼里面具體被回調的。源碼來源于:Copyright(C) 2006 the Android Open Source Project,系統架構內核編號是Android 2.3
3.1 觸摸事件的傳遞機制
在Android系統中,每一個View或View的子類都具有下面3個和觸摸事件處理密切相干的方法:dispatchTouchEvent方法用來分發TouchEvent;onTouchEvent方法用來處理TouchEvent;如果這個View同時也是ViewGroup類型的話,那末還具有1個onInterceptTouchEvent方法,它主要用來攔截TouchEvent。
如圖1所示(圖1 View樹圖)1個Android利用程序的界面可以看成是1顆View樹,所有界面的視覺顯現實際上源于View樹的自上而下的繪制。ViewGroup類也是繼承自View,View和ViewGroup之間相互組合和嵌套構成View樹。當TouchEvent產生時,首先系統將用戶的觸摸信號傳遞給最頂層的View,也就是最外層的ViewGroup(實際中可以是Relativelayout等容器),接著TouchEvent從上層逐步傳入到各分支:
圖2:觸摸流程圖
a 對比(圖2 觸摸流程圖),當用戶的手指觸摸得手機屏幕,會觸發最頂層View的dispatchTouchEvent()。最頂層的View首先檢查該函數的返回結果:
(1)如果返回true ,則交給這個View自己的onTouchEvent方法處理,不會向下傳遞。
(2)如果返回false,則交給這個View的 interceptTouchEvent 方法來決定是不是要攔截這個事件。
b 接著到 onInterceptTouchEvent履行:
(1)如果返回 true ,即攔截掉了,則還是交給此View的onTouchEvent處理。
(2)如果返回 false,則傳遞給它的子View ,由子 View的dispatchTouchEvent 再來開始這個事件的分發。
c 以此類推,通常TouchEvent會傳遞到某1層子View 的 onTouchEvent 內,而接下來研究的回調的具體履行進程就是基于這1觸摸事件的傳遞機制進行展開并在onTouchEvent源碼內進行研究。
3.2 Android系統框架層下View內部回調函數的詳細回調進程
還是以Button作為View的1個例子,源碼位于系統結構路徑的位置是 /frameworks/base/core/java/android/view/View.java。
首先是系統框架層下對OnClickListener接口的定義:
public interface OnClickListener {
void onClick(Viewv)
}
}
然后是對接口成員實例化的set函數,需要傳入1個實現了OnClickListener接口的實際類對象if()語句表示Button的屬性1定是clickable(可點擊)的,這也解釋了為何在Button的xml里面不需要申明這個屬性,而TextView需要這個屬性,由于系統會自動對Button設置可點擊這個屬性值。
public void setOnClickListener(OnClickListenerl) {
if (!isClickable()) {
setClickable(true);
getListenerInfo().mOnClickListener = l;
}
getListenerInfo()方法 是返回所有自定義監聽器的1個靜態內部類ListenerInfo的對象,通過它尋覓想要注冊到Button上的某種監聽器(這里使用的是OnClickListener),該內部類在View.java的前半部份被申明:
static class ListenerInfo {
…………
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListner;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
}
………
ListenerInfo getListenerInfo() {
ListenerInfo mListenerInfo;
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
根據3.1中介紹的觸摸事件的傳遞機制,TouchEvent終究會被傳遞到View樹最底層的View,交給該View的onTouchEvent進行處理,在這個方法里面大體要處理3種情況:當手指按下,手指移動和手指松開,系統會在這3種不同情況中進行相應的處理,而手指松開是最重要的情況,由于每個TouchEvent終究都會經歷這1步,onTouchEvent方法的具體代碼以下所示:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
//這里首先判斷這個View是否是可用的,避免之前被設置成禁用
if ((viewFlags & ENABLED_MASK) == DISABLED) {
…………
//下面1句話是說不管是不是可點擊,但總要把這次事件先消耗掉
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//下面是當View可以被點擊時的狀態
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
//第1個case分句處理按下的時候
case MotionEvent.ACTION_DOWN:
/*首先遍歷整棵View樹,isInScrollingContainer方法內部用1個循環判斷當前View外層的父類是否是1個可滑動的容器*/
booleanisInScrollingContainer = isInScrollingContainer();
/*如果是1個可滑動的容器,就設置1下相干標志位,mPendingCheckForTap表示1個消息線程,它在postDelayed()方法中會被延時發送出去,由于要肯定用戶在這個容器下是點擊還是滑動,如果1定時間內用戶沒有滑動會自動履行mPendingCheckForTap消息線程,該消息內部接著會判斷是否是長按操作,如果在這段時間內用戶進行了滑動那末就立即刪除掉這個消息。*/
if (isInScrollingContainer) {
mPrivateFlags |= PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap);
} else {
/*如果父類的容器不可滑動說明1定是點擊操作。接著用上1個子句一樣的思想“等等再履行”檢查是否是長按的操作,如果1段時間用戶沒有松開就會觸發長按消息的線程checkForLongClick。反之,就將這個消息移除。在這里,可以體會到判斷長按短按,滑動點擊獨有的算法:“等等再履行”。*/
mPrivateFlags |= PRESSED;
refreshDrawableState();
checkForLongClick(0);
}
break;
//處理移動的情況
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
//在這里記錄下用戶移動的位置坐標,其中mTouchSlop表示判斷觸摸點是不是在此View中時,向上下左右增大mTouchSlop個像素(允許的誤差范圍)
if (!pointInView(x, y, mTouchSlop)) {
//如果不在View的范圍里面,那末將處理點擊的消息移除
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
//進1步判斷如果都已準備長按了,則將長按的消息移除,并將View的按下狀態設置為false
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
//處理松開的情況
case MotionEvent.ACTION_UP:
………
if (!mHasPerformedLongPress) {
//履行到該標志量表明了這不是長按,是1個點擊,所以移除長按的相干消息。
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//直到最后我們看到了performClick()被履行。
performClick();
}
}
}
…………
}
break;
}
return true;
}
return false;
}
// 在performClick()中回調方法被履行,回調的所有流程到此結束。
public booleanperformClick() {
………
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
//回調方法在這里被履行
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
4 Android系統利用層中折射的事件處理模型
從利用層的角度上來講,View與用戶交互進程中體現的不單單是之前在框架層里面詳細分析的回調機制,同時也是沿襲了java可視化編程開發的1種經典的事件處理方式。這類機制本身包括觸發事件、事件源、事件監聽器、響應事件4個方面。觸發事件指的是用戶操作手機的交互方式,如長按、點擊、觸摸、回滾等操作;事件源是指產生事件的組件,也就是各種各樣的View控件(按鈕、圖片等等);事件監聽器是組件產生事件時響應的接口,如OnClickListener;最后響應事件指的是OnClickListener里面的回調方法onClick()。
圖3 現實場景圖
這類方式非常類似現實生活中的1種場景(如圖3 現實場景圖):現在的高級轎車都有防盜功能,當車產生劇烈震動時就會報警,報警時報警燈會不斷閃爍發出提示音。在這里,事件源是車,事件監聽器是車上的報警器,觸發事件就是感應到的震動,而報警燈的閃爍及發出提示音指的是響應事件。聯系到實際的Android開發中,Button按鈕就是1個接收事件的事件源;事件監聽器就是OnClickListener接口,需要用戶實例化傳入;觸發事件就是用戶的點擊操作;點擊以后產生的回調onClick就是響應事件。這模樣的類比應當更能讓讀者理解這類機制。
接下來編寫1段實際的java代碼摹擬汽車防盜的經典事件處理模型:
public classCar {//事件源
intColor;
intstyle;
floatprice;
String name;
PreventTheftListenerlistener;
staticboolean isDanger=true;
//監聽器的定義
interfacePreventTheftListener{
publicvoid onTheft(Car car);//觸發事件
}
publicCar(String name) {
this.name=name;
}
privateboolean isDanger() {
returnisDanger;
}
publicvoid setPrevent_Theft_Listener(PreventTheftListener listener) {
this.listener=listener;
}
publicvoid doListener(){
if(listener!=null)
listener.onTheft(this);
}
//監聽器的實例化
staticclass Wuhan_CarListener implements PreventTheftListener{
privateint color;
privatefloat price;
Stringname;
publicWuhan_CarListener(String name) {
isDanger=true;
this.name=name;
System.out.println("我已安裝到你的汽車上,我已對周圍的環境處于監聽狀態 ");
}
publicvoid onTheft(Car car) {//回調事件
System.out.println("主人,有震動,小偷要偷你的汽車");
noise();
flash();
}
privatevoid noise() {
System.out.println("摹擬報警器的功能之1-----發出響聲");
}
privatevoid flash() {
System.out.println("摹擬報警器的功能之1-----不斷閃光");
}
}
publicstatic void main(String[] args) {
Car c=newCar(“甲殼蟲”);
c.setPrevent_Theft_Listener(newWuhan_CarListener("##牌防盜監聽器"));//裝上防盜監聽器
if(c.isDanger()==true){
c.doListener(); }
}
}
圖4 事件處理模型圖
(如圖4所示 事件處理模型圖)Android中View點擊事件的回調都存在事件源對象(Button),監聽器對象(OnClickListener),事件的觸發者和事件的響應這4樣東西,4者實際上構成了1種經典的事件處理模型,它能夠靈敏的捕捉到外界的觸發動作并在適合的場合履行回調方法,這模樣就實現了接口定義和接口實現的分離,突出了接口和對象的組合,也體現了軟件設計中的松耦合性和多態性。只不過這個經典的模型在實際的Android系統中,需要斟酌更多實際系統的元素,因此顯得復雜,但在利用層可以看出其本質是相似的。
汽車里面給用戶留下1個接口,試想,如果把防盜的功能直接作為1個實際類成員綁定在汽車上面,那末在有震動的時候固然也能利用該成員調用防盜的動作,但這樣就不夠靈活,不能夠讓每一個設計防盜器的廠商設計自己的防盜器性能,可擴大性非常的差。所以,在車里面定義1個監聽器的接口,而接口的實現交給每個專門設計防盜器的廠商來做,在適合的時候利用監聽器對象來調用不同廠商的實現方式,這也體現出了多態。這模樣,在汽車行業,做防盜器的公司就和做汽車的公司分開了,其經濟效益和生產效力也會加倍。在Android系統中,接口是系統框架提供的,接口的實現是各種公司不同的上層利用開發者實現的。如(圖5 接口設計圖)這樣就能夠到達接口統1,而實現不同,系統只需要在不同狀態回調我們的實現類來到達接口和實現的分離。
圖5 接口設計圖
5 結語
方法回調是功能定義和功能實現相分離的1種手段,是1種松耦合的設計思想。Android作為1種優秀的移動智能終端系統架構,有自己的運行環境同時也在不同的場合為開發者設置了不同的開發接口,而操作系統通過在不同的情形“回調”開發者對接口的不同實現來到達接口定義和接口實現的統1。在Android利用程序中用戶與View的交互是非常普遍和重要的,本文詳細研究了Android系統底層的框架代碼到上層的利用代碼,構建出了虛擬世界和物理世界的聯系,介紹了 Android系統下的回調機制、用戶和View交互時觸發的回調方法在系統框架的詳細履行進程和基于這1機制下的經典事件處理模型。借助這篇文章1方面希望讀者借此理解Android中的回調機制和經典的事件處理模型,另外一方面希望讀者多從系統源碼的角度研究和解決問題。但是,關于這1課題的研究畢竟目前還只是剖析Android框架的1小部份,在這1課題或其他框架理論的發現和創新還有待于進1步的探索和研究。