AndroidのsetContentView、addContentView()、Window、WindowManager、Dialogソースコードの分析。



Analysis Androids Setcontentview



setContenView:

すべてのアクティビティは、onCreat()メソッドで一度setContentViewを実行する必要があり、setContentViewの役割は、作成者によって2つのカテゴリに要約されます。

1 :初めてsetContentViewを実行するときにDecorviewを作成し、それを現在のPhoneWindows(Windowsの特定の実装クラス)に関連付けます。



:DecorViewから適切なFrameLayoutを見つけ、渡したカスタムレイアウトのIDをインスタンス化します。初めて呼び出されない場合は、このFrameLayoutに初めてロードされた子ビューをクリアします。カスタムレイアウトが渡されてから再読み込みされました。

setContentViewは、次のようにPhoneWindowsのsetContentViewを呼び出します。



public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor() } 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) } mContentParent.requestApplyInsets() final Callback cb = getCallback() if (cb != null && !isDestroyed()) { cb.onContentChanged() } mContentParentExplicitlySet = true }

mContentParentは FrameLayoutについて説明しました(便宜上、フォローアップを参照してください)。ここでは、installDecor()が呼び出され、mContentView!= nullがmContentParent.removeAllViews()と呼ばれて、mContenParent内のすべてのコンポーネントがクリアされます。初めて呼び出されない場合はクリアされることを説明します。

if (mContentParent == null) { installDecor() } private void installDecor() { if (mDecor == null) { mDecor = generateDecor(-1) //Intercept the key } else{ mDecor.setWindow(this) } if (mContentParent == null) { mContentParent = generateLayout(mDecor) } }

ここでは主に、generateDecor()、generateLayout(mDecor)の2つのメソッドが実行されます。
前者はDecorViewをインスタンス化し、後者はmContentParentをインスタンス化します。最初にgenerateDecor()を見てみましょう

protected DecorView generateDecor(int featureId) { return new DecorView(context, featureId, this, getAttributes()) //Intercept the key }

okこれは、DecorViewが初めてsetContentViewのときに実際に作成されることを示しています。そうでない場合は、現在のWindwos()にリンクされ、DecorViewコンストラクターを確認します。



DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { setWindow(window) //Intercept part }

したがって、generateDecor(-1)の後、DecorViewにはインスタンスがあり、Windowsに接続されています。次に、generateLayout(mDecor)を確認します。

protected ViewGroup generateLayout(DecorView decor) { layoutResource = R.layout.screen_simple View in = mLayoutInflater.inflate(layoutResource, null) decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)) ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT) return contentParent //Interception part }

ここで、適切なサブビューをDecorViewに追加してから、
findViewById()はVIewGroupを取得し、

public T findViewById(@IdRes int id) { return getDecorView().findViewById(id) }

したがって、findViewById()は、DecorViewでID ID_ANDROID_CONTENTのViewGroupを検索します。このViewGroupは、DecorViewによって追加された子ビューに含まれている必要があります。
ID_ANDROID_CONTENTの値を見てください

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content

R.layout.screen_simpleのXMLファイルを見てください

<LinearLayout xmlns:android='http://schemas.android.com/apk/res/android' android:layout_width='match_parent' android:layout_height='match_parent' android:fitsSystemWindows='true' android:orientation='vertical'> <ViewStub android:id='@+id/action_mode_bar_stub' android:inflatedId='@+id/action_mode_bar' android:layout='@layout/action_mode_bar' android:layout_width='match_parent' android:layout_height='wrap_content' android:theme='?attr/actionBarTheme' /> <FrameLayout android:id='@android:id/content' android:layout_width='match_parent' android:layout_height='match_parent' android:foregroundInsidePadding='false' android:foregroundGravity='fill_horizontal|top' android:foreground='?android:attr/windowContentOverlay' /> LinearLayout>

確かに、IDがcontentであるFrameLayoutがあり、これはmContentParentです。これが、渡したIDをインスタンス化するために残っているものです。 setContentViewの残りのコードを見てください

public void setContentView(int layoutResID) { if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()) transitionTo(newScene) } else { mLayoutInflater.inflate(layoutResID, mContentParent) } }

ここでは、レイアウトで渡したIDをインスタンス化し、mContentParentに配置します。

addContentView

public void addContentView(View view, ViewGroup.LayoutParams params) { getWindow().addContentView(view, params) initWindowDecorActionBar() }

PhoneWIndowsで引き続きaddContentView()を呼び出しています

