Android 应用的 UI 线程是怎么启动的?

  • 什么是 UI 线程
  • UI 线程的启动流程,消息循环是怎么创建的
  • 连接 Android 的显示原理,UI 线程和 UI 之间是怎么关联的

什么是 UI 线程

  • UI 线程就是刷新 UI 的线程

  • UI 刷新是单线程刷新的

      UI 线程到底是哪个线程?
      UI 线程就是主线程?
    

平时写代码时,如果在子线程,然后处理完耗时操作再切换回主线程,有两种方式可以实现

Activity.runOnUiThread(Runnable);

View.post(Runnable)

Activity.runOnUiThread(Runnable);

    public final void runOnUiThread(Runnable action) {
    
    
        if (Thread.currentThread() != mUiThread) {
    
    
            mHandler.post(action);
        } else {
    
    
            action.run();
        }
    }

逻辑很简单,如果判断一下当前线程是不是 mUiThread ,如果是直接 post 到 mHandler 队列中,如果是则直接执行run() 方法。那么?mUiThread 和 mHandler 是在哪里初始化的?

public class Activity extends ContextThemeWrapper{
    
    
	final Handler mHandler = new Handler();


	  final void attach(Context context,...) {
    
    
        attachBaseContext(context);
        // .。。 
        mUiThread = Thread.currentThread();
	}
}

mUiThread 是 Activity 中的成员变量,在 Activity 创建的时候就创建了 mHandler,并没有指定 Looper,所以 Activity 创建在哪个线程 mHandler 用的就是哪个线程的 Looper。

mUiThread 的赋值是在 Activity 的 attach 方法中赋值为当前线程。那么 attach 是什么时候调用的?回忆一下 Activity 的创建流程。

  •   创建 Activity 对象
      创建 Context
      attach() 
      onCreate()
    

Activity 的创建这几步都是在主线程执行的,所以 activity 的 runOnUiThread 是在主线层执行的,所以 UI 线程就是主线程。

View.post(Runnable)

    public boolean post(Runnable action) {
    
    
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
    
    
            return attachInfo.mHandler.post(action);
        }
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

post 也分两种情况,如果 attachInfo 不为 null ,则 attachInfo.mHandler.post(action); mAttachInfo 在 view tree 递归的时候给每个子 view 都赋值一个 AttachInfo 。mAttachInfo 是在 ViewRootImpl 构造函数里面创建的。

	// final class ViewRootHandler extends Handler 
    final ViewRootHandler mHandler = new ViewRootHandler();

    public ViewRootImpl(Context context, Display display) {
    
    
        mContext = context;
        mThread = Thread.currentThread();
        mWindow = new W(this);
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);

构建 AttachInfo 的 mHandler 所对应的 Looper 就是创建时的线程的 Looper。所以关键就是 ViewRootImpl 是在哪个线程创建的。因为 ViewRootImpl 是在 Activity 的 onResume() 之后才创建的 所以,如果在 onResume 之前是拿不到 AttachInfo 的,所以他会先添加到一个队列 等创建好了以后去执行。所以这两种情况最后都会放到 ViewRootImpl 对应的线程里面去执行。

扫描二维码关注公众号,回复: 17092978 查看本文章
  •   所以对于view来说,ViewRootImpl 所在的线程就是他的 UI 线程
    

刷新 UI 线程检测

下面这个方法是在 requestLayout() 时检查的

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

上面的这个方法就是我们在非 UI 线程刷新 UI 时抛出的异常,里面的意思是只能在创建 view hierarchy 的线程才可以。这里并没有说只有主线程才能刷新 UI 。那么方法中 mThread 就是创建 view hierarchy 的线程。他是在 ViewRootImpl 构造函数中初始化的。和上面 提到的 mHandler 时一样,所以 UI 线程就是 ViewRootImpl 所在的线程。

ViewRootImpl 的创建过程

之前在 Activity 绘制原理中讲过 ViewRootImpl 是在哪里创建的

  •   ActivityThread 的 handleResumeActivity()
      ViewManager wm = a.getWindowManager(); 的 addView()
      WindowManagerGlobal.addView()
      ViewRootImpl 创建
    

所以 ViewRootImpl 的创建的起点是 ActivityThread 的 handleResumeActivity() 它是在主线程中调用的。

总结

  • 对于 Activity 来说,UI 线程就是主线程。 activity.runOnUiThread()
  • 对于 View 来说,UI 线程就是 ViewRootImpl 创建的时候所在的线程。view.post()
  • Activity 的 DecorView 对应的 ViewRootImpl 是在主线程创建的。checkThread()

所以 UI 线程就是主线程

问题

  • 如果 ViewRootImpl 不在主线程创建会怎么样呢?是不是代表可以不在主线程刷新 UI 了?
WindowManager.LayoutParams params = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        new Thread() {
    
    
            @Override
            public void run() {
    
    
                super.run();
                Looper.prepare();
                getWindowManager().addView(view, params);
                Looper.loop();
            }
        }.start();

结论:如果是在子线程中 addView() 的,那么如果在主线程中刷新也会报错,只能在 addView 的这个线程刷新。

UI 线程的启动流程

  • 回顾一下应用的启动流程

    Zygote fork() 进程
    启动 binder 线程
    执行 程序的入口函数

  • ActivityThread 的 main() 函数

public static void main(String[] args) {
    
    
	Looper.prepareMainLooper();
	// ... 	
	Looper.loop();
}
  • prepareMainLooper()
    public static void prepareMainLooper() {
    
    
        prepare(false);
        synchronized (Looper.class) {
    
    
            if (sMainLooper != null) {
    
    
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

  • prepare()
    private static void prepare(boolean quitAllowed) {
    
    
        if (sThreadLocal.get() != null) {
    
    
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 创建了一个 Looper 存入 sThreadLocal 
        // quitAllowed = false Looper 代表是否可以退出
        sThreadLocal.set(new Looper(quitAllowed));
    }

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/127474498