《第一行代碼:Android篇》學習筆記(六)

来源:https://www.cnblogs.com/1693977889zz/archive/2022/05/11/16256358.html
-Advertisement-
Play Games

本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...


本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。
每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激感激郭霖先生提供這麼好的書籍。

第6章 數據存儲全方案——詳解持久化技術

任何一個應用程式,其實說白了就是在不停地和數據打交道,聊QQ、看新聞、刷微博,所關心的都是裡面的數據,沒有數據的應用程式就變成了一個空殼子,對用戶來說沒有任何實際用途。現在多數的數據基本都是由用戶產生的,比如你發微博、評論新聞,其實都是在產生數據。

瞬時數據就是指那些存儲在記憶體當中,有可能會因為程式關閉或其他原因導致記憶體被回收而丟失的數據。這對於一些關鍵性的數據信息來說是絕對不能容忍的,誰都不希望自己剛發出去的一條微博,刷新一下就沒了吧。那麼怎樣才能保證一些關鍵性的數據不會丟失呢?這就需要用到數據持久化技術了。

6.1 持久化技術簡介

數據持久化就是指將那些記憶體中的瞬時數據保存到存儲設備中,保證即使在手機或電腦關機的情況下,這些數據仍然不會丟失。保存在記憶體中的數據是處於瞬時狀態的,而保存在存儲設備中的數據是處於持久狀態的,持久化技術則提供了一種機制可以讓數據在瞬時狀態和持久狀態之間進行轉換。

持久化技術被廣泛應用於各種程式設計的領域當中,而本書中要探討的自然是Android中的數據持久化技術。Android系統中主要提供了3種方式用於簡單地實現數據持久化功能,即文件存儲、SharedPreferences存儲以及資料庫存儲。

當然,除了這3種方式之外,你還可以將數據保存在手機的SD卡中,不過使用文件、SharedPreferences或資料庫來保存數據會相對更簡單一些,而且比起將數據保存在SD卡中會更加地安全。

6.2 文件存儲

文件存儲是Android中最基本的一種數據存儲方式,它不對存儲的內容進行任何的格式化處理,所有數據都是原封不動地保存到文件當中的,因而它比較適合用於存儲一些簡單的文本數據或二進位數據。

如果你想使用文件存儲的方式來保存一些較為複雜的文本數據,就需要定義一套自己的格式規範,這樣可以方便之後將數據從文件中重新解析出來。那麼首先我們就來看一看,Android中是如何通過文件來保存數據的。

6.2.1 將數據存儲到文件中

Context類中提供了一個openFileOutput()方法,可以用於將數據存儲到指定的文件中。

  • 第一個參數是文件名,在文件創建的時候使用的就是這個名稱,註意這裡指定的文件名不可以包含路徑,因為所有的文件都是預設存儲到/data/data//files/目錄下的。
  • 第二個參數是文件的操作模式,主要有兩種模式可選,MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是預設的操作模式,表示當指定同樣文件名的時候,所寫入的內容將會覆蓋原文件中的內容,而MODE_APPEND則表示如果該文件已存在,就往文件裡面追加內容,不存在就創建新文件。

其實文件的操作模式本來還有另外兩種:MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,這兩種模式表示允許其他的應用程式對我們程式中的文件進行讀寫操作,不過由於這兩種模式過於危險,很容易引起應用的安全性漏洞,已在Android 4.2版本中被廢棄。

openFileOutput()方法返回的是一個FileOutputStream對象,得到了這個對象之後就可以使用Java流的方式將數據寫入到文件中了。以下是一段簡單的代碼示例,展示瞭如何將一段文本內容保存到文件中:

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

上面通過openFileOutput()方法能夠得到一個FileOutputStream對象,然後再藉助它構建出一個OutputStreamWriter對象,接著再使用OutputStreamWriter構建出一個BufferedWriter對象,這樣你就可以通過BufferedWriter來將文本內容寫入到文件中了。

下麵就編寫一個完整的例子,藉此學習一下如何在Android項目中使用文件存儲的技術。首先創建一個FilePersistenceTest項目,並修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type something here"/>
</LinearLayout>

在佈局中加入了一個EditText,用於輸入文本內容。現在運行一下程式,界面上會有一個文本輸入框。然後在文本輸入框中隨意輸入點什麼內容,再按下Back鍵,這時輸入的內容肯定就已經丟失了,因為它只是瞬時數據,在活動被銷毀後就會被回收。而這裡我們要做的,就是在數據被回收之前,將它存儲到文件當中。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.filepersistencetest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.widget.EditText;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

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);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        String inputText = edit.getText().toString();
        save(inputText);
    }
    public 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();
            }
        }
    }
}

首先在onCreate()方法中獲取了EditText的實例,然後重寫了onDestroy()方法,這樣就可以保證在活動銷毀之前一定會調用這個方法。在onDestroy()方法中我們獲取了EditText中輸入的內容,並調用save()方法把輸入的內容存儲到文件中,文件命名為data。

現在重新運行一下程式,併在EditText中輸入一些內容,如圖所示:

image

然後按下Back鍵關閉程式,這時我們輸入的內容就已經保存到文件中了。

那麼如何才能證實數據確實已經保存成功了呢?

書中描述:“我們可以藉助Android DeviceMonitor工具來查看一下。點擊Android Studio導航欄中的Tools→Android,會看到如圖所示的工具列表”。已經過時了,不是這樣的的。

現在如下圖所示尋找:

image

點擊 Device File Explorer 進入其標簽頁,在這裡找到/data/data/com.zhouzhou.filepersistencetest/files/目錄,可以看到生成了一個data文件。(註:Android 7.0系統的模擬器可能無法正常查看FileExplorer中的內容,這或許是新版模擬器的一個bug,可能會在未來的版本中修複。如果你遇到了這種情況,創建一個Android 6.0系統的模擬器即可解決。)如圖所示:

image

然後書中所述:“點擊下圖中左邊的按鈕” 然後,選中文件,滑鼠右擊——save as 可將這個文件導出到電腦上:

image

image

使用記事本打開這個文件,裡面的內容如圖:

image

這樣就證實了,在EditText中輸入的內容確實已經成功保存到文件中了。不過只是成功將數據保存下來還不夠,我們還需要想辦法在下次啟動程式的時候讓這些數據能夠還原到EditText中,因此接下來我們就要學習一下如何從文件中讀取數據。

6.2.2 從文件中讀取數據

類似於將數據存儲到文件中,Context類中還提供了一個openFileInput()方法,用於從文件中讀取數據。這個方法要比openFileOutput()簡單一些,它只接收一個參數,即要讀取的文件名,然後系統會自動到/data/data//files/目錄下去載入這個文件,並返回一個FileInputStream對象,得到了這個對象之後再通過Java流的方式就可以將數據讀取出來了。

以下是一段簡單的代碼示例,展示瞭如何從文件中讀取文本數據:

    public 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對象中,最後將讀取到的內容返回就可以了。

瞭解了從文件中讀取數據的方法,來繼續完善上一小節中的例子,使得重新啟動程式時EditText中能夠保留我們上次輸入的內容。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.filepersistencetest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

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);
        String inputText = load();
        if (!TextUtils.isEmpty(inputText)) {
            edit.setText(inputText);
            //將輸入游標移動到文本的末尾位置以便於繼續輸入
            edit.setSelection(inputText.length());
            Toast.makeText(this,"Restoring succeeded",Toast.LENGTH_SHORT).show();
        }
    }
    public 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();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        String inputText = edit.getText().toString();
        save(inputText);
    }
    public 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();
            }
        }
    }
}

這裡的思路非常簡單,在onCreate()方法中調用load()方法來讀取文件中存儲的文本內容,如果讀到的內容不為null,就調用EditText的setText()方法將內容填充到EditText里,並調用setSelection()方法將輸入游標移動到文本的末尾位置以便於繼續輸入,然後彈出一句還原成功的提示。

註意,上述代碼在對字元串進行非空判斷的時候使用了TextUtils.isEmpty()方法,這是一個非常好用的方法,它可以一次性進行兩種空值的判斷。當傳入的字元串等於null或者等於空字元串的時候,這個方法都會返回true,從而使得我們不需要先單獨判斷這兩種空值再使用邏輯運算符連接起來了。

現在重新運行一下程式,剛纔保存的Content字元串肯定會被填充到EditText中:

image

然後,編寫一點其他的內容,比如在EditText中輸入Hello,接著按下Back鍵退出程式,再重新啟動程式,這時剛纔輸入的內容並不會丟失,而是還原到了EditText中,如圖所示:

image

文件存儲方面的知識,其實所用到的核心技術就是Context類中提供的openFileInput()和openFileOutput()方法,之後就是利用Java的各種流來進行讀寫操作。

