遇到了自定义广播报错的问题,在此记录:
自定义广播报错/闪退:
需要在接收广播的代码里显式指定:
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>
运行即可