Безперервна промальовування з SurfaceView – РОЗРОБКА ІГОР ДЛЯ ОС ANDROID

Цей пункт – для справжніх чоловіків (і жінок) У ньому йдеться про потоках і всі неприємності, з ними повязаних Але ми вийдемо з неї живими, я обіцяю

Мотивація Коли ми в перший раз здійснювали безперервну промальовування, то робили це неправильно Інтенсивно використовувати користувальницький потік не варто нам необхідно рішення, що робить всю брудну роботу в окремому потоці Зустрічайте SurfaceView

Як натякає його імя, клас SurfaceView успадковується від View і обробляє Surface, ще один клас Android API Що таке Surface Це абстракція необробленого буфера, використовуваного компоновщиком екрану для промальовування певного View Компоновщик екрану – це володар всього рендеринга в Android, відповідальний за передачу всіх пікселів графічному процесору У деяких випадках Surface може бути апаратно прискорений, але ми не будемо сильно замислюватися над цим фактом Все, що нам необхідно знати, що є більш прямий спосіб промальовування елементів на екрані

Наша мета – забезпечити промальовування в окремому потоці, щоб не чіпати потік користувальницького інтерфейсу, зайнятий іншими справами Клас SurfaceVi ew пропонує нам спосіб промальовування в окремому потоці

SurfaceHolder і блокування Щоб малювати SurfaceView з окремого від користувальницького потоку, нам необхідно отримати екземпляр класу Surf aceHolder, приблизно так:

SurfaceHolder – оболонка для Surface, що проводить для нас деякі дії за допомогою двох методів:

Перший метод блокує Surface для промальовування і повертає прекрасний екземпляр Canvas, який ми можемо використовувати Другий метод знову розблокує Surface і перевіряє, що промальовані нами через Canvas контент виведений на екран Ми будемо застосовувати ці два методи в нашому потоці промальовування для отримання Canvas, власне малювання і виведення отриманого зображення на екран Обєкт Canvas, переданий в метод Surf aceHol der unlockAndPost О, повинен бути тим же самим, який ми отримали від SurfaceHolder lockCanvas

Surface не створюється негайно після ініціалізації SurfaceView – це робиться асинхронно Surface буде знищуватися кожен раз при призупинення активності і створюватися знову при її поновленні

Створення та перевірка Surface Ми не можемо отримати Canvas від SurfaceHolder доти, поки Surface не є коректним У нас є можливість перевірити факт його створення за допомогою наступного виразу:

Якщо метод повертає true, ми можемо без побоювання блокувати Surface і малювати в ньому за допомогою отриманого Canvas Нам необхідно бути абсолютно впевненими, що Surface знову розблоковано після виклику Surf aceHolder 1 ockCanvas, інакше наша активність може заблокувати телефон

Всі разом

Отже, як нам тепер обєднати все це з окремим потоком промальовування і життєвим циклом активності Кращий спосіб представити це – подивитися на реальний приклад коду У лістингу 416 представлений повний приклад, що виконує промальовування в окремому потоці за допомогою SurfaceView

Лістинг 416 Активність SurfaceViewTest package combadlogi сandroi dgames

Виглядає лякаюче, чи не так Наша активність містить екземпляр FastRenderView в якості члена Це наш підклас SurfaceView, призначений для обробки всіх потокових операцій та блокування Surface З точки зору активності – це простий View У методі onCreate ми включаємо повноекранний режим, створюємо екземпляр FastRenderView і встановлюємо його в якості контейнера вмісту активності

Крім того, на цей раз ми перевизначають метод onResume О У ньому стартуємо наш потік промальовування непрямим чином – викликаємо метод FastRenderView resume, який робить цю роботу непомітно для нас Це означає, що потік запуститься, коли активність створюється вперше (оскільки слідом за onCreate завжди викликається onResume) Потік також перезапускається при кожному поверненні активності зі стану паузи Безумовно, це має на увазі, що десь потік повинен зупинятися, інакше у нас при кожному виклику onResume буде створюватися ще один новий потік Для цього ми використовуємо метод onPauseO він викликає метод FastRenderView pauset), повністю зупиняє потік Метод не буде закінчувати свою роботу, поки потік дійсно не зупиниться

Погляньмо на ключовий клас цього прикладу: FastRenderView Він схожий на класи RenderView, реалізовані нами у кількох минулих прикладах, тим, що успадковується від іншого класу View У даному випадку ми успадковуємо його безпосередньо від SurfaceView і реалізуємо в ньому інтерфейс Runnable, щоб мати можливість передавати його потоку промальовування і використовувати його логіку

