Приложение для Андроид (Froyo, но также воспроизводится и на Gingerbread, т.е. встречается на API версий 8-9), по событию читает страницу в интернете и отображает ее в виджете. Вроде вся магия простая как в мультфильмах, но почему-то картинки в виджете в одних случаях показываются, в других - не показываются. Стали разбираться.
Проблемы начинаются после перехода на девайсы с большим разрешением экрана - это стало понятно сразу. Лог - чистый и красивый, за исключением одной строки:
JavaBinder "!!! Failed binder transaction !!!"
Ясно, что кто-то куда-то не может прицепиться, из-за чего система выкидывает ошибку и перестает апдейтить процесс. Но что куда не может прицепиться и по какой причине - оставалось загадкой.
Указанную выше ошибку выкидывает JavaBinder, у которого платформой ограничены размеры транзакций (что-то около 100 кб). Ошибка возникает, если между двумя отдельно взятыми процессами единовременно передается большой (т.е. гораздо больше размера транзакции) кусок информации. В нашем случае, колдунство предполагает создание скриншота веб-страницы в одном процессе и передача его для обновления в виджет. То есть прямо по учебнику.
Ключем к пониманию этой магии послужила смена разрешения экранов. Для того, чтобы сохранить отображаемую веб-страницу в качестве картинки, нам нужно воспользоваться контроллом WebView (переопределив его PictureListener). Получаемая на выходе картинка представляет собой загруженную и отображеную в контролле веб-страницу. Линейные размеры картинки не отличаются в зависимости от количества пукселей на дисплее девайса, однако при отображении в WebView или в виджете размер конечного файла может сильно отличаться.
Например, нам надо отобразить скриншот веб-страницы размером 600х600 пукселей на экране разрешением 300х300 (таких экранов на рынок не выпускают, это для примера). Очевидно, что в этом случае требуется вмешательство волшебства, уменьшающего реальный размер картинки в четыре раза (каждую сторону - в два раза). Понятно, что для экранов разрешением 600х600 уменьшать ничего не придется и размер картинки будет в 4 раза большим. Поэтому для относительно небольших экранов картинки удачно обрабатываются транзакциями, в то время как для больших экранов переброска объемных данных вызывает ошибки в транзакциях: виртуальная машина выбрасывает исключение и перестает обновлять процесс, от этого виджет хоть и существует в памяти, на самом деле не обновляется (в нашем случае он оставался невидимым, но это зависит только от лэйаута).
Самый простой способ предотвратить слишком большой размер файла - принудительное уменьшение размера файла:
public static Bitmap makeBitmap() { Bitmap bitmap = null; BitmapFactory.Options bmOp = new BitmapFactory.Options(); String filePath = "foo/bar/baz"; // Здесь указываем, во сколько раз нужно // уменьшить каждую сторону картинки bmOp.inSampleSize = 2; bitmap = BitmapFactory.decodeFile(filePath, bmOp); return bitmap; }
NB. Рекомендую использовать четные числа для семплирования (в теоретическом разделе интернета всем всегда рады).
Однако такой колдунство не всегда прокатывает по двум причинам:
1. Не всегда понятно, во сколько раз нужно уменьшать размеры
2. Уменьшенная картинка - это уменьшенная картинка, т.е. если вы (или ваш заказчик в лице Докомо) придумали показывать такую картинку на весь экран девайса - картика будет в 2 (3, 4, 5 и т.д.) раз меньше размера экрана.
Может, причин сущетсвует еще больше (практически уверен).
Поэтому сейчас колдунство следует совместить с алхимией. Для этого нужно полученную картинку сначала явно уменьшить до какого-нибудь приемлимого значения, а потом вписать уменьшенную картинку в нужную область на Canvas'е:
public static void makeBitmap(Picture picture) { Context con = getApplicationContext(); Resources r = con.getResources(); DisplayMetrics me = r.getDisplayMetrics(); float screenWidth = me.widthPixels; float screenHeight = me.heightPixels; float screenScale; Bitmap b; Canvas c; int newPicWidth, newPicHeight; // Смотрим, во сколько ширина дисплея больше высоты screenScale = screenWidth / screenHeight; // Устанавливаем размер картинки на 80% от размера экрана // При разрешении 854х600 ошибок переполнения буффера транзакции // возникать не должно, но если что - число можно подрегулировать newPicWidth = (int)(screenWidth * 0.8); newPicHeight = (int)(newPicWidth / screenScale * 0.8); // Создаем битмап и холст для него b = Bitmap.createBitmap(newPicWidth, newPicHeight, Bitmap.Config.RGB_565); c = new Canvas(b); Rect dst = new Rect(); dst.top = 0; dst.left = 0; dst.right = newPicWidth; dst.bottom = picHeight; // Рисуем картинку на битмап // Картинка растягивается или ужимается при необходимости c.drawPicture(picture, dst); // После этого можем сохранить битмап как файл // или открыть его в виджете }