Qt no Android? Interoperabilidade entre Qt e o Android
Como alguns de vocês sabem, tenho cada vez mais me aprofundado no desenvolvimento mobile, usando os mais variados SDKs. Recentemente precisei integrar a SDK da RevMob num projeto mobile usando Qt; hoje irei cobrir apenas o Android, mas em breve postarei como fiz o mesmo para iOS.
Como podemos ver na página da SDK da RevMov, temos suporte a diversas plataformas, bibliotecas e frameworks, mas não para o Qt. #chatiado
Minha primeira tentativa consistiu em desenvolver uma SDK do zero, a partir da versão em javascript, porém notei que estaria reinventando a roda, e que levaria muito mais tempo; por outro lado teria apenas uma base de código para iOS, Android, Desktop e o que mais o Qt suportar.
Resolvi recomeçar pela versão em Android. Baixei o SDK para Android, pacote onde temos a documentação, um exemplo e um arquivo jar, que é a RevMob SDK propriamente dita.
Eis que começa a aventura! Olhando o exemplo e a documentação, o primeiro passo é instanciar a classe RevMob usando o método start, que recebe uma instância de Activity do Android em Java, como pode ser visto abaixo:
public static RevMob start(android.app.Activity activity)
Já temos um problema: o Qt é um framework em C++. Embora existam as classes como a QAndroidJniObject e QAndroidJniEnvironment, que nos ajudam na interoperabilidade, abstraindo as chamadas de funções e conversões de tipos usando o JNI, era preciso a instância de alguma Activity.
É sabido que toda aplicação para Android deve ter um AndroidManifest.xml e que deve ter pelo menos uma Activity. E onde estaria esse código que magicamente aparecia durante as etapas de compilação? Infelizmente isto ainda não é muito bem documentado no Qt (ou acabei passando batido :P). De qualquer forma precisava adicionar uma Activity customizada e alterar o AndroidManifest.xml.
Depois de uma breve pesquisa no diretório de instalação do Qt, encontrei o seguinte caminho:
$QTDIR/5.*.*/android_(armv5|android_armv7|android_x86)/src/android/java
- $QTDIR O diretório raiz
- 5.*.* A versão
- android(armv5 | android_armv7 | android_x86) _A arquitetura
É onde tem exatamente o que precisava, o AndroidManifest.xml, version.xml, os diretórios src e res. E o que temos dentro de src/org/qtproject/qt5/android/bindings
? Temos QtActivity QtApplication, que herdam Activity e Application do Android SDK respectivamente, e como podemos ver, no AndroidManifest.xml
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="@string/app_name">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize..."
android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:label="@string/app_name"
android:screenOrientation="unspecified">
...
</application>
Agora que temos quase tudo que precisamos, só é preciso descobrir como colocar toda essa tranqueira na hora do deploy. Lendo a documentação, descobri a variável ANDROID_PACKAGE_SOURCE_DIR, que faz justamente o que precisava, então copiei o AndroidManifest.xml do Qt para o diretório android-sources e adicionei a seguinte entrada no .pro do projeto
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
Para poder usar as classes QAndroidJniObject e QAndroidJniEnvironment é necessário adicionar androidextras à variável QT
QT += core gui widgets androidextras
Precisamos de algumas customizações no AndroidManifest.xml; uma delas é para adicionar a FullscreenActivity do RevMob:
<application ...>
<activity android:name="com.revmob.ads.fullscreen.FullscreenActivity"
android:theme="@android:style/Theme.Translucent"
android:configChanges="keyboardHidden|orientation">
</activity>
...
</application>
Lembram que era preciso a instância de uma activity? Para isto criei um wrapper, No diretório android-sources, criei a estrutura de diretórios src/com/revmob e dentro um arquivo com o nome RevMobActivity.java, com o seguinte conteúdo:
package com.revmob;
import org.qtproject.qt5.android.bindings.QtActivity;
import com.revmob.RevMob;
import com.revmob.RevMobTestingMode;
import com.revmob.client.RevMobClient;
import android.util.Log;
import android.app.Activity;
import java.lang.String;
public class RevMobActivity extends QtActivity {
private static Activity activity;
public RevMobActivity() {
activity = this;
}
public static void startSession(String appId) {
RevMobClient.setSDKName("qt-android");
RevMobClient.setSDKVersion("0.0.1");
RevMob.start(activity, appId);
}
public static void showFullscreen() {
RevMob revmob = RevMob.session();
revmob.showFullscreen(activity);
}
// ...
}
E apontamos essa nova classe no AndroidManifest.xml, substituindo org.qtproject.qt5.android.bindings.QtActivity
por com.revmob.RevMobActivity
. Com isto, o Android passa a instanciar a classe customizada ao invés da classe do Qt, e finalmente temos um wrapper.
Para finalizar esta etapa precisamos adicionar o arquivo revmob-6.8.2.jar da SDK do RevMob no diretório libs, ainda dentro de android-sources.
Finalmente poderemos voltar à programação de verdade.
Para começar a utilizar precisamos iniciar uma sessão, o que pode ser feito na inicializacão do app, da seguinte maneira:
QString appId = "";
QAndroidJniObject param = QAndroidJniObject::fromString(appId);
QAndroidJniObject::callStaticMethod<void>("com/revmob/RevMobActivity",
"startSession",
"(Ljava/lang/String;)V",
param.object<jstring>());
Onde appId é um código fornecido pela RevMob que identifica sua app.
Para mostrar um banner em FullScreen é bem simples - só precisamos invocar aquele método showFullscreen que foi criado logo acima, assim:
QAndroidJniObject::callStaticMethod<void>("com/revmob/RevMobActivity", "showFullscreen");
O trecho acima invoca um método em Java da classe RevMobActivity, responsável por carregar e exibir um anúncio em fullscreen.
É muito legal poder rodar meus projetos em Qt no meu celular, apenas recompilando, e mais legal ainda saber que posso acessar recursos da SDK do Android de forma semi transparente…