Візуальне редагування розмітки всередині Android-програми
Преамбула
Eclipse і Idea мають власні засоби візуального редагування розмітки Android-програми. NetBeans позбавлений цього щастя. Бажання створити щось подібне для звичної NetBeans порівняно простими засобами призвело до ідеї перенести процес візуального редагування в сам додаток. Резонів тут кілька:
- природний Preview розмітки засобами самого Android;
- можливість автономної роботи без desktop-ої IDE (може бути цікаво, в першу чергу, дизайнерам);
- «Богові - богове, а кесарю - кесареве». При реалізації зручно використовувати вже наявні структури даних View-об'єктів, константи класів тощо...
Як це влаштовано?
Дзеркальне дерево розмітки. Основна ідея - паралельно дереву об'єктів розмітки (View), яке генерує Android при завантаженні Layout, програма створює дзеркальне дерево власних об'єктів (однойменні класи з префіксом Z - Zview, ZButton,...). Кожен з них має посилання на об'єкт - оригінал. Класи мають спадкування, аналогічне View, і підтримують весь функціонал, пов'язаний з обходом дерева, наприклад, при генерації xml-файлів розмітки, створення списків параметрів View. Крім того, дзеркальне дерево є основною структурою даних для опису відкритого Layout: кожен об'єкт містить два важливі параметри - вектор параметрів розмітки для View і дзеркальний об'єкт-описник для LayoutParams.
Параметри View. Початкове різноманіття параметрів View підтримується групою класів - спадкоємців ZParam, кожен з них реалізує всю специфіку зовнішнього і внутрішнього представлення параметрів даного типу, конвертування, виклику діалогу його редагування, визначення відповідності типам значень у скомпільованому файлі. На даний момент реалізовані типи - int, hex (16-річний int), boolean, float, string, charSequence, dimen (розмірності), id (ідентифікатор ресурсу), image, color, enumInt (список іменованих цілих констант), enumString, enumClass (список іменованих цілих класів). В останньому випадку для кожного enum-класу створюється свій похідний клас.
Вектор параметрів для кожного View десеріалізується з відповідного xml-файлу, розміщеного в assets. Щоб спростити процедуру створення таких файлів, у програмі є компонента, яка для всіх View шукає геттери/сеттери і за ними створює опис передбачуваних параметрів. Потім вже ці файли редагуються вручну за документацією.
Параметри розмітки LayoutParams. Для параметрів розмітки подібних файлів опису не існує. Замість цього для кожного View читається LayoutParams (його тип залежить від типу предка), потім за допомогою рефлексії для нього генерується однойменний клас описника - ZLP і його похідні, кожен клас створює вектор параметрів і записує в нього значення, зчитані з відповідного LayoutParams.
Скомпільований xml-файл Layout-а. У файлі Android-програми (apk) xml-файли опису Layout містяться в скомпільованому вигляді. Детальний опис формату є на justanapplication.wordpress.com/category/android/android-binary-xml. Безсумнівні переваги формату - обмежена кількість внутрішніх типів даних для параметрів, достатня простота, щоб пропарсити його доморощеними засобами (наприклад, для перегляду дампа або вилучення параметрів у дзеркальне дерево розмітки). Крім того, внутрішній парсер Android - клас android.content. res. XmlBlock здатний створювати View з байтного масиву, що містить дані в аналогічному форматі.
Зауваження. У самому apk-файлі скомпільовані файли містяться в стисненому вигляді. Однак у нашому випадку це не важливо, оскільки парсер XmlBlock отримує їх вже розархівованими.
Як це працює?
При старті додаток засобами рефлексії опитує Android-класи на предмет збору необхідних констант і формує таблиці у вигляді пар < ім'я-значення >:
- ідентифікатори ресурсів з R.java (окремо за різними типами - підкласами);
- константи класів View;
- R.attr - внутрішні константи імен для всіх параметрів;
- R.styleable - символьні позначення констант, які використовуються в параметрах.
Для всіх типів View з xml-файлів, розміщених в assets, десеріалізуються вектори об'єктів-описувачів параметрів (класи ZParam).
Редагування в загальних рисах відбувається таким чином. Початковий Layout завантажується за допомогою id ресурсу стандартним LayoutInflater. За завантаженим View створюється дзеркальне дерево об'єктів, для кожного View копіюється вихідний вектор параметрів. Потім власний CXmlResourceParser, побудований на основі стандартного android.content.res.XmlResourceParser, читає параметри всіх View і записує їх в об'єкти ZParam. Об'єкти-параметри, значення яких були прочитані з файлу розмітки, позначаються, так що в загальному списку параметрів вони завжди будуть виділені. Обробники подій всіх View налаштовуються на програмний код діалогового вікна редагування.
При зміні будь-яких параметрів власний CXmlParser пише байтний масив у форматі скомпільованого xml-файлу, з якого парсер XmlBlock створює змінений View, змінюючи таким чином картинку Preview. Те ж саме CXmlParser робить при довготривалому збереженні змін до двійкового файлу. При завантаженні зміненого Layout працює та ж сама схема, тільки з іншою парою парсерів: внутрішній android.content. res. XmlBlock для завантаження View і власний CXmlParser - для читання параметрів.
Можна експортувати до звичайного xml-файлу для зовнішнього використання розмітку. Для цього використовуються власні засоби груп класів ZView і ZParam.
Розташування даних. Всі дані розміщені в каталозі GUIWizard на SD-карті. Програма створює каталог з назвою власного пакета. Створені xml-файли відредагованої розмітки записуються туди з іменами Layout-ів. Допоміжні двійкові файли скомпільованої розмітки з розширенням cxml записуються в підкаталог data. Створені файли параметрів для View записуються в каталог проекту з іменами власних класів (наприклад, android.widget.ImceView.xml).
Зауваження. Спочатку передбачалося, що при редагуванні змінені параметри будуть відразу ж записуватися сеттерами у відповідні View, а при їх відсутності - безпосередньо в приватні поля View. Аналогічно, для отримання поточних значень полів передбачалося викликати геттери. Однак надалі виявилося, що є досить багато винятків в однозначності «параметр-поле-сеттер-геттер». Наприклад, один параметр може відповідати кільком мережам, один мережу може приймати декілька параметрів. Тому вирішено було повністю відмовитися від взаємодії об'єктів ZView - View. Замість цього після кожної зміни параметра генерується скомпільований xml-файл розмітки і пишеться в байтний потік (тобто мова не йде про файл в прямому сенсі цього слова). Потім парсер XmlBlock створює по ньому змінений View.
Рефлексія, як без неї?
Програма інтенсивно використовує рефлексію, в тому числі для:
- читання констант імен параметрів Android, ресурсів програми і констант класів View;
- генерації об'єктів груп класів Zview, Zparam і Zlayout залежно від імені класу поточного об'єкта;
- обходу обмежень доступу в деяких класах Android.
Інтеграція з NetBeans
Додаток, вбудований у NetBeans, виконує тривіальну роботу. Якщо ви натиснете додаткову кнопку, ADB переглядає вміст каталогу SDCard/GUIWizard/< ім'я пакета Android-програми >. Назва пакунка виводиться з файла маніфесту поточного проекту, вибраного у вікні проектів. Всі файли з розширенням .xml копіюються до/res/layout і видаляються. Старі версії перейменовуються з розширенням .orig. Вихідники додатка - bitbucket.org/solus_rex/netbeans_guiwizard_plugin
Status quo
Вихідники проекту розміщені на bitbucket.org/solus_rex/android_guiwizard
Для зневаджування парсингу скомпільованого файлу розмітки можна використовувати двійкові файли, що містяться в самій програмі. Для цього необхідно розрархувати apk-файл і скопіювати xml-файли з res/layout в підкаталог GUIWizard/< пакет програми >/data (нагадаємо, що вони містяться там у скомпільованому двійковому форматі). У макеті можна робити побайтне порівняння вихідного двійкового файлу розмітки та файлу, створеного програмою при збігу їх розмірності. Для цього програма при записі вихідного файлу сортує таблицю імен і списки параметрів в тому порядку, в якому вони були в вихідному файлі.
Метою поточного етапу було перевірити основні ідеї та реалізувати їх на макеті. Макет дозволяє:
- переглядати списки констант;
- читати власні логи даних і склів винятків;
- створювати файли опису параметрів View;
- створювати нові порожні Layout;
- переглядати список Layout (у всіх списках короткий клік виконує основну команду, довгий - викликає контекстне меню команд).
Короткий клік у списку Layout викликає його Preview, причому відкривається останній збережений у двійковому файлі, а за його відсутності - ресурс у самій програмі.
Довгий клік піднімає меню команд, виконуваних над Layout. У Preview короткий клік за будь-яким елементом викликає список його параметрів, довгий - повідомлення зі згенерованим xml-тегом. У меню команд «Список View» виводиться список з іменами та типами View, відформатованих за вкладеністю. Короткий клік у цьому списку викликає вже згаданий список параметрів, довгий - піднімає контекстне меню, в якому є команда додавання нового View як нащадка до обраного.
Короткий клік у списку параметрів піднімає діалогове вікно, що відповідає його типу, довгий - список ресурсів, які можна призначити йому.
Відкритим залишається питання додавання нових ресурсів - вони повинні бути зашиті у вихідний проект.



