繼承關系:
java.lang.Object
-java.net.URLConnection
-java.net.HttpURLConnection
HttpURLConnection繼承自URLConnection,可以使用HTTP協議發送接收網絡數據。可以接收發送提前不知道長度的數據。
其使用方式參考如下模式:
1.通過調用 URL.openConnection() 方法獲得一個 URLConnection,並將其轉為HttpURLConnection對象。
2.准備request請求,request的首要屬性是它的URI。request 頭還可以包括例如整數,首選內容類型,會話cookies等這些元數據。
3.也可以選擇上傳request body.(必須設定setDoOutput(true)),然後通過URLConnection.getOutputStream()返回的 OutputStream 發送出去。
4.讀取response。response header一般包含一些元數據,比如response body的內容類型和長度,修改時間,會話cookies。response body可以通過URLConnection.getInputStream()方法返回的InputStream中讀取。如果response沒有body。URLConnection.getInputStream()返回一個空的stream。
5.關閉連接,一旦response body被讀取完畢,HttpURLConnection應該通過disconnect()方法來關閉,從而釋放連接所持有的資源。
典型示例代碼:
URL url = new URL("http://www.Android.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
} finally {
urlConnection.disconnect();
}
代碼中並沒有步驟3.
通過上面的例子可以看出,我們可以通過URI來得到網絡上的圖片(例如https://www.linuxidc.com/img/bd_logo1.png)。並通過InputStream得到得到圖像數據,用BitmapFactory解析。最後在ImageView中繪制圖像。
注意:由於網絡操作耗時較大,不要放在主線程,這裡本例將其放在AsyncTask中來做。
示例代碼:
package com.example.hurlconnectionpic;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
public class MainActivity extends Activity {
ImageView iv;
Button bt;
String linuxidcImage = "http://www.linuxidc.com/img/bd_logo1.png";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.imageView1);
bt = (Button) findViewById(R.id.button1);
bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new BackGroundTast().execute(linuxidcImage);
}
});
}
class BackGroundTast extends AsyncTask<String, Void, Bitmap> {
Bitmap bitmap;
HttpURLConnection urlConnection;
InputStream in;
@Override
protected Bitmap doInBackground(String... params) {
try {
URL url = new URL(params[0]);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setDoInput(true);
urlConnection.connect();
in = urlConnection.getInputStream();
bitmap = BitmapFactory.decodeStream(in);
in.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
urlConnection.disconnect();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (result != null) {
iv.setImageBitmap(result);
}
}
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
}
}
Layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/hello_world"
/>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:text="GetImage" />
</RelativeLayout>
AndroidManifest.xml 別忘了添加權限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hurlconnectionpic"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET"/>
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
>
<activity
android:name="com.example.hurlconnectionpic.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
效果圖:
HttpURLConnection是Java的網絡庫的類,而HttpClient是 Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。
HttpClient相對HttpURLConnection功能更加完善易用,主要特點如下:
(1)實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
(2)支持自動轉向
(3)支持 HTTPS 協議
(4)支持代理服務器等
GET:
1. 創建 HttpClient 的實例
2. 創建某HttpGet。在創建時傳入待連接的地址
3. 調用HttpClient實例的 execute 方法來執行第二步中創建好的實例
4. 讀 response
5. 釋放連接。無論執行方法是否成功,都必須釋放連接
6. 對得到後的內容進行處理
POST:
1. 創建 HttpClient 的實例
2. 創建HttpPost對象。調用setParams(HetpParams params),setEntity(HttpEntity entity)方法來設置請求參數
3. 調用第一步中創建好的實例的 execute 方法來執行第二步中創建好的 method 實例
4. 讀 response,getAllHeaders(),getEntity()等
5. 釋放連接。無論執行方法是否成功,都必須釋放連接
6. 對得到後的內容進行處理
//Get 方式
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://targethost/homepage");
CloseableHttpResponse response1 = httpclient.execute(httpGet);
// 下面的HTTP連接一直被response持有,從而使response內容可以直接從網絡socket中傳輸。
//為了保證系統資源能夠正確釋放,用戶必須在finally區塊中調用CloseableHttpResponse.close()方法
//需要注意:如果response內容沒有完全接受,下面的連接是不能安全的復用的,連接將會關閉並被connection manager丟棄。
try {
System.out.println(response1.getStatusLine());
HttpEntity entity1 = response1.getEntity();
//確保response內容全被接受,使用
EntityUtils.consume(entity1);
} finally {
response1.close();
}
//Post 方式
HttpPost httpPost = new HttpPost("http://targethost/login");
List <NameValuePair> nvps = new ArrayList <NameValuePair>();
nvps.add(new BasicNameValuePair("username", "vip"));
nvps.add(new BasicNameValuePair("password", "secret"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
CloseableHttpResponse response2 = httpclient.execute(httpPost);
try {
System.out.println(response2.getStatusLine());
HttpEntity entity2 = response2.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity2);
} finally {
response2.close();
}
上面代碼展示了使用GET和POST兩種方式來完成HTTP會話過程。更多信息可以參考apache的網站上的資源:
HttpClient Tutorial (http://hc.apache.org/httpcomponents-client-4.4.x/tutorial/html/index.html)
HttpClient API文檔:http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/
因為Android中集成了HttpClient,所以我們可以直接使用HttpClient來進行網絡編程。使用HttpClient直接加載一個圖片實在是有點殺雞用牛刀,因為HttpClient還可以實現用戶登陸,維持和服務器之間的Session。不過為了簡單期間,我們還是實現一個如上面的下載圖片並顯示在ImageView中的例子,還是以Get為例。
HttpClient使用起來同樣是發送Request,接收response。
注:由於我的開發環境中Android自帶的是3.X的httpClient。上面講的例子是4.3的httpClient,API有所區別。如果需要使用最新的httpClient,可以自行下載jar包導入
下載:http://hc.apache.org/downloads.cgi 裡面有個HttpClient for Android 4.3.5 (GA) 版本。不過是源碼,自行編譯成Jar即可。
如果使用老版本的httpClient問題也不大,只是API不太一樣了,下面的示例代碼使用的是HttpClient for Android 4.3.5。
示例代碼:
使用HttpClients.createDefault();創建居然會報錯,使用HttpClients.custom().useSystemProperties().build();正常
見http://www.tuicool.com/articles/nyyaYne
package com.example.hurlconnectionpic;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
public class MainActivity extends Activity {
ImageView iv;
Button bt;
String linuxidcImage = "http://www.linuxidc.com/img/bd_logo1.png";
// HttpClient client=new DefaultHttpClient();
CloseableHttpClient httpclient = HttpClients.custom().useSystemProperties().build();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.imageView1);
bt = (Button) findViewById(R.id.button1);
bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new BackGroundTast().execute(linuxidcImage);
}
});
}
class BackGroundTast extends AsyncTask<String, Void, Bitmap> {
Bitmap bitmap;
HttpURLConnection urlConnection;
InputStream in;
CloseableHttpResponse response1;
@Override
protected Bitmap doInBackground(String... params) {
HttpGet get = new HttpGet(params[0]);
try {
response1 = httpclient.execute(get);
HttpEntity entity1 = response1.getEntity();
if (entity1 != null) {
bitmap = BitmapFactory.decodeStream(entity1.getContent());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
response1.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (result != null) {
iv.setImageBitmap(result);
}
}
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
}
}
代碼相對HttpURLConnection變化很小,效果圖和HttpURLConnection一致。
Volley 是 Google 推出的 Android 異步網絡請求框架和圖片加載框架。在 Google I/O 2013 大會上發布。它簡化了http操作,並且加入了對異步加載圖片的使用。Volley的實現中同樣提供了上面提到的HttpURLConnection和HttpClient可供選擇定制。官方推薦在API level = 9之前使用HttpClient,而之後使用HttpURLConnection。
具體使用的官方教程請參考:http://developer.android.com/training/volley/index.html
對於Volley的代碼實現解析請參考:
“http://www.codekk.com/open-source-project-analysis/detail/Android/grumoon/Volley 源碼解析”
雖然對於Volley來說,加載圖片輕而易舉,甚至它還提供了NetworkImageView這個自定義的ImageView來簡化這一操作:
//將Layout中的ImageView轉換為networkImageView
networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
//快速創建一個默認的RequestQueue(處理request的隊列,一般一個Application只需要維護一個單例,這裡簡化了代碼)
mQueue = Volley.newRequestQueue(this);
//創建一個ImageCache緩存,ImageCache是ImageLoader內的一個接口,需要自己去實現它
MyImageCache mImageCache = MyImageCache.instance();
//根據上面兩個實例創建一個ImageLoader,ImageLoader是Volley裡提供的簡化圖像加載的類
ImageLoader imageLoader = new ImageLoader(mQueue,mImageCache);
//設定NetworkImageView默認情況下和出錯情況下的圖片樣式
networkImageView.setDefaultImageResId(R.drawable.ic_launcher);
networkImageView.setErrorImageResId(R.drawable.ic_launcher);
//設定NetworkImageView的URL地址,和輔助的ImageLoader。
//調用完該語句後,會通過ImageLoader首先從ImageCache中找是否有緩存的圖片,如果有就顯示
//沒有就通過網絡下載圖片並加載
networkImageView.setImageUrl(linuxidcImage, imageLoader);
上面的ImageCache可以參考官方實現,摘要如下:
new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap>
cache = new LruCache<String, Bitmap>(20);
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
});
如果有一個問題就是其是在內存中做的緩存,當請求的圖片過大的時候,容易發生OOM問題,而且緩存也不可能做的過大,所以自己實現的時候可以實現一個Disk+內存雙重緩存的方式來做,做一個LruMemCache和LruDiskCache,在LruMemCache滿了的時候trim到LruDiskCache。get的時候先從LruMemCache找,找不到則從ruDiskCache中找,再找不到則從網絡下載,並加入到LruMemCache中。
說到底Volley並不是專門設計用來下載加載網絡圖片的。如果有大量圖片加載任務,可以考慮使用Android-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader) 它在內存緩存和硬盤緩存的管理做的更好。
Volley更擅長的是將一系列各種請求加入到異步網絡請求隊列。大大的提高了網絡請求的銷量和使用的便捷性,比如JsonReques。StringRequest。而不是通過HttpURLConnection,然後我們自己管理多線程,內存溢出這些問題。
使用中國天氣網的接口來獲取Json數據,然後解析天氣內容並顯示
接口地址:http://www.weather.com.cn/data/cityinfo/城市代碼.html
以北京為例,接口地址為:http://www.weather.com.cn/data/cityinfo/101010100.html
返回數據為:
{
"weatherinfo": {
"city": "北京", // 城市中文名
"cityid": "101010100", // 城市 ID
"temp1": "22℃", // ?
"temp2": "31℃", // ?
"weather": "陰轉晴", // 天氣
"img1": "n2.gif", // ? 天氣圖標編號
"img2": "d0.gif", // ? 天氣圖標編號
"ptime": "18:00" // 發布時間
}
}
實現思路:
1,界面顯示ListView。分別顯示不同城市的天氣狀況,每個Item只顯示城市名稱,高低氣溫,天氣狀況,發布時間。
2,刷新時通過Volley發送JsonRequest,並根據返回的Response更新天氣信息。
Q:如果做到比如5個城市發到隊列裡去請求,然後返回的數據異步更新ListView?
notifyDataSetChanged? 拿到具體的Item的View去更新?
notifyDataSetChanged等於是一個個刷新所有的View。算了,為了方便起見,就等所有的城市都更新完數據後,調用一次notifyDataSetChanged來更新所有的列表。
先上效果動畫:
代碼:
<uses-permission android:name="android.permission.INTERNET"/>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
>
</ListView>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="Refresh" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20sp"
android:layout_weight="1"
android:text="城市"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/Weather"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="20sp"
android:text="Weather"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/Low_Temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20sp"
android:text="Low_Temp" />
<TextView
android:id="@+id/to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--" />
<TextView
android:id="@+id/Top_Temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Low_Temp" />
<TextView
android:id="@+id/Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="1"
android:gravity="right"
android:paddingRight="20sp"
android:text="Time" />
</LinearLayout>
</LinearLayout>
package com.example.volleyweather;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
//
public class weatherAdapter extends BaseAdapter {
private List<Map<String, String>> weatherInfo;
private LayoutInflater mInflater;
public weatherAdapter(Context context,List<Map<String, String>> weatherInfo) {
super();
this.mInflater = LayoutInflater.from(context);
this.weatherInfo = weatherInfo;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return weatherInfo.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.weather_info, null);
holder.city = (TextView)convertView.findViewById(R.id.city);
holder.weather= (TextView)convertView.findViewById(R.id.Weather);
holder.lowTemp= (TextView)convertView.findViewById(R.id.Low_Temp);
holder.topTemp= (TextView)convertView.findViewById(R.id.Top_Temp);
holder.time= (TextView)convertView.findViewById(R.id.Time);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
holder.city.setText(weatherInfo.get(position).get("city"));
holder.weather.setText(weatherInfo.get(position).get("weather"));
holder.lowTemp.setText(weatherInfo.get(position).get("temp1"));
holder.topTemp.setText(weatherInfo.get(position).get("temp2"));
holder.time.setText(weatherInfo.get(position).get("ptime"));
return convertView;
}
final class ViewHolder{
public TextView city;
public TextView weather;
public TextView lowTemp;
public TextView topTemp;
public TextView time;
}
}
package com.example.volleyweather;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.json.JSONException;
import org.json.JSONObject;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
String Tag="Weather";
ListView weatherList;
Button refreshButton;
List<Map<String, String>> weatherInfo=new ArrayList<Map<String, String>>();
int[] cityId = { 101010100, 101030100, 101040100, 101050101, 101060801 };
private RequestQueue mQueue;
private weatherAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
weatherList = (ListView) findViewById(R.id.listView1);
refreshButton = (Button) findViewById(R.id.button1);
mQueue=Volley.newRequestQueue(this);
myAdapter=new weatherAdapter(this,weatherInfo);
weatherList.setAdapter(myAdapter);
refreshButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
fetchWeather(weatherInfo);
}
});
}
void fetchWeather(final List weatherInfo){
final AtomicInteger count=new AtomicInteger(0);
//Request weather with Volly,save the result into weatherInfo list
weatherInfo.clear();
for(int i=0;i<cityId.length;i++){
//make a jsonObjectRequest
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://www.weather.com.cn/data/cityinfo/"+cityId[i]+".html",
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject arg0) {
Log.e(Tag,"weather got!!");
//iterator of the String names in this object.
Iterator<String> it=arg0.keys();
while(it.hasNext()){
String key=it.next();//這個key對應weatherinfo
JSONObject weatherinfoObj=null;
try {
weatherinfoObj=arg0.getJSONObject(key);//對應weatherinfo的整個結構
} catch (JSONException e) {
e.printStackTrace();
}
if(weatherinfoObj!=null){
Iterator<String> weatherItems = weatherinfoObj.keys();//分別是city,cityid等。
Map<String, String> map = new HashMap<String, String>();
while(weatherItems.hasNext()){
String weatherItem=weatherItems.next();
String weatherContent;
try {
weatherContent=weatherinfoObj.getString(weatherItem);
map.put(weatherItem, weatherContent);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
weatherInfo.add(map);
}
}
count.addAndGet(1);
if(count.get()==cityId.length) {
//如果返回的response個數達到了請求的個數,更新listView
//Refresh the ListView
myAdapter.notifyDataSetChanged();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError arg0) {
}
});
mQueue.add(jsonObjectRequest);
}
}
不過這個網站有的時候刷新次數多了就不返回響應了,可能是網站加入了保護機制。
HttpComponents 的詳細介紹:請點這裡
HttpComponents 的下載地址:請點這裡