介绍:该模式是为了将构建复杂对象的过程和它的部件解耦,使得构建过程和部件的表示隔离开来。
定义:将一个复杂的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
使用场景:
- 相同的方法,不同的执行顺序,产生不同的事件结果时。
- 多个部件或者零件,都可以装配到一个对象中,但是产生的运行结果又不相同时。
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候使用建造者模式非常合适。
- 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值。
UML类图
Product产品类,产品的抽象类
Builder抽象Builder类,规范产品的组建,一般是由子类实现具体的组建过程。
ConcreteBuilder具体的Builder类。
Director统一组装过程
Builder模式的简单实现
计算机抽象类,即Product角色
// 计算机抽象类,即Product角色
public abstract class Computer {
protected String board; // 主板
protected String display; // 显示器
protected String os; // 操作系统
public void setBoard(String board) {
this.board = board;
}
public void setDisplay(String display) {
this.display = display;
}
public abstract void setOs();
@Override
public String toString() {
return "Computer{" +
"board='" + board + '\'' +
", display='" + display + '\'' +
", os='" + os + '\'' +
'}';
}
}
具体的Computer类,Macbook
// 具体的Computer类,Macbook
public class Macbook extends Computer {
@Override
public void setOs() {
os = "Mac os X 10.10";
}
}
抽象Builder类
// 抽象Builder类
public abstract class Builder {
// 设置主板
public abstract void buildBoard(String board);
// 设置显示器
public abstract void buildDisplay(String display);
// 设置操作系统
public abstract void buildOS();
// 创建Computer
public abstract Computer create();
}
具体的Builder类,MacbookBuilder
// 具体的Builder类,MacbookBuilder
public class MacbookBuilder extends Builder {
private Computer computer = new Macbook();
@Override
public void buildBoard(String board) {
computer.setBoard(board);
}
@Override
public void buildDisplay(String display) {
computer.setDisplay(display);
}
@Override
public void buildOS() {
computer.setOs();
}
@Override
public Computer create() {
return computer;
}
}
Director类,负责构造Computer
// Director类,负责构造Computer
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct(String board, String display) {
builder.buildBoard(board);
builder.buildDisplay(display);
builder.buildOS();
}
}
测试代码
public class Test {
public static void main(String[] args) {
Builder builder = new MacbookBuilder();
Director director = new Director(builder);
director.construct("英特尔主板", "Retina显示器");
System.out.println("Computer info: " + builder.create().toString());
}
}
// Computer info: Computer{board='英特尔主板', display='Retina显示器', os='Mac os X 10.10'}
Director封装了构建复杂产品对象的过程,对外隐藏构建细节。但是在实际开发中,Director会被省略。直接使用一个Builder来进行对象的组装,这个Builder为链式调用,每个setter方法都返回自身,也就是return this。
Android源码中的Builder模式实现
在Android源码中,最常用到的Builder模式就是AlertDialog.Builder,使用该Builder来构建复杂的AlertDialog对象。
AlertDialog对话框的基本使用:
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(R.mipmap.ic_launcher);
builder.setTitle("标题");
builder.setMessage("正文内容");
builder.setPositiveButton("Positive", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setNegativeButton("Negative", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setNeutralButton("Neutral", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
// 显示对话框
builder.create().show();
}
接下来看看AlertDialog系统源码
public class AlertDialog extends AppCompatDialog implements DialogInterface {
// AlertController 接收Builder成员变量 P 中的各个参数
final AlertController mAlert;
// 构造函数
protected AlertDialog(@NonNull Context context) {
this(context, 0);
}
// 构造AlertDialog
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
// 构造AlertController
mAlert = new AlertController(getContext(), this, getWindow());
}
// 实际上调用的是mAlert的setTitle方法
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
// 实际上调用的是mAlert的setCustomTitle方法
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
// 省略...
//************* Builder为AlertDialog的内部类 ******************
public static class Builder {
// 1. 存储AlertDialog的各个参数,如:title、message、icon等
private final AlertController.AlertParams P;
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
// 2. 设置各个参数
public Builder setTitle(@Nullable CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(@Nullable CharSequence message) {
P.mMessage = message;
return this;
}
// 3. 构建AlertDialog,传递参数
public AlertDialog create() {
// 4. 调用new AlertDialog构造对象,并且将参数传递给AlertDialog
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
// 5. 将P中的参数应用到dialog中的mAlert对象中
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
}
在调用Builder类的create函数时会创建AlertDialog,并将Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中,即 P.apply(dialog.mAlert)
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId != 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId != 0) {
dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null || mPositiveButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null, mPositiveButtonIcon);
}
if (mNegativeButtonText != null || mNegativeButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null, mNegativeButtonIcon);
}
if (mNeutralButtonText != null || mNeutralButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null, mNeutralButtonIcon);
}
// 如果设置了mItems,则表示是单选或者多选列表,此时创建一个ListView
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
// 将mView设置给Dialog
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
}
当获取到AlertDialog对象后,通过show函数就可以显示对话框。在看看Dialog的show函数:
// Dialog.java
public void show() {
// 已经是显示状态,则return
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
// 1. onCreate 调用
if (!mCreated) {
dispatchOnCreate(null);
} else {
// Fill the DecorView in on any configuration changes that
// may have occured while it was removed from the WindowManager.
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
// 2. onStart 调用
onStart();
// 3. 获取DecorView
mDecor = mWindow.getDecorView();
// ...
// 4. 获取布局参数
WindowManager.LayoutParams l = mWindow.getAttributes();
boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
restoreSoftInputMode = true;
}
// 5. 将mDecor添加到WindowManager中
mWindowManager.addView(mDecor, l);
mShowing = true;
// 发送一个显示Dialog的消息
sendShowMessage();
}
在show函数主要做了以下事情:
- 通过dispatchOnCreate函数来调用AlertDialog的onCreate函数
- 然后调用AlertDialog的onStart函数
- 最后将Dialog的DecorView添加到WindowManager中
一般AlertDialog的内容视图构建应该在onCreate函数中,我们看看是不是:
// AlertDialog.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 调用了AlertController的installContent方法
mAlert.installContent();
}
在onCreate函数中主要调用了AlertController的installContent方法,Dialog中的onCreate函数只是一个空实现,视图的安装就在installContent函数中:
public void installContent() {
final int contentView = selectContentView();
// 设置窗口的内容视图布局
mDialog.setContentView(contentView);
// 初始化AlertDialog其他子视图的内容
setupView();
}
installContent函数中调用了Window对象的setContentView,这个setContentView与Activity中的一摸一样。而这个布局就是mAlertDialogLayout,这个值在AlertController的构造函数中进行初始化。
public AlertController(Context context, AppCompatDialog di, Window window) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,
R.attr.alertDialogStyle, 0);
// AlertDialog的布局id
mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0);
//....
a.recycle();
}
AlertDialog也可以通过setView传入内容视图,AlertDialog预留了一个customPanel区域用来显示用户自定义的内容视图。来看下setupView函数:
private void setupView() {
final View parentPanel = mWindow.findViewById(R.id.parentPanel);
final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
// 自定义内容区域
final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
// 如果设置了内容区域,那么将它显示在customPanel的custom布局里面
setupCustomContent(customPanel);
final View customTopPanel = customPanel.findViewById(R.id.topPanel);
final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
// Resolve the correct panels and remove the defaults, if needed.
final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
// 设置内容区域
setupContent(contentPanel);
// 设置按钮区域
setupButtons(buttonPanel);
// 设置标题区域
setupTitle(topPanel);
final boolean hasCustomPanel = customPanel != null
&& customPanel.getVisibility() != View.GONE;
final boolean hasTopPanel = topPanel != null
&& topPanel.getVisibility() != View.GONE;
final boolean hasButtonPanel = buttonPanel != null
&& buttonPanel.getVisibility() != View.GONE;
// Only display the text spacer if we don't have buttons.
if (!hasButtonPanel) {
if (contentPanel != null) {
final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
if (spacer != null) {
spacer.setVisibility(View.VISIBLE);
}
}
}
if (hasTopPanel) {
// Only clip scrolling content to padding if we have a title.
if (mScrollView != null) {
mScrollView.setClipToPadding(true);
}
// Only show the divider if we have a title.
View divider = null;
if (mMessage != null || mListView != null) {
divider = topPanel.findViewById(R.id.titleDividerNoCustom);
}
if (divider != null) {
divider.setVisibility(View.VISIBLE);
}
} else {
if (contentPanel != null) {
final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
if (spacer != null) {
spacer.setVisibility(View.VISIBLE);
}
}
}
if (mListView instanceof RecycleListView) {
((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel);
}
// Update scroll indicators as needed.
if (!hasCustomPanel) {
final View content = mListView != null ? mListView : mScrollView;
if (content != null) {
final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0)
| (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0);
setScrollIndicators(contentPanel, content, indicators,
ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);
}
}
final ListView listView = mListView;
if (listView != null && mAdapter != null) {
listView.setAdapter(mAdapter);
final int checkedItem = mCheckedItem;
if (checkedItem > -1) {
listView.setItemChecked(checkedItem, true);
listView.setSelection(checkedItem);
}
}
}
深入了解WindowManager
经过 Dialog 的源码分析,已经知道 Dialog 的内容视图最终是通过 WindowManager 显示到手机屏幕上的。其实不仅是 Dialog ,所有需要显示到屏幕上的内容(包括 Activity)都是通过 WindowManager 操作的,它是一个非常重要的子系统,也就是WMS(WindowManagerService)。
获取 WindowManager 通过Context的getSystemService方法。各种系统服务会注册到SystemServiceRegistry中的一个map容器中,然后通过该服务的字符串来获取,WindowManager也是其中一个服务。
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
从这段代码最后一行可知,WindowManager 在Java层的具体实现类是WindowManagerImpl。接下来看看Dialog是怎么获取WindowManagerImpl的:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
// 1. 获取WindowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
// 2. 设置Window的回调
w.setCallback(this);
// 3. 设置Window的WindowManager对象
w.setWindowManager(mWindowManager, null, null);
}
通过Window对象的setWindowManager函数将Window对象与WindowManager建立了联系。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 调用了createLocalWindowManager函数
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
调用的是WindowManagerImpl中的createLocalWindowManager函数
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
这个方法很简单,创建一个WindowManagerImpl对象,与SystemServiceRegistry中注册的WindowManagerImpl不同的是,多了一个parentWindow参数。说明这个WindowManagerImpl对象是与具体的Window关联的。
接下俩看看WindowManagerImpl核心代码:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
// Window对象
private final Window mParentWindow;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
// ...
}
WindowManagerImpl只是一个代理类,真实实现的是WindowManagerGlobal类。调用addView方法将View显示到屏幕上,来分析下WindowManagerGlobal中的addView实现
// WindowManagerGlobal.java
// 将View添加到WindowManager中
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 1.构建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
// 2.给View设置布局参数
view.setLayoutParams(wparams);
// 3.将View添加到View列表中
mViews.add(view);
// 4.将ViewRootImpl对象root添加到mRoots列表中
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 5. 调用ViewRootImpl的setView方法将View显示到手机窗口中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
}
上面分析到的ViewRootImpl,很多人已经非常熟悉了。它作为native层与java层View系统通信的桥梁,如熟知的 performTraversals 函数就是收到系统绘制View的消息后,通过调用视图树的各个节点的performMeasure、performLayout、performDraw方法来绘制整课视图树。
来看下ViewRootImpl的构造函数
public ViewRootImpl(Context context, Display display) {
mContext = context;
// 1.获取Window Session,也就是与WindowManagerService建立连接
mWindowSession = WindowManagerGlobal.getWindowSession();
// ...
// 保存当前线程,更新UI的线程只能是创建ViewRootImpl时的线程
// 我们在应用开发中,如果在子线程中更新UI会抛出异常,但并不是因为只有UI线程才能更新UI
// 而是因为ViewRootImpl是在UI线程中创建的
mThread = Thread.currentThread()
}
其中WindowManagerGlobal.getWindowSession()就是与Native层建立通信的地方。
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
// 1.获取WindowManagerService
IWindowManager windowManager = getWindowManagerService();
// 2.与WindowManagerService建立一个Session
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
// 获取WindowManagerService
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
在getWindowSession函数中,Fragework层先通过getWindowManagerService函数获取到IWindowManager对象,在函数中通过ServiceManager.getService函数获取WMS,并将WMS转为IWindowManager类型。来看下ServiceManager.getService函数代码:
// ServiceManager.java
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(rawGetService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
该函数返回的是IBinder对象,说明Android Fragework与WMS之间通过Binder机制进行通信的。
此时Dialog或者Activity的View并不能显示在手机屏幕上,WMS 只是负责管理手机屏幕上View的z-order,也就是说 WMS 管理当前状态下哪个View应该在最上层显示。MWS 管理的并不是 Window,而是View,只不过它管理的是属于某个Window下的View。
与WMS 建立Session后就调用ViewRootImpl的setView方法,该方法会向 WMS 发起显示Dialog或者Activity中的DecorView请求
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
// 1.请求布局
requestLayout();
try {
// 2.向 WMS 发送请求
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,mTempInsets);
}
}
}
在来看看requestLayout函数
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); // 发送执行doTraversal函数
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 执行performTraversals函数
performTraversals();
}
}
执行performTraversals函数,触发整个视图树的绘制操作。
private void performTraversals() {
// 1. 获取Surface对象,用于图形绘制
// 2. 测量整个视图树的各个View的大小,performMeasure函数
// 3. 布局整个视图树,performLayout函数
// 4. 绘制整棵视图树,performDraw函数
}
在performDraw中,Framework会获取到图形绘制表面Surface对象,然后获取它的可绘制区域,也就是Canvas对象,然后Framework在这个Canvas对象上绘制。
private void performDraw() {
// ...
try {
// 调用绘制函数
boolean canUseAsync = draw(fullRedrawNeeded);
}
// ...
}
private boolean draw(boolean fullRedrawNeeded) {
// 1. 获取绘制表面
Surface surface = mSurface;
// 2. 绘制表面需要更新
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
// 3. 使用GPU绘制,也就是硬件加速
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
// 使用硬件渲染器绘制
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// 4. 使用CPU绘制图形
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
}
来看下CPU绘制的代码
// 使用CPU绘制图形
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
// Draw with software renderer.
final Canvas canvas;
try {
dirty.offset(-dirtyXOffset, -dirtyYOffset);
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
// 1. 获取知道区域的Canvas对象,用于Framework层绘制
canvas = mSurface.lockCanvas(dirty);
}
try {
// 2. 从DecorView开始绘制,也就是整个Window的根视图,这会引起整棵树的重绘
mView.draw(canvas);
} finally {
try {
// 3. 释放Canvas锁,然后通知SurfaceFlinger更新这块区域
surface.unlockCanvasAndPost(canvas);
}
}
return true;
}
如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。