BroadCastReceiver学习笔记(基础篇)

一、BroadCastReceiver简介

BroadcastReceiver是Android四大组件之一,本质是一种全局的监听器,用于监听系统或者应用全局的广播消息,然后根据广播信息做出相应的逻辑处理,也可以用来传输少量、频率低的数据。因此它可以非常方便的实现不同组件之间的通信。
  Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者。从实现原理看上,Android中的广播使用了观察者模式,基于消息的发布/订阅事件模型。具体实现流程要点粗略概括如下:

  1. 广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;
  2. 广播发送者通过binder机制向AMS发送广播;
  3. AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;
  4. 消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。

二、接收系统广播

注册广播的方式一般有两种,一种是在代码中注册,另一种是在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。

我们先通过网络状态变化这一系统广播来实践一下BroadCastReceiver的动态注册。

首先,我们需要在AndroidManifest文件中申请网络访问权限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kingdee.zhao.broadcastreceivertest">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    ... ...

</manifest>

新建NetworkChangeReceiver继承自BroadCastReceiver,重写onReceive()方法。

public class NetworkChangeReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 获取手机所有连接管理对象
        ConnectivityManager connectivityManager =
                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        // 获取NetworkInfo对象
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        if (networkInfo != null && networkInfo.isAvailable()) {
            Toast.makeText(context, "Network is available!", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "Network is unavailable!", Toast.LENGTH_SHORT).show();
        }
    }
}

首先通过getSystemService() 方法得到了系统服务类ConnectivityManager的实例,专门用于管理网络连接的。然后调用它的getActiveNetworkInfo()方法可以得到NetworkInfo的实例,接着调用NetworkInfo的isAvailable()方法,就可以判断出当前是否有网络连接了。

在MainActivity的onCreate()方法中注册该广播,在onDestroy()方法中注销该广播。

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;

    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 设置对应的intent启动条件为网络状态发生变换
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");

        networkChangeReceiver = new NetworkChangeReceiver();
        // 注册广播
        registerReceiver(networkChangeReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销广播
        unregisterReceiver(networkChangeReceiver);
    }
}

我们创建了一个IntentFilter的实例,并给它添加了一个action,因为当网络状态发生变化时,系统发出的正是一条值为"android.net.conn.CONNECTIVITY_CHANGE"的广播,也就是说我们的广播接收器想要监听什么广播,就在这里添加相应的action进行筛选。接下来创建了一个NetworkChangeReceiver的实例,然后调用registerReceiver()方法进行注册,将NetworkChangeReceiver的实例和IntentFilter的实例都传了进去,这样NetworkChangeReceiver就会收到所有值为android.net.conn.CONNECTIVITY_CHANGE的广播,也就实现了监听网络变化的功能。

运行程序,使用下拉通知栏更改网络连接状态,我们会发现以下现象,证明我们接收到了系统发出的广播。

image.png
image.png

现在我们实践一下广播的静态注册,首先新建一个BootCompleteReceiver,Android Studio右键new中的other可以自动创建broadcastReceiver,这样Android Studio就已经帮我们在Manifest文件中静态注册了BootCompleteReceiver。重写onReceive()方法,弹出Toast

public class BootCompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show();
    }
}

修改AndroidManifest.xml文件,添加监听系统开机广播的权限,并且在receiver中添加对应的action。这样手机重启的时候,该应用就会收到来自系统发出的广播而响应onReceive()。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kingdee.zhao.broadcastreceivertest">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        
        ... ...

        <receiver
            android:name=".broadcastReceiver.BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

三、发送自定义广播

新建一个广播接收器来接受此广播,新建一个MyBroadcastReceiver

public class MyBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
    }
}

修改AndroidManifest文件,让MyBroadcastReceiver接收到“com.kingdee.broadcasttest.MY_BROADCAST”时响应。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    
    ... ...

    <application
    
        ... ...
        
        <receiver
            android:name=".broadcastReceiver.MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.kingdee.broadcasttest.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

    </application>

</manifest>

为Activity添加一个Button来发送自定义的广播信号。先在xml文件中添加button,然后再MainActivity中注册监听事件,调用sendBroadcast(intent)来发送广播。

public class MainActivity extends AppCompatActivity {

    ... ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ... ...
        
        Button sendBtn = findViewById(R.id.btn_send);
        sendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.kingdee.broadcasttest.MY_BROADCAST");
                sendBroadcast(intent);

            }
        });
    }

    ... ...
    
}

运行程序,点击button会发现弹出了Toast,说明我们在button的点击事件中发出的自定义广播已经被接收到,并执行了onReceive()方法。

image.png

广播是一种可以跨进程的通信方式,这一点从前面接收系统广播的时候就可以看出来了。因此在我们应用程序内自定义发出的广播,其他的应用程序应该也是可以收到的。我们可以再新建一个BroadcastTest2项目,在这个项目下定义一个广播接收器,用于接收第一个项目中发出中的自定义广播。

public class AnotherBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show();
    }
}

同样在Manifest文件中设置广播接收器的响应信号。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kingdee.zhao.broadcastreceivertest2">

    <application
    
        ... ...
        
        <receiver
            android:name=".broadcastReceiver.AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.kingdee.broadcasttest.MY_BROADCAST" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

运行程序,点击BroadCastReceiverTest的button,我们会发现弹出了两次Toast,说明广播被跨进程接收了。

接下来我们让广播的发送有序化。修改BroadCastReceiverTest中发送广播的方式为sendOrderedBroadcast()。

public class MainActivity extends AppCompatActivity {

    ... ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        
        ... ...
        
        Button sendBtn = findViewById(R.id.btn_send);
        sendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.kingdee.broadcasttest.MY_BROADCAST");
                sendOrderedBroadcast(intent, null);

            }
        });
    }

    ... ...
        
}

在AndroidManifest文件中设置MyBroadcastReceiver广播的优先级为100(高于AnotherBroadcasrReceiver)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kingdee.zhao.broadcastreceivertest">

    ... ...

    <application
        
        ... ...
        
        <receiver
            android:name=".broadcastReceiver.MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter
                android:priority="100">
                <action android:name="com.kingdee.broadcasttest.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

    </application>

</manifest>

修改MyBroadcastReceiver的代码,在onReceive()的时候调用abortBroadcast()去截断广播。

public class MyBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

再次运行程序,点击button,我们会发现只有MyBroadcastReceiver接收到了广播并弹出了Toast,AnotherBroadcastReceiver没有接收到,因为被前一个广播接收器截断了。

四、使用本地广播

全局广播可以被各个应用程序接收处理,本地广播则只在本应用内部传递,增加了广播的安全性。本地广播使用LocalBroadcastManager来管理广播,广播的发送和接收都通过LocalBroadcastManager来进行。因此,也本地广播无法在AndroidManifest中注册。

修改MainActivity的代码:

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;

//    private NetworkChangeReceiver networkChangeReceiver;

    private LocalReceiver localReceiver;

    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取实例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);

//        // 设置对应的intent启动条件为网络状态发生变换
//        intentFilter = new IntentFilter();
//        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
//
//        networkChangeReceiver = new NetworkChangeReceiver();
//        // 注册广播
//        registerReceiver(networkChangeReceiver, intentFilter);

        Button sendBtn = findViewById(R.id.btn_send);
        sendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.kingdee.broadcasttest.LOCAL_BROADCAST");
                // 发送本地广播
                localBroadcastManager.sendBroadcast(intent);
            }
        });

        // 设置对应的intent对应的条件过滤器
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.kingdee.broadcasttest.LOCAL_BROADCAST");

        // 注册本地广播监听器
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销本地广播
        localBroadcastManager.unregisterReceiver(localReceiver);
    }
}

这基本上就和我们前面所学的动态注册广播接收器以及发送广播的代码是一样的。只不过现在首先是通过LocalBroadcastManager的getInstance() 方法得到了它的一个实例,然后在注册广播接收器的时候调用的是LocalBroadcastManager的registerReceiver() 方法,在发送广播的时候调用的是LocalBroadcastManager的sendBroadcast()方法,仅此而已。这里我们在按钮的点击事件里面发出了一条com.example.broadcasttest.LOCAL_BROADCAST广播,然后在LocalReceiver里去接收这条广播。重新运行程序,点击Button,我们接收到了本地广播。

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容