一、登录界面的布局
在AndroidManifest文件中LoginActivity的配置如下:
需要配置:
1、android:screenOrientation=”portrait”这是为了让登录界面始终保持为竖屏方向。
2、android:windowSoftInputMode=”adjustUnspecified”这是设置的输入法的一个属性,特点:”adjustUnspecified”(未指定状态:系统会根据界面采取相应的软键盘显示模式),它不指定是否该Activity主窗口调整大小以便留出软键盘的空间,或是否窗口上的内容得到屏幕上的焦点是可见的。系统将自动选择这些模式中的一种主要依赖于窗口的内容是否有任何布局视图能够滚动他们的内容。如果有这样一个视图,这个窗口将调整大小,这样的假设可以使滚动窗口的内容在一个较小的区域中可见。这个是主窗口默认的行为设置。
界面布局如下:
登录界面布局代码:
activity.login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<net.oschina.gitapp.widget.StretchScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="25dp"
android:src="@drawable/icon_login_logo" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="25dip">
<AutoCompleteTextView
android:id="@+id/et_account"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/icon_login_account"
android:drawablePadding="10dip"
android:ems="10"
android:hint="@string/login_account_hint"
android:imeOptions="actionNext"
android:inputType="textEmailAddress"
android:singleLine="true" />
<EditText
android:id="@+id/et_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dip"
android:drawableLeft="@drawable/icon_login_pass"
android:drawablePadding="10dip"
android:ems="10"
android:hint="@string/login_password_hint"
android:inputType="textPassword"
android:singleLine="true" />
<Button
android:id="@+id/bt_login"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="35dip"
android:background="@drawable/btn_style_login"
android:enabled="false"
android:padding="10dip"
android:text="@string/login_title"
android:textColor="@color/white"
android:textSize="@dimen/space_15" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dip"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="tips:"
android:textColor="@color/gray"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:text="请使用Git@OSC的push邮箱和密码登录"
android:textColor="@color/gray"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:autoLink="web"
android:gravity="left"
android:text="注册请前往https://git.oschina.net"
android:textColor="@color/gray"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</net.oschina.gitapp.widget.StretchScrollView>
</LinearLayout>
可以看到整个界面是一个线性布局,在线性布局里添加了一个自定义StretchScrollView,可以借鉴的地方有:
1)在用户名和密码输入框中都有一个属性:
android:drawableLeft=”@drawable/icon_login_account”
可以设置出一个小图片来,替代丑陋的文字:用户名、密码,将这些文字改为hint(提示)。
二、用户名和密码的加密、解密,存储和恢复显示
登录流程如下图所示:
用户名和密码的加密和解密:
用户名和密码属于敏感型数据,涉及到用户隐私和安全等重要信息,因此要进行加密存储,加密传输,源码中的加密是通过DES算法进行加密的。
加密并存储用户信息的代码:
//保存用户名和密码 AppContext.getInstance().saveAccountInfo(CyptoUtils.encode(Contanst.ACCOUNT_EMAIL, email)
, CyptoUtils.encode(Contanst.ACCOUNT_PWD, passwd));
解密并回显用户信息的代码:
//解密回显用户名信息
String account = CyptoUtils.decode(Contanst.ACCOUNT_EMAIL, AppContext.getInstance()
.getProperty(Contanst.ACCOUNT_EMAIL));
etAccount.setText(account);
//解密回显用户密码信息
String pwd = CyptoUtils.decode(Contanst.ACCOUNT_PWD, AppContext.getInstance().getProperty
(Contanst.ACCOUNT_PWD));
etPassword.setText(pwd);
具体的加密代码在CyptoUtils.java中。
三、输入法的调用和显示
1、要使用输入法,必须获取输入法的服务:
imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
2、输入法的隐藏,在输入法中点击了完成之后,进行的响应操作:
//在输入法里点击了“完成”,则去登录
if (actionId == EditorInfo.IME_ACTION_DONE) {
checkLogin();
//将输入法隐藏
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(etPassword.getWindowToken(), 0);
return true;
}
return false;
总之,在使用输入法之前,需要获取输入法服务!
四、在输入法中点击“完成”和在界面中点击“登录”后,不同的处理方式
在输入法中点击“完成”后登录的代码:
//在输入法里点击了“完成”,则去登录
if (actionId == EditorInfo.IME_ACTION_DONE) {
checkLogin();
//将输入法隐藏
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(etPassword.getWindowToken(), 0);
return true;
}
在界面中点击“登录”按钮后登录的代码:
@Override
@OnClick(R.id.bt_login)
public void onClick(View v) { imm.hideSoftInputFromWindow(etPassword.getWindowToken(), 0);
checkLogin();
}
两种方式都会完成如下的流程:
1、隐藏输入法;
2、校验输入数据是否合法
3、进行登录
五、登录验证:登录成功、失败的不同提示
如下流程图所示,即登录校验详情流程图:
六、用户信息存储包括:
1、用户名、密码的存储;
mAppContext.saveAccountInfo(CyptoUtils.encode(Contanst.ACCOUNT_EMAIL,email),CyptoUtils.encode(Contanst.ACCOUNT_PWD,passwd));
这里调用了AppContext.java中的saveAccountInfo()方法:
/**
* 保存用户的email和pwd
*/
public void saveAccountInfo(String email, String pwd){
setProperty(ACCOUNT_EMAIL, email);
setProperty(ACCOUNT_PWD, pwd);
}
这里 setProperty方法调用了AppConfig.getAppConfig()用来进行用户信息的设置,这里使用了单例模式,保证整个程序中只有一份AppConfig配置:
public void setProperty(String key, String value) {
AppConfig.getAppConfig(this).set(key, value);
}
接着,set方法获取Properties来进行数据的存储:
public void set(String key,String value)
{
Properties props=get();
props.setProperty(key,value);
setProps(props);
}
接着,调用了setProps()方法:
private void setProps(Properties p) {
FileOutputStream fos = null;
try {
// 把config建在files目录下
// fos = activity.openFileOutput(APP_CONFIG, Context.MODE_PRIVATE);
// 把config建在(自定义)app_config的目录下
File dirConf = mContext.getDir(APP_CONFIG, Context.MODE_PRIVATE);
File conf = new File(dirConf, APP_CONFIG);
fos = new FileOutputStream(conf);
p.store(fos, null);
fos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (Exception e) {
}
}
}
用户名和密码的存储流程图如下:
2、登录成功后,从服务器获取的user信息的存储。
向服务器请求数据是从login(email,passwd)方法发起的,整个过程使用了异步任务来完成的。
1)异步登录
new AsyncTask<Void,Void,Message>()
{
@override
protected Messagge doInBackground(Void...params)
{
Message msg=mew Message();
try
{
User user=mAppContext.loginVerify(account,passwd);
msg.what=1;
msg.obj=user;
}catch(Exception e)
{
......
}
return msg;
}
}.execute();
2)从异步登录的代码中可以看到,其中调用了mAppContext.loginVerify(account,passwd),从代码中可以看到–通过ApiClient.login(this,account,pwd)请求user信息,如果!=null,就存储用户信息,否则return ;
public User loginVerify(String account,String pwd)throws AppException
{
User user=ApiClient.login(this,account,pwd);
if(null != user)
{
//保存登录用户的信息
saveLoginInfo(user);
}
return user;
}
3)接下来调用了AppContext中的saveLoginInfo(user)方法:
/**
* 保存登录用户的信息
*/
@SuppressWarnings("serial")
public void saveLoginInfo(final User user) {
if (null == user) {
return;
}
// 保存用户的信息
this.loginUid = StringUtils.toInt(user.getId());
this.login = true;
setProperties(new Properties() {
{
setProperty(PROP_KEY_UID, String.valueOf(user.getId()));
setProperty(PROP_KEY_USERNAME, String.valueOf(user.getUsername()));
setProperty(PROP_KEY_NAME, String.valueOf(user.getName()));
setProperty(PROP_KEY_BIO, String.valueOf(user.getBio()));// 个人介绍
setProperty(PROP_KEY_WEIBO, String.valueOf(user.getWeibo()));
setProperty(PROP_KEY_BLOG, String.valueOf(user.getBlog()));
setProperty(PROP_KEY_THEME_ID, String.valueOf(user.getTheme_id()));
setProperty(PROP_KEY_STATE, String.valueOf(user.getState()));
setProperty(PROP_KEY_CREATED_AT, String.valueOf(user.getCreated_at()));
setProperty(PROP_KEY_PORTRAIT, String.valueOf(user.getPortrait()));// 个人头像
setProperty(PROP_KEY_NEWPORTRAIT, String.valueOf(user.getNew_portrait()));// 个人头像
setProperty(PROP_KEY_IS_ADMIN, String.valueOf(user.isIsAdmin()));
setProperty(PROP_KEY_CAN_CREATE_GROUP, String.valueOf(user.isCanCreateGroup()));
setProperty(PROP_KEY_CAN_CREATE_PROJECT, String.valueOf(user.isCanCreateProject()));
setProperty(PROP_KEY_CAN_CREATE_TEAM, String.valueOf(user.isCanCreateTeam()));
setProperty(ROP_KEY_FOLLOWERS, String.valueOf(user.getFollow().getFollowers()));
setProperty(ROP_KEY_STARRED, String.valueOf(user.getFollow().getStarred()));
setProperty(ROP_KEY_FOLLOWING, String.valueOf(user.getFollow().getFollowing()));
setProperty(ROP_KEY_WATCHED, String.valueOf(user.getFollow().getWatched()));
}
});
}
4)接着调用了AppContext中的setProperties(ps)方法
public void setProperties(Properties ps) {
AppConfig.getAppConfig(this).set(ps);
}
5)接着调用了AppConfig中的set(ps)方法
public void set(Properties ps) {
Properties props = get();
props.putAll(ps);
setProps(props);
}
6)接着调用了AppConfig中的setProps(ps)方法
private void setProps(Properties p) {
FileOutputStream fos = null;
try {
// 把config建在files目录下
// fos = activity.openFileOutput(APP_CONFIG, Context.MODE_PRIVATE);
// 把config建在(自定义)app_config的目录下
File dirConf = mContext.getDir(APP_CONFIG, Context.MODE_PRIVATE);
File conf = new File(dirConf, APP_CONFIG);
fos = new FileOutputStream(conf);
p.store(fos, null);
fos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (Exception e)
}
}
}
用户数据的存储流程图: