編輯:關於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 ArrayList socketList
= 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);
// 如果傳遞參數個數比較多的話可以對傳遞的參數進行封裝
List params = 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出現INSTALL_FAILED_NO_MATCHING_ABIS錯誤解決方案當我們想在電腦的Android模擬器中安裝APP的
Android控件的繼承關系
Android中所有控件都繼承自android.view.View,其中android.view.ViewGroup是View的一個重要子類,絕大部分的布局都繼承自Vie
Android 四大組件之Service 的生命周期和使用
Service簡介:Service 是Android的四大組件之一,一般用於沒有UI界面,長期執行的後台任務,即使程序退出時,後台任務還在執行。比如:音樂播放。Servi
Android仿微信、QQ附近好友雷達掃描效果
1.概述 最近一直到在帶實習生,因為人比較多,所以很長一段時間沒有更新博客了,今天更新一篇雷達掃描附近好友效果,以後盡量每周更新一篇,先看一下效果: 2.實現 1、效果