Android Weekly Notes Issue #224

Android Weekly Issue #224

September 25th, 2016
Android Weekly Issue #224

本期內容包括: Google Play的pre-launch報告; Wear的Complications API; Android Handler解析; RxAndroid; 測量性能的庫: Pury; 方法數(shù)限制; APK內容分析; Redux for Android; 一種view造成的泄露; 注解處理; 更好的Adapter; Intro屏等等.

ARTICLES & TUTORIALS

Apk的pre-launch報告 Awesome pre-launch reports for Alpha/Beta APK's

Google Play team在I/O 2016的時候宣布了很多新features, 其中有一個pre-launch report.

這個report是干什么的呢, 它會報告在一些設備上測試你的應用的時候發(fā)現(xiàn)的issues.

要生成這種報告, 你應該在Developer console上enable它. 然后上傳alpha/beta apk. 上傳到beta channel之后, 5-10分鐘就會生成報告.

報告主要包括三個部分:

  • Crashes
  • Screenshots
  • Security

官方文檔: pre-launch

Wear Complications API

在鐘表的定義里, complications是指表上除了小時和分鐘指示之外其他的東西.

在Android Wear里面我們已經有一些complications的例子, 比如向用戶顯示計步器, 天氣預報, 下一個會議時間等等.

但是之前有一個很大的限制就是每一個小應用都必須實現(xiàn)自己的邏輯來取數(shù)據, 比如有兩個應用都取了今天的天氣預報信息, 將會有兩套機制取同樣的數(shù)據, 這明顯是一種浪費.

Android Wear 2.0推出了Complications API解決了這個問題.

通信主要是Data providersWatch faces之間的, 前者包含取數(shù)據的邏輯, 后者負責顯示.

Complications API定義了一些Complications Types, 見官方文檔.

作者在他朋友的開源應用里用了新的API: Memento-Namedays, 這個應用是生日或者日期提醒類的.

首先, 作者用Wearable Data Layer API同步了手機和手表的數(shù)據. 然后在Wear module里繼承ComplicationProviderService創(chuàng)建了complication data provider, 這里就提供了onComplicationActivated, onComplicationDeactivated, onComplicationUpdate等回調.

用戶也可以點擊Complications, 可以用setTapAction()指定點擊后要啟動的Activity.

可以指定ComplicationProviderService的更新頻率, 是在manifest里用這個key:
android.support.wearable.complications.UPDATE_PERIOD_SECONDS.

更新得太頻繁會比較費電.
需要注意的是這并不是一個常量, 因為系統(tǒng)也會根據手機的狀況進行一些調節(jié), 不必要的時候就不需要頻繁更新.

本文作者采用的方式是用ProviderUpdateRequester. 在manifest里面設置0.

ComponentName providerComponentName = new ComponentName(
    context,
    MyComplicationProviderService.class
);
ProviderUpdateRequester providerUpdateRequester = new
    ProviderUpdateRequester(context, providerComponentName);
providerUpdateRequester.requestUpdateAll();

最后, 這里是官網文檔:
Complications.

這里是作者PR: PR

Android Handler Internals

首先, 作者舉了一個簡單的例子, 用兩種方法, 用Handler來實現(xiàn)下載圖片并顯示到ImageView上的過程.

主要是因為網絡請求需要在非UI線程, 而View操作需要在UI線程. Handler就用來在這兩種線程之間切換調度.

Handler的組成

  • Handler
  • Message
  • Message Queue
  • Looper

Handler

Handler是線程間消息傳遞的直接接口, 生產者和消費者線程都是通過調用下面的操作和Handler交互:

  • creating, inserting, removing Messages from Message Queue.
  • processing Messages on the consumer thread.

每一個Handler都是和一個Looper和一個Message Queue關聯(lián)的. 有兩種方法來創(chuàng)建一個Handler:

  • 用默認構造器, 將會使用當前線程的Looper.
  • 顯式地指明要用的Looper.

