[关闭]
@Beeder 2017-07-06T10:11:48.000000Z 字数 15496 阅读 894

Android源码设计模式 学习笔记(三) Builder模式

android



第三章 Builder模式

3.1 定义

    将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

3.2 使用场景

(1)相同的方法,不同的执行顺序,产生不同的事件结果时。
(2)多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时。
(3)产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个使用建造者模式非常适合。
(4)当初始化一个对象特别复杂时,如参数多,且很多参数有默认值。

3.2 Builder模式 的UML类图

UML类图如图3-1所示。
角色介绍:

贴图库

3.3 简单实现

  1. public interface Builder {
  2.   //创建部件A  比如创建汽车车轮
  3.   void buildPartA();
  4.   //创建部件B 比如创建汽车方向盘
  5.   void buildPartB();
  6.   //创建部件C 比如创建汽车发动机
  7.   void buildPartC();
  8.   //返回最后组装成品结果 (返回最后装配好的汽车)
  9.   Product getResult();
  10. }
  11. public class ConcreteBuilder implements Builder {
  12.   Part partA, partB, partC;
  13.   public void buildPartA() {
  14.     //这里是具体如何构建partA的代码
  15.   };
  16.   public void buildPartB() {
  17.     //这里是具体如何构建partB的代码
  18.   };
  19.    public void buildPartC() {
  20.     //这里是具体如何构建partB的代码
  21.   };
  22.    public Product getResult() {
  23.     //返回最后组装成品结果
  24.   };
  25. }
  26. //Director 类,负责制造
  27. public class Director {
  28.   private Builder builder;
  29.   public Director( Builder builder ) {
  30.     this.builder = builder;
  31.   }
  32.   // 将部件partA partB partC最后组成复杂对象
  33.   //这里是将车轮 方向盘和发动机组装成汽车的过程
  34.   public void construct() {
  35.     builder.buildPartA();
  36.     builder.buildPartB();
  37.     builder.buildPartC();
  38.   }
  39. }
  40. public interface Product { }//产品
  41. public interface Part { }//部件
  42. //调用
  43. ConcreteBuilder builder = new ConcreteBuilder();
  44. Director director = new Director(builder);
  45. director.construct();
  46. Product product = builder.getResult();

3.4 总结

通常一个类拥有多个角色,将构造函数、字段私有化,把Builder写成内部类,通过builder类的setter方法设置私有函数和方法,如此封装用户就只能通过Builder对象设置属性了。
简单实例如下:

  1. //customView类
  2. public class customView {
  3. //创建静态config对象
  4. private int a;
  5. private int b;
  6. //私有构造方法
  7. private customView(){
  8. }
  9. //Builder内部类
  10. public static class Builder{
  11. int a;
  12. int b;
  13. //setter方法,设置config对象参数
  14. public Builder setA(int a){
  15. this.a = a;
  16. return this;
  17. }
  18. public Builder setB(int b){
  19. this.b = b;
  20. return this;
  21. }
  22. void applyConfig(customView view){
  23. view.a=this.a;
  24. view.b=this.b;
  25. }
  26. //返回view
  27. public customView create(){
  28. customView view = new customView();
  29. applyConfig(view);
  30. return view;
  31. }
  32. }
  33. }

用户调用如下:

  1. customView view = new customView.Builder()
  2. .setA(0)
  3. .setB(0)
  4. .create();

3.4 Android源码中的Builder模式

3.4.1 AlertDialog.Builder模式,通过Builder对象组装

在开发过程中,我们经常用到AlertDialog,AlertDialog.Builder就是源码中最常用到的Builder模式,具体示例如下:

  1. private void showDialogs(Context context){
  2. AlertDialog.Builder builder= new AlertDialog.Builder(context);
  3. builder.setIcon(R.drawable.ic_launcher);
  4. builder.setTitle("title");
  5. builder.setMessage("message");
  6. builder.setPositiveButton("Button", new DialogInterface.OnClickListener() {
  7. @Override
  8. public void onClick(DialogInterface dialog, int whichButton) {
  9. setTitle("点击了对话框上的Button");
  10. }
  11. });
  12. builder.create().show();
  13. }

