Activity常见问题

Activity 系列博客

Activity常见问题

通过这篇博客,我们能知道以下问题:

  1. Activity 各种情况下的生命周期
  2. 弹出 DialogActivity 生命周期有什么影响?
  3. onActivityResult() 在哪两个生命周期之间回调?
  4. ActivityonResume() 之后才显示的原因是什么?
  5. 通过 Sheme 协议打开 Activity
  6. Activity 什么时候会发生重建?
  7. ActivityonCreate() 方法里写死循环会 ANR 吗?

1. Activity 生命周期方法相关

生命周期的回调方法

Activity生命周期的回调主要有 onCreate()onRestart()onStart()onResume()onPause()onStop()onDestory() 几个方法。

调用时机说明:

  • onCreate():系统创建 Activity 时触发,用来初始化 Activity 的基本组件。
  • onRestart():当处于“已停止”状态的 Activity 即将重启时,系统就会调用此回调。
  • onStart()Activity 将进入“已启动”状态,并对用户可见。
  • onResume():系统会在 Activity 开始与用户互动之前调用此回调。此时,该 Activity 位于 Activity 堆栈的顶部,并会捕获所有用户输入
  • onPause():当 Activity 失去焦点并进入“已暂停”状态时,系统就会调用此回调。
  • onStop():当 Activity 对用户不再可见时,系统就会调用此回调。
  • onDestory():系统会在销毁 Activity 之前调用此回调。此回调是 Activity 接收的最后一个回调。通常,实现 onDestroy() 是为了确保该 Activity 的所有资源。

onStart()onStop() 是从 Activity 是否可见来回调的,onResume()onPause() 是从 Activity 是否位于前台来回调的。

在生命周期里几种状态:

  • Activity 的整个生命周期发生在 onCreate()onDestroy() 之间,在 onCreate() 中执行“全局”状态设置(例如状态布局),并在 onDestroy() 中释放资源。

  • Activity 的可见生命周期发生在 onStart()onStop() 之间,在这段时间内Activity对用户可见。在整个生命周期中,当 Activity 在对用户可见和隐藏俩种状态中交替变化时,系统会多次调用 onStart()onStop()

  • Activity 的前台生命周期发生在 onResume()onPause() 之间,Activity位于其他 Activity 之前(栈顶位置),可与用户交互并具有输入焦点。但状态改变频繁,系统会多次调用 onResume()onPause(),建议做些轻量级操作。

打开Activity时两个Activity的生命周期方法的回调顺序

当一个 Activity 启动另一个 Activity 时,它们都会经历生命周期转换。第一个 Activity 停止运行并进入“已暂停”或“已停止”状态,同时创建另一个 Activity

  • Activity A 启动 Activity B,B ActivitylaunchModestandard 或者 B Activity 没有可复用的实例时: A.onPause() -> B.onCreate() -> B.onStart() -> B.onResume() -> A.onStop() --> A.onDestory()(如果需要关闭Activity A[A 被移出栈])

  • Activity A 启动 Activity B,B ActivitylaunchModesingleTop且 B Activity 已经在栈顶时(一些特殊情况如通知栏点击、连点、B Activity自己打开自己),此时只有 B 页面自己有生命周期变化:B.onPause() -> B.onNewIntent() -> B.onResume()

  • 当 B ActivitylaunchModesingleInstancesingleTask 且对应的 B Activity 有可复用的实例时:A.onPause() -> B.onNewIntent() -> B.onRestart() -> B.onStart() -> B.onResume() -> A.onStop() --> A.onDestory()(如果需要关闭Activity A[A 被移出栈])

  • 当 B ActivityThemeDialog 时:A.onPause() -> B.onCreate() -> B.onStart() -> B.onResume()(注意前一个 Activity 不会回调 onStop(),因为只有在 Activity 切到后台(不在Activity栈顶)不可见才会回调 onStop();而弹出 Dialog 主题的 Activity 时前一个页面还是可见的,只是失去了焦点而已所以仅有 onPause() 回调)

2. 弹出 DialogActivity 生命周期有什么影响?

先下结论:通过 《Android Activity——启动过程探索(一)》 《Android Activity——启动过程探索(二)》 《Android Activity——启动过程探索(三)》 我们知道Activity生命周期回调都是通过ActivityTaskManagerService(ATMS)(在Android 10之前是通过 ActivityManagerService(AMS))来回调的。但是弹出 DialogToastPopupWindow 本质上都直接是通过 WindowManager.addView() 显示的(没有经过 ATMS/AMS),所以不会对生命周期有任何影响。

Dialog显示过程源码解析

源码版本为 Android 10(Api 29),不同Android版本可能有一些差别

  1. 构造方法中获取 WindowManager 和创建 PhoneWindow 并通过 PhoneWindowsetWindowManager() 方法关联

     public Dialog(@NonNull Context context, @StyleRes int themeResId) {
         this(context, themeResId, true);
     }
    
     Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
         // 获取 WindManager,实际为实现类 WindowManagerImpl
         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         // 创建 PhoneWindow 对象
         final Window w = new PhoneWindow(mContext);
         mWindow = w;
         w.setCallback(this);
         w.setOnWindowDismissedCallback(this);
         w.setOnWindowSwipeDismissedCallback(() -> {
             if (mCancelable) {
                 cancel();
             }
         });
         w.setWindowManager(mWindowManager, null, null);
         w.setGravity(Gravity.CENTER);
    
         mListenersHandler = new ListenersHandler(this);
     }
    
  2. Dialog#show() 方法源码实现

     public void show() {
         mCanceled = false;
     
         if (!mCreated) {
             dispatchOnCreate(null); // 没有创建过的话,调用 onCreate() 方法
         } else {
             // 创建过的话,就不在调用 onCreate() 回调,只是修改配置
             final Configuration config = mContext.getResources().getConfiguration();
             mWindow.getDecorView().dispatchConfigurationChanged(config);
         }
     
         onStart(); // 调用 onStart() 回调方法
         mDecor = mWindow.getDecorView(); // 通过 PhoneWindow 获取 DecorView
     
         ... // 省略,设置默认logo、icon以及确定软键盘模式等
     
         // 通过 WindowManager(实际为WindowManagerImpl对象)将DecorView增加窗体上
         mWindowManager.addView(mDecor, l);
         
         ... // 设置软键盘模式
     
         mShowing = true; // 修改显示标记为 true
     
         sendShowMessage();
     }
    
     // WindowManagerImpl 的 addView() 方法
     @Override
     public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
         applyDefaultToken(params);
         // 调用 WindowManagerGlobal 的 addView() 方法
         mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
     }
     
     // WindowManagerGlobal 的 addView() 方法核心代码
     public void addView(View view, ViewGroup.LayoutParams params,
             Display display, Window parentWindow) {
         
         ViewRootImpl root;
         View panelParentView = null;
     
         synchronized (mLock) {
             // 创建 ViewRootImpl 对象
             root = new ViewRootImpl(view.getContext(), display);
             view.setLayoutParams(wparams);
             // 调用 ViewRootImpl 的 setView() 方法
             root.setView(view, wparams, panelParentView);
         }
     }
     
     // ViewRootImpl 的 setView() 方法核心代码
     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
         synchronized (this) {
             if (mView == null) {
                 mView = view;
                 // 调用 requestLayout() 方法,进行布局(包括measue、layout、draw)
                 requestLayout();
                 
                 mOrigWindowType = mWindowAttributes.type;
                 mAttachInfo.mRecomputeGlobalAttributes = true;
                 collectViewAttributes();
                 // 通过调用 Session 的 addToDisplay() 方法
                 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                         getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                         mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                         mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                         mTempInsets);
                 setFrame(mTmpFrame);
             }
         }
     }
    

ViewRootImplsetView() 方法说明:

  1. mWindowSession 是通过 WindowManagerGlobalgetWindowSession() 方法获取到的,返回的是通过 WindowManagerServiceopenSession() 方法创建的 Session 对象

  2. mWindow 对象为 ViewRootImpl 的 内部类 static class W extends IWindow.Stub,通过定义可以看出来,是一个 Binder 对象

  3. Session 对象的 addToDisplay() 方法

     Override
     public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
             int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
             Rect outStableInsets, Rect outOutsets,
             DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
             InsetsState outInsetsState) {
          // mService 为 WindowManagerService,window 就是上面的 `ViewRootImpl` 的 内部类 `static class W extends IWindow.Stub`
         return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                 outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                 outInsetsState);
     }
    

