Android開發手記:Socket網絡通信

這個系列的文章就是爲了防止以後自己開發的時候忘了怎麼回事,提醒自己用的……由於自己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" > …………
一定要有,不然上面的寫了等於白寫,而且可惡的是他不告訴你……

暫且先記錄這麼多吧……



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章