====================================
====== 第五章:全局大喇叭 — 详解广播机制 ======
====================================
在一个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的快捷方式创建的广播接收器,因为注册这一步已经被自动完成了。如下:
package=“com.example.broadcasttest” > android:allowBackup=“true” android:icon=“@mipmap/ic_launcher” android:label=“@string/app_name” android:supportsRtl=“true” android:theme=“style/AppTheme” > 。。。 androdi:name=“.BootCompleteReceiver” android:enabled=“true” android:eported=“true” > 所有的静态广播接收都是在 我们还需要修改 android:name=“.BootCompleteReceiver” android:enabled=“true” android:exported=“true” > 由于系统会在启动完成之后发送一条值为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 现在我们就是需要发送一条这样的广播: 新增一个按钮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来设置数值 在接收到这条有序广播的地方,调用 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 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代码,只需要加入强制下线功能即可 android:orientation=“vertical” android:layout_width=“match_parent” android:layout_height=“match_parent” > 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文件: package=“com.example.broadcastbestpractice” > android:allowBackup=“true” android:icon=“@mipmap/ic_launcher” android:label=“@string/app_name” android:supportsRtl=“true” android:theme=“@style/AppTheme” > 5.6 Git — 初始版本控制工具 1、配置git的身份 git config — global user.name ‘Tony’ git config — global user.email ‘[email protected]’ 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参数来加上提交的描述信息,没有描述信息被认为是不合法的。