文件存儲的方式並不適合用於保存一些較為複雜的文本數據,下麵學習一下Android中另一種數據持久化的方式,它比文件存儲更加簡單易用,而且可以很方便地對某一指定的數據進行讀寫操作。

6.3 SharedPreferences存儲

不同於文件的存儲方式,SharedPreferences是使用鍵值對的方式來存儲數據的。

也就是說,當保存一條數據的時候,需要給這條數據提供一個對應的鍵,這樣在讀取數據的時候就可以通過這個鍵把相應的值取出來。而且SharedPreferences還支持多種不同的數據類型存儲,如果存儲的數據類型是整型,那麼讀取出來的數據也是整型的;如果存儲的數據是一個字元串,那麼讀取出來的數據仍然是字元串。

這樣你應該就能明顯地感覺到,使用SharedPreferences來進行數據持久化要比使用文件方便很多。

6.3.1 將數據存儲到SharedPreferences中

要想使用SharedPreferences來存儲數據,首先需要獲取到SharedPreferences對象。Android中主要提供了3種方法用於得到SharedPreferences對象。

  1. Context類中的getSharedPreferences()方法

此方法接收兩個參數,第一個參數用於指定SharedPreferences文件的名稱,如果指定的文件不存在則會創建一個,SharedPreferences文件都是存放在/data/data//shared_prefs/目錄下的。

第二個參數用於指定操作模式,目前只有MODE_PRIVATE這一種模式可選,它是預設的操作模式,和直接傳入0效果是相同的,表示只有當前的應用程式才可以對這個SharedPreferences文件進行讀寫。

其他幾種操作模式均已被廢棄,MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE這兩種模式是在Android 4.2版本中被廢棄的,MODE_MULTI_PROCESS模式是在Android6.0版本中被廢棄的。

  1. Activity類中的getPreferences()方法

這個方法和Context中的getSharedPreferences()方法很相似,不過它只接收一個操作模式參數,因為使用這個方法時會自動將當前活動的類名作為SharedPreferences的文件名。

  1. PreferenceManager類中的getDefaultSharedPreferences()方法

這是一個靜態方法,它接收一個Context參數,並自動使用當前應用程式的包名作為首碼來命名SharedPreferences文件。得到了SharedPreferences對象之後,就可以開始向SharedPreferences文件中存儲數據了,主要可以分為3步實現。

(1) 調用SharedPreferences對象的edit()方法來獲取一個SharedPreferences.Editor對象。

(2) 向SharedPreferences.Editor對象中添加數據,比如添加一個布爾型數據就使用putBoolean()方法,添加一個字元串則使用putString()方法,以此類推。

(3) 調用apply()方法將添加的數據提交,從而完成數據存儲操作。

通過一個例子來體驗一下SharedPreferences存儲的用法吧。新建一個SharedPreferencesTest項目,然後修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/save_data"
        android:text="Save data"/>
</LinearLayout>

只是簡單地放置了一個按鈕,用於將一些數據存儲到SharedPreferences文件當中。然後修改MainActivity中的代碼,如下所示:

package com.zhouzhou.sharedpreferencestest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

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 view) {
                SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
                editor.putString("name","Zhouzhou");
                editor.putInt("age",28);
                editor.putBoolean("married",false);
                editor.apply();
            }
        });
    }
}

可以看到,這裡首先給按鈕註冊了一個點擊事件,然後在點擊事件中通過getSharedPreferences()方法指定SharedPreferences的文件名為data,並得到了SharedPreferences.Editor對象。

接著向這個對象中添加了3條不同類型的數據,最後調用apply()方法進行提交,從而完成了數據存儲的操作。

現在就可以運行一下程式了,進入程式的主界面後,點擊一下Save data按鈕。這時的數據應該已經保存成功了,證實一下,進入/data/data/com.zhouzhou.sharedpreferencestest/shared_prefs/目錄下,可以看到生成了一個data.xml文件,如圖:

image

在按鈕的點擊事件中添加的所有數據都已經成功保存下來了,並且SharedPreferences文件是使用XML格式來對數據進行管理的。那麼接下來我們自然要看一看,如何從SharedPreferences文件中去讀取這些數據了。

6.3.2 從SharedPreferences中讀取數據

使用SharedPreferences來存儲數據是非常簡單的,從SharedPreferences文件中讀取數據會更加地簡單。

