Android Studio com JNI: Exemplo prático

On 16 de outubro de 2015 by Mauricio José

jni Introdução

OpenCV é uma biblioteca largamente escrita em C/C++. Embora a biblioteca forneça uma interface em Java (OpenCV4Android), a maior parte dessa interface é uma camada acima da implementação em C/C++. Quando o OpenCV Java encaminha uma chamada de função para o C++, um pequeno overhead é provocado. Se o programa faz centenas chamadas de função por frame (como por exemplo uma chamada por pixel) este overhead pode se tornar proibitivo.

JNI (Java Native Interface) permite que código em C/C++ faça chamadas a códigos em java e vice-e-versa. Porém, o uso de JNI torna o desenvolvimento mais complexo ao ponto de, em alguma vezes, não justificar o ganho em desempenho. Para comparação, devemos considerar o que a google disse sobre o desenvolvimento utilizando o NDK contra o desenvolvimento utilizando SDK.

Before downloading the NDK, you should understand that the NDK will not benefit most apps. As a developer, you need to balance its benefits against its drawbacks. Notably, using native code on Android generally does not result in a noticeable performance improvement, but it always increases your app complexity. In general, you should only use the NDK if it is essential to your app never because you simply prefer to program in C/C++.

Por outro lado, o desenvolvimento em C/C++ oferece algumas características que não temos quando desenvolvemos em java.

  • Gerência de memória: em Java a gerência de memória é feita pelo Garbage Collector. O controle manual de gerência de memória pode ser usual em situações onde temos restrições de recursos.
  • Interoperabilidade com outras bibliotecas: permite utilizar bibliotecas já existentes em C/C++;
  • Compatibilidade entre plataformas: Permite reutilizar o código em diversas plataformas.

Para o restante do post, nós vamos assumir que você já tenha o Android Studio e o NDK instalados em seu computador, por esse motivo a instalação do Android Studio não será abordada neste post. Caso você ainda não tenha instalado, baste seguir os passos recomendados pelo site oficial do Android Studio (https://developer.android.com/sdk/index.html).

Primeiro projeto com JNI no Android Studio

Primeiramente vamos criar um projeto no Android Studio,  se você ainda não sabe como faz, basta seguir os passos:

  1. Abra o Android Studio e clique em “Start a new Android Studio Project”
  2. Dê um nome ao seu projeto e clique em next. Eu criei o projeto com o nome NDKSample.
  3. Deixe o checkbox Phone and Tablets marcado, escolha o SDK Mínimo e clique em next. Escolhi o SDK API 16.
  4. Escolha “Black Activity” e clique em next. Na tela seguinte clique em finish.

Após todos este passos o nosso novo projeto está criado. Agora precisamos criar os métodos que oferecerão uma interface em java, mas serão implementados em C/C++.

Do lado Java existem dois componentes importantes, o System.loadLibrary(), que nós vamos utilizar para carregar a biblioteca implementada. E a palavra reservada native, que será utilizada para informar ao android que aquele método está implementado na parte nativa da aplicação.

Para que o android faça a ligação entre os métodos que você declarou nativo e sua implementação em C/C++, é necessário que o método na parte C/C++ seja declarado com um nome bem específico, que é a junção da palavra Java, seguido do nome do pacote mais o nome do métodos. Além disso, são criados dois argumentos que são passado implicitamente sempre que uma chamada a um método nativo é feita, estes argumentos correspondem ao ambiente Java e classe Java de seu método. Porém, não precisamos nos preocupar muito em decorar essas particularidades, nós vamos utilizar um comando que irá gerar um stub da classe a ser implementada na parte nativa.

O próximo passo a ser feito é criar um método e declará-lo como nativo dentro da classe que desejamos utilizá-lo. Para isso, abra o arquivo MainActivity.java e adicione, no final da classe, a seguinte chamada:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public native String getStringFromNative();
}

Depois que o método nativo foi declarado em nossa classe, precisamos gerar o stub para fazer a implementação em C++. Uma vantagem de utilizar o Android Studio para isso, é que ele cuidará de todo o processo de compilação e de integração da nova lib no aplicativo. Para gerar o código da parte nativa, abra o terminal e dentro da pasta main do seu projeto digite o seguinte comando:

