Activityインタフェースのロードプロセス

87077 ワード

ActivityのOnCreate()メソッドでsetContentView()を呼び出してlayoutを追加することはよく知られていますが、呼び出さないとインタフェースが見えません.このメソッドが何をしたのか、Activityのインタフェースがどのようにロードされているのかを見てみましょう.
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
}

setContentView()メソッドに進みます.
public void setContentView(@LayoutResintlayoutResID){
	getWindow().setContentView(layoutResID);
	initWindowDecorActionBar();
}

getWindowのsetContentViewを呼び出し、まずgetWindow()を見てみましょう.
private Window mWindow;
...
public Window getWindow(){
	return mWindow;
}

mWindowインスタンスを返します.このmWindowはどこで値を付けますか.Activityの起動プロセスでは、最終的にActivity Threadクラスを呼び出すperformLaunchActivity()メソッドがActivityインスタンスを作成し、activityのattachメソッドを呼び出し、最後にOnCreate()メソッドを呼び出すことを知っています.Activityクラスのattach()メソッドに入ってみましょう.
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*/);
	// PhoneWindow, window null
	mWindow = new PhoneWindow(this, window, activityConfigCallback);
	mWindow.setWindowControllerCallback(this);
	mWindow.setCallback(this);
	mWindow.setOnWindowDismissedCallback(this);
	mWindow.getLayoutInflater().setPrivateFactory(this);
	if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
		mWindow.setSoftInputMode(info.softInputMode);
	}
	if (info.uiOptions != 0) {
		mWindow.setUiOptions(info.uiOptions);
	}
	...
}

PhoneWindowを作成し、mWindowに値を割り当てます.従って、getWindow()によって得られたのはPhoneWindowインスタンスである.PhoneWindowを作成するときに何をしたのか、PhoneWindowのコンストラクション関数を見てみましょう.
public PhoneWindow(Context context, Window preservedWindow,
		ActivityConfigCallback activityConfigCallback) {
	this(context);
	// Only main activity windows use decor context, all the other windows depend on whatever
	// context that was given to them.
	mUseDecorContext = true;
	//preservedWindow null, mDecor 
	if (preservedWindow != null) {
		mDecor = (DecorView) preservedWindow.getDecorView();
		mElevation = preservedWindow.getElevation();
		mLoadElevation = false;
		mForceDecorInstall = true;
		// If we're preserving window, carry over the app token from the preserved
		// window, as we'll be skipping the addView in handleResumeActivity(), and
		// the token will not be updated as for a new window.
		getAttributes().token = preservedWindow.getAttributes().token;
	}
	// Even though the device doesn't support picture-in-picture mode,
	// an user can force using it through developer options.
	boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
			DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
	mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
			PackageManager.FEATURE_PICTURE_IN_PICTURE);
	mActivityConfigCallback = activityConfigCallback;
}

preservedWindowは空なのでifのコードは実行されませんが、ここまでmDecorは割り当てられていません.コードを見続けて、PhoneWindowを呼び出したsetContentView()メソッドであることがわかりました.
@Override
public void setContentView(int layoutResID) {
	// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
	// decor, when theme attributes and the like are crystalized. Do not check the feature
	// before this happens.
	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が空の場合、installDecor()メソッドが実行されます.
private void  installDecor() {
	mForceDecorInstall = false;
	if (mDecor == null) {
		mDecor = generateDecor(-1);
		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);
		...
	}
}

前述したように、mDecorは空なので、generateDecor()を呼び出してmDecorに値を割り当てます.
protected DecorView generateDecor(int featureId) {
	// System process doesn't have application context and in that case we need to directly use
	// the context we have. Otherwise we want the application context, so we don't cling to the
	// activity.
	Context context;
	if (mUseDecorContext) {
		Context applicationContext = getContext().getApplicationContext();
		if (applicationContext == null) {
			context = getContext();
		} else {
			context = new DecorContext(applicationContext, getContext());
			if (mTheme != -1) {
				context.setTheme(mTheme);
			}
		}
	} else {
		context = getContext();
	}
	return new DecorView(context, featureId, this, getAttributes());
}

