2014년 8월 10일 일요일

Android Device to Device Messaging using Google Cloud Messaging (GCM) via Http

This Android tutorial will help to learn Android device to device messaging using Google Cloud Messaging (GCM) via a GCM Http server. This is just an improvisation of downstream messaging using GCM server. Communication flow originates from an Android device and the message to be communicated will be sent to GCM application server. From that server the message will be relayed to the Google Cloud Messaging server with the RegId of the device to send the notification. Then this message will be sent to the another Android device by the Google Cloud Messaging server as a notification.
Tutorial for upstream communication from an Android device using Google Cloud Connection Server (XMPP), will be posted soon. When we say device to device messaging immediate chat application comes to our mind. If you are thinking about a chat application or similar cases then XMPP will be the best way to go. If you are looking for simple notifications between Android devices then continue with this Android tutorial. You may even upgrade the example app provided in this tutorial to a chat application.
Device-To-Device-Messaging-using-GCM
If you are just starting with Google Cloud Messaging (GCM), I recommend you to go through the earlier tutorial Google Cloud Messaging GCM for Android and Push Notifications. It gives you more detailed step by step instruction to get started with Google Cloud Messaging. In this tutorial, I am just directly going to jump into the application and code. For the prerequisite like setting up your development environment, getting a Google API server key and all those preliminary steps you need to refer the above linked tutorial.

Device to Device Messaging Flow

Before start of exchanging messages, Android devices should register itself with the Google Cloud Messaging server and get a RegId. Then the Android device will share that regid with the GCM application server. Along with RegId, an identifier name will also be passed. The GCM app server will persist the name and RegId pair. Similarly all the devices that are going to participate should register themselves.
GCM server application which will act as a relay between the Android devices. Device one will send a message to GCM server by invoking a callback URL provided by the server. In the parameter it will send the recipient name. Using the recipient name GCM app sever will pick the respective RegId from its store and send the message along with RegId to the Google Cloud Messaging server. Subsequently Google Cloud messaging server will pass that message as a notification to the corresponding device.

Google Cloud Messaging Client App

RegisterActivity.java

package com.javapapers.android.gcm.devicetodevice;

import java.io.IOException;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.gms.gcm.GoogleCloudMessaging;

public class RegisterActivity extends Activity {

 Button btnGCMRegister;
 Button btnAppShare;
 GoogleCloudMessaging gcm;
 Context context;
 String regId;
 EditText userName;

 public static final String REG_ID = "regId";
 private static final String APP_VERSION = "appVersion";

