標籤:
其實適配器模式在Android源碼中非常多,而從整體的源碼角度上來看Activity的結構就是一種適配器模式。從這個角度上面看Activity,對Activity和應用程式層架構會有更加深入的理解。
適配器模式意圖
將一個介面轉換為使用者需要的另外一個介面,適配器模式使得原本由於介面不相容不能一起工作的那些類可以一起工作。
UML圖
適配器模式有兩種模式,UML分別如下:
第一種是直接繼承已經有的介面適配目標介面,而第二種是引用已有的介面適配目標介面。
範例程式碼
interface Target{ void request();}class Adaptee{ public void specialRequest(){ System.out.println("special from adaptee"); }}class Adapter extends Adaptee implements Target{ public void request(){ //do something to implements request specialRequest(); }}public static final void main(String args[]){ Target target = new Adapter(); target.request();}
上面是第一種適配器模式的簡單程式碼範例,通過繼承已有的類來適配,另外一種組合的方式如下:
interface Target{ void request();}class Adaptee{ public void specialRequest(){ System.out.println("special from adaptee"); }}class Adapter implements Target{ private Adaptee adaptee ; public void Adapter(Adaptee adaptee){ this.adaptee = adaptee; } public void request(){ //do something to implements request adaptee.specialRequest(); }}public static final void main(String args[]){ Target target = new Adapter(new Adaptee()); target.request();}
兩種方式類適配器和對象適配器。
Activity與適配器模式
Activity是Android的核心組件,它是負責應用UI的組件,可以說是Android四大組件中最重要,使用最多,最複雜的組建。它的源碼也相當地龐大。從適配器的角度上來看,Activity適配了多個介面,先看一下它的類結構圖:
將Activity看成是適配器模式初看可能會有點牽強。但是ContextThemeWrapper是表示主題的環境類,Context可以翻譯為應用環境,但是對於需要顯示UI的一個應用組建除了應用環境外,還需要適應其他的內容資訊,比如Window,比如KeyEvent等等。
拿視窗系統舉例。Android中有Window管理系統,但是視窗系統需要與的Window.Callback介面,但是現在是有了Context,組建需要Window.Callback介面,這樣建立Activity(這個是Adapter)實現Window.Callback介面,並且繼承ContextThemeWrapper,將ContextWrapper與Window.Callback協作,讓Context與Window一起工作。Window.Callback只是Activity適配的其中一個介面,下面分別介紹類結構的每一個部分。
ContextThemeWrapper
這是一個包含主題的Context裝飾器,本身ContextWrapper是一個裝飾器模式,在Android中,四大組建都是ContextWrapper的子類,四大組建都需要應用環境。關於這部分可以看我這篇文章Android源碼裝飾模式—ContextWrapper。需要理解的是Context是一個應用環境類型,Context包含了各種跟應用環境相關的資訊,可以用來與應用系統打交道的。
Window.Callback, Window.OnWindowDismissedCallback
Window.Callback 這個介面包含了很多介面函數,上面的UML圖中只包含了部分介面,全部的介面類可見下面的Outline:
這個介面是視窗的回調介面,主要分為螢幕事件觸發,按鍵事件觸發,Panel相關的View建立與Prepare,Menu的回調,Window的變化回調,SearchRequest的回調,以及ActionMode的回調。
Window.OnWindowDismissedCallback是一個hide類,是無法通過API調用的,是當視窗消失(Window系統移除)的時候的回調介面。Activity的實現也很簡單,直接finish掉自己。
/** * Called when the main window associated with the activity has been dismissed. * @hide */@Overridepublic void onWindowDismissed() { finish();}
Callback,OnWindowDismissedCallback是Window與Activity互動的回調介面。
初始部分代碼為:
//Activity.java final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); ...
但實際上一個Window並不是只和一個Activity關聯,而是一個Window和一個Callback關聯,Activity也是Context,Android中Dialog裡面也包含了Window,Dialog也實現了Callback介面。一個應用環境中(Context)可能包含多個Window,也就會有多個Callback,只是Activity這種應用環境本身就實現了Callback介面。
KeyEvent.Callback
對應著Key事件的回調介面,當按下按鍵的時候,會回調該介面。主要是為了適配輸入系統。
ComponentCallbacks2
它是ComponentCallbacks的子介面,CompoentCallbacks包含下面兩個介面:
void onConfigurationChanged(Configuration newConfig);void onLowMemory();
ComponentCallbacks2新增了onTrimMemory介面。
ComponentCallbacks是專門為Android組件使用的回調介面,Android組件都會實現該介面(目前變成了ConponentCallbacks2),當配置資訊變化,記憶體變化的時候,這些介面會被調用。調用這些介面的是ActivityThread(訊息迴圈中,收到變化訊息時),ViewRootImpl(在Window有變化的時候,ViewRootImpl負責與WindowManagerService通訊)等。該介面是為了適配系統資訊管理部分。
這裡有兩個跟記憶體相關的介面,這其實是為幫應用應對Android記憶體滿負荷,提醒應用程式做一些釋放記憶體處理,如果佔用記憶體過大,應用將會更容易被殺死。具體可以看LowMemoryKiller的介紹。
OnCreateContextMenuListener
Android操作功能表: 當給一個View註冊了操作功能表後,對這個View長按2秒,會彈出一個浮動的菜單。OnCreateContextMenuListener 它只有一個介面函數:
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { }
當View的Context menu被建立的時候,該介面的會被調用,用於擷取Menu(作為實現改介面的Activity來講,是設定Menu)。在Activity中,與這個介面函數對應的函數是onContextItemSelected,而該函數是繼承自Window.Callback介面的onMenuItemSelected函數:
public boolean onMenuItemSelected(int featureId, MenuItem item) { CharSequence titleCondensed = item.getTitleCondensed(); switch (featureId) { case Window.FEATURE_OPTIONS_PANEL: if(titleCondensed != null) { EventLog.writeEvent(50000, 0, titleCondensed.toString()); } if (onOptionsItemSelected(item)) { return true; } ... case Window.FEATURE_CONTEXT_MENU: if(titleCondensed != null) { EventLog.writeEvent(50000, 1, titleCondensed.toString()); } if (onContextItemSelected(item)) { //這裡 return true; } return mFragments.dispatchContextItemSelected(item); default: return false; } }
我們平時監聽普通的Menu的函數onOptionsItemSelected也是由onMenuItemSelected調用的。
另外一邊View中顯示ContextMenu的函數是showContextMenu:
public boolean showContextMenu() { return getParent().showContextMenuForChild(this);}
ViewGroup的showContextMenuForChild為:
public boolean showContextMenuForChild(View originalView) { return mParent != null && mParent.showContextMenuForChild(originalView);}
getParent()最終會到DecorView,DecorView中建立了ContextMenu。然後調用View的createContextMenu方法,最終使用mOnCreateContextMenuListener擷取Menu:
public void createContextMenu(ContextMenu menu) { ContextMenuInfo menuInfo = getContextMenuInfo(); // Sets the current menu info so all items added to menu will have // my extra info set. ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo); onCreateContextMenu(menu); ListenerInfo li = mListenerInfo; if (li != null && li.mOnCreateContextMenuListener != null) { li.mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo); } // Clear the extra information so subsequent items that aren‘t mine don‘t // have my extra info. ((MenuBuilder)menu).setCurrentMenuInfo(null); if (mParent != null) { mParent.createContextMenu(menu); }}
DecorView在PhoneWindow中,Menu其實會由Window統一管理,響應Item的點擊事件的介面是一致的(Window.Callback.onMenuItemSelected),另外ContextMenu實際上顯示出來的就是一個Dialog。但由於ContextMenu是跟View對應的,所以有了OnCreateContextMenuListener介面,它是用於當View需要建立ContextMenu的時候,方便指定ContextMenu的內容。
LayoutInflater.Factory2
這個介面只有一個介面函數:
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
它繼承自Factory:
public interface Factory { /** * Hook you can supply that is called when inflating from a LayoutInflater. * You can use this to customize the tag names available in your XML * layout files. * * <p> * Note that it is good practice to prefix these custom names with your * package (i.e., com.coolcompany.apps) to avoid conflicts with system * names. */ public View onCreateView(String name, Context context, AttributeSet attrs); }
用於跟LayoutInflater系統互動,為了適配LayoutInflater系統。實現改介面,可以在Inflater的時候,解析XML中自訂的Tag。該介面為LayoutInflater調用,而LayoutInflater的實現為PhoneLayoutInflater。對於Window和LayoutInflater結構可以看這篇Android源碼抽象工廠—IPolicy。
除了Activity外,Application和Service都實現了ComponnentCallbacks介面,繼承了ContextWrapper,其實都可以用類適配器模式看待。
設計思考
本身應用組件都應該是一種應用環境(Context),但是又需要滿足Window等系統的回調需求,我們平時可能直接單獨實現Window.Callback介面,但是將Activity實現Window.Callback介面,那麼Activity會更加具有整體性,不過設計意圖在這裡思考過多感覺有點太牽強。
總結
從Android應用程式層源碼整理來看,Activity的類結構完全可以看成是一種適配器模式,在基於應用環境(Context)的情況下,去滿足LayoutInflater系統(LayoutInflater.Factory2),Window系統(Window.Callback,Window.OnWindowDismissedCallback),輸入系統(KeyEvent.Callback)的介面需求,另外ComponnentCallbacks更是ActivityThread和ViewRootImpl需要的介面。通過適配器模式來看Activity,對於Activity,對於Activity與其他部分的互動,對於應用程式層架構會有更好的理解。另外再有裝飾模式看Context,對於整個應用程式層結構會更清晰。
Android源碼適配器模式---Activity類結構