前言
啦啦啦~大家好,又见面啦~
本篇博文讲和大家一起完成一个需要注册、登录的备忘录的,一起学习 SharedPreferences 的基本使用,学习 Android 中常见的文件操作方法,复习 Android 界面编程。
直接进入正题~
基础知识
1.SharedPreferences 的使用
使用SharedPreferences储存用户名和密码,SharedPreferences是直接处理xml文件,不需要做字符串分割,存储效率会比使用内部存储,和外部存储存储用户名和密码高。
(1) SharedPreferences 的读取
在 Android 中,用于获取 SharedPreferences 的接⼝是 getSharedPreferences(String, int) 函数。 两个参数的意义:
String: Desired preferences file. If a preferences file by this name does not exist, it will be created when you retrieve an editor.
int: Operating mode. Use 0 or MODE_PRIVATE for the default operation.
我们对 SharedPreferences 的读取操作是通过 getSharedPreferences(String, int) 函数返回的 SharedPreferences 对象的方法来完成的。查阅文档可以看到,SharedPreferences 支持如下几种方法读取之前存储的数据:
- abstract Map<String, ?> getAll()
- abstract boolean getBoolean(String key, boolean defValue)
- abstract float getFloat(String key, float defValue)
- abstract int getInt(String key, int defValue)
- abstract long getLong(String key, long defValue)
- abstract String getString(String key, String defValue)
- abstract Set<String> getStringSet(String key, Set<String> defValues)
所有方法都需要传入一个 defValue 参数,在给定的 key 不存在时,SharedPreferences 会直接返回 这个默认值。
(2)SharedPreferences 的写入
所有对 SharedPreferences 的写入操作,都必须通过 SharedPreferences.edit() 函数返回的 Editor对象来完成。
举例:
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences("MY_PREFERENCE", Context.MODE_PRIVATE);
// Alternatively, if you need just one shared preference file for your activity, you can use the getPreferences() method:
// SharedPreferences sharedPref =getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("KEY_SCORE", newHighScore);
editor.commit();
使用SharedPreferences将用户名和密码保存在本地后,可以在\data\data\+包名+\shared_prefs目录下找到一个info.xml文件
2.Android 中的文件操作
Android 中的存储空间分为两种:Internal Storage 和 External Storage.
(1)Internal Storage
默认情况下,保存在 Internal Storage 的文件只有应用程序可见,其他应用,以及用户本⾝是无法访问这些文件的。
向 Internal Storage 写入文件的示例代码如下:
try (FileOutputStream fileOutputStream = openFileOutput(FILE_NAME, MODE_PRIVATE)) {
String str = "Hello, World!";
fileOutputStream.write(str.getBytes());
Log.i("TAG", "Successfully saved file.");
} catch (IOException ex) {
Log.e("TAG", "Fail to save file.");
}
若对应的文件不存在,openFileOutput(String, int) 函数会直接新建文件。注意传入的文件名参数不能含有 path separators(即 '/'). 该函数返回一个 FileOutputStream 对象,可以调用 write() 方法写入内容。
相应地,文件的读取可以使用 openFileInput(String) 来读取文件。该函数返回一个 FileInput- Stream,调用 read() 方法读取内容。
try (FileInputStream fileInputStream =openFileInput(FILE_NAME)) {
byte[] contents = new byte[fileInputStream.available()];
fileInputStream.read(contents);
} catch (IOException ex) {
Log.e("TAG", "Fail to read file.");
}
(2)External Storage
Android 支持使用 Java 的文件 API 来读写文件,但是关键的点在于要有一个合适的路径。如果你要存储一些公开的,体积较大的文件(如媒体文件),External Storage 就是一个比较合适的地方。如文档中所说:
All Android devices have two file storage areas: “internal” and “external” storage. These names come from the early days of Android, when most devices offered built-in non-volatile memory (internal storage), plus a removable stor- age medium such as a micro SD card (external storage). Some devices divide the permanent storage space into “internal” and “external” partitions, so even without a removable storage medium, there are always two storage spaces and the API behavior is the same whether the external storage is removable or not.
无论是否支持外置 SD 卡,所有的 Android 设备都会将存储空间分为 internal 和 external 两部分。
要往 External Storage 写入文件,需要在 AndroidManifest.xml 文件中声明权限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
随后调用 getExternalFilesDir(String type) 或 Environment.getExternalStoragePublicDirectory()来获取 SD 卡路径。两者的区别在于:前者指向的目录会在应用卸载时被删除,而后者不会。
上面的两个函数均返回一个 File 对象,代表一个目录路径,使用这个 File 对象,再结合文件名,即 可创建 FileInputStream 或 FileOutputStream 来进⾏文件读写。
举例:
void createExternalStoragePrivateFile() {
// Create a path where we will place our private file on external
// storage.
File file = new File(getExternalFilesDir(null), "DemoFile.jpg");
try {
// Very simple code to copy a picture from the application's
// resource into the external file. Note that this code does
// no error checking, and assumes the picture is small (does
// not try to copy it in chunks). Note that if external storage is // not currently mounted this will silently fail.
InputStream is =getResources().openRawResource(R.drawable.balloons);
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data); is.close();
os.close();
} catch (IOException e) {
// Unable to create file, likely because external storage is
// not currently mounted.
Log.w("ExternalStorage", "Error writing " + file, e);
} }
实验内容
1 2 3 4
Figure 1: 首次进入, 呈现创建密码界面
Figure 2: 若密码不匹 配,弹出 Toast 提示
Figure 3: 若密码为空, 弹出 Toast 提示
Figure 4: 退出后第二次进入呈现输入密码界面
5 6 7 8
Figure 5: 若密码不正 确,弹出 Toast 提示
Figure 6: 文件加载失 败,弹出 Toast 提示
Figure 7: 成功导入文 件,弹出 Toast 提示
Figure 8: 成功保存文 件,弹出 Toast 提示
1.如 Figure 1 ⾄ Figure 8 所示,本次实验演示应用包含两个 Activity.
2.首先是密码输入 Activity:
若应用首次启动,则界面呈现出两个输入框,分别为新密码输入框和确认密码输入框。
输入框下方有两个按钮:
– OK 按钮点击后:
* 若 New Password 为空,则发出 Toast 提示。见 Figure 3。
* 若 New Password 与 Confirm Password 不匹配,则发出 Toast 提示。见 Figure 2。
* 若两密码匹配,则保存此密码,并进入文件编辑 Activity。
– CLEAR 按钮点击后:清除两输入框的内容。
• 完成创建密码后,退出应用再进入应用,则只呈现一个密码输入框。见 Figure 4。
– 点击 OK 按钮后,若输入的密码与之前的密码不匹配,则弹出 Toast 提示。见 Figure 5.
– 点击 CLEAR 按钮后,清除密码输入框的内容。
• 出于演示和学习的目的,本次实验我们使用 SharedPreferences 来保存密码。但实际应用中不会使用这种方式来存储敏感信息,而是采用更安全的机制。
3.文件编辑 Activity:
• 界面底部有三个按钮,⾼度一致,顶对齐,按钮⽔平均匀分布。三个按钮上方除 ActionBar 和 StatusBar 之外的全部空间由一个 EditText 占据(保留 margin)。EditText 内的文字竖直方 向置顶,左对齐。
• 在编辑区域输入任意内容,点击 SAVE 按钮后能保存到指定文件(文件名随意). 成功保存后,弹 出 Toast 提示。见 Figure 8。
• 点击 CLEAR 按钮,能清空编辑区域的内容。
• 点击 LOAD 按钮,能够从同一文件导入内容,并显示到编辑框中。若成功导入,则弹出 Toast。
提示。见 Figure 7。 若读取文件过程中出现异常(如文件不存在),则弹出 Toast 提示。见Figure 6。
4. 特殊要求:进入文件编辑 Activity 后,若点击返回按钮,则直接返回 Home 界面,不再返回密码输入 Activity。
参考实现
1.如何使文件编辑 Activity 的 EditText 占据上方全部空间?
使用 LinearLayout 和 layout_weight 属性。
if there are three text fields and two of them declare a weight of 1, while the other is given no weight, the third text field without weight will not grow and will only occupy the area required by its content. The other two will expand equally to fill the space remaining after all three fields are measured.
2.当 Activity 不可见时,如何将其从 activity stack 中除去(按返回键直接返回Home)?
AndroidManifest.xml 中设置 noHistory 属性。
Whether or not the activity should be removed from the activity stack and finished (its finish() method called) when the user navigates away from it and it’s no longer visible on screen —“true” if it should be finished, and “false”
if not. The default value is “false”.
3.如何根据需要隐藏/显示特定的控件?
Set visibility: You can hide or show views using setVisibility(int).
4.参考工程目录结构
实验过程
本次实验主要是实现一个备忘录,实现数据存储的功能。本次实验主要涉及SharePreferences的使用(读取和写入)。
首先,写好两个界面的XML布局文件(这里注意使用LinearLayout和layout_weigh属性,使得文件编辑Activity的EditText占据上方全部空间)。
接下来完成MainActivity.java类,在 Android 中,用于获取 SharedPreferences 的接口是 getSharedPreferences(String, int) 函数。对 SharedPreferences 的读取操作是通过 getSharedPreferences(String,int)函数返回的 SharedPreferences 对象的方法来完成的:
所有对 SharedPreferences 的写入操作,都必须通过 SharedPreferences.edit()函数返回的Editor对象来完成:
在OK按钮点击事件中,设置若 New Password为空,则发出Toast 提示。若New Password 与 Confirm Password不匹配,则发出Toast 提示,若New Password与 Confirm Password 匹配,则跳转到文件编辑界面:
获取本地存储的密码,密码不为空就隐藏其中一个密码框(保证完成创建密码后,退出应用再进入应用,则只呈现一个密码输入框):
然后在clear按钮点击事件中,通过设置tag来控制密码输入栏的清空事件(tag初始设为true,在创建密码界面中点击clear会清除两个密码输入框中的所有内容,在输入密码的登录界面中点击clear隐藏的密码框中的内容不会清除,通过与隐藏了的密码框中的已存储的密码进行比较确认输入的密码是否正确):
通过在AndroidManifest.xml中设置noHistory属性,使得当Activity不可见时,如何将其从activity stack中除去(按返回键直接返回Home):
在文件编辑的java类中,我们向Internal Storage 写入文件。
默认情况下,保存在 Internal Storage 的文件只有应用程序可见,其他应用,以及用户本身是无法 访问这些文件的。
若对应的文件不存在,openFileOutput(String, int) 函数会直接新建文件。注意传入的文件名参数不能含有 path separators(即 '/')。该函数返回一个 FileOutputStream 对象,可以调用 write() 方法写入内容(写在save点击事件中):
相应地,文件的读取可以使用 openFileInput(String) 来读取文件。该函数返回一个 FileInput- Stream,调用 read()方法读取内容(写在load点击事件中):
实现文件编辑中的文件存储和文件加载的功能。
完成实验~
实验截图
其他知识
1.Android为应用程序的存储提供了五种方式:
Shared Preferences
Internal Storage
External Storage
SQLite Database
Network Connection
2.Internal Storage 和 External Storage 的区别:
Internal storage:总是可用的;这里的文件默认是只能被自己的app所访问的;当用户卸载app的时候,系统会把internal里面的相关文件都清除干净;Internal是在你想确保不被用户与其他app所访问的最佳存储区域。Internal storage 是属于应用程序的,文件管理器看不见。
External storage:并不总是可用的,因为用户可以选择把这部分作为USB存储模式,这样就不可以访问了;是大家都可以访问的,因此保存到这里的文件是失去访问控制权限的;当用户卸载你的app时,系统仅仅会删除external根目录(getExternalFilesDir)下的相关文件;External是在你不需要严格的访问权限并且你希望这些文件能够被其他app所共享或者是允许用户通过电脑访问时的最佳存储区域。External storage 在文件浏览器里是可以看见的/mnt。
这两个概念都是相对于应用来说的,应该理解为逻辑上的概念,不应理解为物理上的外部SD卡和手机或移动设备内存。一个应用把数据存在external storage上时,那么数据成为共有的,所有人都可见的和可用的。存在internal storage上时,只有这个应用本身可以看到和使用。很多没有插SD卡的设备,系统会虚拟出一部分存储空间用来做公共存储(主要是音乐,文档之类的media)。
3.使用方法:
1)向内部存储器中创建一个私有文件并向其中写入数据,使用以下方法:
a.调用openFileOutput(String fileName, int mode)方法,
若fileName对应的文件存在,就打开该文件,若不存在,并以mode权限创建该文件并打开,该方法返回一个指向fileName对应文件的FileOutputStream,使用这个FileOutputStream可向文件中写入数据。
b.调用FileOutputStream对象的write()方法向文件中写入数据。
c.调用FileOutputStream对象的close()方法关闭文件写入流。
例:向内部存储器中写入一个名为"abc.txt"的文件后,会在内部存储器的/data/data/<package name>/files/目录下生成"abc.txt"文件。
读取内部存储器中私有文件的数据,使用以下方法:
a.调用openFileInputStream(String fileName)方法打开内部存储器中fileName对应的文件,若该文件存在,该方法返回一个指向fileName文件的FileInputStream对象。
b.调用FileInputStream对象的read()方法读取fileName文件中的内容。
c.调用FileInputStream对象的close()方法关闭文件读取流。
2)刚才保存到Internal中的时候什么都没有配置,需要保存到外部的时候需要配置读写的权限,读的权限READ_EXTERNAL_STORAGE,写的权限:READ_EXTERNAL_STORAGE。
首先需要判断一下SD卡是不是可用,因为external storage可能是不可用的比如SD卡被拔出,那么你应该在访问之前去检查是否可用。你可以通过执行 getExternalStorageState()来查询external storage的状态。如果返回的状态是MEDIA_MOUNTED, 那么你可以读写。看到这个获取完之后跟上面保存在内部存储设备的过程一样。是路径是在/mnt/sdcard目录下,如果是弄成私有文件,不允许外部访问,目录是在/mnt/sdcard/Android/data/包名 目录下。
4.其它相关:
如果想在编译时就在应用程序中保存一个不允许修改的文件,就把这个文件保存在/res/raw/目录下。
在程序中打开这个文件可以调用openRawResource(int id)方法, 里面的参数id表示R.raw.<file name>,
这个方法打开后会返回一个InputStream,使用它可以读取这个文件。这个文件不能被执行写入操作。
如果有缓存文件需要保存,而这些文件并不需要永久保存,可以调用getCacheDir()方法,该方法执行后会在内部存储器的/data/data/<package name>/目录下创建一个名为cache/的空目录(或打开cache/目录),并返回一个File对象指向这个(空)文件夹。在这个cache/目录下,可以保存缓存文件,当设备的内部存储器空间不够用时,系统会自动删除一部分cache/目录下的缓存文件,但为了保证系统运行效率,应该手动对cache/目录的大小进行控制,如控制它不能大于1M。当用户卸载应用程序时,cache/目录会连同一起被删除。
例:调用getCacheDir()方法,该方法执行后会在内部存储器的/data/data/<package name>/目录下创建一个名为cache/的空目录。
源码下载
源码下载点击这里~
注
1、本实验实验环境:
操作系统 Windows 10
实验软件 Android Studio 2.2.1
虚拟设备:Nexus_5X
API:23
2、贴代码的时候由于插入代码框的大小问题,代码格式不太严整,望见谅~