【Android】Viewの描画をWindowManagerの視点で理解する

序文

毎年恒例の 1024 Programmer's Day ですが、今日何も書かないとこのお祭りが気の毒に思います。熟考の末、Viewの描画について書いただけです。この記事では、View 描画の 3 つの主要なコールバック関数である onMeasure、onLayout、onDraw には焦点を当てませんが、Android フレームワークの観点から View 描画を分析します。

  • ビューはどのように画面にレンダリングされますか?
  • ViewRoot、DecorView、Activity、Window、および WindowManager の関係は何ですか?
  • ビューとサーフェスの関係は何ですか?
  • View と SurfaceFlinger および OpenGL ES の関係は何ですか?

コンピュータの画像は一般に、GPU が必要とするデータを基盤となる画像エンジンを介して GPU に出力する必要があり、ディスプレイはレンダリングされたデータを GPU から継続的に取り出して画面に表示します。

Android アーキテクチャに精通している人なら誰でも、画像をレンダリングするための基盤となる Android エンジンが OpenGL ES/Vulkan であることを知っています。では、View は誰によってレンダリングされるのでしょうか? そうです、View は最終的に基盤となるレンダリング エンジンに引き渡されますが、View から OpenGL ES まではどのようなプロセスを経てきたのでしょうか。

setContentView() プロセス

アクティビティの onCreate では、通常、setContentView を介してアクティビティのインターフェイス レイアウトを設定します。この時点で、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 を作成するために 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. 受信した layoutResID を LayoutInflator で解析し、View に解析して mContentParent に追加します。mContentParent は、xml インターフェイスの id がコンテンツと等しいレイアウトです:
ここに画像の説明を挿入
要約すると、setContentView は主に 2 つの機能を完了します:
1. DecorView をビルドします

そのため、setContentView は実際にはまだ画像のレンダリングを開始していません。

考察: setContentView を呼び出さなければ、Activity は正常に開始できますか? なぜ?

WindowManager.addView プロセス

すべての Android ビューは、WindowManager.addView を通じて画面に追加されます。アクティビティの 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また、そのトリガー時間はonResumeActivity のライフ サイクルの後であるため、View は onResume の後に画面に表示され、View の幅はレンダリングが完了した後にのみ取得できます。

主に、window の getDecorView() メソッドが呼び出される 1 つの場所を見てください。

//com.android.internal.policy.PhoneWindow
   @Override
    public final @NonNull View getDecorView() {
    
    
        if (mDecor == null || mForceDecorInstall) {
    
    
            installDecor();
        }
        return mDecor;
    }

setContentView を呼び出さなかったにもかかわらず、DecorView が初期化され、空白のページが表示されることがわかります。

次に、コードの 2 か所に注目し、WindowManager の addView メソッドを使用して DecorView をウィンドウに追加します。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 ウィンドウの初期化を開始し、Activity のメンバー変数 mWindow に割り当てます。
  • 2 ウィンドウの windowManager を設定します
  • 3 つのアクティビティは、mWindowManager を介してウィンドウ内の WindowManager を参照し、2 つの 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ご覧のとおり、ここにオブジェクト ルートが作成され、 viewrootwparamsがコレクションに保存されます。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 のウィンドウ管理システムです.ビュー ツリーを WMS に登録する前に,レイアウトを実行する必要があります.WMS はウィンドウ管理だけでなく,さまざまなイベントのディスパッチも担当するため,アプリはビュー ツリーを保証してから WMS に登録します.イベントを受け取る準備ができました。

ViewRoot は仲介役であり、View ツリーの管理者であり、WMS との通信機能も果たします。

mWindowSession.addToDisplay は、View のレンダリングを WindowManagerService に渡します。

mWindowSession は IWindowSession 型の変数で、サーバー側の実装クラスは Binder オブジェクトである Session.java です。

  @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);
    }

ようやくWindowManagerServiceWindowの追加が完了したことがわかります。

つづく...

おすすめ

転載: blog.csdn.net/devnn/article/details/127500258