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.

[4] 菜鸟教程, BroadcastReceiver庖丁解牛, 2015.

[5] 凉拌西红柿S, 有序广播详解, 2020.