「第一行代码」六、详解持久化技术

持久化技术简介

瞬时数据,指那些存储在内存当中,有可能会因为程序关闭或其它原因导致内存被回收而丢失的数据。

数据持久化,将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失。

保存在内存中的数据处于瞬时状态,保存在存储设备中的数据处于持久状态。持久化技术可以让数据在瞬时状态和持久状态之间进行转换。

Android系统主要提供了3种方式用于简单的实现数据持久化功能:

  • 文件存储
  • SharedPreference存储
  • 数据库存储

 

文件存储

文件存储,不对存储的内容进行任何格式化处理,所有数据都是原封不动的保存在文件当中,比较适合用于存储一些简单的文本数据或二进制数据。如果想使用文件存储的方式来保存一些较为复杂的文本数据,就需要定义一套自己的格式规范,方便以后将数据从文件中重新解析出来。

 

将数据存储到文件中

Context类中提供了一个openFileOutput()方法

openFileOutput()

作用:

用于将数据存储到指定的文件夹中

参数:

1. 文件名。在文件创建的时候使用的就是这个名称。这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/<package/files/目录下。

2. 文件的操作模式。主要为两种模式可选:MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容;MODE_APPEND则表示如果该文件存在,就往文件里面追加内容,不存在就创建新文件。

返回:

