출처 : http://www.androes.com/122
01-13 16:18:35.675: ERROR/AndroidRuntime(13682): java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131099659, class android.widget.ListView) with Adapter(class android.widget.HeaderViewListAdapter)]

위와 같은 메세지가 뜨면서 강제 종료되는 경우 아래와 같은 방법을 이용하면 됩니다.

단, ListActivity가 아닌 Activity 사용시에 적용가능한 코드이오니 착오없으시길..^^

        adapter = new NewAdapter(this, R.layout.new_list_custom, Items);
        runOnUiThread(new Runnable() { 
            public void run() { 
                adapter.notifyDataSetChanged(); 
            } 
        });        
윈도우에서 안전모드(Safe Mode)로 진입할 수 있듯이 안드로이드에서도 안전모드로 진입할 수 있습니다.
 
여기서 안전모드에 대해서 알 필요가 있습니다. 
안전 모드란 OS를 진단하기 위한 모드로, 전자 장비에 이상이 생겼을 때 주로 실행합니다.
이는 진단 모드 또는 복구 모드라고도 불리기도 합니다.
OS가 안전모드로 부팅되었을 경우 시스템을 부팅에 필요한 최소한의 파일과 드라이버만 사용되는 등 기능적으로 제약이 많으나, 오히려 이를 통해 문제점을 해결하는 데 어느 정도 도움이 되고나 하는 것입니다.
<위의 내용은 위키백과에서 참고하였습니다.>

이제 안드로이드에서 안전모드로 부팅하는 방법을 알려드리겠습니다.

안전모드로 부팅하기 위해서는 다음의 조건이 성립되면 안전모드로 부팅됩니다.

 부팅하기 전부터 다음의 버튼중 하나만 누르고 있는 상태에서 부팅을 완료하게 되면, 안전모드로 들어가게 됩니다.
1. Menu Key
2. S Key
3. Dpad에서 Center Key
4. TrackBall Button

위의 방법은 안전모드로 진입하는 방법입니다.

이 부분에 해당되는 소스 부분은 다음과 같습니다.

frameworks/base/services/java/com/android/server/SystemServer.java
 run() {
...
// Before things start rolling, be sure we have decided whether
// we are in safe mode.
final boolean safeMode = wm.detectSafeMode();
if (safeMode) {
    try {
        ActivityManagerNative.getDefault().enterSafeMode();
        // Post the safe mode state in the Zygote class
        Zygote.systemInSafeMode = true;
        // Disable the JIT for the system_server process
        VMRuntime.getRuntime().disableJitCompilation();
    } catch (RemoteException e) {
    }
} else {
    // Enable the JIT for the system_server process
    VMRuntime.getRuntime().startJitCompilation();
}
...
위의 소스를 보시면 safeMode로 진입하기 위해서는 
detectSafeMode
함수를 타야 됩니다. 이 함수를 들여다 보면 다음과 같습니다.

frameworks/policies/base/phone/com/android/internal/policy/impl/
PhoneWindowManager.java
public boolean detectSafeMode() {
    try {
        int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU);
        int sState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_S);
        int dpadState = mWindowManager.getDPadKeycodeState(KeyEvent.KEYCODE_DPAD_CENTER);
        int trackballState = mWindowManager.getTrackballScancodeState(RawInputEvent.BTN_MOUSE);
        mSafeMode = menuState > 0 || sState > 0 || dpadState > 0 || trackballState > 0;
        performHapticFeedbackLw(null, mSafeMode
                ? HapticFeedbackConstants.SAFE_MODE_ENABLED
                : HapticFeedbackConstants.SAFE_MODE_DISABLED, true);
        if (mSafeMode) {
            Log.i(TAG, "SAFE MODE ENABLED (menu=" + menuState + " s=" + sState
                    + " dpad=" + dpadState + " trackball=" + trackballState + ")");
        } else {
            Log.i(TAG, "SAFE MODE not enabled");
        }
        return mSafeMode;
    } catch (RemoteException e) {
        // Doom! (it's also local)
        throw new RuntimeException("window manager dead");
    }
}

위의 소스를 보시면 알수 있듯이 
 menuState, sState, dpadState, trackballState의 값들에 의해서 SafeMode에 진입 할 수 있는 것을 확인할 수 있을 것입니다.



 
안드로이드의 UI는 단일 스레드 모델이기 때문에 UI단에서 스레드를 사용하고 싶다면 핸들러를 사용해야 합니다.

1.public void onClick(View v) {
2.new Thread(new Runnable() {
3.public void run() {
4.txtView.setText("안녕하세요?"); // 텍스트뷰의 내용변경
5.}
6.}).start();
7.}

위와 같은 코드를 실행하게 된다면 해당코드는 다른스레드에서 UI에 접근하기때문에

CalledFromWrongThreadException 예외가 발생하게 되죠

이런 문제를 해결하기 위해 쓰는것이 바로 핸들러가 되겠습니다.

핸들러를 가지고 위의 코드를 수정해 보겠습니다.

01.Handler handler = new Handler();
02. 
03.public void onClick(View v) {
04.new Thread(new Runnable() {
05.public void run() {
06. 
07.handler.post(new Runnable() {
08.public void run() {
09.txtView.setText("이제는 됩니다.");
10.}
11.});
12. 
13.}
14.}).start();
15.}

이렇게 Handler 인스턴스를 만들어놓고 post안에 Runnable을 구현하면 됩니다.

post안에 Runnable을 구현하는 방법 외에도 많은 방법이 존재합니다.


- CalledFromWrongThreadException 발생 원인 -

왜 다른스레드에서 UI를 변경하려고 하면 해당 예외가 발생하는지 안드로이드 기본소스를 통하여

알아보자면 UI변경이 있게되면 안드로이드 뷰에서는 invalidate를 호출하게 되는데

1.// View.java
2.public void invalidate() {
3....
4.final ViewParent p = mParent;
5....
6.p.invalidateChild(this, r);
7.}

여기서 보게 되면 invalidate()에서는 ViewParent의 invalidateChild()를 호출하는군요

01.// ViewRoot.java
02.public void invalidateChild(View child, Rect dirty) {
03.checkThread();
04....
05.}
06. 
07.public checkThread() {
08.if (mThread != Thread.currentThread()) {
09.throw new CalledFromWrongThreadException(
10."Only the original thread that created a view hierarchy can touch its views.");
11.}
12.}

invalidate()에서는 checkThread()를 호출합니다. checkThread()에서는 위와 같이

현재 실행중인 스레드가 ViewRoot가 가지고 있는 mThread와 참조가 같은지 비교하고

아니라면 CalledFromWrongThreadException 예외를 던지는군요

해결책 2
--------------------------------------------------------------
new Thread(new Runnable() {
    @Override
    public void run() {    
        runOnUiThread(new Runnable(){
            @Override
             public void run() {
                 // 해당 작업을 처리함
             }
        });
    }
}).start();
--------------------------------------------------------------


출처 :  
http://www.androidpub.com/android_dev_info/57470 

+ Recent posts