Android開(kāi)發(fā)套路收集整理與討論
以下做法純屬個(gè)人習(xí)慣,歡迎討論:D
initView()與updateView()通常,我會(huì)添加一個(gè)initView()方法來(lái)初始化所有的View對(duì)象,在這個(gè)方法的具體實(shí)現(xiàn)中,可能會(huì)有兩種不同的細(xì)微差別。第一種是僅僅做findViewById()就好了,也就是僅僅是去找到每一個(gè)View對(duì)象,而不去給它們?cè)O(shè)置屬性,比如setText()之類的。另一種則是在findViewById()后,順便給它們?cè)O(shè)置初始值。
我更傾向于第一種做法,因?yàn)槿绻阍趇nitView()方法中給View設(shè)置一些屬性,那么當(dāng)一些數(shù)據(jù)變更時(shí),你可能也需要去變更View的一些屬性,你必然會(huì)有一個(gè)updateView()這樣的方法。updateView()方法中,需要根據(jù)當(dāng)前頁(yè)面的狀態(tài)和數(shù)據(jù)去給View設(shè)值,問(wèn)題就在于,當(dāng)需求發(fā)生變化的時(shí)候,你可能需要改兩個(gè)地方,initView()和updateView()。考慮到這一點(diǎn)。最佳的做法就是你需要一個(gè)initView()方法和一個(gè)updateView()方法。
initView()方法只做初始化操作,也就是僅僅只會(huì)發(fā)生一次的操作,比如findViewById(),setListener()之類的。而updateView()方法中,則是去做一些根據(jù)某些成員變量,flag,boolean值之類的去變更View的屬性,會(huì)被反復(fù)調(diào)用的操作。
關(guān)于updateView()方法,我又有兩種不同的思路,在此之前,先具體的說(shuō)明一下updateView()中要干的工作。比如我們有一些成員變量dataA,dataB,有一些會(huì)隨之變化的View,ViewA1,ViewA2,ViewB1,ViewB2……然后當(dāng)數(shù)據(jù)dataA改變時(shí),我們需要更改ViewA1,ViewA2的屬性,當(dāng)數(shù)據(jù)dataB改變時(shí),我們要更改ViewB*的屬性,于是,我們通常寫(xiě)的updateView()方法是這樣的。
private void updateView() { ... viewA1.setText(dataA.getContent()); viewA2.setTextColor(dataA.getTextColor());viewB1.setImage(dataB.getImage()); viewB2.setText(dataB.getTitle()); ...}
在我們的Activity/Fragment比較簡(jiǎn)單的時(shí)候,這樣寫(xiě)應(yīng)該沒(méi)有什么問(wèn)題,但是當(dāng)頁(yè)面的邏輯因需求的變更而變得越來(lái)越復(fù)雜,我們可能需要維持很多很多的成員變量(數(shù)據(jù))和View。那么updateView()方法可能里面做了很多很多的工作,這樣調(diào)用一次必然是效率低下的。因此,我認(rèn)為另一種比較好的方式是將數(shù)據(jù)A所關(guān)聯(lián)的Views都封裝成一個(gè)方法,數(shù)據(jù)B所關(guān)聯(lián)的Views都封裝成另一個(gè)方法,像這樣。
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(); ...}
顯然,第二種方式是效率最好的一種方式,也是維護(hù)起來(lái)最麻煩的一種方式,但我個(gè)人還是比較傾向于第二種寫(xiě)法。因?yàn)橛幸恍¬iew它的onDraw()方法本身真的會(huì)消耗比較長(zhǎng)的時(shí)間,如果簡(jiǎn)單粗暴的更新所有的View,可能會(huì)讓UI的流暢度大打折扣。
使用boolean值來(lái)避免updateView()中的空指針異常當(dāng)我們使用initView()和updateView()兩個(gè)方法來(lái)變更View的時(shí)候,要注意空指針的情況,因?yàn)檎{(diào)用updateView的時(shí)機(jī)不是自己能控制的,updateView可能是在網(wǎng)絡(luò)數(shù)據(jù)返回時(shí)調(diào)用,那么如果onCreate的時(shí)候先請(qǐng)求數(shù)據(jù),數(shù)據(jù)馬上返回了并調(diào)用updateView方法,這個(gè)時(shí)候,initView還沒(méi)有執(zhí)行,那么updateView中對(duì)View的操作就會(huì)報(bào)空指針異常。
我們可以使用一個(gè)boolean值來(lái)解決這個(gè)問(wèn)題。
提前考慮Activity和Fragment的復(fù)用當(dāng)我們寫(xiě)Activity或Fragment的時(shí)候需要考慮到這個(gè)頁(yè)面可能會(huì)從哪些地方調(diào)過(guò)來(lái)。比如說(shuō),我們要完成一個(gè)需求,這個(gè)需求是顯示一個(gè)列表,列表里面有特定的數(shù)據(jù),這個(gè)頁(yè)面必須要自己全新寫(xiě)一個(gè)Activity或Fragment來(lái)完成,入口也只有一個(gè),那么我們幾乎是可以“為所欲為”的實(shí)現(xiàn)這個(gè)頁(yè)面,想怎么寫(xiě)就怎么寫(xiě)。
但是當(dāng)需求發(fā)生了變化,比如其他地方也可以點(diǎn)擊進(jìn)入你這個(gè)頁(yè)面,并且還顯示了不一樣的數(shù)據(jù),考慮到頁(yè)面復(fù)用這一點(diǎn),我們應(yīng)該通過(guò)傳入不同的參數(shù),來(lái)改變這個(gè)頁(yè)面的行為(應(yīng)該顯示怎么樣的數(shù)據(jù),或者UI上有哪些其他的變化)。
所以,在我們?nèi)聦?xiě)這個(gè)頁(yè)面的時(shí)候,就應(yīng)該有所收斂,要主動(dòng)思考一下,因?yàn)檫@個(gè)頁(yè)面如果是被復(fù)用的,那么一般來(lái)說(shuō),是這個(gè)頁(yè)面的樣式,行為會(huì)被復(fù)用。不一樣的地方往往是數(shù)據(jù),頁(yè)面的復(fù)用,就要考慮到在onCreate的時(shí)候可以傳入不同的參數(shù),完成不同的要求和顯示。
我們應(yīng)該在Activity或Fragment中添加幾個(gè)成員變量,用來(lái)標(biāo)記狀態(tài),比如:
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;...}
這樣,我們內(nèi)部獲取數(shù)據(jù)的時(shí)候就根據(jù)這個(gè)mDataType來(lái)做具體的處理就好了。考慮到復(fù)用這一點(diǎn),后面擴(kuò)展的時(shí)候就會(huì)更游刃有余。并且這個(gè)mDataType也許會(huì)影響到UI上的一些表現(xiàn),updateView系列方法可能也需要關(guān)心這個(gè)(些)變量的情況。
通過(guò)封裝好的靜態(tài)方法啟動(dòng)Activity初學(xué)的時(shí)候,我們總是是用下面類似的代碼啟動(dòng)Activity。
Intent i = new Intent();i.setClass(context, TargetActivity.class);context.startActivity(i);
但是,根據(jù)上一個(gè)小主題上面所說(shuō)的,往往我們需要告訴要啟動(dòng)的Activity一些特定的信息,然后展示出不同的行為,一般有兩種常見(jiàn)的寫(xiě)法。
方式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 ActivityTargetActivity.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更簡(jiǎn)潔,方式B更繁瑣一些,但是方式B更好,因?yàn)橛袝r(shí)候我們需要啟動(dòng)的Activity結(jié)束時(shí)返回一些東西,那么我們需要調(diào)用到startActivityForResult()方法來(lái)啟動(dòng),在當(dāng)前的Activity調(diào)用這個(gè)方法,必須要獲取到Intent對(duì)象,所以,方式B的obtainIntent使用情況就更廣泛了。
但在編寫(xiě)obtianIntent方法的時(shí)候,建議讓它帶上你需要傳遞的參數(shù),當(dāng)前的demo是只有一個(gè)int型的dataType,也許你還有很多其他的參數(shù),但都請(qǐng)?jiān)趏btainIntent方法中就給Intent填上,這樣外面(其他)的Activity就不需要去填寫(xiě)這些額外的信息了,你的INTENT_KEY可以完全的定義在要用它的內(nèi)部,這樣做真是又干凈又漂亮。
父類應(yīng)該減輕子類的負(fù)擔(dān),而不是給子類添加約束上面幾個(gè)話題,我們講了幾個(gè)常見(jiàn)的套路做法,這樣可以使代碼更加清晰,更加易于維護(hù)。
但是我們習(xí)慣的套路中那些initView,updateView,obtainIntent等方法,并不適合移動(dòng)到父類去,因?yàn)檫@不是邏輯,如果你挪到父類中寫(xiě)成抽象方法,方法就是限定死了,所有的子類都要有這個(gè)initView方法,這樣是不合適的,不同的人也許有不同的代碼習(xí)慣,因此將多余的流程挪到父類,就會(huì)形成對(duì)子類的約束。子類中如果有重復(fù)的邏輯,才是應(yīng)該移動(dòng)到父類的。
監(jiān)聽(tīng)器,觀察者模式,回調(diào)其實(shí)監(jiān)聽(tīng)器和觀察者模式,回調(diào)都是一樣的東西,表面上看,它們就是一群叫OnXxxxx的一群方法或者接口。
它們負(fù)責(zé)告訴你一些事件發(fā)生了,比如系統(tǒng)給你的onClick,onTouch,onSrcoll……還可以是在新的線程發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求,當(dāng)請(qǐng)求結(jié)果返回時(shí),告訴你,像onResult,onPush……這樣的形式。
總之,當(dāng)你理解了這個(gè)東西,你就可以熟練的使用,當(dāng)你想寫(xiě)一個(gè)控件,這個(gè)控件要完成一個(gè)功能或者一些特性,你需要提供一些回調(diào)接口來(lái)供客戶程序員使用。比如我之前寫(xiě)過(guò)一個(gè)底部有l(wèi)oading的控件,滾動(dòng)到底部的時(shí)候,會(huì)出現(xiàn)一個(gè)loading(轉(zhuǎn)菊花),然后給你一個(gè)“時(shí)機(jī)”來(lái)讓你請(qǐng)求數(shù)據(jù),然后讓adapter更新數(shù)據(jù)。這里有是具體的代碼: BottomLoadListView.java in github
通常,我們可以把這個(gè)回調(diào)接口都讓Activity或者Fragment來(lái)實(shí)現(xiàn),像這樣:
public class MyActivity extends Activity implement OnClickListener, OnNetworkChangeListener, IOnRequestCallback{ ...}
這樣,這個(gè)Activity內(nèi)部的一些對(duì)象需要回調(diào)接口的時(shí)候,直接給它this即可,就不需要那么多匿名內(nèi)部類了,而這些回調(diào)方法都放在Activity中,當(dāng)它們被調(diào)用的時(shí)候,也能很好的控制整個(gè)Activity的行為,是很方便的。
多個(gè)頁(yè)面共用數(shù)據(jù)與回調(diào)通常,我們某一個(gè)頁(yè)面(Activity/Fragment)需要顯示一些數(shù)據(jù),這些數(shù)據(jù)的引用都是讓Activity自己持有的,如果僅僅是一個(gè)頁(yè)面需要這些數(shù)據(jù),這么做沒(méi)有什么問(wèn)題,當(dāng)我們有兩個(gè)頁(yè)面需要對(duì)同一份數(shù)據(jù)進(jìn)行操作的時(shí)候,這樣做就不太方便了。通常可以寫(xiě)一個(gè)名為XxxxEngine的東西,xxx具體是什么跟所關(guān)聯(lián)的業(yè)務(wù)邏輯有關(guān),比如說(shuō)是消息列表,那么就叫MessageEngine好了。
這個(gè)Engine一般會(huì)寫(xiě)成單例模式,然后讓它來(lái)持有數(shù)據(jù)的引用,而兩個(gè)或多個(gè)頁(yè)面需要對(duì)這份消息列表(message list)進(jìn)行操作的時(shí)候,就通過(guò)這個(gè)Engine來(lái)獲取就行了。
使用Engine還有另一個(gè)場(chǎng)景,就是兩個(gè)頁(yè)面都需要監(jiān)聽(tīng)某一個(gè)網(wǎng)絡(luò)push,比如說(shuō)在多終端的情況下,我們有一個(gè)個(gè)人信息頁(yè)面,個(gè)人信息是可以在別的終端被修改的,那么我們的頁(yè)面就會(huì)收到一個(gè)通知,有時(shí)候,通知回調(diào)是不帶數(shù)據(jù)的,我們需要手動(dòng)去拉去數(shù)據(jù),就算帶上了數(shù)據(jù),如果兩個(gè)頁(yè)面都監(jiān)聽(tīng)這個(gè)網(wǎng)絡(luò)回調(diào),也會(huì)有問(wèn)題,因?yàn)檫@樣就有兩份數(shù)據(jù),或者說(shuō)有兩個(gè)地方會(huì)對(duì)數(shù)據(jù)進(jìn)行操作。我用來(lái)代碼來(lái)演示。
public class ProfileActivity extends Activity implement OnProfileChangedListener, OnResultForProfileRequest {private Profile mProfile = null;//當(dāng)別的終端更新了個(gè)人信息后調(diào)用這里 @override public void onProfileChanged() {ProfileManager.getInstance().requestProfile(this); //傳入OnResultForProfileRequest接口 }//當(dāng)requestProfile()請(qǐng)求結(jié)果返回時(shí)調(diào)用 @override public void onResult(Profile profile) {mProfile = profile;updateView(); }}
上面代碼展示了一個(gè)頁(yè)面收到數(shù)據(jù)變更的通知以及請(qǐng)求數(shù)據(jù)的情況,那么當(dāng)我們有兩個(gè)頁(yè)面都需要關(guān)心數(shù)據(jù)發(fā)生變化的時(shí)候,如果兩個(gè)頁(yè)面都像上面這樣寫(xiě),那么我們就有兩處來(lái)請(qǐng)求數(shù)據(jù),這樣是不好的,因?yàn)閮蓚€(gè)地方用的是同一份數(shù)據(jù),這樣根據(jù)上面說(shuō)的,我們需要一個(gè)ProfileEngine來(lái)維持這份數(shù)據(jù)的引用,另一方面,我們可以把profile changed的監(jiān)聽(tīng),放在ProfileEngine上,這樣就只有它一個(gè)地方收到變化的通知,一個(gè)地方來(lái)拉取最新數(shù)據(jù),更新好了之后,再通知兩個(gè)(多個(gè))頁(yè)面通過(guò)單例來(lái)獲取最新的數(shù)據(jù)。這種情形下,我們需要定義一個(gè)本地的接口。
public class ProfileEngine implement OnRemoteProfileChangedListener, OnResultForProfileRequest {public interface OnLocalProfileChangedListener { void onLocalProfileChanged(Profile newProfile); }private Profile mProfile = null;//監(jiān)聽(tīng)列表 private ArrayList<OnLocalProfileChangedListener> mListeners = new ArrayList<>();//當(dāng)別的終端更新了個(gè)人信息后調(diào)用這里 @override public void onProfileChanged() {ProfileManager.getInstance().requestProfile(this); //傳入OnResultForProfileRequest接口 }//當(dāng)requestProfile()請(qǐng)求結(jié)果返回時(shí)調(diào)用 @override public void onResult(Profile profile) {mProfile = profile; }//通知所有的頁(yè)面,profile發(fā)生了變更,并且已經(jīng)取好了最新的數(shù)據(jù)了,拿過(guò)去更新UI就好了 private void notifyListener() {for (OnLocalProfileChangedListener l : mListeners) { l.onLocalProfileChanged(mProfile);} }}
這個(gè)套路感覺(jué)真的很簡(jiǎn)潔干練,但我們需要注意一個(gè)問(wèn)題就是本地的監(jiān)聽(tīng)的注冊(cè)與反注冊(cè)。
單例一旦被創(chuàng)建就不會(huì)被銷毀了,除非進(jìn)程被干掉,或者我們主動(dòng)置空(null)并且GC。也就是說(shuō),這個(gè)單例通常情況下會(huì)一直在內(nèi)存中的,也會(huì)一直監(jiān)聽(tīng)remote的profile變化,并且會(huì)去拉去最新的數(shù)據(jù),請(qǐng)注意這里的mListeners,里面存放的兩個(gè)頁(yè)面(Activity/Fragment),如果我們沒(méi)有在頁(yè)面銷毀(onDestory)的時(shí)候?qū)⒆约簭谋O(jiān)聽(tīng)列表中移除,那么mListeners就會(huì)一直持有Activity的引用,但是頁(yè)面卻已經(jīng)是消失了,這樣就造成了內(nèi)存泄露。因此一定要嚴(yán)格的在onCreate和onDestory中調(diào)用注冊(cè)與反注冊(cè)方法。
一種網(wǎng)絡(luò)請(qǐng)求套路這種網(wǎng)絡(luò)請(qǐng)求套路也是最近才學(xué)習(xí)到的,感覺(jué)非常的簡(jiǎn)單巧妙。
//發(fā)起一個(gè)請(qǐng)求檢查一下數(shù)據(jù)是否有變更,如果有變更,會(huì)通過(guò)通知onChanged()告訴客戶端,無(wú)參數(shù)無(wú)返回值void check();//通知,告知客戶端數(shù)據(jù)有變更,要拉取最新數(shù)據(jù)需要另一個(gè)接口,無(wú)參數(shù),無(wú)返回值void onChanged();//通過(guò)網(wǎng)絡(luò)拉取數(shù)據(jù),無(wú)返回值,傳入回調(diào)接口,因?yàn)槭钱惒椒祷財(cái)?shù)據(jù)void request(onRequestResult);//請(qǐng)求數(shù)據(jù)的回調(diào)接口,參數(shù)中是最新的數(shù)據(jù)void onRequestResult(Data)//通過(guò)網(wǎng)絡(luò)更新數(shù)據(jù),無(wú)返回值,通過(guò)參數(shù)傳入新數(shù)據(jù)和回調(diào)接口void set(Data, OnSetResult);//更新數(shù)據(jù)的回調(diào)接口,參數(shù)表示有沒(méi)有成功,以及最新的數(shù)據(jù),同時(shí)也會(huì)調(diào)用onChanged()方法void onSetResult(int, Data);
可以發(fā)現(xiàn),數(shù)據(jù)變化的時(shí)候,總是會(huì)調(diào)用onChanged()方法,而這僅僅是通知,獲取數(shù)據(jù)需要自己手動(dòng)去拉取一次。這樣我們有統(tǒng)一的時(shí)機(jī)可以獲取最新的數(shù)據(jù)。
以上做法純屬個(gè)人習(xí)慣,歡迎討論:D
相關(guān)文章:
1. ASP實(shí)現(xiàn)加法驗(yàn)證碼2. 使用FormData進(jìn)行Ajax請(qǐng)求上傳文件的實(shí)例代碼3. XML入門的常見(jiàn)問(wèn)題(二)4. 解決ajax的delete、put方法接收不到參數(shù)的問(wèn)題方法5. Jsp中request的3個(gè)基礎(chǔ)實(shí)踐6. ASP.NET MVC使用異步Action的方法7. chat.asp聊天程序的編寫(xiě)方法8. CSS hack用法案例詳解9. 詳解瀏覽器的緩存機(jī)制10. 怎樣才能用js生成xmldom對(duì)象,并且在firefox中也實(shí)現(xiàn)xml數(shù)據(jù)島?
