Android模板設(shè)計模式之 - 構(gòu)建整個應(yīng)用的BaseActivity

1. 模式介紹


模式的定義

定義一個操作中的算法的框架捕捂,而將一些步驟延遲到子類中蚀之。使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟邑滨。

所有分享大綱:2017Android進階之路與你同行

視頻講解地址:http://pan.baidu.com/s/1nuNA0Vv

模式的使用場景

  1. 多個子類有公有的方法每界,并且邏輯基本相同時捅僵。
  2. 重要、復(fù)雜的算法眨层,可以把核心算法設(shè)計為模板方法庙楚,周邊的相關(guān)細節(jié)功能則由各個子類實現(xiàn)。
  3. 重構(gòu)時趴樱,模板方法模式是一個經(jīng)常使用的模式馒闷,把相同的代碼抽取到父類中,然后通過鉤子函數(shù)約束其行為叁征。

2. UML類圖


uml.png

角色介紹

  • AbstractClass : 抽象類纳账,定義了一套算法框架。
  • ConcreteClass1 : 具體實現(xiàn)類1捺疼;
  • ConcreteClass2: 具體實現(xiàn)類2疏虫;

3. 模式的簡單實現(xiàn)


簡單實現(xiàn)的介紹
模板方法實際上是封裝一個算法框架,就像是一套模板一樣。而子類可以有不同的算法實現(xiàn)卧秘,在框架不被修改的情況下實現(xiàn)算法的替換呢袱。下面我們以開電腦這個動作來簡單演示一下模板方法。開電腦的整個過程都是相對穩(wěn)定的斯议,首先打開電腦電源产捞,電腦檢測自身狀態(tài)沒有問題時將進入操作系統(tǒng),對用戶進行驗證之后即可登錄電腦哼御,下面我們使用模板方法來模擬一下這個過程坯临。

實現(xiàn)源碼

package com.dp.example.templatemethod;

/**
 * 抽象的Computer
 * @author mrsimple
 *
 */
public abstract class AbstractComputer {

    protected void powerOn() {
        System.out.println("開啟電源");
    }

    protected void checkHardware() {
        System.out.println("硬件檢查");
    }

    protected void loadOS() {
        System.out.println("載入操作系統(tǒng)");
    }

    protected void login() {
        System.out.println("小白的電腦無驗證,直接進入系統(tǒng)");
    }

    /**
     * 啟動電腦方法, 步驟固定為開啟電源恋昼、系統(tǒng)檢查看靠、加載操作系統(tǒng)、用戶登錄液肌。該方法為final挟炬, 防止算法框架被覆寫.
     */
    public final void startUp() {
        System.out.println("------ 開機 START ------");
        powerOn();
        checkHardware();
        loadOS();
        login();
        System.out.println("------ 開機 END ------");
    }
}


package com.dp.example.templatemethod;

/**
 * 碼農(nóng)的計算機
 * 
 * @author mrsimple
 */
public class CoderComputer extends AbstractComputer {
    @Override
    protected void login() {
        System.out.println("碼農(nóng)只需要進行用戶和密碼驗證就可以了");
    }
}


package com.dp.example.templatemethod;

/**
 * 軍用計算機
 * 
 * @author mrsimple
 */
public class MilitaryComputer extends AbstractComputer {


    @Override
    protected void checkHardware() {
        super.checkHardware();
        System.out.println("檢查硬件防火墻");
    }

    @Override
    protected void login() {
        System.out.println("進行指紋之別等復(fù)雜的用戶驗證");
    }
}


package com.dp.example.templatemethod;

public class Test {
    public static void main(String[] args) {
        AbstractComputer comp = new CoderComputer();
        comp.startUp();

        comp = new MilitaryComputer();
        comp.startUp();

    }
}

輸出結(jié)果如下 :

------ 開機 START ------
開啟電源
硬件檢查
載入操作系統(tǒng)
碼農(nóng)只需要進行用戶和密碼驗證就可以了
------ 開機 END ------
------ 開機 START ------
開啟電源
硬件檢查
檢查硬件防火墻
載入操作系統(tǒng)
進行指紋之別等復(fù)雜的用戶驗證
------ 開機 END ------

