blog

アンドロイドビューの表示

アクティビティを開始するメソッドを通じて、マクロ的には、メソッドが呼び出されると、APPが開始され、インターフェイス全体が表示され、この時点で、クリックやスワイプなどの対話を開始することができます。し...

Aug 30, 2020 · 13 min. read
シェア

はじめに

startActivityメソッドを介してアクティビティを開始すると、マクロ的には、メソッドが呼び出されると、APPが起動され、インターフェイス全体が表示され、この時点で、クリックやスワイプ、対話を開始することができます。しかし、システムは、アクションの多くを行うには、プロセスを作成し、アプリケーションを作成し、アクティビティを作成し、アクティビティのライフサイクル管理上のAMSの使用は、すべての準備が整ったときに、onCreateメソッドのアクティビティを呼び出すと、DecorViewにカスタムビューのコンテンツを入力し、レジュームの実行されます。アクティビティは、ウィンドウ全体を表示するmakeVisibleメソッドを呼び出します。インターフェイスが表示されますので、ユーザーは、WMSのこの兄弟の支援のための必要性の特定のコンテンツを見ることができますが、このプロセスでは、AMSは、活動のライフサイクル管理の役割を果たしています。

システムバージョン番号の説明

この分析のために分析されたシステムのバージョンはAndroidバージョン9.0です。

概要

makeVisibleメソッドに移り、アクティビティのattachメソッドが呼び出されたときに表示される2つのウィンドウ関連クラス、WindowManagerとWindowについて説明します:

//frameworks/base/core/java/android/app/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) {
 
 ...
 mWindow = new PhoneWindow(this, window, activityConfigCallback);
 mWindow.setWindowControllerCallback(this);
 mWindow.setCallback(this);
 mWindow.setOnWindowDismissedCallback(this);
 
 ...
 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());
 }
 mWindowManager = mWindow.getWindowManager();
 ...
}

Windowクラスは抽象クラスであり、クラスの具体的な実装が必要であり、クラスの実装はPhoneWindowです:

/frameworks/base/core/java/android/view/Window.java
public WindowManager getWindowManager() {
 return mWindowManager;
}
/frameworks/base/core/java/android/view/Window.java
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);
}
//frameworks/base/core/java/android/view/WindowManagerImpl.java public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow); }

WindowManagerクラスはインターフェースクラスであり、特定のメソッドを実装するにはWindowManagerImplが必要です。

addView

//frameworks/base/core/java/android/app/Activity.java
void makeVisible() {
 if (!mWindowAdded) {
 ViewManager wm = getWindowManager();
 wm.addView(mDecor, getWindow().getAttributes());
 mWindowAdded = true;
 }
 mDecor.setVisibility(View.VISIBLE);
}

makeVisibleでは、システムはaddViewメソッドを一度呼び出しており、この時、addViewが作られていなければ、DecorViewをウィンドウに追加するために、もう一度判定を行います。getWindowManager()はWindowManagerImplオブジェクトから取得したメソッドを返すので、ここでaddViewは実際にWindowManagerImplメソッドを呼び出します:

//frameworks/base/core/java/android/view/WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
 applyDefaultToken(params);
 mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

これは単なる演出で、以下のように初期化されたmGlobalメンバーに対して直接メソッドを呼び出しているとは知りませんでした:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

初期化は宣言時に行われ、getInstanceは以下のように実装されます:

//frameworks/base/core/java/android/view/WindowManagerGlobal.java
public static WindowManagerGlobal getInstance() {
 synchronized (WindowManagerGlobal.class) {
 if (sDefaultWindowManager == null) {
 sDefaultWindowManager = new WindowManagerGlobal();
 }
 return sDefaultWindowManager;
 }
}

ご覧のように、WindowManagerGlobalクラスはシングルトンクラスです。 addViewは最終的にWindowManagerGlobalクラスによって実装されるので、その主要メンバを見てみましょう:

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
 new ArrayList<WindowManager.LayoutParams>();

