activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <ListView android:id="@+id/lv_downLoad" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/tv_fileName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="imooc.apk" /> <ProgressBar android:id="@+id/pb_progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/tv_fileName"/> <Button android:id="@+id/btn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@id/pb_progress" android:text="暂停" /> <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/pb_progress" android:layout_toLeftOf="@id/btn_stop" android:text="下载" /> </RelativeLayout>
package com.download.entities; import java.io.Serializable; public class FileInfo implements Serializable { private int id; private String url; private String fileName; private int length; private int finished; public FileInfo(int id, String url, String fileName, int length, int finished) { this.id = id; this.url = url; this.fileName = fileName; this.length = length; this.finished = finished; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getFinished() { return finished; } public void setFinished(int finished) { this.finished = finished; } @Override public String toString() { return "FileInfo [id=" + id + ", url=" + url + ", fileName=" + fileName + ", length=" + length + ", finished=" + finished + "]"; } }
package com.download.entities; import android.R.integer; public class ThreadInfo { private int id; private String url; private int start; private int end; private int finished; public ThreadInfo() { } public ThreadInfo(int id, String url, int start, int end, int finished) { this.id = id; this.url = url; this.start = start; this.end = end; this.finished = finished; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getEnd() { return end; } public void setEnd(int end) { this.end = end; } public int getFinished() { return finished; } public void setFinished(int finished) { this.finished = finished; } @Override public String toString() { return "ThreadInfo [id=" + id + ", url=" + url + ", start=" + start + ", end=" + end + ", finished=" + finished + "]"; } }
package com.download.db; import java.util.List; import com.download.entities.ThreadInfo; public interface ThreadDAO { public void insertThread(ThreadInfo threadInfo); public void deleteThread(String url); public void updateThread(String url, int thread_id, int finished); public List<ThreadInfo> getThreads(String url); public boolean isExists(String url, int thread_id); }
package com.download.db; import android.R.integer; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; 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 static final String SQL_DROP = "drop table if exists thread_info"; private static DBHelper sDbHelper = null; private DBHelper(Context context) { super(context, DB_NAME, null, VERSION); } public static DBHelper getInstance(Context context) { if (null == sDbHelper) { sDbHelper = new DBHelper(context); } return sDbHelper; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DROP); db.execSQL(SQL_CREATE); } }
package com.download.db; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.download.entities.ThreadInfo; /** * 数据访问接口实现 */ public class ThreadDAOImpl implements ThreadDAO { private DBHelper mHelper = null; public ThreadDAOImpl(Context context) { mHelper = DBHelper.getInstance(context); } @Override 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(); } @Override public synchronized void deleteThread(String url) { SQLiteDatabase db = mHelper.getWritableDatabase(); db.execSQL("delete from thread_info where url = ?", new Object[] { url }); db.close(); } @Override 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(); } @Override public List<ThreadInfo> getThreads(String url) { List<ThreadInfo> list = new ArrayList<ThreadInfo>(); SQLiteDatabase db = mHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("select * from thread_info where url = ?", new String[] { url }); 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; } @Override public 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; } }
package com.download.app; 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.util.Log; import android.view.KeyEvent; import android.widget.ListView; import android.widget.Toast; import com.download.entities.FileInfo; import com.download.services.DownloadService; import com.imooc.DownLoad.R; public class MainActivity extends Activity { public static MainActivity mMainActivity = null; private ListView mListView = null; private List<FileInfo> mFileInfoList = null; private FileListAdapter mAdapter = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // listView视图控件 mListView = (ListView) findViewById(R.id.lv_downLoad); // data数据源 mFileInfoList = new ArrayList<FileInfo>(); // 初始化文件信息对象 FileInfo fileInfo = null; // 为方便测试,用Tomcat作服务器 for (int i = 0; i < 13; i++) { fileInfo = new FileInfo(i, "http://192.168.1.100:8080/imooc" + i + ".apk", "imooc" + i + ".apk", 0, 0); mFileInfoList.add(fileInfo); } // 适配器 mAdapter = new FileListAdapter(this, mFileInfoList); // 设置适配器 mListView.setAdapter(mAdapter); // 注册广播接收器 IntentFilter filter = new IntentFilter(); filter.addAction(DownloadService.ACTION_UPDATE); filter.addAction(DownloadService.ACTION_FINISHED); registerReceiver(mReceiver, filter); mMainActivity = this; } protected void onDestroy() { super.onDestroy(); unregisterReceiver(mReceiver); } /** * 更新UI的广播接收器 */ BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DownloadService.ACTION_UPDATE.equals(intent.getAction())) { int finised = intent.getIntExtra("finished", 0); int id = intent.getIntExtra("id", 0); mAdapter.updateProgress(id, finised); Log.i("mReceiver", id + "-finised = " + finised); } else if (DownloadService.ACTION_FINISHED.equals(intent .getAction())) { // 下载结束 FileInfo fileInfo = (FileInfo) intent .getSerializableExtra("fileInfo"); mAdapter.updateProgress(fileInfo.getId(), 0); Toast.makeText( MainActivity.this, mFileInfoList.get(fileInfo.getId()).getFileName() + "下载完毕", 0).show(); } } }; }
package com.download.app; 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.download.entities.FileInfo; import com.download.services.DownloadService; import com.imooc.DownLoad.R; public class FileListAdapter extends BaseAdapter { private Context mContext; private List<FileInfo> mList; public FileListAdapter(Context context, List<FileInfo> fileInfos) { this.mContext = context; this.mList = fileInfos; } @Override public int getCount() { return mList.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; final FileInfo fileInfo = mList.get(position); if (convertView != null) { viewHolder = (ViewHolder) convertView.getTag(); if (!viewHolder.mFileName.getTag().equals( Integer.valueOf(fileInfo.getId()))) { convertView = null; } } if (null == convertView) { LayoutInflater inflater = LayoutInflater.from(mContext); convertView = inflater.inflate(R.layout.item, null); viewHolder = new ViewHolder( (TextView) convertView.findViewById(R.id.tv_fileName), (ProgressBar) convertView.findViewById(R.id.pb_progress), (Button) convertView.findViewById(R.id.btn_start), (Button) convertView.findViewById(R.id.btn_stop)); convertView.setTag(viewHolder); viewHolder.mFileName.setText(fileInfo.getFileName()); viewHolder.mProgressBar.setMax(100); viewHolder.mStartBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 通知Service开始下载 Intent intent = new Intent(mContext, DownloadService.class); intent.setAction(DownloadService.ACTION_START); intent.putExtra("fileInfo", fileInfo); mContext.startService(intent); } }); viewHolder.mStopBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(mContext, DownloadService.class); intent.setAction(DownloadService.ACTION_STOP); intent.putExtra("fileInfo", fileInfo); mContext.startService(intent); } }); // 将viewHolder.mFileName的Tag设为fileInfo的ID,用于唯一标识viewHolder.mFileName viewHolder.mFileName.setTag(Integer.valueOf(fileInfo.getId())); } viewHolder.mProgressBar.setProgress(fileInfo.getFinished()); return convertView; } public void updateProgress(int id, int progress) { FileInfo fileInfo = mList.get(id); fileInfo.setFinished(progress); notifyDataSetChanged(); } private static class ViewHolder { TextView mFileName; ProgressBar mProgressBar; Button mStartBtn; Button mStopBtn; public ViewHolder(TextView mFileName, ProgressBar mProgressBar, Button mStartBtn, Button mStopBtn) { this.mFileName = mFileName; this.mProgressBar = mProgressBar; this.mStartBtn = mStartBtn; this.mStopBtn = mStopBtn; } } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } }
package com.download.services; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import org.apache.http.HttpStatus; import com.download.entities.FileInfo; import android.app.Service; import android.content.Intent; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.util.Log; import android.webkit.WebView.FindListener; public class DownloadService extends Service { public static final String DOWNLOAD_PATH = Environment .getExternalStorageDirectory().getAbsolutePath() + "/downloads/"; public static final String ACTION_START = "ACTION_START"; public static final String ACTION_STOP = "ACTION_STOP"; public static final String ACTION_UPDATE = "ACTION_UPDATE"; public static final String ACTION_FINISHED = "ACTION_FINISHED"; public static final int MSG_INIT = 0; private String TAG = "DownloadService"; private Map<Integer, DownloadTask> mTasks = new LinkedHashMap<Integer, DownloadTask>(); @Override public int onStartCommand(Intent intent, int flags, int startId) { // 获得Activity传过来的参数 if (ACTION_START.equals(intent.getAction())) { FileInfo fileInfo = (FileInfo) intent .getSerializableExtra("fileInfo"); Log.i(TAG, "Start:" + fileInfo.toString()); // 启动初始化线程 new InitThread(fileInfo).start(); } else if (ACTION_STOP.equals(intent.getAction())) { FileInfo fileInfo = (FileInfo) intent .getSerializableExtra("fileInfo"); Log.i(TAG, "Stop:" + fileInfo.toString()); // 从集合中取出下载任务 DownloadTask task = mTasks.get(fileInfo.getId()); if (task != null) { task.isPause = true; } } return super.onStartCommand(intent, flags, startId); } private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_INIT: FileInfo fileInfo = (FileInfo) msg.obj; Log.i(TAG, "Init:" + fileInfo); // 启动下载任务 DownloadTask task = new DownloadTask(DownloadService.this, fileInfo, 3); task.downLoad(); // 把下载任务添加到集合中 mTasks.put(fileInfo.getId(), task); break; default: break; } }; }; private class InitThread extends Thread { private FileInfo mFileInfo = null; public InitThread(FileInfo mFileInfo) { this.mFileInfo = mFileInfo; } /** * @see java.lang.Thread#run() */ @Override public void run() { HttpURLConnection connection = null; RandomAccessFile raf = null; try { // 连接网络文件 URL url = new URL(mFileInfo.getUrl()); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); int length = -1; if (connection.getResponseCode() == HttpStatus.SC_OK) { // 获得文件的长度 length = connection.getContentLength(); } if (length <= 0) { return; } File dir = new File(DOWNLOAD_PATH); if (!dir.exists()) { dir.mkdir(); } // 在本地创建文件 File file = new File(dir, mFileInfo.getFileName()); raf = new RandomAccessFile(file, "rwd"); // 设置文件长度 raf.setLength(length); mFileInfo.setLength(length); mHandler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget(); } catch (Exception e) { e.printStackTrace(); } finally { if (connection != null) { connection.disconnect(); } if (raf != null) { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } } } @Override public IBinder onBind(Intent intent) { return null; } }
package com.download.services; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpStatus; import android.R.integer; import android.content.Context; import android.content.Intent; import android.nfc.Tag; import android.util.Log; import android.widget.Toast; import com.download.app.MainActivity; import com.download.db.ThreadDAO; import com.download.db.ThreadDAOImpl; import com.download.entities.FileInfo; import com.download.entities.ThreadInfo; /** * 下载任务类 */ public class DownloadTask { private Context mContext = null; private FileInfo mFileInfo = null; private ThreadDAO mDao = null; private int mFinised = 0; public boolean isPause = false; private int mThreadCount = 1; // 线程数量 private List<DownloadThread> mDownloadThreadList = null; // 线程集合 public DownloadTask(Context mContext, FileInfo mFileInfo, int count) { this.mContext = mContext; this.mFileInfo = mFileInfo; this.mThreadCount = count; mDao = new ThreadDAOImpl(mContext); } public void downLoad() { // 读取数据库的线程信息 List<ThreadInfo> threads = mDao.getThreads(mFileInfo.getUrl()); ThreadInfo threadInfo = null; if (0 == threads.size()) { // 计算每个线程下载长度 int len = mFileInfo.getLength() / mThreadCount; for (int i = 0; i < mThreadCount; i++) { // 初始化线程信息对象 threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), len * i, (i + 1) * len - 1, 0); if (mThreadCount - 1 == i) // 处理最后一个线程下载长度不能整除的问题 { threadInfo.setEnd(mFileInfo.getLength()); } // 添加到线程集合中 threads.add(threadInfo); mDao.insertThread(threadInfo); } } mDownloadThreadList = new ArrayList<DownloadTask.DownloadThread>(); // 启动多个线程进行下载 for (ThreadInfo info : threads) { DownloadThread thread = new DownloadThread(info); thread.start(); // 添加到线程集合中 mDownloadThreadList.add(thread); } } /** * 下载线程 */ private class DownloadThread extends Thread { private ThreadInfo mThreadInfo = null; public boolean isFinished = false; // 线程是否执行完毕 public DownloadThread(ThreadInfo mInfo) { this.mThreadInfo = mInfo; } @Override public void run() { HttpURLConnection connection = null; RandomAccessFile raf = null; InputStream inputStream = null; try { URL url = new URL(mThreadInfo.getUrl()); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); // 设置下载位置 int start = mThreadInfo.getStart() + mThreadInfo.getFinished(); connection.setRequestProperty("Range", "bytes=" + start + "-" + mThreadInfo.getEnd()); // 设置文件写入位置 File file = new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName()); raf = new RandomAccessFile(file, "rwd"); raf.seek(start); Intent intent = new Intent(); intent.setAction(DownloadService.ACTION_UPDATE); mFinised += mThreadInfo.getFinished(); Log.i("mFinised", mThreadInfo.getId() + "finished = " + mThreadInfo.getFinished()); // 开始下载 if (connection.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) { // 读取数据 inputStream = connection.getInputStream(); byte buf[] = new byte[1024 << 2]; int len = -1; long time = System.currentTimeMillis(); while ((len = inputStream.read(buf)) != -1) { // 写入文件 raf.write(buf, 0, len); // 累加整个文件完成进度 mFinised += len; // 累加每个线程完成的进度 mThreadInfo .setFinished(mThreadInfo.getFinished() + len); if (System.currentTimeMillis() - time > 1000) { time = System.currentTimeMillis(); int f = mFinised * 100 / mFileInfo.getLength(); if (f > mFileInfo.getFinished()) { intent.putExtra("finished", f); intent.putExtra("id", mFileInfo.getId()); mContext.sendBroadcast(intent); } } // 在下载暂停时,保存下载进度 if (isPause) { mDao.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mThreadInfo.getFinished()); Log.i("mThreadInfo", mThreadInfo.getId() + "finished = " + mThreadInfo.getFinished()); return; } } // 标识线程执行完毕 isFinished = true; checkAllThreadFinished(); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (connection != null) { connection.disconnect(); } if (raf != null) { raf.close(); } if (inputStream != null) { inputStream.close(); } } catch (Exception e2) { e2.printStackTrace(); } } } } /** * 判断所有的线程是否执行完毕 */ private synchronized void checkAllThreadFinished() { boolean allFinished = true; // 遍历线程集合,判断线程是否都执行完毕 for (DownloadThread thread : mDownloadThreadList) { if (!thread.isFinished) { allFinished = false; break; } } if (allFinished) { // 删除下载记录 mDao.deleteThread(mFileInfo.getUrl()); // 发送广播知道UI下载任务结束 Intent intent = new Intent(DownloadService.ACTION_FINISHED); intent.putExtra("fileInfo", mFileInfo); mContext.sendBroadcast(intent); } } }