Handler不能沒有Looper, 如果構造時沒有指明Looper, 當前線程也沒有Looper, 那么將會拋出異常.

因為Handler需要Looper中的消息隊列.

一個線程上的多個Handler共享同一個消息隊列, 因為它們共享同一個Looper.

Message

Message是一個包含任意數(shù)據的容器, 它包含的數(shù)據信息是callback, data bundle和obj/arg1/arg2, 還有三個附加數(shù)據what, time和target.

可以調用Handler的obtainMessage()方法來創(chuàng)建Message, 這樣message是從message pool中取出的, target會自動設置成Handler自己. 所以直接可以在后面調用sendToTarget()方法.

Message pool是一個最大尺寸為50的LinkedList. 當消息被處理完之后, 會放回pool, 并且重置所有字段.

當我們使用Handler來post(Runnable)的時候, 實際上是隱式地創(chuàng)建一個Message, 它的callback存這個Runnable.

Message Queue

Message Queue 是一個無邊界的LinkedList, 元素是Message對象. 它按照時間順序來插入Message, 所以timestamp最小的最先分發(fā).

MessageQueue中有一個dispatch barrier表示當前時間, 當message的timestamp小于當前時間時, 被分發(fā)和處理.

Handler提供了一些方法在發(fā)message的時候設置不同的時間戳:

sendMessageDelayed(): 當前時間 + delay時間.

sendMessageAtFrontOfQueue(): 把時間戳設為0, 不建議使用.

sendMessageAtTime().

Handler經常需要和UI交互, 可能會引用Activity, 所以也經常會引起內存泄漏.
作者舉了兩個例子, 略.

需要注意:
非靜態(tài)內部類會持有外部類實例引用.
Message會持有Handler引用, 主線程的Looper和MessageQueue在程序運行期間是一直存在的.

建議的是, 內部類用static修飾, 另用WeakReference.

Debug Tips
顯示Looper中dispatched的Messages:

final Looper looper = getMainLooper();
looper.setMessageLogging(new LogPrinter(Log.DEBUG, "Looper"));

顯示MessageQueue中和handler相關的pending messages:

handler.dump(new LogPrinter(Log.DEBUG, "Handler"), "");

Looper

Looper 從消息隊列中讀取消息, 然后分發(fā)給target handler. 每當一個Message穿過了dispatch barrier, 它就可以在下一個消息循環(huán)中被Looper讀.

一個線程只能關聯(lián)一個Looper. 因為Looper類中有一個靜態(tài)的ThreadLocal對象保證了只有一個Looper和線程關聯(lián), 企圖再加一個就會拋出異常.

調用Looper.quit()會立即終止Looper, 丟棄所有消息.
Looper.quitSafely()會將已經通過dispatch barrier的消息處理了, 只丟棄pending的消息.

Looper是在Thread的run()方法里setup的, Looper.prepare()會檢查是否之前存在一個Looper和這個線程關聯(lián), 如果有則拋異常, 沒有則建立一個新的Looper對象, 創(chuàng)建一個新的MessageQueue. 見代碼.

現(xiàn)在Handler可以接收或者發(fā)送消息到MessageQueue了. 執(zhí)行Looper.loop()方法將會開始從隊列讀出消息. 每一個loop迭代都會取出下一個消息.

Crunching RxAndroid - Part 10 細細咀嚼RxAndroid

作者這個是個系列文章, 本文是part 10.

Android的listener很多, 我們可以通過RxJava把listener都變成發(fā)射信息的源, 然后我們subscribe.

本文舉例講了Observable.fromCallable()Observable.fromAsync()方法的用法.

Pury a new way to profile your Android application

在做任何優(yōu)化之前我們都應該先定位問題. 首先是收集性能數(shù)據, 如果收集到的信息超過了可以接受的閾值, 我們再進一步深究, 找到引起問題的方法或者API.

