====================================
====== 第五章:全局大喇叭 — 详解广播机制 ======
====================================
在一个IP网络范围中,最大的IP地址是被保留作为广播地址来使用的。比如某个网路的IP范围是192.168.0.XXX,子网掩码是255.255.255.0,那么这个网络的广播地址就是192.168.0.255 广播数据包会被发送到同一网络上的所有端口,这样在该网络中的每一台主机都将会收到这条广播。
为了便于进行系统级别的消息通知,Android也引入了一套类似的广播消息机制。Android中的广播机制会显得更加灵活。
5.1 广播机制简介
Android提供了一套完整的API,允许应用程序自由的发送和接受广播。发送广播接借助于之前我们提到过的Intent,而接受广播的方法则需要引入一个新的概念:广播接收器(Broadcast Receiver)
广播主要分为两类:标准广播和有序广播
广播是一种跨进程的通讯方式,在应用内发送广播,其他的应用程序也可以收到。
1、标准广播:一种完全异步执行的广播,广播发出之后,所有的广播接收器几乎都会同一时刻接收到这条广播消息,没有先后顺序科研。效率比较高,但也意味着无法被截断。
2、有序广播:同步执行的广播,广播发出后,同一时刻只会有一个广播接收器能收到。当这个广播接收器中的逻辑执行完毕之后,广播才会继续传递。优先级高的广播接收器先收到广播消息,而且前面的接收器还可以截断正在传播的广播,这样后面的接收器就收不到广播了。
5.2 接收系统广播
Android内置了很多系统级别的广播。
5.2.1 动态注册监听网络变化:
注册广播的方式一般有两种方式,1在代码中注册 2在AndroidManifest.xml中注册。1为动态注册,2为静态注册。
创建一个广播接收器:新建一个继承自BroadCastReceiver的类,并重写onReceive()方法即可,当有广播到来时,onReceive()方法会得到执行。
新建一个BroadCastTest项目,然后修改MainActivity中的代码:
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@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);
}
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Network changes", Toast.LENGTH_SHORT).show();
}
}
}
我们给IntentFilter的实例添加了一个值为android.net.conn.CONNECTIVITY_CHANGE的anction,为什么添加这个值呢,因为当网络状态发生变化时候,系统发出的正是一条android.net.conn.CONNECTIVITY_CHANGE的广播。也就是我们想要监听什么广播,就添加相应的action。
2、接下来我们收到网络变化之后,还要判断当前网络是否可用。继续修改onReceiver()中的代码。
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable()) {
// 网络可用
} else {
// 网络不可用
}
}
}
Android系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权限才可以,否则程序会崩溃。在AndroidManifest.xml中加入权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
…
</manifest>
Android6.0系统中加入了更加严格的运行时权限,这个将在第七章中学习。
5.2.2 静态注册实现开启启动。
动态注册的广播存在一个缺点,就是必须要程序启动之后才可以接收到广播,因为注册的逻辑是写在onCreate()方法中的。
现在我们用静态注册来接收一条开机广播。然后我们在收到这条广播的时候可以在onReceiver()方法里执行相应的逻辑,从而实现开机启动的功能。(使用Android Studio快捷方式来创建一个广播接收器),右键com.example.broadcasttest包 —> New —> Other —> Boradcast Receiver。
Exported属性表示是否允许这个广播接收器接收本程序以外的广播(选择yes),Enabled表示是否启用这个广播接收器(选择yes)
修改BootCompleteReceiver的代码:
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, “Boot Complete”, Toast.LENGTH_LONG).show();
}
}
静态的广播接收器一定要在AndroidMainifest.xml中注册才可以使用,不过由于我们是使用AndroidStudio的快捷方式创建的广播接收器,因为注册这一步已经被自动完成了。如下:
<mainfesh xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.broadcasttest” >
<uses-permission android:name=“android.permissioni.ACCESS_NETWORK_STATE” />
<application
android:allowBackup=“true”
android:icon=“@mipmap/ic_launcher”
android:label=“@string/app_name”
android:supportsRtl=“true”
android:theme=“style/AppTheme” >
。。。
<receiver
androdi:name=“.BootCompleteReceiver”
android:enabled=“true”
android:eported=“true” >
</receiver>
</application>
</manifesh>
所有的静态广播接收都是在<receiver>中注册,用法和<activity>标签非常相似,也是通过android:name来指定具体注册的是哪一个广播接收器。
我们还需要修改
<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>
由于系统会在启动完成之后发送一条值为android.intent.action.BOOT_COMPLETED的广播,因此我们需要receiver标签里面的intent-filter标签中添加对应的action。另外,监听系统开机广播也是需要声明权限的,
不要在收到广播的onReceive()方法中添加过多逻辑或进行耗时操作,因为在广播接收器中是不允许开启线程的。
5.3 发送自定义广播
5.3.1 发送标准广播
新建一个自定义的广播接收器:MyBroadcashReceiver继承自BroadcashReceiver并实现onReceiver方法。
public class MyBroadcashReceiver extends BroadcashReceiver {
@Override
public void onReceiver(Context context, Intent intent) {
// 简单弹出个toast
}
}
然后在Manifesh.xml中注册action
<intent-filter>
<actioin androdi:name=“com.example.broadcashtest.MY_BROADCASH” />
</intent-filter>
现在我们就是需要发送一条这样的广播:
新增一个按钮Button
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(“com.example.broadcashtest.MY_BROADCAST”);
sendBroadcash(intent);
}
});
先构建一个Intent,并指明广播名字,然后使用sendBroadcash()方法发送该intent即可。(在顶部就可以收到这个广播。类似于iOS中的通知)
5.3.2 发送有序广播
广播是一种跨进程的通讯方式,在应用内发送广播,其他的应用程序也可以收到。
发送有序广播只需修改
sendBroadcast修改为sendOrderedBroadcash(intent, null)即可。
其中第二个参数是与权限相关的字符串,传入null即可。
这时候,修改Manifesh.xml中接收广播的优先级,通过android:priority来设置数值
<intent-filter android:priority=“100”>
<action android:name=“com.example.broadcastbest.MY_BROADCAST” />
</intent-filter>
在接收到这条有序广播的地方,调用
abortBroadcash()方法,即可截断该广播,后面的接收器不再能接收到该广播。
5.4 本地广播(使用LocalBroadcastManager)
前面我们学的广播属于系统全局广播,任何程序都可以接收到。
具体做法:接收器还是继承自BroadcastReceiver,然后重写onReceive()方法。然后使用LocalBroadcastManager.getInstance(this);来创建一个lacalmanager,然后使用localManager.sendBroadcash()即可。具体代码如下:
public class MainActivity extends AppCompataActivity {
private IntentFilter intentFilter;
private LocalBroadcast localBroadcast;
private LocalBroadcastManager localBroadcastManger;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManger.getInstance(this); // 获取实例
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.onClickLinstener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(“com.example.broadcasttest.LOCAL_BROADCAST”);
localBroadcastManager.sendBroadcast(intent); // 发送本地通知
}
});
intentFilter = new IntentFilter();
intentFilter.addAction(“com.example.broadcasttest.LOCAL_BROADCAST”);
loaclReceiver = new LocalReceiver();
localBroadcastManger.registerReceiver(localReceiver, intentFilter); // 注册本地广播监听器
}
@Override
protect void onDestroy() {
super.onDestroy();
localBroadcastManger.unregisterReceiver(localReceiver);
}
class LocalReeiver extentd BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.xxx;
}
}
}
注意:本地通知是无法通过静态注册的方式来接收的。
本地通知的几点优势:
1、可以明确的知道正在发送的通知不会离开我们的程序,不用担心机密数据泄露
2、更加高效
5.5 广播的最佳实践 —— 实现强制下线功能
原理很简单,在接收到强制下线的通知时候弹出一个警告框,然后不允许用户进行其他操作,点击确定按钮,回到登陆页面即可。
现在我们创建一个BroadcastBestPractice项目。
强制下线需要先关闭所有活动的功能。
1、先新建一个ActivityController类。
public class ActivityController {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity) {
activities.add(activity);
}
public static void removeActivity(Activity activity) {
activities.remove(ativity);
}
public static void finishAll {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
2、然后创建一个BaseActivity作为所有活动的父类
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityController.addActivity(this);
}
@Override
protected void onDestoy() {
super.onDestroy();
ActivityController.removeActivity(this);
}
}
3、创建一个登陆界面的活动。activity_login.xml:输入账号: 输入密码:登录按钮
这里我们使用LinearLayout编写了一个登陆布局,最外层是一个纵向的LinearLayout,里面包含3行直接子元素。第一行是一个横向的LinearLayout,用于输入账号信息。第二行也是一个横向的LinearLayout,用于输入密码信息。第三行是一个登陆按钮,
4、接下来修改LoginActivity代码:
public class LoginActivity extends BaseActivity {
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.id.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 view) {
String accountText = accountEdit.getText().toString();
String passworText = passwordEdit.getText().toString();
// 如果账号是admin,其密码是123456,就认为登陆成功
if (accountText.equals(“admin”) && passworText.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();
}
}
});
}
}
5、修改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=“match_parent”
android:text=“Send force offline broadcast” />
</LinearLayout>
6、修改MainActivity中的代码
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(savedInstance);
setContentView(R.layout.activity_main);
Button forceOffline = (Button) findViewById(R.id.force_offline);
forceOffline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(“com.example.broadcastbestpractice.FORCE_OFFLINE”);
sendBroadacst(intent);
}
});
}
}
这里只是发了一个广播,广播的值是com.example.broadcastbestpractice.FORCE_OFFLINE;通知下线的逻辑应该写在广播接收器里面。(广播接收器里面需要弹出一个对话框来阻塞用户的正常操作)只需要在BaseActivity中动态注册一个广播接收器就可以了。
7、修改BaseActivity代码:
public class BaseActivity extends AppCompatActivity {
private ForceOffLineReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityController.addActivity(this);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(“com.example.broadcastpractice.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();
ActivityController.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 offlline, Please try to login agin”);
builder.setCancelable(false);
builder.setPositiveButton(“OK”, new DialogInterface.OnClickListener() {
@Override
public void onClick(View view) {
AcvitigyController.finishAll(); // 销毁所有活动
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent); // 重新启动LoginActivity;
}
});
builder.show();
}
}
}
通过AlertDialog.Builder来构建一个对话框,一定要调用setCancelable()方法将对话框设置为不可取消,否则用户按一下Back就能继续使用程序了。然后使用setPositiveButton()来给对话框注册确定按钮,当用户点击了确定按钮的时候调用ActivitController的finishAll方法来销毁所有活动。
8、最后需要修改AndroidManifesh.xml文件:
<mainfest xmlns:android=“http://schemas.android.com/apl/res/android”
package=“com.example.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=“.MainActivity”>
</activity>
<activity android:name=“.LoginActivity”>
<intent-filter>
<action android:name=“android.intent.action.Main” />
<category android:naem=“android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
5.6 Git — 初始版本控制工具
1、配置git的身份
git config — global user.name ‘Tony’
git config — global user.email ‘tony@gmail.com’
2、创建一个git仓库
git init
仓库创建成功之后,会在项目的跟目录中生成一个隐藏的.git文件夹(如果要删除仓库,只有删除这个文件夹即可)
3、查看所有文件
ls -al
5.6.3 提交本地代码
add操作可以把想要提交的代码先添加进来
commit则是真正去执行提交操作
git add build.gradle // 添加某个文件
git add app // 添加某个目录
git add . // 一次性添加所有文件
git commit -m “First commit” // 在commit后面添加-m参数来加上提交的描述信息,没有描述信息被认为是不合法的。