一、模板方法模式介绍
在面向对象开发过程中,通常会遇到这样的一个问题,我们知道一个算法所需要的关键步骤,并确定了这些步骤的执行顺序,但是,某些步骤的具体实现是未知的,或者说某些步骤的实现是会随着环境的变化而改变的,例如,执行程序的流程大致如下:
(1)检查代码的正确性;
(2)链接相关的类库;
(3)编译相关代码;
(4)执行程序。
对于不同的程序设计语言,上述4个步骤都是不一样的,但是,他们的执行流程是固定的,这类问题的解决方案就是我们本章要讲的模板方法模式。
二、模板方法模式的定义
定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义改算法的某些特定步骤。
三、模板方法模式的使用场景
(1)多个子类有共有的方法,并且逻辑基本相同时。
(2)重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
(3)重构时,模板方法模式是一个经常使用的模板,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。
五、模板方法模式的简单示例
模板方法实际上是封装了一个固定流程,就像是一套执行模板一样,第一步该做什么,第二步该做什么都已经在抽象类中定义好了。而子类可以有不同的算法实现,在框架不被修改的情况下实现某些步骤的算法替换,下面以打开计算机这个动作来简单演示一下模板方法。打开计算机的整个过程都是相对固定的,首先启动计算机电源,计算机检测自身状态没有问题时将进入操作系统,对用户进行验证之后即可登录计算机,下面我们使用模板方法来模拟一下这个过程:
package com.liang.templatemethod;
//抽象的Computer
public abstract class AbstractComputer {
protected void powerOn() {
System.out.println("开启电源");
}
protected void checkHardware() {
System.out.println("硬件检查");
}
protected void loaOs() {
System.out.println("载入操作系统");
}
protected void login() {
System.out.println("小白的计算机无验证,直接进入系统");
}
/**
* 启动计算机方法,步骤固定为: 1开启电源 2系统检查 3加载操作系统 4用户登录。 该方法为final,防止算法框架被覆写
*/
public final void startUp() {
System.out.println("--开机 START--");
powerOn();
checkHardware();
loaOs();
login();
System.out.println("--开机 EDN--");
}
}
package com.liang.templatemethod;
//程序员的计算机
public class CoderComputer extends AbstractComputer {
@Override
protected void login() {
System.out.println("程序要只需要进行用户名和密码验证就可以了");
}
}
package com.liang.templatemethod;
//军用计算机
public class MilitaryComputer extends AbstractComputer {
@Override
protected void checkHardware() {
super.checkHardware();
System.out.println("检查硬件防火墙");
}
@Override
protected void login() {
System.out.println("进行指纹识别等复杂的用户验证");
}
}
package com.liang.templatemethod;
//测试代码
public class Test {
public static void main(String[] args) {
AbstractComputer computer = new CoderComputer();
computer.startUp();
computer = new MilitaryComputer();
computer.startUp();
}
}
输出结果如下:
通过上面的例子可以看到,在startUp方法中有一些固定的步骤,依次为开启电源、检查硬件、加载系统、用户登录4个步骤,这4个步骤是计算机开机过程中不会变动,如下图:
但是,不同用户的这几个步骤的实现可能各不相同,因此,子类需要覆写相应的方法来进行自定义处理,这里需要注意的是startUp为 final 方法,这样就保证了逻辑流程不能被子类修改,子类智能够改变某一步骤中的具体实现,这样就保证了这个逻辑流程的稳定性。startUp中的这几个算法步骤我们可以称为视一个套路,也称为模板方法,这也是该模式的由来。
六、Android 源码中的模板方法模式
在Android 中,AsyncTask 是比较常用的一个类型,这个类就使用了模板方法模式。关于AsyncTask 更详细的分析,请参考 Android中AsyncTask的使用与源码分析,我们这里只分析在该类中使用的模板方法模式。
在使用AsyncTask时,我们都知道把耗时的方法放在 doInBackground(Params… params) 中,在doInBackground 之前,如果还想做一些类似初始化的操作,可以把实现写在 onPreExecute 方法中,当 doInBackground 方法执行完成后,会执行onPostExecute 方法,而我们只需要构建 AsyncTask 对象,然后执行 execute 方法即可。我们可以看到,它整个执行过程其实是一个框架,具体的实现都需要子类来完成,而且它执行的算法框架是固定的,调用 execute 后会一次执行 onPreExecute、doInBackground、onPostExecute, 当然也可以通过 onProgressUpdate 来更新进度,我们可以简单地理解为如下图所示:
下面我们看源码,首先我们看执行异步任务的入口,即execute方法:
public final AsyncTask<Params, Progress, Result> 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 只能被执行一次,因为 AsyncTask 的 RUNNING 和 FINISHED 状态都会抛出异常,因此,每次使用 AsyncTask 时都需要重新创建一个对象。
继续往下看,在 executeOnExecutor 方法中首先执行了 onPreExecute 方法,因为 AsyncTask 的要求是需要在 UI 线程中调用 execute 方法,因此,onPreExecute 方法也执行在 UI 线程,然后将params参数传递给了 mWorker 对象的 mParams 字段,并且执行了 exec.execute(mFuture) 方法。
mWorker 和 mFuture 又是什么呢?其实 mWorker 只是实现了 Callable 接口,并添加了一个参数数组字段,我们分别来分析,通过跟踪代码可以看到,这两个字段都是在构造函数中初始化:
public AsyncTask() {
//构建一个Worker对象
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//调用 doInBackground,并且将结果传递出去
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
mFuture = new FutureTask(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
简单的说就是,mFuture 包装了这个 mWorker 对象,在这个 mFuture 对象的 run 函数中又会调用 mWorker 对象的 call 方法,在 call 方法中又调用了 doInBackgroud 函数。因为 mFuture 提交给了线程池来执行,所以,使得 doInBackgroud 执行在非 UI 线程。得到 doInBackgroud 的结果后,通过 postResult 传递结果给 UI 线程,我们再看看 postResult 的实现:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@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)发送给 Handler ,当InternalHandler 接到 MESSAGE_POST_RESULT 类型的消息时就会调用 result.mTask.finish(result.mData[0]) 方法。我们可以看到 result 为 AsyncTaskResult 类型,具体源码如下:
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 这个逻辑流程,用户可以根据自己的需求再覆写这几个方法,使得用户可以很方便地使用异步任务来完成耗时的操作及更新UI,这其实就是通过线程池来执行耗时的任务,得到结果之后,通过 Handler 将结果传递到 UI 线程来执行。
七、深度拓展
另一个比较好的模板方法示例就是 Activity 的生命周期函数,例如,Activity 从启动到显示到窗口中会经历如下过程:onCreate、onStart、onResume, 这就是一个典型的 Activity 启动流程, 也是一个模板方法的运用。
我们知道,在 Android 系统启动时,第一个启动起来的进程就是 zygote 进程,然后由 zygote 启动 SystemServer, 再后就是启动 ActivityManagerService、WindowManagerService 等系统核心服务,这些服务承载着整个 Android 系统与客户端程序交互的重担。zygote 除了启动系统服务与进程之外,普通的用户进程也由 zygote 进程 fork 而来,当一个应用进程启动起来后,就会加载用户在 AndroidManifest.xml 中配置的默认加载的 Activity, 此时加载的入口是 ActivityThread.main(String[] args) 方法,这个方法就是类似于 C 语言中的 main 方法,是整个应用程序的入口。
在ActivityThread.main(String[] args) 这个方法中,主要的功能就是创建 Application 和 Activity, 并且调用 Activity 的一些生命周期函数,如 onCreate、onResume 等,下面就从 ActivityThread.main(String[] args) 这个入口开始学习:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("" );
//1、创建 UI 线程的Looper
Looper.prepareMainLooper();
//2、创建 ActivityThread 对象,但它并不是线程,感觉是封装了 UI 线程消息循环与操作 Activity 生命周期的工具类
ActivityThread thread = new ActivityThread();
//3、调用 ActivityThread 的 attach 方法,这是核心方法,Application、Activity 的创建入口就在这里
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//4、启动 UI 线程消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
// UI 线程与 Application 关联
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
// 不是系统进程
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("" ,
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
// 5、获取 ActivityManagerService 实例
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
//6、注意这里,将 mAppThread 与 Application 进行管理
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
});
} else {
// Don't set application object here -- if the system crashes,
// we can't display an alert, we just want to die die die.
android.ddm.DdmHandleAppName.setAppName("system_process",
UserHandle.myUserId());
try {
mInstrumentation = new Instrumentation();
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
// add dropbox logging to libcore
DropBox.setReporter(new DropBoxReporter());
ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
synchronized (mResourcesManager) {
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
mResourcesManager.getConfiguration().getLocales());
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
mPendingConfiguration.isOtherSeqNewer(newConfig)) {
mPendingConfiguration = newConfig;
sendMessage(H.CONFIGURATION_CHANGED, newConfig);
}
}
}
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int level) {
}
});
}
从上述程序可以看出,在 ActivityThread.main 中主要的功能就是创建了 UI 线程消息循环,并且启动了消息循环。最重要的是创建了 ActivityThread, 并调用了 attach 方法。在 attach 方法中又调用了 ActivityManagerService 中的 attachApplication(mAppThread) 方法,这个才是我们的重点。mAppThread 是 ApplicationThread 类型,它也不是一个 Thread, 而是一个Binder, 负责与远程的 ActivityManagerService 进行交互。在了解 attachApplication 之前,我们先来了解一下 ApplicationThread 与 ActivityThread 的关联。