5.1 广播机制简介
Android中的每个应用程序都可以对感兴趣的广播进行注册,这样程序就会只接收到自己所关心的广播内容,这些广播可能是来自与系统的,也可能是来自于其他应用程序的。Android提供了一套完整的API,允许应用程序自由地发送和接收广播。发送广播的方法是借助Intent,接收广播的方法需要引入一个新的概念 ---- Broadcast Receiver。
Android中的广播主要可以分为两种类型:标准广播和有序广播。
- 标准广播(Normal broadcasts)是一种完全异步执行的广播,在广播发出后,所有的Broad-castReceiver 几乎都会在同一时刻接收到这条广播消息,因为它们之间没有任何先后顺序可言。这种广播效率会比较高,但同时也意味着它是无法被截断的。
- 有序广播(Ordered broadcasts)是一种同步执行的广播,在广播发出后,同一时刻只会有一个BroadcastReceiver能够收到这条广播消息,当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。优先级高的BroadcastReceiver可以先收到广播消息,并且前面的BroadcastReceiver还可以截断正在传递的广播,这样后面的BroadcastReceiver就无法收到广播消息了。
5.2 接收系统广播
Android内置了很多系统级别的广播,可以在应用程序中通过监听这些广播得到各种系统的状态信息。比如手机开机完成后会发出一条广播,电池电量发生变化会发出一条广播,系统时间发生改变也会发出一条广播,等等。如果想要接收到这些广播,就需要使用BroadcastReceiver。
5.2.1 动态注册监听网络变化
可以根据自己感兴趣的广播,自由地注册BroadcastReceiver,当有相应的广播发出时,相应的BroadcastReceiver就能够收到该广播,并在内部进行逻辑处理。注册BroadcastReceiver的方式一般有两种,在代码中注册(动态注册)和在AndroidManifest.xml中注册(静态注册)。
创建广播接收器,需新建一个类,继承自BroadcastReceiver,并重写父类的onReceive()方法。下面先通过动态注册的方式编写一个能够监听时间变化的程序,借此学习BroadcastReceiver的基本用法。新建一个BroadcastTest项目,然后修改MainActivity中的代码,如下:
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private TimeChangeReceiver timeChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.TIME_TICK");
timeChangeReceiver = new TimeChangeReceiver();
registerReceiver(timeChangeReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(timeChangeReceiver);
}
class TimeChangeReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show();
}
}
}
在MainActivity中定义了一个内部类TimeChangeReceiver,这个类继承自BroadcastReceiv-er,并重写父类的onReceive()方法。这样每当系统时间发生变化时,onReceive()方法就会得到执行。
onCreate()方法中,首先创建一个IntentFilter的实例,并给它添加了一个值为android.inte-nt.action.TIME_TICK的action,这是系统时间发生变化时,系统发出的广播值,也就是Broadcas-tReceiver想要监听什么广播,就在这里添加相应的action。接下来创建了一个TimeChangeRecei-ver的实例,然后调用registerReceiver()方法进行注册,将TimeChangeReceiver的实例和IntentFil-ter的实例都传进去,这样TimeChangeReceiver就会收到所有值为android.intent.action.TIME_TIC-K的广播,也就实现了监听系统时间变化的功能。
动态注册的广播接收器一定要取消注册,这里是在onDestory()方法中通过调用unregisterRe-ceiver()方法实现的。运行程序,等待时间发生变化,系统每隔一分钟就会发出一条android.inten-t.action.TIME_TICK的广播,最多只需等待一分钟就会看到Toast提醒时间发生改变。
这就是动态注册BroadcastReceiver的基本用法,虽然这里只是使用了一种系统广播来举例,但接受其他系统广播的用法是一模一样的。Android系统还会在亮屏熄屏、电量变化、网络变化等场景下发出广播。如果想查看完整的系统广播列表,可以到如下路径中查看:
<Android SDK>/platforms/<任意 android api 版本>/data/broadcast_actions.txt
5.2.2 静态注册实现开机启动
动态注册的广播接收器虽然在灵活性方面有很大的优势,但是存在一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate()方法中的。而静态注册可以让程序在未启动的情况下就能接收到广播。
从理论上来说,动态注册能监听到的系统广播,静态注册也能监听到。但由于大量恶意的应用程序利用这个机制在程序未启动的情况下监听系统广播,从而使任何应用都可以频繁地从后台被唤醒,严重影响了用户手机的电量和性能,因此Android系统几乎每个版本都在削减静态注册Broa-dcastReceiver的功能。
在Android 8.0 系统之后,所有隐式广播都不允许使用静态注册的方式接收。隐式 广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。这些特殊的系统广播列表详见:
https://developer.android.google.cn/guide/components/broadcast-exceptions.html
在这些特殊的系统广播中,有一条值为android.intent.action.BOOT_COMPLETED的广播,这是一条开机广播,下面用它来举例。
这里准备实现一个开机启动的功能,当收到开机广播时就可以在onReceive()方法里执行相应的逻辑,从而实现开机启动的功能。创建BroadcastReceiver除了上面内部类的方式,还可以使用Android Studio 提供的快捷方式创建,右击com.example.broadcasttest包 ---- New ---- Other ---- Broadcast Receiver,会弹出如图所示的窗口:
这里将创建的类命名为BootComleteReceiver,Exported属性表示是否允许这个BroadcastRe-ceiver接收本程序以外的广播,Enable属性表示是否启用这个BroadcastReceiver。勾选这两个属性,点击Finish创建。然后修改BootCompleteReceiver中的代码,在onReceive()方法中使用Toast弹出一段提示信息:
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_LONG).show();
}
}
另外,静态的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用,不过由于是使用Android Studio 的快捷方式创建的广播接收器,因此注册这一步已经被自动完成了。打开AndroidManifest.xml文件,代码如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<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/Theme.BroadcastTest">
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
</receiver>
...
</application>
</manifest>
可以看到,<application>标签内出现了一个新的标签<receiver>,所有静态的广播接收器都是在这里进行注册的。它的用法和<activity>类似。
现在的BootCompleteReceiver是无法收到开机广播的,因为还需要对AndroidManifest.xml文件进行修改,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<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/Theme.BroadcastTest">
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
...
</application>
</manifest>
由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_COMPLETED的广播,因此在<receiver>标签中又添加了一个<intent-filter>标签,并在里面声明了相应的action。
需要注意的是,Android系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,必须在AndroidManifest.xml文件中进行权限声明,否则程序会直接崩溃。比如这里接收系统的开机广播就需要进行权限声明,所以使用<uses-permission>标签声明了android.permission.RECEIVE_BOOT_COMPLETED权限。
重新运行程序,重启设备,启动完成后就会收到开机广播(未成功显示,原因待定)。其中,可以在BroadcastReceiver的onReceive()方法中编写逻辑,但不要添加过多的逻辑或进行任何耗时的操作,因为Broadcast-Receiver中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会出现错误。
5.3 发送自定义广播
5.3.1 发送标准广播
在发送广播之前,需先定义一个BroadcastReceiver来准备接收此广播。新建一个MyBroad-castReceiver,并在onReceive()方法中加入如下代码:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
}
}
当MyBroadcastReceiver收到自定义的广播时,就会弹出“received in MyBroadcastReceiver”的提示。然后在AndroidManifest.xml中对这个广播接收器进行修改:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
...
<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/Theme.BroadcastTest">
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
...
</application>
</manifest>
这里让MyBroadcastReceiver接收一条值为com.example.broadcasttest.MY_BROADCAST的广播,因此待会儿发送广播的时候,就需要发出这样一条广播。接下来修改activity_main.xml中的代码,在布局文件中定义一个按钮,用于作为发送广播的触发点(代码略)。然后修改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("com.example.broadcast.MY_BROADCAST");
intent.setPackage(getPackageName());
sendBroadcast(intent);
}
});
...
}
...
}
在按钮的点击事件里面加入了发送自定义广播的逻辑。首先构建一个Intent对象,并把要发送的广播的值传入。然后调用Intent的setPackage()方法,并传入当前应用程序的包名。最后调用sendBroadcast()方法将广播发送出去,这样所有监听com.example.broadcasttest.MY_BROAD-CAST这条广播的BroadcastReceiver就会收到消息,此时发出的广播是一条标准广播。
其中,第2步调用了setPackage()方法。前面说过,在Android8.0系统之后,静态注册的BroadcastReceiver是无法接收隐式广播的,而默认情况下,发出的自定义广播都是隐式广播。因此这里一定要调用setPackage()方法,指定这条广播是发送给哪个应用程序的,从而让它变成一条显式广播,否则静态注册的BroadcastReceiver将无法接收到这条广播。
重新运行程序,并点击“Send Broadcast”按钮,显示提示信息,成功完成发送自定义广播的功能。另外,可在Intent中携带一些数据传递给相应的BroadcastReceiver,和Activity类似。
5.3.2 发送有序广播
有序广播是一种同步执行的广播,是可以被截断的。再创建一个BroadcastReceiver,新建AnotherBroadcastReceiver,代码如下:
public class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in AnotherBroadcastReceived", Toast.LENGTH_SHORT).show();
}
}
然后在AndroidManifest.xml中对这个BroadcastReceiver的配置进行修改,代码如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
...
<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/Theme.BroadcastTest">
<receiver
android:name=".AnotherBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
...
</application>
</manifest>
重新运行程序,并点击“Send Broadcast”按钮,会分别弹出两次提示信息。
现在尝试发送有序广播,重新回到BroadcastTest项目,然后修改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("com.example.broadcasttest.MY_BROADCAST");
intent.setPackage(getPackageName());
sendOrderedBroadcast(intent,null);
}
});
...
}
...
}
发送有序广播只需要改动一行代码,将sendBroadcast()方法改成sendOrderedBroadcast()方法。sendOrderedBroadcast()方法接收两个参数:第一参数是Intent;第二个参数是一个与权限相关的字符串。现在重新运行程序,并点击“sendBroadcast”按钮,两个BroadcastReceiver仍然可以收到这条广播。这个时候的BroadcastReceiver是有先后顺序的,而且前面的BroadcastReceiver可以将广播截断,以阻止其继续传播。
在注册的时候可以设定BroadcastReceiver的先后顺序,修改AndroidManifest.xml中的代码:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
...
<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/Theme.BroadcastTest">
...
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter
android:priority="100">
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
...
</application>
</manifest>
可以看到,通过android:priority属性给BroadcastReceiver设置了优先级,优先级比较高的BroadcastReceiver就可以先收到广播,这里将MyBroadcastReceiver的优先级设置成了100,以保证它一定会在AnotherBroadcastReceiver之前收到广播。
既然已经获得了接收广播的优先权,那么MyBroadcastReceiver就可以选择是否允许广播继续传递。修改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();
}
}
如果在onReceiver()方法中调用了abortBroadcast()方法,就表示将这条广播截断,后面的BroadcastReceiver将无法接收到这条广播。现在重新运行程序,并点击“Send Broadcast”按钮,会发现只有MyBroadcastReceiver中的Toast信息能够弹出,说明这条广播经过MyBroadcastRecei-ver之后确实是终止传递了。
5.4 使用本地广播(第3版书删除该内容)
为了解决上述系统全局广播的安全性问题,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.broadcasttest.LOCAL_BROADCAST");
intent.setPackage(getPackageName());
localBroadcastManager.sendBroadcast(intent); //发送本地广播
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
//注册本地广播监听器
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
}
}
}
这基本上和动态注册BroadcastReceiver以及发送广播的代码是一样的。只不过现在首先是通过LocalBroadcastManager的getInstance()方法得到了它的一个实例,然后在注册BroadcastRe-ceiver的时候调用的是LocalBroadcastManager的registerReceiver()方法,在发送广播时调用的是LocalBroadcastReceiver的sendBroadcast()方法。重新运行程序,LocalReceiver成功接收到这条本地广播,这条广播只会在BroadcastTest程序内传播。
需要说明的是,在发送本地广播时,程序肯定已经启动了,因此本地广播是无法通过静态注册的方式接收的。
本地广播的优势:
- 可以明确地知道正在发送的广播不会离开程序,因此不必担心机密数据泄露
- 其他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患
- 发送本地广播比发送系统全局广播将会更加高效。
5.5 广播的最佳实践:实现强制下线功能(真机调试未成功)
实现强制下线的思路:只需界面上弹出一个对话框,让用户无法进行任何操作,必须点击对话框中的“确定”按钮,然后回到登陆界面即可。
存在问题:当用户被通知强制下线时,可能正处于任何一个界面,每个界面都编写弹对话框逻辑?
解决方法:借助广播实现
新建一个BroadcastBestPractice项目。强制下线功能功能需要先关闭所有的Activity的功能,前面章节已经实现过了,这里用同样的方案。先创建一个ActivityCollector类用于管理所有的Activity,代码如下:
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类作为所有activity的父类,代码如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
以上代码都是前面出现过的。接下来,首先创建一个LoginActivity作为登陆界面,自动生成布局文件,然后编辑布局文件activity_login.xml,代码如下:
<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(@Nullable 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();
}
}
});
}
}
这里模拟了一个简单的登录功能。首先要将LoginActivity的继承结构改成继承自BaseActivity ,然后调用findViewById()方法获取账号输入框、密码输入框以及登录按钮的实例,接着在登录按钮的点击事件里对输入账号和密码进行判断。
可以将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"/>
</LinearLayout>
只有一个按钮,用于触发强制下线功能。然后修改MainActivity中的代码,如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button forceOffline = (Button) findViewById(R.id.force_offline);
forceOffline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcastbestpractice.FORCE_OFFLINE");
sendBroadcast(intent);
}
});
}
}
在按钮的点击事件里发送了一条广播,这条广播就是用于通知程序强制用户下线的。也就是说强制用户下线的逻辑并不是写在MainActivity里的,而应该写在接收这条广播的BroadcastRece-iver里,这样强制下线的功能就不会依附于任何的界面,不管是在程序的任何地方,只要发出这样一条广播,就可以完成强制下线的操作。
接下来需创建一个BroadcastReceiver接收这条强制下线广播。只需在BaseActivity中动态注册一个BroadcastReceiver,因为所有Activity都继承自BaseActivity。修改BaseActvity中的代码:
public class BaseActivity extends AppCompatActivity {
private ForceOfflineReceiver receiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.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(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); //重新启动LoginActivity
}
});
builder.show();
}
}
}
先看ForceOfflineReceiver中的代码,首先使用AlertDialog.Builder构建一个对话框,注意这里一定要调用setCancelable()方法将对话框设为不可取消,否则一按Back键就可以关闭对话框继续使用程序了。然后使用setPositiveButton()方法给对话框注册确定按钮,当点击确定按钮时,就调用ActivityCollector的finishAll()方法销毁所有活动,并重新启动LoginActivity。
之前都是在onCreate()和onDestroy()方法里注册和取消注册广播接收器的,这里需要保证只有处于栈顶的activity才能接收到这条强制下线广播,非栈顶的activity不应该也没必要去接收。所以重写onResume()和onPause()这两个生命周期函数,然后分别在这两个方法里注册和取消注册了ForceOfflineReceiver。
所有强制下线的逻辑已经完成了,接下来修改AndroidManifest.xml文件,代码如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcastbestpractice">
<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/Theme.BroadcastBestPractice">
<activity
android:name=".ui.login.LoginActivity"
android:exported="true"
android:label="@string/title_activity_login">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
</application>
</manifest>
这里将主activity设置为LoginActivity,因为用户登录后才进入到程序主界面。
5.6 Git ---- 版本控制工具
Git是一个开源的分布式版本控制工具,Git被开发出来的初衷是为了更好地管理Linux内核。
5.6.1 安装Git
由于Git和Linux操作系统是同一个作者,Git在Linux上安装是最简单方便的。比如使用的是Ubuntu系统,只需打开终端界面,输入命令sudo apt -get install git,按下回车后输入密码,即可完成Git的安装。
Mac系统类似,如果已经安装了Homebrew,只需在终端中输入命令brew install git即可。
Windows系统相对麻烦一些,需要先访问Git for Windows官网(https://gitforwindows.org/),可看到下图页面,点击“Download”按钮即可下载,下载完成后安装,之后一直点“下一步”就可完成。
5.6.2 创建代码仓库
尝试通过命令使用Git。如果使用的是Linux或Mac系统,就先打开终端界面;如果使用的是Windows系统,就从“开始”里找到Git Bash并打开。
首先配置身份,这样在提交代码时,Git就知道是谁提交的。命令如下所示:
配置完成后,可使用同样的命令查看是否配置成功,如下图:
然后可以开始创建代码仓库,仓库(repository)是用于保存版本管理所需信息的地方,所有本地提交的代码都会被提交到代码仓库中,如果有需要还可推送到远程仓库中。
这里尝试给BroadcastBestPractice项目建立一个代码仓库,先进入BroadcastBestPractice项目的目录下,然后在这个目录下面输入如下命令:git init
仓库创建完成后,会在BroadcastBestPractice项目的根目录下生成一个隐藏的.git目录,这个目录就是用来记录本地所有的Git操作的,可以通过ls -al命令查看一下,如下图:
如果想删除本地仓库,只需要删除这个目录就行。
5.6.3 提交本地代码
代码仓库建立完之后就可以提交代码了,只需使用add和commit命令。add用于把想要提交的代码先添加进来,而commit则是执行提交操作。比如:
git add build.gradle //添加单个文件的方法
git add app //添加整个app目录下的所有文件
git add . //添加所有文件
现在BroadcastBestPractice项目下的所有文件都已经添加好了,输入如下命令提交:
git commit -m "First commit"
注意,在commit命令的后面,一定要通过-m参数来加上提交的描述信息,没有描述信息的提交被认为是不合法的。