Скачайте файл с Android и покажите прогресс в ProgressDialog - PullRequest
1001 голосов
/ 12 июня 2010

Я пытаюсь написать простое приложение, которое обновляется.Для этого мне нужна простая функция, которая может загрузить файл и показать текущий прогресс в ProgressDialog.Я знаю, как сделать ProgressDialog, но я не уверен, как отобразить текущий прогресс и как загрузить файл в первую очередь.

Ответы [ 12 ]

1819 голосов
/ 12 июня 2010

Есть много способов загрузки файлов. После я опубликую наиболее распространенные способы; Вам решать, какой метод лучше подходит для вашего приложения.

1. Используйте AsyncTask и покажите прогресс загрузки в диалоговом окне

Этот метод позволит вам одновременно выполнять некоторые фоновые процессы и обновлять пользовательский интерфейс (в этом случае мы будем обновлять индикатор выполнения).

Это пример кода:

// 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) всегда выполняется в фоновом потоке. Вы не должны делать никаких задач пользовательского интерфейса там. С другой стороны, onProgressUpdate и onPreExecute выполняются в потоке пользовательского интерфейса, поэтому вы можете изменить индикатор выполнения:

    @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.

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

2. Скачать с сервиса

Большой вопрос здесь: как мне обновить свою активность из службы? . В следующем примере мы собираемся использовать два класса, о которых вы можете не знать: ResultReceiver и IntentService. ResultReceiver - это то, что позволит нам обновлять наш поток из службы; IntentService является подклассом Service, который порождает поток для выполнения фоновой работы оттуда (вы должны знать, что Service фактически выполняется в том же потоке вашего приложения; когда вы расширяете Service, вы должны порождать вручную новые потоки для выполнения операций блокировки процессора).

Сервис загрузки может выглядеть так:

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

Добавьте услугу в свой манифест:

<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

Основание - это библиотека, которая в основном помогает запускать фрагменты кода в фоновом сервисе и основана на концепции ResultReceiver, показанной выше. Эта библиотека устарела на данный момент. Вот как будет выглядеть целый код:

Упражнение, в котором вы показываете диалог ...

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 реализация, используемая Groundy для загрузки файла и отображения прогресса:

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

И просто добавьте это в манифест:

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

Думаю, это не может быть проще. Просто возьмите последнюю баночку с Github , и вы готовы к работе. Имейте в виду, что основная цель Groundy - звонить на внешние REST apis в фоновом режиме и легко публиковать результаты в пользовательском интерфейсе. Если вы делаете что-то подобное в своем приложении, это может быть очень полезно.

2,2 Использование https://github.com/koush/ion

3. Используйте DownloadManager класс (GingerBread и новее)

В 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) {

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

Название метода объясняет все это. Если вы уверены, что 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);

Ход загрузки будет отображаться в панели уведомлений.

Заключительные мысли

Первый и второй методы - лишь вершина айсберга. Есть много вещей, которые вы должны иметь в виду, если вы хотите, чтобы ваше приложение было надежным. Вот краткий список:

  • Вы должны проверить, есть ли у пользователя доступ к интернету
  • Убедитесь, что у вас есть необходимые разрешения (INTERNET и WRITE_EXTERNAL_STORAGE); также ACCESS_NETWORK_STATE, если вы хотите проверить доступность интернета.
  • Убедитесь, что каталог, в который вы собираетесь загружать файлы, существует и имеет права на запись.
  • Если загрузка слишком велика, вы можете захотеть реализовать способ возобновить загрузку, если предыдущие попытки не увенчались успехом.
  • Пользователи будут благодарны, если вы позволите им прервать загрузку.

Если вам не требуется подробный контроль процесса загрузки, рассмотрите возможность использования DownloadManager (3), поскольку он уже обрабатывает большинство элементов, перечисленных выше.

Но учтите, что ваши потребности могут измениться.Например, DownloadManager не кэширует ответы .Он будет слепо загружать один и тот же большой файл несколько раз.Там нет простого способа исправить это после факта.Где, если вы начинаете с базового HttpURLConnection (1, 2), тогда все, что вам нужно, это добавить HttpResponseCache.Таким образом, первоначальные усилия по изучению основных стандартных инструментов могут быть хорошим вложением.

Этот класс устарел на уровне API 26. ProgressDialog - это модальное диалоговое окно, которое не позволяет пользователю взаимодействовать с приложением.,Вместо использования этого класса вы должны использовать индикатор прогресса, такой как ProgressBar, который может быть встроен в пользовательский интерфейс вашего приложения.Кроме того, вы можете использовать уведомление, чтобы проинформировать пользователя о ходе выполнения задачи.Для более подробной информации Ссылка

104 голосов
/ 29 августа 2011

Не забудьте добавить разрешения в файл манифеста, если вы собираетесь загружать материалы из Интернета!

<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>
31 голосов
/ 12 июня 2012

Да, приведенный выше код будет работать. Но если вы обновляете свой progressbar в onProgressUpdate из Asynctask и нажимаете кнопку «Назад» или завершаете свою деятельность AsyncTask теряет свою дорожку с вашим пользовательским интерфейсом. И когда вы идетеВернемся к вашей деятельности, даже если загрузка выполняется в фоновом режиме, вы не увидите никаких обновлений на панели прогресса.Поэтому на OnResume() попробуйте запустить поток, подобный runOnUIThread, с помощью задачи таймера, которая обновляет ur progressbar, обновляя значения из AsyncTask рабочего фона.

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

Не забудьте Уничтожить поток, когда ваша активность не видна.

private void destroyRunningThreads() {
    if (background != null) {
        background.interrupt();
        background=null;
    }
}
17 голосов
/ 07 мая 2014

Я бы порекомендовал вам использовать мой проект Netroid , он основан на Volley . Я добавил некоторые функции, такие как обратный вызов нескольких событий, управление загрузкой файлов. Это может помочь.

15 голосов
/ 21 мая 2015

Я изменил класс AsyncTask для обработки создания progressDialog в том же контексте. Я думаю, что следующий код будет более пригодным для повторного использования. (его можно вызвать из любого действия, просто передайте контекст, целевой файл, диалоговое сообщение)

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();
    }
}
8 голосов
/ 01 августа 2014

Я нашел это сообщение в блоге очень полезным, он использует loopJ для загрузки файла, у него есть только одна простая функция, будет полезно для некоторых новых парней Android.

8 голосов
/ 10 февраля 2014

Не забудьте заменить "/ sdcard ..." на новый файл ("/ mnt / sdcard / ..."), иначе вы получите FileNotFoundException

6 голосов
/ 19 сентября 2014

Когда я начинал изучать разработку Android, я понял, что ProgressDialog - это путь. Существует setProgress метод ProgressDialog, который можно вызывать для обновления уровня прогресса при загрузке файла.

Лучшее, что я видел во многих приложениях, это то, что они настраивают атрибуты этого диалогового окна прогресса, чтобы придать диалогу прогресса лучший вид, чем в стандартной версии. Хорошо, когда пользователь увлекается анимацией, такой как лягушка, слон или милые кошки / щенки. Любая анимация в диалоге прогресса привлекает пользователей, и им не хочется долго ждать.

5 голосов
/ 06 мая 2015

Используйте библиотеку запросов Android, это очень круто. Вы можете изменить ее на использование 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  
        } 
    }
});
5 голосов
/ 02 июля 2013

Мой личный совет - использовать Progress Dialog и создавать перед выполнением или начинать с OnPreExecute(), часто публиковать прогресс, если вы используете горизонтальный стиль индикатора выполнения диалогового окна прогресса. Оставшаяся часть заключается в оптимизации алгоритма doInBackground.

...