APPは複数のウィンドウを持つことがあるので、ArrayListが格納に使用されます。その中でもmViewsはDecorViewを格納するために使用され、全てのウィンドウのビューがここに格納されます;

mRoots は、ビューに対応する ViewRootImpl オブジェクトを格納するために使用され、ビューと WMS 間の通信のためのインターフェースを提供します;

mParamsは各ウィンドウのレイアウトパラメータを保持するために使用されます。

上記の3つのメンバーを紹介した後、具体的なaddViewの実装を見てみましょう:

//frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
 Display display, Window parentWindow) {
 ...
 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
 ...
 ViewRootImpl root;
 View panelParentView = null;
 synchronized (mLock) {
 
 ...
 //ビューが追加されているかどうか、追加されている場合だけでなく、すでに破棄されているかどうかを判断するために、それ以外の場合
 //ビューがすでに追加されていることを示すIllegalStateExceptionを投げる
 int index = findViewLocked(view, false); 
 if (index >= 0) {
 if (mDyingViews.contains(view)) {
 // Don't wait for MSG_DIE to make it's way through root's queue.
 mRoots.get(index).doDie();
 } else {
 throw new IllegalStateException("View " + view
 + " has already been added to the window manager.");
 }
 // The previous removeView() had not completed executing. Now it has.
 }
 
 ...
 //ViewRootImplオブジェクトを取得し、ビューの表示はまだ途中の処理でそれに依存する必要がある
 root = new ViewRootImpl(view.getContext(), display);
 view.setLayoutParams(wparams);
 mViews.add(view); //ビューを追加する
 mRoots.add(root); //ViewRootImplオブジェクトを追加する
 mParams.add(wparams); //ウィンドウレイアウトのパラメーターを追加する
 // do this last because it fires off messages to start doing things
 try {
 //ViewRootImplオブジェクトのsetViewメソッドを呼び出す
 root.setView(view, wparams, panelParentView);
 } catch (RuntimeException e) {
 // BadTokenException or InvalidDisplayException, clean up.
 if (index >= 0) {
 removeViewLocked(index, true);
 }
 throw e;
 }
 
 }
}

addViewで行われることは主に3つあります:

1.追加されたビューがすでに追加されているかどうか、もしそうなら破棄されているかどうか、そうでなければIllegalStateExceptionがスローされ、ビューがすでに追加されていることを示す;
2.ビューが追加されていない場合は、ビューに基づいて対応するViewRootImplオブジェクトをインスタンス化してmRootsリストに追加し、ビューとparamsも対応するリストに追加する;
3.次のステップのために、ViewRootImplオブジェクトのsetViewメソッドを呼び出す;

今のところ、WMS関連の処理は見られません。続きを読む

setView

前述のように、ViewRootImpl クラスはビューと WMS の橋渡しとなり、setView メソッドは WMS との通信を開始します:

//frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
 synchronized (this) {
 ...
 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(), mWinFrame,
 mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
 mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
 } catch (RemoteException e) {
 ...
 } finally {
 if (restore) {
 attrs.restore();
 }
 }
 ...
 }
 
}

requestLayoutメソッドは描画を開始するために使用され、最終的にperformTraversalsメソッドを呼び出します。このメソッドには800行以上のコードが含まれ、主にperformMeasure()、performLayout()、performDraw()の3つのメソッドを実行します、performDraw()メソッドで、それぞれ計測、レイアウト、描画の3つのアクションに対応しています。描画とは、UIデータを表面に書き込み、最終的に画面に表示することです。

addToDisplay

requestLayout メソッドが最初に来ますが、performTraversals メソッドは message メソッド経由で実行されるため、最終的な実装は addToDisplay の後になります。addToDisplayの実装を見る前に、mWindowSessionクラスが何であるかを知っておくことが重要です:

