android從放棄到堅持放棄第六課(上)

第六章數據存儲(上)

持久化技術簡介

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

android系統中主要提供三種方式:

  1. 文件存儲
  2. sharedPreference存儲
  3. 數據庫存儲。

當然還可以在SD卡中存儲。不過前三者會比較簡單,而且更安全一些。


文件存儲

最基本的一種數據存儲方式。不對存儲內容進行任何的格式化處理,所有的數據都是原封不動保存的。因而它適用於一些簡單的文本數據或二進制數據。如果你想使用文件存儲的方式來保存複雜的文本數據,就需要定義一套自己的格式規範。這樣可以方便之後重新解析出來的。

將數據存儲到文件中

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

public abstract FileOutputStream openFileOutput (String name, int mode)

Open a private file associated with this Context’s application package for writing. Creates the file if it doesn’t already exist.

Parameters

name The name of the file to open; can not contain path separators.
mode Operating mode. Use 0 or MODE_PRIVATE for the default operation, MODE_APPEND to append to an existing file, MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE to control permissions.

Returns

  • The resulting FileOutputStream.

Throws

FileNotFoundException

See Also

  • MODE_APPEND
  • MODE_PRIVATE
  • MODE_WORLD_READABLE
  • MODE_WORLD_WRITEABLE
  • openFileInput(String)
  • fileList()
  • deleteFile(String)
  • FileOutputStream(String)

MODE_PRIVATE是默認的操作模式(指定同樣文件名的時候,所寫入的內容將會覆蓋原文件中的內容);

MODE_APPEND則表示如果該文件已存在,就往文件裏面追加內容,不存在就創建新文件夾。

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(new 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來將文本內容寫入到文件中。

新建project,並修改activity_main.xml中的代碼。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.wrjjrw.broadcasttest.MainActivity">

    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type sth"/>

</LinearLayout>

修改MainActivity:


public class MainActivity extends AppCompatActivity {

    private EditText edit;

    @Override
    protected void onCreate(@Nullable 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();
            }
        }
    }
}

很顯然,在銷燬前保留EditText中的數據。跑路,在Tools工具中找到Android,打開Android Device Monitor工具,然後進入了File Explorer標籤頁,在這裏找到/data/data/com.example….就可以看到了那個文件了。右上角有導入導出按鈕,導出就可以打開了。

恢復。

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

修改:

public class MainActivity extends AppCompatActivity {

    private EditText edit;

    @Override
    protected void onCreate(@Nullable 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_LONG).show();
        }
    }

    @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();
            }
        }
    }

    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();
    }
}

setSelection()方法是移動光標。跑路,顯而易見。


SharedPreferences 存儲

使用鍵值對的方式存儲數據,也就是說,當保存一條數據的時候,需要提供一個對應的鍵,這樣在讀取數據的時候就可以使用這個鍵把相應的值取出來。而且SharedPreferences支持多種不同的數據類型存儲。

存儲

要想獲取SharedPreferences來存儲數據,首先要獲取到SharedPreferences對象。有以下三種方式:

  1. context類中的getSharedPreferences()方法

    ​此方法接受兩個參數。

    public abstract SharedPreferences getSharedPreferences (String name, int mode)

    Retrieve and hold the contents of the preferences file ‘name’, returning a SharedPreferences through which you can retrieve and modify its values. Only one instance of the SharedPreferences object is returned to any callers for the same name, meaning they will see each other’s edits as soon as they are made.

    Parameters

    name Desired preferences file. If a preferences file by this name does not exist, it will be created when you retrieve an editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
    mode Operating mode. Use 0 or MODE_PRIVATE for the default operation, MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE to control permissions. The bitMODE_MULTI_PROCESS can also be used if multiple processes are mutating the same SharedPreferences file. MODE_MULTI_PROCESS is always on in apps targetting Gingerbread (Android 2.3) and below, and off by default in later versions.

    Returns

    • The single SharedPreferences instance that can be used to retrieve and modify the preference values.

    See Also

    • MODE_PRIVATE
    • MODE_WORLD_READABLE
    • MODE_WORLD_WRITEABLE
    • MODE_MULTI_PROCESS

    文件一般都存放在/data/data//shared_prefs/目錄。而且只有MODE_PRIVATE一種模式可以選擇。

  2. Activity類中的getPreferences()方法。

public SharedPreferences getPreferences (int mode)

