[Из песочницы] Android Studio для NDK под Windows
На днях я обнаружил, что версия Android Studio неуклонно стремится к единице, в связи с чем задумался об изучении этого инструмента. Чтобы не было скучно, я решил поделиться своим опытом и собранными граблями в виде статьи-туториала.
Сразу хочу оговориться, что я не являюсь гуру Android-разработки, поэтому каких-либо откровений в тексте вы не найдете. Зато тут есть пошаговая инструкция по установке и настройке Android Studio под Windows и созданию простейшего проекта с использованием Android NDK.
Также заранее предупреждаю: статья получилась большой и очень подробной (честно, сам не ожидал), даже несмотря на то, что я почти все скриншоты и некоторые листинги кода спрятал под спойлеры.На момент написания последней версией Android Studio была 0.8.1, для последующих версий необходимые действия могут отличаться от нижеописанных (очень надеюсь, что в лучшую сторону).
Для тех, кого интересует только NDKДля тех, кого интересуют только настройки Gradle для NDK
Установка и настройка Android Studio1. Необходимо установить JDK (Java Development Kit) и JRE (Java Runtime Environment). Раньше Android SDK поддерживал только JDK версии 6, но теперь это в прошлом. Поддерживается 7 и даже 8 (по крайней мере, именно 8-ю версию я указал в качестве JAVA_HOME и в настройках Android Studio, и никаких проблем не испытал).JRE же нужен для запуска самой студии. У меня она использует версию 7.Скачать JDK и JRE версий больше 6 можно с сайта Oracle.Переменную JAVA_HOME теперь, вроде бы, можно не устанавливать, так как в Android Studio мы будем в настройках прописывать путь к JDK. Но я ее установил. Для этого нужно:
Зайти в Панель управления\Система и безопасность\Система, выбрать слева Дополнительные параметры системы, в открывшемся диалоге найти кнопку Переменные среды. Создать системную или пользовательскую переменную JAVA_HOME и указать для нее путь к JDK. У меня указан вот такой путь: C:\Program Files\Java\jdk1.8.0_05. Если вы по каким-либо причинам нуждаетесь в 6 версии JDK JDK 6 без регистрации можно получить так: Скачать Java EE 6 с сайта Oracle. Установить его. В комплекте есть JDK 6. В качестве JAVA_HOME в этом случае нужно указать путь к JDK из Java EE, по умолчанию это C:/glassfish3/jdk. 2. Если у вас установлен Android SDK. В комплекте с Android Studio идет свой Android SDK. И, если вы хотите использовать именно его, то в некоторых случаях может случиться странное. Например, у меня при обновлении SDK через SDK Manager часть файлов записывалась в старую папку, и возникли проблемы, когда я эту папку удалил. Скорее всего это произошло из-за того, что в реестре хранился ключ с путем к старой папке. Поэтому имеет смысл почистить реестр. Для этого нужно запустить regedit.exe и найти HKEY_LOCAL_MACHINE\Software\Android SDK Tools для 32-битных машин либо HKEY_LOCAL_MACHINE\Software\Wow6432Node\Android SDK Tools для 64-битных машин и удалить Android SDK Tools. Если в реестре вашей системы таких ключей нет, то все в порядке.Если у вас установлена переменная среды ANDROID_SDK_HOME и вы хотите, чтобы она указывала на старую установку, то, по идее, это не должно стать проблемой, так как при настройке Android Studio мы укажем ей путь к SDK. Проблемы могут возникнуть, если эту переменную использует какое-либо из приложений, входящих в состав Android SDK.
3. Теперь переходим к установке Android Studio.
Нужно скачать Android Studio для вашей системы с официальной страницы и установить ее. По умолчанию, если выбрать «Установить только для меня» ставится в \Users\
Видим вот такой диалог
В нем делаем следующее: Проверяем обновления и, при необходимости, обновляемся (для этого нажмите на Check в нижней части окна)
Заходим в Configure→SDK Manager (на всякий случай: SDK Manager — это не часть Android Studio, а утилита из Android SDK) и устанавливаем то, что вам необходимо. Если вы не знаете, что необходимо, то лучше установить все, что он предложит.Вот что он установил у меня
Затем я сменил тему на Darcula.Как это сделать
нужно зайти в Configure→Settings→Appearance:
Далее заходим в Configure→Project Defaults→Project Structure и меняем SDK Location на путь к вашему Android SDK, а JDK Location на путь к JDK.Скриншот
Создание проекта
1. New Project
Жмем New Project.Появляется вот такой диалог
В нем можно настроить: Имя проекта (я оставил по умолчанию: My Application)
Домен компании, по умолчанию
2. Настройка виртуального устройства Для того чтобы запустить программу в эмуляторе, нужно настроить виртуальное устройство. Заходим в Tools→Android→AVD Manager (это, как и SDK Manager, утилита из Android SDK). Нам нужна первая вкладка, Android Virtual Device, она и открыта по умолчанию. Справа находим кнопку Create… и нажимаем ее.Появится диалог настройки виртуального устройства AVD Name — имя виртуального устройства, я ввел TestPhone. Дальше я, правда, выбрал планшет, но уже не стал менять название. Device — различные предустановленные настройки устройства, значение выбирается из доступных в выпадающем списке, я выбрал 7'' WSVGA (Tablet) (1024×600: mdpi). Target — целевая версия API, доступны только те, которые есть в SDK. Установить их можно через SDK Manager.W в названии означает Wear, версия для носимых устройств (часы и прочее). Так как мы в настройках проекта выбирали Tablet&Phone, то нам нужен другой пункт: Android L (Preview) — API Level L. CPU/ABI — платформа, я выбрал ARM (armeabi-v7a). Keyboard — тут, думаю, все понятно. Skin — как я понял, внешний вид устройства, я выбрал значение, соответствующее установленному значению Device: WSVGA. Front Camera и Back Camera — наличие камер. Memory Options — оперативная память. Internal Storage — жесткий диск устройства. SD Card — думаю, понятно. Emulation options: Snapshot — позволяет эмулятору сохранять свое состояние между запусками, Use Host GPU — позволяет использовать видеокарту компьютера для OpenGL ES. Последнюю опцию я включил, но OpenGL — приложение у меня впоследствии упало на этом эмуляторе при старте. Наверное, из-за ARM vs x86_64. Внизу диалога выводится, какую ошибку вы допустили при настройке. Например, в имени устройства нельзя вводить пробелы, а некоторые поля обязательны для заполнения. Когда внизу нет надписи — все введено верно. Жмем OK.Устройство появилось в списке Пара слов о вкладке Device Definitions На ней заданы доступные устройства (те, которые потом отображаются в выпадающем списке Device при создании нового виртуального устройства). Чтобы создать новое устройство, нужно нажать на кнопку Create Device… и озаботиться заполнением следующего диалога: Закрываем AVD Manager и возвращаемся в главное окно Android Studio.3. Запуск на эмуляторе Выполняем Run→Run 'app'.Придется подождать, пока произойдет сборка проекта для запуска.В конце концов, появится окно выбора устройства Здесь единственный доступный вариант для выбора — это наше виртуальное устройство. Он нам сразу предложен, так что просто жмем OK.Запускается эмулятор, довольно долго Можно открыть Tools→Android→Android Device Monitor (утилита из Android SDK), чтобы увидеть логи с эмулятора. Он должен автоматически подцепить виртуальное устройство и сразу начать выводить лог.Android Device Monitor А вот приложение после запуска эмулятора у меня с первого раза не открылось, пришлось еще раз нажать Run→Run 'app'.Появился немного изменившийся диалог Здесь устройство перекочевало из списка для запуска в список уже запущенных. В этом списке, само собой, оказываются и реальные устройства.После этого дело пошло, и приложение довольно быстро установилось и запустилось.
Полноэкранный режим А если на приложение тапнуть, то выглядит оно так Это не поведение системы, обработка клика происходит в приложении (файл FullscreenActivity.java): // Set up the user interaction to manually show or hide the system UI. contentView.setOnClickListener (new View.OnClickListener () { @Override public void onClick (View view) { if (TOGGLE_ON_CLICK) { mSystemUiHider.toggle (); } else { mSystemUiHider.show (); } } }); После запуска, Android Studio предложила мне выключить Android Device Monitor, для того чтобы интегрировать вывод прямо в IDE.Вот как выглядит интеграция 4. Запуск в Debug-режиме Run→Debug 'app’Снова уже знакомый нам диалог Жмем OK.Ждем, пока запустится приложение и приконнектится дебаггер. Я поставил точку останова при нажатии на кнопку Dummy Button.
Android Studio в дебаге Немного раздражает отсутствие панельки со всякими Step Into, Step Out etc.Все это находится в меню Run 5. Запуск на 4.0.3 В процессе создания и запуска проекта вы, наверное, заметили, что мы создали приложение, совместимое с версией 4.0.3, но запускали его только на самой последней версии Android. Сейчас мы это исправим. Для этого нужно установить версию 4.0.3 через Tools→Android→SDK Manager.Необходимое отмечено галочками на скриншоте Это SDK Platform, ARM EABI v7a System Image и Intel x86 Atom System Image. На самом деле, мы запускали эмулятор ARM, и Intel нам сейчас ставить не обязательно. Я его поставил сугубо потому, что он установлен и для Android L.Теперь создадим еще одно устройство для новой старой версии Android (или же вы можете отредактировать старое).Настройки нового устройства Запускаем (Run→Run 'app').В диалоге выбираем новое устройство для запуска И смотрим, как выглядит новый эмулятор — явно брутальнее.Запуск Полноэкранный режим После нажатия 5. Запуск на устройстве Перед тем как перейти к работе с NDK, давайте запустим проект на реальном устройстве. Я буду запускать на телефонеHuawei Ascend G300 с Android 4.0.3 Первое, что нужно сделать, это установить adb-драйвер. У меня с этим все довольно просто, драйвер находится прямо на телефоне, единственное, что нужно сделать, это воткнуть шнур в компьютер, зайти в примонтировавшийся диск и запустить исполняемый файл, который и установит adb-драйвер. Для других устройств все может быть сложнее. Например, для планшета Prestigio мне в свое время пришлось прописывать Vendor ID в специальный файл (чтобы использовался стандартный драйвер от Google), для Samsung нужен их собственный Kies, был отдельный драйвер для HTC и так далее. В общем, вам придется самостоятельно разобраться, как установить драйвер для вашего устройства.После того как драйвер был установлен, нужно на устройстве разрешить отладку по USB. У меня на телефоне для этого нужно зайти в Настройки→Для разработчиков→Отладка по USB. Но для разных устройств, а также сборок и версий Android, расположение этого пункта меню может отличаться.
Теперь телефон будет находиться Android SDK, а разрабатываемые приложения будут устанавливаться. Однако, для Huawei это еще не все: с телефона не идут логи, нужно их включить.
Как включить логи на Huawei Набрать в качестве телефонного номера: *#*#2846579#*#*Появится сервисное меню.Перейти в ProjectMenu→Background Setting→Log SettingОткрыть Log switch и установить там ON.Открыть Log level setting и установить необходимый вам уровень логирования (я поставил verbose).Перезагрузить телефон.
Теперь можно запускать приложение на устройстве: Run→Run 'app’В диалоге выбора устройства появилось реальное устройство Запускаемся на телефоне.Результат запуска. Приложение в портрете: На полный экран После нажатия Приложение в ландшафте: На полный экран После нажатия Установка и настройка Android NDK 1. Установка NDK Android SDK, как мы уже выяснили, входит в комплект Android Studio, а вот NDK — нет. Скачиваем NDK, подходящий для вашей системы, отсюда. Распаковываем архив и кладем в какую-нибудь папку, например, в D:\ndk, так, чтобы файл ndk-build.cmd лежал прямо в ней. Важно: необходимо, чтобы в пути к NDK не было пробелов. 2. Добавим переменную ANDROID_NDK_HOME Заходим в Панель управления\Система и безопасность\Система, выбираем слева Дополнительные параметры системы, в открывшемся диалоге нажимаем кнопку Переменные среды. Создаем переменную с именем ANDROID_NDK_HOME и значением D:\ndk (путь к вашей NDK).Альтернативно, вместо указания глобальной переменной можно прописать путь к ndk в файле local.properties вашего проекта (прямо в корневой папке: MyApplication\local.properties). Содержимое файла будет выглядеть примерно так (обратите внимание на двойные бэкслеши, так как для Windows это критично):
## This file is automatically generated by Android Studio.
# Do not modify this file — YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=C:\\Users\\
buildscript { repositories { mavenCentral () } dependencies { classpath 'com.android.tools.build: gradle:0.11.0' } } apply plugin: 'android'
android { compileSdkVersion 19 buildToolsVersion '19.1.0'
defaultConfig { ndk { moduleName «sanangeles» cFlags »-DANDROID_NDK -DDISABLE_IMPORTGL» ldLibs «GLESv1_CM», «dl», «log» stl «stlport_static» }
// This actual the app version code. Giving ourselves 1,000,000 values versionCode = 123
}
buildTypes.debug.jniDebugBuild true
productFlavors { x86 { ndk { abiFilter «x86» }
// this is the flavor part of the version code. // It must be higher than the arm one for devices supporting // both, as x86 is preferred. versionCode = 3 } arm { ndk { abiFilter «armeabi-v7a» } versionCode = 2 } mips { ndk { abiFilter «mips» } versionCode = 1 } fat { // fat binary, lowest version code to be // the last option versionCode = 0 } }
// make per-variant version code applicationVariants.all { variant → // get the single flavor def flavorVersion = variant.productFlavors.get (0).versionCode
// set the composite code variant.mergedFlavor.versionCode = flavorVersion * 1000000 + defaultConfig.versionCode } } А вот измененная версия, чтобы проект собирался у меня: buildscript { repositories { mavenCentral () } dependencies { classpath 'com.android.tools.build: gradle:0.12.+' } } apply plugin: 'android'
android { compileSdkVersion 19 buildToolsVersion '20.0.0'
defaultConfig { ndk { moduleName «sanangeles» cFlags »-DANDROID_NDK -DDISABLE_IMPORTGL» ldLibs «GLESv1_CM», «dl», «log» stl «stlport_static» }
// This actual the app version code. Giving ourselves 1,000,000 values versionCode = 123
}
buildTypes.debug.jniDebugBuild true
productFlavors { x86 { ndk { abiFilter «x86» }
// this is the flavor part of the version code. // It must be higher than the arm one for devices supporting // both, as x86 is preferred. versionCode = 3 } arm { ndk { abiFilter «armeabi-v7a» } versionCode = 2 } mips { ndk { abiFilter «mips» } versionCode = 1 } fat { // fat binary, lowest version code to be // the last option versionCode = 0 } }
// make per-variant version code applicationVariants.all { variant → // get the single flavor def flavorVersion = variant.productFlavors.get (0).versionCode
// set the composite code variant.mergedFlavor.versionCode = flavorVersion * 1000000 + defaultConfig.versionCode } } Изменения следующие: О несовпадении версии плагина gradle (classpath 'com.android.tools.build: gradle:0.11.0') будет сказано при попытке сборки и предложен правильный номер версии. У меня это 0.12.+. compileSdkVersion 19 остается, так как это максимальная версия для текущего NDK. buildToolsVersion изменился на 20. Установленные версии можно посмотреть в SDK Manager, там же можно установить другие. После этого ndkSanAngeles должен собраться. Будьте внимательны, проверяйте версии, установленные у вас. В нашем проекте нужно выбрать правильный файд build.gradle, так как здесь их два. Дело в том, что MyApplication — это проект, а app — это модуль проекта (или подпроект), и у них build.gradle для каждого свой. Сначала рассмотрим файл проектаMyApplication→build.gradle // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { repositories { jcenter () } dependencies { classpath 'com.android.tools.build: gradle:0.12.+'
// NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }
allprojects { repositories { jcenter () } } Из комментариев в коде явно следует, что нам нужен файл модуляMyApplication→app→build.gradle apply plugin: 'com.android.application'
android { compileSdkVersion 20 buildToolsVersion »20.0.0»
defaultConfig { applicationId «com.example.markedone.myapp» minSdkVersion 15 targetSdkVersion 20 versionCode 1 versionName »1.0» } buildTypes { release { runProguard false proguardFiles getDefaultProguardFile ('proguard-android.txt'), 'proguard-rules.pro' } } }
dependencies { compile fileTree (dir: 'libs', include: ['*.jar']) // You must install or update the Support Repository through the SDK manager to use this dependency. //compile 'com.android.support: support-v4:19.+' compile 'com.android.support: support-v4:20.+' } Настроим его для работы с NDK, используя в качестве «донора» build.gradle из ndkSanAngeles.Для начала, заменимcompileSdkVersion 20наcompileSdkVersion 19так как NDK ограничен версией 19.
В defaultConfig мы добавим настройки ndk, а также заменим targetSdkVersion на 19:
defaultConfig { applicationId «com.example.markedone.myapp» minSdkVersion 15 targetSdkVersion 19 versionCode 1 versionName »1.0»
ndk { moduleName «myapp» cFlags »-DANDROID_NDK» ldLibs «log» stl «stlport_static» } } Настройки NDK включают в себяmoduleName — имя модуля, будет использовано для именования *.so файла cFlags — флаги C ldLibs — библиотеки, которые вы хотите подключить к нативному коду stl — версия библиотеки STL В buildTypes включим для debug дебажную сборку JNI: buildTypes { release { runProguard false proguardFiles getDefaultProguardFile ('proguard-android.txt'), 'proguard-rules.pro' }
debug.jniDebugBuild true } Теперь добавим productFlavors. Здесь мы указываем, какие собранные *.so библиотеки включать в сборку под конкретную архитектуру. Таким образом, *.apk собранный для arm будет содержать версию библиотеки только для arm, под x86 — для x86 и так далее. Этот кусок полностью скопирован с ndkSanAngeles. Объяснение значений versionCode из комментариев: для x86 ставим максимальное значение versionCode, так как если устройство поддерживает и x86 и arm, то сборка x86 предпочтительнее (видимо, так как у нее большая версия, то установлена будет именно она), а минимальный versionCode прописывается для fat (по идее это должен быть «толстый» *.apk, содержащий сразу все возможные версии библиотек). productFlavors { x86 { ndk { abiFilter «x86» }
// this is the flavor part of the version code. // It must be higher than the arm one for devices supporting // both, as x86 is preferred. versionCode = 3 } arm { ndk { abiFilter «armeabi-v7a» } versionCode = 2 } mips { ndk { abiFilter «mips» } versionCode = 1 } fat { // fat binary, lowest version code to be // the last option versionCode = 0 } } «Собираем» значение versionCode для каждого из вариантов сборки: // make per-variant version code applicationVariants.all { variant → // get the single flavor def flavorVersion = variant.productFlavors.get (0).versionCode
// set the composite code variant.mergedFlavor.versionCode = flavorVersion * 1000000 + defaultConfig.versionCode } Наконец, последняя секция, dependencies. Если помните, здесь мы меняли compile 'com.android.support: support-v4:19.+' на compile 'com.android.support: support-v4:20.+', чтобы собирать с единственной установленной у нас версией библиотеки. Теперь придется поменять обратно на 19. dependencies { compile fileTree (dir: 'libs', include: ['*.jar']) // You must install or update the Support Repository through the SDK manager to use this dependency. compile 'com.android.support: support-v4:19.+' } Полный листинг измененного файла build.gradle apply plugin: 'com.android.application'
android { compileSdkVersion 19 buildToolsVersion »20.0.0»
defaultConfig { applicationId «com.example.markedone.myapp» minSdkVersion 15 targetSdkVersion 19 versionCode 1 versionName »1.0»
ndk { moduleName «myapp» cFlags »-DANDROID_NDK» ldLibs «log» stl «stlport_static» } }
buildTypes { release { runProguard false proguardFiles getDefaultProguardFile ('proguard-android.txt'), 'proguard-rules.pro' }
debug.jniDebugBuild true }
productFlavors { x86 { ndk { abiFilter «x86» }
// this is the flavor part of the version code. // It must be higher than the arm one for devices supporting // both, as x86 is preferred. versionCode = 3 } arm { ndk { abiFilter «armeabi-v7a» } versionCode = 2 } mips { ndk { abiFilter «mips» } versionCode = 1 } fat { // fat binary, lowest version code to be // the last option versionCode = 0 } }
// make per-variant version code applicationVariants.all { variant → // get the single flavor def flavorVersion = variant.productFlavors.get (0).versionCode
// set the composite code variant.mergedFlavor.versionCode = flavorVersion * 1000000 + defaultConfig.versionCode }
sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } } }
dependencies { compile fileTree (dir: 'libs', include: ['*.jar']) // You must install or update the Support Repository through the SDK manager to use this dependency. compile 'com.android.support: support-v4:19.+' } 5. Создаем папку jni В папке jni мы будем сохранять C/C++ файлы. Этого от нас ожидает NDK. Папку нам нужно создать в MyApplication/app/src/main. Это можно сделать прямо из Android Studio, причем, двумя способами.Во-первых, можно кликнуть правой кнопкой на main и просто создать папку черезNew→Directory Во вторых, можно воспользоваться специальным пунктом менюNew→Folder→JNI Folder Он запускает визард создания папки В первом диалоге мы выбираем, для какой части модуля будет создана папка jni, а во втором можно изменить ее расположение. 6. Добавим C++ файлы Визарда для C++ файлов нет, поэтому для создания файла мы кликаем правой кнопкой на папке jni и выбираемNew→File Для начала создадим заголовочный файл myapp.h: #pragma once
#include
#ifdef __cplusplus extern «C» { #endif
JNIEXPORT jstring JNICALL Java_com_example_markedone_myapp_FullscreenActivity_stringFromJNI (JNIEnv* env, jclass clazz);
#ifdef __cplusplus
}
#endif
Описание
#pragma once — вместо стандартной (#ifndef/#define/#endif) защиты от повторного включения. Сейчас #pragma once понимается большинством C++ компиляторов.
#include
JNIEXPORT jstring JNICALL Java_com_example_markedone_myapp_FullscreenActivity_stringFromJNI (JNIEnv* env, jclass clazz)
{
std: string tag («GREETING»);
std: string message («Hello from C++!»);
__android_log_print (ANDROID_LOG_INFO, tag.c_str (),»%s», message.c_str ());
std: string jniMessage («Hello from JNI!»);
return env→NewStringUTF (jniMessage.c_str ());
}
Описание
#include
import com.example.markedone.myapp.util.SystemUiHider;
import android.annotation.TargetApi; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.view.MotionEvent; import android.view.View;
/** * An example full-screen activity that shows and hides the system UI (i.e. * status bar and navigation/system bar) with user interaction. * * @see SystemUiHider */ public class FullscreenActivity extends Activity { /** * Whether or not the system UI should be auto-hidden after * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. */ private static final boolean AUTO_HIDE = true;
/** * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after * user interaction before hiding the system UI. */ private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/** * If set, will toggle the system UI visibility upon interaction. Otherwise, * will show the system UI visibility upon interaction. */ private static final boolean TOGGLE_ON_CLICK = true;
/** * The flags to pass to {@link SystemUiHider#getInstance}. */ private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
/** * The instance of the {@link SystemUiHider} for this activity. */ private SystemUiHider mSystemUiHider;
@Override protected void onCreate (Bundle savedInstanceState) { super.onCreate (savedInstanceState);
setContentView (R.layout.activity_fullscreen);
final View controlsView = findViewById (R.id.fullscreen_content_controls); final View contentView = findViewById (R.id.fullscreen_content);
// Set up an instance of SystemUiHider to control the system UI for // this activity. mSystemUiHider = SystemUiHider.getInstance (this, contentView, HIDER_FLAGS); mSystemUiHider.setup (); mSystemUiHider .setOnVisibilityChangeListener (new SystemUiHider.OnVisibilityChangeListener () { // Cached values. int mControlsHeight; int mShortAnimTime;
@Override @TargetApi (Build.VERSION_CODES.HONEYCOMB_MR2) public void onVisibilityChange (boolean visible) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { // If the ViewPropertyAnimator API is available // (Honeycomb MR2 and later), use it to animate the // in-layout UI controls at the bottom of the // screen. if (mControlsHeight == 0) { mControlsHeight = controlsView.getHeight (); } if (mShortAnimTime == 0) { mShortAnimTime = getResources ().getInteger ( android.R.integer.config_shortAnimTime); } controlsView.animate () .translationY (visible? 0: mControlsHeight) .setDuration (mShortAnimTime); } else { // If the ViewPropertyAnimator APIs aren’t // available, simply show or hide the in-layout UI // controls. controlsView.setVisibility (visible? View.VISIBLE: View.GONE); }
if (visible && AUTO_HIDE) { // Schedule a hide (). delayedHide (AUTO_HIDE_DELAY_MILLIS); } } });
// Set up the user interaction to manually show or hide the system UI. contentView.setOnClickListener (new View.OnClickListener () { @Override public void onClick (View view) { if (TOGGLE_ON_CLICK) { mSystemUiHider.toggle (); } else { mSystemUiHider.show (); } } });
// Upon interacting with UI controls, delay any scheduled hide () // operations to prevent the jarring behavior of controls going away // while interacting with the UI. findViewById (R.id.dummy_button).setOnTouchListener (mDelayHideTouchListener); }
@Override protected void onPostCreate (Bundle savedInstanceState) { super.onPostCreate (savedInstanceState);
// Trigger the initial hide () shortly after the activity has been // created, to briefly hint to the user that UI controls // are available. delayedHide (100); }
/** * Touch listener to use for in-layout UI controls to delay hiding the * system UI. This is to prevent the jarring behavior of controls going away * while interacting with activity UI. */ View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener () { @Override public boolean onTouch (View view, MotionEvent motionEvent) { if (AUTO_HIDE) { delayedHide (AUTO_HIDE_DELAY_MILLIS); } return false; } };
Handler mHideHandler = new Handler (); Runnable mHideRunnable = new Runnable () { @Override public void run () { mSystemUiHider.hide (); } };
/** * Schedules a call to hide () in [delay] milliseconds, canceling any * previously scheduled calls. */ private void delayedHide (int delayMillis) { mHideHandler.removeCallbacks (mHideRunnable); mHideHandler.postDelayed (mHideRunnable, delayMillis); } } Добавим следующий код в класс FullscreenActivity: static { System.loadLibrary («myapp»); }
private static native String stringFromJNI (); Здесь сначала идет загрузка библиотеки, а затем объявление метода stringFromJNI, который соответствует нашей функции в C++. Обратите внимание, что он объявлен как static (это влияет на то, что (jclass или jobject) будет в качестве второго параметра C++-функции) и native. Реализовывать native-метод не нужно, мы это уже сделали в C++, а остальное за нас сделает JNI.Теперь мы, в общем-то, уже можем вызвать нашу функцию. Если вы, как и я, выбрали FullscreenActivity, то у нас есть Dummy Button, который, по сути, ничего не делает. И даже уже есть touch listener, пусть и не самый лучший (он будет вызываться много раз, пока палец на экране), но, чтобы не плодить лишний код, используем его.
Для начала добавим в список импорта: import android.widget.Button; чтобы можно было нормально работать с кнопкой.
Найдем следующий код:
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener () { @Override public boolean onTouch (View view, MotionEvent motionEvent) { if (AUTO_HIDE) { delayedHide (AUTO_HIDE_DELAY_MILLIS); } return false; } }; и добавим несколько строк перед return false. View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener () { @Override public boolean onTouch (View view, MotionEvent motionEvent) { if (AUTO_HIDE) { delayedHide (AUTO_HIDE_DELAY_MILLIS); } final String message = stringFromJNI (); final Button button = (Button)findViewById (R.id.dummy_button); final String actualText = button.getText ().toString (); if (message.equals (actualText)) { button.setText («Dummy Button»); } else { button.setText (message); } return false; } }; Описание добавленного кода final String message = stringFromJNI (); — получаем строку из C++. Вызов нативного метода — то, ради чего все и затевалось. final Button button = (Button)findViewById (R.id.dummy_button); — находим объект кнопки. final String actualText = button.getText ().toString (); — вытаскиваем текущий текст кнопки. if (message.equals (actualText)) — сравниваем строку, полученную из C++, с текстом кнопки. button.setText («Dummy Button»); — если одинаковы, меняем текст кнопки на Dummy Button. button.setText (message); — если различаются, то меняем на текст, полученный из C++. Полный листинг класса измененного класса package com.example.markedone.myapp;
import com.example.markedone.myapp.util.SystemUiHider;
import android.annotation.TargetApi; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.view.MotionEvent; import android.view.View; import android.widget.Button;
/** * An example full-screen activity that shows and hides the system UI (i.e. * status bar and navigation/system bar) with user interaction. * * @see SystemUiHider */ public class FullscreenActivity extends Activity {
static { System.loadLibrary («myapp»); }
private static native String stringFromJNI ();
/** * Whether or not the system UI should be auto-hidden after * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. */ private static final boolean AUTO_HIDE = true;
/** * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after * user interaction before hid