O mecanismo de jogos Corona permite criar aplicativos e jogos entre plataformas. Mas às vezes a API que eles fornecem não é suficiente. Para esses casos, existe o Corona Native , que permite estender a funcionalidade usando código nativo para cada plataforma.
Este artigo discutirá o uso de Java em projetos Corona para android
Para entender o que está acontecendo no artigo, você precisa de conhecimentos básicos de Java, Lua e o mecanismo Corona
Introdução
O Corona e o Android Studio devem estar instalados no computador
A pasta de instalação do Corona também contém o modelo do projeto: Native \ Project Template \ App. Copie a pasta inteira e renomeie-a para o nome do seu projeto.
Personalização de modelos
Nota: usei a compilação pública mais recente disponível para Corona - 2017.3184 . Nas novas versões, o modelo pode mudar e alguns preparativos deste capítulo não serão mais necessários.
Para android, precisamos de 2 pastas dentro: Corona e android
Na pasta Corona , exclua Images.xcassets e LaunchScreen.storyboardc - não precisamos dessas pastas. No arquivo main.lua , também excluímos todo o código - começaremos a criar o projeto do zero. Se você deseja usar um projeto existente, substitua todos os arquivos na pasta Corona por seus próprios.
A pasta android é um projeto finalizado para o Android Studio, precisamos abri-lo. A primeira mensagem do estúdio será "Falha na sincronização do Gradle". Precisa corrigir o build.gradle:

Para corrigir a situação, inclua um link para repositórios no buildscript. Também alterei a versão no caminho de classe 'com.android.tools.build:gradle' para uma mais nova.
Código Build.gradle// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.1.3' } repositories { jcenter() google() } } allprojects { repositories { jcenter() google() } } task clean(type: Delete) { delete rootProject.buildDir }
O próximo passo é alterar gradle-wrapper.properties . Você pode alterá-lo manualmente, substituindo a versão do gradle em distributionUrl . Ou deixe o estúdio fazer tudo por você.

