使用 Android 下载文件,并在 ProgressDialog 中显示进度 [英] Download a file with Android, and showing the progress in a ProgressDialog

查看:23
本文介绍了使用 Android 下载文件,并在 ProgressDialog 中显示进度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个可以更新的简单应用程序.为此,我需要一个简单的函数来下载文件并在 ProgressDialog显示当前进度.我知道如何执行 ProgressDialog,但我不确定如何显示当前进度以及如何首先下载文件.

I am trying to write a simple application that gets updated. For this I need a simple function that can download a file and show the current progress in a ProgressDialog. I know how to do the ProgressDialog, but I'm not sure how to display the current progress and how to download the file in the first place.

推荐答案

下载文件的方法有很多种.下面我将发布最常见的方式;由您决定哪种方法更适合您的应用.

There are many ways to download files. Following I will post most common ways; it is up to you to decide which method is better for your app.

此方法将允许您同时执行一些后台进程并更新 UI(在本例中,我们将更新进度条).

This method will allow you to execute some background processes and update the UI at the same time (in this case, we'll update a progress bar).

进口:

import android.os.PowerManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;

这是一个示例代码:

// 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); //cancel the task
    }
});

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;
    }

上述方法 (doInBackground) 始终在后台线程上运行.你不应该在那里做任何 UI 任务.另一方面,onProgressUpdateonPreExecute 在 UI 线程上运行,因此您可以更改进度条:

The method above (doInBackground) runs always on a background thread. You shouldn't do any UI tasks there. On the other hand, the onProgressUpdate and onPreExecute run on the UI thread, so there you can change the 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();
    }

要运行此程序,您需要 WAKE_LOCK 权限.

For this to run, you need the WAKE_LOCK permission.

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

2.从服务下载

这里的大问题是:如何从服务更新我的活动?.在下一个示例中,我们将使用两个您可能不知道的类:ResultReceiverIntentService.ResultReceiver 是允许我们从服务更新线程的那个;IntentServiceService 的子类,它产生一个线程从那里做后台工作(你应该知道一个 Service 实际上运行在同一个线程中的应用程序;当您扩展 Service 时,您必须手动生成新线程以运行 CPU 阻塞操作).

2. Download from Service

The big question here is: how do I update my activity from a service?. In the next example we are going to use two classes you may not be aware of: ResultReceiver and IntentService. ResultReceiver is the one that will allow us to update our thread from a service; IntentService is a subclass of Service which spawns a thread to do background work from there (you should know that a Service runs actually in the same thread of your app; when you extends Service, you must manually spawn new threads to run CPU blocking operations).

下载服务可以是这样的:

Download service can look like this:

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 {

            //create url and connect
            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());

            String path = "/sdcard/BarcodeScanner-debug.apk" ;
            OutputStream output = new FileOutputStream(path);

            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);
            }

            // close streams 
            output.flush();
            output.close();
            input.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

        Bundle resultData = new Bundle();
        resultData.putInt("progress" ,100);

        receiver.send(UPDATE_PROGRESS, resultData);
    }
}

将服务添加到您的清单:

Add the service to your manifest:

<service android:name=".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 来玩:

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"); //get the progress
            dialog.setProgress(progress);

            if (progress == 100) {
                dialog.dismiss();
            }
        }
    }
}

2.1 使用 Groundy 库

Groundy 是一个库,基本上可以帮助您在一个后台服务,它基于上面显示的 ResultReceiver 概念.该库目前已弃用.这是整个代码的样子:

2.1 Use Groundy library

Groundy is a library that basically helps you run pieces of code in a background service, and it is based on the ResultReceiver concept shown above. This library is deprecated at the moment. This is how the whole code would look like:

您在其中显示对话框的活动...

The activity where you are showing the dialog...

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;
            }
        }
    };
}

Groundy 用于下载文件并显示进度的 GroundyTask 实现:

A GroundyTask implementation used by Groundy to download the file and show the progress:

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;
        }
    }
}

