BroadcastReceiver(广播接收器)是Android中的四大组件之一。
下面是Android Doc中关于BroadcastReceiver的概述:①广播接收器是一个专注于接收广播通知信息,并做出对应处理的组件。很多广播是源自于系统代码的──比如,通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以进行广播──比如说,通知其它应用程序一些数据下载完成并处于可用状态。
②应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。所有的接收器均继承自BroadcastReceiver基类。
③广播接收器没有用户界面。然而,它们可以启动一个activity来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
Android中的广播事件有两种,一种就是系统广播事件,比如:ACTION_BOOT_COMPLETED(系统启动完成后触发),ACTION_TIME_CHANGED(系统时间改变时触发),ACTION_BATTERY_LOW(电量低时触发)等等。另外一种是我们自定义的广播事件。
广播事件的流程
①注册广播事件:注册方式有两种,一种是静态注册,就是在AndroidManifest.xml文件中定义,注册的广播接收器必须要继承BroadcastReceiver;另一种是动态注册,是在程序中使用Context.registerReceiver注册,注册的广播接收器相当于一个匿名类。两种方式都需要IntentFIlter。
②发送广播事件:通过Context.sendBroadcast来发送,由Intent来传递注册时用到的Action。
③接收广播事件:当发送的广播被接收器监听到后,会调用它的onReceive()方法,并将包含消息的Intent对象传给它。onReceive中代码的执行时间不要超过5s,否则Android会弹出超时dialog。
下面我通过代码演示自定义广播事件和系统广播事件的使用。完整代码下载地址:android_broadcastreceiver.rar
Step1:在MainActivity的onStart方法中注册广播事件。静态注册方式是在AndroidManifest.xml文件中。
Step2: 点击相应按钮后会触发相应的方式来发送广播消息。
[java] view plain copy
1. /**
2. * MainActivity
3. * @author zuolongsnail
4. *
5. */
6. public class MainActivity extends Activity {
7. private Button sendStaticBtn;
8. private Button sendDynamicBtn;
9. private Button sendSystemBtn;
10. private static final String STATICACTION = "com.byread.static";
11. private static final String DYNAMICACTION = "com.byread.dynamic";
12. // USB设备连接
13. private static final String SYSTEMACTION = Intent.ACTION_POWER_CONNECTED;
14. @Override
15. public void onCreate(Bundle savedInstanceState) {
16. super.onCreate(savedInstanceState);
17. setContentView(R.layout.main);
18. sendStaticBtn = (Button) findViewById(R.id.send_static);
19. sendDynamicBtn = (Button) findViewById(R.id.send_dynamic);
20. sendSystemBtn = (Button) findViewById(R.id.send_system);
21. new MyOnClickListener());
22. new MyOnClickListener());
23. new MyOnClickListener());
24. }
25. class MyOnClickListener implements OnClickListener{
26. @Override
27. public void onClick(View v) {
28. // 发送自定义静态注册广播消息
29. if(v.getId() == R.id.send_static){
30. "MainActivity", "发送自定义静态注册广播消息");
31. new Intent();
32. intent.setAction(STATICACTION);
33. "msg", "接收静态注册广播成功!");
34. sendBroadcast(intent);
35. }
36. // 发送自定义动态注册广播消息
37. else if(v.getId() == R.id.send_dynamic){
38. "MainActivity", "发送自定义动态注册广播消息");
39. new Intent();
40. intent.setAction(DYNAMICACTION);
41. "msg", "接收动态注册广播成功!");
42. sendBroadcast(intent);
43. }
44. // 发送系统动态注册广播消息。当手机连接充电设备时会由系统自己发送广播消息。
45. else if(v.getId() == R.id.send_system){
46. "MainActivity", "发送系统动态注册广播消息");
47. new Intent();
48. intent.setAction(SYSTEMACTION);
49. "msg", "正在充电。。。。");
50. }
51. }
52. }
53. @Override
54. protected void onStart() {
55. super.onStart();
56. "MainActivity", "注册广播事件");
57. // 注册自定义动态广播消息
58. new IntentFilter();
59. filter_dynamic.addAction(DYNAMICACTION);
60. registerReceiver(dynamicReceiver, filter_dynamic);
61. // 注册系统动态广播消息
62. new IntentFilter();
63. filter_system.addAction(SYSTEMACTION);
64. registerReceiver(systemReceiver, filter_system);
65. }
66. private BroadcastReceiver dynamicReceiver = new BroadcastReceiver() {
67.
68. @Override
69. public void onReceive(Context context, Intent intent) {
70. "MainActivity", "接收自定义动态注册广播消息");
71. if(intent.getAction().equals(DYNAMICACTION)){
72. "msg");
73. Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
74. }
75. }
76. };
77. private BroadcastReceiver systemReceiver = new BroadcastReceiver() {
78.
79. @Override
80. public void onReceive(Context context, Intent intent) {
81. "MainActivity", "接收系统动态注册广播消息");
82. if(intent.getAction().equals(SYSTEMACTION)){
83. "msg");
84. Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
85. }
86. }
87. };
88. }
Step3:接收广播消息。以下为两个静态注册的广播接收器。
[java] view plain copy
1. /**
2. * 自定义静态注册广播消息接收器
3. * @author zuolongsnail
4. *
5. */
6. public class StaticReceiver extends BroadcastReceiver {
7.
8. @Override
9. public void onReceive(Context context, Intent intent) {
10. "msg");
11. Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
12. }
13. }
[java] view plain copy
1. /**
2. * 系统静态注册广播消息接收器
3. *
4. * @author zuolongsnail
5. *
6. */
7. public class SystemReceiver extends BroadcastReceiver {
8.
9. @Override
10. public void onReceive(Context context, Intent intent) {
11. if (intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) {
12. "SystemReceiver", "电量低提示");
13. "您的手机电量偏低,请及时充电", Toast.LENGTH_SHORT).show();
14. }
15. }
16. }
下面是AndroidManifest.xml文件:
[xhtml] view plain copy
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3. package="com.byread" android:versionCode="1" android:versionName="1.0">
4. <application android:icon="@drawable/icon" android:label="@string/app_name">
5. <activity android:name=".MainActivity" android:label="@string/app_name">
6. <intent-filter>
7. <action android:name="android.intent.action.MAIN" />
8. <category android:name="android.intent.category.LAUNCHER" />
9. </intent-filter>
10. </activity>
11. <!-- 注册自定义静态广播接收器 -->
12. <receiver android:name=".StaticReceiver">
13. <intent-filter>
14. <action android:name="com.byread.static" />
15. </intent-filter>
16. </receiver>
17. <!-- 注册系统静态广播接收器 -->
18. <receiver android:name=".SystemReceiver">
19. <intent-filter>
20. <action android:name="android.intent.action.BATTERY_LOW" />
21. </intent-filter>
22. </receiver>
23. </application>
24. </manifest>
界面布局文件main.xml
[xhtml] view plain copy
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:orientation="vertical" android:layout_width="fill_parent"
4. android:layout_height="fill_parent">
5. <TextView android:layout_width="fill_parent"
6. android:layout_height="wrap_content" android:text="@string/hello" />
7. <Button android:id="@+id/send_static" android:layout_width="wrap_content"
8. android:layout_height="wrap_content" android:text="发送自定义静态注册广播" />
9. <Button android:id="@+id/send_dynamic" android:layout_width="wrap_content"
10. android:layout_height="wrap_content" android:text="发送自定义动态注册广播" />
11. <Button android:id="@+id/send_system" android:layout_width="wrap_content"
12. android:layout_height="wrap_content" android:text="发送系统动态注册广播" />
13. </LinearLayout>
讲解结束,不过有一点我自己也没弄清楚,这个系统广播事件如果我在程序中sendBroadcast的话,那就是自定义广播了。如果不写的话,那是不是系统自己来发送对应Action广播呢?有知道的同学请告诉我一下,再此先谢过。
运行界面:
在 Android 中使用 Activity, Service, Broadcast, BroadcastReceiver
活动(Activity) - 用于表现功能
服务(Service) - 相当于后台运行的 Activity
广播(Broadcast) - 用于发送广播
广播接收器(BroadcastReceiver) - 用于接收广播
Intent - 用于连接以上各个组件,并在其间传递消息
==========================================================================
BroadcastReceiver:
在Android中,Broadcast是一种广泛运用的在应用程序之间传输信息的机制。而BroadcastReceiver是对发送出来的 Broadcast进行过滤接受并响应的一类组件。下面将详细的阐述如何发送Broadcast和使用BroadcastReceiver过滤接收的过程:
首先在需要发送信息的地方,把要发送的信息和用于过滤的信息(如Action、Category)装入一个Intent对象,然后通过调用 Context.sendBroadcast()、sendOrderBroadcast()或sendStickyBroadcast()方法,把 Intent对象以广播方式发送出去。
当Intent发送以后,所有已经注册的BroadcastReceiver会检查注册时的IntentFilter是否与发送的Intent相匹配,若匹配则就会调用BroadcastReceiver的onReceive()方法。所以当我们定义一个BroadcastReceiver的时候,都需要实现onReceive()方法。
注册BroadcastReceiver有两种方式:
一种方式是,静态的在AndroidManifest.xml中用<receiver>标签生命注册,并在标签内用<intent- filter>标签设置过滤器。
另一种方式是,动态的在代码中先定义并设置好一个 IntentFilter 对象,然后在需要注册的地方调Context.registerReceiver()方法,如果取消时就调用 Context.unregisterReceiver()方法。如果用动态方式注册的BroadcastReceiver的Context对象被销毁时,BroadcastReceiver也就自动取消注册了。(特别注意,有些可能需要后台监听的,如短信消息)
另外,若在使用sendBroadcast()的方法是指定了接收权限,则只有在AndroidManifest.xml中用<uses- permission>标签声明了拥有此权限的BroascastReceiver才会有可能接收到发送来的Broadcast。同样,若在注册BroadcastReceiver时指定了可接收的Broadcast的权限,则只有在包内的AndroidManifest.xml中 用<uses-permission>标签声明了,拥有此权限的Context对象所发送的Broadcast才能被这个 BroadcastReceiver所接收。
1.静态注册BroadcastReceiver:
静态注册比动态注册麻烦点,先新建一个类继承BroadcastReceiver,如:
clsReceiver2.java
package
com.testBroadcastReceiver;
import
android.content.BroadcastReceiver;
import
android.content.Context;
import
android.content.Intent;
import
android.widget.Toast;
/*
* 接收静态注册广播的BroadcastReceiver,
* step1:要到AndroidManifest.xml这里注册消息
* <receiver android:name="clsReceiver2">
<intent-filter>
<action
android:name="com.testBroadcastReceiver.Internal_2"/>
</intent-filter>
</receiver>
step2:定义消息的字符串
step3:通过Intent传递消息来驱使BroadcastReceiver触发
*/
public
class
clsReceiver2
extends
BroadcastReceiver{
@Override
public
void
onReceive(Context context, Intent intent) {
String action
=
intent.getAction();
Toast.makeText(context,
"
静态:
"
+
action,
1000
).show();
}
}
然后到AndroidManifest.xml 添加receive标签
<
receiver android:name
=
"
clsReceiver2
"
>
<
intent
-
filter
>
<
action
android:name
=
"
com.testBroadcastReceiver.Internal_2
"
/>
</
intent
-
filter
>
</
receiver
>
第一个name是类名,即你的继承BroadcastReceiver的类的名字,里面实现了BroadcastReceive的onReceive()方法,来处理你接到消息的动作。
第二个name是action的名称,即你要监听的消息名字(其它消息都会被过滤不监听)。
2.动态注册BroadcastReceiver:
主要代码部分为:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(String); //为BroadcastReceiver指定action,即要监听的消息名字。
registerReceiver(MyBroadcastReceiver,intentFilter); //注册监听
unregisterReceiver(MyBroadcastReceiver); //取消监听
(一般:在onStart中注册,onStop中取消unregisterReceiver)
private class MyBroadcastReceive extends BroadcastReceiver
{
{
if(intent.ACTION_BATTERY_CHANGED.equals(action)) //判断是否接到电池变换消息
//处理内容
}
}
========================================================================
Broadcast:
装入一个Intent对象,然后通过调用 Context.sendBroadcast()、sendOrderBroadcast()或sendStickyBroadcast()方法,把 Intent对象以广播方式发送出去。
例如:
Intent intent = new Intent(INTENAL_ACTION_3); intent.putExtra( " Name " , " hellogv " ); intent.putExtra( " Blog " , " " ); sendBroadcast(intent); // 传递过去
在Android系统中,广播(Broadcast)是在组件之间传播数据(Intent)的一种机制;这些组件甚至是可以位于不同的进程中,这样它就像Binder机制一样,起到进程间通信的作用;本文通过一个简单的例子来学习Android系统的广播机制,为后续分析广播机制的源代码作准备。
在Android系统中,为什么需要广播机制呢?广播机制,本质上它就是一种组件间的通信方式,如果是两个组件位于不同的进程当中,那么可以用Binder机制来实现,如果两个组件是在同一个进程中,那么它们之间可以用来通信的方式就更多了,这样看来,广播机制似乎是多余的。然而,广播机制却是不可替代的,它和Binder机制不一样的地方在于,广播的发送者和接收者事先是不需要知道对方的存在的,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起,这样系统就具有高度的可扩展性,容易与其它系统进行集成。
在软件工程中,是非常强调模块之间的高内聚低耦合性的,不然的话,随着系统越来越庞大,就会面临着越来越难维护的风险,最后导致整个项目的失败。Android应用程序的组织方式,可以说是把这种高内聚低耦合性的思想贯彻得非常透彻,在任何一个Activity中,都可以使用一个简单的Intent,通过startActivity或者startService,就可以把另外一个Activity或者Service启动起来为它服务,而且它根本上不依赖这个Activity或者Service的实现,只需要知道它的字符串形式的名字即可,而广播机制更绝,它连接收者的名字都不需要知道。
不过话又说回来,广播机制在Android系统中,也不算是什么创新的东西。如果读者了解J2EE或者COM,就会知道,在J2EE中,提供了消息驱动Bean(Message-Driven Bean),用来实现应用程序各个组件之间的消息传递;而在COM中,提供了连接点(Connection Point)的概念,也是用来在应用程序各个组间间进行消息传递。无论是J2EE中的消息驱动Bean,还是COM中的连接点,或者Android系统的广播机制,它们的实现机理都是消息发布/订阅模式的事件驱动模型,消息的生产者发布事件,而使用者订阅感兴趣的事件。
废话说了一大堆,现在开始进入主题了,和前面的文章一样,我们通过具体的例子来介绍Android系统的广播机制。在这个例子中,有一个Service,它在另外一个线程中实现了一个计数器服务,每隔一秒钟就自动加1,然后将结果不断地反馈给应用程序中的界面线程,而界面线程中的Activity在得到这个反馈后,就会把结果显示在界面上。为什么要把计数器服务放在另外一个线程中进行呢?我们可以把这个计数器服务想象成是一个耗时的计算型逻辑,如果放在界面线程中去实现,那么势必就会导致应用程序不能响应界面事件,最后导致应用程序产生ANR(Application Not Responding)问题。计数器线程为了把加1后的数字源源不断地反馈给界面线程,这时候就可以考虑使用广播机制了。
首先在Android源代码工程中创建一个Android应用程序工程,名字就称为Broadcast吧。关于如何获得Android源代码工程,请参考在Ubuntu上下载、编译和安装Android最新源代码一文;关于如何在Android源代码工程中创建应用程序工程,请参考在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文。这个应用程序工程定义了一个名为shy.luo.broadcast的package,这个例子的源代码主要就是实现在这里了。下面,将会逐一介绍这个package里面的文件。
首先,我们在src/shy/luo/broadcast/ICounterService.java文件中定义计数器的服务接口:
[java] view plain copy
1. package shy.luo.broadcast;
2.
3. public interface ICounterService {
4. public void startCounter(int initVal);
5. public void stopCounter();
6. }
这个接口很简单,它只有两个成员函数,分别用来启动和停止计数器;启动计数时,还可以指定计数器的初始值。
接着,我们来看一个应用程序的默认Activity的实现,在src/shy/luo/broadcast/MainActivity.java文件中:
[java] view plain copy
1. package shy.luo.broadcast;
2.
3. import android.app.Activity;
4. import android.content.BroadcastReceiver;
5. import android.content.ComponentName;
6. import android.content.Context;
7. import android.content.Intent;
8. import android.content.IntentFilter;
9. import android.content.ServiceConnection;
10. import android.os.Bundle;
11. import android.os.IBinder;
12. import android.util.Log;
13. import android.view.View;
14. import android.view.View.OnClickListener;
15. import android.widget.Button;
16. import android.widget.TextView;
17.
18. public class MainActivity extends Activity implements OnClickListener {
19. private final static String LOG_TAG = "shy.luo.broadcast.MainActivity";
20.
21. private Button startButton = null;
22. private Button stopButton = null;
23. private TextView counterText = null;
24.
25. private ICounterService counterService = null;
26.
27. @Override
28. public void onCreate(Bundle savedInstanceState) {
29. super.onCreate(savedInstanceState);
30. setContentView(R.layout.main);
31.
32. startButton = (Button)findViewById(R.id.button_start);
33. stopButton = (Button)findViewById(R.id.button_stop);
34. counterText = (TextView)findViewById(R.id.textview_counter);
35.
36. this);
37. this);
38.
39. true);
40. false);
41.
42. new Intent(MainActivity.this, CounterService.class);
43. bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
44.
45. "Main Activity Created.");
46. }
47.
48. @Override
49. public void onResume() {
50. super.onResume();
51.
52. new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
53. registerReceiver(counterActionReceiver, counterActionFilter);
54. }
55.
56. @Override
57. public void onPause() {
58. super.onPause();
59. unregisterReceiver(counterActionReceiver);
60. }
61.
62. @Override
63. public void onDestroy() {
64. super.onDestroy();
65. unbindService(serviceConnection);
66. }
67.
68. @Override
69. public void onClick(View v) {
70. if(v.equals(startButton)) {
71. if(counterService != null) {
72. 0);
73.
74. false);
75. true);
76. }
77. else if(v.equals(stopButton)) {
78. if(counterService != null) {
79. counterService.stopCounter();
80.
81. true);
82. false);
83. }
84. }
85. }
86.
87. private BroadcastReceiver counterActionReceiver = new BroadcastReceiver(){
88. public void onReceive(Context context, Intent intent) {
89. int counter = intent.getIntExtra(CounterService.COUNTER_VALUE, 0);
90. String text = String.valueOf(counter);
91. counterText.setText(text);
92.
93. "Receive counter event");
94. }
95. };
96.
97. private ServiceConnection serviceConnection = new ServiceConnection() {
98. public void onServiceConnected(ComponentName className, IBinder service) {
99. counterService = ((CounterService.CounterBinder)service).getService();
100.
101. "Counter Service Connected");
102. }
103. public void onServiceDisconnected(ComponentName className) {
104. null;
105. "Counter Service Disconnected");
106. }
107. };
108. }
MainActivity的实现也很简单,它在创建(onCreate)的时候,会调用bindService函数来把计数器服务(CounterService)启动起来,它的第二个参数serviceConnection是一个ServiceConnection实例。计数器服务启动起来后,系统会调用这个实例的onServiceConnected函数将一个Binder对象传回来,通过调用这个Binder对象的getService函数,就可以获得计数器服务接口。这里,把这个计数器服务接口保存在MainActivity的counterService成员变量中。同样,当我们调用unbindService停止计数器服务时,系统会调用这个实例的onServiceDisconnected函数告诉MainActivity,它与计数器服务的连接断开了。
注意,这里通过调用bindService函数来启动Service时,这个Service与启动它的Activity是位于同一个进程中,它不像我们在前面一篇文章Android系统在新进程中启动自定义服务过程(startService)的原理分析中所描述那样在新的进程中启动服务,后面我们再写一篇文章来分析bindService启动服务的过程。
在MainActivity的onResume函数中,通过调用registerReceiver函数注册了一个广播接收器counterActionReceiver,它是一个BroadcastReceiver实例,并且指定了这个广播接收器只对CounterService.BROADCAST_COUNTER_ACTION类型的广播感兴趣。当CounterService发出一个CounterService.BROADCAST_COUNTER_ACTION类型的广播时,系统就会把这个广播发送到counterActionReceiver实例的onReceiver函数中去。在onReceive函数中,从参数intent中取出计数器当前的值,显示在界面上。
MainActivity界面上有两个按钮,分别是Start Counter和Stop Counter按钮,点击前者开始计数,而点击后者则停止计数。
计数器服务CounterService实现在src/shy/luo/broadcast/CounterService.java文件中:
[java] view plain copy
1. package shy.luo.broadcast;
2.
3. import android.app.Service;
4. import android.content.Intent;
5. import android.os.AsyncTask;
6. import android.os.Binder;
7. import android.os.IBinder;
8. import android.util.Log;
9.
10. public class CounterService extends Service implements ICounterService {
11. private final static String LOG_TAG = "shy.luo.broadcast.CounterService";
12.
13. public final static String BROADCAST_COUNTER_ACTION = "shy.luo.broadcast.COUNTER_ACTION";
14. public final static String COUNTER_VALUE = "shy.luo.broadcast.counter.value";
15.
16. private boolean stop = false;
17.
18. private final IBinder binder = new CounterBinder();
19.
20. public class CounterBinder extends Binder {
21. public CounterService getService() {
22. return CounterService.this;
23. }
24. }
25.
26. @Override
27. public IBinder onBind(Intent intent) {
28. return binder;
29. }
30.
31. @Override
32. public void onCreate() {
33. super.onCreate();
34.
35. "Counter Service Created.");
36. }
37.
38. @Override
39. public void onDestroy() {
40. "Counter Service Destroyed.");
41. }
42.
43. public void startCounter(int initVal) {
44. new AsyncTask<Integer, Integer, Integer>() {
45. @Override
46. protected Integer doInBackground(Integer... vals) {
47. 0];
48.
49. false;
50. while(!stop) {
51. publishProgress(initCounter);
52.
53. try {
54. 1000);
55. catch (InterruptedException e) {
56. e.printStackTrace();
57. }
58.
59. initCounter++;
60. }
61.
62. return initCounter;
63. }
64.
65. @Override
66. protected void onProgressUpdate(Integer... values) {
67. super.onProgressUpdate(values);
68.
69. int counter = values[0];
70.
71. new Intent(BROADCAST_COUNTER_ACTION);
72. intent.putExtra(COUNTER_VALUE, counter);
73.
74. sendBroadcast(intent);
75. }
76.
77. @Override
78. protected void onPostExecute(Integer val) {
79. int counter = val;
80.
81. new Intent(BROADCAST_COUNTER_ACTION);
82. intent.putExtra(COUNTER_VALUE, counter);
83.
84. sendBroadcast(intent);
85. }
86.
87. };
88.
89. 0);
90. }
91.
92. public void stopCounter() {
93. true;
94. }
95. }
这个计数器服务实现了ICounterService接口。当这个服务被binderService函数启动时,系统会调用它的onBind函数,这个函数返回一个Binder对象给系统。上面我们说到,当MainActivity调用bindService函数来启动计数器服务器时,系统会调用MainActivity的ServiceConnection实例serviceConnection的onServiceConnected函数通知MainActivity,这个服务已经连接上了,并且会通过这个函数传进来一个Binder远程对象,这个Binder远程对象就是来源于这里的onBind的返回值了。
函数onBind返回的Binder对象是一个自定义的CounterBinder实例,它实现了一个getService成员函数。当系统通知MainActivity,计数器服务已经启动起来并且连接成功后,并且将这个Binder对象传给MainActivity时,MainActivity就会把这个Binder对象强制转换为CounterBinder实例,然后调用它的getService函数获得服务接口。这样,MainActivity就通过这个Binder对象和CounterService关联起来了。
当MainActivity调用计数器服务接口的startCounter函数时,计数器服务并不是直接进入计数状态,而是通过使用异步任务(AsyncTask)在后台线程中进行计数。这里为什么要使用异步任务来在后台线程中进行计数呢?前面我们说过,这个计数过程是一个耗时的计算型逻辑,不能把它放在界面线程中进行,因为这里的CounterService启动时,并没有在新的进程中启动,它与MainActivity一样,运行在应用程序的界面线程中,因此,这里需要使用异步任务在在后台线程中进行计数。
异步任务AsyncTask的具体用法可以参考官方文档http://developer.android.com/reference/android/os/AsyncTask.html。它的大概用法是,当我们调用异步任务实例的execute(task.execute)方法时,当前调用线程就返回了,系统启动一个后台线程来执行这个异步任务实例的doInBackground函数,这个函数就是我们用来执行耗时计算的地方了,它会进入到一个循环中,每隔1秒钟就把计数器加1,然后进入休眠(Thread.sleep),醒过来,再重新这个计算过程。在计算的过程中,可以通过调用publishProgress函数来通知调用者当前计算的进度,好让调用者来更新界面,调用publishProgress函数的效果最终就是直入到这个异步任务实例的onProgressUpdate函数中,这里就可以把这个进度值以广播的形式(sendBroadcast)发送出去了,这里的进度值就定义为当前计数服务的计数值。
当MainActivity调用计数器服务接口的stopCounter函数时,会告诉函数doInBackground停止执行计数(stop = true),于是,函数doInBackground就退出计数循环,然后将最终计数结果返回了,返回的结果最后进入到onPostExecute函数中,这个函数同样通过广播的形式(sendBroadcast)把这个计数结果广播出去。
计算器服务就介绍到这里了,下面我们看看应用程序的配置文件AndroidManifest.xml:
[html] view plain copy
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3. package="shy.luo.broadcast"
4. android:versionCode="1"
5. android:versionName="1.0">
6. <application android:icon="@drawable/icon" android:label="@string/app_name">
7. <activity android:name=".MainActivity"
8. android:label="@string/app_name">
9. <intent-filter>
10. <action android:name="android.intent.action.MAIN" />
11. <category android:name="android.intent.category.LAUNCHER" />
12. </intent-filter>
13. </activity>
14. <service android:name=".CounterService"
15. android:enabled="true">
16. </service>
17. </application>
18. </manifest>
这个配置文件很简单,只是告诉系统,它有一个Activity和一个Service。
再来看MainActivity的界面文件,它定义在res/layout/main.xml文件中:
[html] view plain copy
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. android:orientation="vertical"
4. android:layout_width="fill_parent"
5. android:layout_height="fill_parent"
6. android:gravity="center">
7. <LinearLayout
8. android:layout_width="fill_parent"
9. android:layout_height="wrap_content"
10. android:layout_marginBottom="10px"
11. android:orientation="horizontal"
12. android:gravity="center">
13. <TextView
14. android:layout_width="wrap_content"
15. android:layout_height="wrap_content"
16. android:layout_marginRight="4px"
17. android:gravity="center"
18. android:text="@string/counter">
19. </TextView>
20. <TextView
21. android:id="@+id/textview_counter"
22. android:layout_width="wrap_content"
23. android:layout_height="wrap_content"
24. android:gravity="center"
25. android:text="0">
26. </TextView>
27. </LinearLayout>
28. <LinearLayout
29. android:layout_width="fill_parent"
30. android:layout_height="wrap_content"
31. android:orientation="horizontal"
32. android:gravity="center">
33. <Button
34. android:id="@+id/button_start"
35. android:layout_width="wrap_content"
36. android:layout_height="wrap_content"
37. android:gravity="center"
38. android:text="@string/start">
39. </Button>
40. <Button
41. android:id="@+id/button_stop"
42. android:layout_width="wrap_content"
43. android:layout_height="wrap_content"
44. android:gravity="center"
45. android:text="@string/stop" >
46. </Button>
47. </LinearLayout>
48. </LinearLayout>
这个界面配置文件也很简单,等一下我们在模拟器把这个应用程序启动起来后,就可以看到它的截图了。
应用程序用到的字符串资源文件位于res/values/strings.xml文件中:
[html] view plain copy
1. <?xml version="1.0" encoding="utf-8"?>
2. <resources>
3. <string name="app_name">Broadcast</string>
4. <string name="counter">Counter: </string>
5. <string name="start">Start Counter</string>
6. <string name="stop">Stop Counter</string>
7. </resources>
最后,我们还要在工程目录下放置一个编译脚本文件Android.mk:
[html] view plain copy
1. LOCAL_PATH:= $(call my-dir)
2. include $(CLEAR_VARS)
3.
4. LOCAL_MODULE_TAGS := optional
5.
6. LOCAL_SRC_FILES := $(call all-subdir-java-files)
7.
8. LOCAL_PACKAGE_NAME := Broadcast
9.
10. include $(BUILD_PACKAGE)
接下来就要编译了。有关如何单独编译Android源代码工程的模块,以及如何打包system.img,请参考 如何单独编译Android源代码中的模块
一文。
执行以下命令进行编译和打包:
[html] view plain copy
1. USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Broadcast
2. USER-NAME@MACHINE-NAME:~/Android$ make snod
这样,打包好的Android系统镜像文件system.img就包含我们前面创建的Broadcast应用程序了。
再接下来,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考
一文。
执行以下命令启动模拟器:
[html] view plain copy
1. USER-NAME@MACHINE-NAME:~/Android$ emulator
模拟器启动起,就可以App Launcher中找到Broadcast应用程序图标,接着把它启动起来,然后点击界面上的Start Counter按钮,就可以把计数器服务启动起来了,计数器服务又通过广播把计数值反馈给MainActivity,于是,我们就会在MainActivity界面看到计数器的值不断地增加了:
这样,使用广播的例子就介绍完了。回顾一下,使用广播的两个步骤:
1. 广播的接收者需要通过调用registerReceiver函数告诉系统,它对什么样的广播有兴趣,即指定IntentFilter,并且向系统注册广播接收器,即指定BroadcastReceiver:
[java] view plain copy
1. IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
2. registerReceiver(counterActionReceiver, counterActionFilter);
这里,指定感兴趣的广播就是CounterService.BROADCAST_COUNTER_ACTION了,而指定的广播接收器就是counterActonReceiver,它是一个BroadcastReceiver类型的实例。
2. 广播的发送者通过调用sendBroadcast函数来发送一个指定的广播,并且可以指定广播的相关参数:
[java] view plain copy
- Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
- intent.putExtra(COUNTER_VALUE, counter);
- sendBroadcast(intent)
这里,指定的广播为CounterService.BROADCAST_COUNTER_ACTION,并且附带的带参数当前的计数器值counter。调用了sendBroadcast函数之后,所有注册了CounterService.BROADCAST_COUNTER_ACTION广播的接收者便可以收到这个广播了。
在第1步中,广播的接收者把广播接收器注册到ActivityManagerService中;在第2步中,广播的发送者同样是把广播发送到ActivityManagerService中,由ActivityManagerService去查找注册了这个广播的接收者,然后把广播分发给它们。
在第2步的分发的过程,其实就是把这个广播转换成一个消息,然后放入到接收器所在的线程消息队列中去,最后就可以在消息循环中调用接收器的onReceive函数了。这里有一个要非常注意的地方是,由于ActivityManagerService把这个广播放进接收器所在的线程消息队列后,就返回了,它不关心这个消息什么时候会被处理,因此,对广播的处理是异步的,即调用sendBroadcast时,这个函数不会等待这个广播被处理完后才返回。
下面,我们以一个序列图来总结一下,广播的注册和发送的过程:
虚线上面Step 1到Step 4步是注册广播接收器的过程,其中Step 2通过LoadedApk.getReceiverDispatcher在LoadedApk内部创建了一个IIntentReceiver接口,并且传递给ActivityManagerService;虚线下面的Step 5到Step 11是发送广播的过程,在Step 8中,ActivityManagerService利用上面得到的IIntentReceiver远程接口,调用LoadedApk.performReceiver接口,LoadedApk.performReceiver接口通过ActivityThread.H接口的post函数将这个广播消息放入到ActivityThread的消息队列中去,最后这个消息在LoadedApk的Args.run函数中处理,LoadedApk.Args.run函数接着调用MainActivity.BroadcastReceiver的onReceive函数来最终处理这个广播。
文章开始的时候,我们提到,举这个例子的最终目的,是为了进一步学习Android系统的广播机制,因此,在接下来的两篇文章中,我们将详细描述上述注册广播接收器和发送广播的过程:
1. Android应用程序注册广播接收器(registerReceiver)的过程分析;
2. Android应用程序发送广播(sendBroadcast)的过程分析。
相信学习完这两篇文章后,能够加深对Android系统广播机制的了解,敬请关注。
Android Broadcast 广播
进程内本地广播
如果你是在你的应用之内使用广播,即不需要跨进程,考虑使用LocalBroadcastManager
,这样更有效率(因为不需要跨进程通信),并且你不用考虑一些其他应用可以发送或接收你的广播相关的安全问题。
下面介绍更一般的方法。
广播的两种注册方法
广播有静态和动态两种注册方法:
静态注册:在AndroidManifest.xml中加上<receiver>
标签。
动态注册:通过 Context.registerReceiver()
方法进行注册。比如在onResume中注册,在onPause中注销。
附上例子(例子中的布局、MyReceiver类,常量类都是相同的,在前面列出):
布局文件都一样:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".DemoBroadcastActivity" >
<TextView
android:id="@+id/helloText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:id="@+id/sendBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/helloText"
android:text="@string/send" />
</RelativeLayout>
自己写的Receiver类:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
public class MyReceiver extends BroadcastReceiver
{
public MyReceiver()
{
super();
Log.d(AppConstants.LOG_TAG, "Receiver constructor");
}
@Override
public void onReceive(Context context, Intent intent)
{
Log.d(AppConstants.LOG_TAG, "onReceive");
String message = intent.getStringExtra(AppConstants.MSG_KEY);
Log.i(AppConstants.LOG_TAG, message);
Toast.makeText(context, "Received! msg: " + message, Toast.LENGTH_SHORT).show();
}
}
应用常量:
public class AppConstants
{
public static final String LOG_TAG = "Broadcast";
public static final String MSG_KEY = "msg";
public static final String BROADCAST_ACTION ="com.example.demobroadcast.BroadcastAction";
}
下面就是不同的部分了!
静态注册的实例代码:
静态注册是在manifest文件中进行:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.demobroadcast"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.demobroadcast.DemoBroadcastActivity"
android:label="@string/app_name" >
<intent-filter android:priority="1000">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="com.example.demobroadcast.MyReceiver">
<intent-filter >
<action android:name="com.example.demobroadcast.BroadcastAction" />
</intent-filter>
</receiver>
</application>
</manifest>
所以Java代码:
package com.example.demobroadcast;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
import android.content.Intent;
public class DemoBroadcastActivity extends Activity
{
private Button sendBtn = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo_broadcast);
sendBtn = (Button) findViewById(R.id.sendBtn);
sendBtn.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
Intent intent = new Intent();
intent.setAction(AppConstants.BROADCAST_ACTION);
intent.putExtra("msg", "圣骑士wind");
sendBroadcast(intent);
}
});
}
}
动态注册的实例代码:
动态注册是在Java代码中进行:
package com.example.demobroadcast2;
import com.example.demobroadcast.R;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
public class DemoBroadcastActivity extends Activity
{
private Button sendBtn = null;
private MyReceiver mReceiver;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo_broadcast);
sendBtn = (Button) findViewById(R.id.sendBtn);
sendBtn.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
Intent intent = new Intent();
intent.setAction(AppConstants.BROADCAST_ACTION);
intent.putExtra("msg", "圣骑士wind");
sendBroadcast(intent);
}
});
}
@Override
protected void onResume()
{
super.onResume();
mReceiver = new MyReceiver();
IntentFilter intentFilter= new IntentFilter(AppConstants.BROADCAST_ACTION);
registerReceiver(mReceiver, intentFilter);
}
@Override
protected void onPause()
{
super.onPause();
unregisterReceiver(mReceiver);
}
@Override
protected void onDestroy()
{
super.onDestroy();
}
}
所以Manifest文件中不需要添加标签,正常就行。
两种广播
Normal broadcasts
通过 Context.sendBroadcast
发送,完全是异步的(asynchronous)。所有的接收器以不确定的顺序运行,通常是同时。
这样更有效率,但是也意味着接收器不能传递结果,也不能退出广播。
Ordered broadcasts
通过 Context.sendOrderedBroadcast
发送。一次只向一个接收器发送。
由于每个接收器按顺序执行,它可以向下一个接收器传递结果,也可以退出广播不再传递给其他接收器。
接收器运行的顺序可以通过 android:priority
属性来控制,相同优先级的接收器将会以随机的顺序运行。
接收器的生命周期
一个BroadcastReceiver的对象只在 onReceive(Context, Intent)
被调用的期间有效,一旦从这个方法返回,系统就认为这个对象结束了,不再活跃。
这对你在onReceive中能做什么有很大的影响:不能做任何需要的操作(anything that requires asynchronous operation is not available)。
因为你需要从方法返回去进行你的异步操作,而返回时BroadcastReceiver的对象已经不再活跃了,系统可以(在异步操作完成前)任意杀死它的进程。
特别地,不可以在BroadcastReceiver中显示对话框或者绑定一个service,前者应该用 NotificationManager
,后者应该用Context.startService()。
参考资料
官方文档BroadcastReceiver:
http://developer.android.com/reference/android/content/BroadcastReceiver.html
LocalBroadcastManager:
http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html
Training: Manipulating Broadcast Receivers On Demand
http://developer.android.com/training/monitoring-device-state/manifest-receivers.html
receiver标签
http://developer.android.com/guide/topics/manifest/receiver-element.html