android中如何下載文件并顯示下載進度

seannell 10年前發布 | 29K 次閱讀 Android開發 移動開發

 最近開發中遇到需要下載文件的問題,對于一般的下載來說不用考慮斷點續傳,不用考慮多個線程,比如下載一個apk之類的,這篇文章討論的就是這種情形。

這里主要討論三種方式:AsyncTask、Service和使用DownloadManager。

一、使用AsyncTask并在進度對話框中顯示下載進度

這種方式的優勢是你可以在后臺執行下載任務的同時,也可以更新UI(這里我們用progress bar來更新下載進度)

下面的代碼是使用的例子

// declare the dialog as a member field of your activity
ProgressDialog mProgressDialog;
// instantiate it within the onCreate method
mProgressDialog = new ProgressDialog(YourActivity.this);
mProgressDialog.setMessage("A message");
mProgressDialog.setIndeterminate(true);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(true);
// execute this when the downloader must be fired
final DownloadTask downloadTask = new DownloadTask(YourActivity.this);
downloadTask.execute("the url to the file you want to download");
mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
    @Override
    public void onCancel(DialogInterface dialog) {
        downloadTask.cancel(true);
    }
});

DownloadTask繼承自AsyncTask,按照如下框架定義,你需要將代碼中的某些參數替換成你自己的。

// usually, subclasses of AsyncTask are declared inside the activity class.
// that way, you can easily modify the UI thread from here
private class DownloadTask extends AsyncTask<String, Integer, String> {
    private Context context;
    private PowerManager.WakeLock mWakeLock;
    public DownloadTask(Context context) {
        this.context = context;
    }
    @Override
    protected String doInBackground(String... sUrl) {
        InputStream input = null;
        OutputStream output = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(sUrl[0]);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            // expect HTTP 200 OK, so we don't mistakenly save error report
            // instead of the file
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return "Server returned HTTP " + connection.getResponseCode()

                    + " " + connection.getResponseMessage();
        }
        // this will be useful to display download percentage
        // might be -1: server did not report the length
        int fileLength = connection.getContentLength();
        // download the file
        input = connection.getInputStream();
        output = new FileOutputStream("/sdcard/file_name.extension");
        byte data[] = new byte[4096];
        long total = 0;
        int count;
        while ((count = input.read(data)) != -1) {
            // allow canceling with back button
            if (isCancelled()) {
                input.close();
                return null;
            }
            total += count;
            // publishing the progress....
            if (fileLength > 0) // only if total length is known
                publishProgress((int) (total * 100 / fileLength));
            output.write(data, 0, count);
        }
    } catch (Exception e) {
        return e.toString();
    } finally {
        try {
            if (output != null)
                output.close();
            if (input != null)
                input.close();
        } catch (IOException ignored) {
        }
        if (connection != null)
            connection.disconnect();
    }
    return null;
}</pre> <p> </p>

上面的代碼只包含了doInBackground,這是執行后臺任務的代碼塊,不能在這里做任何的UI操作,但是onProgressUpdate和onPreExecute是運行在UI線程中的,所以我們應該在這兩個方法中更新progress bar。

接上面的代碼:

 

@Override
protected void onPreExecute() {
    super.onPreExecute();
    // take CPU lock to prevent CPU from going off if the user
    // presses the power button during download
    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
         getClass().getName());
    mWakeLock.acquire();
    mProgressDialog.show();
}
@Override
protected void onProgressUpdate(Integer... progress) {
    super.onProgressUpdate(progress);
    // if we get here, length is known, now set indeterminate to false
    mProgressDialog.setIndeterminate(false);
    mProgressDialog.setMax(100);
    mProgressDialog.setProgress(progress[0]);
}
@Override
protected void onPostExecute(String result) {
    mWakeLock.release();
    mProgressDialog.dismiss();
    if (result != null)
        Toast.makeText(context,"Download error: "+result, Toast.LENGTH_LONG).show();
    else
        Toast.makeText(context,"File downloaded", Toast.LENGTH_SHORT).show();
}

注意需要添加如下權限:

<uses-permission android:name="android.permission.WAKE_LOCK" />

 

二、在service中執行下載

在service中執行下載任務的麻煩之處在于如何通知activity更新UI。下面的代碼中我們將用ResultReceiver和IntentService來實現下載。ResultReceiver允許我們接收來自service中發出的廣播,IntentService繼承自service,這IntentService中我們開啟一個線程開執行下載任務(service和你的app其實是在一個線程中,因此不想阻塞主線程的話必須開啟新的線程)。

