Android源碼分析之模板方法模式
模式的定義
定義一個操作中的演算法的架構,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
使用情境
1、多個子類有公有的方法,並且邏輯基本相同時。
2、重要、複雜的演算法,可以把核心演算法設計為模板方法,周邊的相關細節功能則由各個子類實現。
3、重構時,模板方法模式是一個經常使用的模式,把相同的代碼抽取到父類中,然後通過鉤子函數約束其行為。
UML類圖
角色介紹
AbstractClass : 抽象類別,定義了一套演算法架構。
ConcreteClass1 : 具體實作類別1;
ConcreteClass2: 具體實作類別2;
簡單樣本
模板方法實際上是封裝一個演算法架構,就像是一套模板一樣。而子類可以有不同的演算法實現,在架構不被修改的情況下實現演算法的替換。下面我們以開電腦這個動作來簡單示範一下模板方法。開電腦的整個過程都是相對穩定的,首先開啟電腦電源,電腦檢測自身狀態沒有問題時將進入作業系統,對使用者進行驗證之後即可登入電腦,下面我們使用模板方法來類比一下這個過程。
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("載入作業系統"); } protected void login() { System.out.println("小白的電腦無驗證,直接進入系統"); } /** * 啟動電腦方法, 步驟固定為開啟電源、系統檢查、載入作業系統、使用者登入。該方法為final, 防止演算法架構被覆寫. */ public final void startUp() { System.out.println("------ 開機 START ------"); powerOn(); checkHardware(); loadOS(); login(); System.out.println("------ 開機 END ------"); }}package com.dp.example.templatemethod;/** * 碼農的電腦 * * @author mrsimple */public class CoderComputer extends AbstractComputer { @Override protected void login() { System.out.println("碼農只需要進行使用者和密碼驗證就可以了"); }}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("進行指紋之別等複雜的使用者驗證"); }}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(); }}
輸出如下 :
------ 開機 START ------開啟電源硬體檢查載入作業系統碼農只需要進行使用者和密碼驗證就可以了------ 開機 END ------------ 開機 START ------開啟電源硬體檢查檢查硬體防火牆載入作業系統進行指紋之別等複雜的使用者驗證------ 開機 END ------
通過上面的例子可以看到,在startUp方法中有一些固定的步驟,依次為開啟電源、檢查硬體、載入系統、使用者登入四個步驟,這四個步驟是電腦開機過程中不會變動的四個過程。但是不同使用者的這幾個步驟的實現可能各不相同,因此他們可以用不同的實現。而startUp為final方法,即保證了演算法架構不能修改,具體演算法實現卻可以靈活改變。startUp中的這幾個演算法步驟我們可以稱為是一個套路,即可稱為模板方法。因此,模板方法是定義一個操作中的演算法的架構,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。 :
源碼分析
在Android中,使用了模板方法且為我們熟知的一個典型類就是AsyncTask了,關於AsyncTask的更詳細的分析請移步Android中AsyncTask的使用與源碼分析,我們這裡只分析在該類中使用的模板方法模式。
在使用AsyncTask時,我們都有知道耗時的方法要放在doInBackground(Params... params)中,在doInBackground之前如果還想做一些類似初始化的操作可以寫在onPreExecute方法中,當doInBackground方法執行完成後,會執行onPostExecute方法,而我們只需要構建AsyncTask對象,然後執行execute方法即可。我們可以看到,它整個執行過程其實是一個架構,具體的實現都需要子類來完成。而且它執行的演算法架構是固定的,調用execute後會依次執行onPreExecute,doInBackground,onPostExecute,當然你也可以通過onProgressUpdate來更新進度。我們可以簡單的理解為如的模式 :
下面我們看源碼,首先我們看執行非同步任務的入口, 即execute方法 :
public final AsyncTask execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } public final AsyncTask 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類型的方法)調用了executeOnExecutor方法,在該方法中會判斷該任務的狀態,如果不是PENDING狀態則拋出異常,這也解釋了為什麼AsyncTask只能被執行一次,因此如果該任務已經被執行過的話那麼它的狀態就會變成FINISHED。繼續往下看,我們看到在executeOnExecutor方法中首先執行了onPreExecute方法,並且該方法執行在UI線程。然後將params參數傳遞給了mWorker對象的mParams欄位,然後執行了exec.execute(mFuture)方法。
mWorker和mFuture又是什麼呢?其實mWorker只是實現了Callable介面,並添加了一個參數數組欄位,關於Callable和FutureTask的資料請參考Java中的Runnable、Callable、Future、FutureTask的區別與樣本,我們挨個來分析吧,跟蹤代碼我們可以看到,這兩個欄位都是在建構函式中初始化,
public AsyncTask() { mWorker = new WorkerRunnable() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask(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對象,會調用mWorker對象的call方法,並且將之返回給調用者。
關於AsyncTask的更詳細的分析請移步Android中AsyncTask的使用與源碼分析,我們這裡只分析模板方法模式。總之,call方法會在子線程中調用,而在call方法中又調用了doInBackground方法,因此doInBackground會執行在子線程。doInBackground會返回結果,最終通過postResult投遞給UI線程。
我們再看看postResult的實現 :
private Result postResult(Result result) { Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(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)發送給sHandler,sHandler類型為InternalHandler類型,當InternalHandler接到MESSAGE_POST_RESULT類型的訊息時就會調用result.mTask.finish(result.mData[0])方法。我們可以看到result為AsyncTaskResult類型,源碼如下 :
@SuppressWarnings({"RawUseOfParameterizedType"}) private static class AsyncTaskResult { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } }可以看到mTask就是AsyncTask對象,調用AsyncTask對象的finish方法時又調用了onPostExecute,這個時候整個執行過程就完成了。
總之,execute方法內部封裝了onPreExecute, doInBackground, onPostExecute這個演算法架構,使用者可以根據自己的需求來在覆寫這幾個方法,使得使用者可以很方便的使用非同步任務來完成耗時操作,又可以通過onPostExecute來完成更新UI線程的工作。
優點與缺點優點:1、封裝不變部分,擴充可變部分2、提取公用部分代碼,便於維護
缺點:模板方法會帶來代碼閱讀的難度,會讓心覺得難以理解。