liguofeng29’s blog

個人勉強用ブログだっす。

Androidのweb開発 - HttpURLConnectionでマルチスレッドファイルダウンロード

HttpURLConnectionはURLConnectionのサブクラスである。

拡張されているメソッド

  • int getResponseCode()
  • String getResponseMessage()
  • String getRequestMethod()
  • void setRequestMethod()

マルチスレッドでイメージダウンロードサンプルコード

①ダウンロードユーティルクラス

MultiDownloadUtil.java

package com.example.liguofeng.httpurlconnectionsample;

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

/**
 * ダウンロードユーティル
 */
public class MultiDownloadUtil {

    private String path;
    private String targetFile;
    private int threadNum;
    private int fileSize;
    private DownloadThread[] threads;

    /**
     * コンストラクター
     * @param path
     * @param targetFile
     * @param threadNum
     */
    public MultiDownloadUtil(String path, String targetFile, int threadNum) {
        this.path = path;
        this.targetFile = targetFile;
        this.threadNum = threadNum;

        this.threads = new DownloadThread[threadNum];
    }

    public void download() throws Exception{
        URL url = new URL(this.path);

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setConnectTimeout(5 * 1000);
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Accept", "*/*");
        conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("user-agent", "Mozilla/5.0 (compatible; MSIE 6.0; WIndows NT 5.1; SV1)");

        // ファイルサイズ取得
        fileSize = conn.getContentLength();
        // 一旦切断
        conn.disconnect();

        // 一スレッドでダウンロードするサイズ
        int currentPartSize = fileSize / threadNum + 1;
        RandomAccessFile file = new RandomAccessFile(targetFile, "rw");

        // 空ファイルサイズ設定
        file.setLength(fileSize);
        file.close();

        for (int i = 0; i < threadNum; i++)
        {
            // スレッド毎のダウンロード位置
            int startPos = i * currentPartSize;
            // スレッド毎にRandomAccessFile使用
            RandomAccessFile currentPart = new RandomAccessFile(targetFile,
                    "rw");
            // ダウンロード位置に移動
            currentPart.seek(startPos);

            // スレッド生成
            threads[i] = new DownloadThread(startPos, currentPartSize,
                    currentPart);

            System.out.println("-----------------------------------------------");
            System.out.println("startPos:" + startPos +  " currentPartSize:" + currentPartSize + " currentPart:" + currentPart);

            // スレッド起動
            threads[i].start();
        }
    }
    // ダウンロード割合
    public double getCompleteRate()
    {
        // 全スレッドのダウンロードサイズ
        int sumSize = 0;
        System.out.println(sumSize);
        for (int i = 0; i < threadNum; i++)
        {
            sumSize += threads[i].length;
            System.out.println(sumSize);
        }
        // ダウンロード済み割合
        System.out.println(sumSize * 1.0 / fileSize);
        return sumSize * 1.0 / fileSize;
    }

    // InputStreamスキップ
    public static void skipFully(InputStream in, long bytes) throws IOException
    {
        long remainning = bytes;
        long len = 0;
        while (remainning > 0)
        {
            len = in.skip(remainning);
            remainning -= len;
        }
    }

    private class DownloadThread extends Thread {
        // ダウンロード開始位置
        private int startPos;
        // ダウンロードサイズ
        private int currentPartSize;
        // ダウンロードパート
        private RandomAccessFile currentPart;
        // 定义已经该线程已下载的字节数
        public int length;
        public DownloadThread(int startPos, int currentPartSize,
                              RandomAccessFile currentPart)
        {
            this.startPos = startPos;
            this.currentPartSize = currentPartSize;
            this.currentPart = currentPart;
        }
        @Override
        public void run()
        {
            try
            {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection)url
                        .openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");
                conn.setRequestProperty("accept", "*/*");
                conn.setRequestProperty("Charset", "UTF-8");
                conn.setRequestProperty("user-agent", "Mozilla/5.0 (compatible; MSIE 6.0; WIndows NT 5.1; SV1)");

                InputStream inStream = conn.getInputStream();
                // スキップ
                MultiDownloadUtil.skipFully(inStream, this.startPos);
//             inStream.skip(this.startPos); //このメソッドは正確でないらしい
                byte[] buffer = new byte[1024];
                int hasRead = 0;
                // webリソース読み取り
                while (length < currentPartSize
                        && (hasRead = inStream.read(buffer)) > 0)
                {
                    currentPart.write(buffer, 0, hasRead);
                    // ダウンロードサイズ
                    length += hasRead;
                }
                currentPart.close();
                inStream.close();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}

② AndroidManifest.xml

<!-- SDカード -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- SDカード書き込み -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- ネットワーク -->
<uses-permission android:name="android.permission.INTERNET"/>

③ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ダウンロードリソースのURL:"
        />
    <EditText
        android:id="@+id/url"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="http://f.st-hatena.com/images/fotolife/l/liguofeng29/20160113/20160113215930.jpg"
        />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ターゲットファイル"
        />
    <EditText
        android:id="@+id/target"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="/mnt/sdcard/a.jpg"
        />
    <Button
        android:id="@+id/down"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Download"
        />
    <!-- ダウンロード進捗 -->
    <ProgressBar
        android:id="@+id/bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        style="?android:attr/progressBarStyleHorizontal"
        />
</LinearLayout>

④ MainActivity.java

package com.example.liguofeng.httpurlconnectionsample;

import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

    EditText url;
    EditText target;
    Button downBn;
    ProgressBar bar;
    MultiDownloadUtil downUtil;
    private int mDownStatus;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // VIEW取得
        url = (EditText) findViewById(R.id.url);
        target = (EditText) findViewById(R.id.target);
        downBn = (Button) findViewById(R.id.down);
        bar = (ProgressBar) findViewById(R.id.bar);
        // Handler生成
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 0x123) {
                    bar.setProgress(mDownStatus);
                }
            }
        };
        downBn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // DownloadのURL、スレッド数指定
                downUtil = new MultiDownloadUtil(url.getText().toString(),
                        target.getText().toString(), 2);
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            // ダウンロード開始
                            downUtil.download();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        // 進捗更新
                        final Timer timer = new Timer();
                        timer.schedule(new TimerTask() {
                            @Override
                            public void run() {
                                // 完成度
                                double completeRate = downUtil
                                        .getCompleteRate();
                                mDownStatus = (int) (completeRate * 100);
                                // メッセージ送信
                                handler.sendEmptyMessage(0x123);
                                // ダウンロード完了後タイマー停止
                                if (mDownStatus >= 100) {
                                    timer.cancel();
                                }
                            }
                        }, 0, 100);
                    }
                }.start();
            }
        });
    }
}

f:id:liguofeng29:20160113225448g:plain