Wednesday, September 14, 2011

Server push on Android

 

[Google C2DM]


3rd party Server send message to device via Google C2DM server.
1, requirementAndroid 2.2+
signed into Google Account
2, linkshttp://android-developers.blogspot.com/2010/05/android-cloud-to-device-messaging.html
http://code.google.com/android/c2dm/
http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html

3, limitation Before the phone bind with Google account, it can not work with this solution.
And Unfortunately, in some countries, Android phones selling in the market do not have Google products at all.(e.g. PR.China)

update: Google I/O 2012, Google announced the C2DM merged  to  cloud message http://developer.android.com/google/gcm/index.html
And a Google account in unnecessary in the message pushing process after Android 4.0.3

[Google app engine channel API / SocketIO]

Well, actually, Socket IO & GAE channel API can not push stuff to your application directly, it's a kind of web technology, depends on your browser.
You can embed a webview controller to your android application, then get connection to server via the websocket, or long polling or something else.
But, you can not get a message push when your app which contain the webview in background. webview can use in application with UI, can not work as a background service.

1, requirement
GAE web application
JavaScript enabled browser, on both PC and Phones. Android phones are absolutely OK.
on Android Phones, we can connect Phone application and server by webView(http://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String) controller.

2, links
http://code.google.com/appengine/docs/python/channel/
3, work flowhttp://code.google.com/appengine/images/channel_overview02.png
4, limitationyour server side application should use GAE SDK.
In some countries, GAE application with domain names in *.appspot.com were blocked.(such as PR.China)
But, you can ignore this issue by bind your GAE application with custom doamin name.
4, sample code: server side(GAE application & JavaScript client)
push messages to notify(browser on PC or Android) all connected clients know the connecting devices number. view code here: http://code.google.com/p/feifanplus/source/browse/#svn%2Ftrunk%2Fgae_serverpush
<index page>: get user id from query string in URL (“u=xxxx”) user id should be unique, so the best choice is consider phone’s MAC address as user id.
GAE application will create a token by user id in server side, then pass to JavaScript loaded in client. user id just like a channel’s name while token like channel’s id. GAE will push message to client by name(user id). token is used to open channel in JavaScript.
class MainPage(webapp.RequestHandler):
    def get(self):
        userID = self.request.get("u"); # user ID is MAC address   
        #create channel , return token
       token = channel.create_channel(userID);         template_values = {"token": token, "me": userID};
       
        #http resphone, client can show some information
        path = os.path.join(os.path.dirname(__file__), "login.html")
        self.response.out.write(template.render(path, template_values));
in login.html, Javascript code should save variable “me”(user id), that will be used to send message from client to server.

openChannel = function() {
                var token = '{{ token }}';
                var channel = new goog.appengine.Channel(token);
                var handler = {
                    'onopen': onOpened,
                    'onmessage': onMessage,
                    'onerror': function() {log_info('error');},
                    'onclose': function() {log_info('close');},
                };
               socket = channel.open(handler);                socket.onopen = onOpened;
                socket.onmessage = onMessage; /*client receive message from server*/   
            }   

JavaScript function openChannel() will connect to server’s channel which will push messages to client later.

In openChannel(), we registered onMessage() function, it’s used to receive messages pushed from server. The function will look like below. we assume message from server side is in JSON format.

onMessage = function(m) { /*receive message*/
   msg = JSON.parse(m.data);
   log_info(msg.msg);
   try{
      window.gaeclient.set_message(msg.msg); /*call android js interface*/
   }catch(error){}
};

And please put code for calling Android JS interface in a try catch block. Otherwise, JavaScript will interrupt here because of an exception: can not find window.gaeclient.

Actually, “gaeclient” is the name of JavaScript interface you added to Android webView controller.(see detail later)

We will have two buttons to control client connect and disconnect from channel.

    <button id="connBtn" class="mybtn">connect</button>
    <button id="disconnBtn" class="mybtn">disconnect</button>

Add JavaScript code for these 2 buttons. (jQuery used)

$("#connBtn").click(function() {               
        setTimeout(openChannel, 100);
    });   
   
    $("#disconnBtn").click(function() {
         sendMessage('/disconn'); /*ask server remove self from list*/         socket.close();
    });

When connect button pressed. JS will open channel, when open event occurred, onOpened() resisted in openChannel will be invoked.

onOpened = function() { /*channel open*/                       
        sendMessage('/conn'); /*ask server add self to list*/        log_info("connected");                       
    };

Server can manage all connected client, because when channel open , client post to URL /conn, and post to URL /disconn when client disconnected.

OK, let’s see in GAE application, how to process /conn and /disconn, how to let all connected clients know a new client is in.

g_phoneList = set();

class NewConn(webapp.RequestHandler):
    def post(self):
        userID = self.request.get("u");
        logging.info("new connection" + userID);
        g_phoneList.add(userID);       notify_all();

class RemoveConn(webapp.RequestHandler):
    def post(self):
        userID = self.request.get("u");
        logging.info("remove connection" + userID);       
        try:
            g_phoneList.remove(userID);        except:
            pass;
        notify_all();

def notify_all():
    message = {"msg": "connected devices: %d" % len(g_phoneList)};
    for phoneID in g_phoneList:
        logging.info("sending msg to " + phoneID)
        logging.info("sending content:" + json.dumps(message));
        channel.send_message(phoneID, json.dumps(message));

Server maintains a set(prevent duplicate client) to record all connected clients. And when connected or disconnected, server invoke notify_all(), push message to all connected clients.

Client will use onMessage() to process received messages.

Now, you can try in PC browser first. type URL with query string appended. such as “http://10.1.1.97:8086/?u=feifei

You’ll see

2

click connect, show “connected”, then show “connected devices:1” because get server pushed message.

3

open URL in another browser tab, such as “http://10.1.1.97:8086/?u=tuzi” , and click connect, you will see in this new opened browser. Tuzi is 2nd user connected to server, so she got message from server “connected devices:2”

5

and see in first opened (feifei’s) browser. he also get a updated message from server. printed “connected devices: 2”

6

click disconnect in user tuzi’s browser.

8

watch feifei’s browser. server had pushed latest connecting information to feifei. “connected devices: 1”, that’s correct because tuzi’s leaving.

9

5 sample code: android application

We can simply put a webView in Android code, that can load web page as PC browser do. But, I can show the 2-way communication between web application and Android application

I’ll show server pushed message in Android’s TextView controller. And add a Android button to clean printout in webView.

In previous code, we add some code in JavaScript onMessage() function, and put them in try…catch block, that will effect Android application now.

[JavaScript]

window.gaeclient.set_message(msg.msg); /*call android js interface*/

[Android code]

in onCreate():


mWebView = (WebView) findViewById(R.id.webView1); //configure webView
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);  //important!!
webSettings.setSavePassword(false); 
webSettings.setSaveFormData(false);
webSettings.setSupportZoom(false);

mWebView.addJavascriptInterface(new MyJavaScriptInterface(), "gaeclient");

mWebView.loadUrl("http://10.1.1.97:8086/?u="+getMAC()); //open web page, pass MAC address to server as user id.

class MyJavaScriptInterface:


final class MyJavaScriptInterface
   {
       MyJavaScriptInterface() {}

       public void set_message(final String s)  // function name same with JS       {
           mHandler.post(new Runnable(){
               public void run(){          
                   m_tv.setText(s);
               }
           });

       }
   }

When server pushed message arrived at JS level (onMessage()), code “window.gaeclient.set_message” will trigger registered Android JavaScript interface. TextView in Android application will be updated. This is how web page trigger Android.

click “connect “ in webView, you can see same behavior as PC browser did.

specially, TextView also updated when JS get message pushed from server.

device-2011-09-14-180559device-2011-09-14-180610



Next let’s trigger JS function clear_log() in webpage.

[JS code]


function clear_log(){
    $("#info").html("");
}   

[Android code]

        m_clearBtn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
               mWebView.loadUrl("javascript:clear_log()");               
            }           
        });

By method loadUrl() in webView, we can invoke JS code inside the web page. This is how Android trigger web page.

click android button “clean log”, yes, the printout in web page be removed. (Android code –> JS code)

device-2011-09-14-180703

 


[to be continued…]


How server push happen? Look into GAE SDK’s code.

Add server push feature in non-GAE web application.
SocketIO + Node.js

No comments:

Post a Comment