編輯:關於Android編程
1、寫在前面:
雖然demo中程序框架已搭建完成,但是由於筆者時間原因,暫時只完成了核心部分:多線程下載的部分,其他數據庫、服務通知、暫停部分還未添加到項目中。
2、相關知識點:
(1)Java線程及停止線程的方式
(2)Java RandomAccessFile文件操作
(3)HttpURLConnection相關range字段的配置
(4)Sqlite同步操作
2、核心思想:
(1)通過HttpURLConnection判斷服務器是否支持斷電續傳:
<1>否->直接開啟普通的多線程下載(遇到斷網等情況便會重新下載)
<2>是->開啟普通的多線程下載,但是每個線程都含有自己的下載進度信息,以便斷網或用戶暫停開始重新下載重新開啟下載。筆者在針對不同的下載尺寸智能的分配不同的線程數量去下載資源,通過設置緩沖區大小來提高下載速度。
3、核心技術:
(1)HttpURLConnection的配置
(2)RandomAccessFile隨機文件的讀取以及緩沖區的設置
(3)線程的暫停與啟動
4、分析結果:
(1)將功能劃分為三大部分:下載器(統一的外部接口)、存儲器(內部的存儲實現)、通知服務(用戶交互部分)。
(2)項目結構:
5、核心代碼:
package com.jx.downloader; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import android.content.Context; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import com.jx.dbhelper.DownloadRecordDB; import com.jx.model.DownloadModel; /** * 自定義下載器: 1、根據即將下載的內容大小智能的分配下載線程數量,每個線程通過Downloader攜帶自身線程的下載信息(下載起點、終點、線程名稱) * 2、如果服務器支持斷電續傳則開啟數據庫 * ,在下載處於暫停的狀態時(導致下載暫停的原因可能是手動暫停或者網絡不佳),自動保存下載信息到數據庫,在取消下載的時候自動清空數據庫信息, * 重新開始下載的時候,讀取內容重新下載(一般情況本地存儲變量還未被回收,不必從數據庫重新讀取) 2、使用RandomAccessFile存儲下載的內容 * * @author J_X 2016年3月19日09:58:00 */ public class JX_Downloader { /** * 0~3M的下載范圍默認開啟1個線程 */ private final static long BELOW_Three_M_SIZE = 5 * 1024 * 1024; /** * 3~6M的下載范圍默認開啟2個線程 */ private final static long BELOW_SIX_M_SIZE = 6 * 1024 * 1024; /** * 10~18M的下載范圍默認開啟4個線程 */ private final static long BELOW_EIGHTEEN_M_SIZE = 18 * 1024 * 1024; /** * 線程是否暫停的標示 */ private static volatile boolean isStopDownloading = false; /** * 下載器的標示id,以為可以創建多個下載器 */ private long downloaderID; /** * 需要下載的資源鏈接 */ private String resourceUrl; /** * 下載鏈接的遵守URL協議的對象 */ private URL resourceURL; /** * 需要下載的資源的總字節數 */ private long resourceSize; /** * 需要的下載線程的總數量 */ private int totalTreadNum; /** * 資源所在服務器是否支持斷點續傳功能 */ private boolean isSupportLoadingAndSaving; /** * 下載資源存儲的數據庫 */ private DownloadRecordDB downlaodDB; /** * 文件存儲目錄 */ private String savaFileName; /** * 構造器暫無子類所以沒有提供默認構造函數 * * @param context * 上下文 * @param downloadUrl * 將要被下載的鏈接 * @param saveFileName * 帶後綴的下載的文件存儲名稱 */ public JX_Downloader(Context context, String downloadUrl, String saveFileName) throws MalformedURLException { // TODO Auto-generated constructor stub resourceUrl = downloadUrl; this.resourceURL = new URL(resourceUrl); this.downlaodDB = new DownloadRecordDB(context); // 如果用戶沒有設置下載後綴,提供默認存儲文件夾 if (TextUtils.isEmpty(saveFileName)) { this.savaFileName = "JX_DownLoader.txt"; } else { this.savaFileName = saveFileName; } } /** * 開啟下載任務,對外提供方便下載方法 */ public void startDownload() { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { readyDownload(); } catch (IOException e) { // TODO Auto-generated catch block Log.e("Debug", "downloading fail!!!"); } } }).start(); } /** * 核心下載程序(采用類似門面模式方法處理,方法有嚴格的執行順序要求) * * @throws IOException * */ private void readyDownload() throws IOException { // 判斷當前連接所在服務器是否支持斷點續傳下載 // this.isSupportLoadingAndSaving = false; // /** 不支持的給出友好提示,只進行多線程下載的任務 */ // if (!isSupportLoadingAndSaving) { // Log.e("Debug", "Server don't support pause_save download!"); // } else { // /** 支持的情況:1、開啟數據庫存儲各個線程進度 */ // } /* 多線程下載流程 */ // 1、計算需要下載的資源大小 HttpURLConnection httpURLConnection = settingRequestHttp(null); if (httpURLConnection.getResponseCode() == 200) { // TODO: 需要進一步優化網絡 Log.e("Debug", "Coonected sucessfully"); } resourceSize = httpURLConnection.getContentLength(); if (resourceSize <= 0) { Log.e("Debug", "unkown file Length and return"); return; } else { Log.e("Debug", "file length: " + resourceSize + "bytes"); } // 2、按照用戶設置或者資源大小智能的設置下載線程總數量 int tempThreadNum = setTotalTreadNum(resourceSize); // 3、根據分配的線程數量,來將資源“等分”,並設置header ArrayList下載器使用:downloadArray = new ArrayList (); if (isSupportLoadingAndSaving) { // 從數據庫讀取 downloadArray = downlaodDB.getAllInfo(); } else { // 從本地方法讀取 downloadArray = initResoureSize(tempThreadNum, resourceSize); } // 4、劃分線程進行下載 for (int i = 0; i < downloadArray.size(); ++i) { DownloadModel tempModel = downloadArray.get(i); MyRunnable runnable = new MyRunnable(tempModel); Thread thread = new Thread(runnable); thread.start(); } } /** * 配置Http信息,准備開始下載數據 * * @throws IOException */ private HttpURLConnection settingRequestHttp(DownloadModel model) throws IOException { HttpURLConnection coon = (HttpURLConnection) this.resourceURL .openConnection(); coon.setConnectTimeout(3 * 1000); coon.setRequestMethod("GET"); coon.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"); coon.setRequestProperty("Accept-Language", "zh-CN"); coon.setRequestProperty("Referer", resourceUrl); coon.setRequestProperty("Charset", "UTF-8"); coon.setRequestProperty( "User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); coon.setRequestProperty("Connection", "Keep-Alive"); // 斷點續傳的核心代碼,設置下載區間,注意是bytes=在這吃過虧 if (model != null) { coon.setRequestProperty( "Range", "bytes=" + model.getDownloadedLength() + "-" + model.getDownloadLengthSum()); } return coon; } /** * 多線程下載共享的run方法 * * @author Administrator * */ private class MyRunnable implements Runnable { private DownloadModel rModel; public MyRunnable(DownloadModel model) { this.rModel = model; } @Override public void run() { try { Log.i("Debug", rModel.getThreadName()); HttpURLConnection coon = settingRequestHttp(rModel); if (coon.getResponseCode() == 200) { Log.e("Debug", "Coonected sucessfully"); } resourceSize = coon.getContentLength(); if (resourceSize <= 0) { Log.e("Debug", "unkown file Length and return"); return; } else { readResourceAndSave(coon, rModel); } } catch (IOException e) { e.printStackTrace(); } } } /** * 將網絡數據從網上讀取下來 * * @throws IOException */ private void readResourceAndSave(HttpURLConnection coon, DownloadModel model) throws IOException { //創建文件夾 File savDir = Environment.getExternalStorageDirectory(); File file = null; if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { if (!savDir.exists()) { savDir.mkdirs(); } file = new File(savDir, this.savaFileName); } RandomAccessFile rFile = new RandomAccessFile(file, "rwd"); // 跳轉到當前線程對文件的讀寫起始位置 rFile.seek(model.getDownloadedLength()); InputStream finput = coon.getInputStream(); if (finput != null) { Log.e("Debug", "finput=>" + finput.toString()); } else { Log.e("Debug", "finput is null and return"); } Log.e("Debug", "rFile.getFilePointer=>" + rFile.getFilePointer()); BufferedInputStream bInput = new BufferedInputStream(finput); // 設置3KB的緩沖區,加快下載速度,這裡後期改成按字符或者整行讀取方式優化 int size = 3 * 1024; byte[] readBytes = new byte[size]; int readNum = 0; long sum = 0; // 當還有數據可讀且線程沒有暫停 long needReadNum = model.getDownloadLengthSum() - model.getDownloadedLength(); while ((readNum = bInput.read(readBytes, 0, size)) > 0 && sum < needReadNum && !isStopDownloading) { sum += readNum; // 如果請求的尺寸下一次讀取即將超過總需求數量時,修正讀取內容的大小保證不多讀取 if ((sum + size) > needReadNum) { size = (int) (needReadNum - sum); } // 寫入到文件中 rFile.write(readBytes, 0, readNum); } Log.e("Debug", model.getThreadName() + "finish downloading:" + rFile.length() + "-bytes"); rFile.close(); finput.close(); } /** * 計算需要下載的資源大小 * * @param url * 待下載的資源內容鏈接 * @return 返回資源所占的字節數 */ private ArrayList initResoureSize(int tNum, long contentLength) { long tempLastSize = contentLength;// 剩余的下載內容大小 ArrayList downloadArray = new ArrayList (); long commonSize = contentLength / tNum; for (int i = 0; i < tNum; ++i) { DownloadModel downloadModel = new DownloadModel(); // 還未開始下載,所以已下載的大小為0byte,他的長度代表下一個線程的下載的起點 downloadModel.setDownloadedLength(i * commonSize); long loadEnding = tempLastSize; // 剩下的最後一個下載線程的下載大小=總線程-其他線程下載的大小和 if (i == tNum - 1) { loadEnding = contentLength; } else { loadEnding = commonSize * i + commonSize; } tempLastSize = contentLength - commonSize; downloadModel.setDownloadLengthSum(loadEnding); downloadModel.setThreadName("JX_Download_Thread" + i); // 如果支持斷點續傳,就存儲到數據庫中,否則暫時存儲到ArrayList中 if (isSupportLoadingAndSaving) { downlaodDB.insert(downloadModel); } else { downloadArray.add(downloadModel); } } return downloadArray; } /** * 根據資源大小智能的設置下載線程總數量 * * @param totalTreadNum * 設置的下載數據量 */ private int setTotalTreadNum(long contentLength) { // 如果用戶沒有設置下載的線程數量則根據大小智能設置 if (0 == this.totalTreadNum) { if (contentLength <= 0) { Log.i("Debug", "下載的內容太小!"); return 0; } else if (contentLength < BELOW_Three_M_SIZE) { this.totalTreadNum = 1; } else if (contentLength < BELOW_SIX_M_SIZE) { this.totalTreadNum = 2; } else if (contentLength < BELOW_EIGHTEEN_M_SIZE) { this.totalTreadNum = 4; } else { this.totalTreadNum = 5; } } return this.totalTreadNum; } /** * 判斷當前連接所在服務器是否支持斷點續傳下載 * * @return 默認不支持斷電續傳 */ public boolean isSupportLoadingAndSaving() { return isSupportLoadingAndSaving; } public long getDownloaderID() { return downloaderID; } public void setDownloaderID(long downloaderID) { this.downloaderID = downloaderID; } public int getTotalTreadNum() { return totalTreadNum; } public String getResourceUrl() { return resourceUrl; } public long getResourceSize() { return resourceSize; } }
package com.jx.main; import java.net.MalformedURLException; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.example.mutilthreaddownloader.R; import com.jx.downloader.JX_Downloader; /** * 2016年3月19日09:41:00 * * @author J_X 多線程測試類 */ public class MainActivity extends Activity { JX_Downloader downloader; Button btnDownlaod; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnDownlaod = (Button)findViewById(R.id.btn_start_downlaoding); btnDownlaod.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub try { downloader = new JX_Downloader(MainActivity.this, "****.apk""****.apk); downloader.startDownload(); } catch (MalformedURLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } }); } }小結:
下載器的速度,出來外部因素網速,服務器傳輸速度,內部因素主要是線程的數量和緩沖區能夠影響下載速度,但是筆者在魅族酷派小米虛擬機上同一wifi相同線程數量以及同樣大小緩沖區下,下載速度小米的下載速度奇慢,還有待考證具體原因。
一、前言今天我們開啟Android系統篇的文章了,其實一直想弄,只是之前一直沒有太多深入的了解,最近又把這塊拿出來好好看了一下,所以想從新梳理一下,來看看Android中
Native原生相比於Hybrid或H5最大優點是具有流暢和復雜的交互效果,觸摸事件便是其中重要一項,包括點擊(Click)、長按(LongClick)、手勢(gestu
一、什麼是對話框?一種次要窗口,包含按鈕和各種選項,通過它們可以完成特定命令或任務。 查找和替換對話框 對話框與窗口有區別,它沒有最大化按鈕、沒有最小化按鈕、大都不能改變
話不多說先看今天的實現的效果:相信這種效果很多項目都會用到,今天就講講利用RecycleView來實現他,博主把此篇文章定位初級篇,可能因為這確實很簡單,所以我要更要講的