Android学习笔记——数据存储

来源:互联网 发布:腾讯 绝地求生 知乎 编辑:程序博客网 时间:2024/05/18 13:30

参考书籍:Android第一行代码(第二版).郭霖著

保证关键数据不丢失——数据持久化技术:提供了一种可以让数据在瞬时状态(保存在内存中的数据所处状态)和持久状态(保存在存储设备中的数据所处状态)之间进行转换的机制。

持久化技术广泛应用于各种程序设计领域。Android系统中主要提供了3种方式用于简单实现数据持久化功能:文件存储、sharedPreference存储和数据库存储。还可将数据保存在SD卡中,但前三种更简单和安全。

1、文件存储

最基本方式,不对存储内容进行任何格式化处理,所有数据原封不动地保存到文件中。适合于简单文本数据或二进制数据,如果想保存较复杂文本数据需定义自己的格式规范,方便入后解析数据。

(1)将数据存储到文件中
Context类提供了一个openFileOutput()方法,包含两个参数。第一个为文件名,不可包含路径(默认存到/data/data//files/目录下),第二个参数为文件操作模式(MODE_PRIVATE(默认模式,文件名相同时覆盖原文件内容)和MODE_APPEND(文件存在则追加内容,不存在则创建新文件),另外两种模式允许其他程序对文件进行读写操作,过于危险,已在4.2版本中被废弃)。
openFileOutput()返回一个FileOutputStream对象,得到后可使用Java流方式将数据写到文件中。
创建FilePersistenceTest项目,修改布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">    <EditText        android:id="@+id/edit"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:hint="Type something here" /></LinearLayout>

添加一个文本输入框用于输入文本内容。如果直接按下Back键,内容会丢失(瞬时数据,活动被销毁后被回收),现在需在数据被回收前存储到文件中。修改主程序:

public class MainActivity extends AppCompatActivity {    private EditText edit;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        edit = (EditText)findViewById(R.id.edit);    }    @Override    protected void onDestroy() {        super.onDestroy();        String inputText = edit.getText().toString();        save(inputText);    }    public void save(String inputText){        FileOutputStream out = null;        BufferedWriter writer = null;        try{            out = openFileOutput("data", Context.MODE_PRIVATE);            writer = new BufferedWriter(new OutputStreamWriter(out));            writer.write(inputText);        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (writer != null){                    writer.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }    }}

运行程序,输入内容,按下Back键关闭程序。可借助Android Device Monitor工具查看数据是否保存成功。AndroidStudio导航栏中Tools->Android找到此工具,进入data/data//files/目录,可以看到一个data文件。可点击右上方导出到电脑按钮在电脑上查看文件。
(2)从文件中读取数据
对应openFileInput()方法。接收一个文件名参数,返回FileInputStream对象。
修改主程序代码:

public class MainActivity extends AppCompatActivity {    private EditText edit;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        edit = (EditText)findViewById(R.id.edit);        **String inputText = load();        if (!TextUtils.isEmpty(inputText)){//非空判断方法,传入字符串为null和空字符串都返回true(一次进行两种空值判断)            edit.setText(inputText);            edit.setSelection(inputText.length());//将输入光标移动到文本的末尾位置            Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show();        }**    }    **public  String load(){        FileInputStream in = null;        BufferedReader reader = null;        StringBuilder content = new StringBuilder();        try{            in = openFileInput("data");            reader = new BufferedReader(new InputStreamReader(in));            String line = "";            while ((line = reader.readLine()) != null){                content.append(line);            }        } catch (IOException e) {            e.printStackTrace();        }finally {            if (reader != null){                try{                    reader.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        return content.toString();    }**

运行程序会发现输入框出现之前输入的内容。

我的data目录下一直是空的,找不到数据文件,但是运行结果数据可取出,表示数据明显已经保存。就是不知道文件在哪?

2、SharedPreference存储

使用键值对方式存储数据(支持多种不同的数据类型存储),比文件更方便。
(1)将数据保存到SharedPreferences中
首先要获取到SharedPreferences对象,方法主要有三种:
a.Context类中的getSharedPreferences()方法
接收两个参数:指定文件名(/data/data//shared_prefs目录下,文件不存在会创建),指定操作模式(只有MODE_PRIVATE一种,默认,和直接传入0效果相同,表示只有当前程序才可对此文件进行读写操作。其他操作模式已被废弃)。
b.Activity类中的getPreferences()方法
与a中方法相似,但只接收一个操作模式参数(自动将当前活动类名作为SharedPreferences文件名)
c.PreferenceManager类中的getDefaultSharedPreferences()方法
静态方法,接收一个Context参数,自动使用当前程序报名作为前缀命名文件。
向SharedPreferences文件中存储数据,分为三步实现:
a.调用SharedPreferences对象的edit()方法获取SharedPreferences.Editor对象
b.向SharedPreferences.Editor对象中添加数据,putBoolean()/putString()等
c.调用apply()方法提交数据完成存储。

例:新建SharedPreferencesTest项目,修改布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">    <Button        android:id="@+id/save_data"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Save data" /></LinearLayout>

修改主程序:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button saveData = (Button)findViewById(R.id.save_data);        saveData.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();                editor.putString("name", "Tom");                editor.putInt("age",27);                editor.putBoolean("married",false);                editor.apply();            }        });    }}

