Аппаратное ускорение Canvas в Android

в 7:34, , рубрики: android, canvas, mobile development, Блог компании Luxoft, метки: ,

Предположим мы решили написать новое Android приложение. В силу же дизайнерских соображений, стандарные UI компоненты нам не подходят и мы будет рисовать много графики через Canvas или OpenGL ES. Последний способ несколько более трудоемкий, поэтому мы его пока рассматривать не будем. Нас интересует производительность вывода графики на Canvas. Можно ли её ускорить? Если зайти на официальный сайт андроида или блог разработчиков, то можно заметить, что начиная с Android 3.0 (API level 11), в платформе появилась возможность включать аппаратное ускорение Canvas для вывода 2D графики.

Вы можете подумать: "Супер! Я сейчас же добавлю флаг аппаратного ускорения в мое приложение!". Тем более, некоторые сайты утверждают, что приложение спокойно будет работать на Android 2.x — такие приложения просто будут игнорировать флаг ускорения. Другие же сайты говорят, что аппаратное ускорение включено по-умолчанию начиная с Android 4.0. На самом же деле все не так.

И конечно же нас интересует вопрос, насколько повысится производительность приложения при включении аппаратного ускорения.

image

Первое на что я наткнулся при включение флага аппаратного ускорения, так это то, что приложение напрочь отказалось запускаться на Android 2.x. «Синтаксическая ошибка. При анализе пакета возникла проблема.» При этом, приложение нормально стартует на Android 3.x.

android:hardwareAccelerated="true"

Идем дальше. В приложениях с динамичной графикой, как правило, создается цикл (app/game loop), в котором выполняется логика приложения и отрисовка графики. На андроиде цикл можно реализовать через таймер с задержкой в 1 мс c вызовом postInvalidate() или же для этих целей создать новый поток. Однако, как оказалось, аппаратное ускорение Canvas не поддерживает работу в отдельном потоке.

Canvas canvas = surfaceHolder.lockCanvas(); // no hardware acceleration in this case

Интересно, насколько же все-таки повышается скорость при включении ускорения? Чтобы ответить на этот вопрос, я написал простенькое Android приложение, которое рисует картинку 128x128 на Canvas 100 раз в случайном месте, плюс показывает FPS. Дополнительно пользователь может выбирать отрисовку в главном потоке или в дополнительном, а также включать или выключать установление флага аппаратного ускорения.

Перво-наперво код был модифицирован, чтобы он мог запускаться на Android 2.x. Флаг аппаратного ускорения был убран из AndroidManifest.xml и добавлен в activity, т.е. мы будем включать ускорение программным способом, выставляя флаг FLAG_HARDWARE_ACCELERATED.

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        boolean enableAccelerationFlag = getIntent().getExtras()
            .getBoolean(MainActivity.HW_ENABLED_KEY);
        if (enableAccelerationFlag) {
            // Build target in AndroidManifest.xml and/or Eclipse must be 11 or 
            // higher to compile this code.
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        }

        setContentView(R.layout.single_thread);

        surfaceView = (SingleThreadSurfaceView) findViewById(R.id.single_thread_view);
        // Enable calling onDraw() method that is switched off by default. 
        surfaceView.setWillNotDraw(false);
    }

В следующем блоке показывается как идет отрисовка картинок. В нем же находится блок для Android 3.x или выше с вызовом isHardwareAccelerated(). На более низкой версии вызывать этот метод нельзя, т.ч. производится проверка на версию операционной системы.

    @SuppressLint("NewApi")
    private void draw(Canvas canvas) {
        canvas.drawColor(Color.RED);

        for (int i = 0; i < IMAGES_PER_FRAME; i++) {
            int x = (int) (Math.random() * (canvas.getWidth() - bitmap.getWidth()));
            int y = (int) (Math.random() * (canvas.getHeight() - bitmap.getHeight()));
            canvas.drawBitmap(bitmap, x, y, null);
        }

        canvas.drawText("fps=" + fps, 0, 30, paint);

        boolean isViewAccelerated = false;
        boolean isCanvasAccelerated = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // This code must not be executed on a device with API 
            // level less than 11 (Android 2.x, 1.x)
            isViewAccelerated = surfaceView.isHardwareAccelerated();
            isCanvasAccelerated = canvas.isHardwareAccelerated();
        }
        canvas.drawText("isViewAccelerated=" + isViewAccelerated, 0, 60, paint);
        canvas.drawText("isCanvasAccelerated=" + isCanvasAccelerated, 0, 75, paint);
    }

FPS вычисляется довольно просто и вызывается перед отрисовкой картинок.

    private void measureFps() {
        frameCounter++;
        long now = SystemClock.uptimeMillis();
        long delta = now - lastFpsCalcUptime;
        if (delta > FPS_CALC_INTERVAL) {
            fps = frameCounter * FPS_CALC_INTERVAL / delta;

            frameCounter = 0;
            lastFpsCalcUptime = now;
        }
    }

Ниже представлена сводная таблица тестирования производительности для 3-х имеющихся в наличии устройств и эмулятора. Последние две колонки — результат выполнения методов isHardwareAccelerated() для View и Canvas соответственно.

Платформа/Устройство Графическая Модель FPS View ускорено? Canvas ускорен?
Android 2.2
Emulator
800x480
1. Главный поток, ускорения нет 4 нет нет
2. Дополнительный поток, ускорения нет 20 нет нет
Android 2.2.2
HTC Desire
800x480
1. Главный поток, ускорения нет 7 нет нет
2. Дополнительный поток, ускорения нет 50 нет нет
Android 3.2.1
Acer A500
1280x800
1. Главный поток, ускорение по-умолчанию 20 нет нет
2. Главный поток, флаг ускорения установлен 28 да да
3. Дополнительный поток, ускорение по-умолчанию 30 нет нет
4. Дополнительный поток, флаг ускорения установлен 30 да нет
Android 4.0.3
HTC Sensation XE
960x540
1. Главный поток, ускорение по-умолчанию 26 нет нет
2. Главный поток, флаг ускорения установлен 39 да да
3. Дополнительный поток, ускорение по-умолчанию 26 нет нет
4. Дополнительный поток, флаг ускорения установлен 26      да нет

Результаты впечатляют! Честно говоря, я такого не ожидал.

  1. Если нужна поддержка Android 2.x, следует использовать дополнительный поток для цикла логики/отрисовки.
  2. Установка флага аппаратной поддержки на Android 3.x с использование главного потока показывает такие же результаты, как и без ускорения, но в дополнительном потоке.
  3. Аппаратное ускорение выключено по-умолчанию на Android 4.x, и показывает значительное ускорение вывода графики в главном потоке при его включении.
  4. Эмулятор работает в 5 раз быстрее, когда отрисовка производится в отдельном потоке.

Исходный код проекта доступен для скачивания тут.

Автор: Melnosta

* - обязательные к заполнению поля