Handler 메커니즘 상세 분석

13096 단어 Android
약술1: Android 메커니즘에는 하위 스레드가 UI에 액세스할 수 없으며 그렇지 않으면 오류가 보고됩니다.주 스레드에서는 일정 시간 처리가 끝나지 않으면 주 스레드를 막아 ANR 이상이 발생하기 때문에 시간 소모 작업을 허용하지 않습니다.따라서 입출력 읽기와 쓰기, 네트워크 요청 등 일부 시간이 걸리는 작업은 하위 라인을 만들어서 처리해야 한다. 작업이 끝난 후에 UI의 업데이트 작업과 관련이 있으면 주 라인으로 전환해서 후속 처리를 해야 한다.이 라인의 전환은Handle이라는 메커니즘을 사용했다.
약술2: Hander 메커니즘의 네 가지 중요한 구성원: Looper Handler Message MessageQueue.대략적인 절차는 다음과 같다.
        1.현재 라인에서 Looper 대상을 만들고 그 내부에 MessageQueue를 만들고 이 Looper 대상을 현재 라인의 sThreadLocal에 저장합니다.
        2.현재 스레드에서 Hander 대상을 만들고 HandMessage () 방법을 다시 씁니다.Handler 대상을 만드는 과정에서 sThreadLocal에서 현재 스레드의looper 대상과 그 중의 MessageQueue를 가져옵니다.handler 대상에 이 MessageQueue가 있습니다.
        3.Looper.loop (), 로퍼 내부의 MessageQueue에서 끊임없이 정보를 찾습니다. 메시지 대기열에 메시지가 없으면 막힙니다.
        4.다른 라인에서handler를 호출합니다.hande Message (msg) 가 메시지를 보낼 때, 이 msg는 이handler를 msg에 포장하고, 이 msg를 이MessageQueue에 추가합니다.이handler 대상을 만드는 라인에서 대응하는 Looper는 이 msg로 돌아가며 이handler의handleMessage () 방법을 호출하여 대응하는 논리를 처리합니다.
        5.이 looper의 윤문은 이 looper 대상을 만드는 라인에 있기 때문에 라인의 전환을 실현했습니다.
참고:
           1.handler가 보낸 메시지는 대응하는 looper에 의해 조회될 수 있습니다. 왜냐하면 그들은 모두 같은MessageQueue를 가지고 있기 때문입니다.
            2.서로 다른 라인의handler와looper의 착란이 일어나지 않는 이유는 Looper의 접근이 sThreadLocal을 통해 이루어졌기 때문이다.Handler와 Looper는 같은 라인을 기반으로 만들어졌으며 같은 메시지 대기열이 있습니다.
       
위에서 서술한 절차는 사실 몇 가지 원리를 포함하고 있다. 다음은 원본을 분석하여 이 절차들을 더욱 전면적으로 이해하자.
  1.우선 Looper는 Looper를 호출하여 생성됩니다.prepare();방법:
public static void prepare() {
    prepare(true);
}

//   Looper,         sThreadLocal      。
private static void prepare(boolean quitAllowed) {
//quitAllowed       Looper   。      ,     。
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

//다음 코드는 Looper 객체를 만드는 것입니다. 이때 구조 매개 변수에 mQueue 메시지 대기열을 만들고 현재 스레드 mThread를 가져옵니다.
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

ThreadLocal 지식점: 이 클래스는 각종 범주형의 데이터를 저장하는 대상이다. 다른 저장 도구 클래스와 달리 같은 mThreadLocal 대상이 서로 다른 라인에서 데이터를 저장하는 액세스 조작은 서로 독립적이다. 즉, 모든 라인에 이 mThreadLocal 복사본이 있는데 그들은 각자 저장하고 각자 사용하며 서로 간섭하지 않는다.Looper 클래스의 sThreadLocal 대상은 정적 전역 변수입니다. 각 라인에서 만든 Looper 대상을 저장하고 가져올 수 있습니다.그가 서로 간섭하지 않는 이유는 그가 액세스하고 사용하는 키가 현재의 라인이기 때문이다. 즉, Looper와 Threader의 일일이 대응을 실현했기 때문이다.다음은 원본 코드를 살펴보자.
static final ThreadLocal sThreadLocal = new ThreadLocal();

//    :       ,    getMap(t)    ThreadLocalMap  
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

//그리고 현재 스레드 t에 따라 이 스레드 t의 ThreaLocaMap 대상을 꺼내서 set 방법으로 저장합니다
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
private void set(ThreadLocal> key, Object value) {
  
    Entry[] tab = table;                       //      
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);//     sThreadLocal,         i  index  
    tab[i] = new Entry(key, value);         //        sThreadLocal Looper   Entry    table   index
    int sz = ++size;                         //        
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

만약 처음 사용한다면 getMap이 비어 있습니다. ThreadLocalMap 대상을 다시 만들고 이 r의threadLocals 변수에 값을 부여해야 합니다. new 대상에서 저장을 완성해야 합니다. 위와 같습니다.
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

사고방식: 현재 스레드 --->를 가져오고 이 스레드에 대응하는ThreadLocalMap 대상 --->에서 대상 가져오는 그룹 탭------>value를 생성entry로 봉하여 탭[i]에 저장합니다.    
데이터 추출은 저장의 원리와 마찬가지로 역행이다. 그래서 같은 sThreadLocal 대상은 서로 다른 라인에서 추출한 것은 이 라인 아래의 looper 대상일 뿐 착란이 일어나지 않고 looper는 착란되지 않는다.
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

2. Handler의 생성
상황1:
Handler  handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};  

  :
public Handler() {
    this(null, false);
}

//이 과정은 looper와handler를 한 라인에 묶고 메시지 Queue 메시지 대기열을 함께 사용합니다.하나의 라인의looper가 확실하기 때문에 이 라인의handler도 확실합니다.
public Handler(Callback callback, boolean async) {
  
    mLooper = Looper.myLooper();//       Looper
    if (mLooper == null) {   //         handler              looper  
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

상황2:Handler를 실현했다.Callback 인터페이스의handleMessage () 방법
Handler handler=new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

상황3: 이때handler가 있는 라인이 Looper입니다.myLooper()가 있는 스레드입니다.
Handler handler=new Handler(Looper.myLooper(), new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
}) 
   
Handler handler=new Handler(Looper.getMainLooper()){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

3. 메시지 보내기
상황1:send 발송
handler.sendEmptyMessage(100);

상황2: 사실 Runnable을 Message에 포장한 것이다. 즉, Message이다.callback==Message.runnable, 그리고send로 보내기
handler.post(new Runnable() {
    @Override
    public void run() {
        
    }
});
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

그래서 우리는 이어서 send 방법을 보았다.
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

//MessageQueue 메시지 대기열에 전송된 메시지를 추가합니다. 이전에 차단된 상태이면 활성화됩니다.그렇지 않으면 바로 안에 메시지를 추가합니다.
//MessageQueue는 단일 체인 테이블의 구조를 통해 메시지를 유지하는데 효율이 높다.
boolean enqueueMessage(Message msg, long when) {
   .......
    synchronized (this) {
        if (mQuitting) {//  looper   quie ,       quit(),  mQuitting=true         。
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //          ,    
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
           
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
//    MessageQueue       ,          ,   MessageQueue,         Msg   
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

4.Looper.poop();폴링은 MessageQueue의 메시지를 읽고 처리합니다.선진적으로 추가된 선처리.
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
        for (;;) {
        Message msg = queue.next(); //        looper,   messageQueue,     next()      
        if (msg == null) {
            //          , messageQueue   ,    loop()   
            return;
        }

     ...............
       //         msg   handler(       handler)  dispatchMessage(msg)   。
            msg.target.dispatchMessage(msg);
     ..............
}

            :
Message next() {
    .......
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
       //              。0         ,   -1     。           。
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            .....
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                //      -1,        
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }
       }
}

다시 한 번handler.dispatchMessage () 는 메시지를 어떻게 처리합니까: (loop에서 돌아가는 루틴을 처리합니다. 즉 looper,handler의 루틴을 만들기 때문에 루틴을 전환하는 목적을 실현했습니다)
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//   post(Runnable),  msg.callback==runnable,   runnable  
        handleCallback(msg);
    } else {   
        if (mCallback != null) {//    handler    handle.CallBack,        handMeaasge(msg)
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//        handMessage(msg);
    }
}

Looper의 종료:
public void quit() {
    mQueue.quit(false);
}
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();//    ,      msg,    
        } else {
            removeAllMessagesLocked();//    
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

참고: 수동으로 생성된 Looper의 경우 작업을 완료하려면 quit()를 호출하여 종료해야 합니다. 그렇지 않으면 Looper가 폴링 상태로 남아 성능이 저하됩니다.
여기까지도 관련된 몇 가지 원리를 포함해서 절차를 상세하게 한 번 걸은 셈이다.이 절차에 따라 메인 라인이 어떻게 된 일인지 따라갈 수 있다. 물론 메인 라인도 이 원리에 따라 일한다. 단지 그는 비교적 특수하기 때문에 전문적인 방법으로 이 메커니즘을 초기화하고 Looper의 퇴출을 하지 못하게 한다.
 
 
 
 

좋은 웹페이지 즐겨찾기