戻るとDecorView()インスタンスが作成されます.InstallDecor()メソッドに戻ると、mContentParentは空であり、generateLayout()メソッドが呼び出されてmContentParentに値が割り当てられます.
protected ViewGroup generateLayout(DecorView decor) {
	...

	// Inflate the window decor.

	int layoutResource;
	int features = getLocalFeatures();
	// System.out.println("Features: 0x" + Integer.toHexString(features));
	if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
		layoutResource = R.layout.screen_swipe_dismiss;
		setCloseOnSwipeEnabled(true);
	} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
		if (mIsFloating) {
			TypedValue res = new TypedValue();
			getContext().getTheme().resolveAttribute(
					R.attr.dialogTitleIconsDecorLayout, res, true);
			layoutResource = res.resourceId;
		} else {
			layoutResource = R.layout.screen_title_icons;
		}
		// XXX Remove this once action bar supports these features.
		removeFeature(FEATURE_ACTION_BAR);
		// System.out.println("Title Icons!");
	} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
			&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
		// Special case for a window with only a progress bar (and title).
		// XXX Need to have a no-title version of embedded windows.
		layoutResource = R.layout.screen_progress;
		// System.out.println("Progress!");
	} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
		// Special case for a window with a custom title.
		// If the window is floating, we need a dialog layout
		if (mIsFloating) {
			TypedValue res = new TypedValue();
			getContext().getTheme().resolveAttribute(
					R.attr.dialogCustomTitleDecorLayout, res, true);
			layoutResource = res.resourceId;
		} else {
			layoutResource = R.layout.screen_custom_title;
		}
		// XXX Remove this once action bar supports these features.
		removeFeature(FEATURE_ACTION_BAR);
	} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
		// If no other features and not embedded, only need a title.
		// If the window is floating, we need a dialog layout
		if (mIsFloating) {
			TypedValue res = new TypedValue();
			getContext().getTheme().resolveAttribute(
					R.attr.dialogTitleDecorLayout, res, true);
			layoutResource = res.resourceId;
		} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
			layoutResource = a.getResourceId(
					R.styleable.Window_windowActionBarFullscreenDecorLayout,
					R.layout.screen_action_bar);
		} else {
			layoutResource = R.layout.screen_title;
		}
		// System.out.println("Title!");
	} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
		layoutResource = R.layout.screen_simple_overlay_action_mode;
	} else {
		// Embedded, so no decoration is needed.
		layoutResource = R.layout.screen_simple;
		// System.out.println("Simple!");
	}

	mDecor.startChanging();
	mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	if (contentParent == null) {
		throw new RuntimeException("Window couldn't find content container view");
	}

	...

	return contentParent;
}

まず、R.layout.でlayoutResourceのレイアウトリソースを選択します.screen_titleを例に
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <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:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

レイアウトにはFrameLayoutが2つあり、上にはid参照@android:id/titleのTextViewが含まれており、下には空、id参照@android:id/contentが含まれています.そしてdecroViewのonResourcesLoaded()メソッドを呼び出しました
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
	if (mBackdropFrameRenderer != null) {
		loadBackgroundDrawablesIfNeeded();
		mBackdropFrameRenderer.onResourcesLoaded(
				this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
				mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
				getCurrentColor(mNavigationColorViewState));
	}

	mDecorCaptionView = createDecorCaptionView(inflater);
	final View root = inflater.inflate(layoutResource, null);
	if (mDecorCaptionView != null) {
		if (mDecorCaptionView.getParent() == null) {
			addView(mDecorCaptionView,
					new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
		}
		mDecorCaptionView.addView(root,
				new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
	} else {

		// Put it below the color views.
		addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
	}
	mContentRoot = (ViewGroup) root;
	initializeElevation();
}

inflate()メソッドを呼び出してViewインスタンスを作成します.
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
	final Resources res = getContext().getResources();
	if (DEBUG) {
		Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
			  + Integer.toHexString(resource) + ")");
	}

	View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
	if (view != null) {
		return view;
	}
	XmlResourceParser parser = res.getLayout(resource);
	try {
		return inflate(parser, root, attachToRoot);
	} finally {
		parser.close();
	}
}

inflateメソッドの呼び出しを続行
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
	synchronized (mConstructorArgs) {
		Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

		final Context inflaterContext = mContext;
		final AttributeSet attrs = Xml.asAttributeSet(parser);
		Context lastContext = (Context) mConstructorArgs[0];
		mConstructorArgs[0] = inflaterContext;
		View result = root;

		try {
			...
			if (TAG_MERGE.equals(name)) {
				...
			} else {
				// Temp is the root view that was found in the xml
				final View temp = createViewFromTag(root, name, inflaterContext, attrs);

				ViewGroup.LayoutParams params = null;

				if (root != null) {
					if (DEBUG) {
						System.out.println("Creating params from root: " +
								root);
					}
					// Create layout params that match root, if supplied
					params = root.generateLayoutParams(attrs);
					if (!attachToRoot) {
						// Set the layout params for temp if we are not
						// attaching. (If we are, we use addView, below)
						temp.setLayoutParams(params);
					}
				}

				if (DEBUG) {
					System.out.println("-----> start inflating children");
				}

				// Inflate all children under temp against its context.
				rInflateChildren(parser, temp, attrs, true);
				...
			}

		} catch (XmlPullParserException e) {
			...
		}

		return result;
	}
}

