第十六章 Android的數(shù)據(jù)存儲(chǔ)方案

1.引言

??一般在做一些面試題的時(shí)候,Android有幾種數(shù)據(jù)存儲(chǔ)方案這個(gè)問題是經(jīng)常碰到的。在我們實(shí)際應(yīng)用中,任何一個(gè)應(yīng)用程序說白了都是在不停的和數(shù)據(jù)接觸,比如我們通過QQ,微信聊天都是一個(gè)數(shù)據(jù)傳輸?shù)倪^程。又比如我們逛新聞,逛微博,刷淘寶等也都是一個(gè)數(shù)據(jù)展示的過程。說實(shí)在點(diǎn)沒有數(shù)據(jù),所有的應(yīng)用都是空架子。目前絕大多數(shù)數(shù)據(jù)都是由用戶產(chǎn)生的,發(fā)評(píng)論發(fā)朋友圈的等等都是一個(gè)產(chǎn)生數(shù)據(jù)的過程。
??因?yàn)槲覀冎览厥諜C(jī)制,當(dāng)程序關(guān)閉或其他原因?qū)е聰?shù)據(jù)被回收掉的時(shí)候,會(huì)有些數(shù)據(jù)被丟失,這些是瞬時(shí)數(shù)據(jù),例如,在輸入框輸入一個(gè)數(shù)字,發(fā)送一個(gè)字符串等,一旦我們沒有再需要的時(shí)候就會(huì)丟失。但是有些數(shù)據(jù)需要持久的保存不想被回收掉,例如朋友圈的動(dòng)態(tài)和評(píng)論,微博留言等。這就需要保持關(guān)鍵新的數(shù)據(jù)不丟失。

2.保持?jǐn)?shù)據(jù)持久化的簡(jiǎn)介

??數(shù)據(jù)持久化處理是將內(nèi)存中的數(shù)據(jù)能夠被保存在特定的存儲(chǔ)空間中,保證數(shù)據(jù)在程序關(guān)閉和異常情況下不會(huì)被丟失。Android 系統(tǒng)提供了5種方式來保存數(shù)據(jù):
1.文件存儲(chǔ)
2.SharePreference存儲(chǔ)
3.數(shù)據(jù)庫存儲(chǔ)
4.ContentProvider存儲(chǔ)
5.網(wǎng)絡(luò)存儲(chǔ)

3.文件存儲(chǔ)

??文件存儲(chǔ)是Android中最基本的一種數(shù)據(jù)存儲(chǔ)方法。它通過文件的方式將數(shù)據(jù)不做任何處理的存儲(chǔ)進(jìn)文件中,因此適合存儲(chǔ)一些簡(jiǎn)單的文本數(shù)據(jù)或者二進(jìn)制數(shù)據(jù)。當(dāng)然也不是不適合存儲(chǔ)復(fù)雜的文本數(shù)據(jù),只是需要定義一套自己的格式規(guī)范,方便之后將數(shù)據(jù)從文件中重新解析出來。

3.1 將數(shù)據(jù)存儲(chǔ)到文件夾中

??文件IO流的存取提供了FileOutputStram()和FileInputStream()這兩個(gè)方法來存儲(chǔ)和取出文件中的數(shù)據(jù)。openFileOutput()這個(gè)方法可以用于將數(shù)據(jù)存儲(chǔ)到指定為文件中。openFileOutput(String name , int mode)這個(gè)方法第一個(gè)參數(shù)用于指定文件名,第二個(gè)參數(shù)指定打開文件的模式。
具體有:

  1. MODE_PRIVATE:為默認(rèn)操作模式,代表該文件是私有數(shù)據(jù),只能被應(yīng)用本身訪問,在該模式下,寫入的內(nèi)容會(huì)覆蓋原文件的內(nèi)容,如果想把新寫入的內(nèi)容追加到原文件中。可以使用Context. MODE_APPEND 。
  2. MODE_APPEND:模式會(huì)檢查文件是否存在,存在就往文件追加內(nèi)容,否則就創(chuàng)建新文件。
  3. MODE_WORLD_READABLE:表示當(dāng)前文件可以被其他應(yīng)用讀??;
  4. MODE_WORLD_WRITEABLE:表示當(dāng)前文件可以被其他應(yīng)用寫入。(后面兩種在Android4.2版本中被廢棄了)
    具體實(shí)現(xiàn)將數(shù)據(jù)存儲(chǔ)的步驟:
    1.首先新建一個(gè)layout文件activity_file_save.xml.這個(gè)頁面就兩個(gè)控件,一個(gè)輸入框EditText,一個(gè)保存按鈕。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
   >

    <EditText
        android:id="@+id/et_txt_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="寫點(diǎn)什么唄"/>

    <Button
    android:id="@+id/btn_save_data"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:text="保存數(shù)據(jù)"/>
</LinearLayout>

2.新建一個(gè)Activity,處理保存數(shù)據(jù)的邏輯。通過調(diào)用openFileOutPut()的方法返回一個(gè)FileOutputStream對(duì)象。得到這個(gè)對(duì)象之后就可以使用Java保存文件的方法將數(shù)據(jù)寫入到文件中。這里生成一個(gè)data文件保存文件中。代碼如下:

  private void save(String inputtext) {
         FileOutputStream outputStream = null;
         BufferedWriter bufferedWriter = null;
        try {
            outputStream = openFileOutput("data", Context.MODE_PRIVATE);
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write(inputtext);
            Toast.makeText(FileSaveActivity.this, "保存數(shù)據(jù)成功"+inputtext, Toast.LENGTH_SHORT).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bufferedWriter!=null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.運(yùn)行代碼,在模擬器查看保存的數(shù)據(jù)。點(diǎn)擊Android Studio 導(dǎo)航欄的Tools->Android->Android Device Monitor -> File Explorer,在這里找到data/data/com.demo.filesavedemo/files/目錄 查看模擬器的數(shù)據(jù)。

Android 工具列表

Android7.0的模擬器有點(diǎn)小bug,選擇6.0的好了。

生成data文件

點(diǎn)擊左邊這個(gè)按鈕,導(dǎo)出文件到電腦上:

導(dǎo)出導(dǎo)入按鈕

輸入qwert 保存,輸入結(jié)果:

輸出結(jié)果

這就證實(shí)了 輸入的EditText的內(nèi)容被成功保存到文件中了。

3.2 從文件夾中讀取數(shù)據(jù)

類似于數(shù)據(jù)存儲(chǔ)到文件中,Context類還提供了一個(gè)openFileInput()的方法,用于從文件中讀取數(shù)據(jù)這個(gè)相比較openFileOutput()簡(jiǎn)單一些,只要讀取文件名這個(gè)參數(shù)就可以了。系統(tǒng)會(huì)自動(dòng)讀取data/data/com.demo.filesavedemo/files/目錄這個(gè)文件,并返回一個(gè)FileInputStream對(duì)象。得到這個(gè)對(duì)象之后就可以使用Java讀取文件數(shù)據(jù)的方法讀取文件中的數(shù)據(jù)

 private  String load(){
         FileInputStream inputStream=null;
         BufferedReader bufferedReader=null;
        StringBuilder content=new StringBuilder();

        try {
            inputStream = openFileInput("data");
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line=" ";
            try {
                while ((line=bufferedReader.readLine())!=null){
                     content.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(bufferedReader!=null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return  content.toString();
    }

最后將文件存儲(chǔ)輸入和輸出合并完整代碼:
1.activity_file_save.xml

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

    <EditText
        android:id="@+id/et_txt_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="寫點(diǎn)什么唄"/>

    <Button
    android:id="@+id/btn_save_data"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:text="保存數(shù)據(jù)"/>

    <Button
        android:id="@+id/btn_load_data"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="讀取數(shù)據(jù)"/>
    <TextView
        android:id="@+id/tv_txt_load_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textSize="15sp"
        />


</LinearLayout>

2.FileSaveActivity.java

ublic class FileSaveActivity extends AppCompatActivity {

    private static final String TAG = "FileSaveActivity";
    private EditText editText;

    private Button  btn_load_data,btn_save_data;
    private TextView  tv_txt_load_data;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file_save);
        editText = (EditText) findViewById(R.id.et_txt_content);
        tv_txt_load_data= (TextView) findViewById(R.id.tv_txt_load_data);

         btn_save_data= (Button) findViewById(R.id.btn_save_data);
         btn_load_data= (Button) findViewById(R.id.btn_load_data);
        btn_save_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                 String  inputtext=editText.getText().toString();
                save(inputtext);

            }
        });
        btn_load_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String  inputText=load();
                Log.e(TAG, "onClick: "+inputText );
                if(!TextUtils.isEmpty(inputText)){
                    tv_txt_load_data.setText(inputText);
                }
                Toast.makeText(FileSaveActivity.this, "讀取數(shù)據(jù)成功="+inputText, Toast.LENGTH_SHORT).show();
            }

        });

    }

    //保存數(shù)據(jù)到文件中
    private void save(String inputtext) {
         FileOutputStream outputStream = null;
         BufferedWriter bufferedWriter = null;
        try {
            outputStream = openFileOutput("data", Context.MODE_PRIVATE);
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write(inputtext);
            Toast.makeText(FileSaveActivity.this, "保存數(shù)據(jù)成功"+inputtext, Toast.LENGTH_SHORT).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bufferedWriter!=null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //讀取文件中的數(shù)據(jù)
    private  String load(){
         FileInputStream inputStream=null;
         BufferedReader bufferedReader=null;
        StringBuilder content=new StringBuilder();

        try {
            inputStream = openFileInput("data");
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line=" ";
            try {
                while ((line=bufferedReader.readLine())!=null){
                     content.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(bufferedReader!=null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return  content.toString();
    }
}

所用的核心及技術(shù)就是openFileInput()和openFileOutput()方法,之后就是Java的各種讀寫流來進(jìn)行讀寫操作。
但是這種存儲(chǔ)方式不適合保存復(fù)雜的文本數(shù)據(jù)。

4. SharePreferences存儲(chǔ)

與文件存儲(chǔ)不同的是,SharePreferences存儲(chǔ)是通過鍵值對(duì)來進(jìn)行存儲(chǔ),在存儲(chǔ)數(shù)據(jù)的時(shí)候,需要給數(shù)據(jù)提供對(duì)應(yīng)的鍵,然后通過鍵來尋找相對(duì)應(yīng)的值。而且SharePreferences支持多種不同的數(shù)據(jù)類型來進(jìn)行存儲(chǔ),存取的過程中,數(shù)據(jù)的類型不會(huì)發(fā)生改變。

4.1 將數(shù)據(jù)存儲(chǔ)到SharePreferences中

實(shí)現(xiàn)SharedPreferences存儲(chǔ)的步驟如下:   
一、根據(jù)Context獲取SharedPreferences對(duì)象   
二、利用edit()方法獲取Editor對(duì)象。   
三、通過Editor對(duì)象存儲(chǔ)key-value鍵值對(duì)數(shù)據(jù)。   
四、通過commit()方法提交數(shù)據(jù)。
示例代碼:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file_share_preference);
        Button button= (Button) findViewById(R.id.btn_share_save);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SharedPreferences.Editor editor=getSharedPreferences("data",MODE_PRIVATE).edit();
                editor.putString("name","小強(qiáng)");
                editor.putInt("age",18);
                editor.putBoolean("married",false);
                editor.apply();
                Toast.makeText(FileSharePreferenceActivity.this, "保存數(shù)據(jù)成功", Toast.LENGTH_SHORT).show();
            }
        });

    }

