登录 立即注册
安币:

查看: 590079|回复: 62

理解 Android 本地数据存储 API

  [复制链接]

659

主题

2140

帖子

7467

安币

码皇(巴士元老)

Rank: 8Rank: 8

发表于 2011-9-16 12:27:23 | 显示全部楼层 |阅读模式
本帖最后由 anzhuo 于 2011-9-16 12:30 编辑
简介: 对于需要跨应用程序执行期间或生命期而维护重要信息的应用程序来说,能够在移动设备上本地存储数据是一种非常关键的功能。作为一名开发人员,您经常需要存储诸如用户首选项或应用程序配置之类的信息。您还必须根据一些特征(比如访问可见性)决定是否需要涉及内部或外部存储器,或者是否需要处理更复杂的、结构化的数据类型。跟随本文学习 Android 数据存储 API,具体来讲就是首选项、SQLite 和内部及外部内存 API。

前提条件
要跟随本文,需要具备以下技能和工具:
  • 基本了解 Java™ 技术和如何使用 Eclipse(或者您喜欢的 IDE)
  • Java Development Kit(需要版本 5 或 6)
  • Eclipse(版本 3.4 或 3.5)
  • Android SDK 和 ADT 插件
常用缩写词
  • ADT:Android 开发工具
  • API:应用程序编程接口
  • IDE:集成开发环境
  • JDK:Java 开发工具包
  • JSON:JavaScript 对象表示法
  • SDK:软件开发工具包
  • SQL:结构化查询语言
  • UI:用户界面
  • XML:可扩展标记语言









样例应用程序
为了突出 Android 应用程序开发的本地存储方面,我这里介绍一个样例应用程序,它允许您测试各种类型 API 的执行。有 源代码 可供下载。该应用程序支持 图 1 中的操作。

图 1. 用例

图 1 列出了以下用例:
  • 管理和存储首选项
  • 从应用程序资产加载信息
  • 将信息导出到内部内存、外部内存和本地数据库
  • 从内部内存和本地数据库读取数据
  • 清除已存储的信息
  • 在屏幕上查看信息
通篇文章中,详细介绍了在应用程序中使用本地存储,如下:
  • 从用户捕获首选项,本地存储起来,并在整个应用程序中加以使用。
  • 从内部应用程序资产检索一个用户图片,存储在本地内部内存和外部内存中,并呈现在屏幕上。
  • 从应用程序的资产检索一个 JSON 格式的好友列表。解析并存储在本地内部内存、外部内存和关系数据库中,再呈现在屏幕上。
样例应用程序定义了 表 1 中的类。

表 1. 样例应用程序中的类
说明
MainActivity
Main Activity;大多数样例代码都驻留在这里
Friend
描绘一个 Friend
AppPreferenceActivity
Preferences Activity 和屏幕
DBHelper
一个用于 SQLite 数据库管理的帮助器类
示例应用程序使用了两种类型的数据。第一种是应用程序首选项,存储为名-值对。对于首选项,定义了以下信息:
  • 一个 filename,用于加载和存储好友姓名列表
  • 一个 filename,用于加载和存储用户的一幅图片
  • 一个 flag,如果设置了,那么表示在应用程序启动时自动删除所有已存储的数据
第二种类型的数据是好友列表。好友列表最初表示为 Facebook Graph API JSON 格式,包含一组姓名和好友对象(参见 清单 1)。

清单 1. 好友列表(Facebook Graph API JSON 格式)

  1.                                 
  2. {
  3.    "data": [
  4.       {
  5.          "name": "Edmund Troche",
  6.          "id": "500067699"
  7.       }
  8.    ]
  9. }
复制代码

上面的简单格式使得 Friend 对象和数据库模式也简单。清单 2 展示了 Friend 类。

清单 2. Friend
  1. package com.cenriqueortiz.tutorials.datastore;

  2. import android.graphics.Bitmap;

  3. /**
  4. * Represents a Friend
  5. */
  6. public class Friend {
  7.     public String id;
  8.     public String name;
  9.     public byte[] picture;
  10.     public Bitmap pictureBitmap;;
  11. }
复制代码
除了 ID 和姓名之外,样例应用程序也保留了对好友图片的引用。尽管样例应用程序没有使用这些引用,但是您很容易扩展样例应用程序,以从 Facebook 检索图片并显示在主屏幕中。
数据库模式包含单个表,用于存储 Friend 的信息。表有三列:
  • 惟一的 ID 或键
  • Facebook ID
  • Friend 的姓名
清单 3 展示了相应关系表声明的 SQL 语句。

清单 3. Friend 数据库表
  1. db.execSQL("create table " + TABLE_NAME + " (_id integer primary key autoincrement, "
  2. + " fid text not null, name text not null) ");
复制代码
根据此信息,您可以在主屏幕上显示姓名;使用 ID,您可以检索所选用户的额外详细信息。在样例应用程序中,只显示了姓名。检索额外信息留给您去试验。注意,您很容易更改代码,以直接转向 Facebook。




存储应用程序首选项
本节介绍 Preferences API 和屏幕。Android API 提供很多方式处理首选项。其中一种方式是直接使用 SharedPreferences,并使用您自己的屏幕设计和首选项管理。第二种方法是使用 PreferenceActivityPreferenceActivity 自动负责首选项如何呈现在屏幕上(默认情况下,看起来跟系统首选项一样),并通过使用 SharedPreferences 在用户与每个首选项交互时自动存储或保存首选项。
为了简化样例应用程序,使用一个 PreferenceActivity 来管理首选项和首选项屏幕(参见 图 2)。首选项屏幕显示两个部分:Assets 和 Auto Settings。在 Assets 下,您可以为 Friends List 和 Picture 选项输入文件名。在 Auto Settings 下,您可以选中一个复选框,以便在启动时删除信息。

图 2. 实现的 Preferences 屏幕

在 图 2 中,布局是用 XML 以声明式方法定义的(不是以编程方式);声明式 XML 是首选方法,因为它保持了源代码的清晰可读。清单 4 展示了 Preferences UI 的 XML 声明。

清单 4. Preferences 屏幕的 XML 声明
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <PreferenceScreen
  3.         xmlns:android="http://schemas.android.com/apk/res/android"
  4.         android:id="@+id/prefs_screen"
  5.         android:key="preferencescreen"
  6.     >
  7.     <PreferenceCategory android:title="Assets">
  8.         <EditTextPreference
  9.             android:key="@string/prefs_assetname_friendslist_key"
  10.             android:title="Friends List"
  11.             android:summary="Please enter filename"
  12.             android:defaultValue="friends.txt"
  13.         />
  14.         <EditTextPreference
  15.             android:key="@string/prefs_assetname_picture_key"
  16.             android:title="Picture"
  17.             android:summary="Please enter filename"
  18.             android:defaultValue="pict2.jpg"
  19.         />
  20.     </PreferenceCategory>

  21.     <PreferenceCategory android:title="Auto Settings">
  22.         <CheckBoxPreference
  23.             android:key="@string/prefs_autodelete_key"
  24.             android:title="Delete at Startup"
  25.             android:summary="Check to clear at startup"
  26.             android:defaultValue="false"
  27.         />
  28.     </PreferenceCategory>
  29. </PreferenceScreen>
复制代码
PreferenceScreen 包含 EditTextPreference 的两个实例、一个 CheckBoxPreference 和两个由 PreferenceCategory 定义的类别组(一个用于 Asset,另一个用于 Auto Settings)。
在样例应用程序中,设计要求 Preference 屏幕使用菜单项进行调用。为此,使用一条 Intent 消息来调用叫做 AppPreferenceActivity的 Preference Screen Activity(参见 清单 5)。注意,我没有详细介绍 Intent 如何工作。关于 Intent 的更多信息,请参见 参考资料。

