因爲註冊頁和上一篇的FanChat學習筆記(二)——登錄頁框架很類似,幾乎沒有多少新知識,所以這篇文章就不像上文那樣剖析了,只講我看到的覺得可以單獨記錄下來的地方,如果對MVP不熟悉的話,可以先看看學習筆記一和學習筆記二。今天我們需要在學習代碼之前,先看看作者需要實現的業務邏輯,原文是這樣介紹的:
- 實際項目中,註冊會將用戶名和密碼註冊到APP的服務器,然後APP的服務器再通過REST API方式註冊到環信服務器。
- 由於本項目沒有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