Android基础篇-多线程断点下载(三)

效果图:
这里写图片描述

Log:
这里写图片描述

网上关于讲解挺多的,我这里不讲解了,不懂的可以评论留言,从问题中解决问题
我可以说一下我解决问题的方式,将复杂问题划分成多个简单的问题

多线程下载一:请点击这里

多线程下载二:请点击这里

权限:

 <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

布局:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:hint="URL"
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.TextInputLayout>
    <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:hint="线程数"
            android:id="@+id/et_thread"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.TextInputLayout>

    <ProgressBar
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/progressBar"
        android:indeterminate="false"/>

    <TextView
        android:id="@+id/tv_progress"
        android:textSize="20sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:onClick="startDownload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始下载"
        android:id="@+id/button"/>

    <Button
        android:onClick="stopDownload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暂停"
        android:id="@+id/button2"
        />
    <Button
        android:onClick="restartDownload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="重新下载"
        android:id="@+id/button3"
        />


</LinearLayout>

DBHelp:

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

/**
 * 用于存储下载记录的数据库
 */
public class DBHelp extends SQLiteOpenHelper {

    public DBHelp(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER,alldown INTEGER)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int i, int i1) {

    }


}

DownlaodSqlTool:

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

/**
 *
 * 数据库操作工具类
 */
public class DownlaodSqlTool {
    DBHelp dbHelper;

    public DownlaodSqlTool(Context context) {
        dbHelper = new DBHelp(context,"down",null,1);
    }

    /**
     * 创建下载的具体信息
     */
    public void insertInfos(String downPath,int threadID,int downLength) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
            String sql = "insert into filedownlog(downpath,threadid, downlength) values (?,?,?)";
            Object[] bindArgs = {downPath,threadID,downLength};
            database.execSQL(sql, bindArgs);
    }


    /**
     * 根据线程和URL得到已经下载的长度
     */
    public int getInfo(String urlstr,String threadID) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "select downlength from filedownlog where downpath=? and threadid=?";
        String[] bindArgs = {  urlstr,threadID };
        Cursor cursor = database.rawQuery(sql,  bindArgs);
        if (cursor.getCount()>0){
            while (cursor.moveToNext()) {
                int downlength=cursor.getInt(0);
                return downlength;
            }
        }
        cursor.close();
        return -1;
    }
    /**
     * 用于判断表中是否有数据
     * 返回1表示有数据
     * 0表示没有数据
     */
    public int getInfo(String urlstr) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "select * from filedownlog where downpath=?";
        String[] bindArgs = { urlstr};
        Cursor cursor = database.rawQuery(sql,  bindArgs);
        Log.i("DownloadSqoTool",cursor.getCount()+"Count");
        if (cursor.getCount()>0){
            return 1;
        }
        cursor.close();
        return 0;
    }

    /**
     * 关闭数据库
     */
    public void closeDb() {
        dbHelper.close();
    }
    /**
     * 更新数据库中的每个线程的下载信息
     * @param threadId
     * @param compeleteSize
     * @param urlstr
     */
    public  void updataInfos( int threadId, int compeleteSize, String urlstr) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "update filedownlog set downlength=? where threadid=? and downpath=?";
        Object[] bindArgs = { compeleteSize ,threadId, urlstr};
        database.execSQL(sql, bindArgs);
    }


    /**
     * 下载完成后删除数据库中的数据
     */
    public void delete(String url) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        database.delete("filedownlog", "downpath=?", new String[] { url });
    }
}

MainActivity:

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
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.regex.Matcher;
import java.util.regex.Pattern;


class MainActivity extends AppCompatActivity {

    DownlaodSqlTool sqlTool = new DownlaodSqlTool(MainActivity.this);
    Object object = new Object();

    Button button, button2,button3;
    TextView tv_progress;
    EditText textInputEditText, textInputEditText1;
    ProgressBar progressBar;
    //SD卡路径
    String SDpath = Environment.getExternalStorageDirectory() + "";
    //SD卡不存在错误
    final int SDPATH_ERROR = 1;
    //服务器错误
    final int SERVICE_ERROR = 2;
    //URL错误
    final int URL_ERROR = 3;
    //Thread错误
    final int THREAD_ERROR = 4;
    //已经下载的长度
    final int HASLENGTH = 5;
    //重新下载
    final int RESTART=6;
    //初始化进度
    final int UPDATE_DATA = 6;
    //判断线程是否下载完成
    int finish = 0;
    //计算多个线程累计下载的进度
    int allDown = 0;
    int hasAllDown = 0;

    //是否暂停
    boolean isPause = false;

    //是否重新下载
    boolean isRestart=false;


