安卓学习笔记之多线程下载

细节与方法

  1. 多线程涉及到数据库的操作,而数据库不允许多个线程同时操作,故在每个时刻只允许有一个数据库对象被操作
  2. 数据插入时,要检查是否已有对应数据,有则更新,防止数据被覆盖,造成数据错乱
  3. 要善于封装下载对象,提高数据处理效率
  4. 多线程下载原理在于将一个任务拆分成多个线程进行(请求部分网络数据)
  5. 下载任务是耗时操作,应创建服务进行下载

实现步骤

  1. 解析下载链接,获取下载任务信息
  2. 确认下载,开启服务,并将新建任务添加到下载队列
  3. 开始下载, 开启子线程分段下载

解析下载链接,并显示下载任务

package com.yu.threadsdownload;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.w3c.dom.Text;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.yu.bean.DownloadInfo;


public class MainActivity extends Activity {
// 下载链接
    public static final String QQ = "http://a5.pc6.com/cx3/QQmobile2016.pc6.apk";
    public static final String WEIXIN = "http://a5.pc6.com/lffaz3/weixin2016.apk";
    public static final String PC6 = "http://a3.pc6.com/cx/pc6shichang.pc6.apk";
// handler 消息
    public static final int MSG_DATA_LOADED = 0; // 数据加载完成时
    public static final int MSG_PROGRESS_UPDATE = 1;// 下载进度更新时
    public static final int MSG_TASK_DONE = 2; // 下载完成时

    public  static final int MSG_ONCLICK = 3; // 点击下载时
    static ListView lv;
    static List<DownloadInfo> infos;
    static DownloadAdapter adapter;
    static MainActivity ma;
    static Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MSG_DATA_LOADED:
                infos.add((DownloadInfo) msg.obj);
                if (adapter == null) {
                    adapter = ma.new DownloadAdapter();
                }else{
                    adapter.notifyDataSetChanged();
                }
                lv.setAdapter(adapter);
                break;

            case MSG_PROGRESS_UPDATE :
                DownloadInfo info  =(DownloadInfo) msg.obj;
                ProgressBar pb = (ProgressBar) lv.findViewWithTag(info.getUrl());
                TextView tvProgress = (TextView) lv.findViewWithTag(info.getUrl()+"tvProgress");
                if (pb != null) {
                    pb.setProgress(info.getPercent());
                }
                if (tvProgress != null) {
                    tvProgress.setText(info.getPercent()+"%");
                }
                if (info.getPercent() == 100) {
                    Toast.makeText(ma, info.getFileName()+"下载完成!", 1).show();
                }
                break;
            }

        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();

    }

    private void initView() {
        lv = (ListView) findViewById(R.id.lv_download);
        infos = new ArrayList<DownloadInfo>();
        ma = this;
        adapter = new DownloadAdapter();
    }


    private void initData() {
        String[] urls = {QQ,WEIXIN,PC6};
        for (final String url : urls) {
            newTask(url);
        }
    }

    /** * 新建下载任务 * @param url */
    public void newTask(final String url) {
        new Thread(){
            HttpURLConnection conn;
            public void run() {
                try {
                    conn = (HttpURLConnection) new URL(url).openConnection();
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5000);
                    conn.setReadTimeout(5000);
                    if (conn.getResponseCode() == 200) {
                        String fileSize = Formatter.formatFileSize(MainActivity.this, conn.getContentLength());
                        String fileName = getFileName(url);
                        DownloadInfo info = new DownloadInfo(fileName, fileSize, conn.getContentLength(),url,0);
                        Message msg = handler.obtainMessage();
                        msg.what = MSG_DATA_LOADED;
                        msg.obj = info;
                        handler.sendMessage(msg);
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally{
                    conn.disconnect();
                }

            };
        }.start();
    }


    /** * 获取下载软件的文件名 * @param url * @return */
    public String getFileName(String url) {
        int index = url.lastIndexOf("/");
        return url.substring(index+1);
    }

    // 适配器类
    class DownloadAdapter extends BaseAdapter{
        @Override
        public int getCount() {
            return infos.size();
        }

        @Override
        public Object getItem(int position) {
            return infos.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder ;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = View.inflate(MainActivity.this, R.layout.item_threads_download, null);
                holder.tvFileName = (TextView) convertView.findViewById(R.id.tv_fileName);
                holder.tvProgress = (TextView) convertView.findViewById(R.id.tv_progress);

                holder.pb = (ProgressBar) convertView.findViewById(R.id.pb_progress);
                holder.pb.setMax(100);
                holder.btDownload = (Button) convertView.findViewById(R.id.bt_download);
                convertView.setTag(holder);
            }else{
                holder = (ViewHolder) convertView.getTag();
            }
            final DownloadInfo info = infos.get(position);
            holder.tvFileName.setText(info.getFileName()+"/"+info.getFileSize());
            holder.tvProgress.setText(info.getProgress()+"%");
            holder.pb.setTag(info.getUrl());
            holder.tvProgress.setTag(info.getUrl()+"tvProgress");
            holder.btDownload.setOnClickListener(new OnClickListener() {
                public void onClick(View v) {
                    startService(new Intent(MainActivity.this,DownloadService.class));
                    Message msg = DownloadService.handler.obtainMessage();
                    msg.what = MSG_ONCLICK;
                    msg.obj = info;
                    DownloadService.handler.sendMessage(msg );
                }
            });
            return convertView;
        }
    }

    class ViewHolder{
        TextView  tvFileName,tvProgress;
        ProgressBar pb;
        Button btDownload;
    }

    /** * 新建下载任务 * @param view */
    public void addTask(View view) {
        EditText et = (EditText) findViewById(R.id.et_url);
        String url = et.getText().toString().trim();
        if (TextUtils.isEmpty(url)) {
            return;
        }
        newTask(url);
    }
}

