Android学习笔记——广播机制
来源:互联网 发布:mac find 查找文件夹 编辑:程序博客网 时间:2024/05/02 00:24
参考书籍:Android第一行代码(第二版).郭霖著
网络通信中,在一个IP网络范围内,最大的IP地址是被保留作为广播地址来使用的。如某网络IP范围是192.168.0.XXX,子网掩码是255.255.255.0,那这个网络的广播地址就是192.168.0.255.广播数据包会被发送到同一网络的所有端口,该网络中的每台主机都会收到这条广播。
Android也引入一套类似的广播消息机制,更加灵活。
1、广播机制
Android中每个应用程序都可对自己感兴趣的广播进行注册,这样只会接收关心的广播内容(来自与系统或其他应用程序)。Android提供了一套完整API让应用程序自由发送(Intent)和接受广播(广播接收器Broadcast Receiver)。
Android中广播主要分为标准广播(完全异步执行,所有广播接收器几乎同时接收,无先后顺序,效率高无法被截断)和有序广播(同步执行,发出后同时有一个广播接收器接收,其逻辑执行完后广播才继续传播,有先后顺序即优先级,可截断,前面的接收器截断,后面就无法接收)。
2、接收系统广播
Android中内置很多系统级别广播,可用来在应用程序中监听得到系统状态信息,如开机、电池电量变化、时间时区改变等都会发出一条广播。要接收就需使用广播接收器。
广播注册方式一般有两种:动态注册(在代码中注册)和静态注册(在AndroidManifest.xml中注册)。
(1)动态注册
创建一个广播接收器:新建一个类继承自BroadcastReceiver,并重写父类的onReceive()方法(当有广播到来时被执行,具体逻可在此处理)即可。
例:动态注册监听网络变化。新建一个BroadcastTest项目,修改主程序:
public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private NetworkChangReceiver networkChangReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentFilter = new IntentFilter(); intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); //网络状态发生变化时,系统发出一条android.net.conn.CONNECTIVITY_CHANGE广播 networkChangReceiver = new NetworkChangReceiver(); registerReceiver(networkChangReceiver,intentFilter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(networkChangReceiver);//动态注册的广播接收器一定要取消注册 } class NetworkChangReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) {//每当网络状态发生变化时被执行 Toast.makeText(context,"network changes", Toast.LENGTH_SHORT).show(); } }}
运行程序,按下Home键挂起,在Setting—>Data usage中打开/关闭Celluar data来启动/禁止网络,会看到Toast信息。
要能准确告诉用户有/无网络,需进一步优化:
class NetworkChangReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) {//每当网络状态发生变化时被执行 ConnectivityManager connectionManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); //ConnectivityManager为系统服务类,专门用于管理网络连接 NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();//得到NetworkInfo实例 if (networkInfo != null && networkInfo.isAvailable()){//判断是否有网络 Toast.makeText(context,"network is available", Toast.LENGTH_SHORT).show(); }else { Toast.makeText(context,"network is unavailable", Toast.LENGTH_SHORT).show(); } } }
Android系统为保护用户设备安全和隐私,做了严格规定:如果程序需要进行对用户来说比较敏感的操作,必须在配置文件中声明权限,否则程序将直接崩溃。访问系统网络状态需要声明权限,在AndroidManifest.xml中加入如下权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.jojo.broadcasttest"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> 。。。 </manifest>
重新运行程序测试。
(2)静态注册
动态注册灵活性大,但必须在程序启动后才能接收到广播(注册逻辑在onCreate()中)。要让程序在未启动状态下接收广播,需要静态注册。
例:接收开机广播。可使用Android Studio提供的快捷方式创建广播接收器,包->New->Other->Broadcast Receiver,命名即可(Exported表示是否允许接受本程序以外的广播,Enabled表示是否启用这个广播接收器)。修改其中代码:
public class BootCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // TODO: This method is called when the BroadcastReceiver is receiving // an Intent broadcast. Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show(); }}
静态广播接收器一定要在AndroidManifest.xml中注册,不过AS快捷方式创建的会自动完成注册。打开此文件可看到:
<receiver android:name=".BootCompleteReceiver" android:enabled="true" android:exported="true"></receiver>
标签为,与很相似。
目前还不能接收到开机广播,还需做如下修改:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>。。。 <receiver android:name=".BootCompleteReceiver" android:enabled="true" android:exported="true"> **<intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter>** </receiver> 。。。
监听系统开机广播需要权限。运行程序,将模拟器关闭重启后会收到开机广播。
注意:不要在onReceive()中添加过多逻辑/进行耗时操作,广播接收器中不允许开启线程,onReceive()运行较长时间没有结束就会报错。所以广播接收器一般用来打开程序其他组件(如创建状态栏通知、启动服务等)。
3、发送自定义广播
(1)发送标准广播
先定义广播接收器用于接收,新建MyBroadcastReceiver:
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show(); }}
在AndroidManifest.xml中对它进行修改:
<receiver android:name=".MyBroadcastReceiver" android:enabled="true" android:exported="true"> **<intent-filter> <action android:name="com.example.jojo.broadcasttest.MY_BROADCAST"/> </intent-filter>** </receiver>
修改activity_main.xml中代码,添加一个用于发送广播的按钮。再修改MainActivity中代码,给按钮绑定点击事件:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); **Button button = (Button)findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.jojo.broadcasttest.MY_BROADCAST"); sendBroadcast(intent);//发送标准广播 } });** 。。。
运行程序,点击按钮会弹出Toast信息。
(2)发送有序广播
广播时一种跨进程的通信方式,所以应用程序内发出的光波,其他应用程序也可以收到。
新建一个BroadcastTest2项目。在此项目下定义一个广播接收器,用于接收第一个项目中的自定义广播,新建AnotherBroadcastReceiver:
public class AnotherBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show(); }}
再注册信息中进行修改:
<receiver android:name=".AnotherBroadcastReceiver" android:enabled="true" android:exported="true"> **<intent-filter> <action android:name="com.example.jojo.broadcasttest.MY_BROADCAST"/> </intent-filter>** </receiver>
运行程序,然后重新回到BroadcastTest项目主界面,点击按钮,会分别弹出两次提示信息。
由此可见,应用程序发出的广播可以被其他应用程序接收到。
发送有序广播:
修改MainActivity:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.jojo.broadcasttest.MY_BROADCAST"); **sendOrderedBroadcast(intent,null);//发送有序广播,第二个参数是与权限相关的字符串** } });
重新运行程序,会发现跟之前效果一样。
但是这个时候广播接收器有先后顺序,前面的接收器可将广播截断阻止其继续传播。广播接收器的先后顺序在注册时进行设定:
<receiver android:name=".MyBroadcastReceiver" android:enabled="true" android:exported="true"> <intent-filter **android:priority="100"**> <action android:name="com.example.jojo.broadcasttest.MY_BROADCAST"/> </intent-filter> </receiver>
使用android:priority设置优先级,这里设置为100,保证它一定会在AnotherBroadcastReceiver之前收到广播。活得优先权的接收器可以选择是否允许广播继续传递。修改MyBroadcastReceiver:
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show(); **abortBroadcast();//截断广播** }}
重新运行程序发现,第二个广播接收器确实没收到广播。
4、本地广播
前面的广播全部属于系统广播(可被任意程序接收,可接收来自任意程序的广播),这容易引起安全问题。
为解决广播安全问题,Android引入一套本地广播机制(本地广播只在应用程序内部传递)。
本地广播主要使用一个LocalBroadcastManager进行管理(提供了发送广播和注册广播接收器的方法)。例,修改MainActivity:
public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private LocalReceiver localReceiver; private LocalBroadcastManager localBroadcastManager; @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("com.example.jojo.broadcasttest.LOCAL_BROADCAST"); localBroadcastManager.sendBroadcast(intent);//发送本地广播 } }); intentFilter = new IntentFilter(); intentFilter.addAction("com.example.jojo.broadcasttest.LOCAL_BROADCAST"); localReceiver = new LocalReceiver(); localBroadcastManager.registerReceiver(localReceiver,intentFilter);//注册本地广播监听器 } class LocalReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"received local broadcast", Toast.LENGTH_SHORT).show(); } }
跟之前的动态注册广播接收器及发送广播的方法类似,只不过用的是LocalBroadcastManager中的方法。
运行程序,点击按钮,会弹出Toast消息:
这时如果想在BroadcastTest2中接收这条广播是不行的。
本地广播无法通过静态注册方式接收,也不需要(发送本地广播时,程序肯定已经启动了)。
优点:不必担心泄露机密数据;其他程序无法将广播发送到我们程序内部,不需担心安全漏洞隐患;发送本地广播比系统全局广播更高效。
5、广播的最佳实践
实现强制下线功能:在界面上弹出一个对话框,让用户无法进行其他操作,必须点击确定按钮回到登录界面即可。
新建BroadcastBestPractice项目,强制下线功能需要先关闭所有的活动,然后回到登录界面。先创建一个ActivityCollector类管理所有活动:
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类作为所有活动的父类:
public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("BaseActivity",getClass().getSimpleName());//获取当前实例的类名并打印出来 ActivityCollector.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); ActivityCollector.removeActivity(this); }}
创建一个登录界面活动LoginActivity,编辑相应布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="60dp"> <TextView android:layout_width="90dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textSize="18sp" android:text="Account:"/> <EditText android:id="@+id/account" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical"/> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="60dp"> <TextView android:layout_width="90dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textSize="18sp" android:text="Password:"/> <EditText android:id="@+id/password" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" android:inputType="textPassword"/> </LinearLayout> <Button android:id="@+id/login" android:layout_width="match_parent" android:layout_height="60dp" android:text="Login"/></LinearLayout>
包含账号信息输入、密码信息输入和登录按钮。
修改LoginActivity:
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.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(); } } }); }}
MainActivity为登录成功后进入的主界面,加入强制下线功能即可,修改activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/force_offline" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Send force offline broadcast" android:textAllCaps="false"/></LinearLayout>
加了一个用于出发强制下线功能的按钮。修改MainActivity:
public class MainActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button)findViewById(R.id.force_offline); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.jojo.broadcastbestpractice.FORCE_OFFLINE");//用于通知程序用户下线的广播 sendBroadcast(intent); } }); }}
这里按钮点击事件里发送了一条广播,用于通知程序强制用户下线的,而强制下线的逻辑应该卸载广播接收器里(不依附于任何界面)。
广播接收器需要弹出一个对话框阻塞用户正常操作,但静态注册的广播接收器没有办法在OnReceive()方法里弹出对话框UI控件,也不可能在每个活动里注册动态广播接收器。只需在BaseActivity中动态注册广播接收器就可以了,修改BaseActivity:
public class BaseActivity extends AppCompatActivity { private ForceOfflineReceiver receiver; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("BaseActivity",getClass().getSimpleName());//获取当前实例的类名并打印出来 ActivityCollector.addActivity(this); } @Override protected void onResume() { super.onResume(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("com.example.jojo.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, Intent intent) { 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 intent = new Intent(context, LoginActivity.class); context.startActivity(intent);//重新启动登录活动 } }); builder.show(); } }}
这里是在onResume()和onPause()这两个方法中注册和取消注册广播接收器的,因为要始终保证只有处于栈顶的活动才能接收到这条广播,当活动失去栈顶位置时就会自动取消广播接收器的注册。
还需对AndroidManifest.xml进行修改,将主活动设置为LoginActivity而不是MainActivity:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.jojo.broadcastbestpractice"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> **<activity android:name=".LoginActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".MainActivity"></activity>** </application></manifest>
运行程序。
- Android学习笔记——广播机制
- Android学习笔记——广播机制
- Android学习笔记(十一)——广播机制
- android学习笔记---广播机制
- android广播机制学习笔记
- Android学习笔记-广播机制
- Android学习笔记-广播机制
- 【Android 学习笔记】 之 广播机制
- Mars Android视频学习笔记——01_21/22_广播机制
- Mars Android视频学习笔记——01_21/22_广播机制
- Android之广播机制—自定义广播
- Android之广播机制—有序广播
- Android之广播机制—本地广播
- Android学习--广播机制
- Android广播机制学习
- Android 学习 广播机制
- Android学习:广播机制
- android广播机制初步学习——短信黑名单
- POJ2185 (summer III N)
- “比特币现金”上线 一度大涨超200% 成市值第三高数字货币
- 使用FileUtils简化你的文件操作
- Java接口的游戏实战应用
- linux 修改数据库密码
- Android学习笔记——广播机制
- 编程语言理解
- 动态规划!!!动态规划!!!
- C编程之memcpy函数详解(附带几个例子)
- POJ1733 带权并查集+离散化
- Mat-深拷贝函数
- CC2640R2F BLE5.0 应用程序框架
- HDU6027(快速幂)(水)
- zoj 1203