Android数据存储的五种方式

实现数据持久化的方式

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



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值