简体   繁体   English

使用 Android 下载文件,并在 ProgressDialog 中显示进度

[英]Download a file with Android, and showing the progress in a 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 .为此,我需要一个可以下载文件并在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.我知道如何进行ProgressDialog ,但我不确定如何显示当前进度以及如何首先下载文件。

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.由您决定哪种方法更适合您的应用程序。

  1. Use AsyncTask and show the download progress in a dialog =============================================================使用AsyncTask并在对话框中显示下载进度 ========================================= ======================

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).此方法将允许您执行一些后台进程并同时更新 UI(在这种情况下,我们将更新进度条)。

Imports:进口:

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

This is an example code:这是一个示例代码:

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

The AsyncTask will look like this: 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;
    }

The method above ( doInBackground ) runs always on a background thread.上面的方法( doInBackground )总是在后台线程上运行。 You shouldn't do any UI tasks there.你不应该在那里做任何 UI 任务。 On the other hand, the onProgressUpdate and onPreExecute run on the UI thread, so there you can change the progress bar:另一方面, onProgressUpdateonPreExecute在 UI 线程上运行,因此您可以更改进度条:

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

For this to run, you need the WAKE_LOCK permission.为此,您需要 WAKE_LOCK 权限。

<uses-permission android:name="android.permission.WAKE_LOCK" />
  1. 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 .在下一个示例中,我们将使用您可能不知道的两个类: ResultReceiverIntentService ResultReceiver is the one that will allow us to update our thread from a service; ResultReceiver允许我们从服务更新我们的线程; 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). IntentServiceService的一个子类,它产生一个线程来从那里做后台工作(你应该知道一个Service实际上在你的应用程序的同一个线程中运行;当你扩展Service时,你必须手动产生新线程来运行 CPU 阻塞操作) .

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"/>

And the activity will look like this:活动将如下所示:

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

Here is were ResultReceiver comes to play:这是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 Use Groundy library 2.1 使用 Groundy 库

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. Groundy是一个库,基本上可以帮助您在后台服务中运行代码片段,它基于上面显示的ResultReceiver概念。 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;
            }
        }
    };
}

A GroundyTask implementation used by Groundy to download the file and show the progress: GroundyTask用于下载文件并显示进度的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;
        }
    }
}

And just add this to the manifest:只需将其添加到清单中:

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

It couldn't be easier I think.我想这再简单不过了。 Just grab the latest jar from Github and you are ready to go.只需从 Github获取最新的 jar,就可以开始使用了。 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.请记住, Groundy的主要目的是在后台服务中调用外部 REST API,并将结果轻松发布到 UI。 If you are doing something like that in your app, it could be really useful.如果你在你的应用程序中做类似的事情,它可能真的很有用。

2.2 Use https://github.com/koush/ion 2.2 使用https://github.com/koush/ion

  1. Use DownloadManager class ( GingerBread and newer only) =============================================================使用DownloadManager类(仅限GingerBread和更新版本)========================================= =====================

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

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

Method's name explains it all.方法的名称说明了一切。 Once you are sure DownloadManager is available, you can do something like this:一旦您确定DownloadManager可用,您可以执行以下操作:

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.下载进度将显示在通知栏中。

Final thoughts最后的想法

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:这是一个简短的列表:

  • You must check whether user has an internet connection available您必须检查用户是否有可用的互联网连接
  • Make sure you have the right permissions ( INTERNET and WRITE_EXTERNAL_STORAGE );确保您拥有正确的权限( INTERNETWRITE_EXTERNAL_STORAGE ); also ACCESS_NETWORK_STATE if you want to check internet availability.如果您想检查互联网可用性,还可以访问ACCESS_NETWORK_STATE
  • 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.如果您允许他们中断下载,用户将不胜感激。

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 (3),因为它已经处理了上面列出的大部分项目。

But also consider that your needs may change.但也要考虑到您的需求可能会发生变化。 For example, DownloadManager does no response caching .例如, DownloadManager没有响应缓存 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 .如果您从基本的HttpURLConnection (1, 2) 开始,那么您只需要添加一个HttpResponseCache So the initial effort of learning the basic, standard tools can be a good investment.因此,学习基本的标准工具的初步努力可能是一项不错的投资。

