четверг, 18 ноября 2010 г.

Анимированный виджет на Android и setImageViewUri

В прошлый раз, обсуждая возможность создания анимированного виджета на Android, я остановился на прискорбном выводе: виджет с динамически генерируемой анимацией создать не получается. Если картинки для анимации сидят в ресурсах - тогда другое дело, функция setImageViewResource отрабатывает бодро. А вот пересылка в remoteview битмапок через setImageViewBitmap по несколько раз в секунду приводит к катастрофическому проседанию FPS и ошибкам FAILED BINDER TRANSACTION. Остался открытым вопрос - что если для обновления виджета применить функцию setImageViewUri? Попробуем.

За основу я взял тот же тестовый проект, что и в прошлый раз - TestAnimatedWidget. Добавил в него третий режим эмуляции анимации:
private enum tmodes {
  MODE_PASS_BITMAP_ID //setImageViewResource
  , MODE_PASS_BITMAP_ITSELF //use setImageViewBitmap
  , MODE_PASS_BITMAP_URI //use setImageViewUri
}; 
 private static String PATH_TO_INTERNAL_FILES; //path to stored bitmaps
Ввел функцию save_all_bitmaps_from_resources_to_application_directory для сохранения битмапок из ресурсов в файлы:
private void save_all_bitmaps_from_resources_to_application_directory(Context context) {
  for (int i = 0; i < WidgetInstanceContent.WHEEL_ANIMATION.length; ++i) {
    Bitmap bmp = WidgetInstanceContent.GetCachedBitmapById(i + 1);
    try {    
      String filename = String.valueOf(i + 1) + ".png";
      FileOutputStream out = context.openFileOutput(filename, Context.MODE_WORLD_READABLE);    
      bmp.compress(Bitmap.CompressFormat.PNG, 100, out);
    } catch (Exception e) {
      e.printStackTrace();
   }
 }
}
Эта функция вызывается в OnUpdate.
Uri uri = Uri.fromFile(context.getFileStreamPath("FILENAME"));
PATH_TO_INTERNAL_FILES = uri.toString();
if (USE_GENERATED_BITMAPS == tmodes.MODE_PASS_BITMAP_URI) {
  save_all_bitmaps_from_resources_to_application_directory(context);
}
В функцию update_widget_view добавил обработку третьего режима эмуляции анимации:
...
} else if (USE_GENERATED_BITMAPS == tmodes.MODE_PASS_BITMAP_URI) { 
//pass URI of bitmap to remote view
  int step = wic.FrameId;
  String s = PATH_TO_INTERNAL_FILES;
  s = s.replace("FILENAME", String.valueOf(step + 1) + ".png");
  try {   
    s = s.replace("file://", "");
    Uri uri = Uri.parse(new File(s).toString());
    remoteViews.setImageViewUri(R.id.bkview, uri);
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
  } catch (Exception ex) {
    Log.e(TAG_LOG, ex.toString());
  }
}
Несколько замечаний по коду. Переменная PATH_TO_INTERNAL_FILES используется для хранения пути к сохраняемым файлам (получить этот путь в update_widget_view не удается, т.к. там нет context). В качестве имени файла в ней указано "FILENAME" - в процессе работы вместо "FILENAME" подставляется реальное имя файла. Префикс "file://" приходится удалять, т.к. с ним setImageViewUri не работает, похоже из-за вот этого бага вылазит ошибка "resolveUri failed on bad bitmap uri...".

Результат

Результат отрицательный. Ошибка FAILED BINDER TRANSACTION вылазить перестала, но анимация выглядит отвратительно: слишком многие кадры пропускаются. Функцию setImageViewUri можно использовать для загрузки больших картинок в виджет, а вот увеличить вызывать ее 10-25 раз в секунду, чтобы сэмулировать анимацию, не получится.

Исходные коды

Обновленную версию примера можно взять в репозитории TestAnimatedWidget

1 комментарий: