概述
我們?cè)陂_(kāi)發(fā)中經(jīng)常需要從服務(wù)器下載文件,下載的內(nèi)容可能有交換的信息,緩存的圖片,程序更新包等。我們使用URLConnection來(lái)實(shí)現(xiàn)下載。
先看幾行代碼:
String urlDownload = "";
urlDownload = "http://www.baidu.com/img/baidu_sylogo1.gif";
URL url = new URL(urlDownload );
// 打開(kāi)連接
URLConnection con = url.openConnection();
// 輸入流
InputStream is = con.getInputStream();
如上面的代碼所示,指定一個(gè)下載的目標(biāo)鏈接,我們上面指定了一個(gè)圖片地址。然后構(gòu)建一個(gè)URL對(duì)象,調(diào)用該對(duì)象的openConnection方法來(lái)建立一個(gè)數(shù)據(jù)通路(連接)。代碼的最后一行使用 con.getInputStream,拿到一個(gè)輸入流對(duì)象,通過(guò)這個(gè)流對(duì)象我們就可以讀取到這個(gè)文件的內(nèi)容了。下面要做的,就是讀取這個(gè)流,將流寫入我們的本地文件。
不過(guò)在這之前,我們還要說(shuō)下這個(gè)方法:
//獲得文件的長(zhǎng)度
int contentLength = con.getContentLength();
System.out.println("長(zhǎng)度 :"+contentLength);
獲得文件長(zhǎng)度的方法
ContentLength是不很熟啊。它是http協(xié)議里描述頭(head)部分的描述屬性之一。實(shí)際這里是發(fā)送了一個(gè)http請(qǐng)求,分析了返回(response)里數(shù)據(jù)內(nèi)容。
我們常常會(huì)把文件下載到手機(jī)的存儲(chǔ)卡里,所以還會(huì)用到獲得存儲(chǔ)卡路徑的方法:
獲得存儲(chǔ)卡路徑,構(gòu)成 保存文件的目標(biāo)路徑
String dirName = "";
dirName = Environment.getExternalStorageDirectory()+"/MyDownload/";
File f = new File(dirName);
if(!f.exists())
{
f.mkdir();
}
Environment.getExternalStorageDirectory() 方法會(huì)返回一個(gè)字符串,指示了存儲(chǔ)卡的路徑。我們拼接字符串出一個(gè)準(zhǔn)備存放下載文件的文件夾。并先判斷文件夾是是否存在,如果不存在,則新建一個(gè)文件夾。
做完了上面的準(zhǔn)備后,基本就能實(shí)現(xiàn)下載了。
完整代碼
下載前的準(zhǔn)備工作:
//要下載的文件路徑
String urlDownload = "";
//urlDownload = "http://192.168.3.39/text.txt%22;
urlDownload = "http://www.baidu.com/img/baidu_sylogo1.gif%22;
// 獲得存儲(chǔ)卡路徑,構(gòu)成 保存文件的目標(biāo)路徑
String dirName = "";
dirName = Environment.getExternalStorageDirectory()+"/MyDownload/";
File f = new File(dirName);
if(!f.exists())
{
f.mkdir();
}
下載的操作
//準(zhǔn)備拼接新的文件名(保存在存儲(chǔ)卡后的文件名)
String newFilename = _urlStr.substring(_urlStr.lastIndexOf("/")+1);
newFilename = _dirName + newFilename;
File file = new File(newFilename);
//如果目標(biāo)文件已經(jīng)存在,則刪除。產(chǎn)生覆蓋舊文件的效果
if(file.exists())
{
file.delete();
}
try {
// 構(gòu)造URL
URL url = new URL(_urlStr);
// 打開(kāi)連接
URLConnection con = url.openConnection();
//獲得文件的長(zhǎng)度
int contentLength = con.getContentLength();
System.out.println("長(zhǎng)度 :"+contentLength);
// 輸入流
InputStream is = con.getInputStream();
// 1K的數(shù)據(jù)緩沖
byte[] bs = new byte[1024];
// 讀取到的數(shù)據(jù)長(zhǎng)度
int len;
// 輸出的文件流
OutputStream os = new FileOutputStream(newFilename);
// 開(kāi)始讀取
while ((len = is.read(bs)) != -1) {
os.write(bs, 0, len);
}
// 完畢,關(guān)閉所有鏈接
os.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
進(jìn)階篇 - 增加進(jìn)度條提示下載進(jìn)度
我們先來(lái)看下進(jìn)度條
<ProgressBar
android:id="@+id/ProgressBar01"
style="?android:attr/progressBarStyleHorizontal"
android:layout_height="wrap_content"
android:visibility="visible"
android:max="100"
android:progress="1"
android:layout_width="200dp"/>
上面展示了一個(gè)水平的進(jìn)度條。max屬性指定了最大值,progress指示當(dāng)前的進(jìn)度位置。style指定了一個(gè)現(xiàn)實(shí)風(fēng)格。
“觀察者”模式
不得不說(shuō)的一個(gè)是設(shè)計(jì)模式里的“觀察者”模式。觀察者模式提供了一個(gè)“讓一個(gè)觀察者去觀察一個(gè)對(duì)象,當(dāng)觀察的目標(biāo)發(fā)生變化時(shí),通知給 訂閱了觀察結(jié)果的對(duì)象 ”。這句話純屬于個(gè)人理解。
它表達(dá)了幾個(gè)對(duì)象:
1.訂閱了“觀察結(jié)果的對(duì)象”,該對(duì)象會(huì)收到“觀察者”的通知。
2.觀察者對(duì)象,一個(gè)緊盯這 目標(biāo)對(duì)象 的對(duì)象。它負(fù)責(zé)將 觀察的目標(biāo) 的變化 通知給 “訂閱者”
3.被觀察的目標(biāo),會(huì)發(fā)生變化的目標(biāo)對(duì)象,它的變化及時(shí)的都被觀察者所知。
在我們的下載時(shí)我們的幾個(gè)對(duì)象是
1.進(jìn)度條,是訂閱者,它接受觀察者對(duì)象的消息,來(lái)顯示自己的進(jìn)度條位置。
2.觀察者,是一個(gè)handler對(duì)象。該對(duì)象適合在線程間傳遞消息。我們就用它傳遞消息的特點(diǎn),并且該對(duì)象屬于android平臺(tái)核心框架,和主界面的消息循環(huán)有聯(lián)系。
3.被觀察的目標(biāo)就是下載的過(guò)程了。這個(gè)過(guò)程中下載文件的進(jìn)度。
我們分別實(shí)現(xiàn)它
private Handler myHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
//獲得進(jìn)度,該進(jìn)度由實(shí)際的操作進(jìn)行通知。這里負(fù)責(zé)對(duì)通知進(jìn)行處理
int progress = msg.arg1;
//設(shè)置進(jìn)度條的當(dāng)前位置
_ProgressBar01.setProgress(progress);
if(progress == 100)
{
Toast.makeText(getApplicationContext(), "下載成功", 0).show();
}
};
};
我們構(gòu)建一個(gè)myHandler 對(duì)象,并重載了它的handleMessage方法,該方法會(huì)捕獲(收到)所有發(fā)送給myHandler 的消息。我們接收消息,并根據(jù)消息攜帶的信息arg1作為當(dāng)前的進(jìn)度表示。
下面我們將這個(gè)myHandler 傳遞給下載的線程
new FileDownloader(urlDownload,dirName,myHandler).start();
如上面這行代碼所示,F(xiàn)ileDownloader對(duì)象是個(gè)下載器對(duì)象,它負(fù)責(zé)下載文件,同時(shí)他和觀察者myHandler關(guān)聯(lián)。有了消息,它就告訴這個(gè)觀察者。觀察者收到消息,通知給訂閱了消息的對(duì)象(本文為進(jìn)度條)。
下面看下如何進(jìn)行下載進(jìn)程的:
// 構(gòu)造URL
URL url = new URL(_urlStr);
// 打開(kāi)連接
URLConnection con = url.openConnection();
//獲得文件的長(zhǎng)度
int contentLength = con.getContentLength();
System.out.println("長(zhǎng)度 :"+contentLength);
// 輸入流
InputStream is = con.getInputStream();
int hasRead = 0;//已經(jīng)讀取了多少
int progress = 0;
// 1K的數(shù)據(jù)緩沖
byte[] bs = new byte[128];
// 讀取到的數(shù)據(jù)長(zhǎng)度
int len;
// 輸出的文件流
OutputStream os = new FileOutputStream(newFilename);
// 開(kāi)始讀取
while ((len = is.read(bs)) != -1) {
os.write(bs, 0, len);
//記錄完成了的多少
hasRead +=len;
progress = (int)((double)hasRead/(double)contentLength * 100);//完成的百分比
//發(fā)送通知
Message msg = _myHandler.obtainMessage();
msg.arg1 = progress;
msg.sendToTarget();
Thread.sleep(500);//故意延遲,不然進(jìn)度條跑的太快看不清楚
}
// 完畢,關(guān)閉所有鏈接
os.close();
is.close();
我們記錄我們當(dāng)前從服務(wù)器讀了多少字節(jié),又知道總共需要下載多少字節(jié)。計(jì)算后,得到一個(gè)完成了多少的百分比。將這個(gè)百分比通知給 觀察者。