android中涉及到将服务器中数据变化信息通知用户一般有两种办法,推送和轮询。
消息推送是服务端主动发消息给客户端,因为第一时间知道数据发生变化的是服务器自己,所以推送的优势是实时性高。但服务器主动推送需要单独开发一套能让客户端持久连接的服务端程序,不过现在已经有很多开源的代码实现了基于xmmp协议的推送方案,而且还可以使用谷歌的推送方案。但有些情况下并不需要服务端主动推送,而是在一定的时间间隔内客户端主动发起查询。
譬如有这样一个app,实时性要求不高,每天只要能获取10次最新数据就能满足要求了,这种情况显然轮询更适合一些,推送显得太浪费,而且更耗电。
但是不管是轮询还是推送都需要无论应用程序是否正在运行或者关闭的情况下能给用户发送通知,因此都需要用到service。我们有两种方案来使用service达到此目的:
方案一:service +Thread
在service中开启一个带有while循环的线程,使其不断的从服务器查询数据(一定时间间隔内),当发现有需要通知用户的情况下发送notification。这种方案的代码大致是:
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
|
import
org.apache.http.Header; import
org.json.JSONObject; import
android.app.Notification; import
android.app.NotificationManager; import
android.app.PendingIntent; import
android.app.Service; import
android.content.Context; import
android.content.Intent; import
android.os.IBinder; import
android.widget.Toast; import
com.loopj.android.http.AsyncHttpClient; import
com.loopj.android.http.AsyncHttpResponseHandler; /** * *
短信推送服务类,在后台长期运行,每个一段时间就向服务器发送一次请求 * *
@author jerry * */ public
class PushSmsService extends Service { private
MyThread myThread; private
NotificationManager manager; private
Notification notification; private
PendingIntent pi; private
AsyncHttpClient client; private
boolean flag = true ; @Override public
IBinder onBind(Intent intent) { //
TODO Auto-generated method stub return null ; } @Override public
void onCreate() { System.out.println( "oncreate()" ); this .client
= new AsyncHttpClient(); this .myThread
= new MyThread(); this .myThread.start(); super .onCreate(); } @Override public
void onDestroy() { this .flag
= false ; super .onDestroy(); } private
void notification(String content, String number, String date) { //
获取系统的通知管理器 manager
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notification
= new Notification(R.drawable.ic_menu_compose,
content, System.currentTimeMillis()); notification.defaults
= Notification.DEFAULT_ALL; //
使用默认设置,比如铃声、震动、闪灯 notification.flags
= Notification.FLAG_AUTO_CANCEL; //
但用户点击消息后,消息自动在通知栏自动消失 notification.flags
|= Notification.FLAG_NO_CLEAR; //
点击通知栏的删除,消息不会依然不会被删除 Intent
intent = new Intent(getApplicationContext(), ContentActivity.class); intent.putExtra( "content" ,
content); intent.putExtra( "number" ,
number); intent.putExtra( "date" ,
date); pi
= PendingIntent.getActivity(getApplicationContext(), 0, intent, 0); notification.setLatestEventInfo(getApplicationContext(),
number + "发来短信" ,
content, pi); //
将消息推送到状态栏 manager.notify(0,
notification); } private
class MyThread extends Thread { @Override public
void run() { String
url = "你请求的网络地址" ; while (flag)
{ System.out.println( "发送请求" ); try { //
每个10秒向服务器发送一次请求 Thread.sleep(10000); } catch (InterruptedException
e) { e.printStackTrace(); } //
采用get方式向服务器发送请求 client.get(url, new AsyncHttpResponseHandler()
{ @Override public
void onSuccess(int statusCode, Header[] headers, byte[]
responseBody) { try { JSONObject
result = new JSONObject( new String( responseBody, "utf-8" )); int
state = result.getInt( "state" ); //
假设偶数为未读消息 if (state
% 2 == 0) { String
content = result.getString( "content" ); String
date = result.getString( "date" ); String
number = result.getString( "number" ); notification(content,
number, date); } } catch (Exception
e) { e.printStackTrace(); } } @Override public
void onFailure(int statusCode, Header[] headers, byte[]
responseBody, Throwable error) { Toast.makeText(getApplicationContext(), "数据请求失败" ,
0) .show(); } }); } } } } |
其中AsyncHttpClient
为网络异步请求的开源库,可以很方便的实现异步网络请求。
这种方案存在的不足有很多,一是应用长期有一个后台程序运行,如果是一个喜欢用手机安全的用户,这个service很可能被他杀死;二是虽然service可以运行在后台,但在手机休眠的情况下线程好像是被挂起的,这里涉及一个Android系统锁的机制,即系统在检测到一段时间没有活跃以后,会关闭一些不必要的服务来减少资源和电量消耗,这跟很多应用表现出来的都不一样,不符合用户习惯。因此我们还是选择第二种方案。
方案二:service+AlarmManager+Thread
虽然alarm的意思是闹钟,而且在原生android自带的闹钟应用中AlarmManager也确实非常重要,但并不代表AlarmManager只是用来做闹钟应用的,作为一个一种系统级别的提示服务,肯定应该有着非常重要的地位,实际上android中很多东西都可以利用AlarmManager来实现。
AlarmManager在特定的时刻为我们广播一个指定的Intent。简单的说就是我们设定一个时间,然后在该时间到来时,AlarmManager为我们广播一个我们设定的Intent。这个intent可以指向一个activity,也可以指向一个service。
下面就是使用alarm定时调用service实现轮询的实现方法:
一、新建轮询工具类PollingUtils.java
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
|
public
class PollingUtils { //开启轮询服务 public
static void startPollingService(Context context, int seconds, Class<?> cls,String action) { //获取AlarmManager系统服务 AlarmManager
manager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); //包装需要执行Service的Intent Intent
intent = new Intent(context,
cls); intent.setAction(action); PendingIntent
pendingIntent = PendingIntent.getService(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT); //触发服务的起始时间 long
triggerAtTime = SystemClock.elapsedRealtime(); //使用AlarmManger的setRepeating方法设置定期执行的时间间隔(seconds秒)和需要执行的Service manager.setRepeating(AlarmManager.ELAPSED_REALTIME,
triggerAtTime, seconds
* 1000, pendingIntent); } //停止轮询服务 public
static void stopPollingService(Context context, Class<?> cls,String action) { AlarmManager
manager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); Intent
intent = new Intent(context,
cls); intent.setAction(action); PendingIntent
pendingIntent = PendingIntent.getService(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT); //取消正在执行的服务 manager.cancel(pendingIntent); } } |
二、构建轮询任务执行PollingService.java
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
|
public
class PollingService extends Service { public
static final String ACTION = "com.ryantang.service.PollingService" ; private
Notification mNotification; private
NotificationManager mManager; @Override public
IBinder onBind(Intent intent) { return null ; } @Override public
void onCreate() { initNotifiManager(); } @Override public
void onStart(Intent intent, int startId) { new PollingThread().start(); } //初始化通知栏配置 private
void initNotifiManager() { mManager
= (NotificationManager) getSystemService(NOTIFICATION_SERVICE); int
icon = R.drawable.ic_launcher; mNotification
= new Notification(); mNotification.icon
= icon; mNotification.tickerText
= "New
Message" ; mNotification.defaults
|= Notification.DEFAULT_SOUND; mNotification.flags
= Notification.FLAG_AUTO_CANCEL; } //弹出Notification private
void showNotification() { mNotification.when
= System.currentTimeMillis(); //Navigator
to the new activity when click the notification title Intent
i = new Intent( this ,
MessageActivity.class); PendingIntent
pendingIntent = PendingIntent.getActivity( this ,
0, i, Intent.FLAG_ACTIVITY_NEW_TASK); mNotification.setLatestEventInfo( this , getResources().getString(R.string.app_name), "You
have new message!" ,
pendingIntent); mManager.notify(0,
mNotification); } /** *
Polling thread *
模拟向Server轮询的异步线程 *
@Author Ryan *
@Create 2013-7-13 上午10:18:34 */ int
count = 0; class
PollingThread extends Thread { @Override public
void run() { System.out.println( "Polling..." ); count
++; //当计数能被5整除时弹出通知 if (count
% 5 == 0) { showNotification(); System.out.println( "New
message!" ); } } } @Override public
void onDestroy() { super .onDestroy(); System.out.println( "Service:onDestroy" ); } } |
三、在MainActivity.java中开启和停止PollingService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
class MainActivity extends Activity { @Override protected
void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Start
polling service System.out.println( "Start
polling service..." ); PollingUtils.startPollingService( this ,
5, PollingService.class, PollingService.ACTION); } @Override protected
void onDestroy() { super .onDestroy(); //Stop
polling service System.out.println( "Stop
polling service..." ); PollingUtils.stopPollingService( this ,
PollingService.class, PollingService.ACTION); } } |
可以看出第二种方案和第一种方案的本质区别是实现定时查询的方式不同,一种是利用系统服务,一种是自己通过while循环。显然使用系统服务具有更高的稳定性,而且恰好解决了休眠状态下轮询中断的问题,因为AlarmManager是始终运行者的。