Android开发-java版:BroadcastReceiver广播

遇到了自定义广播报错的问题,在此记录:

自定义广播报错/闪退:

需要在接收广播的代码里显式指定:

1.Context.RECEIVER_NOT_EXPORTED

(1)仅接收来自"本应用(同 UID)"或系统进程发出的广播。

(2)外部应用发送的广播不会投递到该接收器。

(3)更安全、推荐作为默认选择。

(4)想要使用,在发送端添加intent.setPackage(getPackageName());代码,表示为本应用包内的广播

2.Context.RECEIVER_EXPORTED

(1)允许接收外部应用发送的广播(满足权限与匹配条件时)。

(2)增加外部输入面,需确保校验数据与权限控制。

(3)只有在确实需要接收其他应用的广播时才使用。

为什么必须指定?

  • 从 Android 14 起,动态注册非"纯系统广播"接收器时必须明确导出状态,否则抛 SecurityException。

  • 指定这两个标志相当于为"动态注册的接收器"声明了运行期的导出策略(类似于静态接收器的 android:exported )。

系统广播的关系

  • 系统可向两种接收器( EXPORTED / NOT_EXPORTED )投递广播; NOT_EXPORTED 不会阻止系统广播。

  • 对于仅监听系统广播的接收器,仍建议显式设置其中一个标志以满足平台校验。

一、BroadcastReceiver动态注册

1.优缺点:

优点

  • 灵活性高:可以在运行时根据条件动态注册和注销

  • 生命周期可控:与注册的 Context 生命周期绑定,避免内存泄漏

  • 精确控制接收范围:可以根据应用状态决定何时接收广播

  • 安全性较好:只在应用运行时接收广播

缺点

  • 应用停止后无法接收:当应用进程被杀死时,无法接收广播

  • 需要代码管理:需要手动注册和注销,增加代码复杂度

  • 仅限于应用内:通常用于应用内部的通信

2.实现方式:

创建intentFilter的实例,并且给它添加一个值为

android.net.conn.CONNECTIVITY_CHANGE的action,这个值正是系统在网络状态发生变化的时候发出的值,我们所做的工作正是接受这个值并执行对应的操作。

动态注册的接收器一定要取消注册:onDestroy()方法

定义了一个NetWorkChangeReceiver的内部类继承BroadcastReceiver并重写了onReceive方法

给registerReceiver传入netWorkChangeReceiver和intentFilter实例进行注册接收器

然后在AndroidManifest.xml文件中声明权限

运行后可以开关网络状态,同时会弹出提示

二、BroadcastReceiver静态注册

1.优缺点:

优点

  • 持久性监听:应用未运行时也能接收广播(系统广播)

  • 自动唤醒:收到广播时可以启动应用进程

  • 声明简单:在 AndroidManifest.xml 中声明即可

  • 系统事件监听:适合监听系统级事件(如开机完成、网络变化)

缺点

  • 资源消耗:即使应用不需要,系统也要维护接收器

  • 安全性风险:可能被恶意应用发送广播攻击

  • 灵活性差:无法根据运行时条件动态控制

  • Android 8.0+ 限制:对隐式广播有很多限制

2.实现方法:

Exported属性代码是否允许这个广播接收器接收除本程序之外的广播

Enabled属性表示是否启动这个广播接收器

在文件中写入接收到对应的信息后要执行的代码

同时由于我们使用的Android Studio快捷方式创建的广播接收器,所以可以在AndroidManifest.xml文件中找到自动注册的代码

将其改为如下代码,安卓系统在启动完成后会发出一条值为android.intent.action.BOOT_COMPLETED的广播

同时要启动这个接收器也要声明权限:

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

Android 10+ 限制后台启动 Activity,需谨慎处理逻辑。

三、强制下线功能(实践)

用户在别处登录时,弹窗提示,点击OK回到登录页

但是我们不可能在每个页面都编写弹出弹窗的代码,所以我们选择广播的形式

强制下线功能需要关闭所有的活动,然后回到登录界面

首先我们先写一个ActivityCollector用于管理所有的活动

java 复制代码
public class ActivityCollector {
    public static List<Activity> activities = new ArrayList<>();
    public static void addActivity(Activity activity){
        activities.add(activity);
    }
    public static void removeActivity(Activity activity){
        activities.remove(activity);
    }
    public static void finishAll(){
        for(Activity activity:activities){
            if(!activity.isFinishing()){
                activity.finish();
            }
        }
    }
}

然后我们创建一个BaseActivity,我们可以让所有的活动都继承这个父类,因为所有的活动都继承,所以我们可以在这个父类里动态注册广播接收器,这样就不必每个页面都注册广播接收器

那为什么我们要在onResume生命周期里注册广播接收呢,因为我们需要保证只有处于栈顶的活动需要接收这一条广播,非栈顶的活动不应该也没有必要去接收这条广播

onPause方法是为了保证,当前活动不再处于栈顶位置时,自动取消广播注册

在ForceOffLineReceiver类里定义,销毁所有活动并跳转至登录界面

java 复制代码
public class BaseActivity extends AppCompatActivity {
    private ForceOffLineReceiver receiver;
    protected void onCreate(Bundle savedInstancesState){
        super.onCreate(savedInstancesState);
        ActivityCollector.addActivity(this);
    }
    protected void onResume(){
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadCastBestPractice.Force_OFFLINE");
        receiver = new ForceOffLineReceiver();
        registerReceiver(receiver,intentFilter,Context.RECEIVER_NOT_EXPORTED);
    }
    protected void onPause(){
        super.onPause();
        if(receiver != null){
            unregisterReceiver(receiver);
            receiver = null;
        }
    }
    protected void onDestroy(){
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
    class ForceOffLineReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("Warning");
            builder.setMessage("登出");
            builder.setCancelable(false);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCollector.finishAll();
                    Intent intent = new Intent(context, LoginActivity.class);
                    context.startActivity(intent);
                }
            });
            builder.show();
        }
    }
}

然后,我们写一个登录界面。登录页面的布局代码在此省略,页面如下:

创建LoginActivity活动页面,继承自BaseActivity父类

java 复制代码
public class LoginActivity extends BaseActivity {
    private EditText accountEdit;
    private EditText passwordEdit;
    private Button login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        accountEdit = (EditText) findViewById(R.id.accountEdit);
        passwordEdit = (EditText) findViewById(R.id.passwordEdit);
        login = (Button) findViewById(R.id.login);
        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();
                if(account.equals("admin")&&password.equals("123456")){
                    Intent intent = new Intent(LoginActivity.this, MainActivity2.class);
                    startActivity(intent);
                    finish();
                }else {
                    Toast.makeText(LoginActivity.this,"错误",Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}

在代码中我们指定了intent跳转到MainActivity2页面,我们创建这个activity文件,继承自BaseActivity父类,然后写一个广播发送器,添加intent.setPackage(getPackageName());代码,表示为本应用包内的广播,对应广播接收器里的Context.RECEIVER_NOT_EXPORTED(接收本应用内的广播)

java 复制代码
public class MainActivity2 extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Button off = (Button) findViewById(R.id.logout);
        off.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.broadCastBestPractice.Force_OFFLINE");
                intent.setPackage(getPackageName());
                sendBroadcast(intent);
            }
        });
    }
}

然后在AndroidManifest.xml中将登录活动设置为主活动

java 复制代码
<activity
            android:name=".LoginActivity"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

运行即可

相关推荐
程序员老刘2 小时前
华为小米都在布局的多屏协同,其实Android早就有了!只是你不知道...
android·flutter
koo3642 小时前
李宏毅机器学习笔记
人工智能·笔记·机器学习
锅拌饭2 小时前
IM系统-客户端架构(一)
android
洋洋的笔记2 小时前
小白银行测试初步了解(四)信用卡
经验分享·笔记·学习
im_AMBER2 小时前
HTTP概述 01
javascript·网络·笔记·网络协议·学习·http
4z332 小时前
Android15 Framework(1): 用户空间启动的第一个进程 Init
android·源码阅读
Tonya432 小时前
测开学习DAY27
学习
笨鸟笃行3 小时前
百日挑战——单词篇(第二十天)
学习
QT 小鲜肉3 小时前
【QT/C++】Qt样式设置之CSS知识(系统性概括)
linux·开发语言·css·c++·笔记·qt