Android NDK: faire du C avec Android !

Android met un disposition des développeurs un NDK (Native Developpement Kit). Le NDK permet d’écrire une partie du code dans le langage C ou C++.
C’est un langage compilé, il est de plus bas niveau que le java qui lui est interprété. Réaliser des portions de code (qui demande beaucoup de calcul, comme la compression), peut être réaliser en C/C++ afin d’augmenter significativement les performances.

Dans cet astuce je vais décrire les étapes de réalisation d’une classe en C, utilisable depuis une classe en Java.

1- JNI

Le kit de développement java contient nativement la bibliothèque JNI (Java Native Interface). Celle librairie permet d’interfacer du code Java avec avec d’autres langages comme le C.

2- Installation

Puisque je travaille sous Windows, j’ai besoin d’utiliser un compilateur afin de compiler mon code C. J’ai donc installé le logiciel Cygwin qui permet de simuler une console Linux sous Windows. Note, il faudra préciser manuellement qu’il faut installer l’outils « make ».

Ensuite nous pouvons télécharger le NDK d’Android. Il suffira de décompresser l’archive dans le répertoire de votre choix (par exemple pour moi c:\Users\julien\android_ndk).

3- La base de l’application Android

Pour ce projet, nous allons réaliser une application qui comportera 5 principaux fichiers:

MainActivity: qui permettra de démarrer l’application et d’initialiser la vue.

MyClassInC: C’est une classe Java qui nous permettra de déclarer et d’utiliser les méthodes qui seront utilisable en C.

ClassInC: Ce fichier contiendra l’implémentation de la classe en C.

Android.mk: Ce fichier servira à décrire les fichiers à compiler.

Dans le répertoire du projet, nous devons créer à coté du répertoire « src », « res » et autre, le répertoire « jni » qui nous servira à placer les fichier Android.mk et les fichiers C/C++.

Vous devrez respecter le nom du répertoire.

4- La vue

Pour notre vue, nous aurons simplement, un champ de texte éditable, un bouton, et un champ de texte pour visualiser le résultat.

Voici le code XML de cette vue:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="center_horizontal|center_vertical"
   android:orientation="vertical">

    <EditText
       android:id="@+id/edit_text"
       android:layout_margin="20dip"
       android:layout_width="190dip"
       android:layout_height="wrap_content"/>
    <Button
       android:id="@+id/edit_button"
       android:layout_margin="20dip"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Editer"/>
    <TextView
       android:id="@+id/data_text"
       android:layout_margin="20dip"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Aucune données" />

</LinearLayout>

5- L’activité

L’activité principale de notre application contient le code suivant:

package com.example.testndk;
/**
 * @author: julien dumortier
 */


import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {

      private MyClassInC mMyClassInC;
     
      protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mMyClassInC = new MyClassInC();

            //action du bouton d'édition
            findViewById(R.id.edit_button).setOnClickListener(new OnClickListener(){
                  public void onClick(View v) {
                        //récupération du text de l'EditText
                        String data = ((EditText)findViewById(R.id.edit_text)).getText().toString();

                        //on set les data de la classe en C
                        mMyClassInC.setData(data);

                        //on get les data de la classe en C
                        String dataFromClassInC = mMyClassInC.getData();

                        //on edite la TextView
                        ((TextView)findViewById(R.id.data_text)).setText(dataFromClassInC);
                  }
            });
      }
}


6- La classe de liaison entre le Java et le C

Voici le code de la classe MyClassInC:

package com.example.testndk;
/**
 * @author: julien dumortier
 */


public class MyClassInC {

      //chargement de la librairie
      static {
            System.loadLibrary("classinc");
      }    
     
      public native void setData(String data);
      public native String getData();
}

Notez ici que les que nous déclarons des méthodes en native, celles-ci seront implémentés en C.

7- Le fichier Android.mk

Comme je le disais plus haut, ce fichier décrit les fichiers à compiler. Mais c’est aussi lui qui sera charger de mettre a disposition la librairie (celle qui est chargée dans MyClassInC).

Voici le code de ce fichier:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := classinc
LOCAL_SRC_FILES := classinc.c
LOCAL_LDLIBS := -L/cygdrive/c/Users/julien/android-ndk-1.5_r1-windows/android-ndk-1.5_r1/build/platforms/android-1.5/arch-arm/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)

ce fichier doit être inclus dans le répertoire jni.

include $ (CLEAR_VARS) permet d’utiliser les variables LOCAL_MODULE et LOCAL_SRC_FILES

LOCAL_MODULE permet de déclarer le nom de la librairie.

LOCAL_SRC_FILES permet de déclarer la liste des fichier C/C++ à compiler.

8- L’implémentation en C

Nous y arrivons, voici le code en C, qui implémente les deux méthodes déclarées nativement dans la classe MyClassInC:

/*
 * CLASSINC.C
 *
 * @Description: contient un getter et un setter
 *               sur une chaine de caractères.
 *               Utilise des méthodes de convertion
 *               jstring -> char et inversement.
 *
 * @Author: julien dumortier
 */


//chargement de la librairie JavaNativeInterface
#include <jni.h>
#include <stdio.h>

char *mData ;

void Java_com_example_testndk_MyClassInC_setData(JNIEnv *env, jobject ioThis, jstring data)
{
      //jbyte* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy)
      //convertion d'un jstring en char*
      mData = (*env)->GetStringUTFChars(env, data, JNI_FALSE);
}

jstring Java_com_example_testndk_MyClassInC_getData(JNIEnv *env, jobject ioThis)
{
      //jstring NewStringUTF(JNIEnv *env, const char *bytes);
      //convertion d'un char* en jstring
      return (jstring) (*env)->NewStringUTF(env, mData);
}

ce fichier doit être inclus dans le répertoire jni.

jstring est un type faisant partie de la librairie jni, et qui correspond au format String de Java. Le nom de la méthode doit commencer par « Java_ » puis être suivit de l’arborescence des packages jusqu’au nom de la méthode native dans la classe MyClassInC.
GetStringUTFChars et NewStringUTF sont deux méthodes de la librairie jni permettant la conversion de chaîne de caractères.
voici un lien vers la documentation des méthodes proposés par jni:
documentation

9- compilation

Vous pouvez maintenant démarrer Cygwin. Avec la commande « cd », placez-vous dans le répertoire de votre projet:

ls_cygwinA noter que l’arborescence des fichiers commence par « /cygdrive/LISTE_DES_DISQUES ».

Pour compiler, vous devrez utiliser la commande « ndk-build », disponible dans le répertoire du ndk. Vous pouvez l’exécuter comme ceci:

build_cygwinSi tout se passe bien vous devriez obtenir un écran comme celui-ci:

builded_cygwin

10- Test

Une fois la compilation finie, vous pouvez lancer l’application. lorsque vous cliquez sur le bouton, le texte contenu dans l’EditText est envoyé à la classe en C par l’intermédiaire de MyClassInC puis de jni. Et est ensuite récupérer pour être affiché.

resultat_ndk

Voici les sources de ce projet:
sources

Les commentaires sont désactivés.