寫一個簡單的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 } }