这一章的知识点比较简单。主要介绍了Android
的广播机制,具体知识点有广播的注册方式(分两种:在内部类代码中注册,称为动态注册;在AndroidMainfest.xml
中注册,称为静态注册)、发送标准广播、发送有序广播、使用本地广播等。
广播是一种可以跨进程的通信发方式。标准广播完全异步执行,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此没有任何先后顺序 ;而有序广播则是同步执行,在广播发出之后,同一时刻只有一个广播接收器能够收到这条广播消息,当这个广播接收器的逻辑执行完毕后,广播才会继续传递。这是广播是有先后顺序的,优先级较高的广播接收器先收到消息,并且前面的广播接收器还可以截断正在传递的广播,广播被截断后,后面的接收器就不能再接受。
本地广播区别于系统全局广播,系统全局广播发出的广播可以被其他任意的应用程序接收到,并且也可以接收来自于其他任何引用程序的广播。这样就很容易因其安全性问题,比如我们发送的一些携带关键性数据的广播有可能被其他的应用程序截获,或者其他的程序也可以不同的向我们的广播接收器里发送各种垃圾广播。而为了解决这个问题,Android
引入了一套本地广播机制,使用这个机制发出的广播只能在应用程序内部进行传递,并且广播接收器也只能接收来自本应用程序的发出的广播。
在这里我主要就我的理解,整理出静态注册和本地广播这两块内容。
静态注册
Broadcast
所谓静态注册,就是修改AndroidManifest.xml
文件,我们只需要在<application>
标签添加<receiver>
标签即可,代码如下
<receiver android:name=".BootCompleteReceiver">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
这条广播用来监听系统开机,因此要声明权限,在AndroidManifest.xml
文件中添加<uses-permission>
标签,如下
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
本地广播
LocalBroadcast
其实前面已经提到,本地广播主要是在应用程序的内部发送与接收广播消息的,依照书中的例子—实现强制下线功能—来说明,对于这样一个功能我们会想到,当你的程序收到一个强制下线的通知后,会在当前界面弹出一个对话框,让你无法进行其他操作,给出一段提示信息:你已被迫下线,请重新登陆!然后你点击“确定”按钮跳转到登录界面。
那么按照上面的思路,我们需要有一个登录界面LoginActivity
、一个登录之后的逻辑功能界面MainActivity
、然后就是控制程序强制下线的逻辑实现,包括一个ActivityCollector
、一个BaseActivity
、以及一个负责传递强制下线消息的ForceOfflineReceiver
广播接收器。
下面我们来看控制程序强制下线的逻辑实现,包括一个ActivityCollector
、一个BaseActivity
。代码实现如下
package com.example.broadcastbestpractice;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<Activity>();
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();
}
}
}
}
上面是ActivityCollector
,封装了控制程序界面Activity
的添加、移除、全部结束等操作逻辑。下面的BaseActivity
作为程序所有Activity
的父类,继承自Activity
,实现了最基的onCreate()
、onDestory()
两个方法。
package com.example.broadcastbestpractice;
import android.app.Activity;
import android.os.Bundle;
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
接下来是登录界面的布局文件login.xml
,如下
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="1" >
<TableRow >
<TextView
android:layout_height="wrap_content"
android:text="@string/account"
/>
<EditText
android:id="@+id/account"
android:layout_height="wrap_content"
android:hint="@string/input"
/>
</TableRow>
<TableRow >
<TextView
android:layout_height="wrap_content"
android:text="@string/password"
/>
<EditText
android:id="@+id/password"
android:layout_height="wrap_content"
android:inputType="textPassword"
/>
</TableRow>
<TableRow >
<Button
android:id="@+id/login"
android:layout_height="wrap_content"
android:layout_span="2"
android:text="@string/login"
/>
</TableRow>
</TableLayout>
接下来新建LoginActivity
继承自BaseActivity
,如下
package com.example.broadcastbestpractice;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class LoginActivity extends BaseActivity {
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login);
accountEdit = (EditText) findViewById(R.id.account);
passwordEdit = (EditText) findViewById(R.id.password);
login = (Button) findViewById(R.id.login);
login.setOnClickListener(new 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_LONG).show();
}
}
});
}
}
首先使用setContentView(R.layout.login)
加载主布局,然后为按钮点击事件加入处理逻辑,我们默认的登录账号密码分别是:admin
、123456
,登陆成功就跳转到MainActivity
界面。下面是主布局文件activity_main.xml
如下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<Button
android:id="@+id/force_offline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/send"
/>
</RelativeLayout>
接下来修改MainActivity
代码如下
package com.example.broadcastbestpractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
@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 OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.examole.broadcastbestpractice.FORCE_OFFLINE");
sendBroadcast(intent);
}
});
}
}
在按钮点击事件中发送了一条广播,广播内容是"com.examole.broadcastbestpractice.FORCE_OFFLINE"
,用于通知用户强制下线。代码如下
Intent intent = new Intent("com.examole.broadcastbestpractice.FORCE_OFFLINE");
sendBroadcast(intent);
到这里你会发现,我们的强制下线逻辑并没写在MainActivity
中,而是写在专门接收这条广播的接收器中,这样强制下线功能就不会依附于任何界面而是独立出来,不管在什么地方,只要接受到这条广播,就执行强制下线逻辑。接下来创建一个ForceOfflineReceiver
继承自BroadcastReceiver
,代码如下
package com.example.broadcastbestpractice;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.view.WindowManager;
public class ForceOfflineReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
dialogBuilder.setMessage("you are forced to be offline. please try to login again.");
dialogBuilder.setCancelable(false); // 将对话框设置为不可取消
dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll(); // 销毁所有活动
Intent intent = new Intent(context, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); // 重新启动LoginActivity
}
});
AlertDialog alertDialog = dialogBuilder.create();
// 需要设置AlertDialog的类型,保证在广播接受器中可以正常弹出
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alertDialog.show();
}
}
值得注意,这里使用AlertDialog.Builder
来构建一个强制下线的对话框,一定要调用dialogBuilder.setCancelable(false)
方法将对话框设置为不可取消,否则只要按一下返回键就可以退出对话框了。然后用setPositiveButton("OK", new DialogInterface.OnClickListener()
方法为对话框注册“确定”按钮,当点击“确定”按钮后,就调用ActivityCollector
的finishAll()
方法销毁所有Activity
,并重新启动进入登录界面。另外,由于我们的广播是在广播接收器中启动Activity
的,因此一定要用alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
给Intent
加入TYPE_SYSTEM_ALERT
这个标志,否则它将无法在广播接收器里弹出。最后,我们来配置AndroidManifest.xml
文件,如下
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcastbestpractice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.SYATEM_ALERT_WINDOW" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".LoginActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" >
</activity>
<receiver android:name=".ForceOfflineReceiver" >
<intent-filter>
<action android:name="com.example.broadcastbestpractice.FORCE_OFFLINE" />
</intent-filter>
</receiver>
</application>
</manifest>
需要注意,因为我们在ForceOfflineReceiver
弹出了一个系统级别的对话框,因此必须要声明 <uses-permission android:name="android.permission.SYATEM_ALERT_WINDOW" />
权限。然后对LoginActivity
进行注册,并将其指定为主Activity
,接着注册ForceOfflineReceiver
,并用<action android:name="com.example.broadcastbestpractice.FORCE_OFFLINE" />
指定它接收"com.example.broadcastbestpractice.FORCE_OFFLINE"
这条广播消息。