您的位置:首页 > 移动开发 > Android开发

Android学习随笔(12)------持久化技术

2017-12-12 16:31 489 查看
学习流程来自《第一行代码》(第二版)

在内存中的数据有可能会因为程序关闭或者其他原因导致内存被回收而丢失数据。数据持久化就是指将那些内存中的瞬时数据保存到存储设备中。

文件存储

save

在布局文件中添加一个EditTest用于输入文本内容

<?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中输入文本,当活动被销毁时我们保存文本数据。

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


Content类提供了openFileOutput()方法,将数据存放到/data/data/packagename/files/目录下

在EditText中输入This is a text.



点击Back键,关闭程序。(直接点击多任务菜单中的Clear All无法调用到onDestroy()方法),因为使用Tools下的Android Device Monitor有权限的限制(如果打开的那次手机系统确认你的身份不是管理员那么需要无限次重启。。。。),所以在这边使用SDK/platform-tools下的adb shell :



更改整个/data文件夹中所有文件的权限为777(可读可写可执行)

再cd /data/data/文件所在的包,ls可以看到有 cache files两个文件(在没有保存任何数据的时候只有cache一个文件),cd files,ls就可以看见我们创建的data了,利用cat data即可以查看data中的详细信息。



可以看到我们在EditText中的文本信息被保存了。

Load

Content类还提供了一个openFileInput()方法,用来读取我们保存的数据。

在界面上添加一个button,用来销毁当前的Activity :

<?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"
/>

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="destroy"/>
</LinearLayout>


在onCreate()方法中添加load()方法 :

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)) {    // 当传入的字符串==null 或者为空字符串时 true
edit.setText(inputText);
edit.setSelection(inputText.length());
Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show();
}

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onDestroy();
}
});
}

@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();
}
18558
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.txt");
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();
}
}


再次重新启动应用,内容就会被填充到EditText中了。

SharedPreferences存储

SharedPreferences是使用键值对的方式来存储数据的。

SharedPreferences文件是放在/data/data/package name/shared_prefs/目录下的。

Android中提供了3种方法来获取SharedPreferences对象

Content类的getSharedPreferences()方法,第一个参数用于指定SharedPreferences文件的名称,第二个参数用于指定操作模式,MODE_PRIVATE表示只有当前应用程序才能使用。

Activity类中的getPreferences()方法,只接收一个操作模式参数,自动将当前活动的类名做为SharedPreferences的文件名。

PreferenceManager类中的getDefaultSharedPreferences()方法,静态方法,接收Context参数,自动使用当前应用程序的包名作为前缀。

三步实现save()方法 :

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

向SharedPreferences.Editor对象中添加数据。

调用apply()方法提交

主界面布局文件 :

<?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:id="@+id/save_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="save data"
/>

<Button
android:id="@+id/restore_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="restore data"
/>
</LinearLayout>


创建两个按钮用于保存和恢复。

package com.yezhou.example.com.sharedpreferencestest;

import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
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("SharedPreferences_data",MODE_PRIVATE).edit();
editor.putString("name","yezhou");    // 储存String类型数据
editor.putInt("age",20);    // int
editor.putBoolean("married",false);    // boolean
editor.apply();
}
});
Button restoreData = (Button) findViewById(R.id.restore_data);
restoreData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SharedPreferences pref = getSharedPreferences("SharedPreferences_data",MODE_PRIVATE);
String name = pref.getString("name", "");    // 若没有找到值则用这里设置的默认值代替
int age = pref.getInt("age", 0);
boolean married = pref.getBoolean("married",false);
Log.d("admin", "name is " + name);
Log.d("admin", "age is " + age);
Log.d("admin", "married is " + married);
}
});
}
}


取出数据与保存数据的操作相对应,得到名为“SharedPreferences_data”的对象,利用get数据类型(“key”, “defaultValue”)来获取数据。

点击Save data,利用adb shell来查看保持的情况 :



可以看到生成的是一个XML文件。

再点击Restore data,在logcat中可以查看取出的log信息



实现记住密码功能