返回一个FileOutputStream对象。得到了这个对象之后就可以使用Java流的方式将数据写入到文件中了

    private void save(String inputText) {
        FileOutputStream out = null;
        BufferedWriter writer = null;
        try {
            out = openFileOutput("data", Context.MODE_PRIVATE);
            writer = new BufferedWriter(new OutputStreamWriter(out));
            writer.write(inputText);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

这里通过openFileOutput()方法得到一个FileOutputStream对象,然后再借助它构建出一个OutputStreamWriter对象,接着再使用OutputStreamWriter构建出一个BufferedWriter对象,这样就可以通过BufferedWriter来将文本内容写入到文件中了。

 

从文件中读取数据

Context类中还提供了一个openFileInput()方法

openFileInput()

作用:

用于从文件中读取数据

参数:

1. 要读取的文件名。系统会自动到/data/data/<package name>/files/目录下去加载这个文件

返回:

返回一个FileInputStream对象。得到了这个对象之后再通过Java流的方式就可以将数据读取出来了

    private String load() {
        FileInputStream in = null;
        BufferedReader reader = null;
        StringBuilder content = new StringBuilder();
        try {
            in = openFileInput("data");
            reader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            while ((line = reader.readLine()) != null) {
                content.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return content.toString();
    }

首先通过openFileInput()方法获取到一个FileInputStream对象,然后借助它构建出一个InputStreamReader对象,接着再使用InputStreamReader构建出一个BufferedReader对象,这样我们就可以通过BufferedReader进行一行行的读取,把文件中所有的文本内容全部读取出来,并存放在一个StringBuilder对象中,最后将读取到的内容返回就可以了。

 

public class MainActivity extends AppCompatActivity {

    private EditText edit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        edit = (EditText) findViewById(R.id.edit_text);
        String inputText = load();
        if (!TextUtils.isEmpty(inputText)) {
            edit.setText(inputText);
            edit.setSelection(inputText.length());
            Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        String inputText = edit.getText().toString();
        save(inputText);
    }

    private void save(String inputText) {
        FileOutputStream out = null;
        BufferedWriter writer = null;
        try {
            out = openFileOutput("data", Context.MODE_PRIVATE);
            writer = new BufferedWriter(new OutputStreamWriter(out));
            writer.write(inputText);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String load() {
        FileInputStream in = null;
        BufferedReader reader = null;
        StringBuilder content = new StringBuilder();
        try {
            in = openFileInput("data");
            reader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            while ((line = reader.readLine()) != null) {
                content.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return content.toString();
    }
}

在onCreate()方法中调用load()方法来读取文件中存储的文本内容,如果读到的内容不为null,就调用EditText的setText()方法将内容填充到EditText()里,并调用setSelection()方法将输入光标移动到文本的末尾位置以便继续输入,然后弹出一句还原成功的提示。

TextUtils.isEmpty()

作用:

进行非空判断。可以一次性进行两种空值的判断。当传入的字符串等于null或者等于空字符串的时候,这个方法都会返回true,从而使得我们不需要先单独判读这两种空值再使用逻辑运算符连接起来了。

 

核心技术就是Context类中提供的openFileInput()和openFileOutput()方法,之后就是利用Java的各种流来进行读写操作。

 

SharedPreferences存储

SharedPreferences使用键值对的方式来存储数据。当保存一条数据时,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把对应的值读取出来。

SharedPreferences支持不同的数据类型存储,包括整型、字符串等。

 

将数据存储到SharedPreferences中

想要使用SharedPreferences来存储数据,首先需要获取到SharedPreferences对象。Android中主要提供了3种方法用于得到SharedPreferences对象:

  • Context类中的getSharedPreferences()方法
  • Activity类中的getPreferences()方法
  • PreferenceManager类中的getDefaultSharedPreferences()方法

 

Context类中的getSharedPreferences()方法

作用:

得到SharedPreference对象

参数:

1. 用于指定SharedPreference文件的名称,如果指定的文件不存在则会创建一个,SharedPreference文件都存放在/data/data/<package name>/shared_prefs/目录下

2. 指定操作模式,目前只有MODE_PRIVATE这一种模式可选,是默认的操作模式,和直接传入0的效果相同,表示只有当前的应用程序才可以对这个SharedPreference文件进行读写

 

Activity类中的getPreferences()方法

和Context中的getSharedPreferences()方法很相似,不过它只接受一个操作模式参数。因为使用这个方式会自动将当前活动的类名作为SharedPreferenceces的文件名

 

PreferenceManager类中的getDefaultSharedPreferences()方法

静态方法。接受一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件

 

得到了SharedPreferences对象之后,就可以开始向SharedPreferences文件中存储数据了,主要分为3步实现:

(1)调用SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象

(2)向SharedPreferences.Editor对象中添加数据。添加布尔型使用putBoolean()方法,添加字符串使用putString(),以此类推

(3)调用apply()方法将添加的数据提交,完成数据存储操作

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button saveData = (Button) findViewById(R.id.save_data);
        saveData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Context类中的getSharedPreferences()方法
                //调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象
                SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
                //添加数据
                editor.putString("name", "Cun");
                editor.putInt("age", 24);
                editor.putBoolean("married", false);
                //调用apply()方法将添加的数据提交
                editor.apply();
            }
        });
    }
}

首先给按钮注册了一个点击事件,在点击事件中通过getSharedPreferences()方法指定SharedPreferences的文件名为data,并得到了SharedPreferences.Editor对象。接着向这个对象中添加了3条不同类型的数据,最后调用apply()方法进行提交,从而完成了数据存储的操作。

 

Android Studio 3.0后“弃用”了Android Device Monitor,所以我们这里使用Device File Explorer进行查看。

 

从SharedPreferences中读取数据

SharedPreferences对象中提供了一系列的get()方法。

SharedPreferences对象.getXxx()

作用:

用于对存储的数据进行读取

参数:

1. 键,传入存储数据时使用的键就可以得到相应的值

2. 默认值,表示当传入的键找不到对应的值时会以什么样的默认值进行访问

        Button restoreData = (Button) findViewById(R.id.restore_data);
        restoreData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
                String name = pref.getString("name", "");
                int age = pref.getInt("age", 0);
                boolean married = pref.getBoolean("married", false);
                Log.d("MainActivity", "name is " + name);
                Log.d("MainActivity", "age is " + age);
                Log.d("MainActivity", "married is " + married);
            }
        });

首先通过getSharedPreferences()方法得到了SharedPreferences对象,然后分别调用它的getString(),getInt()和getBoolean()方法,去获取前面所存储的姓名、年龄和是否已婚,如果没有找到相应的值,就会使用方法中传入的默认值来代替,最后通过Log将这些值打印出来。

 

实现记住密码功能

public class LoginActivity extends BaseActivity {

    private SharedPreferences pref;
    private SharedPreferences.Editor editor;