設(shè)置點(diǎn)擊按鈕,保存數(shù)據(jù)到data/data/com.demo.filesavedemo/shared_prefs/目錄。查看的方法和上面的一樣。生成了一個(gè)xml的文件

SharedPreferences存儲(chǔ)

這里補(bǔ)充一下Android中主要提供了3種方法去得到SharedPreferences對(duì)象
1.Context類中的getSharedPreferences()方法。這個(gè)方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)是指定存放SharedPreferences文件的名稱。如果指定的文件不存在,則會(huì)創(chuàng)建一個(gè),SharedPreferences文件的存放目錄為/data/data/<包名>/shared_prefs/目錄,第二個(gè)參數(shù)用于指定操作模式。目前只有MODE_PRIVATE這一種模式可選,它是默認(rèn)的操作模式,和直接傳入數(shù)字0是一樣的效果,表示只有當(dāng)前的應(yīng)用程序才可以對(duì)這個(gè)SharedPreferences文件進(jìn)行存儲(chǔ)。其他的操作模式均已廢棄。MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,在Android4.2版本被廢棄了,MODE_MULTI_PROCESS是在6.0被廢棄。
2.Activity類中的getPreference()方法。這個(gè)方法與getSharedPreferences()方法很像,只是它只接受一個(gè)操作模式參數(shù),使用這個(gè)方法會(huì)自動(dòng)將當(dāng)前活動(dòng)的類名作為SharedPreferences的文件名。
3.PreferenceManager類中的getDefaultSharedPreferences()方法。這是個(gè)靜態(tài)方法,它接收一個(gè)Context參數(shù),自動(dòng)使用當(dāng)前包名作為前綴來明明SharedPreferences文件。得到SharedPreferences對(duì)象之后,就可以開始存儲(chǔ)數(shù)據(jù)了。

4.2 從SharedPreferences中讀取數(shù)據(jù)

SharedPreferences的存儲(chǔ)數(shù)據(jù)非常的簡(jiǎn)單,讀取數(shù)據(jù)其實(shí)更加的簡(jiǎn)單。SharedPreferences提供了一系列的get()方法來對(duì)應(yīng)存儲(chǔ)的put()方法。但是與存儲(chǔ)不同的是,提取的方法有兩個(gè)參數(shù),第一個(gè)參數(shù)是對(duì)應(yīng)的字符類型,第二個(gè)是默認(rèn)值,當(dāng)傳入的鍵找不到對(duì)應(yīng)的值的時(shí)候會(huì)以什么樣的默認(rèn)值進(jìn)行返回。
1.修改activity_file_share_preference.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
   >
    <Button
        android:id="@+id/btn_share_save"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="SharePreference存儲(chǔ)"/>
    <Button
        android:id="@+id/btn_share_load"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="SharePreference讀取"/>

</LinearLayout>

2.修改FileSharePreferenceActivity.java文件


public class FileSharePreferenceActivity extends AppCompatActivity {
    private static final String TAG = "FileSharePreferenceActi";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file_share_preference);
        Button button= (Button) findViewById(R.id.btn_share_save);
        Button button2= (Button) findViewById(R.id.btn_share_load);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                saveDate();
            }
        });
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                loadDate();
            }
        });

    }

    public void saveDate(){
        SharedPreferences.Editor editor=getSharedPreferences("data",MODE_PRIVATE).edit();
        editor.putString("name","小強(qiáng)");
        editor.putInt("age",18);
        editor.putBoolean("married",false);
        editor.apply();
        Toast.makeText(FileSharePreferenceActivity.this, "保存數(shù)據(jù)成功", Toast.LENGTH_SHORT).show();
    }

    public  void loadDate(){
         SharedPreferences spf=getSharedPreferences("data",MODE_PRIVATE);
        String name= spf.getString("name","");//字符串默認(rèn)為空
        boolean  married= spf.getBoolean("married",false);
       int age=  spf.getInt("age",0);
        Log.i(TAG, "loadDate: name="+name);
        Log.i(TAG, "loadDate: married="+married);
        Log.i(TAG, "loadDate: age="+age);
    }

}

先將數(shù)據(jù)存儲(chǔ)到shared_prefs目錄下的文件,再將文件里的數(shù)據(jù)讀取出來


SharedPreferences讀取數(shù)據(jù)

5.數(shù)據(jù)庫存儲(chǔ)

之前在講ListView的時(shí)候講過了數(shù)據(jù)庫存儲(chǔ)。Android 對(duì)數(shù)據(jù)庫的支持很好,自身就支持SQLite 這種輕量級(jí)的數(shù)據(jù)庫,它的運(yùn)算速度快,占用資源少,通常幾百KB的內(nèi)存就夠了,并且支持標(biāo)準(zhǔn)的SQL語法,遵循數(shù)據(jù)庫的ACID事務(wù)(是Atomic(原子性)、Consistency(一致性)、Isolation(隔離性)和Durability(持久性)的英文縮寫)。相比較一般的數(shù)據(jù)庫上手簡(jiǎn)單。
??在存儲(chǔ)一些簡(jiǎn)單的數(shù)據(jù)和鍵值對(duì)的時(shí)候,可能會(huì)用上SharedPreferences存儲(chǔ)。但是需要存儲(chǔ)大量的復(fù)雜的關(guān)系型數(shù)據(jù)的時(shí)候,SharedPreferences顯然不能滿足要求,這時(shí)候就需要SQLite數(shù)據(jù)庫的存儲(chǔ)。數(shù)據(jù)庫存儲(chǔ)的主要操作:創(chuàng)建數(shù)據(jù)庫,升級(jí)數(shù)據(jù)庫,增,刪,查,改操作。

5.1 創(chuàng)建數(shù)據(jù)庫

Android 為了能方便管理數(shù)據(jù)庫,專門提供了一個(gè)SQLiteOpenHelper類,借助這個(gè)類就能簡(jiǎn)單的對(duì)數(shù)據(jù)庫進(jìn)行創(chuàng)建和升級(jí)了。在了解SQLiteOpenHelper之前,查看源碼,發(fā)現(xiàn)它是一個(gè)抽象類,所以在使用的時(shí)候需要?jiǎng)?chuàng)建自己的幫助類去繼承它,SQLiteOpenHelper有兩個(gè)抽象方法,分別是onCreat()和onUpgrade(),實(shí)現(xiàn)創(chuàng)建和升級(jí)數(shù)據(jù)庫的邏輯。這里貼出SQLiteOpenHelper 的源碼,下面會(huì)對(duì)主要的方法進(jìn)行分析。


package android.database.sqlite;

public abstract class SQLiteOpenHelper {
    public SQLiteOpenHelper(android.content.Context context, java.lang.String name, android.database.sqlite.SQLiteDatabase.CursorFactory factory, int version) { /* compiled code */ }

    public SQLiteOpenHelper(android.content.Context context, java.lang.String name, android.database.sqlite.SQLiteDatabase.CursorFactory factory, int version, android.database.DatabaseErrorHandler errorHandler) { /* compiled code */ }

    public java.lang.String getDatabaseName() { /* compiled code */ }

    public void setWriteAheadLoggingEnabled(boolean enabled) { /* compiled code */ }

    public android.database.sqlite.SQLiteDatabase getWritableDatabase() { /* compiled code */ }

    public android.database.sqlite.SQLiteDatabase getReadableDatabase() { /* compiled code */ }

    public synchronized void close() { /* compiled code */ }

    public void onConfigure(android.database.sqlite.SQLiteDatabase db) { /* compiled code */ }

    public abstract void onCreate(android.database.sqlite.SQLiteDatabase sqLiteDatabase);

    public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase sqLiteDatabase, int i, int i1);

    public void onDowngrade(android.database.sqlite.SQLiteDatabase db, int oldVersion, int newVersion) { /* compiled code */ }

    public void onOpen(android.database.sqlite.SQLiteDatabase db) { /* compiled code */ }
}