 static final String TAG = "Register Activity";

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_register);

  context = getApplicationContext();

  // to register with Google GCM Server
  btnGCMRegister = (Button) findViewById(R.id.btnGCMRegister);
  btnGCMRegister.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View arg0) {
    if (TextUtils.isEmpty(regId)) {
     regId = registerGCM();
     Log.d("RegisterActivity", "GCM RegId: " + regId);
    } else {
     Toast.makeText(getApplicationContext(),
       "Already Registered with GCM Server!",
       Toast.LENGTH_LONG).show();
    }
   }
  });

  // to share regid to our custom GCM application server
  btnAppShare = (Button) findViewById(R.id.btnAppShare);
  btnAppShare.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View arg0) {

    userName = (EditText) findViewById(R.id.userName);
    String name = userName.getText().toString();

    if (TextUtils.isEmpty(regId)) {
     Toast.makeText(getApplicationContext(), "RegId is empty!",
       Toast.LENGTH_LONG).show();
    } else if (TextUtils.isEmpty(name)) {
     Toast.makeText(getApplicationContext(), "Name is empty!",
       Toast.LENGTH_LONG).show();
    } else {
     Intent i = new Intent(getApplicationContext(),
       MainActivity.class);
     i.putExtra(Config.REGISTER_NAME, name);
     i.putExtra("regId", regId);
     Log.d("RegisterActivity",
       "onClick of Share: Before starting main activity.");
     startActivity(i);
     finish();
     Log.d("RegisterActivity", "onClick of Share: After finish.");
    }
   }
  });
 }

 public String registerGCM() {

  gcm = GoogleCloudMessaging.getInstance(this);
  regId = getRegistrationId(context);
  if (TextUtils.isEmpty(regId)) {
   registerInBackground();
   Log.d("RegisterActivity",
     "registerGCM - successfully registered with GCM server - regId: "
       + regId);
  } else {
   Toast.makeText(getApplicationContext(),
     "RegId already available. RegId: " + regId,
     Toast.LENGTH_LONG).show();
  }
  return regId;
 }

 private String getRegistrationId(Context context) {
  final SharedPreferences prefs = getSharedPreferences(
    MainActivity.class.getSimpleName(), Context.MODE_PRIVATE);
  String registrationId = prefs.getString(REG_ID, "");
  if (registrationId.isEmpty()) {
   Log.i(TAG, "Registration not found.");
   return "";
  }
  int registeredVersion = prefs.getInt(APP_VERSION, Integer.MIN_VALUE);
  int currentVersion = getAppVersion(context);
  if (registeredVersion != currentVersion) {
   Log.i(TAG, "App version changed.");
   return "";
  }
  return registrationId;
 }

 private static int getAppVersion(Context context) {
  try {
   PackageInfo packageInfo = context.getPackageManager()
     .getPackageInfo(context.getPackageName(), 0);
   return packageInfo.versionCode;
  } catch (NameNotFoundException e) {
   Log.d("RegisterActivity",
     "I never expected this! Going down, going down!" + e);
   throw new RuntimeException(e);
  }
 }

 private void registerInBackground() {
  new AsyncTask() {
   @Override
   protected String doInBackground(Void... params) {
    String msg = "";
    try {
     if (gcm == null) {
      gcm = GoogleCloudMessaging.getInstance(context);
     }
     regId = gcm.register(Config.GOOGLE_PROJECT_ID);
     Log.d("RegisterActivity", "registerInBackground - regId: "
       + regId);
     msg = "Device registered, registration ID=" + regId;

     storeRegistrationId(context, regId);
    } catch (IOException ex) {
     msg = "Error :" + ex.getMessage();
     Log.d("RegisterActivity", "Error: " + msg);
    }
    Log.d("RegisterActivity", "AsyncTask completed: " + msg);
    return msg;
   }

   @Override
   protected void onPostExecute(String msg) {
    Toast.makeText(getApplicationContext(),
      "Registered with GCM Server." + msg, Toast.LENGTH_LONG)
      .show();
   }
  }.execute(null, null, null);
 }

 private void storeRegistrationId(Context context, String regId) {
  final SharedPreferences prefs = getSharedPreferences(
    MainActivity.class.getSimpleName(), Context.MODE_PRIVATE);
  int appVersion = getAppVersion(context);
  Log.i(TAG, "Saving regId on app version " + appVersion);
  SharedPreferences.Editor editor = prefs.edit();
  editor.putString(REG_ID, regId);
  editor.putInt(APP_VERSION, appVersion);
  editor.commit();
 }
}

activity_register.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="20dp"
        android:src="@drawable/gcm_logo" />

    <Button
        android:id="@+id/btnGCMRegister"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="10dp"
        android:text="Register Device with Google GCM Server" />

    <View
        android:layout_width="fill_parent"
        android:layout_height="2dp"
        android:layout_margin="10dp"
        android:background="@android:color/darker_gray" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:text="Enter name and register with GCM app server."
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textStyle="italic" />

    <EditText
        android:id="@+id/userName"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:singleLine="true" />

    <Button
        android:id="@+id/btnAppShare"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:text="Share GCM-RegId with App Server" />

</LinearLayout>
Android-Google-GCM-Device-To-Device-Communication-Register

MainActivity.java

