runOnUiThread 、Handler.post、View.post 분석

7732 단어
이 소스 코드는 Android API 26 Platform 기반

예제


먼저 다음 코드를 보고 출력 결과를 판단합니다.
public class MainThreadTestActivity extends AppCompatActivity {

  private static final String TAG = MainThreadTestActivity.class.getSimpleName();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_thread_test);

    View view = new View(this);
    view.post(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[view.post] >>>> 1 ");
      }
    });

    new Handler(Looper.getMainLooper()).post(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[handler.post] >>>> 2");
      }
    });

    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[runOnUiThread] >>>>> 3");
      }
    });

    new Thread(new Runnable() {
      @Override
      public void run() {
        runOnUiThread(new Runnable() {
          @Override
          public void run() {
            Log.i(TAG, "[runOnUiThread from thread] >>>> 4");
          }
        });
      }
    }).start();
  }
}

우선 출력 결과가 어떻게 될지 예측해 봅시다.
실행 결과는 다음과 같습니다.
...I/MainThreadTestActivity: [runOnUiThread] >>>>> 3
...I/MainThreadTestActivity: [handler.post] >>>> 2
...I/MainThreadTestActivity: [runOnUiThread from thread] >>>> 4

그럼 질문이 왔습니다.
  • 첫 번째 단계의 View.post은 왜 실행되지 않았습니까?
  • runOnUiThreadHandler.post보다 빨리 실행합니까?
  • 은 정상적인 상황에서 runOnUiThread, Handler.post, View.post 이 세 가지의 집행 순서는 어떻게 될까요?

  • 다음은 우리가 각각 해석을 진행한다.

    해석


    2.1 View.post


    2.1.1 View.질문


    먼저 View.post 소스를 살펴보겠습니다.
    //View.java
    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;
    }
    

    즉, View.post 방법을 집행할 때 AttachInfo이 비어 있지 않으면 AttachInfoHandler을 통해 Runnable을 집행한다.그렇지 않으면 이 RunnableView의 집행대열 HandlerActionQueue에 던진다.
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            mAttachInfo = info;
            //...
    }
    

    View attch가 Window에 이르러야만 AttachInfo에 값을 부여할 수 있다.따라서 예시된 코드는 getRunQueue().post(action)으로 바로 들어간다.우리는 계속해서 원본 코드를 따라 내려다보았다.View에서 HandlerActionQueue을 통해 실행 가능한 요청 대기열을 봉인했다.공식 주석은 Class used to enqueue pending work from Views when no Handler is attached., 즉view는 Handler이 후속 임무(attach에서 Window에 없음)를 수행하기 위해 모든 요청을 입대하지 않았다는 것이다.
    // HandlerActionQueue.java
    public class HandlerActionQueue {
    
        public void removeCallbacks(Runnable action) {
            synchronized (this) {
                final int count = mCount;
                int j = 0;
    
                final HandlerAction[] actions = mActions;
                for (int i = 0; i < count; i++) {
                    if (actions[i].matches(action)) {
                        // Remove this action by overwriting it within
                        // this loop or nulling it out later.
                        continue;
                    }
    
                    if (j != i) {
                        // At least one previous entry was removed, so
                        // this one needs to move to the "new" list.
                        actions[j] = actions[i];
                    }
    
                    j++;
                }
    
                // The "new" list only has j entries.
                mCount = j;
    
                // Null out any remaining entries.
                for (; j < count; j++) {
                    actions[j] = null;
                }
            }
        }
    
        public void executeActions(Handler handler) {
            synchronized (this) {
                final HandlerAction[] actions = mActions;
                for (int i = 0, count = mCount; i < count; i++) {
                    final HandlerAction handlerAction = actions[i];
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }
    
                mActions = null;
                mCount = 0;
            }
        }
    
    }
    
    
    View에서
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
           ...
            // Transfer all pending runnables.
            if (mRunQueue != null) {
                mRunQueue.executeActions(info.mHandler);
                mRunQueue = null;
            }
            ...
    }
    

    요약하면 View view = new View(this);이view attch를 window에 저장하지 않았기 때문에 실행 가능한 View.post 방법은 실행 가능한 요청을 요청 대기열에 캐시합니다.
    따라서 예제의 코드는 다음과 같이 변경될 수 있습니다.
    View view = new View(this);
        rootView.addView(view);
        view.post(new Runnable() {
    

    출력 결과는 다음과 같습니다.
    ...I/MainThreadTestActivity: [runOnUiThread] >>>>> 3
    ...I/MainThreadTestActivity: [handler.post] >>>> 2
    ...I/MainThreadTestActivity: [runOnUiThread from thread] >>>> 4
    ...I/MainThreadTestActivity: [view.post] >>>> 1 
    
    View.post 방법을 성공적으로 집행하여 정확하게 수정하였다.View.post()View attachedToWindow에만 즉시 실행

    2.1.2 View.post 원본 분석

    2.1.1절을 통해 View.post()View attachedToWindow에서만 즉시 실행된다는 것을 알 수 있습니다.ViewRootImplViewRootHandler을 실행하여

    2.2 runOnUiThread

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

    참고:
  • 현재 스레드가 주 스레드인 경우 요청은 즉시
  • 현재 스레드가 주 스레드가 아니라면 요청은 UI 스레드의 시간 대기열로 발송됩니다.

  • 즉, 주 라인이 아닌 라인에서 이 방법을 사용하면 라인 전환 비용이 존재한다.

    2.3 Handler.post


    먼저 요청을 Handler 대기열에 추가하고 실행해야 합니다.

    3. 집행 순서 분석


    요약하면 주 라인에서 각각 View.post, Handler.post, runOnUiThread, new Thread() - [runOnUiThread] 네 가지 방법의 집행 순서는 속도에서 느림으로 나뉜다.runOnUiThread - Handler.post - new Thread() - [runOnUiThread] - View.post
    (전면 유효성 검사 결과 일치)
    분석:
  • runOnUiThread 현재 UI 에서 실행되고 있으므로 스레드 전환 없이
  • Handler.post 가입 요청
  • UI Handler 새 스레드를 오픈, 시작 완료 후 new Thread() - [runOnUiThread] 가입 요청, 스레드 전환, 입대 및 출전 시간
  • 증가
  • UI Handler은view attach에서 Window에 도착한 후 View.postViewRootImpl을 통해 요청을 수행해야 한다.스레드 전환 시간이 UI 렌더링 시간보다 훨씬 작기 때문에 최대
  • 확장: 예시에서 코드가 UI가 아닌 라인에서 실행될 때 ViewRootHandlerrunOnUiThread은 비용이 거의 같기 때문에 실행 결과도 순서대로 완성된다.그러므로 UI가 아닌 라인에서 각각 Handler.post, View.post, Handler.post을 호출하면 runOnUiThread 네 가지 방법의 집행 순서는 속도에서 느림으로 한다.new Thread() - [runOnUiThread] - Handler.post - runOnUiThread - new Thread() - [runOnUiThread]
  • 공중번호 View.post이 제기한 발산 문제에 감사 드립니다
  • 좋은 웹페이지 즐겨찾기