Toutiao 項目源碼閱讀

項目地址
一款第三方今日頭條客戶端, MVP + RxJava + Retrofit

架構

標準的MVP架構

IBasePresenter

每個P層都有刷新界面以及顯示網絡錯誤需求

public interface IBasePresenter {
    void doRefresh();
    void doShowNetError();
}
IBaseView

每個V層都有展示狀態頁,設置Presenter,以及解除RxJava訂閱防止內存泄漏的接口.

public interface IBaseView<T> {
    void onShowLoading();
    void onHideLoading();
    void onShowNetError();
    void setPresenter(T presenter); // P類型由V層泛型決定
    <X> AutoDisposeConverter<X> bindAutoDispose();
}
對新聞頁架構分析

新建INewsArticle接口,其中INewsArticle.View接口由NewsArticleView實現,INewsArticle.PresenterNewsArticlePresenter實現.

public interface INewsArticle {
    // INewsArticle.View接口繼承自IBaseListView接口,可以看作是對IBaseListView接口方法的補充.
    // INewsArticle.View接口中定義了新聞頁的行爲方法
    // IBaseListView<Presenter> 該泛型決定了P層類型,也就是該P層類型必須爲INewsArticle.Presenter類型.
    interface View extends IBaseListView<Presenter> {
        void onLoadData();
        void onRefresh();
    }
    // INewsArticle.Presenter接口繼承自IBasePresenter接口,可以看作是對IBaseListView接口方法的擴充
    // INewsArticle.Presenter接口中定義了新聞頁P層的行爲方法
    interface Presenter extends IBasePresenter {
        void doLoadData(String... category);
        void doLoadMoreData();
        void doSetAdapter(List<MultiNewsArticleDataBean> dataBeen);
        void doShowNoMore();
    }
}
對新聞頁分析
  1. NewsArticlePresenter
public class NewsArticlePresenter implements INewsArticle.Presenter {
    private INewsArticle.View view;// NewsArticleView層引用, 用來更新UI.
    public NewsArticlePresenter(INewsArticle.View view) {
            this.view = view;
    }
}
  1. NewsArticleView
// <INewsArticle.Presenter>: 該泛型是用來確定創建得P層對象的類型.
public class NewsArticleView extends BaseListFragment<INewsArticle.Presenter> implements INewsArticle.View {
    // IBaseView接口中定義得setPresenter()方法,在這裏實現.
    @Override
    public void setPresenter(INewsArticle.Presenter presenter) {
        if (null == presenter) {
            // this.presenter: 在BaseFragment中定義了一個變量presenter,專門用來接收P層對象..
            this.presenter = new NewsArticlePresenter(this);
        }
    }
}
MVP架構總結
// 頂層接口定義一些共有的方法
interface IBaseP {
}

interface IBaseV<T> {
    // 爲V層創建P層對象
    // 泛型T確定了P層對象的類型
    T setPresenter(T t);
}

// 次級接口定義自己特有的方法
interface IMy {
    // <MyP>: 確定P層對象的類型爲MyP
    interface MyV extends IBaseV<MyP> {
        void refreshPage();// 更新UI
    }

    interface MyP extends IBaseP {
        void getData();// 獲取數據
    }
}
// P層的實現
// <T extends IMy.MyV>: 確定P層持有的V層類型
class MyPresenter<T extends IMy.MyV> implements IMy.MyP {
    T mView;

    public MyPresenter(T mView) {
        this.mView = mView;
    }

    @Override
    public void getData() {
        System.out.println("獲取數據");
        mView.refreshPage();// 獲取數據之後更新界面
    }
}
// 定義BaseActivity,<T>用來確定P層對象的類型.
abstract class BaseActivity<T> implements IBaseV<T> {
    T mPresenter;

    public BaseActivity() {
        // 調用子類實現的setPresenter()方法,創建P層對象,由父類mPresenter變量保存.
        mPresenter = setPresenter(mPresenter);
    }
}
// V層實現類
// <IMy.MyP>: 確定P層對象類型.
class MyActivity extends BaseActivity<IMy.MyP> implements IMy.MyV {
    public MyActivity() {
        mPresenter.getData();
    }

    @Override
    public void refreshPage() {
        System.out.println("更新界面");
    }
    // IBaseV.setPresenter()接口方法的具體實現.
    @Override
    public IMy.MyP setPresenter(IMy.MyP myP) {
        // <IMy.MyV>: 用來確認P層中V對象的類型.
        return new MyPresenter<IMy.MyV>(this);
    }
}
public class MVP {
    public static void main(String[] args) {
        // 模擬Activity啓動之後獲取數據然後刷新界面
        new MyActivity();
    }
}

網絡

網絡框架使用的是Retorfit+OkHttp+RxJava

Cookie管理

使用的是一個第三方庫PersistentCookieJar
簡單的設置如下:

ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(InitApp.AppContext));
OkHttpClient client = new OkHttpClient.Builder()
        .cookieJar(cookieJar)
        .cache(cache)
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(15, TimeUnit.SECONDS)
        .writeTimeout(15, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .build();
緩存管理

OkHttp的緩存推薦使用RxCache,這個真的非常好用.

處理RxJava內存泄漏

項目中沒有使用RxLifecycle,使用的是AutoDispose.
例子:

 RetrofitFactory.getRetrofit().create(IMobileNewsApi.class)
         .getNewsComment(groupId, offset)
         .subscribeOn(Schedulers.io())
         ...
         .observeOn(AndroidSchedulers.mainThread())
         .as(view.bindAutoDispose()) // bindAutoDispose該方法由View層實現.
         .subscribe(list -> {
             if (null != list && list.size() > 0) {
                 doSetAdapter(list);
             } else {
                 doShowNoMore();
             }
         }, throwable -> {
             doShowNetError();
             ErrorAction.print(throwable);
         });

View層如何實現的bindAutoDispose()

public abstract class BaseFragment<T extends IBasePresenter> extends Fragment implements IBaseView<T> {
    /**
     * 綁定生命週期
     */
    @Override
    public <X> AutoDisposeConverter<X> bindAutoDispose() {
        return AutoDispose.autoDisposable(AndroidLifecycleScopeProvider
                .from(this, Lifecycle.Event.ON_DESTROY)); // 當Activity被銷燬時候,接觸RxJava間的訂閱.
    }
}

高亮提示效果

TapTargetView使用的是這個庫,項目中是在MainActivity.showTapTarget()方法中用到.

動態設置APP屬性

項目中SettingUtil工具類可以設置主題顏色,導航欄上色,視頻橫屏,字體大小等.

manifestActivity配置

<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenSize|uiMode"
    android:label="@string/app_name"
    android:theme="@style/AppTheme.NoActionBar"/>

Android 設備運行時候,設備的配置可能發生改變,如橫豎屏切換,鍵盤等,這樣會導致Activity重啓,銷燬前先調用onSaveInstanceState()保存應用中的一些數據,供Activity恢復時候使用.如果想讓手機的配置改變不重啓Activity的話需要爲當前Activity的android:configChanges添加某些屬性,這樣當配置改變時不會重啓Activity而是會調用Activity的onConfigurationChanged()方法.

  • navigation:導航欄發生了變化
  • fontScale: 字體發生了變化
  • uiMode:用戶模式發生了變化
  • orientation:屏幕方向發生了變化
  • screenSize:屏幕大小發生了變化
  • keyboardHidden: 鍵盤可用性發生了變化
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章