Android MVP BaseFragment 通用式封装的实现

这篇已经是我们的 BaseMVP 基础框架系列文章的第六篇了,BaseMVP 已经被我们封装了快差不多了,从上篇的文章(Android MVP 架构(五)MVP 多个 Presenter 依赖注入)中,我们解决了多的 Presenter 的问题,这是利用依赖注入及反射的方式,动态的去实例化不同 Presenter 层实现类,并且与同一个 View 做了绑定和解绑的操作,这就是一个 View 对多 Presenter 的需要手动 new 的解决方案。

BaseMVP 基础框架的搭建,到这里的话,还缺少一点东西。我们之前只封装过了一个基类的 BaseActivity 类,这个类是提供给 Activity 来继承的,但是,我们的实际项目中,难免会有 Fragment 的出现,于是乎,今天我们又带大家来封装一个 BaseFragment 基类吧,尽量的把基础框架给完善。

要封装 BaseFragment 基类,参考 BaseActivity 的封装并不难,因为 Activity 和 Fragment 的生命周期很相似,而且 Fragment 是寄托在 Activity 上的,所以说白了都差不多,不过代码肯定有一点偏差。对比之前的版本,这一次我在包中添加了一个 BaseFragment 基类,以及添加了几个测试它的类。

下面我们来看看 BaseFragment 基类吧,直接上代码:

新建 BaseFragment 基类:

package com.test.mvp.mvpdemo.mvp.v6.basemvp;

import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.test.mvp.mvpdemo.mvp.v6.inject.InjectPresenter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public abstract class BaseFragment extends Fragment implements IBaseView {

  private List<BasePresenter> mInjectPresenters;

  private View mLayoutView;

  protected abstract @LayoutRes int setLayout();

  protected abstract void initViews(@Nullable Bundle savedInstanceState);

  protected abstract void initData();

  @SuppressWarnings("ConstantConditions")
  protected <T extends View> T $(@IdRes int viewId) {
    return this.getView().findViewById(viewId);
  }

  @SuppressWarnings({"unchecked", "TryWithIdenticalCatches"})
  @Nullable
  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(setLayout(), container, false);

    mInjectPresenters = new ArrayList<>();

    //获得已经申明的变量,包括私有的
    Field[] fields = this.getClass().getDeclaredFields();
    for (Field field : fields) {
      //获取变量上面的注解类型
      InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
      if (injectPresenter != null) {
        try {
          Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
          BasePresenter mInjectPresenter = type.newInstance();
          //绑定
          mInjectPresenter.attach(this);
          field.setAccessible(true);
          field.set(this, mInjectPresenter);
          mInjectPresenters.add(mInjectPresenter);
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        } catch (java.lang.InstantiationException e) {
          e.printStackTrace();
        } catch (ClassCastException e) {
          e.printStackTrace();
          throw new RuntimeException("SubClass must extends Class:BasePresenter");
        }
      }
    }
    return view;
  }

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    initViews(savedInstanceState);
    initData();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    for (BasePresenter presenter : mInjectPresenters) {
      presenter.detach();
    }
    mInjectPresenters.clear();
    mInjectPresenters = null;
  }
}

由于上篇文章中,我们使用了依赖注入,所以这里的 BaseFragment 类的泛型参数就给我们去掉了。还有 BaseActivity 在这一版本中,我也去除了这个泛型参数,如图:

去除之后:

这里的 BaseActivity 就显得干净简洁了一点,不然每次都需要传入一个参数,我觉得想想都累。好了,我们的 BaseFragment 与 BaseActivity 几乎都一样吧,这里也就不做多的解释了,可以去看前面的几篇文章中有对代码的讲解。

写完了一个 BaseFragment 基类后,然后就是迫不及待的去测试一些,到底能不能工作。这里,我新建了一个 SecondActivity 类,目的就是为了在新的 Activity 中存放一个 Fragment 用于测试。SecondActivity 没有什么难度的代码,就是在里面存放这一个 SecondFragment,对了这里的 SecondActivity 并不是继承我们的 BaseActivity 类,这就是一个普通的 Activity ,要特别注意。代码很简单,如下:

新建 SecondActivity 类:

public class SecondActivity extends AppCompatActivity {

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    /**
     * 开启一个 fragment
     */
    getSupportFragmentManager().beginTransaction().replace(R.id.second_container, new SecondFragment()).commit();
  }
}

SecondActivity 的布局:是一个 FrameLayout 用于存放 SecondFragment。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".mvc.MainActivity">

  <FrameLayout
    android:id="@+id/second_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

接下来,才是我们的 BaseFragment 类的正真使用。我们新建一个 SecondFragment 实现类,继承与 BaseFragment 类,这里的 SecondFragment 就是 MVP 的 View 层了,与我们的 Activity 一样,同属于 View 层。这里,我偷懒,把 MainActivity 类的基本代码都考过来了。这里就不要太在意什么业务逻辑了,我们只要能测试 MVP 中的 BaseFragment 能够工作就好了。来看代码:

View 层:新建 SecondFragment 实现类:

package com.test.mvp.mvpdemo.mvp.v6.view;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.widget.TextView;
import android.widget.Toast;
import com.test.mvp.mvpdemo.R;
import com.test.mvp.mvpdemo.mvp.v6.SecondContract;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.BaseFragment;
import com.test.mvp.mvpdemo.mvp.v6.inject.InjectPresenter;
import com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter;

public class SecondFragment extends BaseFragment implements SecondContract.ISecondView {

  private TextView tvFragment;

  @InjectPresenter
  private SecondPresenter mPresenter;

  @Override
  protected int setLayout() {
    return R.layout.fragment_second;
  }

  @Override
  protected void initViews(@Nullable Bundle savedInstanceState) {
    tvFragment = $(R.id.tv_fragment);
  }

  @Override
  protected void initData() {
    mPresenter.handlerData();
  }

  @Override
  public void showDialog() {
//    Toast.makeText(getContext(), "this is Fragment", Toast.LENGTH_SHORT).show();
  }

  @SuppressWarnings("ConstantConditions")
  @Override
  public void succes(String content) {
    getActivity().runOnUiThread(new Runnable() {
      @Override
      public void run() {
        Toast.makeText(getContext(), "" + content, Toast.LENGTH_SHORT).show();
        tvFragment.setText(content);
      }
    });
  }

}

与之对应的就是 SecondPresenter 了,我们的 Presenter 层代码如下,代码与前面几篇文章一样,这里不做介绍了,代码如下所示:

###Presenter 层:新建 SecondPresenter 实现类:

package com.test.mvp.mvpdemo.mvp.v6.presenter;

import com.test.mvp.mvpdemo.mvp.v6.SecondContract;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.BasePresenter;
import com.test.mvp.mvpdemo.mvp.v6.model.SecondModel;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

public class SecondPresenter extends BasePresenter<SecondContract.ISecondView, SecondModel> implements SecondContract.ISecondPresenter {

  @Override
  public void handlerData() {
    getView().showDialog();

    getModel().requestBaidu(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
      }

      @Override
      public void onResponse(Call call, Response response) throws IOException {
        String content = response.body().string();
        getView().succes(content);
      }
    });
  }
}

接下来剩余的就是我们的 Model 层了,我们与之对应的是 SecondModel 类,还是请求网络数据,因为我们之前请求的是百度首页的网页文本,为了形成区别,我这里将 URL 改成了我的 博客 地址,哈哈。代码如下:

Model 层:新建 SecondModel 实现类:

package com.test.mvp.mvpdemo.mvp.v6.model;

import com.test.mvp.mvpdemo.mvp.v6.SecondContract;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.BaseModel;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;

public class SecondModel extends BaseModel implements SecondContract.ISecondModel {
  @Override
  public void requestBaidu(Callback callback) {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
        .url("https://blog.csdn.net/smile_running")
        .build();
    client.newCall(request).enqueue(callback);
  }
}

最后,还有一个它们的契约类,其中都是接口类型。代码如下:

新建 SecondContract 接口类:

package com.test.mvp.mvpdemo.mvp.v6;

import com.test.mvp.mvpdemo.mvp.v6.basemvp.IBasePresenter;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.IBaseView;

import okhttp3.Callback;

public interface SecondContract {
  interface ISecondModel {
    void requestBaidu(Callback callback);
  }

  interface ISecondView extends IBaseView {
    void showDialog();

    void succes(String content);
  }

  interface ISecondPresenter extends IBasePresenter {
    void handlerData();
  }
}

分包情况就是文章篇头的那张包图,好了,把代码写完了,就跑起来试试吧。

这里的运行情况是,从 MainActivity 中点击 textview 跳转到 SecondActivity,由于在 SecondActivity 显示的是我们的 SecondFragment ,所以会从网络上获取我的博客的地址文本,返回将数据设置到 SecondFragment 的 textview 上,运行效果就是这样,如下图:

好吧,效果虽然简单了点,但我们的 BaseFragment 算是封装完成了,经过测试,也是能够派上用场的了。经过我们的不懈努力,又把 BaseMVP 基础框架的搭建工作推进了一小步,在 BaseFragment 的封装过程中,我写的代码确实出现了一些小失误,这个是我们,原因是,我没有去拷贝代码!哈哈哈哈,好气啊,花了我好大把时间去改这个错误。

记录错误原因:在子线程中更新 UI 操作。

