Android---MVP學(xué)習(xí)之路(三)

通過 Andriod---MVP學(xué)習(xí)之路(二)亲雪,我們通過attachview和detachView很好解決了空指針問題。現(xiàn)在還有一個(gè)問題惨好,如果手機(jī)屏幕旋轉(zhuǎn)等,觸發(fā)了activity的重啟随闺,該怎么處理Presenter呢日川?

有如下幾種方法:

通過onSaveInstanceState恢復(fù)數(shù)據(jù)

通過onSaveInstanceState保存界面和presenter的數(shù)據(jù)。橫豎屏切換后通鍋onCreated中的onSaveInstanceState恢復(fù)數(shù)據(jù)

將Presenter保存在一個(gè)地方板壮,再次onCreate時(shí)還原

很多實(shí)現(xiàn)方法

最佳方案逗鸣,通過Loader延長Presenter生命周期

什么是Loader合住?它有什么用绰精?

我們都知道,當(dāng)手機(jī)狀態(tài)發(fā)生改變比如旋轉(zhuǎn)時(shí)透葛,Activity會(huì)重新啟動(dòng)笨使。Loader是Android框架中提供的在手機(jī)狀態(tài)改變時(shí)不會(huì)被銷毀的工具。Loader的生命周期是是由系統(tǒng)控制的僚害,只有在向Loader請求數(shù)據(jù)的Activity/Fragment被永久銷毀時(shí)才會(huì)被清除硫椰,所以也不需要自己寫代碼來清空它。

一般Loader是用來在后臺(tái)加載數(shù)據(jù)的萨蚕,而且是用它的子類CursorLoader或AsyncTaskLoader靶草,尤其是CursorLoader,直接就綁定了Content Provider和數(shù)據(jù)庫岳遥。當(dāng)然如果寫個(gè)類繼承Loader基類的話也不需要開啟后臺(tái)線程奕翔。
聽起來好厲害。但這和Presenter有什么關(guān)系浩蓉?

就像剛才說的一樣派继,關(guān)鍵問題就是在哪里存儲(chǔ)Presenter以及什么時(shí)候銷毀它們。而我們剛剛就看到了Loader的強(qiáng)大之處:由安卓系統(tǒng)框架提供捻艳,有單獨(dú)生命周期驾窟,會(huì)被自動(dòng)回收且不必在后臺(tái)運(yùn)行。

所以思考一下需求以及Loader的功能认轨,我們可以讓Loader作為Presenter的提供者绅络,而不需要擔(dān)心手機(jī)狀態(tài)改變。

將同步的Loader作為存放Presenter的緩存嘁字。

這里的重點(diǎn)就在于同步使用Loader時(shí)昨稼,我們可以知道在生命周期的哪個(gè)階段Presenter被創(chuàng)建了并且可以工作了。甚至是在Activity/Fragment可見之前拳锚。

要注意的是假栓,Activity和Fragment在何時(shí)傳遞Presenter對象這個(gè)問題上是有區(qū)別的。對于任何一個(gè)Activity實(shí)例霍掺,只需要在調(diào)用super.onStart()之后就可以使用Presenter了匾荆,但對于Fragment實(shí)例來說拌蜘,首次創(chuàng)建時(shí)可以在super.onStart()之后傳入,但在Fragment被重新create時(shí)牙丽,就必須要在super.onResume()之后傳入了简卧。所以在Fragment中,只需要在onResume()方法執(zhí)行后將Presenter傳入就行了烤芦。

使這種方法可行的另外一個(gè)要點(diǎn)就是系統(tǒng)對Loader的處理方式举娩,每一個(gè)Activity/Fragment都有一個(gè)LoaderManager,而且只有這個(gè)LoaderManager可以管理與Activity/Fragment相關(guān)聯(lián)的Loader构罗,這就使得相同的Fragment與其Presenter可以同時(shí)存在多個(gè)實(shí)例铜涉。

上代碼,繼上篇文章中的項(xiàng)目代碼遂唧,進(jìn)行改造芙代。

先看以下步驟完成后的項(xiàng)目目錄:


圖片.png
步驟1:創(chuàng)建PresenterLoader
public class PresenterLoader<T extends BaseMVPPresenterImp> extends Loader<T> {

    public static String TAG = "PresenterLoader";
    private final PresenterFactory<T> factory;
    private T presenter;


    public PresenterLoader(Context context, PresenterFactory factory) {
        super(context);
        this.factory = factory;
    }

    @Override
    protected void onStartLoading() {
        Log.i(TAG, "hpdhpd11 PresenterLoader onStartLoading");
        if (presenter != null) {
            // 如果已經(jīng)有Presenter,就直接返回盖彭,其實(shí)不用這個(gè)方法也行纹烹,因?yàn)榉祷氐氖峭粋€(gè)對象
            //deliverResult(presenter)不會(huì)觸發(fā)onLoadFinished。
            deliverResult(presenter);
            return;
        }
        // 如果沒有召边,需要執(zhí)行onForceLoad
        forceLoad();
    }

    @Override
    protected void onForceLoad() {
        Log.i(TAG, "hpdhpd11 PresenterLoader onForceLoad");
        // 通過工廠來實(shí)例化Presenter
        presenter = factory.create();
        // 返回Presenter
        deliverResult(presenter);
    }

    @Override
    protected void onReset() {
        Log.i(TAG, "hpdhpd11 PresenterLoader onReset");
        presenter = null;
    }
}

onStartLoading():會(huì)在Activity的onStart()調(diào)用之后被系統(tǒng)調(diào)用來獲取一個(gè)Loader實(shí)例铺呵。在這里先判斷是否已經(jīng)有Presenter對象了還是需要?jiǎng)?chuàng)建。

onForceLoad():在調(diào)用forceLoad()方法后自動(dòng)調(diào)用隧熙,我們在這個(gè)方法中創(chuàng)建Presenter并返回它片挂。

deliverResult():會(huì)將Presenter傳遞給Activity/Fragment。

onReset():會(huì)在Loader被銷毀之前調(diào)用贱鼻,我們可以在這里告知Presenter以終止某些操作或進(jìn)行清理工作宴卖。

PresenterFactory:這個(gè)接口可以隱藏創(chuàng)建Presenter所需要的參數(shù)。通過這個(gè)接口我們可以調(diào)用各種構(gòu)造器邻悬,這樣可以避免寫一堆PresenterLoader的子類來返回不同類型的Presenter症昏。
這個(gè)接口形式上大概就是這樣:

public interface PresenterFactory<T extends BaseMVPPresenterImp> {
    T create();
}
在Activity/Fragment中如何實(shí)現(xiàn)呢?

現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了Loader父丰,下面要將Loader和Activity/Fragment連接起來肝谭,剛才說過,這個(gè)連接點(diǎn)就是LoaderManager蛾扇。我們需要調(diào)用FragmentActivity的getSupportLoaderManager()或Fragment的getLoaderManager()方法來獲得LoaderManager實(shí)例并調(diào)用其initLoader()方法攘烛。Google建議在Activity的onCreate()與Fragment的onActivityCreated()中調(diào)用此方法。

當(dāng)調(diào)用initLoader()方法時(shí)要傳入一個(gè)id镀首,只需要保證在一個(gè)Activity/Fragment內(nèi)單一即可坟漱,不需要全局單一。這個(gè)id就是用來識(shí)別Loader的更哄。還可以選擇傳入一個(gè)Bundle芋齿,但在這個(gè)例子中不需要腥寇。還要穿入一個(gè)LoaderCallbacks實(shí)例。

剛才說過觅捆,不要再Fragment的onCreate()方法中調(diào)用initLoader()方法赦役,要在onActivityCreated()中調(diào)用它,不然就會(huì)遇到不同F(xiàn)ragment共享一個(gè)Loader的問題栅炒。

如果你發(fā)現(xiàn)在手機(jī)狀態(tài)改變時(shí)onLoadFinished()會(huì)被調(diào)用兩次掂摔,不妨參考stackoverflow下的這個(gè)問題。Presenter在被傳入Activity后的邏輯可能會(huì)使這個(gè)成為一個(gè)問題赢赊,此時(shí)可以嘗試在Fragment的onResume()方法中調(diào)用initLoader()方法乙漓,或者在onActivityCreate()方法中存一個(gè)flag以防多次獲取Presenter。

當(dāng)我們調(diào)用了initLoader()后Loader和Activity/Fragment的生命周期就綁定了:執(zhí)行onStart()方法時(shí)會(huì)調(diào)用onStartLoading()域携,執(zhí)行onStop()方法時(shí)會(huì)調(diào)用onStopLoading()簇秒。但onReset()方法只會(huì)在Activity/Fragment被銷毀或主動(dòng)調(diào)用destroyLoader()時(shí)被調(diào)用鱼喉。

LoaderManager有一個(gè)restartLoader()方法可以強(qiáng)制重新加載秀鞭。不過除非我們需要重新創(chuàng)建Presenter,不然不需要調(diào)用這個(gè)方法扛禽。

通過LoaderCallbacks獲取Presenter

LoaderCallbacks是Activity/Fragment和Loader之間的橋梁锋边。共有三個(gè)回調(diào)方法:

onCreateLoader():在這里構(gòu)造Loader實(shí)例。

onLoadFinished():Loader在這里傳入數(shù)據(jù)编曼,在這個(gè)例子中豆巨,也就是Presenter。

onLoadReset():在這里清除對于數(shù)據(jù)的引用掐场。

修改BaseMVPActivity

public class BaseMVPActivity<P extends BaseMVPPresenterImp<V>, V extends BaseMVPViewInterface> extends HPDBaseActivity implements BaseMVPViewInterface, LoaderManager.LoaderCallbacks<P> {

    public final static int BASE_LOADER_ID = 100;
    protected ProgressDialog progressDialog;
    protected P presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "hpdhpd11 BaseMVPActivity onCreate");
        progressDialog = new ProgressDialog(this);
        getSupportLoaderManager().initLoader(BASE_LOADER_ID, null, this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.i(TAG, "hpdhpd11 BaseMVPActivity onStart");
        if (presenter != null) {
            presenter.attachView((V) this);
        }
    }

    @Override
    protected void onDestroy() {
        Log.i(TAG, "hpdhpd11 BaseMVPActivity onDestroy");
        if (presenter != null) {
            presenter.detachView();
        }
        super.onDestroy();
    }

    @Override
    public void showLoading() {

        progressDialog.setMessage("showLoading");
        if (!progressDialog.isShowing()) {
            progressDialog.show();
        }
    }

    @Override
    public void hideLoading() {

        if (progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

    @Override
    public void showFailError(String message) {
        if (TextUtils.isEmpty(message)) {
            message = "發(fā)生錯(cuò)誤";
        }
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }


    @Override
    public Loader<P> onCreateLoader(int id, Bundle args) {

        return null;
    }

    @Override
    public void onLoadFinished(Loader<P> loader, P data) {
        Log.i(TAG, "hpdhpd11 BaseMVPActivity onLoadFinished");
        presenter = data;
    }

    @Override
    public void onLoaderReset(Loader<P> loader) {
        Log.i(TAG, "hpdhpd11 BaseMVPActivity onLoaderReset");
        presenter = null;
    }
}

修改LoginActivity

public class LoginActivity extends BaseMVPActivity<LoginPresenter, LoginViewInterface> implements LoginViewInterface {

    private EditText etName, etPassword;
    private Button btnLogin, btnClear;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        Log.i(TAG, "hpdhpd11 LoginActivity onCreate");
        initView();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "hpdhpd11 LoginActivity onDestroy");
    }

    @Override
    public Loader<LoginPresenter> onCreateLoader(int id, Bundle args) {
        return new PresenterLoader(this, new PresenterFactory<LoginPresenter>() {
            @Override
            public LoginPresenter create() {
                Log.i(TAG, "hpdhpd11 LoginActivity onCreateLoader");
                return new LoginPresenter();
            }
        });
    }


    private void initView() {

        etName = findViewById(R.id.et_name);
        etPassword = findViewById(R.id.et_password);
        btnLogin = findViewById(R.id.btn_login);
        btnClear = findViewById(R.id.btn_clear);

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                presenter.login();
            }
        });

        btnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                presenter.clear();
            }
        });

    }

    @Override
    public String getUserName() {
        return etName.getText().toString().trim();
    }

    @Override
    public String getUserPassword() {
        return etPassword.getText().toString().trim();
    }

    @Override
    public void clearName() {
        etName.setText("");
    }

    @Override
    public void clearPassword() {
        etPassword.setText("");
    }

    @Override
    public void go2OtherActivity() {
        Toast.makeText(this, "go2OtherActivity", Toast.LENGTH_SHORT).show();
    }

}

我們再看看打印的Log往扔,看看Log是怎么走的?
進(jìn)入這個(gè)LoginActivity后:可以看到熊户,onLoadFinished之后萍膛,也就是presenter已經(jīng)是綁定了。然后activity的onStart執(zhí)行了嚷堡, 就可以交互了

com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onCreate
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreate
com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onStartLoading
com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onForceLoad
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreateLoader
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoadFinished
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart

當(dāng)我們關(guān)閉屏幕后蝗罗,再打開APP,看看Log是怎么走的蝌戒?
可以看到串塑,執(zhí)行了PresenterLoader的onStartLoading方法,就是去檢查presenter是否存在北苟,不存在則創(chuàng)建桩匪,存在則返回,因?yàn)榘l(fā)返回的是同一個(gè)對象友鼻,deliverResult(presenter)不會(huì)觸發(fā)onLoadFinished傻昙。

com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onStartLoading
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart

當(dāng)我們橫豎屏切換時(shí)瑟慈,看看Log是怎么走的?
可以看到,橫豎屏切換后屋匕,activity會(huì)重啟葛碧,getSupportLoaderManager().initLoader(BASE_LOADER_ID, null, this);執(zhí)行后过吻,因?yàn)橐呀?jīng)存在這個(gè)對應(yīng)ID的loader进泼,會(huì)直接執(zhí)行onLoadFinished(),返回這個(gè)presenter.

com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onDestroy
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onDestroy
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onCreate
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreate
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoadFinished
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart

當(dāng)我們推出這個(gè)activity后纤虽,看看Log是怎么走的乳绕?
可以看到:activity后,會(huì)執(zhí)行onLoaderReset和onReset方法逼纸,我們可以在這個(gè)方法里面釋放presenter洋措。

com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onDestroy
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoaderReset
com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onReset
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onDestroy

我們之后寫Activity的時(shí)候,可以直接使用繼承BaseMVPActivity,并指定泛型杰刽。如:

public class LoginActivity extends BaseMVPActivity<LoginPresenter, LoginViewInterface> implements LoginViewInterface 

并且實(shí)現(xiàn)onCreateLoader方法菠发,如:

  @Override
    public Loader<LoginPresenter> onCreateLoader(int id, Bundle args) {
        return new PresenterLoader(this, new PresenterFactory<LoginPresenter>() {
            @Override
            public LoginPresenter create() {
                Log.i(TAG, "hpdhpd11 LoginActivity onCreateLoader");
                return new LoginPresenter();
            }
        });
    }

就不用再去管presenter的狀態(tài)。

參考文章:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0314/4050.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贺嫂,一起剝皮案震驚了整個(gè)濱河市滓鸠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌第喳,老刑警劉巖糜俗,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異曲饱,居然都是意外死亡悠抹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門扩淀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來楔敌,“玉大人,你說我怎么就攤上這事引矩×呵穑” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵旺韭,是天一觀的道長氛谜。 經(jīng)常有香客問我,道長区端,這世上最難降的妖魔是什么值漫? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮织盼,結(jié)果婚禮上杨何,老公的妹妹穿的比我還像新娘酱塔。我一直安慰自己,他們只是感情好危虱,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布羊娃。 她就那樣靜靜地躺著,像睡著了一般埃跷。 火紅的嫁衣襯著肌膚如雪蕊玷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天弥雹,我揣著相機(jī)與錄音垃帅,去河邊找鬼。 笑死剪勿,一個(gè)胖子當(dāng)著我的面吹牛贸诚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厕吉,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼酱固,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赴涵?” 一聲冷哼從身側(cè)響起媒怯,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤订讼,失蹤者是張志新(化名)和其女友劉穎髓窜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欺殿,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寄纵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了擒滑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渊抽。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讼渊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恃鞋,到底是詐尸還是另有隱情,我是刑警寧澤亦歉,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布恤浪,位于F島的核電站,受9級特大地震影響肴楷,放射性物質(zhì)發(fā)生泄漏水由。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一赛蔫、第九天 我趴在偏房一處隱蔽的房頂上張望砂客。 院中可真熱鬧泥张,春花似錦、人聲如沸鞠值。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彤恶。三九已至筝野,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粤剧,已是汗流浹背歇竟。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抵恋,地道東北人焕议。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像弧关,于是被迫代替她去往敵國和親盅安。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,302評論 25 707
  • Google官方MVP Sample代碼解讀 關(guān)于Android程序的構(gòu)架, 當(dāng)前(2016.10)最流行的模式即...
    圣騎士wind閱讀 3,979評論 2 62
  • 舒安經(jīng)常丟東西世囊。 打從小别瞭,舒安就這樣了。出去玩一趟株憾,最喜歡的帽子不見了蝙寨;在外面吃頓飯,懷中的小熊落下了...
    冬天的柴犬金閃閃閱讀 309評論 0 0
  • 平時(shí)調(diào)侃夸亨拖梗口墙歪, 你是棍他膽大, 地震來時(shí)現(xiàn)原形贝奇, 統(tǒng)統(tǒng)楞充硬裝的虹菲。 老天扯把離根扔, 地動(dòng)樓搖玩驚嚇掉瞳。 不論高官...
    一葉知秋99閱讀 227評論 0 1
  • 全局的對所有用戶都可以的使用的PATH: 可以通過修改配置文件:/etc/.bashrc 和 /etc/profi...
    無良之徒閱讀 1,406評論 0 0