Android数据存储

前言

在前面,我们已经学了控件和布局,那么我们在存储数据的时候,并不能持久化的存储,所以我们需要来学习一些如何持久化存储数据的方式.

数据存储方式

  1. 文件存储:在android中提供了openFileInput()方法和openFileOutput()方法来读取设备上的文件,读写方式和java中I/O程序是一样的。可以用来存放大量的数据,如
  2. SharedPreferences存储SharedPreferencesAndroid提供用来存储一些简单配置信息的一种机制,采用XML格式将数据存储到设备中。一般用于存储一些应用程序的各种配置信息,如用户名、密码等。
  3. SQLite数据库存储:SQLite是Android自带的一个轻量级的数据库运算速度快,占用资源少,支持SQL语法,一般作为复杂数据的存储引擎,用来存储用户信息等。
  4. ContentProviderContentProvider是Android的四大组件之一,主要用于应用程序间的数据交换,可以将自己的数据共享给其他应用程序使用。
  5. 网络存储:网络存储需要与Android网络数据包打交道,将数据存储到服务器上,通过网络提供的存储空间来存储或获取数据信息。

对于以上这5种存储方式,各有优缺点,具体使用哪种方式,根据开发需求选择。

本篇我们主要讲文件存储

文件存储

1.存储方式

在Android中,有两种方式可以让应用进行数据持久化存储到文件中。

  • 内部存储
  • 外部存储

内部存储

内部存储是指将应用程序中的数据以文件的形式存储到应用程序中,此时存储的文件会被其所在的应用程序私有化(private),如果其他应用程序想要操作本应用程序中的文件,需要申请权限。当创建的应用程序被卸载时,其内部的存储文件也会被删除。

在Android开发中,内部存储使用的是Context提供的openFileInput()方法和openFileOutput()方法,这两个方法会分别会返回FileInputStream对象和FileOutputStream对象

前者用于打开应用程序中对应的输入流,读取指定文件的数据,后者用于将数据存储到指定文件下。

    FileInputStream = openFileInput(name);FileOutputStream = openFileOutput(name,mode);
  • name:文件名;
  • mode:读写文件的模式。 

mode有4种取值:

  1. MODE_PRIVATE:表示该文件只能被当前文件读写;
  2. MODE_APPEND:表示该文件的内容可以追加;
  3. MODE_WORLD_READABLE:表示该文件的内容可以被其他程序读;
  4. MODE_WORLD_WRITEABLE:表示该文件的内容可以被其他程序写。 

内部存储在/data目录下的data文件夹下。每个应用在安装成功之后,会自动创建新的目录(data/data/package-name),并且目录名称就是该应用的包名,每个应用都有属于自己的内部存储目录,当应用被卸载后,该目录都会被系统自动删除。所以,如果将数据存储于内部存储中,其实就是将数据存储到应用程序自己对应的应用包名对应的内部存储目录下 。

示例:实现读取文件的功能。

