实现全局弹出Dialog提醒异地登录功能

入职后不就,从做第一个项目,写第一个商业 APP 开始,就一直被一个问题困扰,即“异地登录提醒”。现在许多 APP 都要求只能一个设备登录,要求是,如果设备 A 已经登录了,设备 B 再登录,那就要给设备 A 的用户一个提醒,并强制其退出登录,必须重新登录。提醒用的 Dialog,是一个我写在 Activity 基类的一个不可取消的 Dialog,只给一个“确认”按钮,点击即退出登录,并跳转到登录页面。提醒功能有了,最大的问题就是,如何去显示这个 Dialog,因为我并不知道当接收到“异地登录”信号的时候,用户当前会在哪个 Activity 中,而 ApplicationContext 是不能作为 Dialog 的 Context 的。

我们后台给的“异地登录”信号,是当我们进行网络请求的时候,附在返回的请求信息中的,所以之前一直是用的一个笨办法。即在每个进行了网络请求的 Activity 或 Fragment 中,在网络请求的回调中都对请求的状态码进行一个判断,根据状态码决定是否调用“异地登录”的警示 Dialog。这样,我就需要在每个网络请求的结果回调中都要来上这么一段,而我使用的 MVVM 框架,网络请求都是在 ViewModel 部分进行的,ViewModel 里压根就没有保存 Activity Context;我使用的网络请求框架是 Retrofit + RxJava,所以请求结果是在 Observer 中处理的,所以我也不可能传个 Activity Context 到 Observer 基类中去。所以前几个项目,我都一直是每个网络请求中都来上那么一段一模一样的代码,可想而知有多蛋疼了。

期间,通过陆陆续续 Google 和请教前辈,也获取过几种解决方法,但是没有一种能完美解决我的需求的。首先我的统一处理网络请求结果的 Observer 是这样的。

public abstract class BaseObserver<T> extends ResourceObserver<T> {

    @Override
    public void onNext(@NonNull T t) {
        if (t instanceof BaseEntity) {
            if (((BaseEntity) t).getStatus() == 1) {
                onSuccess(t);
            } else if (((BaseEntity) t).getStatus() == 602) {
                // 状态码 602 就是发现异地登录了
                onComplete();
            } else {
                onError(new Throwable(((BaseEntity) t).getMessage()));
            }
        }
    }

    @Override
    public void onError(@NonNull Throwable e) {
        if (!Kits.NetWork.isNetworkConnected(App.getInstance())) {
            onFailed(new Throwable("网络连接错误"));
        } else {
            onFailed(e);
        }
        e.printStackTrace();
    }

    @Override
    public void onComplete() {
    }

    /**
     * 加载成功
     * @param t 获得的数据实体
     */
    public abstract void onSuccess(@NonNull T t);

    /**
     * 连接失败或返回数据错误
     * @param e
     */
    public abstract void onFailed(Throwable e);
}

通过事件总线来提示,我用的是 RxBus

首先在基类 Activity 默认开启注册 RxBus,然后写上异地登录事件的接收者方法,在上文中的统一 BaseObserver 中接收到602状态码的时候,post一个事件,通知接收者(此时是所有 Activity)异地登录了,然后注册了的接收者们就可以弹窗了。

  • 注意,我上面说的是接收者“们”,所以同一时间存在的接收者可能不止一个,因为不可能每个 Activity 或 Fragment 都会在onResume()方法中解除注册,此时就会有多个弹窗,体验是非常不好的。
  • 后来我又尝试过只在MainActivity类中注册异地登录事件的接收者,但如果触发 Dialog 的时候不是在MainActivity,而是在另外的 Activity 中,那MainActivity中的 Dialog 倒是会正常弹出来,不过你是看不到的,需要回退到MainActivity才能看到这个弹框,而在回退到MainActivity之前如果多次触发了,那照样会给你连弹几个 Dialog,体验极差。

所以,用事件总线来提示是行不通了。

通过 ApplicationContext 启动 DialogActivity

如题所说,用 Activity 做一个提醒“异地登录”的 Dialog,然后通过 ApplicationContext 去启动这个 DialogActivity 即可。

  • 这样就又带来了一个新的问题,启动的 DialogActivity 是在一个新的 Task 中,而我们还要通过这个 Dialog 去启动登录页呢,至于怎样从新的 Task 将登录 Activity 启动到原来的 Task 中,由于该方法还有另外一点不足,所以这里我也没有去深入探究。
  • 另外一点不足就是,这个 DialogAcivity 是可取消的,也就是说,这个 Dialog 跟平常我们使用正常的 Activity 一样,回退键一按,就没了,这就不符合我要求的只能操作一个“确定”按钮的需求了。

所以,DialogActivity 的方式也是行不通。

通过 Service 去实现的全局 Dialog

这个方法我是在打造全局Dialog,Toast,解决Toast多次弹出以及小米无法弹出悬浮窗问题这篇文章中看到的,经过仔细观察和 Google,发现 Service 无法启动 Activity,文中作者也仅仅是实现了一个弹出的 Loading Dialog 而已,并且在小米机型中还会出现问题,者肯定是不行的。

用系统级的 Dialog 来实现

即使用系统级的弹窗来提醒,好象是依靠服务来实现的。我就在想啊,那我 APP 在后台运行的时候,这时候收到了异地登录事件,那这时候用户如果正在使用别的 APP,在别的 APP 里弹出我这 APP 的 Dialog,会不会不太好…

恰好这时候又同时看到了最合适的一种方法,于是这个系统级 Dialog 也就没有继续深入了。

在 Application 中记录当前是哪个 Activity,然后利用当前 Activity show Dialog

这个方法是受Android 获取栈最顶层Activity和Application Context解决方案这篇文章的启发。虽然上个项目采用过这个方法来监听 APP 的休眠和重启间隔来着,但完全没有将它和现在这个需求联系起来。

在 Application 中,可以注册一个监听器Application.ActivityLifecycleCallbacks()来监听 Activity 的启动情况,并将当前resume的 Activity 记录下来:

public class App extends Application {
    ...
    private static App INSTANCE;
    private ZActivity mActivity;

    @Override
    public void onCreate() {
        super.onCreate();
        ...
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {
                // ZActivity 是我定义的基类 Activity
                if (activity instanceof ZActivity) {
                    mActivity = (ZActivity) activity;
                }
            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }

    public static App getInstance() {
        return INSTANCE;
    }

    public ZActivity getTopActivity() {
        return mActivity;
    }
}

如此,我们就可以在 BaseObserver 中判断状态码后,如果是“异地登录”,就直接用当前 Activity 给用户一个 Dialog,强制其下线并转去登录页面了:

...
} else if (((BaseEntity) t).getStatus() == 602) {
    BaseInfo.clearUserInfo();
    // 这一句即是用当前 Activity 弹出“异地登录”提醒的 Dialog
    App.getInstance().getTopActivity().showOtherLogin(LoginActivity.class);
    onComplete();
} else {
...

至此,这个“异地登录”提醒并强制重新登录的需求算是比较完美的实现了,美滋滋。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值