//frameworks/base/core/java/android/view/ViewRootImpl.java
mWindowSession = WindowManagerGlobal.getWindowSession();
public static IWindowSession getWindowSession() {
 synchronized (WindowManagerGlobal.class) {
 if (sWindowSession == null) {
 try {
 InputMethodManager imm = InputMethodManager.getInstance();
 //WMSでセッションを開き
 IWindowManager windowManager = getWindowManagerService();
 sWindowSession = windowManager.openSession(
 new IWindowSessionCallback.Stub() {
 @Override
 public void onAnimatorScaleChanged(float scale) {
 ValueAnimator.setDurationScale(scale);
 }
 },
 imm.getClient(), imm.getInputContext());
 } catch (RemoteException e) {
 throw e.rethrowFromSystemServer();
 }
 }
 return sWindowSession;
 }
}

mWindowSessionはWMSによって生成され、返されます。mWindowSessionはシングルトンであり、1つのプロセスに対してmWindowSessionオブジェクトは1つしかありません。実装は以下の通りです:

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
 IInputContext inputContext) {
 if (client == null) throw new IllegalArgumentException("null client");
 if (inputContext == null) throw new IllegalArgumentException("null inputContext");
 Session session = new Session(this, callback, client, inputContext);
 return session;
}

mWindowSessionの由来を知った上で、addToDisplayの実装に戻りましょう:

//frameworks/base/services/core/java/com/android/server/wm/Session.java
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);
}

mWindowSessionはWMSのプロキシインタフェースであり、APPプロセスからの要求に応答し、物事の具体的な処理はまだWMSに戻る必要があります。

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
 LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
 Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
 DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
 ...
 synchronized(mWindowMap) {
 if (!mDisplayReady) {
 throw new IllegalStateException("Display has not been initialialized");
 }
 ...
 
 WindowToken token = displayContent.getWindowToken(
 hasParent ? parentWindow.mAttrs.token : attrs.token);
 
 ...
 if (token == null) {
 ...
 token = new WindowToken(this, binder, type, false, displayContent,
 session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
 } else if {
 ...
 }
 ...
 final WindowState win = new WindowState(this, session, client, token, parentWindow,
 appOp[0], seq, attrs, viewVisibility, session.mUid,
 session.mCanAddInternalSystemWindow);
 
 ...
 final boolean openInputChannels = (outInputChannel != null
 && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
 if (openInputChannels) {
 win.openInputChannel(outInputChannel);
 }
 ...
 win.attach();
 mWindowMap.put(client.asBinder(), win);
 ...
 
 }

addWindowでは以下のことが行われます:

1.Windowが有効であることを確認するために、タイプやトークンによって検証し、分類する;
2.Windowに対応するWindowStateオブジェクトを一つずつ作る;
3.inputChanelを登録する;
4.WindowStateのattachメソッドを呼び出し、サーフェスフリンガーとの接続を作成する;

WMSのaddWindowはウィンドウを管理するためのWindowStateを作成し、タッチイベントを渡すためのinputChanelを登録します。そして、後で実際のサーフェスを作成するための準備として、surfaceflinerへの接続を作成します。以下は上記で行われていることを説明する図です:

APPプロセスはIwindowSessionを通じてウィンドウの追加を完了し、WMSはIWindowを通じてタッチなどの入力イベントをアプリケーションプロセスに報告します。IWindowは、ウィンドウを追加するときにaddToDisplayメソッドで渡される最初のパラメータです。

surface

ウィンドウが追加されたら、それを描画する必要があり、前述のように、描画アクションはperformTraversalsメソッドに配置されます。このメソッドの実装を見てみましょう:

private void performTraversals() {
 final View host = mView;
 ...
 //実際にサーフェスを描けるサーフェスを取得するメソッド
 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
 ...
 //描画を開始する
 performDraw();
}

relayoutWindowメソッドは表面の実物を取得します。ウィンドウにビューを追加する過程で作成されたViewRootImplオブジェクトを覚えていますか?このオブジェクトを作成すると、新しいサーフェスオブジェクトも作成されます:

public final Surface mSurface = new Surface();

しかし、サーフェスは空で、relayoutWindowメソッドが呼ばれたときに満たされます。アプリケーションレイヤーが描画できるのはそのときだけです。relayoutWindow の実装を見てみましょう:

//frameworks/base/core/java/android/view/ViewRootImpl.java
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
 boolean insetsPending) throws RemoteException {
 ...
 int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
 (int) (mView.getMeasuredWidth() * appScale + 0.5f),
 (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
 insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
 mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
 mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
 mPendingMergedConfiguration, mSurface);
 ...
}

relayoutメソッドはIWindowSession経由で呼び出されます。 ここで渡されるmSurfaceパラメータは実際には空のシェルであることに注意してください。空のシェルなので、何を使うのでしょうか?AIDLツールによって生成されたJavaファイルを見ると、このパラメータが出力パラメータであることを示すout属性を持つことがわかります。relayoutメソッドは、最終的にWMS relayoutWindowを呼び出します:

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
 int requestedWidth, int requestedHeight, int viewVisibility, int flags,
 long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
 Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
 DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
 Surface outSurface) {
 ...
 try {
 result = createSurfaceControl(outSurface, result, win, winAnimator);
 } catch (Exception e) {
 mInputMonitor.updateInputWindowsLw(true /*force*/);
 Slog.w(TAG_WM, "Exception thrown when creating surface for client "
 + client + " (" + win.mAttrs.getTitle() + ")",
 e);
 Binder.restoreCallingIdentity(origId);
 return 0;
 }
 ...
}

