构建Android Push Notification Service服务端及客户端

来源:互联网 发布:软件 地图 编辑:程序博客网 时间:2024/05/21 06:35

终于又开始上班了,只有在值班的时候,才是我比较清闲的时候,可以静下来做自己喜欢的事情,看自己喜欢的文章,写自己喜欢的博客。在Android架构部分,几个比较难啃的骨头里面,Android Push Notification Service算一个。我想今天来解释一下她的实现以及使用。

1 这个服务的必要性问题

在手机的使用过程中,我们知道,正睡觉呢,突然响起了短信声,打开一看,原来是移动/电信在提醒我们该上厕所了,或者天边冷了,多穿点衣服吧之类的话语。而在使用Android手机的时候,我们发现,如果有Gmail端,收到邮件的时候,会弹出一个提示,你有一条新邮件,并包含邮件的标题和相关信息。不知道你会不会好奇,这是如何实现的呢?我很好奇,所以便有了此文的写作动机。而对于QQ、安卓市场之类的软件,时不时的也弹出来这类信息,相信大家可以明白,这东西应该是有点用处的。比如我们开发一款应用,需要实时的提醒我们的安装用户一些事情,相信,你就会明白,这个服务是很有必要的,相信,在未来移动互联网、物联网占据大片江山的时候,也是很有必要的。

2 几个问题
好了,我们提出了这个东西的必要性,但是在做的时候,我们必须要考虑几个问题。
2.1 俺的电池不怎么抗用,可千万别太耗我的电量啊,这是哥最在意的啊。
2.2 除了花点流量,这玩意不要花我另外的钱,我可是月光族啊。
2.3 我着急要收到这个消息,别半小时后才把消息发给我,那样的话,会损失我的订单的。
2.4 必须要可靠哦,别用着用着,不好使了。

秉着以上的几个关键问题,我们开始了下一部分的探讨了。

3 几种可能的方案
我们来思考一下,要实现实时得到信息,有哪几种方法呢?
1 通过http/https或者其他协议,客户端以服务的方式,每隔10分钟或者10秒钟,向服务器请求一次,服务器判断这段时间是否有新消息,需要发给客户端,如果有就通过json或者xml方式发给客户端。
2 通过短信的方式,服务器端通过SMS的方式,将所需要的消息及时发送回来。
3 使用tcp长连接和心跳包的机制,实现数据定时推送。

4 采用的方案
从我的能力,我目前只能想到这么几种办法,下面我们来根据第二条里面的准则来分析上面提到的几种方案。
第一条通过http或者https的方式,向服务器每隔多长时间请求一次的方式,的确可以实现我们的功能,但是违反了我们的2.1和2.3原则。首先这种方式会耗电,当然你可以说时间设置长一点,但是这样又违背了2.3原则。所以这条一般是不会被采纳的。除非某些特殊应用。
第二条呢,2.1、2.3、2.4都符合,可是,违背了2.2,所以我们也不会考虑的。
第三条呢,好像全部符合,但是有一个小问题在里面,就是如果以Service的方式进行,由于Android系统的特殊性,在内存不够用的时候,会主动结束一些服务,这个服务包括了我们的定义服务,这么说,他违背了2.4。

但是,我们还是有办法的。

5 被采用方案的可实施方法
在Android 2.2以后,Google放出了C2DM【Android Cloud to Device Messaging Framework】服务,从服务的使用方法上,我们就可以明白他们采用了第三种方式。
随着他们推出这个服务后,很多公司开始基于这个服务做一些应用,如推送广告、推送定制信息等。如xtify和airpush等,国内也有一些企业加入了这种阵营,如单独提供服务的push-notification,当然QQ也有这样的服务存在。

在这种方案里面,有几个细节地方,需要来解释一下。
5.1 传输的时候使用什么协议?
5.2 传输的时候如何保证数据的安全性?
5.3 对于多平台,多用户的push如何保证惟一性?
5.4 服务器端的如何部署?

5.1的问题目前有几种方式,使用xmpp协议、IBM的MQTT、自定义协议。 目前有一些开源的项目中,大都采用第一种和第二种,当然,如果有特殊需求,可以采取自定义协议的。
5.2的问题可以对数据进行可逆加密。
5.3的问题,一般是将手机的ID传递到服务器端进行惟一性验证。
5.4的问题,服务器端可以自己使用任何语言开发,也可以使用Nginx + 脚本语言部署。