错误代码如下:在 SecondFragment 中更新 UI

 @Override
  public void succes(String content) {
    Toast.makeText(getContext(), "" + content, Toast.LENGTH_SHORT).show();
    tvFragment.setText(content);
  }

这个不是很简单嘛,这都不会改!

这可不一样,它报的错误信息可并不是子线程修改主线程异常,而是这么一堆错误日志:

07-09 23:51:21.887 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:21.915 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:23.362 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:27.742 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:28.069 9769-9798/com.test.mvp.mvpdemo E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
  Process: com.test.mvp.mvpdemo, PID: 9769
  java.lang.reflect.UndeclaredThrowableException
    at $Proxy2.succes(Unknown Source)
    at com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter$1.onResponse(SecondPresenter.java:25)
    at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
    at java.lang.Thread.run(Thread.java:818)
   Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.test.mvp.mvpdemo.mvp.v6.basemvp.BasePresenter$1.invoke(BasePresenter.java:31)
    at java.lang.reflect.Proxy.invoke(Proxy.java:397)
    at $Proxy2.succes(Unknown Source)
    at com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter$1.onResponse(SecondPresenter.java:25)
    at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
    at java.lang.Thread.run(Thread.java:818)
   Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:200)
    at android.os.Handler.<init>(Handler.java:114)
    at android.widget.Toast$TN.<init>(Toast.java:359)
    at android.widget.Toast.<init>(Toast.java:100)
    at android.widget.Toast.makeText(Toast.java:273)
    at com.test.mvp.mvpdemo.mvp.v6.view.SecondFragment.succes(SecondFragment.java:44)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.test.mvp.mvpdemo.mvp.v6.basemvp.BasePresenter$1.invoke(BasePresenter.java:31)
    at java.lang.reflect.Proxy.invoke(Proxy.java:397)
    at $Proxy2.succes(Unknown Source)
    at com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter$1.onResponse(SecondPresenter.java:25)
    at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
    at java.lang.Thread.run(Thread.java:818)
07-09 23:51:28.126 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH) 

首先,我看了标记中的第一个和第二个错误原因,原来是反射那块有问题,根据它代码中提示的位置,说我的 Presenter 中的 getView() 方法出错了,如:

点击去看了下,是动态代理的代码,这里搞什么鬼,我又没修改这里的代码,怎么就错了呢?

一脸懵逼的我,回头看了看,在这里尝试了断点调试,没有什么结果。后来意外发现,我的把上面图中的 getView().succes(content) 注释掉了就不报错了。这才找到了原因,原来是这里的数据是通过网络请求传过来的,我们的 okhttp 需要转到 ui 线程中去更新,这个我是知道的。

所以要记得,切到主线程去更新 UI 操作。虽然发生了一点小失误,刚开始以为是动态代理的问题,所以查了好多关于动态代理的知识,借此还能学到一点额外的知识,美滋滋,哈哈。