在广播章节最后的练习(登录页面)的基础上,利用SharedPreferences实现记住密码功能。

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="60dp">
<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="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Login"/>

</LinearLayout>


添加一个checkBox,判断用户是否需要记住密码。

LoginActivity.java :

public class LoginActivity extends BaseActivity {    // 登录界面代码

private EditText accountEdit;
private EditText passwordEdit;
private Button login;

private CheckBox rememberPass;
private SharedPreferences pref;
private SharedPreferences.Editor editor;

@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.password);
login = (Button) findViewById(R.id.login);

rememberPass = (CheckBox) findViewById(R.id.remember_pass);
pref = PreferenceManager.getDefaultSharedPreferences(this);
boolean isRemember = pref.getBoolean("remember_password", false);    // 找不到值默认用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();    // account:admin password: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();    // 登录成功此Activity无用销毁
} else {
Toast.makeText(LoginActivity.this, "account or password is invalid", Toast.LENGTH_SHORT).show();
}
}
});
}
}


添加了一个标识符isRemember,从SharedPreferences中读取是否选择了记住密码,默认是没有。如果选择了记住密码就在Activity onCreate的时候把数据(账号、密码)进行填充。

在点击登录的时候进行判断,如果选择记住密码就把账号密码存储进SharedPreferences中。





数据库

Android内置了一个轻量级的数据库SQLite。SQLiteOpenHelper是一个抽象类,需要重写onCreate()方法和onUpgrade()方法。

这个类中还有两个重要的实例方法 :getReadableDatabase()方法和getWritableDatabase()方法 ,用于打开或创建数据库,并返回一个可对数据库进行读写操作的对象。但当数据库不可写入的时候(磁盘空间已满)

1. getReadableDatabase()方法 :返回的对象将以只读的方式去打开数据库

2. getWritableDatabase()方法 :出现异常

构造函数一般使用参数少的那个(Context context, String databaseName, Cursor cursor, id version)。

数据库文件一般被存放在/data/data/packageName/databases目录下。

建表

MyDatabaseHelper.java

public class MyDatabaseHelper extends SQLiteOpenHelper {

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

private Context mContext;

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

@Override
public void onCreate(SQLiteDatabase db) {    // 创建数据库
db.execSQL(CREATE_BOOK);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}

@Override
public void onUpgrade(SOLiteDatabase db, int oldVersion, int new Version) {
}
}


在onCreate()方法中将会执行创建表的语句。

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 v) {
dbHelper.getWritableDatabase();
}
});
}


点击按钮来创建表





可以在adb shell中用sqlite3 数据库名 进入数据库,.table 查看数据库中的表,.schema查看建表语句。

更新数据库

想要更新数据库有两种方法,一种是直接把应用删除,然后再安装。另外一种就是不需要卸载,利用onUpgrade()方法来更新。

public class MyDatabaseHelper extends SQLiteOpenHelper {

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

private Context mContext;

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

@Override
public void onCreate(SQLiteDatabase db) {    // 创建数据库
db.execSQL(CREATE_BOOK);    // 执行创建表
db.execSQL(CREATE_CATEGORY);    // 创建表(需要先删除现有的表)
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
Log.d("admin", mContext.toString());

}

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


想要更新数据库中的表就先在onUpgrade()中删除原表,再调用onCreate()方法创建。

dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);


在调用时要将int version++或者只要大于原来输入的版本就好。



可以看到数据库中的表更新了。

向表中添加数据

Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values);
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);
}
});


利用insert()函数来进行数据的插入。

第一个参数是 表名

第二个参数是 用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL。

第三个参数 ContentValues对象,用于封装要插入的数据。

更新数据

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",10.99);
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code"});
}
});


第一个参数 表名

第二个参数 指定要修改的内容

第三个参数 相当于where

第四个参数 为第三个参数中的每个占位符指定相应的内容。



可以看到达芬奇密码的价格被更改了。

删除数据

Button deleteData = (Button) findViewById(R.id.delete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {    // 数据库删除数据操作
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
}
});