SQLiteOpenHelper中還有兩個(gè)非常重要的實(shí)例方法:getReadableDatabase()和getWriteableDatabase()。這兩個(gè)方法都可以創(chuàng)建或打開一個(gè)現(xiàn)有的數(shù)據(jù)庫(如果有數(shù)據(jù)庫了,就直接打開,沒有就創(chuàng)建),并返回一個(gè)可對(duì)數(shù)據(jù)庫進(jìn)行讀寫操作的對(duì)象。不同的是,當(dāng)數(shù)據(jù)庫不可寫入的時(shí)候(比如磁盤滿了),getReadableDatabase()方法返回的對(duì)象將以只讀的方式打開數(shù)據(jù)庫,而getWriteableDatabase()的方法則將出現(xiàn)異常。所以要注意下。
??通過源碼我們可以發(fā)現(xiàn)SQLiteOpenHelper 有兩個(gè)構(gòu)造方法可以重寫,一般都用參數(shù)少的那個(gè)構(gòu)造方法,這個(gè)構(gòu)造方法中接收4個(gè)參數(shù),第一個(gè)參數(shù)是Context,必須要有它才能對(duì)數(shù)據(jù)庫進(jìn)行操作,第二個(gè)參數(shù)是數(shù)據(jù)庫名,創(chuàng)建數(shù)據(jù)庫時(shí)使用的就是這里指定的名稱,第三個(gè)參數(shù)是允許我們?cè)诓樵償?shù)據(jù)的時(shí)候返回一個(gè)Cursor,一般都穿null.第四個(gè)參數(shù)表示當(dāng)前數(shù)據(jù)庫的版本號(hào)??捎糜趯?duì)數(shù)據(jù)庫進(jìn)行升級(jí)操作。構(gòu)建出SQLiteOpenHelper的實(shí)例之后,再調(diào)用getReadableDatabase()或getWriteableDatabase()的方法就能創(chuàng)建數(shù)據(jù)庫了。數(shù)據(jù)庫文件會(huì)存在/data/data/<包名>/databases/目錄下。此時(shí)需要重寫onCreate()方法創(chuàng)建表。
代碼如下所示:
1.activity_sqlsave.xml,設(shè)置一個(gè)點(diǎn)擊事件,創(chuàng)建一個(gè)庫

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.demo.filesavedemo.SQLSave.SQLSaveActivity">

    <Button
        android:id="@+id/btn_create_data"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:textSize="18sp"
        android:text="創(chuàng)建數(shù)據(jù)庫"/>

</LinearLayout>

  1. 創(chuàng)建一個(gè)類MySQliteDataHelper繼承SQLiteOpenHelper,然后創(chuàng)建一張表CREATE_BOOK, 然后通過sqLiteDatabase.execSQL(CREATE_BOOK)執(zhí)行創(chuàng)建表的操作。這里設(shè)置一個(gè)Toast消息,提示創(chuàng)建庫成功。

public class MySQliteDataHelper 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 MySQliteDataHelper(Context context, String name, 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, "創(chuàng)建數(shù)據(jù)庫成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

3.在創(chuàng)建SQLSaveActivity ,創(chuàng)建數(shù)據(jù)庫MYBOOKStore.db


public class SQLSaveActivity extends AppCompatActivity {

    private MySQliteDataHelper mySQliteDataHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlsave);
        mySQliteDataHelper = new MySQliteDataHelper(this,"MYBOOKStore.db",null,1);
        Button btn_create_data= (Button) findViewById(R.id.btn_create_data);
        btn_create_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                 mySQliteDataHelper.getWritableDatabase();
            }
        });

    }
}

創(chuàng)建數(shù)據(jù)庫

在創(chuàng)建數(shù)據(jù)庫之后,我們只是通過一個(gè)Toast消息來知道到底是否創(chuàng)建了成功了數(shù)據(jù)庫,但是眼見為實(shí)吧。這里有兩種方法查看操作的數(shù)據(jù)庫:
方法一:Android Studio的導(dǎo)航欄->Tools->Android->Android Device Motior->File Explorer->data/data/<項(xiàng)目的包名>/databases/創(chuàng)建的數(shù)據(jù)庫

創(chuàng)建數(shù)據(jù)庫

方法二:復(fù)雜一點(diǎn),首先需要在環(huán)境變量中配置adb,找到你的Android Studio 的sdk的地址,然后找到platform-tools,創(chuàng)建一個(gè)系統(tǒng)變量 android_adb(已經(jīng)配置了的不用管這樣一步)


創(chuàng)建adb目錄

然后將這個(gè)目錄加到path的末尾。

添加目錄

最后打開控制臺(tái),輸入adb shell 回車,然后輸入 cd /data/data/全包名/databases 回車,最后輸入 ls 查看里面的目錄。創(chuàng)建好的庫就在這里了。

創(chuàng)建數(shù)據(jù)庫

這里會(huì)看到兩個(gè)數(shù)據(jù)庫文件,一個(gè)文件是我們創(chuàng)建的MYBOOKStore.db,另一個(gè)MYBOOKStore.db-journal 文件是為了數(shù)據(jù)庫能夠支持事務(wù)而產(chǎn)生的臨時(shí)日志文件,通常情況下這個(gè)文件是0字節(jié)。

在查看了數(shù)據(jù)庫之后接下來還要結(jié)束sqlite命令來打開數(shù)據(jù)庫,輸入sqlite3,后面加上數(shù)據(jù)庫的名稱就可以了。