清单 5. AppPreferenceActivity
  1. /*
  2. * AppPreferenceActivity is a basic PreferenceActivity
  3. * C. Enrique Ortiz | [url]http://CEnriqueOrtiz.com[/url]
  4. */
  5. package com.cenriqueortiz.tutorials.datastore;

  6. import android.os.Bundle;
  7. import android.preference.PreferenceActivity;

  8. public class AppPreferenceActivity extends PreferenceActivity {

  9.     /**
  10.      * Default Constructor
  11.      */
  12.     public AppPreferenceActivity() {}

  13.    /**
  14.     * Called when the activity is first created.
  15.     *   Inflate the Preferences Screen XML declaration.
  16.     */
  17.     @Override
  18.     public void onCreate(Bundle savedInstanceState) {
  19.         super.onCreate(savedInstanceState);
  20.         addPreferencesFromResource(R.xml.prefs); // Inflate the XML declaration
  21.     }   

  22. }
复制代码
在样例应用程序中,像 清单 6 中一样,从菜单项处理程序中调用 Intent。

清单 6. 使用 Intent 调用 Preference Activity
  1. /**
  2. * Invoked when a menu item has been selected
  3. */
  4. @Override
  5. public boolean onOptionsItemSelected(MenuItem item) {
  6.     switch (item.getItemId()) {

  7.         // Case: Bring up the Preferences Screen
  8.         case R.id.menu_prefs: // Preferences
  9.             // Launch the Preference Activity
  10.             Intent i = new Intent(this, AppPreferenceActivity.class);
  11.             startActivity(i);
  12.             break;

  13.         case R.id.menu...:
  14.             :
  15.             break;

  16.     }
  17.     return true;
  18. }
复制代码
此外,您必须在 AndroidManifest XML 文件中定义所有的 Intent,如 清单 7 所示。

清单 7. 在 AndroidManifest.xml 中定义 Intent
  1. :

  2. <application android:icon="@drawable/icon" android:label="@string/app_name">

  3.     :
  4.     :
  5.    
  6.     <activity
  7.         android:name="AppPreferenceActivity"
  8.         android:label="Preferences">
  9.     </activity>  

  10.      :

  11. </application>
复制代码
回想一下,PreferenceActivity 使用 SharedPreferences 在用户与首选项屏幕交互时自动存储首选项。然后应用程序在执行各种任务时使用这些首选项。清单 8 展示了如何直接使用 SharedPreferences 来加载存储的首选项;关于加载的首选项在整个样例代码中是如何被使用的,您可以参考相应的样例代码。此外,清单 8 也展示了如何利用 SharedPreferences 直接存储首选项,以防您喜欢自己管理首选项(不是通过 PrefenceActivity),使用了一个 Editor
清单 8 展示了如何使用 SharedPreferences 加载已存储的首选项,以及如何使用 Editor 更改已存储的首选项。

清单 8. 使用 SharedPreferences
  1. /////////////////////////////////////////////////////////////
  2. // The following methods show how to use the SharedPreferences
  3. /////////////////////////////////////////////////////////////

  4. /**
  5. * Retrieves the Auto delete preference
  6. * @return the value of auto delete
  7. */
  8. public boolean prefsGetAutoDelete() {
  9.     boolean v = false;
  10.     SharedPreferences sprefs =
  11.        PreferenceManager.getDefaultSharedPreferences(appContext);
  12.     String key = appContext.getString(R.string.prefs_autodelete_key);
  13.     try {
  14.         v = sprefs.getBoolean(key, false);
  15.     } catch (ClassCastException e) {
  16.     }
  17.     return v;
  18. }   

  19. /**
  20. * Sets the auto delete preference
  21. * @param v the value to set
  22. */
  23. public void  prefsSetAutoDelete(boolean v) {
  24.     SharedPreferences sprefs =
  25.     PreferenceManager.getDefaultSharedPreferences(appContext);
  26.     Editor e = sprefs.edit();
  27.     String key = appContext.getString(R.string.prefs_autodelete_key);               
  28.     e.putBoolean(key, v);
  29.     e.commit();
  30. }
复制代码
接下来,将介绍如何使用数据库来存储数据。





使用 SQLite 数据库
Android 通过 SQLite 提供对本地关系数据库的支持。表中(定义在以下代码清单中)汇总了样例应用程序中使用的重要数据库类。
样例应用程序使用了一个 DBHelper 类来封装一些数据库操作(参见 清单 9)。

清单 9. DBHelper
  1. package com.cenriqueortiz.tutorials.datastore;

  2. import java.util.ArrayList;

  3. import android.content.Context;
  4. import android.database.Cursor;
  5. import android.database.sqlite.SQLiteDatabase;
  6. import android.database.sqlite.SQLiteOpenHelper;

  7. public class DBHelper extends SQLiteOpenHelper {
复制代码
为数据库版本、数据库名称和表名称定义了很多常量(参见 清单 10)。

清单 10. 初始化 DBHelper
  1. private SQLiteDatabase db;
  2.     private static final int DATABASE_VERSION = 1;
  3.     private static final String DB_NAME = "sample.db";
  4.     private static final String TABLE_NAME = "friends";

  5.     /**
  6.      * Constructor
  7.      * @param context the application context
  8.      */
  9.     public DBHelper(Context context) {
  10.         super(context, DB_NAME, null, DATABASE_VERSION);
  11.         db = getWritableDatabase();
  12.     }
复制代码
在准备好创建数据库时,会调用 onCreate() 方法。在该方法中,创建表(参见 清单 11)。

清单 11. 创建数据库表
  1.   /**
  2.      * Called at the time to create the DB.
  3.      * The create DB statement
  4.      * @param the SQLite DB
  5.      */
  6.     @Override
  7.     public void onCreate(SQLiteDatabase db) {
  8.         db.execSQL(
  9.                 "create table " + TABLE_NAME + " (_id integer primary key autoincrement,
  10. " + " fid text not null, name text not null) ");
  11.     }
复制代码
insert() 方法在信息导出到数据库时由 MainActivity 调用(参见 清单 12)。

清单 12. 插入一行
  1. /**
  2.      * The Insert DB statement
  3.      * @param id the friends id to insert
  4.      * @param name the friend's name to insert
  5.      */
  6.     public void insert(String id, String name) {
  7.         db.execSQL("INSERT INTO friends('fid', 'name') values ('"
  8.                 + id + "', '"
  9.                 + name + "')");
  10.     }
复制代码
deleteAll() 方法在清理数据库时由 MainActivity 调用。它删除表(参见 清单 13)。

清单 13. 删除数据库表
  1. /**
  2.      * Wipe out the DB
  3.      */
  4.     public void clearAll() {
  5.         db.delete(TABLE_NAME, null, null);
  6.     }
复制代码
提供了两个 SELECT ALL 方法:cursorSelectAll()listSelectAll(),前者返回一个游标,后者返回一个 Friend 对象 ArrayList。这些方法在从数据库加载信息时由 MainActivity 调用(参见 清单 14)。

清单 14. 运行返回 ArrayListSelect All
  1.   /**
  2.      * Select All returns a cursor
  3.      * @return the cursor for the DB selection
  4.      */
  5.     public Cursor cursorSelectAll() {
  6.         Cursor cursor = this.db.query(
  7.                 TABLE_NAME, // Table Name
  8.                 new String[] { "fid", "name" }, // Columns to return
  9.                 null,       // SQL WHERE
  10.                 null,       // Selection Args
  11.                 null,       // SQL GROUP BY
  12.                 null,       // SQL HAVING
  13.                 "name");    // SQL ORDER BY
  14.         return cursor;
  15.     }
复制代码
listSelectAll() 方法返回 ArrayList 容器中选定的行,该容器由 MainActivity 用来将它绑定到 MainScreen ListView(参见 清单 15)。

清单 15. 运行返回游标的 Select All
  1. /**
  2.      * Select All that returns an ArrayList
  3.      * @return the ArrayList for the DB selection
  4.      */
  5.     public ArrayList<Friend> listSelectAll() {
  6.         ArrayList<Friend> list = new ArrayList<Friend>();
  7.         Cursor cursor = this.db.query(TABLE_NAME, new String[] { "fid", "name" },
  8. null, null, null, null, "name");
  9.         if (cursor.moveToFirst()) {
  10.             do {
  11.                 Friend f = new Friend();
  12.                 f.id = cursor.getString(0);
  13.                 f.name = cursor.getString(1);
  14.                 list.add(f);
  15.             } while (cursor.moveToNext());
  16.         }
  17.         if (cursor != null && !cursor.isClosed()) {
  18.             cursor.close();
  19.         }
  20.         return list;
  21.     }
复制代码
如果检测到数据库版本更改,就会调用 onUpgrade() 方法(参见 清单 16)。

清单 16. 检测数据库版本是否更改
  1.   /**
  2.      * Invoked if a DB upgrade (version change) has been detected
  3.      */
  4.     @Override
  5.     /**
  6.      * Invoked if a DB upgrade (version change) has been detected
  7.      */
  8.     @Override
  9.     public void onUpgrade(SQLiteDatabase db,
  10.        int oldVersion, int newVersion) {
  11.         // Here add any steps needed due to version upgrade
  12.         // for example, data format conversions, old tables
  13.         // no longer needed, etc
  14.     }
  15. }
复制代码
整个 MainActivity 中,当您将信息导出到数据库、从数据库加载信息以及清理数据库时,都会使用 DBHelper。第一件事是在创建MainActivity 时实例化 DBHelper。在 onCreate() 时执行的其他任务包括初始化不同的屏幕视图(参见 清单 17)。

清单 17. MainActivity onCreate() 初始化数据库
  1. public void onCreate(Bundle savedInstanceState) {
  2.     super.onCreate(savedInstanceState);
  3.     appContext = this;
  4.     setContentView(R.layout.main);
  5.     dbHelper = new DBHelper(this);
  6.     listView = (ListView) findViewById(R.id.friendsview);
  7.     friendsArrayAdapter = new FriendsArrayAdapter(
  8.        this, R.layout.rowlayout, friends);
  9.     listView.setAdapter(friendsArrayAdapter);
  10.     :
  11.     :
  12. }
复制代码
清单 18 展示了如何从资产加载好友列表以及如何将之解析并插入数据库中。

清单 18. MainActivity 插入到数据库中
  1. String fname = prefsGetFilename();
  2. if (fname != null && fname.length() > 0) {
  3.     buffer = getAsset(fname);
  4.     // Parse the JSON file
  5.     String friendslist = new String(buffer);
  6.     final JSONObject json = new JSONObject(friendslist);
  7.     JSONArray d = json.getJSONArray("data");
  8.     int l = d.length();
  9.     for (int i2=0; i2<l; i2++) {
  10.         JSONObject o = d.getJSONObject(i2);
  11.         String n = o.getString("name");
  12.         String id = o.getString("id");
  13.         dbHelper.insert(id, n);
  14.     }
  15.     // Only the original owner thread can touch its views                           
  16.     MainActivity.this.runOnUiThread(new Runnable() {
  17.         public void run() {
  18.             friendsArrayAdapter.notifyDataSetChanged();
  19.         }
  20.     });         
  21. }
复制代码
清单 19 展示了如何执行 SELECT ALL 以及如何将数据绑定到主屏幕 ListView

清单 19. MainActivity Select All 和将数据绑定到 ListView
  1. final ArrayList<Friend> dbFriends = dbHelper.listSelectAll();
  2. if (dbFriends != null) {
  3.     // Only the original owner thread can touch its views                           
  4.     MainActivity.this.runOnUiThread(new Runnable() {
  5.         public void run() {
  6.             friendsArrayAdapter =
  7.             new FriendsArrayAdapter(
  8.                 MainActivity.this, R.layout.rowlayout, dbFriends);
  9.             listView.setAdapter(friendsArrayAdapter);
  10.             friendsArrayAdapter.notifyDataSetChanged();
  11.         }
  12.     });
  13. }           
复制代码
接下来,了解一下在示例应用程序中使用 Internal Storage API。





为私有数据使用设备的内部存储器
有了数据存储 API,您可以使用内部存储器存储数据。信息可以是私有的,您可以有选择地让其他应用程序对之具有读或写的访问权限。本节介绍这个存储私有数据的 API,它使用 android.content.Context.openFileInputopenFileOutputgetCacheDir() 来高速缓存数据,而不是永久地存储。
清单 20 中的代码片段展示了如何从内部私有存储器读取数据。使得存储器为私有的方法是对 openFileOutput() 使用MODE_PRIVATE

