先看看效果
image.png
我的这个登录功能是手机号和密码都已经在后台数据库有存储的,所以是直接登录。
重点有三个:
1、账号密码的存储,实现自动登录;
2、网络通信;
3、密码一定要Md5加密之后再传输
先把布局放上来
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_login"
android:orientation="vertical"
>
android:layout_width="match_parent"
android:layout_height="@dimen/item_height_normal"
android:layout_marginLeft="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large"
android:layout_marginTop="@dimen/dp_300">
android:id="@+id/img_account"
android:layout_width="@dimen/dp_19"
android:layout_height="@dimen/dp_20"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_tiny"
android:layout_marginLeft="@dimen/margin_tiny"
android:scaleType="fitXY"
android:src="@drawable/ic_login_account"/>
android:id="@+id/et_account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/margin_tiny"
android:layout_marginLeft="@dimen/margin_normal"
android:layout_toRightOf="@+id/img_account"
android:background="@null"
android:hint="@string/account"
android:maxLines="1"
android:textColor="@android:color/black"
android:textColorHint="@color/tv_gray_deep"
android:textSize="@dimen/text_size_normal"/>
android:layout_width="match_parent"
android:layout_height="@dimen/line_height"
android:layout_alignParentBottom="true"
android:layout_marginLeft="@dimen/margin_normal"
android:layout_toRightOf="@+id/img_account"
android:background="@color/line_new"/>
android:layout_width="match_parent"
android:layout_height="@dimen/item_height_normal"
android:layout_marginLeft="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large">
android:id="@+id/img_pw"
android:layout_width="@dimen/dp_18"
android:layout_height="@dimen/dp_20"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/margin_tiny"
android:layout_marginLeft="@dimen/margin_tiny"
android:scaleType="fitXY"
android:src="@drawable/ic_login_pw"/>
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/margin_tiny"
android:layout_marginLeft="@dimen/margin_normal"
android:layout_toRightOf="@+id/img_pw"
android:background="@null"
android:hint="@string/password"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="@android:color/black"
android:textColorHint="@color/tv_gray_deep"
android:textSize="@dimen/text_size_normal"/>
android:id="@+id/iv_see_password"
android:layout_width="@dimen/image_width_litter"
android:layout_height="@dimen/image_height_litter"
android:src="@drawable/image_password_bg"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:scaleType="fitXY"
/>
android:layout_width="match_parent"
android:layout_height="@dimen/line_height"
android:layout_alignParentBottom="true"
android:layout_marginLeft="@dimen/margin_normal"
android:layout_toRightOf="@+id/img_pw"
android:background="@color/line_new"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large"
android:layout_marginTop="@dimen/margin_small"
android:paddingBottom="@dimen/margin_small"
android:paddingTop="@dimen/margin_small"
android:orientation="horizontal"
android:gravity="center"
>
android:id="@+id/checkBox_login"
android:padding="@dimen/dp_10"
android:textSize="@dimen/text_size_normal"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/check_login"
android:textColor="@color/top_bar_normal_bg" android:checked="false"/>
android:id="@+id/checkBox_password"
android:padding="@dimen/dp_10"
android:textSize="@dimen/text_size_normal"
android:layout_gravity="right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/check_password"
android:textColor="@color/top_bar_normal_bg" android:checked="false"/>
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large"
android:layout_marginTop="@dimen/margin_huge"
android:paddingBottom="@dimen/margin_small"
android:paddingTop="@dimen/margin_small"
android:text="@string/login"
android:background="@drawable/btn_orange_selector"
android:textColor="@android:color/white"
android:textSize="@dimen/text_size_normal"/>
进入正题,同理,先上源码,再解释关键的一些地方
public class LoginByPhone extends AppCompatActivity implements View.OnClickListener,CompoundButton.OnCheckedChangeListener{
private EditText etPhone;
private EditText etPassword;
private Button btn_login;
private CheckBox checkBox_password;
private CheckBox checkBox_login;
private ImageView iv_see_password;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_first);
initView();
initEvent();
initData();
}
private void initEvent() {
btn_login.setOnClickListener(this);
checkBox_password.setOnCheckedChangeListener(this);
checkBox_login.setOnCheckedChangeListener(this);
iv_see_password.setOnClickListener(this);
}
private void initView() {
etPassword = (EditText) findViewById(R.id.et_password);
etPhone = (EditText) findViewById(R.id.et_account);
checkBox_password = (CheckBox) findViewById(R.id.checkBox_password);
checkBox_login = (CheckBox) findViewById(R.id.checkBox_login);
iv_see_password = (ImageView) findViewById(R.id.iv_see_password);
btn_login = findViewById(R.id.btn_login);
}
/**
* 登录
*/
private void login(){
Log.i("etPhone",etPhone.getText().toString());
if(TextUtils.isEmpty(etPhone.getText().toString() )){
Toast.makeText(LoginByPhone.this,R.string.phone_num_can_not_be_empty,Toast.LENGTH_LONG).show();
return;
}
if(TextUtils.isEmpty(etPassword.getText())){
Toast.makeText(LoginByPhone.this,R.string.password_can_not_be_empty,Toast.LENGTH_LONG).show();
return;
}
final ProgressDialog pd = ProgressDialog.show(LoginByPhone.this,getResources().getString(R.string.connecting),getResources().getString(R.string.connecting_to_server));
Thread loginRunnable = new Thread(){
@Override
public void run() {
super.run();
setLoginBtnClickable(false);
new baichuang.callmonitor.Net.Login(etPhone.getText().toString(), Md5.md5(etPassword.getText().toString()),new baichuang.callmonitor.Net.Login.SuccessCallback(){
@Override
public void onSuccess(String token){
Config.cachedToken(LoginByPhone.this,token);
Config.cachedPhoneNum(LoginByPhone.this,etPhone.getText().toString());
pd.dismiss();
loadCheckBoxState();//记录下当前用户记住密码和自动登录的状态;
Intent i = new Intent(LoginByPhone.this,Call_Monitor.class);
i.putExtra(Config.KEY_TOKEN,token);
i.putExtra(Config.KEY_PHONE_NUM, etPhone.getText().toString());
startActivity(i);
finish();
}
}, new Login.FailCallback(){
@Override
public void onFail(){
setLoginBtnClickable(true); //这里解放登录按钮,设置为可以点击
pd.dismiss();
Toast.makeText(LoginByPhone.this,R.string.fail_to_login,Toast.LENGTH_LONG).show();
}
});
}
};
loginRunnable.start();
}
/**
* 保存用户选择“记住密码”和“自动登陆”的状态
*/
private void loadCheckBoxState() {
loadCheckBoxState(checkBox_password, checkBox_login);
}
/**
* 保存按钮的状态值
*/
public void loadCheckBoxState(CheckBox checkBox_password, CheckBox checkBox_login) {
//获取SharedPreferences对象,使用自定义类的方法来获取对象
SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
//如果设置自动登录
if (checkBox_login.isChecked()) {
//创建记住密码和自动登录是都选择,保存密码数据
helper.putValues(
new SharedPreferencesUtils.ContentValue("remenberPassword", true),
new SharedPreferencesUtils.ContentValue("autoLogin", true),
new SharedPreferencesUtils.ContentValue("password", Base64Utils.encryptBASE64(getPassword())));
} else if (!checkBox_password.isChecked()) { //如果没有保存密码,那么自动登录也是不选的
//创建记住密码和自动登录是默认不选,密码为空
helper.putValues(
new SharedPreferencesUtils.ContentValue("remenberPassword", false),
new SharedPreferencesUtils.ContentValue("autoLogin", false),
new SharedPreferencesUtils.ContentValue("password", ""));
} else if (checkBox_password.isChecked()) { //如果保存密码,没有自动登录
//创建记住密码为选中和自动登录是默认不选,保存密码数据
helper.putValues(
new SharedPreferencesUtils.ContentValue("remenberPassword", true),
new SharedPreferencesUtils.ContentValue("autoLogin", false),
new SharedPreferencesUtils.ContentValue("password", Base64Utils.encryptBASE64(getPassword())));
}
}
/**
* 是否可以点击登录按钮
*
* @param clickable
*/
public void setLoginBtnClickable(boolean clickable) {
btn_login.setClickable(clickable);
}
/**
* 获取密码
*/
public String getPassword() {
return etPassword.getText().toString().trim();//去掉空格
}
private void initData() {
//判断用户第一次登陆
if (firstLogin()) {
checkBox_password.setChecked(false);//取消记住密码的复选框
checkBox_login.setChecked(false);//取消自动登录的复选框
}
//判断是否记住密码
if (remenberPassword()) {
checkBox_password.setChecked(true);//勾选记住密码
setTextNameAndPassword();//把密码和账号输入到输入框中
} else {
setTextName();//把用户账号放到输入账号的输入框中
}
//判断是否自动登录
if (autoLogin()) {
checkBox_login.setChecked(true);
login();//去登录就可以
}
}
/**
* 设置数据到输入框中
*/
public void setTextName() {
etPhone.setText("" + getLocalName());
}
/**
* 把本地保存的数据设置数据到输入框中
*/
public void setTextNameAndPassword() {
etPhone.setText("" + getLocalName());
etPassword.setText("" + getLocalPassword());
}
/**
* 获得保存在本地的用户名
*/
public String getLocalName() {
//获取SharedPreferences对象,使用自定义类的方法来获取对象
SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
String name = helper.getString("name");
return name;
}
/**
* 获得保存在本地的密码
*/
public String getLocalPassword() {
//获取SharedPreferences对象,使用自定义类的方法来获取对象
SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
String password = helper.getString("password");
return Base64Utils.decryptBASE64(password); //解码一下
}
/**
* 判断是否是第一次登陆
*/
private boolean firstLogin() {
//获取SharedPreferences对象,使用自定义类的方法来获取对象
SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
boolean first = helper.getBoolean("first", true);
if (first) {
//创建一个ContentVa对象(自定义的)设置不是第一次登录,,并创建记住密码和自动登录是默认不选,创建账号和密码为空
helper.putValues(new SharedPreferencesUtils.ContentValue("first", false),
new SharedPreferencesUtils.ContentValue("remenberPassword", false),
new SharedPreferencesUtils.ContentValue("autoLogin", false),
new SharedPreferencesUtils.ContentValue("name", ""),
new SharedPreferencesUtils.ContentValue("password", ""));
return true;
}
return false;
}
/**
* 判断是否自动登录
*/
private boolean autoLogin() {
//获取SharedPreferences对象,使用自定义类的方法来获取对象
SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
boolean autoLogin = helper.getBoolean("autoLogin", false);
return autoLogin;
}
/**
* 判断是否记住密码
*/
private boolean remenberPassword() {
//获取SharedPreferences对象,使用自定义类的方法来获取对象
SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
boolean remenberPassword = helper.getBoolean("remenberPassword", false);
return remenberPassword;
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_login:
loadUserName(); //无论如何保存一下用户名
login(); //登陆
break;
case R.id.iv_see_password:
setPasswordVisibility(); //改变图片并设置输入框的文本可见或不可见
break;
}
}
/**
* 保存用户账号
*/
public void loadUserName() {
if (!getAccount().equals("") || !getAccount().equals("请输入登录账号")) {
SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
helper.putValues(new SharedPreferencesUtils.ContentValue("name", getAccount()));
}
}
/**
* 设置密码可见和不可见的相互转换
*/
private void setPasswordVisibility() {
if (iv_see_password.isSelected()) {
iv_see_password.setSelected(false);
//密码不可见
etPassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
} else {
iv_see_password.setSelected(true);
//密码可见
etPassword.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
}
}
/**
* 获取账号
*/
public String getAccount() {
return etPhone.getText().toString().trim();//去掉空格
}
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if (compoundButton == checkBox_password) { //记住密码选框发生改变时
if (!isChecked) { //如果取消“记住密码”,那么同样取消自动登陆
checkBox_login.setChecked(false);
}
} else if (compoundButton == checkBox_login) { //自动登陆选框发生改变时
if (isChecked) { //如果选择“自动登录”,那么同样选中“记住密码”
checkBox_password.setChecked(true);
}
}
}
}
基本的initView(),initData()就不啰嗦了。
先看login()这个函数
/**
* 登录
*/
private void login(){
Log.i("etPhone",etPhone.getText().toString());
if(TextUtils.isEmpty(etPhone.getText().toString() )){
Toast.makeText(LoginByPhone.this,R.string.phone_num_can_not_be_empty,Toast.LENGTH_LONG).show();
return;
}
if(TextUtils.isEmpty(etPassword.getText())){
Toast.makeText(LoginByPhone.this,R.string.password_can_not_be_empty,Toast.LENGTH_LONG).show();
return;
}
final ProgressDialog pd = ProgressDialog.show(LoginByPhone.this,getResources().getString(R.string.connecting),getResources().getString(R.string.connecting_to_server));
Thread loginRunnable = new Thread(){
@Override
public void run() {
super.run();
setLoginBtnClickable(false);
new baichuang.callmonitor.Net.Login(etPhone.getText().toString(), Md5.md5(etPassword.getText().toString()),new baichuang.callmonitor.Net.Login.SuccessCallback(){
@Override
public void onSuccess(String token){
Config.cachedToken(LoginByPhone.this,token);
Config.cachedPhoneNum(LoginByPhone.this,etPhone.getText().toString());
pd.dismiss();
loadCheckBoxState();//记录下当前用户记住密码和自动登录的状态;
Intent i = new Intent(LoginByPhone.this,Call_Monitor.class);
i.putExtra(Config.KEY_TOKEN,token);
i.putExtra(Config.KEY_PHONE_NUM, etPhone.getText().toString());
startActivity(i);
finish();
}
}, new Login.FailCallback(){
@Override
public void onFail(){
setLoginBtnClickable(true); //这里解放登录按钮,设置为可以点击
pd.dismiss();
Toast.makeText(LoginByPhone.this,R.string.fail_to_login,Toast.LENGTH_LONG).show();
}
});
}
};
loginRunnable.start();
}
ProgressDialog可设可不设,但是考虑到有些人的网络情况不好,所以给用户点友好体验还是蛮重要的。所以还是加上吧。
new一个Thread(),在其中实现网络请求将用户名和密码传输给后台并获取结果。
这个网络封装类用的是HttpUrlConnection,自己写一个NetConnection封装好基本网络请求,这样之后关于字符串和json的网络请求就不用每次都这样重新写了。
public class NetConnection {
private static String TAG="netConnection: ";
public NetConnection(final String url,final HttpMethod method,final SuccessCallback successCallback,final FailCallback failCallback,final String...kvs ){
new AsyncTask(){
@Override
protected String doInBackground(Void... voids) {
StringBuffer paramsStr = new StringBuffer();
for (int i = 0; i
paramsStr.append(kvs[i]).append("=").append(kvs[i+1]).append("&");
}
try{
HttpURLConnection uc;
switch (method){
case POST:
uc = (HttpURLConnection)new URL(url).openConnection();
System.out.println(uc);
// 显示开启请求体
uc.setDoInput(true);
uc.setDoOutput(true);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(uc.getOutputStream(), Config.CHARSET));
bw.write(paramsStr.toString());
bw.flush();
bw.close();
break;
default:
uc = (HttpURLConnection) new URL(url+"?"+ paramsStr.toString()).openConnection();
break;
}
BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream(),Config.CHARSET));
if(uc.getResponseCode() == 200){
String line = null;
StringBuffer result = new StringBuffer();
while((line=br.readLine())!=null){
result.append(line);
}
return result.toString();
} else if( uc.getResponseCode() == 404){
Log.i(TAG,"404访问服务器失败");
} else if( uc.getResponseCode() == 500 ){
Log.i(TAG,"500服务器拒绝访问");
}
} catch (MalformedURLException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
if (result!= null) {
successCallback.onSuccess(result);
} else {
if(failCallback!=null){
failCallback.onFail();
}
}
super.onPostExecute(result);
}
}.execute();
}
public static interface SuccessCallback{
void onSuccess(String result);
}
public static interface FailCallback{
void onFail();
}
}
两个接口记得写哦。
Login网络请求类如下
public class Login {
public Login(String phone, String password, final SuccessCallback successCallback, final FailCallback failCallback){
new NetConnection(Config.SVERVICE_URL, HttpMethod.POST, new NetConnection.SuccessCallback() {
@Override
public void onSuccess(String result) {
try {
JSONObject obj = new JSONObject(result);
switch (obj.getInt(Config.KEY_STATUS)){
case Config.RESULT_STATUS_SUCCESS:
System.out.println(successCallback);
if(successCallback!=null){
successCallback.onSuccess(obj.getString(Config.KEY_TOKEN));
}
break;
default:
if(failCallback!=null){
failCallback.onFail();
}
break;
}
} catch (JSONException e) {
e.printStackTrace();
if(failCallback!=null){
failCallback.onFail();
}
}
}
}, new NetConnection.FailCallback() {
@Override
public void onFail() {
if(failCallback!=null){
failCallback.onFail();
}
}
},Config.PHONE_NUM,phone,Config.PASS_WORD,password);
}
public static interface SuccessCallback{
void onSuccess(String token);
}
public static interface FailCallback{
void onFail();
}
}
这样其实登录基本功能就好了。其他的都是关于记住密码啊之类的了。
本地保存密码用到的是SharedPreferences, 我自己封装的如下:
public class SharedPreferencesUtils {
//定义一个SharePreference对象
SharedPreferences sharedPreferences;
//定义一个上下文对象
//创建SharePreference对象时要上下文和存储的模式
//通过构造方法传入一个上下文
public SharedPreferencesUtils(Context context, String fileName) {
//实例化SharePreference对象,使用的是get方法,而不是new创建
//第一个参数是文件的名字
//第二个参数是存储的模式,一般都是使用私有方式:Context.MODE_PRIVATE
sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
}
/**
* 存储数据
* 这里要对存储的数据进行判断在存储
* 只能存储简单的几种数据
* 这里使用的是自定义的ContentValue类,来进行对多个数据的处理
*/
//创建一个内部类使用,里面有key和value这两个值
public static class ContentValue {
String key;
Object value;
//通过构造方法来传入key和value
public ContentValue(String key, Object value) {
this.key = key;
this.value = value;
}
}
//一次可以传入多个ContentValue对象的值
public void putValues(ContentValue... contentValues) {
//获取SharePreference对象的编辑对象,才能进行数据的存储
SharedPreferences.Editor editor = sharedPreferences.edit();
//数据分类和存储
for (ContentValue contentValue : contentValues) {
//如果是字符型类型
if (contentValue.value instanceof String) {
editor.putString(contentValue.key, contentValue.value.toString()).commit();
}
//如果是int类型
if (contentValue.value instanceof Integer) {
editor.putInt(contentValue.key, Integer.parseInt(contentValue.value.toString())).commit();
}
//如果是Long类型
if (contentValue.value instanceof Long) {
editor.putLong(contentValue.key, Long.parseLong(contentValue.value.toString())).commit();
}
//如果是布尔类型
if (contentValue.value instanceof Boolean) {
editor.putBoolean(contentValue.key, Boolean.parseBoolean(contentValue.value.toString())).commit();
}
}
}
//获取数据的方法
public String getString(String key) {
return sharedPreferences.getString(key, null);
}
public boolean getBoolean(String key, Boolean b) {
return sharedPreferences.getBoolean(key, b);
}
public int getInt(String key) {
return sharedPreferences.getInt(key, -1);
}
public long getLong(String key) {
return sharedPreferences.getLong(key, -1);
}
//清除当前文件的所有的数据
public void clear() {
sharedPreferences.edit().clear().commit();
}
}
其他好像没啥可讲的了,都在代码中。大家有想要了解的,可以私我。
服务器端:
controllers/android/login.js
image.png
models/android/login.js
image.png
顺便讲一下服务器端的路由逻辑,
main.js中声明路由路径是根目录到controllers
image.png
在controllers/android.js中再配置一次controllers里面路由的路径
image.png
没什么难的。大家不懂私我吧~