This class was deprecated in API level 26. ProgressDialog is a modal dialog, which prevents the user from interacting with the app.此类在 API 级别 26 中已弃用。ProgressDialog 是一个模式对话框,可防止用户与应用程序交互。 Instead of using this class, you should use a progress indicator like ProgressBar, which can be embedded in your app's UI.您应该使用 ProgressBar 之类的进度指示器,而不是使用此类,它可以嵌入到应用程序的 UI 中。 Alternatively, you can use a notification to inform the user of the task's progress.或者,您可以使用通知来通知用户任务的进度。 For more details Link更多详情链接

Don't forget to add permissions to your manifest file if you're gonna be downloading stuff from the internet!如果您要从 Internet 下载内容,请不要忘记为您的清单文件添加权限!

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.helloandroid"
    android:versionCode="1"
    android:versionName="1.0">

        <uses-sdk android:minSdkVersion="10" />

        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
        <uses-permission android:name="android.permission.INTERNET"></uses-permission>
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
        <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>

        <application 
            android:icon="@drawable/icon" 
            android:label="@string/app_name" 
            android:debuggable="true">

        </application>

</manifest>

Yes the code above will work .But if you are updating your progressbar in onProgressUpdate of Asynctask and you press back button or finish your activity AsyncTask looses its track with your UI .And when you go back to your activity, even if download is running in background you will see no update on progressbar.是的,上面的代码可以工作。但是如果你在AsynctaskonProgressUpdate中更新你的progressbar条并且你按下后退按钮或完成你的活动AsyncTask会在你的 UI 上失去它的轨道。当你回到你的活动时,即使下载正在运行背景您将在进度条上看到没有更新。 So on OnResume() try to run a thread like runOnUIThread with a timer task that updates ur progressbar with values updating from the AsyncTask running background.所以在OnResume()上尝试运行一个像runOnUIThread这样的线程,它有一个定时器任务,它使用从AsyncTask运行后台更新的值来更新你的progressbar条。

private void updateProgressBar(){
    Runnable runnable = new updateProgress();
    background = new Thread(runnable);
    background.start();
}

public class updateProgress implements Runnable {
    public void run() {
        while(Thread.currentThread()==background)
            //while (!Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(1000); 
                Message msg = new Message();
                progress = getProgressPercentage();        
                handler.sendMessage(msg);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (Exception e) {
        }
    }
}

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        progress.setProgress(msg.what);
    }
};

Don't forget to Destroy the thread when ur activity is not visible.当您的活动不可见时,不要忘记销毁线程。

private void destroyRunningThreads() {
    if (background != null) {
        background.interrupt();
        background=null;
    }
}

I'd recommend you to use my Project Netroid , It's based on Volley .我建议你使用我的 Project Netroid ,它基于Volley I have added some features to it such as multi-events callback, file download management.我为其添加了一些功能,例如多事件回调,文件下载管理。 This could be of some help.这可能会有所帮助。

I have modified AsyncTask class to handle creation of progressDialog at the same context .I think following code will be more reusable.我已经修改了AsyncTask类来处理在同一上下文中创建progressDialog 。我认为下面的代码将更可重用。 (it can be called from any activity just pass context,target File,dialog message) (它可以从任何活动中调用,只需传递上下文、目标文件、对话框消息)

public static class DownloadTask extends AsyncTask<String, Integer, String> {
    private ProgressDialog mPDialog;
    private Context mContext;
    private PowerManager.WakeLock mWakeLock;
    private File mTargetFile;
    //Constructor parameters :
    // @context (current Activity)
    // @targetFile (File object to write,it will be overwritten if exist)
    // @dialogMessage (message of the ProgresDialog)
    public DownloadTask(Context context,File targetFile,String dialogMessage) {
        this.mContext = context;
        this.mTargetFile = targetFile;
        mPDialog = new ProgressDialog(context);

        mPDialog.setMessage(dialogMessage);
        mPDialog.setIndeterminate(true);
        mPDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mPDialog.setCancelable(true);
        // reference to instance to use inside listener
        final DownloadTask me = this;
        mPDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                me.cancel(true);
            }
        });
        Log.i("DownloadTask","Constructor done");
    }

    @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();
            }
            Log.i("DownloadTask","Response " + connection.getResponseCode());

            // 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(mTargetFile,false);

            byte data[] = new byte[4096];
            long total = 0;
            int count;
            while ((count = input.read(data)) != -1) {
                // allow canceling with back button
                if (isCancelled()) {
                    Log.i("DownloadTask","Cancelled");
                    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;
    }
    @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) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                getClass().getName());
        mWakeLock.acquire();

        mPDialog.show();

    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        super.onProgressUpdate(progress);
        // if we get here, length is known, now set indeterminate to false
        mPDialog.setIndeterminate(false);
        mPDialog.setMax(100);
        mPDialog.setProgress(progress[0]);

    }

    @Override
    protected void onPostExecute(String result) {
        Log.i("DownloadTask", "Work Done! PostExecute");
        mWakeLock.release();
        mPDialog.dismiss();
        if (result != null)
            Toast.makeText(mContext,"Download error: "+result, Toast.LENGTH_LONG).show();
        else
            Toast.makeText(mContext,"File Downloaded", Toast.LENGTH_SHORT).show();
    }
}

我发现这篇博文很有帮助,它使用 loopJ 来下载文件,它只有一个简单的功能,对一些新的 android 人会有帮助。

不要忘记用新文件(“/mnt/sdcard/...”)替换“/sdcard...”,否则您将收到FileNotFoundException

While I was starting to learn android development, I had learnt that ProgressDialog is the way to go.当我开始学习 android 开发时,我了解到ProgressDialog是要走的路。 There is the setProgress method of ProgressDialog which can be invoked to update the progress level as the file gets downloaded. ProgressDialogsetProgress方法可以在文件下载时被调用以更新进度级别。

The best I have seen in many apps is that they customize this progress dialog's attributes to give a better look and feel to the progress dialog than the stock version.我在许多应用程序中看到的最好的一点是,它们自定义了此进度对话框的属性,以便为进度对话框提供比普通版本更好的外观和感觉。 Good to keeping the user engaged with some animation of like frog, elephant or cute cats/puppies.很好地让用户参与一些像青蛙、大象或可爱的猫/小狗这样的动画。 Any animation with in the progress dialog attracts users and they don't feel like being kept waiting for long.进度对话框中的任何动画都会吸引用户,他们不会觉得等待太久。

We can use the coroutine and work manager for downloading files in kotlin.我们可以使用协程和工作管理器在 kotlin 中下载文件。

Add a dependency in build.gradle在 build.gradle 中添加依赖

    implementation "androidx.work:work-runtime-ktx:2.3.0-beta01"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"

WorkManager class WorkManager 类

    import android.content.Context
    import android.os.Environment
    import androidx.work.CoroutineWorker
    import androidx.work.WorkerParameters
    import androidx.work.workDataOf
    import com.sa.chat.utils.Const.BASE_URL_IMAGE
    import com.sa.chat.utils.Constants
    import kotlinx.coroutines.delay
    import java.io.BufferedInputStream
    import java.io.File
    import java.io.FileOutputStream
    import java.net.URL

    class DownloadMediaWorkManager(appContext: Context, workerParams: WorkerParameters)
        : CoroutineWorker(appContext, workerParams) {

        companion object {
            const val WORK_TYPE = "WORK_TYPE"
            const val WORK_IN_PROGRESS = "WORK_IN_PROGRESS"
            const val WORK_PROGRESS_VALUE = "WORK_PROGRESS_VALUE"
        }

        override suspend fun doWork(): Result {

            val imageUrl = inputData.getString(Constants.WORK_DATA_MEDIA_URL)
            val imagePath = downloadMediaFromURL(imageUrl)

            return if (!imagePath.isNullOrEmpty()) {
                Result.success(workDataOf(Constants.WORK_DATA_MEDIA_URL to imagePath))
            } else {
                Result.failure()
            }
        }

        private suspend fun downloadMediaFromURL(imageUrl: String?): String? {

            val file = File(
                    getRootFile().path,
                    "IMG_${System.currentTimeMillis()}.jpeg"
            )

            val url = URL(BASE_URL_IMAGE + imageUrl)
            val connection = url.openConnection()
            connection.connect()

            val lengthOfFile = connection.contentLength
            // download the file
            val input = BufferedInputStream(url.openStream(), 8192)
            // Output stream
            val output = FileOutputStream(file)

            val data = ByteArray(1024)
            var total: Long = 0
            var last = 0

            while (true) {

                val count = input.read(data)
                if (count == -1) break
                total += count.toLong()

                val progress = (total * 100 / lengthOfFile).toInt()

                if (progress % 10 == 0) {
                    if (last != progress) {
                        setProgress(workDataOf(WORK_TYPE to WORK_IN_PROGRESS,
                                WORK_PROGRESS_VALUE to progress))
                    }
                    last = progress
                    delay(50)
                }
                output.write(data, 0, count)
            }

            output.flush()
            output.close()
            input.close()

            return file.path

        }

        private fun getRootFile(): File {

            val rootDir = File(Environment.getExternalStorageDirectory().absolutePath + "/AppName")

            if (!rootDir.exists()) {
                rootDir.mkdir()
            }

            val dir = File("$rootDir/${Constants.IMAGE_FOLDER}/")

            if (!dir.exists()) {
                dir.mkdir()
            }
            return File(dir.absolutePath)
        }
    }