通過上面的例子可以看到,在startUp方法中有一些固定的步驟嗦哆,依次為開啟電源谤祖、檢查硬件、加載系統(tǒng)老速、用戶登錄四個步驟粥喜,這四個步驟是電腦開機過程中不會變動的四個過程。但是不同用戶的這幾個步驟的實現(xiàn)可能各不相同橘券,因此他們可以用不同的實現(xiàn)额湘。而startUp為final方法,即保證了算法框架不能修改旁舰,具體算法實現(xiàn)卻可以靈活改變锋华。startUp中的這幾個算法步驟我們可以稱為是一個套路,即可稱為模板方法箭窜。因此毯焕,模板方法是定義一個操作中的算法的框架,而將一些步驟延遲到子類中磺樱。使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟芥丧。如圖 :


flow.png

4. Android源碼中的模式實現(xiàn)


在Android中,使用了模板方法且為我們熟知的一個典型類就是AsyncTask了坊罢,關(guān)于AsyncTask的更詳細的分析請移步Android中AsyncTask的使用與源碼分析续担,我們這里只分析在該類中使用的模板方法模式。

在使用AsyncTask時活孩,我們都有知道耗時的方法要放在doInBackground(Params... params)中物遇,在doInBackground之前如果還想做一些類似初始化的操作可以寫在onPreExecute方法中,當doInBackground方法執(zhí)行完成后,會執(zhí)行onPostExecute方法询兴,而我們只需要構(gòu)建AsyncTask對象乃沙,然后執(zhí)行execute方法即可。我們可以看到诗舰,它整個執(zhí)行過程其實是一個框架警儒,具體的實現(xiàn)都需要子類來完成。而且它執(zhí)行的算法框架是固定的眶根,調(diào)用execute后會依次執(zhí)行onPreExecute,doInBackground,onPostExecute,當然你也可以通過onProgressUpdate來更新進度蜀铲。我們可以簡單的理解為如下圖的模式 :

async-flow.png

下面我們看源碼,首先我們看執(zhí)行異步任務(wù)的入口, 即execute方法 :

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

可以看到execute方法(為final類型的方法)調(diào)用了executeOnExecutor方法属百,在該方法中會判斷該任務(wù)的狀態(tài)记劝,如果不是PENDING狀態(tài)則拋出異常,這也解釋了為什么AsyncTask只能被執(zhí)行一次族扰,因此如果該任務(wù)已經(jīng)被執(zhí)行過的話那么它的狀態(tài)就會變成FINISHED厌丑。繼續(xù)往下看,我們看到在executeOnExecutor方法中首先執(zhí)行了onPreExecute方法渔呵,并且該方法執(zhí)行在UI線程怒竿。然后將params參數(shù)傳遞給了mWorker對象的mParams字段,然后執(zhí)行了exec.execute(mFuture)方法扩氢。
mWorker和mFuture又是什么呢愧口?其實mWorker只是實現(xiàn)了Callable接口,并添加了一個參數(shù)數(shù)組字段类茂,我們挨個來分析吧掰伸,跟蹤代碼我們可以看到摇邦,這兩個字段都是在構(gòu)造函數(shù)中初始化。

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                return postResult(doInBackground(mParams));
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    final Result result = get();

                    postResultIfNotInvoked(result);
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                } catch (Throwable t) {
                    throw new RuntimeException("An error occured while executing "
                            + "doInBackground()", t);
                }
            }
        };
    }

簡單的說就是mFuture就包裝了這個mWorker對象椅寺,會調(diào)用mWorker對象的call方法示启,并且將之返回給調(diào)用者兢哭。關(guān)于AsyncTask的更詳細的分析請移步Android中AsyncTask的使用與源碼分析,我們這里只分析模板方法模式夫嗓〕俾荩總之,call方法會在子線程中調(diào)用舍咖,而在call方法中又調(diào)用了doInBackground方法矩父,因此doInBackground會執(zhí)行在子線程。doInBackground會返回結(jié)果排霉,最終通過postResult投遞給UI線程窍株。 我們再看看postResult的實現(xiàn) :

