Android Activity Result API使用

在Android開發(fā)中時常需要用到跳轉新頁面獲取結果回傳數(shù)據(jù),一直以來使用的方法就是startActivityForResult和onActivityResult兩個方法凡泣,但是startActivityForResult方法卻已經(jīng)被deprecation艰匙,官方推薦使用Activity Result API。

跳轉新頁面回傳數(shù)據(jù)之startActivityForResult

操作步驟一般為三步:
1、定義REQUEST_CODE棋电,同一個頁面有多個數(shù)據(jù)時,避免重復苇侵;
2赶盔、調用 startActivityForResult(Intent, REQUEST_CODE)進行新頁面的跳轉;
3榆浓、重寫 onActivityResult()于未,判斷requestCode和resultCode,獲取到回傳數(shù)據(jù)執(zhí)行后續(xù)邏輯陡鹃。

示例:

class MainActivity : AppCompatActivity() {
    companion object {
        private const val REQUEST_CODE_1 = 20
        private const val REQUEST_CODE_2 = 21
    }

    private val binding: ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar?.hide()
        setContentView(binding.root)
        binding.click1.setOnClickListener {
            startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE_1)
        }
        binding.click2.setOnClickListener {
            //ARouter里仍然是使用startActivityForResult
            ARouter.getInstance()
                .build(ARouterPath.SecondActivity)
                .navigation(this, REQUEST_CODE_2)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == RESULT_OK) {
            when (requestCode) {
                REQUEST_CODE_1 -> {
                    Toast.makeText(
                        this,
                        "startActivityForResult回調:${data?.getStringExtra("data")}",
                        Toast.LENGTH_SHORT
                    )
                        .show()
                }
                REQUEST_CODE_2 -> {
                    Toast.makeText(
                        this,
                        "ARouter回調:${data?.getStringExtra("data")}",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
        }
    }
}

即使是大家常用的使用ARouter管理頁面路由的方式烘浦,通過navigation跳轉,也是塞入了REQUEST_CODE萍鲸,其內部也是使用startActivityForResult方法跳轉闷叉。

private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
        if (requestCode >= 0) {  // Need start for result
            if (currentContext instanceof Activity) {
                ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
            } else {
                logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
            }
        } else {
            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
        }

        if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
        }

        if (null != callback) { // Navigation over.
            callback.onArrival(postcard);
        }
    }

在新頁面通過一個簡單的setResult(int resultCode, Intent data),在關閉新頁面回到原頁面時猿推,在原頁面的onActivityResult方法就能拿到回傳數(shù)據(jù)片习。

跳轉新頁面回傳數(shù)據(jù)之Activity Result API

使用Activity Result API進行跳轉新頁面回傳數(shù)據(jù),操作分為兩步:
1蹬叭、通過registerForActivityResult方法定義一個函數(shù)處理回傳數(shù)據(jù);
2状知、通過launch()進行新頁面的跳轉秽五。

示例:

//使用registerForActivityResult
        val secondLauncher =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
                if (it.resultCode == RESULT_OK) {
                    it.data?.getStringExtra("data")?.let {
                        Toast.makeText(
                            this,
                            "registerForActivityResult回調:${it}",
                            Toast.LENGTH_SHORT
                        )
                            .show()
                    }
                }
            }
        binding.click3.setOnClickListener {
            secondLauncher.launch(Intent(this, SecondActivity::class.java))
        }

新頁面仍然是通過setResult(int resultCode, Intent data)回傳數(shù)據(jù),可以看到原頁面上移除了onActivityResult()的重寫饥悴,并且少寫了一個REQUEST_CODE坦喘。

探查Activity Result API原理

    @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull ActivityResultContract<I, O> contract,
            @NonNull ActivityResultCallback<O> callback) {
        return registerForActivityResult(contract, mActivityResultRegistry, callback);
    }

Activity Results API由三個要素組成盲再,Launcher、Contract瓣铣、Callback

ActivityResultLauncher

public abstract class ActivityResultLauncher<I> {

    /**
     * Executes an {@link ActivityResultContract}.
     *
     * <p>This method throws {@link android.content.ActivityNotFoundException}
     * if there was no Activity found to run the given Intent.

     * @param input the input required to execute an {@link ActivityResultContract}.
     *
     * @throws android.content.ActivityNotFoundException
     */
    public void launch(@SuppressLint("UnknownNullness") I input) {
        launch(input, null);
    }

