【Android学习】数据存储

目录

一、共享参数SharedPreferences

二、数据库SQLite

三、文件存储

四、应用Application

五、内容提供与处理

一、共享参数SharedPreferences

SharedPreferences是Android平台上一个轻量级的存储类,主要用于保存应用的配置参数,如用户设置、应用状态等。它以键值对(Key-Value)的形式存储数据,并且是以XML文件格式存放在设备上的。保存SharedPreferences键值对信息的文件路径是/data/data/应用包名/shared_prefs/文件名.xml

1.基本用法

1.获取SharedPreferences实例

使用ContextgetSharedPreferences(String name, int mode)方法获取SharedPreferences对象。这里的name是偏好文件的名称mode通常是MODE_PRIVATE,表示这个文件是私有的,只能被本应用访问。

2.编辑SharedPreferences:

通过SharedPreferences对象的edit()方法获取SharedPreferences.Editor对象,然后使用该对象的put方法(如putString、putInt等)来添加或修改数据。

3.提交更改:

使用Editor对象的apply()commit()方法来提交更改。apply()是异步的,不会阻塞主线程,而commit()是同步的,会返回一个布尔值表示提交是否成功。

4.读取SharedPreferences中的数据:

使用SharedPreferences对象的get方法(如getString、getInt等)来读取数据。如果指定的键不存在,则返回一个默认值。

2.示例代码

public class MainActivity extends AppCompatActivity {  
  
    // SharedPreferences文件名  
    private static final String PREFS_NAME = "UserPrefs";  
    // SharedPreferences中的键  
    private static final String KEY_USER_NAME = "userNameKey";  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        // 写入数据到SharedPreferences  
        saveUserName("JohnDoe");  
  
        // 读取数据从SharedPreferences  
        String userName = getUserName();  
          
        // 显示用户名  
        TextView textView = findViewById(R.id.textViewUserName);  
        textView.setText("Hello, " + userName + "!");  
    }  
  
    // 保存用户名到SharedPreferences  
    private void saveUserName(String userName) {  
        SharedPreferences sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);  
        SharedPreferences.Editor editor = sharedPreferences.edit();  
        editor.putString(KEY_USER_NAME, userName);  
        editor.apply(); // 或者使用 editor.commit();  
    }  
  
    // 从SharedPreferences获取用户名  
    private String getUserName() {  
        SharedPreferences sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);  
        return sharedPreferences.getString(KEY_USER_NAME, "Guest"); // 第二个参数是默认值  
    }  
}

3.使用场景

  • 存储应用的配置参数,如用户登录状态、音量设置等。
  • 保存少量的、简单的状态信息,不需要频繁修改。

二、数据库SQLite

SQLite是Android开发中常用的轻量级数据库,它提供了一个用于存储和管理数据的简单方法。在Android中使用SQLite可以帮助你轻松地保存应用程序的数据,如用户信息、设置、缓存数据等。

1.数据管理器SQLiteDatabase

SQLiteDatabase是SQLite的数据库管理类,它提供了丰富的API来执行SQL语句,对数据库进行增删改查等操作。

常用方法:

execSQL:执行拼接好的SQL控制语句。一般用于建表、删表、变更表结构。

delete:删除符合条件的记录

update:更新符合条件的记录

insert:插入一条记录

query:执行查询操作,返回结果集的游标

rawQuery:执行拼接好的SQL查询语句,返回结果集的游标

openDatabase:打开指定路径的数据库

isOpen:判断数据库是否已打开

close:关闭数据库

setVersion:设置数据库的版本号

getVersion:获取数据库的版本号

beginTransaction:开始事务

setTransactionSuccessful:设置事务的成功标志。

endTransaction:结束事务。执行本方法时,系统会判断是否已执行setTransactionSuccessful,如果之前已设置就提交,如果没有设置就回滚事务

2.数据库帮助器SQLiteOpenHelper

SQLiteOpenHelper是一个抽象类,用于帮助开发者管理数据库的创建和版本更新。

常用方法

getReadableDatabase(): 以读写方式打开数据库(如果磁盘空间不足,会尝试以只读方式打开)。

getWritableDatabase(): 以读写方式打开数据库(如果磁盘空间不足,将抛出异常)。

onCreate(): 当数据库第一次创建时调用此方法,用于初始化数据库表和数据。

onUpgrade(): 当数据库版本升级时调用此方法,用于更新数据库表和数据。

onDowngrade(): 当数据库版本降级时调用此方法。

onOpen(): 当数据库打开时调用此方法。

onConfigure():在数据库连接建立之后但在任何数据库操作之前调用。

3.游标类Cursor

游标类Cursor在数据库操作中扮演着重要角色,它允许开发者逐行遍历查询结果集。

常用方法:

close:关闭游标。

isClosed:判断游标是否关闭。

isFirst:判断游标是否在开头。

isLast:判断游标是否在末尾。

moveToFirst:移动游标到开头。

moveToLast:移动游标到末尾。

moveToNext:移动游标到下一条记录。

moveToPrevious:移动游标到上一条记录。

move:往后移动游标若干条记录。

moveToPosition:移动游标到指定位置的记录。

getCount:获取结果记录的数量。

getInt:获取指定字段的整型值。

getLong:获取指定字段的长整型值。

getFloat:获取指定字段的浮点数值。

getString:获取指定字段的字符串值。

getType:获取指定字段的字段类型。

4.基本用法

1.新建一个继承自SQLiteOpenHelper的数据库操作类,按提示重写onCreateonUpgrade两个方法。onCreate方法用于创建数据库和表结构,而onUpgrade方法用于处理数据库的版本升级。

public class MyDatabaseHelper extends SQLiteOpenHelper {  
  
    private static final String DATABASE_NAME = "mydatabase.db";  
    private static final int DATABASE_VERSION = 1;  
  
    public MyDatabaseHelper(Context context) {  
        super(context, DATABASE_NAME, null, DATABASE_VERSION);  
    }  
  
   @Override
    public void onCreate(SQLiteDatabase db) {
        // 创建数据库表的SQL语句
        String createTableSQL = "CREATE TABLE users (" +
                                "id INTEGER PRIMARY KEY AUTOINCREMENT," +
                                "name TEXT," +
                                "email TEXT" +
                                ")";
        db.execSQL(createTableSQL);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 升级数据库时的操作,一般是删除旧表并重新创建新表
        String dropTableSQL = "DROP TABLE IF EXISTS users";
        db.execSQL(dropTableSQL);
        onCreate(db);
    }
}  
  

2.在Activity或Fragment中实化数据库帮助类

public class MainActivity extends AppCompatActivity {  
  
    private MyDatabaseHelper dbHelper;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        // 实例化 MyDatabaseHelper
        dbHelper = new MyDatabaseHelper(this);  
    }
}

3.执行数据库操作:调用getReadableDatabase()getWritableDatabase()方法来获取SQLiteDatabase对象。使用SQLiteDatabase对象提供的方法执行CRUD(创建、读取、更新、删除)操作。

// 获取可写数据库
SQLiteDatabase db = dbHelper.getWritableDatabase();

// 插入新用户  
ContentValues values = new ContentValues();
values.put("name", "John Doe");
values.put("email", "[email protected]");
long newRowId = db.insert("users", null, values);

// 查询所有用户
Cursor cursor = dbHelper.getAllUsers(this);
try {
    while (cursor.moveToNext()) {
        int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
        String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
        String email = cursor.getString(cursor.getColumnIndexOrThrow("email"));
    }
} finally {
    cursor.close();
}

// 更新用户信息
ContentValues updateValues = new ContentValues();
updateValues.put("name", "Jane Doe");
updateValues.put("email", "[email protected]");
int rowsUpdated = dbHelper.updateUser(this, newRowId, updateValues);

// 删除用户
int rowsDeleted = dbHelper.deleteUser(this, newRowId);

4.确保在不再需要时关闭数据库连接:在Activity的onDestroy()方法或Fragment的onDestroyView()方法中关闭。但从API级别16(Jelly Bean)开始,SQLiteOpenHelper管理的数据库连接是由SQLiteConnectionPool自动管理的,因此通常不需要手动关闭

5.使用场景

  • 需要存储结构化数据,如用户信息、历史记录、订单等。
  • 需要频繁查询、插入、更新数据的场景。

三、文件存储

文件存储是指直接将数据以文件的形式保存到设备的内部或外部存储中。根据数据的访问权限和存储位置的不同,文件存储可以分为内部存储外部存储。内部存储是应用私有的存储空间,外部存储则是共享的存储空间,通常用于存储大文件,如图片、音频、视频等。

1.内部存储

内部存储是指设备自带的、固定不可移除的存储空间,通常位于设备的内部存储器中。存储在内部存储中的文件默认只能被创建它的应用访问,其他应用无法访问

1.1基本用法

写入文件:使用Context类的openFileOutput(String name, int mode)方法,该方法返回一个FileOutputStream对象,可用于将数据写入文件。

  • 文件名(name)不能包含路径分隔符,因为所有文件都默认存储在应用的专属目录中(如/data/data/<包名>/files/)。
  • 文件操作模式(mode)可以是MODE_PRIVATE(默认,文件私有,写入时会覆盖原文件)或MODE_APPEND(追加内容到文件末尾)。
String fileName = "example.txt";  
String data = "Hello, Internal Storage!";  
try (FileOutputStream fos = openFileOutput(fileName, MODE_PRIVATE)) {  
    fos.write(data.getBytes());  
} catch (IOException e) {  
    e.printStackTrace();  
} 

读取文件:使用Context类的openFileInput(String name)方法,该方法返回一个FileInputStream对象,可用于从文件中读取数据。

try (FileInputStream fis = openFileInput(fileName)) {  
    InputStreamReader isr = new InputStreamReader(fis);  
    BufferedReader br = new BufferedReader(isr);  
    StringBuilder sb = new StringBuilder();  
    String line;  
    while ((line = br.readLine()) != null) {  
        sb.append(line);  
    }    
} catch (IOException e) {  
    e.printStackTrace();  
}

1.2使用场景

存储应用的私有数据,如用户配置、缓存文件等。

存储敏感数据或不希望被其他应用访问的文件

2.外部存储

外部存储是指设备外部的、可插拔的存储空间,如SD卡、USB存储设备等。在Android系统中,外部存储通常被划分为公有空间和私有空间。

2.1公有空间

公有空间是外部存储中可以被其他应用程序和用户访问的目录。这些目录通常用于存储共享数据,如图片、音乐、视频等多媒体文件。

公共空间操作→Environment

getExternalStorageState():此方法返回 SD 卡(或主外部存储)的当前状态。返回的状态常量包括:

状态

描述

MEDIA_BAD_REMOVAL

SD 卡在卸载前已被移除。

MEDIA_CHECKING

正在检查存储媒体。

MEDIA_MOUNTED

SD 卡存在且具有读/写权限。

MEDIA_MOUNTED_READ_ONLY

SD 卡权限为只读。

MEDIA_NOFS

不支持的文件系统(SD 卡格式不正确)。

MEDIA_REMOVED

SD 卡已移除。

MEDIA_SHARED

当前 SD 卡未安装,但可通过 USB 存储共享。

MEDIA_UNMOUNTABLE

SD 卡无法安装。

MEDIA_UNMOUNTED

SD 卡已卸掉(假设 SD 卡存在但未被安装)。

getExternalStoragePublicDirectory(type):返回指定类型的公共目录路径。

type 参数可以是 Environment 类中定义的目录类型常量:

目录常量

描述

DIRECTORY_DCIM

相机拍摄的图片和视频存放目录。

DIRECTORY_DOCUMENTS

文档存放目录,通常用于存储用户的文档文件。

DIRECTORY_DOWNLOADS

下载文件存放目录,用于存储用户下载的文件。

DIRECTORY_MOVIES

视频存放目录,用于存储用户保存的视频文件。

DIRECTORY_MUSIC

音乐存放目录,用于存储用户保存的音乐文件。

DIRECTORY_PICTURES

图片存放目录(与DIRECTORY_DCIM不同,可能用于存储用户从其他来源获取的图片,如网络下载或截图)。

getExternalStorageDirectory():返回外部存储(SD 卡或模拟 SD 卡)的根路径。

getStorageState(path):返回指定路径的存储状态。不过,对于大多数应用来说,更常用的是 getExternalStorageState() 来检查整个外部存储的状态。

具体步骤:

1. 获取外部存储的根目录

使用getExternalStorageDirectory()方法可以获取外部存储(如SD卡)的根目录。这个方法返回一个File对象,你可以通过调用getAbsolutePath()方法获取其路径。

File externalStorageDir = Environment.getExternalStorageDirectory();  
String externalStoragePath = externalStorageDir.getAbsolutePath();

2. 访问外部存储的公共目录

外部存储包含一些预定义的公共目录,如图片、音乐、下载等。你可以使用getExternalStoragePublicDirectory(String type)方法来访问这些目录。

// 获取图片目录  
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);  
  
// 获取下载目录  
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

3. 检查外部存储状态

在访问外部存储之前,你应该检查其状态以确保它是可用的。使用getExternalStorageState()方法可以获取外部存储的状态。

String state = Environment.getExternalStorageState();  
if (Environment.MEDIA_MOUNTED.equals(state)) {  
    // 外部存储已挂载且可读写  
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {  
    // 外部存储已挂载但只读  
} else {  
    // 外部存储不可用  
}

4. 请求存储权限(针对Android 6.0及以上版本)

如果你的应用需要写入外部存储,你需要在运行时请求WRITE_EXTERNAL_STORAGE权限(对于读取,你需要READ_EXTERNAL_STORAGE权限,但在Android 11及以上版本中,对于公共目录的读取通常不需要额外权限,除非你在访问特定类型的文件时需要额外的存储访问框架(SAF)权限)。

if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)  
        != PackageManager.PERMISSION_GRANTED) {  
    ActivityCompat.requestPermissions(this,  
            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},  
            MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);  
}

2.1私有空间

私有空间是外部存储中仅能被创建它的应用程序访问的目录。这些目录用于存储应用程序的私有数据,确保数据的安全性和隔离性。

私有空间(目录)操作→Context

getExternalFilesDir(String type)用于获取应用外部存储私有空间中的特定类型目录的File对象。如果不需要特定类型的子目录,可以传递null作为参数。

getExternalCacheDir()用于获取应用外部存储私有空间中的缓存目录。这个目录通常用于存储临时文件,系统在设备存储空间不足时可能会自动清除这些文件。

四、应用Application

ApplicationAndroid的一大组件类,在App运行过程中有且仅有一个Application对象贯穿应用的整个生命周期。Application 类提供了一个全局的上下文,可以用于在应用的整个生命周期内存储和访问数据。虽然它并不直接涉及文件存储或数据库,但可以用于存储全局共享的内存数据。

1.Application的生命周期

onCreate:App启动时调用

onTerminate:App退出时调用该方法是供模拟环境调用,在真机上不会被调用

onLowMemory:低内存时调用

onConfigurationChanged:配置改变时调用,例如从竖屏变为横屏

2.利用Application操作全局变量

2.1基本用法

1.创建自定义Application类:

public class MyApplication extends Application {  
    private String username;  
    private Bitmap userAvatar;  
    private Handler handler;  
  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        // 初始化全局变量  
        handler = new Handler(Looper.getMainLooper());  
    }  
  
    public String getUsername() {  
        return username;  
    }  
  
    public void setUsername(String username) {  
        this.username = username;  
    }  
  
    public Bitmap getUserAvatar() {  
        return userAvatar;  
    }  
  
    public void setUserAvatar(Bitmap userAvatar) {  
        this.userAvatar = userAvatar;  
    }  
  
    public Handler getHandler() {  
        return handler;  
    }  
}

2.在AndroidManifest.xml中声明自定义Application类:

<application  
    android:name=".MyApplication"  
    ... >  
    ...  
</application>

3.在应用程序中访问全局变量:

// 获取Application实例  
MyApplication app = (MyApplication) getApplication();  
  
// 读取全局变量  
String username = app.getUsername();  
Bitmap userAvatar = app.getUserAvatar();  
  
// 设置全局变量  
app.setUsername("newUsername");  
app.setUserAvatar(someBitmap);

2.2适用场景

1.会频繁读取的信息,例如用户名、手机号码等。

2.不方便由意图传递的数据,例如位图对象、非字符串类型的集合对象等。

3.容易因频繁分配内存而导致内存泄漏的对象,例如Handler处理器实例等。

五、内容提供与处理

完整的内容组件由内容提供器 ContentProvider、内容解析器 ContentResolver、内容观察器 ContentObserver 这三部分组成。

 1.内容提供器 ContentProvider

ContentProvider是Android的四大组件之一,它提供了一种在不同应用之间共享数据的方式。它允许一个应用通过标准化的接口向其他应用提供数据,并支持权限控制。

在实际编码中,ContentProvider只是服务端App存取数据的抽象类,开发者需要在其基础上实现一个完整的内容提供器,并重写其数据库管理方法。

主要方法

onCreate:创建数据库并获得数据库连接

query:查询数据

insert:插入数据

update:更新数据

delete:删除数据

getType:获取数据类型

示例代码

public class MyContentProvider extends ContentProvider {  
    // 数据库相关操作  
    private SQLiteDatabase db;  
    private MyDatabaseHelper dbHelper;  
    private static final String AUTHORITY = "com.example.contentprovider";  
    private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/mydata");  
    private static final int MY_DATA = 1;  
    private UriMatcher uriMatcher;  

    @Override  
    public boolean onCreate() {  
        dbHelper = new MyDatabaseHelper(getContext());  
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);  
        uriMatcher.addURI(AUTHORITY, "mydata", MY_DATA);  
        return true;  
    }  

    @Override  
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {  
        // 根据URI匹配到的路径执行相应的查询操作  
        db = dbHelper.getReadableDatabase();  
        Cursor cursor = null;  
        switch (uriMatcher.match(uri)) {  
            case MY_DATA:  
                cursor = db.query("my_table", projection, selection, selectionArgs, null, null, sortOrder);  
                break;  
            default:  
                throw new IllegalArgumentException("Unknown URI: " + uri);  
        }  
        // 设置MIME类型  
        cursor.setNotificationUri(getContext().getContentResolver(), uri);  
        return cursor;  
    }  

    // 其他重写的方法(insert、update、delete、getType)  
}

AndroidManifest.xml中配置内容提供器:

<provider  
    android:name=".MyContentProvider"  
    android:authorities="com.example.contentprovider"  
    android:exported="true" />

2.内容解析器 ContentResolver

ContentResolver 是 Android 框架中用于访问 ContentProvider 数据的工具类。它提供了一种统一的方式允许应用程序访问和修改内容提供器中存储的数据。ContentResolver 通常与ContentProvider 一起使用,以实现跨程序的数据共享和访问。

主要方法

query():从内容提供器中检索数据。

insert():向内容提供器中插入新的数据行。

update():修改内容提供器中的现有数据。

delete():从内容提供器中删除数据。

示例代码

ContentResolver contentResolver = getContentResolver();  
Uri uri = Uri.parse("content://com.example.contentprovider/mydata");  

// 插入数据  
ContentValues values = new ContentValues();  
values.put("column1", "value1");  
values.put("column2", "value2");  
contentResolver.insert(uri, values);  

// 查询数据  
Cursor cursor = contentResolver.query(uri, null, null, null, null);  
while (cursor.moveToNext()) {  
    String column1 = cursor.getString(cursor.getColumnIndex("column1"));  
    String column2 = cursor.getString(cursor.getColumnIndex("column2"));  
    // 处理查询结果  
}  
cursor.close();  

// 更新数据  
ContentValues updateValues = new ContentValues();  
updateValues.put("column1", "new_value1");  
String selection = "column2=?";  
String[] selectionArgs = new String[]{"value2"};  
contentResolver.update(uri, updateValues, selection, selectionArgs);  

// 删除数据  
String deleteSelection = "column1=?";  
String[] deleteSelectionArgs = new String[]{"value1"};  
contentResolver.delete(uri, deleteSelection, deleteSelectionArgs);

3.内容观察器 ContentObserver

ContentObserver 是一个用于监听 ContentProvider 中数据变化的类。它允许应用程序注册一个观察者来监听特定 URI 下的数据变化。当 ContentProvider 中的数据发生变化时(如插入、更新或删除数据),ContentObserver 将收到通知,并可以执行相应的操作。

主要方法

registerContentObserver:注册内容观察器

unregisterContentObserver:注销内容观察器。

notifyChange:通知内容观察器发生了数据变化。

具体用法

1.创建内容观察器

public class MyContentObserver extends ContentObserver {  
    public MyContentObserver(Handler handler) {  
        super(handler);  
    }  

    @Override  
    public void onChange(boolean selfChange, Uri uri) {  
        super.onChange(selfChange, uri);  
        // 处理数据变化逻辑  
        if (uri.toString().equals("content://com.example.contentprovider/mydata")) {  
            // 数据已变化,执行相应操作  
        }  
    }  
}

2.注册内容观察器在需要监听数据变化的组件(如Activity或Service)中注册内容观察器:

MyContentObserver contentObserver = new MyContentObserver(new Handler());  
getContentResolver().registerContentObserver(Uri.parse("content://com.example.contentprovider/mydata"), true, contentObserver);

在不再需要监听时注销内容观察器:

getContentResolver().unregisterContentObserver(contentObserver);