清单 20. 从本地私有存储器读取数据
  1. /**
  2. * Writes content to internal storage making the content private to
  3. * the application. The method can be easily changed to take the MODE
  4. * as argument and let the caller dictate the visibility:
  5. * MODE_PRIVATE, MODE_WORLD_WRITEABLE, MODE_WORLD_READABLE, etc.
  6. *
  7. * @param filename - the name of the file to create
  8. * @param content - the content to write
  9. */
  10. public void writeInternalStoragePrivate(
  11.         String filename, byte[] content) {
  12.     try {
  13.         //MODE_PRIVATE creates/replaces a file and makes
  14.         //  it private to your application. Other modes:
  15.         //    MODE_WORLD_WRITEABLE
  16.         //    MODE_WORLD_READABLE
  17.         //    MODE_APPEND
  18.         FileOutputStream fos =
  19.            openFileOutput(filename, Context.MODE_PRIVATE);
  20.         fos.write(content);
  21.         fos.close();
  22.     } catch (FileNotFoundException e) {
  23.         e.printStackTrace();
  24.     } catch (IOException e) {
  25.         e.printStackTrace();
  26.     }
  27. }
复制代码
清单 21 中的代码片段展示了如何从内部私有存储器读取数据;注意 openFileInput() 的使用。

清单 21. 从内部私有存储器读取数据
  1.                                 
  2. /**
  3. * Reads a file from internal storage
  4. * @param filename the file to read from
  5. * @return the file content
  6. */
  7. public byte[] readInternalStoragePrivate(String filename) {
  8.     int len = 1024;
  9.     byte[] buffer = new byte[len];
  10.     try {
  11.         FileInputStream fis = openFileInput(filename);
  12.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  13.         int nrb = fis.read(buffer, 0, len); // read up to len bytes
  14.         while (nrb != -1) {
  15.             baos.write(buffer, 0, nrb);
  16.             nrb = fis.read(buffer, 0, len);
  17.         }
  18.         buffer = baos.toByteArray();
  19.         fis.close();
  20.     } catch (FileNotFoundException e) {
  21.         e.printStackTrace();
  22.     } catch (IOException e) {
  23.         e.printStackTrace();
  24.     }
  25.     return buffer;
  26. }
复制代码
清单 22 展示了如何从内部私有存储器删除数据。

清单 22. 从本地私有存储器删除数据

  1.                                 
  2. /**
  3. * Delete internal private file
  4. * @param filename - the filename to delete
  5. */
  6. public void deleteInternalStoragePrivate(String filename) {
  7.     File file = getFileStreamPath(filename);
  8.     if (file != null) {
  9.         file.delete();
  10.     }
  11. }
复制代码
现在可以来看为公共数据使用外部存储器了。





为公共数据使用设备的外部存储器
有了数据存储 API,您可以使用外部存储器存储数据。信息可以是私有的,您可以有选择地让其他应用程序对之具有读或写的访问权限。本节您将对此 API 进行编程,以便使用包括getExternalStorageState()getExternalFilesDir()getExternalStorageDirectory()getExternalStoragePublicDirectory() 在内的很多 API 来存储公共数据。您为公共数据使用下面的路径:/Android/data/<package_name>/files/
在使用外部存储器之前,必须看看它是否可用,是否可写。下面两个代码片段展示了测试这些条件的帮助器方法。清单 23 测试外部存储器是否可用。

清单 23. 测试外部存储器是否可用
  1.                                 
  2. /**
  3. * Helper Method to Test if external Storage is Available
  4. */
  5. public boolean isExternalStorageAvailable() {
  6.     boolean state = false;
  7.     String extStorageState = Environment.getExternalStorageState();
  8.     if (Environment.MEDIA_MOUNTED.equals(extStorageState)) {
  9.         state = true;
  10.     }
  11.     return state;
  12. }
复制代码

清单 24 测试外部存储器是否只可读。
清单 24. 测试外部存储器是否只可读
  1.                                 
  2. /**
  3. * Helper Method to Test if external Storage is read only
  4. */
  5. public boolean isExternalStorageReadOnly() {
  6.     boolean state = false;
  7.     String extStorageState = Environment.getExternalStorageState();
  8.     if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(extStorageState)) {
  9.         state = true;
  10.     }
  11.     return state;
  12. }