iMac-de-Ricardo:main mauriciosilva$ javah -d jni -classpath java/ br.com.badricio.ndksample.MainActivity
iMac-de-Ricardo:main mauriciosilva$ 

Dentro da pasta main do seu projeto será criado uma pasta chamada jni com o stub para o método que declaramos como nativo. Veja a figura abaixo:

Captura de Tela 2015-10-15 às 11.28.28

Agora vamos renomear este arquivo e escrever o arquivo .c com a implementação do método getStringFromNative. Que deve ficar como na Figura abaixo:

Captura de Tela 2015-10-15 às 11.35.11

Observem que renomeei o stub que foi gerado anteriormente, mas mantive seu conteúdo inalterado. No arquivo helloJNI.c implementei o método getStringFromNative para retornar a string “Hello From JNI!”. Agora vamos compilar o projeto, para isso clique no menu build > Make project. Ao terminar de compilar, o seguinte erro irá aparecer.

Error:Execution failed for task ':app:compileDebugNdk'.
> Error: NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin. For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental. Set "android.useDeprecatedNdk=true" in gradle.properties to continue using the current NDK integration.

Para corrigir esse erro precisamos abrir o arquivo “gradle.properties” e adicionar a linha android.useDeprecatedNdk=true.

Captura de Tela 2015-10-15 às 11.40.10

Agora abra o arquivo app/build.gradle e configure o nome do módulo NDK.

Captura de Tela 2015-10-15 às 11.50.09

Feito isso, precisamos preparar nosso aplicativo para exibir a string retornada pelo método nativo. Para isso, deixe eu main_activity.xml da seguinte forma:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

Após editar o arquivo de layout da aplicação, temos que fazer as ultimas alterações para que nosso aplicativo esteja pronto.

Uma vez que já implementamos nosso código nativo e preparamos o gradle para compilá-lo, precisamos incorporar a biblioteca compilada no nosso projeto. Para isso, o comando System.loadLivrary já mencionado anteriormente deve ser adicionado em um bloco estático no início do arquivo em nós declaramos o método nativo. Desta forma:

 static{
        System.loadLibrary("HelloJNI");
    }

Para finalizar, vamos adicionar no método onCreate código para recuperar o TextView que será exibido a string retornada pelo método nativo, e vamos exibir o texto na tela. o código completo da classe MainActivity.java fica assim:

package br.com.badricio.ndksample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView tview;

    static{
        System.loadLibrary("HelloJNI");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tview = (TextView) findViewById(R.id.textView);
        tview.setText(getStringFromNative());
    }

    public native String getStringFromNative();
}

 

Resultado

Ao mandar executar o aplicativo você tera como resultado:

Screenshot_2015-10-15-13-56-49

 

Esta série de posts tem com o objetivo fazer com que o entendimento sobre o assunto seja atingido de forma espiral. Neste post nos vimos como fazer um aplicativo que exibe um texto enviado por um método JNI. Nos próximos vamos executar o exemplo de detecção de faces já implementado no OpenCV e fazer a analise de desempenho entre um aplicativo desenvolvido com NDK e um aplicativo desenvolvido com SDK.

Link para download do projeto utilizado neste tutorial.

Android_NDK-app

Referências

[1] https://developer.android.com/ndk/index.html

[2] https://software.intel.com/en-us/articles/training-series-for-development-on-intel-based-android-devices

[3] Joseph Howse. Android Application Programming with OpenCV 3. Packt Publishing, 2015

[4] Salil Kapur e Nisarg Thakkar. Mastering OpenCV Android Application Programming. Packt Publishing, 2015

 

 

Summary
Android Studio com JNI: Exemplo prático
Article Name
Android Studio com JNI: Exemplo prático
Description
Um exemplo prático de como usar o JNI no Android Studio
Author
Publisher Name
iMobilis
Publisher Logo

One Response to “Android Studio com JNI: Exemplo prático”

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *