因为注册页和上一篇的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