第十五课:数据持久性(基于AndroidStudio3.2)

当应用程序从用户输入创建或捕获数据时,这些数据仅在应用程序的生命周期内可用。 只要应用程序尚未被运行时杀死,您就只能访问该数据。 关闭应用程序时,应用程序运行时创建的所有数据都将丢失。 Android为我们提供了多种保存数据的方式,使其可以比应用程序生命周期更长,并允许我们跨应用程序生命周期访问相同的数据。 表中列出了可供使用的存储选项。

SharedPreferences这是最简单的存储形式。 它只是一个使用的字典对象键/值对成语。 如果您的数据足够简单,这非常有用结构化为字典对象(键/值对)。 Android存储这些文件内部为XML文件。 SharedPrefs仅存储简单数据类型(例如,字符串和原始数据类型)。 它无法存储更复杂的数据
Internal or external storage 将数据存储在设备存储(内部)或媒体存储中,如SDCARD(外部)。 如果您需要存储结构比字典更复杂的数据(例如,音频,视频文件),您可能需要考虑使用这种类型的持久性机制
SQLite database这个使用关系数据库。 如果您之前使用过其他数据库 - MS SQL服务器,MySQL,PostgreSQL或任何其他关系数据库 - 这基本上是相同的。 数据存储在表中,您需要使用SQL语句来创建,读取,更新和删除数据
Network如果您可以假设您的用户始终可以访问Internet,并且您拥有Internet上托管的数据库服务器,则可以使用此选项。 这种设置可能会有点复杂,因为您需要在某个地方托管数据库(亚马逊,谷歌,任何其他云提供商),为数据提供REST接口,并使用HTTP库作为Android应用程序中的客户端。 
Content ProvidersContent Provider是Android平台上的另一个组件; 它有活动,服务和广播接收器。 该组件使数据可用于除其自身之外的应用程序。 可以把它想象成具有公共HTTP API层的数据库。 通过HTTP进行通信的任何应用程序都可以读取和写入数据

在课中,我们将介绍使用共享首选项和内部存储。

一、SharedPreferences

SharedPreferences是在Android中持久保存数据的最简单,最快捷的方式。 数据的创建和检索使用键/值对的字典习语。 Android中还有其他一些用于管理数据的习惯用法; 其中一些你已经在我们过去的项目中看到过(例如,Intents和Bundles)。 使用SharedPreferences应该让我们非常熟悉。
要创建SharedPreferences文件,我们需要在Activity类中使用getPreferences方法,然后指定文件的访问模式。

SharedPreferences sp = getPreferences(CONTEXT.MODE_PRIVATE);

根据Android文档,Context.MODE_PRIVATE是我们应该使用的,因为公共模式自API级别17以来已被弃用。接下来,我们需要一个Editor object,以便我们可以开始修改新创建的文件中的数据。

SharedPreferences.Editor editor = sp.edit();

现在我们可以开始输入一些数据了。

edit.putString("name","Gandalf the Grey");
edit.putInt("age", 2019);

put命令的第一个参数始终是键,第二个参数是值。 在前面的例子中,“name”和“age”是键,“Gandalf the Grey”和2019是值。 put命令本身不会将数据保存到文件中,因此我们需要使用apply或commit方法。

editor.apply(); // or
editor.commit();

commit或apply方法将保存信息并将其保存在XML文件中;这两种方法之间只有细微的差别。

  • commit-这是同步的并返回一个布尔值,如果写操作成功,则返回true
  • apply-这也保存数据但不返回任何值。 它是异步执行的

注意:您无需为共享首选项文件指定文件名; Android运行时将自动为新创建的文件指定名称。 按照惯例,新创建的文件遵循调用getPreferences的活动类的名称; 例如,如果从MainActivity.java调用getPreferences,则共享首选项文件的名称将为MainActivity.xml。

从共享首选项文件中检索数据与创建数据一样简单。 要访问创建的共享首选项文件,我们在首先创建文件时使用相同的语法。

SharedPreferences sp = getPreferences(CONTEXT.MODE_PRIVATE);