    /**
     * Executes an {@link ActivityResultContract}.
     *
     * <p>This method throws {@link android.content.ActivityNotFoundException}
     * if there was no Activity found to run the given Intent.
     *
     * @param input the input required to execute an {@link ActivityResultContract}.
     * @param options Additional options for how the Activity should be started.
     *
     * @throws android.content.ActivityNotFoundException
     */
    public abstract void launch(@SuppressLint("UnknownNullness") I input,
            @Nullable ActivityOptionsCompat options);

    /**
     * Unregisters this launcher, releasing the underlying result callback, and any references
     * captured within it.
     *
     * You should call this if the registry may live longer than the callback registered for this
     * launcher.
     */
    @MainThread
    public abstract void unregister();

    /**
     * Get the {@link ActivityResultContract} that was used to create this launcher.
     *
     * @return the contract that was used to create this launcher
     */
    @NonNull
    public abstract ActivityResultContract<I, ?> getContract();
}

ActivityResultLauncher是registerForActivityResult的返回值答朋,用于連接啟動對象和返回對象的。

ActivityResultContract

ActivityResultContract是registerForActivityResult的第一個入?yún)⑻男Γs定了一個輸入類型和一個結果的返回類型梦碗。

public abstract class ActivityResultContract<I, O> {

    /** Create an intent that can be used for {@link Activity#startActivityForResult} */
    public abstract @NonNull Intent createIntent(@NonNull Context context,
            @SuppressLint("UnknownNullness") I input);

    /** Convert result obtained from {@link Activity#onActivityResult} to O */
    @SuppressLint("UnknownNullness")
    public abstract O parseResult(int resultCode, @Nullable Intent intent);

    /**
     * An optional method you can implement that can be used to potentially provide a result in
     * lieu of starting an activity.
     *
     * @return the result wrapped in a {@link SynchronousResult} or {@code null} if the call
     * should proceed to start an activity.
     */
    public @Nullable SynchronousResult<O> getSynchronousResult(
            @NonNull Context context,
            @SuppressLint("UnknownNullness") I input) {
        return null;
    }

    /**
     * The wrapper for a result provided in {@link #getSynchronousResult}
     *
     * @param <T> type of the result
     */
    public static final class SynchronousResult<T> {
        private final @SuppressLint("UnknownNullness") T mValue;

        /**
         * Create a new result wrapper
         *
         * @param value the result value
         */
        public SynchronousResult(@SuppressLint("UnknownNullness") T value) {
            this.mValue = value;
        }

        /**
         * @return the result value
         */
        public @SuppressLint("UnknownNullness") T getValue() {
            return mValue;
        }
    }
}

ActivityResultContract內主要就兩個方法,createIntent()方法創(chuàng)建一個Intent用于startActivityForResult蓖救,parseResult()方法對onActivityResult的結果進行轉換洪规。

ActivityResultContracts里提供了常用的ActivityResultContract,可以直接拿來使用循捺。


image.png

比如我們最常用的跳轉新頁面回傳數(shù)據(jù):ActivityResultContracts.StartActivityForResult()

    public static final class StartActivityForResult
            extends ActivityResultContract<Intent, ActivityResult> {

        /**
         * Key for the extra containing a {@link android.os.Bundle} generated from
         * {@link androidx.core.app.ActivityOptionsCompat#toBundle()} or
         * {@link android.app.ActivityOptions#toBundle()}.
         *
         * This will override any {@link ActivityOptionsCompat} passed to
         * {@link androidx.activity.result.ActivityResultLauncher#launch(Object,
         ActivityOptionsCompat)}
         */
        public static final String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result"
                + ".contract.extra.ACTIVITY_OPTIONS_BUNDLE";

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @NonNull Intent input) {
            return input;
        }

        @NonNull
        @Override
        public ActivityResult parseResult(
                int resultCode, @Nullable Intent intent) {
            return new ActivityResult(resultCode, intent);
        }
    }

繼承ActivityResultContract斩例,約定輸入類型為Intent,結果返回類型為ActivityResult从橘。在createIntent方法中因為輸入類型就是Intent念赶,所以沒做處理,直接返回恰力。parseResult方法中根據(jù)指定的resultCode和intent叉谜,創(chuàng)建了一個ActivityResult實例返回。

