Android数据存储的五种方式
来源:互联网 发布:新手开淘宝装修店铺 编辑:程序博客网 时间:2024/05/29 17:58
实现数据持久化的方式
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()获取所有的数据,返回值为MapgetInt(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_APPENDMODE_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 网络数据包打交道。
- Android数据的五种存储方式
- Android数据的五种存储方式
- android数据存储的五种方式
- android数据存储的五种方式
- Android数据存储的五种方式
- Android数据存储的五种方式
- android存储数据的五种方式
- Android的五中数据存储方式
- android数据存储的五大方式
- Android中的数据存储的五种方式
- Android五种常用数据的存储方式
- android 数据存储五种方式总结
- Android数据存储五种方式总结
- Android数据存储五种方式总结
- Android数据存储五种方式总结
- Android数据存储五种方式总结
- Android数据存储五种方式总结
- Android数据存储五种方式总结
- 第十二周 项目2-操作用邻接表存储的图
- uva12563
- C++入门(二)C++基本知识
- 菜鸟录之错题
- jQuery控制控件文本的长度
- Android数据存储的五种方式
- java基础---I/O流--字节流(2)
- Unity客户端与后台通信
- Android 用 adb forword + Tcpdump + Wireshark 实时抓包的方法
- 链栈 链队列 share
- ListView添加HeaderView出现Cannot add header view to list -- setAdapter has already been called.
- v4l2-ctl 控制命令
- Activity的生命周期记录
- RAID及软RAID的实现,包括各级别RAID的原理及各级别RAID的实现