Android4.4DialogUI 스레드CalledFromWrongThreadExcection

최근에 이상한 일이 생겼어요. 원래android4.2 아래 달리기에 전혀 문제가 없는 코드는 4.4 아래에서 다음과 같은 이상이 발생할 수 있다.
01-17 13:06:25.087: E/AndroidRuntime(12673): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6094)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.view.ViewRootImpl.doDie(ViewRootImpl.java:5333)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.view.ViewRootImpl.die(ViewRootImpl.java:5318)
 
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.view.WindowManagerGlobal.removeViewLocked(WindowManagerGlobal.java:346)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:301)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:84)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.app.Dialog.dismissDialog(Dialog.java:329)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.app.Dialog$1.run(Dialog.java:121)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.os.Handler.handleCallback(Handler.java:733)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.os.Handler.dispatchMessage(Handler.java:95)
01-17 13:06:25.087: E/AndroidRuntime(12673): at android.os.Looper.loop(Looper.java:136)
 
이상을 던지면 Called FromWrong Thread Exception이다. 첫 번째 반응은 ui가 아닌 라인이 ui 조작을 해서 이 이상을 초래한 것이 분명하다.하지만 4.2에 대해 잘못 보고하지 않으면 또 말이 안 되잖아~
이로부터 조사를 시작하다
1) ui라인이 아닌 ui라인에서 ui작업을 수행하면 반드시 오류가 발생합니까?
==> ViewRootImpl 코드에서 보기
void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
}

위의 코드를 보면 checkThread까지 실행하는 방법은 틀리지 않으면 반드시
mThread==Thread.currentThread

.mThread는 다음과 같은 클래스의 속성입니다.
final Thread mThread;

구조 함수에서나 성명과 함께 초기화를 완성하는 것이 분명하다.
이것은viewRootImpl이 checkThread를 실행하는 라인에서 초기화를 완성해야 한다는 것을 의미한다.이view를 만드는 라인에서만 checkThread 방법을 실행할 수 있습니다.물론 대부분의 응용 프로그램은 ui 라인에 있습니다.그래서 ui라인이 아닌 ui라인이 ui조작을 실행하면 오류가 발생할 수 있다는 항소설이 나왔다.Called FromWrongThreadException 던지기
2.왜 이전에 4.2 버전에서 ui가 아닌 라인에서 ui 조작을 실행하면 오류가 발생하지 않았습니까?여기서는 작업 객체가 ProgressDialog임을 보여 줍니다.코드 오류 부분은 ProgressDialog의 dismiss 부분입니다.
소스 보기 = "Dialog는 ui 작업에 대한 특별한 지침을 제공합니다.
/**
     * Dismiss this dialog, removing it from the screen. This method can be
     * invoked safely from any thread.  Note that you should not override this
     * method to do cleanup when the dialog is dismissed, instead implement
     * that in {@link #onStop}.
     */

다이어로그가 ui 조작에 대해 특별히 처리한 것 같습니다.코드를 자세히 보세요.
 @Override
    public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            mHandler.post(mDismissAction);
        }
    }

Looper에서 보기에 현재 디스미스 작업을 수행하는 스레드가 mHandler에 첨부된 스레드와 일치하지 않으면 디스미스 작업이 대응하는 mHandler의 스레드 대기열에 버려져서 실행을 기다리는 것 같습니다.그럼 이 핸들러는 또 어디서 왔을까요?ViewRootImpl과 유사하고 final의Handler입니다.물론 이 핸들러와 new Dialog의 라인이 직접적인 관계가 있을 것이라는 분석도 가능하다.분석한 후에 분명히 다음과 같은 결론이 있을 것이다.이 Dialog가 UI 스레드에서 초기화되면, 이 Dialog에 대해 ui 작업을 하든 이 이상을 제거하지 않습니다. (이 결론은 원래 업계에서 널리 알려진 ui 스레드에서 ui를 조작한 것에 근거한 것입니다.)불행하게도 이 코드는 다음과 같이 쓰여도 오타가 난다(4.4기계에서4.2기계는 오타가 없다)
public class MyActivity extends Activity {
    private ProgressDialog mProgressDialog = null;
    private Handler mHandler = null;
 
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mProgressDialog = new ProgressDialog(MyActivity.this);
        HandlerThread handlerThread = new HandlerThread("atthread");
        handlerThread.start();
        mHandler = new Handler(handlerThread.getLooper());
        mHandler.post(new AtThread());
    }
 
    public class AtThread implements Runnable {
 
        @Override
        public void run() {
            mProgressDialog.setMessage(getResources().getString(R.string.app_name));
            mProgressDialog.show();
        }
    }

