安卓入门之本地存储

Android提供了在本地存储数据的功能,这在需求(比如保存登陆密码)和性能(缓存数据)方面是十分有用的。下面整理安卓中本地数据存储的知识,跟前面一样,我们将对比web中的数据存储来学习(PS:貌似还没有写过localStorage相关的博客呢~)

<!--more-->

参考:

  • 《第一行代码 Android》

1. 文件存储

在web中,由于安全限制,数据持久化的实现一般是依赖浏览器实现的,从最初的cookie存储到HTML5中的localStoragesessionStorage,以及Web SQL Databases等。

在Android中,可以使用系统原生的文件IO功能,因此在硬盘上存储数据变得简单很多。 其具体实现就是:把数据以文件的形式导出到硬盘上,再需要的时候再通过读取文件来恢复数据。

1.1. 存储数据

public void saveFileData(View view){
    String data = "Hello file";
    FileOutputStream out = null;
    BufferedWriter writer = null;
    try {
        out = openFileOutput("testData", Context.MODE_PRIVATE);
        writer = new BufferedWriter(new OutputStreamWriter(out));
        writer.write(data);
    }catch (IOException e){
        e.printStackTrace();
    }finally {
        try {
            if (writer != null){
                Log.i(TAG, "done~~");
                writer.close();
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

上面的方法新建了一个testData的文件,并向其中写入了需要保存的数据"Hello file",然后打开DDMS,沿着路径/data/data/应用包名/files可以查看对应的文件了,将文件导入查看,可以发现实际上就是一个纯文本文件(如果使用DDMS遇见了问题,请参考最后一章,因为我也遇见了很多问题~)。

1.2. 读取文件数据

纯文本文件意味着,我们读取获得的就是整个文件内容(一个字符串),如果需要获得特定的内容,则需要按照一定的格式存储(比如按行或特定的分隔符啥的),最后使用对应的格式进行解析

public void getFileData(View view){
    FileInputStream in = null;
    BufferedReader reader = null;
    StringBuilder sb = new StringBuilder();
    try {
        in = openFileInput("testData");
        reader = new BufferedReader(new InputStreamReader(in));
        String line = "";
        while ((line = reader.readLine()) != null){
            sb.append(line);
        }
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        if (reader != null){
            try {
                // 获取到了文件内容
                Log.i(TAG, sb.toString());
                reader.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

通过StringBuilder对象逐行拼接文件内容,最后输出的就是一个字符串~

1.3. 小结

通过纯文本来保存一些简单的文本信息还好(比如保存用户的编辑草稿?),如果保存一些有特定格式的数据,可能就不是很实用了。

PS:Java中的异常处理是强制的,对于程序的健壮性来说是好的,但总归有点不太习惯

2. SharedPreferences

上面提到,使用纯文本文件来保存的数据,在读取部分特定数据时比较麻烦,在需求中,除了能够保存本地数据之外,更方便的读取数据也是一个很重要的因素。

Android提供了SharedPreferences,将数据以键值对的形式,以xml文件格式保存在本地,这样,在读取数据的时候只需要提供指定的键值,就可以获得对应的数据了。就接口的用法来将,我感觉SharedPreferences跟localStorage简直一模一样啊~

2.1. 存储数据

只需要获得SharedPreferences.Editor实例,然后调用对应的put接口就可以了,比起冗长的文件流保存和异常处理,这里的代码简单得多。

public void saveLocalData(View view){
      // xml文件夹名称为data
    SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
      // 存储的主要是java内置类型
    editor.putString("name", "txm");
    editor.putString("password", "123456");
      // editor.commit();
    editor.apply();
}

使用put接口之后,需要调用commitapply进行确认,最终保存数据。关于这两个方法的区别,请移步这里,主要区别在于:

  • apply将数据提交到内存,然后异步保存到硬盘,因此效率更高,但是当连续调用apply时,后续调用apply可能覆盖前面apply的数据
  • commit将数据同步保存到磁盘上,因此效率慢,但是可以返回一个操作状态的布尔值

在执行存储操作之后,打开DDMS,我们可以在data/data/包名/shared_prefs文件夹下发现对应的xml文件了,可以导出查看具体的文件内容。(同上,如果DDMS出现问题,请参考最后一章)

2.2. 读取数据

同理,读取数据只需要获得SharedPreferences实例,然后调用对应的get接口即可。

public void getLocalData(View view){
    SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
    // 第二个参数是默认值
    String name = pref.getString("name", "");
    String password = pref.getString("password", "");
    Log.i(TAG, "name: " + name);
    Log.i(TAG, "password: " + password);
}

2.3. 小结

使用xml保存数据的应用范围就广一些了,比如保存登陆密码,用户偏好设置啥的。但是如果本地缓存重复字段的列表数据,可能也不是那么方便,接下来就是数据存储最重要的部分SQLite了。

3. SQLite

Android系统内置了这个SQLite的关系型数据库,可以很方便的实现数据存储。数据库是一个很广泛的概念,这里只能先整理基础使用方法。

3.1. 新建数据库

新建数据库需要定义一个继承自SQLiteOpenHelper的Helper类,然后重写onCreateonUpgrade方法

public class MyDataBaseHelper extends SQLiteOpenHelper{
    public static final String TAG = "shymean";

    public static final String CREATE_USER = "create table user (id integer primary key autoincrement, name text, password text)";

    private Context mContext;

    public MyDataBaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int versrion){
        super(context, name, factory, versrion);
        mContext = context;
    }
    @Override
    public void onCreate(SQLiteDatabase db){
          // 第一次创建的运行
        db.execSQL(CREATE_USER);
        Toast.makeText(mContext, "create success", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
           // todo
    }
}

然后在对应的Activity中实例化一个数据库对象

db = new MyDataBaseHelper(this, "Test.db", null, 1);
dbHelper.getWritableDatabase();
// 然后根据dbHelper操作对应的数据库Test数据库

打开DDMS,修改模拟器权限啥的之后进入data/data/包名/databases中就可以看见对应的数据库文件Test.db了(还有一个Test.db-journal文件,该文件是一个临时的日志文件,用于数据库事务的回滚机制,可以暂时先不用理会)

# 进入数据库
sqlite3 Test.db
# 查看表
.tables

可以看见刚才新增了user表,除此之外还有一个默认的android_metadata表,此时就可以执行相关的sql语句了。

3.2. CRUD

插入数据

public void addData(View view){
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    ContentValues values = new ContentValues();

    values.put("name", "txmmm");
    values.put("password", "123456");
    db.insert("user", null, values);

    Log.i(TAG, "add data...");
}

更新数据

public void updateData(View view){
    SQLiteDatabase db = dbHelper.getWritableDatabase();

    ContentValues values = new ContentValues();
    values.put("name", "updateTXX");
    db.update("user", values, "id = ?", new String[]{"1"});
}

删除数据

public void removeData(View view){
    SQLiteDatabase db = dbHelper.getWritableDatabase();

    db.delete("user", "id > ?", new String[]{"3"});

}

查询数据

public void queryData(View view){
    Log.i(TAG, "get data...");
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    Cursor cursor = db.query("user", null, null, null, null, null, null);
    if (cursor.moveToFirst()){
        do {
            String name = cursor.getString(cursor.getColumnIndex("name"));
            Log.i(TAG, "name: " + name);
        }while (cursor.moveToNext());
    }
    cursor.close();
}

这里的query方法接收的参数表示对应的查询子句

3.3. 原生sql语句

实际上上面的这些方法都是Android对SQLite进行的封装,SQLite的语法跟其他sql语言的语法基本相似,我们可以使用execSQL执行原生的数据库操作

db.execSQL("insert into user (name, password) values(?, ?)", new String[]{"txm", "123456"});

是不是很赞?

3.4. 小结

这里粘了很多代码上来,也许几个月回头看就会觉得这些代码太丑陋了,现在先将就着吧,之前用PHP和NodeJS的时候也封装过一些数据库类,这里学起来也没有那么晦涩。总之,SQLite应该是客户端开发中比较重要的东西,还是得慢慢学习。

4. DDMS的问题

在刚开始使用AS中模拟器的DDMS的时候遇见了不少问题,这里一并整理。

4.1. 打开data文件夹是空的?

如果发现根目录下的data文件夹是空,这是因为没有相关目录权限的关系,找到你的Android Studio存放sdk的目录,然后切换到sdk/platform-tools,打开控制台,输入下面命令

adb shell
su
chmod 777 /data 
# 如果只是想修改某个应用包的权限,把*替换成对应文件名即可
chmod 777 /data/data * -R

4.2. 文件无法导出

修改文件的访问权限就可以在目录中看见文件的存在了,但是却无法查看文件内容,这是因为模拟器权限的缘故

adb root
adb remount

# 出现 remount succeeded则成功

此时选择文件然后右上角的pull a file from the device即可

5. 小结

这篇文章粘贴了大量的代码,却没有啥干货,先挖些坑后面填吧。