Клас FastRenderView має три члена renderThread – просто посилання на екземпляр Thread, відповідальний за виконання логіки потоку промальовування Мінлива holder – посилання на екземпляр SurfaceHolder, отриманий нами від базового класу Surf aceView Нарешті, член runni ng – простий логічний прапор, який використовується нами для повідомлення потоку промальовування про те, що він повинен зупинитися Модифікатор volatile має спеціальне призначення, про який ми поговоримо трохи пізніше У конструкторі ми лише викликаємо конструктор базового класу і зберігаємо посилання на SurfaceHolder у змінній класу

Тепер приходить час методу FastRenderView resumeC) Він відповідальний за запуск потоку промальовування Зверніть увагу – ми створюємо новий екземпляр потоку кожного разу при виклику цього методу Це відповідає того, що ми говорили про методи активності onResumeO і onPauseC) Крім цього ми встановлюємо прапор running в значення true (трохи пізніше ви побачите, як він використовується в потоці промальовування) Останнє, що слід уточнити, – ми встановили примірник FastRenderVi ew реалізує інтерфейс Runnable, що дозволить виконати наступний метод FastRenderVi ew в цьому новому потоці

Метод FastRenderVi ew run С виконує основну роботу для нашого класу View Його тіло виконується в потоці промальовування Як бачите, він складається з одного циклу, який припиняє своє виконання при установці прапора running в f al se Якщо це відбувається, потік зупиняється і знищується Всередині циклу while ми спочатку перевіряємо валідність Surface і при позитивному результаті блокуємо його, малюємо в ньому і знову розблокуємо (як говорилося раніше) У даному прикладі ми просто заповнюємо весь Surface червоним кольором

Метод FastRenderView pauseC виглядає трохи дивно На його початку ми встановлюємо прапор running рівним false (це сигнал для методу FastRenderViewrunO зупинити обробку і закрити потік) Наступні кілька рядків – очікування повного знищення потоку, здійснюване викликом методу Thread joinO Цей метод чекає знищення потоку, але може також викликати виключення InterruptedExcepti on до того, як потік насправді згине Оскільки нам необхідно бути абсолютно впевненими у знищенні потоку, перш ніж повертатися з цього методу, ми виконуємо Thread joinO в безумовному циклі доти, поки операція не буде завершена успішно

Повернемося до модифікатору volatiе прапора running Для чого він потрібен Причина досить тонка: компілятор може вирішити перевизначити порядок виразів в методі FastRenderViewpauseC, якщо розпізнає відсутність залежності між першим рядком методу і блоком while У нього є таке право, якщо він вважає, що це призведе до прискорення виконання коду Проте в даному випадку порядок команд для нас дуже важливий Уявіть, що ми встановили прапор running після спроби приєднатися до потоку Ми отримаємо нескінченний цикл, оскільки потік ніколи не буде знищений

Модифікатор volatile запобігає цю колізію Будь-які вирази, повязані зі змінними, які мають цим модифікатором, будуть виконані у встановленому порядку Це вберігає нас від прихованої помилки, яку неможливо буде відтворити

Залишилася ще одна річ, про яку ви могли замислитися як про потенційного баге Що відбудеться, якщо Surface буде знищений між викликами SurfaceHolder getSurfaceisVal id і SurfaceHolder lock На щастя, нам пощастило – такого ніколи не станеться Що зрозуміти чому, нам потрібно повернутися до теми життєвого циклу активності і його звязки з Surface

Ми знаємо, що Surface створюється асинхронно Схоже, що наш потік промальовування може виконатися до того, як Surface стане дійсним Захист від цього казусу полягає в тому, що ми не блокуємо Surface до тих пір, поки він не стане дійсним, що вирішує проблему його створення

Причина, по якій код потоку промальовування не викликає помилки при знищенні Surface між перевіркою валідності і блокуванням, повязана з моментом часу, коли Surface знищується Це завжди відбувається після повернення з методу onPause активності І оскільки в цьому методі ми очікуємо знищення потоку за допомогою FastRenderViewpause, потік промальовування вже точно не буде живим при знищенні Surface Здорово, чи не так Однак тут легко можна збитися з пантелику

Тепер ми правильно виконуємо безперервну промальовування Ми не завантажуємо потік користувальницького інтерфейсу, використовуючи замість цього окремий потік для малювання Крім того, ми враховуємо при цьому фази життєвого циклу активності, тому він не працює у фоновому режимі (що береже акумулятор при постановці активності на паузу) Звичайно, нам ще потрібно синхронізувати обробку подій введення в призначеному для користувача потоці з нашим потоком промальовування

Джерело: Mario Zechner / Маріо Цехнер, «Програмування ігор під Android», пров Єгор Сидорович, Євген зазноби, Видавництво «Пітер»

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*