Android MVP BaseFragment 通用式封裝的實現(xiàn)
這篇已經(jīng)是我們的 BaseMVP 基礎框架系列文章的第六篇了,BaseMVP 已經(jīng)被我們封裝了快差不多了,從上篇的文章(Android MVP 架構(gòu)(五)MVP 多個 Presenter 依賴注入)中,我們解決了多的 Presenter 的問題,這是利用依賴注入及反射的方式,動態(tài)的去實例化不同 Presenter 層實現(xiàn)類,并且與同一個 View 做了綁定和解綁的操作,這就是一個 View 對多 Presenter 的需要手動 new 的解決方案。
BaseMVP 基礎框架的搭建,到這里的話,還缺少一點東西。我們之前只封裝過了一個基類的 BaseActivity 類,這個類是提供給 Activity 來繼承的,但是,我們的實際項目中,難免會有 Fragment 的出現(xiàn),于是乎,今天我們又帶大家來封裝一個 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<>(); //獲得已經(jīng)申明的變量,包括私有的 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 類的泛型參數(shù)就給我們?nèi)サ袅?。還有 BaseActivity 在這一版本中,我也去除了這個泛型參數(shù),如圖:
去除之后:
這里的 BaseActivity 就顯得干凈簡潔了一點,不然每次都需要傳入一個參數(shù),我覺得想想都累。好了,我們的 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 實現(xiàn)類,繼承與 BaseFragment 類,這里的 SecondFragment 就是 MVP 的 View 層了,與我們的 Activity 一樣,同屬于 View 層。這里,我偷懶,把 MainActivity 類的基本代碼都考過來了。這里就不要太在意什么業(yè)務邏輯了,我們只要能測試 MVP 中的 BaseFragment 能夠工作就好了。來看代碼:
View 層:新建 SecondFragment 實現(xiàn)類:
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 實現(xiàn)類: 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 類,還是請求網(wǎng)絡數(shù)據(jù),因為我們之前請求的是百度首頁的網(wǎng)頁文本,為了形成區(qū)別,我這里將 URL 改成了我的 博客 地址,哈哈。代碼如下:
Model 層:新建 SecondModel 實現(xiàn)類:
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 跳轉(zhuǎn)到 SecondActivity,由于在 SecondActivity 顯示的是我們的 SecondFragment ,所以會從網(wǎng)絡上獲取我的博客的地址文本,返回將數(shù)據(jù)設置到 SecondFragment 的 textview 上,運行效果就是這樣,如下圖:
好吧,效果雖然簡單了點,但我們的 BaseFragment 算是封裝完成了,經(jīng)過測試,也是能夠派上用場的了。經(jīng)過我們的不懈努力,又把 BaseMVP 基礎框架的搭建工作推進了一小步,在 BaseFragment 的封裝過程中,我寫的代碼確實出現(xiàn)了一些小失誤,這個是我們,原因是,我沒有去拷貝代碼!哈哈哈哈,好氣啊,花了我好大把時間去改這個錯誤。
記錄錯誤原因:在子線程中更新 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)
首先,我看了標記中的第一個和第二個錯誤原因,原來是反射那塊有問題,根據(jù)它代碼中提示的位置,說我的 Presenter 中的 getView() 方法出錯了,如:
點擊去看了下,是動態(tài)代理的代碼,這里搞什么鬼,我又沒修改這里的代碼,怎么就錯了呢?
一臉懵逼的我,回頭看了看,在這里嘗試了斷點調(diào)試,沒有什么結(jié)果。后來意外發(fā)現(xiàn),我的把上面圖中的 getView().succes(content) 注釋掉了就不報錯了。這才找到了原因,原來是這里的數(shù)據(jù)是通過網(wǎng)絡請求傳過來的,我們的 okhttp 需要轉(zhuǎn)到 ui 線程中去更新,這個我是知道的。
所以要記得,切到主線程去更新 UI 操作。雖然發(fā)生了一點小失誤,剛開始以為是動態(tài)代理的問題,所以查了好多關(guān)于動態(tài)代理的知識,借此還能學到一點額外的知識,美滋滋,哈哈。
這里的 BaseFragment 還是有代碼重復的問題,比如我們的依賴注入那塊代碼,就放到下篇文章中去解決這個問題吧,這篇文章已經(jīng)完成我們該完成的任務了,明天的事情放到后天干吧,哈哈哈哈。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android OnFocuChangeListener焦點事件詳解
這篇文章主要為大家詳細介紹了Android OnFocuChangeListener焦點事件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09在Flutter中制作翻轉(zhuǎn)卡片動畫的完整實例代碼
最近Flutter的勢頭是越來越猛了,作為一個Android程序猿,我自然也是想要趕緊嘗試一把,這篇文章主要給大家介紹了關(guān)于在Flutter中制作翻轉(zhuǎn)卡片動畫的相關(guān)資料,需要的朋友可以參考下2021-10-10Android Studio無法執(zhí)行Java類的main方法問題及解決方法
這篇文章主要介紹了Android Studio無法執(zhí)行Java main方法的問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03