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>

运行即可

相关推荐
阿巴斯甜10 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker11 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952712 小时前
Andorid Google 登录接入文档
android
黄林晴13 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android