Android手机应用开发(六) | 数据存储(上)

实验目的

  1. 学习SharedPreference的基本使用。
  2. 学习Android中常见的文件操作方法。
  3. 复习Android界面编程。

实现效果

GIF

SharedPreference的使用

它是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data/<package name>/shared_prefs目录下

先来创建一个简单的设置密码的页面

1540994988442

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

<EditText
android:id="@+id/new_password"
android:layout_marginTop="150dp"
android:layout_width="match_parent"
android:layout_height="60dp"
android:hint="New Password"
android:inputType="textPassword"
app:layout_constraintTop_toTopOf="parent"
/>

<EditText
android:id="@+id/confirm_password"
android:layout_marginTop="60dp"
android:layout_width="match_parent"
android:layout_height="60dp"
android:hint="Confirm Password"
android:inputType="textPassword"
app:layout_constraintTop_toTopOf="@id/new_password"
/>
<Button
android:id="@+id/ok"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="OK"
app:layout_constraintTop_toBottomOf="@id/confirm_password"/>

<EditText
android:id="@+id/login_password"
android:layout_width="match_parent"
android:visibility="invisible"
android:hint="Password"
android:inputType="textPassword"
android:layout_height="60dp"
app:layout_constraintTop_toBottomOf="@id/new_password"/>

<Button
android:id="@+id/clear"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="Clear"
android:layout_marginStart="30dp"
app:layout_constraintTop_toBottomOf="@id/confirm_password"
app:layout_constraintStart_toEndOf="@id/ok"
/>

</android.support.constraint.ConstraintLayout>

其实这个页面是有三个EditText的,

有两个是设置密码时的输入框(设置密码和确认密码)

有一个是登陆时的密码输入框(登录密码)

此时还没有设置密码,所以另外一个EditText设置不可见INVISIBLE

处理按钮点击的事件

文本清除按钮:

1
2
3
4
5
6
7
8
9
button_clear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//清空文本框中内容
pw1.setText("");
pw2.setText("");
pw.setText("");
}
});

确定按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
button_ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//还没注册密码成功
if(pw.getVisibility() != View.VISIBLE){
if(pw1.getText().toString().isEmpty()){
Toast.makeText(MainActivity.this, "Password cannot be empty.", Toast.LENGTH_SHORT).show();
//清空文本框中内容
//模拟点击按钮
button_clear.performClick();
}else if(! pw1.getText().toString().equals(pw2.getText().toString())){
Toast.makeText(MainActivity.this, "Password mismatch.", Toast.LENGTH_SHORT).show();
button_clear.performClick();
}else{
//简单两句代码,把密码写进存储中
//getActivity()得到的就是MainActivity.this,不需要用这个函数了
SharedPreferences sharedPref = MainActivity.this.getSharedPreferences("MY_PASSWORD", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
//保存注册密码
editor.putString("NEW_PASSWORD", pw1.getText().toString());
//保存已注册状态
editor.putString("STATUS", "LOGIN");
//系统提醒我将commit改成apply的
editor.apply();
Toast.makeText(MainActivity.this, "Successfully.", Toast.LENGTH_SHORT).show();
pw1.setVisibility(View.INVISIBLE);
pw2.setVisibility(View.INVISIBLE);
pw.setVisibility(View.VISIBLE);
}
}else{
//获取密码比较是否正确
SharedPreferences sharedPref = MainActivity.this.getSharedPreferences("MY_PASSWORD", Context.MODE_PRIVATE);
String real_password = sharedPref.getString("NEW_PASSWORD", null);
//if(!pw.getText().equals(real_password))我之前这样写是错误的
if(!pw.getText().toString().equals(real_password)){
//登录失败
Toast.makeText(MainActivity.this, "Password incorrect", Toast.LENGTH_SHORT).show();
button_clear.performClick();
}else{
//登陆成功
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
}

}
});

输入密码之后,查看虚拟机的文件

Logcat->Device File Explore->data/data/com.janking.project3.shared_prefs->MY_PASSWORD

可以发现,确实存进去了密码,而且还存进去了一个关键字为STATUS的东西,这个是用来辨别是否已经设置了密码的

1540996583921

为了利用好这个STATUS字段,在MainActivityonCreate方法里添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//这三句话也很重要,即使xml已经设置了三个可见度是,但是经过测试,中途删除了SharedPreference里面的文件的话输入框布局还是没变
pw1.setVisibility(View.VISIBLE);
pw2.setVisibility(View.VISIBLE);
pw.setVisibility(View.INVISIBLE);
//读取状态
SharedPreferences sp = MainActivity.this.getSharedPreferences("MY_PASSWORD", Context.MODE_PRIVATE);
if(sp != null){
String last_status = sp.getString("STATUS", null);
//不用判断是否是“LOGIN”,只要存在就行
if(last_status != null){
pw1.setVisibility(View.INVISIBLE);
pw2.setVisibility(View.INVISIBLE);
pw.setVisibility(View.VISIBLE);
}
}

