目录
自定义广播:
无序广播(标准广播):
有序广播:
本地广播:
系统广播:
动态注册:
静态注册:
广播实践----实现强制下线功能:
自定义广播:
无序广播(标准广播):
是一种异步执行的广播,在广播发出之后,所有的广播接收器几乎在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播效率会比较高,同时也是无法被截断或修改数据的。这种广播是全局性的,其他的应用程序都可以接受到。
首先来看一个无序广播的例子,不管有没有人接收我都要广播出去,就像新闻联播有没有人看我都是准点开播
发送方:
MainActivity.java
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 点击按钮,发送一条无序广播
public void onclick(View view) {
Intent intent = new Intent();
intent.setAction("mybroadcast");
intent.putExtra("name", "新联联播,我来广播");
sendBroadcast(intent);
}
}
这里因为是自定义广播,这个action我们就可以自己设置,我在接收方设置成了"mybroadcast"
所以这里intent.setAction("mybroadcast");
activity_main.xml
再来看接收方:
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
ReceiveCustomReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class ReceiveCustomReceiver extends BroadcastReceiver {
// 当接收到发送的自定义广播时调用
@Override
public void onReceive(Context context, Intent intent) {
String content = intent.getStringExtra("name");
Toast.makeText(context, "在接受无序广播中收到了该内容:" + content, Toast.LENGTH_SHORT).show();
}
}
AndroidManifest.xml
这里和发送方对应,action的name为"mybroadcast"
依次把接收方和发送方运行到模拟器,来看看运行结果:
在发送方的界面,点击发送无序广播,看到接收方的Toast显示出来了,接收到了广播
有序广播:
是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕之后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先接收到广播消息,并且前面的广播接收器还可以截断或者修改正在传递的广播,这样后面的广播无法接受到广播消息或者接收到不真实的广播消息。这种广播是全局性的,其他的应用程序都可以接受到。
接下来演示有序广播,用上级领导给下农民一级一级的发大米的例子
可能中途被某一级官员贪污,或者修改反馈大米的数量,下面一起来看看
领导发送方:
MainActivity.java
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 点击按钮发送有序广播
public void onlick(View view) {
Intent intent = new Intent();
intent.setAction("myfadami");
// 第3个参数为最后的广播,如果为null则不接收
// 但是最后的广播也可能造假,可能中途被某一级修改并终止了
sendOrderedBroadcast(intent, null, new FinalReceiver(), null,
1, "上级领导给每个村民发1000斤大米", null);
}
}
关于
public void sendOrderedBroadcast (Intent intent,
String receiverPermission,
BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode,
String initialData,
Bundle initialExtras)
sendBroadcast(Intent)
的版本,允许您从广播中接收数据。这是通过在呼叫时提供自己的广播收发器来实现的,它将被视为广播结束时的最终接收器-ITSBroadcastReceiver.onReceive(Context, Intent)
方法将使用从其他接收方收集的结果值调用。广播将以与调用相同的方式序列化。sendOrderedBroadcast(Intent, String)
.
喜欢sendBroadcast(Intent)
,此方法是异步的;它将在调用结果tReceiver.onRecept()之前返回。
看见BroadcastReceiver
有关意图广播的更多信息。
参数 | |
---|---|
intent |
Intent: 广播的意图;所有符合这一意图的接收者都将收到广播。
|
receiverPermission |
String :字符串命名接收方接收广播时必须持有的权限。如果为NULL,则不需要任何权限。
|
resultReceiver |
BroadcastReceiver :您自己的广播收发器作为广播的最终接收方。
|
scheduler |
Handler :一个自定义处理程序,用于调度结果收发器回调;如果为NULL,则将在上下文的主线程中调度它。
|
initialCode |
int : 结果代码的初始值。通常是Activity.RESULT_OK。
|
initialData |
String :结果数据的初始值。通常是空的。
|
initialExtras |
Bundle :结果附加值的初始值。通常是空的。 |
FinalReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class FinalReceiver extends BroadcastReceiver { // 不用在清单文件配置
@Override
public void onReceive(Context context, Intent intent) {
// 获取发送广播携带的数据
String content = getResultData();
// 显示结果
Toast.makeText(context, "报告领导,接收到下级发送的反馈消息:\n" + content, Toast.LENGTH_SHORT).show();
}
}
接收方官员和农民的过程:
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onDestroy() { // 广播在该应用程序销毁后仍能接收
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
}
部署运行只有可直接返回退出,看到onDestroy之后广播消息仍然能够接收
省领导:
ProvinceReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class ProvinceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取发送广播携带的数据
String content = getResultData();
// 显示结果
Toast.makeText(context, "省:" + content, Toast.LENGTH_SHORT).show();
// 如果上级领导发的大米不想给下面的人,那么就直接终止广播
// abortBroadcast();
// 如果此时贪污300斤大米,再往下面发大米
// 修改收到的广播内容
setResultData("上级领导给每个村民发700斤大米");
}
}
市领导:
CityReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class CityReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取发送广播携带的数据
String content = getResultData();
// 显示结果
Toast.makeText(context, "市:" + content, Toast.LENGTH_SHORT).show();
// 如果上级领导发的大米不想给下面的人,那么就直接终止广播
// abortBroadcast();
// 如果此时贪污200斤大米,再往下面发大米
// 修改收到的广播内容
setResultData("上级领导给每个村民发500斤大米");
}
}
乡领导:
CountryReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class CountryReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取发送广播携带的数据
String content = getResultData();
// 显示结果
Toast.makeText(context, "乡:" + content, Toast.LENGTH_SHORT).show();
// 如果上级领导发的大米不想给下面的人,那么就直接终止广播
// abortBroadcast();
// 如果此时贪污200斤大米,再往下面发大米
// 修改收到的广播内容
setResultData("上级领导给每个村民发300斤大米");
}
}
农民:
FarmerReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class FarmerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取发送广播携带的数据
String content = getResultData();
// 显示结果
Toast.makeText(context, "农民:" + content, Toast.LENGTH_SHORT).show();
}
}
AndroidManifest.xml
运行结果如下:
如果此时省领导很大野心,想贪污1000斤大米,如下:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class ProvinceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取发送广播携带的数据
String content = getResultData();
// 显示结果
Toast.makeText(context, "省:" + content, Toast.LENGTH_SHORT).show();
// 如果上级领导发的大米不想给下面的人,那么就直接终止广播
abortBroadcast();
// 如果此时贪污1000斤大米
// 修改收到的广播内容
setResultData("上级领导给每个村民发1000斤大米");
}
}
直接终止广播,并且修改最后广播的内容,那么向上级反馈的就是1000斤大米,这样就成功的欺骗的上级
运行结果图:
自己发送的自定义广播可以根据是sendOrderedBroadcast还是sendBroadcast来判断是有序广播还是无序广播,系统广播我们可以用abortBroadcast来阻止接收广播测试,如果能够终止广播,那么说明是有序广播,反之则为无序广播。
本地广播:
不管是有序广播无序广播还是系统的广播都是全局性的广播,即发出的广播可以被其他任何应用程序接收到,并且我们也可以接受来自于其他任何应用程序的广播,这样就很容易引起安全问题,比如我们发送一些携带关键性数据的广播有可能被其他应用程序截获,或者其他应用程序向我们的广播接收器里发送各种垃圾广播。
1、本地广播:发送的广播事件不被其他应用程序获取,也不能响应其他应用程序发送的广播事件。本地广播只能被动态注册,不能静态注册。动态注册或方法时需要用到LocalBroadcastManager。
2、全局广播:发送的广播事件可被其他应用程序获取,也能响应其他应用程序发送的广播事件(可以通过 exported–是否监听其他应用程序发送的广播 在清单文件中控制) 全局广播既可以动态注册,也可以静态注册。
MainActivity.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private LocalBroadcastManager localBroadcastManager;
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
@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 v) {
Intent intent = new Intent("mylocalbroadcast");
localBroadcastManager.sendBroadcast(intent); // 发送本地广播
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("mylocalbroadcast");
localReceiver = new LocalReceiver();
// 注册本地广播监听器
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
}
}
}
运行结果:
另外还要说明,本地广播是无法通过静态注册方式来接收的,因为静态注册主要就是为了让程序在未启动的情况下也能接收到广播,而发送本地广播时,我们的程序已经启动了,因此也完全不需要使用静态注册的功能。
本地广播的优点:
1.可以明确的知道正在发送的广播不会离开我们的程序,因此完全不需要担心机密数据被泄露。
2.其他的程序无法将广播发送到我们程序内部,因此不需要担心会有安全漏洞的隐患。
3.发送本地广播会比发送系统全局广播更高效。
系统广播:
广播接收器可以自由的对自己感兴趣的广播进行注册,这样当有相应的广播发出时,广播接收器就能够接收到该广播,并在内部处理相应的逻辑。注册广播的方式一般有2种,在代码中注册和在AndroidManifest.xml中注册,前者被称为动态注册,后者被称为静态注册。
动态注册一般用于操作特别频繁的广播事件,比如屏幕的锁屏与解锁,电池电量的变化。
动态注册:
体验一下动态注册,屏幕的锁屏与解锁:
ScreenReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class ScreenReceiver extends BroadcastReceiver {
private static final String TAG = "ScreenReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if ("android.intent.action.SCREEN_OFF".equals(action)){
Log.d(TAG, "屏幕锁屏了");
} else if ("android.intent.action.SCREEN_ON".equals(action)){
Log.d(TAG, "屏幕解锁了");
}
}
}
MainActivity.java
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private ScreenReceiver screenReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 动态的注册广播接收者
screenReceiver = new ScreenReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.SCREEN_ON");
intentFilter.addAction("android.intent.action.SCREEN_OFF");
registerReceiver(screenReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 当activity销毁的时候要取消注册广播接收者
unregisterReceiver(screenReceiver);
}
}
如果是静态注册,那么需要在清单文件如下操作:
所以对应到动态注册就是:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.SCREEN_ON");
intentFilter.addAction("android.intent.action.SCREEN_OFF");
一定要记得,动态注册的广播接收器一定要取消注册才行,否则会报错
android.app.IntentReceiverLeaked: Activity com.example.dynamicregistrationbroadcastreceiver.MainActivity has leaked IntentReceiver com.example.dynamicregistrationbroadcastreceiver.ScreenReceiver@8bd6a7b that was originally registered here. Are you missing a call to unregisterReceiver()?
运行结果:
静态注册:
静态注册实现开机启动:
动态注册的广播接收器可以自由的控制与注销,在灵活性方面有很大的优势,但是它也存在一个缺点,就是必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate()中的,那么有什么办法可以让程序未启动的情况下就能接收到广播呢,那就是静态注册了。
BootReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
public class BootReceiver extends BroadcastReceiver {
private static final String TAG = "BootReceiver";
// 当手机重新启动的时候调用,经测试6.0以后此方法行不通
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "=================onReceive: ");
// 在这个方法里面开启activity
Intent intent1 = new Intent(context, MainActivity.class);
// ==========注意,不能在广播中直接开启activity,需要添加一个任务栈标记
// intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 开启activity
context.startActivity(intent1);
Toast.makeText(context, "开机自动启动成功", Toast.LENGTH_LONG).show();
}
}
==========注意,不能在广播中直接开启activity,需要添加一个任务栈标记
intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
其实这句话可以不加,但是需要早AndroidManifest中添加属性,继续往下看吧
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "==============onCreate: ");
}
}
AndroidManifest.xml
批注:
Exported属性表示是否允许这个广播接收器接收本程序以外的广播,Enabled属性表示是否启用这个广播接收器。
如果只有Exported属性而不加Enabled属性,则会报错,如下:E/MS_RegisterService: Exception getting GCM token
java.io.IOException: AUTHENTICATION_FAILED
如果只有Enabled属性而不加Exported属性,则会打开activity页面但是不会弹出Toast
两个属性都加上就没问题了,就不需要在广播接收器中添加intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
运行结果:
模拟器可以成功,遗憾的是真机不一定成功。具体解决方案参考这里:
Android 8.0 的开机广播 和 IntentService:https://www.jianshu.com/p/378819c21bde
注意:不要在onReceive()方法中添加过多的逻辑或进行任何耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive()方法进行了较长时间而没有结束时,程序就会报错。因此广播接收器更多的是扮演一种打开程序其他组件的角色。如创建一条状态栏通知,或者启动一个服务等。
广播实践----实现强制下线功能:
demo地址:https://github.com/liuchenyang0515/BroadcastBestPractice
运行效果图:
ActivityCollector.java
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
public class ActivityCollector {
public static List 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 activty : activities) {
if (!activty.isFinishing()){
activty.finish();
}
}
}
}
activity_login.xml
LoginActivity.java
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class LoginActivity extends BaseActivity {
private EditText passwordEdit;
private EditText accountEdit;
private Button login;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
accountEdit = (EditText) findViewById(R.id.account);
passwordEdit = (EditText) findViewById(R.id.password);
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();
// 只演示广播功能,用固定帐号admin密码123456
if (account.equals("admin") && password.equals("123456")) {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
} else {
Toast.makeText(LoginActivity.this, "account or password is invalid", Toast.LENGTH_SHORT).show();
}
}
});
}
}
activity_main.xml
MainActivity.java
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button forceOffline = (Button) findViewById(R.id.force_offline);
forceOffline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcastbestpractice.FORCE_OFFLINE");
sendBroadcast(intent);
}
});
}
}
BaseActivity.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class BaseActivity extends AppCompatActivity {
private ForceOfflineReceiver receiver;
private static final String TAG = "BaseActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcastbestpractice.FORCE_OFFLINE");
receiver = new ForceOfflineReceiver();
registerReceiver(receiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
class ForceOfflineReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
Log.d(TAG, "onReceive: ");
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Warning");
builder.setMessage("You are forced to be offline. Please try to login again");
builder.setCancelable(false);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll(); // 销毁所有活动
Intent intent1 = new Intent(context, LoginActivity.class);
context.startActivity(intent1); // 重新启动LoginActivity
}
});
builder.show();
}
}
}
批注:
注册ForceOfflineReceiver这个广播接收器时,这里重写了onResume()和onPause()这两个生命周期方法,然后在这两个方法中注册和取消注册了ForceOfflineReceiver。为什么呢?之前不是都在onCreate()和onDestroy()方法里的来注册和取消广播接收器的吗?这是因为我们始终需要保证只有处于栈顶的活动才能接收到这条强制下线广播,非栈顶的活动不应该也没有必要去接收这条广播,所以写在onResume()和onPause()方法里就可以很好的解决这个问题,当一个活动失去栈顶位置时,就会自动取消广播接收器的注册。
AndroidManifest.xml
==========================Talk is cheap, show me the code========================