Wednesday, September 18, 2013

Android Looper and Toast from WorkerThread

Have you ever tried to launch Android Toast message from worker thread ? Probably you are wondering why the heck it is giving this error-

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

In this article we are going to explore reason behind the above exception and try to understand how Looper works in Android. At the end, I am going to explain one approach to run Toast from a worker thread, but before that we need to understand how Looper works. If you already know in/out of Looper, you can skip below section and directly move to the solution part.

Looper is a very basic wrapper class which attach a MessageQueue to a Thread and manage this queue. MessageQueue is a structure to sequentialize simultaneous processing requests of a Thread.  In Android, message/request processing classes like Handler uses Looper to manage their respective MessageQueue.
Looper = Thread + MessageQueue

Android Looper Life Cycle:
                                         
As you can see in the above figure, Looper Life cycle starts when we call prepare(), this static method creates instance of Looper class and store this in a ThreadLocal variable. Below code snippet from Looper.java
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
    }
 
Once Looper instance is created we have to call loop() which starts an infinite Loop and process requests accumulated in the Message Queue. Below code from Looper.java
    public static void loop() {

    // Looper instance and Thread verification

        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            // Process Messages
        }
    }

Due to above Infinite Loop, Looper blocks the current Thread execution until we call Quit. To get a hook to know when Looper is done with Message processing or when Message Queue is empty, we need to register MessageQueue.IdleHandler listener to the MessageQueue associated to the respective Looper. This can be achieved as- 
// Prepare looper
Looper.prepare();
                 
// Register Queue listener hook
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new IdleHandler() {
@Override
     public boolean queueIdle() {
          // TODO Auto-generated method stub
          return false;
     }
});
                 
// Start looping Message Queue
Looper.loop();

Running Toast from Worker Thread
Now, lets explain how we can run Toast in a thread other than UI or Main Thread. If we look inside of Toast.java, when we call makeText() internally it initialize Handler on the Calling thread. So, if the calling thread has not yet been attached to a MessageQueue Toast fails to create its instance. The only way to attach MessageQueue to a Thread is to use Looper, as a result Toast.java throws below exception-
 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

Toast.java -> Toast.TN.java -> Handler.java


public Toast(Context context) {
        mContext = context;
        mTN = new TN();
        mTN.mY = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.toast_y_offset);
}

public Handler() {
        // .......
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
}


So, I hope we now understand the real meaning of this famous error message. Lets now try an alternate approach to execute Toast in a separate Thread.

We need to initialize Looper and attach MessageQueue to our calling Thread prior to launch Toast. When we call show() method of Toast, it sends Handler message to process display logic of Toast, then it waits for defined time (LONG/SHORT) and then calls hide() which again sends Handler message to remove Toast View. Thus Looper associated to Toast is requesting for MessageQueue twice, so we'll keep a counter to track number of MessageQueue requests and when it is 2, we'll quit the Looper. It is really important to quit the Looper, as if we don't do so, it will block all further operations, since Looper.loop() is a blocking call.

Here is the code snippet to run Toast in a separate Thread-


     public void aboutLooper() {
         Thread th = new Thread() {
              public void run() {
                  System.out.println("Start Looper...");
                  // Prepare looper
                  Looper.prepare();

                  // Register Queue listener hook
                  MessageQueue queue = Looper.myQueue();
                  queue.addIdleHandler(new IdleHandler() {
                       int mReqCount = 0;

                       @Override
                       public boolean queueIdle() {
                           if (++mReqCount == 2) {
                                // Quit looper
                                Looper.myLooper().quit();
                                return false;
                           } else
                                return true;
                       }
                  });

                  // Show Toast- will be called when Looper.loop() starts
                  Toast.makeText(MainActivity.this, "Hey there!!",
                           Toast.LENGTH_LONG).show();
                  // Start looping Message Queue- Blocking call
                  Looper.loop();
                  System.out.println("It appears after Looper.myLooper().quit()");
              }
         };
         th.start();
     }

Tuesday, September 17, 2013

Java Reflection as Android API

Have you ever tried or wished to re-use or access source code which is part of a different Android APK ? If "Yes", then this article is for you. 
Well, I must agree its not a very common use case and when it comes to API in Android, we rather prefer JAR or Remote Service to expose functionalists. Java Reflection can be an alternate and useful API approach to expose functionalists from an APK or Application.

Here I'm going to explain a design approach where we can define API for Android using Java Reflection. For this example, Host.apk is an Application which hosts one API method which we are going to access from another application Client.apk.

Host.apk includes- HostApi.java which defines API method getMeta(). This method returns some pre-defined Text appended to calling Application's Package name.

package com.pras.host;

import android.content.Context;

public class HostApi {

     Context mContext;

     public HostApi(Context context) {
         this.mContext = context;
     }

     public String getMeta() {
         String pkgName = mContext.getPackageName();
         return "HostKey-" + pkgName;
     }
}

To access above API method getMeta(), we need to write a HostApi Proxy which access HostApi.java using Reflection. Here is the code snippet-

package com.pras.client; 

import java.lang.reflect.Constructor; 
import java.lang.reflect.Method; 
import android.content.Context;

public class HostApiProxy {

     private final String REMOTE_PACKAGE = "com.pras.host";
     private final String REMOTE_CLASS = "com.pras.host.HostApi";
     private final String REMOTE_METHOD = "getMeta";

     Context mContext;

     public HostApiProxy(Context context) {
         this.mContext = context;
     }

     public String getMeta() {
         String metaData = null;
         try {
              Class hostApiClass = getRemoteClass(mContext, REMOTE_PACKAGE,
                       REMOTE_CLASS);
              Object hostApiInstance = getRemoteInstance(hostApiClass,
                       new Class[] { Context.class }, new Object[] { mContext });
              metaData = (String) invokeRemoteMethod(hostApiInstance,
                       REMOTE_METHOD, new Class[] {}, new Object[] {});
         } catch (Exception ex) {
              ex.printStackTrace();
         }
         return metaData;
     }

     public Class getRemoteClass(Context context, String remotePackage,
              String remoteClass) throws Exception {

         // Get remote Application Context and request to include Source
         Context remoteContext = context.createPackageContext(remotePackage,
                  Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
         // Get Class loader associated to Remote Context
         ClassLoader loader = remoteContext.getClassLoader();
         Class cls = loader.loadClass(remoteClass);
         return cls;
     }

     public Object getRemoteInstance(Class remoteClass, Class[] args,
              Object[] argValues) throws Exception {
         Constructor constructor = remoteClass.getConstructor(args);
         return constructor.newInstance(argValues);
     }

     public Object invokeRemoteMethod(Object instance, String methodName,
              Class[] args, Object[] argValues) throws Exception {
         Method method = instance.getClass().getDeclaredMethod(methodName, args);
         // Make sure its accessible (if it is Private method)
         method.setAccessible(true);
         return method.invoke(instance, argValues);
     }
}
 

I have defined 3 utility methods- getRemoteClass(), getRemoteInstance() and invokeRemoteMethod() which help to get reference of remote Class stored in Host.apk and invoke getMeta() method. The important point to note here; to load the remote class, we need to get the instance of Class Loader of the respective Application's context. In Android, each application Context has its own Class Loader which is responsible to load/manage classes included inside the Application or APK.

Above approach and Reflection methods can be used to define any API. So, if you like this design approach, give a 2nd thought during designing your next API. Please find the complete Source and APK (Host.apk and Client.apk).