Android登錄攔截場景-多種實現方式

登錄攔截與放行是大部分App開發(fā)都會遇到的一個場景钱床,如果你的App有游客模式誉券,但是部分高級功能需要登錄之后才能使用击吱。
那么我們就需要在用戶點擊這個操作的時候校驗是否登錄虑椎,當登錄完成之后再跳轉到指定的頁面或彈窗榆鼠。如果這些入口很多的話纲爸,那么我們就需要到處寫這些邏輯。比較初級的用法是使用消息總線妆够,當登錄完成之后發(fā)送對應key消息识啦,然后去完成對應key的事件。
有沒有一種更簡單的方式神妹,集中統(tǒng)一方便的管理登錄攔截再放行這一個場景颓哮。
下面我們一起來看一看具體的方案。

一鸵荠、方法池方案

本質就是把你要攔截執(zhí)行的方法作為一個對象冕茅,存入到一個方法池列表中,使用完之后再自動釋放掉蛹找。(需要注意生命周期姨伤,當頁面Destory的時候要主動釋放)
先定義方法對象

public abstract class IFunction {

    public String functionName;

    public IFunction(String functionName) {

        this.functionName = functionName;
    }

    protected abstract void function();

}

方法池:

public class FunctionManager {

    private static FunctionManager functionManager;

    private static HashMap<String, IFunction> mFunctionMap;

    public FunctionManager() {
        mFunctionMap = new HashMap<>();

    }

    public static FunctionManager get() {
        if (functionManager == null) {
            functionManager = new FunctionManager();
        }
        return functionManager;
    }


    /**
     * 添加方法
     */
    public FunctionManager addFunction(IFunction function) {
        if (mFunctionMap != null) {
            mFunctionMap.put(function.functionName, function);
        }
        return this;
    }