6 实例说明
本文的实例采用了mqtt的架构,完全按照tokudu兄的文章而来,并成功实现了。里面采取的不是IBM的Really Small Message Broker,而是采用的开源Mosquitto实现,

准备工作:

6.1 Android真机,本文为三星I809
6.2 Apache + Php环境
6.3 tokudu兄的Android源代码
6.4 tukudu兄的php代码
6.5 mosquitto的可执行程序。

步骤1:
下载mosquitto的可执行程序,我选择的是cygwin版本的,安装后,进入目录双击mosquitto.exe执行即可。

步骤2:下载tokudu兄的php代码,官方地址为:https://github.com/tokudu/PhpMQTTClient
我这里也提供下载:androidpushservice

主要代码为如下:

<?php
  
require('SAM/php_sam.php');
  
//create a new connection object
$conn new SAMConnection();
  
//start initialise the connection
$conn->connect(SAM_MQTT, array(SAM_HOST => '202.198.21.131',
                               SAM_PORT => 1883));
//create a new MQTT message with the output of the shell command as the body
$msgCpu new SAMMessage($_REQUEST['message']);
  
//send the message on the topic cpu
$conn->send('topic://'.$_REQUEST['target'], $msgCpu);
  
$conn->disconnect();         
  
echo 'MQTT Message to ' $_REQUEST['target'] . ' sent: ' $_REQUEST['message']; 
  
?>

将代码部署到php环境目录里面。输入地址:http://localhost/androidpushservice/

步骤三:下载tokudu兄的android代码:
地址:https://github.com/tokudu/AndroidPushNotificationsDemo
本文提供下载:
tokudu-AndroidPushNotificationsDemo-ea18b09

导入项目,编译,在真机上面使用打开即可。

这里有一个Device Target号码需要在php的界面里面输入。才可以发送成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*
* $Id$
*/


package com.tokudu.demo;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.os.Environment;

public class ConnectionLog
{
private String mPath;
private Writer mWriter;

private static final SimpleDateFormat TIMESTAMP_FMT =
new SimpleDateFormat("[HH:mm:ss] ");

public ConnectionLog()
throws IOException
{
File sdcard = Environment.getExternalStorageDirectory();
File logDir = new File(sdcard, "tokudu/log/");
if (!logDir.exists()) {
logDir.mkdirs();
// do not allow media scan
new File(logDir, ".nomedia").createNewFile();
}

open(logDir.getAbsolutePath() + "/push.log");
}

public ConnectionLog(String basePath)
throws IOException
{
open(basePath);
}

protected void open(String basePath)
throws IOException
{
File f = new File(basePath + "-" + getTodayString());
mPath = f.getAbsolutePath();
mWriter = new BufferedWriter(new FileWriter(mPath)2048);

println("Opened log.");
}

private static String getTodayString()
{
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-hhmmss");
return df.format(new Date());
}

public String getPath()
{
return mPath;
}

public void println(String message)
throws IOException
{
mWriter.write(TIMESTAMP_FMT.format(new Date()));
mWriter.write(message);
mWriter.write('\n');
mWriter.flush();
}

public void close()
throws IOException
{
mWriter.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.tokudu.demo;

import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.provider.Settings.Secure;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class PushActivity extends Activity {
private String mDeviceID;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

mDeviceID = Secure.getString(this.getContentResolver(), Secure.ANDROID_ID);
((TextView) findViewById(R.id.target_text)).setText(mDeviceID);

final Button startButton = ((Button) findViewById(R.id.start_button));
final Button stopButton = ((Button) findViewById(R.id.stop_button));
startButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Editor editor = getSharedPreferences(PushService.TAG, MODE_PRIVATE).edit();
editor.putString(PushService.PREF_DEVICE_ID, mDeviceID);
editor.commit();
PushService.actionStart(getApplicationContext());
startButton.setEnabled(false);
stopButton.setEnabled(true);
}
});
stopButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
PushService.actionStop(getApplicationContext());
startButton.setEnabled(true);
stopButton.setEnabled(false);
}
});
}

