工作生活

Android多线程断点下载功能(一)

2019-07-03  本文已影响0人  见哥哥长高了

安卓的断点下载功能和Java SE的断点下载功能逻辑代码是可以共享的,在这里我们以任意一个服务端的文件为例,看一下断点下载的功能如何实现。其大致思路如何?
由于下载文件需要联网、文件存储等操作,所以需要用到一下权限:

    //链接网络
    <uses-permission-sdk-23 android:name="android.permission.INTERNET"/>
    //要往SD卡里面写数据 需要权限WRITE_EXTERNAL_STORAGE
    <uses-permission-sdk-23 android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

结下来我们搞一个简易的线性布局xml文件,其页面内容为:

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="请输入要下载的文件路径"
            android:id="@+id/et_path"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="下载"
            android:onClick="downloadAction"/>

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

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

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

    </LinearLayout>

接着在MainActivity文件中定义:

    public static String path = "http://weige.teacheredu.cn//attached/20181114/20181114175439_91.docx";
    private EditText et_path;
    private static ProgressBar pb0;
    private static ProgressBar pb1;
    private static ProgressBar pb2;

    //开启几个线程从服务器下载数据
    public  static int threadCount = 3;
    public  static int runingThreadCount = 3;

在此文件中找到各view并实现方法downloadAction方法

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_path = (EditText)findViewById(R.id.et_path);

        et_path.setText(path);

        pb0 = (ProgressBar)findViewById(R.id.progressBar0);

        pb1 = (ProgressBar)findViewById(R.id.progressBar1);

        pb2 = (ProgressBar)findViewById(R.id.progressBar2);
    }



    public void downloadAction(View view){
        //得到下载路径
        final String downPath = et_path.getText().toString().trim();

        if (TextUtils.isEmpty(downPath) && downPath.startsWith("http://")){
            Toast.makeText(this,"对不起,路径不合法",0).show();
            return;
        }
//        访问网络的操作不能在主线程执行
        new Thread(){

            @Override
            public void run() {
                super.run();
                try {
                    URL url = new URL(downPath);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5000);
                    int code = conn.getResponseCode();
                    if (code == 200){

                        int length = conn.getContentLength();
                        System.out.println("服务器文件的长度:"+ length);

                        RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ getFileName(downPath),"rw");
                        raf.setLength(length);

                        raf.close();
                        int blockSize = length / threadCount;
                        runingThreadCount = threadCount;

                        for (int threadId = 0; threadId < threadCount;threadId++){

                            int startIndex = threadId * blockSize;
                            int endIndex = (threadId + 1) * blockSize - 1;

                            if (threadId == (threadCount - 1)){
                                endIndex =  length - 1;
                            }
                            new DownloadThread(threadId,startIndex,endIndex).start();
                        }
                    }

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }.start();
    }

由于我们要演示的功能是多个线程同时下载的情况,需要创建多个线程同时协作完成下载任务,所以我们在上面的代码中创建了多个线程。下面我们声明一个类继承自Thread,实现具体的下载细节。(本操作以保存在SD卡为例)

    private static class DownloadThread extends Thread{

        private int threadId;
        private int startIndex;
        private int endIndex;
        private int currentPosition;

        public  DownloadThread(int threadId,int startIndex,int endIndex){

            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            System.out.println(threadId+"号线程下载的范围"+startIndex+"----------"+endIndex);
            currentPosition = startIndex;

        }

        @Override
        public void run() {
            super.run();
            try{
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                //检查当前线程是否已经下载过一部分的数据了
                File info = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ threadId + ".position");
                RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/" + getFileName(path),"rw");

                if (info.exists() && info.length() > 0){

                    FileInputStream fis = new FileInputStream(info);

                    BufferedReader br = new BufferedReader(new InputStreamReader(fis));

                    currentPosition = Integer.valueOf(br.readLine());

                    conn.setRequestProperty("Range","bytes="+currentPosition+"-"+endIndex);

                    System.out.println("原来有下载进度,从上一次终止的位置处开始下载"+"bytes="+currentPosition+"-"+endIndex);

                    fis.close();

                    raf.seek(currentPosition);//每个线程写文件的开始位置都是你不一样的

                }else {

                    //告诉服务器只想下载资源中的一部分
                    conn.setRequestProperty("Range","bytes="+startIndex+"-" + endIndex);

                    System.out.println("原来没有下载进度,新的下载"+"bytes="+startIndex+"-"+endIndex);

                    raf.seek(startIndex);//每个线程写文件的开始位置都是你不一样的
                }

                InputStream is = conn.getInputStream();

                byte[] buffer = new byte[1024*1024*10];

                int len = -1;

                while ((len=is.read(buffer))!= -1){

                    //把每个线程下载的数据放在自己的控件里面
                    raf.write(buffer,0,len);

                    currentPosition += len;

                    File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/" + threadId +".position");

                    RandomAccessFile randomAccessFile = new RandomAccessFile(file,"rwd");

                    randomAccessFile.write(String.valueOf(currentPosition).getBytes());

                    randomAccessFile.close();
                    //fileOutputSteam数据是不一定被写道底层里面的 有可能存储在缓存里面
                    //raf是rwd的模式,数据是直接被存储到底层硬盘里

                     int max = (endIndex - startIndex);

                     int progress = (currentPosition - startIndex);

                     if (threadId == 0){
                         pb0.setMax(max);
                         pb0.setProgress(progress);
                     }else if (threadId == 1){
                         pb1.setMax(max);
                         pb1.setProgress(progress);
                     }else if (threadId == 2){
                         pb2.setMax(max);
                         pb2.setProgress(progress);
                     }
                }

                raf.close();

                is.close();

                System.out.println("线程"+ threadId+"下载完毕了");

                File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ threadId +".position");

                f.renameTo(new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ threadId +".position.finish"));

                synchronized (MainActivity.class){
                    runingThreadCount--;
                    if (runingThreadCount <= 0){
                        for (int i = 0;i < threadCount;i++){
                            File fi = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ i +".position.finish");
                            fi.delete();
                        }
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

下面这个方法根据服务端文件路径得到文件名:

    public static String getFileName(String path){
        int start = path.lastIndexOf("/") + 1;
        return path.substring(start);
    }

以上工作完成之后,可以愉快的下载了。如果是多个文件同时下载的话大致功能类似,大家可以改装,自由发挥了。

上一篇 下一篇

猜你喜欢

热点阅读