原文一開始是寫在csdn上的,複製過來java
如下作法純屬我的習慣,歡迎討論:Dandroid
一般,我會添加一個initView()
方法來初始化全部的View
對象,在這個方法的具體實現中,可能會有兩種不一樣的細微差異。第一種是僅僅作findViewById()
就行了,也就是僅僅是去找到每個View
對象,而不去給它們設置屬性,好比setText()
之類的。另外一種則是在findViewById()
後,順便給它們設置初始值。git
我更傾向於第一種作法,由於若是你在initView()
方法中給View
設置一些屬性,那麼當一些數據變動時,你可能也須要去變動View
的一些屬性,你必然會有一個updateView()
這樣的方法。updateView()
方法中,須要根據當前頁面的狀態和數據去給View
設值,問題就在於,當需求發生變化的時候,你可能須要改兩個地方,initView()
和updateView()
。考慮到這一點。最佳的作法就是你須要一個initView()
方法和一個updateView()
方法。程序員
initView()
方法只作初始化操做,也就是僅僅只會發生一次的操做,好比findViewById()
,setListener()
之類的。而updateView()
方法中,則是去作一些根據某些成員變量,flag,boolean值之類的去變動View
的屬性,會被反覆調用的操做。github
關於updateView()
方法,我又有兩種不一樣的思路,在此以前,先具體的說明一下updateView()
中要乾的工做。好比咱們有一些成員變量dataA
,dataB
,有一些會隨之變化的View
,ViewA1
,ViewA2
,ViewB1
,ViewB2
……而後當數據dataA
改變時,咱們須要更改ViewA1
,ViewA2
的屬性,當數據dataB
改變時,咱們要更改ViewB*
的屬性,因而,咱們一般寫的updateView()
方法是這樣的。網絡
private void updateView() { ... viewA1.setText(dataA.getContent()); viewA2.setTextColor(dataA.getTextColor()); viewB1.setImage(dataB.getImage()); viewB2.setText(dataB.getTitle()); ... }
在咱們的Activity
/Fragment
比較簡單的時候,這樣寫應該沒有什麼問題,可是當頁面的邏輯因需求的變動而變得愈來愈複雜,咱們可能須要維持不少不少的成員變量(數據)和View
。那麼updateView()
方法可能裏面作了不少不少的工做,這樣調用一次必然是效率低下的。所以,我認爲另外一種比較好的方式是將數據A所關聯的Views都封裝成一個方法,數據B所關聯的Views
都封裝成另外一個方法,像這樣。app
private void updateAViews() { viewA1.setText(dataA.getContent()); viewA2.setTextColor(dataA.getTextColor()); ... } private void updateBViews() { viewB1.setImage(dataB.getImage()); viewB2.setText(dataB.getTitle()); ... } private void updateAllViews() { updateAViews(); updateBViews(); ... }
顯然,第二種方式是效率最好的一種方式,也是維護起來最麻煩的一種方式,但我我的仍是比較傾向於第二種寫法。由於有一些View
它的onDraw()
方法自己真的會消耗比較長的時間,若是簡單粗暴的更新全部的View
,可能會讓UI的流暢度大打折扣。ssh
當咱們使用initView()
和updateView()
兩個方法來變動View
的時候,要注意空指針的狀況,由於調用updateView
的時機不是本身能控制的,updateView
多是在網絡數據返回時調用,那麼若是onCreate
的時候先請求數據,數據立刻返回了並調用updateView
方法,這個時候,initView
尚未執行,那麼updateView
中對View
的操做就會報空指針異常。異步
咱們可使用一個boolean
值來解決這個問題。ide
當咱們寫Activity
或Fragment
的時候須要考慮到這個頁面可能會從哪些地方調過來。好比說,咱們要完成一個需求,這個需求是顯示一個列表,列表裏面有特定的數據,這個頁面必需要本身全新寫一個Activity
或Fragment
來完成,入口也只有一個,那麼咱們幾乎是能夠「隨心所欲」的實現這個頁面,想怎麼寫就怎麼寫。
可是當需求發生了變化,好比其餘地方也能夠點擊進入你這個頁面,而且還顯示了不同的數據,考慮到頁面複用這一點,咱們應該經過傳入不一樣的參數,來改變這個頁面的行爲(應該顯示怎麼樣的數據,或者UI上有哪些其餘的變化)。
因此,在咱們全新寫這個頁面的時候,就應該有所收斂,要主動思考一下,由於這個頁面若是是被複用的,那麼通常來講,是這個頁面的樣式,行爲會被複用。不同的地方每每是數據,頁面的複用,就要考慮到在onCreate
的時候能夠傳入不一樣的參數,完成不一樣的要求和顯示。
咱們應該在Activity
或Fragment
中添加幾個成員變量,用來標記狀態,好比:
public class DataListActivity extends Activity { public static final int DATA_TYPE_ALL = 1; public static final int DATA_TYPE_PART = 2; private int mDataType = DATA_TYPE_ALL; ... }
這樣,咱們內部獲取數據的時候就根據這個mDataType
來作具體的處理就行了。考慮到複用這一點,後面擴展的時候就會更遊刃有餘。而且這個mDataType
也許會影響到UI上的一些表現,updateView
系列方法可能也須要關心這個(些)變量的狀況。
初學的時候,咱們老是是用下面相似的代碼啓動Activity
。
Intent i = new Intent(); i.setClass(context, TargetActivity.class); context.startActivity(i);
可是,根據上一個小主題上面所說的,每每咱們須要告訴要啓動的Activity
一些特定的信息,而後展現出不一樣的行爲,通常有兩種常見的寫法。
方式A:
public class TargetActivity extends Activity { public static final String INTENT_KEY_DATA_TYPE = "INTENT_KEY_DATA_TYPE"; public static final int DATA_TYPE_ALL = 1; public static final int DATA_TYPE_PART = 2; public static void start(Context c, int dataType) { Intent i = new Intent(); i.setClass(c, TargetActivity.class); i.putExtras(INTENT_KEY_DATA_TYPE, dataType); c.startActivity(i); } } //in other Activity TargetActivity.start(context, TargetActivity.DATA_TYPE_ALL);
方式B:
public class TargetActivity extends Activity { public static final String INTENT_KEY_DATA_TYPE = "INTENT_KEY_DATA_TYPE"; public static final int DATA_TYPE_ALL = 1; public static final int DATA_TYPE_PART = 2; public static Intent obtainIntent(Context, int dataType) { Intent i = new Intent(); i.setClass(c, TargetActivity.class); i.putExtras(INTENT_KEY_DATA_TYPE, dataType); return i; } } //in other Activity. startActivity(TargetActivity.obtainIntent(this, TargetActivity.DATA_TYPE_ALL));
方式A更簡潔,方式B更繁瑣一些,可是方式B更好,由於有時候咱們須要啓動的Activity
結束時返回一些東西,那麼咱們須要調用到startActivityForResult()
方法來啓動,在當前的Activity
調用這個方法,必需要獲取到Intent
對象,因此,方式B的obtainIntent
使用狀況就更普遍了。
但在編寫obtianIntent
方法的時候,建議讓它帶上你須要傳遞的參數,當前的demo是隻有一個int
型的dataType
,也許你還有不少其餘的參數,但都請在obtainIntent
方法中就給Intent
填上,這樣外面(其餘)的Activity
就不須要去填寫這些額外的信息了,你的INTENT_KEY
能夠徹底的定義在要用它的內部,這樣作真是又幹淨又漂亮。
上面幾個話題,咱們講了幾個常見的套路作法,這樣可使代碼更加清晰,更加易於維護。
可是咱們習慣的套路中那些initView
,updateView
,obtainIntent
等方法,並不適合移動到父類去,由於這不是邏輯,若是你挪到父類中寫成抽象方法,方法就是限定死了,全部的子類都要有這個initView
方法,這樣是不合適的,不一樣的人也許有不一樣的代碼習慣,所以將多餘的流程挪到父類,就會造成對子類的約束。子類中若是有重複的邏輯,纔是應該移動到父類的。
其實監聽器和觀察者模式,回調都是同樣的東西,表面上看,它們就是一羣叫OnXxxxx
的一羣方法或者接口。
它們負責告訴你一些事件發生了,好比系統給你的onClick
,onTouch
,onSrcoll
……還能夠是在新的線程發起一個網絡請求,當請求結果返回時,告訴你,像onResult
,onPush
……這樣的形式。
總之,當你理解了這個東西,你就能夠熟練的使用,當你想寫一個控件,這個控件要完成一個功能或者一些特性,你須要提供一些回調接口來供客戶程序員使用。好比我以前寫過一個底部有loading的控件,滾動到底部的時候,會出現一個loading(轉菊花),而後給你一個「時機」來讓你請求數據,而後讓adapter
更新數據。這裏有是具體的代碼:BottomLoadListView.java in github
一般,咱們能夠把這個回調接口都讓Activity
或者Fragment
來實現,像這樣:
public class MyActivity extends Activity implement OnClickListener, OnNetworkChangeListener, IOnRequestCallback{ ... }
這樣,這個Activity
內部的一些對象須要回調接口的時候,直接給它this
便可,就不須要那麼多匿名內部類了,而這些回調方法都放在Activity
中,當它們被調用的時候,也能很好的控制整個Activity
的行爲,是很方便的。
一般,咱們某一個頁面(Activity
/Fragment
)須要顯示一些數據,這些數據的引用都是讓Activity
本身持有的,若是僅僅是一個頁面須要這些數據,這麼作沒有什麼問題,當咱們有兩個頁面須要對同一份數據進行操做的時候,這樣作就不太方便了。一般能夠寫一個名爲XxxxEngine
的東西,xxx具體是什麼跟所關聯的業務邏輯有關,好比說是消息列表,那麼就叫MessageEngine
好了。
這個Engine
通常會寫成單例模式,而後讓它來持有數據的引用,而兩個或多個頁面須要對這份消息列表(message list)進行操做的時候,就經過這個Engine
來獲取就好了。
使用Engine
還有另外一個場景,就是兩個頁面都須要監聽某一個網絡push,好比說在多終端的狀況下,咱們有一個我的信息頁面,我的信息是能夠在別的終端被修改的,那麼咱們的頁面就會收到一個通知,有時候,通知回調是不帶數據的,咱們須要手動去拉去數據,就算帶上了數據,若是兩個頁面都監聽這個網絡回調,也會有問題,由於這樣就有兩份數據,或者說有兩個地方會對數據進行操做。我用來代碼來演示。
public class ProfileActivity extends Activity implement OnProfileChangedListener, OnResultForProfileRequest { private Profile mProfile = null; //當別的終端更新了我的信息後調用這裏 @override public void onProfileChanged() { ProfileManager.getInstance().requestProfile(this); //傳入OnResultForProfileRequest接口 } //當requestProfile()請求結果返回時調用 @override public void onResult(Profile profile) { mProfile = profile; updateView(); } }
上面代碼展現了一個頁面收到數據變動的通知以及請求數據的狀況,那麼當咱們有兩個頁面都須要關心數據發生變化的時候,若是兩個頁面都像上面這樣寫,那麼咱們就有兩處來請求數據,這樣是很差的,由於兩個地方用的是同一份數據,這樣根據上面說的,咱們須要一個ProfileEngine
來維持這份數據的引用,另外一方面,咱們能夠把profile changed
的監聽,放在ProfileEngine
上,這樣就只有它一個地方收到變化的通知,一個地方來拉取最新數據,更新好了以後,再通知兩個(多個)頁面經過單例來獲取最新的數據。這種情形下,咱們須要定義一個本地的接口。
public class ProfileEngine implement OnRemoteProfileChangedListener, OnResultForProfileRequest { public interface OnLocalProfileChangedListener { void onLocalProfileChanged(Profile newProfile); } private Profile mProfile = null; //監聽列表 private ArrayList<OnLocalProfileChangedListener> mListeners = new ArrayList<>(); //當別的終端更新了我的信息後調用這裏 @override public void onProfileChanged() { ProfileManager.getInstance().requestProfile(this); //傳入OnResultForProfileRequest接口 } //當requestProfile()請求結果返回時調用 @override public void onResult(Profile profile) { mProfile = profile; } //通知全部的頁面,profile發生了變動,而且已經取好了最新的數據了,拿過去更新UI就行了 private void notifyListener() { for (OnLocalProfileChangedListener l : mListeners) { l.onLocalProfileChanged(mProfile); } } }
這個套路感受真的很簡潔幹練,但咱們須要注意一個問題就是本地的監聽的註冊與反註冊。
單例一旦被建立就不會被銷燬了,除非進程被幹掉,或者咱們主動置空(null
)而且GC。也就是說,這個單例一般狀況下會一直在內存中的,也會一直監聽remote的profile變化,而且會去拉去最新的數據,請注意這裏的mListeners
,裏面存放的兩個頁面(Activity
/Fragment
),若是咱們沒有在頁面銷燬(onDestory
)的時候將本身從監聽列表中移除,那麼mListeners
就會一直持有Activity的引用,可是頁面卻已是消失了,這樣就形成了內存泄露。所以必定要嚴格的在onCreate
和onDestory
中調用註冊與反註冊方法。
這種網絡請求套路也是最近才學習到的,感受很是的簡單巧妙。
//發起一個請求檢查一下數據是否有變動,若是有變動,會經過通知onChanged()告訴客戶端,無參數無返回值 void check(); //通知,告知客戶端數據有變動,要拉取最新數據須要另外一個接口,無參數,無返回值 void onChanged(); //經過網絡拉取數據,無返回值,傳入回調接口,由於是異步返回數據 void request(onRequestResult); //請求數據的回調接口,參數中是最新的數據 void onRequestResult(Data) //經過網絡更新數據,無返回值,經過參數傳入新數據和回調接口 void set(Data, OnSetResult); //更新數據的回調接口,參數表示有沒有成功,以及最新的數據,同時也會調用onChanged()方法 void onSetResult(int, Data);
能夠發現,數據變化的時候,老是會調用onChanged()
方法,而這僅僅是通知,獲取數據須要本身手動去拉取一次。這樣咱們有統一的時機能夠獲取最新的數據。
以上作法純屬我的習慣,歡迎討論:D