    //URL   2: 13817637     3:26018378  1:1638400
    // String path="http://ddd1.pc6.com/soft/explorer.exe";
    //http://66dx.pc6.com/lzz3/360compkill32w.zip
    //http://down.360safe.com/yunpan/360wangpan_setup_6.6.0.1307.exe
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button2 = (Button) findViewById(R.id.button2);
        button3= (Button) findViewById(R.id.button3);
        button2.setClickable(false);
        button3.setClickable(false);
        textInputEditText = (EditText) findViewById(R.id.et_name);
        textInputEditText1 = (EditText) findViewById(R.id.et_thread);
        tv_progress = (TextView) findViewById(R.id.tv_progress);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        progressBar.setProgress(0);
    }

    /**
     * 按钮的点击事件
     * 点击创建文件,开始下载
     *
     * @param view
     */
    public void startDownload(View view) {
        isPause = false;
        isRestart=false;
        button.setClickable(false);
        button2.setClickable(true);
        button3.setClickable(true);
        //首先创建一个文件,然后下载
        new CreateSameFileSize().start();


    }

    public void stopDownload(View view) {
        button2.setClickable(false);
        button.setClickable(true);
        button3.setClickable(true);
        isPause = true;
    }
    public void restartDownload(View view){
        button2.setClickable(true);
        button.setClickable(false);
        button3.setClickable(false);
        isRestart=true;

    }



    /**
     * 切割URL得到下载应用的名字
     */
    public String splitURL(String url) {
        //得到 / 最后出现的位置
        int index = url.lastIndexOf("/");
        Log.i("splitURL", index + "");
        //截取 / 后面的字符串
        String appName = url.substring(index + 1, url.length());
        Log.i("splitURL", appName);
        return appName;
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SDPATH_ERROR:
                    Toast.makeText(MainActivity.this, "SD卡路径错误", Toast.LENGTH_SHORT).show();
                    break;
                case SERVICE_ERROR:
                    Toast.makeText(MainActivity.this, "服务器错误", Toast.LENGTH_SHORT).show();
                    break;
                case URL_ERROR:
                    Toast.makeText(MainActivity.this, "URL为空", Toast.LENGTH_SHORT).show();
                    break;
                case THREAD_ERROR:
                    Toast.makeText(MainActivity.this, "线程数不规范", Toast.LENGTH_SHORT).show();
                    break;
                case HASLENGTH:
                        int progressSize = msg.getData().getInt("size");
                        Log.i("progress:", "progress:" + progressSize);
                        progressBar.setProgress(progressSize);
                        float temp = (float) progressBar.getProgress() / (float) progressBar.getMax();
                        int progress = (int) (temp * 100);
                        if (progress == 100) {
                            Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
                        }
                        tv_progress.setText("下载进度:" + progress + " %");
                    break;
                case RESTART:
                    int newprogressSize = msg.getData().getInt("newsize");
                    Log.i("progress:", "progress:" + newprogressSize);
                    progressBar.setProgress(newprogressSize);
                    float newtemp = (float) progressBar.getProgress() / (float) progressBar.getMax();
                    int newprogress = (int) (newtemp * 100);
                    if (newprogress == 100) {
                        Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
                    }
                    tv_progress.setText("下载进度:" + newprogress + " %");
                    break;
            }
        }
    };

    /**
     * 创建一个空的下载文件
     * synchronized保证下载之前必定会先创建文件
     * 给线程分配开始下载位置   结束下载位置
     */
    public class CreateSameFileSize extends Thread {
        @Override
        public void run() {
            if (Environment.getExternalStorageState() != null) {
                try {
                    //得到下载的应用名称
                    String inputPath = textInputEditText.getEditableText().toString();
                    if (inputPath == null || inputPath.equals("")) {
                        handler.obtainMessage(URL_ERROR);
                    } else {
                        String appName = splitURL(inputPath);
                        URL url = new URL(inputPath);
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        conn.setRequestMethod("GET");
                        conn.setConnectTimeout(5000);
                        //200表示ok
                        if (conn.getResponseCode() == 200) {
                            //得到文件的长度
                            int length = conn.getContentLength();
                            progressBar.setMax(length);
                            Log.i("文件大小:", "文件大小:" + length);
                            int isData = sqlTool.getInfo(inputPath);
                            if (isData == 0) {
                                //在SDpath目录下声明一个appName的文件,此时还没有创建文件
                                File file = new File(SDpath, appName);
                                if (file.exists()) {
                                    file.delete();
                                }
                                //创建文件,rwd表示文件可读可写,一旦更新立即写入
                                RandomAccessFile raf = new RandomAccessFile(file, "rwd");
                                raf.setLength(length);
                                Log.i("raf", "创建文件完成");
                            }
                            //获取线程数
                            String threadCount = textInputEditText1.getEditableText().toString();
                            //正则表达式,判断是不是数字
                            Pattern p = Pattern.compile("[0-9]*");
                            Matcher m = p.matcher(threadCount);
                            if (threadCount == null || threadCount.equals("") || (!m.matches())) {
                                handler.obtainMessage(THREAD_ERROR);
                            } else {
                                int threadCount1 = Integer.parseInt(threadCount);
                                finish = threadCount1;
                                //平均每个线程下载的长度
                                int blockSize = length / threadCount1;
                                //计算每个线程下载的起始位置和结束位置
                                for (int threadID = 1; threadID <= threadCount1; threadID++) {
                                    //通过公式计算出起始位置
                                    int startIndex = (threadID - 1) * blockSize;
                                    //通过公式计算出结束位置
                                    int endIndex = threadID * blockSize - 1;
                                    if (threadCount1 == threadID) {
                                        endIndex = length;
                                    }
                                    Log.i("下载位置:", threadID + ":" + startIndex + "--->" + endIndex);
                                    //开始下载
                                    Log.i("开始下载", threadID + "开始");
                                    new DownloadThread(threadID, startIndex, endIndex, inputPath).start();
                                }
                                //完成后断开连接
                                conn.disconnect();
                            }
                        } else {
                            handler.obtainMessage(SERVICE_ERROR);
                        }
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                    handler.obtainMessage(SERVICE_ERROR);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                handler.obtainMessage(SDPATH_ERROR);
            }
        }
    }

    /**
     * 下载类
     */
    public class DownloadThread extends Thread {
        int threadId;//线程ID
        int startIndex;//下载起始位置
        int endIndex;//下载结束位置
        String path;//下载路径

        public DownloadThread(int threadId, int startIndex, int endIndex, String path) {
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.path = path;
        }

        @Override
        public void run() {
            //得到下载的应用名称
            URL url = null;
            InputStream is = null;
            RandomAccessFile raf = null;
            try {
                //记录已经下载的长度
                String newthreadID = Integer.toString(threadId);
                int hasDownloadLength = 0;
                /**
                 * 每次下载去查询表中是否有数据,有数据的话就
                 * 将数据取出来作为下载的开始位置
                 */
                int haslength = sqlTool.getInfo(path, newthreadID);
                if (haslength != -1) {
                    allDown = haslength - startIndex;
                    hasAllDown += allDown;
                    Log.i("更新", "更新");
                    Log.i("已经存储", haslength + "..." + threadId);
                    hasDownloadLength = haslength;
                    startIndex = haslength;
                } else {
                    Log.i("插入", "插入");
                    hasDownloadLength += startIndex;
                    sqlTool.insertInfos(path, threadId, hasDownloadLength);
                }

                url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                //重要:请求服务器下载指定位置的文件
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
                Log.i("真实下载位置:", threadId + ":" + startIndex + "--->" + endIndex);
                conn.setConnectTimeout(5000);
                //200 ok 下载全部资源
                //206 ok  下载部分资源
                int code = conn.getResponseCode();
                Log.i("code", "code:" + code);
                //已经设置了指定请求,所以这里是指定位置的输出流
                is = conn.getInputStream();
                //得到下载的应用名称
                String inputPath = textInputEditText.getEditableText().toString();
                String appName = splitURL(inputPath);
                File file = new File(SDpath, appName);
                //随机写文件的时候,从哪个位置开始写
                raf = new RandomAccessFile(file, "rwd");
                raf.seek(startIndex);
                int length = 0;
                byte[] buf = new byte[1024];
                int i = 0;
                while ((length = is.read(buf)) != -1) {
                    synchronized (MainActivity.this) {
                        if (isPause) {
                            return;
                        }
                        if (isRestart){
                            Message message = new Message();
                            message.what = RESTART;
                            message.getData().putInt("newsize", 0);
                            handler.sendMessage(message);
                            return;
                        }
                        Log.i("has", threadId + "之前:" + hasDownloadLength);
                        hasAllDown += length;
                        hasDownloadLength += length;
                        Log.i("allDownload", "总共下载" + hasAllDown);
                        sqlTool.updataInfos(threadId, hasDownloadLength, path);
                        Log.i("has", threadId + "之后:" + hasDownloadLength);
                        raf.write(buf, 0, length);
                        Message message = new Message();
                        message.what = HASLENGTH;
                        message.getData().putInt("size", hasAllDown);
                        handler.sendMessage(message);
                    }
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (isPause) {
                    allDown = 0;
                    hasAllDown=0;
                    try {
                        if (is != null) {
                            is.close();
                        }

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if (raf != null) {
                        try {
                            raf.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    Log.i("stop", threadId +"暂停下载");
                } else {
                    if (isRestart){
                        Log.i("重新下载", "重新下载");
                    }else{
                        Log.i("下载结束", threadId + "下载结束");
                    }
                    finish--;
                    if (finish == 0) {
                        allDown = 0;
                        hasAllDown=0;
                        button.setClickable(true);
                        try {
                            if (is != null) {
                                is.close();
                            }

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        if (raf != null) {
                            try {
                                raf.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        sqlTool.delete(path);
                        sqlTool.closeDb();
                        Log.i("表格删除", "表格删除");
                    }
                }

            }
        }
    }
}

有问题的不懂的欢迎评论

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有头发的猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值