程序員自我修養之解析WindowManagerService

一、WMS的作用

窗口管理

WMS是窗口的管理者,它負責窗口的啟動、添加和刪除,另外窗口的大小和層級也是由WMS進行管理的。窗口管理的核心成員有DisplayContent、WindowToken和WindowState。

窗口動畫

窗口間進行切換時,使用窗口動畫可以顯得更炫一些,窗口動畫由WMS的動畫子系統來負責,動畫子系統的管理者為WindowAnimator。

輸入系統的中轉站

通過對窗口的觸摸從而產生觸摸事件,InputManagerService(IMS)會對觸摸事件進行處理,它會尋找一個最合適的窗口來處理觸摸反饋信息,WMS是窗口的管理者,因此,WMS“理所應當”的成為了輸入系統的中轉站。

Surface管理

窗口并不具備有繪制的功能,因此每個窗口都需要有一塊Surface來供自己繪制。為每個窗口分配Surface是由WMS來完成的。

二、WMS的核心成員介紹

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

final WindowManagerPolicy mPolicy;
final IActivityManager mActivityManager;
final ActivityManagerInternal mAmInternal;
final AppOpsManager mAppOps;
final DisplaySettings mDisplaySettings;
...
final ArraySet<Session> mSessions = new ArraySet<>();
final WindowHashMap mWindowMap = new WindowHashMap();
final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
final ArrayList<AppWindowToken> mFinishedEarlyAnim = new ArrayList<>();
final ArrayList<AppWindowToken> mWindowReplacementTimeouts = new ArrayList<>();
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
WindowState[] mPendingRemoveTmp = new WindowState[20];
final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
final H mH = new H();
final WindowAnimator mAnimator;
 final InputManagerService mInputManager
  • mPolicy:WindowManagerPolicy

WindowManagerPolicy(WMP)類型的變量。WindowManagerPolicy是窗口管理策略的接口類,用來定義一個窗口策略所要遵循的通用規范,并提供了WindowManager所有的特定的UI行為。它的具體實現類為PhoneWindowManager,這個實現類在WMS創建時被創建。WMP允許定制窗口層級和特殊窗口類型以及關鍵的調度和布局。

  • mSessions:ArraySet<Session>

ArraySet類型的變量,元素類型為Session.它主要用于進程間通信,其他的應用程序進程想要和WMS進程進行通信就需要經過Session,并且每個應用程序進程都會對應一個Session,WMS保存這些Session用來記錄所有向WMS提出窗口管理服務的客戶端。

  • mWindowMap:WindowHashMap

WindowHashMap類型的變量,WindowHashMap繼承了HashMap,它限制了HashMap的key值的類型為IBinder,value值的類型為WindowState。WindowState用于保存窗口的信息,在WMS中它用來描述一個窗口。綜上得出結論,mWindowMap就是用來保存WMS中各種窗口的集合。

  • mFinishedStarting:ArrayList

ArrayList類型的變量,元素類型為AppWindowToken,它是WindowToken的子類。
WindowToken主要有兩個作用:

  • 可以理解為窗口令牌,當應用程序想要向WMS申請新創建一個窗口,則需要向WMS出示有效的WindowToken。AppWindowToken作為WindowToken的子類,主要用來描述應用程序的WindowToken結構,應用程序中每個Activity都對應一個AppWindowToken(AMS的ActivityRecord)。
  • WindowToken會將相同組件(Acitivity)的窗口(WindowState)集合在一起,方便管理。
    mFinishedStarting就是用于存儲已經完成啟動的應用程序窗口(Acitivity)的AppWindowToken的列表。
  • mFinishedEarlyAnim和mWindowReplacementTimeouts,其中mFinishedEarlyAnim存儲了已經完成窗口繪制并且不需要展示任何已保存surface的應用程序窗口的AppWindowToken。
  • mWindowReplacementTimeout存儲了等待更換的應用程序窗口的AppWindowToken,如果更換不及時,舊窗口就需要被處理。

mResizingWindows:ArrayList

  • ArrayList類型的變量,元素類型為WindowState。
  • mResizingWindows是用來存儲正在調整大小的窗口的列表。
  • mPendingRemove是在內存耗盡時設置的,里面存有需要強制刪除的窗口。
  • mDestroySurface里面存有需要被Destroy的Surface。mDestroyPreservedSurface里面存有窗口需要保存的等待銷毀的Surface。

mInputManager:InputManagerService

InputManagerService類型的變量,輸入系統的管理者。InputManagerService(IMS)會對觸摸事件進行處理,它會尋找一個最合適的窗口來處理觸摸反饋信息,WMS是窗口的管理者,因此,WMS“理所應當”的成為了輸入系統的中轉站。

三、WMS的作用

image.png

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService .java

private WindowManagerService(Context context, InputManagerService inputManager,
         boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy) {
    ...
    mInputManager = inputManager;
    ...
     mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
     //通過DisplayManager的getDisplays方法得到Display數組,每個顯示設備都有一個Display實例
     mDisplays = mDisplayManager.getDisplays();
     for (Display display : mDisplays) {
         createDisplayContentLocked(display);
     }
    ...
      mActivityManager = ActivityManager.getService();
    ...
     //創建了WindowAnimator,它用于管理所有的窗口動畫
     mAnimator = new WindowAnimator(this);
     mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
             com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
     LocalServices.addService(WindowManagerInternal.class, new LocalService());
     initPolicy();//6
     // Add ourself to the Watchdog monitors.
     Watchdog.getInstance().addMonitor(this);
  ...
 }

2、Window的添加過程

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

1、對所要添加的窗口進行檢查,如果窗口不滿足一些條件,就不會再執行下面的代碼邏輯。
2、WindowToken相關的處理,比如有的窗口類型需要提供WindowToken,沒有提供的話就不會執行下面的代碼邏輯,有的窗口類型則需要由WMS隱式創建WindowToken。
3、WindowState的創建和相關處理,將WindowToken和WindowState相關聯。
4、創建和配置DisplayContent,完成窗口添加到系統前的準備工作。

 public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {

        int[] appOp = new int[1];
        //檢查相關的權限
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        ...
        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }
            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);//2
            if (displayContent == null) {
                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            ...
            //type代表一個窗口的類型,它的數值介于FIRST_SUB_WINDOW和LAST_SUB_WINDOW之間(1000~1999),
            //這個數值定義在WindowManager中,說明這個窗口是一個子窗口
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                //attrs.token是IBinder類型的對象,windowForClientLocked方法內部會根據attrs.token作為key值
                //從mWindowMap中得到該子窗口的父窗口
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
      }
         AppWindowToken atoken = null;
         final boolean hasParent = parentWindow != null;
         //得到WindowToken保存窗口相關信息
         WindowToken token = displayContent.getWindowToken(
                 hasParent ? parentWindow.mAttrs.token : attrs.token);
         final int rootType = hasParent ? parentWindow.mAttrs.type : type;
         boolean addToastWindowRequiresToken = false;

         if (token == null) {
             if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                 Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             if (rootType == TYPE_INPUT_METHOD) {
                 Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             if (rootType == TYPE_VOICE_INTERACTION) {
                 Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             if (rootType == TYPE_WALLPAPER) {
                 Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             ...
             if (type == TYPE_TOAST) {
                 // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                 if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                         parentWindow)) {
                     Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                             + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
             }
             final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
             //這說明當我們添加窗口時是可以不向WMS提供WindowToken的,前提是rootType和type的值不為前面條件判斷篩選的值。
             //WindowToken隱式和顯式的創建肯定是要加以區分的,注釋3處的第4個參數為false就代表這個WindowToken是隱式創建的
             token = new WindowToken(this, binder, type, false, displayContent,
                     session.mCanAddInternalSystemWindow);
             //處判斷如果窗口為應用程序窗口
         } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
             //將WindowToken轉換為專門針對應用程序窗口的AppWindowToken,AppWindowToken對應ActivityRecord
             atoken = token.asAppWindowToken();
             if (atoken == null) {
                 Slog.w(TAG_WM, "Attempted to add window with non-application token "
                       + token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
             } else if (atoken.removed) {
                 Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                       + token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_APP_EXITING;
             }
         } else if (rootType == TYPE_INPUT_METHOD) {
             if (token.windowType != TYPE_INPUT_METHOD) {
                 Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
         }
        .....
         //創建了WindowState,它存有窗口的所有的狀態信息,在WMS中它代表一個窗口
         final WindowState win = new WindowState(this, session, client, token, parentWindow,
                  appOp[0], seq, attrs, viewVisibility, session.mUid,
                  session.mCanAddInternalSystemWindow);
          //窗口的客戶端是否已經死亡
          if (win.mDeathRecipient == null) {
              // Client has apparently died, so there is no reason to
              // continue.
              Slog.w(TAG_WM, "Adding window client " + client.asBinder()
                      + " that is dead, aborting.");
              return WindowManagerGlobal.ADD_APP_EXITING;
          }
          //窗口的DisplayContent是否為null,沒有展示內容
          if (win.getDisplayContent() == null) {
              Slog.w(TAG_WM, "Adding window to Display that has been removed.");
              return WindowManagerGlobal.ADD_INVALID_DISPLAY;
          }
          //調用了WindowManagerPolicy的adjustWindowParamsLw方法,該方法的實現在PhoneWindowManager中,
          //會根據窗口的type對窗口的LayoutParams的一些成員變量進行修改
          mPolicy.adjustWindowParamsLw(win.mAttrs);
          win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
          res = mPolicy.prepareAddWindowLw(win, attrs);
          .....
          win.attach();
          mWindowMap.put(client.asBinder(), win);
          if (win.mAppOp != AppOpsManager.OP_NONE) {
              int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
                      win.getOwningPackage());
              if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
                      (startOpResult != AppOpsManager.MODE_DEFAULT)) {
                  win.setAppOpVisibilityLw(false);
              }
          }

          final AppWindowToken aToken = token.asAppWindowToken();
          if (type == TYPE_APPLICATION_STARTING && aToken != null) {
              aToken.startingWindow = win;
              if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + aToken
                      + " startingWindow=" + win);
          }

          boolean imMayMove = true;
          win.mToken.addWindow(win);
           if (type == TYPE_INPUT_METHOD) {
              win.mGivenInsetsPending = true;
              setInputMethodWindowLocked(win);
              imMayMove = false;
          } else if (type == TYPE_INPUT_METHOD_DIALOG) {
              displayContent.computeImeTarget(true /* updateImeTarget */);
              imMayMove = false;
          } else {
              if (type == TYPE_WALLPAPER) {
                  displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                  displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
              } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                  displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
              } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
                  displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
              }
          }
}

