10) 十分钟学会android–app数据保存三种植艺术

则好当onPause()时保留有消息以免用户之以快被丢,但大多数Android
app仍然是亟需实行保存数据的动作。大多数比较好的apps都亟待保留用户之装置信息,而且出一对apps必须保障大量之文件信息与DB信息。本章节用介绍Android中第一的多寡存储方,包括:

  • 保存到Preferences

    上学运用Shared Preferences文件为Key-Value的艺术保留简要的信。

  • 封存至文件

    学保存基本的公文。

  • 保存到数据库

    上应用SQLite数据库读写多少。

保存到Preference

当有一个相对比小之key-value集合需要保留时,可以运用SharedPreferences APIs。
SharedPreferences 对象对一个保留key-value
pairs的文件,并也念写他们提供了简约的计。每个 SharedPreferences
文件均是因为framework管理,其既可以是个人的,也得以是共享的。
这节课会演示如何以 SharedPreferences APIs 来囤积和追寻简单的数码。

Note: SharedPreferences APIs
仅仅提供了读写key-value对的效力,请不要与Preference APIs相混淆。后者可以帮助我们成立一个装置用户配置的页面(尽管其实质上是使SharedPreferences
来落实保存用户配置的)。更多关于Preference
APIs的音讯,请参考Settings 指南。

获取SharedPreference

咱得以经以下简单种植方式之一创建或者看shared preference 文件:

  • getSharedPreferences()) —
    如果需要差不多个经过名称参数来区分的shared preference文件,
    名称可以经第一只参数来指定。可于app中经过任何一个Context 执行该方法。
  • getPreferences()) —
    当activity仅需一个shared
    preference文件时。因为该方法会检索activity下默认的shared
    preference文件,并不需要提供文件名称。

条例:下面的示范在一个 Fragment 中给执行,它以private模式访问名也 R.string.preference_file_key 的shared
preference文件。这种情形下,该公文就能于我们的app访问。

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

 

诺因和app相关的主意呢shared
preference文件命名,该名应唯一。如本例中不过将该取名吧 "com.example.myapp.PREFERENCE_FILE_KEY" 。

本来,当activity仅需一个shared
preference文件时,我们可动用getPreferences())方法:

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

Caution: 如果创建了一个MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE 模式的shared
preference文件,则其它任何app均只是透过文件称访问该文件。

写Shared Preference

为了写shared preferences文本,需要经实践edit())创建一个 SharedPreferences.Editor。

通过类似putInt())与putString())等措施传递keys与values,接着通过commit()) 提交改变.

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

 

读Shared Preference

以打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使用以及任何平台类的依据磁盘的文件系统(disk-based file
systems)。本课程将叙如何以Android文件系统上运 File 的读写APIs对Andorid的file
system进行读写。

File
对象非常适合于流式顺序数据的读写。如图文件或者网络中交换的数据等。

以课程将会以身作则如何在app中实践基本的文件有关操作。假定读者既针对性linux的文件系统及java.io中标准的I/O
APIs有必然认识。

积存于中间还是外部

装有的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所共享或者是容用户通过计算机访问时之顶尖存储区域。

Tip: 尽管app是默认为装置至internal
storage的,我们要得经过以先后的manifest文件中宣示android:installLocation 属性来指定程序安装到external
storage。当有程序的安装文件很十分还用户的external
storage空间大于internal storage时,用户会支持于将拖欠程序安装到external
storage。更多安信息见App Install
Location。

获取External存储的权力

为写多少到external storage,
必须以公manifest文件中请求WRITE_EXTERNAL_STORAGE权限:

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

Caution:目前,所有的apps都得以于匪点名某个专门的权限下做external
storage的动作。但这当之后的安卓版被见面有变动。如果我们的app只待的权位(不是摹写),
那么以待声明 READ_EXTERNAL_STORAGE 权限。为了保app能连地健康工作,我们现在在编写程序时就是待声明读权限。

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