下载服务,处理下载相关信息

package com.yu.threadsdownload;

import java.util.ArrayList;
import java.util.List;

import com.yu.bean.DownloadInfo;
import com.yu.db.DownloadDBImpl;

import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class DownloadService extends Service {
    static DownloadService dls ;
    static DownloadTask task;
    static List<DownloadTask> tasks; // 任务队列
    static DownloadDBImpl db ;
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        dls = DownloadService.this;
        tasks = new ArrayList<DownloadTask>();
        db = new DownloadDBImpl(dls);// 仅在service创建时,得到db对象实例

    }


    public static Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MainActivity.MSG_ONCLICK:   // 当点击开启下载任务时
                DownloadInfo info  = (DownloadInfo) msg.obj;
                // Log.e("", "info:"+info.toString());
                task = new DownloadTask(info, 3,dls, db);
                if (tasks.size() >=2) {
                    Toast.makeText(dls, "已加入下载任务队列!", 0).show();
                }
                tasks.add(task);
                info.setTaskId(tasks.indexOf(task));
                for (DownloadTask task : tasks) {       
                    if (tasks.indexOf(task)<2 && !task.isRunning) {   // 超过两个的任务需要等待
                        task.start();
                    }
                }
                break;

            case MainActivity.MSG_TASK_DONE:  // 当有任务完成时
                int taskId = msg.arg1;
                if (taskId  < tasks.size()) {
                    DownloadTask task2 = tasks.remove(taskId);
                    Log.e("", "移除"+task2);
                }
                for (DownloadTask currTask : tasks) {           // 开启排队等待的任务 
                    if (tasks.indexOf(task)<2 && !currTask.isRunning) {
                        task.start();
                    }
                }
                break;
            }

        };
    };

}

下载任务类,下载任务的具体实现

package com.yu.threadsdownload;

import java.io.File;
import java.io.FileInputStream;
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 java.util.List;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.os.Message;
import android.util.Log;

