上一期我们讲解了如何调试和寻找bug,程序出问题了我们一定要清楚是哪一个环节,可以根据程序报错的位置,在那里打上断点,然后启动调试,查看变量值的变化情况,如果有异常的值或者变量我们就跟着它,从定义到结束都查看一下就可以找到问题了;
那么这一期我们来学习一下数据的轻量级存储,之前我们学习了使用intent和bundle来存储数据,而且他们的存储是没有本地的数据的,程序销毁之后就消失了,不利于数据长期存储,现在我们来学习一个常常用在存储登录信息的存储类,SharedPerferences,这个类是需要上下文点出来的,所以跟上下文有关联,在我们创建的时候要注意,学完了存储类之后我们就做一个QQ登录的小案例;
1、创建
看下面的代码,创建这个对象后还需要创建一个编辑对象才能实现数据的添加,跟之前我们学习的bundle一样,也是键值对的存储方式;
public static void appendData(Context context , String username, String password){
//1.4 新建一个数据存储对象,第一个参数是自定义的文件夹名,第二个是模式
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//创建一个文件编辑对象
SharedPreferences.Editor editor = sp.edit();
editor.putString("username",username);
editor.putString("password",password);
//必须提交,不然不起作用
editor.commit();
}
注意这个是使用的sharedperences的编辑对象edit(),这种创建方法非常常见,一定要理解,然后就是放数据使用putString(),当然,不止可以放String,还有boolean、float等,最后记住commit一下;
2、读取
读取数据同样使用这个方法来获取到文件对象,不过读取是不需要创建编辑器对象的,直接在sharedprefernces点出getString()根据我们自己定义的键值就可以拿到数据,不过注意有一个默认值,之类设置为null是为了后面的判断是否存在数据,这个方法是将数据以数组的形式返回,当然存在数据不存在的情况;
//1.2 读取数据,因为含有多个数据,所以我们使用数组存储
public static String[] readData(Context context){
//1.5 同样新建一个数据存储对象,获取到我们存储的数据
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//判断是否数据为空
String username=sp.getString("username",null);
String password=sp.getString("password",null);
String[] datas;
if(username == null){
//如果没有数据就返回null
return null;
}else{
//读取数据
datas = new String[2];
datas[0]=username;
datas[1]=password;
return datas;
}
}
在我们的android stdio中有一个地方可以查看这个类存储的信息,会自动生成一个xml格式的文件,存放的键值对,也就是map;这个文件会存储为:data/包名/data/shres_refs路径下,在android studio右下角的文件浏览器中可以找到(注意需要启动手机才可以找到):
3、删除
删除同样需要创建这个对象(名字太长,懒得写,懂的都懂),毕竟是删除还是要创建一个编辑器对象,然后执行clean()或者remove(“键值”)进行删除,前者是清空;
//1.3 删除,在我们不勾选记住密码的时候需要清除一下数据
public void deleteData(Context context){
//1.6 同样新建一个数据存储对象,获取到我们存储的数据
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//创建一个文件编辑对象
SharedPreferences.Editor editor = sp.edit();
editor.clear();
}
小案例:
接下来就是我们的小案例部分,我们要创建两个activity,一个是登录界面,一个是登陆后的提示界面;
还要创建一个工具类,里面存放有上面介绍的三个方法:
4、创建工具类:
package com.example.qqloginactivity;
import android.content.Context;
import android.content.SharedPreferences;
/*
1.1 创建一个工具类用于简化数据的增删改查
,存储文件是根据上下文联系起来的,所以我们需要获取到上下文
*/
public class Utils {
//添加,因为只有数据的操作,所以我们使用静态,将来可以直接用类名调用
public static void appendData(Context context , String username, String password){
//1.4 新建一个数据存储对象,第一个参数是自定义的文件夹名,第二个是模式
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//创建一个文件编辑对象
SharedPreferences.Editor editor = sp.edit();
editor.putString("username",username);
editor.putString("password",password);
//必须提交,不然不起作用
editor.commit();
}
//1.2 读取数据,因为含有多个数据,所以我们使用数组存储
public static String[] readData(Context context){
//1.5 同样新建一个数据存储对象,获取到我们存储的数据
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//判断是否数据为空
String username=sp.getString("username",null);
String password=sp.getString("password",null);
String[] datas;
if(username == null){
//如果没有数据就返回null
return null;
}else{
//读取数据
datas = new String[2];
datas[0]=username;
datas[1]=password;
return datas;
}
}
//1.3 删除,在我们不勾选记住密码的时候需要清除一下数据
public static void deleteData(Context context){
//1.6 同样新建一个数据存储对象,获取到我们存储的数据
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//创建一个文件编辑对象
SharedPreferences.Editor editor = sp.edit();
editor.clear();
editor.commit();
}
}
注意,清除数据一样是需要提交事务的,不然会清除失败
5、布局登录界面:
当然,按你们的喜好就行,我这里提供一个参考:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/tou4"
android:scaleType="fitXY"
android:layout_marginTop="100dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/img_tou"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户名:"
android:textSize="30sp"
android:layout_marginTop="30dp"
android:layout_marginLeft="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/img_tou"
android:id="@+id/tv_username"/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginRight="20dp"
app:layout_constraintTop_toBottomOf="@id/img_tou"
app:layout_constraintLeft_toRightOf="@id/tv_username"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/ed_username"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密码:"
android:textSize="30sp"
android:layout_marginTop="30dp"
android:layout_marginLeft="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/ed_username"
android:id="@+id/tv_pwd"/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginRight="20dp"
android:inputType="textPassword"
app:layout_constraintTop_toBottomOf="@id/ed_username"
app:layout_constraintLeft_toRightOf="@id/tv_pwd"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/ed_pwd"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="记住密码"
android:textSize="20sp"
android:layout_marginTop="30dp"
android:layout_marginLeft="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/ed_pwd"
android:id="@+id/check_pwd"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录"
android:textSize="30sp"
android:layout_marginTop="30dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/check_pwd"
android:id="@+id/btn_login"/>
</androidx.constraintlayout.widget.ConstraintLayout>
6、提示布局界面:
毕竟只是提示一下,所以就一个登录成功
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录成功"
android:textSize="50sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
7、登录界面的逻辑处理代码:
首先我们理一下思路,变量什么的、组件什么的先初始化好,然后添加一个按钮监听、先读取一下文件,如果有数据的话显示到编辑框中;在监听方法里面判断是否勾选记住密码,如果勾选了,我们执行插入数据函数,如果没有就执行删除函数;
package com.example.qqloginactivity;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
//实现监听接口
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
//2.1 创建好变量
EditText ed_username,ed_pwd;
Button btn_login;
CheckBox check_pwd;
//创建好用户名和密码
String username,password;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initParams();
//2.4 当程序启动时,如果数据存在,我们就显示到编辑框上
String[] datas = Utils.readData(this);
if(datas != null){
ed_username.setText(datas[0]);
ed_pwd.setText(datas[1]);
}
}
//2.2 初始化各个变量,包括添加监听器
private void initParams(){
ed_username = findViewById(R.id.ed_username);
ed_pwd = findViewById(R.id.ed_pwd);
btn_login = findViewById(R.id.btn_login);
check_pwd = findViewById(R.id.check_pwd);
btn_login.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch(view.getId()){
case R.id.btn_login:
//2.5 注意,我们需要设置一个正确的密码,在让用户登录
//获取到用户填写的数据
username = ed_username.getText().toString();
password = ed_pwd.getText().toString();
if(password.equals("123456")){
//2.3 监听到用户按下按钮之后,我们判断是否勾选了复选框
if(check_pwd.isChecked()){
//如果勾选了,那么存储数据
Utils.appendData(this,username,password);
}else{
//否则,删除数据
Utils.deleteData(this);
}
//最后,打开我们的登录界面
Intent intent = new Intent(this,MainActivity.class);
startActivity(intent);
}else{
Toast.makeText(this, "密码错误", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
而我们的登录提示页面是不需要写的;这里要注意我是实现的监听接口要给按钮写监听函数;
8、拓展:
当我们写好登录和数据存储之后,可以拓展一下,我们设计一个记住密码之后自动跳转的功能,也就是自动登录;
大家想一想,我这里提供一个参考,我们先要在布局文件里再添加一个复选框,表示我们要自动登录,于是复选框变成了两个:
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="记住密码"
android:textSize="20sp"
android:layout_marginTop="30dp"
android:layout_marginLeft="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/ed_pwd"
app:layout_constraintRight_toLeftOf="@id/check_login"
android:id="@+id/check_pwd"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自动登录"
android:textSize="20sp"
android:layout_marginTop="30dp"
android:layout_marginRight="20dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/ed_pwd"
app:layout_constraintLeft_toRightOf="@id/check_pwd"
android:id="@+id/check_login"/>
其实别看是一个拓展,可是别想当然我加一个Boolean就可以,大错特错!
我们可是重启项目,无法保存你的Boolean,有人就说我们存到文件里,对,但是存一个Boolean还要定另一个变量来存储,麻烦,我给大家一个建议,就直接存字符串,“true”和“false”然后使用字符串判断就可以:
我定义一个变量flag,存字符串true和false还更简单,因为我们后面读取数据的时候就可以:
//1.2 读取数据,因为含有多个数据,所以我们使用数组存储
public static String[] readData(Context context){
//1.5 同样新建一个数据存储对象,获取到我们存储的数据
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//判断是否数据为空
String username=sp.getString("username",null);
String password=sp.getString("password",null);
String flag = sp.getString("flag",null);
String[] datas;
if(username == null){
//如果没有数据就返回null
return null;
}else{
//读取数据
datas = new String[3];
datas[0]=username;
datas[1]=password;
datas[2]=flag;
return datas;
}
}
但是,我们添加了之后又怎么判断用户勾选了自动登录呢?如果用户没有勾选我们又怎么处理呢?
别慌,我这里专门准备了一个更改数据的方法,同样放在utils类里面:
//1.4 更新自动登录
public static void updateLogin(Context context,String flag){
//1.6 同样新建一个数据存储对象,获取到我们存储的数据
SharedPreferences sp = context.getSharedPreferences("data",Context.MODE_PRIVATE);
//创建一个文件编辑对象
SharedPreferences.Editor editor = sp.edit();
editor.remove(flag);
editor.putString("flag",flag);
editor.commit();
}
我们传入flag不就可以了,在判断到用户没有勾选的时候我修改一下flag的值就完美解决了;
那么我们的逻辑就变了,首先,我们登陆的时候先判断data文件是否存在,如果存在判断一下是否是自动登录,如果是那么自动登录一下;然后,当我们点击按钮的时候,判断密码是否正确,如果正确判断时候勾选了记住密码,如果勾选了,那么将数据存进去,注意我们有三个数据和,我们还没有判断是否勾选自动登录,于是我们存默认值flag为false,然后再判断是否勾选,然后将我们的flag的值更改,最后调用更行数值的函数就可以解决了,这里最怕一些老六会尝试各种办法让我的程序出bug,所以就严谨一点,于是代码变成了:
package com.example.qqloginactivity;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
//实现监听接口
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
//2.1 创建好变量
EditText ed_username,ed_pwd;
Button btn_login;
CheckBox check_pwd,check_login;
//创建好用户名和密码,外加一个判断是否自动登录
String username,password,flag;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initParams();
//2.4 当程序启动时,如果数据存在,我们就显示到编辑框上
String[] datas = Utils.readData(this);
if(datas != null){
ed_username.setText(datas[0]);
ed_pwd.setText(datas[1]);
//如果数据存在,自动跳转
if(datas[2].equals("true")){
check_login.setChecked(true);
jump();
}
}else{
//如果数据不存在,不跳转且将自动登录变为false
check_login.setChecked(false);
}
}
//2.2 初始化各个变量,包括添加监听器
private void initParams(){
ed_username = findViewById(R.id.ed_username);
ed_pwd = findViewById(R.id.ed_pwd);
btn_login = findViewById(R.id.btn_login);
check_pwd = findViewById(R.id.check_pwd);
check_login = findViewById(R.id.check_login);
btn_login.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch(view.getId()){
case R.id.btn_login:
//2.5 注意,我们需要设置一个正确的密码,在让用户登录
//获取到用户填写的数据
username = ed_username.getText().toString();
password = ed_pwd.getText().toString();
if(password.equals("123456")){
//2.3 监听到用户按下按钮之后,我们判断是否勾选了复选框
if(check_pwd.isChecked()){
//如果勾选了,那么存储数据
Utils.appendData(this,username,password,flag);
if(check_login.isChecked()){
flag = "true";
}else{
flag = "false";
}
Utils.updateLogin(this,flag);
}else{
//否则,删除数据
Utils.deleteData(this);
}
jump();
}else{
Toast.makeText(this, "密码错误", Toast.LENGTH_SHORT).show();
}
break;
}
}
//2.6 封装跳转方法
private void jump(){
//最后,打开我们的登录界面
Intent intent = new Intent(this,MainActivity.class);
startActivity(intent);
}
}
最后完美运行:
好了,那么这一期就到这,学会了就快去试试吧。