但是,如果我们的次来声明WRITE_EXTERNAL_STORAGE 权,那么尽管默认有矣的权限。

于internal
storage,我们无待声明任何权力,因为程序默认就来读写程序目录下之文书之权能。

保存到Internal Storage

当保存文件及internal
storage时,可以由此实行下两单方法之一来获取合适的目作为 FILE 的对象:

  • getFilesDir()) :
    返回一个File,代表了俺们app的internal目录。
  • getCacheDir()) :
    返回一个File,代表了咱app的internal缓存目录。请保管这个目录下之公文能够在如不再需要之早晚就叫删,并对准其尺寸进行合理界定,例如1MB
    。系统的其中存储空间不够时,会活动选择去缓存文件。

好行使File()) 构造器在那些目录下创办一个初的文件,如下:

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

一样,也得实施openFileOutput()) 获取一个 FileOutputStream 用于形容文件及internal目录。如下:

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())。例如:下面的措施从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;
}

 

Note: 我们的app的internal storage
目录为app的包名作为标识存放于Android文件系统的特定目录下[data/data/com.example.xx]。
从技术上讲,如果文件于设置为可读之,那么其他app就可读取该internal文件。然而,其他app需要掌握包名与公事称。若没安装为可读或可写,其他app是无主意读写的。因此我们要用了MODE_PRIVATE ,那么这些文件就无容许于外app所走访。

保留文件及External Storage

因为external
storage可能是不可用的,比如遇到SD卡被拔等状况时常。因此于做客之前应本着那可用性进行检查。我们好由此实行 getExternalStorageState())来查询external
storage的状态。若返回状态呢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;
}

 

尽管external
storage对于用户与其它app是可改的,我们也许会见保留下面两种植档次的文书。

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

纪念如果以文件为public形式保留在external
storage中,请以getExternalStoragePublicDirectory())方法来抱一个
File 对象,该对象表示存储于external
storage的目。这个方法会需要带有一个特定的参数来指定这些public的文件类型,以便让跟其他public文件进行分类。参数类型包括DIRECTORY_MUSIC 或者 DIRECTORY_PICTURES.
如下:

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形式保留在external
storage中,可以经过实行getExternalFilesDir()) 来博相应的目录,并且传递一个指令文件类型的参数。每一个为这种办法开创的目录都见面给上加到external
storage封装我们app目录下之参数文件夹下(如下则是albumName)。这下面的文本会当用户卸载我们的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在external
storage下的private的干净目录。

要记住,getExternalFilesDir()
方法会创建的目录会在app被卸载时给系统除去。如果我们的公文想以app被删除时仍然保留,请以getExternalStoragePublicDirectory().

任凭以 getExternalStoragePublicDirectory()
来储存可以共享的文书,还是采取 getExternalFilesDir()
来储存那些对我们的app来说是个人的公文,有好几死重点,那就是若动那些看似DIRECTORY_PICTURES 的API的常量。那些目录项目参数可以确保那些文件被系统对的对立统一。例如,那些坐DIRECTORY_RINGTONES 类型保存之公文就会见被系统的media
scanner认为是ringtone而未是音乐。

询问剩余空间

倘先知情想使封存的文件大小,可以透过实践getFreeSpace()) or getTotalSpace()) 来判断是否生足的空间来保存文件,从而避免来IOException。那些方法提供了即可用之半空中还有存储系统的总容量。

但,系统并无可知保证得描绘副通过getFreeSpace()询问及之容量文件,
如果查询的剩余容量比较咱的文件大小多几MB,或者说文件系统使用率还供不应求90%,这样虽然可持续拓展摹写的操作,否则极不用写上。

Note:连从未强制要求在描绘文件前去检查剩余容量。我们得尝尝先做写的动作,然后通过捕获
IOException
。这种做法就副吃事先并不知道想使写的文本的贴切大小。例如,如果当把PNG图片转换成JPEG之前,我们并不知道最终生成的图片大小是多少。

去文件

于非需要使用一些文件之上许去其。删除文件最直接的不二法门是直实施文书之delete()方法。