打開數(shù)據(jù)庫

在打開數(shù)據(jù)庫之后,可以輸入 .table 來查看數(shù)據(jù)庫中建的表,

查看表

輸入 .schema 來查看創(chuàng)建表的SQL語句

查看創(chuàng)建語句

輸入.quite 或者 .exit命令退出數(shù)據(jù)庫的編輯,在輸入exit命令退出控制臺(tái)。

5.2 升級(jí)數(shù)據(jù)庫

在之前的MySQliteDataHelper 的類中,還有一個(gè)onUpgrade()方法沒有用,這個(gè)方法之前說過了是升級(jí)數(shù)據(jù)庫用的。所以來講講怎么升級(jí)數(shù)據(jù)庫。因?yàn)槲覀冎耙呀?jīng)創(chuàng)建了數(shù)據(jù)庫MYBOOKSore.db,所以我們?cè)趏nCreate()方法中無法再創(chuàng)建同樣的數(shù)據(jù)庫了。但是你又需要添加一張表進(jìn)這個(gè)庫怎么辦,創(chuàng)建又不能用。修改代碼:
1.修改MySQliteDataHelper ,在onUpgrade()中執(zhí)行DROP語句,如果發(fā)現(xiàn)數(shù)據(jù)庫中已經(jīng)有了Book或者Category 表就刪除掉,然后在重新調(diào)用onCreate()方法。這樣就可以解決了,重復(fù)庫的問題。

public class MySQliteDataHelper 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 MySQliteDataHelper(Context context, String name, 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, "創(chuàng)建數(shù)據(jù)庫成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int olderVersion, int newVersion) {
        sqLiteDatabase.execSQL("drop table if exists BOOK");
        sqLiteDatabase.execSQL("drop table if exists Category");
        onCreate(sqLiteDatabase);
    }
}

2.解決了重復(fù)庫的問題,接下來就要解決升級(jí)的問題,也很簡(jiǎn)單,MySQliteDataHelper修改最后一個(gè)版本參數(shù),讓它大于1就可以了,這樣就可以完成數(shù)據(jù)庫升級(jí)了。(和我們的App版本升級(jí)一樣)


public class SQLSaveActivity extends AppCompatActivity {

    private MySQliteDataHelper mySQliteDataHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlsave);
        mySQliteDataHelper = new MySQliteDataHelper(this,"MYBOOKStore.db",null,2);
        Button btn_create_data= (Button) findViewById(R.id.btn_create_data);
        btn_create_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                 mySQliteDataHelper.getWritableDatabase();
            }
        });

    }
}

5.3 刪除數(shù)據(jù)庫

假如一步我在這里創(chuàng)建了兩個(gè)數(shù)據(jù)庫了,但是你想刪除一個(gè)數(shù)據(jù)庫了。這時(shí)候怎么辦?方法很簡(jiǎn)單,創(chuàng)建的數(shù)據(jù)庫都是以文件的形式保存在內(nèi)存中的,既然要?jiǎng)h除數(shù)據(jù)庫,首先需要找到這個(gè)數(shù)據(jù)庫的地址。然后調(diào)用文件的delete()方法刪除數(shù)據(jù)庫

刪除前有兩個(gè)數(shù)據(jù)庫

1.添加一個(gè)刪除按鈕

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.demo.filesavedemo.SQLSave.SQLSaveActivity">

     ..............

    <Button
        android:id="@+id/btn_delete_data"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:textSize="18sp"
        android:text="刪除數(shù)據(jù)庫"
        android:visibility="visible"/>
</LinearLayout>

  1. 寫一個(gè)方法刪除文件。注釋在代碼里面。

    public void deleteFile(File file) {
        if (file.exists()) { // 判斷文件是否存在
            if (file.isFile()) { // 判斷是否是文件
                // 設(shè)置屬性:讓文件可執(zhí)行,可讀,可寫
                file.setExecutable(true, false);
                file.setReadable(true, false);
                file.setWritable(true, false);
                file.delete(); // delete()方法
            } else if (file.isDirectory()) { // 否則如果它是一個(gè)目錄
                File files[] = file.listFiles(); // 聲明目錄下所有的文件 files[];
                for (int i = 0; i < files.length; i++) { // 遍歷目錄下所有的文件
                    this.deleteFile(files[i]); // 把每個(gè)文件 用這個(gè)方法進(jìn)行迭代
                }
            }
            file.setExecutable(true, false);
            file.setReadable(true, false);
            file.setWritable(true, false);
            file.delete();
            Toast.makeText(this, "成功刪除"+file.getName(), Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, file.getName()+"不存在?。?!", Toast.LENGTH_SHORT).show();
        }
    }

3.然后點(diǎn)擊刪除按鈕出發(fā)刪除事件,因?yàn)槭莾蓚€(gè),所以兩個(gè)文件都要?jiǎng)h除。

    Button btn_delete_data = (Button) findViewById(R.id.btn_delete_data);
  btn_delete_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                File file1 = new File("/data/data/com.demo.filesavedemo/databases/MYBOOKStore.db");
                deleteFile(file1);
                File file2 = new File("/data/data/com.demo.filesavedemo/databases/MYBOOKStore.db-journal");
                deleteFile(file2);

            }
        });
刪除后只有一個(gè)數(shù)據(jù)庫

5.4 添加數(shù)據(jù)

熟悉數(shù)據(jù)庫操作的話,一般只有增刪查改(CRUD)這四種操作,其中C代表添加(Create),R代表查詢(Retrieve),U代表更新(Update),D代表刪除(Delete)。熟悉SQL語言的話,肯定了解添加數(shù)據(jù)。Android 的添加數(shù)據(jù)語言和SQl語言和相似,并且提供了很多輔助方法。前面說了SQLiteOpenHelper中有兩個(gè)非常重要的實(shí)例方法:getReadableDatabase()和getWriteableDatabase()。這兩個(gè)方法都可以創(chuàng)建或打開一個(gè)現(xiàn)有的數(shù)據(jù)庫,并返回一個(gè)可對(duì)數(shù)據(jù)庫進(jìn)行讀寫操作的對(duì)象SQLiteDatabase。正是通過這個(gè)對(duì)象對(duì)數(shù)據(jù)進(jìn)行CRUD操作。
??添加數(shù)據(jù)的方法insert() ,這個(gè)方法有3個(gè)參數(shù)。第一個(gè)參數(shù)是表的名稱,第二個(gè)參數(shù)是在未指定的情況下給某些可為空的列自動(dòng)賦值,直接傳入null就可以了。第三個(gè)參數(shù)是一個(gè)ContentValues對(duì)象,提供了一系列的put()方法重載,用于向ContentValues中添加數(shù)據(jù)。只需要將表中的每個(gè)列名和相應(yīng)的待添加的數(shù)據(jù)傳入即可。
1.在activity_sqlsave.xml文件中添加一個(gè)Button。

 <Button
        android:id="@+id/btn_insert_data"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:textSize="18sp"
        android:text="添加數(shù)據(jù)"/>

2.在SQLSaveActivity中添加數(shù)據(jù)

     Button btn_insert_data = (Button) findViewById(R.id.btn_insert_data);
   btn_insert_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase sqLiteDatabase=mySQliteDataHelper.getWritableDatabase();
                ContentValues values=new ContentValues();
                //開始加入第一條數(shù)據(jù)
                values.put("author","Tom");
                values.put("price",20);
                values.put("pages",555);
                values.put("name","your name");
                sqLiteDatabase.insert("Book",null,values);
                values.clear();
                //開始加入第二條數(shù)據(jù)
                values.put("author","Json");
                values.put("price",60);
                values.put("pages",999);
                values.put("name","my name");
                sqLiteDatabase.insert("Book",null,values);
                Toast.makeText(SQLSaveActivity.this, "插入數(shù)據(jù)成功", Toast.LENGTH_SHORT).show();
            }
        });

最后出發(fā)點(diǎn)擊事件就可以了。打開 adb查詢數(shù)據(jù)庫里面的數(shù)據(jù)。輸入 select * from Book; 記得要在末尾添加 分號(hào);,最后的結(jié)果如圖所示:


插入數(shù)據(jù)

5.5 更新數(shù)據(jù)

和添加數(shù)據(jù)相類似,SQLiteDatabase添加了update()的方法,對(duì)表中數(shù)據(jù)進(jìn)行更新。這個(gè)方法的第一個(gè)參數(shù)是表名,第二個(gè)參數(shù)是ContentValues對(duì)象,第三、四個(gè)參數(shù)是用于約束更新某一行或幾行中的數(shù)據(jù),不指定就默認(rèn)更新全部。
修改之前的代碼
1.在activity_sqlsave.xml文件中添加一個(gè)Button。

  <Button
        android:id="@+id/btn_update_data"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:textSize="18sp"
        android:text="更新數(shù)據(jù)"/>

2.在SQLSaveActivity中添加 更新數(shù)據(jù)的事件。這里將數(shù)據(jù)pages 的值換成1010,

 Button btn_update_data = (Button) findViewById(R.id.btn_update_data);
   btn_update_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase sb=mySQliteDataHelper.getWritableDatabase();
                ContentValues values=new ContentValues();
                values.put("pages",1010);
                sb.update("Book",values,"name=?",new String[]{"my name"});
                Toast.makeText(SQLSaveActivity.this, "更新數(shù)據(jù)成功", Toast.LENGTH_SHORT).show();
            }
        });

最后再輸入一遍查詢語句,查看觸發(fā)更新之后的數(shù)據(jù)結(jié)果如下圖所示:

數(shù)據(jù)更新

5.6 刪除數(shù)據(jù)