public void addContentView(View view, ViewGroup.LayoutParams params) { if (mContentParent == null) { installDecor() } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { // TODO Augment the scenes/transitions API to support this. Log.v(TAG, 'addContentView does not support content transitions') } mContentParent.addView(view, params) mContentParent.requestApplyInsets() final Callback cb = getCallback() if (cb != null && !isDestroyed()) { cb.onContentChanged() } }

addContentVIewはinstallDecor()も実行できることがわかります。つまり、setContentViewが実行されない場合、setContentViewとは異なり、addContentViewの通常の使用には影響しません。setContentViewが複数回呼び出されると、前回のmContentParentが次回クリアされます。 addContentVIewは単に呼び出されますが、mContentParent.addVIew()は、複数回追加できることを意味します。

Window.addContentView

ソースコードを読んだ後、Window.addContentView()は最終的にPhoneWindow.addContentView()を呼び出したので、煩わしさがなくなりました。

ウィンドウ、WindowManager

これについて話す前に、WindowとWindowManagerに関する私の個人的な理解と見解を共有します。

窓:

  • Windowは抽象クラスであり、特定の実装クラスはPhoneWindowであり、その機能は、DecorViewを作成し、関連するパラメーターを保持することです。

  • ウィンドウ自体は、保存するビュー(DecorView)を保存せず、単なる参照です。

*ウィンドウは1つのビューにのみ関連付けることができます。

*ウィンドウとウィンドウの間の効果モデルは階層的です。つまり、上位レイヤーはデフォルトでフォーカスをプリエンプションしますが、ウィンドウパラメーターで制御できます。

WindowManager

  • WindowManagerはWindowによってインスタンス化されます。各ウィンドウには、WindowManagerへの参照が含まれています。

  • WIndowに関連付けられているすべてのビューは静的クラスに格納され、WindowManager実装クラスにはこの静的クラスが含まれているため、異なるWindowManagerが同じビュー(つまりWindow)を管理できます。

アクティビティは、作成プロセス中にデフォルトでWindowクラスを実装します

public class Activity{ private Window mWindow final void attach(......){ mWindow = new PhoneWindow(this, window, activityConfigCallback) mWindow.setWindowManager(....) } }

Windowがattach()メソッドで作成され、WindowManagerがインターフェースであり、特定の実装クラスがWindowManagerImplであることがわかります。上記から、ActivtyにWindowManagerへの参照もあることがわかります。このクラスの主な責任は何ですか?何?継承を見てください

public interface WindowManager extends ViewManager{}

ViewManagerを継承して、ビューを追加または削除できます。もちろん、DecorViewのバインドにより、削除するとエラーが報告されるため、これはWindowManager.addView()を呼び出して追加したビューのみを対象にすることができます。では、WindowManagerの具体的な実装クラスはどこでインスタンス化されますか?ソースコードを表示するために、mWindow.setWindowManager(…。)が上記で呼び出されていることに注意してください。

public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken mAppName = appName mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false) if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE) } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this) }

AppToken、appNameは、IBinderメカニズムの作成者がまだ調査していないため、当面は言及されていません。そのため、再度作成する機会があるかどうかはわかりません。もちろん、一部のウィンドウは現在のアクティビティでのみ表示する必要があるため、ウィンドウをマークするものと見なします。したがって、これらのウィンドウをマークするために、トークンがウィンドウパラメータに追加されます
。着信wm(他のウィンドウのWindowManagerImpl)を介して、上記のソースコードを引き続き確認し、独自のWindowManagerImplを再作成しました。

public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow) }

最後に、WINdow内にこのWindowManagerImplへの参照があるため、インスタンス化はOKであるため、Windowインスタンスオブジェクトには最初にWindowManagerインスタンスオブジェクトが含まれます。したがって、ウィンドウは複数のWindowManagerに対応でき、これらのWindowManagerはこのウィンドウへの参照を持ちますが、このウィンドウには1つのWindowManagerしか存在できません。
次に、アクティビティ内のWindowManagerは、ウィンドウ自体内のWindowManagerですか?

mWindowManager = mWindow.getWindowManager()

アクティビティのWindowManager参照は、WindowManagerをWindowから直接取得します。これで問題ありません。
次に、WindowまたはWindowManagerを取得する方法を学習します

ウィンドウの取得:アクティビティ内でgetWindow()を呼び出します

public Window getWindow() { return mWindow }

アクティビティのWindowオブジェクトに直接戻ります。
WindowManagerの取得:アクティビティ内でgetWindowManager()またはcontext.getSystemService((WINDOW_SERVICE)を呼び出します

public WindowManager getWindowManager() { return mWindowManager }

アクティビティのWindowManagerリファレンスに直接戻ります。

public Object getSystemService(@ServiceName @NonNull String name) { if (WINDOW_SERVICE.equals(name)) { return mWindowManager } }

最後に、アクティビティのWindowManagerが返されます。もちろん、異なるコンテキストによって返されるWindowManagerは異なることに注意する必要があります。ここには2つの違いがあります。

1つ:Windowによって生成されたさまざまなWindowManagerオブジェクト
TWO:アクティビティに対応するWindowManagerのトークン! = null、およびその他のトークン== null。これにより、異なるWIndowManagerが生成されます。

トークンがnullであるかどうかは、追加されたウィンドウに影響します。ソースコードを参照してください。

@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params) mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow) }