Start downloading through work manager in activity class在活动课通过工作管理器开始下载

 private fun downloadImage(imagePath: String?, id: String) {

            val data = workDataOf(WORK_DATA_MEDIA_URL to imagePath)
            val downloadImageWorkManager = OneTimeWorkRequestBuilder<DownloadMediaWorkManager>()
                    .setInputData(data)
                    .addTag(id)
                    .build()

            WorkManager.getInstance(this).enqueue(downloadImageWorkManager)

            WorkManager.getInstance(this).getWorkInfoByIdLiveData(downloadImageWorkManager.id)
                    .observe(this, Observer { workInfo ->

                        if (workInfo != null) {
                            when {
                                workInfo.state == WorkInfo.State.SUCCEEDED -> {
                                    progressBar?.visibility = View.GONE
                                    ivDownload?.visibility = View.GONE
                                }
                                workInfo.state == WorkInfo.State.FAILED || workInfo.state == WorkInfo.State.CANCELLED || workInfo.state == WorkInfo.State.BLOCKED -> {
                                    progressBar?.visibility = View.GONE
                                    ivDownload?.visibility = View.VISIBLE
                                }
                                else -> {
                                    if(workInfo.progress.getString(WORK_TYPE) == WORK_IN_PROGRESS){
                                        val progress = workInfo.progress.getInt(WORK_PROGRESS_VALUE, 0)
                                        progressBar?.visibility = View.VISIBLE
                                        progressBar?.progress = progress
                                        ivDownload?.visibility = View.GONE

                                    }
                                }
                            }
                        }
                    })

        }

My personal advice is to use Progress Dialog and build up before execution , or initiate at OnPreExecute() , publish progress often if you use horizontal style of progress bar of the progress dialog.我个人的建议是使用Progress Dialog并在执行之前构建,或者在OnPreExecute()启动,如果您使用进度对话框的进度条的水平样式,请经常发布进度。 The remaining part is to optimize the algorithm of doInBackground .剩下的部分是优化doInBackground的算法。

Use Android Query library, very cool indeed.You can change it to use ProgressDialog as you see in other examples, this one will show progress view from your layout and hide it after completion.使用 Android Query 库,确实很酷。您可以将其更改为使用ProgressDialog ,就像您在其他示例中看到的那样,这个将从您的布局中显示进度视图并在完成后将其隐藏。

File target = new File(new File(Environment.getExternalStorageDirectory(), "ApplicationName"), "tmp.pdf");
new AQuery(this).progress(R.id.progress_view).download(_competition.qualificationScoreCardsPdf(), target, new AjaxCallback<File>() {
    public void callback(String url, File file, AjaxStatus status) {
        if (file != null) {
            // do something with file  
        } 
    }
});

Permissions权限

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

Using HttpURLConnection使用HttpURLConnection

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

public class DownloadFileUseHttpURLConnection extends Activity {

ProgressBar pb;
Dialog dialog;
int downloadedSize = 0;
int totalSize = 0;
TextView cur_val;
String dwnload_file_path =  
"http://coderzheaven.com/sample_folder/sample_file.png";
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button b = (Button) findViewById(R.id.b1);
    b.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
             showProgress(dwnload_file_path);

                new Thread(new Runnable() {
                    public void run() {
                         downloadFile();
                    }
                  }).start();
        }
    });
}

void downloadFile(){

    try {
        URL url = new URL(dwnload_file_path);
        HttpURLConnection urlConnection = (HttpURLConnection)   
 url.openConnection();

        urlConnection.setRequestMethod("GET");
        urlConnection.setDoOutput(true);

        //connect
        urlConnection.connect();

        //set the path where we want to save the file           
        File SDCardRoot = Environment.getExternalStorageDirectory(); 
        //create a new file, to save the downloaded file 
        File file = new File(SDCardRoot,"downloaded_file.png");

        FileOutputStream fileOutput = new FileOutputStream(file);

        //Stream used for reading the data from the internet
        InputStream inputStream = urlConnection.getInputStream();

        //this is the total size of the file which we are downloading
        totalSize = urlConnection.getContentLength();

        runOnUiThread(new Runnable() {
            public void run() {
                pb.setMax(totalSize);
            }               
        });

        //create a buffer...
        byte[] buffer = new byte[1024];
        int bufferLength = 0;

        while ( (bufferLength = inputStream.read(buffer)) > 0 ) {
            fileOutput.write(buffer, 0, bufferLength);
            downloadedSize += bufferLength;
            // update the progressbar //
            runOnUiThread(new Runnable() {
                public void run() {
                    pb.setProgress(downloadedSize);
                    float per = ((float)downloadedSize/totalSize) *     
                    100;
                    cur_val.setText("Downloaded " + downloadedSize +  

                    "KB / " + totalSize + "KB (" + (int)per + "%)" );
                }
            });
        }
        //close the output stream when complete //
        fileOutput.close();
        runOnUiThread(new Runnable() {
            public void run() {
                // pb.dismiss(); // if you want close it..
            }
        });         

    } catch (final MalformedURLException e) {
        showError("Error : MalformedURLException " + e);        
        e.printStackTrace();
    } catch (final IOException e) {
        showError("Error : IOException " + e);          
        e.printStackTrace();
    }
    catch (final Exception e) {
        showError("Error : Please check your internet connection " +  
e);
    }       
}

void showError(final String err){
    runOnUiThread(new Runnable() {
        public void run() {
            Toast.makeText(DownloadFileDemo1.this, err,  
      Toast.LENGTH_LONG).show();
        }
    });
}

void showProgress(String file_path){
    dialog = new Dialog(DownloadFileDemo1.this);
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.setContentView(R.layout.myprogressdialog);
    dialog.setTitle("Download Progress");

    TextView text = (TextView) dialog.findViewById(R.id.tv1);
    text.setText("Downloading file from ... " + file_path);
    cur_val = (TextView) dialog.findViewById(R.id.cur_pg_tv);
    cur_val.setText("Starting download...");
    dialog.show();

     pb = (ProgressBar)dialog.findViewById(R.id.progress_bar);
     pb.setProgress(0);
            pb.setProgressDrawable(
      getResources().getDrawable(R.drawable.green_progress));  
  }
}

I am adding another answer for other solution I am using now because Android Query is so big and unmaintained to stay healthy.我正在为我现在使用的其他解决方案添加另一个答案,因为 Android Query 太大且无法保持健康。 So i moved to this https://github.com/amitshekhariitbhu/Fast-Android-Networking .所以我搬到了这个https://github.com/amitshekhariitbhu/Fast-Android-Networking

    AndroidNetworking.download(url,dirPath,fileName).build()
      .setDownloadProgressListener(new DownloadProgressListener() {
        public void onProgress(long bytesDownloaded, long totalBytes) {
            bar.setMax((int) totalBytes);
            bar.setProgress((int) bytesDownloaded);
        }
    }).startDownload(new DownloadListener() {
        public void onDownloadComplete() {
            ...
        }

        public void onError(ANError error) {
            ...
        }
    });

You can observer the progress of the download manager using LiveData and coroutines, see the gist below您可以使用 LiveData 和协程观察下载管理器的进度,请参阅下面的要点

https://gist.github.com/FhdAlotaibi/678eb1f4fa94475daf74ac491874fc0e https://gist.github.com/FhdAlotaibi/678eb1f4fa94475daf74ac491874fc0e