draw

サーフェスが配置されたら、次はそれを描画します。formDrawメソッドで描画を開始します:

private void performDraw() { ... try { boolean canUseAsync = draw(fullRedrawNeeded); if (usingAsyncReport && !canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null); usingAsyncReport = false; } } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... } private boolean draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } ... } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { ... canvas = mSurface.lockCanvas(dirty); ... mView.draw(canvas); ... surface.unlockCanvasAndPost(canvas); //このサーフェスも実はmSurfaceである }

formDrawメソッドから、最終的にdrawSoftwareメソッドを呼び出しますし、キャンバスオブジェクトに到達するSurfaceクラスのlockCanvasメソッドを介して、描画が実施されているキャンバスのビュー。ここで、mViewは実際にはDecorViewと呼ばれ、その描画メソッドで、自分自身を描画するonDrawメソッドを呼び出しますし、サブビューを描画するdispatchDrawメソッドを呼び出すので、すべてのビューの描画のウィンドウが完了するまで、再帰的です。描画が完了すると、Surface クラスは unlockCanvasAndPost メソッドを呼び出して、描画されたビューをウィンドウにポストし、表示を解析します。ユーザーはビューを見ることができるようになります。

結論

ビューの表示処理はこれくらいです。スキルが足りず、通過するだけで深入りしていない処理もあります。もっと深く理解したいので、Androidのソースコードを全部分解するのは嫌です。しかし、この方法は頭を見つけやすく、結果的にコードが細部をさまよっていることがわかりました。

まずは一歩ずつ、プロセスを整理してください。大まかな概要がわかったら、いくつかのモジュールに深く入っていきましょう。

コードフローの並べ替えは本当に便利で、少なくとも問題に対処するときには、アイデアが浮かび、混乱することはありません。

これならくっつくでしょう。

Read next

TS+reactデモ:プロジェクトをビルドする

プロジェクトのディレクトリ構造を構築するには、公式のcreate-react-appを使用します。antdのオンデマンド導入を使用するには、ルートディレクトリに新しいconfigを作成する必要があります - 効果を確認するには、srcディレクトリに新しいcompを作成します。

Aug 30, 2020 · 2 min read