参考学习视频:阿里、腾讯等一线大厂必问的WMS面试题
目录
- 问题一、View的绘制流程?onMeasure职责是做什么?
- 问题二、Activity如何与window与view进行分工合作的?
- 问题三、onResume函数中度量高有效吗?
- 问题四、线程中view.setText一定会报错吗,为什么?
- 问题五、View的绘制过程都是用的同一个canvas吗?
- 问题六:Activity、Window、View三者的联系和区别?
- 问题七:首次View的绘制流程是在什么时候触发的?
- 问题八:调用invalidate()之后会马上进行屏幕刷新吗?
- 问题九: 我们说丢帧是因为主线程做了耗时操作,为什么主线程做了耗时操作就会引起丢帧?
- 问题十:连续两次setTextView到底会触发几次UI重绘呢?
- 问题十一:为什么Android APP的帧率一般是60FPS?
问题一、View的绘制流程?onMeasure职责是做什么?
View绘制流程
问题二、Activity如何与window与view进行分工合作的?
ViewRootImpl是UI刷新的核心类,所有UI相关动作,包括触摸、触屏事件、响应事件、按键事件都是有ViewRootImpl来完成。
Activity的启动流程:
ActivityThread.java
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
... ...
// 获取WindowManagerService的binder引用
WindowManagerGlobal.initialize();
... ...
final Activity a = performLaunchActivity(r, customIntent);
... ...
}
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
... ...
// 通过类加载,找到activity
ava.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
... ...
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
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);
... ...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}
Activity.java
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) {
... ...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
... ...
// 设置WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
//创建完成之后通过getWindowManager()就得到WindowManager 这个实例
mWindowManager = mWindow.getWindowManager(); //这个就是WindowManagerImpl
}
在此处构造了一个PhoneWindow,并进行了setWindowManager操作,此时Activity与Window就建立了联系。
执行完了attach操作,就会返回上一个流程中执行mInstrumentation.callActivityOnCreate(activity, r.state),其实也就是调用了应用层的onCreate()方法。进而执行setContentView()方法解析xml文件(其实也就是序列化),创建一个根视图。
但是此时Window与View还没有建立联系
Activity生命周期是为了更好的管理Window,Window上面所显示的都是View。
接下来继续看Window如何与View进行建立联系的?
ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
if (a.mVisibleFromClient) { //a指Activity
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); //wm指ViewManager, decor指View
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
}
ViewManager是一个接口,WindowManager类继承于ViewManager类,WindowManager是一个接口,具体实现类为WindowManagerImpl。
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//mGlobal指WindowManagerGlobal,是一个单例
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
每个进程只有一个WindowManagerGlobal对象
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
... ...
root = new ViewRootImpl(view.getContext(), display); //此处创建一个根布局
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
... ...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
... ...
}
即通过WindowSession进行IPC调用,将View添加到Window上
mWindow即W类(W继承于IWindow.Stub),用来接收WMS的消息,同时使用inputChannel接收触摸事件回调。
Session.java
/**
* window 提供给 WMS 的回调接口
* attrs layout 参数
* outContentInsets WMS计算后返回这个View在显示屏上的位置
* outInputChannel 用户输入通道Handle
*
*
/
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel)
return mService.addWindow(this, window, seq, attrs, viewVisibility,displayId,outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
Session类是继承于IWindowSession.Stub。
addWindowI()后面会调用WMS的WindowState操作,最后会交给SurfaceFling进行完成。
Activity中会有唯一的一个PhoneWindow,PhoneWindow包含了一个ViewRootImpl。
Activity生命周期是用来管理View,View的整个显示是由ViewRootImpl来完成的。
问题三、onResume函数中度量高有效吗?
答:Activity第一次调用onResume的时候是无效的,Activity第二次之后调用onResume是有效的。
addView操作是在handleResumeActivity()方法中执行(ActivityThread.java),即PerformResumeActivity操作之后执行
- 情况1,如果第一次进去的时候,执行到onResume函数获取度量高的值为0,此时WindowManager与DocorView还没进行相关的绑定。
- 情况2,如果跳转到一个新的界面中,再返回该界面中,此时已经完成一次onResume的生命周期操作,UI已经度量完成,再返回onResume进行获取度量高是可以获取到值的。
handleResumeActivity–>performResumeActivity–>onResume–>WindowManager与 DecorView绑定
handleResumeActivity.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
... ...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
... ...
}
handleResumeActivity.java
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
String reason) {
... ...
r.activity.performResume(r.startsNotResumed, reason);
... ...
}
Activity.java
final void performResume(boolean followedByPause, String reason) {
... ...
mInstrumentation.callActivityOnResume(this);
... ...
}
最后调用应用层的OnResume()方法。在返回到ActivityThread的handleResumeActivity方法中。
ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
... ...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); // 此步往下执行会执行到应用层的OnResume方法
... ...
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible(); //将ativity设置为可见,在里面添加add
}
}
Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes()); //将DecorView与WindowManager进行绑定
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
问题四、线程中view.setText一定会报错吗,为什么?
答:不一定。
view.setText(“****”),最终触发调用的的invalidate(可以参考上图中的View绘制流程),触发不到刷新的流程,ViewRootImpl中的checkThread压根不会执行
问题五、View的绘制过程都是用的同一个canvas吗?
答:是的。canvas至始至终都是从ViewRootImpl通过参数传到View类中的draw(Canvas canvas)方法中,执行后续的draw流程。
canvas要lock锁住和unlock解锁操作。
问题六:Activity、Window、View三者的联系和区别?
答:Activity
负责界面的交互和业务逻辑
window
代表屏幕的一块显示区域,它是一个抽象的概念,是view的一个载体
view
是显示的控件
window是view的一个容器
,view是window的一个真实实体内容
Activity向用户展示一个界面内容的时候,它本身并没有界面,需要通过window向用户展示。
问题七:首次View的绘制流程是在什么时候触发的?
答:WindowManagerImpl.addView() --> WindowManagerGlobal.addView() --> ViewRootImpl.setView() --> ViewRootImpl.requestLayout() -->ViewRootImpl.scheduleTraversals() -->后面就是执行绘制流程
问题八:调用invalidate()之后会马上进行屏幕刷新吗?
答:不会,必须等下一个同步信号VSYNC来了再进行刷新
问题九: 我们说丢帧是因为主线程做了耗时操作,为什么主线程做了耗时操作就会引起丢帧?
答:做了耗时操作就会影响下一帧的绘制,如果影响到了帧的绘制就会出现掉帧问题
问题十:连续两次setTextView到底会触发几次UI重绘呢?
答:UI必须至少等待16ms的间隔才会绘制下一帧,所有连续两次setTextView只会触发一次重绘。
问题十一:为什么Android APP的帧率一般是60FPS?
答:以电影为例,动画至少要得达到24FPS,才能保证画面的流畅度,低于这个值,肉眼会感觉到卡顿。在手机上,这个值被调整到60FPS,增加丝滑度,这也是为什么有个(1000/60)16ms的指标,一般而言目前的Android系统FPS也就是60,它是通过一个VSYNC来保证每16ms最多绘制一帧。