Android 数据存储

.1. **Android系统中的三种存储方式 **

  • 文件存储
  • SharedPreference存储
  • SQLite存储

.2. 文件存储

文件存储又可分为内部存储(Internal storage)和外部存储(External storage).

1.内部存储

  • 总是可用的,程序默认将文件保存在这里
  • 当程序被卸载时,保存在这里的文件是默认全部被移除的

2.外部存储

  • 保存的文件可以随时读取,并且所有程序都可以获得这个文件的访问权
  • 当程序被卸载时,系统会移除这些文件,但是如果你在getExternalFileDir() 方法获得目录下保存文件的话,它将不会被移除

3.两种方法的比较

  • 如果不想要文件被用户或者其他的app访问,那么内部存储是一个不错的选择
  • 如果是保存音乐,图片或者视屏类的文件,我们通常不希望应用程序被卸载时文件也被移除,所以最好选用外部存储的方式

4.使用内部存储 存储和读取文件

Context类提供了一个openFileOutput()方法,用于将数据存储到指定文件中,这个方法接受两个参数,第一个参数是文件名,第二个是文件的操作模式,有MODE_PRIVATE和MODE_APPEND两种方式,MODE_PRIVATE是系统默认的操作方式,当有相同文件名的文件时,这种方式会覆盖原文件,MODE_APPEND表示当文件存在时,追加内容,不存在则创建文件,这个方法返回一个FileOutputStream对象。

Context 类还提供了一个openFileInput()方法,用于从文件中读取数据,这个方法只接收一个参数,即要读取的文件名,返回一个FileInputStream对象,得到这个对象后,可以通过Java流的方式获得数据。

public void save(View view) {

        FileOutputStream out  = null;
        BufferedWriter writer = null;
        String data = editText.getText().toString();
        try{
            out = openFileOutput("data",MODE_PRIVATE);
            writer = new BufferedWriter(new OutputStreamWriter(out));
            writer.write(data);
        }catch (IOException e){
            e.printStackTrace();
        } finally {
            if (writer != null){
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
 public void load(View view) {

        FileInputStream in = null;
        BufferedReader reader = null;
        StringBuilder builder = new StringBuilder();

        try {
            in = openFileInput("data");
            reader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            while((line = reader.readLine())!=null){
                builder.append(line);
            }
        }catch (IOException e){

        } finally {
            if (reader !=null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        editText.setText(builder.toString());
    }

Tips:

为了提升保存效率以及防止引起IOException异常,可以通过getFreeSpace()或者getTotalSpace()查看当前是否有足够的内存去保存文件。

这种方式保存文件,文件默认保存在 /data/data/<package name>/files/目录下,可以借助DDMS下的File Explorer找到该文件。

5.使用外部存储保存文件

通过getExternalStorageState()方法查询外部设备是否可用,当返回值为MEDIA_MOUNTED时,才可以读取和写入文件。

.3.SharedPreferences存储

  • getSharedPreferences(String fileName, int mode)
    第一个参数为文件名称,第二个参数为操作模式,有MODE_PRIVATE 和MODE_MULTI_PROCESS两种。MODE_PRIVATE 仍然是系统默认的选择,和传入0效果相同,表示只有当前应用才可以对这个SharedPreferences文件进行读取。MODE_MULTI_PROCESS一般是用于有多个进程对同一个SharedPreferences进行读写的情况。
    这种方法定义的文件,你可以通过文件名在app任何一个Context中访问。

  • getPreferences(int mode)
    这种方式只接收一个参数,也即操作模式,使用这个方式将默认当前活动的类名为SharePreferences文件的名称。这种方式定义的文件默认属于这个Activity,不需要提供文件名称。

  • getDefaultSharedPreferences(Context context)
    这个方法是PreferenceManager类中的一个静态方法,接受一个Context参数,并自动使用当前应用程序的包名作为前缀命名SharedPreferences文件。

获取到SharedPreferences对象后,就可以开始向SharedPreferences文件存储数据了,一般可分为三步:

  1. 调用SharedPreferences的edit()方法,获取一个SharedPreferences.Editor对象。
  2. 向SharedPreferences.Editor对象中添加数据。
  3. 调用commit()方法提交数据。
public void save(View view){
        SharedPreferences sharedPreferences = getSharedPreferences("data",MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        
        editor.putString("name","value");
        editor.putInt("age",20);
        editor.putBoolean("alive",true);
        
        editor.commit();
    }
}


public void load(View view){
        SharedPreferences reader = getSharedPreferences("data",MODE_PRIVATE);
        
        String name = reader.getString("name", ""); //第二个参数为默认值
        int age = reader.getInt("age",0);
        Boolean alive = reader.getBoolean("alive",false);
    }

.4.SQLite数据库存储

为了方便的管理数据库,Android系统专门提供了一个抽象帮助类SQLiteOpenHelper,借助这个类可以对数据库进行创建和升级。SQLiteOpenHelper是抽象类,这意味着我们不能对它使用new方法,因此我们必须自定义一个类去继承他,并实现内部的抽象方法。

SQLiteOpenHelper内部包含两个抽象方法onCreate()和onUpgrade()方法,顾名思义,一个用来创建数据库,一个用来升级数据库。

SQLiteOpenHelper还有两个重要的实例方法 :

  1. getReadableDatabase() : 创建或者打开一个现有的数据库,如果数据库不可写入,将以只读方式打开。
  2. getWritableDatabase() : 创建或者打开一个现有的数据库,如果数据库不可写入,将会出现异常。

使用SQLite数据库存储文件通常需要三步 :

  1. 自定义类继承SQLiteHelper并实现其内部抽象方法。
  2. 利用getWritableDatabase() 或者 getReadableDatabase() 获得操作对象。
  3. 进行C(Create) R(Retrieve) U(Update) D(Delete)操作。

具体实现如下所示:

根据Google开发者文档的建议,我们最好定义一个合约类去管理我们所有的建表语句,并将它们设置为全局变量,以便Activity统一访问。

public final class TableManager {

    public TableManager(){}

    public static abstract  class PERSON_TABLE{
        public static final String ID = "id";
        public static final String PERSON_NAME = "name";
        public static final String PERSON_AGE = "age";
        public static final String PERSON_SEX = "sex";
        public static final String TABLE_NAME = "person";
        public static final String DATABASE_NAME = "db_person";

        public static final String CREATE_TABLE = "create table "+TABLE_NAME +"("
                + ID + " integer primary key autoincrement, "
                +PERSON_NAME + " text, "
                +PERSON_AGE + " integer, "
                +PERSON_SEX + " text)";

    }
}

新建MyDatabaseHelper继承自SQLiteOpenHelper:

public class MyDatabaseHelper extends SQLiteOpenHelper{
    
    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
            
        db.execSQL(TableManager.PERSON_TABLE.CREATE_TABLE);
    }

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

    }
}

之后就可以在Activity中创建数据库 :

public class MainActivity extends AppCompatActivity {
    
    private MyDatabaseHelper helper;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        helper = new MyDatabaseHelper(this,TableManager.PERSON_TABLE.DATABASE_NAME,null,1);
        Button button = (Button) findViewById(R.id.create);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                helper.getWritableDatabase();
            }
        });
        
    }
  }

