網站首頁 編程語言 正文
前言
又是一年一度的1024程序員節了,今天不寫點什么總感覺對不起這個節日。想來想去,就寫點關于View的繪制。本文不會重點講View繪制三大回調函數:onMeasure、onLayout、onDraw,而是站在Android framework的角度去分析一下View的繪制。
- View是如何被渲染到屏幕中的?
- ViewRoot、DecorView、Activity、Window、WindowManager是什么關系?
- View和Surface是什么關系?
- View和SurfaceFlinger、OpenGL ES是什么關系?
計算機的圖像一般是需要經過底層的圖像引擎輸出GPU需要的數據交給GPU,顯示器從GPU從不斷的取出渲染好的數據顯示到屏幕上。
熟悉Andriod體系架構的人都知道,Android底層渲染圖像的引擎是OpenGL ES/Vulkan。那么View是被誰渲染的呢?沒錯,View最終也是交給底層渲染引擎的,那么從View到OpenGL ES這中間經歷了哪些過程呢?
setContentView()流程
在Activity的onCreate中我們一般會通過setContentView來給Activity設置界面布局。這個時候,Activity是否開始渲染了呢?并沒有,setContentView只是構建整個DecorView的樹。
//android.app.Activity public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
setContentView是調用Window的setContentView,而PhoneWindow是Window的唯一實現類:
//com.android.internal.policy.PhoneWindow @Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor();//1,安裝DecorView } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent);//2,解析layoutResID } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true;
1處開始安裝DecorView,主要是new一個DecorView,并找到其中id等于content的布局,通過mContentParent引用。我們在xml的寫的布局就是添加到這個mContentParent容器中。
//com.android.internal.policy.PhoneWindow private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1);//new一個DecorView mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { mDecor.setWindow(this); } if (mContentParent == null) { mContentParent = generateLayout(mDecor);//找到id==content的ViewGroup ... }
2處通過LayoutInflator解析傳入的layoutResID,解析成View并添加到mContentParent中。mContentParent就是我們xml界面的中的id等于content的布局:
綜上分析,setContentView主要完成兩個功能:
1、構建DecorView
2、解析自定義的xml布局文件,添加到DecorView的content中。
所以setContentView還沒有真正開始渲染圖像。
思考:如果我們沒有調用setContentView,Activity能正常啟動嗎?為什么?
WindowManager.addView流程
Android的所有View都是通過WindowManager.addView添加到屏幕中的。那么Activity的DecorView是什么時調被添加到屏幕中的呢?
答案在ActivityThread
的handleResumeActivity
方法中:
//android.app.ActivityThread @Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { ... //執行Activity的onResume生命周期 final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); ... if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView();//1、調用了window.getDecorView()方法 decor.setVisibility(View.INVISIBLE); 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; // Normally the ViewRoot sets up callbacks with the Activity // in addView->ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l);//2、開始調用WindowManager.addView將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); } } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; }
wm.addView
才是開始DecorView
渲染的入口。而它的觸發時機是在Activity的onResume
生命周期之后,所以說onResume之后View才會顯示在屏幕上,并且渲染完成才可以獲取到View的寬度。
主要看下1處調用了window的getDecorView()方法:
//com.android.internal.policy.PhoneWindow @Override public final @NonNull View getDecorView() { if (mDecor == null || mForceDecorInstall) { installDecor(); } return mDecor; }
這里可以看出,即使我們沒有調用setContentView,DecorView也會初始化,只是會顯示空白頁面。
然后我們重點看下2處的代碼,通過WindowManager的addView方法將DecorView添加到window中了:wm.addView(decor, l)
繼續分析addView之前先梳理一下必要的基本知識。
上面的wm雖然是ViewManager類型的,它實際就是WindowManager。
WindowManager是一個接口,它繼承自ViewManager。
public interface WindowManager extends ViewManager { ... }
可以看到WindowManager
的實現類是WindowManagerImpl
,后面WindowManager
的功能都是靠WindowManagerImpl
來實現的。
Window是抽象類,PhoneWindow是它的實現。WindowManager是Window的成員變量,Window和WindowManager都是在Activity的attach方法中初始化的:
//android.app.Activity 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) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window, activityConfigCallback);//1,初始化window ...省略無關代碼 mWindow.setWindowManager( //2、給window初始化windowManager (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager();//3、Activity通過mWindowManager引用window中的WindowManager,兩個wm是同一個東西。 mCurrentConfig = config; mWindow.setColorMode(info.colorMode); setAutofillOptions(application.getAutofillOptions()); setContentCaptureOptions(application.getContentCaptureOptions()); }
1處開始初始化window,并賦值給Activity的成員變量mWindow
2處給window設置windowManager
3處Activity通過mWindowManager引用window中的WindowManager,兩個wm是同一個東西。
然后重點看下setWindowManager方法的實現:
//android.view.Window public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated; if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
梳理完基本關系,再回頭看下wm.addView
過程。
//android.view.WindowManagerImpl @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }
可以看到wm.addView交給了mGolbal對象。
mGolbal是WindowManagerGlobal類型的全局單例:
public final class WindowManagerImpl implements WindowManager { @UnsupportedAppUsage private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ...
繼續看WindowManagerGlobal.addView是如何實現的。
//android.view.WindowManagerGlobal public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { // ...省略無關代碼 ViewRootImpl root; View panelParentView = null; // ...省略無關代碼 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
對象root,并將view
、root
、wparams
保存到了集合中。最后調用了ViewRootImpl
的setView方法設置視圖。
繼續跟蹤ViewRootImpl
的setView
方法。
//android.view.ViewRootImpl public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; //...省略不重要代碼 mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } mForceDecorViewVisibility = (mWindowAttributes.privateFlags & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0; try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); setFrame(mTmpFrame); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } //...省略不重要代碼 if (view instanceof RootViewSurfaceTaker) { mInputQueueCallback = ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); } if (mInputChannel != null) { if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue); } mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); } view.assignParent(this); //...省略不重要代碼 } } }
WMS是Android窗口管理系統,在將View樹注冊到WMS之前,必須先執行一次layout,WMS除了窗口管理之外,還負責各種事件的派發,所以在向WMS注冊前app在確保這棵view樹做好了接收事件準備。
ViewRoot起到中介的作用,它是View樹的管理者,同時也兼任與WMS通信的功能。
mWindowSession.addToDisplay將View的渲染交給了WindowManagerService。
mWindowSession是IWindowSession類型的變量,在服務端的實現類是Session.java,它是一個Binder對象。
@UnsupportedAppUsage public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { // Emulate the legacy behavior. The global instance of InputMethodManager // was instantiated here. // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; } }
@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); }
可以看到最終是通過WindowManagerService
完成了Window的添加。
原文鏈接:https://blog.csdn.net/devnn/article/details/127500258
相關推薦
- 2022-08-01 GoLand利用plantuml生成UML類圖_Golang
- 2022-04-08 記一次go語言使用time.Duration類型踩過的坑_Golang
- 2022-03-23 詳細聊聊Redis的過期策略_Redis
- 2022-06-23 C#獲取計算機硬件與操作系統的相關信息_C#教程
- 2023-04-24 React組件與事件的創建使用教程_React
- 2023-02-15 docker刪除拉取的鏡像釋放內存的操作方法_docker
- 2022-04-18 Android實現繪制折線圖APP代碼_Android
- 2022-01-21 面試題:說一說es6新增方法
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支