보기에 매우 이상한 것 같습니다. 분명히 ui 조작을 주 라인에 잃어버릴 것입니다.
그럼 계속해서 분석해 보면 다음과 같다.
우선 4.2와 4.4의 같은 코드 실행 결과는 다르다.그러면 첫 번째로 생각한 것은 틀림없이 4.4에서 일부 원본을 수정해서 보고가 틀렸다는 것이다.그럼 디프 할게요.다음과 같은 결과를 얻다.
4.2에서 Dialog의 dismissDialog와 4.4에서 Dialog의 dismissDialog의 차이는 다음과 같다.
 
try {
            mWindowManager.removeView(mDecor);
        }

===》
 try {
            mWindowManager.removeViewImmediate(mDecor);
        }

설마 이 차이일까?계속해서 추적하여 최종적으로 윈도우 관리자 글로벌 클래스를 발견하는 방법
removeViewLocked는 다음과 같은 말이 있습니다.
 
boolean deferred = root.die(immediate);

다음 방법으로 ViewRootImpl die를 계속 볼 수 있습니다.
 
boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
 
        ……
        return true;
}

익숙한 오류 보고를 보았습니다. 프로그레스 다이얼로그의 오류 보고도 다이얼을 할 때 check Thread가 실패했다고 표시되지 않습니까?다시 말하면 ViewRootImpl 본생의thread와handler는 같은 라인에 있지 않다(1viewRootImpl의 mThread의 유래를 설명한다).이전 4.2에서 호출된api는removeView가 최종적으로doDie 방법을 실행하지 않는 것으로 4.2버전이 왜 끊기지 않는지 설명한다.4.4 버전에서는 끊기는 경우가 있다.
3. 여기까지 순조로운 분석 완료???NO,NO.각종 실험에서 다음과 같은 기이한 상황을 발견하였다.
실험 코드를 다음과 같이 바꾸면 4.4 아래에서도 틀리지 않는다
 
public class AtThread implements Runnable {
 
        @Override
        public void run() {
          
 mProgressDialog.setMessage(getResources().getString(R.string.app_name));
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mProgressDialog.show();
                }
            });        }
    }

이상한 일이 자꾸 일어나네. 샤프리스야(인술명, 사랑4를 보면 다 알아)~ 위의 분석은 디스미스 집행checkThread가 틀렸다는 걸 잘 알고 있어.show 방법을 runOnUi Thread에 싸면 틀리지 않아요. 이건 또 털인가요?생각할 수 있는 것은 반드시 checkThread에서 mThread==Thread.currentThread.Thread.currentThread는Dialog의 mHandler에 의존하는 Thread입니다.그럼 mThread에 변화가 생겼을 거예요.그럼 원본 코드를 다시 한 번 볼게요.removeViewImmediate(mDecor)가 틀리지 않는 것을 볼 수 있다. 그러면 mDecor의viewRootImpl의 Thread와 메인 라인이 일치해야 한다는 것을 의미한다. =
public void show() {
      ……
        mDecor = mWindow.getDecorView();
    ……
    }

위의 mWindow.getDecorView () 는 다음 동작을 수행합니다. 대응하는view가 존재하면 바로 되돌아오고, 그렇지 않으면 대응하는view가 new로 나타납니다.그게 관건이야.그 스레드 new에서view를 내보내면view의viewroot에 대응하는 스레드의 인용을 저장합니다.결국 checkThread에서 이상 투매 여부에 직접적인 영향을 미칠 수 있다는 얘기다.따라서 show를 주 라인에 넣고 완성하면 최종 4.4에서 이상을 던지지 않습니다.
방법 1:
dialog의 show 방법을 ui 라인에 놓고 실행하기
방법2:
다이어로그의 초기화를 하위 라인에 놓고 실행합니다
 
이상 두 가지 모두 이상이 나타나지 않습니다.
이로써 이 버그의 분석이 기본적으로 완료되었습니다.

좋은 웹페이지 즐겨찾기