复制代码


清单 25 展示了如何写到外部存储器,以存储公共数据。

清单 25. 写到外部内存

  1. /**
  2. * Write to external public directory
  3. * @param filename - the filename to write to
  4. * @param content - the content to write
  5. */
  6. public void writeToExternalStoragePublic(String filename, byte[] content) {

  7.     // API Level 7 or lower, use getExternalStorageDirectory()
  8.     //  to open a File that represents the root of the external
  9.     // storage, but writing to root is not recommended, and instead
  10.     // application should write to application-specific directory, as shown below.

  11.     String packageName = this.getPackageName();
  12.     String path = "/Android/data/" + packageName + "/files/";

  13.     if (isExternalStorageAvailable() &&
  14.        !isExternalStorageReadOnly()) {
  15.         try {
  16.             File file = new File(path, filename);
  17.             file.mkdirs();
  18.             FileOutputStream fos = new FileOutputStream(file);
  19.             fos.write(content);
  20.             fos.close();
  21.         } catch (FileNotFoundException e) {
  22.             e.printStackTrace();
  23.         } catch (IOException e) {
  24.             e.printStackTrace();
  25.         }
  26.     }
  27. }
复制代码



清单 26 展示了如何从外部存储器读取数据。

清单 26. 从外部内存读取数据
  1.                                 
  2. /**
  3. * Reads a file from internal storage
  4. * @param filename - the filename to read from
  5. * @return the file contents
  6. */
  7. public byte[] readExternallStoragePublic(String filename) {
  8.     int len = 1024;
  9.     byte[] buffer = new byte[len];
  10.     String packageName = this.getPackageName();
  11.     String path = "/Android/data/" + packageName + "/files/";

  12.     if (!isExternalStorageReadOnly()) {     
  13.         try {
  14.             File file = new File(path, filename);            
  15.             FileInputStream fis = new FileInputStream(file);
  16.             ByteArrayOutputStream baos = new ByteArrayOutputStream();
  17.             int nrb = fis.read(buffer, 0, len); //read up to len bytes
  18.             while (nrb != -1) {
  19.                 baos.write(buffer, 0, nrb);
  20.                 nrb = fis.read(buffer, 0, len);
  21.             }
  22.             buffer = baos.toByteArray();
  23.             fis.close();
  24.         } catch (FileNotFoundException e) {
  25.             e.printStackTrace();
  26.         } catch (IOException e) {
  27.             e.printStackTrace();
  28.         }
  29.     }
  30.     return buffer;
  31. }
复制代码

清单 27 中的代码片段展示了如何从外部内存删除文件。
清单 27. 从外部内存删除文件
  1.                                 
  2. /**
  3. * Delete external public file
  4. * @param filename - the filename to write to
  5. */
  6. void deleteExternalStoragePublicFile(String filename) {
  7.     String packageName = this.getPackageName();
  8.     String path = "/Android/data/" + packageName + "/files/"+filename;
  9.     File file = new File(path, filename);
  10.     if (file != null) {
  11.         file.delete();
  12.     }
  13. }
复制代码


处理外部存储器需要特殊的权限 WRITE_EXTERNAL_STORAGE,它通过 AndroidManifest.xml 请求得到(参见 清单 28)。

清单 28. WRITE_EXTERNAL_STORAGE
  1.                                 
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
复制代码

