Broadcast 广播
Android中的广播主要可以分为两种广播
- 标准广播
- 这是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息, 因此他们之间没有任何先后顺序。这种广播的效率会比较高,但同时也意味着他是无法被截断的。
- 有序广播
- 是一种同步执行的广播, 在广播发出之后同一时刻内只有一个广播接收器能够收到这个广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续向下传递,所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以阶段正在传递得广播,这样后面的广播接收器就无法收到广播消息了。
接收系统广播
Android中内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种的系统的状态信息,比如:手机在开完成后会发出一条广播,电池的电量发生变化时也会发出一条广播。时间或时区发生编发是也会发出一条广播,如果想要接收这些广播就要使用广播接收器。
广播的注册方式一般有两种
- 在代码中注册 (动态注册)
- 在AndroidManifest.xml中注册 (静态注册)
动态注册监听网络变化。
创建一个广播需要新建一个类,并让他继承自BroadcastReceiver,而且要重写父类的onReceive()方法就可以了。这样当有广播到来时 onReceive()方法就会得到执行。
修改MainActivity中的代码
public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private NetworkchangeReceiver networkchangeReceiver; class NetworkchangeReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { //要执行的代码 ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); if(networkInfo!=null && networkInfo.isAvailable()){ Toast.makeText(context, "网络可用", Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(context, "网络不可用", Toast.LENGTH_SHORT).show(); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentFilter = new IntentFilter(); intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); networkchangeReceiver = new NetworkchangeReceiver(); registerReceiver(networkchangeReceiver,intentFilter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(networkchangeReceiver); } }
在onReceive()方法中, 通过getSystemService()方法得到了ConnectivityManager的实例,这是一个系统服务类, 专门用于管理网络连接的, 然后调用他的getActiveNetworkInfo()方法可以得到NetworkInfo的实例,接着调用NetworkInfo的isAvailable()方法就可以判断出当前是否有网络了,最后通过Toast的方式对用户进行提示。
tip: Android系统为了保护用户设备的安全和隐私, 做了严格的规定,如果程序要进行一些对用户来说比较敏感的操作就必须在配置文件中声明权限才可以。否则程序将会直接崩溃。 访问系统的网络状态就是需要声明权限的。 打开AndroidManifest.xml文件在标签内加入以下权限就可以访问了。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
静态注册实现开机启动
动态注册的广播接收器可以自由度控制注册和注销,在灵活性方面有很大的有事,但是他也存在这一个缺点,就是必须在程序启动之后才能接收到广播,因为注册的逻辑实在onCreate()方法中的,如果要在程序未启动的情况下就能接受到广播就要使用静态注册的方式了。
我们准备让程序接收一条开机广播,当收到这条广播的时候就可以在onReceive()方法里执行相应的逻辑。从而实现开机启动的功能,可以使用AndroidStudio提供的快捷方式来创建一个广播接收器,new——>Other-->Broadcast Receiver, 会弹出一个窗口。我们直接将广播接收器命名为BootCompleteReceiver Exproted属性表示是否允许这个广播接收器接受本程序以外的广播,Enable属性表示是否启用这个广播接收器, 勾选这两个属性 然后点击Finish即可完成创建。
修改BootCompleteReceiver中的代码如下
public class BootCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "开机启动完毕", Toast.LENGTH_LONG).show(); } }
静态的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用, 由于使用的是Androidstudio 的快捷方式创建的所以注册这一步被自动完成了。
<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>
这样就可以完成开机启动了。
自定义广播
发送标准广播
在发送广播之前,我们需要先定义一个广播接收器来准备节后我们要发送的广播。
- 新建一个MyBroadcastReceiver 类 并且继承BroadcastReceiver
在Manifest.xml对这个广播接收器进行注册。并添加一个intent-filter
<receiver android:name=".MyBroadcastReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="cn.zbuter.BroadcastDemo.MY_BROADCAST"> </intent-filter> </receiver>
这里让MyBroadcastReceiver接收一条值为cn.zbuter.BroadcastDemo.MY_BROADCAST的广播。因此待会发送广播的时候,我们就要发出这样的一条广播。
接下来修改activity_main.xml中的内容。添加一个按钮
<Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Broadcast" />
4.然后在MainActivity中添加如下的代码,
public class MainActivity extends AppCompatActivity { @Override 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("cn.zbuter.mybroadcasttest.MY_BROADCAST"); // android 8.0以上的系统必须加上这句 参数1是自定义广播的包名,参数2是自定义广播的路径 或者使用动态注册广播 intent.setComponent(new ComponentName("cn.zbuter.mybroadcasttest","cn.zbuter.mybroadcasttest.MyReceiverTest")); sendBroadcast(intent); } }); } }
发送有序广播
- 在MainActivity中不再使用sendBroadcast(intent)方法 而是使用sendOrderedBroadcast(intent,null),的二个参数是一个与权限相关的字符串,一般传入null即可,
在AndroidManifest文件中的 receiver 标签内的intent-filter的属性设置 android:priority="100" 其中100代表优先级
<receiver android:name=".MyReceiverTest" android:enabled="true" android:exported="true"> <intent-filter android:priority="100"> <action android:name="cn.zbuter.mybroadcasttest.MY_BROADCAST" /> </intent-filter> </receiver>
优先级高的就会比优先级低的程序先接收到广播,而且高优先级的程序可以选择是否允许广播继续传递。 利用 abortBroadcast()方法即可截断广播的传播
使用本地广播
前面的广播全部数据全局广播, 即翻出的广播可以被其他任何程序接收到, 并且我们也可以接收到来自与任何程序的广播, 这样容易引起安全问题, 为了解决这一问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,而且,广播接收器也只能接受来自本应用程序发出的广播。
本地广播的用法并不复杂, 主要是使用了一个LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法,
修改MainActivity中的代码,
public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private LocalBroadcastManager localBroadcastManager; private LocalReceiver localReceiver; class LocalReceiver extends BroadcastReceiver{ public void onReceive(Context context, Intent intent) { Toast.makeText(context, "这是一条本地广播", Toast.LENGTH_SHORT).show(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button ) findViewById(R.id.button); localBroadcastManager = LocalBroadcastManager.getInstance(this); //获取实例 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("cn.zbuter.mybroadcasttest.MY_BROADCAST"); localBroadcastManager.sendBroadcast(intent); } }); intentFilter = new IntentFilter(); intentFilter.addAction("cn.zbuter.mybroadcasttest.MY_BROADCAST"); localReceiver = new LocalReceiver(); localBroadcastManager.registerReceiver(localReceiver, intentFilter); //注册本地广播监听器 } @Override protected void onDestroy() { super.onDestroy(); localBroadcastManager.unregisterReceiver(localReceiver); } }
广播的实践 -- 实现强制下线
先创建一个ActivityCollector类用来管理所有的活动。
public class ActivityCollector { // 用于管理所有Activity public static List<Activity> activities = new ArrayList<>(); public static void addActivity(Activity activity){//添加一个activity activities.add(activity); } public static void removeActivity(Activity activity){//去掉一个activity 当一个activity在栈顶移除时 activities.remove(activity); } public static void finishAllAcivity(){ // 结束所有activity for(Activity activity : activities){ if(!activity.isFinishing()){ activity.finish(); } } activities.clear(); // 将管理器中的所有元素清空。 } }
创建一个BaseActivity作为所有的活动的父类。
public class BaseActivity extends AppCompatActivity { UserOffLineReceiver userOffLineReceiver; @Override public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { super.onCreate(savedInstanceState, persistentState); ActivityCollector.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); ActivityCollector.removeActivity(this); } @Override protected void onResume() { // 当一个activity 恢复栈顶位置的时候注册广播。 super.onResume(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("cn.zbuter.BroadcastBestDemo.USER_OFFLINE"); userOffLineReceiver = new UserOffLineReceiver(); registerReceiver(userOffLineReceiver, intentFilter); } @Override protected void onPause() { super.onPause(); if(userOffLineReceiver != null){//当一个activity失去栈顶位置取消注册的广播 unregisterReceiver(userOffLineReceiver); userOffLineReceiver = null; } } class UserOffLineReceiver extends BroadcastReceiver{// 用户离线的广播接收器 @Override public void onReceive(final Context context, Intent intent) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle("提示:") .setMessage("你已经被强制下线,请重新登录") .setCancelable(false) .setPositiveButton("返回登录界面", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCollector.finishAllAcivity(); Intent intent = new Intent(context, LoginActivity.class); context.startActivity(intent); } }).show(); } } }
创建一个 activity_login.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:id="@+id/user_layout" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/txt_username" android:text="用户名:" android:gravity="center" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" /> <EditText android:id="@+id/et_username" android:hint="请输入用户名" android:layout_width="0dp" android:layout_weight="5" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:id="@+id/passwd_layout" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/txt_passwd" android:text="密 码:" android:gravity="center" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" /> <EditText android:id="@+id/et_passwd" android:hint="请输入密码" android:layout_width="0dp" android:layout_weight="5" android:layout_height="wrap_content" /> </LinearLayout> <Button android:id="@+id/btn_login" android:inputType="textPassword" android:text="登录" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
创建一个与之对应的LoginActivity 继承 BaseActivity
public class LoginActivity extends BaseActivity { private Button btn_login; private EditText et_username; private EditText et_passwd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); initView(); intiOnclick(); } public void intiOnclick(){ btn_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = et_username.getText().toString(); String passwd = et_passwd.getText().toString(); if("admin".equals(username)&& "123".equals(passwd)){ //用户密码正确进入到主界面 Intent intent = new Intent(LoginActivity.this,MainActivity.class); startActivity(intent); finish(); //关闭登录activity }else{ Toast.makeText(LoginActivity.this, "用户名或密码输入错误", Toast.LENGTH_SHORT).show(); } } }); } public void initView(){ btn_login = (Button) findViewById(R.id.btn_login); et_username = (EditText) findViewById(R.id.et_username); et_passwd = (EditText)findViewById(R.id.et_passwd); } }
修改activity_main.xml的布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_logout" android:text="强制下线" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
修改MainActivity
public class MainActivity extends BaseActivity { private Button btn_logout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initOnclick(); } public void initOnclick(){ btn_logout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("cn.zbuter.BroadcastBestDemo.USER_OFFLINE"); sendBroadcast(intent); } }); } public void initView(){ btn_logout = (Button )findViewById(R.id.btn_logout); } }
修改AndroidManifest中的代码 把主界面修改成activity_login
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.zbuter.broadcastbestdemo"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> </activity> <activity android:name=".LoginActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>