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(除了自己访问外还可以被其它应该程序读取和写入)
若该Activity只需要创建一个SharedPreferences对象的时候,可以使用getPreferences方法,不需要为SharedPreferences对象命名,只要传入参数mode即可
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、内部存储
一说内部存储,有人可能会和内存混淆在一起,其实这两个概念很好区分,内部存储是用于持久化存储的,属于ROM,手机关机或者退出App数据是不会丢失的,而内存是RAM,退出App或者关机之后数据就会丢失。所以,内部存储不是内存。所谓的内部存储,其实是手机ROM上的一块存储区域,主要用于存储系统以及应用程序的数据。内部存储在Android系统对应的根目录是 /data/data/,这个目录普通用户是无权访问的,用户需要root权限才可以查看。不过我们可以通过Android Studio的View----Tool Windows----Device File Explorer工具来查看该目录,内部存储目录的大致结构如下所示。
内部存储目录内部存储目录
从上图可以看到,/data/data目录是按照应用的包名来组织的,每个应用都是属于自己的内部存储目录,而且目录的名称就是该应用的包名,这个目录是在安装应用的时候自动创建的,当应用被卸载后,该目录也会被系统自动删除。所以,如果你将数据存储于内部存储中,其实就是把数据存储到自己应用包名对应的内部存储目录中。每个应用的内部存储目录都是私有的,也就是说内部存储目录下的文件只能被应用自己访问到,其他应用是没有权限访问的。应用访问自己的内部存储目录时不需要申请任何权限。
一个应用典型的内部存储目录结构如下所示。
相信很多人看到内部存储的目录结构,都有似曾相识的感觉,没错我们平常经常和内部存储打交道,只不过我们不知道罢了,下面我们来看下内部存储目录下各个子目录的作用。
app_webview:主要用于存储webview加载过程中的数据,例如Cookie,LocalStorage等。
cache:主要用于存储使用应用过程中产生的缓存数据。
databases:主要用于存储数据库类型的数据。我们平常创建的数据库文件就是存储在这里。
files:可以在该目录下存储配置文件,敏感数据等。
shared_prefs:用于存储SharedPreference文件。我们使用SharedPreference的时候只指定了文件名,并没有指定存储路径,其实SP的文件就是保存到了这个目录下。
那么有哪些API可以获取到内部存储目录呢,我们主要是使用Context类提供的接口来访问内部存储目录。
1.getDataDir() //获取的目录是/data/user/0/package_name,即应用内部存储的根目录
2.getFilesDir() //获取的目录是/data/user/0/package_name/files,即应用内部存储的files目录
3.getCacheDir() //获取的目录是/data/user/0/package_name/cache,即应用内部存储的cache目录
4.getDir(String name, int mode) //获取的目录是/data/user/0/package_name/app_name,如果该目录不存在,系统会自动创建该目录。
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()。
如果要在编译时保存应用程序中的静态文件,请将该文件保存在项目res/raw/目录中。您可以打开它 openRawResource(),传递资源ID(R.raw.)。此方法返回一个 可用于读取文件(但不能写入原始文件)的 InputStream实例。
保存缓存文件
如果您想缓存一些数据,而不是持久存储,那么您应该使用它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权限,因为它隐含地还需要读取访问权限。
注意:从Android 4.4开始,如果您正在阅读或仅写入您的应用程序的私有文件,则不需要这些权限。
检查媒体可用性
在对外部存储进行任何处理之前,应始终调用**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()方法返回您可能想要检查的其他状态,如媒体是否被共享(连接到计算机),完全丢失,已被删除等等。您可以使用这些来通知用户更多信息您的应用程序需要访问媒体。
保存可与其他应用共享的文件
外部存储的方法
Environment.getExternalStorageDirectory().getAbsolutePath():/storage/emulated/0 这个方法是获取外部存储的根路径
Environment.getExternalStoragePublicDirectory().getAbsolutePath():/storage/emulated/0 这个方法是获取外部存储的根路径
getExternalFilesDir:/storage/emulated/0/Android/data/包名/files 获取某个应用在外部存储中的files路径
getExternalCacheDir:/storage/emulated/0/Android/data/包名/cache 获取某个应用在外部存储中的cache路径
注意方法3,4在android4.4之前可能返回为null,因为用户可能没有插入外部存储的SD卡,但是4.4后手机自带外部存储,所以不会返回null
从媒体扫描器隐藏您的文件
通常,用户可以通过应用程序获取的新文件应该保存到其他应用可以访问的设备上的“公共”位置,用户可以轻松地从设备中复制它们。这样做时,你应该使用的共享的公共目录,如之一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” />
注意: 当用户卸载您的应用程序时,此目录及其所有内容将被删除。此外,系统介质扫描程序不会读取这些目录中的文件,因此它们无法从MediaStore内容提供程序访问。因此,您不应将这些目录用于最终属于用户的媒体,例如您的应用程序捕获或编辑的照片,或用户使用您的应用购买的音乐 - 这些文件应保存在公共目录中。
保存缓存文件
打开一个File,该File代表您应该保存缓存文件的外部存储目录,请调用getExternalCacheDir()。如果用户卸载您的应用程序,这些文件将被自动删除。
提示: 要保留文件空间并保持应用程序的性能,请务必仔细管理缓存文件,并在整个应用程序的生命周期中删除不再需要的缓存文件。
4、数据库存储(SQLite数据库)
一、SQLite数据库简介
SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入 式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持 Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如Tcl、PHP、Java、C++、.Net等,还有ODBC接口,同样比起 Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。
二、SQLite数据类型
一般数据采用的固定的静态数据类型,而SQLite采用的是动态数据类型,会根据存入值自动判断。SQLite具有以下常用的数据类型:
(1)NULL: 这个值为空值
(2)VARCHAR(n):长度不固定且其最大长度为 n 的字串,n不能超过 4000。
(3)CHAR(n):长度固定为n的字串,n不能超过 254。
(4)INTEGER: 值被标识为整数,依据值的大小可以依次被存储为1,2,3,4,5,6,7,8.
(5)REAL: 所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号.
(6)TEXT: 值为文本字符串,使用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE).
(7)BLOB: 值是BLOB数据块,以输入的数据格式进行存储。如何输入就如何存储,不改变格式。
(8)DATA :包含了 年份、月份、日期。
(9)TIME: 包含了 小时、分钟、秒。
三、Android之SQLiteOpenHelper介绍
Android 为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper 帮助类,借助这个类就可以非常简单地对数据库进行创建和管理
public abstract class SQLiteOpenHelper;
SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
SQLiteOpenHelper 中还有两个非常重要的实例方法, getReadableDatabase() 和getWritableDatabase()。这两个方法都可以创建
或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对
象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据
库,而getWritableDatabase()方法则将出现异常。
public SQLiteDatabase getReadableDatabase();
public SQLiteDatabase getWritableDatabase()
SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version);
这个构造方法中接收四个参数:
第一个参数是Context,这个没什么好说的,必须要有它才能对数据库进行操作。
第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。
第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null。
第四个参数表示当前数据库的版本号, 可用于对数据库进行升级操作。
构建出SQLiteOpenHelper 的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
下面,将详细讲解SQLiteOpenHelper的使用操作。
四、创建数据库
public class DBHelper extends SQLiteOpenHelper {
// 数据库名字
private static final String DB_NAME = "person.db";
// 数据库版本 version >= 1
private static final int DB_VERSION = 1;
// 指定了数据库的名称,版本信息
public DBHelper(@Nullable Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 创建数据库中的表
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
// SQLite数据库里的字段一般是不区分类型的,但是主键除外,主键必须是整形
String sql = "CREATE TABLE person (_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,name CHAR(10),nickname CHAR(10))";
db.execSQL(sql);
}
/**
* 数据库升级的方法
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion > oldVersion){
String sql = "DROP TABLE IF EXITS person";
db.execSQL(sql);
// 先删表在建表
onCreate(db);
}
}
}
在MainActivity 中 创建数据库和表:
需要调用以下两个方法之一,数据库和表才能真正创建出来
正常情况下,getReadableDatabase()
和getWritableDatabase()
得到的结果是一样的非正常情况,比如明确要求以只读的方式来打开数据库,或者磁盘满了,此时getReadableDatabase()得到的只是只读的数据库
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DBHelper dbHelper = new DBHelper(this);
// 需要调用以下两个方法之一,数据库和表才能真正创建出来
// 正常情况下,getReadableDatabase() 和 getWritableDatabase()得到的结果是一样的
// 非正常情况,比如明确要求以只读的方式来打开数据库,或者磁盘满了,此时getReadableDatabase()得到的只是只读的数据库
//SQLiteDatabase readableDatabase = dbHelper.getReadableDatabase();
SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase();
}
}
使用abd查看数据的存储位置:
进入数据库路径:
cd data/data/ <你的项目名> / databases/
进入sqlite数据库
sqlite3 数据库名
五、数据库中的CURD(增删改查)
1、添加数据的方法
-
execSQL方法进行添加数据
/** * SQL插入语句: * INSERT INTO Book(name,author,pages,price) VALUES * ("The Da Vinci Code" ,"Dan Brown",454,16.96); */ db.execSQL("INSERT INTO Book(name,author,pages,price) VALUES(?,?,?,?", new String[]{"The Lost Symbol", "Dan Brown", "510", "19.95"});
-
使用SQLiteDatabase 中提供了一个 insert() 方法,这个方法就是专门用于添加数据的。
insert() 方法接收三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到这个功能,直接传入 null 即可。第三个参数是一个 ContentValues 对象,它提供了一系列的 put() 方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名及相对应的待添加数据传入即可。
public void onClick(View v) { //获取 SQLiteDatabase 对象 SQLiteDatabase db = dbHelper.getWritableDatabase(); //使用ContentValues 对数据进行组装 ContentValues values = new ContentValues(); //开始组装第一条数据 values.put("name", "The Da Vinci Code"); values.put("author", "Dan Brown"); values.put("pages", 454); values.put("price", 16.96); //插入第一条数据 db.insert("Book", null, values); values.clear(); //开始组装第二条数据 values.put("name", "The Lost Symbol"); values.put("author", "Dan Brown"); values.put("pages", 510); values.put("price", 19.95); //插入第二条数据 db.insert("Book", null, values); }
这里只对Book表里其中四列的数据进行了组装,id并没有赋值。因为创建表的时候就将 id 列设置为自动增长了,它的值会在入库的时候自动生成,不需要手动赋值。
2、删除数据的方法
-
execSQL方法进行添加数据
/** *SQL删除语句: * DELETE FROM Book WHERE pages > 500; */ db.execSQL("DELETE FROM Book WHERE pages > ?",new String[]{"500"});
-
SQLiteDatabase 中提供了一个 delete()方法专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,第二、第三个参数用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("Book", "pages > ?", new String[]{"500"}); }
3、修改(更新)数据的方法
-
execSQL方法进行添加数据
/** * SQL更新语句: * UPDATE Book SET price = 10.99 WHERE name = "The Da Vinci Code" ; */ db.execSQL("UPDATE Book SET price = ? WHERE name = ?", new String[]{"10.99", "The Da Vinci Code"});
-
SQLiteDatabase 中提供了一个非常好用的 update() 方法用于对数据进行更新,
这个方法接收四个参数,第一个参数和 insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。第二个参数是 ContentValues 对象,把要更新的数据在这里组装进去。第三、第四个参数用于去约束更新某一行的数据,不指定的话默认就是更新所有行。
public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price", 10.99); //?是一个占位符,通过字符串数组为每个占位符指定相应的内容 db.update("Book", values, "name = ?", new String[]{"The Da Vinci Code"}); }
4、查询数据的方法
-
execSQL方法进行添加数据
/** * SQL查询语句: * SELECT * FROM BOOK ; */ db.rawQuery("SELECT * FROM BOOK", null);
-
SQLiteDatabase 中还提供了一个 query() 方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。
query()方法参数及对应SQL:
table:指定查询的表名,对应 from table_name
columns:指定查询的列名,对应 select column1,column2 …
selection:指定 where 的约束条件,where column = value
selectionArgs:为 where 中的占位符提供具体的值
groupBy:指定需要分组的列,group by column
having:对分组后的结果进一步约束,having column = value
orderBy:指定查询结果的排序方式,order by column
虽然 query()方法的参数非常多,但是不必每条查询语句都指定上所有的参数,多数情况下只需传入少数几个参数就可以完成查询操作了。
调用 query()方法后会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出。
public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); //查询Book表中的所有数据 Cursor cursor = db.query("Book", null, null, null, null, null, null, null); //遍历Curosr对象,取出数据并打印 while (cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndex("name")); String author = cursor.getString(cursor.getColumnIndex("author")); int pages = cursor.getInt(cursor.getColumnIndex("pages")); double price = cursor.getDouble(cursor.getColumnIndex("price")); Log.d("woider", "Book Name:" + name + " Author:" + author + " Pages:" + pages + " Price:" + price); } //关闭Cursor cursor.close(); }
5、网络存储
不同存储方法的使用场景
1、SharedPreference存储:简单的信息……
2、内部存储:保存在app内部存储空间,非公开……
3、外部存储:公共空间……
4、数据库存储:结构化数据……
5、网络存储:云……