《2022年Android官方开发教程中文版 .pdf》由会员分享,可在线阅读,更多相关《2022年Android官方开发教程中文版 .pdf(17页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Android官方开发教程中文版维信科技保存数据保存数据大部分 Android 应用都需要保存数据, 即使只是在 onPause() 期间不让用户进度丢失而保存状态信息。大部分有意义的App 还需要保存用户设置,某些App必须管理大量来自于文件和数据库的信息(维信科技提供)。本课程介绍 Android中保存数据的主要方式。包括:简单数据保存到参数文件中的键值对。保存到 Android 文件系统的任意文件中。使用 SQLite数据库管理。保存键值对集合如 果 你 有 一 个 想 保 存 的 、 相 对 较 小 的 键 值 对 集 合 , 你 应 用 使 用SharedPreferences AP
2、I 。 SharedPreferences 对象指向一个包含了键值对的文件并提供一些简单的方法以读写它们。 每个 SharedPreferences文件由框架管理, 可以设为私有或共享。本课程展示如何使用SharedPreferences API 储存相对简单的数据。注意: SharedPreferences API 只读写键值对,不要把它和 Preference API混淆,Preference API是为 App 设置构建用户界面(尽管在界面中使用SharedPreferences API来做为保存设置数据的实现) 。有关 Preference的更多信息,请参见“设置”指南。名师资料总结
3、- - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 17 页 - - - - - - - - - 获取 SharedPreferences 的句柄你可以创建新的参数文件或通过以下两个方法之一访问一个存在的参数文件:getSharedPreferences() 如果你需要从由名称标识的多个共享参数文件中,用方法的第一个参数指定一个,那么用这个。你可以从App 的任意上下文中调用它。getPreferences() 如果你需要为Activity 使用仅有的参数文件, 那么用这个。因为这会检索A
4、ctivity 默认的共享参数文件,你不需要提供名称。例如,下面的代码在一个Fragment 中执行,它访问了一个由资源字符串R.string.preference_file_key标识的参数文件,并且用私有模式打开它,因此这个文件只能由你的 App 访问(维信科技提供) 。Context context = getActivity(); SharedPreferences sharedPref = context.getSharedPreferences( getString(R.string.preference_file_key), Context.MODE_PRIVATE); 命名 你
5、的 共 享 参数 文 件 时, 应该 使 用 唯一的 、 可识 别 的名 称。比 如“com.example.myapp.PREFERENCE_FILE_KEY” 。或者,如果你的Activity 就一个共享参数文件,你可以使用getPreference()方法:SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); 警告:如果你 MODE_WORLD_READABLE或 MODE_WORLD_WRITEABLE创建共享参数文件,那么任何知道文件标识符的其它App 都可以访问你的数据。
6、写入共享参数写入共享参数文件,用SharedPreferences 对象的edit()方法创建一个名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 17 页 - - - - - - - - - SharedPreference.Editor对象。把你要写入的键和值传递给putInt() 或 putString()之类的方法,然后调用commit()方法提交改变。如:SharedPreferences sharedPref = getActivity().getPrefere
7、nces(Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPref.edit(); editor.putInt(getString(R.string.saved_high_score), newHighScore); mit();读取共享参数调用 getInt()或 getString()之类的方法从共享参数文件中检索值,提供一个你想要得到值的键,以及一个可选的、当键不存在时返回的默认值。如:SharedPreferences sharedPref = getActivity().getPreferences(Con
8、text.MODE_PRIVATE); int defaultValue = getResources().getInteger(R.string.saved_high_score_default); long highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue); 保存文件Android 使用的文件系统类似其它平台上的基于磁盘的文件系统。本节内容描述了 File API如何工作于 Android 文件系统以及读写文件。File 对象适合按从头到尾的、 不跳过任何内容的顺序, 读写大量
9、数据。例如,图像文件或任何通过网络交换的数据(维信科技提供)。本节内容展示了如何在你的App 中执行文件相关的基本任务。本节课假设你熟悉基本的 Linux 文件系统以及 Java.io中标准的文件输入输出API。选择内部或外部存储所有 Android 系统都有两个文件存储区: “内部”或“外部”。这些叫法来自名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 17 页 - - - - - - - - - 早期 Android,大部分 Android 设备提供了内建的、非易失性
10、的存储器(内部储存) ,加上一个可移动的存储介质如微型SD卡(外部储存)。某些设备将永久储存空间划分为“内部”和“外部”分区,因此,就算没有可移动存储介质,也同样有两种存储空间和API行为,无论外部储存是否可移动。下表是对两种储存空间的总结:内容存储:总是可用的。存放在这里的文件默认情况只能被你的App 访问。当用户卸载你的 App 时, 系统会在内部存储中删除所有你的App 的文件。内部存储区最好用来存放你即不想让用户,也不想让其它 App 访问的文件。外部存储:不总是可用的,用户可以把外部存储器做为USB设备插入,也可以从设备上移除。它是“全世界可读的”(公开的),文件存放在这里会在你控制
11、之外被读取。当用户卸载你的App 时,只有保存在通过getExternalFilesDir() 方法获得的目录中的 App 文件才会被系统删除。外部存储区最好用来存放不需要限制访问,或是你想共享给其它App,或是允许用户通过电脑来访问的文件。提示:虽然 App 默认情况下是安装在内部存储区的,但你可以在清单文件中指定 android:installLocation 属性让你的 App安装在外部存储区上。 用户会喜欢这个选项的,当APK 的大小非常大,而用户有一个比内部存储区大得多的外部名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - -
12、- 名师精心整理 - - - - - - - 第 4 页,共 17 页 - - - - - - - - - 存储区时。详情请参考“应用安装位置” 。获得外部存储区权限要写入外部存储区,你必须在清单文件中请求WRITE_EXTERNAL_STORAGE权限: . 警告:目前,所有的App 都可以读取外部存储区而不需要指定权限。无论如何, 这将在未来的版本中改变。 如果你的 App 需要读取外部存储区(但不写入),你需要声明READ_EXTERNAL_STORAGE权限(维信科技提供)。要确保你的应用如期望中的那样继续工作,在更改生效之前,你应该立即声明这个权限。把文件保存到内部存储区中不需要任何
13、权限,应用程序任何时候在自己的内部存储区目录中都有权限读写文件。把文件保存到内部存储区当把文件保存到内部存储区时, 你可以通过下列两个方法之一获得文件对应的目录:getFilesDir() 为 App 返回表示内部目录的文件对象getCacheDir() 为 App 的临时缓存文件返回表示内部目录的文件对象。确保文件不再使用时删除它,并且在任何时候都为它执行一个合理的大小限制,比如1MB。如果系统检查到存储空间过低,它会删除你的缓存文件而不做任何警告。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - -
14、- - - 第 5 页,共 17 页 - - - - - - - - - 在这些目录中创建新的文件,你可以使用File()构造函数,传递通过上面的方法之一提供的,指定了内部存储目录的文件对象。如:File file = new File(context.getFilesDir(), filename); 或者,你也可以调用openFileOutput() 获得一个 FileOutputStream 来写入到你的内部目录中的文件。如:String filename = myfile; String string = Hello world!; FileOutputStream outputStr
15、eam; try outputStream = openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(string.getBytes(); outputStream.close(); catch (Exception e) e.printStackTrace(); 又或者,如果你需要缓存某些文件, 你应该使用 createTempFile() (维信科技提供) 。下例中的方法从 URL中提取了文件名,并用这个文件名在内部缓存目录中创建了一个文件:public File getTempFile(Context con
16、text, 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; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页
17、,共 17 页 - - - - - - - - - 注意: App 的内部存储目录是在Android 文件系统的特定位置由App 的包名指定的。从技术上说,如果你把文件设置为可读,其它App 可以读取你的内部文件。但无论如何,其它App 还需要知道你的包名和文件名,其它App 不能浏览你的内部目录也没读写权限,除非你显示地把文件设为可读或可写(维信科技提供) 。所以只你对内部存储的文件使用MODE_PRIVATE,它们永远不可能被其它 App 访问。把文件保存到外部存储区因为外部存储区可能是不可用的(比如用户把存储器安装到电脑上,或把提供外部存储的SD 卡移除了),你应该总是在访问前验证卷(意
18、指磁盘的卷)是否允许。 你可调用 getExternalStorageState() 方法查询外部存储区的状态,如果返回状态值是MEDIA_MOUNTED ,那么你可以读写文件。下例的方法可用于判断外部存储是否可用:/* 检查外部存储是否允许读写*/ public boolean isExternalStorageWritable() String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) return true; return false; /* 检查外
19、部存储是否可读*/ public boolean isExternalStorageReadable() String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) | 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页,共 17 页 - - - - - - - - - Environment.MEDIA_MOUNTED_READ_ONLY.eq
20、uals(state) return true; return false; 尽管外部存储可以被用户和其它App 更改,你可以在上面保存两类文件:公共文件:文件对用户和其它App 是自由的,当用户卸载你的App 时,这些文件允许用户保留。例如,你的App 拍摄的相片或其它下载的文件。私有文件:文件当然属于你的App,用户卸载App 时会删除它们。由于它们在外部存储区上,这些文件从技术上说可以被用户和其它App 访问,但实际上不会为你的 App 之外的用户提供价值。当用户卸载你的App 时,系统删除所有外部私有目录中的文件。如被你的App 或临时媒体文件下载的额外资源。如 果 你想 在 外 部
21、存 储区 中 保 存 公 共文 件 , 使 用getExternalStoragePublicDirectory()方法获得表示外部存储区正确目录的文件对象。这个方法有一个指定要保存的文件类型的参数,这样它们可以和其它公共文件逻辑上组织到一起,比如DIRECTORY_MUSIC或 DIRECTORY_PICTURES(维信科技提供)。如:public File getAlbumStorageDir(String albumName) / 获得用户公共的图片目录File file = new File(Environment.getExternalStoragePublicDirectory(
22、Environment.DIRECTORY_PICTURES), albumName); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 17 页 - - - - - - - - - if (!file.mkdirs() Log.e(LOG_TAG, Directory not created); return file; 如果你想把文件保存为App 私有的,你可调用 getExternalFilesDir()方法并且给它传递一个表示目录类型的名称。这种方式创建的目录添加
23、到父目录中,并封装了所有 App 的外部存储文件,当用户卸载App 时,它们被系统删除。例如,下面的方法可以用来创建个人相册的目录:public File getAlbumStorageDir(Context context, String albumName) / 获得 App 的私有图片目录File file = new File(context.getExternalFilesDir( Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs() Log.e(LOG_TAG, Directory not created);
24、 return file; 如果没预定义的子目录名称适合你的文件,你可以调用 getExternalFilesDir()方法传递 null 值来代替,这会获得外部存储区中App 私有目录的根目录。记住 getExternalFilesDir()是在一个目录里面创建一个目录,当用户卸载App时,这个目录会被删除 (维信科技提供)。如果 App 卸载后保存的文件仍然保留可用(比如你的App 是一个相机应用,并且用户希望能保留相片),你应该用getExternalStoragePublicDirectory() 代替它。无论 App 是为共享文件而使用getExternalStoragePublic
25、Directory() 还是为私有文件而使用 getExternalFilesDir() ,重要的是你使用API常量提供的目录名,如名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 17 页 - - - - - - - - - DIRECTORY_PICTURES。这些目录名能确保文件被系统正确地对待。例如,文件保存在 DIRECTORY_RINGTONES将被系统媒体扫描分类为铃声而不是音乐。查询可用空间如 果 你 提 前 知 道 要 保 存 多 少 数 据 , 你 可
26、以 调 用getFreeSpace()或getTotalSpace() 获得是否有足够的空间供你保存而不会产生IOException。这些方法分别提供了存储卷上当前可用空间和总空间。对避免填充存储卷超过一定阈值,这些信息也是有用的。无论如何,系统并不能保证你可以写入和getFreeSpace() 指明的一样多的字节。如果返回的数量比你想要保存的数据大小多几个MB,或者文件系统已用的空间小于 90%,那么你的继续可能是安全的。否则,你可能不应该写入存储区。注意 :你不需要在保存文件之前检查可有空间数量,你可以试着立即写入文件,然后捕获 IOException (维信科技提供)。如果你不知道需要多
27、少空间,你可以这么做。比如, 把 PNG图片转换成 JPEG 图片, 在保存之前改变了文件编码,你无法预先知道文件大小。删除文件你应该总是在不需要文件的时候把它删除,多数情况下,直接通过打开的文件引用调用 delete()方法:myFile.delete(); 如果文件储存在内部存储区, 你也可以请求 Context 调用 deleteFile()来定位和删除文件:myContext.deleteFile(fileName); 注意 :当用户卸载你的App 时,Android 系统会删除:1.所有保存在内部存储区的文件。名师资料总结 - - -精品资料欢迎下载 - - - - - - - -
28、- - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 17 页 - - - - - - - - - 2.所有用 getExternalFilesDir()保存的外部存储区的文件。无论如何,你应该定期手动删除所有通过getCacheDir() 创建的缓存文件,以及定期删除其它不再需要的文件。在 SQL数据库中保存数据把重复或结构化的数据保存到数据库中是理想的。这节课假设你已经熟悉一般的 SQL数据库,并帮助你开始了解Android 系统中的 SQLite数据库。你需要用到的数据库API在 android.database.sqlite 包中。定义架构
29、和合约数据库的主要原则之一是架构:数据库是如何组织的正式声明。架构被体现在你用来创建数据库的SQL语句中。你可能会发现创建一个同伴类(称为合约类)是很有用的,它以系统化和自我文档化的方式明确规定架构布局。合约类是一个为URI 、表、列定义名称的常量容器。 合约类允许你在同一个包中交差使用同一个常量。这可以让你在一个地方修改列名而影响你所有的代码。组织合约类的好办法是把数据库的全局定义放在类的根级别上,然后为每个表创建内部类来列举它的列。注意 :实现 BaseColumns接口,你的内部类会继承一个主键字段:_ID,某些 Android 类,如游标适配器可能会期望有它。这不是必需的,但这可以帮助
30、你的数据库更好地工作在Android 框架中。下面的代码片断为单表定义了表名和列名:public final class FeedReaderContract / 防止有人意外地实例化合约类名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 11 页,共 17 页 - - - - - - - - - / 给它一个空的构造函数public FeedReaderContract() /* 定义表的内部类*/ public static abstract class FeedEntry imp
31、lements 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 创建数据库定义数据库之后,你应该实现创建与维护数据库和表的方法,下面是一些典型的创建和删
32、除表的语句: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_N
33、AME_TITLE + TEXT_TYPE + COMMA_SEP + . / CREATE 命令的其它选项 ); private static final String SQL_DELETE_ENTRIES = DROP TABLE IF EXISTS + FeedEntry.TABLE_NAME; 就象保存在内部存储区的文件一样,Android 系统把数据库储存到应用程序名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 12 页,共 17 页 - - - - - - - - - 关
34、联的私有磁盘空间中。你的数据是安全的,因为默认情况下,其它应用程序不能访问这个区域 (维信科技提供)。SQLiteOpenHelper类提供了一些有用的API。当你使用这个类得到对数据库的引用时,系统只在需要时并且不是App 启动期间,才会执行可能需要较长时间的创建及更新数据库的操作。你要做的就是调用getWritableDatabase()和getReadableDatabase() 方法。注意 :因 为它 们可 能需要 较 长时间 运 行, 确保 你在后台 线 程 中 调用getWritableDatabase()和 getReadableDatabase() ,如 AsyncTask或
35、IntentService。要使用 SQLiteOpenHelper ,创建它的子类并重写onCreate()、onUpdate()和onOpen()回调方法。你可能还想实现onDowngrade(),但是没必要。下例是使用了上面看到的某些命令实现的SQLiteOpenHelper子类:public class FeedReaderDbHelper extends SQLiteOpenHelper / 如果你改变了数据库架构,你必须升级数据库的版本public static final int DATABASE_VERSION = 1; public static final String D
36、ATABASE_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) / 这个数据库仅是一个在线数据的缓存,所以更新策略/
37、是简单地删除数据并重新开始db.execSQL(SQL_DELETE_ENTRIES); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 13 页,共 17 页 - - - - - - - - - onCreate(db); public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) onUpgrade(db, oldVersion, newVersion); 要访问你的数据库,实例化SQLit
38、eOpenHelper子类:FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext(); 把信息放入数据传递一个 ContentValues对象给 insert()方法,把数据插入到数据库:/ 获取可写模式的数据库对象SQLiteDatabase db = mDbHelper.getWritableDatabase(); / 创建值的映射,用列名做键ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_ENTRY_ID,
39、id); values.put(FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedEntry.COLUMN_NAME_CONTENT, content); / 插入新行并返回新行的主键值long newRowId; newRowId = db.insert( FeedEntry.TABLE_NAME, FeedEntry.COLUMN_NAME_NULLABLE, values); insert()方法的第一个参数是表名,第二个参数是当ContentValues 为空时,框架可以插入 null 值的列名(如果这个参数用 “null” 替代,
40、那么当没有数据时,名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 14 页,共 17 页 - - - - - - - - - 框架不会插入新行) 。从数据库中读取信息使用 query()方法从数据库中读取,查询条件和输出列做为参数传递给它。除了用想要获取的数据列名列表替代要插入的数据之外,这个方法组合了 insert()和 update()方法的元素 (维信科技提供)。查询结果通过游标(Cursor)对象返回。SQLiteDatabase db = mDbHelper.getRead
41、ableDatabase(); / 声明 projection 变量指定哪些列出现在查询结果中/ you will actually use after this query. String projection = FeedEntry._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_UPDATED, . ; / 如何对游标结果集进行排序String sortOrder = FeedEntry.COLUMN_NAME_UPDATED + DESC; Cursor c = db.query( FeedEntry.TABLE_NAME
42、, / The table to query projection, / 返回的列selection, / WHERE子句中出现的列selectionArgs, / WHERE子句出现的值null, / 不对行集分组名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 15 页,共 17 页 - - - - - - - - - null, / 不对分组结果筛选sortOrder / 排序); 使用 Cursor 类的某个移动方法来查看游标中的行,你必须在读取值之前调用。通常情况下,你应该从
43、moveToFirst()开始,它表示结果集中第一条的“读取位置”。对每行数据,你都可以用Cursor 类的 get 方法读取列的值,例如getString()或 getLong()。对每个 get 方法,你都需要传递列的索引,你可以调用getColumnIndex()或 getColumnIndexOrThrow()方法获得索引。例如:cursor.moveToFirst(); long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedEntry._ID) ); 从数据库中删除信息从表中删除行,你需要提供标识行集的查询条件。
44、数据库API 提供了一种创建查询条件以防止注入攻击的机制。这种机制把具体的查询划分为查询子句和查询参数。子句中定义要查询的列,它还允许你组合列。参数是子句中限制的值。因为结果不会被处理成正规的SQL语句,所以它对SQL注入攻击免疫。/ 定义查询的 where 部分String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + LIKE ?; / 指定占位符的值String selectionArgs = String.valueOf(rowId) ; / 生成 SQL语句db.delete(table_name, selection, selection
45、Args); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 16 页,共 17 页 - - - - - - - - - 更新数据库需要修改数据库的数据时,使用update()方法。更新表组合了 insert()和 delete()的语法。SQLiteDatabase db = mDbHelper.getReadableDatabase(); / 某列的新值ContentValues values = new ContentValues(); values.put(FeedEntry
46、.COLUMN_NAME_TITLE, title); / 哪一行被更新,基于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);名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 17 页,共 17 页 - - - - - - - - -