SharedPreferences對象中提供了一系列的get方法,用於對存儲的數據進行讀取,每種get方法都對應了SharedPreferences.Editor中的一種put方法,比如讀取一個布爾型數據就使用getBoolean()方法,讀取一個字元串就使用getString()方法。

這些get方法都接收兩個參數,第一個參數是鍵,傳入存儲數據時使用的鍵就可以得到相應的值了;第二個參數是預設值,即表示當傳入的鍵找不到對應的值時會以什麼樣的預設值進行返回。

在SharedPreferencesTest項目的基礎上繼續開發,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/save_data"
        android:text="Save data"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/restore_data"
        android:text="Restore data"/>
</LinearLayout>

增加了一個還原數據的按鈕,通過點擊這個按鈕來從SharedPreferences文件中讀取數據。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.sharedpreferencestest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

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 view) {
                SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
                editor.putString("name","Zhouzhou");
                editor.putInt("age",28);
                editor.putBoolean("married",false);
                editor.apply();
            }
        });
        Button restoreData = (Button) findViewById(R.id.restore_data);
        restoreData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                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將這些值列印出來。

現在重新運行一下程式,並點擊界面上的Restore data按鈕,然後查看logcat中的列印信息,如圖:

image

相比之下,SharedPreferences存儲確實要比文本存儲簡單方便了許多,應用場景也多了不少,比如很多應用程式中的偏好設置功能其實都使用到了SharedPreferences技術。下麵我們就來編寫一個記住密碼的功能,通過這個例子能夠加深對SharedPreferences的理解。

6.3.3 實現記住密碼功能

在上一章中的最佳實踐部分已經編寫過一個登錄界面了,那就首先打開BroadcastBestPractice項目,來編輯一下登錄界面的佈局。修改activity_login.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Account:"/>
        <EditText
            android:id="@+id/account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Password"/>
        <EditText
            android:id="@+id/password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <CheckBox
            android:id="@+id/remember_pass"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Remember password"/>
    </LinearLayout>
    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="Login"/>
</LinearLayout>

使用到了一個新控制項CheckBox。這是一個覆選框控制項,用戶可以通過點擊的方式來進行選中和取消,我們就使用這個控制項來表示用戶是否需要記住密碼。然後修改LoginActivity中的代碼,如下所示:

package com.zhouzhou.broadcastbestpractice;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

import java.util.prefs.PreferenceChangeEvent;

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);
        pref = PreferenceManager.getDefaultSharedPreferences(this);
        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 view) {
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();
                //如果賬號是admin且密碼是123456,就認為登錄成功
                if (account.equals("admin") && password.equals("123456")) {
                    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();
                }
            }
        });
    }
}
  1. 首先在onCreate()方法中獲取到了SharedPreferences對象。
  2. 然後調用它的getBoolean()方法去獲取remember_password這個鍵對應的值。

(一開始當然不存在對應的值了,所以會使用預設值false,這樣就什麼都不會發生。)

  1. 接著在登錄成功之後,會調用CheckBox的isChecked()方法來檢查覆選框是否被選中。

(如果被選中了,則表示用戶想要記住密碼,這時將remember_password設置為true,然後把account和password對應的值都存入到SharedPreferences文件當中並提交。如果沒有被選中,就簡單地調用一下clear()方法,將SharedPreferences文件中的數據全部清除掉。)

當用戶選中了記住密碼覆選框,併成功登錄一次之後,remember_password鍵對應的值就是true了,這個時候如果再重新啟動登錄界面,就會從SharedPreferences文件中將保存的賬號和密碼都讀取出來,並填充到文本輸入框中,然後把記住密碼覆選框選中,這樣就完成記住密碼的功能了。

現在重新運行一下程式,可以看到界面上多出了一個記住密碼覆選框,如圖所示:

image

然後賬號輸入admin,密碼輸入123456,並選中記住密碼覆選框,點擊登錄,就會跳轉到MainActivity。接著在MainActivity中發出一條強制下線廣播,會讓程式重新回到登錄界面,此時你會發現,賬號密碼都已經自動填充到界面上了,如圖所示:

image

這樣我們就使用SharedPreferences技術將記住密碼功能成功實現了。不過需要註意,這裡實現的記住密碼功能仍然只是個簡單的示例,並不能在實際的項目中直接使用。因為將密碼以明文的形式存儲在SharedPreferences文件中是非常不安全的,很容易就會被別人盜取,因此在正式的項目里還需要結合一定的加密演算法來對密碼進行保護才行。

6.4 SQLite資料庫存儲