Retrieve a SharedPreferences object for accessing preferences that are private to this activity. This simply calls the underlying getSharedPreferences(String, int)method by passing in this activity’s class name as the preferences name.

Parameters

mode Operating mode. Use MODE_PRIVATE for the default operation, MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE to control permissions.

Returns

  • Returns the single SharedPreferences instance that can be used to retrieve and modify the preference values.

和上面的一個方法類似,只不過只接受一種模式。使用這個方式時會自動將當前活動的類名作爲文件名。

  1. PreferenceManager類中的getDefaultSharedPerferences()方法

public static SharedPreferences getDefaultSharedPreferences (Context context)

Gets a SharedPreferences instance that points to the default file that is used by the preference framework in the given context.

Parameters

context The context of the preferences whose values are wanted.

Returns

  • A SharedPreferences instance that can be used to retrieve and listen to values of the preferences.

靜態方法,接受一個Context參數。並自動使用當前應用程序的包名做前綴命名SharedPreferences文件.然後就開始存儲數據,分三步驟:

  1. 調用SharedPreferences對象的edit()方法來獲取一個對象。
  2. 向SharedPreferences.Editor對象中添加數據,比如添加一個布爾就使用putBoolean()方法。。。
  3. 調用apply()方法將添加的數據提交,從而完成數據存儲操作。

栗子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.wrjjrw.broadcasttest.MainActivity">

    <Button
        android:id="@+id/save_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save data"
        android:textAllCaps="false"
        />
</LinearLayout>

修改MainActivity:

public class MainActivity extends AppCompatActivity {

    private EditText edit;

    @Override
    protected void onCreate(@Nullable 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){
                SharedPreferences.Editor editor =
                        getSharedPreferences("data2", MODE_PRIVATE).edit();
                editor.putString("name", "huchi");
                editor.putInt("age", 18);
                editor.putBoolean("married", false);
                editor.apply();
            }
        });
    }
}

通過getSharedPreferences()方法指定SharedPreferences的文件名爲data,並得到了SharedPreferences.Editor對象,接着向這個對象中添加了3條不同的數據,最後調用apply()方法。

跑路,自己看數據。


讀取

修改

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.wrjjrw.broadcasttest.MainActivity">

    <Button
        android:id="@+id/save_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save data"
        android:textAllCaps="false"
        />

    <Button
        android:id="@+id/restore_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:text="Restore data"
        android:textAllCaps="false"
        />

</LinearLayout>

修改MainActivity:

public class MainActivity extends AppCompatActivity {

    private EditText edit;

    private static final String Tag = "MainActivity";

    @Override
    protected void onCreate(@Nullable 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){
                SharedPreferences.Editor editor =
                        getSharedPreferences("data2", MODE_PRIVATE).edit();
                editor.putString("name", "huchi");
                editor.putInt("age", 18);
                editor.putBoolean("married", false);
                editor.apply();
            }
        });

        Button restoreData = (Button) findViewById(R.id.restore_data);
        restoreData.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                SharedPreferences pref = getSharedPreferences("data2", MODE_PRIVATE);
                String name = pref.getString("name","");
                int age = pref.getInt("age", 0);
                boolean married = pref.getBoolean("married", false);
                Log.d(Tag, "name is" + name);
                Log.d(Tag, "age is" + age);
                Log.d(Tag, "married is" + married);
                Toast.makeText(MainActivity.this ,"name is " + name + ",age is " + age +
                        ((married == true)?"and get married":"and is a single dog"),Toast.LENGTH_LONG).show();
            }
        });
    }
}

SharedPreferences存儲比文本存儲簡單多了,應用場景也多。


實戰:記住密碼

打開上次BroadcastBestPractise項目:

修改login界面的xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_login"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.wrjjrw.broadcastbestpractise.LoginActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal"
        >
        <TextView
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textSize="18sp"
            android:inputType="number"
            android:text="     賬號:"
            />
        <EditText
            android:id="@+id/account"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal"
        >
        <TextView
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textSize="18sp"
            android:text="     密碼:"
            />
        <EditText
            android:id="@+id/passord"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:inputType="textPassword"
            />


    </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            >

            <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:text="記住密碼"
                />
    </LinearLayout>
    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:layout_margin="30dp"
        android:layout_gravity="center"
        android:text="登陸"
        android:textSize="40dp"
        android:background="@mipmap/ic_launcher"
        />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/huchi"
        />

