android基礎--網絡通信(上)

網絡編程

URL類及相關API
URL,統一資源定位符,用於標識網絡資源的位置。

URL構造函數
URL(String spec),根據 String 表示形式創建 URL 對象。
URL(String protocol, String host, int port, String file),根據指定 protocol、host、port 號和 file 創建 URL 對象。
URL(String protocol, String host, String file),根據指定的 protocol 名稱、host 名稱和 file 名稱創建 URL。

常用API
Object getContent(),獲取此 URL 的內容。
Object getContent(Class[] classes),獲取此 URL 的內容。
int getDefaultPort(),獲取與此 URL 關聯協議的默認端口號。
String getFile(),獲取此 URL 的文件名。
String getHost(),獲取此 URL 的主機名(如果適用)。
String getPath(),獲取此 URL 的路徑部分。
int getPort(),獲取此 URL 的端口號。
String getProtocol(),獲取此 URL 的協議名稱。
String getQuery(),獲取此 URL 的查詢部分。
String getRef(),獲取此 URL 的錨點(也稱爲“引用”)。

打開到網絡資源的連接 
URLConnection openConnection(),返回一個URLConnection 對象,它表示到URL所引用的遠程對象的連接。

URLConnection類及相關API
URLConnection,代表一個URL連接。
獲取連接內容的相關方法
InputStream getInputStream(),返回從此打開的連接讀取的輸入流。
OutputStream getOutputStream(),返回寫入到此連接的輸出流。

設置參數的相關方法
void setIfModifiedSince(long ifmodifiedsince),將此URLConnection 的 ifModifiedSince 字段的值設置爲指定的值。
void setConnectTimeout(int timeout),設置一個指定的超時值(以毫秒爲單位),該值將在打開到此 URLConnection 引用的資源的通信鏈接時使用。
void setRequestProperty(String key, String value),設置一般請求屬性。 

獲取頭信息的相關方法:
String getHeaderField(int n),返回第 n 個頭字段的值。
String getHeaderField(String name),返回指定的頭字段的值。
String getContentEncoding(),返回 content-encoding 頭字段的值。
int getContentLength(),返回 content-length 頭字段的值。
String getContentType(),返回 content-type 頭字段的值。
long getDate(),返回 date 頭字段的值。
long getExpiration(),返回 expires 頭字段的值。
long getIfModifiedSince(),返回此對象的 ifModifiedSince 字段的值。
long getLastModified(),返回 last-modified 頭字段的值。

HttpURLConnection類
面向Http協議的URLConnection。
繼承自URLConnection,新增或複寫的方法有:
String getHeaderField(int n),返回 nth 頭字段的值。
long getHeaderFieldDate(String name, long Default),返回解析爲日期的指定字段的值。
String getHeaderFieldKey(int n),返回 nth 頭字段的鍵。
String getRequestMethod(),獲取請求方法。
int getResponseCode(),從 HTTP 響應消息獲取狀態碼。
String getResponseMessage(),獲取與來自服務器的響應代碼一起返回的 HTTP 響應消息(如果有)。
void setRequestMethod(String method),設置 URL 請求的方法, GET POST HEAD OPTIONS PUT DELETE TRACE 以上方法之一是合法的,具體取決於協議的限制。

BitmapFactory
工具類,裏面都是靜態方法,可以根據傳入的參數生成bitmap圖片。
static Bitmap decodeByteArray(byte[] data, int offset, int length)
static Bitmap decodeFile(String pathName)
static Bitmap decodeResource(Resources res, int id)
static Bitmap decodeStream(InputStream is) 
--上述方法還有加BitmapFactory.Options opts參數的重載形式。這個類在後面android多媒體中還會用,到時有詳細用法說明。      

注:訪問互聯網的android應用,都必須加上訪問互聯網的權限android.permission.INTERNET,清單文件中是
<uses-permission android:name="android.permission.INTERNET"/>

利用上面的API可以實現服務端圖片或文本數據獲取並顯示在android客戶端,但是因爲有以下2個問題,導致android網絡編程沒那麼簡單:
1. 網絡在主線程上的異常:android.os.NetworkOnMainThreadException
android4.0開始,谷歌更加註重UI界面運行的流暢性,強制要求訪問網絡的操作不能在主線程進行,只能在子線程進程。

2. 只有創建UI界面的那個線程才能修改UI:Only the original thread that created a view hierarch can touch its views。
android中創建UI界面的是main主線程。
子線程修改UI,系統會驗證當前線程是不是主線程,如果不是主線程,就會終止運行。

這個問題的演示代碼如下:

import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;

public class MainActivity extends Activity {
	private TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        tv = (TextView) findViewById(R.id.tv);
       //子線程可以修改UI,但系統會驗證修改UI的線程是不是主線程,如果不是主線程,就會終止運行。

       new Thread(){
    	   public void run(){  		   
    	   //tv.setText放在sleep()之前是可以看到子線程對UI的更新的,子線程只有在修改了UI纔會被系統檢測到。
    	   //tv.setText放在sleep()之後,因爲線程運行太快了,看不到界面更新效果。
    	   //如果用debug調試程序運行,可以看到,不管tv.setText()放在哪,都是在執行後程序就終止了。實際運行可能cpu運行時機會有不同。
  		   int i;
		   for(i=0; i<10; i++){
			   tv.setText("子線程 ");
			   tv.setText("hehe");//最終顯示結果也是hehe
			   //子線程中不能執行Toast,會報java.lang.RuntimeException:can't create handler inside thread
			   //that has not called Looper.prepare()。
			   //Toast.makeText(MainActivity.this, "hhhhhh", Toast.LENGTH_LONG);
		   }
    		try{
				this.sleep(10000);
			} catch (InterruptedException e) {
				System.out.println("888888888888888888");
				e.printStackTrace();
			}  		   
            System.out.println("i="+i+".....");//i=10    		     		   
    	   };   	   
       }.start();
    }   
}
爲了解決第1個問題,將上述URLConnetion相關的代碼放到一個子線程中,用new Thread()內部類的方式實現;
爲了解決第2個問題,就需要用到Handler消息處理機制,步驟如下:

1)、在主線程中創建handler
private Handler handler = new Handler(){
   //接收消息並處理消息
   @Override
 public void handleMessage(Message msg) {
    super.handleMessage(msg);
 }  
    };
2)、在子線程中使用handler的引用,發送消息給主線程
   Message msg = new Message();
   msg.obj = bm;
   handler.sendMessage(msg);
3)、在主線程中修改UI
public void handleMessage(Message msg) {
  super.handleMessage(msg);
  Bitmap bm = (Bitmap) msg.obj;
  iv.setImageBitmap(bm);
 }
