Android学习 开发文档(Training)05 saving data

版权声明:转载标明来源 https://blog.csdn.net/qq_33347077/article/details/82108049

Saving Data

  • Saving key-value pairs of simple data types in a shared preferences
    file
  • Saving arbitrary files in Android’s file system
  • Using databases managed by SQLite

1. saving key-value sets

如果你需要存储一小部分的Key-value的数据,你可以使用SharedPreferenceAPI

1.1 get a handle to a sharedpreference

你可以通过调用两个方法中任何一个来创建一个新的sharedpreference文件或者访问一个已经存在的。

  • getSharedPreferences() — Use this if you need multiple shared preference files identified by name, which you specify with the first parameter. You can call this from any Context in your app.
  • getPreferences() — Use this from an Activity if you need to use only one shared preference file for the activity. Because this retrieves a default shared preference file that belongs to the activity, you don’t need to supply a name.

这里是通过第一种方法,name是在string文件中定义的内容,并且通过私有模式打开,这样这个文件就只能在这个app中使用

Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
        getString(R.string.preference_file_key), Context.MODE_PRIVATE);

When naming your shared preference files, you should use a name that’s uniquely identifiable to your app, such as “com.example.myapp.PREFERENCE_FILE_KEY”

如果只需要一个文件的话就可以使用第二种方法

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

1.2 write to shared preferences

为了写入数据,通过调用edit()方法创建一个 SharedPreferences.Editor。 Pass the keys and values you want to write with methods such as putInt() and putString(). Then call commit() to save the changes. For example:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

1.3 Read from Shared Preferences

call methods such as getInt() and getString(), 提供一个key值,并且可以选择性的提供一个默认的值,防止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), defaultValue);

2. saving files

2.1 choose internal or external storage

所有的android设备有两个存储区域,内部和外部存储。
这里写图片描述

结合自己使用手机的情况来总结一下,因为自己在使用手机的时候也发现,有些app的视频内容已经下下来了,但是在文件管理器里面就是找不到。这里应该是存储到了它内部的存储种。而且对于每一个app都有它自己对应的data文件夹且没有root的手机是看不到的。实际上对于内部存储和外部存储来说,他们都是存储在机身64g的内存中。只不过看不见的就叫内部存储,看得见的就叫外部存储。

2.2 obtain permissions for external storage

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

目前,所有的app都能够在没有特定的许可下读取外部的存储,但是在未来的发布版本可能改变。所以尽量进行权限说明

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

如果你的程序申请了write权限,那么就会自动有读取权限。

2.3 save a file on internal storage

getFilesDir()

Returns a File representing an internal directory for your app.

getCacheDir()

Returns a File representing an internal directory for your app’s temporary cache files. Be sure to delete each file once it is no longer needed and implement a reasonable size limit for the amount of memory you use at any given time, such as 1MB. If the system begins running low on storage, it may delete your cache files without warning.

为了在这些目录下创建一个新的文件,你可以使用File()的构造方法,将上面得到的目录放进去。

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

你也可以调用openFileOutput()得到一个文件输出流来在你的内部目录中写内容。

String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;

try {
  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
  outputStream.write(string.getBytes());
  outputStream.close();
} catch (Exception e) {
  e.printStackTrace();
}

如果你需要隐藏一些文件,你应该使用createTempFile()。下面的例子提取了文件的名字,并且在app的隐藏目录中新建了一个名称一致的文件。

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.4 save file on external storage

为了确保外部内存的可用性。需要调用getExternalStorageState()如果结果和MEDIA_MOUNTED一致,才能进行读写操作。

/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

/* Checks if external storage is available to at least read */
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

    Files that should be freely available to other apps and to the user. When the user uninstalls your app, these files should remain available to the user.
    For example, photos captured by your app or other downloaded files.

  • Private files

    Files that rightfully belong to your app and should be deleted when the user uninstalls your app. Although these files are technically accessible by the user and other apps because they are on the external storage, they are files that realistically don’t provide value to the user outside your app. When the user uninstalls your app, the system deletes all files in your app’s external private directory.
    For example, additional resources downloaded by your app or temporary media files.

如果想要将公共文件保存在外部的存储中,我们需要使用getExternalStoragePublicDirectory()得到一个File代表了外部存储的合适目录。 The method takes an argument specifying the type of file you want to save so that they can be logically organized with other public files, such as DIRECTORY_MUSIC or DIRECTORY_PICTURES. For example:

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;
}

如果你想存储只对app私有的文件,可以调用 getExternalFilesDir() 来得到合适的目录,然后把目录的名字传递进去。Each directory created this way is added to a parent directory that encapsulates all your app’s external storage files, which the system deletes when the user uninstalls your app.每个这样创建的目录都会被添加到打包所有app外部存储文件的父目录,并且会和app同时被删除。

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

如果没有已经定义好的子目录符合你的文件,你可以调用getExternalFilesDir() 并且传递null参数,然后会返回外部存储中app私有目录的根目录。

无论是哪种方法,使用API提供的目录常量名例如DIRECTORY_PICTURES是很重要的。这些目录名确保系统能够合理的处理这些文件。For instance, files saved in DIRECTORY_RINGTONES are categorized by the system media scanner as ringtones instead of music.

2.5 query free space