    private EditText accountEdit;
    private EditText passwordEdit;
    private Button login;
    private CheckBox rememberPass;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        //获取SharedPreferences
        pref = getSharedPreferences("rememberPassword", MODE_PRIVATE);
        accountEdit = (EditText) findViewById(R.id.account);
        passwordEdit = (EditText) findViewById(R.id.password);
        rememberPass = (CheckBox) findViewById(R.id.remember_pass);
        login = (Button) findViewById(R.id.login);
        boolean isRemember = pref.getBoolean("remember_password", false);
        if (isRemember) {
            //将账号密码都设置到文本框中
            String account = pref.getString("account", "");
            String password = pref.getString("password", "");
            accountEdit.setText(account);
            passwordEdit.setText(password);
            rememberPass.setChecked(true);
        }
        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();
                //如果账号密码正确,就认为登录成功
                if (account.equals("admin") && password.equals("123456")) {
                    //获取SharedPreferences.Editor对象
                    editor = pref.edit();
                    if (rememberPass.isChecked()) {
                        //添加数据
                        editor.putBoolean("remember_password", true);
                        editor.putString("account", account);
                        editor.putString("password", password);
                    } else {
                        editor.clear();
                    }
                    //提交数据
                    editor.apply();
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    startActivity(intent);
                    finish();
                } else {
                    Toast.makeText(LoginActivity.this, "account or password is invalid", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

}

首先在onCreate()方法中获取到SharedPreferences对象,然后调用它的getBoolean()方法去获取remember_password这个键对应的值。一开始不存在对应的值,所以会使用默认的false,这样就什么都不会发生。接着在登录成功之后会调用CheckBox的isCheck()方法来检查复选框是否被选中,如果被选中,则表示用户想要记住密码,这时将remember_password设置为true,然后把account和password对应的值都存入到SharedPreferences文件当中并提交。如果没有被选中,就调用clear()方法,将SharedPreferences文件中的数据清除掉。

当用户选中了记住密码的复选框,并成功登陆一次后,remember_password键对应的值就是true了,这个时候如果重启登陆界面,就会从SharedPreferences文件中将保存的账号和密码都读取出来,并填充到文本输入框中,然后把记住密码复选框选中,这样就完成了记住密码的功能了。

 

SQLite数据库存储

创建数据库

Android提供了一个SQLiteOpenHelper帮助类,借助这个类就可以简单的对数据库进行创建升级

SQLiteOpenHelper是一个抽象类,如果想要使用它,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate()onUpgrade(),我们必须在自己的帮助类里重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑

SQLiteOpenHelper中有两个非常重要的实例方法getReadableDatabase()getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库,并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候,getReadableDatabase()方法返回的对象将已只读的方式去打开数据库,而getWritableDatabase()方法则会抛出异常。

SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个即可。这个构造方法一共接收4个参数,第一个参数Context第二个参数数据库名,创建数据库时使用的就是这里指定的名称;第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入一个null;第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。

构建出SQLiteOpenHelper的实例后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了。

数据库文件会存放在/data/data/<package name>/databases/目录下。

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public static final String CREATE_BOOK = "create table Book ("
            + "id integer primary key autoincrement, "
            + "author text, "
            + "price real, "
            + "pages integer, "
            + "name text)";

    private Context mContext;

    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        Toast.makeText(mContext, "Create Succeeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

integer:整型

real:浮点型

text:文本类型

blob:二进制类型

public class MainActivity extends AppCompatActivity {
    
    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //通过构造方法,指定数据库名和版本号,构建SQLiteOpenHelper实例
        dbHelper = new MyDatabaseHelper(this, "BoosStore.db", null, 1);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //创建数据库
                dbHelper.getWritableDatabase();
            }
        });
    }
}

这里在onCreate()方法中构建了一个MyDatabaseHelper对象,并且通过构造函数的参数指定了数据库名和版本号。在按钮点击事件里调用了getWritableDatabase()方法。当第一次点击时会监测到程序中并没有BookStore.db数据库,于是会创建该数据库并调用MyDatabaseHelper中的onCreate()方法。再一次点击时,此时已经存在该数据库,因此不会再创建一次。

我们需要adb shell来对数据库和表的创建情况进行检查。

adb是Android SDK中自带的调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在/Users/xiecun/Library/Android/sdk/platform-tools目录下,如果想在命令行中使用这个工具,需要先把它的路径配置到环境变量里。

 

