July 3, 2020

Android 使用 Volley 下載文件附進度條

Tags: androidvolleytutorial | (4 min read)
Android 使用 Volley 下載文件附進度條

Volley官方文檔說:

"Volley is not suitable for large download or streaming operations, since Volley holds all responses in memory during parsing. For large download operations, consider using an alternative like DownloadManager."

這是真的,特別是內存小的手機,使用Volley下載很多文件或者單個大文件就不是很恰當了。這裡的large是相對手機內存大小而定。

首先準備一個帶Progress Bar的layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/download"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginEnd="32dp"
        android:background="@drawable/rounded_edge"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintCircleRadius="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_gravity="center"
            android:layout_marginTop="16dp"
            android:background="@drawable/ic_download"
            android:progressDrawable="@drawable/circular_progress_bar" />

        <TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="16dp"
            android:gravity="center"
            android:text="@string/download_desc"
            android:textColor="#fff" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="4dp"
            android:orientation="horizontal">

            <Button
                android:id="@+id/dialog_button_agree"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_weight="1"
                android:background="@android:color/transparent"
                android:drawableStart="@drawable/ic_agree"
                android:text="@string/agree"
                android:textAlignment="viewStart"
                android:textColor="#F4E829" />

            <View
                android:layout_width="1px"
                android:layout_height="match_parent"
                android:layout_marginHorizontal="16dp"
                android:background="@android:color/darker_gray" />

            <Button
                android:id="@+id/dialog_button_back"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="16dp"
                android:layout_weight="1"
                android:background="@android:color/transparent"
                android:drawableStart="@drawable/ic_disagress"
                android:text="@string/back"
                android:textAlignment="viewStart"
                android:textColor="#F4E829" />
        </LinearLayout>

    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
res/layout/download.xml

準備一下進度條。

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="270"
    android:toDegrees="270">
    <shape
        android:innerRadiusRatio="2.44"
        android:shape="ring"
        android:thickness="2dp"
        android:useLevel="true">

        <gradient
            android:angle="0"
            android:endColor="#FFF000"
            android:startColor="#FFF000"
            android:type="sweep"
            android:useLevel="false" />
    </shape>
</rotate>
drawable/circular_progress_bar.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="80dp"
    android:height="80dp"
    android:viewportWidth="80"
    android:viewportHeight="80">
    <path
        android:fillColor="#686868"
        android:pathData="M40,7.5163C48.6699,7.5163 56.8366,10.902 62.9673,17.0327C69.098,23.1634 72.4837,31.3301 72.4837,40C72.4837,48.6699 69.098,56.8366 62.9673,62.9673C56.8366,69.098 48.6699,72.4837 40,72.4837C31.3301,72.4837 23.1634,69.098 17.0327,62.9673C10.902,56.8366 7.5163,48.6699 7.5163,40C7.5163,31.3301 10.902,23.1634 17.0327,17.0327C23.1634,10.902 31.3301,7.5163 40,7.5163ZM40,5C20.6699,5 5,20.6699 5,40C5,59.3301 20.6699,75 40,75C59.3301,75 75,59.3301 75,40C75,20.6699 59.3301,5 40,5Z" />
    <path
        android:fillColor="#FFF000"
        android:pathData="M41.0378,49.9393L56.2402,34.4966L54.0244,32.3541L41.5146,45.0422V20H38.4293V45.0422L25.9195,32.3541L23.7037,34.4966L38.9061,49.9393H17V53H63V49.9393H41.0378ZM40.0281,46.6003H39.9719H40.0281Z" />
</vector>
drawable/ic_download.xml

封裝一下 Volley。

package com.szeching.volleyexample;

import androidx.annotation.Nullable;

import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;

import java.util.Map;

public class DownloadRequest extends Request<byte[]> {

    private final Response.Listener<byte[]> mListener;

    public Map<String, String> responseHeaders;

    public DownloadRequest(int method, String url, Response.Listener<byte[]> listener, @Nullable Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        setShouldCache(false);
        this.mListener = listener;
    }

    @Override
    protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {

        responseHeaders = response.headers;
        return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));

    }

    @Override
    protected void deliverResponse(byte[] response) {
        mListener.onResponse(response);
    }

}
DownloadRequest.java

在Activity中發起請求並設置進度條進度。

package com.szeching.volleyexample;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.widget.ProgressBar;

import androidx.appcompat.app.AppCompatActivity;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.Volley;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.download);
    }
    
    private void requestSource() {
        request = new DownloadRequest(Request.Method.GET, "https://example.com/example.mp4", new Response.Listener<byte[]>() {

            @Override
            public void onResponse(byte[] response) {
                HashMap<String, Object> map = new HashMap<String, Object>();
                try {
                    if (response != null) {
                        long file_length = response.length;
                        InputStream input = new ByteArrayInputStream(response);
                        File path = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_MOVIES);
                        File file = new File(path, "filename.mp4");
                        map.put("resume_path", file.toString());
                        BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(file));
                        byte[] data = new byte[1024];

                        long total = 0;

                        while ((count = input.read(data)) != -1) {
                            total += count;
                            output.write(data, 0, count);
                            int progress = (int) ((int) total * 100 / file_length);
                            Log.d("onProgress",""+progress);
                            progressBar.setProgress(progress);
                        }

                        output.flush();

                        output.close();
                        input.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
            }
        });

        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext(), new HurlStack());
        mRequestQueue.add(request);
    }
}
MainActivity.java