import com.yu.bean.DownloadInfo;
import com.yu.bean.ThreadInfo;
import com.yu.db.DownloadDBImpl;

/** * 下载任务 * @author Administrator * */
public class DownloadTask {
    DownloadInfo info;
    int threadCount;
    DownloadDBImpl db ;
    long progress = 0;
    boolean isPurse;
    boolean isRunning;
    List<DownloadThread> taskThreads;
    public DownloadTask(DownloadInfo info,int threadCount,Context context,DownloadDBImpl db) {
        super();
        this.info = info;
        this.threadCount = threadCount;
        this.db = db;
        taskThreads = new ArrayList<DownloadTask.DownloadThread>();
        isPurse = false;
    }

    /** * 初始化下载 * @param url */
    public void start(){
        isRunning = true;
        long length = info.getFileLength();
        long part = length / threadCount;
        for (int i = 0; i < threadCount; i++) {
            long start = part*i;
            long end = start+part-1;
            if (i == threadCount - 1) {
                end = length;
            }
            int threadId = i;
            Log.e("", "start:"+start+"end:"+end);
            ThreadInfo threadInfo = new ThreadInfo(threadId, 0, info.getUrl()   , start, end);
            db.insertThread(threadInfo );
            DownloadThread thread = new DownloadThread(start, end, info.getUrl(),threadId);
            thread.start();
            taskThreads.add(thread);
        }

    }

    /** * 下载线程 * @author Administrator * */
    class DownloadThread extends Thread{
        long start,end;
        int threadId;
        String url;
        ThreadInfo threadInfo;
        long threadProgress = 0;
        boolean finished;
        public DownloadThread(long start, long end, String url, int threadId) {
            super();
            finished = false;
            this.start = start;
            this.end = end;
            this.url = url;
            this.threadId = threadId; 
            threadInfo = new ThreadInfo( start,  end,  url,threadId);
        }

