使用基于云的告警服务的 Android* 客户端应用案例研究

下载文章

下载使用基于云的告警服务的 Android* 客户端应用案例研究 [PDF 443KB]

摘要

本文讨论使用基于云的 web 服务的安卓客户端应用案例研究。 本项目的构建基础是由谷歌提供的免费 Google* 云消息(GCM)服务。 该服务使用第三方库 — 安卓异步超文本传输协议客户端 — 发送基于回调函数的 HTTP 请求。 它通过应用服务器和 JSON 串交换消息。 使用 google-gson 库解析 JSON 串。 该客户端还使用由应用服务器通过 RESTful API 提供的 web 服务。

概述

本案例研究的重点是开发面向基于云的医保告警服务的安卓客户端应用。 医生或护士可将这一客户端应用安装在安卓设备中。 医院的中央呼叫系统向设备发送简单通知消息。 通知会发出告警声音,并显示在设备的状态栏。 用户接收通知并回复告警。 用户还可浏览所有未阅复的告警及历史记录。 这一使用案例十分典型;易于推广至其它行业。

面向安卓的谷歌云消息

使用谷歌免费提供的面向安卓的谷歌云消息(GCM),您可从您的服务器向正在运行应用的安卓设备发送消息(图 1)。 它还支持您的服务器接收从安卓设备发送的逆向消息。 如需深入了解 GCM,请访问:http://developer.android.com/google/gcm/gcm.html



图 1. Android* 应用使用应用服务器通过 GCM 交换数据

安卓异步超文本传输协议客户端

GCM 为 HTTP 和 XMPP 提供连接服务器。 在本项目中,我们使用第三方库 — 安卓异步超文本传输协议客户端 — 向应用服务器发送请求。 该库经过 Apache Version 2 许可。 这一基于回调的 HTTP 客户端,其中,HTTP 请求发生在 UI 线程之外。 关于库的详细用法,请访问:http://loopj.com/android-async-http/

数据交换格式

JSON (JavaScript* 对象表示, http://www.json.org/) 是用于服务与客户端之间的主流数据互换格式。 在我们的安卓客户端应用中,我们使用 google-gson 解析来自服务器的 JSON 串。 如需深入了解 google-gson 库,请访问:https://code.google.com/p/google-gson/

使用 GCM 和应用服务器注册

您的安卓应用首次使用 GCM 服务时,需要使用 GCM 进行注册:调用com.google.android.gcm.GCMRegistrar 方法 register()。 在我们的安卓客户端应用中,采用的是 onCreate() 启动操作方法(代码示例 1)。


package com.intcmobile.medipaging;

…
import com.google.android.gcm.GCMRegistrar;
import com.loopj.android.http.*;

…
public class SplashActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
…
		registerDeviceWithGCM();
…
	}

…
	/*
	 * Register device with GCM in background
	 */
	AsyncTask<Void, Void, Void> mRegisterTask;	
	void registerDeviceWithGCM() {
		if ((SENDER_ID == null) && (SERVER_URL == null)) {
			return;
		}
		gcmRegistrationId = GCMRegistrar.getRegistrationId(this);
		if(gcmRegistrationId.equals("")) {
			GCMRegistrar.register(this, SENDER_ID);
		} else {
			if (!GCMRegistrar.isRegisteredOnServer(this)) {
				final Context context = this;
				mRegisterTask = new AsyncTask<Void, Void, Void>() {
					@Override
					protected Void doInBackground(Void... params) {
						boolean registered = Utilities.serverRegister(context, gcmRegistrationId);
						if (!registered) {
							// Asynchronous registration task failed. Unregister this device with GCM
							GCMRegistrar.unregister(context);
						
						}
						return null;
					}					
					@Override
					protected void onPostExecute(Void result) {
						mRegisterTask = null;
					}
				};
			}
		}
	}
…
}

代码示例 1 使用 GCM 服务注册设备**

在代码示例 1 中,我们首先查看设备是否已经使用 GCM 注册。 如果未注册,我们使用 GCM 服务器进行注册。 然后查看设备是否已经在应用服务器上注册,如果未注册,我们开启 AsyncTask 在后台注册。 代码示例 2 是实用程序类别的定义。

package com.intcmobile.medipaging;

import static com.intcmobile.medipaging.Utilities.DEVICE_NAME;
import static com.intcmobile.medipaging.Utilities.SERVER_URL;
import static com.intcmobile.medipaging.Utilities.TAG;
…
import com.google.android.gcm.GCMRegistrar;

…
public class Utilities {
…
    /**
     * Handle register/unregister call back from GCMIntentService
     */