这样就已经创建好数据库了,在File Explorer中的databases目录下可以查找到,但是.db文件是无法通过File Explorer查看到的,因此可以通过adb shell 来查看。

接下来介绍数据库的基本操作 :

  • 添加数据

    使用SQLiteDatabase的insert()方法可以往数据库中添加数据,这个方法接收三个参数。第一个参数是要插入的表的名字,第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值null,一般直接传入null即可,第三个参数为ContentValues对象。

public void addData(View view) {

            SQLiteDatabase database = helper.getWritableDatabase();

            ContentValues values = new ContentValues();
            
            values.put(TableManager.PERSON_TABLE.PERSON_NAME,"author");
            values.put(TableManager.PERSON_TABLE.PERSON_AGE,20);
            values.put(TableManager.PERSON_TABLE.PERSON_SEX,"男");
            
            database.insert(TableManager.PERSON_TABLE.TABLE_NAME,null,values);
 }
  • 更新数据

    使用SQLiteDatabase中的update()方法可以对数据进行更新,这个方法接收四个参数。第一个参数还是表名,第二个参数为ContentValues对象,把要更新的信息装入即可,第三四个参数用于约束更新某一行或者某几行的数据,不指定的话就是默认更新所有行。

 public void updateData(View view){
            
         SQLiteDatabase database = helper.getWritableDatabase();
            
         ContentValues values = new ContentValues();
            
         values.put(TableManager.PERSON_TABLE.PERSON_AGE,100);
            
         database.update(TableManager.PERSON_TABLE.TABLE_NAME,
         values,
         TableManager.PERSON_TABLE.PERSON_NAME + " = ? ", 
         new String[]{"author"});
}
  • 删除数据

    SQLiteDataBase提供了delete()方法,用于对数据的删除,这个方法和update()方法类似。这个方法接受三个参数,第一个参数是表名,第二三参数用于约束删除某一行和几行的数据,不指定的话就是默认删除所有行。

public void deleteData(View view){
            
            SQLiteDatabase database = helper.getWritableDatabase();
            
            database.delete(TableManager.PERSON_TABLE.TABLE_NAME,
                    TableManager.PERSON_TABLE.PERSON_NAME +" = ?",
                    new String[]{"author"});
}
  • 查询数据

SQLiteDatabase提供了query()方法去查询数据,不过query()方法接受的参数比较多,最短的重载方法也含有七个参数。

  1. table_name,指定查询的表名
  2. select column,指定查询的列名
  3. column selection,指定where的约束条件
  4. selectionArgs,为where 中的占位符提供具体的值
  5. groupBy,指定需要group by的列
  6. having, 对group by后的结果进行进一步的约束
  7. orderBy,指定查询结果的排序方式
 public void queryData(View view){
        
        SQLiteDatabase database = helper.getWritableDatabase();

        Cursor cursor = database.query(TableManager.PERSON_TABLE.TABLE_NAME,null,null,null,null,null,null);
        if (cursor.moveToFirst()){
            do {
                String name = cursor.getString(cursor.getColumnIndex(TableManager.PERSON_TABLE.PERSON_NAME));
                String sex = cursor.getString(cursor.getColumnIndex(TableManager.PERSON_TABLE.PERSON_SEX));
                int age = cursor.getInt(cursor.getColumnIndex(TableManager.PERSON_TABLE.PERSON_AGE));
                
            }while (cursor.moveToNext());
        }
        cursor.close();
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容

  • 之前闲着无聊,研究了下 Android 存储方面的知识,顺便翻译了下官方文档(虽然有已经被翻译过...)。这里就算...
    koguma阅读 969评论 0 3
  • 一、使用SharedPreferences存储数据 默认存储路径:/data/data/ /shared_pref...
    zoky_阅读 872评论 0 1
  • 译自android.com 安卓为你是实现持久化数据提供了几种选择。具体的方案选择取决于你的特定需要,比如存储的数...
    gdut6049阅读 624评论 1 5
  • 前面两篇文章Android数据存储(一)和Android数据存储(二)分别使用文件存储、SharedPrefere...
    前端develop阅读 6,270评论 0 12
  • 那个人摆摊 卖上帝 标了价 还要赠送条香肠 在这大热天。
    留子尧阅读 250评论 3 5