package com.javapapers.android.gcm.devicetodevice;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

 ShareExternalServer appUtil;
 String regId;
 String userName;
 AsyncTask shareRegidTask;

 EditText toUser;
 EditText message;
 Button btnSendMessage;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  appUtil = new ShareExternalServer();

  regId = getIntent().getStringExtra("regId");
  Log.d("MainActivity", "regId: " + regId);

  userName = getIntent().getStringExtra(Config.REGISTER_NAME);
  Log.d("MainActivity", "userName: " + userName);

  shareRegidTask = new AsyncTask() {
   @Override
   protected String doInBackground(Void... params) {
    String result = appUtil
      .shareRegIdWithAppServer(regId, userName);
    return result;
   }

   @Override
   protected void onPostExecute(String result) {
    shareRegidTask = null;
    Toast.makeText(getApplicationContext(), result,
      Toast.LENGTH_LONG).show();
   }

  };

  // to send message to another device via Google GCM
  btnSendMessage = (Button) findViewById(R.id.sendMessage);
  btnSendMessage.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View arg0) {

    toUser = (EditText) findViewById(R.id.toUser);
    String toUserName = toUser.getText().toString();

    message = (EditText) findViewById(R.id.message);
    String messageToSend = message.getText().toString();

    if (TextUtils.isEmpty(toUserName)) {
     Toast.makeText(getApplicationContext(),
       "To User is empty!", Toast.LENGTH_LONG).show();
    } else if (TextUtils.isEmpty(messageToSend)) {
     Toast.makeText(getApplicationContext(),
       "Message is empty!", Toast.LENGTH_LONG).show();
    } else {
     Log.d("MainActivity", "Sending message to user: "
       + toUserName);
     sendMessageToGCMAppServer(toUserName, messageToSend);

    }
   }
  });

  shareRegidTask.execute(null, null, null);
 }

 private void sendMessageToGCMAppServer(final String toUserName,
   final String messageToSend) {
  new AsyncTask() {
   @Override
   protected String doInBackground(Void... params) {

    String result = appUtil.sendMessage(userName, toUserName,
      messageToSend);
    Log.d("MainActivity", "Result: " + result);
    return result;
   }

   @Override
   protected void onPostExecute(String msg) {
    Log.d("MainActivity", "Result: " + msg);
    Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG)
      .show();
   }
  }.execute(null, null, null);
 }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/lblMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="20dp"
        android:text="Send Message to other Device"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="50dp"
        android:text="To User:"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <EditText
        android:id="@+id/toUser"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:singleLine="true" >

        <requestFocus />
    </EditText>

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="10dp"
        android:text="Message:"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <EditText
        android:id="@+id/message"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:singleLine="true" />

    <Button
        android:id="@+id/sendMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="20dp"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="Send Message" />

</LinearLayout>
Android-Google-GCM-Device-To-Device-Send-Message

ShareExternalServer.java

package com.javapapers.android.gcm.devicetodevice;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import android.util.Log;

public class ShareExternalServer {

 public String shareRegIdWithAppServer(final String regId,
   final String userName) {
  String result = "";
  Map paramsMap = new HashMap();
  paramsMap.put("action", "shareRegId");
  paramsMap.put("regId", regId);
  paramsMap.put(Config.REGISTER_NAME, userName);
  result = request(paramsMap);
  if ("success".equalsIgnoreCase(result)) {
   result = "RegId shared with GCM application server successfully. Regid: "
     + regId + ". Username: " + userName;
  }
  Log.d("ShareExternalServer", "Result: " + result);
  return result;
 }

 public String sendMessage(final String fromUserName,
   final String toUserName, final String messageToSend) {

  String result = "";
  Map paramsMap = new HashMap();
  paramsMap.put("action", "sendMessage");
  paramsMap.put(Config.REGISTER_NAME, fromUserName);
  paramsMap.put(Config.TO_NAME, toUserName);
  paramsMap.put(Config.MESSAGE_KEY, messageToSend);
  result = request(paramsMap);
  if ("success".equalsIgnoreCase(result)) {
   result = "Message " + messageToSend + " sent to user " + toUserName
     + " successfully.";
  }
  Log.d("ShareExternalServer", "Result: " + result);
  return result;
 }