@Override
protected void onResume() {
super.onResume();

SharedPreferences p = getSharedPreferences(PushService.TAG, MODE_PRIVATE);
boolean started = p.getBoolean(PushService.PREF_STARTEDfalse);

((Button) findViewById(R.id.start_button)).setEnabled(!started);
((Button) findViewById(R.id.stop_button)).setEnabled(started);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
package com.tokudu.demo;

import java.io.IOException;

import com.ibm.mqtt.IMqttClient;
import com.ibm.mqtt.MqttClient;
import com.ibm.mqtt.MqttException;
import com.ibm.mqtt.MqttPersistence;
import com.ibm.mqtt.MqttPersistenceException;
import com.ibm.mqtt.MqttSimpleCallback;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.IBinder;
import android.util.Log;

/*
* PushService that does all of the work.
* Most of the logic is borrowed from KeepAliveService.
* http://code.google.com/p/android-random/source/browse/trunk/TestKeepAlive/src/org/devtcg/demo/keepalive/KeepAliveService.java?r=219
*/

public class PushService extends Service
{
// this is the log tag
public static final String TAG = "DemoPushService";

// the IP address, where your MQTT broker is running.
private static final String MQTT_HOST = "88.88.88.111";
// the port at which the broker is running.
private static int MQTT_BROKER_PORT_NUM = 1883;
// Let's not use the MQTT persistence.
private static MqttPersistence MQTT_PERSISTENCE = null;
// We don't need to remember any state between the connections, so we use a clean start.
private static boolean MQTT_CLEAN_START = true;
// Let's set the internal keep alive for MQTT to 15 mins. I haven't tested this value much. It could probably be increased.
private static short MQTT_KEEP_ALIVE = 60 * 15;
// Set quality of services to 0 (at most once delivery), since we don't want push notifications
// arrive more than once. However, this means that some messages might get lost (delivery is not guaranteed)
private static int[] MQTT_QUALITIES_OF_SERVICE = { 0 } ;
private static int MQTT_QUALITY_OF_SERVICE = 0;
// The broker should not retain any messages.
private static boolean MQTT_RETAINED_PUBLISH = false;

// MQTT client ID, which is given the broker. In this example, I also use this for the topic header.
// You can use this to run push notifications for multiple apps with one MQTT broker.
public static String MQTT_CLIENT_ID = "tokudu";

// These are the actions for the service (name are descriptive enough)
private static final String ACTION_START = MQTT_CLIENT_ID + ".START";
private static final String ACTION_STOP = MQTT_CLIENT_ID + ".STOP";
private static final String ACTION_KEEPALIVE = MQTT_CLIENT_ID + ".KEEP_ALIVE";
private static final String ACTION_RECONNECT = MQTT_CLIENT_ID + ".RECONNECT";

// Connection log for the push service. Good for debugging.
private ConnectionLog mLog;

// Connectivity manager to determining, when the phone loses connection
private ConnectivityManager mConnMan;
// Notification manager to displaying arrived push notifications
private NotificationManager mNotifMan;

// Whether or not the service has been started.
private boolean mStarted;

// This the application level keep-alive interval, that is used by the AlarmManager
// to keep the connection active, even when the device goes to sleep.
private static final long KEEP_ALIVE_INTERVAL = 1000 * 60 * 28;

// Retry intervals, when the connection is lost.
private static final long INITIAL_RETRY_INTERVAL = 1000 * 10;
private static final long MAXIMUM_RETRY_INTERVAL = 1000 * 60 * 30;

// Preferences instance
private SharedPreferences mPrefs;
// We store in the preferences, whether or not the service has been started
public static final String PREF_STARTED = "isStarted";
// We also store the deviceID (target)
public static final String PREF_DEVICE_ID = "deviceID";
// We store the last retry interval
public static final String PREF_RETRY = "retryInterval";

// Notification title
public static String NOTIF_TITLE = "Tokudu";
// Notification id
private static final int NOTIF_CONNECTED = 0;

// This is the instance of an MQTT connection.
private MQTTConnection mConnection;
private long mStartTime;

// Static method to start the service
public static void actionStart(Context ctx) {
Intent i = new Intent(ctx, PushService.class);
i.setAction(ACTION_START);
ctx.startService(i);
}

// Static method to stop the service
public static void actionStop(Context ctx) {
Intent i = new Intent(ctx, PushService.class);
i.setAction(ACTION_STOP);
ctx.startService(i);
}

// Static method to send a keep alive message
public static void actionPing(Context ctx) {
Intent i = new Intent(ctx, PushService.class);
i.setAction(ACTION_KEEPALIVE);
ctx.startService(i);
}

@Override
public void onCreate() {
super.onCreate();

log("Creating service");
mStartTime = System.currentTimeMillis();

try {
mLog = new ConnectionLog();
Log.i(TAG, "Opened log at " + mLog.getPath());
} catch (IOException e) {
Log.e(TAG, "Failed to open log", e);
}

// Get instances of preferences, connectivity manager and notification manager
mPrefs = getSharedPreferences(TAG, MODE_PRIVATE);
mConnMan = (ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE);
mNotifMan = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

/* If our process was reaped by the system for any reason we need
* to restore our state with merely a call to onCreate. We record
* the last "started" value and restore it here if necessary. */

handleCrashedService();
}

// This method does any necessary clean-up need in case the server has been destroyed by the system
// and then restarted
private void handleCrashedService() {
if (wasStarted() == true) {
log("Handling crashed service...");
// stop the keep alives
stopKeepAlives();

// Do a clean start
start();
}
}

@Override
public void onDestroy() {
log("Service destroyed (started=" + mStarted + ")");

// Stop the services, if it has been started
if (mStarted == true) {
stop();
}

try {
if (mLog != null)
mLog.close();
} catch (IOException e) {}
}

@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
log("Service started with intent=" + intent);

// Do an appropriate action based on the intent.
if (intent.getAction().equals(ACTION_STOP) == true) {
stop();
stopSelf();
} else if (intent.getAction().equals(ACTION_START) == true) {
start();
} else if (intent.getAction().equals(ACTION_KEEPALIVE) == true) {
keepAlive();
} else if (intent.getAction().equals(ACTION_RECONNECT) == true) {
if (isNetworkAvailable()) {
reconnectIfNecessary();
}
}
}

@Override
public IBinder onBind(Intent intent) {
return null;
}

// log helper function
private void log(String message) {
log(message, null);
}
private void log(String message, Throwable e) {
if (!= null) {
Log.e(TAG, message, e);

} else {
Log.i(TAG, message);
}

if (mLog != null)
{
try {
mLog.println(message);
} catch (IOException ex) {}
}
}

// Reads whether or not the service has been started from the preferences
private boolean wasStarted() {
return mPrefs.getBoolean(PREF_STARTED, false);
}

// Sets whether or not the services has been started in the preferences.
private void setStarted(boolean started) {
mPrefs.edit().putBoolean(PREF_STARTED, started).commit();
mStarted = started;
}

private synchronized void start() {
log("Starting service...");

// Do nothing, if the service is already running.
if (mStarted == true) {
Log.w(TAG, "Attempt to start connection that is already active");
return;
}

// Establish an MQTT connection
connect();

// Register a connectivity listener
registerReceiver(mConnectivityChanged, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}

private synchronized void stop() {
// Do nothing, if the service is not running.
if (mStarted == false) {
Log.w(TAG, "Attempt to stop connection not active.");
return;
}

// Save stopped state in the preferences
setStarted(false);

// Remove the connectivity receiver
unregisterReceiver(mConnectivityChanged);
// Any existing reconnect timers should be removed, since we explicitly stopping the service.
cancelReconnect();

// Destroy the MQTT connection if there is one
if (mConnection != null) {
mConnection.disconnect();
mConnection = null;
}
}

//
private synchronized void connect() {
log("Connecting...");
// fetch the device ID from the preferences.
String deviceID = mPrefs.getString(PREF_DEVICE_ID, null);
// Create a new connection only if the device id is not NULL
if (deviceID == null) {
log("Device ID not found.");
} else {
try {
mConnection = new MQTTConnection(MQTT_HOST, deviceID);
} catch (MqttException e) {
// Schedule a reconnect, if we failed to connect
log("MqttException: " + (e.getMessage() != null ? e.getMessage() : "NULL"));
if (isNetworkAvailable()) {
scheduleReconnect(mStartTime);
}
}
setStarted(true);
}
}

private synchronized void keepAlive() {
try {
// Send a keep alive, if there is a connection.
if (mStarted == true &amp;&amp; mConnection != null) {
mConnection.sendKeepAlive();
}
} catch (MqttException e) {
log("MqttException: " + (e.getMessage() != null? e.getMessage(): "NULL"), e);

mConnection.disconnect();
mConnection = null;
cancelReconnect();
}
}

// Schedule application level keep-alives using the AlarmManager
private void startKeepAlives() {
Intent i = new Intent();
i.setClass(this, PushService.class);
i.setAction(ACTION_KEEPALIVE);
PendingIntent pi = PendingIntent.getService(this0, i, 0);
AlarmManager alarmMgr = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + KEEP_ALIVE_INTERVAL,
KEEP_ALIVE_INTERVAL, pi);
}

// Remove all scheduled keep alives
private void stopKeepAlives() {
Intent i = new Intent();
i.setClass(this, PushService.class);
i.setAction(ACTION_KEEPALIVE);
PendingIntent pi = PendingIntent.getService(this0, i, 0);
AlarmManager alarmMgr = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmMgr.cancel(pi);
}

// We schedule a reconnect based on the starttime of the service
public void scheduleReconnect(long startTime) {
// the last keep-alive interval
long interval = mPrefs.getLong(PREF_RETRY, INITIAL_RETRY_INTERVAL);

// Calculate the elapsed time since the start
long now = System.currentTimeMillis();
long elapsed = now - startTime;

// Set an appropriate interval based on the elapsed time since start
if (elapsed &lt; interval) {
interval = Math.min(interval * 4, MAXIMUM_RETRY_INTERVAL);
} else {
interval = INITIAL_RETRY_INTERVAL;
}

log("Rescheduling connection in " + interval + "ms.");

// Save the new internval
mPrefs.edit().putLong(PREF_RETRY, interval).commit();

// Schedule a reconnect using the alarm manager.
Intent i = new Intent();
i.setClass(this, PushService.class);
i.setAction(ACTION_RECONNECT);
PendingIntent pi = PendingIntent.getService(this0, i, 0);
AlarmManager alarmMgr = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmMgr.set(AlarmManager.RTC_WAKEUP, now + interval, pi);
}

// Remove the scheduled reconnect
public void cancelReconnect() {
Intent i = new Intent();
i.setClass(this, PushService.class);
i.setAction(ACTION_RECONNECT);
PendingIntent pi = PendingIntent.getService(this0, i, 0);
AlarmManager alarmMgr = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmMgr.cancel(pi);
}

private synchronized void reconnectIfNecessary() {
if (mStarted == true &amp;&amp; mConnection == null) {
log("Reconnecting...");
connect();
}
}

// This receiver listeners for network changes and updates the MQTT connection
// accordingly
private BroadcastReceiver mConnectivityChanged = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Get network info
NetworkInfo info = (NetworkInfo)intent.getParcelableExtra (ConnectivityManager.EXTRA_NETWORK_INFO);

// Is there connectivity?
boolean hasConnectivity = (info != null &amp;&amp; info.isConnected()) ? true : false;

log("Connectivity changed: connected=" + hasConnectivity);

if (hasConnectivity) {
reconnectIfNecessary();
} else if (mConnection != null) {
// if there no connectivity, make sure MQTT connection is destroyed
mConnection.disconnect();
cancelReconnect();
mConnection = null;
}
}
};

