Android中的数据存储
1、SharedPreference存储(共享参数)
1.1、使用SharedPreferences存储和读取数据的步骤
存储数据
保存数据一般分为四个步骤:
- 使用Activity类的getSharedPreferences方法获得SharedPreferences对象;
- 使用SharedPreferences接口的edit获得SharedPreferences.Editor对象;
- 通过SharedPreferences.Editor接口的putXXX方法保存key-value对;
- 通过过SharedPreferences.Editor接口的commit方法保存key-value对。
读取数据
读取数据一般分为两个步骤:
- 使用Activity类的getSharedPreferences方法获得SharedPreferences对象;
- 通过SharedPreferences对象的getXXX方法获取数据;
用到的方法
- 获取SharedPreferences对象
(根据name查找SharedPreferences,若已经存在则获取,若不存在则创建一个新的)
public abstract SharedPreferences getSharedPreferences (String name, int mode)
参数
name:命名
mode:模式,包括
MODE_PRIVATE(只能被自己的应用程序访问)
MODE_WORLD_READABLE(除了自己访问外还可以被其它应该程序读取)
MODE_WORLD_WRITEABLE(除了自己访问外还可以被其它应该程序读取和写入)
public SharedPreferences getPreferences (int mode)
- 获取Editor对象(由SharedPreferences对象调用)
abstract SharedPreferences.Editor edit()
- 写入数据(由Editor对象调用)
//写入boolean类型的数据
abstract SharedPreferences.Editor putBoolean(String key, boolean value)
//写入float类型的数据
abstract SharedPreferences.Editor putFloat(String key, float value)
//写入int类型的数据
abstract SharedPreferences.Editor putInt(String key, int value)
//写入long类型的数据
abstract SharedPreferences.Editor putLong(String key, long value)
//写入String类型的数据
abstract SharedPreferences.Editor putString(String key, String value)
//写入Set<String>类型的数据
abstract SharedPreferences.Editor putStringSet(String key, Set<String> values)
参数
key:指定数据对应的key
value:指定的值
- 移除指定key的数据(由Editor对象调用)
abstract SharedPreferences.Editor remove(String key)
参数
key:指定数据的key
- 清空数据(由Editor对象调用)
abstract SharedPreferences.Editor clear()
- 提交数据(由Editor对象调用)
abstract boolean commit()
- 读取数据(由SharedPreferences对象调用)
//读取所有数据
abstract Map<String, ?> getAll()
//读取的数据为boolean类型
abstract boolean getBoolean(String key, boolean defValue)
//读取的数据为float类型
abstract float getFloat(String key, float defValue)
//读取的数据为int类型
abstract int getInt(String key, int defValue)
//读取的数据为long类型
abstract long getLong(String key, long defValue)
//读取的数据为String类型
abstract String getString(String key, String defValue)
//读取的数据为Set<String>类型
abstract Set<String> getStringSet(String key, Set<String> defValues)
参数
key:指定数据的key
defValue:当读取不到指定的数据时,使用的默认值defValue
1.2、SharedPreferences的使用
activity_main.xml
<?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=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="姓名"/>
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:padding="5dp"
android:hint="请输入用户名"
android:background="@android:drawable/editbox_background"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="年龄:"
android:layout_marginTop="10dp"/>
<EditText
android:id="@+id/et_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:padding="5dp"
android:hint="请输入年龄"
android:background="@android:drawable/editbox_background"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="20dp">
<Button
android:id="@+id/btn_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="#fff"
android:text="保存参数"/>
<Button
android:id="@+id/btn_resume"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:text="恢复参数"/>
</LinearLayout>
</LinearLayout>
MainActivity.java
package com.tinno.sharedpreference;
import androidx.appcompat.app.AppCompatActivity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText etName,etAge;
private Button btnSave,btnResume;
private SharedPreferences sp; //共享参数 ---> 本质就是一个map结构的xml结构.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
sp = getSharedPreferences("configs", MODE_PRIVATE);
}
private void initView() {
etName = (EditText) findViewById(R.id.et_name);
etAge = (EditText) findViewById(R.id.et_age);
btnSave = (Button) findViewById(R.id.btn_save);
btnResume = (Button) findViewById(R.id.btn_resume);
btnSave.setOnClickListener(this);
btnResume.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_save: //保存数据
String name = etName.getText().toString().trim();
String age = etAge.getText().toString().trim();
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(age)){
return;
}
// 编辑器
SharedPreferences.Editor editor = sp.edit();
// 数据存储,只能存储简单的数据类型
editor.putString("name",age);
editor.putString("age",name);
// 提交 --> 对数据的一个保存
boolean commit = editor.commit();
if (commit){
Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
etName.setText("");
etAge.setText("");
}
break;
case R.id.btn_resume: //恢复数据
String nameValue = sp.getString("name","");
String ageValue = sp.getString("age","");
etName.setText(nameValue);
etAge.setText(ageValue);
break;
}
}
}
效果
点击保存参数获取输入框的内容,并清空输入框,点击恢复参数,将之前输入的内容再次显示在输入框中
2、内部存储
Internal Storage:内部存储器。
我们可以直接将文件保存在设备的内部存储设备上。默认情况下,保存到内部存储的文件对您的应用程序是私有的,其他应用程序无法访问它们(用户也不可以)。当用户卸载您的应用程序时,这些文件将被删除。
创建和写入私有文件到内部存储
String content = "abcdefghigk";
try {
FileOutputStream os = openFileOutput("internal.txt", MODE_PRIVATE);
//写数据
os.write(content.getBytes());
os.close();//关闭文件
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
- 调用openFileOutput(),参数:文件的名称和操作模式。这返回一个FileOutputStream。该方式创建的文件路径为:/data/data/<包名>/files/目录下。
- write()将字符串写入文件。
- close()关闭输出流。
文件操作模式有以下四种:
- MODE_PRIVATE 将创建文件(或替换相同名称的文件),并使其对您的应用程序是私有的。
- MODE_APPEND 如果文件已经存在,则将数据写入现有文件的末尾,而不是将其删除。
- MODE_WORLD_READABLE 可读(不建议)
- MODE_WORLD_WRITEABLE 可写(不建议)
从内部存储读取文件
byte buffer[] = new byte[4096];
try {
FileInputStream in = openFileInput("internal.txt");
//将数据读到buffer.
int len = in.read(buffer);
in.close();//关闭文件
Log.i(TAG, "buffer="+new String(buffer,0,len));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
- 调用openFileInput()并传递要读取的文件的名称。这返回一个FileInputStream。
- 从文件中读取字节read()。
- 然后关闭流 close()。
保存缓存文件
如果您想缓存一些数据,而不是持久存储,那么您应该使用它getCacheDir()来打开File代表应用程序应该保存临时缓存文件的内部目录。
当设备的内部存储空间不足时,Android可能会删除这些缓存文件以恢复空间。但是,您不应该依靠系统来清理这些文件。您应该始终自己维护缓存文件,并保持在合理的空间上限,例如1MB。当用户卸载您的应用程序时,这些文件将被删除。
其他有用的方法
- getFilesDir()
获取保存内部文件的文件系统目录的绝对路径。 - getDir()
在内部存储空间内创建(或打开现有的)目录。 - deleteFile()
删除保存在内部存储上的文件。 - fileList()
返回应用程序当前保存的文件数组。
内部存储的使用:
xml
<?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"
tools:context=".MainActivity"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="请输入文件名"
android:singleLine="true"
android:padding="10dp"
android:background="@android:drawable/editbox_background"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="保存"
android:background="@android:drawable/edit_text"
android:onClick="saveFile"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="打开"
android:background="@android:drawable/edit_text"
android:onClick="openFile"/>
</LinearLayout>
<EditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="5dp"
android:hint="请输入文件内容"
android:background="@android:drawable/editbox_background"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除文件"
android:onClick="deleteFile"
android:background="@android:drawable/edit_text"/>
</LinearLayout>
java
package com.tinno.internalstorage;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class MainActivity extends AppCompatActivity {
// 内部存储,openFileOutput(),openFileInput(),deleteFile()...
// 文件的保存位置: /data/data/<包名>/files/...
// 内部存储的特点:内部存储里的东西,会随着app的卸载而被清掉
private EditText etName,etContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
etName = (EditText) findViewById(R.id.et_name);
etContent = (EditText) findViewById(R.id.et_content);
}
/**
* 保存文件
* @param view
*/
public void saveFile(View view){
String fileName = etName.getText().toString().trim();
String content = etContent.getText().toString().trim();
if(TextUtils.isEmpty(fileName) || TextUtils.isEmpty(content)){
return;
}
try {
// 打开一个用来读写的文件,该文件是与当前上下文所在的包有关,而且该方法不需要添加任何权限,因为这是内部存储
FileOutputStream fos = openFileOutput(fileName,MODE_PRIVATE);
fos.write(content.getBytes(StandardCharsets.UTF_8));
fos.close();
Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
etName.setText("");
etContent.setText("");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 打开文件
* @param view
*/
public void openFile(View view){
String fileName = etName.getText().toString().trim();
if (TextUtils.isEmpty(fileName)){
return;
}
try {
// 得到一个用来只读的输入流
FileInputStream fis = openFileInput(fileName);
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
fis.close();
etContent.setText(new String(buffer));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除文件
* @param view
*/
public void deleteFile(View view){
String fileName = etName.getText().toString().trim();
if (TextUtils.isEmpty(fileName)){
return;
}
// 删除当前上下文中指定名称的文件
boolean deleteFile = deleteFile(fileName);
if(deleteFile){
Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
etName.setText("");
etContent.setText("");
}
}
}
效果
可以根据之前写入的文件名,查询该文件名的内容
3、外部存储
External Storage
每个Android兼容设备都支持一个共享的“外部存储”,您可以使用它来保存文件。这可以是可移动存储介质(例如SD卡)或内部(不可移动)存储。保存到外部存储器的文件是可读的,用户可以在使用USB大容量存储在计算机上传输文件时进行修改。
访问外部存储
为了在外部存储上读取或写入文件,您的应用程序必须获取 **READ_EXTERNAL_STORAGE 或WRITE_EXTERNAL_STORAGE**系统权限。例如:
<uses-permission android:name = “android.permission.READ_EXTERNAL_STORAGE” />
<uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE” />
如果您需要读取和写入文件,则需要仅请求 WRITE_EXTERNAL_STORAGE权限,因为它隐含地还需要读取访问权限。
检查媒体可用性
在对外部存储进行任何处理之前,应始终调用**getExternalStorageState()**以检查介质是否可用。介质可能安装到计算机,丢失,只读或处于某种其他状态。这里有几种可用于检查可用性的方法:
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
该getExternalStorageState()方法返回您可能想要检查的其他状态,如媒体是否被共享(连接到计算机),完全丢失,已被删除等等。您可以使用这些来通知用户更多信息您的应用程序需要访问媒体。
保存可与其他应用共享的文件
从媒体扫描器隐藏您的文件
通常,用户可以通过应用程序获取的新文件应该保存到其他应用可以访问的设备上的“公共”位置,用户可以轻松地从设备中复制它们。这样做时,你应该使用的共享的公共目录,如之一Music/,Pictures/和Ringtones/。
为了得到一个File较合适的公共目录,呼叫getExternalStoragePublicDirectory(),通过它你想要的目录的类型,如 DIRECTORY_MUSIC,DIRECTORY_PICTURES, DIRECTORY_RINGTONES,或其他。通过将文件保存到相应的媒体类型目录,系统的媒体扫描器可以对系统中的文件进行适当的分类(例如,铃声在系统设置中显示为铃声,而不是音乐)。
例如,以下是在公共图片目录中为新相册创建目录的方法:
public File getAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
保存专用的文件
如果您正在处理不适用于其他应用程序的文件(例如只有您的应用程序使用的图形纹理或声音效果),则应通过调用在外部存储上使用专用存储目录getExternalFilesDir()。此方法还需要一个type参数来指定子目录的类型(如DIRECTORY_MOVIES)。如果您不需要特定的媒体目录,请传递null以接收您应用的私有目录的根目录。
从Android 4.4开始,在应用程序的私有目录中读取或写入文件不需要READ_EXTERNAL_STORAGE 或WRITE_EXTERNAL_STORAGE 权限。因此,您可以通过添加maxSdkVersion 属性来声明只能在较低版本的Android上请求权限:
<uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE” android:maxSdkVersion = “18” />
保存缓存文件
打开一个File,该File代表您应该保存缓存文件的外部存储目录,请调用getExternalCacheDir()。如果用户卸载您的应用程序,这些文件将被自动删除。
4、数据库存储
5、网络存储
不同存储方法的使用场景
1、SharedPreference存储:简单的信息……
2、内部存储:保存在app内部存储空间,非公开……
3、外部存储:公共空间……
4、数据库存储:结构化数据……
5、网络存储:云……