【從 0 開始開發(fā)一款直播 APP】5.1 MVP 完全解析 -- 實(shí)現(xiàn)直播登錄

本文為菜鳥窩作者蔣志碧的連載皆刺。“從 0 開始開發(fā)一款直播 APP ”系列來聊聊時(shí)下最火的直播 APP耕突,如何完整的實(shí)現(xiàn)一個(gè)類"騰訊直播"的商業(yè)化項(xiàng)目
視頻地址:http://www.cniao5.com/course/10121


【從 0 開始開發(fā)一款直播 APP】5.1 MVP 完全解析 -- 實(shí)現(xiàn)直播登錄
【從 0 開始開發(fā)一款直播 APP】5.2 MVP 之 Fragment 交互實(shí)現(xiàn)滑動(dòng)導(dǎo)航
【從 0 開始開發(fā)一款直播 APP】5.3 MVC 完全解析 -- 實(shí)現(xiàn)直播登錄


MVP 概述

在無任何架構(gòu)模式下的開發(fā)時(shí)笤成,Activity 和 Model 之間的關(guān)系太緊密,做了所有的操作眷茁,不易維護(hù)炕泳,擴(kuò)展性差。如果所有邏輯都在 Activity 中實(shí)現(xiàn)上祈,代碼顯得臃腫不易維護(hù)和修改培遵。

MVP(Model-View-Presenter浙芙,模型-視圖-表示器)模式則是由 IBM 開發(fā)出來的一個(gè)針對 C++ 和 Java 的編程模型,大概出現(xiàn)于 2000 年籽腕,是 MVC 模式的一個(gè)變種嗡呼,主要用來隔離UI、UI 邏輯和業(yè)務(wù)邏輯节仿、數(shù)據(jù)晤锥。MVP 模式中不容許 View 直接訪問 Model,這是 MVP 和 MVC 之間最大的不同廊宪。View 中應(yīng)該只有 UI 邏輯,捕捉用戶輸入以及視圖的渲染女轿。這樣將其它復(fù)雜的邏輯抽離出來放到 Presenter 中去箭启,這樣就出現(xiàn)了MVP。簡單的說蛉迹,就是將 View 中的復(fù)雜工作抽取到 Presenter 中傅寡,降低了耦合度,便于維護(hù)和測試北救,也增強(qiáng)了復(fù)用性荐操。

Presenter 是 Model 和 View 之間的橋梁,為了讓結(jié)構(gòu)變得更加簡單珍策,View 并不能直接對 Model 進(jìn)行操作托启,這也是 MVP 與 MVC 最大的不同之處。

Model — 業(yè)務(wù)邏輯和數(shù)據(jù)模型攘宙。

Model 層主要負(fù)責(zé)

1屯耸、從網(wǎng)絡(luò),數(shù)據(jù)庫蹭劈,文件疗绣,傳感器,第三方等數(shù)據(jù)源讀寫數(shù)據(jù)铺韧。

2多矮、對外部的數(shù)據(jù)類型進(jìn)行解析轉(zhuǎn)換為 APP 內(nèi)部數(shù)據(jù)交由上層處理。

3哈打、對數(shù)據(jù)的臨時(shí)存儲,管理塔逃,協(xié)調(diào)上層數(shù)據(jù)請求。

View — View 的繪制和用戶交互

View 層主要負(fù)責(zé)

1前酿、提供 UI 交互

2患雏、在 Presenter 的控制下修改 UI

3、將業(yè)務(wù)事件交由 Presenter 處理

注意:View 層不存儲數(shù)據(jù)罢维,不與 Model 層交互

Presenter — View 和 Model 間的交互

Presenter 層主要負(fù)責(zé)

1淹仑、作為 View 和 Model 之間的紐帶丙挽,處理與用戶交互的邏輯實(shí)現(xiàn)。

2匀借、根據(jù)用戶在視圖中的行為更新模型的邏輯

3颜阐、負(fù)責(zé)從 View 視圖中取得數(shù)據(jù)發(fā)送給模型

MVP 優(yōu)點(diǎn)

1、降低耦合度吓肋,實(shí)現(xiàn)了Model和View真正的完全分離凳怨,可以修改 View 而不影響 Model

2、模塊職責(zé)劃分明顯是鬼,層次清晰

