P4b - Programmation mobile

1 - Débuter avec Android

Adrien Krähenbühl IUT Robert Schuman

Introduction

Bref historique

  


2003 : création de la startup “Android Inc”.
2005 : rachat d’Android Inc par Google.
2008 : Premier téléphone commercialisé avec Android.
2014 : v5 avec support 64 bits, moteur ART et Material Design.
2021 : sortie de la version 12.

Présentation d’Android


Android est un système d’exploitation dédié aux terminaux mobiles.


Android c’est :

  • une license “Open source” Apache 2.0
  • des bibliothèques (SQLite, OpenGL, …),
  • un noyau Linux (et ses pilotes),
  • des applications natives : téléphonie, contacts, navigateur, etc.

Architecture Android

  • Application Framework est la couche principalement utilisée par les développeurs pour interagir avec le système.
  • Android System Service est une couche accessibles aux applications pour interagir directement avec le système.
  • HAL (Hardware Abstraction layer) est une couche d’abstraction du matériel.
  • Le noyau est un noyau Linux personnalisé.

Qu’est qu’une application Android ?


Une application Android est une archive APK (Android PacKage) d’extension .apk, basé sur les archives JAR de Java.


Une archive APK contient :

  • des fichiers binaire compilés au format .dex,
  • un ensemble de ressources (images, sons, etc.),
  • un Manifeste, fichier XML de méta-données de l’application,
  • des méta-informations permettant de vérifier l’intégrité de l’archive.

Compilation et exécution

ART : Android Runtime

  • Environnement d’exécution avec compilation anticipée (AOT).
  • Utilisation d’un ramasse-miette.
  • Pas d’interprétation, c’est compilé en langage machine.
  • Remplace Dalvik depuis 2014, lui-même alternative de la JVM.

SDK Android

Un SDK (Software Development Kit) est un ensemble d’outils
destiné aux développeurs.

Le SDK d’Android contient :

  • des bibliothèques logicielles
  • un débogueur (ADB)
  • un émulateur (basé sur QEMU)
  • la documentation
  • des exemples de code et des tutoriels.

Android Studio

Structure d’un projet Android

  • AndroidManifest.xml : fichier XML contenant les informations essentielles de compilation et d’exécution
  • java : code source Java du projet
  • res/drawable : ressources images
  • res/layout : description des interfaces graphiques en XML
  • res/values : dictionnaires de variables à utiliser dans le code et l’UI
  • build.gradle : fichier de configuration de Gradle, système de build d’Android Studio.

Attention, cette architecture n’est pas celle des fichiers,
c’est une vue simplifiée proposée par Android Studio.

Concepts

Principes généraux

Réutilisabilité
Chaque application peut tirer partie de certains éléments d’autres applications en appelant uniquement une action spécifique proposée par cette application.
Indépendance
Il n’y a pas de point d’entrée global pour les applications : elles sont composées d’éléments indépendants, utilisables individuellement.

Les 4 principaux composants

Les activités (class Activity)
Elles permettent d’interagir avec l’utilisateur.
Les services (classe Services)
Ils permettent de réaliser de tâches en arrière-plan.
Les fournisseurs de contenu (classe ContentProvider)
Ils gèrent des données partagées au sein de votre application et avec les autres applications, par exemple dans une BDD SQLite.
Les récepteurs d’émissions (classe BroadcastReceiver)
Ils permettent de réagir à des messages diffusés par le système Android ou d’autres applications.

Pour communiquer, ils utilisent les intentions (classe Intent).

Le fichier AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
                package="p4b.tp1.myapplication">
    <application
        android:label="@string/app_name"
        android:theme="@style/Theme.App">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

<manifest />

  • Chaque activité doit être déclarée dans le manifest
  • package="..." désigne le package racine de l’application

<application />

  • android:label est le nom de l’application. @string fait référence aux balises <string> des fichiers XML placés dans res/values
  • android:theme fait référence au style <style name="Theme.App"> situé dans un fichier de res/values

<activity />

  • android:name est le nom de l’activité depuis le package racine
  • android:exported (dés)active l’accès de cette activité aux autres
  • L’intent-filter désigne cette activité comme principale, à lancer au démarrage de l’application. Il en faut nécessairement une.