这里的 BaseFragment 还是有代码重复的问题,比如我们的依赖注入那块代码,就放到下篇文章中去解决这个问题吧,这篇文章已经完成我们该完成的任务了,明天的事情放到后天干吧,哈哈哈哈。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android封装MVP实现登录注册功能

    本文实例为大家分享了Android封装MVP实现登录注册功能,供大家参考,具体内容如下 model包: import com.bwei.mvps.bean.UserBean; /** * 1. 类的用途 * 2. @author forever * 3. @date 2017/9/1 16:00 */ public interface IUserModel { void setFirstName(String firstName); void setLastName(String lastNam

  • Android如何从实现到封装一个MVP详解

    前言 MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负 责显示.下面这篇文章主要给大家介绍了关于Android从实现到封装MVP的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. MVP之间的联系 大概简单的解释就是M->module处理数据,V->Act显示界面,P->M和V沟通的渠道,即P用来将数据和界面联系到一起,这样子界面和数据就可以完全独立开来,Ac

  • Android MVP BaseFragment 通用式封装的实现

    这篇已经是我们的 BaseMVP 基础框架系列文章的第六篇了,BaseMVP 已经被我们封装了快差不多了,从上篇的文章(Android MVP 架构(五)MVP 多个 Presenter 依赖注入)中,我们解决了多的 Presenter 的问题,这是利用依赖注入及反射的方式,动态的去实例化不同 Presenter 层实现类,并且与同一个 View 做了绑定和解绑的操作,这就是一个 View 对多 Presenter 的需要手动 new 的解决方案. BaseMVP 基础框架的搭建,到这里的话,还

  • android编程实现图片库的封装方法

    本文实例讲述了android编程实现图片库的封装方法.分享给大家供大家参考,具体如下: 大家在做安卓应用的时候 经常要从网络中获取图片 都是通过URL去获取 可是如果本地有图片数据 从本地获取数据不更加快一些 自己在工作中遇到这个问题 所以采用了一个URL和本地图片的一个映射关系 先从本地区获取 假如本地没有再从网络中获取 本方法考虑到多线程问题 欢迎大家一起共同探讨! public class PictureLibrary { /* * 图片库的操作 */ File file; URL url

  • Android编程之ICS式下拉菜单PopupWindow实现方法详解(附源码下载)

    本文实例讲述了Android编程之ICS式下拉菜单PopupWindow实现方法.分享给大家供大家参考,具体如下: 运行效果截图如下: 右边这个就是下拉菜单啦,看见有的地方叫他 ICS式下拉菜单,哎哟,不错哦! 下面先讲一下实现原理: 这种菜单实际上就是一个弹出式的菜单,于是我们想到android PopupWindow 类,给他设置一个view 在弹出来不就OK了吗. PopupWindow 的用法也很简单 主要方法: 步骤1.new 一个实例出来,我们使用这个构造方法即可, 复制代码 代码如

  • Google 开发Android MVP架构Demo深入解析

    目录 1.什么是MVP? 2.Google官方的MVP 3.V1.1 My MVP V1 4.V1.2 My MVP V2 1.什么是MVP? Google在2016年推出了官方的Android MVP架构Demo,本文主要分析一下官方的MVP Demo,并且借由自己的一些经验,提出一些学习过程中,遇到的问题和自己的改进.封装措施. MVP架构已经推出很多年了,现在已经非常普及了,我在这里就不过多介绍,简单的说,它分为以下三个层次: Model:数据模型层,主要用来数据处理,获取数据: View

  • Android startActivityForResult的调用与封装详解

    目录 前言 一.原生的使用 二.对原生的封装Ghost 三.Result Api 的使用 四.Result Api 的封装 4.1 封装简化创建方式 4.2 自动注册/按需注册 总结 前言 startActivityForResult 可以说是我们常用的一种操作了,用于启动新页面并拿到这个页面返回的数据,是两个 Activity 交互的基本操作. 虽然可以通过接口,消息总线,单例池,ViewModel 等多种方法来间接的实现这样一个功能,但是 startActivityForResult 还是使

  • 基于Json序列化和反序列化通用的封装完整代码

    1. 最近项目已经上线了 ,闲暇了几天 想将JSON的序列化以及反序列化进行重新的封装一下本人定义为JSONHelp,虽然Microsoft 已经做的很好了.但是我想封装一套为自己开发的项目使用.方便后期的扩展以及开发使用. 2. 什么是 JSON ? JSON:JavaScript 对象表示法(JavaScript Object Notation).JSON 是存储和交换文本信息的语法.类似 XML.JSON 比 XML 更小.更快,更易解析.  现在开发Web应用程序 JSON 是 必不可少

  • php、java、android、ios通用的3des方法(推荐)

    php服务器,java服务器,android,ios开发兼容的3des加密解密, php <?php class DES3 { var $key = "my.oschina.net/penngo?#@"; var $iv = "01234567"; function encrypt($input){ $size = mcrypt_get_block_size(MCRYPT_3DES,MCRYPT_MODE_CBC); $input = $this->pk

  • Android 中RecyclerView通用适配器的实现

    Android 中RecyclerView通用适配器的实现 前言: SDK的5.0版本出来已经N久了,可以说是已经经过许多人的检验了,里面的新控件不能说是非常完美,但也是非常好用了,其中最让我喜爱的就是RecyclerView了,可以完美替代ListView和GridView(除了添加headerview和footview了,网上有许多解决方式.这个下面会以一种简单的方式顺带解决,肯定为大家省心),而且可以代码动态切换这两种布局方式以及瀑布流布局.相关切换方式网上有很多,大家自行搜索,我就不贴连

  • 浅析jQuery Ajax通用js封装

    本文大概分为三步实现jquery ajax通过js封装,通过代码实例讲解,代码附有注释,比较容易理解,具体详情如下所示: 第一步:引入jQuery库 <script type="text/javascript" src="<%=path%>/resources/js/jquery.min.js"></script> 第二步:开发Ajax封装类,已测试通过,可以直接调用,直接贴代码,讲解就省了 /******************

  • Android 中Volley二次封装并实现网络请求缓存

    Android 中Volley二次封装并实现网络请求缓存 Android目前很多同学使用Volley请求网络数据,但是Volley没有对请求过得数据进行缓存,因此需要我们自己手动缓存. 一下就是我的一种思路,仅供参考 具体使用方法为: HashMap<String,String> params = new HashMap<>(); params.put("id", "1"); params.put("user", &quo

随机推荐