上一篇博客整理了一下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的代码?
我觉得没必要了(笑),代码都很简单。
.
.
.
没有更多了,玩~