public class DownloadService extends IntentService {
    public static final int UPDATE_PROGRESS = 8344;
    public DownloadService() {
        super("DownloadService");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        String urlToDownload = intent.getStringExtra("url");
        ResultReceiver receiver = (ResultReceiver) intent.getParcelableExtra("receiver");
        try {
            URL url = new URL(urlToDownload);
            URLConnection connection = url.openConnection();
            connection.connect();
            // this will be useful so that you can show a typical 0-100% progress bar
            int fileLength = connection.getContentLength();
            // download the file
            InputStream input = new BufferedInputStream(connection.getInputStream());
            OutputStream output = new FileOutputStream("/sdcard/BarcodeScanner-debug.apk");
            byte data[] = new byte[1024];
            long total = 0;
            int count;
            while ((count = input.read(data)) != -1) {
                total += count;
                // publishing the progress....
                Bundle resultData = new Bundle();
                resultData.putInt("progress" ,(int) (total * 100 / fileLength));
                receiver.send(UPDATE_PROGRESS, resultData);
                output.write(data, 0, count);
            }
            output.flush();
            output.close();
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        Bundle resultData = new Bundle();
        resultData.putInt("progress" ,100);
        receiver.send(UPDATE_PROGRESS, resultData);
    }
}

注冊DownloadService

<service android:name=".DownloadService"/>

activity中這樣調用DownloadService

// initialize the progress dialog like in the first example
// this is how you fire the downloader
mProgressDialog.show();
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("url", "url of the file to download");
intent.putExtra("receiver", new DownloadReceiver(new Handler()));
startService(intent);

使用ResultReceiver接收來自DownloadService的下載進度通知

private class DownloadReceiver extends ResultReceiver{
    public DownloadReceiver(Handler handler) {
        super(handler);
    }
    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        super.onReceiveResult(resultCode, resultData);
        if (resultCode == DownloadService.UPDATE_PROGRESS) {
            int progress = resultData.getInt("progress");
            mProgressDialog.setProgress(progress);
            if (progress == 100) {
                mProgressDialog.dismiss();
            }
        }
    }
}

 

2.1使用 Groundy library

Groundy 可以幫助你在后臺service中運行一些代碼,其實也是基于剛剛用到的 ResultReceiver,下面是使用Groundy的大致代碼:

public class MainActivity extends Activity {
    private ProgressDialog mProgressDialog;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                String url = ((EditText) findViewById(R.id.edit_url)).getText().toString().trim();
                Bundle extras = new Bundler().add(DownloadTask.PARAM_URL, url).build();
                Groundy.create(DownloadExample.this, DownloadTask.class)
                        .receiver(mReceiver)
                        .params(extras)
                        .queue();
                mProgressDialog = new ProgressDialog(MainActivity.this);
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.setCancelable(false);
                mProgressDialog.show();
            }
        });
    }
    private ResultReceiver mReceiver = new ResultReceiver(new Handler()) {
        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            super.onReceiveResult(resultCode, resultData);
            switch (resultCode) {
                case Groundy.STATUS_PROGRESS:
                    mProgressDialog.setProgress(resultData.getInt(Groundy.KEY_PROGRESS));
                    break;
                case Groundy.STATUS_FINISHED:
                    Toast.makeText(DownloadExample.this, R.string.file_downloaded, Toast.LENGTH_LONG);
                    mProgressDialog.dismiss();
                    break;
                case Groundy.STATUS_ERROR:
                    Toast.makeText(DownloadExample.this, resultData.getString(Groundy.KEY_ERROR), Toast.LENGTH_LONG).show();
                    mProgressDialog.dismiss();
                    break;
            }
        }
    };
}

其中GroundyTask的定義如下:

public class DownloadTask extends GroundyTask {  
    public static final String PARAM_URL = "com.groundy.sample.param.url";
    @Override
    protected boolean doInBackground() {
        try {
            String url = getParameters().getString(PARAM_URL);
            File dest = new File(getContext().getFilesDir(), new File(url).getName());
            DownloadUtils.downloadFile(getContext(), url, dest, DownloadUtils.getDownloadListenerForTask(this));
            return true;
        } catch (Exception pokemon) {
            return false;
        }
    }
}

但是請記住要在activity中注冊了相關service才行:

<service android:name="com.codeslap.groundy.GroundyService"/>

 

三、使用DownloadManager

其實這才是解決下載問題的終極方法,因為他使用起來實在是太簡單了。可惜只有在GingerBread 之后才能使用。

先判斷能不能使用DownloadManager:

/**

  • @param context used to check the device version and DownloadManager information
  • @return true if the download manager is available */ public static boolean isDownloadManagerAvailable(Context context) { try {
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
         return false;
     }
     Intent intent = new Intent(Intent.ACTION_MAIN);
     intent.addCategory(Intent.CATEGORY_LAUNCHER);
     intent.setClassName("com.android.providers.downloads.ui", "com.android.providers.downloads.ui.DownloadList");
     List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(intent,
             PackageManager.MATCH_DEFAULT_ONLY);
     return list.size() > 0;
    
    } catch (Exception e) {
     return false;
    
    } }</pre>

    如果能,那么只需要這樣就可以開始下載一個文件了:

    String url = "url you want to download";
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
    request.setDescription("Some descrition");
    request.setTitle("Some title");
    // in order for this if to run, you must use the android 3.2 to compile your app
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
     request.allowScanningByMediaScanner();
     request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    }
    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "name-of-the-file.ext");
    // get download service and enqueue file
    DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
    manager.enqueue(request);

    下載的進度會在消息通知中顯示。

    總結

    前兩種方法需要你考慮的東西很多,除非是你想完全控制下載的整個過程,否則用最后一種比較省事。

 本文由用戶 seannell 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!