再看一個ActivityResultContracts.TakePicturePreview()

    public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap> {

        @CallSuper
        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @Nullable Void input) {
            return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        }

        @Nullable
        @Override
        public final SynchronousResult<Bitmap> getSynchronousResult(@NonNull Context context,
                @Nullable Void input) {
            return null;
        }

        @Nullable
        @Override
        public final Bitmap parseResult(int resultCode, @Nullable Intent intent) {
            if (intent == null || resultCode != Activity.RESULT_OK) return null;
            return intent.getParcelableExtra("data");
        }
    }

輸入類型為Void牺勾,因為在createIntent中自己創(chuàng)建了一個MediaStore.ACTION_IMAGE_CAPTURE的Intent實例正罢。parseResult中根據(jù)指定的intent中獲取到Bitmap實例返回。
如果ActivityResultContracts里常用的這些無法滿足需求驻民,自然也可以自定義一波翻具,實現(xiàn)相應的createIntent方法和parseResult方法即可。

ActivityResultCallback

顧名思義回还,就是結果回調裆泳。

public interface ActivityResultCallback<O> {

    /**
     * Called when result is available
     */
    void onActivityResult(@SuppressLint("UnknownNullness") O result);
}

registerForActivityResult在Activity中的實現(xiàn)

在Activity、Fragment中可以直接使用registerForActivityResult()柠硕,是因為ComponentActivity和Fragment都實現(xiàn)了ActivityResultCaller接口工禾。

    @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultRegistry registry,
            @NonNull final ActivityResultCallback<O> callback) {
        return registry.register(
                "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
    }

第一個參數(shù)使用"activity_rq#" + mNextLocalRequestCode.getAndIncrement()構造了一個key,mNextLocalRequestCode是一個AtomicInteger值蝗柔,使用這種方式就不需要額外定義REQUEST_CODE來進行區(qū)分了闻葵。
繼續(xù)ActivityResultRegistry的register方法:

    @NonNull
    public final <I, O> ActivityResultLauncher<I> register(
            @NonNull final String key,
            @NonNull final LifecycleOwner lifecycleOwner,
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultCallback<O> callback) {

        //獲取到當前生命周期組件的lifecycle
        Lifecycle lifecycle = lifecycleOwner.getLifecycle();
        //register要在當前生命周期組件處于STARTED狀態(tài)之前調用
        if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
            throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "
                    + "attempting to register while current state is "
                    + lifecycle.getCurrentState() + ". LifecycleOwners must call register before "
                    + "they are STARTED.");
        }
        //通過傳入的key生成requestCode
        final int requestCode = registerKey(key);
        //通過key在集合中獲取LifecycleContainer實例,沒有則生成一個
        LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
        if (lifecycleContainer == null) {
            lifecycleContainer = new LifecycleContainer(lifecycle);
        }
        //生成觀察者癣丧,當狀態(tài)為ON_START時執(zhí)行回調槽畔,為ON_STOP時移除與回調的關聯(lián),為ON_DESTROY時取消注冊
        LifecycleEventObserver observer = new LifecycleEventObserver() {
            @Override
            public void onStateChanged(
                    @NonNull LifecycleOwner lifecycleOwner,
                    @NonNull Lifecycle.Event event) {
                if (Lifecycle.Event.ON_START.equals(event)) {
                    mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
                    if (mParsedPendingResults.containsKey(key)) {
                        @SuppressWarnings("unchecked")
                        final O parsedPendingResult = (O) mParsedPendingResults.get(key);
                        mParsedPendingResults.remove(key);
                        callback.onActivityResult(parsedPendingResult);
                    }
                    final ActivityResult pendingResult = mPendingResults.getParcelable(key);
                    if (pendingResult != null) {
                        mPendingResults.remove(key);
                        callback.onActivityResult(contract.parseResult(
                                pendingResult.getResultCode(),
                                pendingResult.getData()));
                    }
                } else if (Lifecycle.Event.ON_STOP.equals(event)) {
                    mKeyToCallback.remove(key);
                } else if (Lifecycle.Event.ON_DESTROY.equals(event)) {
                    unregister(key);
                }
            }
        };
        //為LifecycleContainer實例添加觀察者
        lifecycleContainer.addObserver(observer);
        mKeyToLifecycleContainers.put(key, lifecycleContainer);
        //返回了一個ActivityResultLauncher實例
        return new ActivityResultLauncher<I>() {
            @Override
            public void launch(I input, @Nullable ActivityOptionsCompat options) {
                mLaunchedKeys.add(key);
                Integer innerCode = mKeyToRc.get(key);
                onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options);
            }

            @Override
            public void unregister() {
                ActivityResultRegistry.this.unregister(key);
            }

            @NonNull
            @Override
            public ActivityResultContract<I, ?> getContract() {
                return contract;
            }
        };
    }