data class DownloadItem(val bytesDownloadedSoFar: Long = -1, val totalSizeBytes: Long = -1, val status: Int)

class DownloadProgressLiveData(private val application: Application, private val requestId: Long) : LiveData<DownloadItem>(), CoroutineScope {

    private val downloadManager by lazy {
        application.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    }

    private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.IO + job

    override fun onActive() {
        super.onActive()
        launch {
            while (isActive) {
                val query = DownloadManager.Query().setFilterById(requestId)
                val cursor = downloadManager.query(query)
                if (cursor.moveToFirst()) {
                    val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
                    Timber.d("Status $status")
                    when (status) {
                        DownloadManager.STATUS_SUCCESSFUL,
                        DownloadManager.STATUS_PENDING,
                        DownloadManager.STATUS_FAILED,
                        DownloadManager.STATUS_PAUSED -> postValue(DownloadItem(status = status))
                        else -> {
                            val bytesDownloadedSoFar = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
                            val totalSizeBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
                            postValue(DownloadItem(bytesDownloadedSoFar.toLong(), totalSizeBytes.toLong(), status))
                        }
                    }
                    if (status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED)
                        cancel()
                } else {
                    postValue(DownloadItem(status = DownloadManager.STATUS_FAILED))
                    cancel()
                }
                cursor.close()
                delay(300)
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        job.cancel()
    }

}

Important重要的

AsyncTask is deprecated in Android 11. AsyncTask 在 Android 11 中已弃用。

For more information please checkout following posts欲了解更多信息,请查看以下帖子

Probably should move to concorency Framework as suggested by google可能应该按照谷歌的建议转移到一致性框架

    private class DownloadTask extends AsyncTask<String, Integer, String> {

        private PowerManager.WakeLock mWakeLock;
        String onlinePathBundle, onlinePathMusic, offlinePathBundle, offlinePathMusic;

        CircleProgressBar progressBar;
        RelativeLayout rl_progress;
        ImageView btn_download;
        TextView tv_progress;

        public DownloadTask(String onlinePathBundle, String onlinePathMusic, String offlinePathBundle, String offlinePathMusic, CircleProgressBar progressBar, RelativeLayout rl_progress, ImageView btn_download,
                            TextView tv_progress) {
            this.offlinePathBundle = offlinePathBundle;
            this.offlinePathMusic = offlinePathMusic;
            this.progressBar = progressBar;
            this.rl_progress = rl_progress;
            this.btn_download = btn_download;
            this.tv_progress = tv_progress;
            this.onlinePathBundle = onlinePathBundle;
            this.onlinePathMusic = onlinePathMusic;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressBar.setMax(100);
            rl_progress.setVisibility(View.VISIBLE);
            btn_download.setVisibility(View.GONE);

            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
            mWakeLock.acquire();
        }

        @Override
        protected String doInBackground(String... sUrl) {
            String firstResult = downTask(onlinePathBundle, 1, offlinePathBundle);
            String seondResult = downTask(onlinePathMusic, 2, offlinePathMusic);
            if (firstResult != null) {
                Toast.makeText(SplashActivity.this, "Download error: " + firstResult, Toast.LENGTH_LONG).show();
                return firstResult;
            } else {
                return seondResult;
            }

        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            progressBar.setProgress(progress[0]);
            tv_progress.setText(progress[0] + "%");

        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            rl_progress.setVisibility(View.GONE);
            btn_download.setVisibility(View.VISIBLE);
            if (result != null) {
                Toast.makeText(SplashActivity.this, "Download error: " + result, Toast.LENGTH_LONG).show();
            } else {
                rl_progress.setVisibility(View.GONE);
                btn_download.setVisibility(View.GONE);
            }

        }

        public String downTask(String onlineNewPath, int numberOfTask, String offlineNewPath) {
            InputStream input = null;
            OutputStream output = null;
            HttpURLConnection connection = null;
            try {
                URL url = new URL(onlineNewPath);
                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(offlineNewPath);

                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
                        if (numberOfTask == 1) {
                            publishProgress((int) (total * 50 / fileLength));
                        } else if (numberOfTask == 2) {
                            publishProgress(((int) (total * 50 / fileLength)) + 50);
                        }

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM