Android本地广播详解

本地广播用途

~~~~本地广播用于同一个app内通信(是否可以多进程通信?),且其他app无法接收到本地广播的消息,可以大大提高安全性,且只能动态注册。

本地广播的使用和原理分析

本地广播使用到的几个类

~~~~1、public final class LocalBroadcastManager这个类是无法继承的,直接使用即可,后续我们深入分析这个类。
~~~~2、public abstract class BroadcastReceiver这个类是抽象类,在使用过程中一般需要继承,实现onReceive函数。后续我们会分析为什么需要继承这个类且为什么要实现onReceive函数。
~~~~3、public class IntentFilter implements Parcelable这个类是用于添加本地广播Inten过滤动作的,且我们后续会深入分析这个类。

本地广播实现源代码

~~~~1、新建一个BroadcastTest工程,创建一个空的活动即可,在MainActivity中增加若干私有成员,新建一个LocalReceiver类继承BroadcastReceiver并重写onReceive函数,以下是一些代码片段,后续我也会先贴出片段代码方便分析,最后再贴出完整代码。

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private static final String TAG="MainActivity";
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;
    private static final String localBroadcastAction = "com.example.broadcasttest.LOCAL_BROADCAST";
    private class LocalReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received a localbroadcast", Toast.LENGTH_SHORT).show();
        }
    }

~~~我们跳进BroadcastReceiver找到onReceive函数,源代码如下:

     *
     * @param context The Context in which the receiver is running.
     * @param intent The Intent being received.
     */
    public abstract void onReceive(Context context, Intent intent);

~~~~从这里我们可以看出onReceive函数是抽象函数,所以我们需要重新实现这个函数。刚开始学习Android的时候通常我们会看到书上说我们要实现xxx函数,但是通常不会告诉我们为什么要重新实现这个函数,而且函数的参数是哪些也都不知道。这个时候我们可以点开源码,阅读源码知根知底。关于如何调用onReceive后面会详细分析。
~~~~2、创建一个按钮控件,方便后面测试,按下按钮使用本地广播发送一条消息,并用Toast显示。
~~~~3、在MainActivity的onCreate函数中进行初始化变量,注册按钮监听器。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view){
                Log.d(TAG, "button clicked");
                Intent intent = new Intent(localBroadcastAction);
                localBroadcastManager.sendBroadcast(intent);
                //localBroadcastManager.sendBroadcastSync(intent);
            }
        });
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(localBroadcastAction);
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter);

~~~~进入LocalBroadcastManager源码我们发现该类的构造函数是私有的,也就是我们无法通过new创建LocalBroadcastManager的实例,而且还发现它有一个静态私有的LocalBroadcastManager成员变量,那么意思很明显了,只能通过一个公有的方法获取LocalBroadcastManager单例对象实例。

    private static final Object mLock = new Object();
    private static LocalBroadcastManager mInstance;

    @NonNull
    public static LocalBroadcastManager getInstance(@NonNull Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext());
            }
            return mInstance;
        }
    }

    private LocalBroadcastManager(Context context) {
        mAppContext = context;
        mHandler = new Handler(context.getMainLooper()) {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_EXEC_PENDING_BROADCASTS:
                        executePendingBroadcasts();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    }

~~~通过代码我们知道,基于懒汉式单例模式通过公有方法getInstance创建对象实例,所以我们只能通过调用getInstance函数获取LocalBroadcastManager对象实例。从构造函数我们也能大概猜出,初始化的过程中创建了一个线程处理发送的广播消息。由于本地广播可以发送多条消息,因此要想接收到指定的本地广播消息,则需要一个广播消息ID。IntentFilter则是通过存储本地广播消息ID,并和广播接收器BroadcastReceiver绑定在一起实现接收指定的广播数据。

     * @param action Name of the action to match, such as Intent.ACTION_VIEW.
     */
    public final void addAction(String action) {
        if (!mActions.contains(action)) {
            mActions.add(action.intern());
        }
    }
    @UnsupportedAppUsage
    private final ArrayList<String> mActions;
     * @see #unregisterReceiver
     */
    public void registerReceiver(@NonNull BroadcastReceiver receiver,
            @NonNull IntentFilter filter) {
        synchronized (mReceivers) {
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
            if (filters == null) {
                filters = new ArrayList<>(1);
                mReceivers.put(receiver, filters);
            }
            filters.add(entry);
           for (int i=0; i<filter.countActions(); i++) {
                String action = filter.getAction(i);
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    mActions.put(action, entries);
                }
                entries.add(entry);
            }
        }
    }
    private static final class ReceiverRecord {
        final IntentFilter filter;
        final BroadcastReceiver receiver;
        boolean broadcasting;
        boolean dead;

        ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
            filter = _filter;
            receiver = _receiver;
        }

IntentFilter将action存储在ArrayList中,LocalBroadcastManager的registerReceiver函数将传入的receiver和filter通过ReceiverRecord绑定在一起,实现了接收特定本地广播消息的功能。
~~~4、调用sendBroadcast将本地广播消息发送出去(实际上只是把数据放在了一个消息队列里面,线程异步将消息发送出去),我们直接点开sendBroadcast源码,以下是代码片段。

                if (receivers != null) {
                    for (int i=0; i<receivers.size(); i++) {
                        receivers.get(i).broadcasting = false;
                    }
                    mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }

~~~~从源码我们知道,存储Intent并发送一条广播消息到消息队列里面,并且将receiver和BroadcastRecord联系在一起,且将BroadcastRecord存储在mPendingBroadcasts里面。我们进入LocalBroadcastManager的构造函数调用的executePendingBroadcasts函数源码。

    void executePendingBroadcasts() {
        while (true) {
            final BroadcastRecord[] brs;
            synchronized (mReceivers) {
                final int N = mPendingBroadcasts.size();
                if (N <= 0) {
                    return;
                }
                brs = new BroadcastRecord[N];
                mPendingBroadcasts.toArray(brs);
                mPendingBroadcasts.clear();
            }
            for (int i=0; i<brs.length; i++) {
                final BroadcastRecord br = brs[i];
                final int nbr = br.receivers.size();
                for (int j=0; j<nbr; j++) {
                    final ReceiverRecord rec = br.receivers.get(j);
                    if (!rec.dead) {
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }

~~~~从mPendingBroadcasts中取出BroadcastRecord对象实例,获取调用receiver,调用onReceive函数。这就是我们为什么可以在onReceive函数中接收到本地广播消息了。

总结

~~~~本地广播的基本原理和使用我们已经讲得差不多了,其实还有很多没有讲到。但是没办法,要是把全部实现源代码追踪下去,无穷无尽。所以点到为止,继续学习后续的知识,先应付工作先。
~~~~我们在学习Android的时候不仅仅学习表面如何使用,也要适当的追踪源码,学习底层实现原理。Android实在是知识太丰富了,有很多可以学习的地方,如果不是时间有限,真想把所有知识点追踪下去。

完整代码

package com.example.broadcasttest;

import androidx.appcompat.app.AppCompatActivity;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import android.app.admin.NetworkEvent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private static final String TAG="MainActivity";
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;
    private static final String localBroadcastAction = "com.example.broadcasttest.LOCAL_BROADCAST";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view){
                Log.d(TAG, "button clicked");
                Intent intent = new Intent(localBroadcastAction);
                localBroadcastManager.sendBroadcast(intent);
                //localBroadcastManager.sendBroadcastSync(intent);
            }
        });
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(localBroadcastAction);
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter);
    }

    private class LocalReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received a localbroadcast", Toast.LENGTH_SHORT).show();
        }
    }

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