3肤舞、隱藏?cái)?shù)據(jù)

4、 Presenter 可以復(fù)用均蜜,一個(gè) Presenter 可以用于多個(gè) View李剖,而不需要更改 Presenter的邏輯(當(dāng)然是在 View 的改動(dòng)不影響業(yè)務(wù)邏輯的前提下)

5、利于測試驅(qū)動(dòng)開發(fā)囤耳。在使用 MVP 的項(xiàng)目中 Presenter 對 View 是通過接口進(jìn)行篙顺,在對 Presenter 進(jìn)行不依賴 UI 環(huán)境的單元測試的時(shí)候〕湓瘢可以通過 Mock 一個(gè) View 對象德玫,這個(gè)對象只需要實(shí)現(xiàn)了 View 的接口即可。然后依賴注入到 Presenter 中椎麦,單元測試的時(shí)候就可以完整的測試 Presenter 應(yīng)用邏輯的正確性宰僧。

6、View可以進(jìn)行組件化铃剔。在MVP當(dāng)中撒桨,View不依賴Model。這樣就可以讓View從特定的業(yè)務(wù)場景中脫離出來键兜,可以說View可以做到對業(yè)務(wù)完全無知凤类。它只需要提供一系列接口提供給上層操作。這樣就可以做到高度可復(fù)用的View組件普气。

7谜疤、提高代碼靈活性

MVP 缺點(diǎn)

1、Presenter中除了應(yīng)用邏輯以外现诀,還有大量的 View -> Model夷磕,Model -> View 的手動(dòng)同步邏輯,造成 Presenter 比較笨重仔沿,維護(hù)起來會(huì)比較困難坐桩。

2、由于對視圖的渲染放在了 Presenter 中封锉,所以視圖和 Presenter 的交互會(huì)過于頻繁绵跷。

3膘螟、如果 Presenter 過多地渲染了視圖,往往會(huì)使得它與特定的視圖的聯(lián)系過于緊密碾局。一旦視圖需要變更荆残,那么 Presenter 也需要變更了。

4净当、額外的代碼復(fù)雜度及學(xué)習(xí)成本内斯。

MVP 使用

MVP 的邏輯實(shí)現(xiàn)類圖,根據(jù) MVP 模型像啼,定義 P 層和 V 層接口以及基本方法以及 M 層相關(guān)方法俘闯。

使用 MVP 大致要做以下步驟

1、創(chuàng)建 IPresenter 接口忽冻,把所有業(yè)務(wù)邏輯接口都定義在內(nèi)备徐,并創(chuàng)建它的實(shí)現(xiàn)類 PresenterImpl。Ipresenter 持有 IView 的引用甚颂,調(diào)用 IView 中的方法。

2秀菱、創(chuàng)建 IView接口振诬,把所有視圖邏輯的接口都定義在內(nèi),創(chuàng)建其實(shí)現(xiàn)類 Activity / Fragment衍菱。

3赶么、IPresenter 間接持有 Model 的引用,但 Model 并不是必須有的脊串,但是一定會(huì)有 Presenter 和 View辫呻。

4、在上圖中可以看到琼锋,Activity 中包含了一個(gè) IPresenter放闺,而 PresenterImpl 里包含了一個(gè) IView并依賴于 Model。Activity 只保留了對 IPresenter 的調(diào)用缕坎,其它工作全部由 PresenterImpl 實(shí)現(xiàn)怖侦。

MVP 實(shí)現(xiàn)登錄

Presenter — LoginPresenter

1、定義 BasePresenter 接口谜叹,主要方法是 start() — presenter 開始處理數(shù)據(jù)匾寝,綁定 Presenter 。 finish() — 處理銷毀工作荷腊,界面結(jié)束時(shí)調(diào)用艳悔,取消綁定 Presenter 的方法。

2女仰、定義 ILoginPresenter 抽象類實(shí)現(xiàn) BasePresenter 接口猜年,定義登錄所需邏輯處理方法抡锈,與 ILoginView 關(guān)聯(lián)。

3码倦、定義 LoginPresenter 繼承 ILoginPresenter 抽象類企孩,實(shí)現(xiàn)抽象方法以及處理邏輯。

View — LoginActivity

1袁稽、定義 BaseView 接口勿璃,將通用方法封裝到里面。

2推汽、定義 ILoginView 接口补疑,定義登錄需要的通用方法。

3歹撒、創(chuàng)建 LoginActivity 實(shí)現(xiàn) ILoginView 接口莲组,并與 LoginPresenter 關(guān)聯(lián)。

Model — UserInfo

根據(jù)登錄請求數(shù)據(jù)定義 Model 所需字段以及 set() 和 get() 方法暖夭,注意:Model 要實(shí)現(xiàn)序列化接口锹杈。


根據(jù)相關(guān)類圖創(chuàng)建包和類。

對登錄不了解的請查看 【從 0 開始開發(fā)一款直播 APP】4.4 網(wǎng)絡(luò)封裝之 OkHttp -- 網(wǎng)絡(luò)請求實(shí)現(xiàn)直播登錄

在后面的代碼中會(huì)涉及到網(wǎng)絡(luò)的狀態(tài)判斷迈着,倒計(jì)時(shí)器的加載竭望,弱引用,ACache 緩存等裕菠,有興趣的可以看看咬清。
【從 0 開始開發(fā)一款直播 APP】6 緩存 ACache 源碼解析
【從 0 開始開發(fā)一款直播 APP】7 倒計(jì)時(shí)器 CountDownTimer 源碼解析
【從 0 開始開發(fā)一款直播 APP】8 弱引用 WeakReference
【從 0 開始開發(fā)一款直播 APP】9 網(wǎng)絡(luò)連接狀態(tài)詳解

Presenter 所有類實(shí)現(xiàn)

1、BasePresenter

public interface BasePresenter{
    /**
     * presenter 開始處理方法
     */
    void start();
    /**
     * 處理一些銷毀工作奴潘,在界面結(jié)束時(shí)候調(diào)用
     */
    void finish();
}

2旧烧、ILoginPresenter

public abstract class ILoginPresenter implements BasePresenter {

    protected BaseView mBaseView;
    public ILoginPresenter(BaseView baseView) {
        mBaseView = baseView;
    }

    /**
     * 檢查手機(jī)號驗(yàn)證碼是否合法
     * @param phone
     * @param verifyCode
     * @return
     */
    public abstract boolean checkPhoneLogin(String phone, String verifyCode);


    /**
     * 檢查用戶名密碼是否合法
     * @param userName
     * @param password
     * @return
     */
    public abstract boolean checkUserNameLogin(String userName, String password);

    /**
     * 手機(jī)號登錄
     * @param phone
     * @param verifyCode
     */
    public abstract void phoneLogin(String phone, String verifyCode);

    /**
     * 用戶名登錄
     * @param userName
     * @param password
     */
    public abstract void userNameLogin(String userName, String password);

    /**
     * 發(fā)送驗(yàn)證碼
     * @param phoneNum
     */
    public abstract void sendVerifyCode(String phoneNum);
}

3、LoginPresenter

實(shí)現(xiàn) ILoginPresenter 接口及其方法的邏輯

public class LoginPresenter extends ILoginPresenter {

    private ILoginView mLoginView;

    public LoginPresenter(ILoginView loginView) {
        super(loginView);
        mLoginView = loginView;
    }

    @Override
    public void start() {

    }

    @Override
    public void finish() {
        //與 View 解綁
        if (mLoginView!=null){
            mLoginView = null;
        }
    }

    @Override
    public boolean checkPhoneLogin(String phone, String verifyCode) {
        //判斷手機(jī)號是否合法
        if (OtherUtils.isPhoneNumValid(phone)) {
            //判斷驗(yàn)證碼是否正確
            if (OtherUtils.isVerifyCodeValid(verifyCode)) {
              //監(jiān)測網(wǎng)絡(luò)狀態(tài)画髓,包括網(wǎng)絡(luò)類型(Wi-Fi掘剪,2G,3G雀扶,4G)以及網(wǎng)絡(luò)是否連接正常杖小,設(shè)置網(wǎng)絡(luò)等
                if (OtherUtils.checkNetWorkState(mLoginView.getContext())) {
                    return true;
                } else {
                    mLoginView.showMsg("當(dāng)前無網(wǎng)絡(luò)連接!");
                }
            } else {
                mLoginView.verifyCodeError("驗(yàn)證碼錯(cuò)誤愚墓!");
            }
        } else {
            mLoginView.phoneError("手機(jī)格式錯(cuò)誤予权!");
        }
        mLoginView.dismissLoading();
        return false;
    }

    @Override
    public boolean checkUserNameLogin(String userName, String password) {
      //判斷用戶名是否合法
        if (OtherUtils.isUsernameVaild(userName)) {
          //判斷密碼是否合法
            if (OtherUtils.isPasswordValid(password)) {
                //監(jiān)測網(wǎng)絡(luò)狀態(tài),包括網(wǎng)絡(luò)類型(Wi-Fi浪册,2G扫腺,3G,4G)以及網(wǎng)絡(luò)是否連接正常村象,設(shè)置網(wǎng)絡(luò)等
                if (OtherUtils.checkNetWorkState(mLoginView.getContext())) {
                    return true;
                } else {
                    mLoginView.showMsg("當(dāng)前無網(wǎng)絡(luò)連接笆环!");
                }
            } else {
                mLoginView.passwordError("密碼過短攒至!");
            }
        } else {
            mLoginView.usernameError("用戶名不符合規(guī)范!");
        }
        //取消loading進(jìn)度顯示
        mLoginView.dismissLoading();
        return false;
    }

    @Override
    public void phoneLogin(final String phone, final String verifyCode) {
        //判斷手機(jī)號和驗(yàn)證碼是否正確
        if (checkPhoneLogin(phone, verifyCode)) {
            final PhoneLoginRequest request = new                               PhoneLoginRequest(RequestComm.loginPhone, phone, verifyCode);
            AsyncHttp.instance().postJson(request, new AsyncHttp.IHttpListener() {
                @Override
                public void onStart(int requestId) {
                  //顯示loading進(jìn)度
                    mLoginView.showLoading();
                }

                @Override
                public void onSuccess(int requestId, Response response) {
                    if (response.getStatus() == RequestComm.SUCCESS) {
              ACache.get(mLoginView.getContext()).put(CacheConstants.LOGIN_PHONE, phone);
                        mLoginView.loginSuccess();
                    } else {
                        mLoginView.loginFailed(response.getStatus(), response.getMsg());
                    }
                    mLoginView.dismissLoading();
                }

                @Override
                public void onFailure(int requestId, int httpStatus, Throwable error) {
                    mLoginView.verifyCodeFailed("網(wǎng)絡(luò)異常");
                    mLoginView.dismissLoading();
                }
            });
        }
    }

    @Override
    public void userNameLogin(final String userName, final String password) {
        //判斷用戶名是否合法
        if (checkUserNameLogin(userName, password)) {
            LoginRequest request = new LoginRequest(RequestComm.loginUsername, userName, password);
            AsyncHttp.instance().postJson(request, new AsyncHttp.IHttpListener() {
                @Override
                public void onStart(int requestId) {
                    mLoginView.showLoading();
                }

                @Override
                public void onSuccess(int requestId, Response response) {
                    if (response.getStatus() == RequestComm.SUCCESS) {
                        //請求數(shù)據(jù)成功
                        UserInfo info = (UserInfo) response.getData();
                        //將登陸數(shù)據(jù)存入緩存躁劣,這個(gè)在后面的文章會(huì)介紹
                        UserInfoCache.saveCache(mLoginView.getContext(), info);
         ACache.get(mLoginView.getContext()).put(CacheConstants.LOGIN_USERNAME, userName);
         ACache.get(mLoginView.getContext()).put(CacheConstants.LOGIN_PASSWORD, password);
                        mLoginView.loginSuccess();
                    } else {
                        mLoginView.loginFailed(response.getStatus(), response.getMsg());
                        mLoginView.dismissLoading();
                    }
                }

                @Override
                public void onFailure(int requestId, int httpStatus, Throwable error) {
                    mLoginView.loginFailed(httpStatus, error.getMessage());
                    mLoginView.dismissLoading();
                }
            });
        }
    }

    @Override
    public void sendVerifyCode(String phoneNum) {
        //判斷手機(jī)號是否合法
        if (OtherUtils.isPhoneNumValid(phoneNum)) {
          //監(jiān)測網(wǎng)絡(luò)狀態(tài)迫吐,包括網(wǎng)絡(luò)類型(Wi-Fi,2G账忘,3G志膀,4G)以及網(wǎng)絡(luò)是否連接正常,設(shè)置網(wǎng)絡(luò)等
            if (OtherUtils.checkNetWorkState(mLoginView.getContext())) {
                VerifyCodeRequest request = new VerifyCodeRequest(RequestComm.verifyCodeRequestId, phoneNum);
                AsyncHttp.instance().postJson(request, new AsyncHttp.IHttpListener() {
                    @Override
                    public void onStart(int requestId) {
                        mLoginView.showLoading();
                    }

                    @Override
                    public void onSuccess(int requestId, Response response) {
                        if (response.getStatus() == RequestComm.SUCCESS) {
                            UserInfo info = (UserInfo) response.getData();
                            if (null != mLoginView){
                                //倒計(jì)時(shí)器 60 秒發(fā)送驗(yàn)證碼
                                mLoginView.verifyCodeSuccess(60,60);
                            }
                        }else {
                            mLoginView.verifyCodeFailed("獲取后臺驗(yàn)證碼失敱钋堋溉浙!");
                        }
                        mLoginView.dismissLoading();
                    }

                    @Override
                    public void onFailure(int requestId, int httpStatus, Throwable error) {

                        if (null != mLoginView){
                            mLoginView.verifyCodeFailed("獲取后臺驗(yàn)證碼失敗蒋荚!");
                        }
                        mLoginView.dismissLoading();
                    }
                });
            } else {
                mLoginView.showMsg("當(dāng)前無網(wǎng)絡(luò)連接戳稽!");
            }
        } else {
            mLoginView.phoneError("手機(jī)號碼不符合規(guī)范!");
        }
    }
}

View 所有類實(shí)現(xiàn)

1期升、BaseView

public interface BaseView {
    /**
     * 數(shù)據(jù)加載或耗時(shí)加載時(shí)界面顯示
     */
    void showLoading();

    /**
     * 數(shù)據(jù)加載或耗時(shí)加載完成時(shí)界面顯示
     */
    void dismissLoading();

    /**
     * 消息提示惊奇,如 Toast,Dialog等
     */
    void showMsg(String msg);
    void showMsg(int msgId);

    /**
     * 獲取Context
     * @return
     */
    Context getContext();
}

2播赁、ILoginView

在 LoginPresenter 中綁定了 ILoginView赊时,在 LoginPresenter 實(shí)現(xiàn)邏輯時(shí)調(diào)用的時(shí) ILoginView 中的方法。

public interface ILoginView extends BaseView {

    /**
     * 登錄成功
     */
    void loginSuccess();

    /**
     * 登錄失敗
     * @param status
     * @param msg
     */
    void loginFailed(int status, String msg);

    /**
     * 用戶名錯(cuò)誤
     * @param errorMsg
     */
    void usernameError(String errorMsg);

    /**
     *手機(jī)號錯(cuò)誤
     * @param errorMsg
     */
    void phoneError(String errorMsg);

    /**
     * 密碼錯(cuò)誤
     * @param errorMsg
     */
    void passwordError(String errorMsg);

    /**
     * 驗(yàn)證碼錯(cuò)誤
     * @param errorMsg
     */
    void verifyCodeError(String errorMsg);

    /**
     * 驗(yàn)證失敗
     * @param errorMsg
     */
    void verifyCodeFailed(String errorMsg);

    /**
     * 驗(yàn)證成功
     * @param reaskDuration
     * @param expireDuration
     */
    void verifyCodeSuccess(int reaskDuration, int expireDuration);

}

3行拢、LoginActivity

之前的文章中有講過用戶登錄實(shí)現(xiàn),那時(shí)是硬實(shí)現(xiàn)诞吱,沒有邏輯分層舟奠,可以結(jié)合前面的文章一起學(xué)習(xí)。

public class LoginActivity extends BaseActivity implements View.OnClickListener, ILoginView {
    //共用控件
    private ProgressBar progressBar;
    private EditText etPassword;
    private EditText etLogin;
    private Button btnLogin;
    private Button btnPhoneLogin;
    private TextInputLayout tilLogin, tilPassword;
    private Button btnRegister;
    //手機(jī)驗(yàn)證登陸控件
    private TextView tvVerifyCode;
    private boolean isPhoneLogin = false;

    private LoginPresenter mLoginPresenter;

    @Override
    protected void setActionBar() {
    }

    @Override
    protected void setListener() {

    }

    @Override
    protected void initData() {
        etLogin.setText(ACache.get(this).getAsString(CacheConstants.LOGIN_USERNAME));
        etPassword.setText(ACache.get(this).getAsString(CacheConstants.LOGIN_PASSWORD));
    }

    @Override
    protected void initView() {
        mLoginPresenter = new LoginPresenter(this);
        etLogin = obtainView(R.id.et_username);
        etPassword = obtainView(R.id.et_password);
        btnRegister = obtainView(R.id.btn_register);
        btnPhoneLogin = obtainView(R.id.btn_phone_login);
        btnLogin = obtainView(R.id.btn_login);
        progressBar = obtainView(R.id.progressbar);
        tilLogin = obtainView(til_login);
        tilPassword = obtainView(R.id.til_password);
        tvVerifyCode = obtainView(R.id.btn_verify_code);

        userNameLoginViewInit();
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_login;
    }

    /**
     * 用戶名密碼登錄界面init
     */
    public void userNameLoginViewInit() {
        //用戶名登錄切換
        userLoginTrans();
        tvVerifyCode.setOnClickListener(this);
        //注冊
        btnRegister.setOnClickListener(this);
        //手機(jī)號登錄
        btnPhoneLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //手機(jī)號登錄
                phoneLoginViewinit();
            }
        });
        //用戶名登錄
        btnLogin.setOnClickListener(this);
    }

    public void phoneLoginViewinit() {
        phoneLoginTrans();
        btnPhoneLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //轉(zhuǎn)換為用戶名登錄界面
                userNameLoginViewInit();
            }
        });

        btnLogin.setOnClickListener(this);
        btnRegister.setOnClickListener(this);
        tvVerifyCode.setOnClickListener(this);
    }

    private void phoneLoginTrans() {
        isPhoneLogin = true;
        tvVerifyCode.setVisibility(View.VISIBLE);
        AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
        alphaAnimation.setDuration(250);
        tvVerifyCode.setAnimation(alphaAnimation);
        //設(shè)定點(diǎn)擊優(yōu)先級于最前(避免被EditText遮擋的情況)
        tvVerifyCode.bringToFront();
        //設(shè)置輸入框輸入類型為 手機(jī)號
        etLogin.setInputType(EditorInfo.TYPE_CLASS_PHONE);
        etLogin.setText("");
        etPassword.setText("");
        //手機(jī)號登錄按鈕文字改為 用戶名登錄
        btnPhoneLogin.setText("用戶名登錄");
        tilLogin.setHint("手機(jī)號");
        tilPassword.setHint("密碼");
    }

    private void userLoginTrans() {
        isPhoneLogin = false;
        tvVerifyCode.setVisibility(View.GONE);
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        alphaAnimation.setDuration(250);
        tvVerifyCode.setAnimation(alphaAnimation);
        etLogin.setInputType(EditorInfo.TYPE_CLASS_TEXT);
        etLogin.setText("");
        etPassword.setText("");
        btnPhoneLogin.setText("手機(jī)號登錄");
        tilLogin.setHint("用戶名");
        tilPassword.setHint("密碼");
    }

    /**
     * 手機(jī)登錄和用戶名登錄界面顯示或隱藏
     * @param active
     */
    public void showOnLoading(boolean active) {
        if (active) {
            progressBar.setVisibility(View.VISIBLE);
            btnLogin.setVisibility(View.INVISIBLE);
            etLogin.setEnabled(false);
            etPassword.setEnabled(false);
            btnPhoneLogin.setClickable(false);
        btnRegister.setTextColor(getResources().getColor(R.color.colorTransparentGray));
       btnPhoneLogin.setTextColor(getResources().getColor(R.color.colorTransparentGray));
            btnRegister.setClickable(false);
        } else {
            progressBar.setVisibility(View.GONE);
            btnLogin.setVisibility(View.VISIBLE);
            etLogin.setEnabled(true);
            etPassword.setEnabled(true);
            btnPhoneLogin.setClickable(true);
            btnRegister.setClickable(true);
            btnRegister.setTextColor(getResources().getColor(R.color.white));
            btnPhoneLogin.setTextColor(getResources().getColor(R.color.white));
        }
    }

    public boolean checkPhoneLogin(String phone, String verifyCode) {
        if (OtherUtils.isPhoneNumValid(phone)) {
            if (OtherUtils.isVerifyCodeValid(verifyCode)) {
                if (OtherUtils.checkNetWorkState(this)) {
                    return true;
                } else {
                    ToastUtils.showShort(this, "當(dāng)前無網(wǎng)絡(luò)連接");
                }
            } else {
                ToastUtils.showShort(this, "驗(yàn)證碼錯(cuò)誤");
            }
        } else {
            ToastUtils.showShort(this, "手機(jī)格式錯(cuò)誤");
        }
        return false;
    }

    public boolean checkUserNameLogin(String userName, String password) {
        if (OtherUtils.isUsernameVaild(userName)) {
            if (OtherUtils.isPasswordValid(password)) {
                if (OtherUtils.checkNetWorkState(this)) {
                    return true;
                } else {
                    ToastUtils.showShort(this, "當(dāng)前無網(wǎng)絡(luò)連接");
                }
            } else {
                ToastUtils.showShort(this, "密碼過短");
            }
        } else {
            ToastUtils.showShort(this, "用戶名不符合規(guī)范");
        }
        return false;
    }

    @Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.btn_login:
                if (isPhoneLogin) {
                    mLoginPresenter.phoneLogin(etLogin.getText().toString(), etPassword.getText().toString());
                } else {
                    mLoginPresenter.userNameLogin(etLogin.getText().toString(), etPassword.getText().toString());
                }
                break;
            case R.id.btn_verify_code:
                mLoginPresenter.sendVerifyCode(etLogin.getText().toString());
                break;
            case R.id.btn_register:
                break;
        }
    }

    @Override
    public void loginSuccess() {
        dismissLoading();
        invoke(this, MainActivity.class);
        finish();
    }

    @Override
    public void loginFailed(int status, String msg) {
        dismissLoading();
        showMsg("登錄失敺课:" + msg);
    }

    @Override
    public void usernameError(String errorMsg) {
        etLogin.setError(errorMsg);
    }

    @Override
    public void phoneError(String errorMsg) {
        etLogin.setError(errorMsg);
    }

    @Override
    public void passwordError(String errorMsg) {
        etPassword.setError(errorMsg);
    }

    @Override
    public void verifyCodeError(String errorMsg) {
        showMsg(errorMsg);
    }

    @Override
    public void verifyCodeFailed(String errorMsg) {
        showMsg(errorMsg);
    }

    @Override
    public void verifyCodeSuccess(int reaskDuration, int expireDuration) {
        showMsg("注冊短信下發(fā)沼瘫,驗(yàn)證碼 " + expireDuration / 60 + " 分鐘內(nèi)有效!");
        OtherUtils.startTimer(new WeakReference<TextView>(tvVerifyCode), "驗(yàn)證碼", reaskDuration, 1);
    }

    @Override
    public void showLoading() {
        showOnLoading(true);
    }

    @Override
    public void dismissLoading() {
        showOnLoading(false);
    }

    @Override
    public void showMsg(String msg) {
        showToast(msg);
    }

    @Override
    public void showMsg(int msgId) {
        showMsg(msgId);
    }

    @Override
    public Context getContext() {
        return this;
    }
}

Model 的定義

1咙俩、UserInfo 要實(shí)現(xiàn)序列化接口

public class UserInfo extends IDontObfuscate{

    private String nickname;
    private int sex;
    private String headPic;
    private String sigId;
    private String userId;
    private String sdkAppId;
    private String sdkAccountType;
    private String token;

    public UserInfo() {
    }

    public UserInfo(String nickname, int sex, String headPic, String userId) {
        this.nickname = nickname;
        this.sex = sex;
        this.headPic = headPic;
        this.userId = userId;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getHeadPic() {
        return headPic;
    }

    public void setHeadPic(String headPic) {
        this.headPic = headPic;
    }

    public String getSigId() {
        return sigId;
    }

    public void setSigId(String sigId) {
        this.sigId = sigId;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getSdkAppId() {
        return sdkAppId;
    }

    public void setSdkAppId(String sdkAppId) {
        this.sdkAppId = sdkAppId;
    }

    public String getSdkAccountType() {
        return sdkAccountType;
    }

    public void setSdkAccountType(String sdkAccountType) {
        this.sdkAccountType = sdkAccountType;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}

2耿戚、UserInfoCache 也要實(shí)現(xiàn)序列化接口

賬號字段在后面的文章介紹,這里直接貼出來阿趁。

public class UserInfoCache extends IDontObfuscate{
    public static void saveCache(Context context, UserInfo info){
        ACache.get(context).put("user_id",info.getUserId());
        ACache.get(context).put("nickname",info.getNickname());
        ACache.get(context).put("head_pic",info.getHeadPic());
        ACache.get(context).put("sig_id",info.getSigId());
        ACache.get(context).put("token",info.getToken());
        ACache.get(context).put("sdk_app_id",info.getSdkAppId());
        ACache.get(context).put("adk_account_type",info.getSdkAccountType());
        ACache.get(context).put("sex",info.getSex());

        if (info.getSdkAppId() != null && TextUtils.isDigitsOnly(info.getSdkAccountType())){
            Constants.IMSDK_ACCOUNT_TYPE = Integer.parseInt(info.getSdkAccountType());
        }
    }

    public static String getUserId(Context context){
        return ACache.get(context).getAsString("user_id");
    }

    public static String getNickname(Context context){
        return ACache.get(context).getAsString("nickname");
    }

    public static String getHeadPic(Context context){
        return ACache.get(context).getAsString("head_pic");
    }

    public static String getSigId(Context context){
        return ACache.get(context).getAsString("sig_id");
    }

    public static String getToken(Context context){
        return ACache.get(context).getAsString("token");
    }

    public static String getSdkAccountType(Context context){
        return ACache.get(context).getAsString("adk_account_type");
    }

    public static String getSdkAppId(Context context){
        return ACache.get(context).getAsString("sex");
    }

    public static String getSex(Context context){
        return ACache.get(context).getAsString("sdk_app_id");
    }

    public static void clearCache(Context context){
        ACache.get(context).remove("user_id");
        ACache.get(context).remove("nickname");
        ACache.get(context).remove("head_pic");
        ACache.get(context).remove("sig_id");
        ACache.get(context).remove("token");
        ACache.get(context).remove("adk_account_type");
        ACache.get(context).remove("sdk_app_id");
        ACache.get(context).remove("sex");
    }
}

登錄實(shí)現(xiàn)運(yùn)行效果


參考:

http://www.reibang.com/p/9a6845b26856

http://www.reibang.com/p/f6252719b3af

140套Android優(yōu)秀開源項(xiàng)目源碼膜蛔,領(lǐng)取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市脖阵,隨后出現(xiàn)的幾起案子皂股,更是在濱河造成了極大的恐慌,老刑警劉巖命黔,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呜呐,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蒜茴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門浇冰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洋魂,你說我怎么就攤上這事绷旗。” “怎么了忧设?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵刁标,是天一觀的道長。 經(jīng)常有香客問我址晕,道長膀懈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任谨垃,我火速辦了婚禮启搂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刘陶。我一直安慰自己胳赌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布匙隔。 她就那樣靜靜地躺著疑苫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纷责。 梳的紋絲不亂的頭發(fā)上捍掺,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機(jī)與錄音再膳,去河邊找鬼挺勿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛喂柒,可吹牛的內(nèi)容都是我干的不瓶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼灾杰,長吁一口氣:“原來是場噩夢啊……” “哼蚊丐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起艳吠,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤吠撮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泥兰,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弄屡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鞋诗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膀捷。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖削彬,靈堂內(nèi)的尸體忽然破棺而出全庸,到底是詐尸還是另有隱情,我是刑警寧澤融痛,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布壶笼,位于F島的核電站,受9級特大地震影響雁刷,放射性物質(zhì)發(fā)生泄漏覆劈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一沛励、第九天 我趴在偏房一處隱蔽的房頂上張望责语。 院中可真熱鬧,春花似錦目派、人聲如沸坤候。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽白筹。三九已至,卻和暖如春谅摄,著一層夾襖步出監(jiān)牢的瞬間遍蟋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工螟凭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人它呀。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓螺男,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纵穿。 傳聞我的和親對象是個(gè)殘疾皇子下隧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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