</LinearLayout>

這裏用到了一個複選框控件CheckBox。

修改Activity:

public class LoginActivity extends BaseActivity {

    private EditText accountEdit;

    private EditText passwordEdit;

    private Button login;

    private SharedPreferences pref;

    private SharedPreferences.Editor editor;

    private CheckBox remember_pass;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        accountEdit = (EditText) findViewById(R.id.account);
        passwordEdit = (EditText) findViewById(R.id.passord);

        pref = PreferenceManager.getDefaultSharedPreferences(this);
        remember_pass = (CheckBox) findViewById(R.id.remember_pass);
        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);
            remember_pass.setChecked(true);
        }

        login = (Button) findViewById(R.id.login);
        login.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();

                if(account.length() > 0 && password.length() > 0){
                    editor = pref.edit();
                    if(remember_pass.isChecked()){
                        editor.putString("account",account);
                        editor.putString("password",password);
                        editor.putBoolean("remember_password",true);
                    }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_LONG).show();
                }
            }
        });
    }
}

跑路。。。。

這只是一個示例,並不能這樣使用,因爲將密碼以明文的形式存儲在SharedPreferences文件中不安全,容易被盜取,需要結合一定的加密算法。


SQLite數據庫存儲

SQLite是一款輕量級的關係型數據庫,運算速度非常快,佔用資源少。不僅支持SQL語法,還遵循數據庫ACID事務,所以只要你以前使用過其他數據庫就可以快速上手了,它甚至不用設置用戶名和密碼就可以使用了。

前面學的文件存儲和SharedPreferences存儲畢竟只適用保存一些簡單的數據和鍵值對,當存儲複雜的關係型數據時,就需要SQLite數據庫。比如手機短信程序中很多的會話,會話中很多的信息內容。

創建數據庫

通過SQLiteOpenHelper幫助類,藉助這個類就可以非常簡單的對數據庫進行創建和升級。

SQLiteOpenHelper是一個抽象類,這意味着我們想要使用它,就需要創建一個自己的幫助類去繼承它。SQLiteOpenHelper中有兩個抽象方法。分別是onCreate(),onUpgrade(),必須在自己的幫助類裏面重寫這兩個方法。然後分別在這兩個方法中去實現創建和升級數據庫的邏輯。

SQLiteOpenHelper中有兩個非常重要的實例方法:getReadableDatabase()和getWritableDatabase()。這兩個方法都可以創建或打開一個現有的數據庫,並返回一個可對數據庫進行讀寫操作的對象。不同的是當數據庫不可寫入時(如磁盤空間已滿),getReadableDatabase()方法返回的對象將以只讀的方式去打開數據庫,而getWritableDatabase()方法則將拋出異常。

SQLiteOpenHelper中有兩個構造方法可供重寫,一般使用參數少一點的那個構造方法即可。這個構造方法有四個參數。第二個是數據庫名,第三個是允許我們在查詢數據的時候返回一個自定義的Cursor,一般傳入null,第四個參數是版本號,可用於對數據庫進行升級操作。構建實例後,就可以調用他的getReadableDatabase()或getWritableDatabase()方法就能夠創建數據庫了。數據庫文件會被放置在/data/data//databases/目錄下。此時重寫的onCreate()也會得到執行,所以通常會在這裏處理一些創建表的邏輯。

Public constructors
SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)Create a helper object to create, open, and/or manage a database.

Click me,我的mysql掃盲筆記

新建一個MyDatabaseHelper類繼承SQLiteOpenHelper。

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 mContent;

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

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        Toast.makeText(mContent, "Create succeeded", Toast.LENGTH_LONG).show();
    }

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

    }
}

Tips:後期發現使用auto_increment會錯誤。。跟mysql還是有點區別的。。。。

可以發現把建表的語句定義成了一個字符串常量,然後在onCreate()方法中調用了SQLiteDatabase的execSQL()方法去執行這條建表語句。

修改activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.wrjjrw.broadcastbestpractise.MainActivity">

    <Button
        android:id="@+id/create_base"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Creat database"
        />

</LinearLayout>

修改MainActivity:

public class MainActivity extends BaseActivity {

    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_base);
        createDatabase.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                dbHelper.getWritableDatabase();
            }
        });
    }
}

成功構建一個數據庫Bookstore.db。

