• android MVP模式思考


        在软件开发设计中,有多种软件设计模式,如web开发中经典的MVC, 将后台分为三层:Model层,View层和Controller层,其中,Model主要是数据处理,如数据库,文件,或网络数据等;View层是视图层,主要是指前端或后端用于直接展现给用户的页面,Controller层则是负责业务逻辑处理,即将Model中读取的数据显示到View层,同时又将View层触发的操作事件经过业务处理后将数据保存到Model层中。

        在android中,可能很多开发者使用的还是mvc模式,比如,在代码中可以发现大量的IxxEntity,IxxDao,IxxDaoImpl,IxxController,IxxControllerImpldengdeng。但是,Android开发中,还有一种不错的开发设计,那就是MVP模式。在Android4.4源码中的InCallUI中,我们会发现就应用了这种模式。

        首先,先解释下,什么是MVP,MVP模式其实跟MVC模式的意图是一样的,都是为了将视图,数据和业务层分离开了,从而更好的应用于后续的开发,升级以及维护。其中MVP中的P,代表Presenter,即主持的意思,主要负责业务逻辑处理,跟MVC模式不同的是,在MVP中View层是不允许跟Model层直接交互的。MVP模式的理想场景就是可以只修改View层的界面代码,但是Presenter层和model层不需要修改任何代码,这是因为在android中,开发者面对最多的需求就是界面,变化最多的也是界面,所以mvp模式对android开发来说是一个非常不错的选择,当然,弊端就是,额外增加的代码量也有点多。。。

        具体调用逻辑,借用下面网上一张图:

        接着,我们来看看android4.4中InCallUI源码中MVP代码的应用。

        InCallUI源码路径是在/packages/apps/InCallUI,我们直接看/src/com/android/incallui目录,会发现该目录下有一大堆xxxPresenter的类。其中,AnswerPresenter是处理接听电话的Presenter,CallButtonPresenter是处理拨号盘的Presenter,CallCardPresenter是通话界面的Presenter。在InCallUI中,从所有的界面中抽象出一个接口:Ui,它是一个空接口,它的子类分别对应不同的Ui,因为对应MVP每一个V,它的界面都是不同的,所以需要抽象出一个接口,并且让这个接口继承Ui,具体代码如下:

    /*
     * Copyright (C) 2013 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License
     */
    
    package com.android.incallui;
    
    /**
     * Base class for all presenter ui.
     */
    public interface Ui {
    
    }

        如前面所说,CallButtonXX是负责拨号盘,对于拨号盘的界面显示,InCallUI抽象了一个继承Ui的接口CallButtonUi(定义在CallButtonPresenter类中),相关代码如下:

        public interface CallButtonUi extends Ui {
            void setEnabled(boolean on);
            void setMute(boolean on);
            void enableMute(boolean enabled);
            void setHold(boolean on);
            void showHold(boolean show);
            void enableHold(boolean enabled);
            void showMerge(boolean show);
            void showSwap(boolean show);
            void showAddCall(boolean show);
            void enableAddCall(boolean enabled);
            void displayDialpad(boolean on);
            boolean isDialpadVisible();
            void setAudio(int mode);
            void setSupportedAudio(int mask);
            void showManageConferenceCallButton();
            void showGenericMergeButton();
            void hideExtraRow();
            void displayManageConferencePanel(boolean on);
        }

        在InCallUI中,抽象类Presenter是所有XXPresenter的父类,他主要实现了跟View交互时所有Presenter都所需的onUiReady和onUiUnready两个方法。具体代码如下:

    /*
     * Copyright (C) 2013 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License
     */
    
    package com.android.incallui;
    
    /**
     * Base class for Presenters.
     */
    public abstract class Presenter<U extends Ui> {
    
        private U mUi;
    
        /**
         * Called after the UI view has been created.  That is when fragment.onViewCreated() is called.
         *
         * @param ui The Ui implementation that is now ready to be used.
         */
        public void onUiReady(U ui) {
            mUi = ui;
        }
    
        /**
         * Called when the UI view is destroyed in Fragment.onDestroyView().
         */
        public final void onUiDestroy(U ui) {
            onUiUnready(ui);
            mUi = null;
        }
    
        /**
         * To be overriden by Presenter implementations.  Called when the fragment is being
         * destroyed but before ui is set to null.
         */
        public void onUiUnready(U ui) {
        }
    
        public U getUi() {
            return mUi;
        }
    }

    其中Presenter中的实现CallButtonPresenter的代码如下:

    /*
     * Copyright (C) 2013 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License
     */
    
    package com.android.incallui;
    
    import com.android.incallui.AudioModeProvider.AudioModeListener;
    import com.android.incallui.InCallPresenter.InCallState;
    import com.android.incallui.InCallPresenter.InCallStateListener;
    import com.android.incallui.InCallPresenter.IncomingCallListener;
    import com.android.services.telephony.common.AudioMode;
    import com.android.services.telephony.common.Call;
    import com.android.services.telephony.common.Call.Capabilities;
    
    import android.telephony.PhoneNumberUtils;
    
    /**
     * Logic for call buttons.
     */
    public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
            implements InCallStateListener, AudioModeListener, IncomingCallListener {
    
        //省略若干行代码
    
        public CallButtonPresenter() {
        }
    
        @Override
        public void onUiReady(CallButtonUi ui) {
            super.onUiReady(ui);
    
            AudioModeProvider.getInstance().addListener(this);
    
            // register for call state changes last
            InCallPresenter.getInstance().addListener(this);
            InCallPresenter.getInstance().addIncomingCallListener(this);
        }
    
        @Override
        public void onUiUnready(CallButtonUi ui) {
            super.onUiUnready(ui);
    
            InCallPresenter.getInstance().removeListener(this);
            AudioModeProvider.getInstance().removeListener(this);
            InCallPresenter.getInstance().removeIncomingCallListener(this);
        }
    
        @Override
        public void onStateChange(InCallState state, CallList callList) {
    
            //省略若干行代码
        }
    
        @Override
        public void onIncomingCall(InCallState state, Call call) {
            onStateChange(state, CallList.getInstance());
        }
    
        @Override
        public void onAudioMode(int mode) {
            if (getUi() != null) {
                getUi().setAudio(mode);
            }
        }
    
        @Override
        public void onSupportedAudioMode(int mask) {
            if (getUi() != null) {
                getUi().setSupportedAudio(mask);
            }
        }
    
        @Override
        public void onMute(boolean muted) {
            if (getUi() != null) {
                getUi().setMute(muted);
            }
        }
    
        public int getAudioMode() {
            return AudioModeProvider.getInstance().getAudioMode();
        }
    
        public int getSupportedAudio() {
            return AudioModeProvider.getInstance().getSupportedModes();
        }
    
        public void setAudioMode(int mode) {
    
            //省略若干行代码
        }
    
        /**
         * Function assumes that bluetooth is not supported.
         */
        public void toggleSpeakerphone() {
            //省略若干行代码
        }
    
        public void endCallClicked() {
            if (mCall == null) {
                return;
            }
    
            CallCommandClient.getInstance().disconnectCall(mCall.getCallId());
        }
    
        public void manageConferenceButtonClicked() {
            getUi().displayManageConferencePanel(true);
        }
    
        public void muteClicked(boolean checked) {
            Log.d(this, "turning on mute: " + checked);
    
            CallCommandClient.getInstance().mute(checked);
        }
    
        public void holdClicked(boolean checked) {
            if (mCall == null) {
                return;
            }
    
            Log.d(this, "holding: " + mCall.getCallId());
    
            CallCommandClient.getInstance().hold(mCall.getCallId(), checked);
        }
    
        public void mergeClicked() {
            CallCommandClient.getInstance().merge();
        }
    
        public void addCallClicked() {
            //省略若干行代码
        }
    
        public void swapClicked() {
            //省略若干行代码
        }
    
        public void showDialpadClicked(boolean checked) {
            //省略若干行代码
        }
    
        private void updateUi(InCallState state, Call call) {
            //省略若干行代码
        }
    
        private void updateExtraButtonRow() {
            //省略若干行代码
        }
    
        public void refreshMuteState() {
            //省略若干行代码
        }
    
        public interface CallButtonUi extends Ui {
            void setEnabled(boolean on);
            void setMute(boolean on);
            void enableMute(boolean enabled);
            void setHold(boolean on);
            void showHold(boolean show);
            void enableHold(boolean enabled);
            void showMerge(boolean show);
            void showSwap(boolean show);
            void showAddCall(boolean show);
            void enableAddCall(boolean enabled);
            void displayDialpad(boolean on);
            boolean isDialpadVisible();
            void setAudio(int mode);
            void setSupportedAudio(int mask);
            void showManageConferenceCallButton();
            void showGenericMergeButton();
            void hideExtraRow();
            void displayManageConferencePanel(boolean on);
        }
    }

        在InCallUI中,除了InCallActivity,其他界面都是由Fragment负责界面,即,每个Fragment都属于MVP中的View,InCallUI中抽象出了一个BaseFragment来作为所有Fragment的父类,BaseFragment代码如下:

    /*
     * Copyright (C) 2013 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License
     */
    
    package com.android.incallui;
    
    import android.app.Fragment;
    import android.os.Bundle;
    
    /**
     * Parent for all fragments that use Presenters and Ui design.
     */
    public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends Fragment {
    
        private T mPresenter;
    
        abstract T createPresenter();
    
        abstract U getUi();
    
        protected BaseFragment() {
            mPresenter = createPresenter();
        }
    
        /**
         * Presenter will be available after onActivityCreated().
         *
         * @return The presenter associated with this fragment.
         */
        public T getPresenter() {
            return mPresenter;
        }
    
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            mPresenter.onUiReady(getUi());
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            mPresenter.onUiDestroy(getUi());
        }
    }

        即在BaseFragment或继承自BaseFragment的子Fragment被创建的时候,将V(Fragment)跟P(Presenter)关联,在Fragment被销毁的时候,将V和P解绑。这里我们继续看看CallButtonFragment的代码:

    /*
     * Copyright (C) 2013 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License
     */
    
    package com.android.incallui;
    
    import android.graphics.drawable.LayerDrawable;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.ViewGroup;
    import android.widget.CompoundButton;
    import android.widget.ImageButton;
    import android.widget.PopupMenu;
    import android.widget.PopupMenu.OnDismissListener;
    import android.widget.PopupMenu.OnMenuItemClickListener;
    import android.widget.ToggleButton;
    
    import com.android.services.telephony.common.AudioMode;
    
    /**
     * Fragment for call control buttons
     */
    public class CallButtonFragment
            extends BaseFragment<CallButtonPresenter, CallButtonPresenter.CallButtonUi>
            implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
            View.OnClickListener, CompoundButton.OnCheckedChangeListener {
    
        private ImageButton mMuteButton;
        private ImageButton mAudioButton;
        private ImageButton mHoldButton;
        private ToggleButton mShowDialpadButton;
        private ImageButton mMergeButton;
        private ImageButton mAddCallButton;
        private ImageButton mSwapButton;
    
        private PopupMenu mAudioModePopup;
        private boolean mAudioModePopupVisible;
        private View mEndCallButton;
        private View mExtraRowButton;
        private View mManageConferenceButton;
        private View mGenericMergeButton;
    
        @Override
        CallButtonPresenter createPresenter() {
            // TODO: find a cleaner way to include audio mode provider than
            // having a singleton instance.
            return new CallButtonPresenter();
        }
    
        @Override
        CallButtonPresenter.CallButtonUi getUi() {
            return this;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            //省略若干行代码
    
            mManageConferenceButton = parent.findViewById(R.id.manageConferenceButton);
            mManageConferenceButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getPresenter().manageConferenceButtonClicked();
                }
            });
            mGenericMergeButton = parent.findViewById(R.id.cdmaMergeButton);
            mGenericMergeButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getPresenter().mergeClicked();
                }
            });
            //省略若干行代码
        }
    
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            //省略若干行代码
        }
    
        @Override
        public void onResume() {
            if (getPresenter() != null) {
                getPresenter().refreshMuteState();
            }
            //省略若干行代码
        }
    
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        }
    
        @Override
        public void onClick(View view) {
            //省略若干行代码
        }
    
        @Override
        public void setEnabled(boolean isEnabled) {
           //省略若干行代码
        }
    
        @Override
        public void setMute(boolean value) {
            mMuteButton.setSelected(value);
        }
    
        @Override
        public void enableMute(boolean enabled) {
            mMuteButton.setEnabled(enabled);
        }
    
        @Override
        public void setHold(boolean value) {
            mHoldButton.setSelected(value);
        }
    
        @Override
        public void showHold(boolean show) {
            mHoldButton.setVisibility(show ? View.VISIBLE : View.GONE);
        }
    
        @Override
        public void enableHold(boolean enabled) {
            mHoldButton.setEnabled(enabled);
        }
    
        @Override
        public void showMerge(boolean show) {
            mMergeButton.setVisibility(show ? View.VISIBLE : View.GONE);
        }
    
        @Override
        public void showSwap(boolean show) {
            mSwapButton.setVisibility(show ? View.VISIBLE : View.GONE);
        }
    
        @Override
        public void showAddCall(boolean show) {
            mAddCallButton.setVisibility(show ? View.VISIBLE : View.GONE);
        }
    
        @Override
        public void enableAddCall(boolean enabled) {
            mAddCallButton.setEnabled(enabled);
        }
    
        @Override
        public void setAudio(int mode) {
            updateAudioButtons(getPresenter().getSupportedAudio());
            refreshAudioModePopup();
        }
    
        @Override
        public void setSupportedAudio(int modeMask) {
            updateAudioButtons(modeMask);
            refreshAudioModePopup();
        }
    
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            //省略若干行代码
        }
    
        // PopupMenu.OnDismissListener implementation; see showAudioModePopup().
        // This gets called when the PopupMenu gets dismissed for *any* reason, like
        // the user tapping outside its bounds, or pressing Back, or selecting one
        // of the menu items.
        @Override
        public void onDismiss(PopupMenu menu) {
            Log.d(this, "- onDismiss: " + menu);
            mAudioModePopupVisible = false;
        }
    
        /**
         * Checks for supporting modes.  If bluetooth is supported, it uses the audio
         * pop up menu.  Otherwise, it toggles the speakerphone.
         */
        private void onAudioButtonClicked() {
            Log.d(this, "onAudioButtonClicked: " +
                    AudioMode.toString(getPresenter().getSupportedAudio()));
    
            if (isSupported(AudioMode.BLUETOOTH)) {
                showAudioModePopup();
            } else {
                getPresenter().toggleSpeakerphone();
            }
        }
    
        /**
         * Refreshes the "Audio mode" popup if it's visible.  This is useful
         * (for example) when a wired headset is plugged or unplugged,
         * since we need to switch back and forth between the "earpiece"
         * and "wired headset" items.
         *
         * This is safe to call even if the popup is already dismissed, or even if
         * you never called showAudioModePopup() in the first place.
         */
        public void refreshAudioModePopup() {
            if (mAudioModePopup != null && mAudioModePopupVisible) {
                // Dismiss the previous one
                mAudioModePopup.dismiss();  // safe even if already dismissed
                // And bring up a fresh PopupMenu
                showAudioModePopup();
            }
        }
    
        /**
         * Updates the audio button so that the appriopriate visual layers
         * are visible based on the supported audio formats.
         */
        private void updateAudioButtons(int supportedModes) {
            //省略若干行代码
        }
    
        private boolean isSupported(int mode) {
            return (mode == (getPresenter().getSupportedAudio() & mode));
        }
    
        private boolean isAudio(int mode) {
            return (mode == getPresenter().getAudioMode());
        }
    
        @Override
        public void displayDialpad(boolean value) {
            mShowDialpadButton.setChecked(value);
            if (getActivity() != null && getActivity() instanceof InCallActivity) {
                ((InCallActivity) getActivity()).displayDialpad(value);
            }
        }
    
        @Override
        public boolean isDialpadVisible() {
            if (getActivity() != null && getActivity() instanceof InCallActivity) {
                return ((InCallActivity) getActivity()).isDialpadVisible();
            }
            return false;
        }
    
        @Override
        public void displayManageConferencePanel(boolean value) {
            if (getActivity() != null && getActivity() instanceof InCallActivity) {
                ((InCallActivity) getActivity()).displayManageConferencePanel(value);
            }
        }
    
    
        @Override
        public void showManageConferenceCallButton() {
            mExtraRowButton.setVisibility(View.VISIBLE);
            mManageConferenceButton.setVisibility(View.VISIBLE);
            mGenericMergeButton.setVisibility(View.GONE);
        }
    
        @Override
        public void showGenericMergeButton() {
            mExtraRowButton.setVisibility(View.VISIBLE);
            mManageConferenceButton.setVisibility(View.GONE);
            mGenericMergeButton.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void hideExtraRow() {
           mExtraRowButton.setVisibility(View.GONE);
        }
    }

     从而,通过Ui,CallButtonUi,BaseFragment,CallButtonFragment,Presenter,CallButtonPresenter将M,V,P分开,让数据,业务,展示分开开发维护,代码变得清晰,每层只需要关注自己的东西就行,这就比我们以前都只在一个Activity或Fragment中糅杂在一起好很多。

      如果有需要查看Android源码的童鞋,可以自行到Android官网下载或去下面两个网站进行在线查看。

     1,http://androidxref.com

     2,http://www.grepcode.com

  • 相关阅读:
    ElasticSearch 2 (1)
    Vagrant (2) —— 基本安装与配置(下)
    Vagrant (1) —— 基本安装与配置(上)
    Vagrant (3) —— 复制/备份Vagrant Box
    vue中$forceUpdate的使用
    vue+ElementUi 选择框选中之后翻页进行状态保持及默认选中
    loonflow 工单系统
    一些后端知识
    前端学习计划
    async/await函数的执行顺序的理解
  • 原文地址:https://www.cnblogs.com/xiaoyang2009/p/5387041.html
Copyright © 2020-2023  润新知