Android第一行代码读书笔记 - 第六章

====================================

====== 第六章:数据存储全方案 — 详解持久化技术 ======

====================================

持久化技术就是将那些内存中的瞬时数据保存到存储设备中。

Android中主要通过3种用于简单实现数据持久化的功能:1、文件储存。2、SharedPreference存储。3、数据库存储。

除此之外,还可以将数据保存在手机的SD卡中。

6.2、文件存储

Context类提供了一个openFileOutput()方法,可以用于将数据储存在指定的文件中。方法接收两个参数,第一个是文件名,不包含路径,因为所有的文件都是默认存储在/data/data/<packagename>/files/目录下的。第二个参数是文件的操作模式,主要由两种模式可选:MODE_PRIVATE和MODE_APPEND。其中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.printStatckTrace();

} finally {

try {

if (writer != null) {

writer.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

代码解释:

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

现在创建一个FilePersistenceTest项目。

1、修改activity_main.xml文件:

<LinearLayout xmlns:android=“http://schemas.android.com/apl/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>

只有一个输入框,如果输入了内容点击了返回,里面内容就消失了。

2、修改MainActivity的代码

public class MainActivity extends AppCompatActivity {

private EditText editText;

@Override

protected void onCreate(Bundle saveInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

editText = (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;

BufferWriter writer = null;

try {

out = openFileOutput(“data”, Context.MODE_PRIVATE);

writer = new BufferWriter(new OutputStreamWriter(out));

writer.write(inputText);

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

if (writer != null) {

writer.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

重写了onDeatroy()方法,可以保证在活动销毁之前保存数据。

那么,我们怎么知道我们保存的数据是否已经成功了呢。使用Android Device Monitor工具(在AndroidStudio导航栏的Tools —> Android中可以看到工具列表)

Device File Exployor进行查询,输入/data/data/com.example.filepersistencetest/files/目录,可以看到生成了一个data文件。

我们打开这个文件发现数据已经保存成功。

现在我们需要从文件中读取数据。

6.2.2 从文件中读取数据:

Context类中还提供了一个openFileInput()方法,用于从文件中读取数据,这个方法只接受一个参数,即要读取的文件的文件名。然后系统会自动到/data/data/<package name>/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对象,接着再根据它构建一个BufferedReader对象,这样我们就可以通过BufferedReader进行一行还地读取,把文件中所有的文本内容全部取出来,并存放在一个StringBuilder对象中。

3、完善代码,取数据。修改MainActivity的代码:

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(input.length()); // 将光标定位到文本的末尾以便继续输入

Toast.makeText(this, “Restoreing succeeded”, Toase.LENGTH_SHORT).show();

}

public String load() {

FileInputStream in = null;

BufferReader reader = null;

StringBuilder content = new StringBuilder();

try {

in = openFileInput(“data”);

reader = new BufferedReader(new InputStreamReader(in));

。。。跟上面的代码保持一样。

上述代码中用到了TextUtils.isEmpty()方法,这是一个非常好用的方法,可以一次性进行两种空值的判断。当传入的字符串等于null或者等于空字符串的时候,方法都会返回true。

文件存储方面的知识核心技术就是Context类中提供的openFileInput()和openFileOutput()方法。

6.3 SharePreferences存储

SharePreferences是使用键值对方式来存储数据的。跟iOS中类似。如果存储的数据类型是正行,那么读取出来的数据也是正行的。如果存储的数据是一个字符串,那么读取出来的数据也是字符串。

6.3.1 将数据存储到SharePreferences中

先要获得SharePreferences对象。Android主要提供了三种方法用于得到SharePreferences对象:

1、Context类中的getSharePreferences()方法。

2、Activity类中的getPerferences()方法;

3、PreferenceManager类中的getDefaultSharePreferences()方法。

1、此方法接收两个参数,第一个用于指定SharePreferences文件的名称,如果文件不存在则创建一个。SharePreferences文件是存放在/data/data/<package name>/shared_prefs/目录下的。第二个参数用于指定操作模式(目前只有MODE_PRIVATE这一只模式可选),直接传0也可。

2、此方法与方法1类似。不过他只接受一个操作模式的参数。因为使用这个方法会自动将当前活动的类名作为SharePreferences的文件名。

3、第三个方法这是一个静态方法。它接收一个Context的参数,并自动使用当前应用程序的包名作为前缀来命名SharePreferences文件。

当得到了SharePreferences对象之后,就可以向文件中存储数据了,存储主要分为3个步骤:

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

2、向SharePreferences.Editor对象中添加数据,比如添加一个布尔类型的数据就可以使用putBoolean()方法,添加一个字符串数据就可以使用putString()方法,以此类推。

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

现在来实践:创建一个SharePreferencesTest项目。

1、然后修改activity_main.xml文件

<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/save_data”

androdi:layout_width=“match_pareng”

android:layout_height=“wrap_contetn”

android:text=“Save data” />

</LinearLayout>

2、修改MainActivity中的代码

public class MainActivity extends AppCompatActity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstaneState);

setContentView(R.layout.activity_main);

Button saveData = (Button) findViewById(R.id.save_data);

saveData.setOnClickLisener(new View.OnClickListener() {

@Override

public void onClick(View view) {

SharePreferences.Editor editor = getSharePreferences(“data”, MODE_PRIVATE).edit();

editor.putString(“name”, “Tom”);

editor.putInt(“age”, 28);

editor.putBool(“married”, false);

editor.apply();

}

});

}

}

可以看到,SharePreferences文件是使用XML格式来对数据进行管理的。

6.3.2 从SharePreferences中读取数据。

读取数据非常简单。SharePreferences对象中有一系列的get方法,可以用于数据的读取。每一种get方法都对应了SharedPreferences.Editor中的一种put方法。比如读取布尔类型就用getBoolean方法,

现在创建一个新的SharedPreferencesTest项目:

1、activity_main.xml代码:

<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/save_data”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

androd:text=“Save data” />

<Button

android:id=“@+id/restore_data”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Restore data” />

</LinearLayout>

上面增加了一个还原数据的按钮,用于从SharedPreferences文件中读取数据。

public class MainActivity extends AppCompatActivity {

@Overrdie

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

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

}

});

}

}

SharedPreferences pref = getSharedPreferences(“data”, MODE_PRIVATE);

通过文件名,获取模式获取到SharedPreferences对象,然后通过getString(),getInt(),getBoolean()等传入对应的key来获取对应的value。

很多应用程序里面的偏好设置都是使用到了SharedPreferences技术。

6.3.3 实现记住密码功能。

public class LoginActivity extends BaseActivity {

private SharePrefereneces pref;

private ShaerPrefereneces.Editor editor;

private EditText accountEdit;

private EditText passwordEdit;

private Button login;

private CheckBox remenberPass;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.id.activity_login);

pref = PreferenceManager.getDefaultSharePreferences(this);

accountEdit = (EditText) findViewById(R.id.account);

passwordEdit = (EditText) findViewById(R.id.password);

rememberPass = (CheckBox) findViewById(R.id.remenber_pass);

login = (Button) findViewById(R.id.login);

boolean isRemenber = pref.getBoolean(“remenber_password”, false);

if (isRemenber) {

// 将账号和密码都设置到文本框中

String account = pref.getString(“account”, “”);

String password = pref.getString(“password”, “”);

accountEdit.setText(account);

passwordEdit.setText(password);

remenberPass.setChecked(true);

}

login.setOnClickLinstener(new View.OnClickListener() {

@Override

public void onClick(View view) {

String accont = accountEdit.getText().toString();

String password = passwordEdit.getText().toString();

// 如果账号是admin和密码是123456.就认为登录成功

if (account.equals(“admin”) && password.eqauls(“123456”)) {

editor = pref.edit();

if (remenberPass.isChceked()) {

editor.putBoolean(“remember_password”, true);

editor.putString(“accont”, account);

editor.putString(“password”, password);

} else {

editor.clear(); // 清空数据

}

editor.apply(); // 同步一下

Intent intent = new Intent(LoginActivity.this, MainActivity.class);

startActivity(intent)’;

finish();

} else {

Toask.make(LoginActivy.this, “account or password is invalid”, Toast.LENGTH_SHOT).show();

}

}

});

}

}

直接存储明文的用户名密码是不安全的,实际项目中需要结合一定的加密算法来进行密码保护。

6.4 SQLite数据库存储

Android系统是内置了数据库的。

6.4.1 创建数据库

Android为了我们更好的管理数据库,专门提供了一个SQLiteOpenHelper的帮助类(抽象类),有两个抽象方法:onCreate()和onUpgrade()方法。必须重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()和 getWritableDatabase()。当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()返回的对象将以只读的方式打开数据库。getWritableDatabase()将会报错

SQLiteOpenHelper有两个构造方法。构造方法有四个参数。构建完实例之后再调用get方法就能创建数据库了,然后重写的onCreate()方法就能得到执行,在onCreate()方法里面处理创建表的一些逻辑。

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

新建一个DataBaseTest项目。

1、创建一个Book表,建表语句如下所示:

create table Book (

id integer primary key autoincrement,

author text,

price real,

pages integer,

name text)

integer表示整形,real表示浮点型,text表示文本类型,blob表示二进制类型。primary key将id类设置为主键,autoinrement关键字表示id列是自增长的。

2、用代码去执行这条SQL语句,才能完成创建表的操作。新建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 mContext;

public MyDatabaseHelper (Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {

super(context, name, factory, version);

mContext = context;

}

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_BOOK);