输入adb shell,然后cd到/data/data/com.cunxie.databasetext/databases/目录下,键入sqlite3后面加上数据库名即可。

如果权限受阻,应选用不带Google Play版本的VM,并在进入adb shell后输入su

 

ADB

Android Debug Birdge (Android调试桥)

ADB分为三部分:PC上的adb clientadb server以及Android设备上的adb daemon(adbd)

PC和Android是一个多对多的关系:一个PC可以连接多个Android设备;一个Android设备也可以连接多个PC。

 

ADB client:Client本质上就是Shell,用来发送命令给Server。发送命令时,首先检测PC上有没有启动Server,如果没有Server,则自动启动一个Server,然后将命令发送到Server,并不关心命令发送过去以后会怎样。

ADB server:运行在PC上的后台程序,目的是检测USB接口何时连接或者移除设备。
ADB Server维护着一个“已连接的设备的链表”,并且为每一个设备标记了一个状态:offline,bootloader,recovery或者online。
Server一直在做一些循环和等待,以协调client和Server还有daemon之间的通信。offline说明Server发现了一个设备,但是不能成功连接到Daemon。

ADB Daemon:运行在Android 设备上的一个进程,作用是连接到adb server(通过usb或tcp-ip)。并且为client提供一些服务。

三者之间的通信涉及到两条通讯通道:
Client <-----> Server <-----> Daemon

Client发送的指令也分为三种:1. 不需要经过Server处理就能成功的,如adb version,adb help。2. 需要和Server通讯,但不需要和Demon通讯的指令,如adb devices。3. 需要Daemon进行处理的命令。
ADB Server对本地的TCP5037端口进行监听,等待ADB Client的命令尝试连接5037端口。
ADB Client每个命令都包含两个部分,前一部分包含固定四个字节,以十六进制的方式指明指令的长度;后一部分才是真正的指令内容;发送命令的接口为writex,最终调用_fh_socket_write,通过send发送出去,因此这两部分至少需要发送两个tcp包。

 

升级数据库

onUpgrade()方法是用于对数据库进行升级的

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public static final String CREATE_BOOK = "create table Book ("
            + "id integer primary key autoincrement, "
            + "author text, "
            + "price real, "
            + "pages integer, "
            + "name text)";
    public static final String CREATE_CATEGORY = "create table Category ("
            + "id integer primary key autoincrement, "
            + "category_name text,  "
            + "category_code integer)";

    private Context mContext;

    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        //添加Category表
        db.execSQL(CREATE_CATEGORY);
        Toast.makeText(mContext, "Create Succeeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //如果已经存在Book表或Category表
        //则删除
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Category");
        onCreate(db);
    }
}

在onUpgrade()方法中执行了两条DROP语句,如果发现数据库中已经存在Book表或Category表,就将这两张表删除掉,然后调用onCreate()方法重新创建。

只要在SQLiteOpenHelper的构造方法里传入一个比之前数据库版本号大的数字,就可以让onUpgrade()方法得到执行

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //将数据库版本号指定为2
        dbHelper = new MyDatabaseHelper(this, "BoosStore.db", null, 2);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //创建数据库
                dbHelper.getWritableDatabase();
            }
        });
    }
}

 

添加数据

对数据进行的操作无非是4种,CRUD

C(create)添加R(retrieve)查询U(update)更新D(delete)删除

添加数据用insert查询数据用select更新数据用update删除数据用delete

调用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法可以用于创建或升级数据库,同时这两个方法还会返回一个SQLiteDatabase对象,借助这个对象就可以进行CRUD操作了。

向数据库表中添加数据,SQLiteDatabase中提供了一个insert()方法

insert()

作用:

添加数据

参数:

1. 表名

2. 在未指定添加数据的情况下给某些可为空的列自动赋值为NULL,一般不用这个功能,直接传入null即可

3. 一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可

        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                //组装第一条数据
                values.put("name", "The Da Vinci Code");
                values.put("author", "Dan Brown");
                values.put("pages", 454);
                values.put("price", 16.96);
                //插入第一条数据
                db.insert("Book", null, values);
                //clear
                values.clear();
                //组装第二条数据
                values.put("name", "The Lost Symbol");
                values.put("author", "Dan Brown");
                values.put("pages", 510);
                values.put("price", 19.95);
                //插入第二条数据
                db.insert("Book", null, values)
            }
        });

先获得了SQLiteDatabase对象,然后使用ContentValues来对添加的数据进行组装。我们将id列设置为自增长,所以不需要手动给它赋值,它的值会在入库的时候自动生成。

 

更新数据

SQLiteDatabase中提供了一个update()方法

update()

作用:

更新数据

参数:

1. 表名

2. ContentValues对象,把更新数据在这里组装进去

3. 约束更新某一行或某几行中的数据,默认是更新所有行。对应的是SQL语句的where部分

4. 约束更新某一行或某几行中的数据,默认是更新所有行。提供一个字符串数组为第三个参数中的每个占位符指定相应内容

        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                values.put("price", 10.99);
                db.update("Book", values, "name = ?", new String[] {"The Da Vinci Code"});
            }
        });

在更新数据按钮的点击事件里构建了一个ContentValues对象,并只给它指定了一组数据。调用SQLiteDatabase的update()方法执行具体的更新操作。第三个参数对应的是SQL语句的where部分,表示更新所有name等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。

 

删除数据

SQLiteDatabase中提供了一个delete()方法

delete()

作用:

删除数据

参数:

1. 表名

2. 约束删除某一行或某几行的数据,默认是删除所有行

3. 约束删除某一行或某几行的数据,默认是删除所有行

        Button deleteButton = (Button) findViewById(R.id.delete_data);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                db.delete("Book", "pages > ?", new String[] {"500"});
            }
        });

在删除按钮的点击事件里指明去删除Book表中的数据,并且通过第二、第三个参数来指定仅删除那些页数超过500页的书。

 

查询数据

SQLitedatabase中提供了一个query()方法。

query()

作用:

查询数据

参数:

1. 表名

2. 指定去查询哪几列,不指定则默认查询所有列

3. 约束查询某一行或某几行的数据,不指定则默认查询所有行

4. 约束查询某一行或某几行的数据,不指定则默认查询所有行

5. 指定需要去group by的列,不指定则表示不对查询结果进行group by操作

6. 用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤

7. 用于指定查询结果的排序方式,不指定则表示使用默认的排序方式

返回:

一个Cursor对象,查询到的所有数据都将从这个对象中取出

        Button queryButton = (Button) findViewById(R.id.query_data);
        queryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                //查询Book表中所有的数据
                Cursor cursor = db.query("Book", null, null, null, null, null, null);
                if (cursor.moveToFirst()) {
                    do {
                        //遍历Book表中所有的数据
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String author = cursor.getString(cursor.getColumnIndex("author"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        double price = cursor.getDouble(cursor.getColumnIndex("price"));
                        Log.d("MainActivity", "book name is " + name);
                        Log.d("MainActivity", "book author is " + author);
                        Log.d("MainActivity", "book pages is " + pages);
                        Log.d("MainActivity", "book price is " + price);
                    } while (cursor.moveToNext());
                }
                cursor.close();
            }
        });

在查询按钮的点击事件里调用了SQLiteDatabase的query()方法去查询数据。这里的query()方法只使用了第一个参数指明去查询Book表,后面的参数全部为null,表示希望查询这张表中所有数据,虽然这张表中目前只剩下一条数据。查询完之后得到了一个Cursor对象,接着调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环中,去遍历查询到的每一行数据。在这个循环中可以通过Cursor的getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据。最后调用close()方法来关闭Cursor。

 

使用SQL操作数据库

除了查询数据的时候调用SQLiteDatabase的rawQuery()方法,其他操作都是调用execSQL()方法

 

使用LitePal操作数据库

LitePal简介

LitePal是一款开源的Android数据库框架,它采用对象关系映射(ORM)的模式,并将平时开发最常用到的一些数据库功能进行了封装。地址为:https://github.com/LitePalFramework/LitePal。

 

配置LitePal

使用LitePal的第一步,编辑app/build.gradle文件,在dependencies闭包中添加:

