Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

setContentView那些事

$
0
0

刨根问底setContentView

在平时的android开发中,经常会使用到Activity#setContentView方法来设置我们自己的布局,那么setContentView中到底做了什么,我们的布局
是怎么加载并显示到手机屏幕上的,这就是今天要讨论的内容,看下Activity#setContentView方法

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}

可以看到这里是使用getWindow().setContentView(layoutResID);来进行具体的设置,那么getWindow()又是什么

public Window getWindow() {
        return mWindow;
}

这里直接返回mWindow,其实mWindow是在Activity#attach方法中赋值的。

 mWindow = new PhoneWindow(this);

根据前面的activity启动流程中知道Activity#attach方法是在activity启动过程中执行的,所以当该activity启动完成以后,mWindow其实就是PhoneWindow

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;

@Override
public void setContentView(int layoutResID) {

        if (mContentParent == null) {//如果我们之前没有设置过布局,或者第一次设置布局
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {//默认为false
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 加载我们设置的布局到mContentParent中去
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        // 可以多次设置布局,当布局发生改变,会回调activity中的方法
        final Window.Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
}

在activity中,我们可以多次设置布局,如果我们之前没有设置过布局,或者第一次设置布局,此时mContentParent就为null,mContentParent是我们设置布局的父布局,是一个ViewGroup,也就是系统中id是”com.android.internal.R.id.content”的ViewGroup

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

hasFeature(FEATURE_CONTENT_TRANSITIONS)默认值为false,通过inflate来加载我们设置的布局

创建DecorView

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); // 1.创建一个DecorView
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) { // 如果第一次设置布局,mContentParent是我们设置布局的父布局
            mContentParent = generateLayout(mDecor);

            .......
        }
    }

将需要显示的布局添加到DecorView中

DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。在该布局下面,有标题view和内容view这两个子元素,而内容view则是上面提到的mContentParent

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 获取系统自定义的一些属性
        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        // 下面是根据应用层在代码中或者样式文件里设置的requestFeature进行具体的设置,可以看到这里
        // requestFeature(FEATURE_NO_TITLE)的设置就是在setContentView中设置的,这也就是为什么
        // 我们必须要在setContentView之前设置requestFeature(FEATURE_NO_TITLE)的原因,其实不止这一个,
        // 下面的属性都应遵循这样的规则
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        ......

        // 设置WindowManager窗口显示的一些布局属性
        WindowManager.LayoutParams params = getAttributes();

        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

        if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            if (!haveDimAmount()) {
                params.dimAmount = a.getFloat(
                        android.R.styleable.Window_backgroundDimAmount, 0.5f);
            }
        }

        .....
        mDecor.startChanging();
        // 加载设置的布局,并且添加到decor中
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

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

        mDecor.finishChanging();

        return contentParent;
    }

DecorView和Window的关联

到现在位置,我们的布局已经加载到了DecorView中,那么DecorView是怎么和window关联起来的呢,在activity启动流程中,我们知道当activity启动的时候,会执行ActivityThread#handleLaunchActivity方法


private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
    ....
    Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);

        }
    ....

}





final void handleResumeActivity(IBinder token,
      ....
            // 获取当前activity对应的window,并将其和DecorView进行关联
            // 这里多说一句,由于
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.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 (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    // 添加当前decor和对应的layoutparams到window中
                    wm.addView(decor, l);
                }
           }
    .....
}

我们知道Window的实现类是WindowManagerImpl,看下WindowManagerImpl#addView

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        // 每一个window都又一个唯一标识的token,这里如果没有,则设置系统默认的
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
}

mGlobal是一个WindowManagerGlobal类型对象,最终添加view到窗口显示也是由它来具体操作的

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        .......
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            .......
            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 {
            // 2. 通过ViewRootImpl设置当前DecorView
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

View的绘制流程

继续,看下ViewRootImpl的setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ....
    requestLayout(); // 核心的调用,requestLayout开始绘制当前DecorView
    ....


}


@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
       checkThread(); //检查当前是否是UI线程
       mLayoutRequested = true;
       scheduleTraversals(); 
    }
}

在requestLayout方法中,又调用了scheduleTraversals,我们继续:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里开启一个TraversalRunnable线程

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
}




void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals方法很长,实际上主要做了下面三件事情:

private void performTraversals() {
    ....
    if (需要重新计算宽度和高度) {
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    if (需要重新布局) {
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    }

    if (需要重新布局) {
        performDraw();
    }

    ....

}

在performMeasure中调用了对应的View的measure方法

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}



public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ....
    // measure ourselves, this should set the measured dimension flag back
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ....
}

可以发现,最终通过onMeasure方法设置我们的View的宽度和高度,并且一定要通过setMeasuredDimension来设置

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    ....
    final View host = mView;
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ....

}



public void layout(int l, int t, int r, int b) {
        ....

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

        }

}

这里的onLayout主要是用来设置ViewGroup的子元素的位置的,这也就是为什么我们需要在自定义的ViewGroup中onLayout方法来设置子元素的位置了。

接下来是performDraw

private void performDraw() {
    ....
     try {
            draw(fullRedrawNeeded);
     } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
    ....

}



private void draw(boolean fullRedrawNeeded) {

        .....
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
        }

        // 如果是动画的,比如scroller控制的,则又会接着重新调用scheduleTraversals进行绘制
        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
}


private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
    ....
    mView.draw(canvas);
    ....

}

在看下View#draw方法

@CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // we're done...
            return;
        }


        .....
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
}

在该方法中调用了onDraw方法进行具体的绘制操作.

ok,关于setContentView的流程就到这里了,其中包括DecorView是如何与Window建立关联的,以及View的绘制流程。如有补充,欢迎指教。

作者:mockingbirds 发表于2016/11/8 20:44:07 原文链接
阅读:14 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>