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 网络数据包打交道。



1 0
原创粉丝点击