2013-05-08 51 views
1

我希望有人能解釋我爲什麼失敗?從錯誤的線程+內部類叫..奇怪的行爲

我的活動有一個內部類,AppHelper,可導出函數setThrobber。爲了簡單起見,我省略了所有的初始化代碼等。

此函數setThrobber是爲了在UI線程之外調用,通常在網絡加載期間報告進度......然後,這些類使用內部類AppHelper使用setThrobber方法從網絡加載器線程執行此操作。

令我吃驚的是,第一種方法失敗了(見最後的錯誤),第二種方法成功。爲什麼不是第一個在UI線程中執行的,第二個是?更奇怪的是,在錯誤堆棧跟蹤看起來它是從UI線程調用的,即使Android拋出了「來自錯誤的線程」異常。爲什麼不是兩個代碼塊都等同於線程的角度?

PD-我還嘗試了一個handler.post(),結果相同! PD2- AppHelper類實例化在onCreate

在此先感謝!

public class MyApplication extends Activity { 

    private ProgressDialog progressDialog; 

    void setThrobber_internal (String message) { 
     progressDialog.setMessage(message); 
    } 

    public class AppHelper { 

     public setThrobber(final String msg) { 
      MyApplication.this.runOnUiThread(new Runnable() { 
       @Override 
       public void run() { 
        setThrobber_internal(msg); 
        // This throws CalledFromWrongThread (!!) 
       } 
      }); 
     } 
    } 
} 

VERSUS

public class MyApplication extends Activity { 

    private ProgressDialog progressDialog; 

    private void setThrobber_internal(final String msg) { 

     // runUiThread here instead of in inner class wrapper 

     runOnUiThread(new Runnable() { 
      @Override 
      public void run() { 
       progressDialog.setMessage(msg); 
      } 
     }); 
    } 

    public class AppHelper { 

     public void setThrobber(final String msg) { 
      setThrobber_internal(msg); // this works OK 
     } 
    } 
} 

的第一種情況的堆棧跟蹤:

E/AndroidRuntime(17677): FATAL EXCEPTION: main 
E/AndroidRuntime(17677): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 
E/AndroidRuntime(17677): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039) 
E/AndroidRuntime(17677): at android.view.ViewRootImpl.invalidateChild(ViewRootImpl.java:722) 
E/AndroidRuntime(17677): at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:771) 
E/AndroidRuntime(17677): at android.view.ViewGroup.invalidateChild(ViewGroup.java:4005) 
E/AndroidRuntime(17677): at android.view.View.invalidate(View.java:8576) 
E/AndroidRuntime(17677): at android.view.View.invalidate(View.java:8527) 
E/AndroidRuntime(17677): at android.widget.TextView.checkForRelayout(TextView.java:6760) 
E/AndroidRuntime(17677): at android.widget.TextView.setText(TextView.java:3306) 
E/AndroidRuntime(17677): at android.widget.TextView.setText(TextView.java:3162) 
E/AndroidRuntime(17677): at android.widget.TextView.setText(TextView.java:3137) 
E/AndroidRuntime(17677): at com.android.internal.app.AlertController.setMessage(AlertController.java:261) 
E/AndroidRuntime(17677): at android.app.AlertDialog.setMessage(AlertDialog.java:185) 
E/AndroidRuntime(17677): at android.app.ProgressDialog.setMessage(ProgressDialog.java:314) 
---------------------------------- 
E/AndroidRuntime(17677): at com.regaliz.libneo.NativeStory.setThrobber_internal(NativeStory.java:269) 
E/AndroidRuntime(17677): at com.regaliz.libneo.NativeStory$AppHelper$8.run(NativeStory.java:865) 
---------------------------------- 
E/AndroidRuntime(17677): at android.os.Handler.handleCallback(Handler.java:605) 
E/AndroidRuntime(17677): at android.os.Handler.dispatchMessage(Handler.java:92) 
E/AndroidRuntime(17677): at android.os.Looper.loop(Looper.java:137) 

