Android 存储管理

article/2025/8/20 15:38:49

文章目录

  • 保存数据的方式
    • 应用专属文件
      • 访问内部存储的专属空间
        • 持久化数据目录操作
        • 缓存数据目录操作
      • 访问外部存储的专属空间
        • 验证存储空间的可用性
        • 选择物理存储位置
        • 访问和存储持久性文件
        • 操作缓存文件
        • 应用专属媒体内容
    • 共享存储空间
      • 媒体内容
        • 请求必要权限
        • 检查媒体文件的更新
        • 打开媒体文件
        • 使用实例(以Image的访问为例)
      • 文档和其他文件
        • 创建新文件
        • 打开文件
        • 授予对目录的访问权限
        • 在所选位置进行操作
          • 确定该系统(内容提供者)支持的操作
          • 检查文档的元数据
          • 打开文档
          • 修改文档
          • 删除文档
      • 数据集
      • 管理存储设备上的所有文件
    • 键值对文件(用户偏好设置)
    • 将数据保存到本地数据库
      • 使用SQLiteAPI进行数据库操作
      • 使用Room为SQLiteAPI提供便利封装(建议使用)
        • 添加依赖
        • 主要组件
        • 实现实例
          • 数据实体
          • 数据访问对象(DAO)
          • 数据库
          • 具体用法
          • 其它详细用法参见官方文档


保存数据的方式

android为我们提供了应用专属空间、共享媒体库(图片、视频、音频等)、偏好设置和数据库来进行应用数据保存。

应用专属文件

仅供本应用使用,在内部存储和外部存储都有应用的独有专属空间。如果用户卸载应用,系统会移除保存在应用专属存储空间中的文件。如果,如果应用允许用户拍摄照片,用户会希望即使卸载应用后仍可访问这些照片,那么应改为使用共享存储空间将此类文件保存到适当的媒体集合中,而不是保存在专属空间中。如需进一步保护应用专属文件,请使用 Android Jetpack 中包含的 Security 库对这些静态文件进行加密。加密密钥专属于您的应用。

访问内部存储的专属空间

应用的内部专属空间分为存储持久化数据的目录和存储缓存数据的目录,后者较之前者可以被更早的移除(调用相应的API即可),而前者只有当应用被卸载时才会自动删除,无法手动移除。另外,我们不需要任何权限就可以对这些目录进行操作。但是,请注意,这些目录的空间通常比较小。在将应用专属文件写入内部存储空间之前,应用应查询设备上的可用空间。

持久化数据目录操作

上下文对象提供的File相关API或者信息流(输入、输出流)都可以进行该墓的数据访问和存储操作,而对于前者而言,更适合于进行目录的创建等包含目录操作的情况下使用,而对于一般的创建文件和进行文件读写来讲还是使用信息流更为快捷方便。如果允许其他应用来访问此目录中的文件,请使用具有 FLAG_GRANT_READ_URI_PERMISSION 属性的 FileProvider。

  • File API访问和存储
File file = new File(context.getFilesDir(), filename);
//然后可以创建输入流输出流进行文件读写或者创建目录都可以
  • 信息流访问和存储
//数据存储
String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {fos.write(fileContents.toByteArray());
}//数据访问
FileInputStream fis = context.openFileInput(filename);
InputStreamReader inputStreamReader =new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {String line = reader.readLine();while (line != null) {stringBuilder.append(line).append('\n');line = reader.readLine();}
} catch (IOException e) {// Error occurred when opening raw file for reading.
} finally {String contents = stringBuilder.toString();
}
  • 其它操作-查看文件列表

通过fileList()可以获取此目录下所有文件名称的数组

Array<String> files = context.fileList();
  • 其它操作-创建嵌套目录

通过以下方式创建嵌套目录或打开内部目录,将根目录和新目录名称传递到 File 构造函数

File directory = context.getFilesDir();
//这里通过以父路径为根路径,filename为子路径,获取对应实例
//可以创建不存在的目录和文件
File file = new File(directory, filename);

缓存数据目录操作