    /**
     * 執(zhí)行方法
     */
    public void invokeFunction(String key) {
        if (TextUtils.isEmpty(key)) {
            return;
        }
        if (mFunctionMap != null) {
            IFunction function = mFunctionMap.get(key);

            if (function != null) {
                function.function();
                //用完移除掉
                removeFunction(key);
            } else {
                try {
                    throw new RuntimeException("function not found");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 使用之后移除相關的緩存
     */
    public void removeFunction(String key) {
        if (mFunctionMap != null) {
            mFunctionMap.remove(key);
        }
    }

}

使用的時候也是非常簡單

    private fun checkLogin() {
        if (SP().getString(Constants.KEY_TOKEN, "").checkEmpty()) {

            FunctionManager.get().addFunction(object : IFunction("gotoProfilePage") {
                override fun function() {
                    gotoProfilePage()
                }
            })

            gotoLoginPage()

        } else {

            gotoProfilePage()
        }
    }

登錄完成之后,我們需要手動調用

    //方法池的方式
    FunctionManager.get().invokeFunction("gotoProfilePage")

這樣就可以觸發(fā)回調完成登錄攔截的功能了庸疾。
如果想對游客的校驗也做一個封裝乍楚,也可以在 FunctionManager 中定義好,可以自由擴展届慈。

二徒溪、消息回調方案

其本質是通過消息總線實現凌箕,通過管理類發(fā)送消息,接收消息词渤,通過回調的方式去執(zhí)行攔截的方法牵舱。相比前者,他的好處是不需要我們處理生命周期缺虐。
我們指定好統(tǒng)一的消息key之后芜壁,都通過這個key來處理登錄完成的邏輯

public class FunctionManager {

    private static FunctionManager functionManager;

    private static HashMap<String, Function> mFunctionMap;

    public FunctionManager() {
        mFunctionMap = new HashMap<>();

    }

    public static FunctionManager get() {
        if (functionManager == null) {
            functionManager = new FunctionManager();
        }
        return functionManager;
    }

    public void addLoginCallback(LifecycleOwner owner, ILoginCallback callback) {
        LiveEventBus.get("login", Boolean.class).observe(owner, aBoolean -> {
            if (aBoolean != null && aBoolean) {
                callback.callback();
            }
        });
    }

    public interface ILoginCallback {
        void callback();
    }

    public void finishLogin() {
        LiveEventBus.get("login").post(true);
    }
}

 FunctionManager.get().addLoginCallback(this) {
            gotoProfilePage()
        }

登錄完成之后,我們需要手動調用

    //方法池的方式
    FunctionManager.get().finishLogin()

這樣就可以觸發(fā)回調完成登錄攔截的功能了高氮。
和方法池的方式又異曲同工之妙慧妄。

三、Intent的方案

其實不使用一些容器剪芍,我們原始的使用Intent也是可以實現邏輯的塞淹。
原理是通過登錄成功之后startActivity啟動自己的頁面,然后通過 onNewIntent 拿到對應的操作意圖去執(zhí)行對應的操作罪裹。
只是需要我們把原始的意圖封裝到啟動自己的Intent中饱普。

    fun switchPage3() {
            f (!LoginManager.isLogin()) {
            val intent = Intent(mActivity, Demo3Activity::class.java)
            intent.addCategory(switch_tab3)

            gotoLoginPage(intent)


        } else {
                switchFragment(3)
        }
    }

    //把原始意圖當參數傳遞
    fun gotoLoginPage(targetIntent: Intent) {
        val intent = Intent(mActivity, LoginDemoActivity::class.java)
        intent.putExtra("targetIntent", targetIntent)
        startActivity(intent)
    }

    //通過這樣的方式可以拿到攜帶的數據
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        YYLogUtils.w("收到newintent:" + intent.toString())
        val categories = intent.categories

        when (categories.take(1)[0]) {
            switch_tab1 -> {
                switchFragment(1)
            }
            switch_tab2 -> {
                switchFragment(2)
            }
            switch_tab3 -> {
                switchFragment(3)
            }
        }

    }

那么在Login頁面登錄完成之后再啟動當前頁面即可把攜帶的數據傳遞回來,通過newIntent就可以做對應的操作状共。

四套耕、動態(tài)代理+Hook的方案

如果說Intent的方案還需要我們手動的處理跳轉,那么此方案就是升級版峡继,自動的攔截跳轉冯袍,之后的放行方案我們還是通過 Intent 與 onNewIntent 的回調來處理。
難點就是如何使用Hook代替Activity的啟動碾牌。

public class DynamicProxyUtils {

    //修改啟動模式
    public static void hookAms() {
        try {

            Field singletonField;
            Class<?> iActivityManager;
            // 1康愤,獲取Instrumentation中調用startActivity(,intent,)方法的對象
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                // 10.0以上是ActivityTaskManager中的IActivityTaskManagerSingleton
                Class<?> activityTaskManagerClass = Class.forName("android.app.ActivityTaskManager");
                singletonField = activityTaskManagerClass.getDeclaredField("IActivityTaskManagerSingleton");
                iActivityManager = Class.forName("android.app.IActivityTaskManager");
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                // 8.0,9.0在ActivityManager類中IActivityManagerSingleton
                Class activityManagerClass = ActivityManager.class;
                singletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
                iActivityManager = Class.forName("android.app.IActivityManager");
            } else {
                // 8.0以下在ActivityManagerNative類中 gDefault
                Class<?> activityManagerNative = Class.forName("android.app.ActivityManagerNative");
                singletonField = activityManagerNative.getDeclaredField("gDefault");
                iActivityManager = Class.forName("android.app.IActivityManager");
            }
            singletonField.setAccessible(true);
            Object singleton = singletonField.get(null);

            // 2,獲取Singleton中的mInstance舶吗,也就是要代理的對象
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);

            Method getMethod = singletonClass.getDeclaredMethod("get");
            Object mInstance = getMethod.invoke(singleton);
            if (mInstance == null) {
                return;
            }

            //開始動態(tài)代理
            Object proxy = Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class[]{iActivityManager},
                    new AmsHookBinderInvocationHandler(mInstance));

            //現在替換掉這個對象
            mInstanceField.set(singleton, proxy);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //動態(tài)代理執(zhí)行類
    public static class AmsHookBinderInvocationHandler implements InvocationHandler {

        private Object obj;

        public AmsHookBinderInvocationHandler(Object rawIActivityManager) {
            obj = rawIActivityManager;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if ("startActivity".equals(method.getName())) {

                Intent raw;
                int index = 0;
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                        break;
                    }
                }

                //原始意圖
                raw = (Intent) args[index];
                YYLogUtils.w("原始意圖:" + raw);


                //設置新的Intent-直接制定LoginActivity
                Intent newIntent = new Intent();
                String targetPackage = "com.guadou.kt_demo";
                ComponentName componentName = new ComponentName(targetPackage, LoginDemoActivity.class.getName());
                newIntent.setComponent(componentName);

                YYLogUtils.w("改變了Activity啟動");

                args[index] = newIntent;

                YYLogUtils.w("攔截activity的啟動成功" + " --->");

                return method.invoke(obj, args);

            }

            //如果不是攔截的startActivity方法征冷,就直接放行
            return method.invoke(obj, args);
        }

    }
}

