Что такое мобильные технологии и Android?
В последнее время большую популярность приобрели мобильные устройства, и сегодня мы имеем три основных ОС для работы с мобильными устройствами: Android, Apple iOS и Microsoft Windows Phone. При этом подавляющее большинство устройств работает под управлением Android.
В качестве основной среды разработки под Android применяют технологии Java и виртуальную машину, которая занимается запуском и выполнением всех приложений. Стандартные приложения, несильно зависящие от мощности аппаратных средств устройства, в данном случае процессоры и видеокарты, используют для работы виртуальную машину Java как среду выполнения.
Сейчас мощность мобильных устройств возросла настолько, что на них возможно уже играть в те же игры, в которые играли на стационарных компьютерах восемь лет назад. Тенденция развития такая, что мощность мобильных процессорных платформ будет только увеличиваться, причем нарастающими темпами. Из этого следует, что качество и сложность мобильных приложений тоже будет возрастать.
Исходя из выше описанной ситуации применение технологии Java оправдано, так как она позволяет унифицировать разработку программных продуктов. Но в ситуации с графическими приложениями, где необходима не только унификация, но и скорость работы, причем скорость выходит на первый план, иначе использование этой технологии будет неэффективным. Всевозможные оптимизации Java-технологий привели к значительному ускорению выполнения приложений, но этого не достаточно для 3D-графики с большим количеством используемых аппаратных ресурсов, где идет оптимизация наоснове аппаратной реализации. Мое мнение, что нужно использовать технологии, имеющие доступ к аппаратным ресурсам устройства без лишних прослоек.
В данной статье я рассмотрю два подхода к программированию 3D-графики на OpenGL.
- Разработка с использованием только средства Java-технологий для OpenGL и Android.
- Разработка с использованием возможностей С\С++ для OpenGL.
Программирование компьютерной графики на основе OpenGL с использованием Java-технологий для OpenGL и Android
Для начала рассмотрим основы программирования под Android.
Для создания приложений используется много IDE и визуальных средств. Основным я считаю ADT (Android Developer Tools) на основе Eclipse. Это среда разработки для Java, C++ и для множества других языков. Скачать и установить это среду разработки можно с сайта [1].
Есть другие средства разработки, такие как Android Studio, основанный на NetBeans. Они могут считаться альтернативой, но пока только ADT Eclipse поддерживает разработку с помощью C\C++. Дляэтого необходимо поставить NDK (Native Development Kit) и подключить его к ADT Eclipse. Так же на сайте расположено много полезных статей для программирования на Android.
После установки данной среды разработки необходимо еще доставить SDK (Software Development Kit) для работы с указанным приложением. Для создания первого приложения уже установленных изADT средств достаточно, но рекомендую сразу установить все необходимое, используя SDK Manager (см. рис.1).
Рисунок 1. SDK Manager
Далее хочу рассказать о системе обновления Eclipse из Интернета. Она располагается в меню Help → Install New Software… Там есть возможность загрузки и установки недостающих компонент.
Теперь о структуре самого приложения (см. рис. 2). В папке src находятся исходники, в папке bin – пакет для выполнения на Android. В папке Res находятся XML-файлы. Это файлы, которые содержат многие статически хранящиеся ресурсы, например, текст, изображения, цвета и т.д. В папке res/layout находятся шаблоны окон на Android, активностей или слоев. ADT Eclipse позволяет визуально редактировать форму активности с возможностью добавления кнопок, текстовых полей и т.д.
Рисунок 2. Структура исходных кодов приложения
Структура OpenGL приложения под Android – это обычное приложение, но в качестве инструмента рендеринга используются уже созданные классы и интерфейсы.
Теперь, как в приложениях для стационарных систем, нам необходимо получить контекст рендеринга, то есть где все будет рисоваться, и сопоставить его с контекстом отображения, то есть где мы увидим, что там нарисовалось. Для этого создадим активность средствами Eclipse.
public class OpenGLES20Activity extends Activity {
private GLSurfaceView mGLView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Создаем экземпляр GLSurfaceView и устанавливаем его
// как контекст отображения для нашей активности или окна
mGLView = new MyGLSurfaceView(this);
setContentView(mGLView);
}
@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}
@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}
}
Методы onPause() и onResume() соответственно отрабатывают, когда приложение для Android активно или свернуто или неактивно.
Далее расскажу о самой процедуре создания контекста рендеринга, то есть где все будет рисоваться. Будет показано, как прикрепить к нему класс рендеринга, где будем рисовать нашу графику средствами OpenGL.
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer mRenderer;
public MyGLSurfaceView(Context context) {
super(context);
// Создаем OpenGL контекст, который будет
// отображаться в активности
setEGLContextClientVersion(2);
// Создаем экземпляр класса рендеринга
mRenderer = new MyGLRenderer();
setRenderer(mRenderer);
// Режим рендеринга, где отображение новой картинки
// происходит при ее изменении
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
// Метод onTouchEvent вызывается при прикосновении
// к экрану устройства
@Override
public boolean onTouchEvent(MotionEvent e)
{
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
{ … }
}
return true;
}
}
Класс MyGLRenderer как раз и занимается созданием нашего изображения, отображаемого на экране устройства. В нем переопределяются три основных метода:
- onSurfaceCreated – вызывается при создании и инициализации контекста OpenGL;
- onSurfaceChanged – вызывается при изменении размеров экрана;
- onDrawFrame – метод генерирует кадр графики для отображения.
public class MyGLRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "MyGLRenderer";
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
private final float[] mRotationMatrix = new float[16];
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Инициализация данных при создании контекста OpenGL
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
@Override
public void onDrawFrame(GL10 unused) {
// Очистка экрана
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
// Генерируем матрицу камеры
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Перемножаем видовую матрицу и матрицу камеры
// для получения необходимой матрицы отображения
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
// Отображаем нашу сцену
mSquare.draw(mMVPMatrix);
// Создаем матрицу поворота
Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, 1.0f);
Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);
}
@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// Меняем видовую матрицу в соответствии с новыми
// параметрами
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
// Полезная функция отображения и проверки ошибок
public static void checkGlError(String glOperation) {
int error;
while ((error = GLES20.glGetError()) != ↵
GLES20. GL_NO_ERROR) {
Log.e(TAG, glOperation + ": glError " + error);
throw new RuntimeException(glOperation + ": glError " + error);
}
}
}
Теперь о некоторых особенностях создания и работы со сценой. В Android Java – некоторая оптимизация работы с матрицами. Поскольку метод отображения не является конечным автоматом, поэтому вся работа формируется с матрицей. Координаты вершины умножаются на необходимую матрицу, и мы видим изменение при отображении на экране. Соответственно для каждого объекта отображения создается класс, и ему передается сформированная матрица для конкретного объекта, например, шарик, кубик, человек – сама большая сцена.
В общем схематическом виде можно представить создание программы для Android c использованием OpenGL (см. рис. 3).
Рисунок 3. Схема формирования отображения кадра
Как видите, достаточно проста и понятна работа с OpenGL-графикой с помощью Java применительно к Android. Но, как всегда бывает, за удобства нужно платить производительностью. Так как все этиклассы – это код, созданный на Java и обрабатывающийся виртуальной машиной Java, некоторые задержки и дополнительные вычисления будут присутствовать.
Программирование компьютерной графики на основе OpenGL с использованием NDK С\С++ для Android
Теперь рассмотрим, как реализовать эти задачи с помощью NDK и С\С++.
Начнем с того, что Android по своей структуре – это один из клонов операционной системы Linux. Соответственно ему присущи все особенности работы и программирования под подобные операционные системы. Как и в Linux, под Android можно использовать SDL. Пока же ограничимся только приложением, использующим напрямую NDK и ресурсы Android. Итак, для начала надо подготовить среду ADT Eclipse для работы с NDK. Для этого надо обновить и поставить недостающие компоненты. Выполнить это можно, открыв меню Help → Install New Software… и в окне в редакторе текста Work with указать https://dl.google.com/android/eclipse (см. рис. 4).
Рисунок 4. Установка компонент NDK для Eclipse
В дальнейшем открыть меню Window → Preferences. В полученном окне открыть в дереве Android\NDK и указать путь папке, где распакован NDK, например, C:\Android\android-ndk-r9d.
Для подключения к проекту возможностей NDK надо выбрать Android Tools → Add Native Support… нажав правой кнопкой мыши на необходимый проект. Там будет предоставлен выбор имени статической библиотеки на C\С++ в формате для Linux «lib<имя библиотеки>.so». Будет создана папка jni с необходимыми файлами С++ и Android.mk, скриптом для компиляции библиотеки.
Теперь расскажу о структуре вызова процедур из библиотеки C++. Так как среда разработки и само приложение Android реализованы с помощью Java-технологий, то придется использовать окружение Java для работы. Для начала надо экспортировать процедуры или функции из C++ библиотеки в класс Java.
В коде С++ необходимые процедуры и функции экспортируются, как указано ниже.
extern "C" {
JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_init(JNIEnv * env, jobject obj, jint width, jint height);
JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_step(JNIEnv * env, jobject obj);
};
JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_init(JNIEnv * env, jobject obj, jint width, jint height)
{
setupGraphics(width, height);
}
JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_step(JNIEnv * env, jobject obj)
{
renderFrame();
}
Здесь длинное имя экспортируемой функции Java_com_android_gl2jni_GL2JNILib_ должно соответствовать src.com.android.gl2jni.GL2JNILib.java, то есть знак «_» в Java меняется на «.».
В Java импорт выглядит вот так:
package com.android.gl2jni;
public class GL2JNILib
{
static {
System.loadLibrary("gl2jni");
}
public static native void init(int width, int height);
public static native void step();
}
Здесь с помощью вызова системного метода System.loadLibrary("gl2jni") указывается, откуда производить импорт. С помощью директивы public static native указывается имя вызываемой функции изсозданного нами класса C++ (см. рис. 5).
Рисунок 5. Экспорт функций C++ в Java
Для линковки библиотеки необходимо подключить необходимые библиотеки для работы с OpenGL -lGLESv2 и вывода отладочных сообщений в лог Android «-llog» в файле Android.mk.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libgl2jni
LOCAL_CFLAGS := -Werror
LOCAL_SRC_FILES := gl_code.cpp
LOCAL_LDLIBS := -llog -lGLESv2
include $(BUILD_SHARED_LIBRARY)
За выполнение действий за отрисовку буферов отвечает класс GLSurfaceView.
private void init(boolean translucent, int depth, int stencil)
{
setEGLContextFactory(new ContextFactory());
setRenderer(new Renderer());
}
В нем инициализируются два следующих класса-потомка GLSurfaceView.EGLContextFactory и GLSurfaceView.Renderer.
В классе GLSurfaceView.Renderer переопределяются методы onDrawFrame(GL10 gl) и onSurfaceChanged(GL10 gl, int width, int height)
public void onDrawFrame(GL10 gl) {
GL2JNILib.step();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
GL2JNILib.init(width, height);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Do nothing.
}
Вся остальная работа будет производиться средствами Android в созданном экземпляре класса Activity. Метод setContentView(mView) сопоставляет активность и наш класс для работы с OpenGL – GLSurfaceView.
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mView = new GL2JNIView(getApplication());
setContentView(mView);
}
Другие действия в Java части нашего приложения производить нет смысла, так как основную логику работы будут содержать функции и процедуры в С++ библиотеке.
Так как Android является по сути своей Linux, то есть большая вероятность, что, если код работает под Linux, он будет работать и под Android. Единственное различие – в аппаратных ресурсах. Все-таки мобильные приложения пока еще не могут поддерживать и выполнять все аппаратные функции стационарных компьютеров. Поэтому приложение надо тестировать на Android.
Там будут использоваться стандартные процедуры из OpenGL-библиотек. Так как библиотеки изменены и урезаны, то стандартных glBegin(), glVertex3f() и т.п. в этих библиотеках нет. Есть стандартные вызовы методов glDrawArray() и других.
Используя данный функционал, можно без существенных затрат перенести приложения со стационарных рабочих станций на мобильные устройства. OpenGL придерживается своего стандарта. Если егопридерживаться при программировании, сложностей при портировании будет гораздо меньше.
Сравнение двух подходов
Как и везде при формировании программы с использование OpenGL с помощью Java и нативных средств операционной системы NDK, есть достоинства и недостатки.
Начну с достоинств подхода использования только Java для формирования графики:
- Возможность использования полученного кода на множестве устройств.
- Удобство и заранее решенные вопросы с работой с устройством, то есть не нужно перехватывать события изменения на устройстве и нажатия.
- Удобство отладки.
Данный подход эффективен при создании приложений с не очень сложной графикой, например, отображение карт или простых каких-либо графических эффектов, связанных с текстурами.
Если есть достоинства, то должны быть и недостатки. Самый главный и, по моему мнению, критический недостаток – это объектно-ориентированный подход к формированию приложения. Достаточно большие затраты ресурсов и производительности будут тратиться на внутреннюю работу с экземплярами классов.
Все методы в классах отображения OpenGL являются статическими. Они, конечно же, используют нативные средства расчетов. То есть время отображения какого-либо объекта ничем не будет отличаться от средств С++. Но вот другие действия, которые связаны с формированием изменения кадра или какой-либо динамики, будут заметно замедлять работу.
Теперь по поводу достоинств и недостатков нативного подхода с использованием NDK.
Достоинства:
- Скорость формирования и обработки кадра.
- Доступ к ресурсам устройства на более низком уровне.
- Возможность портирования приложений со стационарных компьютеров.
Скорость отображения кадра при использовании NDK визуально выше, чем с помощью Java. Есть возможность работы с памятью и с внутренними, более обширными ресурсами устройства.
Недостатками можно считать следствия достоинств:
- Проблемный перенос программы на другие мобильные устройства. Не всегда NDK-приложение на текущем устройстве будет работать на других. Каждый бренд изменяет Android под свое устройство, и некоторые функции могут присутствовать в другом контексте или вообще отсутствовать.
- Контроль над использованием памяти и работы с периферией ложится на плечи программиста без использования Java.
- Проблемная отладка ошибок.
Разработчики Android рекомендуют для совместимости использовать только Java-ресурсы. NDK был создан для совместимости Android c другими приложениями, на других платформах.
Если учесть, что современные графические фреймворки уже создаются с учетом использования их на мобильных устройствах, NDK будет только развиваться.
- Ресурс для разработчиков Android – http://developer.android.com.