在register方法中胁编,首先獲取到當前生命周期組件的lifecycle厢钧。然后register要在當前生命周期組件處于STARTED狀態(tài)之前調用鳞尔。通過傳入的key生成requestCode。通過key在集合中獲取LifecycleContainer實例早直,沒有則生成一個寥假。生成觀察者,當狀態(tài)為ON_START時執(zhí)行回調霞扬,為ON_STOP時移除與回調的關聯(lián)糕韧,為ON_DESTROY時取消注冊。為LifecycleContainer實例添加觀察者祥得。最終返回了一個ActivityResultLauncher實例兔沃。

onLaunch在Activity中的實現(xiàn)

在registerForActivityResult中最終返回了ActivityResultLauncher實例,而ActivityResultLauncher的launch方法里調用了ActivityResultRegistry.onLaunch方法级及,該方法是一個抽象方法乒疏,其實現(xiàn)在ComponentActivity中。

        this.mActivityResultRegistry = new ActivityResultRegistry() {
            public <I, O> void onLaunch(final int requestCode, @NonNull ActivityResultContract<I, O> contract, I input, @Nullable ActivityOptionsCompat options) {
                ComponentActivity activity = ComponentActivity.this;
                final SynchronousResult<O> synchronousResult = contract.getSynchronousResult(activity, input);
                if (synchronousResult != null) {
                    //不需要啟動Activity就能知道結果的場景處理
                    (new Handler(Looper.getMainLooper())).post(new Runnable() {
                        public void run() {
                            dispatchResult(requestCode, synchronousResult.getValue());
                        }
                    });
                } else {
                    //需要啟動Activity才能知道結果的場景處理
                    //通過ActivityResultContract.createIntent初始化Intent實例
                    Intent intent = contract.createIntent(activity, input);
                    //初始化Bundle
                    Bundle optionsBundle = null;
                    if (intent.getExtras() != null && intent.getExtras().getClassLoader() == null) {
                        intent.setExtrasClassLoader(activity.getClassLoader());
                    }

                    if (intent.hasExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE")) {
                        optionsBundle = intent.getBundleExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE");
                        intent.removeExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE");
                    } else if (options != null) {
                        optionsBundle = options.toBundle();
                    }
                    //如果是權限申請饮焦,請求權限
                    if ("androidx.activity.result.contract.action.REQUEST_PERMISSIONS".equals(intent.getAction())) {
                        String[] permissions = intent.getStringArrayExtra("androidx.activity.result.contract.extra.PERMISSIONS");
                        if (permissions == null) {
                            permissions = new String[0];
                        }

                        ActivityCompat.requestPermissions(activity, permissions, requestCode);
                    } else if ("androidx.activity.result.contract.action.INTENT_SENDER_REQUEST".equals(intent.getAction())) {
                        IntentSenderRequest request = (IntentSenderRequest)intent.getParcelableExtra("androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST");

                        try {
                            ActivityCompat.startIntentSenderForResult(activity, request.getIntentSender(), requestCode, request.getFillInIntent(), request.getFlagsMask(), request.getFlagsValues(), 0, optionsBundle);
                        } catch (final SendIntentException var11) {
                            (new Handler(Looper.getMainLooper())).post(new Runnable() {
                                public void run() {
                                    dispatchResult(requestCode, 0, (new Intent()).setAction("androidx.activity.result.contract.action.INTENT_SENDER_REQUEST").putExtra("androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION", var11));
                                }
                            });
                        }
                    } else {
                        ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
                    }
                }
            }
        };

首先區(qū)分是否需要啟動Activity怕吴,需要啟動Activity的情況下通過ActivityResultContract.createIntent初始化Intent實例,初始化Bundle县踢,最終也是通過ActivityCompat.startActivityForResult跳轉新頁面转绷。