private Result postResult(Result result) {
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

    private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }


    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

可以看到,postResult就是把一個消息( msg.what == MESSAGE_POST_RESULT)發(fā)送給sHandler,sHandler類型為InternalHandler類型球订,當InternalHandler接到MESSAGE_POST_RESULT類型的消息時就會調(diào)用result.mTask.finish(result.mData[0])方法后裸。我們可以看到result為AsyncTaskResult類型,源碼如下 :

    @SuppressWarnings({"RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

可以看到mTask就是AsyncTask對象冒滩,調(diào)用AsyncTask對象的finish方法時又調(diào)用了onPostExecute微驶,這個時候整個執(zhí)行過程就完成了。 總之开睡,execute方法內(nèi)部封裝了onPreExecute, doInBackground, onPostExecute這個算法框架因苹,用戶可以根據(jù)自己的需求來在覆寫這幾個方法,使得用戶可以很方便的使用異步任務(wù)來完成耗時操作士八,又可以通過onPostExecute來完成更新UI線程的工作容燕。
另一個比較好的模板方法示例就是Activity的聲明周期函數(shù),例如Activity從onCreate婚度、onStart蘸秘、onResume這些程式化的執(zhí)行模板,這就是一個Activity的模板方法蝗茁。

5. 構(gòu)建整個應(yīng)用的BaseActivity

接下來我們看一下怎么才能用到開發(fā)中... 等等我要去舉報你 --> 怎么啦醋虏? 你抄襲別人的。怎么哮翘?被發(fā)現(xiàn)了嗎颈嚼,我這不是抄襲只是把好東西拿出來與大家分享而已,給大家推薦一下:https://github.com/simple-android-framework-exchange/android_design_patterns_analysis#schedule 里面有很多設(shè)計模式饭寺,最好大家可以買一本書看一下這么好的東西還是支持一下吧希望可以出一些更好的(雖然我看的是電子文檔)阻课。說到這里非常感謝大家的打賞雖然不多但是我內(nèi)心真的很感謝,希望以后可以出更多好的文章艰匙。
  在網(wǎng)上查了很多關(guān)于別人講的模板設(shè)計模式限煞,都是只簡單的介紹一下概念然后寫一個小的事例如:程序員的一天,銀行辦理業(yè)務(wù)等等等等员凝。我最近剃了個光頭署驻,因為上班閑來無事看別人的博客的時候老是抓頭發(fā),一直都在想想我該用到哪里呢健霹?哪里又能用到呢旺上?.......所以干脆剃了個光頭。

因為目前還是停留在內(nèi)涵段子項目的系統(tǒng)架構(gòu)部分糖埋,而且每個項目肯定離不開Activity宣吱,在Activity的onCreate()方法里面基本都是:設(shè)置布局 ->初始化頭部 -> 初始化界面 -> 初始化數(shù)據(jù)。如果按照這么個流程是不是剛好符合我們模板設(shè)計模式瞳别。

/**
 * Created by Darren on 2017/2/8.
 * Email: 240336124@qq.com
 * Description: 整個應(yīng)用的BaseActivity
 */
public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView();
        initTitle();
        initView();
        initData();
    }

    // 初始化數(shù)據(jù)
    protected abstract void initData();

    // 初始化界面
    protected abstract void initView();

    // 設(shè)置界面視圖
    protected abstract void setContentView();

    // 初始化頭部
    protected abstract void initTitle();
}

以后我們每次新建Activity就不會直接繼承自 AppCompatActivity 而是我們自己的BaseActivity就會自動要我們復(fù)寫B(tài)aseActivity里面的幾個抽象方法凌节,我們在方法里面寫對應(yīng)的代碼钦听。

/**
 * Created by Darren on 2017/2/8.
 * Email: 240336124@qq.com
 * Description: 主頁面MainActivity
 */
public class MainActivity extends BaseActivity {

    @Override
    protected void initData() {

    }

    @Override
    protected void initView() {

    }

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void initTitle() {

    }
}

