FanChat學習筆記(三)——註冊頁

因爲註冊頁和上一篇的FanChat學習筆記(二)——登錄頁框架很類似,幾乎沒有多少新知識,所以這篇文章就不像上文那樣剖析了,只講我看到的覺得可以單獨記錄下來的地方,如果對MVP不熟悉的話,可以先看看學習筆記一學習筆記二。今天我們需要在學習代碼之前,先看看作者需要實現的業務邏輯,原文是這樣介紹的:

  1. 實際項目中,註冊會將用戶名和密碼註冊到APP的服務器,然後APP的服務器再通過REST API方式註冊到環信服務器。
  2. 由於本項目沒有APP服務器,會將用戶數據註冊到第三方雲數據庫Bmob,註冊成功後,再在客戶端發送請求註冊到環信服務器。

這裏寫圖片描述

明白了上面的邏輯後,接下來看看java代碼的具體實現,代碼如下:

package com.itheima.leon.qqdemo.presenter.impl;

import com.hyphenate.chat.EMClient;
import com.hyphenate.exceptions.HyphenateException;
import com.itheima.leon.qqdemo.app.Constant;
import com.itheima.leon.qqdemo.model.User;
import com.itheima.leon.qqdemo.presenter.RegisterPresenter;
import com.itheima.leon.qqdemo.utils.StringUtils;
import com.itheima.leon.qqdemo.utils.ThreadUtils;
import com.itheima.leon.qqdemo.view.RegisterView;

import cn.bmob.v3.exception.BmobException;
import cn.bmob.v3.listener.SaveListener;

/**
 * 創建者:   Leon
 * 創建時間:  2016/10/16 22:21
 * 描述:    註冊
 */
public class RegisterPresenterImpl implements RegisterPresenter {
    public static final String TAG = "RegisterPresenterImpl";

    public RegisterView mRegisterView;

    public RegisterPresenterImpl(RegisterView registerView) {
        mRegisterView = registerView;
    }

    @Override
    public void register(String userName, String pwd, String pwdConfirm) {
    //檢查用戶名是否符合規範
        if (StringUtils.checkUserName(userName)) {
        //檢查密碼是否符合規範
            if (StringUtils.checkPassword(pwd)) {
            //檢查確認密碼與輸入密碼是否一致
                if (pwd.equals(pwdConfirm)) {
                //UI展示開始登錄
                    mRegisterView.onStartRegister();
                    registerBmob(userName, pwd);
                } else {
                //二次輸入密碼錯誤
                    mRegisterView.onConfirmPasswordError();
                }
            } else {
            //密碼不符合規範
                mRegisterView.onPasswordError();
            }
        } else {
        //用戶名不符合規範
            mRegisterView.onUserNameError();
        }
    }

    /**
     * 註冊用戶到Bmob
     * @param userName
     * @param pwd
     */
    private void registerBmob(final String userName, final String pwd) {
        User user = new User(userName, pwd);
        user.signUp(new SaveListener<User>() {
            @Override
            public void done(User user, BmobException e) {
                if (e == null) {
                    registerEaseMob(userName, pwd);
                } else {
                    notifyRegisterFailed(e);
                }
            }
        });
    }

    /**
     * 註冊到環信
     * @param userName
     * @param pwd
     */
    private void registerEaseMob(final String userName, final String pwd) {
        ThreadUtils.runOnBackgroundThread(new Runnable() {
            @Override
            public void run() {
                try {
                    EMClient.getInstance().createAccount(userName, pwd);
                    ThreadUtils.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mRegisterView.onRegisterSuccess();
                        }
                    });
                } catch (HyphenateException e) {
                    e.printStackTrace();
                    ThreadUtils.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mRegisterView.onRegisterError();
                        }
                    });
                }
            }
        });
    }

    /**
     * 註冊頁失敗異常甄別
     * @param e
     */
    private void notifyRegisterFailed(BmobException e) {
        if (e.getErrorCode() == Constant.ErrorCode.USER_ALREADY_EXIST) {
            mRegisterView.onResisterUserExist();
        } else {
            mRegisterView.onRegisterError();
        }
    }
}

既然是用戶,那麼還是先看看Ueer這個實體是怎麼來定義的?代碼如下:

package com.itheima.leon.qqdemo.model;

import cn.bmob.v3.BmobUser;

/**
 * 創建者:   Leon
 * 創建時間:  2016/10/16 23:31
 * 描述:    用戶實體
 */
public class User extends BmobUser {

    public User(String userName, String password) {
        setUsername(userName);
        setPassword(password);
    }

}

對於雲數據庫Bmob,我們可以把它理解爲一個服務器。那麼我們繼承了服務器中的用戶,然後對用戶的用戶名和密碼進行了賦值。至於用戶的其它諸如手機號碼、郵箱地址等可選字段,大家可以查看官方文檔,這裏不作過多的介紹。其它文章中用到的話,我們及時查看官方文檔即可。

接下來我還需要看看它ThreadUtils類的線程處理方法:

runOnUiThread(Runnable runnable)
runOnBackgroundThread(Runnable runnable)

在查看方法的源碼之前,我覺得這個項目中的命名都非常規範,有一種讓人一眼看上去能見名知意的感覺,比如“runOnUiThread”我知道是需要在主線程執行的,“runOnBackgroundThread”我知道是後臺子線程執行的,所以這種命名應該值得我們學習。話不多說,接下來看看方法的具體實現,我們找到源碼:

package com.itheima.leon.qqdemo.utils;

import android.os.Handler;
import android.os.Looper;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * 創建者:   Leon
 * 創建時間:  2016/10/16 23:43
 * 描述:    線程工具類
 */
public class ThreadUtils {
    public static final String TAG = "ThreadUtils";
    //SingleThreadExecutor使用單線程執行任務
    //SingleThreadExecutor保證了任務執行的順序,不會存在多線程活動。
    private static Executor sExecutor = Executors.newSingleThreadExecutor();

    private static Handler sHandler = new Handler(Looper.getMainLooper());
    //將Runnable作爲callback屬性然後生產一個新的Message對象,通過Handler發送出去
    //重點是沒有產生新的線程
    public  static void runOnUiThread(Runnable runnable) {
        sHandler.post(runnable);
    }
    //開子線程執行任務
    public static void runOnBackgroundThread(Runnable runnable) {
        sExecutor.execute(runnable);
    }
}

這個工具類代碼量很少,但是卻非常有意義。Executor我第一次接觸是在弘神的自定義ImageLoader項目看見的,當時的理解就是一個線程池的管理工具,但是我現在才知道,它原來也有很多的構造方法,除了上文中提到的單線程,還有固定線程數的線程池(第一次接觸就是這種類型),無界線程池,構造方法如下:


 1. public static ExecutorService newFixedThreadPool(int nThreads);
    public static ExecutorService newFixedThreadPool(int nThreads,
    ThreadFactory threadFactory);
    使用固定線程數的線程池,滿足了資源管理的需求,可以限制當前線程數量。適用於負載較重的服務器環境。

 2. public static ExecutorService newCachedThreadPool(); public static
    ExecutorService newCachedThreadPool(ThreadFactory threadFactory);
    無界線程池,適用於執行很多短期異步任務的小程序,適用於負載較輕的服務器。

除了Executor接口,文中還使用到了Handler來傳遞Runnable,但是這裏不是開啓線程,而是將Runnable作爲屬性賦值Message的Callback,然後通過handler來發送消息到主線程執行Runnable中的方法,源碼如下:


    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
   private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

最後,需要處理的就是對密碼的處理,因爲我們不應該將密碼明文的傳入服務器,這是極大的不安全。所以我們需要對密碼進行加密後再傳入服務器。加密方法目前比較常用的有MD5加密和SHA加密,還有Base64進行加密的,其中MD5的密文是32位,SHA的密文是40位,Base64的密文最高長度根據傳入的密碼長度決定,但是因爲Base64算法的計算方式和碼錶都是公開的,違反了柯克霍夫原則,比較容易被破解。所以剩下的加密方案只能在SHA和MD5中決定,雖然SHA在同樣的硬件下,運行速度稍慢與MD5(我的電腦測試,SHA運行13毫秒,MD5運行1毫秒不到),但是SHA的安全性相對較高。假設使用強行技術攻擊,產生任何一個報文使其摘要等於給定報摘要的難度對MD5是2^128數量級的操作,而對SHA-1則是2^160數量級的操作。這樣,SHA-1對強行攻擊有更大的強度。

所以給出工具類,加密解密,代碼如下:

  private static MessageDigest sha = null;

    /**
     * SHA加密
     * @param inStr
     * @return
     * @throws Exception
     */
    public static String shaEncode(String inStr) {

        try {
            if(sha==null)
                sha = MessageDigest.getInstance("SHA");
        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = sha.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString().substring(10, 30);
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return inStr;
        }

    }

    /**
     * SHA解密
     * @param newPassWord
     * @param oldPassWord
     * @return
     */
    public static boolean shaDecode(String newPassWord,String oldPassWord){

            return shaEncode(newPassWord).equals(shaEncode(oldPassWord));

    }

使用方式——註冊:


 @Override
    public void register(String userName, String pwd, String pwdConfirm) {
        if (StringUtils.checkUserName(userName)) {
            if (StringUtils.checkPassword(pwd)) {
                if (pwd.equals(pwdConfirm)) {
                    mRegisterView.onStartRegister();
                    registerBmob(userName, StringUtils.shaEncode(pwd));
                } else {
                    mRegisterView.onConfirmPasswordError();
                }
            } else {
                mRegisterView.onPasswordError();
            }
        } else {
            mRegisterView.onUserNameError();
        }
    }

使用方式——登錄:

 @Override
    public void login(String userName, String pwd) {
        if (StringUtils.checkUserName(userName)) {
            if (StringUtils.checkPassword(pwd)) {
                mLoginView.onStartLogin();
                startLogin(userName, StringUtils.shaEncode(pwd));
            } else {
                mLoginView.onPasswordError();
            }
        } else {
            mLoginView.onUserNameError();
        }
    }

OK,本篇文章就寫到這裏,有空繼續補充!!

學習的項目地址:

github:https://github.com/uncleleonfan/FanChat

參考文章:

http://www.cnblogs.com/micrari/p/5634447.html
http://blog.csdn.net/lmj623565791/article/details/38377229/
http://docs.bmob.cn/data/Android/b_developdoc/doc/index.html#%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86
http://www.blogjava.net/amigoxie/archive/2014/06/01/414299.html

發佈了61 篇原創文章 · 獲贊 28 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章