使用的時候我們需要啟動代理,在跳轉頁面的時候就會自動攔截了裤翩。

    mBtnProfile.click {

        //啟動動態(tài)代理
         DynamicProxyUtils.hookAms()

        gotoActivity<ProfileDemoActivity>()
    }

之后的邏輯和上面的Intent方案是一樣的回調處理资盅,走 onNewIntent 里面處理。
目前的Hook只兼容到Android12踊赠。還沒有看13的源碼不知道有沒有變動呵扛。并且此方案只能適用于頁面的跳轉,有些場景比如切換Tab筐带、ViewPager的情況下今穿,是無法實現攔截的。
如果不想全部的頁面都攔截伦籍,大家也可以自行實現白名單的管理蓝晒,只攔截部分的頁面腮出。
但相對其他方案來說其實不是很好用,這樣的自動感覺還不如全手動的Intent靈活芝薇。

五胚嘲、Java線程方案

相對其他的方案,此方案的思路就比較清奇洛二,利用線程的等待與恢復來實現馋劈,當我們跳轉到登錄頁面的時候我們讓線程等待,然后等待登錄完成之后我們再恢復等待晾嘶。

/**
 * 登錄攔截的線程管理
 */
public class LoginInterceptThreadManager  {

    private static LoginInterceptThreadManager threadManager;

    private static final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    private static final Handler mHandler = new Handler();

    private LoginInterceptThreadManager() {
    }

    public static LoginInterceptThreadManager get() {
        if (threadManager == null) {
            threadManager = new LoginInterceptThreadManager();
        }

        return threadManager;
    }