然后将其添加到清单中:

And just add this to the manifest:

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

我觉得再简单不过了.只需从 Github 获取最新的 jar ,您就可以开始了.请记住,Groundy 的主要目的是在后台服务中调用外部 REST api,并轻松地将结果发布到 UI.如果你在你的应用中做类似的事情,它可能真的很有用.

It couldn't be easier I think. Just grab the latest jar from Github and you are ready to go. Keep in mind that Groundy's main purpose is to make calls to external REST apis in a background service and post results to the UI with easily. If you are doing something like that in your app, it could be really useful.

GingerBread 带来了一个新功能,DownloadManager,它允许您轻松下载文件并将处理线程、流等的繁重工作委托给系统.

GingerBread brought a new feature, DownloadManager, which allows you to download files easily and delegate the hard work of handling threads, streams, etc. to the system.

首先来看一个实用的方法:

First, let's see a utility method:

/**
 * @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) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
        return true;
    }
    return false;
}

方法的名称说明了一切.一旦您确定 DownloadManager 可用,您可以执行以下操作:

Method's name explains it all. Once you are sure DownloadManager is available, you can do something like this:

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);

下载进度将显示在通知栏中.

Download progress will be showing in the notification bar.

第一种和第二种方法只是冰山一角.如果您希望您的应用程序健壮,您必须记住很多事情.这是一个简短的列表:

First and second methods are just the tip of the iceberg. There are lots of things you have to keep in mind if you want your app to be robust. Here is a brief list:

  • 您必须检查用户是否有可用的互联网连接
  • 确保您拥有正确的权限(INTERNETWRITE_EXTERNAL_STORAGE);如果您想检查互联网可用性,也可以ACCESS_NETWORK_STATE.
  • 确保您要下载文件的目录存在并且具有写入权限.
  • 如果下载过大,您可能需要实现一种在之前尝试失败的情况下恢复下载的方法.
  • 如果您允许用户中断下载,他们将不胜感激.
  • You must check whether user has an internet connection available
  • Make sure you have the right permissions (INTERNET and WRITE_EXTERNAL_STORAGE); also ACCESS_NETWORK_STATE if you want to check internet availability.
  • Make sure the directory were you are going to download files exist and has write permissions.
  • If download is too big you may want to implement a way to resume the download if previous attempts failed.
  • Users will be grateful if you allow them to interrupt the download.

除非您需要对下载过程进行详细控制,否则可以考虑使用 DownloadManager (3),因为它已经处理了上面列出的大部分项目.

Unless you need detailed control of the download process, then consider using DownloadManager (3) because it already handles most of the items listed above.

但也要考虑到您的需求可能会发生变化.例如,DownloadManager 不缓存响应.它会盲目地多次下载同一个大文件.事后没有简单的方法来修复它.如果您从基本的 HttpURLConnection (1, 2) 开始,那么您只需要添加一个 HttpResponseCache.因此,最初学习基本标准工具的努力可能是一项不错的投资.

But also consider that your needs may change. For example, DownloadManager does no response caching. It will blindly download the same big file multiple times. There's no easy way to fix it after the fact. Where if you start with a basic HttpURLConnection (1, 2), then all you need is to add an HttpResponseCache. So the initial effort of learning the basic, standard tools can be a good investment.

该类在 API 级别 26 中已弃用.ProgressDialog 是一个模态对话框,它阻止用户与应用程序交互.反而使用此类时,您应该使用进度指示器,例如ProgressBar,可以嵌入到您的应用程序的 UI 中.或者,您可以使用通知来通知用户任务的进度.更多详情链接

This class was deprecated in API level 26. ProgressDialog is a modal dialog, which prevents the user from interacting with the app. Instead of using this class, you should use a progress indicator like ProgressBar, which can be embedded in your app's UI. Alternatively, you can use a notification to inform the user of the task's progress. For more details Link

这篇关于使用 Android 下载文件,并在 ProgressDialog 中显示进度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