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)
    ); 
  • 相关阅读:
    jvisualm 结合 visualGC 进行jvm监控,并分析垃圾回收
    linux 查看服务器cpu 与内存配置
    arthas 使用总结
    selinux contexts 安全上下文的临时更改
    Android 8.1 Doze模式分析(五) Doze白名单及Debug方式
    Window 任意窗口置顶软件Window TopMost Control
    Android ApkToolPlus一个可视化的跨平台 apk 分析工具
    SVN Please execute the 'Cleanup' command.
    Android 如何在64位安卓系统中使用32位SO库
    Android cmd命令查看apk是32位还是64位?
  • 原文地址:https://www.cnblogs.com/slothccc/p/7391549.html
Copyright © 2011-2022 走看看