    /**
     * 檢查是否需要登錄
     */
    public void checkLogin(Runnable nextRunnable, Runnable loginRunnable) {

        if (LoginManager.isLogin()) {
            //已經登錄
            mHandler.post(nextRunnable);
            return;
        }

        //如果沒有登錄-先去登錄頁面
        mHandler.post(loginRunnable);


        singleThreadExecutor.execute(() -> {

            try {
                YYLogUtils.w("開始運行-停止");

                synchronized (singleThreadExecutor) {
                    singleThreadExecutor.wait();

                    YYLogUtils.w("等待notifyAll完成了,繼續(xù)執(zhí)行");

                    if (LoginManager.isLogin()) {
                        mHandler.post(nextRunnable);
                    }
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

    }

    public void loginFinished() {
        if (mHandler == null) return;
        if (singleThreadExecutor == null) return;

        synchronized (singleThreadExecutor) {
            singleThreadExecutor.notifyAll();
        }
    }
    
}

使用的時候也簡單

    private fun checkLogin() {
        LoginInterceptThreadManager.get().checkLogin( {
            gotoProfilePage()
        }, {
            gotoLoginPage()
        })
    }

    private fun gotoLoginPage() {
        gotoActivity<LoginDemoActivity>()
    }

    private fun gotoProfilePage() {
        gotoActivity<ProfileDemoActivity>()
    }

登錄完成之后妓雾,我們需要手動調用

    //方法池的方式
    oginInterceptThreadManager.get().loginFinished()

這樣就可以觸發(fā)回調完成登錄攔截的功能了。

六垒迂、Kotlin協(xié)程方案

既然線程都可以械姻,沒道理協(xié)程不能使用這樣的方案,協(xié)程也可以使用等待恢復的方案机断,還能使用協(xié)程通信的方案楷拳,開啟兩個協(xié)程,然后當登錄完成之后去通知其中的接收協(xié)程去繼續(xù)執(zhí)行毫缆。

class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {

    companion object {
        private var instance: LoginInterceptCoroutinesManager? = null
            get() {
                if (field == null) {
                    field = LoginInterceptCoroutinesManager()
                }
                return field
            }

        fun get(): LoginInterceptCoroutinesManager {
            return instance!!
        }
    }

    private lateinit var mCancellableContinuation: CancellableContinuation<Boolean>

    fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {

        launch {

            if (LoginManager.isLogin()) {
                nextAction()
                return@launch
            }

            loginAction()

            val isLogin = suspendCancellableCoroutine<Boolean> {

                mCancellableContinuation = it

                YYLogUtils.w("暫停協(xié)程唯竹,等待喚醒")
            }


            YYLogUtils.w("已經恢復協(xié)程乐导,繼續(xù)執(zhí)行")
            if (isLogin) {
                nextAction()
            }

        }
    }

    fun loginFinished() {

        if (!this@LoginInterceptCoroutinesManager::mCancellableContinuation.isInitialized) return

        if (mCancellableContinuation.isCancelled) return

        mCancellableContinuation.resume(LoginManager.isLogin(), null)

    }

    override fun onDestroy(owner: LifecycleOwner) {
        YYLogUtils.w("LoginInterceptCoroutinesManager - onDestroy")

        mCancellableContinuation.cancel()
        cancel()
    }

}

使用也比較簡單

       //協(xié)程的方式
        mBtnProfile2.click {
            LoginInterceptCoroutinesManager.get().checkLogin(loginAction = {
                gotoLoginPage()
            }, nextAction = {
                gotoProfilePage()
            })

        }

登錄完成之后苦丁,我們需要手動調用

    //方法池的方式
    oginInterceptThreadManager.get().loginFinished()

這樣就可以觸發(fā)回調完成登錄攔截的功能了。
協(xié)程另一種方案就是通知的方式:

class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {

    companion object {
        private var instance: LoginInterceptCoroutinesManager? = null
            get() {
                if (field == null) {
                    field = LoginInterceptCoroutinesManager()
                }
                return field
            }

        fun get(): LoginInterceptCoroutinesManager {
            return instance!!
        }
    }

    private val channel = Channel<Boolean>()


    fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {

        launch {

            if (LoginManager.isLogin()) {
                nextAction()
                return@launch
            }

            loginAction()


            val isLogin = channel.receive()

            YYLogUtils.w("收到消息:" + isLogin)

            if (isLogin) {
                nextAction()
            }
        }
    }

    fun loginFinished() {

        launch {

            async {
                YYLogUtils.w("發(fā)送消息:" + LoginManager.isLogin())
                channel.send(LoginManager.isLogin())
            }


        }
    }

    override fun onDestroy(owner: LifecycleOwner) {
        cancel()
    }

}

使用起來和暫臀锉郏恢復的方案是一樣的旺拉。

七、Aop切面方案

除了這些方案之外棵磷,網上比較流行的就是面向切面AOP的方案蛾狗。
需要我們集成 AspectJ 框架來實現。
使用的時候就需要定義一個自定義的注解仪媒,然后圍繞這個注解做一些操作沉桌。

//不需要回調的處理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}

除了注解的類

@Aspect
public class LoginAspect {

    @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.Login)")
    public void Login() {
    }

    @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.LoginCallback)")
    public void LoginCallback() {
    }

    //帶回調的注解處理
    @Around("LoginCallback()")
    public void loginCallbackJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        YYLogUtils.w("走進AOP方法-LoginCallback()");
        Signature signature = joinPoint.getSignature();

        if (!(signature instanceof MethodSignature)){
            throw new RuntimeException("該注解只能用于方法上");
        }

        LoginCallback loginCallback = ((MethodSignature) signature).getMethod().getAnnotation(LoginCallback.class);
        if (loginCallback == null) return;

        //判斷當前是否已經登錄
        if (LoginManager.isLogin()) {
            joinPoint.proceed();

        } else {
            LifecycleOwner lifecycleOwner = (LifecycleOwner) joinPoint.getTarget();

            LiveEventBus.get("login").observe(lifecycleOwner, new Observer<Object>() {
                @Override
                public void onChanged(Object integer) {
                    try {
                        joinPoint.proceed();
                        LiveEventBus.get("login").removeObserver(this);

                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                        LiveEventBus.get("login").removeObserver(this);
                    }
                }
            });

            LoginManager.gotoLoginPage();
        }
    }

    //不帶回調的注解處理
    @Around("Login()")
    public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        YYLogUtils.w("走進AOP方法-Login()");
        Signature signature = joinPoint.getSignature();

        if (!(signature instanceof MethodSignature)){
            throw new RuntimeException("該注解只能用于方法上");
        }

        Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);
        if (login == null) return;

        //判斷當前是否已經登錄
        if (LoginManager.isLogin()) {
            joinPoint.proceed();
        } else {
            //如果未登錄,去登錄頁面
            LoginManager.gotoLoginPage();
        }


    }
}

定義一個工具類來定義一些固定的方法:

object LoginManager {

    @JvmStatic
    fun isLogin(): Boolean {
        val token = SP().getString(Constants.KEY_TOKEN, "")
        YYLogUtils.w("LoginManager-token:$token")
        val checkEmpty = token.checkEmpty()
        return !checkEmpty
    }

    @JvmStatic
    fun gotoLoginPage() {
        commContext().gotoActivity<LoginDemoActivity>()
    }
}

到這里我們就能使用AOP來攔截了算吩。我們把需要攔截的方法使用我們的自定義注解來標記留凭。然后我們的處理器就會對這個注解做一些圍繞的操作。

    override fun init() {

        mBtnCleanToken.click {
            SP().remove(Constants.KEY_TOKEN)
            toast("清除成功")
        }

        mBtnProfile.click {

           //不帶回調的登錄方式
           gotoProfilePage2()
        }

    }

    @Login
    private fun gotoProfilePage2() {
        gotoActivity<ProfileDemoActivity>()
    }

可以看到內部也是通過消息總線來執(zhí)行繼續(xù)操作的邏輯的偎巢,我們需要在登錄完成之后發(fā)送這個通知才行蔼夜。

八、攔截器的方案

最后一種方案是基于責任鏈模式的改版压昼,自定義攔截器實現的求冷,和默認的責任鏈是有些差異的瘤运。其中沒有用到參數的傳遞。
原理是我們定義2層攔截匠题,一個是校驗登錄拯坟,一個是執(zhí)行邏輯。當我們校驗登錄不通過的時候就會跳轉到登錄頁面韭山,當登錄完成之后似谁,我們繼續(xù)攔截器就會走到執(zhí)行邏輯。間接的完成一個登錄攔截的功能掠哥。
攔截器的定義

object LoginInterceptChain {

    private var index: Int = 0

    private val interceptors by lazy(LazyThreadSafetyMode.NONE) {
        ArrayList<Interceptor>(2)
    }

    //默認初始化Login的攔截器
    private val loginIntercept = LoginInterceptor()


    // 執(zhí)行攔截器巩踏。
    fun process() {

        if (interceptors.isEmpty()) return

        when (index) {
            in interceptors.indices -> {
                val interceptor = interceptors[index]
                index++
                interceptor.intercept(this)
            }

            interceptors.size -> {
                clearAllInterceptors()
            }
        }
    }

