Android MVP架构浅析

本文介绍了MVP架构的基本概念及其在Android开发中的应用。通过对比MVC架构,解析了MVP架构如何解决Activity臃肿的问题,并提供了一个具体的电话号码归属地查询实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MVP从更早的MVC架构演变过来的,与MVC有一定的相似:

(1)MVC的缺点:在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载页面布局和初始化用户界面,接受并处理来自用户的操作请求。随着界面及逻辑的复杂度不断提升,Activity类的职能不断增加,以致变得庞大臃肿。

(2)MVP通常有以下几个部分组成:

  • View:负责绘制和更新UI,与用户进行交互(在Android中体现为Activity或Fragment)。
  • Model:负责存储,检索,操作数据或与后台通信,简单理解就是获取数据的地方(有时也实现一个Model Interface来降低耦合)。
  • Presenter:作为View与Model的中间纽带,通知model获取数据,model获取数据完之后,通知View更新界面。
  • View interface:需要View实现的借口,View通过View interface与Presenter进行交互,降低耦和,方便进行单元测试。

(3)MCV—MVP:当我们将Activity复杂的逻辑处理移至另一个类(Presenter)中时,Activity实际上就是MVP模式中的View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂逻辑交由Presenter处理)。

MVP的Presenter是架构的控制者,承担了大量的逻辑操作,因此APP中引入MVP的原因,是为了将此前在Activity中包含的大量逻辑操作放到控制层,避免Activity的臃肿。

下面通过一个获取电话号码归属地的例子来说明(网络请求部分使用Retrofit结合Rxjava):

电话号码归属地API地址:
http://api.k780.com:88/?app=phone.get&phone=18926502506&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json

成功后返回一段json数据如下:
{“success”:”1”,”result”:{“status”:”ALREADY_ATT”,”phone”:”18926502506”,”area”:”0755”,”postno”:”518000”,”att”:”中国,广东,深圳”,”ctype”:”中国电信189卡”,”par”:”1892650”,”prefix”:”189”,”operators”:”中国电信”,”style_simcall”:”中国,广东,深圳”,”style_citynm”:”中华人民共和国,广东省,深圳市”}}

号码不存在时返回一段json数据如下:
{“success”:”1”,”result”:{“status”:”NOT_ATT”,”phone”:”1892652506”,”msg”:”暂无相关归属地信息”}}

代码如下:
MainActivity:

public class FirstActivity extends AppCompatActivity implements IBaseView , View.OnClickListener
{
    private EditText input;
    private Button search;
    private TextView show;
    private UserPresenter userPresenter;
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        initView();
    }

    private void initView()
    {
        input = (EditText) findViewById(R.id.number);
        search = (Button) findViewById(R.id.search);
        show = (TextView) findViewById(R.id.show);
        userPresenter = new UserPresenter(this);
        search.setOnClickListener(this);
        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("Loading");

        AppContextUtil.init(FirstActivity.this);
    }

    @Override
    public String getNumber()
    {
        return input.getText().toString().trim();
    }

    @Override
    public void setSearchResult(String result)
    {
        show.setText(result);
    }

    @Override
    public void showProgress()
    {
        progressDialog.show();
    }

    @Override
    public void hideProgress()
    {
        progressDialog.dismiss();
    }

    @Override
    public void onClick(View view)
    {
        switch (view.getId())
        {
            case R.id.search:
                userPresenter.loadData(getNumber());
                break;
        }
    }
}

View Interface:

public interface IBaseView<T>
{
    String getNumber();
    void setSearchResult(String result);
    void showProgress();
    void hideProgress();
}

Presenter类:

public class UserPresenter
{
    private IBaseView iBaseView;
    private RequestModel requestModel;

    public UserPresenter(IBaseView iBaseView)
    {
        this.iBaseView = iBaseView;
        requestModel = RequestModel.getInstance();
    }

    public void loadData(String number)
    {
        iBaseView.showProgress();
        requestModel.getPhoneAlt(number, new Subscriber<Result>()
        {
            @Override
            public void onCompleted()
            {
                iBaseView.hideProgress();
            }

            @Override
            public void onError(Throwable e)
            {
                iBaseView.hideProgress();
                if (NetUtil.isNetworkConnected())
                {
                    Toast.makeText(AppContextUtil.getInstance() , e.getMessage() , Toast.LENGTH_SHORT).show();
                }
                else
                {
                    iBaseView.setSearchResult("请检查您的网络是否连接!!!");
                }
            }

            @Override
            public void onNext(Result result)
            {
                if (result.getStatus().equals("NOT_ATT"))
                {
                    iBaseView.setSearchResult("请输入正确的手机号码");
                }
                else
                {
                    iBaseView.setSearchResult(result.toString());
                }
            }
        });
    }
}

Retrofit网络请求Service接口:

public interface NumberService
{
    @GET("/")
    Observable<NumberEntity<Result>> getPhoneNumber(@Query("app") String app ,
                                                    @Query("phone") String phone ,
                                                    @Query("appkey") String appKey ,
                                                    @Query("sign") String sign ,
                                                    @Query("format") String format);
}

RequestModel类:

public class RequestModel
{
    private static final String BASE_URL = "http://api.k780.com:88";
    private static final String APP = "phone.get";
    private static final String APP_KEY = "10003";
    private static final String SIGN = "b59bc3ef6191eb9f747dd4e83c99f2a4";
    private static final String FORMAT = "json";
    private static final int DEFAULT_TIMEOUT = 5;
    private Retrofit retrofit;
    private NumberService numberService;

    private RequestModel()
    {
        //手动创建一个OkHttpClient并设置超时
        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
        clientBuilder.connectTimeout(DEFAULT_TIMEOUT , TimeUnit.SECONDS);

        retrofit = new Retrofit.Builder()
                .client(clientBuilder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(BASE_URL)
                .build();

        numberService = retrofit.create(NumberService.class);
    }

    private static class SingletonHolder
    {
        private static final RequestModel INSTANCE = new RequestModel();
    }

    public static RequestModel getInstance()
    {
        return SingletonHolder.INSTANCE;
    }

    public void getPhoneAlt(String number , Subscriber<Result> subscriber)
    {
        numberService.getPhoneNumber(APP , number , APP_KEY , SIGN , FORMAT)
                .map(new NumberResultFunc<Result>())
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }

    private class NumberResultFunc<T> implements Func1<NumberEntity<T> , T>
    {
        @Override
        public T call(NumberEntity<T> numberEntity)
        {
            return numberEntity.getResult();
        }
    }
}

实体类比较简单,这里不再贴出,大家可以根据返回的json数据自行编写。最后贴出两个工具类:分别用于获取网络连接状态和获取Context对象。

public class NetUtil
{
    private NetUtil()
    {
    }
    public static boolean isNetworkConnected()
    {
        if (AppContextUtil.getInstance() != null)
        {
            ConnectivityManager connectivityManager = (ConnectivityManager) AppContextUtil.getInstance()
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            if (networkInfo != null)
            {
                return networkInfo.isAvailable();
            }
        }
        return false;
    }
}
public class AppContextUtil
{
    private static Context sContext;
    private AppContextUtil()
    {
    }

    public static void init(Context context)
    {
        sContext = context;
    }

    public static Context getInstance()
    {
        if (sContext == null)
        {
            throw new NullPointerException("the Context is null , please init AppContextUtil in Activity first.");
        }
        return sContext;
    }
}

运行截图

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值