幸運的是, 有一些工具可以幫我們profiling:

  • Hugo@DebugLog注解來標記方法, 然后參數(shù), 返回值, 執(zhí)行時間都會log出來.
  • Android Studio toolset. 比如System Trace, 非常準確, 提供了很多信息, 但是需要你花時間來收集和分析數(shù)據.
  • 后臺解決方案, 比如JMeter, 它們提供了很多功能, 需要花時間來學習如何使用, 第二就是高并發(fā)profile也不是常見的需求.

Missing tool

關于我們關心的應用的速度問題, 大多數(shù)可以分為兩種:

  • 特定方法和API的執(zhí)行時間, 這個可以被Hugo cover.
  • 兩個事件之間的時間, 這可能是獨立的兩段代碼, 但是在邏輯上關聯(lián). Android Studio toolset可以cover這種, 但是你需要花很多時間來做profile.

作者意識到下面的需求沒有被滿足:

  • 開始和結束profiling應該是被兩個獨立的事件觸發(fā)的, 這樣才可以滿足我們靈活性的需求.
  • 如果我們想監(jiān)控performance, 僅僅開始和結束事件是不夠的. 有時候我們需要知道這之間發(fā)生了什么, 這些階段信息應該被放在一個報告里, 讓我們更容易明白和分享數(shù)據.
  • 有時候我們需要做重復操作, 比如loading RecyclerView的下一頁, 那么一個回合的操作顯然是不夠的, 我們需要進行多次操作, 然后顯示統(tǒng)計數(shù)據, 比如平均值, 最小最大值.

基于上面的需求, 作者創(chuàng)建了Pury.

Introduction to Pury

Pury是一個profiling的庫, 用于測量多個獨立事件之間的時間.
事件可以通過注解或者方法調用來觸發(fā), 一個scenario的所有事件被放在同一個報告里.

然后作者舉了兩個例子, 一個用來測量啟動時間, 另一個用來測量loading pages.

Inner structure and limitations

性能測量是Profilers做的, 每一個Profiler包含一個list, 里面是Runs. 多個Profilers可以并行運行, 但是每個Profiler中只有一個Run是active的.

Profiling with Pury

Pury可以測量多個獨立事件之間的時間, 事件可以用注解或者方法調用觸發(fā).
基本的注解有: @StartProfiling, @StopProfiling, @MethodProfiling

方法:

Pury.startProfiling();

Pury.stopProfiling();

最后作者介紹了一些使用細節(jié).
項目地址: Pury

處理方法數(shù)限制問題 Dealing With the 65K Methods limit on Android

作為Android開發(fā), 你可能會看到過這種信息:

Too many field references: 88974; max is 65536.
You may try using –multi-dex option.

首先, 為什么會存在65k的方法數(shù)限制呢?

Android應用是放在APK文件里的, 這里面包含了可執(zhí)行的二進制碼文件(DEX - Dalvik Executable), 里面包含了讓app工作的代碼.

DEX規(guī)范限制了單個的DEX文件中的方法總數(shù)最大為65535, 包括了Android framework方法, library方法, 還有你自己代碼中的方法. 如果超過了這個限制你將不得不配置你的app來生成多個DEX文件(multidex configuration).

但是開啟了multidex配置之后有一些隨機性的兼容問題, 所以我們在決定開啟multidex之前, 首先采取的第一步是減少方法數(shù)來避免這個問題.

在我們開始改動之前, 先提出了這些問題:

  • 我們有多少方法?
  • 這些方法都是從哪里來?
  • 主要的方法來源是誰?
  • 我們真的需要所有這些方法嗎?

在搜尋這些問題的答案的過程中, 我們發(fā)現(xiàn)了一些有用的工具和tips:

MethodsCount.com 將會告訴你一個庫有多少方法, 還提供了每個方法的依賴.

JakeWharton/dex-method-list utility 可以顯示.apk, .aar, .dex, .jar或.class文件中的所有方法引用. 這可以用來發(fā)現(xiàn)一個庫中到底有多少方法是被你的app使用了.

mihaip/dex-method-counts 這個工具可以按包來輸出方法, 計算出一個DEX文件中的方法數(shù)然后按包來分組輸出. 這有利于我們明白哪些庫是方法數(shù)的主要來源.