SQLite是一款輕量級的關係型資料庫,它的運算速度非常快,占用資源很少,通常只需要幾百KB的記憶體就足夠了,因而特別適合在移動設備上使用。SQLite不僅支持標準的SQL語法,還遵循了資料庫的ACID事務。而SQLite又比一般的資料庫要簡單得多,它甚至不用設置用戶名和密碼就可以使用。Android正是把這個功能極為強大的資料庫嵌入到了系統當中,使得本地持久化的功能有了一次質的飛躍。

前面所學的文件存儲和SharedPreferences存儲畢竟只適用於保存一些簡單的數據和鍵值對,當需要存儲大量複雜的關係型數據的時候,你就會發現以上兩種存儲方式很難應付得了。比如我們手機的簡訊程式中可能會有很多個會話,每個會話中又包含了很多條信息內容,並且大部分會話還可能各自對應了電話簿中的某個聯繫人。很難想象如何用文件或者SharedPreferences來存儲這些數據量大、結構性複雜的數據吧?但是使用資料庫就可以做得到。

6.4.1 創建資料庫

Android為了讓我們能夠更加方便地管理資料庫,專門提供了一個SQLiteOpenHelper幫助類,藉助這個類就可以非常簡單地對資料庫進行創建和升級。

  1. SQLiteOpenHelper是一個抽象類,這意味著如果想要使用它的話,就需要創建一個自己的幫助類去繼承它。
  • SQLiteOpenHelper中有兩個抽象方法,分別是onCreate()onUpgrade(),必須在自己的幫助類裡面重寫這兩個方法,然後分別在這兩個方法中去實現創建、升級資料庫的邏輯。
  1. SQLiteOpenHelper中還有兩個非常重要的實例方法:getReadableDatabase()和get-WritableDatabase()。
  • 這兩個方法都可以創建或打開一個現有的資料庫(如果資料庫已存在則直接打開,否則創建一個新的資料庫),並返回一個可對資料庫進行讀寫操作的對象。
  • 不同的是,當資料庫不可寫入的時候(如磁碟空間已滿),getReadableDatabase()方法返回的對象將以只讀的方式去打開資料庫,而getWritableDatabase()方法則將出現異常。
  1. SQLiteOpenHelper中有兩個構造方法可供重寫,一般使用參數少一點的那個構造方法即可。
  • 這個構造方法中接收4個參數,第一個參數是Context,必須要有它才能對資料庫進行操作。
  • 第二個參數是資料庫名,創建資料庫時使用的就是這裡指定的名稱。
  • 第三個參數允許我們在查詢數據的時候返回一個自定義的Cursor,一般都是傳入null。
  • 第四個參數表示當前資料庫的版本號,可用於對資料庫進行升級操作。

構建出SQLiteOpenHelper的實例之後,再調用它的getReadableDatabase()或getWritableDatabase()方法就能夠創建資料庫了,資料庫文件會存放在/data/data//databases/目錄下。此時,重寫的onCreate()方法也會得到執行,所以通常會在這裡去處理一些創建表的邏輯。

接下來通過例子的方式來更加直觀地體會SQLiteOpenHelper的用法吧,首先新建一個DatabaseTest項目。這裡我們希望創建一個名為BookStore.db的資料庫,然後在這個資料庫中新建一張Book表,表中有id(主鍵)、作者、價格、頁數和書名等列。創建資料庫表當然還是需要用建表語句的,這裡也是要考驗一下你的SQL基本功了,Book表的建表語句如下所示:

create table Book (
    id integer primary key autoincrement,
    author text,
    price real,
    pages integer,
    name text)

SQLite不像其他的資料庫擁有眾多繁雜的數據類型,它的數據類型很簡單,integer表示整型,real表示浮點型,text表示文本類型,blob表示二進位類型。另外,上述建表語句中我們還使用了primary key將id列設為主鍵,並用autoincrement關鍵字表示id列是自增長的。

然後需要在代碼中去執行這條SQL語句,才能完成創建表的操作。新建MyDatabaseHelper類繼承自SQLiteOpenHelper,代碼如下所示:

package com.zhouzhou.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;