Toast.makeText(mContext, “Create succeeded”, Toast.LENGTH_SHORT).show();

}

@Override

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

}

}

使用execSQL()方法创建表

3、修改activity_main.xml文件

<LinearLayout xmlns:android=“http://schemas.andrdoid.com/apk/res/android

android:orientation=“vertical”

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<Button

android:id=“@+id/create_database”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Create database” />

</LinearLayout>

布局文件很简单,只是加入了一个按钮

4、修改MainActivity代码:

public class MainActivity extends AppCompatActivity {

private MyDatabaseHelper dbHelper;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.id.activity_main);

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

Button createDatabase = (Button) findViewById(R.id.create_database);

createDatabase.setOnClickLisener(new View.OnClickListener() {

@Override

public void onClick(View view) {

// 第一次点击的时候创建数据库,再次点击不会重复创建

dbHelper.getWriteableDatabase();

}

});

}

}

如果这时候我们再新增一个新表Category表,我们修改MyDatabaseHelper的内容

public class MyDatabaseHelper extends SQLiteOpenHelper {

public static final String CREATE_CATEGORY = “create table Categoty (”

  • “id integer primary key autoincrement,”

  • “categoty_name text,”

  • “categoty_code integer)”;

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_BOOK);

db.execSQL(CREATE_CATRGORY);

Toast.makeText(mContext, “Create succeeded”, Toast.LENGTH_SHORT).show();

}

@Override

public void onUpdate(SQLiteDatabase db, int oldVersion, int newVersion) {

db.execSQL(“drop table if exists Book”);

db.execSQL(“drop table if exists”

}

}

关键点:1、修改onUpdate方法,如果表存在则移除掉。2、修改MyDatabaseHelper的创建,修改version为2,只要传入的数字大于1,就可以执行onUpdata方法了。

6.4.3 添加数据:C

我们需要学习CRUD的操作。每一个操作都会代表一个SQL命令。

C:Create添加:insert操作

R:Retrieve查询:select操作

U:Update更新:update操作

D:Delete删除:delete操作

Android中无需去编写SQL语句,也能轻松完成所有的CRUD操作。

前面我们已经知道,调用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法是可以创建和升级数据库的。不仅如此,还会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了。

1、SQLiteDatabase的insert()方法,接收三个参数,1是表名 2是用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到,直接传null,3是ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。

添加一个按钮:

Button addData = (Button) findViewById(R.id.add_data);

addData.setOnClickListener(new View.OnClickListener() {

@Override

public void onCliK(View view) {

SQLiteDatabase db = dbHelper.getWriteabelDatabase();

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

}

});

我们只对Book表的其他四条数据进行赋值,没有对id进行操作,因为前面我们已经设置了id为自增长的了。

6.4.4 更新数据:U

SQLiteDatabase也提供了update()方法进行数据的更新,这个方法接收4个参数:1表名。2ContentValues对象,把需要更新的数据组装进去。3和4用于约束更新某一行或者某几行的数据,不指定的话默认就是更新所有行。

添加一个Button,

Button updateData = (Button) findViewById(R.id.updata_date);

updateData.setOnClickLisener(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”});

}

});

第三个参数是对应的SQL语句的where语部分。即name = ?的行

第四个参数是提供的一个字符串数组为第三个参数中的每一个占位符指定相应的内容。

6.4.5 删除数据:

删除数据调用SQLiteDatabase的delete()方法。这个方法接收三个参数:1、表名。2和3用于约束删除某一行或某几行的数据。不指定的话就是删除所有行。

继续添加一个Button,

Button deleteButton = (Button) findViewById(R.id.delete_data);

deleteButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

SQLiteDatabase db = dbHelper.getWritableDatabase();

db.delete(“Book”, “pages > ?”, “new String[] {“500”}” );

}

});

6.4.6 查询数据

查询数据调用SQLiteDatabase的query()方法进行查询。这个方法的参数非常复杂,最短的一个方法也需要重载7个参数。

参数1 table、表名

参数2 columns、指定去查询那几行

参数3 selection(where字段) 和4selectionArgs、用于约束查询某一行或某几行的数据,不指定则默认查询所有行

参数5groupBy、用于指定需要去group by的列,如果不指定,则表示不对查询数据进行group by操作。

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

参数7 orderBy、指定查询结果的排序方式,不指定则表示使用默认的排序方式。

调用query()方法会返回一个Cursor对象。查询到的数据都是从这个对象中取出。

继续添加一个Button

Button query_data = (Button) findViewById(r.id.query_data);

query_data.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view){

SQLiteDatabaes db = dbHelper.getWritableDatabase();

// 查询Book表中的所有数据

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”);

String pages = cursor.getString(cursor.getColumnIndex(“price”);

double price = cursor.getDouble(corsor.getColumnIndex(“price”);

} while (cursor.moveToNext()) ;

}

cursor.close();

}

});

6.4.7 使用SQL语句直接操作数据库:

1、添加数据:

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

2、更新数据:

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

3、删除数据:

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

4、查询数据

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

6.5 使用LitePal操作数据库

1、需要编辑app/build.gradle文件,在dependencies中添加如下内容

dependencies {

compile ‘org.litepal.android:core:1.3.2’

}

添加这行声明,前面部分是固定的,最后的1.3.2是版本号的意思,最新的版本号可以去LitePal的项目主页上查看

接下来需要配置litepal.xml文件。

2、右键app/src/main目录 —> New —> Directory创建一个assets目录,然后在assets目录下再建一个litepal.xml文件,接着编辑litepal.xml文件中的内容。

<?xml version=“1.0” encoding=“utf-8” ?>

<litepal>

<dbname value=“BookStore” > </dbname>

<version value=“1” ></version>

<list>

</list>

</litepal>

3、修改AndroidMainifest.xml中的代码

<mainifest xmlns:androdi=“http://schemas.android.com/apl/res/android

package=“com.example.litepaltest”>

<application

android:name=“org.litepal.LitePalApplication”

android:allow

。。。

算了,数据库的之后再继续看了

。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容