使用DownloadManager下载大文件

乐刚毅
2023-12-01

上一篇博客整理了一下HttpURLConnection断点下载文件的方法。其实还有另外一种下载大文件的方法。就是通过DownloadManager来进行下载的,如果可以后台下载更新,那么用DownloadManager来下载更轻松一点。

先说我遇到的坑吧:
1,文件下载地址必须是https
2,文件下载地址不能有302等重定向下载操作

那么如果真的是有302的话,那么可以怎么绕过呢?

public interface OnCallback {
    void onUrl(String url);
}

public void fetchRealUrl(final String urlString, final OnCallback callback) {
    new AsyncTask<String, Boolean, Boolean>() {
        private String mUrl = "";

        @Override
        protected Boolean doInBackground(String... strings) {
            mUrl = urlString;
            HttpURLConnection con = null;
            try {
                URL url = new URL(mUrl);
                con = (HttpURLConnection) url.openConnection();
                con.setInstanceFollowRedirects(false);
                con.setConnectTimeout(1000);
                con.connect();
                int resCode = con.getResponseCode();
                if (resCode == HttpURLConnection.HTTP_SEE_OTHER
                        || resCode == HttpURLConnection.HTTP_MOVED_PERM
                        || resCode == HttpURLConnection.HTTP_MOVED_TEMP) {
                    String Location = con.getHeaderField("Location");
                    if (Location.startsWith("/")) {
                        Location = url.getProtocol() + "://" + url.getHost() + Location;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (con != null) con.disconnect();
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);
            callback.onUrl(mUrl);
        }
    }.execute();
}

然后这里给一个简单的demo。参考官方文档

AndroidManifest.xml

<receiver
    android:name=".DownloadReceiver"
    android:exported="true"
    android:icon="@mipmap/ic_launcher">
    <intent-filter>
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
    </intent-filter>
</receiver>

DownloadReceiver.java

package org.yeshen.download;

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class DownloadReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        Toast.makeText(context, "Download Completed, id:" + id, Toast.LENGTH_SHORT).show();
    }
}

MainActivity.java

package org.yeshen.download;

import android.app.Activity;
import android.app.DownloadManager;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import java.io.File;

public class MainActivity extends Activity {
    private DownloadReceiver onDownloadComplete = new DownloadReceiver();
    private DownloadManager downloadManager;
    private long downloadID;

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

        registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
        downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(onDownloadComplete);
        super.onDestroy();
    }

    public void onStart(View view) {
        File file = new File(getExternalFilesDir(null), "robots.txt");
        Log.d("Yeshen file path:", file.getAbsolutePath());
        if (file.exists()) {
            //noinspection ResultOfMethodCallIgnored
            file.delete();
        }

        DownloadManager.Request request = new DownloadManager.Request(
                Uri.parse("http://yeshen.org/robots.txt"))
                .setTitle("Yeshen robots download")
                .setDescription("Downloading")
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
                .setDestinationUri(Uri.fromFile(file))
                .setAllowedOverRoaming(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            request.setRequiresCharging(false).setAllowedOverMetered(true);
        }
        request.setShowRunningNotification(true);
        downloadID = downloadManager.enqueue(request);

        Toast.makeText(this, "pending,id:'" + downloadID, Toast.LENGTH_SHORT).show();
    }

    public void onCheck(View view) {
        Cursor c = downloadManager.query(new DownloadManager.Query().setFilterById(downloadID));

        if (c == null) {
            Toast.makeText(this, "Download not found!", Toast.LENGTH_LONG).show();
        } else {
            c.moveToFirst();
            Log.d(getClass().getName(), "COLUMN_ID: " +
                    c.getLong(c.getColumnIndex(DownloadManager.COLUMN_ID)));
            Log.d(getClass().getName(), "COLUMN_BYTES_DOWNLOADED_SO_FAR: " +
                    c.getLong(c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)));
            Log.d(getClass().getName(), "COLUMN_LAST_MODIFIED_TIMESTAMP: " +
                    c.getLong(c.getColumnIndex(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP)));
            Log.d(getClass().getName(), "COLUMN_LOCAL_URI: " +
                    c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
            Log.d(getClass().getName(), "COLUMN_STATUS: " +
                    c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)));
            Log.d(getClass().getName(), "COLUMN_REASON: " +
                    c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON)));
            Toast.makeText(this, statusMessage(c), Toast.LENGTH_LONG).show();
        }
    }

    private String statusMessage(Cursor c) {
        switch (c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS))) {
            case DownloadManager.STATUS_SUCCESSFUL:
                return = "Download complete!";
            default:
                return "";
        }
    }

代码量很少对不对,一两百行代码就搞定一个稳定的异步下载,比我之前唾沫横飞的写了几百行断点下载的代码容易多了,还可以检查下载的进度,还是异步任务,还不怕切后台,还可以重新下载,还可以设置下载的header,放一些cookie什么的。

有时候,为了偷懒,我们会把下载任务移交给浏览器去做,大概就是这样:

public void installByBrowser(@NonNull Context context,@NonNull String url) {
    final Uri uri = Uri.parse(url);
    final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    if (context instanceof Activity) {
        context.startActivity(intent);
    } else if (context instanceof Service) {
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}

浏览器是怎么处理的呢,当然也是调用DownloadManger去处理的呀。

再深问一句,DownloadManger的系统服务的代码在哪里呢?

代码在packages/provider/DownloadProvider中,它从content-provider中获取下载参数,然后开线程执行下载。点进去看具体的细节,思路把异步任务包装了发送到线程池中。

主要的下载逻辑在DownloadThread中,也是基于HttpURLConnection。看了一下细节,包装比较用心,而且支持开机启动下载,各个方面的细节考虑得确实比较周到。比我随手写的HttpURLConnection断点下载文件的方法,考虑的细节要多上许多。

要不要分析下DownloadProvider的代码?

我觉得没必要了(笑),代码都很简单。

.
.
.

没有更多了,玩~

 类似资料: