实现数据持久化的方式
1 SharedPreferences存储数据(如app的配置信息)
2 文件存储数据(I/O流 如保存网络图片)
3 SQLite数据库存储数据(如保存网络数据)
4 ContentProvider
5 网络存储数据
一、 SharedPreferences
1.1 简单介绍
app基本都需要保存用户的设置信息,比如是否自动登录,是否记住账号密码,是否打开音效,是否使用震动,是否在Wifi下才能联网,小游戏的玩家积分,解锁口令等相关信息,此时使用sharedPreferences进行存储数据,当然会有人说可以使用数据库啊,可是在上述提到的情况下使用数据库的话,多少会有点小题大做的感觉。
- SharedPreferences是一个轻量级的存储类
- SharedPreferences数据总是存储在/data/data/<包名>/shared_prefs目录下(通过DDMS可知)
- 保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。
- 保存少量的数据,且这些数据的格式非常简单:字符串型,基本类型的值
- SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内
部接口Editor对象实现。
1.2 获取SharedPreferences
有两种方法:
(1)getSharedPreferences()---如果应用中有多个 Shared Preferences 文件需要保存,这个方法很适合第一个参数为你给这个文件指定的 ID,可以通过应用的上下文调用,getSharedPreferences 是 Context 类中的方法, 可以指定 filename 以及 mode。
(2)getPreferences()---如果应用只需要保存一个 Shared Preferences 文件,这个方法很适合由于应用只有一个 Shared Preferences 文件,所以不需要为其指定名称,系统在创建时会默认一个名称getPreferences 是 Activity 类中的方法,只需指定 mode。
1.3 mode
Context.MODE_PRIVATE | 指定该SharedPreferences数据只能被本应用程序读、写。 |
Context.MODE_WORLD_READABLE | 指定该SharedPreferences数据能被其他应用程序读,但不能写。(google建议,如果该数据需要被其他应用读取,应该使用ContentProvider 在API17以后该属性已经被逐步启用) |
Context.MODE_WORLD_WRITEABLE | 指定该SharedPreferences数据能被其他应用程序读,写。(google建议,如果该数据需要被其他应用读取,应该使用ContentProvider 在API17以后该属性已经逐步启用) |
1.4 SharedPreferences.Editor 方法
clear() | 清空数据 |
commit() | 保存写入数据 |
putBoolean(String, boolean value) | 存入boolean类型数据 |
putFloat(String key, float value) | 存入float数据类型 |
putInt(String, key, int value) | 存入int类型数据 |
putLong(String long value) | 存入Long类型数据 |
putString(String key, String value) | 存入String类型数据 |
remove(String key) | 通过key值移除数据 |
1.5读取SharedPreferences相关方法
contains(String key) | 判断是否包含该key,返回值为Boolean类型 |
edit() | 获取Editor对象 |
getAll() | 获取所有的数据,返回值为Map |
getInt(String key, Int defVAlue) | 通过key获取int类型数据,参数2为当查找的key不存在时函数的默认返回值(其他类型也是如此) |
1.6示例代码
上文已给出getSharedPreFerences()和getPreferences()的区别,这里就给出getSharedPreFerences()的代码
如上布局较为简单,代码就不粘贴了。
public class MainActivity extends AppCompatActivity {
private EditText main_et_name;
private EditText main_et_pwd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化View
initView();
}
/**
* 初始化View
*/
private void initView() {
//获取文本输入框中的内容
main_et_name = (EditText) findViewById(R.id.main_et_name);
main_et_pwd = (EditText) findViewById(R.id.main_et_pwd);
}
/**
* btn 点击处理
* @param v
*/
public void btnClick(View v){
//
//[1]定义路径
String packageName = getApplicationContext().getPackageName();
String path = "/data/data/"+packageName+
"/shared_prefs/com.example.student.xml";
//[2]判断路径是否存在
File file = new File(path);
if (file.exists()){
//[2.1]存在则读取内容
read();
}else{
//不存在则写入 将内容写入到文件中去
String name = main_et_name.getText().toString().trim();
String pwd = main_et_pwd.getText().toString().trim();
//内容若为空则不存储
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)){
Toast.makeText(MainActivity.this, "不能为空 ", Toast.LENGTH_SHORT).show();
return;
}
//将内容写入文件
save(name,pwd);
}
}
/**
* 读取数据
*/
private void read() {
//获取到shared
SharedPreferences preferences = getSharedPreferences("com.example.student", MODE_PRIVATE);
//获取所有key_values
Map<String, ?> all = preferences.getAll();
//将Map中的所有Key获取到并存储到set集合中
Set<String> set = all.keySet();
//获取到set集合的迭代器
Iterator<String> iterator = set.iterator();
//遍历迭代器
while (iterator.hasNext()) {
//获取迭代器中的内容
String key = iterator.next();
String value = (String) all.get(key);
if (key.equals("name")) {
main_et_name.setText(value);
} else if (key.equals("pwd")) {
main_et_pwd.setText(value);
}
}
}
/**
* 存储
* @param name
* @param pwd
*/
private void save(String name, String pwd) {
//获取Shared Preferences
SharedPreferences preferences = getSharedPreferences("com.example.student", MODE_PRIVATE);
//获取SharedPreferences中的内部类 Editor
SharedPreferences.Editor edit = preferences.edit();
//将数据存放edit对象中
edit.putString("name",name);
edit.putString("pwd",pwd);
//将数据写入到文件中
edit.commit();
}
}
二、文件存储
核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选:
MODE_PRIVATE | 为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可 以使用Context.MODE_APPEND |
MODE_APPEND | 模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 |
MODE_WORLD_READABLE | 表示当前文件可以被其他应用读取; |
MODE_WORLD_WRITEABLE | 表示当前文件可以被其他应用写入 |
除此之外, Context还提供了如下几个重要的方法:
getDir(String name , int mode): | 在应用程序的数据文件夹下获取或者创建name对应的子目录 |
File getFilesDir() | 获取该应用程序的数据文件夹得绝对路径 |
String[] fileList() | 返回该应用数据文件夹的全部文件 |
getCAcheDir() | 用于获取/data/data/包名/cache目录 |
2.1内部存储
注意内部存储不是内存。内部存储位于系统中很特殊的一个位置,如果你想将文件存储于内部存储中,那么文件默认只能被你的应用访问到,且一个应用所创建的所有文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。当一个应用卸载之后,内部存储中的这些文件也被删除
public class MainActivity extends AppCompatActivity {
private EditText main_et_in;
private EditText main_et_out;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化组件
initView();
}
/**
* 初始化组件
*/
private void initView() {
main_et_in = ((EditText) findViewById(R.id.main_et_in));
main_et_out = ((EditText) findViewById(R.id.main_et_out));
}
/**
* btn事件处理
* @param v
*/
public void btnClick(View v){
switch (v.getId()) {
case R.id.main_btn_save:
//获取文本输入框中内容
String content = main_et_in.getText().toString().trim();
if (TextUtils.isEmpty(content)){
Toast.makeText(MainActivity.this, "内容不能为空", Toast.LENGTH_SHORT).show();
return;
}
//调用方法 进行存储openFileOutput()方法的第一参数用于指定文件名称,
// 不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它
boolean flag =save("data12", content);
if (flag) {
Toast.makeText(MainActivity.this, "操作完成", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(MainActivity.this, "操作失败", Toast.LENGTH_SHORT).show();
}
break;
case R.id.main_btn_show:
//获取到内容
String buf = read("data12");
//简单校验
if (TextUtils.isEmpty(buf)){
Toast.makeText(MainActivity.this, "没有读取到内容", Toast.LENGTH_SHORT).show();
return;
}
//将获取到内容 设置到文本输出框中
main_et_out.setText(buf);
break;
}
}
/**
* 使用内部存储 存储数据 ---IO流
* data/data/<package-name>/files/文件名
* @param fileName 文件的名字
* @param fileContent 文件的内容
*/
private boolean save(String fileName, String fileContent) {
boolean flag = false;
FileOutputStream fileOutputStream = null;
BufferedWriter writer = null;
try {
//获取输出流
fileOutputStream = openFileOutput(fileName,
Context.MODE_PRIVATE);
//获取 缓冲区字符流输出流 ---- 转换流
writer = new BufferedWriter(
new OutputStreamWriter(fileOutputStream));
//写出数据
writer.write(fileContent);
// 将标志位设置为true;
flag = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭字符输出流
if( writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭 字节输出流
if (fileOutputStream!=null){
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 将标识返回
return flag;
}
/**
* 内部存储 读取操作
*
* @param fileName 文件名
* @return 文件中的内容
*/
public String read(String fileName){
String ret = "";
FileInputStream fis = null;
BufferedReader br = null;
try {
//创建文件输入流
fis = openFileInput(fileName);
//创建字符输出流
br =new BufferedReader(
new InputStreamReader(fis));
//读取内容
//缓存
String temp = null;
//存储所有的数据
StringBuffer sb = new StringBuffer();
while( (temp =br.readLine()) !=null ){
//存储所有的数据
sb.append(temp);
}
//将sb 转换为字符串 并返回
// return sb.toString();
ret = sb.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//return "";
return ret;
}
}
2.2 外部存储
外部存储中的文件是可以被用户或者其他应用程序修改的,所以系统不应该存储敏感数据在外部存储上。
调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
权限
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
获取SD卡的目录
Environment.getExternalStorageDirectory().getAbsolutePath()
主要代码:
// 文件写操作函数
private void write(String content) {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) { // 如果sdcard存在
File file = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath()+"/aaa"); // 定义File类对象,定义文件存储路径
if (!file.exists()) { // 父文件夹不存在
file.mkdirs(); // 创建文件夹
}
PrintStream out = null; // 打印流对象用于输出
try {
out = new PrintStream(new FileOutputStream(file, true)); // 追加文件
out.println(content);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close(); // 关闭打印流
}
}
} else { // SDCard不存在,使用Toast提示用户
Toast.makeText(this, "保存失败,SD卡不存在!", Toast.LENGTH_LONG).show();
}
}
// 文件读操作函数
private String read() {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) { // 如果sdcard存在
File file = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath()+"/aaa"); // 定义File类对象,此处路径为sd卡存在路径
if (!file.exists()) { // 父文件夹不存在
file.mkdirs(); // 创建文件夹
}
Scanner scan = null; // 扫描输入
StringBuilder sb = new StringBuilder();
try {
scan = new Scanner(new FileInputStream(file)); // 实例化Scanner
while (scan.hasNext()) { // 循环读取
sb.append(scan.next() + "\n"); // 设置文本
Log.e("TAG", "read: "+sb );
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (scan != null) {
scan.close(); // 关闭打印流
}
}
} else { // SDCard不存在,使用Toast提示用户
Toast.makeText(this, "读取失败,SD卡不存在!", Toast.LENGTH_LONG).show();
}
return null;
}
三 、SQLite
想要学习SQLite数据库就需要了解SQL, 全称Structured Query Language(结构化查询语言),主要用于操作数据库的语言。数据库文件会存放在/data/data/<package name>/databases/目录下。 SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的 ACID 事务。
3.1 SQL的基础语句
(1)增(插入数据):Insert into 表名 (字段名1, 字段名2) values (值1, 值2);
如果值是数据,我们可以直接使用,如果是字符串,就需要使用引号
eg:插入一条数据
Insert into Student(_id,name,age)values(7,‘二子’,19);
Insert into Student values(7,‘老大’,21,‘11’,‘333’);
(2)删:Delete from 表名 where 条件
删除棉铃为2 的数据
delete from student where age = 2;
删除所有 从上到下一条一条删除
delete from student;
(3)改:Update 表名 set 字段名=值, 字段名=值 where 条件
update student set time = ‘1999-9-9’;
update student set time = ‘2012-12-12’ where age = 19;
(4)查:select 字段1 字段2 from 表名 where 条件
查询student表中的所有数据
Select*from Student
Select * From MAIN.[Student] Limit 1000;
--查询Student中的数据
select * from student;
--查询Student中age为43
select * from Student where age = 43;
--查询所有的姓名和年龄
select name,age from Student;
--查询年龄倒序 asc为默认正序 desc倒序
select * from Student order by age desc;
--取出三条数据从第二条开始
--limit 开始索引位置 , 取几条 索引是从零开始
select * from Student limit 1,2;
--当前Student一共有几条
select count(*) from Student;
--那么字段一共有几条数据
select count(name) from student;
--别名
--字段 as 别名
select count(*) as total from student;
3.2 SQLiteOpenHelper(SQLite打开帮助器)
SQLite是轻量级嵌入式数据库引擎,提供数据库打开、关闭等操作函数,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧。
SQLiteOpenHelper是一个抽象类。一般的用法是常见SQLiteOpenHelper的子类,并重写onCreate、onUpgrade方法。除上述两个必须实现的方法外,还可以选择性实现onOpen方法,该方法会在每次打开数据库时被调用。
/**
* 数据库帮助类
* Created by liruijie on 16/6/25.
*/
public class DBHelper extends SQLiteOpenHelper {
//数据库名称
public static final String DATABASE_NAME = "moblie.db";
//数据库版本
public static final int DATABASE_VERSION = 1;
//表名
public static final String TABLE_NAME = "call_table";
/**
* 由于每次都调用四个参数太繁琐,还可能出现问题,所以将MyOpenHelper构造器进行优化
* @param context
*/
public DBHelper(Context context){
super(context,DATABASE_NAME,null,DATABASE_VERSION);
}
/**
* 构造器
* @param context 上下文
* @param name 数据库名字
* @param factory 默认值 null 即可
* @param version 当前数据库的版本号 只能增加不能减小
*/
public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建一个表
String sql = "CREATE TABLE " + TABLE_NAME + " (" +
" id integer PRIMARY KEY AUTOINCREMENT," +
" name varchar(20)," +
" moblie varchar(20)," +
" time varchar(10)" +
");";
//执行创建
db.execSQL(sql);
//定义一个增加的语句数组
String[] addSql = {
"insert into " + TABLE_NAME + " (name, moblie,time) values('小明', '1234567890','2016-06-25')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小红', '456455460','2016-06-25')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小绿', '76556345890','2016-05-25')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小灰', '234235290','2016-05-28')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小白', '3453345390','2016-06-01')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小蓝', '15675688890','2016-06-02')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小黄', '34545623890','2016-06-02')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小李', '13453463460','2016-06-09')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小贾', '456745690','2016-06-10')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小张', '234234890','2016-06-14')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小丁', '34534590643','2016-06-15')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('小弟', '4563463450','2016-06-17')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('阿猫', '234235890','2016-06-18')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('阿狗', '23423523590','2016-06-18')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('公公', '3453462390','2016-06-19')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('阿哥', '23423523490','2016-06-20')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('皇上', '52342353290','2016-06-21')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('妃子', '4353462340','2016-06-21')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('丁丁', '3453234220','2016-06-21')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('姚明', '2342352890','2016-06-23')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('詹姆斯', '3452352890','2016-06-24')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('库里', '2343235890','2016-06-24')"
, "insert into " + TABLE_NAME + " (name, moblie,time) values('欧文', '6456242890','2016-06-25')"
};
//循环执行增加数据
for (int i = 0; i < addSql.length; i++) {
db.execSQL(addSql[i]);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//数据库更新会调用
}
}
3.3 SQLiteDatabase
执行对数据库的插入记录、查询记录等操作,需要创建构建OpenHelper对象,并调用getReadableDatabase或getWritableDatabase来创建并打开数据库。
getReadableDatabase()并不是以只读方式打开数据库,而是先执行getWritableDatabase(),失败的情况下才调用。
getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。
但getWritableDatabase()方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,
getWritableDatabase()打开数据库就会出错。getReadableDatabase()方法先以读写方式打开数据库,倘若使用如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
使用SQL语句对数据的操作,主要是用到了SQLiteDatabase中的execSQl方法和rawQuery方法:
execSQL()方法可执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句;
rawQuery()方法用于执行select语句。
增(其他语句类似,这里就不一一写出了):
public void testInsert(){
MyOpenHelper mo = MyOpenHelper(getContext(), "student.db",null,1);
SQLiteDatabase db = mo.getReadableDatabase();
//使用占位符
db.execSQL("insert into stu(name,phone,salary) values (?,?,?)",
new Object[]{"张三",“11111”,33});
//关闭数据库连接
db.close();
}
3.4 使用Android API实现对数据库的增删改查操作
增:
//创建ContentValues对象
ContentValues values = new ContentValues();
values.put("name","彪哥");
values.put("age",22);
values.put("shouru",6999);
//进行插入数据
db.insert("stu",null,values);
删:
public void testDeletedAPI(){
//delete from stu where _id = 2;
int r = db.delete("stu","age=?",new String[]{"22"});
}
改:
//定义更新的数据
ContentValues values1 = new ContentValues();
values1.put("shouru",10000);
values1.put("age",20);
db.update("stu",values1,"name=?",new String[]{"张三"});
查:
//第一个参数是 表名
//第二个参数是 要查询的列名 所有的话使用null即可
//第三个参数是 where 子句 如果为空则返回表的所有行
//第四个参数是 where 子句对应的条件值
//第五个参数式 分组方式,若为空怎不分组
//第六个参数是 having条件
//第七个参数是 排序方式 order by
//第八个参数是 限制返回的记录的条数 limit
//select*from stu
//Cursor为游标,用来访问查询结果中的记录
Cursor cursor = db.query("stu",null,null,null,null,null,null,null);
//数据总数
Log.e("数据总数","数据总数: "+cursor.getCount());
//遍历获取cursor中的数据
if (cursor != null){
while (cursor.moveToNext()){
int age = cursor.getInt(cursor.getColumnIndex("age"));
int id = cursor.getInt(cursor.getColumnIndex("_id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
int shouru = cursor.getInt(cursor.getColumnIndex("shouru"));
3.5完整代码
布局:使用的是ListView
activity_main.xml布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="通话记录" />
<Button
android:id="@+id/btn_add"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:background="@drawable/add_bg" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#cccccc" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
item_callrecord_list.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="name" />
<TextView
android:id="@+id/moblie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="@id/name"
android:layout_margin="5dp"
android:text="moblie" />
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="5dp"
android:text="time" />
<Button
android:id="@+id/delete"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:background="@drawable/delete_bg" />
</RelativeLayout>
数据库帮助类上边已经给出,这里就不重复写了。
callRecord通话记录实体类:
public class CallRecord {
private int id;//数据表中对应的id,方便操作数据库
private String name;
private String moblie;
private String time;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMoblie() {
return moblie;
}
public void setMoblie(String moblie) {
this.moblie = moblie;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
数据库工具类:
public class DBUtils {
/**
* 从数据库获取通话记录
*
* @param helper 数据库帮助类
* @return
*/
public static List<CallRecord> getCallRecordList(DBHelper helper) {
List<CallRecord> list = new ArrayList<>();
SQLiteDatabase db = helper.getReadableDatabase();
String sql = "select * from " + DBHelper.TABLE_NAME + " order by id DESC";
Cursor cursor = db.rawQuery(sql, null);
while (cursor.moveToNext()) {
CallRecord callRecord = new CallRecord();
callRecord.setId(cursor.getInt(cursor.getColumnIndex("id")));
callRecord.setName(cursor.getString(cursor.getColumnIndex("name")));
callRecord.setMoblie(cursor.getString(cursor.getColumnIndex("moblie")));
callRecord.setTime(cursor.getString(cursor.getColumnIndex("time")));
list.add(callRecord);
}
db.close();
return list;
}
/**
* 通过id删除数据库中对应的数据
*
* @param helper 数据库帮助对象
* @param id id
* @return 操作成功与否代码 小于0为操作失败
*/
public static int deleteRecordById(DBHelper helper, int id) {
SQLiteDatabase db = helper.getReadableDatabase();
String whereStr = "id=?";
String[] whereArgs = new String[]{String.valueOf(id)};
int resultCode = db.delete(DBHelper.TABLE_NAME, whereStr, whereArgs);
db.close();
return resultCode;
}
/**
* 开启一个新线程去删除数据,删除操作之后将返回码通过Handler通知UI线程
*
* @param helper 数据库帮助类
* @param id 要删除的数据id
* @param handler 通知UI线程的工具
*/
public static void statrNewThreadDeleteAndMessageUI(final DBHelper helper, final int id, final Handler handler) {
new Thread(new Runnable() {
@Override
public void run() {
int resultCode = DBUtils.deleteRecordById(helper, id);
Message message = handler.obtainMessage();
message.arg1 = resultCode;//删除结果返回码
message.arg2 = id;//删除数据对应的Id
message.what = 0x01;
handler.sendMessage(message);
}
}).start();
}
/**
* 插入一条数据,这里是固定的数据,只是时间不同
*
* @param helper
* @return
*/
public static long insertNewDataToDataBase(DBHelper helper) {
//获取一个可以写操作的数据库对象
SQLiteDatabase db = helper.getWritableDatabase();
//类似map的一个类,可以通过put方法存放键值对,进而可以被数据库类进行操作
ContentValues contentValues = new ContentValues();
contentValues.put("name", "Bosh");
contentValues.put("moblie", "1218297128739");
contentValues.put("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//调用insert方法插入数据,第一个参数:表名,第二个参数:null,第三个参数:ContentValues
long resultCode = db.insert(DBHelper.TABLE_NAME, null, contentValues);
//操作完成后,关闭数据库
db.close();
return resultCode;
}
/**
* 开启一个新线程去插入数据,插入操作之后将返回码通过Handler通知UI线程
*
* @param helper
* @param handler
*/
public static void statrNewThreadAddAndMessageUI(final DBHelper helper, final Handler handler) {
new Thread(new Runnable() {
@Override
public void run() {
long resultCode = DBUtils.insertNewDataToDataBase(helper);
Message message = handler.obtainMessage();
message.obj = resultCode;//插入结果返回码
message.what = 0x02;
handler.sendMessage(message);
}
}).start();
}
}
LIstView的适配器:
public class CallRecordListAdapter extends BaseAdapter {
private Context mContext;
private List<CallRecord> mList;
//要操作数据库,所以需要帮助类
private DBHelper mDBHelper;
//用来通知删除操作之后的结果给Activity
private Handler mHandler;
public CallRecordListAdapter(Context context, List<CallRecord> list, DBHelper helper, Handler handler) {
this.mContext = context;
this.mList = list;
this.mDBHelper = helper;
this.mHandler = handler;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_callrecord_list, null);
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.moblie = (TextView) convertView.findViewById(R.id.moblie);
holder.time = (TextView) convertView.findViewById(R.id.time);
holder.delete = (Button) convertView.findViewById(R.id.delete);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final CallRecord callRecord = mList.get(position);
holder.name.setText(callRecord.getName());
holder.moblie.setText(callRecord.getMoblie());
holder.time.setText(callRecord.getTime());
holder.delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取要删除数据的id
int id = callRecord.getId();
//因为要操作数据库所以要新开启一个线程来操作
DBUtils.statrNewThreadDeleteAndMessageUI(mDBHelper, id, mHandler);
}
});
return convertView;
}
class ViewHolder {
private TextView name;
private TextView moblie;
private TextView time;
private Button delete;
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
//数据库帮助类
private DBHelper dbHelper;
//listView数据集
private List<CallRecord> dataList;
//listView适配器
private CallRecordListAdapter adapter;
//列表控件
private ListView listView;
//增加按钮
private Button btn_add;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0://获取数据成功,创建adapter,绑定listView
adapter = new CallRecordListAdapter(MainActivity.this, dataList, dbHelper, handler);
listView.setAdapter(adapter);
break;
case 0x01://删除操作之后调用
//删除操作之后返回的删除结果码
int deleteResultCode = msg.arg1;
//被删除的数据的id
int id = msg.arg2;
//判断结果码是否为操作成功
if (deleteResultCode > 0) {
//循环获取操作成功的数据的id值在已经绑定给listview的adapter的数据中的位置
for (int i = 0; i < dataList.size(); i++) {
int itemId = dataList.get(i).getId();
if (id == itemId)
dataList.remove(i);//如果id相同就删除
adapter.notifyDataSetChanged();//通知adapter刷新数据
}
} else {
Toast.makeText(MainActivity.this, "操作失败!", Toast.LENGTH_SHORT).show();
}
break;
case 0X02://插入线程操作完成后调用
long insertResultCode = (long) msg.obj;
if (insertResultCode > 0) {
getDataFromDB();//插入成功之后重新获取数据
} else {
Toast.makeText(MainActivity.this, "操作失败!", Toast.LENGTH_SHORT).show();
}
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
getDataFromDB();
initEvent();
}
/**
* 初始化事件
*/
private void initEvent() {
btn_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开启新线程插入数据并将结果通知UI,这里是添加的固定数据,可以想想怎么添加动态数据
DBUtils.statrNewThreadAddAndMessageUI(dbHelper, handler);
}
});
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
//弹出对话框提示用户
showDialogToUser(position);
return false;
}
});
}
/**
* 弹出确认删除框
*
* @param position 长按的Item下标
*/
private void showDialogToUser(int position) {
//获取长按item所对应的CallRecord
final CallRecord callRecord = dataList.get(position);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("确认删除吗?");
builder.setTitle("提示");
builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DBUtils.statrNewThreadDeleteAndMessageUI(dbHelper, callRecord.getId(), handler);
dialog.dismiss();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.create().show();
}
/**
* 初始化数据集
*/
private void initData() {
//上下文,数据库名,null,数据库版本号
dbHelper = new DBHelper(this);
dbHelper.getReadableDatabase();
//初始化数据集
dataList = new ArrayList<>();
}
/**
* 从数据库取数据
*/
private void getDataFromDB() {
new Thread(new Runnable() {
@Override
public void run() {
//调用
dataList = DBUtils.getCallRecordList(dbHelper);
if (dataList.size() > 0) {
handler.sendEmptyMessage(0);
}
}
}).start();
}
/**
* 初始化页面控件
*/
private void initView() {
listView = (ListView) findViewById(R.id.listView);
btn_add = (Button) findViewById(R.id.btn_add);
}
}
3.6 事务
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
ACID是Atomic(原子性)
Consistency(一致性)
Isolation(隔离性)
Durability(持久性)的英文缩写。
Atomic(原子性):指整个数据库事务是不可分割的工作单位。只有使据库中所有的操作执行成功,才算整个事务成功;事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。
Consistency(一致性):指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNTS表中Tom和Jack的存款总额为2000元。
Isolation(隔离性):指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。
Durability(持久性):指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
四、ContentProvider
ContentProvider不仅是Android五大存储方式之一,还是Android四大组件之一。当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。 Android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)。
ContentProvider有三个类:
ContentProvider:--内容提供者,需要在清单文件中进行注册
<provider
android:authorities="com.example.xx"
android:name="com.example.xx.contentproviderdemo1.provider.UserProvider"
android:exported="true"/>
ContentResolver;--内容解析器
Uri.paser("content://ContentProvider清单文件中的authorities值/[表]")
ContentObserver:--内容观察者, 数据变化后,发送更新通知
4.1 Uri介绍
Uri代表了要操作的数据,Uri主要包含了两部分信息:1》需要操作的ContentProvider ,2》对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content://
主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
4.1.1 要操作person表中id为10的记录,可以构建这样的路径:/person/10
4.1.2 要操作person表中id为10的记录的name字段, person/10/name
4.1.3 要操作person表中的所有记录,可以构建这样的路径:/person
4.1.4 要操作xxx表中的记录,可以构建这样的路径:/xxx
4.1.5要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name
4.1.6如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://com.provider.personprovider/person")
由于ContentProvider存储数据可以对外共享,我们需要写两个应用,一个座位内容提供者,另一个用于访问内容提供者。
4.2 UriMatcher
uri匹配器,主要用于匹配Uri。
在实际开发中,一个库会存在多张表,若使用内容提供者对多张表进行操作的话,不能将ContentProvider中的操作写死。需要在内容提供者应用中使用Uri匹配器来判断我们操作的Uri路径。
4.2.1创建UriMatcher对象:
//创建uri匹配器对象,常量 UriMatcher.NO_MATCH表示不匹配任何路径的返回码
static UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH);
4.2.2 注册需要的URi:
//注册需要的Uri
//#为任意数字符
//*为任意文本字符
static {
um.addURI("com.example.xx", "user", 1);//content://com.example.xx/user
um.addURI("com.example.xx", "teacher", 2);//content://com.example.xx/teacher
um.addURI("com.example.xx", "user/#", 3);//content://com.example.xx/user/7
}
4.3操作
在继承ContentProvider的子类中的重写的方法中各根据已经注册的Uri进行匹配操作,而在不同表中进行增删改查,如下:
/**
* 供其他应用调用 用于查询本应用中user表中的数据
* @param uri 内容提供者的主机名,也就是地址
* @param projection 要查询数据的列名
* @param selection 查询条件
* @param selectionArgs 查询条件的使用占位符的参数
* @param sortOrder 排序条件
* @return 查询的数据集
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
if (um.match(uri)==1) {
cursor = db.query("user", projection, selection, selectionArgs,
null, null, sortOrder, null);
}else if(um.match(uri)==2){
cursor = db.query("teacher", projection, selection, selectionArgs,
null, null, sortOrder, null);
}else if(um.match(uri)==3){
//把uri末尾携带的数字取出来
long id = ContentUris.parseId(uri);
cursor = db.query("user", projection, "_id = ?", new String[]{id + ""},
null, null, sortOrder, null);
}else{
throw new IllegalArgumentException("uri有问题");
}
return cursor;
}
在另一个应用(用于访问内容提供者的应用)进行操作时,只需要将路径更改为内容提供者中已经注册好的路径即可。
/**
* 查询数据
* 当按下查询按钮的时候,会调用该方法
* 在该方法中获取到内容提供者提供的数据,
* 我们需要借助内容解析器来获取,
* 内容解析器用于来访问内容提供者的。 *
* @param v
*/
public void btnQuery(View v){
//拿到内容解析器
ContentResolver cr = getContentResolver();
//获取所有的ContentProviderDemo1中user表数据数据
Cursor cursor = cr.query(Uri.parse("content://com.example.xx/user"),
null,null,null,null);
// 如果没有数据则终止执行
if (cursor.getCount() == 0 ){
Toast.makeText(this,"没有获取到数据",Toast.LENGTH_SHORT).show();
return;
}
//遍历数据并进行输出
while( cursor.moveToNext()){
String id = cursor.getString(cursor.getColumnIndex("_id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
String mobile = cursor.getString(cursor.getColumnIndex("mobile"));
String time = cursor.getString(cursor.getColumnIndex("time"));
Log.i(TAG, "id="+id+"; name="+name+"; mobile="+mobile+"; time="+time);
}
}
4.4 创建自定义内容提供者
内容提供者对外提供的是访问数据库中的内容,为此,我们需要创建一个数据库,并且添加些数据。数据库的创建如同SQLite中的数据库的创建,需要一个类继承SQLiteOpenHelper,重写onCreate和onUpgrade方法。
自定义类继承ContentProvider,会重写onCreate(创建),query(查询)、insert(增加)、update(更新)、delete(删除)、getType(得到数据类型),在onCreate中创建数据库帮助者和数据库对象。
4.5访问内容提供者的应用
通过ContentResolver内容解析器进行增删改查,增删改代码形式相似,再次仅列出增和查询代码。
增加:
//获取到内容解析器
ContentResolver resolver = getContentResolver();
//将数据插入到ContentProviderDemo1工程中的user表中
//定义要插入的数据
ContentValues values = new ContentValues();
values.put("name","春哥");
values.put("mobile","138000");
values.put("time","2016-01-01");
//url:内容提供者的地址
//values:要插入的数据
resolver.insert(Uri.parse("content://com.example.xx/user"),values);
values.clear();
values.put("name","凤姐");
values.put("mobile","139999");
resolver.insert(Uri.parse("content://com.example.xx/teacher"),values);
查询:
/**
* 查询数据
* 当按下查询按钮的时候,会调用该方法
* 在该方法中获取到内容提供者提供的数据,
* 我们需要借助内容解析器来获取,
* 内容解析器用于来访问内容提供者的。 *
* @param v
*/
public void btnQuery(View v){
//拿到内容解析器
ContentResolver cr = getContentResolver();
//获取所有的ContentProviderDemo1中user表数据数据
Cursor cursor = cr.query(Uri.parse("content://com.example.denny/user"),
null,null,null,null);
// 如果没有数据则终止执行
if (cursor.getCount() == 0 ){
Toast.makeText(this,"没有获取到数据",Toast.LENGTH_SHORT).show();
return;
}
//遍历数据并进行输出
while( cursor.moveToNext()){
String id = cursor.getString(cursor.getColumnIndex("_id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
String mobile = cursor.getString(cursor.getColumnIndex("mobile"));
String time = cursor.getString(cursor.getColumnIndex("time"));
Log.i(TAG, "id="+id+"; name="+name+"; mobile="+mobile+"; time="+time);
}
}
内容观察者对数据的更改进行实时监测并通知更新:
1、 创建我们特定的ContentObserver派生类,必须重载父类构造方法,必须重载onChange()方法去处理回调后的功能实现
2、 利用context.getContentResolover()获得ContentResolove对象,接着调用registerContentObserver()方法去注册内容观察者
3、 由于ContentObserver的生命周期不同步于Activity和Service等,因此,在不需要时,需要手动的调用
unregisterContentObserver()去取消注册。
如果是自己写的ContentProvider,则需要在继承ContentProvider的类中的各重写的操作方法中添加一行代码,同时观察者uri的数据发生变化了。
getContext().getContentResolver().notifyChange(uri,null);
五、网络存储数据
网络存储方式,需要与Android 网络数据包打交道。