ウィンドウを追加する前のWindowManager.addView()は次のとおりです。applyDefaultToken(params)はウィンドウパラメーターを微調整します。実際、独自のトークンをparamsに割り当てます。

上記は何を反映できますか?ここで私は小さな例を作ります:

public int onStartCommand(Intent intent, int flags, int startId) Gravity.TOP Button button=new Button(this) WindowManager windowManager1= (WindowManager)getSystemService(WINDOW_SERVICE) windowManager1.addView(button,la) return null

これは、Serviceで作成したWindowにボタンを追加する例です。ここに注意してください la.type = WindowManager.LayoutParams.TYPE_APPLICATION
現在のアクティビティに対応するウィンドウの表示を指します。アクティビティがスタックの一番上にない場合、そのアクティビティは表示されません。
エラーが発生します:
トークンがnullであることを意味します。サービス自体にはWindowManagerインスタンスがないため、他のContext実装クラスから取得する必要がありますが、それらはActivityではないため、token == nullになり、エラーが報告されます。
上記のコードをアクティビティに配置すると、正常に実行されます。したがって、トークンはアクティビティを識別する必要があります。
ただし、WindowManager.LayoutParams.TYPE_APPLICATIONは他のものに変更され、WindowManager.LayoutParams.TYPE_PHONEなどの表示は正常です。これは、アプリケーションが実行されている限り、アクティビティがないかどうかを気にする必要はありません。画面。

WindowManager.addView()

上記のViewの追加またはVIewGroupを介した追加は親のみに依存し、これらの親の上部はDecorviewであるため、WindowManager.addView()の追加は実質的な追加と見なされることを個人的に理解しています。これらのVIewは直接です。または、DecorVIew Connectedに間接的に接続してツリー構造を形成し、ウィンドウに表示して関連する操作を実行できるようにします。したがって、実際に追加されるのは、DecorViewだけです。
WindowManager.addVIew()を直接呼び出す場合は、異なります。ソースコードを見てください:
WindowManagerは、インターフェイス固有の実装クラスのみです。WindowWindowManagerImpl

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params) mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow) }

WindowManagerImplは、mGlobalを呼び出すことにより、VIew、つまり操作ウィンドウを操作します。それはただのエージェントです。 mGlobalは、インスタンス化されたWindowManagerGlobalオブジェクトです。注意: アプリ全体でWindowManagerGlobalオブジェクトは1つだけです ソースコードを見てください:

private static WindowManagerGlobal sDefaultWindowManager public static WindowManagerGlobal getInstance() { synchronized (WindowManagerGlobal.class) { if (sDefaultWindowManager == null) { sDefaultWindowManager = new WindowManagerGlobal() } return sDefaultWindowManager } }

ええ、そのaddView()を見てください

public final class WindowManagerGlobal { private final ArrayList mViews = new ArrayList() private final ArrayList mRoots = new ArrayList() private final ArrayList mParams = new ArrayList() 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) try { root.setView(view, wparams, panelParentView) } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true) } throw e } } }

作成者は、mGlobalがWindowManagerServiceを処理することを理解してから、ウィンドウを生成および破棄します。上記のコードでは、mViews、mRoots、およびmParamsは3つのコレクションオブジェクトです。 mRootsの作成者は、記録ウィンドウの階層関係を推測し、WMSと通信します。 mVIewsはすべてのウィンドウのビューを保存し、mParamsはすべてのウィンドウのパラメーターを保存します。最後に、root.setView(view、wparams、panelParentView)を呼び出してウィンドウを表示します。

ダイアログ

ダイアログは、実際にはWindowManagerを介して追加された単なるビューです。

public class Dialog .......{ private final WindowManager mWindowManager final Context mContext final Window mWindow View mDecor Dialog(Context context ,........){ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE) final Window w = new PhoneWindow(mContext) mWindow = w } }

Window、WindowManager、およびDecorViewもDIalog内に格納されていることがわかります。初期化プロセスでは、mWindowManager =(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)次に、ActivityのWindowManagerを取得するので、トークン! = null、次に新しいPhoneWindow。上で述べたように、Windowの目的は、装飾を生成し、ウィンドウパラメータを保存することです。次に、show()メソッドを見てください。

public void show() { mDecor = mWindow.getDecorView() WindowManager.LayoutParams l = mWindow.getAttributes() ..... mWindowManager.addView(mDecor, l) }

実際、プロセスは非常に簡単です。ウィンドウからDecorViewとParamsを取得し、Paramsを調整して、最後にウィンドウを追加します。 dismiss()も非常に簡単です。

mWindowManager.removeViewImmediate(mDecor)

実際、removeViewImmediate()を呼び出し、WindowManagerでは次のように呼び出します。

@Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true) }

私はそれをmGlobaに渡し、ウィンドウを削除するためにWMSと通信しました。 WMSの内部運用については、引き続き検討が必要です。 。 。 。

何か問題があれば、Shaoxiaが私に思い出させてくれることを願っています。ありがとうございました! !