<uses permission />

  • Déclare que cette application a besoin de cette permission.
  • Il n’est pas possible d’utiliser une fonctionnalité nécessitant une permission si elle n’est pas ajoutée au manifeste.
  • Il y a une looooongue liste des permissions.

Les ressources

La classe R

La classe R
  • contient tous les identifiants des resources déclarées dans les fichiers XML du répertoire res/.
  • est générée automatiquement à la compilation
  • ne contient que des champs statiques de type int
Déclarer une resource
<resources>
    <r_type name="r_name">r_value</r_type>
</resources>

Accéder aux ressources

R.r_type.r_name
setContentView(R.layout.activity_main);

… dans le code Java.

@r_type/r_name
<application android:label="@string/app_name" />

… dans les fichiers XML.

Les Activités

La classe Activity

L’activité est l’élément fondamental d’une application Android.

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
  • Une application standard est composée de plusieurs Activity
  • Une activité est une sous-classe de Activity ou AppCompatActivity
  • Chaque Activity est un point d’entrée potentiel de l’application
  • Couplage faible entre activités :
    • Ne pas invoquer de méthode appartenant à d’autres activités
    • Ne pas garder de références sur des objets Activity

Créer une activité

  1. Créer une nouvelle classe qui hérite de la classe Activity.
  2. Déclarer l’activité dans le fichier AndroidManifest.xml.
  3. Surcharger la méthode onCreate qui invoque super.onCreate(...).
  4. Sélectionner la vue (layout en XML du répetoire res/layout)
    avec setContentView(...).

Android Studio réalise tout cela automatiquement lorsqu’on crée une nouvelle activité via le menu.

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Bonne pratique : Le constructeur d’une activité n’est jamais utilisé !

Le cycle de vie des activités

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState)
{ ... }

@Override
protected void onStart() { ... }

@Override
protected void onResume() { ... }

@Override
protected void onPause() { ... }

@Override
protected void onStop() { ... }

@Override
protected void onRestart() { ... }

@Override
protected void onDestroy() { ... }
}

Sauvegarde de l’état d’une activité

Lorsqu’une application est stoppée, il est possible de sauvegarder sont état dans un Bundle pour le restaurer ensuite.

1 - Avant d’être détruite, l’activité exécute onSaveInstanceState :

static final String STATE_SCORE = "playerScore";

@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    super.onSaveInstanceState(savedInstanceState);
}

2.a - Le Bundle en paramètre de onCreate permet de restaurer les valeurs précédemment sauvegardées :

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null)
        currentScore = savedInstanceState.getInt(STATE_SCORE);
}

2.b - … ou celui de cette méthode, appelée après onStart() seulement s’il y a un Bundle sauvegardé :

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    // Pas de vérification nécessaire de savedInstanceState != null
    currentScore = savedInstanceState.getInt(STATE_SCORE);
}

La pile d’activités

Une application contient en général plusieurs activités

  • Chaque activité lancée est ajoutée à une unique tâche.
  • Lorsqu’elle lance une activité d’une autre application, cette activité est ajoutée à la pile d’activités de la tâche courante.
1 - Quand une nouvelle activité est lancée :
  • elle devient active et est placée en haut de la pile.
  • l’activité précédente est stoppée et son état sauvegardé.
2 - Quand l’utilisateur appuie sur le bouton “Retour” :
  • l’activité courante est détruite.
  • l’activité précédente est restaurée.
3 - Quand “Retour” alors que la tâche n’a plus d’activité :
  • la tâche est détruite.
  • on retourne à l’écran d’accueil.

Tâche active / en arrière-plan

Quand une tâche est lancée :
  • la tâche active est mise en arrière plan,
  • toutes les activités de la tâche sont stoppées,
  • toutes les activités de la tâche restent dans la pile.
Quand la tâche est à nouveau active :
  • la pile correspondante est restaurée,
  • l’activité en haut de la pile devient active.

Android Design
Layout Editor

Présentation de l’éditeur

Tous les détails sont sur la doc officielle.

Layouts & Widgets

Android définit deux types d’objets graphiques :

Les Layouts (héritent de ViewGroup)

  • ConstrainedLayout
  • LinearLayout
  • RelativeLayout
  • etc.

Les Widgets (héritent de View)

  • Button
  • TextView
  • ImageView
  • etc.

Propriétés des layouts

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>
android:id="@+id/name
@+id indique qu’il s’agit d’un nouvel identifiant
  • création d’un nouvel entier dans R accessible avec R.id.name
  • dans le code, on le récupère avec :
    Button myButton = (Button) findViewById(R.id.my_button);
android:layout_{width|height}
  • ="match_parent" : l’élément remplit tout l’élément parent
  • ="wrap_content" prend juste la place nécessaire pour s’afficher

Constrained Layout

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        tools:layout_editor_absoluteX="158dp"
        tools:layout_editor_absoluteY="341dp" />
</androidx.constraintlayout.widget.ConstraintLayout>


<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintTop_toTopOf="parent"
        tools:layout_editor_absoluteY="341dp" />
</androidx.constraintlayout.widget.ConstraintLayout>


<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        tools:layout_editor_absoluteY="341dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

  • Un ConstrainedLayout permet de placer ses enfants de façon flexible (surtout relative) par rapport à son parent ou un autre élément.
  • Ex. : app:layout_constraintLeft_toRightOf="@+id/widget" signifie que la gauche de l’objet courant doit être à droite de l’objet d’id widget.
  • Pour être valide, un positionnement valide doit avoir au moins une contrainte horizontal et verticale.
  • Sans utilisation de marges, les objets sont équirépartis.

Les événements

Les Event en une seule slide

Button button =
    (Button)findViewById(R.id.myButton);

button.setOnClickListener(
    new OnClickListener() {
        public void onClick(View v) {
            ...
        }
    };
);

TextEdit myEditText =
    (TextEdit)findViewById(R.id.edit);

myEditText.addTextChangedListener(
    new TextWatcher() {
        @Override
        public void onTextChanged( CharSequence s,
            int start, int before, int count) {
                ...
        }
);

Les écouteurs sont instanciés avec des classes anonymes.


Les Intents

Qu’est-ce qu’un Intent ?

Un Intent représente l’intention d’un composant
de déléguer une action à un autre composant.

Un Intent peut servir à :

  1. Démarrer une activité : startActivity[ForResult] (intent)
  2. Démarrer un service : startService(intent) ou bindService(intent)
  3. Diffuser un message : send[Ordered]Broadcast(intent)

Fonctionnement d’un Intent

Intent intent = new Intent(Intent.ACTION_INSERT);
intent.setType(Contacts.CONTENT_TYPE);
intent.putExtra(Intents.Insert.NAME, name);
intent.putExtra(Intents.Insert.EMAIL, email);

// Try to invoke the intent.
try {
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // Define what your app should do if no activity can handle the intent.
}

Insertion implicite d’un nouveau contact.

Un Intent contient :

  • l’action à réaliser,
  • le nom du composant ciblé, (facultatif)
  • les données associées à l’action (ex. : URI) (facultatif),
  • une catégorie pour cibler un type d’application (facultatif),
  • des données supplémentaires (Extra) via un Bundle (facultatif).

Intent implicite ou explicite

Uri telnumber=Uri.parse("tel:0123456789");
Intent myActivity=new Intent(Intent.ACTION_DIAL, telnumber);
startActivity(myActivity);

Intention implicite de téléphone.

Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

Intention explicite de téléchargement.

Un Intent peut être :

explicite
démarrage d’une nouvelle activité spécifique propre à l’application.
implicite
démarrage d’une activité choisie par l’OS pour gérer l’action demandée (par exemple : “prendre une photo”).

Exemple d’intention avec retour attendu

static final int REQUEST_SELECT_CONTACT = 1;

public void selectContact() {
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType(ContactsContract.Contacts.CONTENT_TYPE);

    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(intent, REQUEST_SELECT_CONTACT);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_SELECT_CONTACT && resultCode == RESULT_OK) {
        Uri contactUri = data.getData();
        // Do something with the selected contact at contactUri
        ...
    }
}

Sélection d’un contact.

Il faut implémenter la méthode onActivityResult de la classe Activity.

Plus de détails sur cette page.