全屏浏览
缩小浏览
回到页首

android程序---->android多线程下载(二)

  上篇我们讲到了android中下载的断点续传问题,今天我们开始学习下载的多线程问题。本次的多线程源码下载:androdi中多线程下载的实现代码。有关断点续传的问题,请参见博客:android程序---->android多线程下载(一)

 

目录导航

  1.   android中多线程下载的思路
  2.   android中多线程中的原理说明
  3.   android中多线程下载的实现
  4.   友情链接

 

android中多线程下载的思路

一、 多线程下载的步骤说明:

第一步: 我们要获得下载资源的的长度,用http请求中HttpURLConnection的getContentLength()方法

第二步:在本地创建一个文件,设计其长度。File file = new File()

第三步:根据文件长度和线程数计算每条线程下载的数据长度和下载位置。

第四步:从下载的位置下载数据,通过connection.setRequestProperty("Range", "bytes=" + start + "-" + end)方法;

第五步:保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。

 

二、 根据文件长度和线程数计算每条线程下载的数据长度和下载位置

第一个线程从0~32字节处写入

第二个线程从33~65字节范围内写入

第三个线程从66~100字节范围内写入

 

android中多线程中的原理说明

对于多线程的下载,有两个需要学习的知识点就是

1.  connection.setRequestProperty("Range", "bytes=" + start + "-" + end)方法,它用于请求指定范围内的数据。

2.  RandomAccessFile类的seek方法从指定位置开始写入数据到文件:

一、 connection.setRequestProperty("Range", "bytes=" + start + "-" + end)方法:

我们写一个Java类进行测试,请求文件我本机上的android.txt文件是:http://192.168.250.232:8080/android.txt

My name is huhx, and my blog address is http://www.cnblogs.com/huhx.welcome to my blog.

测试类如下,Http请求2到10位置之间的数据,由于从0开始,所以请求的数据应该有12个字符为:name is huhx

package com.huhx.mutilthread;

import java.net.HttpURLConnection;
import java.net.URL;

public class MultiThread {
    public static void main(String[] args) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL("http://192.168.250.232:8080/android.txt");
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestProperty("Range", "bytes=" + 3 + "-" + 14);
            connection.setReadTimeout(5000);
            connection.setRequestMethod("GET");

            int length = -1;
            if (connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
                length = connection.getContentLength();
            }
            if (length < 0) {
                return;
            }
            byte[] buffer = new byte[length];
            while ((length = connection.getInputStream().read(buffer)) != -1) {
                System.out.println(new String(buffer));
                System.out.println("length: " + buffer.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            connection.disconnect();
        }
    }
}

如我们所想打印结果如下:

二、 RandomAccessFile类的seek方法与多线程:

我们写一个线程Download,用于写入指定位置的数据到文件:

package com.huhx.randomfile;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * 
 * @author huhx
 *
 */
public class Download implements Runnable {
    private String content;
    private int start;

    public Download(String content, int start) {
        this.content = content;
        this.start = start;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ", " + content);
        File file = new File("output.txt");
        RandomAccessFile accessFile = null;
        try {
            accessFile = new RandomAccessFile(file, "rwd");
            accessFile.seek(start);
            accessFile.write(content.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                accessFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在MainTest类中,开启三个线程写入数据:

我们用一个string[]数组进行模拟,从服务器通过setRequestProperty方法设置Range参数得到的数据:

package com.huhx.randomfile;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 
 * @author huhx
 *
 */
public class MainTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        String[] contents = {"hello", "world", "linux"};
        for(int i = 0; i < 3; i ++) {
            Download download = new Download(contents[i], i * contents[i].length());
            executorService.execute(download);
        }
        executorService.shutdown();
    }
}

打印结果如下:

pool-1-thread-2, world
pool-1-thread-1, hello
pool-1-thread-3, linux

并且output.txt文件内容如下,三个线程需要读的顺序有先后,但是最终的结果还是我们预期要的:

helloworldlinux

 

android中多线程下载的实现

有了上述android中多线程中原理的测试的理解,相信我们对于android中使用多线程下载有了比较不错的印象。现在我们通过实例,来讲述整个实现过程。本次的案例,为也避免内容的冗余,没有加断点续传的功能。项目结构如下:

在手机的存储卡,得到下载完成的文件:

一、 在MainActivity中初始化开始下载按钮,绑定开始下载事件,传递参数文件的下载地址和文件的名称:

package com.example.linux.multithreaddownload;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

/**
 * writer: huhx
 */
public class MainActivity extends AppCompatActivity {
    private Button startButon;
    private String fileUrl = "http://f4.market.mi-img.com/download/AppStore/0548a945da1eb4bf830dac7e60a50aaf9883d03db/net.oschina.gitapp.apk";
    private String fileName = "linux.apk";
    private TextView textView;

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

        textView = (TextView) findViewById(R.id.textView);
        textView.setText(fileName);
        startButon = (Button) findViewById(R.id.start);
        startButon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, DownloadService.class);
                intent.setAction(DownloadService.DOWNLOAD_START);
                intent.putExtra("fileUrl", fileUrl);
                intent.putExtra("fileName", fileName);
                startService(intent);
            }
        });
    }
}

 

二、 在DownloadService的onStartCommand方法中,启动一个线程去请求下载文件的大小:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (DOWNLOAD_START.equals(intent.getAction())) {
        fileUrl = intent.getStringExtra("fileUrl");
        fileName = intent.getStringExtra("fileName");
        // 开启多线程下载
        new InitThread(fileUrl, fileName).start();
    }
    return super.onStartCommand(intent, flags, startId);
}

InitThread线程中,代码如下,获取下载文件的长度,并发送下载的消息:

@Override
public IBinder onBind(Intent intent) {
    return null;
}
/**
 * 初始化子线程
 */
class InitThread extends Thread {
    private String fileUrl;
    private String fileName;

    public InitThread(String fileUrl, String fileName) {
        this.fileUrl = fileUrl;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(fileUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setReadTimeout(5000);
            int length = -1;
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                // 获取文件的长度
                length = connection.getContentLength();
            }
            if (length <= 0) {
                return;
            }
            handler.obtainMessage(DOWNLOAD, length).sendToTarget();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            connection.disconnect();
        }
    }
}

定义一个handler,用于消息的捕获及处理,在handleMessage方法中调用Download的download方法去下载文件

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case DOWNLOAD:
                int fileLength = (int) msg.obj;
                Download download = new Download();
                download.download(fileUrl, fileName, fileLength);break;
        }
    }
};

 

二、 在Download的类中,download方法用于开启多个线程去下载文件:

package com.example.linux.multithreaddownload;

import android.content.Intent;
import android.os.Environment;
import android.widget.Toast;

import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Linux on 2016/4/10.
 */
public class Download {
    final static int THREAD_NUMBER = 3;
    private ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUMBER);
    public static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/download/";

    public void download(String fileUrl, String fileName, int fileLength) {
        int block = fileLength / THREAD_NUMBER;
        File dir = new File(DOWNLOAD_PATH);
        if (!dir.exists()) {
            dir.mkdir();
        }
        File file = new File(dir, fileName);
        for (int i = 0; i < THREAD_NUMBER; i++) {
            long start = i * block;
            long end = (i + 1) * block - 1;
            if (i == THREAD_NUMBER - 1) {
                end = fileLength;
            }
            DownloadThread downloadThread = new DownloadThread(fileUrl, file.getAbsolutePath(), start, end);
            executor.execute(downloadThread);
        }
        executor.shutdown();
    }
}

 

四、 在DownloadThread中具体去执行下载文件的任务:

package com.example.linux.multithreaddownload;

import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Created by huhxon 2016/4/10.
 */
public class DownloadThread implements Runnable {
    private String fileUrl;
    private String filePath;
    private long start;
    private long end;

    public DownloadThread(String filUrl, String filePath, long start, long end) {
        this.fileUrl = filUrl;
        this.filePath = filePath;
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        Log.i("Main", "thread: " + Thread.currentThread().getName() + ", start: " + start + ", end: " + end);
        HttpURLConnection connection = null;
        RandomAccessFile raf = null;
        InputStream is = null;
        try {
            URL url = new URL(fileUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(5000);
            connection.setRequestProperty("Range", "bytes=" + start + "-" + end);

            raf = new RandomAccessFile(new File(filePath), "rwd");
            raf.seek(start);
            if (connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
                is = connection.getInputStream();
                byte[] buffer = new byte[4 * 1024];
                int len;
                while ((len = is.read(buffer)) != -1) {
                    raf.write(buffer, 0, len);
                }
            }
            Log.i("Main", Thread.currentThread().getName() + "完成下载");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                raf.close();
                is.close();
                connection.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

五、 在AndroidManifest.xml文件中定义服务和声明权限:

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

<service android:name=".DownloadService" android:exported="true" />

 

六、 打印日志结果如下:

04-10 20:41:19.491 313-567/com.example.linux.multithreaddownload I/Main: thread: pool-1-thread-3, start: 2265146, end: 3397719
04-10 20:41:19.493 313-566/com.example.linux.multithreaddownload I/Main: thread: pool-1-thread-2, start: 1132573, end: 2265145
04-10 20:41:19.503 313-565/com.example.linux.multithreaddownload I/Main: thread: pool-1-thread-1, start: 0, end: 1132572

04-10 20:41:36.642 313-565/com.example.linux.multithreaddownload I/Main: pool-1-thread-1完成下载
04-10 20:41:37.119 313-567/com.example.linux.multithreaddownload I/Main: pool-1-thread-3完成下载
04-10 20:41:37.254 313-566/com.example.linux.multithreaddownload I/Main: pool-1-thread-2完成下载

 

友情链接

 

posted @ 2016-04-11 08:39  huhx  阅读(1206)  评论(1编辑  收藏  举报