Android開發套路收集整理與討論

原文一開始是寫在csdn上的,複製過來java

如下作法純屬我的習慣,歡迎討論:Dandroid

initView()與updateView()

一般,我會添加一個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()中要乾的工做。好比咱們有一些成員變量dataAdataB,有一些會隨之變化的ViewViewA1ViewA2ViewB1ViewB2……而後當數據dataA改變時,咱們須要更改ViewA1ViewA2的屬性,當數據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

使用boolean值來避免updateView()中的空指針異常

當咱們使用initView()updateView()兩個方法來變動View的時候,要注意空指針的狀況,由於調用updateView的時機不是本身能控制的,updateView多是在網絡數據返回時調用,那麼若是onCreate的時候先請求數據,數據立刻返回了並調用updateView方法,這個時候,initView尚未執行,那麼updateView中對View的操做就會報空指針異常。異步

咱們可使用一個boolean值來解決這個問題。ide

提早考慮Activity和Fragment的複用

當咱們寫ActivityFragment的時候須要考慮到這個頁面可能會從哪些地方調過來。好比說,咱們要完成一個需求,這個需求是顯示一個列表,列表裏面有特定的數據,這個頁面必需要本身全新寫一個ActivityFragment來完成,入口也只有一個,那麼咱們幾乎是能夠「隨心所欲」的實現這個頁面,想怎麼寫就怎麼寫。

可是當需求發生了變化,好比其餘地方也能夠點擊進入你這個頁面,而且還顯示了不同的數據,考慮到頁面複用這一點,咱們應該經過傳入不一樣的參數,來改變這個頁面的行爲(應該顯示怎麼樣的數據,或者UI上有哪些其餘的變化)。

因此,在咱們全新寫這個頁面的時候,就應該有所收斂,要主動思考一下,由於這個頁面若是是被複用的,那麼通常來講,是這個頁面的樣式,行爲會被複用。不同的地方每每是數據,頁面的複用,就要考慮到在onCreate的時候能夠傳入不一樣的參數,完成不一樣的要求和顯示。

咱們應該在ActivityFragment中添加幾個成員變量,用來標記狀態,好比:

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

初學的時候,咱們老是是用下面相似的代碼啓動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能夠徹底的定義在要用它的內部,這樣作真是又幹淨又漂亮。

父類應該減輕子類的負擔,而不是給子類添加約束

上面幾個話題,咱們講了幾個常見的套路作法,這樣可使代碼更加清晰,更加易於維護。
可是咱們習慣的套路中那些initViewupdateViewobtainIntent等方法,並不適合移動到父類去,由於這不是邏輯,若是你挪到父類中寫成抽象方法,方法就是限定死了,全部的子類都要有這個initView方法,這樣是不合適的,不一樣的人也許有不一樣的代碼習慣,所以將多餘的流程挪到父類,就會造成對子類的約束。子類中若是有重複的邏輯,纔是應該移動到父類的。

監聽器,觀察者模式,回調

其實監聽器和觀察者模式,回調都是同樣的東西,表面上看,它們就是一羣叫OnXxxxx的一羣方法或者接口。
它們負責告訴你一些事件發生了,好比系統給你的onClickonTouchonSrcoll……還能夠是在新的線程發起一個網絡請求,當請求結果返回時,告訴你,像onResultonPush……這樣的形式。
總之,當你理解了這個東西,你就能夠熟練的使用,當你想寫一個控件,這個控件要完成一個功能或者一些特性,你須要提供一些回調接口來供客戶程序員使用。好比我以前寫過一個底部有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的引用,可是頁面卻已是消失了,這樣就形成了內存泄露。所以必定要嚴格的在onCreateonDestory中調用註冊與反註冊方法。

一種網絡請求套路

這種網絡請求套路也是最近才學習到的,感受很是的簡單巧妙。

//發起一個請求檢查一下數據是否有變動,若是有變動,會經過通知onChanged()告訴客戶端,無參數無返回值
void check();

//通知,告知客戶端數據有變動,要拉取最新數據須要另外一個接口,無參數,無返回值
void onChanged();

//經過網絡拉取數據,無返回值,傳入回調接口,由於是異步返回數據
void request(onRequestResult);

//請求數據的回調接口,參數中是最新的數據
void onRequestResult(Data)

//經過網絡更新數據,無返回值,經過參數傳入新數據和回調接口
void set(Data, OnSetResult);

//更新數據的回調接口,參數表示有沒有成功,以及最新的數據,同時也會調用onChanged()方法
void onSetResult(int, Data);

能夠發現,數據變化的時候,老是會調用onChanged()方法,而這僅僅是通知,獲取數據須要本身手動去拉取一次。這樣咱們有統一的時機能夠獲取最新的數據。

以上作法純屬我的習慣,歡迎討論:D

相關文章
相關標籤/搜索