要求提供補充代碼:

  • 的AppHelper類的主要內部實例化活動,並通過編輯在活動的其他子類,即保持與WeakReference的(檢查,這是沒有問題的)

  • 使用失敗做AppHelper的類:創建

public void story_loadfonts(String jsonFonts) { 

    final AppHelper appHelper=mWeakAppHelper.get(); // apphelper stored in a weak ref 

    try { 
     . 
     . 
     . 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        for (int i=0; i<NUMFONTS; i++) { 
         load_font_from_network(i); 
         appHelper.setThrobber("LOADING FONT "+i+"/"+NUMFONTS); 
        } 
       } catch (JSONException e) { 
        e.printStackTrace(); 
       } 
      } 
     }).start(); 
    } catch (Exception e) { 
     return; 
    } 
} 
+0

代碼看起來不錯。也許更多的代碼會有幫助 – Blackbelt 2013-05-08 15:07:00

+0

是的,它也看起來很好!事情是,這個問題沒有太多相關的代碼。如果我只是切換runUiThread它完美的作品,但我不知道區別! – rupps 2013-05-08 15:08:58

+0

請分享實例化AppHelper並調用方法的代碼。 – tbkn23 2013-05-08 15:21:35

回答

1

Looking at the ticked answer from Android: Accessing UI Element from timer thread , I wonder if the issue is to do with where the runOnUiThread電話。

據Android開發者頁面,runOnUiThread

運行在UI線程上指定的操作。如果當前線程是UI線程,那麼該動作立即執行。如果當前線程不是UI線程,則該操作將被髮布到UI線程的事件隊列中。從Android Java runOnUiThread()

注意對活動的處理程序調用post基本上與調用runOnUithread

因此,問題是Handler與第一種情況關聯的方式與第二種情況的Handler不同。

在第二種情況下,我懷疑你保證發佈到與主要活動相關的處理程序。

在第二種情況下,我推斷這是與創建AppHelper的線程相關聯的處理程序,應該與您說的相同。

編輯:基於這樣一個問題筆者進一步互動,主要內容如下:

它將似乎有效地停止活動UI線程死了,onResume創建一個新的一個。所以我們可以訪問它的處理程序也通過這個過程被更新。

(最終的,弱)appHolder實例化與舊的UI線程處理程序相關聯,因此如果runOnUiThread在其中執行,則引用舊的持有者(通過runOnUiThread)。這是第一種情況。

然而,在第二種情況下,向runOnUiThread呼叫不再在該代碼執行(稱爲應運而生預),而是將已更新處理程序runOnUiThread呼叫的主要活動內的方法。

總之:請確保調用runOnUiThread(實際上handler.post())總是在保證他們使用的是最新的live版的活動(和它的UI線程),而不是鏈接到的方式完成之前的版本。

+0

嗨Neil!非常有趣的指針! (+1)AppHelper確實是在Oncreate上創建的,但我必須檢查一些與稍後使用的Handler有關的事情......我很確定它是在UI線程上,因爲我在一百萬個地方使用它該應用程序執行UI工作沒有問題...但我對你的答案有疑問:runOnUiThread和Handler之間的關係是什麼? runOnUiThread的處理程序是不是內部的東西? – rupps 2013-05-08 16:02:57

+0

順便說一句,我懷疑這是與Synthesized方法訪問內部類中的父方法有關,但爲什麼他們在runUiThread()之外執行是我不明白的...... – rupps 2013-05-08 16:24:30

+0

感謝RunOnUiThread上的文檔,但有趣事情是我沒有超過一個活動,並且AppHelper應該只存在一次。這就是爲什麼這個問題是我的X檔案。我越來越確信它與內部類如何通過合成的靜態獲取器和設置器訪問其父類有關,因爲除此之外,我看不到任何其他差異。如果Activity的主要處理程序存在這樣的問題,我想它會在其他地方顯示,該應用程序會下載數百個資源,動態創建視圖,共享一個處理程序,但不會出現問題... – rupps 2013-05-08 18:48:48