运行程序,点击按钮。可用与文件存储一样的方式查看数据文件(用xml格式管理数据的)。
(2)从SharedPreferences中读取数据
SharedPreferences对象中提供了一些列get()方法(与put()对应),接收两个参数:键,默认值(键找不到对应值时返回默认值)。
修改布局文件,添加一个恢复数据按钮。修改主程序:

Button restoreData = (Button) findViewById(R.id.restore_data);        restoreData.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE);                String name = pref.getString("name", "");                int age = pref.getInt("age",0);                Boolean married = pref.getBoolean("married", false);                Log.d(TAG, "name is " + name);                Log.d(TAG, "age is " + age);                Log.d(TAG, "married is" + married);            }        });

运行程序点击按钮,查看日志信息可看到设置的消息。
(3)实现记住密码功能
打开之前的BroadcastBestPractice项目,编辑登录界面布局,修改activity_login.xml代码:

。。。    <LinearLayout        android:orientation="horizontal"        android:layout_width="match_parent"        android:layout_height="wrap_content">        <CheckBox            android:id="@+id/remember_pass"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textSize="18sp"            android:text="Remember password"/>    </LinearLayout>    <Button        android:id="@+id/login"        android:layout_width="match_parent"        android:layout_height="60dp"        android:text="Login"/></LinearLayout>

CheckBox,复选框控件。修改该LoginActivity中代码:

public class LoginActivity extends BaseActivity {    **private SharedPreferences pref;    private SharedPreferences.Editor editor;    private CheckBox rememberPass;**    private EditText accountEdit;    private EditText passwordEdit;    private Button login;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_login);        **pref = PreferenceManager.getDefaultSharedPreferences(this);**        accountEdit = (EditText)findViewById(R.id.account);        passwordEdit = (EditText)findViewById(R.id.password);        **rememberPass = (CheckBox)findViewById(R.id.remember_pass);        login = (Button)findViewById(R.id.login);        boolean isRemember = pref.getBoolean("remember_password",false);        if (isRemember){            String account = pref.getString("account","");            String password = pref.getString("password", "");            accountEdit.setText(account);            passwordEdit.setText(password);            rememberPass.setChecked(true);        }**        login.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String account = accountEdit.getText().toString();                String password = passwordEdit.getText().toString();                //如果账号是admin,密码是123456,则登录成功                if (account.equals("admin") && password.equals("123456")){                    **editor = pref.edit();                   if (rememberPass.isChecked()){//检查复选框是否被选中                       editor.putBoolean("remember_password",true);                       editor.putString("account", account);                       editor.putString("password", password);                   }else {                       editor.clear();                   }                    editor.apply();**                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);                    startActivity(intent);                    finish();                }else {                    Toast.makeText(LoginActivity.this, "account or password is invalid", Toast.LENGTH_SHORT).show();                }            }        });    }}

运行程序,查看效果,一开始需输入用户名和密码,选中复选框后,成功登录,强制下线后回到登录界面会发现信息自动填充上去了。

3、SQLite数据存储

SQLite:轻量级关系型数据库,运算速度快占用资源少(通常几百KB内存)。支持标准SQL语法,遵循数据库ACID(原子性、一致性、隔离性、持久性)事务,不用设置用户名密码。存储大量复杂的关系型数据。

