本文章讲解的内容是深入了解Android的View工作原理,建议对着示例项目阅读文章,示例项目链接如下:
ViewDemo
本文章分析的相关的源码基于Android SDK 29(Android 10.0,即Android Q)。
Android的窗口结构
先从Activity的startActivity系列方法开始说起,我选了一个常用的startActivitiy(Intent intent)方法,源码如下所示:
// Activity.java
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
看下startActivity(Intent intent, @Nullable Bundle options)方法,源码如下所示:
// Activity.java
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
// 如果形式参数options不为空,就调用startActivityForResult(@RequiresPermission Intent intent, int requestCode)方法
startActivityForResult(intent, -1, options);
} else {
// 如果形式参数options为空,就调用startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)方法,这里形式参数options为空,所以调用这个方法
startActivityForResult(intent, -1);
}
}
看下startActivityForResult(@RequiresPermission Intent intent, int requestCode)方法,源码如下所示:
// Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
}
startActivityForResult(@RequiresPermission Intent intent, int requestCode)方法最终也是调用startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)方法。
看下startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)方法,源码如下所示:
// Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
// 如果是第一次启动,成员变量mParent为空,否则不为空
if (mParent == null) {
// 如果是第一次启动,就执行以下逻辑
options = transferSpringboardActivityOptions(options);
// 调用Instrumentation的execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options)方法
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// 如果是请求一个结果(result),我们可以使Activity不可见,直到收到结果,在onCreate(Bundle savedInstanceState)方法或者onResume()期间设置此代码将在此期间隐藏Activity,以避免闪烁,只有在请求结果时才可以这样做,因为这样可以保证在Activity完成时我们将获得信息,无论它发生了什么
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
} else {
// 如果不是第一次启动,就执行以下逻辑
if (options != null) {
// 如果形式参数options不为空,就调用startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)方法
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// 如果形式参数option为空,就调用startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent, int requestCode)方法
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
看下startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent, int requestCode)方法,源码如下所示:
// Activity.java
public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
int requestCode) {
startActivityFromChild(child, intent, requestCode, null);
}
startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent, int requestCode)方法最终也是调用startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)方法。
看下tartActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)方法,源码如下所示:
// Activity.java
public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
int requestCode, @Nullable Bundle options) {
options = transferSpringboardActivityOptions(options);
// 调用Instrumentation的execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options)方法
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, child,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, child.mEmbeddedID, requestCode,
ar.getResultCode(), ar.getResultData());
}
cancelInputsAndStartExitTransition(options);
}
我们可以看到,其实最终就是调用Instrumentation的execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options)方法,源码如下所示:
// frameworks/base/core/java/android/app/Instrumentation.java
@UnsupportedAppUsage
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// 省略部分代码
try {
intent.migrateExtraStreamToClipData(who);
intent.prepareToLeaveProcess(who);
// 调用ActivityTaskManagerService的startActivity方法
int result = ActivityTaskManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
ActivityTaskManager.getService()方法得到的是IActivityTaskManager接口,它是通过进程间通讯(Inter-Process Communication,即IPC)调用的,IActivityTaskManager的服务端是ActivityTaskManagerService,所以最终是调用ActivityTaskManagerService的startActivity方法,后面的调用过程,这里就不再详细地讲解,最后调用的是ActivityThread类的handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent)方法,源码如下所示:
// frameworks/base/core/java/android/app/ActivityThread.java
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
// 省略部分代码
// 调用performLaunchActivity(ActivityClientRecord r, Intent customIntent)方法
final Activity a = performLaunchActivity(r, customIntent);
// 省略部分代码
// 返回Activity
return a;
}
看下performLaunchActivity(ActivityClientRecord r, Intent customIntent)方法,源码如下所示:
// frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 省略部分代码
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 反射创建Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
// 创建Application对象,它是个单例,一个进程只有一个Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// 省略部分代码
if (activity != null) {
// 省略部分代码
// 调用Activity的attach方法,这个方法会创建PhoneWindow,它是Window抽象类的子类
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
// 省略部分代码
// 调用Activity的onCreate方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
}
// 设为ON_CREATE状态
r.setState(ON_CREATE);
// updatePendingActivityConfiguration()方法的作用是从mActivities中读取数据以更新ActivityClientRecord,它在不同的线程中运行,所以要取ResourcesManager作为锁对象
synchronized (mResourcesManager) {
mActivities.put(r.token, r);
}
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
// 返回Activity
return activity;
}
在这个方法中,会调用Activity的attach方法,源码如下所示:
// frameworks/base/core/java/android/app/Activity.java
@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
// 省略部分代码
// 创建PhoneWindow对象,并且赋值给成员变量mWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 省略部分代码
}
可以看到,成员变量mWindow其实就是PhoneWindow对象。
然后我们接着前面的逻辑,接着就会调用ActivityThread类的handleStartActivity(ActivityClientRecord r, PendingTransactionActions pendingActions)方法,然后Activity类的onStart方法就会被调用,然后调用ActivityThread类的handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason)方法,源码如下所示:
// frameworks/base/core/java/android/app/ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// 省略部分代码
// 调用Activity的onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
// 省略部分代码
final Activity a = r.activity;
// 省略部分代码
if (r.window == null && !a.mFinished && willBeVisible) {
// 得到Window,Window是抽象类,其中,PhoneWindow类继承Window类,它是Window类的唯一子类
r.window = r.activity.getWindow();
// 得到DecorView
View decor = r.window.getDecorView();
// 将DecorView设为不可见
decor.setVisibility(View.INVISIBLE);
// 得到ViewManager,ViewManager是一个接口,WindowManger也是一个接口,它继承ViewManager接口,其中,WindowManagerImpl类实现ViewManager接口,这里的vm可以看作是WindowMangerImpl对象
ViewManager wm = a.getWindowManager();
// 省略部分代码
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 调用WindowManagerImpl类的addView(View view, ViewGroup.LayoutParams params)方法,执行绘制流程
wm.addView(decor, l);
} else {
// 省略部分代码
}
}
} else if (!willBeVisible) {
// 如果Window已经被添加,但是在恢复(resume)期间启动了另一个Activity,这样的话,就不要使Window可见
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
// 省略部分代码
}
View的绘制流程会在Activity的onResume方法之后执行。
最后调用WindowManagerImpl类的addView(View view, ViewGroup.LayoutParams params)方法,分别传入了DecorView和PhoneWindow的属性,源码如下所示:
// frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
@UnsupportedAppUsage
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
// 省略部分代码
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// 调用WindowManagerGlobal的addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow)方法
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// 省略部分代码
}
看下WindowManagerGlobal类的addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow)方法,源码如下所示:
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略部分代码
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 省略部分代码
// 创建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
// 设置DecorView的LayoutParams
view.setLayoutParams(wparams);
// 将DecorView添加到View的ArrayList中
mViews.add(view);
// 将ViewRootImpl添加到ViewRootImpl的ArrayList中
mRoots.add(root);
// 将DecorView的LayoutParams添加到WindowManager.LayoutParams的ArrayList中
mParams.add(wparams);
try {
// 调用ViewRootImpl的setView(View view, WindowManager.LayoutParams attrs, View panelParentView)方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// 抛出BadTokenException或者InvalidDisplayException
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
看下ViewRootImpl类的setView(View view, WindowManager.LayoutParams attrs, View panelParentView)方法,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// 如果成员变量mView为空,就将传进来的DecorView赋值给它
mView = view;
// 省略部分代码
// 调用requestLayout()方法
requestLayout();
// 省略部分代码
// 调用View的assignParent(ViewParent parent)方法
view.assignParent(this);
// 省略部分代码
}
}
}
先看下View类的assignParent(ViewParent parent)方法,源码如下所示:
// frameworks/base/core/java/android/view/View.java
@UnsupportedAppUsage
void assignParent(ViewParent parent) {
if (mParent == null) {
// 如果这个View还没有parent,就将传进来的ViewRootImpl赋值给成员变量mParent
mParent = parent;
} else if (parent == null) {
// 如果形式参数parent为空,就将成员变量mParent设为空
mParent = null;
} else {
// 如果这个View已经有parent,再次设置parent就会抛出RuntimeException异常
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
这个方法的作用是将ViewRootImpl和DecorView关联起来,这里传进来的是ViewRootImpl,ViewRootImpl实现ViewParent接口,每一个Activity的根布局是DecorView,DecorView的parentView是ViewRootImpl,所以在子元素中调用invalidate()方法这类的方法的时候,需要遍历找到parentView,最后都会调用ViewRootImpl相关的方法。
requestLayout()方法的作用是检查当前线程是不是创建ViewRootImpl所在的线程,如果是就通知View执行绘制流程,否则就抛出CalledFromWrongThreadException异常,将会在后面详细讲解。
总结一下:
Window是一个抽象类,它的具体实现是PhoneWindow,WindowManager是外界访问Window的入口,Window的实现是位于WindowManagerService中,WindowManger和WindowManagerService是通过进程间通信(Inter-Process Communication,简称IPC)交互的。
Window分为三种窗口类型:
- 应用窗口:对应于一个Activity,创建一个应用窗口只能在Activity内部完成,层级在1~99之间。
- 子窗口:必须依附于任何类型的父窗口,层级在1000~1999之间。
- 系统窗口:不需要对应于任何Activity,应用程序不能创建系统窗口,层级在2000~2999之间。
要注意的是,层级大的窗口会覆盖在层级小的窗口上。
setContentView(@LayoutRes int layoutResID)
我们看下一个经常使用的方法:setContentView(@LayoutRes int layoutResID)方法,要注意的是,我们的Activity类是继承AppCompatActivity类,源码如下所示:
// AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
// 调用AppCompatDelegate的setContentView(int resId)方法
getDelegate().setContentView(layoutResID);
}
看下getDelegate()方法,源码如下所示:
// AppCompatActivity.java
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
// 如果mDelegate为空,就调用AppCompatDelegate抽象类的create(@NonNull Activity activity, @Nullable AppCompatCallback callback)方法
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
Appcompat最开始是出现在com.android.support:appcompat-v7库中,它是为了让Android SDK 7(Android 2.1,即Android Eclair)以上的设备可以使用ActionBar,而在com.android.support:appcompat-v7:21以上的库,AppCompat可以为Android SDK 7(Android 2.1,即Android Eclair)以上的设备带来Material Color Palette、Widget着色、Toolbar等功能,并且用AppCompatActivity替代ActionBarActivity,在Android SDK 28(Android 9.0,即Android Pie)发布后,appcompat库都迁移到了AndroidX库,AndroidX库是Android Jetpack组件。
看下AppCompatDelegate抽象类的create(@NonNull Activity activity, @Nullable AppCompatCallback callback)方法,源码如下所示:
// AppCompatDelegate.java
@NonNull
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
// 创建AppCompatDelegateImpl对象
return new AppCompatDelegateImpl(activity, callback);
}
AppCompatDelegateImpl类继承AppCompatDelegate抽象类,看下AppCompatDelegateImpl类的setContentView(int resId)方法,源码如下所示:
// AppCompatDelegateImpl.java
@Override
public void setContentView(int resId) {
// 创建DecorView,并且将其添加到Window
ensureSubDecor();
// 找到DecorView中的contentView
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
// 移除contentView中所有的View
contentParent.removeAllViews();
// 根据传入的resId将要加载的XML布局添加到contentView
LayoutInflater.from(mContext).inflate(resId, contentParent);
// onContentChanged()方法是一个钩子方法,它会在屏幕的内容视图发生变化时调用
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
ensureSubDecor()方法的作用是创建DecorView,并且将其添加到Window中,同时创建面板菜单,源码如下所示:
// AppCompatDelegateImpl.java
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
// 如果还没有创建DecorView,就调用createSubDecor()方法创建DecorView
mSubDecor = createSubDecor();
// 得到title的字符
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
// 如果设置了title,就执行以下逻辑
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
applyFixedSizeWindow();
onSubDecorInstalled(mSubDecor);
// 标记已经创建DecorView
mSubDecorInstalled = true;
// 创建面板菜单
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mIsDestroyed && (st == null || st.menu == null)) {
// 通过添加刷新通知消息到主线程对应的消息队列中来刷新界面,目的是防止onCreateOptionsMenu(Nenu menu)方法在Activity调用onCreate(@Nullable Bundle savedInstanceState)方法的途中被调用
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
createSubDecor()方法的作用是创建DecorView,并且将其添加到Window中,源码如下所示:
// AppCompatDelegateImpl.java
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
// 如果使用AppCompatActivity,但是没有设置一个Theme.AppCompat的主题,就抛出IllegalStateException异常
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
// 设置Window的属性
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// 不允许没有标题的actionBar
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
// 调用ensureWindow()方法来检查DecorView是否已经添加到Window
ensureWindow();
// 调用成员变量mWindow的getDecorView()方法创建DecorView,要注意的是,成员变量mWindow声明为Window,Window是一个抽象类,这里的实现类是PhoneWindow,下面会详细讲解
mWindow.getDecorView();
// 得到LayoutInflater
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
// 根据标志添加到对应的布局
if (!mWindowNoTitle) {
if (mIsFloating) {
// 如果窗口需要浮动,就添加id是abc_dialog_title_material的布局
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
// 浮动窗口不能有操作栏,重置标志
mHasActionBar = mOverlayActionBar = false;
} else if (mHasActionBar) {
// 如果窗口有actionBar,就执行以下逻辑
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
Context themedContext;
if (outValue.resourceId != 0) {
themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
} else {
themedContext = mContext;
}
// 使用主题化上下文(themedContext)添加id是abc_screen_toolbar的布局
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
// 将特性传给DecorContentParent
if (mOverlayActionBar) {
mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (mFeatureProgress) {
mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
}
if (mFeatureIndeterminateProgress) {
mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
}
}
} else {
if (mOverlayActionMode) {
// 如果需要覆盖Activity的内容,就添加id是abc_screen_simple_overlay_action_mode的布局
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
// 如果不需要覆盖Activity的内容,就添加id是abc_screen_simple的布局
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
// 省略部分代码
}
if (subDecor == null) {
// 如果DecorView这个时候还是空,就抛出IllegalArgumentException异常
throw new IllegalArgumentException(
"AppCompat does not support the current theme features: { "
+ "windowActionBar: " + mHasActionBar
+ ", windowActionBarOverlay: "+ mOverlayActionBar
+ ", android:windowIsFloating: " + mIsFloating
+ ", windowActionModeOverlay: " + mOverlayActionMode
+ ", windowNoTitle: " + mWindowNoTitle
+ " }");
}
if (mDecorContentParent == null) {
// 如果成员变量mDecorContentParent为空,就将DecorView中的id为title的TextView赋值给成员变量mTitleView
mTitleView = (TextView) subDecor.findViewById(R.id.title);
}
// 让装饰可以选择适合系统窗口,例如:Window的装饰
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
// 得到contentView
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
while (windowContentView.getChildCount() > 0) {
// 如果已经有View添加到Window的contentView,就把它们迁移到contentView
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
windowContentView.setId(View.NO_ID);
// 给contentView添加android.R.id.content的id,对Fragment挺有用
contentView.setId(android.R.id.content);
// decorContent有一个前景可绘制的设置(windowContentOverlay),设为空,是因为我们自己处理它
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// 调用PhoneWindow的setContentView(View view)方法
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
// 返回DecorView
return subDecor;
}
总结一下,ViewRoot的实现是ViewRootImpl类,WindowMananger负责窗口的管理,它是连接WindowManager和DecorView的纽带,View的三大流程(measure、layout、draw)都是通过ViewRoot来完成,当Activity创建完成后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并且通过ViewRootImpl类的setView(View view, WindowManager.LayoutParams attrs, View panelParentView)方法和DecorView建立连接,每一个Activity都包含一个Window,它对应的窗口类型是应用窗口,它的具体实现是PhoneWindow,PhoneWindow包含DecorView,DecorView包含TitleView(ActionBar的容器)和ContentView(窗口内容的容器),其中,ContentView是一个FrameLayout,它的id是android.R.id.content,我们调用Activity的setContentView方法,就是将内容添加到这个FrameLayout中,它实际上是调用PhoneWindow的setContentView方法。
绘制流程
绘制流程从requestLayout()方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 调用checkThread()方法
checkThread();
mLayoutRequested = true;
// 调用scheduleTraversals()方法
scheduleTraversals();
}
}
checkThread()方法的作用是检查当前线程是不是创建ViewRootImpl所在的线程,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
// 如果当前线程不是创建ViewRootImpl所在的线程就抛出CalledFromWrongThreadException异常
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
scheduleTraversals()方法的作用是让Android系统优先执行跟View更新相关的异步消息,优先处理跟View更新相关的逻辑,在深入了解Android消息机制和源码分析(Java层和Native层)(上)和深入了解Android消息机制和源码分析(Java层和Native层)(下)这两篇文章有提及过,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
// 省略部分代码
final Thread mThread;
// 省略部分代码
public ViewRootImpl(Context context, Display display) {
// 得到当前线程
mThread = Thread.currentThread();
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 添加同步屏障消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 执行添加同步屏障消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// 省略部分代码
// 创建TraversalRunnable对象
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
// 省略部分代码
}
成员变量mTraversalRunnable是TraversalRunnable类型,TraversalRunnable是ViewRootImpl的被关键字final的内部类,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 调用doTraversal()方法
doTraversal();
}
}
看下doTraversal()方法,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 删除同步屏障消息
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 调用performTraversals()方法
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
看下performTraversals()方法,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
// 省略部分代码
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// 省略部分代码
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// 调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法,分别传入的是根视图的宽的MeasureSpec和高的MeasureSpec
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 实现WindowManager.LayoutParams中的weight,根据需要增加尺寸,并且在需要时重新测量
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
// 如果需要重新测量,就再次调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法,分别传入的是根视图的宽的MeasureSpec和高的MeasureSpec
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// 省略部分代码
}
// 省略部分代码
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 调用performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)方法
performLayout(lp, mWidth, mHeight);
// 省略部分代码
}
// 省略部分代码
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
// 省略部分代码
// 调用performDraw()方法
performDraw();
} else {
if (isViewVisible) {
// 如果取消draw,同时View是可见的,就再次调用scheduleTraversals()方法
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
// 省略部分代码
}
View的绘制三大流程分别从performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法、performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)方法和performDraw()方法开始。
measure流程
MeasureSpec是View类的静态内部类,它代表了一个32位的int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(在某个测量模式下的规格大小),源码如下所示:
// View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
// 省略部分代码
}
SpecMode分为三种模式,如下所示:
- UNSPECIFIED:父元素没有对子元素添加任何约束,它可以是任何大小,这个模式一般是在系统内部使用。
- EXACTLY:父元素已经确定了子元素的精确大小,也就是子元素的最终大小由SpecSize的值决定,对应于LayoutParams中的match_parent和具体数值这两种模式。
- AT_MOST:子元素的大小不能大于父元素的SpecSize的值,子元素的默认大小,对应于LayoutParams中的wrap_content。
View的MeasureSpec是由父元素的MeasureSpec和自身的LayoutParams共同决定的。
measure流程从ViewRootImpl类的performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 调用View的measure(int widthMeasureSpec, int heightMeasureSpec)方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
看下View类的measure(int widthMeasureSpec, int heightMeasureSpec)方法,源码如下所示:
// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 省略部分代码
if (forceLayout || needsLayout) {
// 省略部分代码
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 调用onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
// 省略部分代码
}
// 省略部分代码
}
// 省略部分代码
}
看下onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,源码如下所示:
// View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
开发者可以重写这个方法来改变测量View的逻辑。
先看下getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()方法,源码如下所示:
// View.java
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
getSuggestedMinimumWidth()方法返回的是View的建议的最小宽度,如果View没有背景,就返回minWidth,否则就返回背景的最小宽度。
getSuggestedMinimumHeight()方法返回的是View的建议的最小高度,如果View没有背景,就返回minHeight,否则就返回背景的最小高度。
然后看下getDefaultSize(int size, int measureSpec)方法,源码如下所示:
// View.java
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
// 得到测量模式
int specMode = MeasureSpec.getMode(measureSpec);
// 得到在这种测量模式下的规格大小
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
最后看下setMeasuredDimension(int measuredWidth, int measuredHeight)方法,源码如下所示:
// View.java
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
// 得到这个View的测量宽度
measuredWidth += optical ? opticalWidth : -opticalWidth;
// 得到这个View的测量高度
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
// 调用setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)方法
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
看下setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)方法,源码如下所示:
// View.java
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
View的getMeasuredWidth()方法使用到成员变量mMeasuredWidth的值,它的作用是返回原始的测量宽度,View的getMeasuredHeight()方法使用到成员变量mMeasuredHeight的值,它的作用是返回原始的测量高度,源码如下所示:
// View.java
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
前面看的是View的measure过程,接下来看下ViewGroup的measure过程,看下ViewGroup类的measureChildren(int widthMeasureSpec, int heightMeasureSpec)方法,源码如下所示:
// View.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
// 循环执行
for (int i = 0; i < size; ++i) {
// 得到ViewGroup中的子元素
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
// 如果子元素的状态不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE),就测量子元素,调用measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)方法
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
看下measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)方法,源码如下所示:
// View.java
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 调用View的measure(int widthMeasureSpec, int heightMeasureSpec)方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
例子
最后看下一个例子:LinearLayout,它重写了onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,源码如下所示:
// LinearLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
// 如果是垂直方向,就调用measureVertical(int widthMeasureSpec, int heightMeasureSpec)方法
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
// 如果是水平方向,就调用measureHorizontal(int widthMeasureSpec, int heightMeasureSpec)方法
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
这里只看垂直方向的这种情况,看下measureVertical(int widthMeasureSpec, int heightMeasureSpec)方法,源码如下所示:
// LinearLayout.java
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// mTotalLength的值是所有子元素的高度加paddingTop和paddingBottom,要注意的是,它和LinearLayout本身的高度不同
mTotalLength = 0;
// 所有子元素的最大宽度
int maxWidth = 0;
int childState = 0;
// 所有layout_weight属性的值小于等于0的子元素中宽度的最大值
int alternativeMaxWidth = 0;
// 所有layout_weight属性的值大于0的子元素中宽度的最大值
int weightedMaxWidth = 0;
boolean allFillParent = true;
// 所有子元素的weight之和
float totalWeight = 0;
final int count = getVirtualChildCount();
// 得到宽度的测量模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 得到高度的测量模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
int nonSkippedChildCount = 0;
// 循环执行
for (int i = 0; i < count; ++i) {
// 得到子元素
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
// 省略部分代码
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
// 调用measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)方法
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
// 省略部分代码
}
// 省略部分代码
}
// 省略部分代码
// mTotalLength再加上paddingTop和paddingBottom之和
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// 取高度大小和建议最小高度的最大值
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// 省略部分代码
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
// maxWidth再加上paddingLeft和paddingRight之和
maxWidth += mPaddingLeft + mPaddingRight;
// 取最大宽度和建议最小宽度的最大值
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 调用setMeasuredDimension(int measuredWidth, int measuredHeight)方法
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)方法的作用是测量LinearLayout中子元素,让这些子元素执行measure流程,同时系统通过成员变量mTotalLength来存储LinearLayout在垂直方向的初步高度,每测量一个子元素,都会使成员变量mTotalLength加上子元素的高度和子元素在垂直方向上的margin属性和padding属性。
layout流程
layout流程从ViewRootImpl类的performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 省略部分代码
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
// 调用View的layout(int l, int t, int r, int b)方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 省略部分代码
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
调用layout(int l, int t, int r, int b)方法,并且left的位置传入0,top的位置传入0,right的位置传入测量宽度,bottom的位置传入测量高度,源码如下所示:
// View.java
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
// 省略部分代码
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 调用onLayout(boolean changed, int left, int top, int right, int bottom)方法
onLayout(changed, l, t, r, b);
// 省略部分代码
}
// 省略部分代码
}
看下isLayoutModeOptical(Object o)方法,它的作用是如果是传进来是一个使用光学边界布局的ViewGroup,就返回true,否则就返回false,源码如下所示:
// View.java
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
先看下setOpticalFrame(int left, int top, int right, int bottom)方法,源码如下所示:
// View.java
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
// 调用setFrame(int left, int top, int right, int bottom)方法
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
然后看下setFrame(int left, int top, int right, int bottom)方法,这个方法的作用是确定View的四个顶点位置,也就是确定了子元素在父元素中的位置,源码如下所示:
// View.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// 使旧的位置无效
invalidate(sizeChanged);
// 将left赋值给成员变量mLeft
mLeft = left;
// 将top赋值给成员变量mTop
mTop = top;
// 将right赋值给成员变量mRight
mRight = right;
// 将bottom赋值给成员变量mBottom
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
// 省略部分代码
}
return changed;
}
View的getWidth()的值就是使用mRight的值减mLeft的值,View的getHeight()的值就是使用mBottom的值减mTop的值,源码如下所示:
// View.java
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
return mBottom - mTop;
}
接着上面,看下onLayout(boolean changed, int left, int top, int right, int bottom)方法,源码如下所示:
// View.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
开发者可以重写这个方法来改变布局View的逻辑。
例子
最后看下一个例子:LinearLayout,它重写了onLayout(boolean changed, int l, int t, int r, int b)方法,源码如下所示:
// LinearLayout.java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
// 如果是垂直方向,就调用layoutVertical(int left, int top, int right, int bottom)方法
layoutVertical(l, t, r, b);
} else {
// 如果是垂直方向,就调用layoutHorizontal(int left, int top, int right, int bottom)方法
layoutHorizontal(l, t, r, b);
}
}
这里只看垂直方向的这种情况,看下layoutVertical(int left, int top, int right, int bottom)方法,源码如下所示:
// LinearLayout.java
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// 父元素的默认宽度
final int width = right - left;
// 子元素默认的right的值
int childRight = width - mPaddingRight;
// 子元素的可用空间
int childSpace = width - paddingLeft - mPaddingRight;
// 子元素的数量
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
// 根据设置的gravity属性,设置第一个子元素的top的值
switch (majorGravity) {
case Gravity.BOTTOM:
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
// 循环执行
for (int i = 0; i < count; i++) {
// 得到子元素
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
// 得到子元素的测量宽度
final int childWidth = child.getMeasuredWidth();
// 得到子元素的测量高度
final int childHeight = child.getMeasuredHeight();
// 得到子元素的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
// 根据子元素的gravity属性设置left的值
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
// 如果有分割线,就添加分割线的高度
childTop += mDividerHeight;
}
// 子元素的top的值加上marginTop的值
childTop += lp.topMargin;
// 调用setChildFrame(View child, int left, int top, int width, int height)方法设置子元素在父元素的布局位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
setChildFrame(View child, int left, int top, int width, int height)方法的作用是设置子元素在父元素的布局位置,源码如下所示:
// LinearLayout.java
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
这个方法调用了子元素的layout(int l, int t, int r, int b)方法,使子元素执行layout流程。
draw流程
draw流程从ViewRootImpl类的performDraw()方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
// 省略部分代码
try {
// 调用draw(Canvas canvas)方法
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// 省略部分代码
}
看下draw(Canvas canvas)方法,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
int saveCount;
// 第一步:如果有必要,就画背景
drawBackground(canvas);
// 一般情况下,如果可能的话会跳过第二步和第五步
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// 第三步:画View的内容
onDraw(canvas);
// 第四步:画子元素
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// 覆盖(overlay)是内容的一部分,它在前景下面绘制
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 第六步:绘制装饰(前景、滚动条)
onDrawForeground(canvas);
// 第七步:绘制默认焦点突出显示
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// 执行到这里代表整个绘制流程就执行完成了
return;
}
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// 第二步:如果有必要,就保存画布的图层,准备褪色(fading)
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// 剪辑(clip)褪色长度,如果顶部和底部褪色重叠将会产生奇怪的工件
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// 如果有必要,还可以剪辑(clip)水平渐变
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int topSaveCount = -1;
int bottomSaveCount = -1;
int leftSaveCount = -1;
int rightSaveCount = -1;
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 第三步:画View的内容
onDraw(canvas);
// 第四步:画子元素
dispatchDraw(canvas);
// 第五步:如果有必要,就画褪色的边缘(fading edges)和恢复图层(restore layers)
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
// 必须按照保存的顺序进行恢复
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(rightSaveCount, p);
} else {
canvas.drawRect(right - length, top, right, bottom, p);
}
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(leftSaveCount, p);
} else {
canvas.drawRect(left, top, left + length, bottom, p);
}
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(bottomSaveCount, p);
} else {
canvas.drawRect(left, bottom - length, right, bottom, p);
}
}
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(topSaveCount, p);
} else {
canvas.drawRect(left, top, right, top + length, p);
}
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
// 覆盖(overlay)是内容的一部分,它在前景下面绘制
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 第六步:绘制装饰(前景和滚动条)
onDrawForeground(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
看下View类的dispatchDraw(Canvas canvas)方法,源码如下所示:
// View.java
protected void dispatchDraw(Canvas canvas) {
}
这个方法没有任何逻辑处理,ViewGroup类继承View类,看下ViewGroup类重写了这个方法,源码如下所示:
// ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
// 得到子元素的数量
final int childrenCount = mChildrenCount;
// 得到子元素的数组,也就是View数组
final View[] children = mChildren;
int flags = mGroupFlags;
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
// 循环执行
for (int i = 0; i < childrenCount; i++) {
// 得到子元素
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// 如果这个View是可见状态,就处理其动画
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
// 省略部分代码
final ArrayList preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// 省略部分代码
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
// 调用drawChild(Canvas canvas, View child, long drawingTime)方法
more |= drawChild(canvas, child, drawingTime);
}
}
// 省略部分代码
}
看下drawChild(Canvas canvas, View child, long drawingTime)方法,源码如下所示:
// ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
这个方法调用View的draw(Canvas canvas)方法。
总结一下,draw流程分为以下七步:
- 画背景。
- 如果有必要,就保存画布的图层,准备褪色(fading)。
- 画View的内容。
- 画子元素。
- 如果有必要,画褪色的边缘(fading edges)和恢复图层(restore layers)。
- 绘制装饰(前景和滚动条)。
- 绘制默认焦点突出显示。
例子
最后看下一个例子:LinearLayout,它重写了onDraw(Canvas canvas)方法,源码如下所示:
// LinearLayout.java
@Override
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
// 如果没有分割线,就结束方法
return;
}
if (mOrientation == VERTICAL) {
// 如果是垂直方向,就调用drawDividersVertical(Canvas canvas)方法
drawDividersVertical(canvas);
} else {
// 如果是水平方向,就调用drawDividersHorizontal(Canvas canvas)方法
drawDividersHorizontal(canvas);
}
}
这里只看垂直方向的这种情况,看下drawDividersVertical(Canvas canvas)方法,源码如下所示:
// LinearLayout.java
void drawDividersVertical(Canvas canvas) {
// 得到子元素的虚拟数量
final int count = getVirtualChildCount();
// 循环执行
for (int i = 0; i < count; i++) {
// 得到子元素
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
// 如果子元素的状态不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE),同时这个View有分割线,就画水平的分割线
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int top = child.getTop() - lp.topMargin - mDividerHeight;
drawHorizontalDivider(canvas, top);
}
}
}
if (hasDividerBeforeChildAt(count)) {
// 得到最后一个不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE)的View
final View child = getLastNonGoneChild();
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom() + lp.bottomMargin;
}
// 画水平的分割线
drawHorizontalDivider(canvas, bottom);
}
}
这个方法的作用是画水平的分割线。
在子线程更新UI的问题
先看下如下第一个例子,代码如下所示:
package com.tanjiajun.viewdemo
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
/**
* Created by TanJiaJun on 2020/10/8.
*/
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建子线程,并且启动它
Thread {
// 在子线程更新UI,设置id为tv_content的TextView的文本为谭嘉俊
findViewById(R.id.tv_content).text = "谭嘉俊"
}.start()
}
}
这段代码是在子线程更新UI,结果很顺利地执行完毕,并且符合预期。
我修改下第一个例子,得到第二个例子,代码如下所示:
package com.tanjiajun.viewdemo
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
/**
* Created by TanJiaJun on 2020/10/8.
*/
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建子线程,并且启动它
Thread {
// 让子线程睡眠一秒
Thread.sleep(1000)
// 在子线程更新UI,设置id为tv_content的TextView的文本为谭嘉俊
findViewById(R.id.tv_content).text = "谭嘉俊"
}.start()
}
}
这段代码也是在子线程更新UI,并且让子线程睡眠一秒,结果就抛出了如下异常:
2020-10-08 17:02:25.544 8619-8665/com.tanjiajun.viewdemo E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.tanjiajun.viewdemo, PID: 8619
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
at android.view.View.requestLayout(View.java:23093)
at android.widget.TextView.checkForRelayout(TextView.java:8908)
at android.widget.TextView.setText(TextView.java:5730)
at android.widget.TextView.setText(TextView.java:5571)
at android.widget.TextView.setText(TextView.java:5528)
at com.tanjiajun.viewdemo.MainActivity$onCreate$1.run(MainActivity.kt:20)
at java.lang.Thread.run(Thread.java:764)
抛出了CalledFromWrongThreadException异常,在前面讲解checkThread()方法的时候也提及过,这个方法的作用是检查当前线程是不是创建ViewRootImpl所在的线程,如果是就通知View执行绘制流程,否则就抛出CalledFromWrongThreadException异常,示例代码的ViewRootImpl是在主线程创建的,也就是判断是否为主线程,第一个例子没有抛出CalledFromWrongThreadException异常的原因是,因为调用setText(CharSequence text)方法的时候ViewRootImpl还没创建,View的绘制流程会在Activity的onResume方法之后执行,也就是ViewRootImpl是在onResume方法之后创建的,所以checkThread()方法还没调用,因此这个时候通知UI刷新就不会抛出CalledFromWrongThreadException异常,第二个例子抛出CalledFromWrongThreadException异常的原因是,因为让线程睡眠一秒,可能这个时候onResume方法已经执行了,并且已经在主线程 创建ViewRootImpl,这个时候调用setText(CharSequence text)后通知UI刷新就会调用checkThread()方法,然后得到当前线程是子线程,和主线程不是同一个线程,因此就抛出CalledFromWrongThreadException异常。
其实Google是如下说法:
The Android UI toolkit is not thread-safe.
Google的意思是Android的UI toolkit不是线程安全的,Google也没说不允许在工作线程(非主线程)中更新UI。
题外话
介绍一个Android开源项目(AOSP)的代码搜索工具,网址如下:
Android开源项目(AOSP)的代码搜索工具
我的GitHub:TanJiaJunBeyond
Android通用框架:Android通用框架
我的掘金:谭嘉俊
我的:谭嘉俊
我的CSDN:谭嘉俊