編輯:關於Android編程
最近在做一個單線程多任務的斷點排隊下載的功能,網上確實有很多這樣的demo。但是呢我發現大部分網上的demo都是很些不完整的要麼就是有缺陷的,可能是我還沒找到。今天我給大家帶來的一個功能完整的並且可以多界面刷新,就比如:我當前界面點了下載放後台下載了,退出了當前界面在進來網上很多這樣demo都沒做繼續更新界面。並且還做了排隊。
1、我們做的是斷點下載,斷點下載肯定是需要記錄當前下載的進度和文件總大小的。這個當然是放在數據庫裡面最好,當然肯定會是異步來操作數據庫,這裡我們就需要用到線程鎖synchronized來控制了。
2、多任務是指可以多個任務一起下載,但是我們是有排隊機制的,所以當前任務只是一個任務在下載,其他的按點擊順序排隊執行,這裡我們就需要寫一個下載管理器來管理整個下載團隊,當暫停和取消都是需要把隊列排隊給去掉的,當然我這裡是用的線程池(ThreadPoolExecutor)來管理線程的。
3、還需要做到多界面刷新,我這裡是通過注冊一個下載監聽來做的,所有的監聽都放在map集合裡面,已id為key來進行保存,每次需要回調監聽的時候都是通過map裡面去找,所以銷毀的時候需要去注銷下所有的監聽,然而每次去新的界面只要注冊了下載監聽,我又可以再map裡面找到你新注冊的監聽從而來進行新的回調。
由於我們是單線程,所以線程池裡面同時執行的線程只需為1就可以,(意思就是說當前線程執行的只有1條線程,當前的線程還沒執行完畢就把後面添加進來的線程進行有序排序<先來後到順序>)。並且還可以拿到排隊中並還沒執行的線程,這樣我們就可以做很多事了。
package com.down.executors;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
/**
* 線程池工具
*/
public class DownLoadExecutor {
/** 請求線程池隊列,同時允許1個線程操作 */
private static ThreadPoolExecutor mPool ;
//當線程池中的線程小於mCorePoolSize,直接創建新的線程加入線程池執行任務
private static final int mCorePoolSize = 1;
//最大線程數
private static final int mMaximumPoolSize = 1;
//線程執行完任務後,且隊列中沒有可以執行的任務,存活的時間,後面的參數是時間單位
private static final long mKeepAliveTime = 5L;
/** 執行任務,當線程池處於關閉,將會重新創建新的線程池 */
public synchronized static void execute(Runnable run) {
if (run == null) {
return;
}
if (mPool == null || mPool.isShutdown()) {
//參數說明
//當線程池中的線程小於mCorePoolSize,直接創建新的線程加入線程池執行任務
//當線程池中的線程數目等於mCorePoolSize,將會把任務放入任務隊列BlockingQueue中
//當BlockingQueue中的任務放滿了,將會創建新的線程去執行,
//但是當總線程數大於mMaximumPoolSize時,將會拋出異常,交給RejectedExecutionHandler處理
//mKeepAliveTime是線程執行完任務後,且隊列中沒有可以執行的任務,存活的時間,後面的參數是時間單位
//ThreadFactory是每次創建新的線程工廠
mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), Executors.defaultThreadFactory(), new AbortPolicy());
}
mPool.execute(run);
}
/** 取消線程池中某個還未執行的任務 */
public synchronized static boolean cancel(Runnable run) {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
return mPool.getQueue().remove(run);
}else{
return false;
}
}
/** 查看線程池中是否還有某個還未執行的任務 */
public synchronized static boolean contains(Runnable run) {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
return mPool.getQueue().contains(run);
} else {
return false;
}
}
/** 立刻關閉線程池,並且正在執行的任務也將會被中斷 */
public static void stop() {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
mPool.shutdownNow();
}
}
/** 平緩關閉單任務線程池,但是會確保所有已經加入的任務都將會被執行完畢才關閉 */
public synchronized static void shutdown() {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
mPool.shutdownNow();
}
}
}
package com.down.executors;
import com.down.bean.DownLoadBean;
/**
* 觀察者
*/
public interface DownLoadObserver {
/**准備下載*/
void onPrepare(DownLoadBean bean);
/** 開始下載 */
void onStart(DownLoadBean bean);
/** 下載中 */
void onProgress(DownLoadBean bean);
/** 暫停 */
void onStop(DownLoadBean bean);
/** 下載完成 */
void onFinish(DownLoadBean bean);
/** 下載失敗 */
void onError(DownLoadBean bean);
/** 刪除成功 */
void onDelete(DownLoadBean bean);
}
管理器全局操作:開始下載之前必須先注冊下載監聽,不然後不會回調,注冊監聽的時候就把監聽放到map集合,開啟下載的時候就把當前線程也到map集合保存,這樣就簡單啦,假如我需要暫停或者刪除當前下載的任務,直接改變狀態在下載的while循環裡面直接return就可,如果是排隊中的呢,則是先拿到隊列中的任務線程再把線程中的bean對象裡面的狀態置為暫停或者刪除,這樣就算到它執行的時候也會直接返回(因為我的執行線程裡面第一行代碼就是判斷是否被暫停或刪除),繼續執行排在他後面的任務線程。刪除下載任務則是先從map集合裡面通過id拿到需要刪除的線程,然後把線程裡面的狀態改為刪除,然後刪除數據庫,刪除文件。
package com.down.executors;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.down.bean.DownLoadBean;
import com.down.uitl.DataBaseUtil;
/**
* 下載管理器
*/
public class DownLoadManager {
public static final int STATE_NONE = -1;
/** 開始下載 */
public static final int STATE_START = 0;
/** 等待中 */
public static final int STATE_WAITING = 1;
/** 下載中 */
public static final int STATE_DOWNLOADING = 2;
/** 暫停 */
public static final int STATE_PAUSED = 3;
/** 下載完畢 */
public static final int STATE_DOWNLOADED = 4;
/** 下載失敗 */
public static final int STATE_ERROR = 5;
/** 刪除下載成功 */
public static final int STATE_DELETE = 6;
/** 用於記錄觀察者,當信息發送了改變,需要通知他們 */
private Map mObservers = new ConcurrentHashMap();
/** 用於記錄所有下載的任務,方便在取消下載時,通過id能找到該任務進行刪除 */
private Map mTaskMap = new ConcurrentHashMap();
/** 全局記錄當前正在下載的bean */
private DownLoadBean down_bean;
private static DownLoadManager instance;
public static DownLoadManager getInstance() {
if (instance == null) {
instance = new DownLoadManager();
}
return instance;
}
/** 注冊觀察者 */
public void registerObserver(String id, DownLoadObserver observer) {
if (!mObservers.containsKey(id)) {
mObservers.put(id, observer);
}
}
/** 移除觀察者 */
public void RemoveObserver() {
mObservers.clear();
}
/** 刪除當前正在下載的任務 */
public void DeleteDownTask(DownLoadBean bean) {
if (mTaskMap.containsKey(bean.id)) {
// 拿到當前任務
DownLoadTask task = mTaskMap.get(bean.id);
// 暫停下載任務(等於取消了該線程)
task.bean.downloadState = STATE_DELETE;
// 再更改刪除界面狀態(這是也調一次是怕在沒下載的時候刪除)
bean.downloadState = STATE_DELETE;
notifyDownloadStateChanged(bean);
// 最後刪除數據庫數據
DataBaseUtil.DeleteDownLoadById(bean.id);
// 刪除文件
File file = new File(bean.getPath());
if (file.exists()) {
file.delete();
}
file = null;
}
}
/** 銷毀的時候關閉線程池以及當前執行的線程,並清空所有數據和把當前下載狀態存進數據庫 */
public void Destory() {
DownLoadExecutor.stop();
mObservers.clear();
mTaskMap.clear();
if (down_bean != null) {
down_bean.downloadState = STATE_PAUSED;
DataBaseUtil.UpdateDownLoadById(down_bean);
}
}
/** 當下載狀態發送改變的時候回調 */
private ExecuteHandler handler = new ExecuteHandler();
/** 拿到主線程Looper */
@SuppressLint("HandlerLeak")
private class ExecuteHandler extends Handler {
private ExecuteHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
DownLoadBean bean = (DownLoadBean) msg.obj;
if (mObservers.containsKey(bean.id)) {
DownLoadObserver observer = mObservers.get(bean.id);
switch (bean.downloadState) {
case STATE_START:// 開始下載
observer.onStart(bean);
break;
case STATE_WAITING:// 准備下載
observer.onPrepare(bean);
break;
case STATE_DOWNLOADING:// 下載中
observer.onProgress(bean);
break;
case STATE_PAUSED:// 暫停
observer.onStop(bean);
break;
case STATE_DOWNLOADED:// 下載完畢
observer.onFinish(bean);
break;
case STATE_ERROR:// 下載失敗
observer.onError(bean);
break;
case STATE_DELETE:// 刪除成功
observer.onDelete(bean);
break;
}
}
}
}
/** 當下載狀態發送改變的時候調用 */
private void notifyDownloadStateChanged(DownLoadBean bean) {
Message message = handler.obtainMessage();
message.obj = bean;
handler.sendMessage(message);
}
/** 開啟下載,需要傳入一個DownAppBean對象 */
public void download(DownLoadBean loadBean) {
// 先判斷是否有這個app的下載信息
DownLoadBean bean = DataBaseUtil.getDownLoadById(loadBean.id);
if (bean == null) {// 如果沒有,則根據loadBean創建一個新的下載信息
bean = loadBean;
DataBaseUtil.insertDown(bean);
}
// 判斷狀態是否為STATE_NONE、STATE_PAUSED、STATE_ERROR、STATE_DELETE。只有這4種狀態才能進行下載,其他狀態不予處理
if (bean.downloadState == STATE_NONE
|| bean.downloadState == STATE_PAUSED
|| bean.downloadState == STATE_DELETE
|| bean.downloadState == STATE_ERROR) {
// 下載之前,把狀態設置為STATE_WAITING,因為此時並沒有產開始下載,只是把任務放入了線程池中,當任務真正開始執行時,才會改為STATE_DOWNLOADING
bean.downloadState = STATE_WAITING;
DataBaseUtil.UpdateDownLoadById(bean);
// 每次狀態發生改變,都需要回調該方法通知所有觀察者
notifyDownloadStateChanged(bean);
DownLoadTask task = new DownLoadTask(bean);// 創建一個下載任務,放入線程池
// 線程放入map裡面方便管理
mTaskMap.put(bean.id, task);
DownLoadExecutor.execute(task);
} else if (bean.downloadState == STATE_START
|| bean.downloadState == STATE_DOWNLOADING
|| bean.downloadState == STATE_WAITING) {// 如果正在下載則暫停
if (mTaskMap.containsKey(bean.id)) {
DownLoadTask task = mTaskMap.get(bean.id);
task.bean.downloadState = STATE_PAUSED;
DataBaseUtil.UpdateDownLoadById(task.bean);
// 取消還在排隊中的線程
if (DownLoadExecutor.cancel(task)) {
mObservers.get(bean.id).onStop(task.bean);
}
}
}
}
下載線程:這裡需要注意的事,我沒有在他實時下載的時候保存他的進度,只在他斷掉的時候保存,因為我覺得實時下載的時候一直操作數據庫不妥,性能肯定有問題。好,這時你們就會問,假如我操作不當會導致數據庫裡面沒有當前進度,但是注意看清楚了,我下載的時候是拿的文件當前長度而不是數據庫的下載進度,其實這很好理解,文件都沒了,就算你數據庫裡面記錄的有進度,也於事無補所以說拿文件的當前長度是最合理的。當然如果文件沒了,肯定得先創建文件然後下載進度肯定也是0了。
我們使用的斷點下載是使用的http裡面的Range屬性,裡面參數為當前進度-某進度,這個是支持多線程下載的,這裡我們只是單線程,所以就是當前進度-文件的總進度。當然我們還得看這個服務器是否支持斷點下載,有的服務器不支持斷點下載,你下載下來那就是坑爹,怎麼判斷呢,這個http裡面有返回狀態碼的,返回206就證明是可以斷點下載的。
當然下載中途報任何崩潰都是下載失敗,都是需要刪除數據。還有就是記得下載完之後一定要把保存線程的map集合裡面remove掉這條線程
public class DownLoadTask implements Runnable {
private DownLoadBean bean;
public DownLoadTask(DownLoadBean bean) {
this.bean = bean;
}
@Override
public void run() {
// 等待中就暫停了
if (bean.downloadState == STATE_PAUSED) {
bean.downloadState = STATE_PAUSED;
DataBaseUtil.UpdateDownLoadById(bean);
return;
} else if (bean.downloadState == STATE_DELETE) {// 等待中就刪除直接回調界面,然後直接返回
bean.downloadState = STATE_DELETE;
notifyDownloadStateChanged(bean);
mTaskMap.remove(bean.id);
return;
}
bean.downloadState = DownLoadManager.STATE_START;// 開始下載
DataBaseUtil.UpdateDownLoadById(bean);
notifyDownloadStateChanged(bean);
// 當前下載的進度
long compeleteSize = 0;
File file = new File(bean.getPath());// 獲取下載文件
if (!file.exists()) {
// 如果文件不存在
bean.currentSize = 0;
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// 如果存在就拿當前文件的長度,設置當前下載長度
// (這樣的好處就是不用每次在下載文件的時候都需要寫入數據庫才能記錄當前下載的長度,一直操作數據庫是很費資源的)
compeleteSize = file.length();
}
try {
URL url = new URL(bean.url);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setConnectTimeout(30 * 1000);
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes=" + compeleteSize
+ "-" + bean.appSize);
// 獲取的狀態碼
int code = connection.getResponseCode();
// 判斷是否能夠斷點下載
if (code == 206) {
@SuppressWarnings("resource")
OutputStream outputStream = new FileOutputStream(file, true);
// 將要下載的文件寫到保存在保存路徑下的文件中
InputStream is = connection.getInputStream();
byte[] buffer = new byte[102400];
int length = -1;
// 進入下載中狀態
bean.downloadState = STATE_DOWNLOADING;
DataBaseUtil.UpdateDownLoadById(bean);
while ((length = is.read(buffer)) != -1) {
// 暫停就回調,然後直接返回
if (bean.downloadState == STATE_PAUSED) {
bean.downloadState = STATE_PAUSED;
DataBaseUtil.UpdateDownLoadById(bean);
notifyDownloadStateChanged(bean);
return;
} else if (bean.downloadState == STATE_DELETE) {// 下載的時候刪除直接回調界面,然後直接返回
bean.downloadState = STATE_DELETE;
notifyDownloadStateChanged(bean);
mTaskMap.remove(bean.id);
return;
}
// 把當前下載的bean給全局記錄的bean
down_bean = bean;
outputStream.write(buffer, 0, length);
compeleteSize += length;
// 更新數據庫中的下載信息
// 用消息將下載信息傳給進度條,對進度條進行更新
bean.currentSize = compeleteSize;
notifyDownloadStateChanged(bean);
}
if (bean.appSize == bean.currentSize) {
bean.downloadState = STATE_DOWNLOADED;
DataBaseUtil.UpdateDownLoadById(bean);
notifyDownloadStateChanged(bean);
} else {
bean.downloadState = STATE_ERROR;
DataBaseUtil.UpdateDownLoadById(bean);
notifyDownloadStateChanged(bean);
bean.currentSize = 0;// 錯誤狀態需要刪除文件
file.delete();
}
} else {
Log.e("123456", "不支持斷點下載");
}
} catch (IOException e) {
bean.downloadState = STATE_ERROR;
DataBaseUtil.UpdateDownLoadById(bean);
notifyDownloadStateChanged(bean);
bean.currentSize = 0;// 錯誤狀態需要刪除文件
file.delete();
}
mTaskMap.remove(bean.id);
}
}
}
監聽回調:當然回調監聽都是通過map集合裡面來找,有就通過id回調,沒有就不回調。這樣就能保證你每次注冊的監聽回調都是最新的。達到只要注冊了下載監聽並且是當前下載的那條id。就能回調的到你最新注冊的監聽,刷新界面。當然你界面銷毀的時候也要注銷監聽(也就是清除保存監聽map的集合)。
/** 當下載狀態發送改變的時候回調 */
private ExecuteHandler handler = new ExecuteHandler();
/** 拿到主線程Looper */
@SuppressLint("HandlerLeak")
private class ExecuteHandler extends Handler {
private ExecuteHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
DownLoadBean bean = (DownLoadBean) msg.obj;
if (mObservers.containsKey(bean.id)) {
DownLoadObserver observer = mObservers.get(bean.id);
switch (bean.downloadState) {
case STATE_START:// 開始下載
observer.onStart(bean);
break;
case STATE_WAITING:// 准備下載
observer.onPrepare(bean);
break;
case STATE_DOWNLOADING:// 下載中
observer.onProgress(bean);
break;
case STATE_PAUSED:// 暫停
observer.onStop(bean);
break;
case STATE_DOWNLOADED:// 下載完畢
observer.onFinish(bean);
break;
case STATE_ERROR:// 下載失敗
observer.onError(bean);
break;
case STATE_DELETE:// 刪除成功
observer.onDelete(bean);
break;
}
}
}
}
/** 當下載狀態發送改變的時候調用 */
private void notifyDownloadStateChanged(DownLoadBean bean) {
Message message = handler.obtainMessage();
message.obj = bean;
handler.sendMessage(message);
}
這裡我們拿到數據的同時需要跟數據庫裡面的進行比較,如果數據庫裡面有跟現在的數據id相同的就得把數據庫的數據放入
當前的集合裡面並且拿到文件路徑去取文件的長度來顯示。
注意在onDestroy方法裡面注銷監聽,也就是銷毀的時候注銷監聽
public class MainActivity extends Activity {
/** 列表控件 */
public ListView listview;
/** 列表數據bean */
public ArrayList list;
public DownAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = new ArrayList();
Test();
listview = (ListView) findViewById(R.id.listview);
adapter = new DownAdapter(MainActivity.this, list);
listview.setAdapter(adapter);
}
// 測試數據
private void Test() {
DownLoadBean bean1 = new DownLoadBean();
bean1.id = "3210158";
bean1.appName = "劍與魔法";
bean1.appSize = 252821785;
bean1.appIcon = "http://p1.qhimg.com/dr/160_160_/t0170f68197b3c8efe9.png";
bean1.url = "http://m.shouji.360tpcdn.com/160315/168f6b5f7e38b95f8d7dcce94076acc4/com.longtugame.jymf.qihoo_22.apk";
DownLoadBean bean2 = new DownLoadBean();
bean2.id = "2981222";
bean2.appName = "花椒-高顏值的直播App";
bean2.appSize = 17699443;
bean2.appIcon = "http://p1.qhimg.com/dr/160_160_/t01c52b0ee594a7f507.png";
bean2.url = "http://m.shouji.360tpcdn.com/160318/a043152dd8789131a12c5beeb7e42e34/com.huajiao_4071059.apk";
DownLoadBean bean3 = new DownLoadBean();
bean3.id = "21972";
bean3.appName = "唯品會";
bean3.appSize = 33411097;
bean3.appIcon = "http://p1.qhimg.com/dr/160_160_/t016c539aa97fdef5bf.png";
bean3.url = "http://m.shouji.360tpcdn.com/160310/5aae1072a87bf4cef0ccec0e17999d27/com.achievo.vipshop_436.apk";
DownLoadBean bean4 = new DownLoadBean();
bean4.id = "1625930";
bean4.appName = "開心消消樂";
bean4.appSize = 67771094;
bean4.appIcon = "http://p1.qhimg.com/dr/160_160_/t01fbaee14a2b65be0f.png";
bean4.url = "http://m.shouji.360tpcdn.com/160310/ca3b2c5ab347fc988dde0325e6f7c658/com.happyelements.AndroidAnimal_31.apk";
DownLoadBean bean5 = new DownLoadBean();
bean5.id = "8043";
bean5.appName = "阿裡旅行";
bean5.appSize = 33840292;
bean5.appIcon = "http://p1.qhimg.com/dr/160_160_/t01c513232212e2d915.png";
bean5.url = "http://m.shouji.360tpcdn.com/160317/0a2c6811b5fc9bada8e7e082fb5a9324/com.taobao.trip_3001049.apk";
DownLoadBean bean6 = new DownLoadBean();
bean6.id = "65533";
bean6.appName = "蘇寧易購";
bean6.appSize = 27854306;
bean6.appIcon = "http://p1.qhimg.com/dr/160_160_/t01f9b42c0addddd698.png";
bean6.url = "http://m.shouji.360tpcdn.com/160316/deab26b43b55089736817040f921c1e7/com.suning.mobile.ebuy_120.apk";
list.add(bean1);
list.add(bean2);
list.add(bean3);
list.add(bean4);
list.add(bean5);
list.add(bean6);
ArrayList list_lin = DataBaseUtil.getDownLoad();
for (int i = 0; i < list_lin.size(); i++) {
for (int j = 0; j < list.size(); j++) {
if (list_lin.get(i).id.equals(list.get(j).id)) {
list.remove(j);
File file = new File(list_lin.get(i).getPath());
if (file.exists()) {
list_lin.get(i).currentSize = file.length();
}
list.add(j, list_lin.get(i));
break;
}
}
}
}
@Override
public void onDestroy() {
DownLoadManager.getInstance().RemoveObserver();
super.onDestroy();
}
}
這是adapter,顯示時則需要根據狀態來不一樣的顯示,然後注冊下載監聽(根據每個回調來進行不一樣的顯示,每個回調都會有個下載bean)、點擊下載和點擊刪除。
public class DownAdapter extends BaseAdapter {
public ArrayList list;
public Context context;
public DownAdapter(Context context, ArrayList list) {
this.context = context;
this.list = list;
}
/**
* 換算文件的大小
*/
public String FormetFileSize(long fileSize) {// 轉換文件大小
if (fileSize <= 0) {
return "0M";
}
DecimalFormat df = new DecimalFormat("#.00");
String fileSizeString = "";
if (fileSize < 1024) {
fileSizeString = df.format((double) fileSize) + "B";
} else if (fileSize < 1048576) {
fileSizeString = df.format((double) fileSize / 1024) + "K";
} else if (fileSize < 1073741824) {
fileSizeString = df.format((double) fileSize / 1048576) + "M";
} else {
fileSizeString = df.format((double) fileSize / 1073741824) + "G";
}
return fileSizeString;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@SuppressLint("InflateParams")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
final DownLoadBean bean = list.get(position);
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(
R.layout.item_down, null);
viewHolder.txt_name = (TextView) convertView
.findViewById(R.id.txt_name);
viewHolder.txt_state = (TextView) convertView
.findViewById(R.id.txt_state);
viewHolder.btn_delete = (Button) convertView
.findViewById(R.id.btn_delete);
viewHolder.txt_file_size = (TextView) convertView
.findViewById(R.id.txt_file_size);
viewHolder.seekbar = (SeekBar) convertView
.findViewById(R.id.seekbar);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.txt_name.setText(bean.appName);
viewHolder.txt_file_size.setText(FormetFileSize(bean.currentSize)
+ " / " + FormetFileSize(bean.appSize));
if (bean.downloadState == DownLoadManager.STATE_START) {
viewHolder.txt_state.setText("開始下載");
} else if (bean.downloadState == DownLoadManager.STATE_WAITING) {
viewHolder.txt_state.setText("排隊等待中");
} else if (bean.downloadState == DownLoadManager.STATE_DOWNLOADING) {
viewHolder.txt_state.setText("下載中");
} else if (bean.downloadState == DownLoadManager.STATE_PAUSED) {
viewHolder.txt_state.setText("暫停中");
} else if (bean.downloadState == DownLoadManager.STATE_DOWNLOADED) {
viewHolder.txt_state.setText("下載完成");
viewHolder.btn_delete.setVisibility(View.GONE);
} else if (bean.downloadState == DownLoadManager.STATE_ERROR) {
viewHolder.txt_state.setText("下載失敗,請重新下載");
} else {
viewHolder.txt_state.setText("點擊下載");
}
viewHolder.seekbar.setProgress((int) ((float) bean.currentSize
/ (float) bean.appSize * 100f));
viewHolder.seekbar.setMax(100);
DownLoadManager.getInstance().registerObserver(bean.id,
new DownLoadObserver() {
@Override
public void onStop(DownLoadBean bean) {
viewHolder.txt_state.setText("暫停中");
}
@Override
public void onStart(DownLoadBean bean) {
viewHolder.txt_state.setText("開始下載");
}
@Override
public void onProgress(DownLoadBean bean) {
viewHolder.txt_file_size
.setText(FormetFileSize(bean.currentSize)
+ " / "
+ FormetFileSize(bean.appSize));
viewHolder.txt_state.setText("下載中");
viewHolder.seekbar
.setProgress((int) ((float) bean.currentSize
/ (float) bean.appSize * 100f));
}
@Override
public void onPrepare(DownLoadBean bean) {
viewHolder.txt_state.setText("排隊等待中");
}
@Override
public void onFinish(DownLoadBean bean) {
viewHolder.txt_state.setText("下載完成");
viewHolder.btn_delete.setVisibility(View.GONE);
}
@Override
public void onError(DownLoadBean bean) {
viewHolder.txt_state.setText("下載失敗,請重新下載");
}
@Override
public void onDelete(DownLoadBean bean) {
// 刪除成功之後的接口
viewHolder.txt_state.setText("點擊下載");
viewHolder.txt_file_size.setText(FormetFileSize(0)
+ " / " + FormetFileSize(bean.appSize));
viewHolder.seekbar.setProgress(0);
}
});
convertView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 開啟下載
DownLoadManager.getInstance().download(bean);
}
});
viewHolder.btn_delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 刪除當前任務
DownLoadManager.getInstance().DeleteDownTask(bean);
}
});
return convertView;
}
private class ViewHolder {
private TextView txt_name;
private Button btn_delete;
private TextView txt_state;
private TextView txt_file_size;
private SeekBar seekbar;
}
}
首先配置一個布局: 然後在activity中把發短信的代碼寫出來: package com.ydl.smssender;i
在開發中,我們常常用打印log的方式來調試我們的應用。在Java中我們常常使用方法System.out.println()來在控制台打印日志,以便我們
接著第一個Android UI手勢密碼設計的基礎上繼續改進,效果圖如下activity_main.xml<LinearLayout xmlns:android=ht
在安卓開發中,會碰到選開始日期和結束日期的問題。特別是在使用Pad時,如果彈出一個Dialog,能夠同時選擇開始日期和結束日期,那將是極好的。我在開發中在DatePick