Console Apps
Saturday, March 12, 2011, Modified: Sunday, March 13, 2011 Refs
As developers we all know that sometimes the easiest way to see what is going on with your program is to drop in a printf() or, in the Java world, a System.out.println(). There are also whole classes of apps for which a GUI is just not needed. This is especially the case for little throwaway programs that get written to try something out quickly: you don't want to be crafting a GUI for each of those.

It is possible, given that the application-level Android platform is based on a fairly full Java environment, to make those System.out.println() calls, but a puzzle that meets the new developer is where they all go. A bit of rooting around soon turns that output up in the Android log, and at first this seems to be a reasonable solution for little test apps, But it is not really a first rate one. For one thing, the log mixes all sorts of other information into the same sequence of log entries. In the case of a console app, System.out is its main output and the user shouldn't have to trawl through a lot of noise to pick it out. For another thing, and this is the strongest motivator here, if the app is intended to go wild and get into the hands of users, they need to be able to see what it is trying to tell them without having to install a log dumping utility.

Ok, hopefully you are still with me and agree that we want to make console apps happen on Android, so how do we go about it? Well, standard Java allows System.out and System.err to be overriden. Indeed, that is what the Android platform does to send output to the log. If we were to create a class which extended java.io.OutputStream and replaced the default values of System.out and System.err with instances of it, we could send our output wherever we liked.

Since we are on android, we are going to need a gui of sorts, but lets make it as simple as possible and write it once to be reused in all our console apps. We'll fill the screen with a single text view and pipe stdout and stderr into it.

The first component we will need to define is the overridden OutputStream to plumb into stdout and stderr.
public class TextViewOutStream extends ByteArrayOutputStream {
Rather than subclassing OutputStream directly, we extend ByteArrayOutputStream to borrow its buffering abilities. This will allow us to do all our work in one method: flush() which is called when its buffer is full.
   /**
    * @param out A text view widget that it will be okay to append lines of text
    * to. This is best hosted inside a scrollview and linear layout - these will
    * allow scroll up and down, or call
    * "out.setMovementMethod(ScrollingMovementMethod.getInstance());" before
    * passing the view in and set the property android:scrollbars="vertical" in
    * its layout xml file.
    */
   public TextViewOutStream(Activity a, TextView out) {
      super();
      if(a == null){
         throw new IllegalArgumentException(this.getClass().getName() +
               ", Activity passed to constructor was null.");
      }
      if(out == null){
         throw new IllegalArgumentException(this.getClass().getName() +
               ", TextView passed to constructor was null.");
      }
      a_ = a;
      tv_ = out;
   }
Nothing tricky in the constructor: we just remember the text view that we will be filling and the Android Application that we are part of.
   
   @Override
   public void flush() throws IOException {
      synchronized(this) {
         super.flush();
         try{
            final String buffered_output = this.toString();
         
            a_.runOnUiThread(new Runnable() {
               @Override
               public void run() {
                  try{
                     final int x = tv_.getScrollX();
                     tv_.append(buffered_output);
Here in flush() is the meat of the implementation. First we let the superclass flush itself, and then we grab the contents of its buffer as a string using the line, final String buffered_output = this.toString(); . Then we spawn a new runnable to be run on the main UI thread with the line, a_.runOnUiThread(new Runnable() { . This is important as Android's UI is single threaded. Only the main UI thread that runs the event loop can modify GUI elements safely. The runnable that we spawn does the work of pasting our string into the view.
                     // Reset the textview so the new content is in view and the old scrolls up:
                     final int num_lines = tv_.getLineCount();
                     final float density = a_.getResources().getDisplayMetrics().density;
                     final float height_of_lines = num_lines * tv_.getLineHeight() * density;
                     final float v_space = tv_.getHeight();
                     final int y = (int)(height_of_lines > v_space ?
                           height_of_lines - v_space : tv_.getScrollY());
Scrolling the view so that new output appears at the bottom of the screen and not scrolling past the newest line is complicated by unit ambiguity. The docs are not clear about which values are in pixels and which are density-independent, so getting the right result depends on tweaking and seeing what works. Once we know where to scroll to, we tell the text view to do the scroll, we clear our buffers, and we are done:
                     tv_.scrollTo(x, y);
                  } catch(Throwable t){
                     Core.onThrowableSuppressed(t, this.getClass(),
                           "Thrown while updating text view with stdout on UI thread.\n");
                  }
               }
            });
            // Forget the output that we've already forwarded to the textview:
            this.reset();
         } catch(Throwable t){
            Core.onThrowableSuppressed(t, this.getClass(),
                  "Thrown while updating text view with stdout.\n");
         }
      }
   }
Core.onThrowableSuppressed() Is a hoogli utility method. You might want to call the Android standard Log.e() instead.
   private Activity a_;
   private TextView tv_;
}

The code for the TextViewOutStream class is a part of the hoogli shared project (just a regular Java project, not an Android library or even a jar, but the classes built by it are still reusable in dependent projects). A driver project is also available, with an activity that lives here.

Try Buffer, a smarter way to share links and pictures by spreading your tweets and posts out over time. Sign up from this link and we both get extra features.

Buffer
Home :: Items