温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Android MVP中如何实现BaseFragment通用式封装

发布时间:2021-08-06 11:40:12 来源:亿速云 阅读:150 作者:小新 栏目:移动开发

这篇文章将为大家详细讲解有关Android MVP中如何实现BaseFragment通用式封装,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

代码示例:

Android MVP中如何实现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 在这一版本中,我也去除了这个泛型参数,如图:

Android MVP中如何实现BaseFragment通用式封装

去除之后:

这里的 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 上,运行效果就是这样,如下图:

Android MVP中如何实现BaseFragment通用式封装

好吧,效果虽然简单了点,但我们的 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() 方法出错了,如:

Android MVP中如何实现BaseFragment通用式封装

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

Android MVP中如何实现BaseFragment通用式封装

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

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

关于“Android MVP中如何实现BaseFragment通用式封装”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI