这个系列的文章就是为了防止以后自己开发的时候忘了怎么回事,提醒自己用的……由于自己Android开发非常非常菜,所以贴上来的代码很有可能像一坨shi一样,如果有新手朋友看到,希望谨慎地作为参考,同时如果有神牛看到的话,非常希望能批评指正。


到现在为止做了两个安卓的app,都跟网络通讯有关,通过WIFI连接电脑上的服务器,然后他们之间互相交流一点数据。目前我会的最简单的实现方法是利用socket,它就像一个文件一样,连接好以后写/读它就可以了。

要注意的是安卓凡是涉及网络的事情,都不允许在主线程中完成,都必须单开一个线程做,否则会抛出异常,名字的意思就是“主线程中有网络操作”。

另外需要注意的是要开启网络权限,第二个app我调了半天收不到数据,最后猛然想起是没有开网络权限导致的…… 这个下面说。


一般我们都是用一个线程的run函数来做我们希望做的网络操作,就像下面这个登录Activity一样:

代码大部分其实没什么用,看几个关键点就可以了……

有一点这个代码里没有的,就是socket的定义,直接

Socket socket = null;

就可以了。

public class LoginActivity extends Activity 
{
	ImageButton btn;
	EditText editText;
	GetThread getThread; //这个是网络线程
	
	Handler handler; //网络线程要是想更新UI,需要一个handler来转消息
	
	THUClient the_app; //这个东西是为了使用全局变量用的,不用看,THUClient是个类名
	
	class GetThread implements Runnable //一般线程用内部类写,然后implements Runnable
	{
		public void getMsg()
		{
			try 
			{
				Scanner in = new Scanner(the_app.socket.getInputStream());//输入流包装一下
				String gotmsg = in.nextLine();//就像读控制台和读文件一样读就可以了
				Message msg = new Message();
				msg.obj = gotmsg;//用msg的obj域搭载消息
				LoginActivity.this.handler.sendMessage(msg);//这句话就能把msg扔给handler让他去处理了
				
			} catch (UnknownHostException e) 
			{
				Message msg = new Message();
				msg.obj = "net error";
				LoginActivity.this.handler.sendMessage(msg);
			} catch (IOException e) 
			{
				Message msg = new Message();
				msg.obj = "net error";
				LoginActivity.this.handler.sendMessage(msg);
			}
			
		}
		
		@Override
		public void run() //启动线程时,启动这个函数
		{
			try 
			{
				the_app.socket = new Socket(); //实例化socket
				the_app.socket.connect(new InetSocketAddress(the_app.ip, the_app.port) , 5000);//connect函数,为什么要这么写呢?因为这样可以设置超时时间,比如这里就是5000毫秒 = 5秒
			} catch (UnknownHostException e) 
			{
				Message msg = new Message();
				msg.obj = "did not login";
				LoginActivity.this.handler.sendMessage(msg);
			} catch (IOException e) {
				Message msg = new Message();
				msg.obj = "did not login";
				LoginActivity.this.handler.sendMessage(msg);
			}//凡是这种try-catch语句块,都要处理好必要的异常,否则程序很容易崩出去(比如UnknownHostException不处理的话如果服务器没开就崩出去了),我的处理方法是遇到网络异常的时候,通知主线程弹一个Toast,告诉一下用户
			
			if( the_app.socket != null)
				getMsg();
			else
			{
				Message msg = new Message();
				msg.obj = "did not login";
				LoginActivity.this.handler.sendMessage(msg);
			}
		}
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_login);
		
		the_app = (THUClient) getApplicationContext();
		
		btn = (ImageButton)findViewById(R.id.buttonId);
		editText = (EditText) findViewById(R.id.editTextId);
		
		Context ctx = LoginActivity.this;//这些是干别的事用的不用看
		SharedPreferences sp = ctx.getSharedPreferences("SP", MODE_PRIVATE);
		
		final String lastIP = sp.getString("IP", "none");
		if(! lastIP.equals("none") )
		{
			the_app.ip = lastIP;
			editText.setText(lastIP);
		}
		final Editor editor = sp.edit();
		
		handler = new Handler()//handler是这么用的
		{
			public void handleMessage(Message msg)
			{
				String words = (String)msg.obj;
				if( words.equals("ok") == true )
				{
					Toast.makeText(LoginActivity.this, "来自服务器的问候:登陆成功!!欢迎使用~",Toast.LENGTH_LONG).show();
					finish();
				}
				else if(words.equals("did not login") )
				{
					Toast.makeText(LoginActivity.this,"ip输错了或者是主机没有开", Toast.LENGTH_SHORT).show();
				}
			}
		};
		
		btn.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View arg0) 
			{
				
				the_app.ip = editText.getText().toString();
				if(! lastIP.equals(the_app.ip))
				{
					editor.putString("IP",the_app.ip);
					editor.commit();
				}
				
				getThread = new GetThread();//点击按钮,发起线程
				new Thread(getThread).start();

			}
		});
		
	}

}







总的来看,过程就是这样的:

1)用户点击按钮,发起网络线程;

2)run函数进入,socket连接;

3)连接正常的话,收数据,

4)告诉handler更新UI。

有时候我们可能希望和服务器进行交互,下面是一个例子:

public void run() 
{
	try 
	{
		PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
		, true); // 用PrintWriter包装一下输出流
		out.println("1 "+ nowFrom +" "+ nowTo);//这样就可以向socket输出
				
		BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));//刚才用Scanner,用BufferedReader也行
		String ans = in.readLine();
		if(ans != null)//有时候会读进来null,我也不知道怎么回事,总之这个判断可以帮你的程序变得更健壮
		{
		<span style="white-space:pre">	</span>Message msg = new Message();
			msg.obj = ans;
			AskInputActivity.this.handler.sendMessage(msg);
		}
				
		} catch (IOException e) {
			Message msg = new Message();
			msg.obj = "net error";
			AskInputActivity.this.handler.sendMessage(msg);
		}
			
	}
}








服务器端就是典型的java/c++网络编程,跟这篇文章要讨论的就没关系了。

一点经验是网络线程能不一直跑着就不要一直跑着,如果是严格的“请求-响应”工作模式的话,最好每次要发起连接的时候发起一个网络线程,发完-收完数据就结束,不然想结束一个线程不是那么轻松的事情。

最后,想正常利用网络,你需要在manifest里面申请网络权限:

<uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.INTERNET" /> //这一句

    <application
        android:name="com.zero.client.THUClient"
        android:allowBackup="true"
        android:icon="@drawable/icon_mod"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" > …………

一定要有,不然上面的写了等于白写,而且可恶的是他不告诉你……

暂且先记录这么多吧……