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

Posted by Y Cheung on Fri, Jul 3, 2020

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是相對手機內存大小而定。

製作帶進度條的layout

首先準備一個帶Progress Bar的layout

 1<!-- res/layout/download.xml -->
 2<?xml version="1.0" encoding="utf-8"?>
 3<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 4    xmlns:app="http://schemas.android.com/apk/res-auto"
 5    android:id="@+id/download"
 6    android:layout_width="match_parent"
 7    android:layout_height="match_parent"
 8    tools:context=".MainActivity">
 9
10    <LinearLayout
11        android:id="@+id/download"
12        android:layout_width="wrap_content"
13        android:layout_height="wrap_content"
14        android:layout_marginStart="32dp"
15        android:layout_marginEnd="32dp"
16        android:background="@drawable/rounded_edge"
17        android:orientation="vertical"
18        app:layout_constraintBottom_toBottomOf="parent"
19        app:layout_constraintCircleRadius="5dp"
20        app:layout_constraintEnd_toEndOf="parent"
21        app:layout_constraintStart_toStartOf="parent"
22        app:layout_constraintTop_toTopOf="parent">
23
24        <ProgressBar
25            android:id="@+id/progressBar"
26            style="?android:attr/progressBarStyleHorizontal"
27            android:layout_width="80dp"
28            android:layout_height="80dp"
29            android:layout_gravity="center"
30            android:layout_marginTop="16dp"
31            android:background="@drawable/ic_download"
32            android:progressDrawable="@drawable/circular_progress_bar" />
33
34        <TextView
35            android:id="@+id/textView3"
36            android:layout_width="wrap_content"
37            android:layout_height="wrap_content"
38            android:layout_gravity="center"
39            android:layout_marginTop="16dp"
40            android:gravity="center"
41            android:text="@string/download_desc"
42            android:textColor="#fff" />
43
44        <LinearLayout
45            android:layout_width="wrap_content"
46            android:layout_height="wrap_content"
47            android:layout_marginBottom="4dp"
48            android:orientation="horizontal">
49
50            <Button
51                android:id="@+id/dialog_button_agree"
52                android:layout_width="wrap_content"
53                android:layout_height="wrap_content"
54                android:layout_marginStart="16dp"
55                android:layout_weight="1"
56                android:background="@android:color/transparent"
57                android:drawableStart="@drawable/ic_agree"
58                android:text="@string/agree"
59                android:textAlignment="viewStart"
60                android:textColor="#F4E829" />
61
62            <View
63                android:layout_width="1px"
64                android:layout_height="match_parent"
65                android:layout_marginHorizontal="16dp"
66                android:background="@android:color/darker_gray" />
67
68            <Button
69                android:id="@+id/dialog_button_back"
70                android:layout_width="wrap_content"
71                android:layout_height="wrap_content"
72                android:layout_marginEnd="16dp"
73                android:layout_weight="1"
74                android:background="@android:color/transparent"
75                android:drawableStart="@drawable/ic_disagress"
76                android:text="@string/back"
77                android:textAlignment="viewStart"
78                android:textColor="#F4E829" />
79        </LinearLayout>
80
81    </LinearLayout>
82</androidx.constraintlayout.widget.ConstraintLayout>

製作進度條

準備一下進度條。

 1<!-- drawable/circular_progress_bar.xml -->
 2<?xml version="1.0" encoding="utf-8"?>
 3<rotate xmlns:android="http://schemas.android.com/apk/res/android"
 4    android:fromDegrees="270"
 5    android:toDegrees="270">
 6    <shape
 7        android:innerRadiusRatio="2.44"
 8        android:shape="ring"
 9        android:thickness="2dp"
10        android:useLevel="true">
11
12        <gradient
13            android:angle="0"
14            android:endColor="#FFF000"
15            android:startColor="#FFF000"
16            android:type="sweep"
17            android:useLevel="false" />
18    </shape>
19</rotate>
 1<!-- drawable/ic_download.xml -->
 2<vector xmlns:android="http://schemas.android.com/apk/res/android"
 3    android:width="80dp"
 4    android:height="80dp"
 5    android:viewportWidth="80"
 6    android:viewportHeight="80">
 7    <path
 8        android:fillColor="#686868"
 9        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" />
10    <path
11        android:fillColor="#FFF000"
12        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" />
13</vector>

封裝 Volley

封裝一下 Volley。

 1// DownloadRequest.java
 2package com.szeching.volleyexample;
 3
 4import androidx.annotation.Nullable;
 5
 6import com.android.volley.NetworkResponse;
 7import com.android.volley.Request;
 8import com.android.volley.Response;
 9import com.android.volley.toolbox.HttpHeaderParser;
10
11import java.util.Map;
12
13public class DownloadRequest extends Request<byte[]> {
14
15    private final Response.Listener<byte[]> mListener;
16
17    public Map<String, String> responseHeaders;
18
19    public DownloadRequest(int method, String url, Response.Listener<byte[]> listener, @Nullable Response.ErrorListener errorListener) {
20        super(method, url, errorListener);
21        setShouldCache(false);
22        this.mListener = listener;
23    }
24
25    @Override
26    protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
27
28        responseHeaders = response.headers;
29        return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));
30
31    }
32
33    @Override
34    protected void deliverResponse(byte[] response) {
35        mListener.onResponse(response);
36    }
37
38}

設置進度條

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

 1// MainActivity.java
 2package com.szeching.volleyexample;
 3
 4import androidx.appcompat.app.AppCompatActivity;
 5
 6import android.content.Intent;
 7import android.net.Uri;
 8import android.os.Bundle;
 9import android.os.Environment;
10import android.os.Handler;
11import android.widget.ProgressBar;
12
13import androidx.appcompat.app.AppCompatActivity;
14
15import com.android.volley.Request;
16import com.android.volley.RequestQueue;
17import com.android.volley.Response;
18import com.android.volley.VolleyError;
19import com.android.volley.toolbox.HurlStack;
20import com.android.volley.toolbox.Volley;
21
22import java.io.BufferedOutputStream;
23import java.io.ByteArrayInputStream;
24import java.io.File;
25import java.io.FileOutputStream;
26import java.io.IOException;
27import java.io.InputStream;
28import java.util.HashMap;
29
30public class MainActivity extends AppCompatActivity {
31
32    @Override
33    protected void onCreate(Bundle savedInstanceState) {
34        super.onCreate(savedInstanceState);
35        setContentView(R.layout.download);
36    }
37    
38    private void requestSource() {
39        request = new DownloadRequest(Request.Method.GET, "https://example.com/example.mp4", new Response.Listener<byte[]>() {
40
41            @Override
42            public void onResponse(byte[] response) {
43                HashMap<String, Object> map = new HashMap<String, Object>();
44                try {
45                    if (response != null) {
46                        long file_length = response.length;
47                        InputStream input = new ByteArrayInputStream(response);
48                        File path = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_MOVIES);
49                        File file = new File(path, "filename.mp4");
50                        map.put("resume_path", file.toString());
51                        BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(file));
52                        byte[] data = new byte[1024];
53
54                        long total = 0;
55
56                        while ((count = input.read(data)) != -1) {
57                            total += count;
58                            output.write(data, 0, count);
59                            int progress = (int) ((int) total * 100 / file_length);
60                            Log.d("onProgress",""+progress);
61                            progressBar.setProgress(progress);
62                        }
63
64                        output.flush();
65
66                        output.close();
67                        input.close();
68                    }
69                } catch (Exception e) {
70                    e.printStackTrace();
71                }
72            }
73        }, new Response.ErrorListener() {
74            @Override
75            public void onErrorResponse(VolleyError error) {
76                error.printStackTrace();
77            }
78        });
79
80        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext(), new HurlStack());
81        mRequestQueue.add(request);
82    }
83}