3、Window的刪除過程

frameworks/base/core/java/android/view/WindowManagerGlobal.java

public void removeView(View view, boolean immediate) {
      if (view == null) {
          throw new IllegalArgumentException("view must not be null");
      }
      synchronized (mLock) {
          //找到view的索引
          int index = findViewLocked(view, true);
          View curView = mRoots.get(index).getView();
          //刪除當前的view
          removeViewLocked(index, immediate);
          if (curView == view) {
              return;
          }
          throw new IllegalStateException("Calling with view " + view
                  + " but the ViewAncestor is attached to " + curView);
      }
  }

      private void removeViewLocked(int index, boolean immediate) {
       ViewRootImpl root = mRoots.get(index);
       View view = root.getView();
       if (view != null) {
           InputMethodManager imm = InputMethodManager.getInstance();
           if (imm != null) {
               imm.windowDismissed(mViews.get(index).getWindowToken());
           }
       }
       boolean deferred = root.die(immediate);//4
       if (view != null) {
           view.assignParent(null);
           if (deferred) {
               mDyingViews.add(view);
           }
       }
   }

四、WMS主要類介紹:

1、WindowManagerService類

WindowManagerService服務就可以通過它在內部所創建的WindowState對象的成員變量mClient來 要求運行在應用程序進程這一側的Activity組件來配合管理窗口的狀態,例如:

  • 當一個Activity組件的窗口的大小發生改變后,WindowManagerService服務就會調用這個
    IWindow接口的成員函數resized來通知該Activity組件,它的大小發生改變了。
  • 當一個Activity組件的窗口的可見性之后,WindowManagerService服務就會調用這個Iwindow
    接口的成員函數dispatchAppVisibility來通知該Activity組件,它的可見性發生改變了。
  • 當一個Activity組件的窗口獲得或者失去焦點之后,WindowManagerService服務就會調用這個
    IWindow接口的成員函數windowFoucusChanged來通知該Activity組件,它的焦點發生改變了。

2、Window類

  • 定義Callback接口,它包含一系列dispatchXxxx方法和一系列onXxxx方法,用于處理UI事件。
  • 定義了一些接口,如setContentView、findViewById()等。由PhoneWindow來實現。

3、WindowManager類

  • WindowManager繼承自ViewManager這個接口,這個接口主要有以下的實現子接口:
    addView()、updateViewLayout()、removeView();
    WindowManager可以添加view到屏幕,也可以從屏幕刪除view。它面向的對象一端是屏幕,另一端就是View,通過WindowManager的 addView方法創建View,這樣產生出來的View根據WindowManager.LayoutParams屬性不同,效果也就不同了,比如創建系統頂級窗口,實現懸浮窗口效果。

4、ViewRootImpl類

ViewRootImpl這個類在android的UI結構中扮演的是一個中間者的角色,連接的是PhoneWindow 和WindowManagerService,也就是窗口管理系統與窗口呈現系統之間的橋梁。
它的主要作用有兩個:
1.向DecorView分發收到的用戶發起的event事件,如按鍵,觸屏等事件;
2.與WindowManagerService交互,完成整個Activity的GUI的繪制。
3.里面兩個重要的變量 mWindowSessoin和mWindow。

  • mWindowSessoin是ViewRootImpl和WindowManagerService之間的一個會話層,它的實體是在WMS中定義,作為ViewRootImpl 向WMS發送請求的的橋梁。
  • mWindow是ViewRootImpl提供給WMS,以便WMS反向通知ViewRootImpl的接口。由于ViewRootImpl處在application端,而WMS處在system_server端,它們處在不同的進程,因此需要添加這個W接口,便于WMS向ViewRootImpl傳遞信息。

5、WindowState類

  • WMS中最基本的元素,描述WMS中的一個窗口。它既可以是由App添加過來的View,也可以是系統創建的系統窗口。mAttrs為WindowManager.LayoutParams類型,描述布局參數。mClient為IWindow類型,也就是App端的ViewRootImpl::W。為了查找方便,WMS中的mWindowMap保存了IWindow到WindowState的映射,mTokenMap保存了IApplicationToken到WindowToken的映射。

6、Session類

  • 向App提供IWindowSession接口讓其可以和WMS通信,每個App在WMS有一個Session對象,App就是通過這個Session來向WMS發出窗口管理申請的,命令dumpsys window sessions可以查看系統中的Session。

7、WindowToken類

  • 描述WM中一組相關的窗口,這些Window對應的WindowState放在其成員變量windows里。其主要繼承類AppWindowToken,它是針對App的WindowToken結構。WindowState中的mAppToken指向所屬的AppWindowToken,如果是系統窗口,mAppToken為空,mToken指向WindowToken對象。

8、AppWindowToken

每個App的Activity對應一個AppWindowToken。其中的appToken為IApplicationToken類型,連接著對應的AMS中的ActivityRecord::Token對象,有了它就可以順著AppWindowToken找到AMS中相應的ActivityRecord。其中allAppWindows是一個無序的列表,包含該Activity中所有的窗口。用dumpsys window display可以查看z-ordered的AppWindowToken列表。

8、TaskStack類

  • AppWindowToken保存了屬于它的WindowState的有序列表,而它本身也作為一個列表被管理在TaskStack中的mTasks成員中。
  • TaskStack中有個重要的變量mBounds,在相同Task里的AppWindowToken對應的Activity的大小是相同的,所以mBounds代表的就是Activity對應的大小。

9、DisplayContent類

表示一個顯示設備上的內容,這個顯示設備可以是外接顯示屏,也可以是虛擬顯示屏。其中 mWindows是一個WindowState的有序(Z-ordered,底部最先)列表。mStackBoxes包含了若干個StackBox,其中 一個為HomeStack,另一個是App的StackBox。所有的StackBox被組織成二叉樹,StackBox是其中的節點,其中有三個重要成 員變量,mFirst和mSecond指向左和右子結點(也是StackBox),StackBox的成員mStack才是我們真正關心的東西 -TaskStack。可以看到,為了要把TaskStack存成樹的結構,需要一個容器,這個容器就是StackBox。

五、AMS和WMS交互圖

1、AMS主要類關系圖

image.png

2、WMS主要類關系圖

image.png

3、AMS、WMS數據結構關系

image.png

參考鏈接:http://liuwangshu.cn/tags/Android%E6%A1%86%E6%9E%B6%E5%B1%82/
出版的“Android進階解密”這本書值得看

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容