上一篇我們講到了使用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);
}
}