編輯:關於Android編程
基於TCP協議的網絡通信
使用URL訪問網絡資源
使用HTTP訪問網絡
使用WebView視圖顯示網頁
基於TCP協議的網絡通信
TCP/IP通信協議是一種可靠的網絡協議,它在通信的兩端各建立一個Socket,通信的兩端之間形成網絡虛擬鏈路。Java對基於TCP協議的網絡通信提供了良好的封裝,Java使用Socket對象來代表兩端的通信接口,並通過Socket產生IO流來進行網絡通信。
1.1 使用ServerSocket創建TCP服務器端
Java中能接收其他通信實體連接請求的類是ServerSocket, ServerSocket對象用於監聽來自客戶端的Socket連接,如果沒有連接,它將一直處於等待狀態。 ServerSocket包含一個監聽來自客戶端連接請求的方法。
Socket accept():如果接收到一個客戶端Socket的連接請求,該方法將返回一個與客戶端Socket對應的Socket;否則該方法將一直處於等待狀態,線程也被阻塞。
為了創建ServerSocket對象,ServerSocket類提供了如下幾個構造器:
ServerSocket(int port):用指定的端口port來創建一個ServerSocket。該端口應該是有一個有效的端口整數值:0~65535。
ServerSocket(int port,int backlog):增加一個用來改變連接隊列長度的參數backlog。
ServerSocket(int port,int backlog,InetAddress localAddr):在機器存在多個 IP地址的情況下,允許通過localAddr這個參數來指定將ServerSocket綁定到指定的IP地址。
當ServerSocket使用完畢,應使用ServerSocket的close()方法來關閉該ServerSocket。
通常情況下,服務器不應該只接受一個客戶端請求,而應該不斷地接受來自客戶端的所有請求,所以Java程序通常會通過循環,不斷地調用ServerSocket的accept()方法。如下代碼片段所示:
1.2 使用Socket進行通信
客戶端通常可使用Socket的構造器來連接到指定服務器,Socket通常可使用如下兩個構造器:
Socket(InetAddress/String remoteAddress, int port):創建連接到指定遠程主機、遠程端口的Socket。
Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort):創建連接到指定遠程主機、遠程端口的Socket,並指定本地IP地址和本地端口號。
上面兩個構造器中指定遠程主機時既可使用InetAddress來指定,也可直接使用String對象來指定,但程序通常使用String對象(如127.0.0.1)來指定遠程IP。
以上代碼將會連接到指定服務器,讓服務器端的ServerSocket的accept()方法向下執行。
當客戶端、服務器端產生了對應的Socket之後,程序無須再區分服務器、客戶端,而是通過各自的Socket進行通信,Socket提供如下兩個方法來獲取輸入流和輸出流:
InputStream getInputStream():返回該Socket對象對應的輸入流,讓程序通過該輸入流從Socket中取出數據。
OutputStream getOutputStream():返回該Socket對象對應的輸出流,讓程序通過該輸出流向Socket中輸出數據。
例:簡單網絡通信:
服務器端程序代碼:
SimpleServer.java
public class SimpleServer { public static void main(String[] args) throws IOException { //創建一個ServerSocket,用於監聽客戶端Socket的連接請求 ServerSocket ss = new ServerSocket(30000); //采用循環不斷接受來自客戶端的請求 while (true) { //每當接受到客戶端Socket的請求,服務器端也對應產生一個Socket Socket s = ss.accept(); OutputStream os = s.getOutputStream(); os.write(您好,您收到了服務器的新年祝福! .getBytes(utf-8)); //關閉輸出流,關閉Socket os.close(); s.close(); } } }
客戶端程序:
SimpleClient.java
public class SimpleClient extends Activity { EditText show; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); show = (EditText) findViewById(R.id.show); //關閉輸入流、socket try { Socket socket = new Socket(127.0.0.1 , 30000); //將Socket對應的輸入流包裝成BufferedReader BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); //進行普通IO操作 String line = br.readLine(); show.setText(來自服務器的數據: + line); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
總結Socket通信,創建服務器步驟:
指定端口實例化一個ServerSocket
調用ServerSocket的accept()等待連接
獲取位於該底層Socket的流以進行讀寫操作
對數據封裝成流
對Socket進行讀寫
關閉打開的流
總結Socket通信,創建客戶端的步驟:
通過IP地址和端口實例化Socket,請求連接服務器
獲取Socket上的流以進行讀寫
把流包裝進BufferedReader/PrintWriter的實例
對Socket進行讀寫
關閉打開的流
1.3 多線程
實際應用中的客戶端則可能需要和服務器端保持長時間通信,即服務器需要不斷地讀取客戶端數據,並向客戶端寫入數據;客戶端也需要不斷地讀取服務器數據,並向服務器寫入數據。
服務器應該為每個Socket單獨啟動一條線程,每條線程負責與一個客戶端進行通信。
客戶端讀取服務器數據的線程同樣會被阻塞,所以系統應該單獨啟動一條線程,該線程專門負責讀取服務器數據。
例:C/S聊天室程序:
服務端程序:
MyServer.java
public class MyServer { //定義保存所有Socket的ArrayList public static ArrayListsocketList = new ArrayList (); public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(30000); while(true) { //此行代碼會阻塞,將一直等待別人的連接 Socket s = ss.accept(); socketList.add(s); //每當客戶端連接後啟動一條ServerThread線程為該客戶端服務 new Thread(new ServerThread(s)).start(); } } }
ServerThread.java
//負責處理每個線程通信的線程類 public class ServerThread implements Runnable { //定義當前線程所處理的Socket Socket s = null; //該線程所處理的Socket所對應的輸入流 BufferedReader br = null; public ServerThread(Socket s) throws IOException { this.s = s; //初始化該Socket對應的輸入流 br = new BufferedReader(new InputStreamReader( s.getInputStream() , utf-8)); //② } public void run() { try { String content = null; //采用循環不斷從Socket中讀取客戶端發送過來的數據 while ((content = readFromClient()) != null) { //遍歷socketList中的每個Socket, //將讀到的內容向每個Socket發送一次 for (Socket s : MyServer.socketList) { OutputStream os = s.getOutputStream(); os.write((content + ).getBytes(utf-8)); } } } catch (IOException e) { e.printStackTrace(); } } //定義讀取客戶端數據的方法 private String readFromClient() { try { return br.readLine(); } //如果捕捉到異常,表明該Socket對應的客戶端已經關閉 catch (IOException e) { //刪除該Socket。 MyServer.socketList.remove(s); //① } return null; } }
客戶端程序:
MultiThreadClient.java
public class MultiThreadClient extends Activity { // 定義界面上的兩個文本框 EditText input, show; // 定義界面上的一個按鈕 Button send; OutputStream os; Handler handler; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); input = (EditText) findViewById(R.id.input); send = (Button) findViewById(R.id.send); show = (EditText) findViewById(R.id.show); Socket s; handler = new Handler() { @Override public void handleMessage(Message msg) { // 如果消息來自於子線程 if (msg.what == 0x123) { // 將讀取的內容追加顯示在文本框中 show.append( + msg.obj.toString()); } } }; try { s = new Socket(127.0.0.1, 30000); // 客戶端啟動ClientThread線程不斷讀取來自服務器的數據 new Thread(new ClientThread(s, handler)).start(); // ① os = s.getOutputStream(); } catch (Exception e) { e.printStackTrace(); } send.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { try { // 將用戶在文本框內輸入的內容寫入網絡 os.write((input.getText().toString() + ) .getBytes(utf-8)); // 清空input文本框 input.setText(); } catch (Exception e) { e.printStackTrace(); } } }); } }
客戶端線程:
ClientThread.java
public class ClientThread implements Runnable { //該線程負責處理的Socket private Socket s; private Handler handler; //該線程所處理的Socket所對應的輸入流 BufferedReader br = null; public ClientThread(Socket s , Handler handler) throws IOException { this.s = s; this.handler = handler; br = new BufferedReader( new InputStreamReader(s.getInputStream())); } public void run() { try { String content = null; //不斷讀取Socket輸入流中的內容。 while ((content = br.readLine()) != null) { // 每當讀到來自服務器的數據之後,發送消息通知程序界面顯示該數據 Message msg = new Message(); msg.what = 0x123; msg.obj = content; handler.sendMessage(msg); } } catch (Exception e) { e.printStackTrace(); } } }
使用URL訪問網絡資源
URL對象代表統一資源定位器,它是指向互聯網”資源”的指針,資源可以是簡單的文件或目錄,也可以是對更復雜的對象的引用,URL可由協議名、主機、端口和資源組成 。
URL類提供了多個構造器用於創建URL對象,一旦獲得了URL對象之後,可以調用如下常用方法來訪問該URL對應的資源。
String getFile():獲取此URL的資源名
String getHost():獲取此URL的主機名
String getPath():獲取些URL的路徑部分
int getPort():獲取此URL的端口號
String getProtocol():獲取此URL的協議名稱
String getQuery():獲取此URL的查詢字符串部分
URLConnection openConnection():URL所引用遠程對象連接
InputStream openStream():打開與些URL的連接,並返回一個用於讀取該URL資源的InputStream。
例:使用URL讀取網絡資源:
URLTest.java
public class URLTest extends Activity { ImageView show; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); show = (ImageView) findViewById(R.id.show); // 定義一個URL對象 try { URL url = new URL(http://www.xxx.com/photo.png); // 打開該URL對應的資源的輸入流 InputStream is = url.openStream(); // 從InputStream中解析出圖片 Bitmap bitmap = BitmapFactory.decodeStream(is); // 使用ImageView顯示該圖片 show.setImageBitmap(bitmap); is.close(); // 再次打開URL對應的資源的輸入流 is = url.openStream(); // 打開手機文件對應的輸出流 OutputStream os = openFileOutput(crazyit.png , MODE_WORLD_READABLE); byte[] buff = new byte[1024]; int hasRead = 0; // 將URL對應的資源下載到本地 while((hasRead = is.read(buff)) > 0) { os.write(buff, 0 , hasRead); } is.close(); os.close(); } catch (Exception e) { e.printStackTrace(); } } }
2.1 使用URLConnection提交請求
通常創建一個和URL的連接,並發送請求。讀取此URL引用的資源需要如下幾個步驟 :
通過調用Url對象openConnection()方法創建URLConnection對象。
設置URLConnection的參數和普通請求屬性。
如果只是發送get方式請求,使用Connect方法建立和遠程資源之間的實際連接即可;如果需要發送post方式的請求需要獲取URlConnection實例對應的輸出流來發送請求參數。
遠程資源變為可用,程序可以訪問遠程資源的頭字段或通過輸入流讀取遠程資源的數據。
在建立和遠程資源的實際連接之前,可以通過如下方法來設置請求頭字段。
setAllowUserInteraction:設置該URLConnection的allowUserInteraction請求頭字段的值。
setDoInput:設置該URLConnection的doInput請求頭字段的值。
setDoOutput:設置該URLConnection的doOutput請求頭字段的值。
setIfModifiedSince:設置該URLConnection的ifModifiedSince請求頭字段的值。
setUseCaches:設置該URLConnection的useCaches請求頭字段的值
還可以使用如下方法來設置或增加通用頭字段。
setRequestProperty(String key,String value):設置該URLConnection的key請求頭字段的值為value。
addRequestProperty(String key,String value):為該URLConnection的key請求頭字段增加value值。
當遠程資源可用時,程序可以使用以下方法用於訪問頭字段和內容。
Object getContent():獲取該URLConnection的內容
String getHeaderField(String name):獲取指定響應頭字段的值
getInputStream():返回該URLConnection對應的輸入流,用於獲取URLConnection響應的內容。
getOutputStream():返回該URLConnection對應的輸出流,用於向URLConnection發送請求參數。
例:向Web站點發送GET、POST請求:
GetPostMain.java
public class GetPostMain extends Activity { Button get , post; EditText show; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); get = (Button) findViewById(R.id.get); post = (Button) findViewById(R.id.post); show = (EditText)findViewById(R.id.show); get.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String response = GetPostUtil .sendGet(http://127.0.0.1:8080/abc/a.jsp , null); show.setText(response); } }); post.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String response = GetPostUtil .sendPost(http://127.0.0.1:8080/abc/login.jsp , name=xxx&pass=123); show.setText(response); } }); } }
GetPostUtil.java
public class GetPostUtil { /** * 向指定URL發送GET方法的請求 * * @param url * 發送請求的URL * @param params * 請求參數,請求參數應該是name1=value1&name2=value2的形式。 * @return URL所代表遠程資源的響應 */ public static String sendGet(String url, String params) { String result = ; BufferedReader in = null; try { String urlName = url + ? + params; URL realUrl = new URL(urlName); // 打開和URL之間的連接 URLConnection conn = realUrl.openConnection(); // 設置通用的請求屬性 conn.setRequestProperty(accept, */*); conn.setRequestProperty(connection, Keep-Alive); conn.setRequestProperty(user-agent, Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)); // 建立實際的連接 conn.connect(); // 獲取所有響應頭字段 Map> map = conn.getHeaderFields(); // 遍歷所有的響應頭字段 for (String key : map.keySet()) { System.out.println(key + ---> + map.get(key)); } // 定義BufferedReader輸入流來讀取URL的響應 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += + line; } } catch (Exception e) { System.out.println(發送GET請求出現異常! + e); e.printStackTrace(); } // 使用finally塊來關閉輸入流 finally { try { if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } /** * 向指定URL發送POST方法的請求 * * @param url * 發送請求的URL * @param params * 請求參數,請求參數應該是name1=value1&name2=value2的形式。 * @return URL所代表遠程資源的響應 */ public static String sendPost(String url, String params) { PrintWriter out = null; BufferedReader in = null; String result = ; try { URL realUrl = new URL(url); // 打開和URL之間的連接 URLConnection conn = realUrl.openConnection(); // 設置通用的請求屬性 conn.setRequestProperty(accept, */*); conn.setRequestProperty(connection, Keep-Alive); conn.setRequestProperty(user-agent, Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)); // 發送POST請求必須設置如下兩行 conn.setDoOutput(true); conn.setDoInput(true); // 獲取URLConnection對象對應的輸出流 out = new PrintWriter(conn.getOutputStream()); // 發送請求參數 out.print(params); // flush輸出流的緩沖 out.flush(); // 定義BufferedReader輸入流來讀取URL的響應 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += + line; } } catch (Exception e) { System.out.println(發送POST請求出現異常! + e); e.printStackTrace(); } // 使用finally塊來關閉輸出流、輸入流 finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } }
使用HTTP訪問網絡
3.1 使用HttpURLConnection
URLConnection還有一個子類:HttpURLConnection,可以用於向指定網站發送GET請求、POST請求。它在URLConnection的基礎上提供了如下方法:
int getResponseCode():獲取服務器的響應代碼
String getResponseMessage():獲取服務器的響應信息
String getRequestMethod():獲取發送請求的方法
Void setRequestMethod(String method):設置發送請求的方法
例:多線程下載:
為了實現多線程,程序可按如下步驟進行:
創建URL對象
獲取指定URL對象所指象的資源大小(由getContentLength()方法實現),此處用到了HttpURLConnection類。
在本地磁盤上創建一個與網絡資源相同大小的空文件
計算每條線程應該下載網絡資源的哪個部分
依次創建、啟動多條線程來下載網絡資源的指定部分。
3.2 使用Apache HttpClient
HttpClient是一個增強版的HttpURLConnection,它是一個簡單的客戶端(並不是浏覽器),可以發送HTTP請求,接收HTTP響應,以及管理HTTP連接。但不會緩存服務器的響應,不能執行HTML頁面中嵌入的JavaScript代碼,也不會對頁面內容進行任何解析、處理。
Android已經成功地集成了HttpClient,可以直接在Android應用中使用HttpClient來訪問提交請求、接收響應。使用HttpClient的步驟如下:
創建HttpClient對象
如果需要發送GET請求,創建HttpGet對象,如果需要發送POST請求,創建HttpPost對象。
如果需要發送請求參數,可調用HttpGet、HttpPost共同的setParams(HttpParams params)方法來添加請求參數。
調用HttpClient對象的execute(HttpUriRequest request)發送請求,該方法返回一個HttpResponse。
調用HttpResponse的getAllHeader()、getHeaders(String name)等方法可獲取服務器的響應頭;調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包含了服務器的響應內容。程序可通過該對象獲取服務器的響應內容。
例:HttpClient訪問被保護的資源:
HttpClientTest.java
public class HttpClientTest extends Activity { Button get; Button login; EditText response; HttpClient httpClient; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 創建DefaultHttpClient對象 httpClient = new DefaultHttpClient(); get = (Button) findViewById(R.id.get); login = (Button) findViewById(R.id.login); response = (EditText) findViewById(R.id.response); get.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 創建一個HttpGet對象 HttpGet get = new HttpGet( http://127.0.0.1:8080/foo/secret.jsp); try { // 發送GET請求 HttpResponse httpResponse = httpClient.execute(get); HttpEntity entity = httpResponse.getEntity(); if (entity != null) { // 讀取服務器響應 BufferedReader br = new BufferedReader( new InputStreamReader(entity.getContent())); String line = null; response.setText(); while ((line = br.readLine()) != null) { // 使用response文本框顯示服務器響應 response.append(line + ); } } } catch (Exception e) { e.printStackTrace(); } } }); login.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final View loginDialog = getLayoutInflater().inflate( R.layout.login, null); new AlertDialog.Builder(HttpClientTest.this) .setTitle(登錄系統) .setView(loginDialog) .setPositiveButton(登錄, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String name = ((EditText) loginDialog .findViewById(R.id.name)).getText() .toString(); String pass = ((EditText) loginDialog .findViewById(R.id.pass)).getText() .toString(); HttpPost post = new HttpPost( http://127.0.0.1:8080/foo/login.jsp); // 如果傳遞參數個數比較多的話可以對傳遞的參數進行封裝 Listparams = new ArrayList (); params .add(new BasicNameValuePair(name, name)); params .add(new BasicNameValuePair(pass, pass)); try { // 設置請求參數 post.setEntity(new UrlEncodedFormEntity( params, HTTP.UTF_8)); // 發送POST請求 HttpResponse response = httpClient .execute(post); // 如果服務器成功地返回響應 if (response.getStatusLine() .getStatusCode() == 200) { String msg = EntityUtils .toString(response.getEntity()); // 提示登錄成功 Toast.makeText(HttpClientTest.this, msg, 5000).show(); } } catch (Exception e) { e.printStackTrace(); } } }).setNegativeButton(取消, null).show(); } }); } }
使用WebView視圖顯示網頁
4.1 使用WebView浏覽網頁
WebView的用法與普通的ImageView組件的用法基本相似,它提供了大量方法來執行浏覽器操作,例如如下常用方法。
void goBack():後退
void goForward():前進
void loadUrl(String url):加載指定的URL對應的網頁
boolean zoomIn():放大網頁
boolean zoomOut():縮小網頁
例:迷你浏覽器:
MiniBrowser.java
public class MiniBrowser extends Activity { EditText url; WebView show; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 獲取頁面中文本框、WebView組件 url = (EditText) findViewById(R.id.url); show = (WebView) findViewById(R.id.show); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_SEARCH) { String urlStr = url.getText().toString(); // 加載、並顯示urlStr對應的網頁 show.loadUrl(urlStr); return true; } return false; } }
Main.xml
4.2 使用WebView加載HTML代碼
利用WebView可以對HTML字符串進行解析、當成HTML頁面來顯示。 WebView提供了一個loadDataWithBaseURL(String baseUrl,String data,String mimeType,String encoding,String historyUrl)方法,該方法是對loadData(String data, data,String mimeType,String encoding)方法的增強,它不會產生亂碼。
例:使用WebView加載HTML:
在配置文件中加上訪問網絡的權限
ViewHtml.java
public class ViewHtml extends Activity { WebView show; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 獲取程序中的WebView組件 show = (WebView) findViewById(R.id.show); StringBuilder sb = new StringBuilder(); // 拼接一段HTML代碼 sb.append(); sb.append(); sb.append(
Android模擬器安裝APP出現INSTALL_FAILED_NO_MATCHING_ABIS錯誤解決方案當我們想在電腦的Android模擬器中安裝APP的
Android中所有控件都繼承自android.view.View,其中android.view.ViewGroup是View的一個重要子類,絕大部分的布局都繼承自Vie
Service簡介:Service 是Android的四大組件之一,一般用於沒有UI界面,長期執行的後台任務,即使程序退出時,後台任務還在執行。比如:音樂播放。Servi
1.概述 最近一直到在帶實習生,因為人比較多,所以很長一段時間沒有更新博客了,今天更新一篇雷達掃描附近好友效果,以後盡量每周更新一篇,先看一下效果: 2.實現 1、效果