Android view的显示

前言

我们通过startActivity方法来启动一个activity,宏观上来看,当我们调用该方法的时候,APP就启动了,整个界面显示,此时可以开始交互,点击或滑动。但系统在其中做了不少动作,创建进程,创建Application,创建Activity,利用AMS对activity进行生命周期管理,当一切准备就绪,调用activity的onCreate方法,将自定义view内容填充到DecorView(具体是填充到ContentParent对象中)中,接着执行resume该activity,调用makeVisible方法将整个窗口显示出来。在这个过程中,AMS扮演的是对activity进行生命周期管理的角色,而将界面显示出来,让用户能看到具体内容的,则需要WMS这位仁兄的协助。

系统版本号说明

此次分析的系统版本为Android9.0版本。

概述

我们接着makeVisible方法进行分析之前,先聊下两个跟窗口相关的类,那就是WindowManager和Window,这两个类是在调用Activity的attach方法时出现的:

//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。而WindowManager对象则是通过Window的getWindowManager方法获取:

/frameworks/base/core/java/android/view/Window.java
public WindowManager getWindowManager() {
    return mWindowManager;
}

方法很简单,直接返回mWindowManager成员,mWindowManager是在调用setWindowManager方法时实现赋值的:

/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);
}

这里通过WindowManagerImpl类的createLocalWindowManager方法创建一个WindowManagerImpl对象:

//frameworks/base/core/java/android/view/WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}

WindowManager类是个接口类,具体的方法需要WindowManagerImpl来实现。

addView

大概介绍了Activity中mWindowManager的由来,那么接下来就从makeVisible开始:

//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()返回的就是attach方法中获取的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成员的方法,而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类是个单例类,即一个进程中有且只有一个mGlobal。
既然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,所有窗口的view都存放在这里;

mRoots是用来存放view对应的ViewRootImpl对象,提供view和WMS之间通讯的接口,后续对view的处理,需要通过ViewRootImpl来实现;

mParams用来存放各个窗口的布局参数。

介绍了以上三个成员后,我们再来看看具体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) {
    
        ...
        //是否该view已经添加过,如果已经添加过,还要判断是否已经正在被销毁,否则
        //抛出IllegalStateException异常,表示该view已经被添加过
        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对象,view的显示还得靠它在中间处理
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view); //添加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中主要做了三件事:

1.所添加的view是否已经添加过,如果添加过,还要判断是否正在被销毁,否则抛出IllegalStateException异常,表示该view已经被添加过;
2.如果该view未添加过,则根据view实例化一个对应的ViewRootImpl对象,并添加到mRoots列表中,同时将view和params也添加到对应的列表中;
3.调用ViewRootImpl对象的setView方法,进行下一步处理;

到目前为止,还未看到WMS相关的处理。继续往下看。

setView

上文说了,ViewRootImpl类是view和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()方法,分别对应测量、布局以及绘制三个动作。绘制就是将UI数据写到surface中,最终显示到屏幕上。

addToDisplay

虽然requestLayout方法在前面,但由于通过消息的方法来执行performTraversals方法,所以最终执行却是在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打开一个session,并返回
                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对象。APP进程就是通过mWindowSession跟WMS通讯的。具体实现如下:

//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);
}

看到没,这里转身就调用了WMS的addWindow方法,session本身不做什么操作。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.通过type和token对Window进行验证和分类,确保其有效性;
2.创建WindowState对象,与Window一一对应;
3.注册inputChanel;
4.调用WindowState的attach方法,创建与surfaceflinger的连接;

WMS中addWindow创建了WindowState用于管理窗口,并注册inputChanel用于触摸事件的传递,这样才能进行交互。接着创建了与surfacefliner的连接,为后面创建真正的surface做准备。这里先用一个图大概描述下我们上文的内容:

在这里插入图片描述

APP进程通过IwindowSession完成窗口的添加,而WMS则通过IWindow向应用进程上报输入事件,比如触摸。IWindow是在添加窗口时,addToDisplay方法中传入的第一个参数。

surface

窗口添加后,则需要进行绘制,上文说过,绘制的动作放在performTraversals方法中。我们再来看看该方法的实现(涉及测量和布局的先不看,只看绘制相关):

private void performTraversals() {

    final View host = mView;
    ...
    //该方法中,真正获取了可以用来绘制的surface
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    ...
    //开始绘制
    performDraw();
}

relayoutWindow方法得到一块真正的surface,为什么这么说呢?还记得在将view添加窗口的过程中所创建的ViewRootImpl对象么?在创建该对象时,也会去new 一个surface对象:

public final Surface mSurface = new Surface();

但该surface是空的,当调用了relayoutWindow方法后才被填充。才能让应用层在上面进行绘制。我们看看relyoutWindow的实现:

//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);
    ...
}

通过IWindowSession调用到了relayout方法,注意这里传入的mSurface参数,其实就是个空壳。既然是空壳,又有什么用?如果去看通过AIDL工具生成的Java文件,会发现这个参数这里会有一个out属性,表明该参数是输出参数,在接下来的方法中,就会往这个出参拷贝真正的surface。继续跟踪,Session是IWindowSession的服务端实现,在Session的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;
    }
    ...
}

createSurfaceControl方法会创建一块surface,并填充到outSurface中。后续view的绘制就会在这块surface上进行。

draw

surface有了,接下来就是绘制了。在performDraw方法中开始绘制:

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); //此surface其实也是mSurface

}

从performDraw方法最终会调用到drawSoftware方法,接着通过Surface类的lockCanvas方法获取到Canvas对象,view的绘制就在此画布上进行的。这里的mView其实就是我们之前所说的DecorView,在它的draw方法中,会调用onDraw方法绘制自己,接着调用dispatchDraw方法绘制子view,如此递归,直至把窗口中所有的view绘制完毕。绘制完毕后,就会调用Surface类的unlockCanvasAndPost方法把所绘制的view post到窗口解析显示。用户就能看到view了。

结语

view的显示流程大概就是这些了。功力不够,有些流程都是一笔带过,并没有深入。很想深入去了解,恨不得把整个Android源码解剖。但发现这样一来就很容易【找不到头】,导致一直在代码细节中徘徊。

先一步一步来,理清流程。找到了大概的脉络,再深入去了解一些模块。

代码流程的梳理确实是有用的,至少在处理问题时,会有思路,不至于一脸懵逼。

这件事会坚持去做。

微信公众号

我在微信公众号也有写文章,更新比较及时,有兴趣者可以扫描如下二维码,或者微信搜索【Android系统实战开发】,关注有惊喜哦!

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值