您的位置:首页 > 其它

广播的巧妙利用——仿QQ实现强制下线功能

2017-04-17 15:10 951 查看

利用广播实现强制下线功能

强制下线功能大家应该都知道,很多程序都有此功能,比如QQ,微信,微博等。意思是你的账号在别处登录,就会强制你下线,如图所示





其实实现强制下线功能的思路也比较简单,只需要在界面上弹出一个对话框,让用户无法进行任何其他操作,必须要点击对话框中的确定按钮,才能返回到登录界面,但是存在这样一个问题,我们被通知需要强制下线可能处在任何一个界面,那么我们需要在每个界面上都写一个弹出对话框的逻辑吗?实际并不是,要是那样我们真的写不起啊!

那么现在看一下我们的思路,强制下线功能首先需要关掉所有的Activity,然后回到登录界面,这里我们就需要用到Java的特点:封装。

首先我们需要新建一个类作为所有Activity的父类,这种类我们叫他基类(BaseActivity),在这个类里写一个内部类管理所有Activity,叫ActivityController

package com.example.czy.forceofflinedemo;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
* Created by CZY on 2017/4/17.
*/

public abstract class BaseActivity extends AppCompatActivity {

//定义一个内部类对象
private ActivityController activityController;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//绑定布局
setContentView(bindLayout());
//初始化组件
initView();
//初始化数据
initData();
//创建出管理所有Activity类的对象
activityController = new ActivityController();
//将所有的Activity添加进来
activityController.addActivity(this);
}

@Override
protected void onDestroy() {
//在这个生命周期中销毁所有的Activity
activityController.removeActivity(this);
super.onDestroy();
}

/**
* 绑定布局
*
* @return
*/
protected abstract int bindLayout();

/**
* 初始化组件
*/
protected abstract void initView();

/**
* 初始化数据
*/
protected abstract void initData();

/**
* 绑定组件
*
* @param resId 组件id
* @param <T>   强制转换类型
* @return
*/
protected <T extends View> T bindView(int resId) {
return (T) findViewById(resId);
}

/**
* 管理所有Activity的类
*/
private class ActivityController {

private List<Activity> activities = new ArrayList<>();

/**
* 添加Activity
*
* @param activity
*/
private void addActivity(Activity activity) {
activities.add(activity);
}

/**
* 移除Activity
*
* @param activity
*/
private void removeActivity(Activity activity) {
activities.remove(activity);
}

/**
* 结束所有Activity
*/
private void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}

}

}


以上就是Activity基类的代码,接下来我们要写布局文件了,创建一个登录的Activity,名字叫做LoginActivity,布局文件activity_login.xml就不说了,是一个很简易的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="用户名"
android:textColor="#000000"
android:textSize="16sp" />

<EditText
android:id="@+id/name_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="6"
android:hint="请输入用户名" />

</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="密    码"
android:textColor="#000000"
android:textSize="16sp" />

<EditText
android:id="@+id/password_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="6"
android:hint="请输入密码" />

</LinearLayout>

<Button
android:id="@+id/login_btn"
android:layout_width="match_parent"
android:layout_height="wrap_co
4000
ntent"
android:text="登录" />

</LinearLayout>


接下来我们修改LoginActivity中的代码

package com.example.czy.forceofflinedemo;

import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

/**
* Created by CZY on 2017/4/17.
*/

public class LoginActivity extends BaseActivity {

private EditText nameEt, passwordEt;
private Button loginBtn;

@Override
protected int bindLayout() {
return R.layout.activity_login;
}

@Override
protected void initView() {

nameEt = bindView(R.id.name_et);
passwordEt = bindView(R.id.password_et);
loginBtn = bindView(R.id.login_btn);

}

@Override
protected void initData() {
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取用户名
String name = String.valueOf(nameEt.getText());
//获取密码
String password = String.valueOf(passwordEt.getText());
//如果用户名为user并且密码为123456,就认为登录成功
if (name.equals("user") && password.equals("123456")) {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
} else {
Toast.makeText(LoginActivity.this, "用户名或密码错误,请重新输入", Toast.LENGTH_SHORT).show();
}
}
});
}

}


通过模拟一个简单的登录功能,首先将LoginActivity继承BaseActivity,然后调用findViewById()分别获取到用户名输入框、密码输入框、登录按钮的对象,在登录按钮里对输入的用户名和密码做判断,如果输入的用户名admin和密码123456一致,就认为登录成功,否则失败。登录成功后跳转到MainActivity,这里就认为我们跳转到了主界面,那么我们就在这里进行强制下线功能。

因为要将LoginActivity设置为启动Activity,所以还需要在AndroidManifest.xml中将起设置为启动Activity

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.czy.forceofflinedemo">

<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/AppTheme">

<activity android:name=".MainActivity" />
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


将activity_main.xml进行修改

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:gravity="center"
tools:context="com.example.czy.forceofflinedemo.MainActivity">

<Button
android:id="@+id/force_offline_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="发送广播" />

</LinearLayout>


这里我们就添加一个按钮来触发强制下线功能,修改MainActivty中的代码

