2017-07-03 58 views
3

我剛剛開始使用Android Concurrency/Loopers/Handers進行遊戲,而且我剛剛面臨奇怪的異常。下面的代碼不會阻止我從不同線程的TextView上設置文本。在後臺線程異常情況下在TextView上設置文本

TextView tv; 
Handler backgroundHandler; 

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

    tv = (TextView) findViewById(R.id.sample_text); 
    Runnable run = new Runnable() { 
     @Override 
     public void run() { 
      Looper.prepare(); 
      backgroundHandler = new Handler() { 
       @Override 
       public void handleMessage(Message msg) { 
        String text = (String) msg.obj; 
        tv.setText(Thread.currentThread().getName() + " " + text); 
       } 
      }; 
      Looper.loop(); 
     } 
    }; 

    Thread thread = new Thread(run); 
    thread.setName("Background thread"); 
    thread.start(); 
    try { 
     Thread.sleep(100); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
    Message message = backgroundHandler.obtainMessage(); 
    message.obj = "message from UI"; 
    backgroundHandler.sendMessage(message); 
} 

你猜怎麼發生

enter image description here

但是,當我睡覺後臺線程一會兒

backgroundHandler = new Handler() { 
      @Override 
      public void handleMessage(Message msg) { 
       try { 
        Thread.sleep(1000); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       String text = (String) msg.obj; 
       tv.setText(Thread.currentThread().getName() + " " + text); 
      } 
     }; 

它確實如我所料

07-03 18:54:40.506 5996-6025/com.stasbar.tests E/AndroidRuntime: FATAL EXCEPTION: Background thread 
                  Process: com.stasbar.tests, PID: 5996 
                  android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 
                   at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7275) 
拋出異常0

有人可以解釋我發生了什麼事嗎?爲什麼我能夠從不同的線程設置文本?

回答

3

從最初的嘗試,你的代碼是錯誤的,因爲你在不同的線程創建Handler。這導致循環/處理程序在不同的線程上運行。基於你的評論,我想你知道這個問題,你想知道爲什麼異常不會第一次嘗試,但第二次。

您應該小心:在除UI Thread之外的其他線程上訪問UI元素會導致未定義的行爲。這意味着:

  • 有時你看到它的工作原理。
  • 有時候你沒有。你會遇到異常,如你所見。

這意味着在不同線程上訪問UI元素並不總是100%可重現。

爲什麼你應該只在UI線程上訪問所有UI元素?由於處理UI元素(更改內部狀態,繪製到屏幕...)是一個複雜的過程,需要在相關方之間進行同步。例如,您可以在屏幕上顯示的兩個片段上撥打TextView#setText(String)。 Android不會同時執行此操作,而是將所有作業推送到UI消息隊列中,然後按順序執行此操作。這不僅從您的應用觀點來看也是如此,從整個Android系統角度來看也是如此。從系統調用的狀態欄更新,從您的應用程序更新應用程序始終將操作推送到相同 UI處理前的消息隊列。

當你訪問和修改不同線程上的UI元素時,你打破了這個過程。這意味着也許兩個線程可能會在同一狀態和同一時間訪問和修改元素。因此,您在某個時間將遇到競賽狀況。發生錯誤時。

解釋你的情況很難,因爲沒有足夠的數據進行分析。但有幾個原因:

  • 在您第一次嘗試,TextView尚未顯示在屏幕上。所以不同的線程能夠在TextView上進行更改。但第二次嘗試時,你會睡一秒鐘。此時,所有的視圖都在屏幕上成功呈現並顯示,所以異常拋出。你可以試試Thread.sleep(0),希望你的代碼不會崩潰。
  • 這是會發生在某些情況下,但很難猜測爲什麼。有些機會,你的線程和UI線程訪問同一個鎖對象,拋出異常。

你可以閱讀更多關於多線程的問題在這裏Android Thread

明確提到在非主線程

許多任務更新UI對象的最終目標 。但是,如果其中一個線程訪問視圖層次結構中的對象,則可能導致應用程序不穩定:如果工作線程在同一時間更改該對象的屬性,並且其他線程正在引用對象,則結果爲 未定義。

希望這對你有所幫助。

0

只有UI線程才能編輯UI項目。換句話說,你不能從後臺線程編輯用戶界面。

所以,與其tv.setText(Thread.currentThread().getName() + " " + text);,使用下面的代碼中你backgroundHandler: -

runOnUiThread(new Runnable() { 
    public void run() { 
     tv.setText(Thread.currentThread().getName() + " " + text); 
    } 
}); 
+0

我知道了,請再閱讀我的文章。我想知道爲什麼當我從不同的線程訪問TextView時工作。 –

相關問題