文章目录
完整代码在文章结尾。
一、共享参数 SharedPreferences
- SharedPreferences 是 Android 的一个轻量级存储工具,采用的存储结构是 Key-Value 的键值对方式,符合 XML 规范的配置文件。
SharedPreferences 主要适用于:
- 简单且孤立的数据。若是复杂且相互间有关的数据,则要保存在数据库中。
- 文本形式的数据。若是二进制数据,则要保存在文件中。
- 需要持久化存储的数据。在 App 退出后再次启动时,之前保存的数据仍然有效。
SharedPreferences 对数据的存储和读取操作类似与 Map,也有 put 函数用于存储数据、get 函数用于读取数据。在使用前,要先调用 getSharedPreferences 函数声明文件名与操作模式,示例代码:
//从 share.xml 中获取共享参数对象
SharedPreferences shared = getSharedPreferences("share",MODE_PRIVATE);
getSharedPreferences 方法的第一个参数是文件名,上面的 share 表示当前使用的共享参数文件名是 share.xml;第二个参数是操作模式,一般都填 MODE_PRIVATE,表示私有模式。
共享参数存储数据要借助于 Editor 类,示例代码:
SharedPreferences.Editor editor = shared.edit(); //获得编辑器的对象
editor.putString("name","Lee"); //添加一个名叫name的字符串
editor.putInt("age",21);
editor.commit(); //提交编辑器中的修改
共享参数读取数据直接使用对象就可,get 方法第二个参数表示默认值,示例代码:
String name = shared.getString("name","");
int age = shared.getInt("age",0);
以下是一个实例:
在页面上利用 EditText 录入用户注册信息,并保存到共享参数文件中。在另一个页面,App 从共享参数文件中读取用户注册信息(局限在于只能记住一个用户的登录信息)。
写入共享参数:
public class ShareWriteActivity extends AppCompatActivity implements OnClickListener {
private SharedPreferences mShared; // 声明一个共享参数对象
private EditText et_name;
private EditText et_age;
private EditText et_height;
private EditText et_weight;
private boolean bMarried = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_write);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
et_height = findViewById(R.id.et_height);
et_weight = findViewById(R.id.et_weight);
findViewById(R.id.btn_save).setOnClickListener(this);
initTypeSpinner();
// 从share.xml中获取共享参数对象
mShared = getSharedPreferences("share", MODE_PRIVATE);
}
// 初始化婚姻状况的下拉框
private void initTypeSpinner() {
ArrayAdapter<String> typeAdapter = new ArrayAdapter<String>(this,
R.layout.item_select, typeArray);
typeAdapter.setDropDownViewResource(R.layout.item_dropdown);
Spinner sp_married = findViewById(R.id.sp_married);
sp_married.setPrompt("请选择婚姻状况");
sp_married.setAdapter(typeAdapter);
sp_married.setSelection(0);
sp_married.setOnItemSelectedListener(new TypeSelectedListener());
}
private String[] typeArray = {"未婚", "已婚"};
class TypeSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
bMarried = (arg2==0)?false:true;
}
public void onNothingSelected(AdapterView<?> arg0) {
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_save) {
String name = et_name.getText().toString();
String age = et_age.getText().toString();
String height = et_height.getText().toString();
String weight = et_weight.getText().toString();
if (TextUtils.isEmpty(name)) {
showToast("请先填写姓名");
return;
} else if (TextUtils.isEmpty(age)) {
showToast("请先填写年龄");
return;
} else if (TextUtils.isEmpty(height)) {
showToast("请先填写身高");
return;
} else if (TextUtils.isEmpty(weight)) {
showToast("请先填写体重");
return;
}
SharedPreferences.Editor editor = mShared.edit(); // 获得编辑器的对象
editor.putString("name", name); // 添加一个名叫name的字符串参数
editor.putInt("age", Integer.parseInt(age)); // 添加一个名叫age的整型参数
editor.putLong("height", Long.parseLong(height)); // 添加一个名叫height的长整型参数
editor.putFloat("weight", Float.parseFloat(weight)); // 添加一个名叫weight的浮点数参数
editor.putBoolean("married", bMarried); // 添加一个名叫married的布尔型参数
editor.putString("update_time", DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss"));
editor.commit(); // 提交编辑器中的修改
showToast("数据已写入共享参数");
}
}
private void showToast(String desc) {
Toast.makeText(this, desc, Toast.LENGTH_SHORT).show();
}
}
从共享参数读取:
@SuppressLint("DefaultLocale")
public class ShareReadActivity extends AppCompatActivity {
private TextView tv_share;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_read);
tv_share = findViewById(R.id.tv_share);
readSharedPreferences();
}
private void readSharedPreferences() {
// 从share.xml中获取共享参数对象
SharedPreferences shared = getSharedPreferences("share", MODE_PRIVATE);
String desc = "共享参数中保存的信息如下:";
// 获取共享参数中保存的所有映射配对信息
Map<String, Object> mapParam = (Map<String, Object>) shared.getAll();
// 遍历该映射对象,并将配对信息形成描述文字
for (Map.Entry<String, Object> item_map : mapParam.entrySet()) {
String key = item_map.getKey(); // 获取该配对的键信息
Object value = item_map.getValue(); // 获取该配对的值信息
if (value instanceof String) { // 如果配对值的类型为字符串
desc = String.format("%s\n %s的取值为%s", desc, key,
shared.getString(key, ""));
} else if (value instanceof Integer) { // 如果配对值的类型为整型数
desc = String.format("%s\n %s的取值为%d", desc, key,
shared.getInt(key, 0));
} else if (value instanceof Float) { // 如果配对值的类型为浮点数
desc = String.format("%s\n %s的取值为%f", desc, key,
shared.getFloat(key, 0.0f));
} else if (value instanceof Boolean) { // 如果配对值的类型为布尔数
desc = String.format("%s\n %s的取值为%b", desc, key,
shared.getBoolean(key, false));
} else if (value instanceof Long) { // 如果配对值的类型为长整型
desc = String.format("%s\n %s的取值为%d", desc, key,
shared.getLong(key, 0L));
} else { // 如果配对值的类型为未知类型
desc = String.format("%s\n参数%s的取值为未知类型", desc, key);
}
}
if (mapParam.size() <= 0) {
desc = "共享参数中保存的信息为空";
}
tv_share.setText(desc);
}
}
二、数据库 SQLite
- SQLite 是一个小巧的嵌入式数据库,SQLite 的多数 SQL 语法与 Oracle 一样。
SQLiteDatabase 是 SQLite 的数据库管理类,可以在活动页面代码或任何能娶到 Context 的地方获取数据库实例:
//创建名叫 test.db 的数据库,数据库如果不存在就创建,如果存在就打开
SQLiteDatabase db = openOrCreateDatabase(getFilesDir()+"/test.db",Context.MODE_PRIVATE,null);
//删除名叫 test.db 数据库
deleteDatabase(getFilesDir()+"/test.db");
SQLiteDatabase 提供了若干操作数据表的 API,常用的方法有 3 类:
1、 管理类,用于数据库层面的操作
- openDatabase:打开指定路径的数据库
- isOpen:判断数据库是否已经打开
- close:关闭数据库
- getVersion:获取数据库的版本号
- setVersion:设置数据库的版本号
2、事务类,用于事务层面的操作
- beginTransaction:开始事务
- setTransactionSuccessful:设置事务的成功标志
- endTransaction:结束事务。执行本方法时,系统会判断是否已执行 setTransactionSuccessful,如果已设置就提交,未设置就回滚
3、数据处理类,用于数据表层面的操作
- execSQL:执行拼接好的 SQL 控制语句。一般用于建表、删表、变更表结构
- delete:删除符合条件的记录
- update:更新符合条件的记录
- insert:插入一条记录
- query:执行查询操作,返回结果集的游标
- rawQuery:执行拼接好的 SQL 查询语句,返回结果集的游标
数据库帮助器 SQLiteOpenHelper 用于指导开发者进行 SQLite 的合理使用。
SQLiteOpenHelper 具体使用步骤:
- 新建一个继承自 SQLiteOpenHelper 的数据库操作类,重写 onCreate 和 onUpgrade 两个方法。
- 封装保证数据库安全的必要方法,包括获取单例对象、打开数据库连接、关闭数据库连接。
- 提供对表记录进行增加、删除、修改、查询的操作方法。
下面是用户注册信息数据库的 SQLiteOpenHelper 操作类的完整代码:
@SuppressLint("DefaultLocale")
public class UserDBHelper extends SQLiteOpenHelper {
private static final String TAG = "UserDBHelper";
private static final String DB_NAME = "user.db"; // 数据库的名称
private static final int DB_VERSION = 1; // 数据库的版本号
private static UserDBHelper mHelper = null; // 数据库帮助器的实例
private SQLiteDatabase mDB = null; // 数据库的实例
public static final String TABLE_NAME = "user_info"; // 表的名称
private UserDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
private UserDBHelper(Context context, int version) {
super(context, DB_NAME, null, version);
}
// 利用单例模式获取数据库帮助器的唯一实例
public static UserDBHelper getInstance(Context context, int version) {
if (version > 0 && mHelper == null) {
mHelper = new UserDBHelper(context, version);
} else if (mHelper == null) {
mHelper = new UserDBHelper(context);
}
return mHelper;
}
// 打开数据库的读连接
public SQLiteDatabase openReadLink() {
if (mDB == null || !mDB.isOpen()) {
mDB = mHelper.getReadableDatabase();
}
return mDB;
}
// 打开数据库的写连接
public SQLiteDatabase openWriteLink() {
if (mDB == null || !mDB.isOpen()) {
mDB = mHelper.getWritableDatabase();
}
return mDB;
}
// 关闭数据库连接
public void closeLink() {
if (mDB != null && mDB.isOpen()) {
mDB.close();
mDB = null;
}
}
// 创建数据库,执行建表语句
public void onCreate(SQLiteDatabase db) {
Log.d(TAG, "onCreate");
String drop_sql = "DROP TABLE IF EXISTS " + TABLE_NAME + ";";
Log.d(TAG, "drop_sql:" + drop_sql);
db.execSQL(drop_sql);
String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+ "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"
+ "height LONG NOT NULL," + "weight FLOAT NOT NULL,"
+ "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"
//演示数据库升级时要先把下面这行注释
+ ",phone VARCHAR" + ",password VARCHAR"
+ ");";
Log.d(TAG, "create_sql:" + create_sql);
db.execSQL(create_sql);
}
// 修改数据库,执行表结构变更语句
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG, "onUpgrade oldVersion=" + oldVersion + ", newVersion=" + newVersion);
if (newVersion > 1) {
//Android的ALTER命令不支持一次添加多列,只能分多次添加
String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "phone VARCHAR;";
Log.d(TAG, "alter_sql:" + alter_sql);
db.execSQL(alter_sql);
alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";
Log.d(TAG, "alter_sql:" + alter_sql);
db.execSQL(alter_sql);
}
}
// 根据指定条件删除表记录
public int delete(String condition) {
// 执行删除记录动作,该语句返回删除记录的数目
return mDB.delete(TABLE_NAME, condition, null);
}
// 删除该表的所有记录
public int deleteAll() {
// 执行删除记录动作,该语句返回删除记录的数目
return mDB.delete(TABLE_NAME, "1=1", null);
}
// 往该表添加一条记录
public long insert(UserInfo info) {
ArrayList<UserInfo> infoArray = new ArrayList<UserInfo>();
infoArray.add(info);
return insert(infoArray);
}
// 往该表添加多条记录
public long insert(ArrayList<UserInfo> infoArray) {
long result = -1;
for (int i = 0; i < infoArray.size(); i++) {
UserInfo info = infoArray.get(i);
ArrayList<UserInfo> tempArray = new ArrayList<UserInfo>();
// 如果存在同名记录,则更新记录
// 注意条件语句的等号后面要用单引号括起来
if (info.name != null && info.name.length() > 0) {
String condition = String.format("name='%s'", info.name);
tempArray = query(condition);
if (tempArray.size() > 0) {
update(info, condition);
result = tempArray.get(0).rowid;
continue;
}
}
// 如果存在同样的手机号码,则更新记录
if (info.phone != null && info.phone.length() > 0) {
String condition = String.format("phone='%s'", info.phone);
tempArray = query(condition);
if (tempArray.size() > 0) {
update(info, condition);
result = tempArray.get(0).rowid;
continue;
}
}
// 不存在唯一性重复的记录,则插入新记录
ContentValues cv = new ContentValues();
cv.put("name", info.name);
cv.put("age", info.age);
cv.put("height", info.height);
cv.put("weight", info.weight);
cv.put("married", info.married);
cv.put("update_time", info.update_time);
cv.put("phone", info.phone);
cv.put("password", info.password);
// 执行插入记录动作,该语句返回插入记录的行号
result = mDB.insert(TABLE_NAME, "", cv);
// 添加成功后返回行号,失败后返回-1
if (result == -1) {
return result;
}
}
return result;
}
// 根据条件更新指定的表记录
public int update(UserInfo info, String condition) {
ContentValues cv = new ContentValues();
cv.put("name", info.name);
cv.put("age", info.age);
cv.put("height", info.height);
cv.put("weight", info.weight);
cv.put("married", info.married);
cv.put("update_time", info.update_time);
cv.put("phone", info.phone);
cv.put("password", info.password);
// 执行更新记录动作,该语句返回记录更新的数目
return mDB.update(TABLE_NAME, cv, condition, null);
}
public int update(UserInfo info) {
// 执行更新记录动作,该语句返回记录更新的数目
return update(info, "rowid=" + info.rowid);
}
// 根据指定条件查询记录,并返回结果数据队列
public ArrayList<UserInfo> query(String condition) {
String sql = String.format("select rowid,_id,name,age,height,weight,married,update_time," +
"phone,password from %s where %s;", TABLE_NAME, condition);
Log.d(TAG, "query sql: " + sql);
ArrayList<UserInfo> infoArray = new ArrayList<UserInfo>();
// 执行记录查询动作,该语句返回结果集的游标
Cursor cursor = mDB.rawQuery(sql, null);
// 循环取出游标指向的每条记录
while (cursor.moveToNext()) {
UserInfo info = new UserInfo();
info.rowid = cursor.getLong(0); // 取出长整型数
info.xuhao = cursor.getInt(1); // 取出整型数
info.name = cursor.getString(2); // 取出字符串
info.age = cursor.getInt(3);
info.height = cursor.getLong(4);
info.weight = cursor.getFloat(5); // 取出浮点数
//SQLite没有布尔型,用0表示false,用1表示true
info.married = (cursor.getInt(6) == 0) ? false : true;
info.update_time = cursor.getString(7);
info.phone = cursor.getString(8);
info.password = cursor.getString(9);
infoArray.add(info);
}
cursor.close(); // 查询完毕,关闭游标
return infoArray;
}
// 根据手机号码查询指定记录
public UserInfo queryByPhone(String phone) {
UserInfo info = null;
ArrayList<UserInfo> infoArray = query(String.format("phone='%s'", phone));
if (infoArray.size() > 0) {
info = infoArray.get(0);
}
return info;
}
}
完整代码在文章结尾,实在太长了
三、SD 卡文件操作
获取手机上的 SD 卡信息通过 Environment 类实现,该类是 App 获取各种目录信息的工具,主要有 7 种方法:
- getRootDirectory:获得系统根目录的路径
- getDataDirectory:获得系统数据目录的路径
- getDownloadCacheDirectory:获得下载缓存目录的路径
- getExternalStorageDirectory:获得外部存储(SD卡)的路径
- getExternalStorageState:获得 SD 卡的状态
- getStorageState:获得指定目录的状态
- getExternalStoragePublicDirectory:获得 SD 卡指定类型目录的路径
外部存储分为公共空间和私有空间两部分。
- 获取公共空间的存储路径,调用的是
Environment.getExternalStoragePublicDirectory
方法; - 获取应用私有空间的存储路径,调用的是
getExternalFilesDir
方法。
文本文件写入:
文本文件的读写一般借助于 FileOutputStream 和 FileInputStream 。FileOutputStream 用于写文件,FileInputStream 用于读文件。
public static void saveText(String path, String txt) {
try {
// 根据指定文件路径构建文件输出流对象
FileOutputStream fos = new FileOutputStream(path);
// 把字符串写入文件输出流
fos.write(txt.getBytes());
// 关闭文件输出流
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 从指定路径的文本文件中读取内容字符串
public static String openText(String path) {
String readStr = "";
try {
// 根据指定文件路径构建文件输入流对象
FileInputStream fis = new FileInputStream(path);
byte[] b = new byte[fis.available()];
// 从文件输入流读取字节数组
fis.read(b);
// 把字节数组转换为字符串
readStr = new String(b);
// 关闭文件输入流
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
// 返回文本文件中的文本字符串
return readStr;
}
图片文件读写:
Android 的图片处理类是 Bitmap,App 读写 Bitmap 可以使用 FileOutputStream 和 FileInputStream。但在实际开发中,读写图片文件一般用性能更好的 BufferedOutputStream 和 BufferedInputStream。
保存图片文件时用到 Bitmap 的 compress 方法,可指定图片类型和压缩质量;打开图片文件时使用 BitmapFactory 的 decodeStream 方法:
// 把位图数据保存到指定路径的图片文件
public static void saveImage(String path, Bitmap bitmap) {
try {
// 根据指定文件路径构建缓存输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
// 把位图数据压缩到缓存输出流中
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos);
// 完成缓存输出流的写入动作
bos.flush();
// 关闭缓存输出流
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 从指定路径的图片文件中读取位图数据
public static Bitmap openImage(String path) {
Bitmap bitmap = null;
try {
// 根据指定文件路径构建缓存输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
// 从缓存输入流中解码位图数据
bitmap = BitmapFactory.decodeStream(bis);
bis.close(); // 关闭缓存输入流
} catch (Exception e) {
e.printStackTrace();
}
// 返回图片文件中的位图数据
return bitmap;
}
四、Application
适合在 Application 中保存的全局变量主要有 3 种数据:
- 会频繁读取的信息;
- 从网络上获取的临时数据,如logo、商品图片
- 容易因频繁分配内存而导致内存泄漏的对象,如 Handle 对象等
五、ContentProvider
1、内容提供器 ContentProvider
ContentProvider 为 App 存取内存数据提供统一的外部接口,让不同的应用之间得以共享数据。
在实际编码中,ContentProvider 只是一个服务端的数据存取接口,开发者需要在其基础上实现一个具体类,并重写以下方法:
- onCreate:创建数据库并获得连接
- query:查询数据
- insert:插入数据
- update:更新数据
- delete:删除数据
- getType:获取数据类型
下面是使用 ContentProvider 提供用户信息对外接口的代码:
public class UserInfoProvider extends ContentProvider {
private final static String TAG = "UserInfoProvider";
private UserDBHelper userDB; // 声明一个用户数据库的帮助器对象
public static final int USER_INFO = 1; // Uri匹配时的代号
public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static { // 往Uri匹配器中添加指定的数据路径
uriMatcher.addURI(UserInfoContent.AUTHORITIES, "/user", USER_INFO);
}
// 根据指定条件删除数据
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
if (uriMatcher.match(uri) == USER_INFO) {
// 获取SQLite数据库的写连接
SQLiteDatabase db = userDB.getWritableDatabase();
// 执行SQLite的删除操作,返回删除记录的数目
count = db.delete(UserInfoContent.TABLE_NAME, selection, selectionArgs);
db.close(); // 关闭SQLite数据库连接
}
return count;
}
// 插入数据
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri newUri = uri;
if (uriMatcher.match(uri) == USER_INFO) {
// 获取SQLite数据库的写连接
SQLiteDatabase db = userDB.getWritableDatabase();
// 向指定的表插入数据,返回记录的行号
long rowId = db.insert(UserInfoContent.TABLE_NAME, null, values);
if (rowId > 0) { // 判断插入是否执行成功
// 如果添加成功,利用新记录的行号生成新的地址
newUri = ContentUris.withAppendedId(UserInfoContent.CONTENT_URI, rowId);
// 通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(newUri, null);
}
db.close(); // 关闭SQLite数据库连接
}
return uri;
}
// 创建ContentProvider时调用,可在此获取具体的数据库帮助器实例
@Override
public boolean onCreate() {
userDB = UserDBHelper.getInstance(getContext(), 1);
return false;
}
// 根据指定条件查询数据库
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
if (uriMatcher.match(uri) == USER_INFO) {
// 获取SQLite数据库的读连接
SQLiteDatabase db = userDB.getReadableDatabase();
// 执行SQLite的查询操作
cursor = db.query(UserInfoContent.TABLE_NAME,
projection, selection, selectionArgs, null, null, sortOrder);
// 设置内容解析器的监听
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
// 获取Uri数据的访问类型,暂未实现
@Override
public String getType(Uri uri) {
throw new UnsupportedOperationException("Not yet implemented");
}
// 更新数据,暂未实现
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
2、内容解析器 ContentResolver
内容解析器 ContentResolver 是客户端 App 操作服务端数据的工具,相对应的内容提供器是服务端的数据接口。
要获取 ContentResolver 对象,在 Activity 代码中调用 getContentResolver 方法即可。
3、内容观察器 ContentObserver
给目标内容注册一个观察器,目标内容的数据一旦发生变化,观察器规定好的动作马上触发,从而执行开发者预先定义的代码。
- registerContentObserver:注册内容观察器
- unregisterContentObserver:注销内容观察器
- notifyChange:通知内容观察器发生了数据变化