外部存储 API 通过根据文件类型(比如 Pictures、Ringtones)将文件存储在预先确定的目录中,允许您公共地存储文件。本文没有介绍这种方法,但是您应该熟悉它。此外,记住外部存储器中的文件任何时候都可能消失。
相关的方法
如果您具有不需要长期永久保存的临时文件,那么可以将这些文件存储在高速缓存中。高速缓存是一种特殊的内存,可以用于存储中小型数据(少于兆字节),但是您一定要知道,取决于有多少内存可用,高速缓存的内容任何时候都可能被清除。
清单 29 展示了一个帮助器方法,它返回到内部内存中高速缓存的路径。
清单 29. 检索到内部内存高速缓存的路径
  1.                                 
  2. /**
  3. * Helper method to retrieve the absolute path to the application
  4. * specific internal cache directory on the file system. These files
  5. * will be ones that get deleted when the application is uninstalled or when
  6. * the device runs low on storage. There is no guarantee when these
  7. * files will be deleted.
  8. *
  9. * Note: This uses a Level 8+ API.
  10. *
  11. * @return the absolute path to the application specific cache
  12. * directory
  13. */
  14. public String getInternalCacheDirectory() {
  15.     String cacheDirPath = null;
  16.     File cacheDir = getCacheDir();
  17.     if (cacheDir != null) {
  18.         cacheDirPath = cacheDir.getPath();
  19.     }
  20.     return cacheDirPath;        
  21. }
复制代码


清单 30 展示了一个帮助器方法,它返回到外部内存中高速缓存的路径。

清单 30. 检索到外部内存高速缓存的路径
  1.                                 
  2. /**
  3. * Helper method to retrieve the absolute path to the application
  4. * specific external cache directory on the file system. These files
  5. * will be ones that get deleted when the application is uninstalled or when
  6. * the device runs low on storage. There is no guarantee when these
  7. * files will be deleted.
  8. *
  9. * Note: This uses a Level 8+ API.
  10. *
  11. * @return the absolute path to the application specific cache
  12. * directory
  13. */
  14. public String getExternalCacheDirectory() {
  15.     String extCacheDirPath = null;
  16.     File cacheDir = getExternalCacheDir();
  17.     if (cacheDir != null) {
  18.         extCacheDirPath = cacheDir.getPath();
  19.     }
  20.     return extCacheDirPath;     
  21. }
复制代码


通过使用示例应用程序,您现在应该很好地理解了如何为公共数据使用设备的外部存储器。

结束语
本文介绍了几个 Android 存储 API,从首选项到使用 SQLite 和内部及外部内存。利用首选项 API,您可以让自己的应用程序收集和存储简单的首选项信息。使用 SQLite API,您可以存储更复杂的数据,利用内部和外部存储器,您可以存储对应用程序是私有的或者对其他应用程序公共可用的文件。跨多个会话持久存储的已存储数据让您的应用程序甚至在与网络断开连接时仍然能够工作。您现在应该掌握了在开发 Android 应用程序时利用所有这些类型的存储器。

下载

描述
名字
大小
下载方法
本文源代码



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

0

主题

92

帖子

157

安币

攻城狮

Rank: 3Rank: 3

发表于 2011-9-26 13:05:31 | 显示全部楼层
种瓜得瓜种豆得豆

0

主题

40

帖子

20

安币

初级码农

Rank: 1

发表于 2011-10-13 22:17:48 | 显示全部楼层
写的不错,下载来学习学习

0

主题

42

帖子

33

安币

程序猿

Rank: 2

发表于 2011-11-4 15:23:39 | 显示全部楼层
放大师傅认为缺乏

0

主题

83

帖子

214

安币

攻城狮

Rank: 3Rank: 3

QQ达人

发表于 2011-11-18 13:15:33 | 显示全部楼层
very good{:soso_e128:}

0

主题

23

帖子

62

安币

初级码农

Rank: 1

发表于 2011-11-30 13:32:57 | 显示全部楼层
设计的思想还是很实用的

1

主题

22

帖子

191

安币

攻城狮

Rank: 3Rank: 3

QQ达人

发表于 2011-11-30 20:06:34 | 显示全部楼层
{:soso_e179:}

0

主题

87

帖子

100

安币

程序猿

Rank: 2

发表于 2011-12-5 11:38:45 | 显示全部楼层
{:soso_e179:}
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

站长推荐

通过邮件订阅最新安卓weekly信息
上一条 /4 下一条

下载安卓巴士客户端

全国最大的安卓开发者社区
联系我们
关闭
合作电话:
15618560077
Email:
805941275@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| 下载客户端|申请友链|手机版|站点统计|安卓巴士 ( 粤ICP备15117877号 )

快速回复 返回顶部 返回列表