Android源码分析Window添加
Posted 小图包
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android源码分析Window添加相关的知识,希望对你有一定的参考价值。
1 系统添加窗口添加的过程
这里以我们常用的 Toast 来分析添加到 SystemWindow 中的过程
//Toast
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration)
//1. 实例化 Toast 对象
Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//2. 内部解析 XML 为一个 View 的过程
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
//3. 记住这里 mNextView 它就是我们待会要添加的对象
result.mNextView = v;
result.mDuration = duration;
return result;
final TN mTN;
int mDuration;
View mNextView;
public Toast(@NonNull Context context, @Nullable Looper looper)
mContext = context;
mTN = new TN(context.getPackageName(), looper);
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
通过 makeText 我们得知 Toast 主要就做了添加了一个默认布局的 View ,然后将 View 赋值给了 Toast 的 mNextView 变量,下面我们来看 Toast show 方法
//Toast.java
public void show()
if (mNextView == null)
throw new RuntimeException("setView must have been called");
//1. 拿到 NMS 代理类
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
//2. TN 接收 NMS 消息
TN tn = mTN;
tn.mNextView = mNextView;
try
//3. 通知 NMS 调用 enqueueToast 方法
service.enqueueToast(pkg, tn, mDuration);
catch (RemoteException e)
// Empty
private static class TN extends ITransientNotification.Stub
...
通过上面代码我们知道首先拿到 INotificationManager ,然后将创建好的 mTN 赋值给 TN 对象,它继承自 ITransientNotification.Stub Binder对象说明具备了接收进程间通信的功能,最后调用 INotificationManager 的 enqueueToast 方法来通知 NotificationManagerService,下面我们来看 NMS 的 enqueueToast 具体实现,代码如下:
//NotificationManagerService.java
public class NotificationManagerService extends SystemService
....
private final IBinder mService = new INotificationManager.Stub()
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
...
synchronized (mToastQueue)
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try
ToastRecord record;
...
//1.
record = new ToastRecord(callingPid, pkg, callback, duration, token);
if (index == 0)
//2.
showNextToastLocked();
finally
Binder.restoreCallingIdentity(callingId);
....
void showNextToastLocked()
ToastRecord record = mToastQueue.get(0);
while (record != null)
try
//3.
record.callback.show(record.token);
scheduleTimeoutLocked(record);
return;
catch (RemoteException e)
....
通过上面代码我们知道在 Toast 调用到 NMS 中,然后构造 ToastRecord 对象将 pkg、callback 等对象传递进去,然后调用注释 3 record.callback.show 函数,这里的 callback 就是定义在 Toast 中接收 NMS 消息的,我们回到 Toast 中 show(Binder binder) 方法,代码如下:
//Toast.java
@Override
public void show(IBinder windowToken)
//1.
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
mHandler = new Handler(looper, null)
@Override
public void handleMessage(Message msg)
switch (msg.what)
case SHOW:
IBinder token = (IBinder) msg.obj;
//2.
handleShow(token);
break;
...
;
上面就是一个线程间通信操作,我们直接看2 具体实现
//Toast.java
public void handleShow(IBinder windowToken)
....
if (mView != mNextView)
...
/**
* 1. 添加的对象
*/
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null)
context = mView.getContext();
/**
* 1. 拿到 WindowManager
*/
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
...
try
/**
* 3. 调用 WindowManager addView 添加
*/
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
catch (WindowManager.BadTokenException e)
/* ignore */
注释 3 ,调用 WindowManager 的 addView 方法,通过上面的介绍,我们知道 WindowManager 的实现是在 WindowManagerImpl ,具体实现代码如下:
//WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
applyDefaultToken(params);
/**
* 委托给 WindowManagerGlobal 来处理 addView
*/
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
addView 的第一个参数类型为 View ,说明窗口都是以 View 的形式存在的,addView 方法中会调用 WindowManagerGlobal 对象中 addView 方法,
//
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow)
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null)
/**
* 1. 根据 WindowManager.LayoutParams 的参数来对添加的子窗口进行相应的调整
*/
parentWindow.adjustLayoutParamsForSubWindow(wparams);
else
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock)
...
/**
* 2. 实例化 ViewRootImpl 对象,并赋值给 root 变量
*/
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
/**
* 3. 添加 view 到 mViews 列表中
*/
mViews.add(view);
/**
* 4. 将 root 存储在 ViewRootImp 列表中
*/
mRoots.add(root);
/**
* 5. 将窗口的参数保存到布局参数列表中。
*/
mParams.add(wparams);
try
/**
* 6. 将窗口和窗口的参数通过 setView 方法设置到 ViewRootImpl 中
*/
root.setView(view, wparams, panelParentView);
catch (RuntimeException e)
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0)
removeViewLocked(index, true);
throw e;
总结上述主要做了以下操作:
- 根据 WindowManager.LayoutParams 的参数来对添加的子窗口进行相应的调整.
- 实例化 ViewRootImpl 对象,并赋值给 root 变量
- 添加 view 到 mViews 列表中
- 将 root 存储在 ViewRootImp 列表中
- 将窗口的参数保存到布局参数列表中
- 将窗口和窗口的参数通过 setView 方法设置到 ViewRootImpl 中
ViewRootImpl 身负很多职责
- View 树的根并管理 View 树
- 触发 View 的测量、布局、绘制
- 输入事件的中转站
- 管理 surface
- 负责与 WMS 进行系统间通信
//ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) synchronized (this) ... try mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); catch (RemoteException e) ... ...
到了 ViewRootImpl 的职责后,我们接着来查看 ViewRootImpl 的 setView 方法:
//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
synchronized (this)
...
try
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
catch (RemoteException e)
...
...
setView 方法中有很多逻辑,这里只截取重要代码,主要就是调用 mWindowSession 类型的 WindowSession 的 addToDisplay 方法,它是一个 Binder 对象,它的服务端的实现是 Session 类。看下Session代码
//Session.java
public class Session extends IWindowSession.Stub implements IBinder.DeathRecipient
final WindowManagerService mService;
...
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel)
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
...
我们知道本地进程的 ViewRootImpl 想要和 WMS 进行通信需要经过 Session, 那么 Session 为何包含在 WMS 中呢? 我们看上面代码知道,Session 内部的 addToDisplay 方法,其实就是调用 WMS 的 addWindow 方法。并将自身也就是 Session 作为参数传递进去,那么这样每一个应用程序进程都会对应一个 Session ,WMS 会调用 ArrayList 来保存这些 Session。这也是为什么 WMS 包裹 Session 的原因了。这样剩下的工作就交给 WMS 来处理了,在 WMS 中会为这个添加的窗口分配 Surface ,可见负责显示界面的是 Surface 而不是 Window. WMS 会将它所管理的 Surface 交由 SurfaceFlinger 处理,SurfaceFlinger 会将这些 Surface 混合并绘制到屏幕上。
Activity 的添加过程
Activity 在启动过程中,如果 Activity 所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例 ActivityThread, ActivityThread 管理着当前应用程序进程的线程,这在 Activity 的启动过程中运用得很明显,当界面要与用户进行交互时,会调用 ActivityThread 的 handleResumeActivity 方法,如下所示:
//ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason)
...
/**
* 1. 最终会调用 Activity onResume 生命周期函数
*/
r = performResumeActivity(token, clearHide, reason);
if (r != null)
...
if (r.window == null && !a.mFinished && willBeVisible)
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
/**
* 2. 得到 ViewManager 对象
*/
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow)
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null)
impl.notifyChildRebuilt();
if (a.mVisibleFromClient)
if (!a.mWindowAdded)
a.mWindowAdded = true;
/**
* 3. 调用 ViewManager 的 addView 方法
*/
wm.addView(decor, l);
else
a.onWindowAttributesChanged(l);
...
其一,通过注释 1 会执行 Activity 的 onResume 生命周期函数,其二,拿到 ViewManager 对象,这里 ViewManager 在之前介绍过,它是一个接口由 WindowManager 继承,最后在 WindowManagerImpl 中具体实现,所以其三,注释三就是调用 WindowManagerImpl 的 addView 方法, 剩下的调用跟 Toast 的执行流程一样
Window 的更新过程
Window 的更新过程和 Window 的添加过程是类似的,需要调用 ViewManager 的 updateViewlayout 方法,updateViewLayout 方法在 WindowmanagerImpl 中实现, WindowManagerImpl 的 updateViewLayout 方法的调用 WindowManagerGlobal 的 updateViewLayout 方法
//WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params)
if (view == null)
throw new IllegalArgumentException("view must not be null");
if (!(params instanceof WindowManager.LayoutParams))
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
/**
* 1. 将更新的参数设置到 View 中
*/
view.setLayoutParams(wparams);
synchronized (mLock)
/**
* 2. 得到要更新的窗口在 View 列表中的索引
*/
int index = findViewLocked(view, true);
/**
* 3. 根据索引得到窗口的 ViewRootImpl
*/
ViewRootImpl root = mRoots.get(index);
/**
* 4. 删除旧的参数
*/
mParams.remove(index);
/**
* 5.重新添加新的参数
*/
mParams.add(index, wparams);
/**
* 6.将更新的参数设置到 ViewRootImpl 中
*/
root.setLayoutParams(wparams, false);
看下注释 6 的代码:
//ViewRootImpl.java
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView)
...
scheduleTraversals();
//ViewRootImpl.java
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable
....
void scheduleTraversals()
if (!mTraversalScheduled)
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
/**
* mChoreographer:用于接收显示系统的 VSync 信号,在下一帧渲染时控制执行一些操作,
* 用于发起添加回调 在 mTraversalRunnable 的 run 中具体实现
*/
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch)
scheduleConsumeBatchedInput();
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
根据注释我们直接看 TraversalRunnable 的 run 实现,代码如下:
//ViewRootImpl.java
final class TraversalRunnable implements Runnable
@Override
public void run()
//调用内部的 doTraversal 方法
doTraversal();
//ViewRootImpl.java
void doTraversal()
if (mTraversalScheduled)
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile)
Debug.startMethodTracing("ViewAncestor");
performTraversals();
if (mProfile)
Debug.stopMethodTracing();
mProfile = false;
在 doTraversal 中又调用了 performTraversals 方法
//ViewRootImpl.java
private void performTraversals()
/**
* 1. 内部会调用 IWindowSession 的 relayout 方法来更新 Window 视图
*/
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
/**
* 2.内部调用 View 的 measure 测试方法
*/
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
/**
* 3. 内部会调用 View 的 layout 完成 View 的布局工作
*/
performLayout(lp, mWidth, mHeight);
...
/**
* 4. 内部会调用 View 的 draw 方法,完成了 View 的绘制工作。
*/
performDraw();
...
通过上面 performTraversals 函数,内部进行了 View 的 measure、layout、draw 这样就完成了 View 的绘制流程。在 performTraversals 方法中更新了 Window 视图,这样就完成了 Window 的更新。
以上是关于Android源码分析Window添加的主要内容,如果未能解决你的问题,请参考以下文章
Android Framework 之 Window / WindowManager基本概念及addView源码分析
Android Framework 之 Window / WindowManager基本概念及addView源码分析