четверг, 19 января 2012 г.

Android: загрузка файла из сети с отображением прогресса

Хорошо сделанное Android-приложение (кроме всего прочего) не заставляет клиента угадывать что в данный момент происходит "по ту сторону экрана". Приятное и аккуратное приложение показывает при всех продолжительных операциях прогресс-бар, который реализует, как правило, с помощью класса AsyncTask. Давайте посмотрим как правильно использовать этот замечательный инструмент на примере загрузки файла из сети:

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.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.TextView;
 
public class BackFLoaderActivity extends Activity {
 
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Button load = new Button(this);
  load.setText("Load file");
  load.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    downloadFile("http://anjedi.com/api_lib/2.2_level8.jar");
   }
  });
  setContentView(load, new LayoutParams(LayoutParams.WRAP_CONTENT,
    LayoutParams.WRAP_CONTENT));
 }
 

 private void downloadFile(String url) {
  final ProgressDialog progressDialog = new ProgressDialog(this);
 
  new AsyncTask<String, Integer, File>() {
   private Exception m_error = null;
 
   @Override
   protected void onPreExecute() {
    progressDialog.setMessage("Downloading ...");
    progressDialog.setCancelable(false);
    progressDialog.setMax(100);
    progressDialog
      .setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
 
    progressDialog.show();
   }
 
   @Override
   protected File doInBackground(String... params) {
    URL url;
    HttpURLConnection urlConnection;
    InputStream inputStream;
    int totalSize;
    int downloadedSize;
    byte[] buffer;
    int bufferLength;
 
    File file = null;
    FileOutputStream fos = null;
 
    try {
     url = new URL(params[0]);
     urlConnection = (HttpURLConnection) url.openConnection();
 
     urlConnection.setRequestMethod("GET");
     urlConnection.setDoOutput(true);
     urlConnection.connect();
 
     file = File.createTempFile("Mustachify", "download");
     fos = new FileOutputStream(file);
     inputStream = urlConnection.getInputStream();
 
     totalSize = urlConnection.getContentLength();
     downloadedSize = 0;
 
     buffer = new byte[1024];
     bufferLength = 0;
 
     // читаем со входа и пишем в выход, 
     // с каждой итерацией публикуем прогресс
     while ((bufferLength = inputStream.read(buffer)) > 0) {
      fos.write(buffer, 0, bufferLength);
      downloadedSize += bufferLength;
      publishProgress(downloadedSize, totalSize);
     }
 
     fos.close();
     inputStream.close();
 
     return file;
    } catch (MalformedURLException e) {
     e.printStackTrace();
     m_error = e;
    } catch (IOException e) {
     e.printStackTrace();
     m_error = e;
    }
 
    return null;
   }
 
   // обновляем progressDialog
   protected void onProgressUpdate(Integer... values) {
    progressDialog
      .setProgress((int) ((values[0] / (float) values[1]) * 100));
   };
 
   @Override
   protected void onPostExecute(File file) {
    // отображаем сообщение, если возникла ошибка
    if (m_error != null) {
     m_error.printStackTrace();
     return;
    }
    // закрываем прогресс и удаляем временный файл
    progressDialog.hide();
    file.delete();
   }
  }.execute(url);
 }
}

В этом маленьком приложении при нажатии на кнопку запускается загрузка файла, при этом пользователь наблюдает прогресс-бар. Не забудьте добавить в манифест запрос разрешений на доступ к интернет и файловой системе.
Основой примера является метод, который я взял отсюда (и поправил пару ошибок).  
Этот метод принимает url файла, который нужно загрузить, загружает файл, отображая при этом горизонатальный прогресс-бар. При этом прогресс-бар реально показывает какая часть файла в данный момент загружена. По окончании загрузки файл удаляется.

Разберём работу метода подробнее:

Главная часть метода - создание AcyncTask-а и переопределение его методов.
В методе onPreExecute мы запускаем progressDialog, установив предварительно текст сообщения и максимальное значение прогресса: 100%.
В методе doInBackground - выполняем собственно загрузку файла. Файл читаем  из urlConnection порциями по 1024 байт, каждый раз прибавляя размер полученной порции к общему счётчику. Счётчик и общий размер файла передаём при каждой итерации в метод publishProgress, благодаря чему в методе onProgressUpdate мы получаем эти данные и обновляем текущий статус progressDialog-а.
И, наконец, в методе onPostExecute мы прячем диалог и удаляем временный файл.
Особенностью использования AsyncTask-a является способ, как он объявляется и как в него передаются параметры. Типы, которыми параметризуется экземпляр AsyncTask-a определяю по порядку: тип входящего значения, тип параметра, опреляющего прогресс опреации и тип результата фоновой операции (то что возвращает doInBackground и принимает onPostExecute). Кроме того конструктор и чаcть методов AsyncTask-а принимает varargs, т.е. произвольное число параметров, что весьма удобно в некоторых случаях.

понедельник, 16 января 2012 г.

Android: делаем редактор исходного кода с подсветкой синтаксиса

Иногда, когда под рукой только мой верный Android, хочется чего-нибудь почитать. Например, исходники любимого проекта :) Есть минимум десяток приложений, которые отображают файлы исходников с удобной подсветкой синтаксиса, некоторые из них ещё и позволяют редактировать файлы. Вот мне и стало и нтересно, насколько сложно реализуется такой функционал. Оказалось - очень просто.
 
Итак, для приготовления "блокнота с подсветкой синтаксиса" в Android нам понадобится:
  1. Хорошая библиотека для подсветки синтаксиса на JavaScript+CSS. Можно выбрать тут, мне больше всего понравилась CodeMirror, её и будем использовать. 
  2. WebView для интеграции всего этого богатства с нашим приложением
  3. Немного кода, для взаимодействия с WebView и работающим в нём JavaScript.