Android技巧2:登入註冊模組解決方案
前言
幾乎每個app都會有登入註冊的功能,可以看看筆者開發的『南方周末新聞閱讀器』,登入、手機註冊、忘記密碼這些入口,這些功能在app中要如何來實現呢?這個模組看似很簡單,但要做好就需要考慮很多細節,比如對使用者的輸入的容錯,操作的提示文案的設定,登入成功儲存使用者資訊等等。
商務程序圖
商務邏輯描述
上一節的流程圖已經很清晰的展現了登入註冊的流程,這裡繼續用文字說明一下:
1. 點擊進入個人中心或者需要使用者登入狀態的操作,先判斷使用者是否已經登入。
2. 如果已經登入,則繼續後面的業務,否則,跳轉到登入頁面進行登入。
3. 如果已經有帳號,則可以直接登入,或者可以直接選擇第三方平台授權登入。
4. 如果未註冊帳號,則需要先進行帳號註冊,註冊成功後再登入;也可以不註冊帳號,通過第三方平台授權進行登入。
5. 如果有帳號,但忘記密碼,則需要進行重設密碼,否則直接登入。
具體實現
登入可以使用帳號登入,現在的app基本上都是手機號碼登入,註冊的時候也是一個手機對應一個帳號,通過發送驗證碼進行驗證;使用者也可以選擇第三方平台進行登入,一般會提供、QQ、新浪微博這樣的主流社交平台進行授權登入,這裡筆者使用了友盟的SDK進行實現。
範例程式碼:LoginActivity.java
package com.devilwwj.loginandregister;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.text.TextUtils;import android.text.method.HideReturnsTransformationMethod;import android.text.method.PasswordTransformationMethod;import android.view.KeyEvent;import android.view.View;import android.view.inputmethod.EditorInfo;import android.widget.TextView;import android.widget.Toast;import com.devilwwj.loginandregister.global.AppConstants;import com.devilwwj.loginandregister.utils.LogUtils;import com.devilwwj.loginandregister.utils.ProgressDialogUtils;import com.devilwwj.loginandregister.utils.RegexUtils;import com.devilwwj.loginandregister.utils.ShareUtils;import com.devilwwj.loginandregister.utils.SpUtils;import com.devilwwj.loginandregister.utils.ToastUtils;import com.devilwwj.loginandregister.utils.Utils;import com.devilwwj.loginandregister.views.CleanEditText;import com.umeng.socialize.bean.SHARE_MEDIA;import com.umeng.socialize.controller.UMServiceFactory;import com.umeng.socialize.controller.UMSocialService;import com.umeng.socialize.controller.listener.SocializeListeners.UMAuthListener;import com.umeng.socialize.controller.listener.SocializeListeners.UMDataListener;import com.umeng.socialize.exception.SocializeException;import java.util.Map;import static android.view.View.OnClickListener;/** * @desc 登入介面 * Created by devilwwj on 16/1/24. */public class LoginActivity extends Activity implements OnClickListener { private static final String TAG = "loginActivity"; private static final int REQUEST_CODE_TO_REGISTER = 0x001; // 介面控制項 private CleanEditText accountEdit; private CleanEditText passwordEdit; // 第三方平台擷取的訪問token,有效時間,uid private String accessToken; private String expires_in; private String uid; private String sns; // 整個平台的Controller,負責管理整個SDK的配置、操作等處理 private UMSocialService mController = UMServiceFactory .getUMSocialService(AppConstants.DESCRIPTOR); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); initViews(); // 配置分享平台 ShareUtils.configPlatforms(this); } /** * 初始化視圖 */ private void initViews() { accountEdit = (CleanEditText) this.findViewById(R.id.et_email_phone); accountEdit.setImeOptions(EditorInfo.IME_ACTION_NEXT); accountEdit.setTransformationMethod(HideReturnsTransformationMethod .getInstance()); passwordEdit = (CleanEditText) this.findViewById(R.id.et_password); passwordEdit.setImeOptions(EditorInfo.IME_ACTION_DONE); passwordEdit.setImeOptions(EditorInfo.IME_ACTION_GO); passwordEdit.setTransformationMethod(PasswordTransformationMethod .getInstance()); passwordEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_GO) { clickLogin(); } return false; } }); } private void clickLogin() { String account = accountEdit.getText().toString(); String password = passwordEdit.getText().toString(); if (checkInput(account, password)) { // TODO: 請求伺服器登入帳號 } } /** * 檢查輸入 * * @param account * @param password * @return */ public boolean checkInput(String account, String password) { // 帳號為空白時提示 if (account == null || account.trim().equals("")) { Toast.makeText(this, R.string.tip_account_empty, Toast.LENGTH_LONG) .show(); } else { // 帳號不匹配手機號格式(11位元字且以1開頭) if ( !RegexUtils.checkMobile(account)) { Toast.makeText(this, R.string.tip_account_regex_not_right, Toast.LENGTH_LONG).show(); } else if (password == null || password.trim().equals("")) { Toast.makeText(this, R.string.tip_password_can_not_be_empty, Toast.LENGTH_LONG).show(); } else { return true; } } return false; } @Override public void onClick(View v) { Intent intent = null; switch (v.getId()) { case R.id.iv_cancel: finish(); break; case R.id.btn_login: clickLogin(); break; case R.id.iv_wechat: clickLoginWexin(); break; case R.id.iv_qq: clickLoginQQ(); break; case R.id.iv_sina: loginThirdPlatform(SHARE_MEDIA.SINA); break; case R.id.tv_create_account: enterRegister(); break; case R.id.tv_forget_password: enterForgetPwd(); break; default: break; } } /** * 點擊使用QQ快速登入 */ private void clickLoginQQ() { if (!Utils.isQQClientAvailable(this)) { ToastUtils.showShort(LoginActivity.this, getString(R.string.no_install_qq)); } else { loginThirdPlatform(SHARE_MEDIA.QZONE); } } /** * 點擊使用登入 */ private void clickLoginWexin() { if (!Utils.isWeixinAvilible(this)) { ToastUtils.showShort(LoginActivity.this, getString(R.string.no_install_wechat)); } else { loginThirdPlatform(SHARE_MEDIA.WEIXIN); } } /** * 跳轉到忘記密碼 */ private void enterForgetPwd() { Intent intent = new Intent(this, ForgetPasswordActivity.class); startActivity(intent); } /** * 跳轉到註冊頁面 */ private void enterRegister() { Intent intent = new Intent(this, SignUpActivity.class); startActivityForResult(intent, REQUEST_CODE_TO_REGISTER); } /** * 授權。如果授權成功,則擷取使用者資訊 * * @param platform */ private void loginThirdPlatform(final SHARE_MEDIA platform) { mController.doOauthVerify(LoginActivity.this, platform, new UMAuthListener() { @Override public void onStart(SHARE_MEDIA platform) { LogUtils.i(TAG, "onStart------" + Thread.currentThread().getId()); ProgressDialogUtils.getInstance().show( LoginActivity.this, getString(R.string.tip_begin_oauth)); } @Override public void onError(SocializeException e, SHARE_MEDIA platform) { LogUtils.i(TAG, "onError------" + Thread.currentThread().getId()); ToastUtils.showShort(LoginActivity.this, getString(R.string.oauth_fail)); ProgressDialogUtils.getInstance().dismiss(); } @Override public void onComplete(Bundle value, SHARE_MEDIA platform) { LogUtils.i(TAG, "onComplete------" + value.toString()); if (platform == SHARE_MEDIA.SINA) { accessToken = value.getString("access_key"); } else { accessToken = value.getString("access_token"); } expires_in = value.getString("expires_in"); // 擷取uid uid = value.getString(AppConstants.UID); if (value != null && !TextUtils.isEmpty(uid)) { // uid不為空白,擷取使用者資訊 getUserInfo(platform); } else { ToastUtils.showShort(LoginActivity.this, getString(R.string.oauth_fail)); } } @Override public void onCancel(SHARE_MEDIA platform) { LogUtils.i(TAG, "onCancel------" + Thread.currentThread().getId()); ToastUtils.showShort(LoginActivity.this, getString(R.string.oauth_cancle)); ProgressDialogUtils.getInstance().dismiss(); } }); } /** * 擷取使用者資訊 * * @param platform */ private void getUserInfo(final SHARE_MEDIA platform) { mController.getPlatformInfo(LoginActivity.this, platform, new UMDataListener() { @Override public void onStart() { // 開始擷取 LogUtils.i("getUserInfo", "onStart------"); ProgressDialogUtils.getInstance().dismiss(); ProgressDialogUtils.getInstance().show( LoginActivity.this, "正在請求..."); } @Override public void onComplete(int status, Map info) { try { String sns_id = ""; String sns_avatar = ""; String sns_loginname = ""; if (info != null && info.size() != 0) { LogUtils.i("third login", info.toString()); if (platform == SHARE_MEDIA.SINA) { // 新浪微博 sns = AppConstants.SINA; if (info.get(AppConstants.UID) != null) { sns_id = info.get(AppConstants.UID) .toString(); } if (info.get(AppConstants.PROFILE_IMAGE_URL) != null) { sns_avatar = info .get(AppConstants.PROFILE_IMAGE_URL) .toString(); } if (info.get(AppConstants.SCREEN_NAME) != null) { sns_loginname = info.get( AppConstants.SCREEN_NAME) .toString(); } } else if (platform == SHARE_MEDIA.QZONE) { // QQ sns = AppConstants.QQ; if (info.get(AppConstants.UID) == null) { ToastUtils .showShort( LoginActivity.this, getString(R.string.oauth_fail)); return; } sns_id = info.get(AppConstants.UID) .toString(); sns_avatar = info.get( AppConstants.PROFILE_IMAGE_URL) .toString(); sns_loginname = info.get( AppConstants.SCREEN_NAME) .toString(); } else if (platform == SHARE_MEDIA.WEIXIN) { // sns = AppConstants.WECHAT; sns_id = info.get(AppConstants.OPENID) .toString(); sns_avatar = info.get( AppConstants.HEADIMG_URL) .toString(); sns_loginname = info.get( AppConstants.NICKNAME).toString(); } // 這裡直接儲存第三方返回來的使用者資訊 SpUtils.putBoolean(LoginActivity.this, AppConstants.THIRD_LOGIN, true); LogUtils.e("info", sns + "," + sns_id + "," + sns_loginname); // TODO: 這裡執行第三方串連(綁定伺服器帳號) } } catch (Exception e) { e.printStackTrace(); } } }); }}
範例程式碼:SignUpActivity.java
package com.devilwwj.loginandregister;import android.app.Activity;import android.os.Bundle;import android.text.TextUtils;import android.util.Log;import android.view.KeyEvent;import android.view.View;import android.view.View.OnClickListener;import android.view.inputmethod.EditorInfo;import android.widget.Button;import android.widget.TextView;import android.widget.TextView.OnEditorActionListener;import com.devilwwj.loginandregister.utils.RegexUtils;import com.devilwwj.loginandregister.utils.ToastUtils;import com.devilwwj.loginandregister.utils.VerifyCodeManager;import com.devilwwj.loginandregister.views.CleanEditText;/** * @desc 註冊介面 * 功能描述:一般會使用手機登入,通過擷取手機驗證碼,跟伺服器互動完成註冊 * Created by devilwwj on 16/1/24. */public class SignUpActivity extends Activity implements OnClickListener{ private static final String TAG = "SignupActivity"; // 介面控制項 private CleanEditText phoneEdit; private CleanEditText passwordEdit; private CleanEditText verifyCodeEdit; private Button getVerifiCodeButton; private VerifyCodeManager codeManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_signup); initViews(); codeManager = new VerifyCodeManager(this, phoneEdit, getVerifiCodeButton); } /** * 通用findViewById,減少重複的類型轉換 * * @param id * @return */ @SuppressWarnings("unchecked") public final E getView(int id) { try { return (E) findViewById(id); } catch (ClassCastException ex) { Log.e(TAG, "Could not cast View to concrete class.", ex); throw ex; } } private void initViews() { getVerifiCodeButton = getView(R.id.btn_send_verifi_code); getVerifiCodeButton.setOnClickListener(this); phoneEdit = getView(R.id.et_phone); phoneEdit.setImeOptions(EditorInfo.IME_ACTION_NEXT);// 下一步 verifyCodeEdit = getView(R.id.et_verifiCode); verifyCodeEdit.setImeOptions(EditorInfo.IME_ACTION_NEXT);// 下一步 passwordEdit = getView(R.id.et_password); passwordEdit.setImeOptions(EditorInfo.IME_ACTION_DONE); passwordEdit.setImeOptions(EditorInfo.IME_ACTION_GO); passwordEdit.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // 點擊虛擬鍵盤的done if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_GO) { commit(); } return false; } }); } private void commit() { String phone = phoneEdit.getText().toString().trim(); String password = passwordEdit.getText().toString().trim(); String code = verifyCodeEdit.getText().toString().trim(); if (checkInput(phone, password, code)) { // TODO:請求服務端註冊帳號 } } private boolean checkInput(String phone, String password, String code) { if (TextUtils.isEmpty(phone)) { // 電話號碼為空白 ToastUtils.showShort(this, R.string.tip_phone_can_not_be_empty); } else { if (!RegexUtils.checkMobile(phone)) { // 電話號碼格式有誤 ToastUtils.showShort(this, R.string.tip_phone_regex_not_right); } else if (TextUtils.isEmpty(code)) { // 驗證碼不正確 ToastUtils.showShort(this, R.string.tip_please_input_code); } else if (password.length() < 6 || password.length() > 32 || TextUtils.isEmpty(password)) { // 密碼格式 ToastUtils.showShort(this, R.string.tip_please_input_6_32_password); } else { return true; } } return false; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_send_verifi_code: // TODO 請求介面發送驗證碼 codeManager.getVerifyCode(VerifyCodeManager.REGISTER); break; default: break; } }}
Github
登入註冊的解決方案,筆者已經做成一個Demo放到github了,大家在實際開發的時候可以參考著根據自身的業務進行調整,但基本上不會差太多,第三方登入、驗證碼這個都可以選用第三方服務來實現,github地址如下:
登入註冊解決方案