前言
在前面,我们已经学了控件和布局,那么我们在存储数据的时候,并不能持久化的存储,所以我们需要来学习一些如何持久化存储数据的方式.
数据存储方式
- 文件存储:在android中提供了openFileInput()方法和openFileOutput()方法来读取设备上的文件,读写方式和java中I/O程序是一样的。可以用来存放大量的数据,如
- SharedPreferences存储:SharedPreferences是Android提供用来存储一些简单配置信息的一种机制,采用XML格式将数据存储到设备中。一般用于存储一些应用程序的各种配置信息,如用户名、密码等。
- SQLite数据库存储:SQLite是Android自带的一个轻量级的数据库。运算速度快,占用资源少,支持SQL语法,一般作为复杂数据的存储引擎,用来存储用户信息等。
- ContentProvider:ContentProvider是Android的四大组件之一,主要用于应用程序间的数据交换,可以将自己的数据共享给其他应用程序使用。
- 网络存储:网络存储需要与Android网络数据包打交道,将数据存储到服务器上,通过网络提供的存储空间来存储或获取数据信息。
对于以上这5种存储方式,各有优缺点,具体使用哪种方式,根据开发需求选择。
本篇我们主要讲文件存储。
文件存储
1.存储方式
在Android中,有两种方式可以让应用进行数据持久化存储到文件中。
- 内部存储
- 外部存储
内部存储
内部存储是指将应用程序中的数据以文件的形式存储到应用程序中,此时存储的文件会被其所在的应用程序私有化(private),如果其他应用程序想要操作本应用程序中的文件,需要申请权限。当创建的应用程序被卸载时,其内部的存储文件也会被删除。
在Android开发中,内部存储使用的是Context提供的openFileInput()方法和openFileOutput()方法,这两个方法会分别会返回FileInputStream对象和FileOutputStream对象。
前者用于打开应用程序中对应的输入流,读取指定文件的数据,后者用于将数据存储到指定文件下。
FileInputStream = openFileInput(name);FileOutputStream = openFileOutput(name,mode);
- name:文件名;
- mode:读写文件的模式。
mode有4种取值:
- MODE_PRIVATE:表示该文件只能被当前文件读写;
- MODE_APPEND:表示该文件的内容可以追加;
- MODE_WORLD_READABLE:表示该文件的内容可以被其他程序读;
- 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卡的使用越来越少,内部存储和外部存储和物理介质的内外就没有任何关系了。
外部存储和内部存储与物理存储的关系
外部存储和内部存储对比
以上就是本篇所有内容咯。
若有不足,欢迎指正~