myFile.delete();

假使文件是保存在internal
storage,我们得以经过Context来拜会并经履行deleteFile()开展删除

myContext.deleteFile(fileName);

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

  • 持有保留到internal storage的公文。
  • 怀有应用getExternalFilesDir()方式保存在external storage的文书。

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

 

保存到数据库

于再次或结构化的多寡(如联络人信息)等保存及DB是单是的主见。本课假定读者既熟悉SQL数据库的常用操作。在Android上恐怕会见动用到的APIs,可以从android.database.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";
        ...
    }
}

 

使用SQL Helper创建DB

概念好了的DB的布局从此,就应实现那些创建和保护db和table的章程。下面是片突出的创始与删除table的语句。

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;

 

类于保存文件及设备的internal
storage ,Android会将db保存及程序的private的上空。我们的数码是受保障的,因为那些区域默认是私家的,不可让另外程序所访问。

在SQLiteOpenHelper看似吃生一对坏有因此之APIs。当以是看似来举行有与db有关的操作时,系统会对那些有或较耗时的操作(例如创建及创新等)在真正要的时候才去执行,而非是于app刚起步的时刻就去开那些动作。我们所要开的但是执行getWritableDatabase())或者getReadableDatabase()).

Note:坐那些操作可能是颇耗时的,请确保于background
thread(AsyncTask or IntentService)里面去实施 getWritableDatabase()
或者 getReadableDatabase() 。

为了用 SQLiteOpenHelper,
需要创造一个子类并重写onCreate()), onUpgrade())与onOpen())等callback方法。也许还索要实现onDowngrade()),
但这并无是必需的。

例如,下面是一个兑现了SQLiteOpenHelper 类的事例:

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

 

为看我们的db,需要实例化 SQLiteOpenHelper的子类:

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

补给加信息到DB

经过传递一个 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(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;
newRowId = db.insert(
         FeedReaderContract.FeedEntry.TABLE_NAME,
         FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE,
         values);

 

insert()法的第一独参数是table名,第二个参数会叫系统自动对那些ContentValues 没有提供数据的列填充数据也null,如果第二个参数传递的是null,那么网虽然未见面指向那些尚未提供数据的排进行填。

起DB中读取信息

为了打DB中读取数据,需要使用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 = {
    FeedReaderContract.FeedEntry._ID,
    FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE,
    FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED,
    ...
    };

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

Cursor c = db.query(
    FeedReaderContract.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中之履,使用cursor的里边一个move方法,但得于念取值之前调用。一般的话应该先调用moveToFirst()函数,将读取位置放置结果集最初步之岗位。对各国一样实施,我们可使用cursor的里边一个get方法而getString()getLong()赢得列的价值。对于每一个get方法必须传递想要得到之排的目录位置(index
position),索引位置好经过调用getColumnIndex()getColumnIndexOrThrow()获得。

脚演示如何由course对象吃读取数据信息:

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

 

抹DB中的信

以及查询信息相同,删除数据一致需提供有剔除标准。DB的API提供了一个预防SQL注入的机制来创造查询和删除标准。

SQL
Injection:
(趁B/S模式采用开发之发展,使用这种模式编写应用程序的程序员也越发多。但出于程序员的程度及涉吗参差不齐,相当可怜组成部分程序员在编写代码时并未针对用户输入数据的合法性进行判定,使应用程序存在安全隐患。用户可交给一截数据库查询代码,根据程序返回的结果,获得某些他想获悉的数码,这就是是所谓的SQL
Injection,即SQL注入
)

拖欠机制将查询语句划分为挑选条件和选择参数两有些。条件定义了询问的排列的风味,参数用于测试是否切合前面的条规。由于拍卖的结果不同为常见的SQL语句,这样可避免SQL注入问题。

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

 

更新数据

当得修改DB中之一点数据经常,使用 update() 方法。

update结合了插入与删除的语法。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

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

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

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

 

网站地图xml地图