Android开发学习 -- Day12 广播实践 -- 强制下
关于广播的简单实用,相信我们已经通过上面的学习掌握了。今天来进行实践操作,写一个具备强制下线的功能的demo。
关于强制下线功能,我们一定不会感到陌生,例如当我们在别处登陆同一账号时,当前的账号就会被强制挤下线。实现的思路也不难,就是在任意一个界面,弹出一个不可取消的对话框,必须要点击对话框中的确定按钮才行,点击确定后回到登陆页面。
强制下线功能要去我们关掉所有Activity,并回到登陆Activity。我们在最早学习Activity的时候,曾经在添加过类似的管理Activity功能,让我们回顾一下,创建一个ActivityCollector类,然后在BaseActivity中盗用添加和删除操作。
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();
}
}
}
}
public class BaseActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseAvtivity", getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
我们直接使用现成的代码就可以了,接下来创建一个登陆的界面LoginActivity,修改布局文件。关于布局的部分就不再讲解了,都是我们之前熟悉的和用过的控件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.johnhao.listviewdemo.activity.LoginActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="用户名"
android:textSize="20sp"/>
<EditText
android:id="@+id/edit_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:hint="用户名试试 tester"
android:textSize="18sp"
android:maxLength="20"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="密 码"
android:textSize="20sp"/>
<EditText
android:id="@+id/edit_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:hint="登陆密码试试 123456"
android:textSize="18sp"
android:maxLength="20"/>
</LinearLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="10dp"
android:background="#ffff00"
android:text="Login"
android:textAllCaps="false"
android:textColor="#000000"/>
<ImageView
android:src="@drawable/qrcode"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"/>
</LinearLayout>

修改LoginActivity代码:
public class LoginActivity extends BaseActivity {
private Button btn;
private EditText editUserName;
private EditText editserPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
setTitle("登陆");
btn = findViewById(R.id.btn_login);
editUserName = findViewById(R.id.edit_name);
editserPassword = findViewById(R.id.edit_password);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userName = editUserName.getText().toString();
String userPassword = editserPassword.getText().toString();
if (userName.equals("tester") && userPassword.equals("123456")) {
// 登陆成功
Intent intent = new Intent(LoginActivity.this, AfterLoginActivity.class);
startActivity(intent);
finish();
} else if (userName.equals("") || userPassword.equals("")) {
// 用户名或密码为空
Toast.makeText(LoginActivity.this, "请输入用户名或者密码", Toast.LENGTH_SHORT).show();
} else {
// 用户名或密码不符合
Toast.makeText(LoginActivity.this, "请输入正确的用户名和密码", Toast.LENGTH_SHORT).show();
editUserName.setText("");
editserPassword.setText("");
}
}
});
}
}
这里我们模拟了登陆的功能,让LoginActivity继承自BaseActivity,通过getText()方法获得用户输入的用户名和密码,并进行了一个简单的验证,当用户名为tester密码为123456时,成功登录到AfterLoginActivity,否则弹对应的Toast提示。
AfterLoginActivity我们暂时不用设置太多东西,一个TextView和一个用来强制下线的Button就够了。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.johnhao.listviewdemo.activity.AfterLoginActivity">
<TextView
android:text="哈哈,居然登陆成功了"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_gravity="center"/>
<Button
android:id="@+id/btn_offline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="强制下线"
android:layout_gravity="center"
android:layout_marginTop="30dp"
android:textColor="#ff3300"/>
</LinearLayout>
修改AfterLoginActivity代码:
public class AfterLoginActivity extends BaseActivity {
private Button btn_offline;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_after_login);
setTitle("登陆后的页面");
btn_offline = findViewById(R.id.btn_offline);
btn_offline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("btn_offline", "onClick: ");
Intent intent = new Intent("com.johnhao.listviewdemo.FORCE_OFFLINE");
sendBroadcast(intent);
}
});
}
}
我们给Button设置一个点击事件,点击Button后,就会发一个com.johnhao.listviewdemo.FORCE_OFFLINE的广播。接下来我们就要写强制下线的广播接收器了。那么我们应该在哪里创建这个接收器呢?首先,我们需要在接收这条广播是弹出一个阻塞式的对话框,这样就不能使用静态注册的方式来注册广播;另外,由于该弹窗要求在任何界面上都能弹出,所以我们又不能在每一个Activity内都注册一个动态的广播接收器。所以,最佳的方案是在BaseActivity中动态注册一个广播接收器,因为所有的Activity都继承自BaseActivity。
public class BaseActivity extends AppCompatActivity{
private OfflineBroadcastReceiver offlineBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseAvtivity", getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
class OfflineBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
Log.d("Offline", "onReceive: ");
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle("警告");
dialog.setMessage("账号在美国洛杉矶地区另一台设备上登陆,您已被强制下线。如果非本人操作,请尽快修改登陆密码");
dialog.setCancelable(false);
dialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll();
Intent intent = new Intent(context, LoginActivity.class);
startActivity(intent);
}
});
dialog.show();
}
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.johnhao.listviewdemo.FORCE_OFFLINE");
offlineBroadcastReceiver = new OfflineBroadcastReceiver();
registerReceiver(offlineBroadcastReceiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
if (offlineBroadcastReceiver != null) {
unregisterReceiver(offlineBroadcastReceiver);
offlineBroadcastReceiver = null;
}
}
}
我们创建了一个内部类OfflineBroadcastReceiver,并继承自BroadcastReceiver,然后在onReceive()方法中,弹出了一个标准的对话框。关于对话框,我们在学习基本控件的时候已经了解过了,这里调用了setCancelable(false)方法来指定对话框不可取消,只能点击确定按钮。在确定按钮的点击事件中,我们调用了ActivityCollector管理类中的finishAll()方法来结束销毁的Activity,并重启LoginActivity。
对于OfflineBroadcastReceiver,我们是在onResume()方法中来注册的,并且在onPause()中取消注册。这是因为,我们只需要在栈顶的Activity能接收到这条强制下线的广播即可,非栈顶的Activity没有必要接收到它,所以动态注册和取消的逻辑写在了onResume()和onPause()中。
例子很简单,重新运行下程序:
