zoukankan      html  css  js  c++  java
  • Android 开发-----数据存储

    数据一般有以下保存方式实现:

    • SharedPreferences 
    • 采用java.io.* 库所提供的I/O 接口,读写文件。
    • SQLite 数据库
    • ContentProvider 

    一.SharedPreferences 

      SharedPreferences 是一种轻量级的数据保存方式,比较类似于我们常用的ini文件,用来保存运用程序的一些属性设置,较简单的参数设置。SharedPreferences将NVP(Name/Value Pair,键值对)保存在Android的文件系统,为XML文件。SharedPreferences 将对文件系统的操作过程封装起来,开发人员仅通过SharedPreferences 的API 即可完成读写过程。

    1.三种数据访问模式

    • 私有(MODE_PRIVATE):仅创建程序可读写。
    • 全局读(MODE_WORLD_READABLE): 创建程序可读写,其他程序可读不可写。
    • 全局写(MODE_WORLD_WRITEABLE):创建程序可读写,其他程序都可写不可读。

    注意:根据官网文档,后两种全局模式在API 17 之后已被弃用(deprecated),强烈不推荐使用SharedPreferences 来实现数据共享。开发者应该选择ContentProvider, 广播和服务等数据共享方式。Android N 中使用这两种模式会抛出异常。

    2.SharedPreferences的使用

    a.获取SharedPreferences 

    • getSharedPreferences() — 如果需要多个通过名称参数来区分的shared preference文件, 名称可以通过第一个参数来指定。可在app中通过任何一个Context 执行该方法。
    Context context = getActivity();
    SharedPreferences sharedPref = context.getSharedPreferences(
            "SharedPreferences_file_name", Context.MODE_PRIVATE);
    • getPreferences() — 当activity仅需要一个shared preference文件时。因为该方法会检索activity下默认的shared preference文件,并不需要提供文件名称。
    SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

    b.写SharedPreferences 

      执行写操作需要同edit()创建一个SharedPreferences.Editor,然后通过类似putInt和putString的方法传递key-value,接着调用Editor 对象的commit() 或apply() 方法保存修改内容。两者的区别在于:commit() 函数会立即将修改结果同步地写入文件中。而apply() 则采用异步的方式写入文件。在主线程(UI线程)中,建议使用apply() 而非commit() 以防阻塞。

      支持数据类型:boolean, float, int, long, String 和Set<String>.

    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putBoolean("RunInBackGround", true);
    editor.putFloat("Height", 163.5f);
    editor.putInt("Age", 20);
    editor.putLong("TimeStamp", 1476931843L);
    editor.putString("Name", "Tom");
    Set<String> tags = new HashSet<>();
    tags.add("Android");
    editor.putStringSet("Tags", tags);
    editor.apply();

    c.读SharedPreferences 

     为了从shared preference中读取数据,可以通过类似于 getInt() 及 getString()等方法来读取。在那些方法里面传递我们想要获取的value对应的key,并提供一个默认的value作为查找的key不存在时函数的返回值。如下:

    SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
    int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
    long highScore = sharedPref.getInt(getString(R.string.saved_high_score), default);  

    二.文件存储

      所有的Android设备均有两个文件存储区域:"internal" 与 "external" 。 这两个名称来自于早先的Android系统,当时大多设备都内置了不可变的内存(internal storage)及一个类似于SD card(external storage)这样的可卸载的存储部件。之后有一些设备将"internal" 与 "external" 都做成了不可卸载的内置存储,虽然如此,但是这一整块还是从逻辑上有被划为"internal"与"external"的。只是现在不再以是否可卸载进行区分了。 下面列出了两者的区别:

    • Internal storage:

      • 总是可用的
      • 这里的文件默认只能被我们的app所访问。
      • 当用户卸载app的时候,系统会把internal内该app相关的文件都清除干净。
      • Internal是我们在想确保不被用户与其他app所访问的最佳存储区域。
    • External storage:

      • 并不总是可用的,因为用户有时会通过USB存储模式挂载外部存储器,当取下挂载的这部分后,就无法对其进行访问了。
      • 是大家都可以访问的,因此保存在这里的文件可能被其他程序访问。
      • 当用户卸载我们的app时,系统仅仅会删除external根目录(getExternalFilesDir())下的相关文件。
      • External是在不需要严格的访问权限并且希望这些文件能够被其他app所共享或者是允许用户通过电脑访问时的最佳存储区域。
      • 无论是否支持SD 卡等外置存储设备,所有Android 手机都将存储空间划分为「内部存储」与「外部存储」两部分。

      一般默认安装到internal storage的,可以通过在manifest文件中声明android:installLocation来指定安装到external storage中。 

    1.Internal storage:

      Android 系统允许应用程序创建仅能够自身访问的私有文件,文件保存在设备的内部存储器上,路径为:/data/data/<package name>/files/。

    Android系统支持四种文件操作模式:

    • MODE_PRIVATE私有模式,文件仅能够被文件创建程序访问,或具有相同UID的程序访问。
    • MODE_APPEND追加模式,如果文件已经存在,则在文件的结尾处添加新数据。
    • MODE_WORLD_READABLE全局读模式,允许任何程序读取私有文件。
    • MODE_WORLD_WRITEABLE全局写模式,允许任何程序写入私有文件

    获取目录:

    • getFilesDir() : 返回一个File,代表了我们app的internal目录。
    • getCacheDir() : 返回一个File,代表了我们app的internal缓存目录。请确保这个目录下的文件能够在一旦不再需要的时候马上被删除,并对其大小进行合理限制,例如1MB 。系统的内部存储空间不够时,会自行选择删除缓存文件。

    可以使用File构造器在目录下创建新的文件,如下:

    File file = new File(context.getFilesDir(), filename);
    

    获取/新建文件:

    openFileOutput():

      用于写入数据,如果指定文件不存在,则创建一个新的文件 

      函数签名:public FileOutputStreamopenFileOutput(String name, intmode),返回FileOuputStream对象

    Stringfilename ="myfile";
    Stringstring="Helloworld!";
    FileOutputStreamoutputStream;
        try{
            outputStream=openFileOutput(filename,Context.MODE_PRIVATE);
            outputStream.write(string.getBytes());
            outputStream.close();
        }catch(Exceptione){
            e.printStackTrace();
        }            

    openFileInput():

      用于打开一个与应用程序关联的私有文件输入流

      函数签名:public FileInputStreamopenFileInput(String name)

      当指定文件名对应的文件不存在时,会抛出FileNotFound异常

    FileInputStreamfileInputStream;
    final String FILE_NAME = "file.txt",
    try {
      fileInputStream= openFileInput(FILE_NAME);
      byte[] contents = new byte[fileInputStream.available()];
    fileInputStream.read(contents);
    } catch (IOExceptionex) {
      Log.e("TAG", "Fail to read file.");
    }

     如果需要缓存一些文件,可以使用createTempFile()。例如:下面的方法从URL中抽取了一个文件名,然后再在程序的internal缓存目录下创建了一个以这个文件名命名的文件。 

     public File getTempFile(Context context, String url) {
        File file;
        try {
            String fileName = Uri.parse(url).getLastPathSegment();
            file = File.createTempFile(fileName, null, context.getCacheDir());
        catch (IOException e) {
            // Error while creating file
        }
        return file;
    }  

    2.external storage:

    获取External存储的权限:

    要往外部存储控件中写入文件,必须在AndroidManifest.xml 中声明权限。

    <manifest ...>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        ...
    </manifest>
    

    检查External存储是否可用:

      因为外部存储可能是不可用的,比如遇到SD卡被拔出等情况时。因此在访问之前应对其可用性进行检查。我们可以通过执行 getExternalStorageState()来查询external storage的状态。若返回状态为MEDIA_MOUNTED, 则为可用。

     /* 检查外部存储可读写*/
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }
    
    /* 检查外部存储至少可读*/
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
            Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }
    

    类型: 

    • Public files :这些文件对与用户与其他app来说是public的,当用户卸载我们的app时,这些文件应该保留。例如,那些被我们的app拍摄的图片或者下载的文件。
    • Private files: 这些文件完全被我们的app所私有,它们应该在app被卸载时删除。尽管由于存储在external storage,那些文件从技术上而言可以被用户与其他app所访问,但实际上那些文件对于其他app没有任何意义。因此,当用户卸载我们的app时,系统会删除其下的private目录。例如,那些被我们的app下载的缓存文件。

      想文件已public形式保存在外部存储中,getExternalStoragePublicDirectory(String))来获取目录,其中的String 参数指的是目录类型,可以为DIRECTORY_MUSIC, DIRECTORY_PICTURES 等。若无指定类型,可以传入null,函数则会返回files 目录本身。

    public File getAlbumStorageDir(String albumName) {
        // Get the directory for the user's public pictures directory.
        File file = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), albumName);
        if (!file.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created");
        }
        return file;
    }
    

      想文件已private形式保存在外部存储中,getExternalFilesDir()来获取目录。

    检查剩余空间:

      可以通过执行getFreeSpace() or getTotalSpace() 来判断是否有足够的空间来保存文件,从而避免发生异常。

    删除文件:

    myFile.delete();
    

      如果文件是保存在internal storage,我们可以通过Context来访问并通过执行deleteFile()进行删除

    myContext.deleteFile(fileName);
    

    Note:

    当用户卸载我们的app时,android系统会删除以下文件:

    • 所有保存到internal storage的文件。
    • 所有使用getExternalFilesDir()方式保存在external storage的文件。

    通常来说,我们应该手动删除所有通过 getCacheDir() 方式创建的缓存文件,以及那些不会再用到的文件。

    3.资源文件:

    如何读取原始格式文件: 

    • 首先调用getResource() 函数获得资源对象
    • 然后通过调用资源对象的openRawResource() 函数,以二进制流的形式打开文件
    • 在读取文件结束后,调用close()函数关闭文件流 
    InputStreaminput = this.getResources().openRawResource(R.raw.filename);
    byte[] reader = new byte[inputStream.available()];
    while (inputStream.read(reader) != -1) {}
    txt_text.setText(new String(reader,“utf-8”));//获得Context资源
    input.close();//关闭输入流
    

    读取XML格式文件:  

    首先通过调用资源对象的getXml() 函数,获取到XML解析器XmlPullParser。

    XmlPullParser parser = resources.getXml(R.xml.toys);
    //通过资源对象的getXml()函数获取到XML解析器
    while (parser.next() != XmlPullParser.END_DOCUMENT) {
    String toys = parser.getName(); // getName()函数获得元素的名称
    ......
    } 

    获取元素个数:

    int count = parser.getAttributeCount();

    获得属性名和属性值:

    String attrName= parser.getAttributeName(i); / /获得属性名
    String attrValue= parser.getAttributeValue(i); // 获得属性值
    

    XmlPullParser的XML事件类型:

    • START_TAG:读取到标签开始标志
    • TEXT:读取文本内容
    • END_TAG:读取到标签结束标志
    • END_DOCUMENT:文档末尾

      读取文件过程中遇到END_DOCUMENT 时停止分析

    三.SQLite数据库

    定义Schema与Contract:

      SQL中一个重要的概念是schema:一种DB结构的正式声明,用于表示database的组成结构。schema是从创建DB的SQL语句中生成的。我们会发现创建一个伴随类(companion class)是很有益的,这个类称为合约类(contract class),它用一种系统化并且自动生成文档的方式,显示指定了schema样式。  

      Contract Clsss是一些常量的容器。它定义了例如URIs,表名,列名等。这个contract类允许在同一个包下与其他类使用同样的常量。 它让我们只需要在一个地方修改列名,然后这个列名就可以自动传递给整个code。

      组织contract类的一个好方法是在类的根层级定义一些全局变量,然后为每一个table来创建内部类。

    Note:通过实现 BaseColumns 的接口,内部类可以继承到一个名为_ID的主键,这个对于Android里面的一些类似cursor adaptor类是很有必要的。这么做不是必须的,但这样能够使得我们的DB与Android的framework能够很好的相容。

    public final class FeedReaderContract {
        // To prevent someone from accidentally instantiating the contract class,
        // give it an empty constructor.
        public FeedReaderContract() {}
    
        /* Inner class that defines the table contents */
        public static abstract class FeedEntry implements BaseColumns {
            public static final String TABLE_NAME = "entry";
            public static final String COLUMN_NAME_ENTRY_ID = "entryid";
            public static final String COLUMN_NAME_TITLE = "title";
            public static final String COLUMN_NAME_SUBTITLE = "subtitle";
            ...
        }
    }
    

      更具定义好的结构,然后定义对应的创建,增删改查等语句。

    private static final String TEXT_TYPE = " TEXT";
    private static final String COMMA_SEP = ",";
    private static final String SQL_CREATE_ENTRIES =
        "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" +
        FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
        FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
        FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
        ... // Any other options for the CREATE command
        " )";
    
    private static final String SQL_DELETE_ENTRIES =
        "DROP TABLE IF EXISTS " + TABLE_NAME_ENTRIES; 

    使用SQL Helper创建DB:

      在SQLiteOpenHelper 类中有一些很有用的APIs。当使用这个类来做一些与db有关的操作时,系统会对那些有可能比较耗时的操作(例如创建与更新等)在真正需要的时候才去执行,而不是在app刚启动的时候就去做那些动作。我们所需要做的仅仅是执行getWritableDatabase()或者getReadableDatabase().

    public class FeedReaderDbHelper extends SQLiteOpenHelper {
        // If you change the database schema, you must increment the database version.
        public static final int DATABASE_VERSION = 1;
        public static final String DATABASE_NAME = "FeedReader.db";
    
        public FeedReaderDbHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(SQL_CREATE_ENTRIES);
        }
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // This database is only a cache for online data, so its upgrade policy is
            // to simply to discard the data and start over
            db.execSQL(SQL_DELETE_ENTRIES);
            onCreate(db);
        }
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            onUpgrade(db, oldVersion, newVersion);
        }
    }
    

    实例化自定义的SQLiteOpenHelper的子类:

    FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
    

    然后通过getWritableDatabase()或getReadableDatabase()来获取DB对象:

    SQLiteDatabase db = mDbHelper.getWritableDatabase();
    

    添加数据:

      我们利用ContentValues对象来添加数据:

      首先构造一个ContentValues对象,然后调用ContentValues对象的put()方法,将每个属性的值写入到ContentValues对象中,最后使用SQLiteDatabase对象的insert()函数,将ContentValues对象中的数据写入指定的数据库表中。

    SQLiteDatabase db = mDbHelper.getWritableDatabase();
    
    // Create a new map of values, where column names are the keys
    ContentValues values = new ContentValues();
    values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id);
    values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);
    values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content);
    
    // Insert the new row, returning the primary key value of the new row
    long newRowId;//insert()函数的返回值是新数据插入的位置,即ID值。
    newRowId = db.insert(
             FeedReaderContract.FeedEntry.TABLE_NAME,
             FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE,
             values);
    

      put方法的参数是一个键值对,第1个参数是名称(列名称),第2个参数是值.

      insert()函数中,第1个参数是数据表的名称,第2个参数是在NULL时的替换数据,第3个参数是需要向数据库添加的数据。

    修改数据:

      更新数据使用到 update() 方法,其也是通过ContentValues对象实现。

      update(String table,ContentValuesvalues,String whereClause,String[] whereArgs);

    • table是表名;
    • values是要更新的数据;
    • whereClause:满足whereClause子句的记录将会被更新;

    whereArgs:为whereClause子句传入的参数

    删除数据:

      使用delete方法delete(String table,String whereClause,String[] whereArgs);

    • table是表名
    • whereClause指的是删除的条件
    • whereArgs用于为whereClause子句传入参数

    查询数据:

      从DB中查询数据使用到query()方法,查询结构返回一个 Cursor 对象。

      query()方法:Cursor android.database.sqlite.SQLiteDatabase.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)

      

       Cursor函数说明:

      

    例如:  

    Cursor cursor = mSQLiteDatabase.query(TABLE_NAME,
    new String[] { TABLE_ID, TABLE_NUM, TABLE_DATA },
    null, null, null, null, null);
    

      要查询在cursor中的行,使用cursor的其中一个move方法,但必须在读取值之前调用。一般来说应该先调用moveToFirst()函数,将读取位置置于结果集最开始的位置。对每一行,我们可以使用cursor的其中一个get方法如getString()getLong()获取列的值。对于每一个get方法必须传递想要获取的列的索引位置(index position),索引位置可以通过调用getColumnIndex()getColumnIndexOrThrow()获得。

    cursor.moveToFirst();
    long itemId = cursor.getLong(
        cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID)
    ); 
  • 相关阅读:
    动态规划5-多重背包
    动态规划4-完全背包
    利用dwebsocket在Django中使用Websocket
    Java学习笔记之:Spring MVC 环境搭建
    Struts2 国际化
    Java学习笔记之:Struts2.0 环境搭建
    LoadRunner:VuGen开发脚本步骤(二)
    LoadRunner:VuGen开发脚本步骤(一)
    Java学习笔记之:Java Servlet 过滤器配置
    Java学习笔记之:Java Servlet环境配置
  • 原文地址:https://www.cnblogs.com/slothccc/p/7391549.html
Copyright © 2011-2022 走看看