下面是獲取網絡上圖片的實現代碼:
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {

	protected static final int SUCCESS = 0;
	protected static final int FAILED = 1;
	protected static final int ERROR = 2;
	private EditText et_path;
	private ImageView iv;
	//1、創建handler
	private Handler handler = new Handler(){
		
		//接收消息並處理消息
		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);
			switch(msg.what){
			case SUCCESS:
				Bitmap bm = (Bitmap) msg.obj;				
				iv.setImageBitmap(bm);
				break;
			case FAILED:
				Toast.makeText(MainActivity.this, (String)msg.obj, Toast.LENGTH_LONG).show();
				break;
			case ERROR:
				Toast.makeText(MainActivity.this, (String)msg.obj, Toast.LENGTH_LONG).show();
				break;
			}
		}		
	};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        et_path= (EditText) findViewById(R.id.et_path);
        iv = (ImageView) findViewById(R.id.iv);
          
    }

   public void click(View view){
	   final String path = et_path.getText().toString().trim();
	   if(TextUtils.isEmpty(path)){
		   Toast.makeText(this, "請輸入圖片的網絡路徑", 0).show();
		   return;
	   }else{
		   new Thread(){
			   public void run() {				   
				   try {
					   //從網絡上下載圖片,並顯示在ImageView裏
					   //1、創建URL,打開一個HTTP的連接;
					 
						URL url = new URL(path);
						HttpURLConnection conn = (HttpURLConnection) url.openConnection();
					    
					   //2、設置請求頭信息:GET(GET、POST)
						conn.setRequestMethod("GET");//GET要大寫,否則會報錯
						conn.setConnectTimeout(5000);
					   //3、接收服務器端返回的響應數據,響應碼:200 ok,404沒有找到資源 ,503服務器端內部錯誤
			              int code = conn.getResponseCode();
			              if(code == 200){
			            	  InputStream is =  conn.getInputStream();
		       	      //4、把接收的二進制數據轉換成圖片
			            	Bitmap bm =  BitmapFactory.decodeStream(is);
	
			            	//2、得到handler的引用
			            	Message msg = new Message();
			            	msg.what=SUCCESS;
			            	msg.obj = bm;
			            	//3、發送消息給主線程
			            	handler.sendMessage(msg);
			            	
			              }else{
			            	  //Toast.makeText(MainActivity.this, "服務器返回數據失敗", 0).show();//子線程中不能寫Toast,應用會終止。
			                  Message msg=new Message();
			                  msg.what=FAILED;
			                  msg.obj="服務器返回數據失敗!";
			                  handler.sendMessage(msg);
			              }												
					   } catch (Exception e) {
						    Message msg = Message.obtain();
							msg.what = ERROR;
							msg.obj = "網絡連接失敗";
							handler.sendMessage(msg);
							e.printStackTrace();
						}				   
			   };
		   }.start();
	   }
   }    
}
獲取網絡上html文件的實現代碼:
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import com.cx.utils.StreamTools;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

	protected static final int SUCCESS = 0;
	protected static final int FAILED = 1;
	protected static final int ERROR = 2;
	private EditText et_path;
	private TextView tv;
	//1、創建handler
	private Handler handler = new Handler(){
		public void handleMessage(android.os.Message msg) {
			String result = (String) msg.obj;
			
			switch (msg.what) {
			case SUCCESS:
				tv.setText(result);
				break;

			case FAILED:
				Toast.makeText(MainActivity.this, result, 0).show();
				break;
			case ERROR:
				Toast.makeText(MainActivity.this, result, 0).show();
				break;
			}
			
		};
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		et_path = (EditText) findViewById(R.id.et_path);
		tv = (TextView) findViewById(R.id.tv);
	}
	
	/**
	 * 點擊查看按鈕,獲取網絡上的HTML數據並顯示到TextView裏
	 * @param view
	 */
	public void click(View view){
		final String path = et_path.getText().toString().trim();
		if(TextUtils.isEmpty(path)){
			Toast.makeText(this, "請輸入一個頁面地址", 0).show();
			return;
		}else
		{
			//從網絡上獲取HTML的數據,並顯示在TextView裏
			new Thread(){
				public void run() {
					try {
						URL url = new URL(path);
						HttpURLConnection conn = (HttpURLConnection) url.openConnection();
						
						conn.setRequestMethod("GET");
						conn.setConnectTimeout(5000);
						
						int code = conn.getResponseCode();
						if(code == 200){
							InputStream is = conn.getInputStream();
							//獲取網絡上的HTML頁面的數據
							String result = StreamTools.readStream(is);
							//2、使用handler的引用向主線程發送消息
							Message msg = Message.obtain();
							msg.what = SUCCESS;
							msg.obj = result;
							handler.sendMessage(msg);
							
						}else{
							Message msg = Message.obtain();
							msg.what = FAILED;
							msg.obj = "服務器返回數據失敗";
							handler.sendMessage(msg);
						
						}
					} catch (Exception e) {
						Message msg = Message.obtain();
						msg.what = ERROR;
						msg.obj = "網絡連接失敗";
						handler.sendMessage(msg);
						
						e.printStackTrace();
					}
									
				};				
			}.start();			
		}
	}	
}
其中用到的將輸入流轉換爲字符串的工具類如下:使用ByteArrayOutputStream來實現轉換
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class StreamTools {	
	/**
	 * 把輸入流轉換成一個字符串
	 * 思路:使用ByteArrayOutputStream作爲中介,先將輸入流中的數據讀到字節數組輸出流中,再將字節數組輸出流轉換爲字節數組,再轉換成字符串。
	 * @param is 輸入流
	 * @return 返回一個字符串,如果轉換失敗就返回一個空字符
	 */
	public static String readStream(InputStream is){		
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			
			byte[] buffer = new byte[1024];
			int len = -1;
			while((len = is.read(buffer)) != -1){
				 baos.write(buffer, 0, len);
				}
			is.close();
			return new String(baos.toByteArray());
		} catch (Exception e) {
			return "";
		}			
	}
}

Handler消息處理機制
所有使用UI界面的操作系統,後臺都運行着一個死循環,在不停的監聽和接收用戶發出的指令,一旦接收指令就立即執行。

Handler中的死循環是一個輪詢器looper,looper內部維護一個消息隊列。

Looper Message  Handler三者之間的關係:
當我們的Android應用程序的進程一創建的時候,系統就給這個進程提供了一個Looper,Looper是一個死循環,它內部維護這一個消息隊列,Looper不停地從消息隊列中取消息(Message),取到消息就發送給了Handler,最後Handler根據接收到的消息去修改UI。