FileStreamActivity.java
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import com.example.file_store.R;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class FileStreamActivity extends AppCompatActivity implements View.OnClickListener {private EditText et_name;private EditText et_age;private TextView result;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.filestream_layout);//获取编辑框et_name = findViewById(R.id.et_name);et_age = findViewById(R.id.et_age);result = findViewById(R.id.read_result);//点击事件findViewById(R.id.btn_internal).setOnClickListener(this);findViewById(R.id.btn_read_internal).setOnClickListener(this);}@Overridepublic void onClick(View view) {if (view.getId() == R.id.btn_internal) {//输入write();//提示Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();//清空et_name.setText("");et_age.setText("");} else {//读取read();//设置文本框为可见result.setVisibility(View.VISIBLE);Toast.makeText(this, "读取成功", Toast.LENGTH_SHORT).show();}}//获取连接,读取数据private void read() {FileInputStream is = null;try {is = openFileInput("data.txt");byte[] buggrt = new byte[is.available()];//获取文件大小is.read(buggrt);String data = new String(buggrt);//添加到文本框result.setText(result.getText().toString() + data);} catch (Exception e) {e.printStackTrace();}}//获取连接,并将数据写入private void write() {FileOutputStream out = null;try {out = openFileOutput("data.txt", MODE_PRIVATE);//写入数据String data = "姓名:" + et_name.getText().toString() + "  年龄:" + et_age.getText().toString() + "\n";out.write(data.getBytes());} catch (Exception e) {e.printStackTrace();} finally {try {if (out != null) {out.close();}} catch (IOException e) {e.printStackTrace();}}}
}
filestream_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#999999"android:gravity="center"android:text="内部存储"android:textSize="20sp" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="姓名:"android:textSize="18dp" /><EditTextandroid:id="@+id/et_name"android:layout_width="0dp"android:layout_height="match_parent"android:layout_marginTop="5dp"android:layout_marginBottom="5dp"android:layout_weight="1"android:background="@drawable/ed_select"android:hint="请输入姓名" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="年龄:"android:textSize="18dp" /><EditTextandroid:id="@+id/et_age"android:layout_width="0dp"android:layout_height="match_parent"android:layout_marginTop="5dp"android:layout_marginBottom="5dp"android:layout_weight="1"android:background="@drawable/ed_select"android:hint="请输入年龄" /></LinearLayout><Buttonandroid:id="@+id/btn_internal"android:layout_width="200dp"android:layout_height="50dp"android:layout_gravity="center"android:layout_marginTop="20sp"android:text="存储" /><Buttonandroid:id="@+id/btn_read_internal"android:layout_width="200dp"android:layout_height="50dp"android:layout_gravity="center"android:layout_marginTop="20sp"android:text="读取" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/read_result"android:textSize="20sp"android:textColor="#000000"android:text="读取结果为:\n"android:visibility="gone"/></LinearLayout>

编辑框样式:

ed_check.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><!--设置选中状态的背景颜色--><solid android:color="@color/white"/><!--设置圆角半径--><corners android:radius="5dp"/><!--设置边框颜色和宽度--><stroke android:color="#039BE5" android:width="1dp"/><!--设置内边距--><padding android:left="5dp" android:top="5dp" android:right="5dp" android:bottom="5dp"/>
</shape>

ed_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><!--设置未选状态背景颜色--><solid android:color="#fff"/><!--设置边框颜色和粗细--><stroke android:width="1dp" android:color="#000"/><!--设置圆角--><corners android:radius="5dp"/><!--设置内边距--><padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp"/>
</shape>

ed_select.xml 

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_focused="true" android:drawable="@drawable/ed_check"/><item android:drawable="@drawable/ed_normal"/>
</selector>

运行结果:

 

外部存储

外部存储是指将数据以文件的形式存储到一些外部设备(例如SD卡或者设备内嵌的存储卡)上,属于永久性的存储方式(外部存储的文件通常是位于storage/emulated/0目录下,不同厂商的生产的手机的存储路径可能会不同)。外部存储的文件可以被其他应用程序共享,当将外部存储设备连接到计算机时,这些文件可以被浏览、修改和删除(并不安全)

外部存储分类

外部存储根据存储特点可以分为两种类型:

  • 外部私有存储
  • 外部公有存储

  • 外部私有目录:在上图中android文件夹中,打开之后其中有一个data文件夹,这个文件夹我们可以看到许多包名组成的文件夹,这些文件就是应用程序的私有目录;
  • 外部公有目录:外部公有存储目录下存储的数据,只要应用或用户有外部访问权限,就可以读取到外部公有目录下的文件。
外部私有存储 