Gradle build system 提供了關于項目結構很有價值的信息. 一個有用的task是dependencies, 讓你看到庫的依賴樹, 這樣你就可以看到重復的依賴, 進而刪除它們來減少方法數(shù).

Classyshark 是一個Android可執(zhí)行文件的瀏覽器. 用這個工具你可以打開Android的可執(zhí)行文件(.jar, .class, .apk, .dex, .so, .aar, 和Android XML)來分析它的內容.

apk-method-count 這是一個工具, 用來快速地查apk中的方法數(shù), 拖拽apk之后就會得到結果.

What's in the APK APK中有什么

APK: Android application package 是Android系統(tǒng)的一種文件格式, 實際上是一種壓縮文件, 如果把.apk重命名為.zip, 就可以取出其內容.

但是此時我們直接在文本編輯器打開AndroidManifest.xml的時候看到的全是機器碼.

當然是有工具來幫我們分析這些東西的, 這個工具從一開始就有, 那就是aapt, 它是Android Build Tool的一部分.

aapt - Android Asset Packaging Tool 這個工具可以用來查看和增刪apk中的文件, 打包資源, 研究PNG文件等等.

它的位置在: <path_to_android_sdk>/build-tools/<build_tool_version_such_as_24.0.2>/aapt.

aapt能做的事情, 從man可以看出:

  • aapt list - Listing contents of a ZIP, JAR or APK file.
  • aapt dump - Dumping specific information from an APK file.
  • aapt package - Packaging Android resources.
  • aapt remove - Removing files from a ZIP, JAR or APK file.
  • aapt add - Adding files to a ZIP, JAR or APK file.
  • aapt crunch - Crunching PNG files.

用這個工具來分析我們的apk:

輸出基本信息:
aapt dump badging app-debug.apk

輸出聲明的權限:
aapt dump permissions app-debug.apk

輸出配置:
aapt dump configurations app-debug.apk

還有其他這些:

# Print the resource table from the APK.
aapt dump resources app-debug.apk

# Print the compiled xmls in the given assets.
aapt dump xmltree app-debug.apk

# Print the strings of the given compiled xml assets.
aapt dump xmlstrings app-debug.apk

# List contents of Zip-compatible archive.
aapt list -v -a  app-debug.apk

Reductor - Redux for Android

Redux是一個當前JavaScript中很火的構架模式. Reductor把它的概念借鑒到了Java和Android中.

關于狀態(tài)管理到底有什么好方法呢, 作者想到了前端開發(fā)中的SPA(Single-page application), 和Android應用很像, 有沒有什么可借鑒的呢? 答案是有.

Redux 是一個JavaScript應用的可預測的狀態(tài)容器, 可以用下面三個基本原則來描述:

  • 單一的真相來源
  • 狀態(tài)只讀
  • 變化是純函數(shù)造成的

Redux的靈感來源有FluxElm Architecture.
強烈建議閱讀一下它的文檔.

Reductor是作者用Java又實現(xiàn)了一次Redux.

作者用了一個Todo app的例子來說明如何使用, 以及它的好處.

作者先寫了一個naive的實現(xiàn), 然后不斷地舉出它的缺點, 然后改進它.

其中作者用到了pcollection來實現(xiàn)persistent/immutable的集合.

最后還把代碼改為對測試友好的.

Android leak pattern: subscriptions in views

開始作者舉了一個例子, 一個自定義View, subscribe了Authenticator單例的username變化事件, 從而更新UI.

public class HeaderView extends FrameLayout {
  private final Authenticator authenticator;

  public HeaderView(Context context, AttributeSet attrs) {...}

  @Override protected void onFinishInflate() {
    final TextView usernameView = (TextView) findViewById(R.id.username);
    authenticator.username().subscribe(new Action1<String>() {
      @Override public void call(String username) {
        usernameView.setText(username);
      }
    });
  }
}