同樣的SQLiteDatabase提供了delete()方法,專門刪除數(shù)據(jù)。這個(gè)方法的第一個(gè)參數(shù)還是表名,第二,三個(gè)用于約束刪除某一行或幾行的數(shù)據(jù),不指定,默認(rèn)全部刪除所有行。
修改之前的代碼
1.在activity_sqlsave.xml文件中添加一個(gè)Button。

  <Button
        android:id="@+id/btn_delete_data_1"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:textSize="18sp"
        android:text="刪除數(shù)據(jù)"/>

2.修改SQLSaveActivity,添加刪除事件。如果price的值為20就刪除

   Button btn_delete_data_1 = (Button) findViewById(R.id.btn_delete_data_1);
  btn_delete_data_1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase sb=mySQliteDataHelper.getWritableDatabase();
                sb.delete("Book","price=?",new String[]{"20"});
                Toast.makeText(SQLSaveActivity.this, "刪除數(shù)據(jù)成功", Toast.LENGTH_SHORT).show();
            }
        });

最后再輸入一遍查詢語句,查看觸發(fā)刪除操作之后的數(shù)據(jù)結(jié)果如下圖所示:


刪除之后的結(jié)果

5.7 查詢數(shù)據(jù)

終于到了查詢語言了,但是這也是數(shù)據(jù)操作功能使用最大的一章。增刪改都只占了一小部分。SQLiteDatabase提供的查詢方法,包含了七個(gè)參數(shù)。第一個(gè)參數(shù)是查詢的表的名字,第二個(gè)是指定查詢的列名,第三,四個(gè)約束條件指定查詢某一行或某幾行的數(shù)據(jù),不指定會(huì)查詢所有行的數(shù)據(jù)。第五個(gè)用于指定group by 的列,不指定表示不對(duì)group by操作。第六個(gè)用于對(duì)group by之后的數(shù)據(jù)進(jìn)行下一步的過濾,不指定表示不過濾。第七個(gè)用于指定查詢結(jié)果的排序方式。

query() 方法參數(shù) 對(duì)應(yīng)的SQL部分 描述
table from table_name 指定查詢的表名
columns select column1,column2 指定查詢的列名
selection where column= value 指定where的約束條件 為
selectionArgs - where中的占位符提供具體的值
groupBy group by column 指定需要group by 的列
having having column = value 對(duì)group by后的結(jié)果進(jìn)一步約束
orderBy order by column1,column2 指定查詢結(jié)果的排列方式

雖然看起來query()的參數(shù)非常多,但是實(shí)際開發(fā)中,你不一定需要每條語句都指定所有的參數(shù),一般情況下傳入幾個(gè)參數(shù)就可以完成查詢操作。調(diào)用后query()后會(huì)返回一個(gè)Cursor()對(duì)象,查詢的所有數(shù)據(jù)都是從這個(gè)對(duì)象中取出來。
具體代碼:
1.在activity_sqlsave.xml文件中添加一個(gè)Button。

 <Button
        android:id="@+id/btn_query_data"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:textSize="18sp"
        android:text="查詢數(shù)據(jù)"/>

2.修改SQLSaveActivity,添加查詢事件,并將查詢的值打印出來。

 Button btn_query_data = (Button) findViewById(R.id.btn_query_data);
btn_query_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase sb=mySQliteDataHelper.getWritableDatabase();
                //查詢表中所有的數(shù)據(jù)
                Cursor cursor=sb.query("Book",null,null,null,null,null,null);
                if(cursor.moveToFirst()){
                    do {
                        //遍歷Cursor對(duì)象,取出數(shù)據(jù)打印
                        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.e(TAG, "book name is"+name );
                        Log.e(TAG, "book author is"+author );
                        Log.e(TAG, "book pages is"+pages );
                        Log.e(TAG, "book price is"+price );
                    }while (cursor.moveToNext());
                }
                cursor.close();
            }
        });

這里通過query()方法查詢數(shù)據(jù),第一個(gè)參數(shù)指定查詢的表,后面的全部設(shè)為空,表示查詢所有的數(shù)據(jù),雖然這張表只剩一條數(shù)據(jù)了,但是查詢完之后可以得到一個(gè)Cursor對(duì)象,然后調(diào)用它的moveToFirst()方法將指針指向第一行的位置,然后進(jìn)入循環(huán)當(dāng)中,遍歷查詢每一個(gè)數(shù)據(jù)。在遍歷的過程中,可以通過cursor的getColumnIndex()方法獲取某一列在表中對(duì)應(yīng)的位置的索引,然后通過索引獲取相應(yīng)的方法。最后打印出來。


查詢結(jié)果

小總結(jié):

1.添加數(shù)據(jù)的方法:

db.execSQL("insert  into Book(name,author,pages,price) values(?,?,?,?)",new String[]{"your name","Tom","555","20"});

2.更新數(shù)據(jù)的方法:

db.execSQL("update Book set price = ? where name = "?",new String[]{"20","your name});

3.刪除數(shù)據(jù)的方法:

db.execSQL("delete from Book  where name = "?",new String[]{"your name});

4.查詢數(shù)據(jù)的方法:

db.rawQuery("select * from Book",null);

github地址:https://github.com/wangxin3119/filesavedemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容