从类名就可以看出这就是一个Builder模式,通过Builder对象组装Dialog的各个部分,如Icon、Title、Message等,将Dialog的构造和表示进行分离。相关源码如下:

  1. //路径:frameworks/base/core/java/android/app/AlertDialog.java
  2. public class AlertDialog extends Dialog implements DialogInterface {
  3. //AlertController 接收Builder成员变量p中的各个参数 实现类
  4. private AlertController mAlert;
  5. //构造函数
  6. protected AlertDialog(Context context, @StyleRes int themeResId) {
  7. this(context, themeResId, true);
  8. }
  9. //构造AlertDialog
  10. AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
  11. super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,createContextThemeWrapper);
  12. mWindow.alwaysReadCloseOnTouchAttr();
  13. //构造AlertController
  14. mAlert = new AlertController(getContext(), this, getWindow());
  15. }
  16. //实际上调用的是mAlert的setTitle方法
  17. @Override
  18. public void setTitle(CharSequence title) {
  19. super.setTitle(title);
  20. mAlert.setTitle(title);
  21. }
  22. //实际上调用的是mAlert的setCustomTitle方法
  23. public void setCustomTitle(View customTitleView) {
  24. mAlert的.setCustomTitle(customTitleView);
  25. }
  26. public void setMessage(CharSequence message) {
  27. mAlert.setMessage(message);
  28. }
  29. //省略
  30. //AlertDialog的内部类
  31. public static class Builder {
  32. //1、储存AlertDialog的各个参数,Title、message、icon等
  33. private final AlertController.AlertParams P;
  34. //属性省略
  35. public Builder(Context context) {
  36. this(context, resolveDialogTheme(context, 0));
  37. }
  38. public Builder(Context context, int themeResId) {
  39. P = new AlertController.AlertParams(new ContextThemeWrapper(
  40. context, resolveDialogTheme(context, themeResId)));
  41. }
  42. //Builder的其他代码省略
  43. //2、设置各种参数
  44. public Builder setTitle(CharSequence title) {
  45. P.mTitle = title;
  46. return this;
  47. }
  48. public Builder setMessage(@StringRes int messageId) {
  49. P.mMessage = P.mContext.getText(messageId);
  50. return this;
  51. }
  52. public Builder setIcon(Drawable icon) {
  53. P.mIcon = icon;
  54. return this;
  55. }
  56. //3、构建AlertDialog,传递参数
  57. public AlertDialog create() {
  58. // 4、new AlertDialog对象,并且将参数传递给AlertDialog实例dialog
  59. // Context has already been wrapped with the appropriate theme.
  60. final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
  61. //5、将p中的参数应用到dialog中的mAlert对象中去
  62. P.apply(dialog.mAlert);
  63. dialog.setCancelable(P.mCancelable);
  64. if (P.mCancelable) {
  65. dialog.setCanceledOnTouchOutside(true);
  66. }
  67. dialog.setOnCancelListener(P.mOnCancelListener);
  68. dialog.setOnDismissListener(P.mOnDismissListener);
  69. if (P.mOnKeyListener != null) {
  70. dialog.setOnKeyListener(P.mOnKeyListener);
  71. }
  72. return dialog;
  73. }
  74. }
  75. }

Builder类可以设置AlertDialog中的title、message、button等参数,这些参数都存储在类型为AlertController.AlertParams的变量p中,AlertController.AlertParams中包含了与AlertDialog识图中对应的成员变量。
在调用Builder类的create函数时会创建AlertDialog,并将Builder成员变量p中保存的参数应用到AlertDialog的nAlert对象中,即p.apply(dialog.mAlert)代码段。
apply函数实现如下:

  1. //代码:frameworks/base/core/java/com/android/internal/app/AlertController.java
  2. public class AlertController {
  3. //省略
  4. public static class AlertParams {
  5. public void apply(AlertController dialog) {
  6. if (mCustomTitleView != null) {
  7. dialog.setCustomTitle(mCustomTitleView);
  8. } else {
  9. if (mTitle != null) {
  10. dialog.setTitle(mTitle);
  11. }
  12. if (mIcon != null) {
  13. dialog.setIcon(mIcon);
  14. }
  15. if (mIconId != 0) {
  16. dialog.setIcon(mIconId);
  17. }
  18. if (mIconAttrId != 0) {
  19. dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
  20. }
  21. }
  22. if (mMessage != null) {
  23. dialog.setMessage(mMessage);
  24. }
  25. if (mPositiveButtonText != null) {
  26. dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
  27. mPositiveButtonListener, null);
  28. }
  29. if (mNegativeButtonText != null) {
  30. dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
  31. mNegativeButtonListener, null);
  32. }
  33. if (mNeutralButtonText != null) {
  34. dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
  35. mNeutralButtonListener, null);
  36. }
  37. if (mForceInverseBackground) {
  38. dialog.setInverseBackgroundForced(true);
  39. }
  40. //如果设置了mItems,则表示是单选或多选列表,此时创建一个ListView
  41. // For a list, the client can either supply an array of items or an
  42. // adapter or a cursor
  43. if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
  44. createListView(dialog);
  45. }
  46. //将mView设置给Dialog
  47. if (mView != null) {
  48. if (mViewSpacingSpecified) {
  49. dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
  50. mViewSpacingBottom);
  51. } else {
  52. dialog.setView(mView);
  53. }
  54. } else if (mViewLayoutResId != 0) {
  55. dialog.setView(mViewLayoutResId);
  56. }
  57. }
  58. }
  59. }

在apply函数中,只是将AlertParams参数设置到AlertController中。例如,将标题设置到Dialog对应的标题识图中,将Message设置到内容视图中等。当我们获取到AlertDialog对象后,通过show函数就可以显示这个对话框。
AlertDialog中相关代码如下:

  1. //路径:frameworks/base/core/java/android/app/AlertDialog.java
  2. public class AlertDialog extends Dialog implements DialogInterface {
  3. public static class Builder {
  4. public AlertDialog create() {
  5. // Context has already been wrapped with the appropriate theme.
  6. final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
  7. //将参数设置到AlertController对象dialog中
  8. P.apply(dialog.mAlert);
  9. dialog.setCancelable(P.mCancelable);
  10. if (P.mCancelable) {
  11. dialog.setCanceledOnTouchOutside(true);
  12. }
  13. dialog.setOnCancelListener(P.mOnCancelListener);
  14. dialog.setOnDismissListener(P.mOnDismissListener);
  15. if (P.mOnKeyListener != null) {
  16. dialog.setOnKeyListener(P.mOnKeyListener);
  17. }
  18. return dialog;
  19. }
  20. /**
  21. * Creates an {@link AlertDialog} with the arguments supplied to this
  22. * builder and immediately displays the dialog.
  23. * <p>
  24. * Calling this method is functionally identical to:
  25. * <pre>
  26. * AlertDialog dialog = builder.create();
  27. * dialog.show();
  28. * </pre>
  29. */
  30. public AlertDialog show() {
  31. final AlertDialog dialog = create();
  32. dialog.show();
  33. return dialog;
  34. }
  35. }
  36. }

我们再看看Dialog的show函数(该函数在Dialog类中):

  1. //路径:frameworks/base/core/java/android/app/Dialog.java
  2. /**
  3. * Start the dialog and display it on screen. The window is placed in the
  4. * application layer and opaque. Note that you should not override this
  5. * method to do initialization when the dialog is shown, instead implement
  6. * that in {@link #onStart}.
  7. */
  8. //显示Dialog
  9. public void show() {
  10. if (DBG) {
  11. Log.d(TAG, "show");
  12. }
  13. //已经是显示状态,则return
  14. if (mShowing) {
  15. if (mDecor != null) {
  16. if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
  17. mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
  18. }
  19. mDecor.setVisibility(View.VISIBLE);
  20. }
  21. return;
  22. }
  23. mCanceled = false;
  24. //1、onCreate调用
  25. if (!mCreated) {
  26. dispatchOnCreate(null);
  27. }
  28. //2、onStart
  29. onStart();
  30. //3、获取DecorView
  31. mDecor = mWindow.getDecorView();
  32. //省略
  33. //4、获取布局参数
  34. WindowManager.LayoutParams l = mWindow.getAttributes();
  35. if ((l.softInputMode
  36. & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
  37. WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
  38. nl.copyFrom(l);
  39. nl.softInputMode |=
  40. WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
  41. l = nl;
  42. }
  43. try {
  44. //5、将mDecor添加到mWindowManager中
  45. mWindowManager.addView(mDecor添加到, l);
  46. mShowing = true;
  47. //发送一个显示Dialog的消息
  48. sendShowMessage();
  49. } finally {
  50. }
  51. }