總結

  • ComponentActivity內部初始化了一個ActivityResultRegistry實例,并重寫了 onLaunch()硼啤。
  • 調用registerForActivityResult() 最終調用ActivityResultRegistry.register()议经,在此添加了一個觀察者,當生命周期狀態(tài)切換到ON_START時谴返,執(zhí)行Contract.parseResult()生成輸出內容煞肾,并把結果作為參數(shù)傳入回調callback.onActivityResult()中。
  • 調用ActivityResultLauncher.launch() 才會發(fā)起跳轉嗓袱,其中回調了onLaunch()方法籍救,在此調用了Contract.createIntent()創(chuàng)建一個和startActivityForResult()搭配使用的Intent實例。
  • 跳轉目標Activity后返回此頁面渠抹,生命周期發(fā)生改變蝙昙,在觀察者中就會執(zhí)行回調的相關代碼。

后記

當一個頁面上需要一下子通過同一個ActivityResultLauncher打開多個頁面時梧却,發(fā)現(xiàn)在不同Android版本上表現(xiàn)不一樣奇颠。
每一個registerForActivityResult內部會生成一個RequestCode作為key,ActivityResultLauncher有一個觀察者隊列放航,ON_START會添加觀察者大刊,ON_STOP會移除觀察者。
當onActivityResult回調時三椿,執(zhí)行dispatchResult方法缺菌,從觀察者隊列中取出觀察者進行回傳,進行doDispatch方法搜锰。
關鍵在doDispatch方法中伴郁,有觀察者進行觀察者的onActivityResult回調,沒有觀察者蛋叼,使用key將數(shù)據(jù)存儲在bundle信息中焊傅。

    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @MainThread
    public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {
        String key = mRcToKey.get(requestCode);
        if (key == null) {
            return false;
        }
        doDispatch(key, resultCode, data, mKeyToCallback.get(key));
        return true;
    }

    private <O> void doDispatch(String key, int resultCode, @Nullable Intent data,
            @Nullable CallbackAndContract<O> callbackAndContract) {
        if (callbackAndContract != null && callbackAndContract.mCallback != null
                && mLaunchedKeys.contains(key)) {
            ActivityResultCallback<O> callback = callbackAndContract.mCallback;
            ActivityResultContract<?, O> contract = callbackAndContract.mContract;
            callback.onActivityResult(contract.parseResult(resultCode, data));
            mLaunchedKeys.remove(key);
        } else {
            // Remove any parsed pending result
            mParsedPendingResults.remove(key);
            // And add these pending results in their place
            mPendingResults.putParcelable(key, new ActivityResult(resultCode, data));
        }
    }

在Android13系統(tǒng)上,回到頁面時先ON_START會添加觀察者狈涮,再onActivityResult回調狐胎,沒有問題。
而在Android10系統(tǒng)上歌馍,回到頁面時先onActivityResult回調握巢,由于觀察者還未添加回隊列,所以使用key存儲bundle信息的松却,因此多次使用同一個registerForActivityResult時會丟失數(shù)據(jù)暴浦,使用key存儲bundle信息會覆蓋,只留下最后一次返回的bundle信息晓锻。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末歌焦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子砚哆,更是在濱河造成了極大的恐慌独撇,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躁锁,死亡現(xiàn)場離奇詭異纷铣,居然都是意外死亡,警方通過查閱死者的電腦和手機灿里,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門关炼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匣吊,你說我怎么就攤上這事儒拂。” “怎么了色鸳?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵社痛,是天一觀的道長。 經(jīng)常有香客問我命雀,道長蒜哀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任吏砂,我火速辦了婚禮撵儿,結果婚禮上乘客,老公的妹妹穿的比我還像新娘。我一直安慰自己淀歇,他們只是感情好易核,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浪默,像睡著了一般牡直。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纳决,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天碰逸,我揣著相機與錄音,去河邊找鬼阔加。 笑死饵史,一個胖子當著我的面吹牛,可吹牛的內容都是我干的掸哑。 我是一名探鬼主播约急,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苗分!你這毒婦竟也來了厌蔽?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤摔癣,失蹤者是張志新(化名)和其女友劉穎奴饮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體择浊,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡戴卜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了琢岩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片投剥。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖担孔,靈堂內的尸體忽然破棺而出江锨,到底是詐尸還是另有隱情,我是刑警寧澤糕篇,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布啄育,位于F島的核電站,受9級特大地震影響拌消,放射性物質發(fā)生泄漏挑豌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氓英。 院中可真熱鬧侯勉,春花似錦、人聲如沸债蓝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饰迹。三九已至,卻和暖如春余舶,著一層夾襖步出監(jiān)牢的瞬間啊鸭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工匿值, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赠制,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓挟憔,卻偏偏與公主長得像钟些,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绊谭,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內容