 public String request(Map paramsMap) {
  String result = "";
  URL serverUrl = null;
  OutputStream out = null;
  HttpURLConnection httpCon = null;
  try {
   serverUrl = new URL(Config.APP_SERVER_URL);
   StringBuilder postBody = new StringBuilder();
   Iterator> iterator = paramsMap.entrySet()
     .iterator();
   while (iterator.hasNext()) {
    Entry param = iterator.next();
    postBody.append(param.getKey()).append('=')
      .append(param.getValue());
    if (iterator.hasNext()) {
     postBody.append('&');
    }
   }
   String body = postBody.toString();
   byte[] bytes = body.getBytes();
   httpCon = (HttpURLConnection) serverUrl.openConnection();
   httpCon.setDoOutput(true);
   httpCon.setUseCaches(false);
   httpCon.setFixedLengthStreamingMode(bytes.length);
   httpCon.setRequestMethod("POST");
   httpCon.setRequestProperty("Content-Type",
     "application/x-www-form-urlencoded;charset=UTF-8");
   Log.d("ShareExternalServer", "Just before getting output stream.");
   out = httpCon.getOutputStream();
   out.write(bytes);
   int status = httpCon.getResponseCode();
   Log.d("ShareExternalServer", "HTTP Connection Status: " + status);
   if (status == 200) {
    result = "success";
   } else {
    result = "Post Failure." + " Status: " + status;
   }

  } catch (MalformedURLException e) {
   Log.e("ShareExternalServer", "Unable to Connect. Invalid URL: "
     + Config.APP_SERVER_URL, e);
   result = "Invalid URL: " + Config.APP_SERVER_URL;
  } catch (IOException e) {
   Log.e("ShareExternalServer",
     "Unable to Connect. Communication Error: " + e);
   result = "Unable to Connect GCM App Server.";
  } finally {
   if (httpCon != null) {
    httpCon.disconnect();
   }
   if (out != null) {
    try {
     out.close();
    } catch (IOException e) {
     // do nothing
    }
   }
  }
  return result;
 }

}
I have not displayed the source of GCMNotificationIntentService.java, GcmBroadcastReceiver.java, Config.java and AndroidManifest.xml files. As they are same as given the previous tutorial. Either you may refer the previous tutorial or you can download the project below to get the complete files.

Download Android GCM Client for Device to Device Messaging

Google Cloud Messaging Server Application

GCMNotification.java

package com.javapapers.java.gcm;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.MulticastResult;
import com.google.android.gcm.server.Result;
import com.google.android.gcm.server.Sender;

@WebServlet("/GCMNotification")
public class GCMNotification extends HttpServlet {
 private static final long serialVersionUID = 1L;

 // Put your Google API Server Key here
 private static final String GOOGLE_SERVER_KEY = "AIzaSyA9DQTcggUtfqABClnV_XYZVE8QiKBEaP4";
 static final String REGISTER_NAME = "name";
 static final String MESSAGE_KEY = "message";
 static final String TO_NAME = "toName";
 static final String REG_ID_STORE = "GCMRegId.txt";

 public GCMNotification() {
  super();
 }

 protected void doGet(HttpServletRequest request,
   HttpServletResponse response) throws ServletException, IOException {
  doPost(request, response);

 }

 protected void doPost(HttpServletRequest request,
   HttpServletResponse response) throws ServletException, IOException {

  String action = request.getParameter("action");

  if ("shareRegId".equalsIgnoreCase(action)) {

   writeToFile(request.getParameter("name"),
     request.getParameter("regId"));
   request.setAttribute("pushStatus",
     "GCM Name and corresponding RegId Received.");
   request.getRequestDispatcher("index.jsp")
     .forward(request, response);

  } else if ("sendMessage".equalsIgnoreCase(action)) {

   try {
    String fromName = request.getParameter(REGISTER_NAME);
    String toName = request.getParameter(TO_NAME);
    String userMessage = request.getParameter(MESSAGE_KEY);
    Sender sender = new Sender(GOOGLE_SERVER_KEY);
    Message message = new Message.Builder().timeToLive(30)
      .delayWhileIdle(true).addData(MESSAGE_KEY, userMessage)
      .addData(REGISTER_NAME, fromName).build();
    Map regIdMap = readFromFile();
    String regId = regIdMap.get(toName);
    Result result = sender.send(message, regId, 1);
    request.setAttribute("pushStatus", result.toString());
   } catch (IOException ioe) {
    ioe.printStackTrace();
    request.setAttribute("pushStatus",
      "RegId required: " + ioe.toString());
   } catch (Exception e) {
    e.printStackTrace();
    request.setAttribute("pushStatus", e.toString());
   }
   request.getRequestDispatcher("index.jsp")
     .forward(request, response);
  } else if ("multicast".equalsIgnoreCase(action)) {

   try {
    String fromName = request.getParameter(REGISTER_NAME);
    String userMessage = request.getParameter(MESSAGE_KEY);
    Sender sender = new Sender(GOOGLE_SERVER_KEY);
    Message message = new Message.Builder().timeToLive(30)
      .delayWhileIdle(true).addData(MESSAGE_KEY, userMessage)
      .addData(REGISTER_NAME, fromName).build();
    Map regIdMap = readFromFile();

    List regIdList = new ArrayList();

    for (Entry entry : regIdMap.entrySet()) {
     regIdList.add(entry.getValue());
    }

    MulticastResult multiResult = sender
      .send(message, regIdList, 1);
    request.setAttribute("pushStatus", multiResult.toString());
   } catch (IOException ioe) {
    ioe.printStackTrace();
    request.setAttribute("pushStatus",
      "RegId required: " + ioe.toString());
   } catch (Exception e) {
    e.printStackTrace();
    request.setAttribute("pushStatus", e.toString());
   }
   request.getRequestDispatcher("index.jsp")
     .forward(request, response);
  }
 }

 private void writeToFile(String name, String regId) throws IOException {
  Map regIdMap = readFromFile();
  regIdMap.put(name, regId);
  PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
    REG_ID_STORE, false)));
  for (Map.Entry entry : regIdMap.entrySet()) {
   out.println(entry.getKey() + "," + entry.getValue());
  }
  out.println(name + "," + regId);
  out.close();

 }

 private Map readFromFile() throws IOException {
  BufferedReader br = new BufferedReader(new FileReader(REG_ID_STORE));
  String regIdLine = "";
  Map regIdMap = new HashMap();
  while ((regIdLine = br.readLine()) != null) {
   String[] regArr = regIdLine.split(",");
   regIdMap.put(regArr[0], regArr[1]);
  }
  br.close();
  return regIdMap;
 }
}

index.jsp

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<%
 String pushStatus = "";
 Object pushStatusObj = request.getAttribute("pushStatus");

 if (pushStatusObj != null) {
  pushStatus = pushStatusObj.toString();
 }
%>
<head>
<title>Google Cloud Messaging (GCM) Server in Java</title>
</head>
<body>

 <h1>Google Cloud Messaging (GCM) Server in Java: Device to Device
  Communication</h1>

 <form action="GCMNotification?action=multicast" method="post">

  <div>
   <input type="hidden" name="name" value="Admin" />
   <textarea rows="2" name="message" cols="23"
    placeholder="Message to transmit via GCM"></textarea>
  </div>
  <div>
   <input type="submit" value="Send Push Notification via GCM" />
  </div>
 </form>
 <p>
  <h3>
   <%=pushStatus%>
  </h3>
 </p>
</body>
</html>

Download Android GCM Server for Device to Device Messaging

Android Device to Device Messaging Output

Device To Device Message Sent:

Android-Google-GCM-Device-To-Device-Message-Sent

Device To Device Message Received:

Android-Google-GCM-Device-To-Device-Message-Received

Device To Device Reply Message Sent:

Android-Google-GCM-Device-To-Device-Reply-Sent

Device To Device Reply Message Received:

Android-Google-GCM-Device-To-Device-Reply-Received

Device To Device both Messages:

Android-Google-GCM-Device-To-Device-Messages

Google Cloud Messaging Multicast Messages

Following output showcases the multicast messaging to devices using Google cloud messaging. From the GCM server application we can send messages to multiple Android devices. In this tutorial example we are sending message to all registered Android devices.
Multicast-Message-from-GCM-Server
Google-GCM-Multicast-message-sent
Google-GCM-multicast-message-received
This Android tutorial was added on 30/03/2014.

댓글 없음:

댓글 쓰기