view的绘制入口

19 阅读7分钟

一、绘制入口源码分析

从Activity的startActivity开始,最终调用到ActivityThread的handleLaunchActivity方法来创建Activity,相关核心代码如下:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 创建Activity,会调用Activity的onCreate方法
    // 从而完成DecorView的创建
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed);
    }
}

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;
    // 调用Activity的onResume方法
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.window == null &&& !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            // 得到DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            // 得到了WindowManager,WindowManager是一个接口
            // 并且继承了接口ViewManager
            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;
                // WindowManager的实现类是WindowManagerImpl,
                // 所以实际调用的是WindowManagerImpl的addView方法
                wm.addView(decor, l);
            }
        }
    }
}

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    ...
}

// WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View pannelParentView = null;
    synchronized (mLock) {
        ...
        // 创建ViewRootImpl实例
        root = new ViewRootImpl(view..getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    try {
        // 把DecorView加载到Window中
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}

// ViewRootImpl的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {
    //...
	requestLayout();
}

// ViewRootImpl的requestLayout方法
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
  
// ViewRootImpl的scheduleTraversals方法
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

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

// ViewRootImpl的doTraversal方法
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

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

        performTraversals();

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

private void performTraversals() {
    //...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    //...
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    //...
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    //...
    mView.draw(canvas);
    //...
 }

  • DecorView 是ViewTree 里面的顶层布局,继承于FrameLayout,包含两个子View:
    • 一个id = statusBarBackground 的 View;
    • 一个LineaLayout,LineaLayout 里面包含:
      • title,就是平时用的TitleBar或者ActionBar;
      • content,是一个FrameLayout,activity通过onCreate方法中的 setContent()加载布局的时候加载到这个View上。
  • 一个 Activity 包含一个Window,并且只有一个子类实现类PhoneWindow;
  • 当执行到handleResumeActivity时,得到DecorViewWindowManagerImpl,调用WindowManagerImpladdView方法,继而调用WindowManagerGlobaladdView方法;
  • 创建ViewRootImpl实例,通过ViewRootImplsetView方法,把DecorView加载到Window中;
  • 通过跟踪代码,发现会执行TraversalRunnable,并且执行run代码块中的doTraversal方法,继而执行performTraversals方法;
  • performTraversals方法特别长,但是核心还是这三个步骤:measure,layout, draw。

二、View 的绘制必须在主线程吗?

由前面我们知道,View的绘制最重要的方法是 ViewRootImpl 中的 setView() 方法,里面会调用 requestLayout()方法:

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

前面我们主要分析了scheduleTraversals(),但是其实还有个checkThread()方法也很重要:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

发现抛出的这句异常经常在开发的时候遇到,当在子线程中更新UI就会提示这句话,显然这里意思是说:只有创建这个View继承关系的线程才能修改这个View。

这里面也只是简单的比较了mThread的值和当前线程是否相同,而mThread的赋值是在ViewRootImp的构造函数里面。

public ViewRootImpl(@UiContext Context context, Display display,IWindowSession session,boolean useSfChoreographer){
    mContext = context;
    mWindowSession = session;
    mDisplay = display;
    mBasePackageName = context.getBasePackageName();
    mThread = Thread.currentThread();
}

那么,我们就知道了,

结论:

  • 在刷新View的时候执行checkThread并不一定是说在检查我们是不是在UI线程修改(并不是说不能在子线程中更新UI)。而是说现在修改的这个View他所在的View树的根创建的线程是否和当前操作View的线程一样。

  • 当然了,DecorView所在的View树就是在UI线程中创建的,因此大多数时候我们操作的View都在这棵树下面,造成的结果是只能在创建DecorView的ViewRoot的线程也就是UI线程中修改View。

那么有两个方面可以考虑:

  • 这个View树是在onResume后才创建的,那么在此之前修改UI由于当前的View树的树根还不存在,因此暂时不会绘制界面,只会保存设置的状态,在下次请求绘制UI时会再次刷新UI。
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv = (TextView)findViewById(R.id.tv);
    new Thread() {
        public void run(){
            tv.setText("change text in non-UI Thread");
        }
    }.start();
}

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv = (TextView)findViewById(R.id.tv);
    new Thread() {
        public void run(){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tv.setText("change text in non-UI Thread");
        }
    }.start();
}

比较一下这两个代码,发现:

上面的代码可以正常的运行,而下面的代码就会报异常。为什么呢?

由于睡眠两秒钟后才更新UI,这段时间内早已完成了前期的初始化,onResume也已经执行完成,有了自己的View树,当更新View状态时会进行线程检查的。

  • 正如上面添加DecorView的方式既然WindowManager.addView方法可以创建一个ViewRootImpl,那么通过这种方式来修改界面就可以有自己的View树,也就不受限于主线程中修改UI了。
new Thread() {
        public void run(){
        	Looper.prepare();
        	
		    view=new View(getApplicationContext());
		    view.setBackgroundResource(R.drawable.ic_launcher); mWindowManager=(WindowManager)
		        getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
		    WindowManager.LayoutParams param=new WindowManager.LayoutParams();
		    param.type=WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
		    param.format=1;
		    param.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
		    param.flags = param.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
		    param.flags = param.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
		    param.alpha = 1.0f;
		    param.width=200;
		    param.height=200;
		    mWindowManager.addView(view, param);
        }
    }.start();

ViewRootImpl是在子线程创建的,需要准备Looper,所以DecorView是在子线程,所以也可以在子线程中更新View。

三、同步屏障机制

Android消息队列MessageQueue中加入的消息分成同步消息和异步消息,在平常开发中接触到的消息基本上都是同步消息,同步消息会被放到消息队列的队尾,Looper在消息循环时从队列头部不断取出同步消息执行。

如果我们想发送异步消息,那么在创建Handler时使用以下构造函数中的其中一种(async传true)

public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);

在Android系统中存在一个VSync消息,它主要负责每16ms更新一次屏幕展示(通常手机刷新是每秒60次,即每隔16.6ms刷新一次),如果其他同步消息在16ms内没有执行完成,那么VSync消息的更新操作就无法执行,在用户看来就出现了掉帧或卡顿的情况。

为此Android开发要求每个消息的执行需要限制在16ms之内完成。但是消息队列中可能会包含多个同步消息。

假如当前主线程消息队列有10个同步消息,每个同步消息要执行10ms,总共也就需要执行100ms,这段时间内就会有近7帧无法正常刷新展示,应用执行过程中遇到这种情况还是很普遍的。

Android系统设计时自然也会考虑到这种情况,同步消息会导致延迟,主要原因在于排队等候,如果消息发送后不必排队等待直接就执行就能够解决消息延迟问题。

Android系统中的异步消息就是专门解决消息处理延迟的问题,它需要配合同步屏障(SyncBarrier)一起工作。

在发送异步消息的时候向消息队列投放同步屏障对象,消息队列会返回同步屏障的token,此时消息队列中的同步消息都会被暂停处理,优先执行异步消息处理,等异步消息处理完成再通过消息队列移除token对应的同步屏障,消息队列继续之前暂停的同步消息处理。

MessageQueue中同步屏障处理的方法都是隐藏API,需要通过反射方法来调用。

// 反射执行投递同步屏障,省略try..catch
public void postSyncBarrier() {
    Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
    token = (int) method.invoke(Looper.getMainLooper().getQueue());
}

// 反射执行移除同步屏障,省略try..catch
public void removeSyncBarrier() {
    Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier", int.class);
    method.invoke(Looper.getMainLooper().getQueue(), token);
}

同步屏障的应用:

Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障:

// ViewRootImpl的scheduleTraversals方法
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 投递同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

// ViewRootImpl的unscheduleTraversals方法
void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

MessageQueue之所以将同步屏障的接口都变成隐藏接口是不想普通的开发者向主线程队列投递同步屏障影响VSync消息的正常执行,开发过程中尽量不要使用异步消息和同步屏障。