Android : uploader une image sur un serveur web

Une petite Mise à jour : pour écrire l’image pour envoi : utilisation de Bitmap.compress !
Ultra plus rapide !!!!

Un petit tuto pour apprendre à uploader une image (jpg ou autre) depuis votre application android vers un serveur web.
Pour ce genre de tâche qui peut être longue, Android conseille d’implémenter un Service plutôt qu’une activité : un service tourne en tâche de fond et ne bloque pas l’application.

Pour ce tuto :

  • créez une nouvelle application TutoUpload et une classe TutoUpload.
  • créez ensuite une deuxième classe HttpUploader
  • n’oubliez pas de déclarer ces classes dans le Manifest et d’ajouter la permission internet pour l’application
  • créez le fichier main.xml pour la layout de la classe TutoUpload

Le code de la classe TutoUpload :

package com.test.upload;

import java.io.File;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class TutoUpload extends Activity {

protected Uri image=null; //acces au fichier via contentResolver
File fichier; //le fichier à uploader

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//un bouton pour envoi du fichier vers le serveur
final Button validbutton = (Button) findViewById(R.id.send);
validbutton.setOnClickListener(send_listener);
}

//pour valider tout pour envoi vers site web
OnClickListener send_listener = new OnClickListener() {
public void onClick(View v) {

image = Uri.fromFile(fichier);
Intent uploadIntent = new Intent( );
uploadIntent.setClassName("com.test.upload", "com.test.upload.HttpUploader");
uploadIntent.setData(image);
startService(uploadIntent);
}
};

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

@Override
protected void onStop() {
Log.i(getClass().getSimpleName(),"on stop");
super.onStop();
}

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

Le code du service HttpUploader

package com.test.upload;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.app.Service;
import android.content.ContentResolver;
import android.content.Intent;
import android.provider.MediaStore;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

public class HttpUploader extends Service {

private Intent mInvokeIntent;
private volatile Looper mUploadLooper;
private volatile ServiceHandler mUploadHandler;

private int check = 0;

private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
//get extra datas
Uri selectedImg = (Uri)msg.obj;
Log.i(getClass().getSimpleName(),"selectedImg =" + selectedImg);

//upload the file to the web server
doHttpUpload(selectedImg);

Log.i(getClass().getSimpleName(), "Message: " + msg);
Log.i(getClass().getSimpleName(), "Done with #" + msg.arg1);
stopSelf(msg.arg1);
}
};

// Method called when (an instance of) the Service is created
public void onCreate() {
Log.i(getClass().getSimpleName(),"HttpUploader on create");

// This is who should be launched if the user selects our persistent
// notification.
mInvokeIntent = new Intent();
mInvokeIntent.setClassName("com.test.upload", "com.test.upload.HttpUploader");

// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block.
HandlerThread thread = new HandlerThread("HttpUploader");
thread.start();

mUploadLooper = thread.getLooper();
mUploadHandler = new ServiceHandler(mUploadLooper);
}

public void onStart(Intent uploadintent, int startId) {
// recup des data pour envoi via msg dans la msgqueue pour traitement
Message msg = mUploadHandler.obtainMessage();
msg.arg1 = startId;
//on place l'uri reçu dans l'intent dans le msg pour le handler
msg.obj = uploadintent.getData();
mUploadHandler.sendMessage(msg);
Log.d(getClass().getSimpleName(), "Sending: " + msg);

}