可以看到有一行数据被删除了。

查询数据

Button queryButton = (Button) findViewById(R.id.query_data);
queryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {    // 数据库表查询数据操作
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {    // 遍历Cursor对象,去除数据并打印
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("admin", "book namne is " + name);
Log.d("admin", "book author is " + author);
Log.d("admin", "book pages is " + pages);
Log.d("admin", "book price is " + price);
Toast.makeText(MainActivity.this, name + author + pages + price +"   ", Toast.LENGTH_LONG).show();
} while (cursor.moveToNext());
}
cursor.close();
}
});


query(表名, 查询的列名, where的约束条件, 为where中的占位符提供具体的值, 指定需要group by的列, 对group by后的结果进一步约束, 查询结果的排序方式);

利用cursor来查询数据库



也可以直接用sql语句对数据库进行操作。

rawQuery(String querySQL)是查询的方法,增删改都用execSQL(String sql)方法。

LitePal开源库

配置LitePal

在build.gradle中添加

compile 'org.litepal.android:core:1.6.0'


在app/src/main目录下新建一个assets目录,用于存放litepal.xml文件

<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="BookStore" />
<version value="1" />    <!--更新数据库版本 不需要先删除数据原库 value+1-->
<list>
</list>
</litepal>


修改AndroidManifest.xml

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

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


LitePal采取的是对象关系映射(ORM)模式(面向对象语言与面向关系的数据库之间的一中映射)

创建一个book类:

public class Book extends DataSupport{    // Alt+Ins键 Getter and setter shift键全选
private int id;
private String author;
private double price;
private int pages;
private String name;
private String press;

public String getPress() {
return press;
}

public void setPress(String press) {
this.press = press;
}

public int getPages() {
return pages;
}

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

public String getName() {
return name;
}

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

public String getAuthor() {
return author;
}

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

public double getPrice() {
return price;
}

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

public int getId() {
return id;
}

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


在litepal.xml中添加映射

<list>
<mapping class="com.yezhou.example.com.litepaltest.Book"></mapping>
<mapping class="com.yezhou.example.com.litepaltest.Category"></mapping>
</list>


MainActivity.java :

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LitePal.initialize(this);

Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Connector.getDatabase();
}
});

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

Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {    // 更新
Book book2 = new Book();
book2.setName("The Lost Symbol");
book2.setAuthor("Dan Brown");
book2.setPages(510);
book2.setPrice(19.95);
book2.setPress("Unknow");
book2.save();
book2.setPrice(10.99);
book2.save();    // Book extends DataSupport
}
});

Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {    // 删除
DataSupport.deleteAll(Book.class, "id = ?", "2");
}
});

Button queryButton = (Button) findViewById(R.id.query_data);
queryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {    // 查询
Log.d("admin","Click QueryButton");
List<Book> books = DataSupport.findAll(Book.class);    //findFirst findLast
if (books == null) {
Log.d("admin","books empty");
}
for (Book book: books) {
Log.d("admin", "book name is " + book.getName());
Log.d("admin", "book author is "+ book.getAuthor());
Log.d("admin", "book pages is " + book.getPages());
Log.d("admin", "book price is "+ book.getPrice());
Log.d("admin", "book press is "+ book.getPress());
}
}
});
}
}


新添加一个Category类

public class Category {

private int id;
private String categoryName;
private int categoryCode;

public int getId() {
return id;
}

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

public String getCategoryName() {
return categoryName;
}

public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}

public int getCategoryCode() {
return categoryCode;
}

public void setCategoryCode(int categoryCode) {
this.categoryCode = categoryCode;
}
}


需要修改litepal.xml

<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="BookStore" />
<version value="2" />    <!--更新数据库版本 不需要先删除数据原库 value+1-->
<list>
<mapping class="com.yezhou.example.com.litepaltest.Book"></mapping>
<mapping class="com.yezhou.example.com.litepaltest.Category"></mapping>
</list>
</litepal>


创建 :



添加 :



删除 :



版本更新 :



此博文为个人学习笔记,仅供个人学习使用,希望对大家有帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android