createView FromTag()メソッドを呼び出します.
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
		boolean ignoreThemeAttr) {
	...
	try {
		// View
		View view = tryCreateView(parent, name, context, attrs);

		if (view == null) {
			final Object lastContext = mConstructorArgs[0];
			mConstructorArgs[0] = context;
			try {
				if (-1 == name.indexOf('.')) {
					view = onCreateView(context, parent, name, attrs);
				} else {
					view = createView(context, name, null, attrs);
				}
			} finally {
				mConstructorArgs[0] = lastContext;
			}
		}

		return view;
	} catch (InflateException e) {
		...
	}
}

まずtryCreateViewを呼び出してviewを作成しようとします
public final View tryCreateView(@Nullable View parent, @NonNull String name,
	…
	View view;
	if (mFactory2 != null) {
		view = mFactory2.onCreateView(parent, name, context, attrs);
	} else if (mFactory != null) {
		view = mFactory.onCreateView(name, context, attrs);
	} else {
		view = null;
	}

	if (view == null && mPrivateFactory != null) {
		view = mPrivateFactory.onCreateView(parent, name, context, attrs);
	}

	return view;
}

2つのfactoryが空かどうかをそれぞれ判断し、factoryが設定されている場合は対応するfactoryのoncreateViewメソッドを呼び出してviewを作成し、factoryが設定されていない場合はnullを返し、viewの作成に失敗したことを示します.もし私たちのActivityがandroidを継承していたら.app.Activityしかも自分で設定していなければ、この2つのfactoryは空です.これは今注目しているポイントではありません.createViewFromTagメソッドに戻ります.tryCreateViewが返すnullのため、次のtryのif判定が実行されます.
if (view == null) {
	final Object lastContext = mConstructorArgs[0];
	mConstructorArgs[0] = context;
	try {
		if (-1 == name.indexOf('.')) {
			view = onCreateView(context, parent, name, attrs);
		} else {
			view = createView(context, name, null, attrs);
		}
	} finally {
		mConstructorArgs[0] = lastContext;
	}
}

name.indexOf(’.’)は、コントロールに「.」が含まれているか否かを判断する.含まれている場合は、これがカスタムコントロールであることを示します.いいえの場合は、Button、TexxxtViewなど、システム固有のコントロールであることを示します.次に、onCreateViewメソッドまたはcreateViewメソッドを呼び出し、コントロールタイプに基づいてViewを作成します.Inflateメソッドに戻り、rInflateChildren()メソッドが呼び出されます.
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
		boolean finishInflate) throws XmlPullParserException, IOException {
	rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

rInflateを呼び出す
void rInflate(XmlPullParser parser, View parent, Context context,
		AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

	final int depth = parser.getDepth();
	int type;
	boolean pendingRequestFocus = false;

	while (((type = parser.next()) != XmlPullParser.END_TAG ||
			parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

		if (type != XmlPullParser.START_TAG) {
			continue;
		}

		final String name = parser.getName();

		if (TAG_REQUEST_FOCUS.equals(name)) {
			...
		} else {
			final View view = createViewFromTag(parent, name, context, attrs);
			final ViewGroup viewGroup = (ViewGroup) parent;
			final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
			rInflateChildren(parser, view, attrs, true);
			viewGroup.addView(view, params);
		}
	}

	...
}

この方法では、createViewFromTagを呼び出し続けてviewを作成し、このviewをparentとしてrInflateChildren()メソッドを呼び出し続けてサブviewを作成し、外側から内側へ1層ずつ作成する.解析されたレイアウトは、前のlayoutResourceに対応するxmlリソースであり、mDecorにロードされています.generateLayout()メソッドに戻り、findViewById()でidをcomとして見つけます.android.internal.R.id.contentのview.
/**
 * The ID that the main layout in the XML layout file should have.
 */
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

findviewbyidという方法を見てみましょう.
public <T extends View> T findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}

getDecorView()のfindView()が呼び出されました.
@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}

ここではmDecorを返し、前に作成しました.この説明ID_ANDROID_CONTENTというid対応のレイアウトはすでに作成されたDecorViewで検索されていますが、実は私たちがロードしたlayoutのidが@android:id/contentのFrameLayoutレイアウトとして参照されています.generateLayout()メソッドに戻り、最後にこのFrameLayoutがmContentParentに付与された値を返します.PhoneWindowの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);
	}
	...
}

mLayoutInflaterを呼び出すinflate(layoutResID,mContentParent)は、mContentParentが作成されています.layoutResIDは、onCreate()メソッドでsetContentView()を呼び出して入力した自分で書いたxmlです.inflate()メソッドは、xmlを1階層ずつ作成し、mContentParentにロードする前に使用しました.ここまでsetContentView()メソッド全体がほぼ完了しました.InstallDecor()メソッドからDecroViewを作成し、システムのルートレイアウトをロードし、自分たちが書いたレイアウトをルートレイアウトにロードします.しかし、このステップは、各xmlリソースをロードして、システムに表示する必要があるものを教えて、これらのものを準備して、インタフェースの描画と表示を完了していません.