public void doHttpUpload(Uri myImage) {
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
String photofile = null;
String httpResponse; //to read http response
String filename=null;

String urlString = "http://www.site.com/fileUpload.php";
HttpURLConnection conn = null;

InputStream fis = null;
Bitmap mBitmap=null;
String pathfile;

if (myImage != null) {
//on récupère le nom du fichier photo construit avec date et heure
filename = "photo.jpg";

String[] projection = { MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.DISPLAY_NAME};
ContentResolver cr = getContentResolver();
Cursor c = cr.query(myImage, projection, null, null, null);
if (c!=null && c.moveToFirst()) {
pathfile = c.getString(0); //column0Value
photofile = c.getString(1); //column1Value
Log.i(getClass().getSimpleName(),"Data : " +pathfile);
Log.i(getClass().getSimpleName(),"Display name : " + photofile);
}

try {
fis = getContentResolver().openInputStream(myImage);
mBitmap = BitmapFactory.decodeStream(fis);

try {
int bytesAvailable = fis.available();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.i(getClass().getSimpleName(),"échec de lecture de la photo");
stopSelf();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
Toast.makeText(HttpUploader.this, "échec de lecture de la photo ", Toast.LENGTH_SHORT).show();
Log.i(getClass().getSimpleName(),"échec de lecture de la photo");
stopSelf();
}

} else Log.i(getClass().getSimpleName(),"myImage is null");

try {
URL site = new URL(urlString);
conn = (HttpURLConnection) site.openConnection();

//on peut écrire et lire
conn.setDoOutput(true);
conn.setDoInput(true);

// Use a post method.
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);

DataOutputStream dos = new DataOutputStream( conn.getOutputStream() );

dos.writeBytes(twoHyphens + boundary + lineEnd);
Log.i(getClass().getSimpleName(),"Display name : " + photofile);
Log.i(getClass().getSimpleName(),"Filename : " + filename);
dos.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + filename + "\"" + lineEnd);
dos.writeBytes(lineEnd);

Log.i(getClass().getSimpleName(),"Headers are written");

//compression de image pour envoi
mBitmap.compress(CompressFormat.JPEG, 75, dos);

// send multipart form data necesssary after file data...
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

// close streams
fis.close();
dos.flush();
dos.close();
Log.e("fileUpload","File is written on the queue");

} catch (MalformedURLException e) {
e.printStackTrace();
Toast.makeText(HttpUploader.this, "échec de connexion au site web ", Toast.LENGTH_SHORT).show();
Log.i(getClass().getSimpleName(),"échec de connexion au site web 1");
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(HttpUploader.this, "échec de connexion au site web ", Toast.LENGTH_SHORT).show();
Log.i(getClass().getSimpleName(),"échec de connexion au site web 2");
}

//lecture de la réponse http
try {
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
Log.i(getClass().getSimpleName(),"try HTTP reponse");
while ((httpResponse = rd.readLine()) != null) {
Log.i(getClass().getSimpleName(),"HTTP reponse= " + httpResponse);
if(httpResponse.contains("error")) {
//there is a http error
check += 1;
}
}
rd.close();
} catch (IOException ioex){
Log.e("HttpUploader", "error: " + ioex.getMessage(), ioex);
ioex.printStackTrace();
Toast.makeText(HttpUploader.this, "échec de lecture de la réponse du site web ", Toast.LENGTH_SHORT).show();
Log.i(getClass().getSimpleName(),"échec de lecture de la réponse du site web");
}

}

// Method called when the (instance of) the Service is requested to terminate
public void onDestroy() {
mUploadLooper.quit();

if(check == 0) { //http response contains no error
Toast.makeText(HttpUploader.this, "photo envoyée", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(HttpUploader.this, "échec d'envoi de la photo", Toast.LENGTH_SHORT).show();
}
super.onDestroy();
}

@Override
public IBinder onBind(Intent intent) {
return null;
}
}

Le fichier de layout main.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>

<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10px"
android:text="Envoi" />
</LinearLayout>

A vous de jouer !

 

Pour répondre à la question de Marvin, voici le code PHP que j’utilise sur mon serveur pour récupérer l’image. Ce code permet également de redimensionner les images reçues pour un affichage ultérieru sur le téléphone en plus petit.

 

//<?php
// Where the file is going to be placed
$target_path =  $_SERVER['DOCUMENT_ROOT']."/uploads/";
$small_path= $_SERVER['DOCUMENT_ROOT']."/uploads/smallimg/";

//$target_path = "uploads/";
//Add the original filename to our target path.
$file = basename($_FILES['uploadedfile']['name']);
$target_path = $target_path.$file;
echo "target_path= ".$target_path." ";

//pour tester que c'est bien une image
$extensions = array('.png', '.gif', '.jpg', '.jpeg', '.JPG', '.JPEG');
$extension = strrchr($_FILES['uploadedfile']['name'], '.');
$ok = 0;

//check if the file is uploaded
if(in_array($extension, $extensions)) {
if(is_uploaded_file($_FILES['uploadedfile']['tmp_name'])) {
echo "Fichier= ". $_FILES['uploadedfile']['name'] ." telechargement OK.\n";

if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'],$target_path) === TRUE) {
echo "The file ".  basename( $_FILES['uploadedfile']['name'])." has been uploaded";
$ok = 1;
} else {
echo "There was an error uploading the file, please try again!";
echo "error =".basename($_FILES['uploadedfile']['error']);
echo "file=".$_FILES['uploadedfile']['tmp_name'];
}
 
} else {
echo " Nom du fichier : '". $_FILES['uploadedfile']['name'];
}
} else {
echo "mauvaise extension de fichier";
}

if ($ok == 1) {
$small_path=$small_path.$file;
$img_path=$target_path;
$redimLargeur=300;
$redimHteur=300;

if(!file_exists($small_path)) {
/* creation de notre copie de travail de l'image */
$tmpImg = imagecreatefromjpeg($img_path);

/* recup des dimensions de l'image a redimensionner */
$tailleImg = getimagesize($img_path);

/* calcul des nouvelles dimensions de l'image */
if($tailleImg[0] &amp;amp;gt; $tailleImg[1]) {

/* format paysage */
$ratioReduc = (($redimLargeur * 100)/$tailleImg[0]);

$newHteur = (($tailleImg[1] * $ratioReduc)/100 );
$newLargeur = $redimLargeur;

} else {
/* format portrait */
$ratioReduc = (($redimHteur * 100)/$tailleImg[1]);
65.
$newLargeur = (($tailleImg[0] * $ratioReduc)/100 );
$newHteur = $redimHteur;

}

/* creation de la nouvelle image en couleurs vraies */
$rszImg = imagecreatetruecolor($newLargeur ,$newHteur) or die ("Erreur");

/* reechantillonnage de l'image */
imagecopyresampled($rszImg , $tmpImg, 0, 0, 0, 0, $newLargeur, $newHteur, $tailleImg[0],$tailleImg[1]);

/* enregisttrement de l'image redimensionnée */
imagejpeg($rszImg , $small_path, 100);

}

}
//?>

This entry was posted in android and tagged , , . Bookmark the permalink.

