上一篇我们讲到了使用
OKhttp实现文件上传
,还有不清楚文件上传的小伙伴可以去看看。
今天,我们的重点是使用OKhttp实现文件下载,还是老规矩,先看效果:
我这个人写博文喜欢把效果先放出来,为什么呢?因为我觉得这样能方便小伙伴们知道,这是不是我所需要的效果,而不是看我长篇大论之后,发现并没有自己想要的。
我来解释一下大致的流程,还是和上一篇文件上传一样的界面,不同的是,现在我是有文本信息的:
- 点击对应文件,弹出一个弹框,点击确认之后开始下载
- 下载成功后,进度条会有提示
- 同时系统会发送一个通知,告诉你文件下载成功
这里我们就不搞那么复杂了,关于弹框和进度条、通知栏啥的,就不细讲了,我们的重头戏是文件下载,那就从点击确定之后开始说咯?
好吧,这里又是两个封装好的工具类,大家接好了:
然后,你们会发现,这里我又把token放到请求头里去了,所以,为了不误人子弟,我还是默默注释了吧,放上正常的。
public class DownloadUtil {
private static DownloadUtil downloadUtil;
private final OkHttpClient okHttpClient;
private Context context;
private String TAG = "下载页面";
public static DownloadUtil get() {
if (downloadUtil == null) {
downloadUtil = new DownloadUtil();
}
return downloadUtil;
}
private DownloadUtil() {
okHttpClient = new OkHttpClient();
}
/**
* @param url 下载连接
* @param saveDir 储存下载文件的SDCard目录
* @param listener 下载监听
*/
public void download(Context context, final String url, final String saveDir,final String fileName, final OnDownloadListener listener) {
this.context= context;
// 需要token的时候可以这样做
// SharedPreferences sp=MyApp.getAppContext().getSharedPreferences("loginInfo", MODE_PRIVATE);
// Request request = new Request.Builder().header("token",sp.getString("token" , "")).url(url).build();
Request request = new Request.Builder().url(url).build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 下载失败
listener.onDownloadFailed();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
// 储存下载文件的目录
String savePath = isExistDir(saveDir);
Log.w(TAG,"存储下载目录:"+savePath);
try {
is = response.body().byteStream();
long total = response.body().contentLength();
File file = new File(savePath, getNameFromUrl(fileName));
Log.w(TAG,"最终路径:"+file);
fos = new FileOutputStream(file);
long sum = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
int progress = (int) (sum * 1.0f / total * 100);
// 下载中
listener.onDownloading(progress);
}
fos.flush();
// 下载完成
listener.onDownloadSuccess();
} catch (Exception e) {
listener.onDownloadFailed();
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
}
}
}
});
}
/**
* @param saveDir
* @return
* @throws IOException
* 判断下载目录是否存在
*/
private String isExistDir(String saveDir) throws IOException {
// 下载位置
File downloadFile = new File(Environment.getExternalStorageDirectory().getPath() + "/download/", saveDir);
if (!downloadFile.mkdirs()) {
downloadFile.createNewFile();
}
String savePath = downloadFile.getAbsolutePath();
Log.w(TAG,"下载目录:"+savePath);
return savePath;
}
/**
* @param url
* @return
* 传入文件名
*/
@NonNull
public String getNameFromUrl(String url) {
return url;
}
public interface OnDownloadListener {
/**
* 下载成功
*/
void onDownloadSuccess();
/**
* @param progress
* 下载进度
*/
void onDownloading(int progress);
/**
* 下载失败
*/
void onDownloadFailed();
}
}
emm,还有一个:
public class FileProviderUtils {
public static Uri getUriForFile(Context mContext, File file) {
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= 24) {
fileUri = getUriForFile24(mContext, file);
} else {
fileUri = Uri.fromFile(file);
}
return fileUri;
}
public static Uri getUriForFile24(Context mContext, File file) {
Uri fileUri = android.support.v4.content.FileProvider.getUriForFile(mContext,
BuildConfig.APPLICATION_ID + ".fileProvider",
file);
return fileUri;
}
public static void setIntentDataAndType(Context mContext,
Intent intent,
String type,
File file,
boolean writeAble) {
if (Build.VERSION.SDK_INT >= 24) {
intent.setDataAndType(getUriForFile(mContext, file), type);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setDataAndType(Uri.fromFile(file), type);
}
}
}
哇,写到这,我不得不吐槽一句,有时候真的是一失足成千古恨呐,之前搞这个搞了好久,报的啥错有点忘了,但好歹把方法总结了。说白了,还是太粗心。
所以才一直强调要注意细节,注意细节,注意细节,重要的事情说三次!
好了,回到正题,前方高能,一定要确保这些步骤:
1. AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.manage_system.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
不知道你有没有注意到第二个工具类FileProviderUtils里有这样一个方法:
public static Uri getUriForFile24(Context mContext, File file) {
Uri fileUri = android.support.v4.content.FileProvider.getUriForFile(mContext,
BuildConfig.APPLICATION_ID + “.fileProvider”,
file);
return fileUri;
}
这里的.fileProvider就是和我们xml文件对应的,而这里最重要的是resource中我们在xml中自定义的file_paths。
2. file_paths.xml
与普通文件一样,在xml文件夹里新建一个,就是这样:
里面就写你下载的路径,我自己自定义的路径是 “ /download/ ”,可以参考下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="download/" name="download" />
</paths>
好了,现在是万事俱备,只欠东风了。怎么调用这个方法呢?
调用之前,提醒一句,确保文件的读写权限在AndroidManifest.xml配置好了。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
调用方法如下:
// url服务器地址,saveurl是下载路径,fileName表示的是文件名字
DownloadUtil.get().download(this, url,saveurl,fileName, new DownloadUtil.OnDownloadListener() {
@Override
public void onDownloadSuccess() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(TeacherThesisCommentActivity.this, "下载成功", Toast.LENGTH_SHORT).show();
// 这里的弹框设置了进度条,下同
dialog.dismiss();
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return;
}
File file = new File(Environment.getExternalStorageDirectory().getPath() + "/download/"+fileName);
Log.w(TAG,"路径2:"+file);
try {
Log.w(TAG,"打开");
OpenFileUtils.openFile(mContext, file);
} catch (Exception e) {
Log.w(TAG,"无打开方式");
e.printStackTrace();
}
}
});
}
@Override
public void onDownloading(int progress) {
dialog.setProgress(progress);
}
@Override
public void onDownloadFailed() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(TeacherThesisCommentActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
});
}
});
这里提两句,你可能看到里面有进度条的dialog,是因为在前面我有自定义下载进度条,如果不需要的话可以去掉。要这个效果的,可以看看下面:
//文件在手机内存存储的路径
final String saveurl="/download/";
Log.w(TAG,"路径1:"+saveurl);
//配置progressDialog
final ProgressDialog dialog= new ProgressDialog(TeacherThesisCommentActivity.this);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(true);
dialog.setTitle("正在下载中");
dialog.setMessage("请稍后...");
dialog.setProgress(0);
dialog.setMax(100);
dialog.show();
评论里有小伙伴提醒我漏了一个OpenFileUtils.java文件,这里补上:
import android.content.Context;
import android.content.Intent;
import java.io.File;
public class OpenFileUtils {
/**
* 声明各种类型文件的dataType
**/
private static final String DATA_TYPE_APK = "application/vnd.android.package-archive";
private static final String DATA_TYPE_VIDEO = "video/*";
private static final String DATA_TYPE_AUDIO = "audio/*";
private static final String DATA_TYPE_HTML = "text/html";
private static final String DATA_TYPE_IMAGE = "image/*";
private static final String DATA_TYPE_PPT = "application/vnd.ms-powerpoint";
private static final String DATA_TYPE_EXCEL = "application/vnd.ms-excel";
private static final String DATA_TYPE_WORD = "application/msword";
private static final String DATA_TYPE_CHM = "application/x-chm";
private static final String DATA_TYPE_TXT = "text/plain";
private static final String DATA_TYPE_PDF = "application/pdf";
/**
* 未指定明确的文件类型,不能使用精确类型的工具打开,需要用户选择
*/
private static final String DATA_TYPE_ALL = "*/*";
/**
* 打开文件
* @param mContext
* @param file
*/
public static void openFile(Context mContext, File file) {
if (!file.exists()) {
return;
}
// 取得文件扩展名
String end = file.getName().substring(file.getName().lastIndexOf(".") + 1, file.getName().length()).toLowerCase();
// 依扩展名的类型决定MimeType
switch (end) {
case "3gp":
case "mp4":
openVideoFileIntent(mContext, file);
break;
case "m4a":
case "mp3":
case "mid":
case "xmf":
case "ogg":
case "wav":
openAudioFileIntent(mContext, file);
break;
case "doc":
case "docx":
commonOpenFileWithType(mContext, file, DATA_TYPE_WORD);
break;
case "xls":
case "xlsx":
commonOpenFileWithType(mContext, file, DATA_TYPE_EXCEL);
break;
case "jpg":
case "gif":
case "png":
case "jpeg":
case "bmp":
commonOpenFileWithType(mContext, file, DATA_TYPE_IMAGE);
break;
case "txt":
commonOpenFileWithType(mContext, file, DATA_TYPE_TXT);
break;
case "htm":
case "html":
commonOpenFileWithType(mContext, file, DATA_TYPE_HTML);
break;
case "apk":
commonOpenFileWithType(mContext, file, DATA_TYPE_APK);
break;
case "ppt":
commonOpenFileWithType(mContext, file, DATA_TYPE_PPT);
break;
case "pdf":
commonOpenFileWithType(mContext, file, DATA_TYPE_PDF);
break;
case "chm":
commonOpenFileWithType(mContext, file, DATA_TYPE_CHM);
break;
default:
commonOpenFileWithType(mContext, file, DATA_TYPE_ALL);
break;
}
}
/**
* Android传入type打开文件
* @param mContext
* @param file
* @param type
*/
public static void commonOpenFileWithType(Context mContext, File file, String type) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addCategory(Intent.CATEGORY_DEFAULT);
FileProviderUtils.setIntentDataAndType(mContext, intent, type, file, true);
mContext.startActivity(intent);
}
/**
* Android打开Video文件
* @param mContext
* @param file
*/
public static void openVideoFileIntent(Context mContext, File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("oneshot", 0);
intent.putExtra("configchange", 0);
FileProviderUtils.setIntentDataAndType(mContext, intent, DATA_TYPE_VIDEO, file, false);
mContext.startActivity(intent);
}
/**
* Android打开Audio文件
* @param mContext
* @param file
*/
private static void openAudioFileIntent(Context mContext, File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("oneshot", 0);
intent.putExtra("configchange", 0);
FileProviderUtils.setIntentDataAndType(mContext, intent, DATA_TYPE_AUDIO, file, false);
mContext.startActivity(intent);
}
}