import androidx.annotation.Nullable;

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(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

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

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

可以看到,我們把建表語句定義成了一個字元串常量,然後在onCreate()方法中又調用了SQLiteDatabase的execSQL()方法去執行這條建表語句,並彈出一個Toast提示創建成功,這樣就可以保證在資料庫創建完成的同時還能成功創建Book表。現在修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/create_database"
        android:text="Create database"/>
</LinearLayout>

佈局文件很簡單,就是加入了一個按鈕,用於創建資料庫。最後修改MainActivity中的代碼,如下所示:

package com.zhouzhou.databasetest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private MyDatabaseHelper dbHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dbHelper.getWritableDatabase();
            }
        });
    }
}

這裡我們在onCreate()方法中構建了一個MyDatabaseHelper對象,並且通過構造函數的參數將資料庫名指定為BookStore.db,版本號指定為1,然後在Create database按鈕的點擊事件里調用了getWritableDatabase()方法.

這樣當第一次點擊Create database按鈕時,就會檢測到當前程式中並沒有BookStore.db這個資料庫,於是會創建該資料庫並調用MyDatabaseHelper中的onCreate()方法,這樣Book表也就得到了創建,然後會彈出一個Toast提示創建成功。再次點擊Create database按鈕時,會發現此時已經存在BookStore.db資料庫了,因此不會再創建一次。

現在就可以運行一下代碼了,在程式主界面點擊Create database按鈕,結果如圖:

image

此時BookStore.db資料庫和Book表應該都已經創建成功了,因為當你再次點擊Create database按鈕時,不會再有Toast彈出。

怎樣才能證實它們的確創建成功了?如果還是使用FileExplorer,那麼最多你只能看到databases目錄下出現了一個BookStore.db文件,Book表是無法通過FileExplorer看到的。

image

因此這次我們準備換一種查看方式,使用adb shell來對資料庫和表的創建情況進行檢查

adb是Android SDK中自帶的一個調試工具,使用這個工具可以直接對連接在電腦上的手機或模擬器進行調試操作。它存放在sdk的platform-tools目錄下,如果想要在命令行中使用這個工具,就需要先把它的路徑配置到環境變數里。如果你使用的是Windows系統,可以右擊電腦→屬性→高級系統設置→環境變數,然後在系統變數里找到Path並點擊編輯,將platform-tools目錄配置進去,如圖:

image

如果你使用的是Linux或Mac系統,可以在home路徑下編輯.bash_文件,將platform-tools目錄配置進去即可(註:這個網站https://www.androiddevtools.cn/ 下載的SDK Platform-Tools),如圖:

export PATH=$PATH:$HOME/android-sdk-linux/platform-toos

配置好了環境變數之後,就可以使用adb工具了。打開命令行界面,輸入adb shell,就會進入到設備的控制台,如圖:

image

其中,#符號是超級管理員的意思,也就是說現在你可以訪問模擬器中的一切數據。如果你的命令行上顯示的是$符號,那麼就表示你現在是普遍管理員,需輸入su命令切換成超級管理員,才能執行下麵的操作。

這裡出現不能切換為超級管理員的問題了,解決方式如下:

  1. 配置好環境變數後,打開cmd輸入adb shell 進入控制界面

  2. 發現是普通管理員,想利用su命令切換為超級管理員,不行

image

問題原因:這個問題是因為我們用的模擬器,帶有了Googel play 是不允許獲得管理員許可權。

解決辦法:

要去下載一個Target是Google APIS的模擬器,打開AVD Manager,選擇創建一個新的設備並給它更換鏡像。

image

image

image

再嘗試一下su命令,成功解決了不存在的問題了,如圖所示:

image

接下來使用cd命令進入到/data/data/com.zhouzhou.databasetest/databases/目錄下,並使用ls命令查看到該目錄里的文件,如圖:

image

這個目錄下出現了兩個資料庫文件,一個正是我們創建的BookStore.db,而另一個BookStore. db-journal則是為了讓資料庫能夠支持事務而產生的臨時日誌文件,通常情況下這個文件的大小都是0位元組。

接下來我們就要藉助sqlite命令來打開資料庫了,只需要鍵入sqlite3,後面加上資料庫名即可,如圖所示:

image

這時就已經打開了BookStore.db資料庫,現在就可以對這個資料庫中的表進行管理了。首先來看一下目前資料庫中有哪些表,鍵入.table命令,如圖:

image

可以看到,此時資料庫中有兩張表,android_metadata表是每個資料庫中都會自動生成的,不用管它,而另外一張Book表就是我們在MyDatabaseHelper中創建的了。這裡還可以通過.schema命令來查看它們的建表語句,如圖所示:

image

由此證明,BookStore.db資料庫和Book表確實已經創建成功了。之後鍵入.exit或.quit命令可以退出資料庫的編輯,再鍵入exit命令就可以退出設備控制台了。

image

6.4.2 升級資料庫

MyDatabaseHelper中還有一個onUpgrade()方法是用於對資料庫進行升級的,它在整個資料庫的管理工作當中起著非常重要的作用,可千萬不能忽視它喲。

目前DatabaseTest項目中已經有一張Book表用於存放書的各種詳細數據,如果我們想再添加一張Category表用於記錄圖書的分類,該怎麼做呢?比如Category表中有id(主鍵)、分類名和分類代碼這幾個列,那麼建表語句就可以寫成:

create table Category (
    id integer primary key autoincrement,
    category_name text,
    category_code integer)

接下來我們將這條建表語句添加到MyDatabaseHelper中,代碼如下所示:

package com.zhouzhou.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;

import androidx.annotation.Nullable;

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(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

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

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

看上去好像都挺對的吧?現在我們重新運行一下程式,並點擊Createdatabase按鈕,竟然沒有彈出創建成功的提示。當然,你也可以通過adb工具到資料庫中再去檢查一下,這樣你會更加地

實沒有創建成功的原因不難思考,因為此時BookStore.db資料庫已經存在了,之後不管我們怎樣點擊Create database按鈕,MyDatabaseHelper中的onCreate()方法都不會再次執行,因此新添加的表也就無法得到創建了

解決這個問題的辦法也相當簡單,只需要先將程式卸載掉,然後重新運行,這時BookStore.db資料庫已經不存在了,如果再點擊Create database按鈕,MyDatabaseHelper中的onCreate()方法就會執行,這時Category表就可以創建成功了。

不過,通過卸載程式的方式來新增一張表毫無疑問是很極端的做法,其實我們只需要巧妙地運用SQLiteOpenHelper的升級功能就可以很輕鬆地解決這個問題。修改MyDatabaseHelper中的代碼,如下所示:

public class MyDatabaseHelper extends SQLiteOpenHelper {
    ...
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        sqLiteDatabase.execSQL("drop table if exists Book");
        sqLiteDatabase.execSQL("drop table if exists Category");
        onCreate(sqLiteDatabase);
    }
}

可以看到,我們在onUpgrade()方法中執行了兩條DROP語句,如果發現資料庫中已經存在Book表或Category表了,就將這兩張表刪除掉,然後再調用onCreate()方法重新創建。

這裡先將已經存在的表刪除掉,因為如果在創建表時發現這張表已經存在了,就會直接報錯。

接下來的問題就是如何讓onUpgrade()方法能夠執行了,還記得SQLiteOpenHelper的構造方法里接收的第四個參數嗎?它表示當前資料庫的版本號,之前我們傳入的是1,現在只要傳入一個比1大的數,就可以讓onUpgrade()方法得到執行了。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.databasetest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private MyDatabaseHelper dbHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dbHelper.getWritableDatabase();
            }
        });
    }
}

這裡將資料庫版本號指定為2,表示我們對資料庫進行升級了。現在重新運行程式,並點擊Create database按鈕,這時就會再次彈出創建成功的提示。

為了驗證一下Category表是不是已經創建成功了,我們在adb shell中打開BookStore.db資料庫,然後鍵入.table命令,結果如圖:

image

接著鍵入.schema命令查看一下建表語句,結果如圖:

image

由此可以看出,Category表已經創建成功了,同時也說明我們的升級功能的確起到了作用。

6.4.3 添加數據

我們可以對數據進行的操作無非有4種,即CRUD。其中C代表添加(Create), R代表查詢(Retrieve), U代表更新(Update),D代表刪除(Delete)。

每一種操作又各自對應了一種SQL命令,添加數據時使用insert,查詢數據時使用select,更新數據時使用update,刪除數據時使用delete。但是開發者的水平總會是參差不齊的,未必每一個人都能非常熟悉地使用SQL語言,因此Android也提供了一系列的輔助性方法,使得在Android中即使不去編寫SQL語句,也能輕鬆完成所有的CRUD操作。

前面我們已經知道,調用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法是可以用於創建和升級資料庫的,不僅如此,這兩個方法還都會返回一個SQLiteDatabase對象,藉助這個對象就可以對數據進行CRUD操作了。

