您好,登录后才能下订单哦!
这篇文章将为大家详细讲解有关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 在这一版本中,我也去除了这个泛型参数,如图:
去除之后:
这里的 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 操作。虽然发生了一点小失误,刚开始以为是动态代理的问题,所以查了好多关于动态代理的知识,借此还能学到一点额外的知识,美滋滋,哈哈。
关于“Android MVP中如何实现BaseFragment通用式封装”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。