【问题标题】:Android send data from main UI thread to another threadAndroid将数据从主UI线程发送到另一个线程
【发布时间】:2013-12-02 00:30:14
【问题描述】:

我需要将一些数据从主线程发送到另一个线程。我已经阅读了很多关于线程、异步任务和处理程序的材料,但也许它们给我带来了一些困惑。我读到我需要为我的“第二个线程”创建一个处理程序,以便我可以从主线程向它发送消息(现在我不担心将任何内容发送回主线程)。

我需要第二个线程连接到服务器(通过套接字)并在某些用户事件上发送一些日期。我正在尝试以有效的方式进行(不要打开/关闭不必要的套接字连接)。所以我想知道我应该把我的打开套接字命令放在哪里?此外,在我的处理程序的 handleMessage() 方法中,我需要对套接字输出流的引用才能将数据发送到服务器。

我目前有以下代码:

protected void initThread(){
    this.thread = new HandlerThread(WorkerHandler.class.getCanonicalName()){        

        @Override
        public void run() {
            super.run();
            try{
                handler = new WorkerHandler(getLooper());
            }catch(Exception e){
                e.printStackTrace();
            }               
        }

    };
    this.thread.start();
}

我的activity的onCreate()方法中调用了方法initThread()。

下面是我的自定义处理程序类的代码:

public class WorkerHandler extends Handler {

protected Socket socket;
protected BufferedWriter writer;

public WorkerHandler(Looper looper) throws Exception{
    super(looper);      
    this.socket = new Socket("192.168.1.7", 5069);
    this.writer = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream(), "utf-8"));

}

public BufferedWriter getWriter(){
    return this.writer;
}

public Socket getSocket(){
    return this.socket;
}