如果你通过调用 getFreeSpace() or getTotalSpace()提前知道你存储数据的大小,你就可以知道是否有足够的内存而不会出现IOException。这两个方法分别得到当前可用的和总共的存储空间大小。
然而系统不能保证 你就能写入getFreeSpace() 这么多的数据。如果你剩余的存储空间比你的数据多一些mb,或者系统剩余量比90%多,你才可以写入。
在一些情况你不知道数据大小的时候,你可以直接进行写入,然后捕获是否出现IOException

2.6 delete a file

myFile.delete();

如果这个文件存储在内部存储中,你可以通过context来定位i和删除。

myContext.deleteFile(fileName);

Note: When the user uninstalls your app, the Android system deletes the following:

All files you saved on internal storage
All files you saved on external storage using getExternalFilesDir().

However, you should manually delete all cached files created with getCacheDir() on a regular basis and also regularly delete other files you no longer need.

3. saving data in SQL database

数据库可以将一些重复的或者结构化的数据进行存储,比如聊天记录信息。
The APIs you’ll need to use a database on Android are available in the android.database.sqlite package.

3.1 Define a schema and contract

SQL数据库的一个主要原则就是schema,这是一个正式的数据库构建声明。schema反应在你创建数据库的SQL语句中。你会发现创建一个名叫contract的伙伴类是很有帮助的。伴侣类用一种系统的,自文档的方式定义了schena的布局。

一个contract类是一个常量的容器,定义了URIs,tables和columns,contract类允许您在同一个包中的所有其他类中使用相同的常量。
This lets you change a column name in one place and have it propagate throughout your code.

组织合同类的一个好方法就是将数据库的全局定义放在类的根level上,然后对每一行创建内部类。

For example, this snippet defines the table name and column names for a single table:

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";
        ...
    }
}

3.2 create a database using a SQL helper

一旦定义了database的外观,你应该implement方法来创建和维护数据库。

private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
    FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
    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 " + FeedEntry.TABLE_NAME;

就像你存储在设备内部存储的文件一样,android存储你的数据库在和app联系的私有盘空间。

有用的APIs是SQLIteOpenHelper类,当你使用这个类来获得数据库引用的时候,系统只有在需要的时候和非app启动的时候,才会执行创建或者更新数据库的这个长时间操作。你所需要的只是调用getWritableDatabase()或者getReadableDatabase()

为了使用SQLiteHelper,创建一个子类重写oncreate(), onupdate(),和onopen()回调方法。

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);
    }
}

为了访问数据库,实例化SQLiteHelper子类

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

3.3 put information into a database

通过将ContentValues对象传递给insert()函数来插入数据。

// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content);

// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
         FeedEntry.TABLE_NAME,
         FeedEntry.COLUMN_NAME_NULLABLE,
         values);

第一个参数是table名称, The second argument provides the name of a column in which the framework can insert NULL in the event that the ContentValues is empty (if you instead set this to “null”, then the framework will not insert a row when there are no values).。第二个参数提供了你可以插入null数据的列名,如果value是零的话。如果第二个参数设置为null,如果value为null,那么不会插入row。

3.4 read information from a database

通过query()方法来读取数据库的内容,传入你的选择标准和需要的列。这个方法结合了insert()update()的元素,但是列的list定义了要获取的数据,而不是要插入的数据。query的结果返回一个Cursor对象。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
    FeedEntry._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_UPDATED,
    ...
    };

// How you want the results sorted in the resulting Cursor
String sortOrder =
    FeedEntry.COLUMN_NAME_UPDATED + " DESC";

Cursor c = db.query(
    FeedEntry.TABLE_NAME,  // The table to query
    projection,                               // The columns to return
    selection,                                // The columns for the WHERE clause
    selectionArgs,                            // The values for the WHERE clause
    null,                                     // don't group the rows
    null,                                     // don't filter by row groups
    sortOrder                                 // The sort order
    );

为了在cursor中看一行,你必须使用cusor的move方法。首先将cursor.moveToFirst()。对于每行,你可以通过调用Cursor的get方法读取一列的值,such as getString() or getLong()对于每个get方法,都必须要得到position的index值。可以通过 getColumnIndex() or getColumnIndexOrThrow()来获得

cursor.moveToFirst();
long itemId = cursor.getLong(
    cursor.getColumnIndexOrThrow(FeedEntry._ID)
)

3.5 delete information from a database

To delete rows from a table, you need to provide selection criteria that identify the rows. The database API provides a mechanism for creating selection criteria that protects against SQL injection. The mechanism divides the selection specification into a selection clause and selection arguments. The clause defines the columns to look at, and also allows you to combine column tests. The arguments are values to test against that are bound into the clause. Because the result isn’t handled the same as a regular SQL statement, it is immune to SQL injection.

要从表中删除行,需要提供识别行的选择条件。数据库API提供了一种创建选择标准的机制,以防止SQL注入。该机制将选择规范分为选择子句和选择参数。子句定义要查看的列,还允许您组合列测试。参数是要测试的值,这些值绑定到子句中。由于处理结果与常规SQL语句不同,所以它不受SQL注入的影响。

(笔者这里不是很理解)

// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, selection, selectionArgs);

update a database

当你需要修改数据库的子集时,使用update方法。更新table包含了插入和删除的语法。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// New value for one column
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);

// Which row to update, based on the ID
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);

至此,结束

猜你喜欢

转载自blog.csdn.net/qq_33347077/article/details/82108049
今日推荐