一篇文章搞定广播
2200 Words|Read in about 10 Min|本文总阅读量次
BroadcastReceiver,Android四大组件之一。 用于响应来自其他应用程序或者系统的广播消息。这些消息有时被称为事件或者意图。
开篇问题
1.广播的用途
2.有序广播是全局广播吗
3.如何adb手动发送广播
广播和广播接收器
笔者在10年的时候去了上海世博会,非常壮观。有各种各样的人,外国人也很多。但是问题来了,由于当天旅客很多,小孩子东跑来西跑去,很容易走丢。这个时候经常会听到广播,寻人启事,家长找不到孩子xx,请听到广播的xx,速到广播中心。那么抽象到android,寻人启事得广播就是广播中心的人发出去的,然后在世博会中所有的旅客都能接收到广播,但只有xx小朋友接收到广播后才有去广播中心的行动。
广播的两种注册方式
广播的两种类型
动态注册
1//动态注册
2public class MainActivity extends AppCompatActivity {
3 private final String TAG = "MainActivity";
4 private final String MY_DYNAMIC_BROADCAST = "android.intent.action.dynamicBroadcast";
5
6 @Override
7 protected void onCreate(Bundle savedInstanceState) {
8 super.onCreate(savedInstanceState);
9 setContentView(R.layout.activity_main);
10
11 initBroadcast();
12 }
13
14 private void initBroadcast()
15 {
16 Log.d(TAG ,"->initBroadcast");
17 IntentFilter itFilter = new IntentFilter();
18 itFilter.addAction(MY_DYNAMIC_BROADCAST);
19 registerReceiver(dynamicBroadcast, itFilter);
20 }
21
22 //别忘了将广播取消掉哦~
23 @Override
24 protected void onDestroy() {
25 super.onDestroy();
26 unregisterReceiver(dynamicBroadcast);
27 }
28
29 /**
30 * 匿名内部类
31 * adb发送给广播
32 * adb shell am broadcast -a android.intent.action.dynamicBroadcast --es "myString" "dynamicBroadcast successed"
33 */
34 private BroadcastReceiver dynamicBroadcast = new BroadcastReceiver() {
35 @Override
36 public void onReceive(Context context, Intent intent) {
37 if (MY_DYNAMIC_BROADCAST.equals(intent.getAction()))
38 {
39 String myString = intent.getStringExtra("myString");
40 Log.d(TAG, "->dynamicBroadcast receiver has receive, mystring is "+myString);
41 Toast.makeText(context, "->dynamicBroadcast receiver has receive, mystring is "+myString,Toast.LENGTH_LONG).show();
42 }
43 }
44 };
45}
有一个动态注册的特例,即在app内部的广播,本地广播。
本地广播
本地广播无法通过静态注册方式来接受,该机制发出的广播只会在APP内部传播,而且 广播接收者也只能收到本应用发出的广播,但是androidx已经废弃,目前还是使用动态广播通过参数指定组件名,即可替代原本的本地广播。
静态注册
8.0版本之后的,非系统广播仅在清单中声明接收器是不行的。
如果需要自定义广播,还需要使用动态广播。
通常用于监听系统广播,比如开机广播
1<!--注册静态广播-->
2 <receiver android:name=".MyBootReceiver"
3 android:enabled="true"
4 android:exported="true">
5 <intent-filter>
6 <action android:name = "android.intent.action.BOOT_COMPLETED"/>
7 </intent-filter>
8 <!--装载SD卡-->
9 <intent-filter>
10 <!-- SD卡已经成功挂载 -->
11 <action android:name="android.intent.action.MEDIA_MOUNTED" />
12 <!-- sd卡存在,但还没有挂载 -->
13 <action android:name="android.intent.action.MEDIA_UNMOUNTED" />
14
15 <action android:name="android.intent.action.MEDIA_EJECT" />
16 <data android:scheme="file" />
17 </intent-filter>
18 </receiver>
1public class MyBootReceiver extends BroadcastReceiver {
2 private final String TAG = "MyBootReceiver";
3 public final static String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED";
4
5
6 @Override
7 public void onReceive(Context context, Intent intent) {
8 if (ACTION_BOOT.equals(intent.getAction()))
9 {
10 Log.d(TAG, "->boot receiver get"+Intent.ACTION_BOOT_COMPLETED);
11 Toast.makeText(context, "开机完毕~", Toast.LENGTH_LONG).show();
12 }
13 }
14}
标准广播
这里添加了两个地方,一个是AndroidManifest.xml注册,且在主activity里动态注册
1<!--七夕广播注册-->
2 <receiver android:name=".MyTanabataReceiver"
3 android:enabled="true"
4 android:exported="true">
5 <intent-filter>
6 <action android:name = "android.intent.action.SENDLOVE"/>
7 </intent-filter>
8 </receiver>
1//七夕动态注册
2myTanabataReceiver = new MyTanabataReceiver();
3IntentFilter intentFilter = new IntentFilter();
4intentFilter.addAction(MyTanabataReceiver.ACTION_SENDLOVE);
5registerReceiver(myTanabataReceiver, intentFilter);
实现七夕的广播接收器
1//七夕节广播
2public class MyTanabataReceiver extends BroadcastReceiver {
3 private final String TAG = "MyTanabataReceiver";
4 public final static String ACTION_SENDLOVE = "android.intent.action.SENDLOVE";
5
6 @Override
7 public void onReceive(Context context, Intent intent) {
8 if (ACTION_SENDLOVE.equals(intent.getAction())) {
9 //adb 发广播
10 //adb shell am broadcast -a android.intent.action.SENDLOVE -n com.example.broadcast/.MyTanabataReceiver
11 // --es "love" "爱你" --ei "days" 10000
12 String myloveString = intent.getStringExtra("love");
13 int myloveInt = intent.getIntExtra("days", -1);
14 Log.d(TAG, "->myloveString = " + myloveString + " myloveInt = " + myloveInt);
15 Toast.makeText(context, "播放黄老板的【perfect】\n\t" + myloveString + myloveInt + "年", Toast.LENGTH_LONG).show();
16 }
17 }
18}
有序广播
跟设计模式中的责任链模式类似。静态注册接收有序广播(Android8.0后不支持),因此下面罗列的都是动态注册的有序广播。有序广播必须按照广播优先级来按顺序依次发送,且发送过程中接收到的广播接收器可以选择中断发送广播,也可以继续发送并且修改数据。
动态注册,并设置优先级
1//动态注册有序广播
2//有序广播需要设置优先级-1000-1000,值越大优先级越高
3orderedReceiver1 = new OrderedReceiver1();
4IntentFilter orderedfilter1 = new IntentFilter();
5orderedfilter1.addAction(MY_ORDERED_BROADCAST);
6orderedfilter1.setPriority(1000);
7this.registerReceiver(orderedReceiver1, orderedfilter1);
8
9orderedReceiver2 = new OrderedReceiver2();
10IntentFilter orderedfilter2 = new IntentFilter();
11orderedfilter2.addAction(MY_ORDERED_BROADCAST);
12orderedfilter2.setPriority(999);
13this.registerReceiver(orderedReceiver2, orderedfilter2);
14
15orderedReceiver3 = new OrderedReceiver3();
16IntentFilter orderedfilter3 = new IntentFilter();
17orderedfilter3.addAction(MY_ORDERED_BROADCAST);
18orderedfilter3.setPriority(998);
19this.registerReceiver(orderedReceiver3, orderedfilter3);
自定义三个有序广播接收器
1/**
2* 有序广播接收1,优先级1000
3*/
4public class OrderedReceiver1 extends BroadcastReceiver {
5 @Override
6 public void onReceive(Context context, Intent intent) {
7 if (MY_ORDERED_BROADCAST.equals(intent.getAction()))
8 {
9 boolean orderedBroadcast = isOrderedBroadcast();
10 if (orderedBroadcast) {
11 Log.d(TAG, "->Priority 1000 is orderedBroadcast");
12 } else {
13 Log.d(TAG, "->Priority 1000 is not orderedBroadcast");
14 abortBroadcast();
15 }
16 String resultData = getResultData();
17 if (null != resultData)
18 {
19 Log.d(TAG, "->OrderedReceiver1|resultData = "+resultData);
20 //有序广播里终止广播
21 //abortBroadcast();
22 setResultData("有序广播1收到,传达给有序广播2");
23 Toast.makeText(context, "有序广播接收1的内容:"+resultData, Toast.LENGTH_SHORT).show();
24 }else
25 {
26 Log.d(TAG, "->OrderedReceiver1");
27 //有序广播里终止广播
28 //abortBroadcast();
29 setResultData("有序广播1收到,传达给有序广播2");
30 Toast.makeText(context, "有序广播接收1的内容:", Toast.LENGTH_SHORT).show();
31 }
32
33 }
34 }
35}
36
37/**
38* 有序广播接收2,优先级999
39*/
40public class OrderedReceiver2 extends BroadcastReceiver {
41 @Override
42 public void onReceive(Context context, Intent intent) {
43 if (MY_ORDERED_BROADCAST.equals(intent.getAction()))
44 {
45 boolean orderedBroadcast = isOrderedBroadcast();
46 if (orderedBroadcast) {
47 Log.d(TAG, "->Priority 999 is orderedBroadcast");
48 } else {
49 Log.d(TAG, "->Priority 999 is not orderedBroadcast");
50 abortBroadcast();
51 }
52 String resultData = getResultData();
53 Log.d(TAG, "->OrderedReceiver2|resultData = "+resultData);
54 //有序广播里终止广播
55 //abortBroadcast();
56 setResultData("有序广播2收到,传达给有序广播3");
57 Toast.makeText(context, "有序广播接收2的内容:"+resultData, Toast.LENGTH_SHORT).show();
58 }
59 }
60}
61
62/**
63* 有序广播接收3,优先级998
64*/
65public class OrderedReceiver3 extends BroadcastReceiver {
66 @Override
67 public void onReceive(Context context, Intent intent) {
68 if (MY_ORDERED_BROADCAST.equals(intent.getAction()))
69 {
70 boolean orderedBroadcast = isOrderedBroadcast();
71 if (orderedBroadcast) {
72 Log.d(TAG, "->Priority 998 is orderedBroadcast");
73 } else {
74 Log.d(TAG, "->Priority 998 is not orderedBroadcast");
75 abortBroadcast();
76 }
77 String resultData = getResultData();
78 Log.d(TAG, "->OrderedReceiver3|resultData = "+resultData);
79 //有序广播里终止广播
80 //abortBroadcast();
81 setResultData("有序广播3收到,继续传递");
82 Toast.makeText(context, "有序广播接收3的内容:"+resultData, Toast.LENGTH_SHORT).show();
83 }
84 }
85}
发送有序广播
1//发送有序广播
2Intent OrderedIntent = new Intent(MY_ORDERED_BROADCAST);
3 sendOrderedBroadcast(intent, null, new OrderedReceiver3(), null,
4 Activity.RESULT_OK, "现在发送有序广播给有序广播1", null);
有序广播发送参数
Parameters intent The Intent to broadcast; all receivers matching this Intent will receive the broadcast. receiverPermission String naming a permissions that a receiver must hold in order to receive your broadcast. If null, no permission is required. resultReceiver Your own BroadcastReceiver to treat as the final receiver of the broadcast. scheduler A custom Handler with which to schedule the resultReceiver callback; if null it will be scheduled in the Context’s main thread. initialCode An initial value for the result code. Often Activity.RESULT_OK. initialData An initial value for the result data. Often null. initialExtras An initial value for the result extras. Often null.
翻译一下
intent:不多说指定intent,所有广播接收者的匹配规则
receiverPermission:指定广播接收器的权限,一般自定义,不常用,可传null。
resultReceiver:指定一个最终的广播接收器
scheduler:调度器,一般传null。
initialCode:指定一个code,一般传Activity.RESULT_OK。
initialData:传一个字符串数据。对应的在BroadcastReceiver中通过getResultData()取得数据
initialExtras:传一个Bundle对象,也就是可以传多种类型的数据。对应的在BroadcastReceiver中通过Bundle bundle = getResultExtras(false)取得Bundle对象
BroadcastReceiver变体
iflyos开发者们还集成了kotlin的方式,使得BroadcastReceiver使用更为方便
1abstract class SelfBroadcastReceiver(vararg actions: String) : BroadcastReceiver() {
2
3 private val filter = IntentFilter()
4
5 private var registered = false
6
7 init {
8 for (action in actions) {
9 filter.addAction(action)
10 }
11 }
12
13 protected abstract fun onReceiveAction(action: String, intent: Intent)
14
15 fun getFilter() = filter
16
17 override fun onReceive(context: Context, intent: Intent?) {
18 intent ?: return
19 val action = intent.action
20 if (!action.isNullOrEmpty()) {
21 onReceiveAction(action, intent)
22 }
23 }
24
25 fun register(context: Context?) {
26 if (!registered) {
27 context?.registerReceiver(this, filter)
28 registered = true
29 }
30 }
31
32 fun unregister(context: Context?) {
33 if (registered) {
34 registered = false
35 context?.unregisterReceiver(this)
36 }
37 }
38
39}
具体监听方式
1private val batteryReceiver = object : SelfBroadcastReceiver(
2 Intent.ACTION_BATTERY_CHANGED,
3 Intent.ACTION_BATTERY_LOW,
4 Intent.ACTION_BATTERY_OKAY,
5 Intent.ACTION_POWER_CONNECTED,
6 Intent.ACTION_POWER_DISCONNECTED
7 ) {
8 override fun onReceiveAction(action: String, intent: Intent) {
9 when (action) {
10 Intent.ACTION_BATTERY_CHANGED -> {
11 updateBattery(intent)
12 }
13 Intent.ACTION_BATTERY_LOW -> {
14
15 }
16 Intent.ACTION_BATTERY_OKAY -> {
17
18 }
19 }
20 }
21 }
问题回答
1.广播的用途
1)通过设置广播接收器监听广播,并做出程序逻辑的处理
2)可以进行android组件的通信,线程通讯,进程通讯,app内通讯,app之间通讯等
2.广播按注册来分动态和静态注册,广播按类型分为标准和有序,显然8.0之后非系统广播只能是动态注册,所以有序也是需要动态注册的。具体可以看本文上述demo。
3.根据下面的例子简单说明
adb shell am broadcast -a android.intent.action.SENDLOVE -n com.example.broadcast/.MyTanabataReceiver –es “love” “爱你” –ei “days” 10000 –ez “reality” true[其中es前面有两个
-
]
参数说明
例如-a指定action,-d指定uri,-p指定包名,-n指定组件名,–es指定{key,value}
–es 表示使用字符串类型参数 –ei 表示int类型参数 –ez 表示boolean类型
[-a ] [-d <DATA_URI>] [-t <MIME_TYPE>] [-c [-c ] …] [-e|–es <EXTRA_KEY> <EXTRA_STRING_VALUE> …] [–ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> …] [-e|–ei <EXTRA_KEY> <EXTRA_INT_VALUE> …] [-n ] [-f ] []
当然广播的参数也可以自定义,广播参数最终调用的是Intent.java中parseCommandArgs方法,这是一个对外隐藏的方法,当然有了源码之后可以自定义参数去发广播指令,具体自定义参数广播可以看散尽万千浮华
1/** @hide */
2public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler)
3 ...
4 while ((opt=cmd.getNextOption()) != null) {
5 switch (opt) {
6 //指定action
7 case "-a":
8 intent.setAction(cmd.getNextArgRequired());
9 if (intent == baseIntent) {
10 hasIntentInfo = true;
11 }
12 break;
13 //指定Uri
14 case "-d":
15 data = Uri.parse(cmd.getNextArgRequired());
16 if (intent == baseIntent) {
17 hasIntentInfo = true;
18 }
19 break;
20 case "-t":
21 type = cmd.getNextArgRequired();
22 if (intent == baseIntent) {
23 hasIntentInfo = true;
24 }
25 break;
26 case "-c":
27 intent.addCategory(cmd.getNextArgRequired());
28 if (intent == baseIntent) {
29 hasIntentInfo = true;
30 }
31 break;
32 //使用字符串类型的键值对{key, value}
33 case "-e":
34 case "--es": {
35 String key = cmd.getNextArgRequired();
36 String value = cmd.getNextArgRequired();
37 intent.putExtra(key, value);
38 }
39 break;
40 case "--esn": {
41 String key = cmd.getNextArgRequired();
42 intent.putExtra(key, (String) null);
43 }
44 break;
45 //使用整数类型的键值对{key, value}
46 case "--ei": {
47 String key = cmd.getNextArgRequired();
48 String value = cmd.getNextArgRequired();
49 intent.putExtra(key, Integer.decode(value));
50 }
51 break;
52 //使用Uri类型的键值对{key, value}
53 case "--eu": {
54 String key = cmd.getNextArgRequired();
55 String value = cmd.getNextArgRequired();
56 intent.putExtra(key, Uri.parse(value));
57 }
58 break;
59 case "--ecn": {
60 String key = cmd.getNextArgRequired();
61 String value = cmd.getNextArgRequired();
62 ComponentName cn = ComponentName.unflattenFromString(value);
63 if (cn == null)
64 throw new IllegalArgumentException("Bad component name: " + value);
65 intent.putExtra(key, cn);
66 }
67 break;
68 //使用字符串类型的键值对{key, value},且value使用,隔开的list
69 case "--eia": {
70 String key = cmd.getNextArgRequired();
71 String value = cmd.getNextArgRequired();
72 String[] strings = value.split(",");
73 int[] list = new int[strings.length];
74 for (int i = 0; i < strings.length; i++) {
75 list[i] = Integer.decode(strings[i]);
76 }
77 intent.putExtra(key, list);
78 }
79 break;
80 //使用整数类型的键值对{key, value},且value使用,隔开的list
81 case "--eial": {
82 String key = cmd.getNextArgRequired();
83 String value = cmd.getNextArgRequired();
84 String[] strings = value.split(",");
85 ArrayList<Integer> list = new ArrayList<>(strings.length);
86 for (int i = 0; i < strings.length; i++) {
87 list.add(Integer.decode(strings[i]));
88 }
89 intent.putExtra(key, list);
90 }
91 break;
92 //使用Long类型的键值对{key, value}
93 case "--el": {
94 String key = cmd.getNextArgRequired();
95 String value = cmd.getNextArgRequired();
96 intent.putExtra(key, Long.valueOf(value));
97 }
98 break;
99 //使用Long类型的键值对{key, value},且value使用,隔开的list
100 case "--ela": {
101 String key = cmd.getNextArgRequired();
102 String value = cmd.getNextArgRequired();
103 String[] strings = value.split(",");
104 long[] list = new long[strings.length];
105 for (int i = 0; i < strings.length; i++) {
106 list[i] = Long.valueOf(strings[i]);
107 }
108 intent.putExtra(key, list);
109 hasIntentInfo = true;
110 }
111 break;
112 case "--elal": {
113 String key = cmd.getNextArgRequired();
114 String value = cmd.getNextArgRequired();
115 String[] strings = value.split(",");
116 ArrayList<Long> list = new ArrayList<>(strings.length);
117 for (int i = 0; i < strings.length; i++) {
118 list.add(Long.valueOf(strings[i]));
119 }
120 intent.putExtra(key, list);
121 hasIntentInfo = true;
122 }
123 break;
124 //使用Float类型的键值对{key, value}
125 case "--ef": {
126 String key = cmd.getNextArgRequired();
127 String value = cmd.getNextArgRequired();
128 intent.putExtra(key, Float.valueOf(value));
129 hasIntentInfo = true;
130 }
131 break;
132 case "--efa": {
133 String key = cmd.getNextArgRequired();
134 String value = cmd.getNextArgRequired();
135 String[] strings = value.split(",");
136 float[] list = new float[strings.length];
137 for (int i = 0; i < strings.length; i++) {
138 list[i] = Float.valueOf(strings[i]);
139 }
140 intent.putExtra(key, list);
141 hasIntentInfo = true;
142 }
143 break;
144 case "--efal": {
145 String key = cmd.getNextArgRequired();
146 String value = cmd.getNextArgRequired();
147 String[] strings = value.split(",");
148 ArrayList<Float> list = new ArrayList<>(strings.length);
149 for (int i = 0; i < strings.length; i++) {
150 list.add(Float.valueOf(strings[i]));
151 }
152 intent.putExtra(key, list);
153 hasIntentInfo = true;
154 }
155 break;
156 case "--esa": {
157 String key = cmd.getNextArgRequired();
158 String value = cmd.getNextArgRequired();
159 // Split on commas unless they are preceeded by an escape.
160 // The escape character must be escaped for the string and
161 // again for the regex, thus four escape characters become one.
162 String[] strings = value.split("(?<!\\\\),");
163 intent.putExtra(key, strings);
164 hasIntentInfo = true;
165 }
166 break;
167 case "--esal": {
168 String key = cmd.getNextArgRequired();
169 String value = cmd.getNextArgRequired();
170 // Split on commas unless they are preceeded by an escape.
171 // The escape character must be escaped for the string and
172 // again for the regex, thus four escape characters become one.
173 String[] strings = value.split("(?<!\\\\),");
174 ArrayList<String> list = new ArrayList<>(strings.length);
175 for (int i = 0; i < strings.length; i++) {
176 list.add(strings[i]);
177 }
178 intent.putExtra(key, list);
179 hasIntentInfo = true;
180 }
181 break;
182 //使用boolean类型的键值对{key, value}
183 case "--ez": {
184 String key = cmd.getNextArgRequired();
185 String value = cmd.getNextArgRequired().toLowerCase();
186 // Boolean.valueOf() results in false for anything that is not "true", which is
187 // error-prone in shell commands
188 boolean arg;
189 if ("true".equals(value) || "t".equals(value)) {
190 arg = true;
191 } else if ("false".equals(value) || "f".equals(value)) {
192 arg = false;
193 } else {
194 try {
195 arg = Integer.decode(value) != 0;
196 } catch (NumberFormatException ex) {
197 throw new IllegalArgumentException("Invalid boolean value: " + value);
198 }
199 }
200
201 intent.putExtra(key, arg);
202 }
203 break;
204 //指定包名
205 case "-n": {
206 String str = cmd.getNextArgRequired();
207 ComponentName cn = ComponentName.unflattenFromString(str);
208 if (cn == null)
209 throw new IllegalArgumentException("Bad component name: " + str);
210 intent.setComponent(cn);
211 if (intent == baseIntent) {
212 hasIntentInfo = true;
213 }
214 }
215 break;
216 //指定组件名
217 case "-p": {
218 String str = cmd.getNextArgRequired();
219 intent.setPackage(str);
220 if (intent == baseIntent) {
221 hasIntentInfo = true;
222 }
223 }
224 break;
225 ...
226 default:
227 if (optionHandler != null && optionHandler.handleOption(opt, cmd)) {
228 // Okay, caller handled this option.
229 } else {
230 throw new IllegalArgumentException("Unknown option: " + opt);
231 }
232 break;
233 }
234 }
235 ...
236}
祝各位宝宝们七夕快乐!
参考
[1] 散尽万千浮华, Android adb构造特定格式的broadcast intent,伪造短信广播, 2019.
[2] 我辛飞翔, 在命令行中通过adb shell am broadcast发送广播通知以及Android的常用adb命令, 2013.
[3] 菜鸟教程, BroadcastReceiver牛刀小试, 2015.