getPreferences方法返回SharedPreferences对象的实例。 第一次调用此方法时,它将查找与调用该方法的活动同名的XML文件; 如果找不到该文件,则会创建该文件,但如果该文件已存在,则将使用该文件。 由于我们第一次调用getPreferences时已经创建了文件,因此Android不会创建新文件,也不会覆盖我们之前创建的文件。
一旦我们有了共享首选项对象,我们就可以从中提取数据。

sp.getString("name", "default value");
sp.getInt("age", 0);

1、测试,建立项目SharedPref。

ui设计如下图。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/etfirstname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:gravity="center"
        android:hint="Last name"
        android:inputType="textPersonName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/etlastname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:gravity="center"
        android:hint="First name"
        android:inputType="textPersonName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etfirstname" />

    <Button
        android:id="@+id/btnload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="load"
        app:layout_constraintStart_toStartOf="@+id/etlastname"
        app:layout_constraintTop_toBottomOf="@+id/etlastname" />

    <Button
        android:id="@+id/btnsave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="26dp"
        android:text="save"
        app:layout_constraintEnd_toEndOf="@+id/etlastname"
        app:layout_constraintTop_toBottomOf="@+id/etlastname" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="320dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="146dp"
        android:gravity="center"
        android:text="TextView"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etlastname" />
</android.support.constraint.ConstraintLayout>

2、实现

此应用程序的基本工作流程如下:
1).在两个文本字段中键入姓氏和名字信息
2).单击“保存”按钮时,从文本字段中提取字符串值
- 创建共享首选项文件(如果尚不存在)
- 使用一个将姓氏和名字数据推送到共享的pref文件中
编辑对象的put方法
- 保存更改
3).单击“加载”按钮时
- 使用与创建时相同的语法检索共享首选项文件
- 使用其中一种get方法检索文件中的数据
- 通过设置TextView对象的text属性来显示检索到的数据

视图对象(EditText,TextView和Button)的对象引用都在onCreate方法中定义。 我们可以这样做,因为我们的按钮的事件处理程序是使用内部(匿名类)创建的。 这也是为什么两个EditTexts和TextView被声明为final的原因。 每当内部类将使用其封闭类的成员变量时,该变量需要声明为final。

package com.example.administrator.sharedpref;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnsave = (Button) findViewById(R.id.btnsave);
        Button btnload = (Button) findViewById(R.id.btnload);

        final EditText etlastname = (EditText) findViewById(R.id.etlastname);
        final EditText etfirstname = (EditText) findViewById(R.id.etfirstname);
        final TextView tv = (TextView) findViewById(R.id.textView);

        btnsave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //Creates the shared preferences file, if one doesn’t exist yet
                SharedPreferences sp = getPreferences(Context.MODE_PRIVATE);
                //We can’t save data to the shared preferences file (yet); we need an interface object for it.
                //The editor objects will do that job
                SharedPreferences.Editor edit = sp.edit();

                //Retrieve whatever the user has typed on the EditText objects and assign them to String variables
                String lname = etlastname.getText().toString();
                String fname = etfirstname.getText().toString();

                //Use the editor object to persist data into the shared preferences file
                edit.putString("lname", lname);
                edit.putString("fname", fname);
                //Commit the changes to the file
                edit.apply();

                Toast.makeText(MainActivity.this, "Saved it", Toast.LENGTH_SHORT).show();
            }
        });

        btnload.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
 /*
 Retrieve the shared preferences object by getting a reference to it. The syntax for creating a
shared preferences object is the same as that for retrieving it. Android is clever enough to figure
out that if the file doesn’t exist, you want to create, and if it does exist, you want to retrieve it
*/
                SharedPreferences sp = getPreferences(Context.MODE_PRIVATE);
                //Get the data out of the shared pref file using one of the get methods; store it in a String variable
                String lname = sp.getString("lname", "na");
                String fname = sp.getString("fname", "na");
                //Set the text of the TextView object using the retrieved data from the shared pref file
                tv.setText(String.format("%s , %s", lname, fname));
            }
        });

    }
}

 

运行ok

3、Application Level SharedPreferences

在上图中,我们使用getSharedPreferences方法而不是getPreferences(如上一节所述)。 该方法需要两个参数:第一个是文件名,第二个是访问模式。 此方法查找第一个参数指定的文件名。 如果找不到该文件,将首次创建该文件。

SharedPreferences sp = getSharedPreferences(filename, Context.MODE);

接下来,我们使用共享首选项对象获取一个编辑器对象,并使用put命令的一些变体开始将一些数据放入该文件。 之后,我们可以保存文件。

Editor edit = sp.edit();
edit.putString("name", "Gandalf the grey");
edit.apply();

此时,我们通过创建显式intent并调用startActivity来启动第二个活动。
创建第二个活动时,我们可以打开共享首选项文件以启动数据检索过程。

SharedPreferences sp = getSharedPreferences(file, Context.MODE_PRIVATE);
String lname = sp.getString("name", "na");

getSharedPreferences方法查找方法的第一个参数中指定的文件名。 由于我们已经在MainActivity中创建了此文件,而不是创建新文件,因此将打开现有文件。

我们可以在一个演示项目上进一步探索。 我们可以创建一个全新的项目,但由于新项目与之前项目的差异非常小,您可能只想复制一个上一个项目并进行一些小的编辑。 按照下一步复制上一个项目。

1).关闭AS3中任何打开的项目; 从主菜单栏File ➤Close project
2).使用操作系统的文件管理器(Finder for macOS,Explorer for Windows)并将文件夹SharedPref复制到SharedPref2。然后在AS3中导入该项目

3)您需要在新项目中更改一些标识符; 至少,将包名称更改为sharedpref2。 让我们使用AS3的重构工具来促进这种变化。 突出显示包名称,如下图所示:右键单击Refactor➤Rename

在该窗口选择Rename package,然后将原工程名修改(都是小写)

点击Refactor

若看到目录结构中的项目并未更改,IDE下方出现一个预览窗口,点击Do refactor即可。如下图:

4)修改下面名称

从主菜单栏中,单击 Build ➤ Clean Project。 然后单击Build ➤ Rebuild Project重建项目

运行项目时出现

Session 'app': Error Launching activity

重新执行“sync Project with Gradle Files”

运行ok

 

4、新建名为Second的activity

第二个Activity中唯一的视图对象是TextView。 我们将用它来显示共享首选项xml文件的内容。
我们来看看如何从MainActivity.java创建共享文件。

我们修改LOAD按钮。 我们打开第二个活动,而不是打开共享的pref文件。

package com.example.administrator.sharedpref2;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnsave = (Button) findViewById(R.id.btnsave);
        Button btnload = (Button) findViewById(R.id.btnload);

        final EditText etlastname = (EditText) findViewById(R.id.etlastname);
        final EditText etfirstname = (EditText) findViewById(R.id.etfirstname);
        final TextView tv = (TextView) findViewById(R.id.textView);

        btnsave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
/*
The file name for a sharedpref should ideally be <package name of project> + <filename>.
The getPackage( ) method should return the package name
*/
                String file = getPackageName() + "myFile";
                //Pass the file name and the mode to the getSharedPreferences method in order to create the file
                SharedPreferences sp = getSharedPreferences(file, Context.MODE_PRIVATE);
                SharedPreferences.Editor edit = sp.edit();

                String lname = etlastname.getText().toString();
                String fname = etfirstname.getText().toString();

                edit.putString("lname", lname);
                edit.putString("fname", fname);
                edit.apply();

                Toast.makeText(MainActivity.this, "Saved it", Toast.LENGTH_SHORT).show();
            }
        });

        btnload.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
        /*
        SharedPreferences sp = getPreferences(Context.MODE_PRIVATE);
        String lname = sp.getString("lname", "na");
        String fname = sp.getString("fname", "na");
        tv.setText(String.format("%s , %s", lname, fname));
        */
                Intent intent = new Intent(MainActivity.this, Second.class);
                startActivity(intent);
            }
        });

    }
}
package com.example.administrator.sharedpref2;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class Second extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        TextView tv = (TextView) findViewById(R.id.textView2);
        String file = getPackageName() + "myFile";
        SharedPreferences sp = getSharedPreferences(file, Context.MODE_PRIVATE);

        String lname = sp.getString("lname", "na");
        String fname = sp.getString("fname", "na");
        tv.setText(String.format("%s , %s", lname, fname));

    }
}