@Override
public void handleMessage(Message msg) {
    Draw draw = (Draw) msg.obj;
    if (draw != null){          
        if (getWriter() != null){
            try{
                getWriter().write(DrawUtil.toJson(draw)+"\n");
                getWriter().flush();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

}

再一次,在我的活动中,我触发了 sendDataToServer() 方法

protected void sendDataToServer(){
    Draw draw = new Draw(getFlowType(), getID(), getSeq(), Calendar.getInstance(), startX, startY, endX, endY);
    if (getWorkerHandler() != null){
        Message msg = getWorkerHandler().obtainMessage();
        msg.obj = draw;
        getWorkerHandler().sendMessage(msg);
    }       
}

但我对 WorkerHandler 对象的引用始终为空。我很确定我误解了一些概念......你能给我一些提示吗?

非常感谢!

【问题讨论】:

  • 在您的活动中显示getWorkerHandler() 的代码。
  • 感谢@DavidWasser!它实际上返回 this.handler。引用在第二个线程的 run() 方法内的 initThread() 方法中设置。
  • 我看到了你的问题,只是在制定我的答案。给我几分钟。

标签: java android multithreading sockets android-asynctask


【解决方案1】:

你不能这样做。您已经使用HandlerThread 创建了第二个线程。 HandlerThread 是具有 LooperThread。这就是HandlerThreadrun() 方法中发生的事情。它正在运行 looper 循环。这意味着HandlerThread 中的run() 方法只有在Looper 退出时才会完成。

在您的initThread() 方法中您写道:

    @Override
    public void run() {
        super.run(); // <-- This call runs the Looper loop and doesn't complete!!
        try{
            handler = new WorkerHandler(getLooper());
        }catch(Exception e){
            e.printStackTrace();
        }               
    }

你可以看到你重写的run()方法首先调用super.run()。这会运行 looper 循环并且不会完成。所以initThread() 中的其余代码永远不会执行。

如果你想使用HandlerThread(),那么你不能乱用它的run() 方法。如果您希望它为您做事,那么您需要向它发布消息(或Runnables),然后在那里完成您的工作。这是一个例子:

    HandlerThread handlerThread = new HandlerThread("myHandlerThread");
    handlerThread.start();
    // Now get the Looper from the HandlerThread so that we can create a Handler that is attached to
    // the HandlerThread
    // NOTE: This call will block until the HandlerThread gets control and initializes its Looper
    Looper looper = handlerThread.getLooper();
    // Create a handler attached to the background message processing thread
    handler = new Handler(looper, this);

现在您可以向“处理程序”发布消息和Runnables。在此示例中,消息将由创建类的重写 handleMessage() 方法处理。

编辑:提供处理程序回调的代码示例

如果你像这样修改它,你可以使用你的WorkerHandler类来处理回调(我把名字改成了Worker,因为它不是真正的Handler,它只是实现了Handler.Callback接口):

public class Worker implements Handler.Callback {

    protected Socket socket;
    protected BufferedWriter writer;

    public Worker() throws Exception{    
        this.socket = new Socket("192.168.1.7", 5069);
        this.writer = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream(), "utf-8"));
    }

    public BufferedWriter getWriter(){
        return this.writer;
    }

    public Socket getSocket(){
        return this.socket;
    }

    @Override
    public void handleMessage(Message msg) {
        Draw draw = (Draw) msg.obj;
        if (draw != null){          
            if (getWriter() != null){
                try{
                    getWriter().write(DrawUtil.toJson(draw)+"\n");
                    getWriter().flush();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

现在您需要创建此Worker 类的实例,并在创建Handler 时将其作为回调参数传递。在你的活动中做:

    HandlerThread handlerThread = new HandlerThread("myHandlerThread");
    handlerThread.start();
    Looper looper = handlerThread.getLooper();
    // Create an instance of the class that will handle the messages that are posted
    //  to the Handler
    Worker worker = new Worker();
    // Create a Handler and give it the worker instance to handle the messages
    handler = new Handler(looper, worker);

【讨论】:

  • 非常感谢@DavidWasser!我理解你的意思,我已经修复了我的代码(在我的 initThreadMethod 上删除了覆盖的 run() 方法)......但是现在,当我运行程序时,我得到了 NetworkOnMainThreadException!有什么提示吗?
  • 处理程序“附加”到创建它的线程,对吗?所以这可能是问题所在......我正在 UI 线程中创建处理程序(在 handlerThread.start() 之后)
  • 这取决于您如何创建Handler。如果您查看我的示例,使用handler = new Handler(looper, this),处理程序附加到HandlerThread,而不是主线程。您可以在 UI 线程中创建 Handler,您只需要确保它附加到另一个 Thread
  • 应该把new Handler的初始化放在Thread里面的什么地方(用什么方法)?或者,如果我在调用handler = new Handler(looper,this) 时使用我当前的代码(在主线程中创建处理程序)this 是一个可调用对象......我应该把什么放在那里?我试图传递对我的线程的引用,但是 - 正如我所料 - 它不是正确的对象。再次感谢您的耐心等待...
  • 您不会在线程内初始化处理程序。您在线程外部初始化处理程序(在这种情况下,我在创建线程后在主线程中完成了它。在我的示例中,使用对 new Handler(looper, this) 的调用,因为对 handleMessage() 的调用在调用中处理类(this)。你需要给处理程序一个方法来处理消息。在我的例子中,我已经通过了this,因为我的类实现了Handler.Callback接口并且我的类中有一个handleMessage()方法. 你可以做的是使用你的WorkerHandler 类,如果你愿意的话。
【解决方案2】:

您可以使用标准 Java 方法解决消费者/生产者问题,即一个线程和任意数量的线程消耗的 BlockingQueue 产生数据。

public class SendingWorker {
    private final BlockingQueue<Draw> sendQueue = new LinkedBlockingQueue<Draw>();
    private volatile Socket socket;

    public void start() {
        thread.start();
    }

    public void stop() {
        // interrupt so waiting in queue is interrupted
        thread.interrupt();
        // also close socket if created since that can block indefinitely
        Socket socket = this.socket;
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // adding to queues is thread safe
    public void send(Draw draw) {
        sendQueue.add(draw);
    }

    private final Runnable task = new Runnable() {
        @Override
        public void run() {
            try {
                socket = new Socket(InetAddress.getLocalHost(), 8000);
                OutputStream out = socket.getOutputStream();
                while (true) {
                    Draw draw = sendQueue.take();
                    out.write(draw);
                    out.flush();
                }
            } catch (Exception e) {
                // handle 
            } finally {
                // cleanup
            }
        }
    };
    private final Thread thread = new Thread(task);
}

【讨论】:

  • 谢谢@zapl!我会试试你的解决方案,我会告诉你的。最好的!
【解决方案3】:

可以通过广播接收器获取值......如下,首先创建自己的IntentFilter为,

Intent intentFilter=new IntentFilter();
intentFilter.addAction("YOUR_INTENT_FILTER");

然后创建内部类BroadcastReceiver为,

    private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    /** Receives the broadcast that has been fired */
    @Override
    public void onReceive(Context context, Intent intent) {
        if(intent.getAction()=="YOUR_INTENT_FILTER"){
           //HERE YOU WILL GET VALUES FROM BROADCAST THROUGH INTENT EDIT YOUR TEXTVIEW///////////
           String receivedValue=intent.getStringExtra("KEY");
        }
    }
};

现在在 onResume() 中注册您的广播接收器,

registerReceiver(broadcastReceiver, intentFilter);

最后在 onDestroy() 中将 BroadcastReceiver 注销为,

unregisterReceiver(broadcastReceiver);

现在最重要的部分...您需要从后台线程触发广播以发送值.....这样做,

Intent i=new Intent();
i.setAction("YOUR_INTENT_FILTER");
i.putExtra("KEY", "YOUR_VALUE");
sendBroadcast(i);

....干杯:)

【讨论】:

  • 不知道为什么你认为 BroadcastReceiver 的 onReceive() 是在非 UI 线程上执行的
猜你喜欢
  • 1970-01-01
  • 2014-01-15
  • 2011-12-16
  • 1970-01-01
  • 2014-08-05
  • 1970-01-01
  • 2015-10-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多