Handler類常用API
void dispatchMessage(Message msg),Handle system messages here.
Message obtainMessage(),Returns a new Message from the global message pool.
Message obtainMessage(int what),Same as obtainMessage(), except that it also sets the what member of the returned Message.
---obtainMessage還有多種重載形式。
void removeMessages(int what),Remove any pending posts of messages with code 'what' that are in the message queue.
boolean sendEmptyMessage(int what),Sends a Message containing only the what value.
boolean sendMessage(Message msg),Pushes a message onto the end of the message queue after all pending messages before the current time.
boolean sendMessageAtTime(Message msg, long uptimeMillis),Enqueue a message into the message queue after all pending messages before the absolute time (in milliseconds) uptimeMillis.
boolean sendMessageDelayed(Message msg, long delayMillis),Enqueue a message into the message queue after all pending messages before (current time + delayMillis).

Message類常用屬性及API
Message.obtain(),從消息池中取消息。obtain()方法有多種重載形式。
Handler getTarget(),Retrieve the Handler implementation that will receive this message.
void setTarget(Handler target) ,設置消息處理者handler。
Message常用屬性
int arg1,arg1 and arg2 are lower-cost alternatives to using setData() if you only need to store a few integer values.
int arg2,arg1 and arg2 are lower-cost alternatives to using setData() if you only need to store a few integer values.
Object obj,消息內容/數據。
Messenger replyTo,Optional Messenger where replies to this message can be sent.
int what,消息ID。

消息處理常用的API
* public final void runOnUiThread(Runnable action),是Activity類中的方法,在UI線程上運行action中的代碼,如果當前線程是UI線程,action中的代碼會立即被執行,如果當前線程不是UI線程,action被放到UI線程的事件隊列中。
---這個方法的原理是使用了線程合併,線程合併多用在主線程想要終止子線程或主線程想要自己執行子線程中代碼的情況,runOnUiThread()方法就是後一種情況。
* boolean postDelayed(Runnable r, long delayMillis),Handler中的方法,將r線程要運行的代碼添加到消息隊列中,並在延遲delayMillis時間後執行,返回值表示r是否被成功被成功添加到消息隊列中。
* final boolean postAtTime(Runnable r,long uptimeMillis),將r線程要執行的操作放到消息隊列中,並在指定的uptimMillis時間執行這些操作,uptimeMillis是以應用啓動的時間爲起點的,不是系統時間,可用在定時考試系統、定時遊戲任務系統等,防止用戶通過修改系統時間進行作弊。

向服務端提交數據
在用戶登錄、文件上傳等應用中,需要通過網絡向服務端提交數據。向服務端提交數據有2種方式:GET方式和POST方式。GET方式提交數據時每次提交的數據量有限制,不能超過4K(window下實際不能超過1K),POST方式沒有限制。

一般方法提交數據:
1. GET方式
使用GET方式向服務器端提交數據,就是把提交的參數附加到url的後面,如:
http://192.168.12.28:8080/web/servlet/LoginServlet?username=232&password=sdfsd.

以這個字符串爲參數創建URL對象,調用openConnection()方法,就將數據以GET方式傳遞給了服務端。

2. POST方式
不管是GET還是POST方式提交數據,服務端接收並處理提交數據的方式是不變的,都可以用request.getParamete()方法獲取數據。

public void login(View view){
	final String qq = et_qq.getText().toString().trim();
	final String pwd = et_pwd.getText().toString().trim();
	//final String urlStr = "http://192.168.1.2:8080/bookStore/ClientController?op=login";
	final String urlStr="http://192.168.12.87:8080/Test/servlet/LoginController";
	
	if(TextUtils.isEmpty(qq) || TextUtils.isEmpty(pwd)){
		Toast.makeText(this, "請輸入qq號碼或者密碼", 0).show();
		return;
	}else{
		final String data = "username="+URLEncoder.encode(qq)+"&password="+URLEncoder.encode(pwd);
		final String path = urlStr ;
		new Thread(){
			public void run() {
				try {
					URL url = new URL(path);
					HttpURLConnection connection = (HttpURLConnection) url.openConnection();

					//設置請求方式爲POST
					connection.setRequestMethod("POST");
					connection.setConnectTimeout(5000);
					//設置表單類型及數據長度
					connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
					connection.setRequestProperty("Content-Length", data.length()+"");
					//設置連接允許向服務器寫數據
					connection.setDoOutput(true);
					connection.getOutputStream().write(data.getBytes());
					
					int code = connection.getResponseCode();
					if(code == 200){
						InputStream is = connection.getInputStream();
						String result = StreamTools.readStream(is);
						
						Message msg= Message.obtain();
						msg.obj = result;
						handler.sendMessage(msg);
					}else
					{
						//
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				
			};
		}.start();
	}
}



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