写一个简单的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 } }