如果您只需要暂时存储敏感数据,应使用应用在内部存储空间中的指定缓存目录保存数据。与所有应用专属存储空间一样,当用户卸载应用后,系统会移除存储在此目录中的文件,但也可以更早地移除此目录中的文件。对于缓存目录来讲,只可以通过File相关API进行数据存储和访问。

  • 创建缓存文件
File file = File.createTempFile(filename, null, context.getCacheDir());
  • 存储和访问缓存文件
File cacheFile = new File(context.getCacheDir(), filename);
  • 移除缓存文件
//方法一
cacheFile.delete();
//方法二
context.deleteFile(cacheFileName);

访问外部存储的专属空间

如果内部存储空间不足以存储应用专属文件,请考虑改为使用外部存储空间。系统会在外部存储空间中提供目录,应用可以在该存储空间中整理仅在应用内对用户有价值的文件。一个目录专为应用的持久性文件而设计,而另一个目录包含应用的缓存文件。
在 Android 4.4(API 级别 19)或更高版本中,应用无需请求任何与存储空间相关的权限即可访问外部存储空间中的应用专属目录。卸载应用后,系统会移除这些目录中存储的文件。

验证存储空间的可用性

由于外部存储空间位于用户可能能够移除的物理卷上,因此在尝试从外部存储空间读取应用专属数据或将应用专属数据写入外部存储空间之前,请验证该卷是否可访问。

// 通过调用 Environment.getExternalStorageState() 查询该卷的状态。
//如果返回的状态为 MEDIA_MOUNTED,那么您就可以在外部存储空间中读取和写入应用专属文件。
//如果返回的状态为 MEDIA_MOUNTED_READ_ONLY,您只能读取这些文件。
private boolean isExternalStorageWritable() {return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}private boolean isExternalStorageReadable() {return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}

选择物理存储位置

有时,分配一部分内部存储空间作为外部存储空间的设备也会提供SD卡插槽,所以我们还需要选择一个外部存储设备。
如需访问其他位置,请调用 ContextCompat.getExternalFilesDirs()。如代码段中所示,返回数组中的第一个元素被视为主外部存储卷。除非该卷已满或不可用,否则请使用该卷。

File[] externalStorageVolumes =ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];

访问和存储持久性文件

//只能通过File的API访问,后续可以创建输入输出流来简化操作
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);

操作缓存文件

//如需将应用专属文件添加到外部存储空间中的缓存,请获取对 externalCacheDir 的引用:
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
//如需从外部缓存目录中移除文件,请对代表该文件的 File 对象使用 delete() 方法:
externalCacheFile.delete();

应用专属媒体内容

如果应用中的媒体文件仅对本应用有价值,那么就将它保存在外部空间的应用专属目录中。

//请务必使用 DIRECTORY_PICTURES 等 API 常量提供的目录名称。
//这些目录名称可确保系统正确处理文件。
//如果没有适合您文件的预定义子目录名称,您可以改为将 null 传递到 getExternalFilesDir()。
//这将返回外部存储空间中的应用专属根目录。
@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {// Get the pictures directory that's inside the app-specific directory on// external storage.File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), albumName);if (file == null || !file.mkdirs()) {Log.e(LOG_TAG, "Directory not created");}return file;
}

共享存储空间

如果在本应用内保存的媒体、文档或其他文件和数据集文件(例如用户拍照、下载等媒体文件)可供其它应用使用或者不应该随着应用的删除而被清楚,则应该将这些数据保存在共享空间下。系统为这些文件提供了分好类的目录区别管理。

媒体内容

系统提供标准的公共目录来存储这些类型的文件,这样用户就可以将所有照片保存在一个公共位置,将所有音乐和音频文件保存在另一个公共位置,依此类推。您的应用可以使用此平台的 MediaStore API 访问此内容。
为了提供更丰富的用户体验,许多应用允许用户提供和访问位于外部存储卷上的媒体。该框架提供经过优化的媒体集合索引,称为媒体库,使您可以更轻松地检索和更新这些媒体文件。即使您的应用已卸载,这些文件仍会保留在用户的设备上。
使用上下文检索得到ContentResolver对象来与共享区域的媒体库互动:

//系统会扫描共享目录中的文件,并且分类存储在各个表中,可以通过ULI链接进行访问
String[] projection = new String[] {media-database-columns-to-retrieve
};
String selection = sql-where-clause-with-placeholder-variables;
String[] selectionArgs = new String[] {values-of-placeholder-variables
};
String sortOrder = sql-order-by-clause;Cursor cursor = getApplicationContext().getContentResolver().query(MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,projection,selection,selectionArgs,sortOrder
);while (cursor.moveToNext()) {// Use an ID column from the projection to get// a URI representing the media item itself.
}

MedicaStore简单介绍:
Android官方文档

请求必要权限

  • 存储权限
    对于android10及以上的系统,不需要申请存储权限即可访问共享存储空间。如果要针对更低版本的android做权限兼容,那么在manifest文件中加入下列条件(声明兼容的最高版本,比它低的版本都将申请此权限)
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="28" />//Android 9(API 级别 28)

对于所有版本的android系统而言,如需访问由其他应用创建的文件,必须满足以下所有条件:
您的应用已获得 READ_EXTERNAL_STORAGE 权限。
这些文件位于以下任一明确定义的媒体集合中:
MediaStore.Images
MediaStore.Video
MediaStore.Audio

检查媒体文件的更新

使用getVersion()可以返回版本的唯一字符串,存储此这个字符串,在下次应用启动时,检查是否相同。请在应用进程启动时完成此项检查。您无需在每次查询媒体库时都检查版本。

打开媒体文件

用于打开媒体文件的具体逻辑取决于媒体内容的最佳表示形式是文件描述符、文件流还是直接文件路径:

  • 文件描述符
// Open a specific media item using ParcelFileDescriptor.
ContentResolver resolver = getApplicationContext().getContentResolver();// "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents.
String readOnlyMode = "r";
try (ParcelFileDescriptor pfd =resolver.openFileDescriptor(content-uri, readOnlyMode)) {// Perform operations on "pfd".
} catch (IOException e) {e.printStackTrace();
}
  • 文件流
// Open a specific media item using InputStream.
ContentResolver resolver = getApplicationContext().getContentResolver();
try (InputStream stream = resolver.openInputStream(content-uri)) {// Perform operations on "stream".
}
  • 直接文件路径(File API)
    如果您的应用尝试使用 File API 访问文件但没有必要的权限,就会发生 FileNotFoundException。

使用实例(以Image的访问为例)

//在manifest文件中声明对应权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
//在Activity中申请对应权限
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1001);
//使用contentResolver访问URI
ContentResolver contentResolver = getContentResolver();
Cursor query = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,null,null,null,null);
while (query.moveToNext()){//获取文件路径int columnIndexOrThrow = query.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA);String string = query.getString(columnIndexOrThrow);//获取文件URI的拼接idint idIdx = query.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID);int id = query.getInt(idIdx);Log.d(TAG, "onCreate: data string is " + string);//拼接具体文件URIUri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);Log.d(TAG, "onCreate: data uri is " + uri.toString());//方式一:通过文件描述符访问try {ParcelFileDescriptor pfd = contentResolver.openFileDescriptor(uri, "r");Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());} catch (FileNotFoundException e) {e.printStackTrace();}//方式二:通过输入流访问InputStream inputStream = contentResolver.openInputStream(uri);Bitmap bitmap = BitmapFactory.decodeStream(inputStream);//方式三:通过文件路径直接访问 Bitmap bitmap = BitmapFactory.decodeFile(string);imageView.setImageBitmap(bitmap);
}

文档和其他文件

系统有一个特殊目录,用于包含其他文件类型,例如 PDF 文档和采用 EPUB 格式的图书。您的应用可以使用此平台的存储访问框架访问这些文件。
这些文件存储在应用专属目录和媒体库之外,在应用卸载后仍会保留在设备上。简单来说,此平台的存储访问框架就是一个文件选择器。
使用存储访问框架涉及以下步骤:

  1. 应用调用包含存储相关操作的 intent。此操作对应于框架支持的特定用例。
  2. 用户看到一个系统选择器,供其浏览文档提供器并选择将执行存储相关操作的位置或文档。
  3. 应用获得对代表用户所选位置或文档的 URI 的读写访问权限。
  4. 利用该 URI,应用可以在选择的位置执行操作。