外部私有存储是指在Android系统中,每个应用都可以拥有自己的外部存储空间,这个空间对于其他应用来说是不可访问的,只有自己的应用可以访问和修改其中的数据。

 特点
  • 与宿主App的生命周期相同,应用卸载时,会被系统自动删除;
  • 宿主App可以直接访问,无需权限(注:从 4.4 版本开始,宿主 App 可以直接读写外部存储空间中的应用私有目录, 4.4 版本之前,开发人员需在 Manifest 申请外部存储空间的文件读写权限。)
  • 普通用户可以访问,但仅限于访问自己应用的外部私有存储空间
  • 其他App也可以访问。(注:自 Android 7.0 开始,系统对应用私有目录的访问权限进一步限制。其它App无法通过 file:// 这种形式的 Uri 直接读写该目录下的文件内容,需通过 FileProvider 访问。)
  • 适合存储那些不适合放在内部存储中的数据,适用于存储私密性和安全性较高的数据
相关API 
1.getExternalCacheDir() 
/*获取到的目录是/storage/emulated/0/Android/data/package_name/cache,如果该目录不存在,调用这个方法会自动创建该目录。*/
2.getExternalFilesDir(String type) 
/* 1.如果type为"",那么获取到的目录是 /storage/emulated/0/Android/data/package_name/files2.如果type不为空,则会在/storage/emulated/0/Android/data/package_name/files目录下创建一个以传入的type值为名称的目录,例如你将type设为了test,那么就会创建/storage/emulated/0/Android/data/package_name/files/test目录,这个其实有点类似于内部存储getDir方法传入的name参数。但是android官方推荐使用以下的type类型public static String DIRECTORY_MUSIC = "Music";public static String DIRECTORY_PODCASTS = "Podcasts";public static String DIRECTORY_RINGTONES = "Ringtones";public static String DIRECTORY_ALARMS = "Alarms";public static String DIRECTORY_NOTIFICATIONS = "Notifications";public static String DIRECTORY_PICTURES = "Pictures";public static String DIRECTORY_MOVIES = "Movies";public static String DIRECTORY_DOWNLOADS = "Download";public static String DIRECTORY_DCIM = "DCIM";public static String DIRECTORY_DOCUMENTS = "Documents";*/

示例:

 

这个其实就是在上面内部存储例子中,改动一些。

ExPriActivity.java

package com.example.file_store.File_out;import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import com.example.file_store.R;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class ExPriActivity extends AppCompatActivity implements View.OnClickListener {private TextView result;private EditText et_name, et_age;private String path;private static File data;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.expri_layout);//获取编辑框中的内容et_name = findViewById(R.id.et_name);et_age = findViewById(R.id.et_age);//文本框result = findViewById(R.id.read_result);//设置按钮监听事件findViewById(R.id.btn_external).setOnClickListener(this);findViewById(R.id.btn_read_external).setOnClickListener(this);path = getPath();if (path == "") {Toast.makeText(this, "获取路径失败", Toast.LENGTH_SHORT).show();return;}//生成文件data = new File(path);}@Overridepublic void onClick(View view) {if (view.getId() == R.id.btn_external) {if(et_name.getText().toString().equals("")||et_age.getText().toString().equals("")){Toast.makeText(this,"请输入完整!",Toast.LENGTH_SHORT).show();return;}//输入write();//提示Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();//清空et_name.setText("");et_age.setText("");} else {//读取read();//设置文本框为可见result.setVisibility(View.VISIBLE);Toast.makeText(this, "读取成功", Toast.LENGTH_SHORT).show();}}private String getPath() {//设置文件名String fileName = "data.txt";//获取外部私有目录//public static String DIRECTORY_DOWNLOADS = "Download";//这里我们放置到Download目录下,获取路径return getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + File.separatorChar + fileName;}//获取连接,读取数据private void read() {FileInputStream fis = null;try {fis = new FileInputStream(data);byte[] buggrt = new byte[fis.available()];//获取文件大小fis.read(buggrt);String data = new String(buggrt);//添加到文本框result.setText( data);} catch (Exception e) {e.printStackTrace();}}//获取连接,并将数据写入private void write() {//获取文件输出流FileOutputStream fos = null;try {//追加fos = new FileOutputStream(data,true);//写入数据String data = "姓名:" + et_name.getText().toString() + "  年龄:" + et_age.getText().toString() + "\n";fos.write(data.getBytes());} catch (Exception e) {e.printStackTrace();} finally {//关闭输出流if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}}

expri_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/gray"android:gravity="center"android:text="外部私有存储"android:textSize="20sp" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="@string/name"android:textSize="@dimen/font_size" /><EditTextandroid:id="@+id/et_name"android:layout_width="0dp"android:layout_height="match_parent"android:layout_marginTop="5dp"android:layout_marginBottom="5dp"android:layout_weight="1"android:background="@drawable/ed_select"android:hint="@string/ed_name" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="@string/age"android:textSize="@dimen/font_size" /><EditTextandroid:id="@+id/et_age"android:layout_width="0dp"android:layout_height="match_parent"android:layout_marginTop="5dp"android:layout_marginBottom="5dp"android:layout_weight="1"android:background="@drawable/ed_select"android:hint="@string/ed_age" /></LinearLayout><Buttonandroid:id="@+id/btn_external"android:layout_width="200dp"android:layout_height="50dp"android:layout_gravity="center"android:layout_marginTop="20sp"android:text="存储" /><Buttonandroid:id="@+id/btn_read_external"android:layout_width="200dp"android:layout_height="50dp"android:layout_gravity="center"android:layout_marginTop="20sp"android:text="读取" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/read_result"android:textSize="20sp"android:textColor="#000000"android:text="从外部私有目录中读取结果为:\n"android:visibility="gone"/></LinearLayout>

可以看到,确实会在外部私有存储目录下生成应用对应的外部私有包。 

 

 外部公有存储

外部公有存储(External Public Storage)在Android系统中指的是设备上的一个存储区域,这个区域对所有应用和用户都是可访问的。

特点
  • 与宿主App生命周期无关,应用卸载后,数据仍然保留;
  • 所有App都需要申请EXTERNAL_STORAGE 权限,Android6.0(API 23)之后开始需要申请动态权限;
  • 用户可以通过文件管理器应用直接访问和操作外部公有存储中的文件;
  • 适合存储不敏感的数据,且希望与其他应用共享数据。
相关API
1.Environment.getExternalStorageDirectory() 
//获取到的目录是/storage/emulated/0,这个也是外部存储的根目录。
2.Environment.getExternalStoragePublicDirectory(String type) 
/* 1.如果type为"",那么获取到的目录是外部存储的根目录即  /storage/emulated/02.如果type不为空,则会在/storage/emulated/0目录下创建一个以传入的type值为名称的目录,例如你将type设为了test,那么就在外部存储根目录下创建test目录,这个方法和getExternalFilesDir的用法一样。android官方推荐使用以下的type类型,我们在SK卡的根目录下也经常可以看到下面的某些目录。public static String DIRECTORY_MUSIC = "Music";public static String DIRECTORY_PODCASTS = "Podcasts";public static String DIRECTORY_RINGTONES = "Ringtones";public static String DIRECTORY_ALARMS = "Alarms";public static String DIRECTORY_NOTIFICATIONS = "Notifications";public static String DIRECTORY_PICTURES = "Pictures";public static String DIRECTORY_MOVIES = "Movies";public static String DIRECTORY_DOWNLOADS = "Download";public static String DIRECTORY_DCIM = "DCIM";public static String DIRECTORY_DOCUMENTS = "Documents";*/

 示例:

所有应用程序都可以读写放置在外部公有存储目录中的文件,用户可以删除它们,在进行读写前,我们需要检查SD卡是否可以使用以及是否可以写入。

1.需要先确保应用程序具有读写用户SD卡的权限,需要在AndroidManifest.xml中添加以下权限.

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

2.检查用户是否具有读写SD卡的权限,如果没有就进行申请。

    //检查应用是否有外部存储权限private final String[] PERMISSIOMS_STORAGE = {Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE};//检查权限PermissionUtils.checkPermission(this, PERMISSIOMS_STORAGE);

这里生成一个权限工具类,来判断是否具有权限

PermissionUtils.java

package com.example.file_store.File_out;import android.content.pm.PackageManager;public class PermissionUtils {/*** 检查应用是否已经获得了所有请求的权限** @param exPubActivity 包含权限检查方法的活动实例,用于检查权限* @param permissiomsStorage 存储需要检查的权限的数组*/public static void checkPermission(ExPubActivity exPubActivity, String[] permissiomsStorage) {// 判断是否已经授予权限boolean isAllGranted = true;// 遍历权限数组,检查每个权限是否都已经授予for (String permisson : permissiomsStorage) {// 使用位运算符"&="来判断所有权限是否都被授予isAllGranted &= exPubActivity.checkSelfPermission(permisson) == PackageManager.PERMISSION_GRANTED;}// 如果没有授予所有权限,就请求权限if(!isAllGranted){exPubActivity.requestPermissions(permissiomsStorage, 1);}}
}

3.获取完权限后,我们就可以进行读写文件了。

ExPubActivity.java

package com.example.file_store.File_out;import android.Manifest;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import com.example.file_store.R;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class ExPubActivity extends AppCompatActivity implements View.OnClickListener {private TextView result;private EditText et_name, et_age;private File data;//检查应用是否有外部存储权限private final String[] PERMISSIOMS_STORAGE = {Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.expub_layout);//检查权限PermissionUtils.checkPermission(this, PERMISSIOMS_STORAGE);//获取编辑框中的内容et_name = findViewById(R.id.et_name);et_age = findViewById(R.id.et_age);//文本框result = findViewById(R.id.read_result);//设置按钮监听事件findViewById(R.id.btn_external_pub).setOnClickListener(this);findViewById(R.id.btn_read_external_pub).setOnClickListener(this);String path = getPath();//生成文件data = new File(path);//打印下文件路径Log.d("ning", path);}@Overridepublic void onClick(View view) {if (view.getId() == R.id.btn_external_pub) {if(et_name.getText().toString().equals("")||et_age.getText().toString().equals("")){Toast.makeText(this,"请输入完整!",Toast.LENGTH_SHORT).show();return;}//输入write();//提示Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();//清空et_name.setText("");et_age.setText("");} else {//读取read();//设置文本框为可见result.setVisibility(View.VISIBLE);Toast.makeText(this, "读取成功", Toast.LENGTH_SHORT).show();}}private String getPath() {//设置文件名String fileName = "data.txt";//获取外部公有目录//public static String DIRECTORY_DOWNLOADS = "Download";//这里我们放置到Download目录下,获取路径return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()+ File.separatorChar + fileName;}//获取连接,读取数据private void read() {FileInputStream fis = null;try {fis = new FileInputStream(data);byte[] buggrt = new byte[fis.available()];//获取文件大小fis.read(buggrt);String data = new String(buggrt);//添加到文本框result.setText( data);} catch (Exception e) {e.printStackTrace();}}//获取连接,并将数据写入private void write() {//获取文件输出流FileOutputStream fos = null;try {//追加fos = new FileOutputStream(data,true);//写入数据String data = "姓名:" + et_name.getText().toString() + "  年龄:" + et_age.getText().toString() + "\n";fos.write(data.getBytes());} catch (Exception e) {e.printStackTrace();} finally {//关闭输出流if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}
}

布局:expub_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/gray"android:gravity="center"android:text="外部公有存储"android:textSize="20sp" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="@string/name"android:textSize="@dimen/font_size" /><EditTextandroid:id="@+id/et_name"android:layout_width="0dp"android:layout_height="match_parent"android:layout_marginTop="5dp"android:layout_marginBottom="5dp"android:layout_weight="1"android:background="@drawable/ed_select"android:hint="@string/ed_name" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="@string/age"android:textSize="@dimen/font_size" /><EditTextandroid:id="@+id/et_age"android:layout_width="0dp"android:layout_height="match_parent"android:layout_marginTop="5dp"android:layout_marginBottom="5dp"android:layout_weight="1"android:background="@drawable/ed_select"android:hint="@string/ed_age" /></LinearLayout><Buttonandroid:id="@+id/btn_external_pub"android:layout_width="200dp"android:layout_height="50dp"android:layout_gravity="center"android:layout_marginTop="20sp"android:text="存储" /><Buttonandroid:id="@+id/btn_read_external_pub"android:layout_width="200dp"android:layout_height="50dp"android:layout_gravity="center"android:layout_marginTop="20sp"android:text="读取" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/read_result"android:textSize="20sp"android:textColor="#000000"android:text="从外部私有目录中读取结果为:\n"android:visibility="gone"/></LinearLayout>

运行结果:

 

 

可以看到,确实是存储在了外部公有存储目录下。

外部存储和内部存储对比

在Android4.4以前,逻辑上和物理上是统一的,但是Android4.4以后,随着外置SD卡的使用越来越少,内部存储和外部存储和物理介质的内外就没有任何关系了。

外部存储和内部存储与物理存储的关系
 

 

 外部存储和内部存储对比


 

以上就是本篇所有内容咯。

若有不足,欢迎指正~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/17330.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础——多线程

1. 线程 是一个程序内部的一条执行流程程序中如果只有一条执行流程&#xff0c;那这个程序就是单线程的程序 2. 多线程 指从软硬件上实现的多条执行流程的技术&#xff08;多条线程由CPU负责调度执行&#xff09; 2.1. 如何创建多条线程 Java通过java.lang.Thread类的对象…

【网络】网络层——IP协议

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解在网络层下的IP协议。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff1a;网络…

获取当前程序运行时的栈大小[C语言]

废话前言 一晃已经毕业了4年&#xff0c;也在某个时间点&#xff0c;从面试者转变成了面试官。 进行第一次面试的时候&#xff0c;我好像比候选人还慌张&#xff0c;压根不知道问什么&#xff0c;好在是同行业&#xff0c;看着简历问了一些协议内容以及模块设计思路&#xff0…

人工智能之数学基础:数学在人工智能领域中的地位

人工智能&#xff08;AI&#xff09;是一种新兴的技术&#xff0c;它的目标是构建能够像人类一样思考、学习、推理和解决问题的智能机器。AI已经成为了许多行业的重要组成部分&#xff0c;包括医疗、金融、交通、教育等。而数学则是AI领域中不可或缺的基础学科。本文将阐述数学…

UE5 第一人称射击项目学习(一)

因为工作需要&#xff0c;需要掌握ue5的操作。 选择了视频资料 UE5游戏制作教程Unreal Engine 5 C作为学习。 第一个目标是跟着视频制作出一款第一人称射击项目。 同时作为入门&#xff0c;这个项目不会涉及到C&#xff0c;而是一个纯蓝图的项目。 项目目标 这个项目将实…

图像分类之花卉识别实验验证

本实验基于37种主流的图像分类算法模型&#xff0c;对64种花卉进行识别。使用包括vgg、resnet、densenet、efficientnet、inception、mobilenet等37种图像分类模型进行实验&#xff0c;评估各种模型对花卉的识别准确度、计算量、参数量&#xff0c;对比不同模型的性能和优缺点。…

Linux基础开发工具使用

目录 1. 软件包管理器yum 1.1 概念介绍 1.2 更换镜像源&#xff08;可选&#xff09; 1.3 工具的搜索/查看/安装/卸载 1.4 优势 2. vim编辑器 2.1 vi和vim 2.2 三种常用模式和操作 2.3 配置vim 3. Linux编译器-gcc/g 4. Linux调试器-gdb 5. make和Makefile 6.…

电脑怎么自动切换IP地址

在现代网络环境中&#xff0c;电脑自动切换IP地址的需求日益增多。无论是出于网络安全、隐私保护&#xff0c;还是为了绕过地域限制&#xff0c;自动切换IP地址都成为了许多用户关注的焦点。本文将详细介绍几种实现电脑自动切换IP地址的方法&#xff0c;以满足不同用户的需求。…

PMBOK® 第六版 控制进度

目录 读后感—PMBOK第六版 目录 制定了明确的计划后&#xff0c;对计划的控制尤为重要。例如&#xff0c;经常提到的“累积效应”&#xff0c;如果某个阶段的评分仅为0.9分&#xff0c;那么五个得分为0.9分的阶段&#xff0c;最终结果可能只是一个0.5分。 特别是在当今这个时…

linux001.在Oracle VM VirtualBox中ubuntu虚拟系统扩容

1.打开终端切换到virtualBox安装目录 2.输入命令扩容 如上终端中的代码解释&#xff1a; D:\Program Files\Oracle\VirtualBox>.\VBoxManage modifyhd D:\ubuntu18.04\Ubuntu18.04\Ubuntu18.04.vdi --resize 40960如上代码说明&#xff1a;D:\Program Files\Oracle\Virtual…

Web导出Excel表格

背景&#xff1a; 1. 后端主导实现 流程&#xff1a;前端调用到导出excel接口 -> 后端返回excel文件流 -> 浏览器会识别并自动下载 场景&#xff1a;大部分场景都有后端来做 2. 前端主导实现 流程&#xff1a;前端获取要导出的数据 -> 常规数据用插件处理成一个e…

函数栈帧的创建与销毁

我是目录 环境理解栈帧函数栈帧图预备知识寄存器MOV 指令SUB 指令PUSH 指令POP 指令LEA 指令CALL 指令REP STOS 指令 一个简单的C程序栈帧创建栈帧销毁 如何传参数值参数变量参数 如何返回值数值返回变量返回 环境 集成环境&#xff1a;VS2022 x86 编辑语言&#xff1a;C 汇…

服务端高并发分布式结构进阶之路

序言 在技术求知的旅途中&#xff0c;鉴于多数读者缺乏在中大型系统实践中的亲身体验&#xff0c;难以从宏观角度把握某些概念&#xff0c;因此&#xff0c;本文特选取“电子商务应用”作为实例&#xff0c;详细阐述从百级至千万级并发场景下服务端架构的逐步演变历程。同时&am…

Linux:版本控制器git和调试工具cgdb

✨✨所属专栏&#xff1a;Linux✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 版本控制器 为了能够更⽅便我们管理这些不同版本的⽂件&#xff0c;便有了版本控制器。所谓的版本控制器&#xff0c;就是能让你了解到⼀个⽂件的历史&#xff0c;以及它的发展过程的系统。通俗的讲就是⼀…

【0x001C】HCI_Write_Page_Scan_Activity详解

目录 一、命令概述 二、命令格式和参数说明 2.1. HCI_Write_Page_Scan_Activity命令格式 2.2. Page_Scan_Interval 2.3. Page_Scan_Window 三、响应事件及参数说明 3.1. HCI_Command_Complete事件 3.2. Status 3.3. 示例 四、命令执行流程 4.1. 命令发起阶段(主机端…

云原生之运维监控实践-使用Prometheus与Grafana实现对Nginx和Nacos服务的监测

背景 如果你要为应用程序构建规范或用户故事&#xff0c;那么务必先把应用程序每个组件的监控指标考虑进来&#xff0c;千万不要等到项目结束或部署之前再做这件事情。——《Prometheus监控实战》 去年写了一篇在Docker环境下部署若依微服务ruoyi-cloud项目的文章&#xff0c;当…

突破工业管理新高度:AI多模态引擎赋能设备维护管理

结合AI技术&#xff0c;可以帮助企业提升设备维护效率和管理复杂信息的能力。以下是一个详细流程和思路&#xff1a; 1. 项目背景概述 在高端制造业领域&#xff0c;如飞机、轮船、光刻机等设备的操作手册及零件图纸涉及大量的零配件信息和操作维护流程。传统方式难以高效管理…

C++重写和重定义和重载

重写 概念&#xff1a; 重写发生在类的继承体系中&#xff0c;是指在派生类中重新定义基类中已声明为虚函数&#xff08;使用 virtual 关键字修饰&#xff09;的函数。其目的是让派生类根据自身的需求对基类的虚函数提供不同的具体实现&#xff0c;从而实现运行时多态。 规则及…

centos7在使用yum源安装依赖时报错

1.在centos7中使用yum命令时候报错如下类似信息&#xff1a; Loading mirror speeds from cached hostfile Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx86_64&repoos&infrastock error was 14: curl#6 - "Could not resol…

小版本大不同 | Navicat 17 新增 TiDB 功能

近日&#xff0c;Navicat 17 迎来了小版本更新。此次版本新增了对 PingCap 公司的 TiDB 开源分布式关系型数据库的支持&#xff0c;进一步拓展了 Navicat 的兼容边界。即日起&#xff0c;Navicat 17 所有用户可免费升级至最新版本&#xff0c;通过 Navicat 工具实现 TiDB 数据库…