// Display the topbar notification
private void showNotification(String text) {
Notification n = new Notification();

n.flags |= Notification.FLAG_SHOW_LIGHTS;
n.flags |= Notification.FLAG_AUTO_CANCEL;

n.defaults = Notification.DEFAULT_ALL;

n.icon = com.tokudu.demo.R.drawable.icon;
n.when = System.currentTimeMillis();

// Simply open the parent activity
PendingIntent pi = PendingIntent.getActivity(this0,
new Intent(this, PushActivity.class)0);

// Change the name of the notification here
n.setLatestEventInfo(this, NOTIF_TITLE, text, pi);

mNotifMan.notify(NOTIF_CONNECTED, n);
}

// Check if we are online
private boolean isNetworkAvailable() {
NetworkInfo info = mConnMan.getActiveNetworkInfo();
if (info == null) {
return false;
}
return info.isConnected();
}

// This inner class is a wrapper on top of MQTT client.
private class MQTTConnection implements MqttSimpleCallback {
IMqttClient mqttClient = null;

// Creates a new connection given the broker address and initial topic
public MQTTConnection(String brokerHostName, String initTopic) throws MqttException {
// Create connection spec
String mqttConnSpec = "tcp://" + brokerHostName + "@" + MQTT_BROKER_PORT_NUM;
// Create the client and connect
mqttClient = MqttClient.createMqttClient(mqttConnSpec, MQTT_PERSISTENCE);
String clientID = MQTT_CLIENT_ID + "/" + mPrefs.getString(PREF_DEVICE_ID, "");
mqttClient.connect(clientID, MQTT_CLEAN_START, MQTT_KEEP_ALIVE);

// register this client app has being able to receive messages
mqttClient.registerSimpleHandler(this);

// Subscribe to an initial topic, which is combination of client ID and device ID.
initTopic = MQTT_CLIENT_ID + "/" + initTopic;
subscribeToTopic(initTopic);

log("Connection established to " + brokerHostName + " on topic " + initTopic);

// Save start time
mStartTime = System.currentTimeMillis();
// Star the keep-alives
startKeepAlives();
}

// Disconnect
public void disconnect() {
try {
stopKeepAlives();
mqttClient.disconnect();
} catch (MqttPersistenceException e) {
log("MqttException" + (e.getMessage() != null? e.getMessage():" NULL"), e);
}
}
/*
* Send a request to the message broker to be sent messages published with
* the specified topic name. Wildcards are allowed.
*/

private void subscribeToTopic(String topicName) throws MqttException {

if ((mqttClient == null) || (mqttClient.isConnected() == false)) {
// quick sanity check - don't try and subscribe if we don't have
// a connection
log("Connection error" + "No connection");
} else {
String[] topics = { topicName };
mqttClient.subscribe(topics, MQTT_QUALITIES_OF_SERVICE);
}
}
/*
* Sends a message to the message broker, requesting that it be published
* to the specified topic.
*/

private void publishToTopic(String topicName, String message) throws MqttException {
if ((mqttClient == null) || (mqttClient.isConnected() == false)) {
// quick sanity check - don't try and publish if we don't have
// a connection
log("No connection to public to");
} else {
mqttClient.publish(topicName,
message.getBytes(),
MQTT_QUALITY_OF_SERVICE,
MQTT_RETAINED_PUBLISH);
}
}

/*
* Called if the application loses it's connection to the message broker.
*/

public void connectionLost() throws Exception {
log("Loss of connection" + "connection downed");
stopKeepAlives();
// null itself
mConnection = null;
if (isNetworkAvailable() == true) {
reconnectIfNecessary();
}
}

/*
* Called when we receive a message from the message broker.
*/

public void publishArrived(String topicName, byte[] payload, int qos, boolean retained) {
// Show a notification
String s = new String(payload);
showNotification(s);
log("Got message: " + s);
}

public void sendKeepAlive() throws MqttException {
log("Sending keep alive");
// publish to a keep-alive topic
publishToTopic(MQTT_CLIENT_ID + "/keepalive", mPrefs.getString(PREF_DEVICE_ID, ""));
}
}
}

7 代码下载

tokudu-AndroidPushNotificationsDemo-ea18b09

androidpushservice

MQTT实现:http://mosquitto.org/download/

8 尾声

我们目前已经完成了整个功能,如果要商业使用,还有一些问题摆在我们面前,如连接数的问题,大并发的问题,Apikey的问题等等。我们也会在tokudu兄的基础上,尝试完善一下,在这里对tokudu兄致以深深的敬意。

参考文章:

1 http://tokudu.com/2010/how-to-implement-push-notifications-for-android/【主参考】
2 http://sourceforge.net/projects/androidpn/
3 http://blog.mediarain.com/2011/03/simple-google-android-c2dm-tutorial-push-notifications-for-android/
4 http://mosquitto.org/download/
5 http://hi.baidu.com/zhu410289616/blog/item/b697f5328b0c405fad4b5f83.html
6 http://blog.csdn.net/joshua_yu/article/details/6563587
7 http://blog.mediarain.com/2011/03/simple-google-android-c2dm-tutorial-push-notifications-for-android/
8 http://www.codeproject.com/Articles/339162/Android-push-notification-implementation-using-ASP
9 http://www.eoeandroid.com/thread-97603-1-1.html
10 https://www14.software.ibm.com/webapp/iwm/web/reg/acceptLogin.do?source=AW-0U9&lang=en_US
11 http://mosquitto.org/
12 https://github.com/tokudu/AndroidPushNotificationsDemo
13 http://code.google.com/intl/zh-CN/android/c2dm/
14 http://stackoverflow.com/questions/1378671/push-notifications-in-android-platform
15 http://dalelane.co.uk/blog/?p=938
16 http://mqtt.org/
17 http://stackoverflow.com/questions/1243066/does-android-support-near-real-time-push-notification
18 http://www.chengyunfeng.com/2010/09/android-push-notification
19 http://www.airpush.com/

-END-

原创粉丝点击