这篇文章将为大家详细讲解有关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通用式封装”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。