跑路,我們發現點一下會顯示Create succeeded,但是之後再點就沒用了。是因爲Bookstore.db已經存在。因此不會再創建。數據庫和表已經創建成功了。現在打開ADM可以檢查一下,是否如上所說在那個文件夾裏面。

這次還可以使用adb shell進行查看,adb是Android SDK中自帶的一個調試工具,使用這個工具可以直接對手機或者模擬器進行調試操作,他存放在sdk的platform-tools下,如果想要在命令行中使用這個工具,則需要添加環境變量。(此處略)

打開cmd, 輸入adb shell ;然後cd data一直打開到我們所在的那個地方(可以用ls),最後用sqlite3 Bookstore.db打開數據庫。.table查看有哪些表。此時有兩張表,一張是Book,就是我們創建的,還有一個是android_metadata表是自動生成的。.exit或者.quit退出數據庫編輯,`exit退出設備控制檯。


升級數據庫

onUpgrade()方法是用戶對數據庫進行升級的。但現在還是個空方法。它在整個數據庫的管理中起着非常重要的作用。

比如說我們現在要添加一張Category表用於記錄圖書的分類。

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 mContent;

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

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
        Toast.makeText(mContent, "Create succeeded", Toast.LENGTH_LONG).show();
    }

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

    }
}

重新運行, 你會發現,竟然沒有新建。。。。成功。。。

當然是因爲Bookstore.db數據庫已經存在了。。

解決方法也很簡單,只要把原先的數據庫刪掉就可以了。。只不過比較極端。

可以用onUpgrade()方法。

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

可以看到我們將可能存在的Book和Category表刪掉了,然後重新執行了一遍onCreate()方法。

接下來的問題既是如何讓onUpgrade()方法執行。

    @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_base);
        createDatabase.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                dbHelper.getWritableDatabase();
            }
        });
     }

只要將數據庫的版本號改成一個比原來大的數就可以了。

讓我們重新打開adb查看一下Bookstore.db數據庫,鍵入.table命令。接着鍵入.schema查看建表語句。


添加數據

數據操作主要有四種CRUD。其中C代表是Create(),其他依次:查詢Retrieve(),Update(),Delete().在android中即使不用SQL語句也能輕鬆完成所有的CRUD操作。

根據getReadableDatabase()返回的SQLiteDatabase對象,藉助這個對象就可以對數據進行CRUD操作。

修改activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.wrjjrw.broadcastbestpractise.MainActivity">

    <Button
        android:id="@+id/create_base"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Creat database"
        />

    <Button
        android:id="@+id/add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:text="Add data"
        android:textAllCaps="false"
        />

</LinearLayout>

在MainActivity添加:

        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();
                //first data
                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);//insert first data
                values.clear();
                values.put("name", "The Lost Symbol");
                values.put("author", "Dan Brown");
                values.put("pages", 510);
                db.insert("Book", null, values);//insert second data
            }
        });

第二個參數用於給未指定添加的數據的情況下給某些可爲空的列自動賦值,一般不用,直接null。

首先獲取了SQLiteDatabase對象,然後使用ContentValues來對要添加的數據進行組裝。我們只添加了期中的四組順序,還有一組因爲是primary key所以是自動生成的。

可以打開Bookstore.db數據庫select * from Book查看。


更新數據

可以使用update()方法

update

int update (String table, 
                ContentValues values, 
                String whereClause, 
                String[] whereArgs)

Convenience method for updating rows in the database.

Parameters
table String: the table to update in
values ContentValues: a map from column names to new column values. null is a valid value that will be translated to NULL.
whereClause String: the optional WHERE clause to apply when updating. Passing null will update all rows.
whereArgs String: You may include ?s in the where clause, which will be replaced by the values from whereArgs. The values will be bound as Strings.
Returns
int the number of rows affected

第三第四個參數用語更新某一行或某幾行的數據,不指定默認所有。

修改MainActivity:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.wrjjrw.broadcastbestpractise.MainActivity">

    <Button
        android:id="@+id/create_base"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Creat database"
        />

    <Button
        android:id="@+id/add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add data"
        android:textAllCaps="false"
        />

    <Button
        android:id="@+id/update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:text="Update data"
        android:textAllCaps="false"
        />

</LinearLayout>

修改MainActivity:

        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"});
            }
        });

我們指向把價格這一列的數據更新成10.99。然後調用了update()方法執行具體的更新操作,可以看到,第三第四個參數用來指定更新哪一行。?是佔位符,可以通過第四個參數提供的字符串數組爲第三個參數重點每一個佔位符指定相應的內容。所以只是將名字是“The Da Vinci Code”這本書的價格改成10.99。update後查看數據。


刪除數據

刪除當然用delete();

delete

int delete (String table, 
               String whereClause, 
               String[] whereArgs)

Convenience method for deleting rows in the database.

Parameters
table String: the table to delete from
whereClause String: the optional WHERE clause to apply when deleting. Passing null will delete all rows.
whereArgs String: You may include ?s in the where clause, which will be replaced by the values from whereArgs. The values will be bound as Strings.
Returns
int the number of rows affected if a whereClause is passed in, 0 otherwise. To remove all rows and get a count pass “1” as the whereClause.

給activity_main.xml增添

    <Button
        android:id="@+id/delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete data"
        android:textAllCaps="false"
        />

修改MainActivity:

        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", "page > ?", new String[]{"500"});
            }
        });

自己運行自己看看。


查詢數據

SQL全稱既然是Structured Query Language,翻譯成中文就是結構化查詢語言。而且查詢數據也是CRUD中最複雜的一種操作。query()方法可以查看數據。

query

Cursor query (String table, 
                String[] columns, 
                String selection, 
                String[] selectionArgs, 
                String groupBy, 
                String having, 
                String orderBy)

Query the given table, returning a Cursor over the result set.

Parameters
table String: The table name to compile the query against.
columns String: A list of which columns to return. Passing null will return all columns, which is discouraged to prevent reading data from storage that isn’t going to be used.
selection String: A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given table.
selectionArgs String: You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings.
groupBy String: A filter declaring how to group rows, formatted as an SQL GROUP BY clause (excluding the GROUP BY itself). Passing null will cause the rows to not be grouped.
having String: A filter declare which row groups to include in the cursor, if row grouping is being used, formatted as an SQL HAVING clause (excluding the HAVING itself). Passing null will cause all row groups to be included, and is required when row grouping is not being used.
orderBy String: How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered.
Returns
Cursor A Cursor object, which is positioned before the first entry. Note that Cursors are not synchronized, see the documentation for more details.

See also:

  • Cursor

來看看最短的也有7個參數。

    <Button
        android:id="@+id/query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:text="Query data"
        android:textAllCaps="false"
        />
Button queryButton = (Button) findViewById(R.id.query_data);
        queryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                //query all data
                Cursor cursor = db.query("Book", null, null, null, null, null,null);
                if(cursor.moveToFirst()){
                    do {
                        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(Tag, "book name is "+name);
                        Log.d(Tag, "book author is "+author);
                        Log.d(Tag, "book pages is "+pages);
                        Log.d(Tag, "book price is "+ price);
                        Toast.makeText(MainActivity.this, "book name is "+name+" and author is "+ author+
                            " and pages is " + pages + " price is "+ price, Toast.LENGTH_LONG
                        ).show();
                    }while (cursor.moveToNext());
                }
                cursor.close();
            }
        });

使用SQL操作數據

還可以用一下語句。

添加:

db.execSQL("insert into Book (name, author , pages, price) values(?,?,?,?)",new String[] {"The Da Vinci Code", "Dan Brown", "454", "16.96"});

更新

db.execSQL("update Book set price = ? where name = ?", new String[]{"10.99","The Da Vinci Code"})

刪除

db.execSQL("delete from Book where pages > ?", new String[]{"500"});

查詢

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

rawQuery

Cursor rawQuery (String sql, 
                String[] selectionArgs, 
                CancellationSignal cancellationSignal)

Runs the provided SQL and returns a Cursor over the result set.

Parameters
sql String: the SQL query. The SQL string must not be ; terminated
selectionArgs String: You may include ?s in where clause in the query, which will be replaced by the values from selectionArgs. The values will be bound as Strings.
cancellationSignal CancellationSignal: A signal to cancel the operation in progress, or null if none. If the operation is canceled, then OperationCanceledException will be thrown when the query is executed.
Returns
Cursor A Cursor object, which is positioned before the first entry. Note that Cursors are not synchronized, see the documentation for more details.

感覺這個蠻方便的,就貼出來了。


問題

  1. sqlite3 :not found

答:有些真機沒有sqlite…..(我的就沒有)Click me

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章