這也沒見到什么好處啊~好處我待會再來說,我們先對BaseActivity來擴展一下倍奢,一些通用的流程和功能其實可以放在BaseActivity里面朴上,比如前幾次所分享的自己動手打造一套IOC注解框架、啟動Activity...

/**
 * Created by Darren on 2017/2/8.
 * Email: 240336124@qq.com
 * Description: 整個應(yīng)用的BaseActivity
 */
public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView();
        // IOC注解注入
        ViewUtils.inject(this);
        initTitle();
        initView();
        initData();
    }

    // 初始化數(shù)據(jù)
    protected abstract void initData();

    // 初始化界面
    protected abstract void initView();

    // 設(shè)置界面視圖
    protected abstract void setContentView();

    // 初始化頭部
    protected abstract void initTitle();


    /**
     * 啟動一個Activity
     * @param activity  需要啟動的Activity的Class
     */
    public void startActivity(Class<? extends Activity> activity) {
        Intent intent = new Intent(this,activity);
        startActivity(intent);
    }

    /**
     *  findViewById 不需要再去強轉(zhuǎn)
     */
    public <T extends View> T viewById(@IdRes int resId) {
        return (T) super.findViewById(resId);
    }
}

接下來我們來說一下好處卒煞,模板設(shè)計模式的好處就不說了網(wǎng)上太多了痪宰,我們就只說在真正的開發(fā)中這么做的好處

  1. 在實際的開發(fā)過程中我們往往不是一個人在開發(fā),如果把直接在onCreate()里面寫很多東西顯然不行畔裕,但是如果寫一些方法衣撬,那么每個成員所寫的方法都會不同,何況有些哥們英語估計沒過四級扮饶,單詞都寫錯具练;
  2. 直接繼承自BaseActivity會自動提示覆蓋方法,更加有利于開發(fā)或者代碼的閱讀甜无,每個方法各盡其職只干自己該干的事扛点,沒事干就歇著;
  3. 我們可以把通用的流程或者是某些方法放到BaseActivity岂丘,這樣又節(jié)省代碼又方便但是不是亂七八糟的放一大堆陵究,細節(jié)會有很多具體可以看視頻講解;
  4. 我們中間肯定還需要邏輯層的BaseActivity奥帘,這樣做其實有利于后期的版本迭代铜邮、層次抽離和代碼重構(gòu)...

所有分享大綱:2017Android進階之路與你同行

視頻講解地址:http://pan.baidu.com/s/1nuNA0Vv

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市寨蹋,隨后出現(xiàn)的幾起案子松蒜,更是在濱河造成了極大的恐慌,老刑警劉巖已旧,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秸苗,死亡現(xiàn)場離奇詭異,居然都是意外死亡评姨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門萤晴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吐句,“玉大人,你說我怎么就攤上這事店读∴率啵” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵屯断,是天一觀的道長文虏。 經(jīng)常有香客問我侣诺,道長,這世上最難降的妖魔是什么氧秘? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任年鸳,我火速辦了婚禮,結(jié)果婚禮上丸相,老公的妹妹穿的比我還像新娘搔确。我一直安慰自己,他們只是感情好灭忠,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布膳算。 她就那樣靜靜地躺著,像睡著了一般弛作。 火紅的嫁衣襯著肌膚如雪涕蜂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天映琳,我揣著相機與錄音机隙,去河邊找鬼。 笑死刊头,一個胖子當著我的面吹牛黍瞧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播原杂,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼印颤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了穿肄?” 一聲冷哼從身側(cè)響起年局,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咸产,沒想到半個月后矢否,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡脑溢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年僵朗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屑彻。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡验庙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出社牲,到底是詐尸還是另有隱情粪薛,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布搏恤,位于F島的核電站违寿,受9級特大地震影響湃交,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜藤巢,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一搞莺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧菌瘪,春花似錦腮敌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至录淡,卻和暖如春捌木,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫉戚。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工刨裆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彬檀。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓帆啃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窍帝。 傳聞我的和親對象是個殘疾皇子努潘,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容