49 Responses to Android : uploader une image sur un serveur web

  1. dewoo says:

    J’ai beaucoup apprécié ce tuto, très explicite et pertinence.

    Un petite question me taraude du coup, pour la récupération d’images, imaginons 5 en même temps, service ou thread/handler du coup ?

  2. flo says:

    Le Service te permet de faire tourner ta récup d’image en tache de fond sans bloquer ton appli. Si tu ne maîtrise pas trop, tu peux opter pour thread / hnadler mais en créant une thread qui gère le chargement des images pour pas que celà tourne sur ton UI thread (la thread principale) en bloquant ton appli.
    Si le chargement des images, c’est pour les afficher pour l’utilisateur, tu peux aussi les charger dans la thread principale avec une ProgressDialog qui dit chargement en cours.

  3. Coucal says:

    Je n’arrive pas à complier ce tutorial : Choosepicture n’est pas défini..
    Merci
    Alai,

  4. zobi8225 says:

    ennorme

  5. flo says:

    Coucal :
    une erreur de copié collé. Corrigé : filename est le nom du fichier à uploader.

  6. exico says:

    hi,
    what i should make the URL if i want make my Laptop like a server

  7. Sami.k says:

    hi,
    moi aussi je n’arrive pa à faire tourné ce code,
    @exico : I think u should make : http://10.0.2.2:Numport/Folder

  8. berrada says:

    j’aime et je bookmark, merci pour cet article

  9. marvin says:

    ça ne fonctionne pas, lors de l’appui sur “Envoi”, l’appli se crash systématiquement.

  10. flo says:

    avec quelques log je pourrai aider au debug….. cela fonctionne tres bein dans mon appli

  11. marvin says:

    Salut, voici le maximum que j’ai pu trouver.
    En résumé, je me suis contenté de mettre les .java où il faut, et de declarer la permission et le service dans le manifest.
    Je lance, j’appuie sur Envoi et l’appli crash.
    (ai-je oublié quelque chose?? comment spécifier le fichier à envoyer??)

    Voici ma pile :

    DalvikVM[localhost:8621]
    Thread [ main] (Suspended (exception NullPointerException))
    ViewRoot.handleMessage(Message) line: 1740
    ViewRoot(Handler).dispatchMessage(Message) line: 99
    Looper.loop() line: 123
    ActivityThread.main(String[]) line: 4363
    Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
    Method.invoke(Object, Object…) line: 521
    ZygoteInit$MethodAndArgsCaller.run() line: 860
    ZygoteInit.main(String[]) line: 618
    NativeStart.main(String[]) line: not available [native method]
    Thread [ Binder Thread #2] (Running)
    Thread [ Binder Thread #1] (Running)

    Le logcat ne donne aucune info lors d ce click, la console de debug non plus.

    J’ai hate de pouvoir faire tourner ce code, tu as l’air d’avoir fait un gros travail. ^^

    PS : Tu devrais peut etre également publier le manifest.xml. La phrase “n’oubliez pas de déclarer ces classes dans le Manifest” est assez abstraite pour un débutant. :S

  12. Bzul says:

    Ça a l’air sympa, mais même question que marvin, comment spécifier le chemin du fichier ?

  13. flo says:

    désolée pour le temps de réponse, je bosse plus sur des applis iPhone en ce moment….
    pour préciser le fichier à uploader c’est en ligne 16 du code. Il faut un peut compléter le code en construisant le nom de fcihier avec le path complet. Pour vous aider, un peu de doc : http://developer.android.com/intl/de/reference/java/io/File.html

    PS : concernant le fichier manifest.xml, je ne l’ai pas publié et je ne pense pas le faire car il me semble qu’un débutant devrait en premier lieu lire la doc android.
    Le fichier manifest.xml est une base très importante des applis, la doc explique très bien comment le construire et donc comment déclarer chaque classe dans ce fichier. La doc : http://developer.android.com/intl/de/guide/topics/manifest/manifest-intro.html

  14. iphone 4 says:

    A formidable share, I just given this onto a colleague who was doing a bit of analysis on this. And he in actual fact bought me breakfast as a result of I discovered it for him.. smile. So let me reword that: Thnx for the treat! But yeah Thnkx for spending the time to discuss this, I feel strongly about it and love reading extra on this topic. If attainable, as you grow to be experience, would you thoughts updating your weblog with extra details? It is highly helpful for me. Huge thumb up for this blog put up!

  15. pikos says:

    bonsoir tout le monde
    merci bien pour le code !!! excellent
    j’ai la même question que marvin “merçi pour la question maven”
    mais j’ai quelque problème coté serveur. j’ai pas arrivé a exécuté un code d’upload files “en PHP” car les méthode input d’un formulaire HTML ne fonctionne pas sur un terminal mobile car le bouton de parcourir pour choisir le fichier a uploader devin grisé “selon les test, mes connaissances et mes recherche sur le net bien-sur ! ,en plus j’aimerai mieux que cette opération d’upload soit avec un clic simple sur un bouton et non pas avec parcourir … car le fichier a étai générer automatiquement par une méthode
    cordialement PiKoS

  16. Marvin says:

    Je repasse ici pour tester ce problème maintenant que j’ai un peu plus d’expérience.
    J’arrive à faire marcher le code Android.

    Par contre coté serveur je ne reçois rien à première vu.
    Où sont censées arriver les données ?

    je n’ai rien dans les variables PHP suivantes :
    – $_POST
    – $_GET
    – $_FILE
    – $HTTP_RAW_POST_DATA

    Comme ça se fait ?

  17. flo says:

    @Marvin : J’ai ajouté en fin du tuto le code PHP que j’utilise pour uploader l’image côté serveur. j’espère que celà t’aidera !

  18. Stef says:

    Ca marche nickel dans mon programme Android. Merci !!!

  19. Raphi says:

    Merci pour ce tuto ! Marche très bien !

  20. monta says:

    slt,
    c’est tres interesant,
    mais ma question c’est comment faire si je veux envoyer des données se trouve dans une listview android vers mon serveur pour récupérer ces variables (nom, prix,description) dans la base
    svp aider moi
    j’attend votre reponse

  21. lina says:

    Slt j’ai une erreur au niveau de ligne 107
    Problem : the method amp (Boolean )is undefined for HttpUploader.java
    Svp aidez moi et merci d’avance

  22. flo says:

    @lina : as-tu déclaré les classes dans ton fichier Manifest.xml ??

  23. lina says:

    oui je fait 🙂

  24. swar says:

    stp comment je fait la déclaration des classes dans le fichier Manifest.xml ??
    donner moi un exemple et merci d’avance:)

  25. lina says:

    Mr flo j’ai fait la declaration des classes dans mon fichier Manifest.xml mais méme probléme l’erreur reste au niveau de ligne 107 svp repend moi c urgent et merci

  26. lina says:

    la problem qui s’affiche est :syntax error,insert”statment “to complete if statment HttpUploader.java 🙂

  27. flo says:

    @lina : je pense que tu as du faire un bête copier-coller sans relire…..
    Si tu as ça dans ton code :
    if (c!=null &amp;&amp; c.moveToFirst()) {
    il y a forcément un problème…..
    Remplace par :
    if (c!=null c.moveToFirst()) {
    Et apprend le code Java !

  28. lina says:

    je lit le Mr.flo mais je ne compris pas cette ligne pour se la

  29. flo says:

    @lina : it’s perhaps a problem of copy-paste.
    If you have some “&amp” in your code, it’s a mistake. you must remove them to have this :

    if (c!=null && c.moveToFirst()) {

  30. lina says:

    pardon j’ai lu le code Mr.flo mais je n’ai pas compris cette ligne pour céla je vous demande de m’aider j’ai pratiquée votre marche
    if (c!=null c.moveToFirst()) { il y a aussi une errure: syntax error on token”null”,new expected
    je suis débutante avec android et je veut que ce code sera executable svp Mr.flo aidez moi

  31. swar says:

    merci b1 MR.flo moi j’ai un erreur au niveau c.moveToFirst())
    The method moveToFirst() is undefined for the type String HttpUploader.java
    stp aidez moi et merci d’avance:)

  32. swar says:

    MR.flo pour quoi vous ne répondez pas ? svp
    c urgent j’att votre réponse

  33. MedAmi says:

    Bonjour,

    J’utilise Tomcat comme serveur web, est ce que le code est valable ?
    si oui, est ce que vous pouvez me donner la page que je dois mettre dans le serveur ?

    Merci ^^

  34. flo says:

    @swar @lina : il faut remplacer la ligne 107 par :
    if (c!=null && c.moveToFirst()) {
    les caractères “&&” étaient masqué par un problème d’encodage de caractères dans le post.

    Et pensez à apprendre le java avant de vous lancer dans du code comme ça !! Vous ne pourrez jamais publier une application qui fonctionne si vous ne comprenez même pas le code que vous écrivez !

  35. flo says:

    @MedAmi : le code php à utiliser sur votre serveur est à la fin du post !!! lisez le en entier !

  36. MedAmi says:

    @flo, nn je demande d’avoir le code java car je ne veux pas utilisé en meme temps les deux interpreteur PHP et Java …

  37. flo says:

    @MedAmi : je n’ai pas de code java pour le serveur.

  38. micho says:

    Bjr svp qu’elle est le code source et le permission qui me permet d’accéder aux photos qui ce trouve dans l’émulateur a l’aide d’un chemin d’accès bien déterminer sachant que je travaille sur local host

  39. micho says:

    Bon soir
    merci bien pour le code !!!
    est ce que vous pouvez me guider comment faire une liaison entre ce code et les image qui ce trouve dans l’émulateur????? et qu’elle est la permission utiliser????? merci d’avance .

  40. simba says:

    bonjour
    est ce que je peut changer la ligne:
    String urlString = “http://www.site.com/fileUpload.php”;
    par:
    String urlString = “http://localhost/fileUpload.php”;
    parce que je veut travailler avec mon serveur interne
    merci de me repondre 🙂

  41. loufi says:

    bonsoir
    lorsque j’éxecute le code une erreur s’affiche:
    The Application TutoUpload (com.test.upload) has stopped unexpectedly.please try again
    qlq peut m’aider

  42. sasuke says:

    hello
    comment je peut acceder et afficher les images dans la carte
    🙁

  43. android says:

    bonBonjour tout le monde
    merci bien pour le code !!! excellent
    j’ai la même question que sasuke
    mais j’ai quelque problème coté serveur. j’ai pas arrivé a exécuté un code d’upload files “en PHP”
    svp aidez moi et merci 🙂

  44. nancy says:

    Bonjour a tous le monde
    merci pour le code il est complètement correcte mais j’ai une erreur aux cours de l’exécution de l’application lorsque j’ouvre le moniteur et j’accéder a mon application un msg s’affiche « fermeture soudaine de l’application tuto upload (processus com. test.upload) merci de réessayer. Forcer la fermeture » svp si quelque ‘un a une réponse sa me fait plait de me répondu .

  45. flo says:

    @Nancy : pas assez d’info pour répondre et aider…. un lot d’erreur serait le bienvenu
    !
    @android : même chose, sans log je ne peux pas répondre. Le fichier PHP doit être mis sur le serveur pour l’upload.

    @sasuke : voici un lien vers un tutoriel qui explique comment accéder à la carte SD dans l’émulateur : http://www.streamhead.com/android-tutorial-sd-card/

    NB : toutes ces infos sont aussi disponibles sur le site android officiel pour les développeurs alors un petit effort de recherche serait pas mal de votre part…….

  46. mariem says:

    bonsoir comment je peux faire des boutton checkbox relier a une liste view et lorsque je fais mes choix et je clique sur un boutton envoi mes données sera envoyer ver un serveur php j’attend votre reppence svp

  47. flo says:

    @mariem : pourquoi ne pas faire un recherche dans Google : android checkbox tutoriel par exemple !
    http://www.mkyong.com/android/android-checkbox-example/

Comments are closed.