1、数据持久化
数据持久化就是指将内存中的瞬时数据保存到存储设备中,保证手机在关机的情况下数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的。
Android系统提供了三种方式简单的数据持久化功能,即文件存储、SharedPreferences存储和SQLite数据库存储。
2、文件存储
文件存储是Android中最基本的一种数据存储方式,它与Java中的文件存储类似,都是通过I/O流的形式把数据直接存储到文件中。
不同的是,Android中的文件存储分为 内部存储 和 外部存储 。
1.内部存储
内部存储是指应用程序中的数据以文件方式存储到应用程序内部中。当创建的应用程序被卸载时,其内部存储文件也随之被删除。
(1)将数据存储到文件中
Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。
这个方法接收两个参数,第一个参数是文件名,文件是默认存储到/data/data/<package name>/files/目录下的。
第二个参数是文件的操作模式,主要有两种模式,MODE_PRIVATE和MODE_APPEND。
其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在就往文件里面追加内容,不存在就创建新文件。
下面我们就编写一个完整的例子,来学习一下如何在Android项目中使用文件存储的技术。
首先创建一个FileStorageTest项目,并修改activity_main.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">
<EditText
android:id="@+id/edtData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要保存的数据" />
<Button
android:id="@+id/btnWriteToApp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="写入到内部文件" />
</LinearLayout>
在布局中添加一个EditText和一个Button,点击Button时,将EditText中的内容存储到文件当中。
修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import java.io.FileOutputStream;
import java.io.IOException;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText edtData;
private Button btnWriteToApp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edtData = (EditText) findViewById(R.id.edtData);
btnWriteToApp = (Button) findViewById(R.id.btnWriteToApp);
btnWriteToApp.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnWriteToApp:
writeToApp(edtData.getText().toString());
break;
}
}
public void writeToApp(String data) {
FileOutputStream out = null;
try {
out = openFileOutput("myFile", MODE_PRIVATE);
out.write(data.getBytes());
out.flush(); // 清空缓冲区的数据流
out.close(); // 关闭输出流
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们通过使用MainActivity去实现监听器接口的方式为Button添加监听事件,在onCreate()方法中获取了EditText和Button的实例,为Button控件添加监听器,在onClick()方法中我们获取了EditText中输入的内容,并调用writeToApp()方法把输入的内容存储到内部文件中,文件命名为myFile。
writeToApp()方法接收一个String类型的参数,用于传入要保存的字符串数据,在该方法内,通过openFileOutput()方法获得一个FileOutputStream对象(文件输出流),然后使用它的write()方法将文本内容写入到文件中,但是write()方法需要一个字节数组参数,我们通过字符串的getBytes()方法可以将字符串转化为一个字节数组。
最后,一定要关闭文件输出流。现在重新运行一下程序,并在EditText中输入一些内容,点击按钮保存,如图所示。
如何才能证实数据确实已经保存成功了呢?我们可以借助Device File Explorer来查看一下。
在这里进入到/data/data/com.sdbi.filepersistencetest/files/目录下,可以看到生成了一个myFile文件,将文件保存到桌面或着直接双击打开,查看里面的内容如图所示。
如果在保存文件或者双击打开时出现如下错误:
There were errors downloading files and/or directories: secure_mkdirs failed: Operation not permitted
我们可以到D:\Android\sdk\platform-tools目录下,使用命令行执行如下命令:
adb root
再去保存或者打开就可以了。
这样就证实了,在EditText中输入的内容确实已经成功保存到文件中了。
不过只是成功将数据保存下来还不够,我们还要学习一下,如何从文件中读取数据。
(2)从文件中读取数据
类似于将数据存储到文件中,Context类中还提供了一个openFileInput()方法,用于从文件中读取数据。
这个方法只接收一个参数,即要读取的文件名,然后系统会自动到/data/data/<package name>/files/目录下去读取这个文件,并返回一个FileInputStream对象(文件输入流),得到了这个对象之后再通过Java流的方式就可以将数据读取出来了。
下面,我们在项目中完成数据的读取代码。
修改activity_main.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">
<EditText
android:id="@+id/edtData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要保存的数据" />
<Button
android:id="@+id/btnWriteToApp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="写入到内部文件" />
<Button
android:id="@+id/btnReadFromApp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="从内部文件读取" />
<TextView
android:id="@+id/tvDisplay"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
在布局文件中增加两个控件:一个Button和一个TextView,分别用于读取文件和显示读取的数据的。
修改MainActivity中的代码,如下所示:
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.TextView;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText edtData;
private Button btnWriteToApp, btnReadFromApp;
private TextView tvDisplay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edtData = (EditText) findViewById(R.id.edtData);
btnWriteToApp = (Button) findViewById(R.id.btnWriteToApp);
btnWriteToApp.setOnClickListener(this);
btnReadFromApp = (Button) findViewById(R.id.btnReadFromApp);
btnReadFromApp.setOnClickListener(this);
tvDisplay = (TextView) findViewById(R.id.tvDisplay);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnWriteToApp:
writeToApp(edtData.getText().toString());
break;
case R.id.btnReadFromApp:
String strData = readFromApp();
if (!TextUtils.isEmpty(strData)) { // 判空处理
tvDisplay.setText(strData);
}
break;
}
}
public void writeToApp(String data) {
FileOutputStream out = null;
try {
out = openFileOutput("myFile", MODE_PRIVATE);
out.write(data.getBytes());
out.flush(); // 清空缓冲区的数据流
out.close(); // 关闭输出流
} catch (IOException e) {
e.printStackTrace();
}
}
public String readFromApp() {
FileInputStream in = null;
StringBuffer buffer = new StringBuffer("");
try {
in = openFileInput("myFile");
byte[] temp = new byte[1024];
int len = 0;
while ((len = in.read(temp)) > 0) {
buffer.append(new String(temp, 0, len));
}
in.close(); // 关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
return buffer.toString();
}
}
在这段代码中我们自己定义了一个方法:readFromApp(),专门用于从内部文件中读取数据,在该方法中,首先通过openFileInput()方法获取到了一个FileInputStream对象in(文件输入流),然后创建一个长度为1024的字节数组类型缓冲区temp,循环将文件的内容读取到缓冲区temp中,再将字节数组缓冲区中的数据转换成字符串添加到StringBuffer中,最后将StringBuffer中的数据转换为字符串返回给该方法。
注意:在循环读取输入流对象后一定要关闭文件输入流。然后,只需在onCreate()方法中调用readFromApp()方法来读取文件中存储的文本内容,如果读到的内容不为空,就调用TextView的setText()方法将内容填充到TextView里。
上述代码在对字符串进行非空判断的时候使用了TextUtils.isEmpty()方法,这是一个非常好用的静态方法,它可以一次性进行两种空值的判断。
当传入的字符串等于null或者等于空字符串的时候,这个方法都会返回true,从而使得我们不需要单独去判断这两种空值,再使用逻辑运算符连接起来了。
重新运行一下程序,点击“从内部文件读取”按钮,可以将原先保存过的数据读取出来,如图所示。
2.外部存储
外部存储就是指将文件存储到一些外部设备上,例如SD卡或者设备内嵌的存储卡,属于永久性的存储方式。
通常位于storage/sdcard文件夹,也有可能是mnt/sdcard文件夹,这个不同厂商生产的手机路径可能会不一样。
外部存储的文件可以被其他应用程序共享使用,当外部存储设备连接到计算机时,这些文件可以被浏览、修改和删除,因此这种方式是不安全的。
由于外部存储设备可能被移除、丢失或者处于其他状态,因此在使用外部设备之前必须使用Environment类的静态方法getExternalStorageState()来确认外部设备是否可用,当外部设备可用并且具有读写权限时,那么就可以通过FileOutputStream、FileInputStream对象来读写外部设备中的文件。
(1)将数据存储到外部设备的文件中
在前面程序的基础上增加一个writeToSdcard()方法用于将EditText输入的文本信息保存到外部存储设备上。代码如下所示:
public void writeToSdcard(String inputText) {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) { // String“mounted”:安装好的
File dir = Environment.getExternalStorageDirectory();
File file = new File(dir, "myData"); // 在dir目录下构建一个新文件myData
Log.d("MainActivity", "path = " + dir.getPath()); //或 dir.toString()
Log.d("MainActivity", "file_name = " + file.getPath()); //或 file.toString()
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
out.write(inputText.getBytes());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,我们使用Environment类的getExternalStorageState()静态方法来判断SD卡是否存在可用。
使用getExternalStorageDirectory()静态方法来获取SD卡根目录的路径,这样可以避免由于手机厂商不同而导致SD路径不同的问题。
另外,对于File对象(路径和文件)我们可以调用其getPath()或者toString()方法来查看路径内容。
(2)从外部设备的文件中读取数据
增加一个readFromSdcard()方法读取外部设备上的文件。
public String readFromSdcard() {
String state = Environment.getExternalStorageState();
StringBuffer buffer = new StringBuffer("");
if (state.equals(Environment.MEDIA_MOUNTED)) {
File dir = Environment.getExternalStorageDirectory();
File file = new File(dir, "myData");
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] temp = new byte[1024];
int len = 0;
while ((len = in.read(temp)) > 0) {
Log.d("MainActivity", "len = " + len);
buffer.append(new String(temp, 0, len));
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer.toString();
}
在外部文件中写入数据和读取数据的方法完成后,我们在布局文件activity_main.xml中增加两个按钮,分别用于“写入到外部文件”和“从外部文件读取”,并且在MainActivity.java中给它们添加监听器。
修改activity_main.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">
<EditText
android:id="@+id/edtData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要保存的数据" />
<Button
android:id="@+id/btnWriteToApp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="写入到内部文件" />
<Button
android:id="@+id/btnReadFromApp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="从内部文件读取" />
<Button
android:id="@+id/btnWriteToSdcard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="写入到外部文件" />
<Button
android:id="@+id/btnReadFromSdcard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="从外部文件读取" />
<TextView
android:id="@+id/tvDisplay"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
修改MainActivity.java文件代码如下:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText edtData;
private Button btnWriteToApp, btnReadFromApp, btnWriteToSdcard, btnReadFromSdcard;
private TextView tvDisplay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edtData = (EditText) findViewById(R.id.edtData);
btnWriteToApp = (Button) findViewById(R.id.btnWriteToApp);
btnWriteToApp.setOnClickListener(this);
btnReadFromApp = (Button) findViewById(R.id.btnReadFromApp);
btnReadFromApp.setOnClickListener(this);
tvDisplay = (TextView) findViewById(R.id.tvDisplay);
btnWriteToSdcard = (Button) findViewById(R.id.btnWriteToSdcard);
btnWriteToSdcard.setOnClickListener(this);
btnReadFromSdcard = (Button) findViewById(R.id.btnReadFromSdcard);
btnReadFromSdcard.setOnClickListener(this);
tvDisplay = (TextView) findViewById(R.id.tvDisplay);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnWriteToApp:
writeToApp(edtData.getText().toString());
break;
case R.id.btnReadFromApp:
String strData = readFromApp();
if (!TextUtils.isEmpty(strData)) { // 判空处理
tvDisplay.setText(strData);
}
break;
case R.id.btnWriteToSdcard:
writeToSdcard(edtData.getText().toString());
break;
case R.id.btnReadFromSdcard:
strData = readFromSdcard();
if (!TextUtils.isEmpty(strData)) {
tvDisplay.setText(strData);
}
break;
}
}
public void writeToApp(String data) {
FileOutputStream out = null;
try {
out = openFileOutput("myFile", MODE_PRIVATE);
out.write(data.getBytes());
out.flush(); // 清空缓冲区的数据流
out.close(); // 关闭输出流
} catch (IOException e) {
e.printStackTrace();
}
}
public String readFromApp() {
FileInputStream in = null;
StringBuffer buffer = new StringBuffer("");
try {
in = openFileInput("myFile");
byte[] temp = new byte[1024];
int len = 0;
while ((len = in.read(temp)) > 0) {
buffer.append(new String(temp, 0, len));
}
in.close(); // 关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
return buffer.toString();
}
public void writeToSdcard(String inputText) {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) { // String“mounted”:安装好的
File dir = Environment.getExternalStorageDirectory();
File file = new File(dir, "myData"); // 在dir目录下构建一个新文件myData
Log.d("MainActivity", "path = " + dir.getPath()); //或 dir.toString()
Log.d("MainActivity", "file_name = " + file.getPath()); //或 file.toString()
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
out.write(inputText.getBytes());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String readFromSdcard() {
String state = Environment.getExternalStorageState();
StringBuffer buffer = new StringBuffer("");
if (state.equals(Environment.MEDIA_MOUNTED)) {
File dir = Environment.getExternalStorageDirectory();
File file = new File(dir, "myData");
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] temp = new byte[1024];
int len = 0;
while ((len = in.read(temp)) > 0) {
Log.d("MainActivity", "len = " + len);
buffer.append(new String(temp, 0, len));
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer.toString();
}
}
需要注意的是:
① Android系统为了保证应用程序的安全性做了相应的规定,由于操作SD卡中的数据属于系统中比较关键的信息,因此需要在清单文件的<manifest>节点中添加SD卡的读写权限,示例代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!--添加SD卡的读写权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FileStorageTest"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
② 在Android6.0(API 23)之后,APP需要动态获取权限,这也是为了用户数据更加安全。
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText edtData;
private Button btnWriteToApp, btnReadFromApp, btnWriteToSdcard, btnReadFromSdcard;
private TextView tvDisplay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edtData = (EditText) findViewById(R.id.edtData);
btnWriteToApp = (Button) findViewById(R.id.btnWriteToApp);
btnWriteToApp.setOnClickListener(this);
btnReadFromApp = (Button) findViewById(R.id.btnReadFromApp);
btnReadFromApp.setOnClickListener(this);
tvDisplay = (TextView) findViewById(R.id.tvDisplay);
btnWriteToSdcard = (Button) findViewById(R.id.btnWriteToSdcard);
btnWriteToSdcard.setOnClickListener(this);
btnReadFromSdcard = (Button) findViewById(R.id.btnReadFromSdcard);
btnReadFromSdcard.setOnClickListener(this);
tvDisplay = (TextView) findViewById(R.id.tvDisplay);
int permission1 = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int permission2 = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
// 常量PackageManager.PERMISSION_GRANTED=0,表示已授权
if (permission1 != PackageManager.PERMISSION_GRANTED || permission2 != PackageManager.PERMISSION_GRANTED) {
// 动态请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE},
1); // 第3个参数是一个表示请求的Code,要求>=0,作为回调方法onRequestPermissionsResult()的第1个参数
}
}
// 授权成功的回调方法,用不着可以不重写
// 第1个参数是一个表示请求的Code,ActivityCompat.requestPermissions()方法的第3个参数
// 第2个参数是请求的权限数组
// 第3个参数是授权结果,0表示已授权
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// 第3个参数是授权结果,0表示已授权
super.onRequestPermissionsResult(requestCode,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE},
grantResults);
Log.d("MainActivity", "onRequestPermissionsResult: requestCode = " + requestCode
+ ", permissions = " + Arrays.toString(permissions)
+ ", grantResults = " + Arrays.toString(grantResults));
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnWriteToApp:
writeToApp(edtData.getText().toString());
break;
case R.id.btnReadFromApp:
String strData = readFromApp();
if (!TextUtils.isEmpty(strData)) { // 判空处理
tvDisplay.setText(strData);
}
break;
case R.id.btnWriteToSdcard:
writeToSdcard(edtData.getText().toString());
break;
case R.id.btnReadFromSdcard:
strData = readFromSdcard();
if (!TextUtils.isEmpty(strData)) {
tvDisplay.setText(strData);
}
break;
}
}
public void writeToApp(String data) {
FileOutputStream out = null;
try {
out = openFileOutput("myFile", MODE_PRIVATE);
out.write(data.getBytes());
out.flush(); // 清空缓冲区的数据流
out.close(); // 关闭输出流
} catch (IOException e) {
e.printStackTrace();
}
}
public String readFromApp() {
FileInputStream in = null;
StringBuffer buffer = new StringBuffer("");
try {
in = openFileInput("myFile");
byte[] temp = new byte[1024];
int len = 0;
while ((len = in.read(temp)) > 0) {
buffer.append(new String(temp, 0, len));
}
in.close(); // 关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
return buffer.toString();
}
public void writeToSdcard(String inputText) {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) { // String“mounted”:安装好的
File dir = Environment.getExternalStorageDirectory();
File file = new File(dir, "myData"); // 在dir目录下构建一个新文件myData
Log.d("MainActivity", "path = " + dir.getPath()); //或 dir.toString()
Log.d("MainActivity", "file_name = " + file.getPath()); //或 file.toString()
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
out.write(inputText.getBytes());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String readFromSdcard() {
String state = Environment.getExternalStorageState();
StringBuffer buffer = new StringBuffer("");
if (state.equals(Environment.MEDIA_MOUNTED)) {
File dir = Environment.getExternalStorageDirectory();
File file = new File(dir, "myData");
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] temp = new byte[1024];
int len = 0;
while ((len = in.read(temp)) > 0) {
Log.d("MainActivity", "len = " + len);
buffer.append(new String(temp, 0, len));
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer.toString();
}
}
运行程序,提示请求权限,如图所示。
保存数据,我们可以看到在storage/sdcard文件夹生成一个myData文件。
输出日志如下。