创建新文件

使用 ACTION_CREATE_DOCUMENT intent 操作加载系统文件选择器,支持用户选择要写入文件内容的位置。此流程类似于其他操作系统使用的“另存为”对话框中使用的流程。
在配置 intent 时,应指定文件的名称和 MIME 类型,并且还可以根据需要使用 EXTRA_INITIAL_URI intent extra 指定文件选择器在首次加载时应显示的文件或目录的 URI。

// Request code for creating a PDF document.
private static final int CREATE_FILE = 1;private void createFile(Uri pickerInitialUri) {Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("application/pdf");intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf");// Optionally, specify a URI for the directory that should be opened in// the system file picker when your app creates the document.intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);startActivityForResult(intent, CREATE_FILE);
}

打开文件

您的应用可以使用文档作为存储单元,供用户在其中输入可能要与同伴分享或要导入到其他文档的数据。例如,用户打开办公文档或打开另存为 EPUB 文件的图书。

在此类情况下,请通过调用 ACTION_OPEN_DOCUMENT intent 来支持用户选择要打开的文件,此 intent 会打开系统的文件选择器应用。若要仅显示应用支持的文件类型,请指定 MIME 类型。此外,您还可以根据需要使用 EXTRA_INITIAL_URI intent extra 指定文件选择器在首次加载时应显示的文件的 URI。

// Request code for selecting a PDF document.
private static final int PICK_PDF_FILE = 2;private void openFile(Uri pickerInitialUri) {Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("application/pdf");// Optionally, specify a URI for the file that should appear in the// system file picker when it loads.intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);startActivityForResult(intent, PICK_PDF_FILE);
}

授予对目录的访问权限

文件管理和媒体创建应用通常在目录层次结构中管理文件组。如需在您的应用中提供此功能,请使用 ACTION_OPEN_DOCUMENT_TREE intent 操作,它支持用户授予应用对整个目录树的访问权限,但在 Android 11(API 级别 30)及以上版本中会有一些例外情况。然后,您的应用便可以访问所选目录及其任何子目录中的任何文件。

使用 ACTION_OPEN_DOCUMENT_TREE 时,您的应用只能访问用户所选目录中的文件。您无权访问位于用户所选目录之外的其他应用的文件。借助这种由用户控制的访问权限,用户可以确切选择自己想要与您的应用共享的具体内容。

您可以根据需要使用 EXTRA_INITIAL_URI intent extra 指定文件选择器在首次加载时应显示的目录的 URI。

public void openDirectory(Uri uriToLoad) {// Choose a directory using the system's file picker.Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);// Optionally, specify a URI for the directory that should be opened in// the system file picker when it loads.intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);startActivityForResult(intent, your-request-code);
}

在所选位置进行操作

在用户使用系统的文件选择器选择文件或目录后,您可以在 onActivityResult() 中使用以下代码检索所选项目的 URI:

@Override
public void onActivityResult(int requestCode, int resultCode,Intent resultData) {if (requestCode == your-request-code&& resultCode == Activity.RESULT_OK) {// The result data contains a URI for the document or directory that// the user selected.Uri uri = null;if (resultData != null) {uri = resultData.getData();// Perform operations on the document using its URI.}}
}

获取对所选项目 URI 的引用后,您的应用可以对该项目执行多项操作。例如,您可以访问该项目的元数据,在原位置修改该项目,以及删除该项目。
下面将介绍如何对用户选择的文件进行各种操作。

确定该系统(内容提供者)支持的操作

不同的内容提供器支持对文档执行不同的操作,例如复制文档或查看文档的缩略图。如需确定指定提供器支持哪些操作,请查看 Document.COLUMN_FLAGS 的值。应用的界面只会显示提供器支持的选项。

检查文档的元数据
public void dumpImageMetaData(Uri uri) {// The query, because it only applies to a single document, returns only// one row. There's no need to filter, sort, or select fields,// because we want all fields for one document.Cursor cursor = getActivity().getContentResolver().query(uri, null, null, null, null, null);try {// moveToFirst() returns false if the cursor has 0 rows. Very handy for// "if there's anything to look at, look at it" conditionals.if (cursor != null && cursor.moveToFirst()) {// Note it's called "Display Name". This is// provider-specific, and might not necessarily be the file name.String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));Log.i(TAG, "Display Name: " + displayName);int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);// If the size is unknown, the value stored is null. But because an// int can't be null, the behavior is implementation-specific,// and unpredictable. So as// a rule, check if it's null before assigning to an int. This will// happen often: The storage API allows for remote files, whose// size might not be locally known.String size = null;if (!cursor.isNull(sizeIndex)) {// Technically the column stores an int, but cursor.getString()// will do the conversion automatically.size = cursor.getString(sizeIndex);} else {size = "Unknown";}Log.i(TAG, "Size: " + size);}} finally {cursor.close();}
}
打开文档
  • 位图:以下代码段显示了如何在已获得 Bitmap 文件的 URI 的情况下打开该文件
private Bitmap getBitmapFromUri(Uri uri) throws IOException {ParcelFileDescriptor parcelFileDescriptor =getContentResolver().openFileDescriptor(uri, "r");FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);parcelFileDescriptor.close();return image;
}
  • 输入流:以下代码段显示了如何在已获得 InputStream 对象的 URI 的情况下打开该对象。在此代码段中,系统会将文件行读取到字符串中:
private String readTextFromUri(Uri uri) throws IOException {StringBuilder stringBuilder = new StringBuilder();try (InputStream inputStream =getContentResolver().openInputStream(uri);BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(inputStream)))) {String line;while ((line = reader.readLine()) != null) {stringBuilder.append(line);}}return stringBuilder.toString();
}
修改文档
private void alterDocument(Uri uri) {try {ParcelFileDescriptor pfd = getActivity().getContentResolver().openFileDescriptor(uri, "w");FileOutputStream fileOutputStream =new FileOutputStream(pfd.getFileDescriptor());fileOutputStream.write(("Overwritten at " + System.currentTimeMillis() +"\n").getBytes());// Let the document provider know you're done by closing the stream.fileOutputStream.close();pfd.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}
}
删除文档
DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

数据集

在 Android 11(API 级别 30)及更高版本中,系统会缓存多个应用可能使用的大型数据集。这些数据集可为机器学习和媒体播放等用例提供支持。应用可以使用 BlobStoreManager API 访问这些共享数据集。

管理存储设备上的所有文件

绝大多数需要共享存储空间访问权限的应用都可以遵循共享媒体文件和共享非媒体文件方面的最佳做法。但是,某些应用的核心用例需要广泛访问设备上的文件,但无法采用注重隐私保护的存储最佳做法高效地完成这些操作。对于这些情况,Android 提供了一种名为“所有文件访问权限”的特殊应用访问权限。

例如,防病毒应用的主要用例可能需要定期扫描不同目录中的许多文件。如果此扫描需要反复的用户交互,让其使用系统文件选择器选择目录,可能就会带来糟糕的用户体验。其他用例(如文件管理器应用、备份和恢复应用以及文档管理应用)可能也需要考虑类似情况。
官方文档

键值对文件(用户偏好设置)

//保存
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score_key), newHighScore);
editor.apply();
//读取
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getInteger(R.integer.saved_high_score_default_key);
int highScore = sharedPref.getInt(getString(R.string.saved_high_score_key), defaultValue);

将数据保存到本地数据库

使用SQLiteAPI进行数据库操作

  • 创建数据库的helper类
    mt
  • 创建该helper对象获取SQLiteDatabase对象执行增删改查
//doc为数据库名,1为版本号
DataBaseHelper m_dataBaseHelper = new DataBaseHelper(StorageActivity.this,"doc",null,1);
final SQLiteDatabase db_w = m_dataBaseHelper.getWritableDatabase();
final SQLiteDatabase db_r = m_dataBaseHelper.getReadableDatabase();//插入记录
db_w.execSQL("insert into account(name) values ("+ met_1.getText().toString() +")");
ContentValues contentValues = new ContentValues();
contentValues.put("name",met_1.getText().toString());
db_w.insert("account","empty",contentValues);//修改记录
db_w.execSQL("update account set name = change where name = " + met_1.getText().toString() + "_change;");
ContentValues contentValues1 = new ContentValues();
contentValues1.put("name",met_1.getText().toString()+"_change");
String[] strList = new String[1];
strList[0] = met_1.getText().toString();
db_w.update("account",contentValues1,"name = ?",strList);//查询记录
strList[0] = met_1.getText().toString() + "_change";
Cursor query = db_r.query("account", new String[]{"id", "name"}, "name = ?",strList,null, null, "id");
query.moveToFirst();
while(query.moveToNext()){Log.d("id",String.valueOf(query.getInt(0)));mtv_1.setText(query.getString(1));query.moveToNext();
}
query.close();//删除记录
db_w.execSQL("delete from account where name = " + mtv_1.getText().toString());
strList[0] = mtv_1.getText().toString() + "_change";
int rows = db_w.delete("account","name = ?",strList);
Log.d("受影响的行数",String.valueOf(rows));

使用Room为SQLiteAPI提供便利封装(建议使用)

Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
针对 SQL 查询的编译时验证。
可最大限度减少重复和容易出错的样板代码的方便注解。
简化了数据库迁移路径。
出于这些方面的考虑,我们强烈建议您使用 Room,而不是直接使用 SQLite API。

添加依赖

dependencies {def room_version = "2.4.3"implementation "androidx.room:room-runtime:$room_version"annotationProcessor "androidx.room:room-compiler:$room_version"// To use Kotlin annotation processing tool (kapt)kapt "androidx.room:room-compiler:$room_version"// To use Kotlin Symbol Processing (KSP)ksp "androidx.room:room-compiler:$room_version"// optional - RxJava2 support for Roomimplementation "androidx.room:room-rxjava2:$room_version"// optional - RxJava3 support for Roomimplementation "androidx.room:room-rxjava3:$room_version"// optional - Guava support for Room, including Optional and ListenableFutureimplementation "androidx.room:room-guava:$room_version"// optional - Test helperstestImplementation "androidx.room:room-testing:$room_version"// optional - Paging 3 Integrationimplementation "androidx.room:room-paging:2.5.0-alpha02"
}

主要组件

官方文档

实现实例

数据实体
@Entity
public class User {@PrimaryKeypublic int uid;@ColumnInfo(name = "first_name")public String firstName;@ColumnInfo(name = "last_name")public String lastName;
}

官方文档
在这里插入图片描述

数据访问对象(DAO)
@Dao
public interface UserDao {@Query("SELECT * FROM user")List<User> getAll();@Query("SELECT * FROM user WHERE uid IN (:userIds)")List<User> loadAllByIds(int[] userIds);@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +"last_name LIKE :last LIMIT 1")User findByName(String first, String last);@Insertvoid insertAll(User... users);@Deletevoid delete(User user);
}
数据库

以下代码定义了用于保存数据库的 AppDatabase 类。 AppDatabase 定义数据库配置,并作为应用对持久性数据的主要访问点。数据库类必须满足以下条件:

该类必须带有 @Database 注解,该注解包含列出所有与数据库关联的数据实体的 entities 数组。
该类必须是一个抽象类,用于扩展 RoomDatabase。
对于与数据库关联的每个 DAO 类,数据库类必须定义一个具有零参数的抽象方法,并返回 DAO 类的实例。

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {public abstract UserDao userDao();
}
具体用法
//创建数据库实例
AppDatabase db = Room.databaseBuilder(getApplicationContext(),AppDatabase.class, "database-name").build();
//获取Dao实例
UserDao userdao = db.userDao();
List<User> users = userDao.getAll();
其它详细用法参见官方文档

http://chatgpt.dhexx.cn/article/qQBqxfOg.shtml

相关文章

存储器管理之分区存储管理

分区式管理是满足多道程序的最简单的存储管理方案。它的基本思想是将内存划分成若干个连续区域&#xff0c;称为分区。每个分区只能存储一个程序&#xff0c;且程序也只能在它所驻留的分区中运行。 ⑴固定分区 操作系统预先把可分配的主存空间分割成若干个连续区域&#xff0…

存储管理的功能

我是一个有强迫症的人&#xff0c;什么文件都要归类&#xff0c;电脑桌面干干净净的放着几个必要的文件夹&#xff0c;所有的文件对应有不同的文件夹存放&#xff0c;如果看到某个文件&#xff08;只要不是临时存放的&#xff09;出现在桌面上&#xff0c;就感觉非常刺眼。 我…

操作系统---存储管理

存储管理 操作系统将外存的文件调入到内存中&#xff0c;以便CPU调用&#xff0c;如果调用的内容不在内存中&#xff0c;则会产生缺页中断&#xff1b;产生缺页中断后&#xff0c;这事需要从外存调数据到内存中&#xff0c;然后CPU接着从断点继续调用内存中的数据&#xff1b;在…

操作系统的存储管理

写在前面&#xff1a;我们都希望计算机拥有一个私有的&#xff0c;无限大的&#xff0c;速度无限快并且是永久性的存储器&#xff0c;但是这样额要求必定会价格昂贵&#xff0c;经过多年的探索&#xff0c;人们提出了“分层存储管理体系”&#xff0c;在这个体系中有&#xff1…

页式存储管理

页式存储管理为操作系统中的内容&#xff0c;但是在计算机组成原理中的虚拟存储器部分也用到了这一方式。 分区式存储管理最大的缺点是碎片问题严重&#xff0c;内存利用率低。究其原因&#xff0c;主要在于连续分配的限制&#xff0c;即它要求每个作用在内存中必须占一个连续…

计算机操作系统-3-存储管理

Lecture3-存储管理 存储管理是操作系统的重要组成部分&#xff0c;负责管理计算机系统的重要资源——内存储器。内存空间一般分为两部分 系统区&#xff1a;存放操作系统内核程序和数据结构等。用户区&#xff1a;存放应用程序和数据。 存储管理包括以下功能&#xff1a; 存储…

(存储管理)存储管理的四大基本功能

存储管理的四大基本功能 1、内存分配与回收 当有作业进入系统时&#xff0c;存储管理模块就会根据当前内存情况来分配内存给它&#xff1b;当作业完成后&#xff0c;就会回收作业占用的内存&#xff0c;将这部分内存设置为可分配状态。 分配方式主要有两种&#xff1a; 静态…

实验三、存储管理

目录 实验三、存储管理实验目的实验内容实验步骤1、虚拟内存信息检测2、分配虚拟内存 实验三、存储管理 实验目的 &#xff08;1&#xff09;通过实验了解windows内存的使用&#xff0c;学习如何在应用程序中管理内存、体会Windows应用程序内存的简单性和自我防护能力&#x…

操作系统存储管理

目录 - 3.1 内存的基础知识 - 3.1.1 什么是内存&#xff0c;有何作用 - 3.1.2 进程运行的基本原理 - 3.2 内存管理的概念 - 3.3 覆盖与交换 - 3.4 连续分配管理方式 - 3.5 动态分区分配算法 - 3.6 基本分页存储管理的基本概念 - 3.7 基本地址变换机构 - 3.8 具有快表的…

计算机操作系统--存储管理

基本概念 1. 存储器的结构 存储器顾名思义&#xff0c;就是用来保存数据的东西。随着科技的进步&#xff0c;存储器正朝着高速度、大容量、小体积方向发展。一般情况下&#xff0c;存储器的结构有如下两类&#xff1a; 寄存器-主存-外存寄存器-缓存-主存-外存 对于存储器有…

操作系统——存储管理

文章目录 1.存储管理概述1.1存储层次结构1.2存储器管理的功能1.2.1内存分配1.2.2地址映射1.2.3存储保护1.2.4内存扩充 1.3地址重定位1.3.1名字空间、地址空间和存储空间1.3.2地址重定位1.3.2.1静态重定位1.3.2.2动态重定位 2.存储器连续分配2.1单一连续分配管理方式2.2分区存储…

拉格朗日乘数法基础

背景 线性可分 SVM 的目标函数最终转换为一个带约束条件的求极值问题&#xff0c;而拉格朗日乘子法&#xff0c;恰恰是一种多元函数在变量受到条件约束时&#xff0c;求极值的方法。正好可以用来解决 SVM 的目标函数最优化。 那么拉格朗日乘数法的理论过程如何呢&#xff1f;…

拉格朗日乘子法和KKT条件

拉格朗日乘子法(Lagrange Multiplier)和KKT(Karush-Kuhn-Tucker)条件是求解约束优化问题的重要方法&#xff0c;在有等式约束时使用拉格朗日乘子法&#xff0c;在有不等约束时使用KKT条件。前提是&#xff1a;只有当目标函数为凸函数时&#xff0c;使用这两种方法才保证求得的是…

拉格朗日乘子法几何意义

为什么出现拉格朗日乘子法&#xff1f; 最短路径问题从几何意义中获得灵感&#xff1a;从数学公式中获得灵感推广到高维空间 一个最短路径问题 假设你在M点&#xff0c;需要先到河边&#xff08;上图右侧曲线 &#xff09;再回到C点&#xff0c;如何规划路线最短&#xff1f;…

拉格朗日乘子法(自己总结一些要点)

主要是研究SVM算法的时候涉及到了拉格朗日乘子法&#xff0c;由于是大学数学的内容&#xff0c;开始看懂&#xff0c;也不高兴认真去看。后来发现绕不开&#xff0c;于是打算认真去研究下。主要还是百度百科&#xff08;https://baike.baidu.com/item/%E6%8B%89%E6%A0%BC%E6%9C…

拉格朗日乘子法:写得很通俗的文章

拉格朗日乘子法 最近在学习 SVM 的过程中&#xff0c;遇到关于优化理论中拉格朗日乘子法的知识&#xff0c;本文是根据几篇文章总结得来的笔记。由于是刚刚接触&#xff0c;难免存在错误&#xff0c;还望指出?。另外&#xff0c;本文不会聊到深层次的数学推导&#xff0c;仅仅…

拉格朗日数乘法

拉格朗日乘数法&#xff08;Lagrange Multiplier Method&#xff09;之前听数学老师授课的时候就是一知半解&#xff0c;现在越发感觉拉格朗日乘数法应用的广泛性&#xff0c;所以特意抽时间学习了麻省理工学院的在线数学课程。新学到的知识一定要立刻记录下来&#xff0c;希望…

真正理解拉格朗日乘子法和KKT条件

转载自&#xff1a;https://www.cnblogs.com/xinchen1111/p/8804858.html 这篇博文中直观上讲解了拉格朗日乘子法和 KKT 条件&#xff0c;对偶问题等内容。 首先从无约束的优化问题讲起&#xff0c;一般就是要使一个表达式取到最小值&#xff1a; minf(x) m i n f ( x ) min…

【最优化】拉格朗日乘子法

拉格朗日乘子法 前面几节讲述的都是无约束优化问题的相关算法&#xff0c;但是在实际生活中碰到的几乎都是有约束问题模型。 等式约束的拉格朗日乘子法 算法框架 1. 问题描述 以下对约束优化问题中常出现的概念做一下简要解释&#xff1a; 可行解&#xff1a;所有满足约束条…

拉格朗日乘子法的通俗理解

拉格朗日乘子法的通俗理解 1. 举例2. 求偏导3. 拉格朗日乘子法4. 乘子 1. 举例 这里举个简单的例子吧 在家里做蛋糕&#xff0c;假如只计算鸡蛋和牛奶的价格 其中鸡蛋的价格为4.5&#xffe5;/斤&#xff0c;牛奶为12&#xffe5;/升&#xff0c;而预算刚好是20&#xffe5; 那…