Além disso, você precisa corrigir o build.gradle para o módulo do aplicativo: em cleanAssets, você precisa adicionar a linha de exclusão "$ projectDir / build / intermediates / jniLibs" , sem a qual você precisará executar um projeto limpo antes de cada início ( extraído daqui )
Agora a sincronização foi bem-sucedida, existem apenas alguns avisos relacionados ao buildToolsVersion obsoleto e à sintaxe antiga na configuração. Corrigi-los não é difícil.
Agora, no estúdio, vemos 2 módulos: aplicativo e plugin. Renomeie o aplicativo (com.mycompany.app) e o plug-in (plugin.library) antes de continuar.
Mais adiante no código, o plug-in será chamado plugin.habrExamplePlugin
O plug-in contém a classe LuaLoader por padrão - ele será responsável por manipular chamadas do código lua. Já existe algum código, mas vamos esclarecer.
Código LuaLoader package plugin.habrExamplePlugin; import com.naef.jnlua.JavaFunction; import com.naef.jnlua.LuaState; @SuppressWarnings({"WeakerAccess", "unused"}) public class LuaLoader implements JavaFunction { @Override public int invoke(LuaState luaState) { return 0; } }
Usando o código do plugin do código lua
O Corona Native usa jnlua para ligação entre código java e lua. LuaLoader implementa a interface jnlua.JavaFunction, portanto, seu método de chamada está disponível no código lua. Para garantir que tudo esteja em ordem, adicione o código de log em LuaLoader.invoke e faça o plug-in de solicitação em main.lua
@Override public int invoke(LuaState luaState) { Log.d("Corona native", "Lua Loader invoke called"); return 0; }
local habrPlugin = require("plugin.habrExamplePlugin") print("test:", habrPlugin)
Após o lançamento do aplicativo, entre os logs, veremos as 2 linhas a seguir:
D / Corona nativo: chamada do Lua Loader chamada
I / Corona: teste verdadeiro
Portanto, nosso aplicativo baixou o plug-in e exige retornos true. Agora vamos tentar retornar uma tabela lua com funções do código Java.
Para adicionar funções ao módulo, usamos a interface jnlua.NamedJavaFunction. Um exemplo de uma função simples sem argumentos e sem valor de retorno:
class HelloHabrFunction implements NamedJavaFunction { @Override public String getName() { return "helloHabr"; } @Override public int invoke(LuaState L) { Log.d("Corona native", "Hello Habr!"); return 0; } }
Para registrar nossa nova função em lua, usamos o método LuaState.register:
public class LuaLoader implements JavaFunction { @Override public int invoke(LuaState luaState) { Log.d("Corona native", "Lua Loader invoke called"); String libName = luaState.toString(1);
Este código requer explicações adicionais:
LuaState, um parâmetro do método invoke, representa essencialmente um invólucro sobre a máquina virtual Lua (por favor, corrija-me se eu colocar errado). Para aqueles familiarizados com o uso do código lua de C, LuaState é igual ao ponteiro lua_State em C.
Para aqueles que querem mergulhar na selva de trabalhar com lua, recomendo a leitura do manual, começando com a Interface do programa de aplicativos
Então, quando invoke é chamado, obtemos LuaState. Ele tem uma pilha que contém parâmetros passados para nossa função a partir do código lua. Nesse caso, esse é o nome do módulo, já que LuaLoader é executado quando a chamada require ("plugin.habrExamplePlugin").
O número retornado por invoke mostra o número de variáveis da pilha que serão retornadas ao código lua. No caso de require, esse número não tem efeito, mas usaremos esse conhecimento posteriormente criando uma função que retorna vários valores
Adicionando campos ao módulo
Além das funções, também podemos adicionar campos adicionais ao módulo, por exemplo, a versão:
luaState.register(libName, luaFunctions);
Nesse caso, usamos o índice -2 para indicar que o campo precisa ser definido para o nosso módulo. Um índice negativo significa que a contagem começa no final da pilha. -1 apontará para a cadeia "0.1.2" (em lua, os índices começam com um).
Para não obstruir a pilha, depois de definir o campo, recomendo chamar luaState.pop (1) - lança 1 elemento da pilha.
Código LuaLoader completo @SuppressWarnings({"WeakerAccess", "unused"}) public class LuaLoader implements JavaFunction { @Override public int invoke(LuaState luaState) { Log.d("Corona native", "Lua Loader invoke called"); String libName = luaState.toString(1); // ( require) NamedJavaFunction[] luaFunctions = new NamedJavaFunction[]{ new HelloHabrFunction(), // }; luaState.register(libName, luaFunctions); // , luaState.register(libName, luaFunctions); // , luaState.pushString("0.1.2"); // luaState.setField(-2, "version"); // version . // 1 lua . // require , require return 0; } }
Exemplos de funções
Um exemplo de uma função que pega várias cadeias e as concatena através do construtor StringImplementação:
class StringJoinFunction implements NamedJavaFunction{ @Override public String getName() { return "stringJoin"; } @Override public int invoke(LuaState luaState) { int currentStackIndex = 1; StringBuilder stringBuilder = new StringBuilder(); while (!luaState.isNone(currentStackIndex)){ String str = luaState.toString(currentStackIndex); if (str != null){
Uso em lua:
local joinedString = habrPlugin.stringJoin("this", " ", "was", " ", "concated", " ", "by", " ", "Java", "!", " ", "some", " ", "number", " : ", 42); print(joinedString)
Exemplo de retorno de vários valoresclasse SumFunction implementa NamedJavaFunction {
Substituir
public String getName () {
retornar "soma";
}
@Override public int invoke(LuaState luaState) { if (!luaState.isNumber(1) || !luaState.isNumber(2)){ luaState.pushNil(); luaState.pushString("Arguments should be numbers!"); return 2; } int firstNumber = luaState.toInteger(1); int secondNumber = luaState.toInteger(1); luaState.pushInteger(firstNumber + secondNumber); return 1; }
}
Reflexão Java - usando classes Java diretamente no lua
A biblioteca jnlua possui uma classe JavaReflector especial que é responsável por criar uma tabela lua a partir de um objeto java. Assim, você pode escrever classes em java e fornecer código lua para uso futuro.
Para fazer isso é bastante simples:
Exemplo de classe
@SuppressWarnings({"unused"}) public class Calculator { public int sum(int number1, int number2){ return number1 + number2; } public static int someStaticMethod(){ return 4; } }
Adicionando uma instância desta classe ao nosso módulo
luaState.pushJavaObject(new Calculator()); luaState.setField(-2, "calc"); luaState.pop(1);
Uso em Lua:
local calc = habrPlugin.calc print("call method of java object", calc:sum(3,4)) print("call static method of java object", calc:getClass():someStaticMethod())
Observe os dois pontos na chamada do método de classe. Para métodos estáticos, você também deve usar dois pontos.
Então notei um recurso interessante do refletor: se passarmos apenas uma instância da classe para lua, chamar o método estático será possível via getClass (). Porém, após uma chamada por meio de getClass (), as chamadas subsequentes serão acionadas no próprio objeto:
print("call method of java object", calc:sum(3,4))
Além disso, usando getClass (), podemos criar novos objetos diretamente no lua:
local newInstance = calc:getClass():new()
Infelizmente, não foi possível salvar o Calculator.class no campo do módulo devido a "java.lang.IllegalArgumentException: tipo ilegal" dentro do setField .
Criando e chamando funções lua em tempo real
Esta seção apareceu porque a coroa não fornece a capacidade de acessar funções de sua API diretamente em Java. Mas o jnlua.LuaState permite carregar e executar o código lua arbitrário:
class CreateDisplayTextFunction implements NamedJavaFunction{
Lembre-se de registrar a função através do LuaLoader.invoke, semelhante aos exemplos anteriores
A chamada em lua:
habrPlugin.createText("Hello Habr!")
Conclusão
Assim, seu aplicativo Android pode usar todos os recursos nativos da plataforma. A única desvantagem desta solução é que você perde a capacidade de usar o Corona Simulator, o que atrasa o desenvolvimento (reiniciar o simulador é quase instantâneo, diferente da depuração em um emulador ou dispositivo que requer build + install)
Links úteis
Código completo disponível no github
Documentação nativa Corona
3) Um dos repositórios jnlua . Me ajudou a entender o objetivo de algumas funções.