【问题标题】:Android Thread.start() CalledFromWrongThreadExceptionAndroid Thread.start() CalledFromWrongThreadException
【发布时间】:2014-05-08 00:17:15
【问题描述】:

我不确定我的低估是否正确,因为我没有得到预期的输出。我有一个类,我在其中调用一个应该启动线程的方法。

public class MainActivity extends Activity {

protected void onCreate(Bundle savedInstanceState) {
beginListenForData()
}

beginListenForData 函数用于启动一个线程并不时检查是否有数据可供读取。如果是这种情况,它会读取并更新一个 UI 变量:

void beginListenForData()
{
    Thread workerThread = new Thread(new Runnable() {
        @Override
        public void run()
        {    
            int bytesAvailable = 3;
            while(!Thread.currentThread().isInterrupted())
            {
                try
                {
                    bytesAvailable = mmInStream.available();
                    if(bytesAvailable > 0)
                    {
                        byte[] packetBytes = new byte[bytesAvailable];
                        mmInStream.read(packetBytes);
                bytesAvailable = mmInStream.available();
                String s = new String(packetBytes);
                text.setText(s);
                    }
                }
                catch (Exception e)
                {
            // TODO Auto-generated catch block
                e.printStackTrace();
                }
            }
        }
    });

       workerThread.start();
   }

}

我没有得到想要的输出。该线程应该读取数据或检查数据是否可用。如果可用,则读取它并将 UI 变量设置为读取的值。

我的实施是否正确?我的代码有问题吗?

【问题讨论】:

  • 改用异步线程
  • 我是 JAVA 新手 - 我们如何创建这种类型的线程?
  • 您无法从后台 Thread 更新 UI。 Main Thread 必须进行所有更新。因此,为了做到这一点,您必须使用Handler 在背景ThreadMain Thread 之间传递Messages。
  • 那么,你得到了什么而不是想要的输出?
  • 初始数据显示在第一次发送的UI中

标签: java android multithreading


【解决方案1】:

一个普通的Thread should not access the UI thread。我建议在 Android 中使用 AsyncTask 而不是使用标准线程或 Runnables。 AsyncTask 可用于同时远离 UI 线程工作,然后对其进行更改。 doInBackground() 方法中调用的所有内容都在主 UI 线程之外完成,onPreExecute()onPostExecute() 方法中调用的所有内容都可以与您的 UI 很好地交互。

当您从 UI 线程上运行的东西(如 Activity,在您的实例中)调用新线程时,建议使用 AsyncTask。下面的例子;

public class YourAsyncTask extends AsyncTask{

    @Override
    protected Object doInBackground(Object... arg0) {
        // Work to be done in the background.
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        // Changes to be made to UI
    }

    @Override
    protected void onPreExecute() {
         // Changes to be made to UI
    }

}

然后你可以像这样从你的活动中运行AysncTask

new YourAsyncTask().execute("");

要处理您的Activity,您可能需要为您的AysncTask 创建一个自定义构造函数,并通过构造函数将您的活动实例传递给它,以存储在实例变量中。我希望这会有所帮助。

更多信息;

【讨论】:

    【解决方案2】:

    线程无法更新 GUI 你可以使用runOnUiThread()方法或者使用Handler

    编辑: 处理程序示例:How to use an Android Handler to update a TextView in the UI Thread?

    【讨论】:

    • 在哪里编写处理程序?
    • 你必须创建一个Class extends Thread 查看我昨天发布的这篇文章,它显示了如何。 stackoverflow.com/questions/22671923/…
    • 我刚刚添加了一个处理程序示例的链接
    • 感谢您对示例的推荐链接让我解决了这个问题。非常感谢
    【解决方案3】:
     text.setText(s);
    

    您无法从工作线程中触摸 UI。应该是执行text.setText(s);的UI Thread

    【讨论】:

    • 我是否需要在我的类中创建另一个线程来更新 UI - 如果需要,我如何从线程中读取数据?
    【解决方案4】:

    这就是我的做法

    创建活动的成员变量:

    private BackgroundWorkerThread m_bgThread = null;
    protected Handler m_bgHandler = null;
    

    请参阅下面我的 BackgroundWorkerThread 类,然后在 onCreate() 中初始化主活动中的线程和处理程序:

      // Notice we pass the Handler Constructor this because we are implementing the interface
      // in our activity
      m_bgHandler = new Handler(this)
      m_bgThread = new BackgroudnWorkerThread();
      m_bgThread.start();
    

    确保您有您的活动implements Handler.Callback 接口,它将将此方法添加到您的活动:

    @Override public boolean handleMessage(Message msg)
     {
         switch(msg.what){
    
         case 0:
              // Will update the UI on the Main Thread
              updateUI(msg.obj);
          break;
    
         case 1:
             // rejoin the Thread with the main Thread
            try {
               m_bgThread.join();
            } catch (InterruptedException e) {
               // TODO Auto-generated catch block
              e.printStackTrace();
          }
          break;
       }
        return true;
     }
    

    在每个 case 语句中,您都可以将消息传递给后台线程。

    例如在您的活动中调用 sendFirstMessage():

     public void sendFirstMessage(){
    
        // Passes the 0 to the background Handler handleMessage
        m_bgThread.workerHandler.obtainMessage(0).sendToTarget();
    
     } 
    

    那么在你的 Activity 的处理程序中,handleMessage() 覆盖

     case 0:
         updateUI(msg.obj);
    
     break;
    

    然后

    public void updateUI(Object obj){
    
        text.setText(obj.toString());
    
        // Call to tell Background Thread to shut down looper
        m_bgThread.workerHandler.obtainMessage(1).sendToTarget();
    
        // Call to tell the Main Thread no more updates needed
        m_bgHandler.obtainMessage(1).sendToTarget();
    

    }

    然后创建你的后台线程类作为你的活动的子类:

    private class BackgroundWorkerThread extends Thread implements Handler.Callback{
      private Looper workerLooper;
      protected Handler workerHandler;
    
     @Override public void run()
     {
         // Set up the Handler and Looper
         Looper.prepare();
         workerLooper = Looper.myLooper();
         workerHandler = new Handler(workerLooper, this);
         Looper.loop();
     }
    
     @Override public boolean handleMessage(Message msg)
     {
         switch(msg.what){
    
         case 0:
             // now this function is being called to run on the background Thread  
          beginListenForData();
          break;
    
         case 1:
          workerLooper.quit();
          break;
       }
        return true;
     }
    
    }
    
    
      // This method is now being execute in the background
     public void beginListenForData()
     {
    
               int bytesAvailable = 3;
              while(!Thread.currentThread().isInterrupted())
              {
                try{
                    bytesAvailable = mmInStream.available();
                    if(bytesAvailable > 0)
                    {
                    byte[] packetBytes = new byte[bytesAvailable];
                    mmInStream.read(packetBytes);
                    bytesAvailable = mmInStream.available();
                    String s = new String(packetBytes);
                    // Instead of trying to set the text view here, send a message back to 
                    // the Activity to update the UI on the Main thread
                    // text.setText(s);
                    // Passes 0, and the String Object to the Activity Handler
                     m_bgHandler.obtainMessage(0, s).sendToTarget();
                    }
            }catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
              }
           }
      }
    

    这有点难以理解,但这就是它的完成方式,手动创建一个线程,使用循环器在后台检查消息,然后将消息从后台线程传递给活动以更新用户界面。

    祝你好运!有任何问题欢迎随时提问。

    或者你可以采取从http://android-developers.blogspot.com/2009/05/painless-threading.html获取的最简单的意识形态路线

     public void beginListenForData()
      {
       String s = null;
    
       new Thread(new Runnable(){
    
          public void run(){
    
               int bytesAvailable = 3;
              while(!Thread.currentThread().isInterrupted())
              {
                try{
                    bytesAvailable = mmInStream.available();
                    if(bytesAvailable > 0)
                    {
                      byte[] packetBytes = new byte[bytesAvailable];
                      mmInStream.read(packetBytes);
                      bytesAvailable = mmInStream.available();
                      s = new String(packetBytes);
    
                       // Takes Care of updating the UI
                       text.post(new Runnable(){
                           public void run(){
                               text.setText(s);
                           }
                        });
                    }
                 }catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                 }
           }
         }).start();
      }
    

    【讨论】:

      【解决方案5】:

      你先打电话给super.onCreate(savedInstanceState);了吗?

      那你得打电话给setContentView(R.layout.yourlayout);

      为什么bytesAvailable 设置为 3 而不是 0?

      为什么bytesAvailable = mmInStream.available(); 被调用两次?一个就够了!

      那么 mmInStreamtext ( !important!text = (TextView) findViewById(R.id.thetextviewid);) 在某处初始化了吗?

      如果所有这些事情都完成了,一切都应该没问题。

      编辑:最后你必须致电text.postInvalidate();

      【讨论】:

      猜你喜欢
      • 2012-03-22
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多