Internal Storage文件的操作

MainActivity设置密码之后,再登录成功之后,跳到另一个页面SecondActivity在这里简单使用一下文件的操作

布局

1540997508834

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".SecondActivity">

<EditText
android:layout_margin="10dp"
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Write like HelloWorld?"
android:gravity="top"
android:layout_weight="1"/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="fill"
android:paddingStart="30dp"
android:paddingEnd="30dp"
android:orientation="horizontal">

<!--按钮均匀分布-->
<Button
android:id="@+id/button_save"
android:layout_width="0dp"
android:layout_weight="1"
android:text="SAVE"
android:layout_marginEnd="30dp"
android:layout_height="match_parent" />

<Button
android:id="@+id/button_load"
android:layout_width="0dp"
android:layout_weight="1"
android:text="LOAD"
android:layout_marginEnd="30dp"
android:layout_height="match_parent" />

<Button
android:id="@+id/button_clear"
android:layout_width="0dp"
android:text="CLEAR"
android:layout_weight="1"
android:layout_height="match_parent" />

</LinearLayout>

</LinearLayout>

LOADSAVE按钮的事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//前面记得加
final String FILE_NAME = "data.txt";

//读取文件
bt_load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try (FileInputStream fileInputStream = openFileInput(FILE_NAME)) {
byte[] contents = new byte[fileInputStream.available()];
fileInputStream.read(contents);
//et.setText(contents.toString())竟然不行
et.setText(new String(contents));
Toast.makeText(SecondActivity.this, "Load successfully.", Toast.LENGTH_SHORT).show();
} catch (IOException ex) {
Toast.makeText(SecondActivity.this, "Fail to load file.", Toast.LENGTH_SHORT).show();
}
}
});
//保存文件
bt_save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try (FileOutputStream fileOutputStream = openFileOutput(FILE_NAME, MODE_PRIVATE)) {
fileOutputStream.write(et.getText().toString().getBytes());
Toast.makeText(SecondActivity.this, "Save successfully.", Toast.LENGTH_SHORT).show();
} catch (IOException ex) {
Toast.makeText(SecondActivity.this, "Fail to save file.", Toast.LENGTH_SHORT).show();
}
}
});

这其中有个小问题,就是读取的文件流是用byte数组接收的,开始我想把它转换为String用的是toString方法,但是得到的是乱码,然后改成new String()就可以了

toString()与new String ()用法区别

str.toString是调用了object对象的类的toString方法。一般是返回这么一个String[class name]@[hashCode]
new String(str)是根据parameter是一个字节数组,使用java虚拟机默认的编码格式,将这个字节数组decode为对应的字符。若虚拟机默认的编码格式是ISO-8859-1,按照ascii编码表即可得到字节对应的字符。

什么时候用什么方法呢?

new String()一般使用字符转码的时候,byte[]数组的时候

toString()将对象打印的时候使用

经检验,文件内容确实写了进去(内容很乱,是我写的,而不是乱码……),文件存放在files文件夹中

1540997910417

当 Activity 不可⻅时,如何将其从 activity stack 中除去(按返回键直接返回Home)

  • 让不处于活动状态的MainActivity直接调用finish方法,点击返回按钮后会发现它就没了……
1
2
3
4
5
6
//当此Acticity暂停时直接结束(移出栈)
@Override
protected void onPause() {
super.onPause();
MainActivity.this.finish();
}
  • 还有一种办法,在manifests文件中添加

android:noHistory="true"

1540998127063

External Storage文件的操作

manifests中添加

1
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

1541001942041

进入sdk目录下的tools文件夹,输入下列命令

.\mksdcard 128M sdcard.img

即可创建一个128M大小的命名为sdcard的映像文件,它可以挂载到虚拟机上作为sd卡目录

1541001233162

添加一个图片文件到mipmap中,这里我加了我的头像face.jpg

SecondActivity中新建一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void createExternalStoragePrivateFile(){
File file = new File(getExternalFilesDir(null), "DemoFile.jpg");
try {
@SuppressLint("ResourceType") InputStream is = getResources().openRawResource(R.mipmap.face);
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
is.close();
os.close();
} catch (IOException e) {
// Unable to create file, likely because external storage is
// not currently mounted.
Log.w("ExternalStorage", "Error writing " + file, e);
}
}

并在onCreate中调用它

应用启动到SecondActivity中时,查看文件确实存在

1541001890672

感谢资助辣条吃!