(1)创建数据库
提供了SQLiteOpenHelper帮助类对数据库创建升级。SQLiteOpenHelper是抽象类,需创建自己的类继承,其中有两个抽象方法onCreate()和onUpgrade()需重写,实现创建升级数据库的逻辑。
还有两个重要的实例方法:getReadableDatabase()和getWritableDatabase()。都可打开或创建现有数据库(没有则创建),并返回可对数据进行读写操作的对象。不同:当数据库不可写入时(如磁盘空间已满),前者返回的对象以只读方式打开数据库,后者将出现异常。
有两个构造方法可供重写(一般用参数少点的),接收四个参数:Context,数据库名,允许查询时返回自定义的Cursor(一般传入null),当前数据库版本号(用于升级操作)。

构建SQLiteOpenHelper实例后,再调用他的getReadableDatabase()/getWritableDatabase()方法就能创建数据库了(文件存放在/data/data//database/目录下)。此时onCreate()方法被执行(通常处理创建表的逻辑)。

例:新建Database项目,创建名为BookStore.db的数据库,并新建一张Book表。新建MyDatabaseHelper类继承自SQLiteOpenHelper:

public class MyDatabaseHelper extends SQLiteOpenHelper {    //将见表语句定义成字符串常量    public  static final String CREATE_BOOK = "create table Book ("            + "id integer primary key autoincrement, "            + "author text, "            + "price real, "            + "pages integer, "            + "name text)" ;    private Context mContext;    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {        super(context, name, factory, version);        mContext = context;    }    @Override    public void onCreate(SQLiteDatabase db) {        //在数据库创建完成时创建Book表        db.execSQL(CREATE_BOOK);        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();    }    @Override    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {    }}

修改activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <Button        android:id="@+id/create_database"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Create database" /></LinearLayout>

加入按钮(创建数据库)。修改主程序:

public class MainActivity extends AppCompatActivity {    private MyDatabaseHelper dbhelper;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        dbhelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);        Button createDatabase = (Button) findViewById(R.id.create_database);        createDatabase.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                dbhelper.getWritableDatabase();            }        });    }}

第一次点击按钮时,由于不存在BookStore.db数据库,所以会创建该数据库并调用MyDatabaseHelper的onCreate()方法,弹出已创建成功的信息。再次点击时,已经存在,不会再创建一次。

可使用adb shell查看创建情况。它时Android SDK自带的调试工具,可直接对连接在电脑上的手机或模拟器进行调试。存放在sdk的plateform-tools目录下(如C:\Users\JOJO\AppData\Local\Android\sdk\platform-tools),如想在命令行使用需先配置到系统环境变量中。打开命令行界面,输入adb shell会进入到设备控制台,然后使用cd命令进入到data/data//databases/目录下,并使用ls命令查看文件:
这里写图片描述
进入目录时如发现以上权限被拒绝的情况,这时输入su root申请root权限(手机上需允许,模拟器上不需要):
这里写图片描述
这时已可访问。目录下有两个文件:一个是创建的BookStore.db,另一个BookStore.db-jouunal则是让数据库支持事务而产生的临时日志文件(通常大小为0字节)。
接下来,借助sqlite命令打开数据库,键入sqlite3,后加数据库名即可:
这里写图片描述
数据库已打开,可以对其中的表进行管理了,键入.table命令查看表:
这里写图片描述
有两张表,android_metadata表是每个数据库会自动生成的。可通过.schema查看建表语句:
这里写图片描述
键入.exit或.quit可退出数据库编辑,再键入exit可退出设备控制台。

(2)升级数据库
再添加一张Category表用于记录图书分类:

public class MyDatabaseHelper extends SQLiteOpenHelper {    //将见表语句定义成字符串常量    public  static final String CREATE_BOOK = "create table Book ("            + "id integer primary key autoincrement, "            + "author text, "            + "price real, "            + "pages integer, "            + "name text)" ;    **public static final String CREATE_CATEGORY = "create table Category ("            + "id integer primary key autoincrement, "            + "category_name text, "            + "category_code integer)";**    private Context mContext;    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {        super(context, name, factory, version);        mContext = context;    }    @Override    public void onCreate(SQLiteDatabase db) {        //在数据库创建完成时创建Book表        db.execSQL(CREATE_BOOK);        **db.execSQL(CREATE_CATEGORY);**        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();    }    @Override    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        **db.execSQL("drop table if exists Book");        db.execSQL("drop table if exists Category");        onCreate(db);**    }}

用到onUpgrade()方法重新调用onCreate()方法(因为第二次运行程序不会被调用)。要让onUpgrade()能执行,需要用到SQLiteOpenHelper的版本号参数,只需传入比之前大的数,就可以让其得到执行,修改主程序:

public class MainActivity extends AppCompatActivity {    private MyDatabaseHelper dbhelper;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        **dbhelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);**        Button createDatabase = (Button) findViewById(R.id.create_database);        createDatabase.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                dbhelper.getWritableDatabase();            }        });    }}

重新运行程序,点击按钮,会再次弹出创建成功提示。可使用adb shell查看。
这里写图片描述

(3)添加数据
在Android中即使不去编写SQL语句,也能轻松完成所有CRUD操作,提供了一系列辅助性方法。SQLiteOpenHelper的两个实例方法getReadableDatabase()/getWritableDatabase()会返回SQLiteDatabase对象,借助这个对象就可对数据进行CRUD操作。
添加数据:修改activity_main.xml,加入用于添加数据的按钮。修改主程序:

public class MainActivity extends AppCompatActivity {    private MyDatabaseHelper dbhelper;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        dbhelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);        Button createDatabase = (Button) findViewById(R.id.create_database);        **Button addData = (Button) findViewById(R.id.add_data);        addData.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                SQLiteDatabase db = dbhelper.getWritableDatabase();                //SQLiteDatabase提供的insert()方法接收三个参数:                // 要添加数据的表名,                // 未指定添加数据的情况下给某些可为空的列自动赋NULL值(一般直接传null),                // ContentValues对象                ContentValues values = new ContentValues();                //开始组装第一条数据                values.put("name", "The Da Vinci Code");                values.put("author", "Dan Brown");                values.put("pages",454);                values.put("price", 16.96);                db.insert("Book",null,values);//插入第一条数据                //开始组装第二条数据                values.put("name", "The Lost Symbol");                values.put("author", "Dan Brown");                values.put("pages",510);                values.put("price", 19.95);                db.insert("Book",null,values);//插入第二条数据            }        });**        。。。

重新运行程序,点击添加按钮。可以打开数据库查看:
这里写图片描述

(4)更新数据
同样在布局文件中添加更新按钮。修改主程序:

 Button updateData = (Button) findViewById(R.id.update_data);        updateData.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                SQLiteDatabase db = dbhelper.getWritableDatabase();                ContentValues values = new ContentValues();                values.put("price", 10.99);                db.update("Book",values, "name = ?", new String[]{"The Da Vinci Code"});                //第三、四个参数用于约束更新某一行或某几行数据,默认更新所有行                //第三个参数对应SQL的where语句,?为占位符,通过第四个参数提供的字符串数组指定相应内容            }        });

运行程序,点击更新按钮,查看数据库:
这里写图片描述

(5)删除数据
同理,添加按钮,修改主程序:

 Button button = (Button) findViewById(R.id.delete_data);        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                SQLiteDatabase db = dbhelper.getWritableDatabase();                db.delete("Book", "pages > ?", new String[]{"500"});            }        });

删除页数大于500的书籍。运行程序,点击按钮。

(6)查询数据
这里写图片描述
查询方法query()的参数复杂,最少的也要传入七个参数。会返回一个Cursor对象。
同样添加一个查询按钮。修改主程序:

Button queryButton = (Button) findViewById(R.id.query_data);        queryButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                SQLiteDatabase db = dbhelper.getWritableDatabase();                //查询Book表中所有数据                Cursor cursor = db.query("Book", null,null,null,null,null,null);                if (cursor.moveToFirst()){                    do {                        //遍历Cursor对象,取出数据并打印                        String name = cursor.getString(cursor.getColumnIndex("name") );                        String author = cursor.getString(cursor.getColumnIndex("author"));                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));                        double price = cursor.getDouble(cursor.getColumnIndex("price"));                        Log.d("MainActivity", "book name is " + name);                        Log.d("MainActivity", "book author is " + author);                        Log.d("MainActivity", "book pages is " + pages);                        Log.d("MainActivity", "book price is " + price);                    }while (cursor.moveToNext());                }                cursor.close();            }        });

运行程序,查看日志信息。

(7)使用SQL操作数据库

这里写图片描述

阅读全文
0 0