編輯:關於Android編程
文件下載在App應用中也用到很多,一般版本更新時多要用的文件下載來進行處理,以前也有看過很多大神有過該方面的博客,今天我也自己來實踐一下,寫的一般,還請大家多提意見,共同進步。主要思路:
1.多線程下載:
首先通過下載總線程數來劃分文件的下載區域:利用int range = fileSize / threadCount;得到每一段下載量;每一段的位置是i * range到(i + 1) * rang - 1,注意最後一段的位置是到filesize - 1;
通過Http協議的Range字段實現下載文件的分段;
通過Java類RandomAccessFile可以實現文件的隨機訪問,利用seek方法定位的文件的指定位置;
由HttpUrlConnection獲取流來進行流的讀寫,實現文件的存儲;
在下載過程中利用Handler來向外傳遞下載的信息。
2.斷點續傳:
對於每一個線程利用一個DownloadInfo類來保存下載的信息,每次在下載過程中向數據庫更新信息(我也有想過只在下載暫停時進行更新,但那樣的話我們的進程被殺掉時信息就無法保存下來)。在進行下載之前去訪問數據庫是否有記錄存在,如果沒有執行第一次下載的初始化,如果存在記錄但下載文件不存在時,刪掉數據庫中的記錄之後進行第一次下載的初始化,如果有記錄且文件存在,則從數據庫中取出信息。
實現的效果如圖,自己封裝的類提供了開始,暫停,刪除,以及重新下載的方法。還沒來得及將工程穿上CSDN,給大家一個百度雲盤的下載地址:http://pan.baidu.com/s/1dD1Xo8T
主要類的結構有:
1)DownloadHttpTool:利用Http協議進行網絡下載類<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+ICAgICAgICAgICAgICAgICAgICAgMqOpRG93bmxhb2RTcWxUb29so6wgRG93bkxvYWRIZWxwZXKjusr9vt2/4s/gudiy2df3wOA8L3A+CjxwPiAgICAgICAgICAgICAgICAgICAgIDOjqURvd25sb2FkSW5mb6O6z8LU2NDFz6Kxo7TmwOA8L3A+CjxwPiAgICAgICAgICAgICAgICAgICAgIDSjqURvd25sb2FkVXRpbKO6t+LXsM/C1Ni3vbeosqLM4bmpttTN4r3Tv9q1xMDgPGJyPgo8L3A+CjxwPiDM+dK7z8K0+sLro7pEb3dubG9hZEluZm/A4DxwcmUgY2xhc3M9"brush:java;">package com.example.test; public class DownloadInfo { /** * 保存每個下載線程下載信息類 * */ private int threadId;// 下載器id private int startPos;// 開始點 private int endPos;// 結束點 private int compeleteSize;// 完成度 private String url;// 下載文件的URL地址 public DownloadInfo(int threadId, int startPos, int endPos, int compeleteSize, String url) { this.threadId = threadId; this.startPos = startPos; this.endPos = endPos; this.compeleteSize = compeleteSize; this.url = url; } public DownloadInfo() { } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getThreadId() { return threadId; } public void setThreadId(int threadId) { this.threadId = threadId; } public int getStartPos() { return startPos; } public void setStartPos(int startPos) { this.startPos = startPos; } public int getEndPos() { return endPos; } public void setEndPos(int endPos) { this.endPos = endPos; } public int getCompeleteSize() { return compeleteSize; } public void setCompeleteSize(int compeleteSize) { this.compeleteSize = compeleteSize; } @Override public String toString() { return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos + ", endPos=" + endPos + ", compeleteSize=" + compeleteSize + "]"; } } DownLoadHelper類,創建我們的數據庫文件
package com.example.test; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; /** * 利用數據庫來記錄下載信息 * @author acer */ public class DownLoadHelper extends SQLiteOpenHelper{ private static final String SQL_NAME = "download.db"; private static final int DOWNLOAD_VERSION=1; public DownLoadHelper(Context context) { super(context, SQL_NAME, null, DOWNLOAD_VERSION); // TODO Auto-generated constructor stub } /** * 在download.db數據庫下創建一個download_info表存儲下載信息 */ @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, " + "start_pos integer, end_pos integer, compelete_size integer,url char)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }DownlaodSqlTool進行數據的插入更新刪除等操作
package com.example.test; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; /** * * 數據庫操作工具類 */ public class DownlaodSqlTool { private DownLoadHelper dbHelper; public DownlaodSqlTool(Context context) { dbHelper = new DownLoadHelper(context); } /** * 創建下載的具體信息 */ public void insertInfos(ListDownloadHttpTool進行網絡下載的類infos) { SQLiteDatabase database = dbHelper.getWritableDatabase(); for (DownloadInfo info : infos) { String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)"; Object[] bindArgs = { info.getThreadId(), info.getStartPos(), info.getEndPos(), info.getCompeleteSize(), info.getUrl() }; database.execSQL(sql, bindArgs); } } /** * 得到下載具體信息 */ public List getInfos(String urlstr) { List list = new ArrayList (); SQLiteDatabase database = dbHelper.getWritableDatabase(); String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?"; Cursor cursor = database.rawQuery(sql, new String[] { urlstr }); while (cursor.moveToNext()) { DownloadInfo info = new DownloadInfo(cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getInt(3), cursor.getString(4)); list.add(info); } return list; } /** * 更新數據庫中的下載信息 */ public void updataInfos(int threadId, int compeleteSize, String urlstr) { SQLiteDatabase database = dbHelper.getWritableDatabase(); String sql = "update download_info set compelete_size=? where thread_id=? and url=?"; Object[] bindArgs = { compeleteSize, threadId, urlstr }; database.execSQL(sql, bindArgs); } /** * 關閉數據庫 */ public void closeDb() { dbHelper.close(); } /** * 下載完成後刪除數據庫中的數據 */ public void delete(String url) { SQLiteDatabase database = dbHelper.getWritableDatabase(); database.delete("download_info", "url=?", new String[] { url }); } }
package com.example.test; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.Log; public class DownloadHttpTool { /** * * 利用Http協議進行多線程下載具體實踐類 */ private static final String TAG = DownloadHttpTool.class.getSimpleName(); private int threadCount;//線程數量 private String urlstr;//URL地址 private Context mContext; private Handler mHandler; private ListDownloadUtils提供下載向外接口方法類:downloadInfos;//保存下載信息的類 private String localPath;//目錄 private String fileName;//文件名 private int fileSize; private DownlaodSqlTool sqlTool;//文件信息保存的數據庫操作類 private enum Download_State { Downloading, Pause, Ready;//利用枚舉表示下載的三種狀態 } private Download_State state = Download_State.Ready;//當前下載狀態 private int globalCompelete = 0;//所有線程下載的總數 public DownloadHttpTool(int threadCount, String urlString, String localPath, String fileName, Context context, Handler handler) { super(); this.threadCount = threadCount; this.urlstr = urlString; this.localPath = localPath; this.mContext = context; this.mHandler = handler; this.fileName = fileName; sqlTool = new DownlaodSqlTool(mContext); } //在開始下載之前需要調用ready方法進行配置 public void ready() { Log.w(TAG, "ready"); globalCompelete = 0; downloadInfos = sqlTool.getInfos(urlstr); if (downloadInfos.size() == 0) { initFirst(); } else { File file = new File(localPath + "/" + fileName); if (!file.exists()) { sqlTool.delete(urlstr); initFirst(); } else { fileSize = downloadInfos.get(downloadInfos.size() - 1) .getEndPos(); for (DownloadInfo info : downloadInfos) { globalCompelete += info.getCompeleteSize(); } Log.w(TAG, "globalCompelete:::" + globalCompelete); } } } public void start() { Log.w(TAG, "start"); if (downloadInfos != null) { if (state == Download_State.Downloading) { return; } state = Download_State.Downloading; for (DownloadInfo info : downloadInfos) { Log.v(TAG, "startThread"); new DownloadThread(info.getThreadId(), info.getStartPos(), info.getEndPos(), info.getCompeleteSize(), info.getUrl()).start(); } } } public void pause() { state = Download_State.Pause; sqlTool.closeDb(); } public void delete(){ compelete(); File file = new File(localPath + "/" + fileName); file.delete(); } public void compelete() { sqlTool.delete(urlstr); sqlTool.closeDb(); } public int getFileSize() { return fileSize; } public int getCompeleteSize() { return globalCompelete; } //第一次下載初始化 private void initFirst() { Log.w(TAG, "initFirst"); try { URL url = new URL(urlstr); HttpURLConnection connection = (HttpURLConnection) url .openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); fileSize = connection.getContentLength(); Log.w(TAG, "fileSize::" + fileSize); File fileParent = new File(localPath); if (!fileParent.exists()) { fileParent.mkdir(); } File file = new File(fileParent, fileName); if (!file.exists()) { file.createNewFile(); } // 本地訪問文件 RandomAccessFile accessFile = new RandomAccessFile(file, "rwd"); accessFile.setLength(fileSize); accessFile.close(); connection.disconnect(); } catch (Exception e) { e.printStackTrace(); } int range = fileSize / threadCount; downloadInfos = new ArrayList (); for (int i = 0; i < threadCount - 1; i++) { DownloadInfo info = new DownloadInfo(i, i * range, (i + 1) * range - 1, 0, urlstr); downloadInfos.add(info); } DownloadInfo info = new DownloadInfo(threadCount - 1, (threadCount - 1) * range, fileSize - 1, 0, urlstr); downloadInfos.add(info); sqlTool.insertInfos(downloadInfos); } //自定義下載線程 private class DownloadThread extends Thread { private int threadId; private int startPos; private int endPos; private int compeleteSize; private String urlstr; private int totalThreadSize; public DownloadThread(int threadId, int startPos, int endPos, int compeleteSize, String urlstr) { this.threadId = threadId; this.startPos = startPos; this.endPos = endPos; totalThreadSize = endPos - startPos + 1; this.urlstr = urlstr; this.compeleteSize = compeleteSize; } @Override public void run() { HttpURLConnection connection = null; RandomAccessFile randomAccessFile = null; InputStream is = null; try { randomAccessFile = new RandomAccessFile(localPath + "/" + fileName, "rwd"); randomAccessFile.seek(startPos + compeleteSize); URL url = new URL(urlstr); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); connection.setRequestProperty("Range", "bytes=" + (startPos + compeleteSize) + "-" + endPos); is = connection.getInputStream(); byte[] buffer = new byte[1024]; int length = -1; while ((length = is.read(buffer)) != -1) { randomAccessFile.write(buffer, 0, length); compeleteSize += length; Message message = Message.obtain(); message.what = threadId; message.obj = urlstr; message.arg1 = length; mHandler.sendMessage(message); sqlTool.updataInfos(threadId, compeleteSize, urlstr); Log.w(TAG, "Threadid::" + threadId + " compelete::" + compeleteSize + " total::" + totalThreadSize); if (compeleteSize >= totalThreadSize) { break; } if (state != Download_State.Downloading) { break; } } } catch (Exception e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } randomAccessFile.close(); connection.disconnect(); } catch (Exception e) { e.printStackTrace(); } } } } }
package com.example.test; import android.annotation.SuppressLint; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.util.Log; /** * 將下載方法封裝在此類 * 提供下載,暫停,刪除,以及重置的方法 */ public class DownloadUtil { private DownloadHttpTool mDownloadHttpTool; private OnDownloadListener onDownloadListener; private int fileSize; private int downloadedSize = 0; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); int length = msg.arg1; synchronized (this) {//加鎖保證已下載的正確性 downloadedSize += length; } if (onDownloadListener != null) { onDownloadListener.downloadProgress(downloadedSize); } if (downloadedSize >= fileSize) { mDownloadHttpTool.compelete(); if (onDownloadListener != null) { onDownloadListener.downloadEnd(); } } } }; public DownloadUtil(int threadCount, String filePath, String filename, String urlString, Context context) { mDownloadHttpTool = new DownloadHttpTool(threadCount, urlString, filePath, filename, context, mHandler); } //下載之前首先異步線程調用ready方法獲得文件大小信息,之後調用開始方法 public void start() { new AsyncTask在MainActivity() { @Override protected Void doInBackground(Void... arg0) { // TODO Auto-generated method stub mDownloadHttpTool.ready(); return null; } @Override protected void onPostExecute(Void result) { // TODO Auto-generated method stub super.onPostExecute(result); fileSize = mDownloadHttpTool.getFileSize(); downloadedSize = mDownloadHttpTool.getCompeleteSize(); Log.w("Tag", "downloadedSize::" + downloadedSize); if (onDownloadListener != null) { onDownloadListener.downloadStart(fileSize); } mDownloadHttpTool.start(); } }.execute(); } public void pause() { mDownloadHttpTool.pause(); } public void delete(){ mDownloadHttpTool.delete(); } public void reset(){ mDownloadHttpTool.delete(); start(); } public void setOnDownloadListener(OnDownloadListener onDownloadListener) { this.onDownloadListener = onDownloadListener; } //下載回調接口 public interface OnDownloadListener { public void downloadStart(int fileSize); public void downloadProgress(int downloadedSize);//記錄當前所有線程下總和 public void downloadEnd(); } }
package com.example.test; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import com.example.test.DownloadUtil.OnDownloadListener; public class MainActivity extends FragmentActivity { private static final String TAG = MainActivity.class.getSimpleName(); private ProgressBar mProgressBar; private Button start; private Button pause; private Button delete; private Button reset; private TextView total; private int max; private DownloadUtil mDownloadUtil; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = (ProgressBar) findViewById(R.id.progressBar1); start = (Button) findViewById(R.id.button_start); pause = (Button) findViewById(R.id.button_pause); delete = (Button) findViewById(R.id.button_delete); reset = (Button) findViewById(R.id.button_reset); total = (TextView) findViewById(R.id.textView_total); String urlString = "http://bbra.cn/Uploadfiles/imgs/20110303/fengjin/013.jpg"; String localPath = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/local"; mDownloadUtil = new DownloadUtil(2, localPath, "abc.jpg", urlString, this); mDownloadUtil.setOnDownloadListener(new OnDownloadListener() { @Override public void downloadStart(int fileSize) { // TODO Auto-generated method stub Log.w(TAG, "fileSize::" + fileSize); max = fileSize; mProgressBar.setMax(fileSize); } @Override public void downloadProgress(int downloadedSize) { // TODO Auto-generated method stub Log.w(TAG, "Compelete::" + downloadedSize); mProgressBar.setProgress(downloadedSize); total.setText((int) downloadedSize * 100 / max + "%"); } @Override public void downloadEnd() { // TODO Auto-generated method stub Log.w(TAG, "ENd"); } }); start.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub mDownloadUtil.start(); } }); pause.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub mDownloadUtil.pause(); } }); delete.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub mDownloadUtil.delete(); } }); reset.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub mDownloadUtil.reset(); } }); } }
今天我們將深入Android的啟動流程,從用戶按下開機鍵到Android界面的過程。在谷歌上搜索“Android boot process”會得到
先來看下要實現效果圖:查閱資料後,發現網上大部分都是用這種方法實現的:多寫一個和需要懸浮的部分一模一樣的layout,先把浮動區域的可見性設置為gone。當浮動區域滑動到
相機調焦:原理,使用豎直seekbar,根據用戶拖拉來獲得距離,然後以Parameter的形式設置到Camera。 實現O
問題分析 相信大家對於ImageView圖片組件並不陌生吧,見名知意,就是用來顯示圖片的咯! 而顯示圖片的話可以通過src屬性,又或者blac