dependencies {
    implementation 'org.litepal.android:java:3.0.0'
}

这样就把LitePal成功引入到当前项目中了。接下来需要配置litepal.xml文件。在app/src/main目录下创建assets目录,然后在该目录下新建一个litepal.xml文件,接着编辑该文件:

<?xml version="1.0" encoding="utf-8"?>
<litepal>
    <!--
    	Define the database name of your application. 
    	By default each database name should be end with .db. 
    	If you didn't name your database end with .db, 
    	LitePal would plus the suffix automatically for you.
    	For example:    
    	<dbname value="demo" />
    -->
    <dbname value="demo" />

    <!--
    	Define the version of your database. Each time you want 
    	to upgrade your database, the version tag would helps.
    	Modify the models you defined in the mapping tag, and just 
    	make the version value plus one, the upgrade of database
    	will be processed automatically without concern.
			For example:    
    	<version value="1" />
    -->
    <version value="1" />

    <!--
    	Define your models in the list with mapping tag, LitePal will
    	create tables for each mapping class. The supported fields
    	defined in models will be mapped into columns.
    	For example:    
    	<list>
    		<mapping class="com.test.model.Reader" />
    		<mapping class="com.test.model.Magazine" />
    	</list>
    -->
    <list>
    </list>
    
    <!--
        Define where the .db file should be. "internal" means the .db file
        will be stored in the database folder of internal storage which no
        one can access. "external" means the .db file will be stored in the
        path to the directory on the primary external storage device where
        the application can place persistent files it owns which everyone
        can access. "internal" will act as default.
        For example:
        <storage value="external" />
    -->
    
</litepal>

其中<dbname>标签用于指定数据库名<version>标签用于指定数据库版本号<list>标签用于指定所有的映射模型

最后还需要配置LitePalApplication修改AndroidManifest.xml中的代码:

    <application
        android:name="org.litepal.LitePalApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

这里将项目的application配置为org.litepal.LitePalApplication,这样才能让LitePal的所有功能正常工作。

 

此时如果Android Studio无法高亮或识别LitePal,可以进行如下操作:

“File -> Invalidate Caches / Restart”然后选择“Invalidate and Restart”。

 

创建和升级数据库

LitePal采取的是对象关系映射(ORM)的模式,可以用面向对象的思维来操作数据库。

public class Book {
    private int id;
    private String author;
    private double price;
    private int pages;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getPages() {
        return pages;
    }

    public void setPages(int pages) {
        this.pages = pages;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

这是一个典型的Java Bean。Book类会对应数据库中的Book表,而类中每一个字段分别对应了表中的每一个列

生成getter和setter的快捷方式为,先将类中的字段定义好,然后按下command + N(Win系统为Alt + Insert键),在弹出菜单中选择Getter and Setter,接着使用Shift键将所有字段都选中,最后点击OK。

接下来需要将Book类添加到映射模型列表当中,修改litepal.xml:

    <list>
        <mapping class="com.cunxie.litepalapplication.Book"></mapping>
    </list>

这里使用<mapping>标签来声明我们要配置的映射模型类,一定要使用完整类名。不管有多少模型类需要映射,都是用同样方式配置在<list>标签下即可。

        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //调用LitePal.getDatabase()方法
                LitePal.getDatabase();
            }
        });

 

使用LitePal来升级数据库,只需要修改内容,并将版本号<version>中的value + 1即可。

 

使用LitePal添加数据

当需要进行CRUD操作时,模型类(之前的Book类和Category类)必须继承自LitePalSupport

//调用save()方法

        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Book book = new Book();
                book.setName("The Da Vinci Code");
                book.setAuthor("Dan Brown");
                book.setPages(454);
                book.setPrice(16.96);
                book.setPress("Unknow");
                //调用save()方法
                book.save();
            }
        });

在添加数据按钮的点击事件里面,首先是创建了一个Book的实例,然后调用Book类中的各种Set方法对数据进行设置,最后再调用book.save()方法就能完成数据添加操作。这个save()方法是从LitePalSupport类中继承而来

 

使用LitePal更新数据

最简单的一种更新方式就是对已存储的对象重新设值,然后重新调用save()方法即可

对于LitePal来说,对象是否已存储是根据调用model.isSaved()方法的结果来判断的,true为已存储,false为未存储。只有在两种情况下model.isSaved()方法会返回true,一种是已经调用过model.save()方法去添加数据;另一种是model对象是通过LitePal提供的查询API查出来的

//重新设值,重新调用save()方法

        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //只能对已存储的对象进行操作
                Book book = new Book();
                book.setName("The Lost Symbol");
                book.setAuthor("Dan Brown");
                book.setPages(510);
                book.setPrice(19.95);
                book.setPress("Unknow");
                book.save();
                //重新设值,重新调用save()方法
                book.setPrice(10.99);
                book.save();
            }
        });

在更新数据按钮的点击事件里,先是添加了一条Book数据,然后调用setPrice()方法将这本书的价格进行了修改,之后再次调用save()方法。此时LitePal会发现当前的Book对象是已存储的,因此不会再向数据库添加了一条新数据,而是直接更新当前数据。

 

//调用updateAll()方法

        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Book book = new Book();
                book.setPrice(14.95);
                book.setPress("Anchor");
                //调用updateAll()方法
                book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown");
            }
        });

首先new了一个Book的实例,然后直接调用setPrice()和setPress()方法来设置要更新的数据,最后再调用updateAll()方法去执行更新操作。

updateAll()方法可以指定一个条件约束如果不指定就表示更新所有数据

 

对于所有想要将数据更新成默认值的操作,LitePal提供了一个setToDefualt()方法,然后传入相应的列名就可以实现了。

//将所有书的页数都更新为0
Book book = new Book();
book.setToDefault("pages");
book.updateAll();

 

使用LitePal删除数据

使用LitePal删除数据的方式主要有两种。

第一种,直接调用已存储对象的delete()方法即可。

第二种:

//调用LitePal.deleteAll()方法

        Button deleteButton = (Button) findViewById(R.id.delete_data);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //调用LitePal.deleteAll()方法
                LitePal.deleteAll(Book.class, "price < ?", "15");
            }
        });

这里调用了LitePal.deleteAll()方法来删除数据。

deleteAll()方法第一个参数用于指定删除哪张表中的数据,Book.class就意味着删除Book表中的数据,后面的参数用于指定约束条件。如果不指定约束条件就意味着删除表中所有数据

 

使用LitePal查询数据

//调用findAll()方法

        Button queryButton = (Button) findViewById(R.id.query_data);
        queryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //调用findAll()方法
                List<Book> books = LitePal.findAll(Book.class);
                for (Book book : books) {
                    Log.d("MainActivity", "book name is " + book.getName());
                    Log.d("MainActivity", "book name is " + book.getAuthor());
                    Log.d("MainActivity", "book name is " + book.getPages());
                    Log.d("MainActivity", "book name is " + book.getPrice());
                    Log.d("MainActivity", "book name is " + book.getPress());
                }
            }
        });

除了findAll()之外,LitePal还提供了findFirst()和findLast()。

 

select()方法

指定查询哪几列的数据,对应SQL当中的select关键字。

例如只查name和author这两列的数据:

List<Book> books = LitePal.select("name", "author").find(Book.class);

where()方法

指定查询的约束条件,对应SQL当中的where关键字。

例如只查页数大于400的数据:

List<Book> books = LitePal.where("pages > ?", "400").find(Book.class);

order()方法

指定结果的排序方式,对应SQL当中的order by关键字。

例如将查询结果按照书价从高到低排序:

List<Book> books = LitePal.order("price desc").find(Book.class);

limit()方法

指定查询结果的数量。

例如只查表中的前3条数据:

List<Book> books = LitePal.limit(3).find(Book.class);

offset()方法

指定查询结果的偏移量。

例如查询表中的第2条、第3条、第4条数据:

List<Book> books = LitePal.limit(3).offset(1).find(Book.class);

 

                List<Book> books = LitePal.select("name", "author", "pages")
                        .where("pages > ?", "400")
                        .order("pages")
                        .limit(10)
                        .offset(10)
                        .find(Book.class);

这段代码表示,查询Book表中第11~20条满足页数大于400这个条件的name、author和pages这3列数据,并将查询结果按照页数生序排列。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值