Skip to content

Lifecycle of a Processing sketch

codeanticode edited this page Mar 19, 2019 · 13 revisions

This page is intended to give an overview of how a Processing for Android sketch is initialized and run by the Android system, and to help understanding what classes from the core library are involved in handling the low-level rendering and input operations through the Android API. This lifecyle is different depending if the sketch is run as a regular app, a live wallpaper, a watch face, or a VR app. Let's start looking into the details for a regular app:

Running a sketch as a regular app

Every time you run a sketch from from the Android mode as a regular app, for example something as simple as the following code:

void setup() {
  fullScreen(P2D);
}

void draw() {
  if (mousePressed) {
    ellipse(mouseX, mouseY, 50, 50);
  }
}

Processing will automatically convert this code into a valid Android project, which will then build into an app package (.apk) using Gradle, and finally install on the device or in the emulator using the Android Debug Bridge (adb) tool available in the Android SDK.

For the code above, Processing will generate two separate java files, one holding the sketch itself and another the main activity that will launch the sketch inside a fragment that contains nothing but our sketch:

MainActivity.java:

package core_debug;

public class MainActivity extends AppCompatActivity {
  private PApplet sketch;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    FrameLayout frame = new FrameLayout(this);
    frame.setId(CompatUtils.getUniqueViewId());
    setContentView(frame, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                                     ViewGroup.LayoutParams.MATCH_PARENT));

    sketch = new Sketch();
    PFragment fragment = new PFragment(sketch);
    fragment.setView(frame, this);
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    if (sketch != null) {
      sketch.onRequestPermissionsResult(
      requestCode, permissions, grantResults);
    }
  }

  @Override
  public void onNewIntent(Intent intent) {
    if (sketch != null) {
      sketch.onNewIntent(intent);
    }
  }

  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (sketch != null) {
      sketch.onActivityResult(requestCode, resultCode, data);
    }
  }

  @Override
  public void onBackPressed() {
    if (sketch != null) {
      sketch.onBackPressed();
    }
  }
}

Sketch.java:

package core_debug;

import processing.core.PApplet;

public class Sketch extends PApplet {
  public void settings() {
    fullScreen(P2D);
  }

  public void draw() {
    if (mousePressed) {
      ellipse(mouseX, mouseY, 50, 50);
    }
  }
}

You will notice that the sketch code is different from what we originally wrote in the PDE. It is all included in a subclass of PApplet, which is the base class containing most of the Processing API. Another detail is that the fullScreen() call was moved from setup() into settings(). We will see why in a bit. These transformations in the code are carried out by the Processing pre-processor, which makes sure that our Processing sketch (which as it is is not 100% syntactically valid Java) is ready to be compiled by the standard Java compiler.

The important part in the Main Activity code is the three lines at the end of the onCreate() method:

sketch = new Sketch();
PFragment fragment = new PFragment(sketch);
fragment.setView(frame, this);

There, we are creating an instance of our sketch and the containing PFragment (which is a subclass of Android's Fragment, specialized to contain Processing sketches and part of the Processing core library for Android), passing the sketch to the fragment, and then setting the FrameLayout view object we created earlier as the view where the PFragment will be added to as part of the activity layout. With these steps, our sketch will be properly initialized and the rendering thread will be launched when the onCreateView() method of the PFragment is called. The different life cycle events triggered by the Android system on the PFragment object -onStart(), onResume(), etc.- will call the corresponding functions in the sketch -start(), resume(), etc.- that the user can use to handle these situations:

Fragment lifecycle

Running a sketch as a live wallpaper

The life cycle of a live wallpaper is different from the above description because an Android wallpaper runs continuously in the background as a service, and it does not have a main activity associated to it. If our sketch is the following:

int hue;

void setup() {
  fullScreen();
  colorMode(HSB, 360, 100, 100);
}

void draw() {
  background(hue % 360, 50, 50);
  hue++;
}

Processing will turn it into the following code files:

MainService.java:

package wallpaper;
        
import processing.android.PWallpaper;
import processing.core.PApplet;
        
public class MainService extends PWallpaper {  
  @Override
  public PApplet createSketch() {
    PApplet sketch = new Sketch();    
    return sketch;
  }
}

Sketch.java:

package wallpaper;

import processing.core.PApplet;

public class Sketch extends PApplet {
  int hue;

  public void settings() {
    fullScreen();
  }

  public void setup() {
    colorMode(HSB, 360, 100, 100);
  }

  public void draw() {
    background(hue % 360, 50, 50);
    hue++;
  }
}

The life cycle of a live wallpaper is simpler than that of a regular app (a wallpaper is an unbound service):

Service lifecycle

However, the actual implementation of a wallpaper is contained in an inner class called WallpaperEngine: the reason for this is that a wallpaper can have several simultaneous engine instances. For example, when the wallpaper is running in the background but the user also selected the wallpaper in the wallpaper picker. Each engine instance will create its own sketch instance. The wallpaper engine will only call the resume() or pause() functions in the sketch upon a visibility change in the wallpaper: resuming when the wallpaper becomes visible, and pausing when it is no longer so. Sketches in a wallpaper never get the start() or stop() functions called.

Running a sketch as a watch face

Watch faces work similarly to live wallpapers, as they are also services. If we start with a sketch like so:

void setup() { 
  fullScreen();
  frameRate(1);
  textFont(createFont("Serif-Bold", 48 * displayDensity));
  textAlign(CENTER, CENTER);
  fill(255);  
}

public void draw() {
  background(0);
  if (wearInteractive()) {
    String str = hour() + ":" + nfs(minute(), 2) + ":" + nfs(second(), 2);
    text(str, width/2, height/2);     
  }  
}

Processing will generate the following two files:

MainService.java:

package watchface;
        
import processing.android.PWatchFaceCanvas;
import processing.core.PApplet;

public class MainService extends PWatchFaceCanvas {
  @Override
  public PApplet createSketch() {
    PApplet sketch = new Sketch();    
    return sketch;
  }
}

Sketch.java:

package watchface;

import processing.core.PApplet; 

public class Sketch extends PApplet {
  public void settings() {  
    fullScreen();
  }

  public void setup() {  
    frameRate(1);
    textFont(createFont("Serif-Bold", 48 * displayDensity));
    textAlign(CENTER, CENTER);
    fill(255);  
  }

  public void draw() {
    background(0);
    if (wearInteractive()) {
      String str = hour() + ":" + nfs(minute(), 2) + ":" + nfs(second(), 2);
      text(str, width/2, height/2);     
    }  
  }
}

The service behind a watch face is very similar to that of a live wallpaper, in fact the WatchFaceService class is a subclass of WallpaperService.