如何使用MVP实现PhoneStateListener

时间:2018-03-08 04:58:20

标签: android android-activity mvp android-mvp presenter

我正在重构MVP中的一个旧应用程序。我已经重构了大部分逻辑,现在我坚持跟随一个。 在我的一个活动中,我已经实现了PhoneStateLister,如下所示。

private class CallStateListener extends PhoneStateListener {

    public void onCallStateChanged(int state, String incomingNumber) {

        switch (state) {
            case TelephonyManager.CALL_STATE_IDLE:
                deactivateSpeaker();
                if (callCount == 0) {
                    doCall();
                } else {
                    checkForHangUp();
                }
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                checkAutoSpeaker();
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                break;
            default:
                break;
        }
    }

    private void checkAutoSpeaker() {
        if (preferenceManager.isAutoSpeaker()) activateSpeaker();
    }

    private void activateSpeaker() {
        final Handler mHandler = new Handler();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                audioManager.setMode(AudioManager.MODE_IN_CALL);
                audioManager.setSpeakerphoneOn(true);
            }
        }, 1000);
    }

    private void deactivateSpeaker() {
        audioManager.setMode(AudioManager.MODE_NORMAL); //Deactivate loudspeaker
    }

}

我很难决定将这个逻辑放在MVP中的哪个位置。 Activity应该处理这个PhoneStateListener还是应该让presenter处理这个? 请帮我解决这个问题。感谢。

2 个答案:

答案 0 :(得分:0)

MVP 模式中设置侦听器总是有点棘手。我很确定您的问题没有“正确”“错误”的答案,因为它在很大程度上取决于您的个人偏好和架构风格。但按照我的一般规则,将尽可能多的业务逻辑移动到演示者并保持其他所有内容(这也包括侦听器)尽可能愚蠢总是很明智。

这就是为什么我建议您在侦听器类中添加界面,并在那里保留对演示者的(弱)引用。 侦听器在该示例中唯一做的就是侦听状态更改,然后将信息转发到演示者(分发的地方)。

public class CallStateListener extends PhoneStateListener {
    private final WeakReference<CallStateListenerInterface> presenterInterface;
    public CallStateListener(CallStateListenerInterface presenterInterface){
        this.presenterInterface = new WeakReference<>(presenterInterface);
    }
    public void onCallStateChanged(int state, String incomingNumber){
        presenterInterface.get().onCallStateListenerCallStateChanged(state, incomingNumber);
    }
    public interface CallStateListenerInterface {
        void onCallStateListenerCallStateChanged(int state, String incomingNumber);
    }
}

演示者需要实现 CallStateListenerInterface (当然),并在调用状态发生变化时处理所有业务逻辑。

public class myPresenter implements MVPInterface, CallStateListenerInterface {

    /*your business logic*/

    public void onCallStateListenerCallStateChanged(int state, String incomingNumber){
        switch (state) { 
           case TelephonyManager.CALL_STATE_IDLE: 
                deactivateSpeaker(); 
                if (callCount == 0) { 
                    doCall(); 
                } else { 
                    checkForHangUp(); 
                } 
               break; 
           case TelephonyManager.CALL_STATE_OFFHOOK: 
               checkAutoSpeaker(); 
               break; 
           case TelephonyManager.CALL_STATE_RINGING: 
               break; 
           default: 
               break; 
        }
    }
}

此处需要与视图进行交互的所有内容都会被调用,但请将这些调用保留为简单的一行说明,并为演示者决定是否复杂。

如果您不想为每个侦听器类添加新的接口,您也可以将整个 MVPInterface 传递给 CallStateChangedListener (并在合同的中心位置声明所有内容。)

如果以这种方式实现监听器,那么您将拥有一个非常干净的架构,应该可以轻松测试。 我希望这是有帮助的,如果您有任何问题,请告诉我:))

小额替代实施:

正如评论者所指出:在我的原始建议中,演示者从TelephonyManager类访问静态电话状态整数。这不是最干净的设计,因为演示者应该与任何Android特定代码完全分开。 由于没有真正的超级干净方法来解决这个问题,我只能建议从TelephonyManager状态到保留在演示者中的(自定义)电话状态的映射(或者更好的是:静态常量文件)。 基本上这意味着CallStateListener中的onCallStateChanged看起来像这样:

public void onCallStateChanged(int state, String incomingNumber){
    int stateValue = myPresenter.PRESENTER_CALL_STATE_IDLE;
    if (state == TelephonyManager.CALL_STATE_RINGING){
        stateValue = myPresenter.PRESENTER_CALL_STATE_RINGING;
    } else if (state == TelephonyManager.CALL_STATE_OFFHOOK){
        stateValue = myPresenter.PRESENTER_CALL_STATE_OFFHOOK;
    }
    presenterInterface.get().onCallStateListenerCallStateChanged(stateValue , incomingNumber);
}

通过此实现,您可以将TelephonyManager状态移出演示者并使用您自己定义的状态。 (再一次:我真的不确定这是否值得付出努力,但我想至少提供替代方案)

答案 1 :(得分:0)

我相信你应该把听众留在你的片段/活动部分。 我想到了两个解决方案。第一个是将开关/案例留在听众中,并将一些逻辑移到演示者中:

public void onCallStateChanged(int state, String incomingNumber) {
    switch (state) {
        case TelephonyManager.CALL_STATE_IDLE:
            mPresenter.onCallStateIDLE();
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK:
            mPresenter.onCallStateOffHook();
            break;
        case TelephonyManager.CALL_STATE_RINGING:
            mPresenter.onCallStateRinging();
            break;
        default:
            break;
    }
}

并在您的演示者中:

void onCallStateIDLE(){
    mView.deactivateSpeaker();
    if (callCount == 0) {
        mView.doCall();
    } else{
        mView.checkForHangUp();
    }
}

另一个选项应该是将开关/盒子移动到演示者中。 棘手的部分应该是如何处理TelephonyManager常量。如您所知,您应该将演示者保留为纯Java代码。

你应该有一个View界面,你可以做这样的事情

interface MyActivityContract{
    interface View{
        int CALL_STATE_IDLE = TelephonyManager.CALL_STATE_IDLE;
        //etc etc the same with the rest of constants
    }
    interface Presenter{}
}

然后在你的听众中,你应该引用你的演示者

private class CallStateListener extends PhoneStateListener {
    public void onCallStateChanged(int state, String incomingNumber) {
        mPresenter.onCallStateChanged(state, incomingNumber);
    }
}

最后你的演示者可能是这样的

public void onCallStateChanged(int state, int incomingNumber){
    switch (state) {
        case MyActivityContract.View.CALL_STATE_IDLE:
            mView.deactivateSpeaker();
            if (callCount == 0) {
                mView.doCall();
            } else {
                mView.checkForHangUp();
            }
            break;
        case MyActivityContract.View.CALL_STATE_OFFHOOK:
            if (preferenceManager.isAutoSpeaker()){
                mView.activateSpeaker();
            }
            break;
        case MyActivityContract.View.CALL_STATE_RINGING:
            break;
        default:
            break;
    }
}

通过这种方式,您的演示者中没有任何Android引用,但是您的合同中有它...