CategoryDevelopment – Intermediate

Tutorial: How to play animated GIFs in Android – Part 2

Animated GIFs in Android is a difficult topic. It is an issue that has been discussed heavily and still, it is very difficult for developers to bring GIFs to life. There are three ways to animate GIFs on Android, each of them has its pros and cons. Each part of this series will cover one of these approaches.

Getting started

For this example, we will use an image I found on gifs.net, it’s this one:

I will store it in our project’s asset folder and name it ‘piggy.gif’. We will also use an Activity to set the views we define as content views. If you want to know everything about playing GIFs, please start at part one.

Approach 2: Extracting the Bitmaps

For this approach, we will use the GifDecoder class published here on googlecode. It’s Apache licensed, so don’t worry. What this class esentially does is, it extracts the different bitmaps from the given stream so you can use it the way you want.

To get started, download this class first. Place it somewhere in your project, maybe in a util package.
Now, we create a new class which inherits from ImageView:

   public class GifDecoderView extends ImageView

We create a constructor with a Context and an InputStream, just like in the first part of this series. This time, we call a method playGif(InputStream) and pass it our InputStream:

   public GifDecoderView(Context context, InputStream stream) {
      super(context);
      playGif(stream);

We give our class five member variables: A boolean which will state whether the thread we will use to play our animation is runningor not, an instance of the GifDecoder-class you just downloaded, a Bitmap which stores the different frames of the animation, a Handler to post our updates to the UI-thread and a Runnable that will arrange that the Bitmap we just defined will be drawn using the Handler:

   private boolean mIsPlayingGif = false;

   private GifDecoder mGifDecoder;

   private Bitmap mTmpBitmap;

   final Handler mHandler = new Handler();

   final Runnable mUpdateResults = new Runnable() {
      public void run() {
         if (mTmpBitmap != null && !mTmpBitmap.isRecycled()) {
            GifDecoderView.this.setImageBitmap(mTmpBitmap);
         }
      }
   };

Let’s take a look at playGif(InputStream). First, we need to initialise mGifDecoder. After that, we let it read our stream and set mIsPlayingGif to true, so that our thread can run:

   private void playGif(InputStream stream) {
      mGifDecoder = new GifDecoder();
      mGifDecoder.read(stream);
      mIsPlayingGif = true;

Now we need to define our thread. We retreive the frame count of our GIF’s frames and the number of repetitions. When GifDecoder.getLoopCount() returns 0, this means the GIF should be played infinitely. Now for every frame, we receive the according Bitmap by calling getFrame() on the GifDecoder. We post our new Bitmap using the Handler and Runnable members we defined and send our thread to sleep until the next Bitmap needs to be drawn.

new Thread(new Runnable() {
         public void run() {
            final int n = mGifDecoder.getFrameCount();
            final int ntimes = mGifDecoder.getLoopCount();
            int repetitionCounter = 0;
            do {
              for (int i = 0; i < n; i++) {
                 mTmpBitmap = mGifDecoder.getFrame(i);
                 final int t = mGifDecoder.getDelay(i);
                 mHandler.post(mUpdateResults);
                 try {
                    Thread.sleep(t);
                 } catch (InterruptedException e) {
                    e.printStackTrace();
                 }
              }
              if(ntimes != 0) {
                 repetitionCounter ++;
              }
           } while (mIsPlayingGif && (repetitionCounter <= ntimes))
        }
     }).start();

That’s it. All we have to do now is use our GifDecoderView in an Activity, just like we did in the last part of this tutorial:

   // ...
   InputStream stream = null;
   try {
      stream = getAssets().open("piggy.gif");
   } catch (IOException e) {
      e.printStackTrace();
   }

// GifMovieView view = new GifMovieView(this, stream);
   GifDecoderView view = new GifDecoderView(this, stream);

   setContentView(view);
   // ...

 

Now, what’ the bad thing about this? It’s the memory footprint. Bitmaps on pre-Honeycomb-devices are stored in an native heap, not in the heap the Dalvik VM uses. Still, this heap counts to the maximum of memory your app can use. So when you have a lot of Bitmaps, the garbage collector might not notice that you are running out of memory because it only controls the heap of the Dalvik VM. To avoid this, make sure you call recycle() on every Bitmap you don’t need anymore. If you want to see how much space your native heap allocated, call Debug.getNativeHeapAllocatedSize().

If you want to know another, maybe better way of playing animated GIFs, stay tuned: The next part of this series will come soon.

 

You can checkout the code of the three parts of this series at http://code.google.com/p/animated-gifs-in-android/.

 

As always, please feel free to leave your thoughts in the comments.

Tutorial: How to play animated GIFs in Android – Part 1

Animated GIFs in Android is a difficult topic. It is an issue that has been discussed heavily and still, it is very difficult for developers to bring GIFs to life. There are three ways to animate GIFs on Android, each of them has its pros and cons. Each part of this series will cover one of these approaches.

Getting started

For this example, we will use an image I found on gifs.net, it’s this one:

I will store it in our project’s asset folder and name it ‘piggy.gif’. We will also use an Activity to set the views we define as content views.

Approach 1: Using Movie

Android provides the class android.graphics.Movie. This class is capable of decoding and playing InputStreams. So for this approach, we create a class GifMovieView and let it inherit from View:

Public class GifMovieView extends View

Now we create a constructor that receives a Context object and an InputStream. We provide our class a member variable which is an instance of the Movie class. We initialize this member by calling Movie.decodeStream(InputStream):

    private Movie mMovie;

    public GifMovieView(Context context, InputStream stream) {
        super(context);

        mStream = stream;
        mMovie = Movie.decodeStream(mStream);        
    }

Now that our Movie-object is initialized with our InputStrem, we just need to draw it. We can do this by calling draw(Canvas, int, int) on our Movie-object. Because we need a Canvas, we should do this in onDraw(). But before we drawing, we have to tell our object what to render. For that, we need a simple calculation to determine how much time has passed since we started the Movie. To do that, we need another member of the primitive type long, I named it mMoviestart. Now we get a timestamp, for example by calling SystemClock.uptimeMillis() or System.currentTimeMillis(). We determine how much time went by since our movie started and tell our movie to play draw the according frame. After that, we invalidate our view so that it’s redrawn:

private long mMoviestart;

    @Override
    protected void onDraw(Canvas canvas) {
       canvas.drawColor(Color.TRANSPARENT);
       super.onDraw(canvas);
       final long now = SystemClock.uptimeMillis();
       if (mMoviestart == 0) {
          mMoviestart = now;
       }
       final int relTime = (int)((now - mMoviestart) % mMovie.duration());
       mMovie.setTime(relTime);
       mMovie.draw(canvas, 10, 10);
       this.invalidate();
    }

All we have to do now is initialize our new View with a Context and an InputStream and set it as content view, we can do this in our Activity like this:

    // ...
    InputStream stream = null;
    try {
       stream = getAssets().open("piggy.gif");
    } catch (IOException e) {
      e.printStackTrace();
    }
    GifMovieView view = new GifMovieView(this, stream);
    setContentView(view);
    // ...

That was easy, right? So where’s the contra? Well, here it is:

As you can see, the Movie-class is not able to deal with every type of animated GIFs. For some formats, the first frame will be drawn well but every other won’t. So when you walk this route, make sure your GIFs are displayed correctly.
If you want to know another, maybe better way of playing animated GIFs, stay tuned: The next part of this series will come tomorrow.

 

You can checkout the code of the three parts of this series at http://code.google.com/p/animated-gifs-in-android/.

 

As always, please feel free to leave your thoughts in the comments.

Tutorial: Android Spieleentwicklung – Praxis (1)

So, weiter geht’s mit der Android Spieleentwicklung. Wie bereits erwähnt besteht diese aus Theorie und Praxis. Der Praxisteil ist etwas umfangreicher. Wir werden dabei die Grundlagen für ein Canvas basiertes 2D-Spiel schaffen, eine Art Miniframework der Aufgaben, die wirklich in jedem Spiel vorkommen.

Heute implementieren wir also die praktische Grundlage für Spiele und schreiben dafür eine abstrakte Basisactivity, die zwei Threads verwaltet (einen für das Rendering, einen für die Simulation) und Touchevents und Beschleunigungen wahr nimmt. Außerdem entwerfen wir ein Listenerkonzept, mit dem die Klasse einfach genutzt werden kann.

Für denjenigen der oder diejenige die nicht weiß, worum es bei den im vorherigen Satz genannten Dingen geht, zumindest theoretisch, und/oder noch keine Kenntnisse in Java besitzt, ist dieses Tutorial wahrscheinlich nicht sehr hilfreich. Wer anderer Meinung ist kann’s trotzdem probieren. Los geht’s!

Zunächst einmal grundlegendes zum Android-Stil:

  • Member Variablen (globale Variablen) werden mit einem m am anfang geschrieben, also zum Beispiel mVariable.
  • Statische Variablen mit einem s
  • Felder (static final …) werden groß geschrieben und Wörter mit Unterstrichen (_) getrennt, wie sonst auch in Java üblich.
  • Methoden werden in der Zeile ihrer Deklaration geöffnet (void testMethode(){ … anstatt void testMethode

{ …).

  • final ist ein gutes Wort wenn eine Variable nicht nochmal verändert werden soll.
  • Nicht private Variablen sollten mit Javadoc dokumentiert sein, Methoden auch (wie auch sonst in Java).
  • Außer die Benennung ist absolut selbsterklärend, das ist aber selten der Fall (auch bei Gettern und Settern).
  • Methodenzugriffe kosten (Rechen) Zeit und die ist auf Mobilen Geräten knapp.
  • Der Garbagecollector sollte Während des Spielbetriebs so selten angeworfen werden müssen wie möglich, sonst kann es zu Rucklern kommen.

Nachdem das gesagt ist geht’s gleich los:

Zunächst einmal erstellen wir eine neue abstrakte Klasse die von Activity ableitet, ich habe sie passenderweise GameActivity genannt.

public abstract class GameActivity extends Activity

Wie bei Activities üblich überschreiben wir onCreate(). Dabei initialisieren wir zunächst eine member Variable vom Typ SurfaceView, auf die wir später alles malen werden. Anschließend holen wir uns von der SurfaceView über getHolder() den zugrunde liegenden SurfaceHolder. Über diesen ist es uns möglich, die SurfaceView zu verändern. Anschließend fügen wir dem Holder die eigene Klasse als Callback hinzu (addCallback()). Dazu müssen wir das Interface SurfaceView.Callback implementieren, was uns zu den Methoden surfaceChanged(), surfaceCreated() und surfaceDestroyed() zwingt. Die Namen sind ziemlich selbsterklärend. In surfaceCreated() und surfaceDestroyed() werden wir weiter unten etwas hineinschreiben, surfaceChanged(), was zum Beispiel aufgerufen wird wenn das Handy gedreht wird, behandeln wir nicht. Wir setzen die SurfaceView-Instanz als Content View.

Als nächstes erstellen wir in onCreate() zwei Threads, einen für die Simulation, einen für das Rendering:

        mRenderingThread = new Thread(new Runnable(){
        	public void run() {
        		render();
        	}
        }, "Rendering Thread");        

        mSimulationThread = new Thread(new Runnable(){
        	public void run() {
        		simulate();
        	}
        }, "Simulation Thread");

Das sind die beiden im Theoriekapitel beschriebenen Threads. Gar nicht so kompliziert. Wie Ihr an den Namen erkennen könnt handelt es sich bei den Threads um globale Variablen, da wir sie später aus einer anderen Methode heraus wieder beenden können wollen. Das ist erstmal alles was wir in onCreate() machen.

Jetzt zum Listener-Konzept. Wir benötigen zwei Interfaces, eines für die Simulation und eines für das Rendering.

Dem SimulationListener geben wir drei Methoden: initializeSimulation(), simulationIteration(float delta) und isInitializingSimulation(). Die erste dieser Methoden dient dazu, alles zu laden was für die Simulation notwendig ist, Daten vom Server oder aus einer Datenbank zum Beispiel. Wenn das Laden abgeschlossen ist muss isInitializingSimulation() false zurückgeben. So weiß unsere abstrakte Basisactivity dass das Laden der Simulation abgeschlossen ist und mit dem Durchlaufen der Simulation begonnen werden kann. Dafür wird simulationIteration(float delta) genutzt. Delta ist die Zeit in Sekunden, die seit der letzten Simulationsiteration vergangen ist. So kann sichergestellt werden dass sich alle Spielgegenstände immer gleich schnell bewegen, unabhängig von der geschwindigkeit mit der die Simulationsiterationen durchlaufen werden und auch unabhängig von der Zahl der Frames pro Sekunde. Diese Technik nennt sich Frame Rate Independent Movements, ein Name der hier nicht so recht passt; näheres kann hier nachgelesen werden.

Der RenderingListener, auch ein Interface, erhält die Methoden render(Canvas canvas), initializeRendering() und isInitializingRendering(). Darüber hinaus bekommt er noch die Methoden renderSplash(Canvas canvas) und initializeSplash(). Die ersten drei Methoden machen das Gleiche wie der SimulationListener auch. render(Canvas canvas) bekommt dabei ein Canvas-Objekt übergeben, auf das der Listener dann alle Spielgrafiken zeichnen kann. Die beiden letztgenannten Methoden sind dafür da, einen Splashscreen oder Ladebildschirm zu laden und zu zeichnen. initializeSplash() sollte dabei so schnell wie möglich durchlaufen werden damit der Nutzer nicht denkt dass das Spiel hängt. Sobald isInitializingRendering() und isInitializingSimulation() false zurück geben ist das Laden abgeschlossen und es kann zum Spielablauf mit render(Canvas canvas) übergegangen werden.

Um die Listener auch setzen zu können müssen wir auch noch jeweils einen Setter implementieren, was eigentlich kein Problem sein sollte.

Mit dem obigen gesagt ist auch schon klar, was die beiden Threadmethoden simulate() und render() machen. Fangen wir bei der Umsetzung mit simulate() an:

 private void simulate(){        
   if(mSimulationListener != null){
     mSimulationListener.initializeSimulation();
   }

   while(mMainLoopRunning){
     if(mSimulationListener != null
       && !mSimulationListener.isInitializingSimulation()
       && mRenderingListener != null
       && !mRenderingListener.isInitializingRendering()){
        mDeltaTime = (System.nanoTime() - mLastSimulationIteration)
          / 1000000000.0f;
        mLastSimulationIteration = System.nanoTime();
        mSimulationListener.simulationIteration(mDeltaTime);            
     }
   }
 }

Zuerst führen wir die Initialisierung der Simulation durch. Ist das erledigt geht die main Loop der Simulation los. Das aber nur, wenn auch alle für das Rendering notwendigen Dinge bereits geladen wurden. Wir wollen ja nicht dass das Spiel schon los geht bevor der User überhaupt irgend etwas sieht. In der main Loop messen wir die delta Time. Wie ihr seht benötigen wir dafür zwei weitere Member. Wir ziehen einfach von System.nanoTime() den Zeitstempel der letzten Iteration ab und teilen das Ergebnis durch eine Milliarde um von Nanosekunden auf Sekunden zu kommen. Nun setzen wir den Zeitstempel auf die aktuelle Zeit und rufen die simulationIteration(float delta) des SimulationListeners auf. So einfach ist das. Nun können Klassen die sich als SimulationListener bei unserer Basisactivity einhängen Entfernungen, Bewegungen, Kollisionen usw. in ihrer eigenen Methode simulationIteration(float delta) berechnen und das mit einer konstanten Geschwindigkeit, z.B. 100 Pixel pro Sekunde.

Machen wir mit der Methode render() weiter:

private void render(){
  if(mRenderingListener != null){ 
    mRenderingListener.initializeSplash();
  }
  new Thread(new Runnable(){
    @Override
    public void run() {
      if(mRenderingListener != null){
        mRenderingListener.initializeRendering();
      }
    }
  }).start();
  Canvas c;
  while(mMainLoopRunning){
    c = null;
    try {
      c = mSurfaceHolder.lockCanvas();
      synchronized (mSurfaceHolder) {
        if(mRenderingListener != null){
          if(mRenderingListener.isInitializingRendering()
           || mSimulationListener == null
           || mSimulationListener.isInitializingSimulation()){
            mRenderingListener.renderSplash(c);
        } else{
          mRenderingListener.render(c);
        }
      }
    }
  } finally {
    if (c != null) {
      mSurfaceHolder.unlockCanvasAndPost(c);
    }
  }                    

 //the fps calculation
 mFPSCount++;
 mTimeElapsed += (System.nanoTime() - mLastRenderingIteration)
 / 1000000000.0;
 if(mTimeElapsed >= 1){
   mFPS = mFPSCount;
   Log.d("GameActivity", "FPS: " + mFPS);
   mFPSCount = 0;
   mTimeElapsed = 0;
 }
 mLastRenderingIteration = System.nanoTime();
 }
}

Etwas umfangreicher, etwa 30 Zeilen, aber auch nicht so kompliziert. Das Vorgehen ist ähnlich wie bei der Simulation. Als erstes initialisieren wir den Splashscreen. Das sollte, wie erwähnt, möglichst zügig passieren damit niemand warten muss. Anschließend initialisieren wir das Rendering. Das machen wir nebenläufig machen damit unser Ladebildschirm schonmal angezeigt werden kann. Dann deklarieren wir ein Canvas-Objekt und steigen auch schon in die Rendering main Loop ein. Der Teil mit der Canvas, der jetzt kommt, ist übrigens größtenteils dem LunarLander Codebeispiel entnommen. Zuerst einmal holen wir uns von unserem SurfaceHolder die aktuelle Canvas und sperren diese. Mit dem Sperren hat es auf sich dass die Canvas, wenn sie nicht gesperrt ist, verändert werden kann, auch von Elementen die wir nicht in unserer Hand haben. So stellen wir also sicher dass am Ende auch das auf dem Bildschirm erscheint, was wir wollen. Nun, da wir uns in einem Thread befinden und nicht wollen dass ein anderer Thread uns einen Strich durch die Canvas macht, synchronisieren wir noch unseren SurfaceHolder und geben anschließend unsere Canvas an den RenderingListener weiter. Je nachdem, ob alle für das Spiel notwendigen Elemente bereits geladen sind oder nicht, kriegt entweder renderSplash() oder render() die Canvas zum Malen überreicht. Abschließend entsperren wir unser CanvasObjekt wieder und posten es mit unserem SurfaceHolder auf den Bildschirm. Das machen wir in einem finally-Block, damit unsere Oberfläche, falls es beim Zeichnen in einer der Renderingmethoden zu Exceptions kommt, nicht in einem inkonsistenten Zustand bleibt.
Das war auch schon alles was das Rendering angeht.
Abschließend sollen noch die Frames pro Sekunde gezählt werden.

Frames pro Sekunde (FPS) sagen aus wie viele komplette Bilder innerhalb einer Sekunde gezeichnet werden. Das menschliche Auge beginnt dabei ab etwa 24 FPS zu glauben, es handle sich um Bewegungen. Alles darunter ist zu langsam und sollte den Entwickler zum Nachdenken bringen. Zur Performanceoptimierung komme ich in einem anderen Kapitel. Die in Android maximale Anzahl von FPS liegt bei 61. Wenn man sein Spiel zwischen 50 und 61 FPS bringen kann, kann man sich schon sehr sicher sein dass es auf den meisten Geräten zuverlässig läuft (testen sollte man aber trotzdem).

Zurück zur Implementierung! In jeder Renderingiteration erhöhen wir ganz einfach einen Counter mFPSCount und zählen anschließend die seit der letzten Iteration vergangene Zeit in einer anderen Membervariable zusammen. Wenn diese zweite Variable größer gleich eins ist, ist eine Sekunde vergangen und die FPS können ausgegeben werden. Da man sie vielleicht auch noch anderswo als in der LogCat sehen will, habe ich auch noch einen Member mFPS erstellt, über den andere Klassen auf die aktuellen Frames pro Sekunde zugreifen können. Wir setzen den FPS-Counter und den Zeitnehmer zurück und halten den Zeitstempel der aktuellen Iteration fest. Fertig ist der Inhalt des rendering-Threads!
Würde man jetzt von der Activity ableiten, Listener einhängen und etwas schönes zeichnen wollen, würde nichts geschehen. Warum nicht? Ganz Klar: Die beiden Hauptthreads wurden ja noch gar nicht gestartet. Aber wo machen wir das am besten? Den simulation-Thread können wir schon in der onCreate()-Methode starten. Mit dem rendering-Thread ist das anders, der muss erst darauf warten dass die SurfaceView, auf der er zeichnen will, bereit ist. Da war doch was… Wir hatten ja das Interface SurfaceHolder.Callback implementiert, das uns eine Methode surfaceCreated() zur Verfügung stellt. Allem Anschein nach der richtige Punkt um das Rendering anzustoßen. Der Einfachheit halber habe ich an dieser Stelle auch den Simulationsthread gestartet. Wie Ihr, wenn Ihr aufmerksam wart, sicherlich bemerkt habt, gibt es in den main Loops der beiden Threads noch eine Variable mMainLoopRunning. Diese müssen wir also auch noch auf true setzen, bevor wir die Threads starten.
Jetzt können wir ein Spiel zum laufen bringen. Aber manchmal, auch wenn das Spiel noch so schön ist, will man es auch wieder beenden. Glücklicherweise stellt das Interface SurfaceHolder.Callback auch noch die Methode surfaceDestroyed() zur Verfügung, in der wir die beiden Threads stoppen können. Das sieht dann für den rendering-Thread so aus:

mMainLoopRunning = false;
boolean retry = true;
while (retry) {
  try {
    mRenderingThread.join();
    retry = false;
  } catch (InterruptedException e) {
  }
}

Wir stoppen einfach den rendering-Thread indem wir join() aufrufen. Wenn es dabei zu einer Exception kommt, versuchen wir es einfach nochmal, solange bis es gut geht. Mit dem Simulationsthread machen wir das Selbe, und schon können wir ein Spiel sowohl starten als auch beenden.
Jetzt haben wir alle Komponenten beisammen um eine Spiel zu zeichnen. Fehlt noch die Eingabe. Alle Geräte, die den Android Market nutzen wollen, müssen über mindestens einen Beschleunigungssensor und einen Touchscreen verfügen. Also kümmern wir uns darum, dass wir diese beiden sicheren Eingabemethoden nutzen können.

Um auf Touch Events reagieren zu können implementieren wir das Interface OnTouchListener, wodurch wir dazu gezwungen werden, die Methode boolean onTouch(MotionEvent event) zu implementieren. Den Listener hängen wir in unserer onCreate() – Methode in unsere SurfaceView ein. Nun wird onTouch() bei jedem Drücken auf unsere Spieloberfläche aufgerufen. Das MotionEvent das dabei übergeben wird, enthält dabei einige wichtige Informationen. Wir verarbeiten von diesen Informationen die Art und die Position des Events. Damit auch ableitende Klassen darauf zugreifen können, erstellen wir darum die drei Membervariablen mIsTouched, mTouchX und mTouchY.

Wenn unser MotionEvent (über die Methode getAction()) nun die Information beinhaltet, dass ein Drücken oder eine Bewegung eines Fingers auf dem Bildschirm stattgefunden hat, halten wir die Position fest und setzen mIsTouched auf true. Hat das Event die Action MotionEvent.ActionUp, so ist kein Finger mehr auf dem Bildschirm und wir setzen mIsTouched auf false. In code sieht das so aus (Getter und Setter nicht vergessen):

if(event.getAction() == MotionEvent.ACTION_DOWN
 || event.getAction() == MotionEvent.ACTION_MOVE){
  mTouchX = (int)event.getX();
  mTouchY = (int)event.getY();
  mIsTouched = true;
}

if( event.getAction() == MotionEvent.ACTION_UP ){
  mIsTouched = false;
}
return true;

Wenn mIsTouched true zurückgibt, befindet sich gerade ein Finger auf dem Bildschirm. Ist es false, befindet sich dort keiner. So können Bewegungen erkannt werden und Beispielsweise Objekte über das Spielfeld gezogen oder Pfade gezeichnet werden.

Nach den Touchevents bleibt uns nur noch eins zu tun, bevor wir mit unserer Basisactivity anfangen können Spiele zu schreiben. Wir sprechen die Beschleunigungssensoren an. Dafür brauchen wir mal wieder ein Interface um uns als Listener einzuhängen – ein Konzept, dass sich durch so ziemlich alles in Android durchzieht. Wir implementieren SensorEventListener und erhalten onAccuracyChanged() und onSensorChanged(SensorEvent event). Die erste dieser beiden Methoden ignorieren wir. Die zweite Methode wird aufgerufen, wenn der Beschleunigungssensor irgend etwas mitgekriegt hat. Wie schon zuvor enthält das Event die entsprechenden Informationen. Für das Weitergeben an ableitende Klassen erstellen wir das float-Array mAcceleration mit der Größe drei. Mit

System.arraycopy( event.values, 0, mAcceleration, 0, 3 );

Kopieren wir die Werte des Events ab der Position 0 in unser Array ab der Position 0, das die Länge 3 hat. Die drei Werte, die sich jetzt in unserem Array befinden, sind die der Beschleunigung auf der X-, Y- und Z-Achse im Raum. Auch diese geben wir über Getter nach außen weiter:

public float getAccelerationOnXAxis( ){
  return mAcceleration[0];
}

Für die anderen beiden Achsen machen wir das entsprechend.

Jetzt haben wir einen super Listener implementiert, haben diesen aber noch nirgends eingehängt. Dafür springen wir zurück in die onCreate()- Methode. Zunächst holen wir uns dort einen SensorManager aus dem SystemService. Das geht wie folgt:

SensorManager manager =
 (SensorManager)getSystemService(Context.SENSOR_SERVICE);

Nun fragen wir ab, ob überhaubt ein Beschleunigungssensor vorhanden ist, indem wir uns die Liste vorhandener Sensoren geben lassen.

if(manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() > 0)

Wenn es Sensoren gibt,  holen wir uns einfach den erstbesten Sensor aus der Liste (weil in der Regel nicht mehr als einer da ist) und registrieren uns bei diesem als Listener:

final Sensor accelerometer =
 manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
manager.registerListener(
 this, accelerometer, SensorManager.SENSOR_DELAY_GAME);

Dabei legen wir über SensorManager.SENSOR_DELAY_GAME fest, mit welcher Präzision wir über Beschleunigungen informiert werden wollen. In unserem Fall wählen wir eine Geschwindigkeit, die für Spiele ausreichend ist, und fertig sind wir.

Der Teil über die Eingabemethoden ist übrigens stark an das Tutorial von Mario Zechner auf AndroidPit angelehnt.

Damit ist es geschafft! Wir haben eine abstrakte Basisactivity geschaffen, die es uns erlaubt, über ein Listenerkonzept 2D-Spiele für Android zu implementieren, inklusive Eingabemechanismen, Splashscreen und zwei-Thread-System.

Wenn Ihr an manchen Stellen nicht mitgekommen seid oder einfach gleich mit dem Spieleentwickeln anfangen wollt, findet ihr hier den Code zu der abstrakten Klasse, hier den Code zum Rendering- und hier den Code zum SimulationListener. Leitet dafür einfach eine Klasse von der GameActivity ab, erstellt Klassen die von SimulationListener und RenderingListener ableiten und hängt diese bevor(!) Ihr in Eurer abgeleiteten Klasse super.onCreate() aufruft als Listener über setRenderingListener() und setSimulationListener() ein. Bastelt darauf basierend Euer eigenes Spiel oder wartet noch auf das nächste Tutorial, da werden wir die erste rudimentäre Figur über den Bildschirm wandern lassen.

How to: Serverkommunikation in Android (REST + JSON)

Es gibt viele Bücher über Android, mindestens drei davon sogar in der deutschen Sprache. Die meisten dieser Bücher sind sich ziemlich ähnlich: Sie erklären Android-Grundkonzepte und manchmal noch ein bisschen mehr (z.b. OpenGL/ES) und das wars dann. Die Frage, die sich mir in diesen Büchern immer gestellt hat, war: Wenn meine Anwendung mit mehr als dem Handy kommunizieren lassen will, was dann? Wie bringe ich meinen Android dazu, mit einem Server zu reden? Dieser Teil wurde in der Regel als “HTTP-Grundlagen” oder ähnliches ausgelassen, zumindest in den Büchern die mir untergekommen sind.

Um ein bisschen Abhilfe zu verschaffen erkläre ich  hier zwei Methoden, mit Hilfe derer Android-Clients REST-Server ansprechen und JSON-Format übertragen, bzw. verarbeiten können.

Zuerst der Theoretische Teil: REST steht für Representional State Transfer und wurde im Jahr 2000 von Roy Fielding vorgestellt. Wie der Name bereits sagt, können Stadien transferiert werden, sprich auf solche zugegriffen werden. Oder andersrum: Ein REST-konformer Server kennt keinen Zustand. Rest baut auf dem HTTP-Protokol auf und verwendet (mindestens) vier der Basiskomponenten:

GET für Datenanfragen,

POST für das Ablegen von Daten,

DELETE für das Löschen von Daten und

PUT für das Updaten von Daten.

Internetnutzer sollten zumindest mit GET-Befehlen vertraut sein. Internetseiten, die Daten anfordern, übertragen, wenn sie so programmiert wurden, wie es von den HTTP-Schaffern (zu denen auch Fielding gehört) gedacht war, in der URL Parameter. Diese Parameter werden mit einem ? eingeleitet (z.B. http://www.andforge.net/?m=201001).

Als Übertragungsformat hat sich auf mobilen Endgeräten aufgrund des geringen Datenoverheads die JavaScript Object Notation, kur JSON, als passend erwiesen. JSON stellt Objekte Beispielsweise wie folgt dar:

  1. {
  2. "Name" : "Mustermann",
  3. "Vorname"      : "Max",
  4. "Adresse"       :
  5. {
  6. "Strasse"        : "Musterstrasse",
  7. "Nummer"     : "123",
  8. "PLZ"  : "98765",
  9. "Ort"   : "Musterstadt
  10. }
  11. }

Das Format ist also durchaus noch lesbar, aber in seinen Zusatzzeichen, die eine Interpretierbarkeit ermöglichen, sehr sparsam. Dies ist für die eventuell schwachen Verbindungen, die es auf Mobiltelefonen geben kann, hilfreich.

Nach dieser kurzen Einführung wenden wir uns DER Frage zu: Wie geht das in Android?

HTTP GET

Zunächst einmal wird ein REST-konformer Webservice, der im JSON-Format antwortet benötigt. Hat man diesen, kann es losgehen

  1. public class Xxx{
  2. //Bezeichnung der Klasse:
  3. private final String TAG="Xxx";
  4. public void getJSONObject(String url)
  5. {
  6. HttpClient httpClient = new DefaultHttpClient();
  7. HttpGet httpGet = new HttpGet(url);
  8. HttpResponse response;
  9. try {
  10. response = httpClient.execute(httpGet);
  11. // TODO: HTTP-Status (z.B. 404) in eigener Anwendung verarbeiten.
  12. Log.i(TAG,response.getStatusLine().toString());
  13. HttpEntity entity = response.getEntity();
  14. if (entity != null) {
  15. InputStream instream = entity.getContent();
  16. BufferedReader reader = new BufferedReader(new InputStreamReader(instream));
  17. StringBuilder sb = new StringBuilder();
  18. String line = null;
  19. while ((line = reader.readLine()) != null)
  20. sb.append(line + "n");
  21. String result=sb.toString();
  22. Log.i(TAG,result);
  23. instream.close()
  24. JSONObject <b style="color:black;background-color:#a0ffff">json</b>=new JSONObject(result);
  25. JSONArray nameArray=<b style="color:black;background-color:#a0ffff">json</b>.names();
  26. JSONArray valArray=<b style="color:black;background-color:#a0ffff">json</b>.toJSONArray(nameArray);
  27. for(int i=0;i<valArray.length();i++)
  28. {
  29. //TODO: Inhalte der Arrays verarbeiten.
  30. }
  31. }
  32. catch (ClientProtocolException e) {
  33. // TODO Auto-generated catch block
  34. e.printStackTrace();
  35. } catch (IOException e) {
  36. // TODO Auto-generated catch block
  37. e.printStackTrace();
  38. } catch (JSONException e) {
  39. // TODO Auto-generated catch block
  40. e.printStackTrace();
  41. } catch (Exception e){
  42. e.printStackTrace();
  43. }finally{
  44. httpGet.abort();
  45. }
  46. }

Zuerst erzeugen wir einen HTTPCLient. Dann wird ein HTTPGet-Objekt erzeugt. Diesem Objekt wird eine URL mitgegeben, die bereits alle Parameter der Anfrage enthält. Da Wir eine Antwort vom Server erwarten, erzeugen wir zudem ein HTTPResponse-Objekt. Nun führen wir den HTTPGet-Request aus und schreiben die Antwort in unser HTTPResponse-Objekt.

Nun machen wir uns an die angehängten Daten. Diese befinden sich in der HTTPEntity der Antwort. Wir lesen sie mit Hilfe eines buffered readers und eines stringbuffers (aus Performancegründen) aus und erzeugen aus dem so generierten String ein JSONObjekt, welches alle übertragenen Daten enthält, sofern sie im JSON-Format waren. Mit diesen Daten kann nun gearbeitet werden.

So einfach lassen sich Daten mittels REST und JSON in Android abfragen.

HTTP POST

Die nächste Frage, die sich aufdrängt: Daten abfragen funktioniert, aber wie ist das mit den drei anderen Dingern?

Fangen wir mit einer Methode zum Abschicken eines HTTP POST- Objektes an:

  1. public void postJSONObject(String url, JSONObject data, String objectName)
  2. {
  3. HttpPost postMethod = new HttpPost(url);
  4. try {
  5. HttpParams params = new BasicHttpParams();
  6. params.setParameter(objectName, data.toString());
  7. postMethod.setParams(params);
  8. httpClient.execute(postMethod);
  9. Log.i(TAG, "Post request, data: " + params.toString());
  10. } catch (ClientProtocolException e) {
  11. // TODO Auto-generated catch block
  12. e.printStackTrace();
  13. } catch (IOException e) {
  14. // TODO Auto-generated catch block
  15. e.printStackTrace();
  16. } catch (Exception e) {
  17. // TODO Auto-generated catch block
  18. e.printStackTrace();
  19. } finally {
  20. postMethod.abort();
  21. }
  22. }

Wie zuvor wird der Methode eine Ziel-URL übergeben. Des Weiteren wird ein JSONObject mit den zu übertragenden Daten und die Bezeichnung der Daten (z.B. “User”) übergeben. Da wir einem HTTPPost nicht einfach so ein JSONObject anhängen können, wandeln wir dieses zuerst in HTTP-Parameter um und hängen diese dann an.  Nun führen wir den POST aus und alles ist gut (hoffentlich). Natürlich sollten an dieser Stelle noch eventuelle Fehlercodes oder -Meldungen abgefangen werden, dies kann analog zur ersten Methode geschehen, wobei Fehlermeldungen natürlich für jeden Server individuell behandelt werden müssen (üblicherweise gillt: wenn ein JSON-Objekt mit “error:” beginnt, ist was schief gelaufen).

HTTP DELETE und HTTP PUT

DELETE und PUT können so wie POST implementiert werden, nur dass der Objekttyp von postMethod in HTTPDelete, bzw. HTTPPut, geändert wird.

Et voilà: So schnell geht’s und schon fangen die Server an zu flüstern, mehr als das, sie hören sogar zu =)

© 2024 Droid-Blog

Theme by Anders NorenUp ↑