編輯:關於Android編程
接著上一篇blog,這一篇是為一個下載任務同時使用多個線程去下載,而且可以同時下載多個任務。
具體實現的思路:跟上一篇差不多,只不過有些地方需要作出改進,因為是多線程下載,所以容易引發線程並發的問題,所以我們使用一個單例模式,DBHelper只能有一個對象,這樣避免數據庫的並發,然後對於數據庫操作的方法也是同樣,每一次都是只有一個對象,一個數據庫實例去訪問數據庫(前提是他們訪問的數據庫是同一個表,否則不用)。
改進之後的代碼如下:
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBHelper extends SQLiteOpenHelper
{
// 表名
private static final String DB_NAME = "download.db";
// 版本號
private static final int VERSION = 1;
// 建表語句
private static final String SQL_CREATE = "create table thread_info(_id integer primary key autoincrement,"
+ "thread_id integer,url text,start integer,end integer,finished integer)";
private DBHelper(Context context)
{
super(context, DB_NAME, null, VERSION);
}
private static DBHelper sHelper = null;
/**
* 單例模式
* @param context
* @return
*/
public static DBHelper getInstanceDBHelper(Context context)
{
//提高效率
if(sHelper == null)
{
//同步鎖
synchronized (DBHelper.class)
{
if(sHelper == null)
sHelper = new DBHelper(context);
}
}
return sHelper;
}
@Override
public void onCreate(SQLiteDatabase db)
{
// 建表
db.execSQL(SQL_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
}
}
對於thread_info表的操作方法,需要把他們改為同步,假如沒有synchronized關鍵字可能出現線程的安全問題
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.myasyncktestdemo.entity.ThreadInfo;
public class ThreadDBService
{
private DBHelper mHelper = null;
public ThreadDBService(Context context)
{
//通過靜態方法來獲取DBhelper實例
mHelper = DBHelper.getInstanceDBHelper(context);
}
/**
* 插入線程信息
*
* @param threadInfo
*/
public synchronized void insertThread(ThreadInfo threadInfo)
{
SQLiteDatabase db = mHelper.getWritableDatabase();
db.execSQL("insert into thread_info(thread_id,url,start,end,finished) values(?,?,?,?,?)",
new Object[]
{ threadInfo.getId(), threadInfo.getUrl(), threadInfo.getStart(), threadInfo.getEnd(),
threadInfo.getFinished() });
db.close();
}
/**
* 刪除線程信息
*
* @param url
* @param thread_id
*/
public synchronized void deletetThread(String url)
{
SQLiteDatabase db = mHelper.getWritableDatabase();
db.execSQL("delete from thread_info where url=?", new Object[]
{ url });
db.close();
}
/**
* 更新線程信息
*
* @param url
* @param thread_id
* @param finished
*/
public synchronized void updateThread(String url, int thread_id, int finished)
{
SQLiteDatabase db = mHelper.getWritableDatabase();
db.execSQL("update thread_info set finished=? where url=? and thread_id=?", new Object[]
{ finished, url, thread_id });
db.close();
}
/**
* 獲取線程信息
*
* @param url
* @return
*/
public synchronized List getThreads(String url)
{
SQLiteDatabase db = mHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from thread_info where url=?", new String[]
{ url });
List list = new ArrayList();
while (cursor.moveToNext())
{
ThreadInfo threadInfo = new ThreadInfo();
threadInfo.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
threadInfo.setUrl(cursor.getString(cursor.getColumnIndex("url")));
threadInfo.setStart(cursor.getInt(cursor.getColumnIndex("start")));
threadInfo.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
threadInfo.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));
list.add(threadInfo);
}
cursor.close();
db.close();
return list;
}
/**
* 查看線程信息想、是否存在
*
* @param url
* @param thread_id
* @return
*/
public synchronized boolean isExists(String url, int thread_id)
{
SQLiteDatabase db = mHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from thread_info where url=? and thread_id=?",
new String[]
{ url, thread_id + "" });
boolean exists = cursor.moveToNext();
cursor.close();
db.close();
return exists;
}
}
然後關於DownLaodTask類,他是對每一個下載任務都回去分配按照要求的線程數量去下載,下載完成之後再統一把有關該下載任務的類的數據庫信息刪除。它會啟動幾條線程去完成下載(有DownloadService傳入),然後每一條線程完成之後去判斷是否下載完成,發送廣播。
這裡有一點需要注意的是關於進度條顯示進度值的問題,假如,我們下載的數據比較大的時候,使用mFinished * 100 / mFileInfo.getLength()可能存在溢出的問題,解決辦法有兩種。
1,(((float) mFinished / mFileInfo.getLength()) * 100)),發送這樣的數據,為什麼要轉為float型?因為下載的長度永遠小於等於文件長度,假如沒有轉型則結果一直為0,所以需要轉型。
2,把進條的最大值設置為文件的長度,每一次發送都是發送已經下載的長度,這樣做最簡單。
DownloadService代碼:他有一個管理下載任務的map集合,可以對下載任務進行暫停,以及負責下載的時候獲取下載文件的總長度
public class DownloadService extends Service
{
// 下載任務的集合
private Map mTasks = new LinkedHashMap();
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
if (Constant.ACTION_START.equals(intent.getAction()))
{
System.out.println("有執行開始");
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
new InitThread(fileInfo).start();
} else if (Constant.ACTION_STOP.equals(intent.getAction()))
{
System.out.println("有執行暫停");
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
DownLoadTask task = mTasks.get(fileInfo.getId());
if (task != null)
{
//存在該下載任務,點擊的時候就暫停
task.setPause(true);
stopSelf();
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent)
{
return null;
}
// 處理子線程的消息回傳
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
if (msg.what == Constant.MSG_INIT)
{
System.out.println("有執行下載");
FileInfo fileInfo = (FileInfo) msg.obj;
DownLoadTask task = new DownLoadTask(DownloadService.this, fileInfo, 3);
task.download();
mTasks.put(fileInfo.getId(), task);
}
};
};
// 初始化的線程,獲取下載文件的長度
class InitThread extends Thread
{
private FileInfo mFileInfo = null;
public InitThread(FileInfo mFileInfo)
{
super();
this.mFileInfo = mFileInfo;
}
@Override
public void run()
{
HttpURLConnection conn = null;
RandomAccessFile raf = null;
try
{
URL url = new URL(mFileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(3000);
conn.setRequestMethod("GET");
int length = -1;
if (conn.getResponseCode() == 200)
{
// 得到下載文件長度
length = conn.getContentLength();
}
if (length <= 0)
{
return;
}
File dir = new File(Constant.DOWNLAOD_PATH);
if (!dir.exists())
{
dir.mkdirs();
}
// 構建文件對象
File file = new File(dir, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.setLength(length);
mFileInfo.setLength(length);
mHandler.obtainMessage(Constant.MSG_INIT, mFileInfo).sendToTarget();
} catch (Exception e)
{
e.printStackTrace();
} finally
{
conn.disconnect();
if(raf!= null)
{
try
{
raf.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
}
}
}
DownLoadTask類,在download方法中,首先去讀取數據庫信息,把該下載任務對於的幾個線程的下載信息讀取出來,假如集合長度為0則不存在則新建,然後插入數據庫,
使用一個List管理每一個下載任務的線程,方便對下載任務完成時候發送廣播刪除數據庫操作,在DownloadThread中真正完成數據下載。各1秒發送廣播更新UI
import java.io.File;
import java.io.IOException;
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.content.Intent;
import com.example.myasyncktestdemo.constant.Constant;
import com.example.myasyncktestdemo.db.ThreadDBService;
import com.example.myasyncktestdemo.entity.FileInfo;
import com.example.myasyncktestdemo.entity.ThreadInfo;
/**
* 下載任務類
*
* @author Administrator
*
*/
public class DownLoadTask
{
private ThreadDBService mDBService = null;
private Context mContext;
// 下載完成的進度,所有的該任務的線程公用,可能會導致並發
// 假如假如synchronized關鍵字,會導致性能消耗,比較嚴重,所以不加了
private FileInfo mFileInfo;
private int mFinished = 0;
private boolean isPause = false;
private int mThreadCount = 1;// 每一個下載任務的下載線程數量
private List mThreadList = null; // 用於管理下載線程
/**
* 設置下載任務暫停,每一個下載任務的所有下載線程公用一個下載監聽, 一旦為true,所有該任務的下載線程暫停下載
*
* @return
*/
public boolean isPause()
{
return isPause;
}
/**
* @param isPause
*/
public void setPause(boolean isPause)
{
this.isPause = isPause;
}
/**
* 下載任務的構造方法
*
* @param mContext
* 上下文
* @param mFileInfo
* 需要下載的文件
* @param mThreadCount
* 下載該文件需要啟動的線程數量
*/
public DownLoadTask(Context mContext, FileInfo mFileInfo, int mThreadCount)
{
this.mContext = mContext;
this.mFileInfo = mFileInfo;
mDBService = new ThreadDBService(mContext);
this.mThreadCount = mThreadCount;
}
/**
* 下載,調用之後先去查詢數據庫,獲取數據之後啟動線程下載數據
*/
public void download()
{
// 首次點擊下載的時候為0,之後都是從數據庫中讀取
List threads = mDBService.getThreads(mFileInfo.getUrl());
System.out.println("獲取到的單個任務下載的線程數" + threads.size());
if (threads.size() == 0)
{
// 首次下載,需要做的工作時獲取上級給的每一個任務的線程數
// 根據線程數給每一個下載線程分配下載長度
// 把每一個的下載線程插入到數據庫中
// 獲得每個線程下載的長度
int length = mFileInfo.getLength() / mThreadCount;
for (int i = 0; i < mThreadCount; i++)
{
ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), length * i, (i + 1) * length
- 1, 0);
mDBService.insertThread(threadInfo);
if (i + 1 == mThreadCount)
{
threadInfo.setEnd(mFileInfo.getLength());
}
// 添加到集合中
threads.add(threadInfo);
}
}
mThreadList = new ArrayList();
// 啟動線程進行下載
for (ThreadInfo info : threads)
{
DownloadThread thread = new DownloadThread(info);
thread.start();
System.out.println("有啟動線程" + "線程狀態" + isPause);
// 添加線程到集合中
mThreadList.add(thread);
}
}
/**
* 判斷是否所有線程都執行完畢
*/
private synchronized void checkAllThreadFinished()
{
boolean allFinished = true;
// 遍歷線程集合
for (DownloadThread thread : mThreadList)
{
if (!thread.isFinished)
{
allFinished = false;
break;
}
}
// 下載完成,發送下載完成廣播,刪除數據庫關於該任務的下載進程所有信息
if (allFinished)
{
System.out.println("有發送下載完成廣播");
mDBService.deletetThread(mFileInfo.getUrl());
Intent intent = new Intent(Constant.ACTION_FINISH);
intent.putExtra("fileInfo", mFileInfo);
mContext.sendBroadcast(intent);
}
}
/**
* 真正的數據下載線程類
*
* @author Administrator
*
*/
class DownloadThread extends Thread
{
private ThreadInfo mThreadInfo;
public boolean isFinished = false;// 標識是該線程的對於任務是否下載完成
public DownloadThread(ThreadInfo mThreadInfo)
{
super();
this.mThreadInfo = mThreadInfo;
}
@Override
public void run()
{
HttpURLConnection conn = null;
RandomAccessFile raf = null;
InputStream input = null;
try
{
conn = (HttpURLConnection) new URL(mThreadInfo.getUrl()).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
// 計算該線程的開始下載位置,從他的start位置+他已經完成的
int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
conn.setRequestProperty("Range", "bytes=" + start + "-" + mThreadInfo.getEnd());
File file = new File(Constant.DOWNLAOD_PATH, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
Intent intent = new Intent(Constant.ACTION_UPDATE);
mFinished += mThreadInfo.getFinished();
// 斷點下載的是206不是200
if (206 == conn.getResponseCode())
{
System.out.println("有執行123");
input = conn.getInputStream();
byte[] buffer = new byte[1024];
int len;
long time = System.currentTimeMillis();
System.out.println("有執行DownloadThread方法下載");
while ((len = input.read(buffer)) != -1)
{
System.out.println("有執行456");
raf.write(buffer, 0, len);
// 真個文件的進度
mFinished += len;// 每一個下載線程都會把他的下載進度假如到mFinished上面
// 每個線程的下載進度
mThreadInfo.setFinished(mThreadInfo.getFinished() + len);// 設置該線程的下載完成度
// 隔一段時間發送廣播更新ProgressBar
if (System.currentTimeMillis() - time > 1000)
{
time = System.currentTimeMillis();
//設置進度條目前的下載長度
//這樣做的目的是防止溢出
intent.putExtra("finished", (int) (((float) mFinished / mFileInfo.getLength()) * 100));
intent.putExtra("id", mFileInfo.getId());
mContext.sendBroadcast(intent);
}
// 暫停保存數據庫,跳出循環
if (isPause)
{
mDBService.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(),
mThreadInfo.getFinished());
System.out.println("有執行中途下載暫停");
return;
}
System.out.println("有執行789");
}
}
System.out.println("有執行ABC");
isFinished = true;
System.out.println("有線程下載完成");
// 每一條線程下載完成之後檢查是否都執行完畢
checkAllThreadFinished();
} catch (Exception e)
{
e.printStackTrace();
} finally
{
conn.disconnect();
if (input != null)
{
try
{
input.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
if (raf != null)
{
try
{
raf.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
}
}
}
適配器類,不多說
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.myasyncktestdemo.R;
import com.example.myasyncktestdemo.constant.Constant;
import com.example.myasyncktestdemo.entity.FileInfo;
import com.example.myasyncktestdemo.services.DownloadService;
public class MyListViewAdapter extends BaseAdapter
{
private Context mContext;
private List mDatas;
public MyListViewAdapter(Context mContext, List mDatas)
{
this.mContext = mContext;
this.mDatas = mDatas;
}
@Override
public int getCount()
{
return mDatas.size();
}
@Override
public Object getItem(int position)
{
return mDatas.get(position);
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if (convertView == null)
{
convertView = LayoutInflater.from(mContext).inflate(R.layout.listviewitem, null);
holder = new ViewHolder();
holder.tvFileName = (TextView) convertView.findViewById(R.id.mTvTip);
holder.start = (Button) convertView.findViewById(R.id.start);
holder.stop = (Button) convertView.findViewById(R.id.stop);
holder.pbProgress = (ProgressBar) convertView.findViewById(R.id.pbProgress);
convertView.setTag(holder);
} else
{
holder = (ViewHolder) convertView.getTag();
}
final FileInfo mFileInfo = mDatas.get(position);
holder.tvFileName.setText(mFileInfo.getFileName());
holder.pbProgress.setProgress(mFileInfo.getFinished());
holder.start.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
Intent intent = new Intent(mContext, DownloadService.class);
intent.putExtra("fileInfo", mFileInfo);
intent.setAction(Constant.ACTION_START);
mContext.startService(intent);
}
});
holder.stop.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
Intent intent = new Intent(mContext, DownloadService.class);
intent.putExtra("fileInfo", mFileInfo);
intent.setAction(Constant.ACTION_STOP);
mContext.startService(intent);
}
});
return convertView;
}
static class ViewHolder
{
TextView tvFileName;
ProgressBar pbProgress;
Button start, stop;
}
/**
* 更新進度條
* @param id 哪一個item的進度條
* @param progress 目前完成的進度
*/
public void setProgressbar(int id, int progress)
{
FileInfo mFileInfo = mDatas.get(id);
mFileInfo.setFinished(progress);
System.out.println("有收到長度");
notifyDataSetChanged();
}
}
MainActivity類,作用也是差不多
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.Toast;
import com.example.myasyncktestdemo.adapter.MyListViewAdapter;
import com.example.myasyncktestdemo.constant.Constant;
import com.example.myasyncktestdemo.entity.FileInfo;
public class MainActivity extends Activity
{
private ListView mListView;
private MyListViewAdapter mAapter;
private List mDatas;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) this.findViewById(R.id.mListview);
mDatas = new ArrayList();
FileInfo fileInfo01 = new FileInfo("imooc.apk", 0, "http://www.imooc.com/mobile/imooc.apk", 0,
0);
FileInfo fileInfo02 = new FileInfo("Activator.exe", 1,
"http://www.imooc.com/download/Activator.exe", 0, 0);
FileInfo fileInfo03 = new FileInfo("iTunes64Setup.exe", 2,
"http://imooc.com/download/iTunes64Setup.exe", 0, 0);
FileInfo fileInfo04 = new FileInfo(
"kugou_V7.exe",
3,
"http://dlsw.baidu.com/sw-search-sp/soft/1a/11798/kugou_V7.6.85.17344_setup.1427079848.exe",
0, 0);
mDatas.add(fileInfo01);
mDatas.add(fileInfo02);
mDatas.add(fileInfo03);
mDatas.add(fileInfo04);
mAapter = new MyListViewAdapter(getApplicationContext(), mDatas);
mListView.setAdapter(mAapter);
IntentFilter filter = new IntentFilter();
filter.addAction(Constant.ACTION_FINISH);
filter.addAction(Constant.ACTION_UPDATE);
registerReceiver(mReceiver, filter);// 代碼注冊Receive
}
protected void onDestroy()
{
super.onDestroy();
unregisterReceiver(mReceiver);
};
BroadcastReceiver mReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(Constant.ACTION_UPDATE))
{
int finished = intent.getIntExtra("finished", 0);
int id = intent.getIntExtra("id", -1);
mAapter.setProgressbar(id, finished);
} else if (Constant.ACTION_FINISH.equals(intent.getAction()))
{
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
mAapter.setProgressbar(fileInfo.getId(), 0);
Toast.makeText(getApplicationContext(), fileInfo.getFileName() + "下載完成", 0).show();
}
}
};
}
常量類
import android.os.Environment;
public class Constant
{
public static final String ACTION_FINISH = "ACTION_FINISH";
public static final String ACTION_UPDATE = "ACTION_UPDATE";
public static final String DOWNLAOD_PATH = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/liweijie/downloads/";
public static final String ACTION_START = "ACTION_START";
public static final String ACTION_STOP = "ACTION_STOP";
public static final int MSG_INIT = 0;
}
Entity類就不貼出來了,一個是ThreadInfo一個是FileInfo,然後大家記得加入對於的權限和注冊Service。
android學習十(android的文件存儲)
在android系統中主要提供了三種方式用於簡單的實現數據持久化功能,即文件存儲,SharePreference存儲以及數據庫存儲。當然還可以把數據保存到SD卡中。
從零開始學android(FrameLayout幀布局.十四.)
FrameLayout布局(幀布局)就是在屏幕上開辟一個區域以填充所有的組件,但是使用FrameLayout布局會將所有的組件都放在屏幕的左上角,而且所有的組件可以層疊
Android自定義View——仿vivo i管家病毒掃描動畫效果
技術是永無止境的,如果真的愛技術,那就勇敢的堅持下去。我很喜歡這句話,當我在遇到問題的時候、當我覺得代碼枯燥的時候,我就會問自己,到底是不是真的熱愛技術,這個時候,我心裡
android 完全退出應用程序實現代碼
android退出應用程序會調用android.os.Process.killProcess(android.os.Process.myPid())或是System.ex