在show函数中主要做了如下几个事情:

很明显,这就是一系列的典型的生命周期函数。按照惯例,AlertDialog的内容视图构建按理应该在onCreate函数中,我们来看看是不是:

  1. //路径:frameworks/base/core/java/android/app/AlertDialog.java
  2. public class AlertDialog extends Dialog implements DialogInterface {
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. //调用了AlertController的installContent方法
  7. mAlert.installContent();
  8. }
  9. }

在onCreate函数中主要调用了AlertController的installContent方法,Dialog中的onCreate函数只是一个空实现而已,可以忽略他。那么AlertDialog的内容视图必然就在installContent函数中。

  1. //路径:frameworks/base/core/java/com/android/internal/app/AlertController.java
  2. public class AlertController {
  3. public void installContent() {
  4. /* We use a custom title so never request a window title */
  5. mWindow.requestFeature(Window.FEATURE_NO_TITLE);
  6. int contentView = selectContentView();
  7. mWindow.setContentView(contentView);
  8. setupView();
  9. setupDecor();
  10. }
  11. private int selectContentView() {
  12. if (mButtonPanelSideLayout == 0) {
  13. return mAlertDialogLayout;
  14. }
  15. if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
  16. return mButtonPanelSideLayout;
  17. }
  18. // TODO: use layout hint side for long messages/lists
  19. return mAlertDialogLayout;
  20. }
  21. }

