## 安卓端登入系统
(一) 触发场景
实际上,登录注册页面对于用户而言处理不当是一种非常差的用户体验。我们需要做到的不仅仅是服务端的连接和账号密码的匹配,更要注意账号信息的存储与App打开时自动登入等状况。
(二) 注册方式—手机号、邮箱、第三方账号
1 手机号注册。实际上这是目前、最为快捷的注册方式,考虑到手机端的输入方式需要切换,为了简化用户操作,建议移动端仅提供手机号注 册,这样可以直接弹出数字键盘,用户无需转换,但是如果应用还有PC端入口需要注意两者的统一问题。譬如,移动端仅提供了手机注册方式,那么PC端注册也应该有手机注册及登陆,如果PC端保留了邮箱注册,那么手机端也应该在设置中提供绑定邮箱的操作。
2 邮箱注册。邮箱注册方式应该属于桌面注册方式,并不适合移动端。原因在于无法确保用户在手机端是否安装相应的邮箱应用或相关提示,即 使安装了还有一个操作的便捷性问题,考虑到手机验证码注册的即时性以及验证码会出现在状态栏,用户无需任何操作便可获取接收到的验证码,因而建议手机端优先采用手机号注册。桌面注册其实两者的操作差距会小很多,但是涉及到密码安全问题,建议如果仅提供手机号注册应 该在账号安全里提醒用户邮箱绑定,以便于找回密码。
3 第三方账号。虽然第三方登陆快捷方便,但是一般网站、应用还是希望用户单独注册一个账号,实际上这样做对于绝大多数网站并没有什么好 处,因为用户可能会忘记这些独立的网站账号而不得不再次注册,这非常影响用户的使用体验,因而除非特别要求不建议采用独立的账户体系。
现附上效果图
然后是布局XML代码 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:layout_height="420dp"
android:layout_width="match_parent"
android:layout_marginTop="100dp">
<EditText
android:layout_marginTop="120dp"
android:id="@+id/username"
android:layout_width="350dp"
android:layout_gravity="center"
android:layout_height="60dp"
android:hint="用户名/手机号/邮箱"
android:background="@drawable/background_edit_div"/>
<EditText
android:layout_marginTop="30dp"
android:id="@+id/password"
android:layout_width="350dp"
android:layout_gravity="center"
android:layout_height="60dp"
android:hint="请输入您的密码"
android:background="@drawable/background_edit_div"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="85dp"
android:layout_marginTop="50dp"
android:orientation="horizontal">
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:background="@drawable/background_button_div"
android:text="登录"
android:textColor="#646868"
android:layout_marginLeft="130dp"
android:textAlignment="center"
android:textSize="18sp" />
<Button
android:id="@+id/register"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_marginLeft="30dp"
android:background="@drawable/background_button_div"
android:text="注册"
android:textColor="#646868"
android:textAlignment="center"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
注:这里background属性值是笔者自定义的,读者可根据其他风格修改
接下来是Activity代码
public class LoginActivity extends Activity{
private LoginUserInfo loginUserInfo;
private EditText username;
private EditText password;
private Button login;
private Button register;
public static SharedPreferences sharedPreferences;
public static SharedPreferences.Editor editor;
//用于接收Http请求的servlet的URL地址,请自己定义
private String loginAddress = "http:***********";
//用于处理消息的Handler
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String result = "";
if ("200".equals(msg.obj.toString())){
result = "登入成功";
//使用sharePreferences保存该账号的登入状态
editor.putString(loginUserInfo.getLoginUser_phoneNumber(),"1");
editor.commit();
Intent intent = new Intent(LoginActivity.this, App_MainActivity.class);
startActivity(intent);
LoginActivity.this.finish();
}else if ("400".equals(msg.obj.toString())){
result = "账号密码不匹配";
}else {
result = msg.obj.toString();
Log.e("Http请求发生错误",result);
}
Log.w("Http请求状态",result);
Toast.makeText(LoginActivity.this, result, Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginUserInfo=(LoginUserInfo)getApplication();
//在加载布局文件先查询是否登陆过
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
sharedPreferences=this.getSharedPreferences("test",MODE_PRIVATE);
editor = sharedPreferences.edit();
//使用sharePreferences保存登入状态
//后面的参数是当login_flag没有时的默认参数
//使用迭代器遍历所有账号此时的登入状态
Map<String,?>map=sharedPreferences.getAll();
for (Map.Entry k:map.entrySet())
{
String value=sharedPreferences.getString(k.getKey().toString(),"0");
if (value.equals("1"))
{
//这边还要再次去服务器请求用户数据才行
//因为程序退出或者重装后LoginUserInfo已经没了
AutoLoginHttpUtil autoLoginHttpUtil=new AutoLoginHttpUtil(loginUserInfo);
Log.e("当前已经登入的账号手机号码为:",(String) k.getKey());
autoLoginHttpUtil.RequestUserInfo((String) k.getKey());
Intent intent = new Intent(LoginActivity.this, App_MainActivity.class);
startActivity(intent);
LoginActivity.this.finish();
}
}
setContentView(R.layout.login);
username = (EditText) findViewById((R.id.username));
password = (EditText) findViewById(R.id.password);
login = (Button) findViewById(R.id.login);
register = (Button) findViewById(R.id.register);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Servlet_login();
}
});
}
public void Servlet_login ()
{
//构造HashMap
HashMap<String, String> params = new HashMap<String, String>();
String tmp= username.getText().toString();
params.put(User.PHONENUMBER,tmp);
params.put(User.PASSWORD, password.getText().toString());
if (tmp.equals(""))
{
Toast.makeText(LoginActivity.this,"请输入手机号码",Toast.LENGTH_SHORT).show();
return;
} else if (tmp.length()!=11) {
Toast.makeText(LoginActivity.this,"手机格式不正确",Toast.LENGTH_SHORT).show();
return;
}
if (password.getText().toString().equals(""))
{
Toast.makeText(LoginActivity.this,"请输入密码",Toast.LENGTH_SHORT).show();
return;
}
try {
HttpUtil httpUtil=new HttpUtil(loginUserInfo);
//构造完整URL
//通过解析URL的附加参数(账号密码会附加在url中)来判断
String compeletedURL = httpUtil.getURLWithParams(loginAddress, params);
//发送请求
httpUtil.sendHttpRequest(compeletedURL, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
Message message = new Message();
message.obj = response;
mHandler.sendMessage(message);
}
@Override
public void onError(Exception e) {
Message message = new Message();
message.obj = e.toString();
mHandler.sendMessage(message);
}
@Override
public void onFail(String response) {
Message message = new Message();
message.obj = response;
mHandler.sendMessage(message);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
总结下主要是使用sharePreferences保存所有账号的登入状态,APP打开后自动检测sharePreferences中所有账号的登入状态,如果检测到有一个账号处于登入状态则跳过登入界面(这里需要注意的是sharePreferences虽然保存了账号的登入状态,但是从服务器缓存下来的账户信息已经在程序退出时清除掉了)
所以我们必须再一次向服务器请求该账号的信息。
同样的,当我们做退出登入的操作时,也要修改sharePreferences文件和通知服务器。
这边再附上HTTP请求的代码
package com.example.strategyapp.HttpTool;
import com.example.strategyapp.LoginUserInfo;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
public class HttpUtil {
private static LoginUserInfo loginUserInfo;
public HttpUtil(LoginUserInfo a)
{
this.loginUserInfo=a;
}
//封装的发送请求函数
public static void sendHttpRequest(final String address, final HttpCallbackListener listener) {
if (!HttpUtil.isNetworkAvailable()){
//这里写相应的网络设置处理
return;
}
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try{
URL url = new URL(address);
//使用HttpURLConnection
connection = (HttpURLConnection) url.openConnection();
//设置方法和参数
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setInstanceFollowRedirects(false); //不自动处理重定向
//获取返回结果
InputStream inputStream = connection.getInputStream();
byte[] data=new byte[1024];
int len=0;
ByteArrayOutputStream out=new ByteArrayOutputStream();
//把response中的数据流写入字节组
while ((len=inputStream.read(data))!=-1)
{
out.write(data,0,len);
}
inputStream.close();
//解析服务器返回Json字段并且传给监听器
String jsonString=new String(out.toByteArray());
boolean re=ParseJson(jsonString);
//成功则回调onFinish
if (re){
listener.onFinish("200");
} else
{
listener.onFail("400");
}
} catch (Exception e) {
e.printStackTrace();
//出现异常则回调onError
if (listener != null){
listener.onError(e);
}
}finally {
if (connection != null){
connection.disconnect();
}
}
}
}).start();
}
//解析登入时服务器发来的Json数据
public static boolean ParseJson(String jsonString)throws JSONException{
JSONObject jsonObject=new JSONObject(jsonString);
int code=jsonObject.getInt("code");
if (code==400)
{
return false;
}
if (code==200)
{
JSONObject data=jsonObject.getJSONObject("data");
loginUserInfo.setLoginUser_id(data.getInt("id"));
loginUserInfo.setLoginUser_phoneNumber(data.getString("phoneNumber"));
loginUserInfo.setLoginUser_name(data.getString("userName"));
loginUserInfo.setLoginUser_avatar("http://10.0.2.2:8080/"+data.getString("avatar"));
return true;
}
return false;
}
//组装出带参数的完整URL
public static String getURLWithParams(String address, HashMap<String,String> params)
throws UnsupportedEncodingException {
//设置编码
final String encode = "UTF-8";
StringBuilder url = new StringBuilder(address);
url.append("?");
//将map中的key,value构造进入URL中
for(Map.Entry<String, String> entry:params.entrySet())
{
url.append(entry.getKey()).append("=");
url.append(URLEncoder.encode(entry.getValue(), encode));
url.append("&");
}
url.deleteCharAt(url.length() - 1);
return url.toString();
}
//判断当前网络是否可用
public static boolean isNetworkAvailable(){
//这里检查网络,后续再添加
return true;
}