    /**
     * Register this account/device pair with the gcm server.
     * This function is normally called from the onRegistered call back function in GCMIntentService
     *
     * @return whether the registration succeeded or not.
     */
    static boolean serverRegister(final Context context, final String regId) {
        String serverUrl  = SERVER_URL + "/login";

        Map<String, String> params = new HashMap<String, String>();
        params.put("username",   Utilities.userName);
        params.put("password",   Utilities.password);
        params.put("regId",      regId);

        try {
            post(serverUrl, params);
            GCMRegistrar.setRegisteredOnServer(context, true);
            return true;
        } 
        catch (IOException e) {
        }
    }
    return false;
        }

    /**
     * Unregister this account/device pair on the server.
     */
    static void serverUnrregister(final Context context, final String regId) {
        String serverUrl = SERVER_URL + "/logout";
        Map<String, String> params = new HashMap<String, String>();
        params.put("regId", regId);
        try {
            post(serverUrl, params);
            GCMRegistrar.setRegisteredOnServer(context, false);
        } catch (IOException e) {
            //handle exception
        }
    }

    
    /**
     * Issue a GET request to the server
     * 
     */
    
    
    private static void get(String endpoint)
    	throws IOException {
    	URL url;
    	try {
        	url = new URL(endpoint);    		
    	} catch (MalformedURLException e) {
    		throw new IllegalArgumentException("invalid url: " + endpoint);
    	}
    	HttpURLConnection conn = null;
    	try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Content-Type","text/html");
            // post the request
            OutputStream out = conn.getOutputStream();
            out.close();
            // handle the response
            int status = conn.getResponseCode();
       
            if (status != 200) {
              throw new IOException("HTTP GET failed with error code " + status);
            }
    		
    	} finally {       
            if (conn != null) {
                conn.disconnect();
            }    		
    	}
    }
    /**
     * Issue a POST request to the server.
     *
     * @param endpoint POST address.
     * @param params request parameters.
     *
     * @throws IOException propagated from POST.
     */
    private static void post(String endpoint, Map<String, String> params)
        throws IOException {
        URL url;
        try {
            url = new URL(endpoint);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("invalid url: " + endpoint);
        }
        
        StringBuilder bodyBuilder = new StringBuilder();
        Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
        // constructs the POST body using the parameters
        while (iterator.hasNext()) {
            Entry<String, String> param = iterator.next();
            bodyBuilder.append(param.getKey())
                       .append('=')
                       .append(param.getValue());
            if (iterator.hasNext()) {
                bodyBuilder.append('&');
            }
        }
        String body = bodyBuilder.toString();
       
        byte[] bytes = body.getBytes();
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setFixedLengthStreamingMode(bytes.length);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded;charset=UTF-8");
            // post the request
            OutputStream out = conn.getOutputStream();
            out.write(bytes);
            out.close();
            // handle the response
            int status = conn.getResponseCode();
            if (status != 200) {
              throw new IOException("HTTP POST operation failed with error code " + status);
            }
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
      }
…

}

代码示例 2 实用方法是关于第三方应用服务器上运行的服务进行设备注册或注销 **

接收推送通知与发送确认

在本项目中,我们使用 GCMIntentService 服务处理 GCM 推送通知。 该服务来自 com.google.android.gcm.GCMBaseIntentService。 当安卓客户端应用接收到来自应用服务器推送的消息,就会调用 GCMIntentService 类别的 onMessage(Context context, Intent intent) 方法,而无论应用是否处于运行状态。

package com.intcmobile.medipaging;

…
import com.google.android.gcm.GCMBaseIntentService;
import com.google.android.gcm.GCMRegistrar;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GCMIntentService extends GCMBaseIntentService {
	
	public GCMIntentService () {
		super(SENDER_ID);
	}
	
	@Override
	protected void onError(Context context, String errorMessage) {
		displayMessage(context, "There were error receiving notification. Error message: " + errorMessage);
	}

	@Override
	protected void onMessage(Context context, Intent intent) {
		String alertType           = intent.getStringExtra("alertType");
		String alertId             = intent.getStringExtra("alertId");
		String alertSubject        = intent.getStringExtra("subject");
		SimpleAlertInfo alert = new SimpleAlertInfo(alertType,   alertId, 								       alertSubject);
		alerts.add(alert);		
		displayMessage(context,alert.toString());
		
	}

	@Override
	protected void onRegistered(Context context, String registrationId) {
		// This method is called when the application successfully register with GCM service
		// register with 3rd party app server
		Utilities.serverRegister(context, registrationId);
	}

	@Override
	protected void onUnregistered(Context context, String registrationId) {
		if (GCMRegistrar.isRegisteredOnServer(context)) {
			Utilities.serverUnregister(context, registrationId);
		}
	}

}

代码示例 3 GCMIntentService 类别处理服务器推送通知**

我们将 GCMIntentService 添加至应用的 AndroidManifest.xml 文件中,如代码示例 4 所示。

        <!--
             Broadcast receiver that will receive intents from GCM
        	 service and hands them to the custom IntentService
        -->
        <receiver
            android:name="com.google.android.gcm.GCMBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>

                <!-- receives the actual GCM message -->
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <!-- receives the registration ID -->
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <!-- category -->
                <category android:name="com.intcmobile.medipaging" />
            </intent-filter>
        </receiver>

        <!--
      		Application specific subclass of GCMIntentService that will handle received message
      	  -->
        <service android:name="com.intcmobile.medipaging.GCMIntentService" />

代码示例 4 在 Android* 客户端应用的 AndroidManifest.xml 文件中定义 GCMIntentService **

使用 RESTful 服务

除了接收来自应用服务器推送的告警通知,安卓客户端应用还使用由应用服务器 API 通过 REST 界面提供的基于云的 web 服务。 其基本机制是安卓客户端应用向服务器发送异步 HTTP 请求。 如果发送成功,则应用服务器向客户端应用返回 JSON 串(代码示例 5)。

package com.intcmobile.medipaging;

…
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;
…


public class DisplayAlertsActivity extends Activity {
…

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_display_alerts);
…
		fetchDataForHomeScreen();
	}
	
	
	private void fetchDataForHomeScreen () {
		AsyncHttpClient  client     = new AsyncHttpClient();
		StdRequestParams parameters = new StdRequestParams();
		String url = SERVER_URL + "/getDataForHomeScreen";
		showProgress(true);
		client.post(url, parameters, new AsyncHttpResponseHandler() {
			@Override
			public void onSuccess(String response) {
				jsonDataForHomeScreen = response;
				Gson gson_obj = new GsonBuilder()
								.setPrettyPrinting()
								.create();
				medipagingDataForHomeScreen = gson_obj.fromJson(jsonDataForHomeScreen, GetDataResp.class);
				
				isHomeScreenDataAvailable = true;
				showProgress(false);
				displayAlerts();
			}
			@Override
		     public void onFailure(Throwable e, String response) {
		         // Response failed :(
					showProgress(false);
		     }
		     @Override
		     public void onFinish() {
					showProgress(false);
		     }
		 });
	}
…
 }

代码示例 5 Android* 客户端应用使用通过安卓异步超文本传输协议客户端提供的 RESTful web 服务**

在代码示例 5 中,我们看到安卓客户端应用使用安卓异步超文本传输协议客户端向应用服务器发送 HTTP 请求。 这发生在 UI 线程之外,因此,安卓应用仍可继续响应用户输入。 我们还看到,如果发送成功,应用服务器将返回 JSON 串。 安卓客户端应用使用 google-gson 解析 JSON 串并创建 GetDataResp 对象。 代码示例 6 是 GetDataResp 类别的定义。

package com.intcmobile.common;

import java.util.*;

/**
 * The return from Get Data as well as a bunch of the other calls.
 */
public class GetDataResp extends BaseReturn {
	/** Outstanding alerts. */
	public ArrayList<AlertInfo>	alertTable;
	
	/**
	 * Constructor.
	 */
	public GetDataResp() {
	}
	
	/**
	 * Constructor.
	 */
	public GetDataResp(int _returnCode, String _reason) {
		super(_returnCode, _reason);
	}

}

代码示例 6 GetDataResp 类别定义**

总结

本文讨论的案例研究阐述了如何使用谷歌云消息创建基于云的告警服务安卓客户端应用。 在本项目中,我们使用第三方异步 HTTP 客户端实现非阻塞 UI。 我们还使用 JSON 作为应用服务器和安卓客户端之间的数据交换格式。 从本案例研究可以看到,云服务安卓客户端应用十分易于实施。 本文讨论的方法还可用于其它基于云的服务客户端应用。

关于作者

Miao Wei 是英特尔软件及服务事业部的软件工程师。 他现在主要负责与英特尔® 凌动™ 处理器有关的项目。

其它相关文章与资源

英特尔云服务平台示例应用
使用 AppMobi* XDK 跨平台应用开发导论
JSON 和 XML
使用 RESTful 服务开发企业 Windows* 存储应用
通过 Web 员工改进 Metro HTML5-JavaScript* 应用的性能

如需深入了解面向安卓开发人员的英特尔工具,请访问:面向安卓的英特尔® 开发人员专区

本文档中的任何软件源代码均经过相关软件许可,其使用或复制均须遵守相关许可条款。
英特尔、英特尔标识和凌动是英特尔公司在美国和/或其它国家的商标。
版权所有© 2013 英特尔公司。 所有权利保留。
*其它名称或品牌可能是其它所有者的资产。

**本文中的示例源代码在发布之前签订了《英特尔示例源代码许可协议》(http://software.intel.com/zh-cn/articles/intel-sample-source-code-license-agreement/)

有关编译器优化的更完整信息,请参阅优化通知