installContent函数的代码,但极为重要,它调用了Window对象的setContentView,这个setContentView就与Activity中的一模一样,实际上Activity最终也是调用Window对象的setContentView函数。因此,这里就是设置AlertDialog的内容布局,这个布局就是mAlertDialogLayout的值。这个值在AlertController的构造函数中进行了初始化,具体代码如下:

  1. //路径:frameworks/base/core/java/com/android/internal/app/AlertController.java
  2. public class AlertController {
  3. public AlertController(Context context, DialogInterface di, Window window) {
  4. mContext = context;
  5. mDialogInterface = di;
  6. mWindow = window;
  7. mHandler = new ButtonHandler(di);
  8. final TypedArray a = context.obtainStyledAttributes(null,
  9. R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
  10. //AlertController的布局id,也就是alert_dialog.xml布局
  11. mAlertDialogLayout = a.getResourceId(
  12. R.styleable.AlertDialog_layout, R.layout.alert_dialog);
  13. mButtonPanelSideLayout = a.getResourceId(
  14. R.styleable.AlertDialog_buttonPanelSideLayout, 0);
  15. mListLayout = a.getResourceId(
  16. R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
  17. //省略
  18. a.recycle();
  19. }
  20. }

从AlertController的构造函数中可以看到,AlertDialog的布局资源就是select_dialog.xml这个布局,我们直接大致看他的结构如下:
贴图库
当通过Builder对象的setTitle、setMessage等方法设置具体内容时,就是将这些内容填充到对应的视图中。而AlertDialog也允许你通过setView传入内容视图,这个内容视图就是替换掉图中的内容视图(蓝色区域),AlertDialog预留了一个costomPanel区域用来显示用户自定义的内容视图。我们来看看setupView函数:

  1. public class AlertController {
  2. private void setupView() {
  3. final View parentPanel = mWindow.findViewById(R.id.parentPanel);
  4. final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
  5. //1、获取内容区域
  6. final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
  7. //1、获取按钮
  8. final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
  9. // Install custom content before setting up the title or buttons so
  10. // that we can handle panel overrides.
  11. final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
  12. //自定义内容视图区域
  13. setupCustomContent(customPanel);
  14. final View customTopPanel = customPanel.findViewById(R.id.topPanel);
  15. final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
  16. final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
  17. // Resolve the correct panels and remove the defaults, if needed.
  18. final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
  19. final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
  20. final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
  21. //2、初始化内容
  22. setupContent(contentPanel);
  23. setupButtons(buttonPanel);
  24. //2、初始化标题
  25. setupTitle(topPanel);
  26. //自定义视图可见性
  27. final boolean hasCustomPanel = customPanel != null
  28. && customPanel.getVisibility() != View.GONE;
  29. final boolean hasTopPanel = topPanel != null
  30. && topPanel.getVisibility() != View.GONE;
  31. final boolean hasButtonPanel = buttonPanel != null
  32. && buttonPanel.getVisibility() != View.GONE;
  33. // Only display the text spacer if we don't have buttons.
  34. if (!hasButtonPanel) {
  35. if (contentPanel != null) {
  36. final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
  37. if (spacer != null) {
  38. spacer.setVisibility(View.VISIBLE);
  39. }
  40. }
  41. mWindow.setCloseOnTouchOutsideIfNotSet(true);
  42. }
  43. if (hasTopPanel) {
  44. // Only clip scrolling content to padding if we have a title.
  45. if (mScrollView != null) {
  46. mScrollView.setClipToPadding(true);
  47. }
  48. // Only show the divider if we have a title.
  49. final View divider;
  50. if (mMessage != null || mListView != null || hasCustomPanel) {
  51. divider = topPanel.findViewById(R.id.titleDivider);
  52. } else {
  53. divider = topPanel.findViewById(R.id.titleDividerTop);
  54. }
  55. if (divider != null) {
  56. divider.setVisibility(View.VISIBLE);
  57. }
  58. }
  59. // Update scroll indicators as needed.
  60. if (!hasCustomPanel) {
  61. final View content = mListView != null ? mListView : mScrollView;
  62. if (content != null) {
  63. final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
  64. | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
  65. content.setScrollIndicators(indicators,
  66. View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
  67. }
  68. }
  69. final TypedArray a = mContext.obtainStyledAttributes(
  70. null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
  71. //设置背景
  72. setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
  73. hasTopPanel, hasCustomPanel, hasButtonPanel);
  74. a.recycle();
  75. }
  76. //自定义内容视图区域
  77. private void setupCustomContent(ViewGroup customPanel) {
  78. final View customView;
  79. //如果用户设置了内容视图,那么将它显示在customPanel的custom布局里面
  80. if (mView != null) {
  81. customView = mView;
  82. } else if (mViewLayoutResId != 0) {
  83. final LayoutInflater inflater = LayoutInflater.from(mContext);
  84. customView = inflater.inflate(mViewLayoutResId, customPanel, false);
  85. } else {
  86. customView = null;
  87. }
  88. final boolean hasCustomView = customView != null;
  89. if (!hasCustomView || !canTextInput(customView)) {
  90. mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
  91. WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
  92. }
  93. if (hasCustomView) {
  94. final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
  95. //显示用户设置的视图
  96. custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
  97. if (mViewSpacingSpecified) {
  98. custom.setPadding(
  99. mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
  100. }
  101. if (mListView != null) {
  102. ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
  103. }
  104. } else {
  105. customPanel.setVisibility(View.GONE);
  106. }
  107. }
  108. }

这个setupView顾名思义就是初始化AlertDialog布局中的各个部分,如标题区域、按钮区域、内容区域等,在该函数调用之后整个Dialog的视图内容全部设置完毕。而这些各区域的视图都属于mAlertDialogLayout布局中的子元素,Window对象又关联了mAlertDialogLayout的整个布局树,当调用完setupView之后整个视图树的数据都填充完毕。当用户调用了show函数时,WindosManager会将Window对象的DecorView(也就是mAlertDialogLayout对应的视图),添加到用户的窗口上,并且显示出来。至此,整个Dialog就会出现在用户的视野中了。





在AlertDialog的Builder模式中并没有看到Director角色的出现(其实在很多场景中,Android并没有按照《设计模式:可复用面向对象软件的基础》一书中描述的经典模式来实现,而是做了些修改,使得更易于使用)。这里的AlertDialog.Builder同时扮演了builder、ConcreteBuilder、Dirctor的角色,简化了Builder模式的设计。


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注