android中使用okhttp實現文件下載

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章