    // 添加一個攔截器。
    fun addInterceptor(interceptor: Interceptor): LoginInterceptChain {
        //默認添加Login判斷的攔截器
        if (!interceptors.contains(loginIntercept)) {
            interceptors.add(loginIntercept)
        }

        if (!interceptors.contains(interceptor)) {
            interceptors.add(interceptor)
        }

        return this
    }


    //放行登錄判斷攔截器
    fun loginFinished() {
        if (interceptors.contains(loginIntercept) && interceptors.size > 1) {
            loginIntercept.loginfinished()
        }
    }

    //清除全部的攔截器
    private fun clearAllInterceptors() {
        index = 0
        interceptors.clear()
    }

}

校驗登錄的攔截器:

/**
 * 判斷是否登錄的攔截器
 */
class LoginInterceptor : BaseLoginInterceptImpl() {

    override fun intercept(chain: LoginInterceptChain) {
        super.intercept(chain)

        if (LoginManager.isLogin()) {
            //如果已經登錄 -> 放行, 轉交給下一個攔截器
            chain.process()
        } else {
            //如果未登錄 -> 去登錄頁面
            LoginDemoActivity.startInstance()
        }
    }


    fun loginfinished() {
        //如果登錄完成续搀,調用方法放行到下一個攔截器
        mChain?.process()
    }
}

繼續(xù)執(zhí)行的攔截器:

/**
 * 登錄完成下一步的攔截器
 */
class LoginNextInterceptor(private val action: () -> Unit) : BaseLoginInterceptImpl() {

    override fun intercept(chain: LoginInterceptChain) {
        super.intercept(chain)

        if (LoginManager.isLogin()) {
            //如果已經登錄執(zhí)行當前的任務
            action()
        }

        mChain?.process()
    }


}

使用的時候我們使用攔截器管理即可

    private fun checkLogin() {
        LoginInterceptChain.addInterceptor(LoginNextInterceptor {
            gotoProfilePage()
        }).process()
    }

登錄完成之后記得手動放行哦

    //攔截器放行
    LoginInterceptChain.loginFinished()

這樣就完成了登錄攔截的功能了塞琼。

來自:鏈接:https://juejin.cn/post/7143040409558581262

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市禁舷,隨后出現的幾起案子彪杉,更是在濱河造成了極大的恐慌,老刑警劉巖牵咙,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件派近,死亡現場離奇詭異,居然都是意外死亡洁桌,警方通過查閱死者的電腦和手機渴丸,發(fā)現死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來另凌,“玉大人谱轨,你說我怎么就攤上這事》托唬” “怎么了土童?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長工坊。 經常有香客問我献汗,道長,這世上最難降的妖魔是什么王污? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任罢吃,我火速辦了婚禮,結果婚禮上玉掸,老公的妹妹穿的比我還像新娘刃麸。我一直安慰自己,他們只是感情好司浪,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布泊业。 她就那樣靜靜地躺著把沼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吁伺。 梳的紋絲不亂的頭發(fā)上饮睬,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音篮奄,去河邊找鬼捆愁。 笑死,一個胖子當著我的面吹牛窟却,可吹牛的內容都是我干的昼丑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼夸赫,長吁一口氣:“原來是場噩夢啊……” “哼菩帝!你這毒婦竟也來了?” 一聲冷哼從身側響起茬腿,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤呼奢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后切平,有當地人在樹林里發(fā)現了一具尸體握础,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年悴品,在試婚紗的時候發(fā)現自己被綠了禀综。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡他匪,死狀恐怖菇存,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情邦蜜,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布亥至,位于F島的核電站悼沈,受9級特大地震影響,放射性物質發(fā)生泄漏姐扮。R本人自食惡果不足惜絮供,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茶敏。 院中可真熱鬧壤靶,春花似錦、人聲如沸惊搏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至向拆,卻和暖如春亚茬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浓恳。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工刹缝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颈将。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓梢夯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晴圾。 傳聞我的和親對象是個殘疾皇子厨疙,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容