使用共享首选项是保存应用程序数据的最简单,最快捷的方法,但它有一些限制。 您只能保存基本类型和字符串类型; 如果您需要处理更复杂的文件类型(例如,音频,视频或图像),则无法使用共享首选项实现此目的。

 

二、Internal Storage

当您需要处理更复杂的类型(如音频,视频或图像)时,您可以使用内部存储器(设备的内部存储器)或外部存储器(可公共访问的存储器,例如SDCARD)。

  • 外部存储

-------- 可以由创建它的应用程序,其他应用程序甚至用户访问
--------即使在卸载后,应用程序也会比应用程序更长久

  • 内部存储

--------只能由创建它的应用程序访问; 没有其他应用可以访问它
--------将在卸载应用程序时删除
下面我们仅使用内部存储。

1、How to Work with Internal Storage

要将数据保存到内部存储,我们首先需要创建一个FileInputStream对象; 这可以通过Activity类的openFileInput方法进行管理。 打开文件进行写入时,将丢弃该文件的所有先前内容。 但是,可以打开一个文件,以便我们可以附加新内容,从而保留先前的内容。

FileOutputStream fout = openFileOutput(<name of file>, Context.MODE_APPEND);

如果您不想以追加模式打开文件,只需将Context.MODE_PRIVATE作为第二个参数传递。

准备好文件后,我们就可以将数据写入其中。

fout.write(<String data>);

openFileOutput和write方法都可以抛出Exceptions,因此需要通过重新抛出Exception或使用try-catch构造处理它们来处理它们。
在我们的示例中,我们使用try-catch块来处理可能的异常。
从内部存储读取数据同样简单。 这很像写数据的过程; 我们只需要准备一个FileInputStream然后从中读取。

FileInputStream fin = openFileInput(<name of file>);

文件输入是一个流。 基本思想是一次从块中读取一个块,直到我们到达文件末尾。
 

2、测试,建立项目名为InternalStorage

1). EditText应该捕获多行文本,因此您需要将其“inputType”属性设置为“textMultiline”。 您可以在设计模式下在属性检查器窗口中执行此操作,或者,您可以直接在XML文件中写入。
2)我们不会以编程方式处理点击事件,所以我们设置了两个按钮的“onClick”属性的值

 在MainActivity中saveData方法与“SAVE”按钮相关联,而loadData与“LOAD”按钮相关联。

 

package com.example.administrator.internalstorage;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
/*
editText is defined as a member variable because we will need to reference this from both
saveData and loadData
*/
    EditText editText;
    //File name is defined as a member variable for the same reason
    private String filename = "myfile.txt";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = (EditText) findViewById(R.id.editText);

    }

    public void saveData(View v) {
        String str = editText.getText().toString();

        FileOutputStream out = null;
        try {
            out = openFileOutput(filename, Context.MODE_APPEND);
            out.write(str.getBytes());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            // You should do more logging here
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            //We need to know that the FileOutputStream object is not null, before we proceed any further
            if (out != null) {
                try {
/*
This closes the file and releases any system resources associated with it.
This method may throw an “IOException”, hence the need for a nested try-catch construction
*/
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public void loadData(View v) {


        TextView tv = (TextView) findViewById(R.id.textView);
        FileInputStream in = null;
        StringBuilder sb = new StringBuilder();

        try {
            in = openFileInput(filename);

            int read = 0;
/*
The read method of the input stream reads one byte of data at a time, and when it reaches the
end of the file where there’s nothing more to read, it will return -1
*/
            while((read = in.read()) != -1) {
 //The in.read( ) method returns an int; we need to cast it to a char so we can use for the StringBuilder
                sb.append((char) read);
            }
 //When we get to the end of the file, we can convert the StringBuilder object to String and set it as the text for Text View

            tv.setText(sb.toString());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }


}

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值