简单的Android TCP Client(Android非UI线程修改界面)

写一个简单的Android TCP Client的测试程序,可以向Emulator外的TCP Server发送消息,并显示服务器的返回信息。

因为这是个很简单的小应用,本来就没想要多线程,结果在运行的时候出现如下错误:

W/System.err(  755): android.os.NetworkOnMainThreadException

原来在主进程中进行网络操作会被Android Framework给毙掉,所以新建了一个线程来进行tcp读写,再次运行又出现如下错误:

W/System.err(  853): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

原来只有创建UI的那个线程可以对UI进行修改,我实现的时候方便起见在新开线程中直接修改了TextView的内容,自然会出错。

这种很严格的程序限制可以增强应用的可靠性,或许会觉得Android给开发者设定诸多限制会影响开发效率,难道写个这么短小的东西都要实现进程间通信么?其实Android Framework本身就提供了相应的解决方案,我们可以利用下面几个方法来实现这一功能:

void Activity.runOnUiThread(Runnable action)
该方法把action添加到指定的UI进程的事件队列中去,如果当前进程就是该UI进程,那么action会立刻被执行。

Boolean View.post(Runnable action)
Booleqn View.postDelayed(Runnable action, long delayMillis)
这两个方法把action添加到特定UI元素的事件队列中区,前者直接加入消息队列等待执行,后者等待指定的时间间隔后执行。

利用以上API,很好的解决了UI修改的问题:

public class MainActivity extends Activity implements OnClickListener {
    public static final String TAG = "testServer";
    
    TextView TextViewOutput;
    EditText EditTextMsg;
    String msg;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button BtnSend = (Button) this.findViewById(R.string.BtnSend);
        TextViewOutput = (TextView) this.findViewById(R.string.TextViewOutput);
        EditTextMsg = (EditText) this.findViewById(R.string.EditTextMsg);
        
        BtnSend.setOnClickListener(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    @Override
    public void onClick(View arg0) {
        // TODO Auto-generated method stub
        msg = EditTextMsg.getText().toString().trim();
        
        if(msg.length() == 0) {
            TextViewOutput.append("cannot send blank msg\n");
            return;
        }
    
        Thread thread = new Thread(new NetThread(), "thread1");
        thread.start();
        
        EditTextMsg.setText("");
    }

    
    class NetThread implements Runnable {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                Socket socket=new Socket("10.0.2.2", 2000);
                PrintWriter pw =new PrintWriter(socket.getOutputStream());
                
                TextViewOutput.post(new ChangeText("Sending:" + msg));
                pw.println(msg);
                pw.flush();
                MainActivity.this.runOnUiThread(new ChangeText("...finished!\n"));
                
                Scanner scan = new Scanner(socket.getInputStream());
                String ret = scan.nextLine();
                TextViewOutput.post(new ChangeText("Return:" + ret + "\n"));
                
                pw.close();
                scan.close();
                
                socket.close();
            } catch (Exception EE) {
                EE.printStackTrace();
            }
        }
    }
    
    class ChangeText implements Runnable {
        String text;
        ChangeText(String text) {
            this.text = text;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            TextViewOutput.append(text);
        }   
    }
}

 另贴上一小段Server.rb

require 'socket'

server = TCPServer.new(2000)
loop {
    client = server.accept
    thread = Thread.new {
        while msg = client.gets
            puts "RECV: #{msg}"
            client.puts "ret #{msg}"
        end
    }
}

 

posted @ 2013-07-16 13:24  Zealoct  阅读(4675)  评论(0编辑  收藏  举报