        @Override
        public void run() {
            super.run();
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            try {
                long threadProgressed = db.getThreadProgress(threadInfo);
                final long currStart = start + threadProgressed;
                progress = db.getTaskProgress(url);
                // Log.e("","threadProgressed:"+ threadProgressed+",taskProgress"+progress);

                conn = (HttpURLConnection) new URL(info.getUrl()).openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(10000);
                conn.setReadTimeout(10000);
                // 请求部分网络数据 响应码为206
                conn.addRequestProperty("Range", "bytes="+currStart+"-"+end);
                File file = new File(Environment.getExternalStorageDirectory(), info.getFileName());
                // 写入SD卡 开启写入权限
                raf = new RandomAccessFile(file,"rw");
                // 跳到分段下载起始位置
                raf.seek(currStart);
                raf.setLength(info.getFileLength());
                if (conn.getResponseCode() == 206) {
                    InputStream is =  conn.getInputStream();
                    byte[] buff = new byte[1024];
                    int len = -1;
                    long currTime = System.currentTimeMillis();
                    while((len = is.read(buff)) != -1){
                        raf.write(buff, 0, len);
                        progress+=len;
                        threadProgress+=len;
                        if (System.currentTimeMillis() - currTime > 1000) {
                            currTime = System.currentTimeMillis();
                            threadInfo.setThreadProgress(threadProgress);
                            info.setProgress(progress);
                            db.addThreadProgress(threadInfo);
                            db.addTaskProgress(info);
                            Message msg = MainActivity.handler.obtainMessage();
                            msg.what = MainActivity.MSG_PROGRESS_UPDATE;
                            info.setPercent((int) (progress * 100/info.getFileLength()));
                            msg.obj = info;
                            MainActivity.handler.sendMessage(msg);
                        }
                    }
                    finished = true;
                    checkFinfish();
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                conn.disconnect();
                if (raf != null) {
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        };
    }

    /** * 检查下载任务是否完成 */
    public void checkFinfish() {
        for (DownloadThread thread : taskThreads) {
            if (!thread.finished) {
                return;
            }
        }
        // 通知任务完成
        Message msg = MainActivity.handler.obtainMessage();
        msg.what = MainActivity.MSG_PROGRESS_UPDATE;
        info.setPercent(100);
        msg.obj = info;
        MainActivity.handler.sendMessage(msg);

        // 通知任务队列变化
        Message msg2 = DownloadService.handler.obtainMessage();
        msg2.what = MainActivity.MSG_TASK_DONE;
        msg2.arg1 = info.getTaskId();
        DownloadService.handler.sendMessage(msg2);
        // 清除数据库下载记录
        db.deleteThread(info.getUrl());
    }
}

数据库的创建与操作

package com.yu.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class ThreadsDBHelper extends SQLiteOpenHelper {
    private static final String DB_NAME  = "download.db";
    private static ThreadsDBHelper sDbHelper;
    private ThreadsDBHelper(Context context) {
        super(context, DB_NAME, null, 1);
    }

    /** * 单例 * @param context * @return */
    public static  ThreadsDBHelper getInstance(Context context) {
        if (sDbHelper == null) {
            synchronized (ThreadsDBHelper.class) {
                if (sDbHelper == null) {
                    sDbHelper = new ThreadsDBHelper(context);
                }
            }
        }
        return sDbHelper;

    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table downloadInfo(_id integer primary key autoincrement,threadId integer,taskProgress integer,finished integer,start integer,end integer,url text)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}
package com.yu.db;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.yu.bean.DownloadInfo;
import com.yu.bean.ThreadInfo;


public class DownloadDBImpl implements DownloadDBInterface{
    ThreadsDBHelper helper;
    SQLiteDatabase db;
    public DownloadDBImpl(Context context) {
        super();
        helper = ThreadsDBHelper.getInstance(context);
    }

    @Override
    public synchronized boolean insertThread(ThreadInfo threadInfo) {
        if (isTheradExist(threadInfo.getUrl(),threadInfo.getThreadId())) {
            return false;
        }else{
            db = helper.getWritableDatabase();
            ContentValues values = new ContentValues();
            values.put("threadId", threadInfo.getThreadId());
            values.put("url", threadInfo.getUrl());
            values.put("start", threadInfo.getStart());
            values.put("end", threadInfo.getEnd());
            long raw = db.insert("downloadInfo", null, values);
            if (raw != -1) {
                db.close();
                return true;
            }
            db.close();
        }
        return false;
    }


    @Override
    public synchronized boolean deleteThread(String url) {
        db = helper.getWritableDatabase();
        int raw = db.delete("downloadInfo", "url=?", new String[]{url});
        if (raw != 0) {
            db.close();
            return true;
        }
        db.close();
        return false;
    }

    @Override
    public synchronized ThreadInfo findThread(String url,int threadId) {
        db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select threadProgress,start,end from downloadInfo where url=? and threadId=?", new String[]{url,threadId+""});
        if (cursor.moveToNext()) {
            int  threadProgress = cursor.getInt(0);
            int  start = cursor.getInt(1);
            int  end = cursor.getInt(2);
            ThreadInfo threadInfo = new ThreadInfo(threadProgress, start, end);
            return threadInfo;
        }
        return null;
    }

    @Override
    public synchronized boolean   isTheradExist(String url, int threadId) {
        db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from downloadInfo where url=? and threadId=?", new String[]{url,threadId+""});
        boolean exist = cursor.moveToNext();
        cursor.close();
        db.close();
        return exist;
    }

    @Override
    public synchronized long getTaskProgress(String url) {
        db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select taskProgress from downloadInfo where url=?", new String[]{url});
        if(cursor.moveToNext()){
            long taskProgress = cursor.getInt(0);
            cursor.close();
            db.close();
            return taskProgress;
        }
        cursor.close();
        db.close();

        return 0;
    }

    @Override
    public synchronized boolean addTaskProgress(DownloadInfo downloadInfo) {
        if (isTaskExist(downloadInfo.getUrl())) {
            db = helper.getWritableDatabase();
            ContentValues values = new ContentValues();
            values.put("taskProgress", downloadInfo.getProgress());
            values.put("url", downloadInfo.getUrl());
            long raw = db.update("downloadInfo", values, "url=?", new String[]{downloadInfo.getUrl()});
            if (raw != -1){
                db.close();
                return true;
            }
            db.close();
        }else{
            db = helper.getWritableDatabase();
            ContentValues values = new ContentValues();
            values.put("taskProgress", downloadInfo.getProgress());
            values.put("url", downloadInfo.getUrl());
            long raw = db.insert("downloadInfo", null, values );
            if (raw != -1){
                db.close();
                return true;
            }
            db.close();
        }
        return false;
    }

    @Override
    public synchronized long getThreadProgress(ThreadInfo info) {
        db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select finished from downloadInfo where url=? and threadId=?", new String[]{info.getUrl(),info.getThreadId()+""});
        if(cursor.moveToNext()){
            long threadProgress = cursor.getInt(0);
                cursor.close();
                db.close();
            Log.e("", "threadProgress----"+threadProgress);

                return threadProgress;
        }

        cursor.close();
        db.close();
        return 0;
    }

    @Override
    public synchronized boolean addThreadProgress(ThreadInfo threadInfo) {
        if (isTheradExist(threadInfo.getUrl(), threadInfo.getThreadId())) {
            db = helper.getWritableDatabase();
            ContentValues values = new ContentValues();
            values.put("finished", threadInfo.getThreadProgress());
            int raw = db.update("downloadInfo", values , "url=? and threadId=?", new String[]{threadInfo.getUrl(),threadInfo.getThreadId()+""});
            if (raw > 0) {
                db.close();
                return true;
            }
            db.close();
        }else{
            db = helper.getWritableDatabase();
            ContentValues values = new ContentValues();
            values.put("finished", threadInfo.getThreadProgress());
            values.put("threadId", threadInfo.getThreadId());
            values.put("url", threadInfo.getUrl());
            long raw = db.insert("downloadInfo", null ,values);
            if (raw != -1) {
                db.close();
                return true;
            }
            db.close();
        }

        return false;
    }

    @Override
    public boolean isTaskExist(String url) {
        db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select taskProgress from downloadInfo where url=?", new String[]{url});
        if(cursor != null) {
            if (cursor.moveToNext()) {
                cursor.close();
                db.close();
                return true;
            }
        }
        cursor.close();
        db.close();
        return false;
    }
}

数据库操作接口

package com.yu.db;

import com.yu.bean.DownloadInfo;
import com.yu.bean.ThreadInfo;

/** * 线程下载接口 * @author Administrator * */
public abstract interface DownloadDBInterface {

    /** * 插入线程信息 * @param threadInfo * @return */
    public  abstract    boolean insertThread(ThreadInfo threadInfo);

    /** * 根据url删除线程信息 * @param url * @return */
    public abstract boolean  deleteThread(String  url);

    /** * 根据url查找线程信息 * @param url * @return */
    public abstract ThreadInfo findThread(String  url,int threadId);

    /** * 根据url threadId判断线程信息是否存在 * @param url * @param threadId * @return */
    public abstract boolean isTheradExist(String url,int threadId);

    /** * 根据url判断任务信息是否存在 * @param url * @return */
    public abstract boolean isTaskExist(String url);

    /** * 获取任务进度 * @return */
    public abstract long getTaskProgress(String url);

    /** * 添加任务进度 * @return */
    public abstract boolean addTaskProgress(DownloadInfo downloadInfo);
    /** * 添加线程进度 * @param url * @return */
    public abstract boolean addThreadProgress(ThreadInfo threadInfo);

    /** * 获取线程进度 * @return */
    public abstract long getThreadProgress(ThreadInfo threadInfo);

}

你可能感兴趣的:(多线程,数据库,android)