Android setContentViewメソッド解析(二)
16463 ワード
前回、ActivityでsetContentView()を呼び出したのはPhoneWindowのsetContentView()です.それからDecorViewを初期化し、DecorViewはPhoneWindowの内部クラスでFrameLayoutのサブクラスで、実はDecorViewはこの画面のViewです.mDecorの初期化が完了すると、mContentParent=generateLayout(mDecor)というコードが表示されます.私たちはそれがどのように実現されているかを見てみましょう.コードが多いので、肝心な点を探してみましょう.
protected ViewGroup generateLayout(DecorView decor) {
…………
/* title*/
if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
…………
if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
…………
int layoutResource;
…………
/* layout*/
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
…………
} else { layoutResource = com.android.internal.R.layout.screen_title; }
…………
{ layoutResource = com.android.internal.R.layout.screen_simple; }
/* layout, add DecorView ,inflate xml , listView getView fragment onCreateView , 。*/
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
…………
if (mTitle != null) {
setTitle(mTitle);
}
setTitleColor(mTitleColor);
…………
return contentParent;}
我们通过查找发现,Android系统自定义的layout有很多,下面只截图了一部分,
通过上面代码我们可以看出generateLayout返回的是contentParent,它其实就是我们自定义layout的父控件,其中ID_ANDROID_CONTENT是在window类中定义的,
/** * 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;
システムがカスタマイズしたlayoutにはこのIDがcontentのViewがあります.勝手に開けてみましょう.次はscreenです.title.xml<!-- This is an optimized layout for a screen, with the minimum set of features enabled. --> <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"<strong></strong> android:layout_height="wrap_content" /> <FrameLayout android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" style="?android:attr/windowTitleBackgroundStyle"><font size="" color=""></font> <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>
これまでsetContentView()のソース分析の差は多くなく、ViewがActivityにどのように表示されているのか、Activityでこのようなコードが見つかったのか、戸惑う人も多いかもしれません.void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }
ここを見て笑った人もいるかもしれませんが、このmDecorは私たちが上述したDecorViewではないでしょうか.makeVisibleはどこで呼び出されたのでしょうか.ソースコードで検索すると、UI自体を表示しない場合は、デフォルトでActivity構成で設定されていることがわかります.これは一般的には少ないですが、ActivityでmakeVisible()メソッドを呼び出すほか、Activity Threadクラスでもこのメソッドが呼び出されていることがわかりました.前回はmainメソッドでlooperを初期化するほか、Activityのライフサイクルの呼び出しを担当し、Androidのメッセージメカニズムについて時間がある場合、メインスレッドでlooperを初期化する必要はありませんが、サブスレッドでメッセージを処理するにはlooperを初期化する必要があります.理由はここにあります.さて、本題に戻りますと、先ほどActivity ThreadクラスでmakeVisible()メソッドを呼び出すといえば、全部で2つありますが、1つは/** * Control whether this activity's main window is visible. This is intended * only for the special case of an activity that is not going to show a * UI itself, but can't just finish prior to onResume() because it needs * to wait for a service binding or such. Setting this to false allows * you to prevent your UI from being shown during that time. * * <p>The default value for this is taken from the * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme. */ public void setVisible(boolean visible) { if (mVisibleFromClient != visible) { mVisibleFromClient = visible; if (mVisibleFromServer) { if (visible) makeVisible(); else mDecor.setVisibility(View.INVISIBLE); } } }
private void updateVisibility(ActivityClientRecord r, boolean show) { ………… r.activity.makeVisible(); ………… }
それは主にStopActivityとWindowVisibilityで呼び出されています.ここでは紹介していませんが、もう一つの場所があります.皆さんはよく知っているかもしれません.ソースコードが多くて、私たちは私たちが見ているものを見て、他のものを少し見ます.final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ………… ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; ………… if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); ………… if (r.activity.mVisibleFromClient) { r.activity.makeVisible() } } ………… } final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ………… ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; ………… if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); ………… if (r.activity.mVisibleFromClient) { r.activity.makeVisible() } } ………… }
実はその内部で呼び出されたのはActivityのOnresume()メソッドで、handleResumeActivityはhandlerのRESUME_を除いてACTIVITYブランチ呼び出しに加えてhandleLaunchActivity()というメソッドで呼び出され、はい、さっき話したmakeVisibleの方法に戻ります.mDecorは私たちが見たページ全体であることを知っています.それからVISIBLEを譲ります.しかし、その前にどのようにaddしたのか、私たちはこのようなコードwmを見ました.addView(mDecor, getWindow().getAttributes());次にViewManagerクラスを見てみましょうprivate void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { ………… /*performLaunchActivity Activity Activity onCreate() onStart() , , , */ Activity a = performLaunchActivity(r, customIntent); ………… handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed); ………… }
/** Interface to let you add and remove child views to an Activity. To get an instance * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. */ public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
彼がただ1つのインタフェースであることを発見して、3つの方法があって、増加して、更新して、削除して、Viewを管理するのに十分で、ソースコードを通じて私達はWindowManagerがViewManagerインタフェースを実現することを発見します.public interface WindowManager extends ViewManager {
しかし、彼はどこで実現したのでしょうか.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) { ………… mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); ………… mWindowManager = mWindow.getWindowManager(); ………… }
見たでしょうWindowManagerの真の実装クラスはWindowManagerImplで、その中にはWindowManagerGlobalがパッケージされています.すべての操作はWindowManagerGlobalによって実現されています.方法が多いので、勝手に選んでみましょう.他は分析していません.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); }
私たちはView RootImplというクラスを見ました.このクラスはView Parentのサブクラスです.このクラスを軽視することはできません.すべてのViewのサブクラスのmeasure、Layout、Drawはイベントの配布メカニズムを含めてそれを経なければなりません.ここで知っていればいいので、先に紹介しません.mViewsとmRootsは実はlistで、ここで最後に太くなったコードを見て、私たちは追跡を続けて、それを見つけました.public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ………… ViewRootImpl root; ………… 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) { ………… } }
上に投げ出された異常が興奮しているかどうかを見たら、dialogまたはPopupWindowがActivityでポップアップされなければならないことを知っています.Activityが存在しない場合は、handleでポップアップされたdialogまたはPopupwindowを通じて、上に現れた異常を報告します.このようなコードも見ましたmRootView=viewでは、mAttachInfoとは何でしょうか.ViewrootImplのコンストラクション関数でこのようなコードが見られます.mAttachInfo=new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);,AttachinfoはViewの内部クラスであることを知っていますが、/** * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ………… mFallbackEventHandler.setView(view); ………… mAttachInfo.mRootView = view; ………… requestLayout(); ………… res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel); ………… if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } mPendingOverscanInsets.set(0, 0, 0, 0); mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingVisibleInsets.set(0, 0, 0, 0); if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- another window of this type already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- permission denied for this window type"); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException( "Unable to add window " + mWindow + " -- the specified display can not be found"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } ………… view.assignParent(this) ………… } } }
内部で見つけただから/** * The view root impl. */ final ViewRootImpl mViewRootImpl;
mDecorはwindowManagerでViewrootImplのmAttachinfoにロードした後、Activity起動時にActivityを呼び出すmakeVisible()メソッドで表示します.はい、setContentViewのメソッドは分析済みです.他のものもあげられない時間があります.自分で分析するのは油断しかねません.皆さん、議論してください.ありがとうございます.android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>