但是代碼存在一個主要的問題: 我們從來沒有unsubscribe. 這樣匿名內部類對象就持有外部類對象, 整個view hierarchy就泄露了, 不能被GC.

為了解決這個問題, 在View的onDetachedFromWindow()回調里調用unsubscribe().

作者以為這樣解決了問題, 但是并沒有, 還是檢測出了泄露, 并且作者發(fā)現(xiàn)View的onAttachedToWindow()onDetachedFromWindow()都沒有被調用.

作者研究了onAttachedToWindow()的調用時機:

  • When a view is added to a parent view with a window, onAttachedToWindow() is called immediately, from addView().
  • When a view is added to a parent view with no window, onAttachedToWindow() will be called when that parent is attached to a window.

而作者的布局是在Activity的onCreate()里面setContentView()設置的.
這時候每一個View都收到了View.onFinishInflate()回調, 卻沒有調View.onAttachedToWindow().

View.onAttachedToWindow() is called on the first view traversal, sometime after Activity.onStart().

onStart()方法是不是每次都會調用呢? 不是的, 如果我們在onCreate()里面調用了finish(), onDestroy()會立即執(zhí)行, 而不經過其中的其他生命周期回調.

明白了這個原理之后, 作者的改進是把訂閱放在了View.onAttachedToWindow()里, 這樣就不會泄露了. 對稱總是好的.

Annotation Processing in Android Studio 注解和其處理器

作者用例子說明了如何自定義注解和其處理器, 讓被標記的類自動成為Parcelable的.
看了這個有助于理解各種依賴和了解相關的目錄結構.

建議使用: android-apt.

Parcelable.
相關庫代碼: aitorvs/auto-parcel.

Writing Better Adapters 寫出更好的Adapter

在Android應用中, 經常需要展示List, 那就需要一個Adapter來持有數(shù)據.

RecyclerView的基本操作是: 創(chuàng)建一個view, 然后這個ViewHolder顯示view數(shù)據; 把這個ViewHolder和adapter持有的數(shù)據綁定, 通常是一個model classes的list.

當數(shù)據類型只有一種時, 實現(xiàn)很簡單, 不容易出錯. 但是當要顯示的數(shù)據有很多種時, 就變得復雜起來.

首先你需要覆寫:

override fun getItemViewType(position: Int) : Int

默認是返回0, 實現(xiàn)以后把不同的type轉換為不同的整型值.

然后你需要覆寫:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder

為每一種type創(chuàng)建一個ViewHolder.

第三步是:

override fun onBindViewHolder(holder: ViewHolder, position: Int): Any

這里沒有type參數(shù).

The Uglyness
好像看起來沒有什么問題?
讓我們重新看getItemViewType()這個方法. 系統(tǒng)需要給每一個position都對應一個type, 所以你可能會寫出這樣的代碼:

if (things.get(position) is Duck) {
    return TYPE_DUCK
} else if (things.get(position) is Mouse) {
    return TYPE_MOUSE
}

這很丑不是嗎?

如果你的ViewHolder沒有一個共同的基類, 在binding的時候也是這么丑:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val thing = things.get(position)
    if (thing is Animal) {
        (holder as AnimalViewHolder).bind(thing as Animal)
    } else if (thing is Car) {
        (holder as CarViewHolder).bind(thing as Car)
    }
...
}

很多的instance-of和強制類型轉換, 它們都是code smells. 違反了很多軟件設計的原則, 并且當我們想要新添一種類型時, 需要改動很多方法. 我們的目標是添加新類型的時候不用更改Adapter之前的代碼.
開閉原則: Open for Extension, Closed for Modification.

Let's Fix It
用一個map來查詢? 不好.
把type放在model里? 不好.

解決問題的一種辦法是: 加入ViewModel, 作為中間層.

但是如果你不想創(chuàng)建很多的ViewModel類, 還有其他的辦法: Visitor模式

interface Visitable {
    fun type(typeFactory: TypeFactory) : Int
}

interface Animal : Visitable
interface Car : Visitable

class Mouse: Animal {
    override fun type(typeFactory: TypeFactory)
        = typeFactory.type(this)
}

工廠:

interface TypeFactory {
    fun type(duck: Duck): Int
    fun type(mouse: Mouse): Int
    fun type(dog: Dog): Int
    fun type(car: Car): Int
}

返回對應的id:

class TypeFactoryForList : TypeFactory {
    override fun type(duck: Duck) = R.layout.duck
    override fun type(mouse: Mouse) = R.layout.mouse
    override fun type(dog: Dog) = R.layout.dog
    override fun type(car: Car) = R.layout.car

Material Intro Screen for Android Apps

現(xiàn)在有兩個主流的libraries為Android 應用提供了好看的intro screens, 但是感覺并不是很好用, 所以作者他們發(fā)布了一個新的歡迎界面的庫TangoAgency/material-intro-screen
, 好用易擴展.

Testing Legacy Code: Hidden Dependencies

本文討論God Object, Blob, 這種很大的類和方法, 做了很多事情. 如果你想要重構, 先加點測試, 也發(fā)現(xiàn)很難, 因為它的依賴太多了, 做了太多事情.

首先, 實例化:
加set方法, 讓數(shù)據庫依賴抽離出來, 這樣測試的時候可以傳一個Fake的進去.

第二, 更多依賴:
把UserManger和網絡請求等依賴也抽為成員變量, 加上set方法或者構造參數(shù), 這樣在測試的時候易于把mock的東西傳進去.

第三, 清理: 要牢記單一職能原則, 進行職能拆分.

最后, 現(xiàn)實: 清理是一個持續(xù)化的過程, 得一步一步來, 有時候小步的改動會幫助你發(fā)現(xiàn)另外需要改動的地方.

LIBRARIES & CODE

EncryptedPreferences

AES-256加密的SharedPreferences.

Pury

報告多個不同事件之間的時間, 可用于性能測量.

Floating-Navigation-View

Floating Action Button, 展開后是一個NavigationView.

Material Intro Screen

易用易擴展的歡迎界面.

SPECIALS

Huge list of useful resources for Android development

資源分享, 包括博客論壇Video社區(qū)等等.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叽赊,隨后出現(xiàn)的幾起案子并徘,更是在濱河造成了極大的恐慌调鲸,老刑警劉巖岗屏,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哪自,死亡現(xiàn)場離奇詭異灾测,居然都是意外死亡爆价,警方通過查閱死者的電腦和手機垦巴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铭段,“玉大人骤宣,你說我怎么就攤上這事⌒蛴蓿” “怎么了憔披?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長爸吮。 經常有香客問我芬膝,道長,這世上最難降的妖魔是什么形娇? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任锰霜,我火速辦了婚禮,結果婚禮上桐早,老公的妹妹穿的比我還像新娘癣缅。我一直安慰自己,他們只是感情好哄酝,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布友存。 她就那樣靜靜地躺著,像睡著了一般陶衅。 火紅的嫁衣襯著肌膚如雪屡立。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天搀军,我揣著相機與錄音膨俐,去河邊找鬼。 笑死奕巍,一個胖子當著我的面吹牛吟策,可吹牛的內容都是我干的。 我是一名探鬼主播的止,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼檩坚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了诅福?” 一聲冷哼從身側響起匾委,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎氓润,沒想到半個月后赂乐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡咖气,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年挨措,在試婚紗的時候發(fā)現(xiàn)自己被綠了挖滤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡浅役,死狀恐怖斩松,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情觉既,我是刑警寧澤惧盹,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站瞪讼,受9級特大地震影響钧椰,放射性物質發(fā)生泄漏。R本人自食惡果不足惜符欠,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一嫡霞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧希柿,春花似錦秒际、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闽颇。三九已至盾戴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兵多,已是汗流浹背尖啡。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剩膘,地道東北人衅斩。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像怠褐,于是被迫代替她去往敵國和親畏梆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內容