目录:
知识点:
5.1 广播机制简介
Android中的广播主要分为两类:标准广播和有序广播。
标准广播:是一种完全异步执行的广播,发出后所有广播接收器几乎同时接收到这条广播,该类型广播无法被截断。
有序广播:是一种同步执行的广播,在广播发出后同一时刻只有一个广播接收器能够接收到这条广播消息。优先级高的广播接收器先收到广播消息,并能截断正在传递的广播。
5.2 接收系统广播
Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。若想要接收到这些广播,就需要使用广播接收器。
5.2.1 动态注册监听网络变化
新建一个内部类继承BroadcastReceiver 重写onReceive 方法,当这样有广播来时,onReceive方法就会得到执行
注意不要忘记加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
同时,因为我们是动态注册,所以也需要在onDestory 方法中
unregisterReceiver(recevier);
package com.dak.administrator.firstcode.broadcast_recevier;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
import com.dak.administrator.firstcode.R;
public class NetWorkActivity extends AppCompatActivity {
NetworkChangeRecevier recevier;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_net_work);
recevier = new NetworkChangeRecevier();
IntentFilter filter = new IntentFilter();
filter.addAction("android.NET.conn.CONNECTIVITY_CHANGE");
registerReceiver(recevier, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(recevier);
}
class NetworkChangeRecevier extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (manager.getActiveNetworkInfo() != null && manager.getActiveNetworkInfo().isAvailable()) {
Toast.makeText(NetWorkActivity.this, "网不错 兄dei", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(NetWorkActivity.this, "没网了 兄dei", Toast.LENGTH_SHORT).show();
}
}
}
}
}
5.2.2 静态注册实现开机启动
动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但它也存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在 onCreate()方法中的。使用静态注册的方式就可以让程序在未启动的情况下接收到广播。
这里我们准备让程序接受一条开机广播
新建BordercastReceiver
package com.dak.administrator.firstcode.broadcast_recevier;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class BootCompleteReceiver extends BroadcastReceiver {
private static List<Callback> callBack = new ArrayList<Callback>();
private static final Object CALLBACK_LOCK = new Object();
public interface Callback {
void onReceive(Intent intent);
}
public static void registerCallback(Callback callback) {
synchronized (CALLBACK_LOCK) {
callBack.add(callback);
}
}
public static void unRegisterCallback(Callback callback) {
synchronized (CALLBACK_LOCK) {
callBack.remove(callback);
}
}
@Override
public void onReceive(Context context, Intent intent) {
// TODO: This method is called when the BroadcastReceiver is receiving
// an Intent broadcast.
Toast.makeText(context, "开机了", Toast.LENGTH_SHORT).show();
callBack(intent);
}
private static void callBack(Intent intent) {
synchronized (CALLBACK_LOCK) {
for (Callback callback : callBack) {
if (callback != null) {
callback.onReceive(intent);
}
}
}
}
}
如果你是手动创建的需要在AndroidManifest.xml 中添加receiver
当然必不可少的是:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name=".broadcast_recevier.BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!-- 开机启动 -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
5.3 发送自定义广播
接下来学习如何在应用程序中发送自定义广播,通过实践的方式来看一下标准广播和有序广播的区别。
5.3.1 发送标准广播
新建一个广播接收器
package com.dak.administrator.firstcode.broadcast_recevier;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
/**
* Created by Administrator on 2018/9/30.
*/
public class MyBroadcastReceiver extends BroadcastReceiver {
public static final String MY_ACTION = "my_action";
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
// abortBroadcast(); 将广播截断
}
public static void sendBroadcast(Context context,Intent intent){
intent.setAction(MY_ACTION);
context.sendBroadcast(intent);
}
}
而后在AndroidManifest.xml中添加receiver
<receiver
android:name=".broadcast_recevier.MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!-- 自定义广播 -->
<action android:name="com.example,broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
在Activity中去发送这条广播
package com.dak.administrator.firstcode.broadcast_recevier;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.dak.administrator.firstcode.R;
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
public void send(View view) {
MyBroadcastReceiver.sendBroadcast(this,new Intent());
}
}
这样就完成了发送自定义广播的功能。另外,由于广播是使用 Intent 进行传递的,因此你还可以在 Intent 中携带一些数据传递给广播接收器。
5.3.2 发送有序广播
只需要 发送的时候调用 context.sendOrderedBroadcast(intent, ""); 就可以了。
另外我们可以在intent-filter 中添加 android:priority="100" 设置优先级,数值越大,优先级就高。
范围是[-1000, 1000]
在onReceive()中调用abortBroadcast ()方法,表示将这条广播截断
5.4 使用本地广播
本地广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播。本地广播的用法并不复杂,主要使用了一个 LocalBroadcastManager 来对广播进行管理,并提供了发送广播和注册广播接收器的方法。
package com.dak.administrator.firstcode.broadcast_recevier;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.dak.administrator.firstcode.R;
public class LoaclActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalBroadcastManager receiverManager;
private LocalReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loacl);
intentFilter = new IntentFilter();
intentFilter.addAction("com.Local_Borad");
receiver = new LocalReceiver();
receiverManager = LocalBroadcastManager.getInstance(this);
receiverManager.registerReceiver(receiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
receiverManager.unregisterReceiver(receiver);
}
class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
}
}
}
5.5 广播最佳实践——实现强制下线功能
又到了实战环节了。此次的需求是:强制下线后在界面上弹出一个对话框,让用户无法进行任何其他操作,必须要点击对话框中的确定按钮, 然后回到登录界面。
借助本次所学的广播知识,可以轻松实现这一功能。
首先,创建一个 ActivityCollector 类用于管理所有的活动:
public class ActivityCollector {
// 通过一个List来缓存活动
public static List<Activity> activities = new ArrayList<Activity>();
/**
* 用于向List中添加一个活动
* @param activity
*/
public static void addActivity(Activity activity) {
activities.add(activity);
}
/**
* 用于从List中移除活动
* @param activity
*/
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
/**
* 将List中存储的活动全部销毁掉
*/
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
然后创建 BaseActivity 类作为所有活动的父类(忽略一些不必要的内容,主要看ActivityCollector 相关的):
public abstract class BaseActivity extends AppCompatActivity {
protected Context mContext;
protected Unbinder mBinder;
/**
* 初始化布局id
* @return 布局id
*/
protected abstract int initLayoutId();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(initLayoutId());
Log.d("BaseActivity", getClass().getSimpleName());// 知晓当前是在哪一个活动
ActivityCollector.addActivity(this);// 将当前正在创建的活动添加到活动管理期里
mContext = this;
mBinder = ButterKnife.bind(this);
}
@Override
protected void onDestroy() {
// 取消绑定
mBinder.unbind();
super.onDestroy();
// 将一个马上要销毁的活动从管理器里移除
ActivityCollector.removeActivity(this);
}
}
接下来创建一个登录界面,新建 LoginActivity,编辑 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: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:gravity="center"
android:textSize="18sp"
android:text="账号:"/>
<EditText
android:id="@+id/et_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:gravity="center"
android:textSize="18sp"
android:text="密码:"/>
<EditText
android:id="@+id/et_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="10dp"
android:text="登录"/>
</LinearLayout>
接着修改 LoginActivity 中的代码:
public class LoginActivity extends BaseActivity {
private EditText et_account, et_password;
private Button btn_login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
et_account = (EditText) findViewById(R.id.et_account);
et_password = (EditText) findViewById(R.id.et_password);
btn_login = (Button) findViewById(R.id.btn_login);
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String account = et_account.getText().toString();
String password = et_password.getText().toString();
// 若账号是 wonderful 且密码是 123456,就认为登录成功
if (account.equals("wonderful") && password.equals("123456")){
// 登录成功跳转到主界面
IntentUtils.myIntent(LoginActivity.this,ForceOfflineActivity.class);
finish();
}else {
ToastUtils.showShort("账号或密码无效!");
}
}
});
}
@Override
protected int initLayoutId() {
return R.layout.activity_login;
}
}
登录成功后跳转到 ForceOfflineActivity 主界面,在主界面加入一个强制下线的功能即可,其布局 activity_force_offline.xml 代码为:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<Button
android:id="@+id/btn_force_offline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送强制下线广播"/>
</RelativeLayout>
只有一个按钮,用于触发强制下线功能,然后修改 ForceOfflineActivity 中的代码:
public class ForceOfflineActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button btn_force_offline = (Button) findViewById(R.id.btn_force_offline);
btn_force_offline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.wonderful.myfirstcode.FORCE_OFFLINE");
// 发送强制下线广播
sendBroadcast(intent);
}
});
}
@Override
protected int initLayoutId() {
return R.layout.activity_force_offline;
}
}
上述代码,在按钮的点击事件里面发送了一条广播,广播的值为 com.wonderful.myfirstcode.FORCE_OFFLINE,这条广播就是用于通知程序强制用户下线的。也就是说强制用户下线的逻辑并不是写在 ForceOfflineActivity 里的,而是写在接收这条广播的广播接收器里面,这样强制下线的功能就不会依附于任何的界面,不管是在程序的任何地方,只需要发出这样一条广播,就可以完成强制下线的操作了。
毫无疑问,要在 BaseActivity 中动态注册一个广播接收器,因为所有的活动都继承 BaseActivity 的,修改 BaseActivity 中的代码,如下:
public abstract class BaseActivity extends AppCompatActivity {
protected Context mContext;
protected Unbinder mBinder;
private ForceOfflineReceiver receiver;
/**
* 初始化布局id
* @return 布局id
*/
protected abstract int initLayoutId();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(initLayoutId());
Log.d("BaseActivity", getClass().getSimpleName());// 知晓当前是在哪一个活动
ActivityCollector.addActivity(this);// 将当前正在创建的活动添加到活动管理期里
mContext = this;
mBinder = ButterKnife.bind(this);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.wonderful.myfirstcode.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() {
// 取消绑定
mBinder.unbind();
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("警告");
builder.setMessage("你已被下线,请重新登录");
// 设置不可取消
builder.setCancelable(false);
// 设置点击事件
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// 销毁所有活动
ActivityCollector.finishAll();
// 重新启动 LoginActivity
IntentUtils.myIntent(context, LoginActivity.class);
}
});
builder.show();
}
}
}
上述代码中,重写了 onResume() 和 onPause() 这两个生命周期的函数,然后分别在这两个方法里注册和取消注册了 ForceOfflineReceiver,之所以这样写而不是在 onCreate() 和 onDestroy() 方法里注册和取消注册是因为我们始终需要保证只有处于栈顶的活动才能接收到下线广播,非栈顶活动不必要接收这条广播,写在 onResume() 和 onPause() 方法里很好的解决了这个问题,当活动失去栈顶位置时就会自动取消广播接收器的注册。
好了,现在去尝试一下代码吧。
5.6 Git时间——初始版本控制工具
5.6.1 安装Git
5.6.2 创建代码仓库
5.6.3 提交本地代码
关于Git 相关的内容请移驾:
https://blog.csdn.net/lhk147852369/article/details/84307580
5.7 小结与点评
郭霖总结:
本章中我们主要是对Android的广播机制进行了深人的研究,不仅了解了广播的理论知识,还掌握了接收广播、发送自定义广播以及本地广播的使用方法。广播接收器属于Android 四大组件之一,在不知不觉中你已经掌握了四大组件中的两个了。
在最佳实践环节中你一定也收获了不少,不仅运用到了本章所学的广播知识,还将前面章节所学到的技巧综合运用到了一起。经过这个例子之后,相信你对所涉及的每个知识点都有了更深一层的认识。 另外,本章还添加了一个最最特殊的环节,即Git 时间。在这个环节中,我们对Git这个版本控制工具进行了初步的学习,后面还会学习关于它的更多内容。
下一章我们本应该继续学习Android四大组件中的内容提供器,不过由于学习内容提供器之前需要先掌握Android中的持久化技术,因此下一章我们就先对这一主题展开讨论。
我的总结:
不是我说,你若是四大组件 不好好学的话,趁早转行吧。。。