package com.example.czy.forceofflinedemo;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends BaseActivity {

private Button forceOfflineBtn;

@Override
protected int bindLayout() {
return R.layout.activity_main;
}

@Override
protected void initView() {
forceOfflineBtn = bindView(R.id.force_offline_btn);
}

@Override
protected void initData() {
forceOfflineBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//发送广播
Intent intent = new Intent("forceOffline");
sendBroadcast(intent);
}
});
}

}


那么在这里有个重点,我们在按钮的点击事件里发送了一条广播,值为forceOffline,这条广播就是用于通知程序强制用户下线的,也就是说强制下线的逻辑并不是写在MainActivity里的,而是应该写在接收这条广播的广播接收器里面,这样强制下线的功能就不会依附于任何界面,不论在程序的任何地方,只需要发出这样一条广播就可以完成强制下线功能了。

那么接下来我们就要创建一个广播接收器来接收这条广播,由于广播接收器里面需要弹出一个对话框来阻止用户的正常操作,如果创建静态广播接收器 的话是没办法在onReceive()里弹出对话框这样的控件的,所以要创建一个动态广播接收器,但是我们并不知道用户是在什么时候,哪个界面中被强制下线,又不能在每个Activity中都去写一个动态广播接收器,那么该怎么办呢?

答案很简单,不要忘记我们前面写的那个基类BaseActivity,我们只需要在这里写一个动态广播接收器就可以了,因为所有的Activity都是继承这个BaseActivity这个类的。

修改BaseActivity中的代码(这次的代码包含了之前里面写的代码,也就是全部代码)

package com.example.czy.forceofflinedemo;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
* Created by CZY on 2017/4/5.
*/

public abstract class BaseActivity extends AppCompatActivity {

private ActivityController activityController;
private ForceOfflineReceiver receiver;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//绑定布局
setContentView(bindLayout());
//初始化组件
initView();
//初始化数据
initData();
//创建出管理所有Activity类的对象
activityController = new ActivityController();

activityController.addActivity(this);
}

@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter("forceOffline");
receiver = new ForceOfflineReceiver();
registerReceiver(receiver, intentFilter);
}

@Override
protected void onPause() {
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
super.onPause();
}

@Override
protected void onDestroy() {
activityController.removeActivity(this);
super.onDestroy();
}

/**
* 绑定布局
*
* @return
*/
protected abstract int bindLayout();

/**
* 初始化组件
*/
protected abstract void initView();

/**
* 初始化数据
*/
protected abstract void initData();

/**
* 绑定组件
*
* @param resId 组件id
* @param <T>   强制转换类型
* @return
*/
protected <T extends View> T bindView(int resId) {
return (T) findViewById(resId);
}

/**
* 管理所有Activity的类
*/
private class ActivityController {

private List<Activity> activities = new ArrayList<>();

/**
* 添加Activity
*
* @param activity
*/
private void addActivity(Activity activity) {
activities.add(activity);
}

/**
* 移除Activity
*
* @param activity
*/
private void removeActivity(Activity activity) {
activities.remove(activity);
}

/**
* 结束所有Activity
*/
private void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}

}

/**
* 广播接收器
*/
private class ForceOfflineReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {

AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("下线通知");
builder.setMessage("您的账号在另一地点登录,您被迫下线了。如果这不是您本人的操作,那么您的密码可能已经泄露,建议您修改密码。");
builder.setPositiveButton("重新登录", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(context, LoginActivity.class);
startActivity(intent);
//调用结束所有Activity的方法
activityController.finishAll();
}
});
builder.setNegativeButton("退出", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
activityController.finishAll();
}
});
builder.setCancelable(false);
builder.show();
}
}

}


先看ForceOfflineReceiver这个内部类,这就是动态广播接收器,我们将弹出对话框的操作写在了这个类的onReceive()里,用AlertDialog.Builder来建造一个弹窗,注意这里一定要调用这个方法builder.setCancelable(),参数设置为false,否则用户按下系统返回键就可以将对话框关闭继续使用程序了。然后调用setPositiveButton()设置点击按钮重新登录跳转到登录页并且销毁所有的Activity。setNegativeButton()设置点击按钮退出程序(销毁所有Activity也就是退出程序)。

那么我们看一下是怎么注册这个广播接收器的,我们重写了Activity的两个方法onResume()和onPause()这两个生命周期,分别在这两个生命周期中注册和注销广播接收器,那为什么要这么写呢?原来不是都在onCreate()和onDestroy()这两个生命周期里去注册和注销广播接收器吗。这是因为我们始终需要保证只有处于栈顶的Activity才能接收到这条广播,非栈顶的Activity没必要去接收这条广播,,当一个Activity失去栈顶位置时就会自动注销该广播接收器所以在onResume()和onPause()这两个生命周期中注册和注销该广播。

到这里强制下线的所有的代码就都写完了。接下来我们运行一下看看效果

这是启动时显示的登录页



我们输入用户名user和密码123456



接下来点击登录跳转到主页



然后点击发送广播,弹出对话框,这时我们点击系统返回键或旁边空白处都是无效的



如果点击重新登录就跳转到登录页,如果点击退出,那么程序就退掉了



希望本次内容能给你带来收获,代码下载请点击 –> 项目源代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  广播 QQ 强制下线