通过这个方法就能够实现客户端与 WindowManagerService 的双向调用了(应用端通过 mWindowSession 调用 WMS, WMS 通过 mWindow (一个 Binder 对象) 调用应用端)

DialogFragment源码解析

注意:该部分源码来自 support v4 包

DialogFragment 的定义

class DialogFragment extends Fragment

发现就是直接继承了 Fragment,那么就可以肯定,Fragment 有的 DialogFragment 都有,像生命周期方法等,又因为我们可以通过 getDialog() 方法获取到 Dialog 。所以可以肯定它包含一个了 Dialog。接着我们先看 show() 方法

public void show(FragmentManager manager, String tag) {
    mDismissed = false;
    mShownByMe = true;
    FragmentTransaction ft = manager.beginTransaction();
    ft.add(this, tag);
    ft.commit();
}

发现就是将 DialogFragment 显示出来,那我们接着找一下,看看 Dialog 是在哪里创建的。发现如下代码:

@Override
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
    mDialog = onCreateDialog(savedInstanceState);
    return (LayoutInflater) mActivity.getSystemService(
            Context.LAYOUT_INFLATER_SERVICE);
}

@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new Dialog(getActivity(), getTheme());
}

发现是在 getLayoutInflater() 方法中创建的,那么这个 getLayoutInflater() 方法又是在哪里被调用的呢?通过查找我们发现这个方法是在 FragmentManager 中调用的,调用完这个方法之后就会调用 FragmentonViewCreated() 方法;而 方法的返回值 LayoutInflater 就是 Fragment 中回调方法 onCreateView() 的参数。

分析到这里,我们就知道了,DialogFragment 实际上是一个 Fragment ,只是他在 onViewCreated() 方法之前通过 getLayoutInflater() 方法创建了一个 Dialog,也就是一个普通的 Fragment 中包含了一个 Dialog。那么又出现了问题,我们刚刚看了 show() 方法,发现并没有调用 Dialog。show() 方法,那么 Dialog 是怎么显示的呢?我们前面就说过,DialogFragment 实际上是一个 FragmentFragment 有的 DialogFragment 都有;我们都知道Fragment有一个生命周期方法 onStart() 是在用户可见时被调用的,那么 Dialog.show() 方法是否在该方法种调用了,看一下代码:

@Override
public void onStart() {
    super.onStart();
    if (mDialog != null) {
        mViewDestroyed = false;
        mDialog.show();
    }
}

果然,在 Fragment 的生命周期方法 onStart()回调中,调用了 Dialog.show() 方法,那么当我么调用 DialogFragmentshow() 方法时,把将 DialogFragment 显示出来了,那么 Fragment 的生命周期方法 onStart() 自然会被回调,Dialog 自然也就显示出来了。Fragment 的生命周期方法回调并非由 ActivityManagerService(AMS)【Android 10 以上 ActivityTaskManagerService(ATMS)】来控制,而是通过 FragmentManager 回掉的,也不会影响到 Activity 的生命周期。

3. onActivityResult 在哪两个生命周期之间回调?

onActivityResult 不属于 Activity 的生命周期。但是答案很简单,因为在 onActivityResult() 方法的注释中就写着答案:「You will receive this call immediately before onResume() when your activity is re-starting.」 所以 onActivityResult() 回调先于该 Activity 的所有生命周期回调,从 B Activity 返回 A Activity 的生命周期调用为:

B.onPause() -> A.onActivityResult() -> A.onRestart() -> A.onStart() -> A.onResume()

4. Activity 在 onResume 之后才显示的原因是什么?