SQLiteDatabase中提供了一個insert()方法,這個方法就是專門用於添加數據的。它接收3個參數:

  • 第一個參數是表名,我們希望向哪張表裡添加數據。
  • 第二個參數用於在未指定添加數據的情況下給某些可為空的列自動賦值NULL,一般我們用不到這個功能,直接傳入null即可。
  • 第三個參數是一個ContentValues對象,它提供了一系列的put()方法重載,用於向ContentValues中添加數據,只需要將表中的每個列名以及相應的待添加數據傳入即可。

介紹完了基本用法,接下來還是通過例子的方式來親身體驗一下如何添加數據吧。修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/create_database"
        android:text="Create database"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/add_data"
        android:text="Add data"/>
</LinearLayout>

在佈局文件中又新增了一個按鈕,稍後就會在這個按鈕的點擊事件里編寫添加數據的邏輯。接著修改MainActivity中的代碼,如下所示:

package com.zhouzhou.databasetest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private MyDatabaseHelper dbHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dbHelper.getWritableDatabase();
            }
        });
        Button addButton = (Button) findViewById(R.id.add_data);
        addButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                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);//插入第一條數據
                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來對要添加的數據進行組裝。

這裡只對Book表裡其中四列的數據進行了組裝,id那一列並沒給它賦值。因為在前面創建表的時候,我們就將id列設置為自增長了,它的值會在入庫的時候自動生成,所以不需要手動給它賦值了。

接下來調用了insert()方法將數據添加到表當中,註意這裡我們實際上添加了兩條數據,上述代碼中使用ContentValues分別組裝了兩次不同的內容,並調用了兩次insert()方法。

現在可以重新運行一下程式了,界面如圖:

image

點擊一下Add data按鈕,此時兩條數據應該都已經添加成功了,不過為了證實一下,我們還是打開BookStore.db資料庫瞧一瞧。輸入SQL查詢語句select * from Book;,結果如圖:(註:總是查不出來,我的辦法是,到Decive File Explorer 中來到 com.zhouzhou.databasetest包,再將databases中的BookStroe.db和BookStroe.db-journal 都手動刪除了。再次運行程式,兩個按鈕的Demo都成立,並且查出數據了。但是,這樣做是極端且非常不正確的。

image

6.4.4 更新數據

SQLiteDatabase中也提供了一個非常好用的update()方法,用於對數據進行更新,這個方法接收4個參數:

  • 第一個參數和insert()方法一樣,也是表名,在這裡指定去更新哪張表裡的數據。
  • 第二個參數是ContentValues對象,要把更新數據在這裡組裝進去。
  • 第三、第四個參數用於約束更新某一行或某幾行中的數據,不指定的話預設就是更新所有行。

那麼接下來仍然是在DatabaseTest項目的基礎上修改,看一下更新數據的具體用法。比如說剛纔添加到資料庫里的第一本書,由於過了暢銷季,賣得不是很火了,現在需要通過降低價格的方式來吸引更多的顧客,我們應該怎麼操作呢?首先修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    ...
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/update_data"
        android:text="Update data"/>
</LinearLayout>

佈局文件中的代碼已經非常簡單了,就是添加了一個用於更新數據的按鈕。然後修改MainActivity中的代碼,如下所示:

public class MainActivity extends AppCompatActivity {
    private MyDatabaseHelper dbHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
       ...
        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                values.put("price",99.9);
                db.update("Book",values,"name = ?",new String[]{
                        "The Da Vinci Code"
                });
            }
        });
    }
}

這裡在更新數據按鈕的點擊事件裡面構建了一個ContentValues對象,並且只給它指定了一組數據,說明我們只是想把價格這一列的數據更新成99.9。然後調用了SQLiteDatabase的update()方法去執行具體的更新操作,可以看到,這裡使用了第三、第四個參數來指定具體更新哪幾行。第三個參數對應的是SQL語句的where部分,表示更新所有name等於?的行,而?是一個占位符,可以通過第四個參數提供的一個字元串數組為第三個參數中的每個占位符指定相應的內容。

現在重新運行一下程式,點擊一下Update data按鈕後,再次輸入查詢語句查看表中的數據情況,可以看到,The Da Vinci Code這本書的價格已經被成功改為99.9了。

6.4.5 刪除數據

SQLiteDatabase中提供了一個delete()方法,專門用於刪除數據,這個方法接收3個參數,第一個參數仍然是表名,這個已經沒什麼好說的了,第二、第三個參數又是用於約束刪除某一行或某幾行的數據,不指定的話預設就是刪除所有行。修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    andr

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...