通过《Activity 的组成》我们知道了在 Activity 中调用 setContentView() 方法会将我们的 View 添加到 DecorView 中,DecorView 也是一个控件,他要加载到界面上让用户可见,是通过 Window (也就是 PhoneWindow)来完成的,所以我们只要知道 PhoneWindow 是在什么时候将 DecorView 加载出来的,就知道了用户是在什么时候才可见布局信息。那么,到底是什么时候开始将 DecorView 添加到 PhoneWindow 中的了, 在 《Android Activity——启动过程探索(二)》 中,我们说到了 Activity 的回调 onResume() 方法,中间有一段是会调用 ActivityThread#handleResumeActivity() 方法,我们来看一下这个方法的源码(省略大部分代码之后的核心代码):

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    // 回调 onResume() 方法
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
   
    final Activity a = r.activity;
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow(); // 获取PhoneWindow并赋值
        View decor = r.window.getDecorView(); // 获取 DecorView 控件
        decor.setVisibility(View.INVISIBLE);
        // 获取ViewManager,也就是WindowManager(因为interface WindowManager extends ViewManager)
        ViewManager wm = a.getWindowManager(); 
        
        a.mWindowAdded = true;
        wm.addView(decor, l); // 关键点
    }
}

方法说明:

  1. ViewManager wm = a.getWindowManager() 方法最终返回的是 WindowManagerImpl 对象,原因如下:

    1. Activity.getWindowManager() 方法返回 Activity 的成员变量 mWindowManager
    2. Activity 的成员变量 mWindowManagerActivityattach() 方法中被赋值 mWindowManager = mWindow.getWindowManager(),调用的是 WindowgetWindowManager() 方法
    3. WindowgetWindowManager() 方法返回的是他的成员变量 mWindowManager
    4. Window 的成员变量 mWindowManager 在他的 setWindowManager() 方法中被赋值 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this) ,所以得出结论 a.getWindowManager() 方法最终返回的是 WindowManagerImpl 对象
  2. 关键点 wm.addView(decor, l) 调用的就是 WindowManagerImpl#addView() 方法了,之后的调用过程在当前文章前面【Dialog#show() 方法源码实现】 部分已经说过了,就不在重复了。

通过以上分析,我们就知道了实际上 Activity 中的布局内容是在 onResume() 回调之后才挂载到 Window 上的,也就是这个时候才是对用户可见的。

5. 通过 URL Sheme 协议打开 Activity

URL Scheme 是一种页面内跳转协议,通过这个协议可以比较方便的跳转到app某一个页面,也可以通过 h5 打开App页面。

URL Sheme 的格式:[scheme]://[host][:port]/[path]?[query]

  • scheme:协议名称 [scheme]:// 为必填,其他为非必填
  • host:主机名(域名)
  • port:端口号
  • path:路径
  • query:参数(多个参数之间用 & 分割,类似 get 请求)

使用

1. APP端需要能通过 sheme 打开的 Activity 进行配置 intent 过滤器(在清单文件中配置)

    <activity android:name=".TestActivity">
        <!--Android 接收外部跳转过滤器-->
        <!--要想在别的App上能成功调起App,必须添加intent过滤器-->
        <intent-filter>
            <!-- 协议部分配置 ,注意需要跟web配置相同-->
            <data
                android:host="action.test.activity"
                android:pathPrefix="/test"
                android:port="999"
                android:scheme="testapp" />
    
            <!--下面这几行也必须得设置-->
            <action android:name="android.intent.action.VIEW" />
    
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
        </intent-filter>
    </activity>

data属性说明:

  • scheme: 协议名称,必填,其他为非必填(由开发人员自定义)

  • host: 域名

  • port:端口

  • path: 完整的路径,如:http://example.com/blog/abc.html,这里将 path 设置为 /blog/abc.html 才能够进行匹配

  • pathPrefix:路径的开头部分,拿上来的 Uri 来说,这里将 pathPrefix 设置为 /blog 就能进行匹配了

  • pathPattern:用表达式来匹配整个路径,匹配符如下:

    • ” 用来匹配0次或更多,如:“a” 可以匹配“a”、“aa”、“aaa”...
    • “.” 用来匹配任意字符,如:“.” 可以匹配“a”、“b”,“c”...
    • 因此 “.*” 就是用来匹配任意字符0次或更多

2. 在 Activity 获取 sheme 信息:

    Uri uri = getIntent().getData();
    if (uri != null) {
        // 完整的url信息
        Log.i(TAG, "url:" + uri);

        // scheme部分
        String scheme = uri.getScheme();
        Log.i(TAG, "scheme:" + scheme);

        // host部分
        String host = uri.getHost();
        Log.i(TAG, "host:" + host);

        // port部分
        int port = uri.getPort();
        Log.i(TAG, "port:" + port);

        // 访问路劲
        String path = uri.getPath();
        Log.i(TAG, "path:" + path);
        List<String> pathSegments = uri.getPathSegments();
        Log.i(TAG, "pathSegments:" + pathSegments);

        // Query部分
        String query = uri.getQuery();
        Log.i(TAG, "query:" + query);

        //获取指定参数值
        String params1 = uri.getQueryParameter("params1");
        String params2 = uri.getQueryParameter("params2");
        Log.i(TAG, "params1:" + params1 + "  params2:" + params2);
    }

3. 打开 Activity 的方式

1. 通过原生代码打开

        String uri = "testapp://action.test.activity:999/test?params1=1&params2=true";
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
        startActivity(intent);

1. 通过h5代码打开(使用浏览器打开包含如下代码的 html 文件)

        <html>
            <title>测试</title>
        
            <body>
                 <a href="testapp://action.test.activity:999/test?params1=1&params2=true">打开APP</a>
            </body>
        </html>

6. Activity 什么时候会发生重建?

发生时机:

  1. 系统资源回收:内存不足,低优先级的Activity会被杀死
  2. 配置发生变化:当系统配置发生变化时,比如屏幕方向、语言的改变

横竖屏切换不销毁重建的方式

在清单文件中为该 Activity 配置 android:configChanges 属性。属性值 orientation|screenSize(Api级别为13以下时只需要配置 orientation 即可) 对应着旋转屏幕,locale 对应着语言变化。如此,在配置发生变化时,不会导致重建,而是走 onConfigurationChanged() 回调方法。

保存和恢复住状态

保存和恢复状态分别通过 onSaveInstanceState() 方法和 onRestoreInstanceState() 方法实现。

  1. ActivityonSaveInstanceState() 执行在 onStop() 之前,与 onPause() 没有固定的时序关系;onRestoreInstanceState() 执行在 onStart() 之后。不止 Activity 有这两个方法,每个 View 也有这两个方法,用来保存 View 的信息。
  2. 正常情况下的活动销毁并不会调用这两个方法,只有当活动异常销毁并且有机会重现展示的时候才会进行调用。
  3. ActivityonRestoreInstanceState()onCreate() 都可以进行数据恢复工作,但建议采用在 onRestoreInstanceState() 中去恢复(因为调用了这个方法,用来存取数据的 Bundle 肯定不为 null)。
  4. onSaveInstanceState()onRestoreInstanceState() 这两个方法中,系统会默认为我们进行一定的恢复工作,比如:会为布局中的每个 View 调用相应的 onSaveInstanceState() 方法,让每个视图都能提供有关自身的应保存信息。

具体调用时机说明

Activity#onSaveInstanceState() 方法调用时机:

  1. 当用户按下HOME键时
  2. 从最近应用中选择运行其他的程序时
  3. 按下电源按键(关闭屏幕显示)时
  4. 从当前activity启动一个新的activity时
  5. 屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)
  6. 被系统回收时

Activity#onRestoreInstanceState() 方法调用时机:

  1. 被系统回收且重建时
  2. 屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)
  3. 语言切换时

7. 在 Activity 的 onCreate() 方法里写死循环会 ANR 吗?

ANR全称:Application Not Responding,也就是应用程序无响应

Android 中产生 ANR 的原因:

  1. Service TimeOut: service 未在规定时间执行完成:前台服务 20s,后台 200s
  2. BroadCastQueue TimeOut: 未在规定时间内未处理完广播:前台广播 10s 内, 后台 60s 内
  3. ContentProvider TimeOut: publish 在 10s 内没有完成
  4. Input Dispatching timeout: 5s 内未响应键盘输入、触摸屏幕等事件

根据以上 ANR 产生的原因,说明在 ActivityonCreate() 方法中写死循环是不会发生ANR的,但是主线程被死循环一直占用了,所以当再有其他事件产生时,就不能及时响应了,从而导致ANR发生。

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

推荐阅读更多精彩内容