Android Firebase Cloud Message(FCM) 推送機(jī)制分析(下)

簡述

本文主要講述Android Firebase Cloud Message(FCM) 推送機(jī)制.

架構(gòu)

主要流程:

  1. 業(yè)務(wù)backend Call FCM backend
  2. FCM backend 推消息到 Android transport layer(ATL)
  3. ATL 推消息到 APP上的FCM SDK
  4. APP處理FCM SDK 回調(diào)


    diagram-FCM.png

流程

1. 業(yè)務(wù)backend Call FCM backend

請參看Android Firebase Cloud Message(FCM) 推送機(jī)制分析(上)

2. FCM backend 推消息到 Android transport layer(ATL)

請參看Android Firebase Cloud Message(FCM) 推送機(jī)制分析(上)

3. ATL 推消息到 APP上的FCM SDK

3.1 總覽

GoogleMobile Service(GMS) -> Firebase Cloud Message (FCM) -> APP FCM SDK
FirebaseInstanceIdReceiver 繼承 GMSCloudMessagingReceiver , 之后通過 Binder 機(jī)制 IPC 到目前 APP 中的 FCM SDK

3.2 FirebaseInstanceIdReceiver 接收message

3.2.1 FirebaseInstanceIdReceiver 通過 onMessageReceive() 接收 message

可以看到FirebaseInstanceIdReceiver 通過 onMessageReceive() 接收 message , 那又是哪里會去調(diào)用到 FirebaseInstanceIdReceiver#onMessageReceive() 呢?我們可以看到這是一個Override方法,我們?nèi)ジ割?CloudMessagingReceiver 找答案

FirebaseInstanceIdReceiver#onMessageReceive.png

3.2.2 CloudMessagingReceiver 調(diào)用 onMessageReceive()

可以看到 CloudMessagingReceiver 是繼承 BroadcastReceiver, 所以CloudMessagingReceiver 是個廣播接收器,所以我們先看 onReceive() 方法.
onReceive() 方法很短,重點(diǎn)在 new zzd(this, var2, var1, var3, var4) 這句代碼.

CloudMessagingReceiver#onReceive()

點(diǎn)進(jìn)去可以知道 zzd 是一個 Runnable , 我們重點(diǎn)查看 run() 方法可知里面只有一行代碼, 就是 CloudMessagingReceiver#zza(), 繼續(xù)追!
zzd#run()

因?yàn)檫@個 CloudMessagingReceiver#zza() 被隱藏了,萬幸我們在萬能的Github找到了答案. 里面會call 到 zza(@NonNull Context var1, @NonNull Intent var2)zzb(@NonNull Context var1, @NonNull Intent var2) 方法, 我們先看 zzb() 方法.
CloudMessagingReceiver#zza()

zzb() 我們終于如愿見到 onMessageReceive() 方法了!
CloudMessagingReceiver#onMessageReceive()

以上就是繼承GMS CloudMessagingReceiverFirebaseInstanceIdReceiver 接收廣播后,調(diào)用 onMessageReceive() 的過程

3.3 IPC 到目前 APP 中的 FCM SDK

3.3.1 FirebaseInstanceIdReceiver 觸發(fā)IPC

`FirebaseInstanceIdReceiver` 觸發(fā)IPC

3.3.2 FcmBroadcastProcessor 啟動 IPC

package com.google.firebase.iid;

/* compiled from: com.google.firebase:firebase-iid@@20.3.0 */
public class FcmBroadcastProcessor {
    private static WithinAppServiceConnection fcmServiceConn;
    private static final Object lock = new Object();
    private final Context context;
    private final Executor executor;

    public Task<Integer> process(Intent intent) {
         //準(zhǔn)備工作...
        // 啟動 FirebaseMessagingService
        return startMessagingService(this.context, intent);
    }

    public Task<Integer> startMessagingService(Context context2, Intent intent) {
        //準(zhǔn)備工作...
       // binder 綁定FirebaseMessagingService
        return bindToMessagingService(context2, intent);
    }

    private static Task<Integer> bindToMessagingService(Context context2, Intent intent) {
        // 劃重點(diǎn)!!!
        // 重點(diǎn)在這里, 實(shí)例化 WithinAppServiceConnection 后, sendIntent(intent) 完成IPC通信
        return getServiceConnection(context2, ServiceStarter.ACTION_MESSAGING_EVENT).sendIntent(intent).continueWith(FirebaseIidExecutors.directExecutor(), FcmBroadcastProcessor$$Lambda$3.$instance);
    }

    // binder 對象代理
    private static WithinAppServiceConnection getServiceConnection(Context context2, String str) {
        WithinAppServiceConnection withinAppServiceConnection;
        synchronized (lock) {
            if (fcmServiceConn == null) {
                fcmServiceConn = new WithinAppServiceConnection(context2, str);
            }
            withinAppServiceConnection = fcmServiceConn;
        }
        return withinAppServiceConnection;
    }
}

3.3.3 WithinAppServiceConnection#sendIntent()

package com.google.firebase.iid;

/* compiled from: com.google.firebase:firebase-iid@@20.3.0 */
public class WithinAppServiceConnection implements ServiceConnection {
    private WithinAppServiceBinder binder;
    private boolean connectionInProgress;
    private final Intent connectionIntent;
    private final Context context;
    private final Queue<BindRequest> intentQueue;
    private final ScheduledExecutorService scheduledExecutorService;

    // IPC發(fā)送Intent
    public synchronized Task<Void> sendIntent(Intent intent) {
        BindRequest bindRequest;
        if (Log.isLoggable("FirebaseInstanceId", 3)) {
            Log.d("FirebaseInstanceId", "new intent queued in the bind-strategy delivery");
        }
        bindRequest = new BindRequest(intent);
        bindRequest.arrangeTimeout(this.scheduledExecutorService);
        // 添加bindRequest 到 隊(duì)列
        this.intentQueue.add(bindRequest);
        // 消化隊(duì)列
        flushQueue();
        return bindRequest.getTask();
    }

    // 處理隊(duì)列
    private synchronized void flushQueue() {
        if (Log.isLoggable("FirebaseInstanceId", 3)) {
            Log.d("FirebaseInstanceId", "flush queue called");
        }
        // 循環(huán)清空隊(duì)列
        while (!this.intentQueue.isEmpty()) {
            if (Log.isLoggable("FirebaseInstanceId", 3)) {
                Log.d("FirebaseInstanceId", "found intent to be delivered");
            }
            // binder 為空時,再次創(chuàng)建
            if (this.binder == null || !this.binder.isBinderAlive()) {
                startConnectionIfNeeded();
                return;
            }
            if (Log.isLoggable("FirebaseInstanceId", 3)) {
                Log.d("FirebaseInstanceId", "binder is alive, sending the intent.");
            }
            // 劃重點(diǎn)!!
            // 關(guān)鍵地方,發(fā)送數(shù)據(jù)到APP FCM SDK
            this.binder.send(this.intentQueue.poll());
        }
    }

    private void startConnectionIfNeeded() {
        if (Log.isLoggable("FirebaseInstanceId", 3)) {
            StringBuilder sb = new StringBuilder(39);
            sb.append("binder is dead. start connection? ");
            sb.append(!this.connectionInProgress);
            Log.d("FirebaseInstanceId", sb.toString());
        }
        if (!this.connectionInProgress) {
            this.connectionInProgress = true;
            try {
                if (!ConnectionTracker.getInstance().bindService(this.context, this.connectionIntent, this, 65)) {
                    Log.e("FirebaseInstanceId", "binding to the service failed");
                    this.connectionInProgress = false;
                    finishAllInQueue();
                }
            } catch (SecurityException e) {
                Log.e("FirebaseInstanceId", "Exception while binding the service", e);
            }
        }
    }
}

4. APP處理FCM SDK 回調(diào) [2]

4.1 總覽

具體使用請參考 [在Android應(yīng)用中處理消息](需要翻墻)
FirebaseInstanceIdReceiver 收到廣播后,通過IPC start Service , FirebaseMessagingService 處理收到的 Intent 后, 經(jīng)過判斷當(dāng)前APP是否在前臺, 之后調(diào)用 FirebaseMessagingService #onMessageReceived() 或 啟動 APP的launchActivity.

4.2 FirebaseMessagingService 接收 Intent

已知一般處理前臺消息時,繼承 FirebaseMessagingService 后,重寫 onMessageReceived() 方法.

class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(msg: RemoteMessage) {
        // 前臺收到消息,會回調(diào)此方法傳遞RemoteMessage
        super.onMessageReceived(msg)
        Log.d(TAG, Thread.currentThread().name + " " + msg.notification?.body)
    }
}

接下來我們查看是如何接收并傳遞到這里的.
查看 FirebaseMessagingService 可知繼承 EnhancedIntentService , 本質(zhì)是 Service. 因?yàn)槭?Service , 我們重點(diǎn)查看 onStartCommand()方法,這個方式是 startService() 的時候會觸發(fā)的方法. FirebaseMessagingService 沒有這個重寫這個方法, 我們查看 EnhancedIntentService 中的這個方法.

package com.google.firebase.messaging;

/* compiled from: com.google.firebase:firebase-messaging@@20.3.0 */
public abstract class EnhancedIntentService extends Service {
    private Binder binder;
    final ExecutorService executor = FcmExecutors.newIntentHandleExecutor();
    private int lastStartId;
    private final Object lock = new Object();
    private int runningTasks = 0;

    // startService()的時候會call這個onStartCommand()方法
    public final int onStartCommand(Intent intent, int i, int i2) {
        synchronized (this.lock) {
            this.lastStartId = i2;
            this.runningTasks++;
        }
        // 獲取Intent
        Intent startCommandIntent = getStartCommandIntent(intent);
        if (startCommandIntent == null) {
            finishTask(intent);
            return 2;
        }
        // intent不為空就調(diào)用processIntent()處理Intent
        Task<Void> processIntent = processIntent(startCommandIntent);
        if (processIntent.isComplete()) {
            finishTask(intent);
            return 2;
        }
        processIntent.addOnCompleteListener(EnhancedIntentService$$Lambda$1.$instance, (OnCompleteListener<Void>) new EnhancedIntentService$$Lambda$2(this, intent));
        return 3;
    }

    /* access modifiers changed from: protected */
    public Intent getStartCommandIntent(Intent intent) {
        // 看源碼可知, FirebaseMessagingService重寫了這個方法
        return intent;
    }
}

根據(jù) EnhancedIntentService#onStartCommand() 方法可以知道, 當(dāng) FirebaseMessagingService 被call startService時, 通過 getStartCommandIntent() 獲取Intent, 之后通過 processIntent(startCommandIntent) 處理Intent.
我們接著看 getStartCommandIntent() 是如何獲取Intent的.

package com.google.firebase.messaging;

/* compiled from: com.google.firebase:firebase-messaging@@20.3.0 */
public class FirebaseMessagingService extends EnhancedIntentService {
    public static final String ACTION_DIRECT_BOOT_REMOTE_INTENT = "com.google.firebase.messaging.RECEIVE_DIRECT_BOOT";
    private static final Queue<String> recentlyReceivedMessageIds = new ArrayDeque(10);

    /* access modifiers changed from: protected */
    public Intent getStartCommandIntent(Intent intent) {
        // 通過ServiceStarter獲取Intent type的MessagingEvent
        return ServiceStarter.getInstance().getMessagingEvent();
    }
}
package com.google.firebase.messaging;

@KeepForSdk
public class ServiceStarter {
    public static final int SUCCESS = -1;
    @KeepForSdk
    public static final int ERROR_UNKNOWN = 500;
    private static ServiceStarter instance;
    @GuardedBy("this")
    @Nullable
    private String firebaseMessagingServiceClassName = null;
    private Boolean hasWakeLockPermission = null;
    private Boolean hasAccessNetworkStatePermission = null;
    private final Queue<Intent> messagingEvents;

    static synchronized ServiceStarter getInstance() {
        if (instance == null) {
            ServiceStarter var0 = new ServiceStarter();
            instance = var0;
        }

        return instance;
    }

    private ServiceStarter() {
        ArrayDeque var1 = new ArrayDeque();
        this.messagingEvents = var1;
    }

    @MainThread
    Intent getMessagingEvent() {
        // 獲取Messaging Event intent
        return (Intent)this.messagingEvents.poll();
    }
}

根據(jù)源碼可知, ServiceStarter 是一個單例,一個進(jìn)程只有一個 ServiceStarter.而且只在MainThread做處理.

4.3 FirebaseMessagingService 處理 Intent

拿到Intent之后就是處理Intent了,由上面可以知道是通過 processIntent() 去處理獲取到的Intent

package com.google.firebase.messaging;

/* compiled from: com.google.firebase:firebase-messaging@@20.3.0 */
public abstract class EnhancedIntentService extends Service {
    private Binder binder;
    final ExecutorService executor = FcmExecutors.newIntentHandleExecutor();
    private int lastStartId;
    private final Object lock = new Object();
    private int runningTasks = 0;
    // 4. abstract 方法,看FirebaseMessagingService
    public abstract void handleIntent(Intent intent);

    public boolean handleIntentOnMainThread(Intent intent) {
        return false;
    }

    /* access modifiers changed from: private */
    // 1.處理Intent
    public Task<Void> processIntent(Intent intent) {
        // 默認(rèn)返回false,跳過
        if (handleIntentOnMainThread(intent)) {
            return Tasks.forResult(null);
        }
        TaskCompletionSource taskCompletionSource = new TaskCompletionSource();
        // 2.重點(diǎn)是這里,執(zhí)行EnhancedIntentService$$Lambda$0 runnable
        this.executor.execute(new EnhancedIntentService$$Lambda$0(this, intent, taskCompletionSource));
        return taskCompletionSource.getTask();
    }

    /* access modifiers changed from: package-private */
    public final /* synthetic */ void lambda$onStartCommand$1$EnhancedIntentService(Intent intent, Task task) {
        finishTask(intent);
    }

    /* access modifiers changed from: package-private */
    public final /* synthetic */ void lambda$processIntent$0$EnhancedIntentService(Intent intent, TaskCompletionSource taskCompletionSource) {
        try {
            // 3.匿名函數(shù),繞個圈之后會到這里, handleIntent(intent)
            handleIntent(intent);
        } finally {
            taskCompletionSource.setResult(null);
        }
    }
}

processIntent() 之后會去調(diào)用 handleIntent() , FirebaseMessagingService 實(shí)現(xiàn)了 handleIntent() 方法.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.google.firebase.messaging;


public class FirebaseMessagingService extends EnhancedIntentService {
    @NonNull
    public static final String ACTION_DIRECT_BOOT_REMOTE_INTENT = "com.google.firebase.messaging.RECEIVE_DIRECT_BOOT";
    private static final Queue<String> recentlyReceivedMessageIds;

    public void handleIntent(@NonNull Intent intent) {
        String var2 = intent.getAction();
        // 當(dāng)action不等于"com.google.android.c2dm.intent.RECEIVE" 和 "com.google.firebase.messaging.RECEIVE_DIRECT_BOOT" 時進(jìn)去
        // 所以我們跳過
        if (!"com.google.android.c2dm.intent.RECEIVE".equals(var2) && !"com.google.firebase.messaging.RECEIVE_DIRECT_BOOT".equals(var2)) {
            if ("com.google.firebase.messaging.NEW_TOKEN".equals(var2)) {
                this.onNewToken(intent.getStringExtra("token"));
            } else {
                String var3 = String.valueOf(intent.getAction());
                String intent1 = "Unknown intent action: ";
                if (var3.length() != 0) {
                    var3 = intent1.concat(var3);
                } else {
                    var3 = new String(intent1);
                }

                Log.d("FirebaseMessaging", var3);
            }
        } else {
            // 已知FCM的messaging action是"com.google.android.c2dm.intent.RECEIVE"
            this.handleMessageIntent(intent);
        }
    }

    // 處理messaging Intent
    private void handleMessageIntent(Intent var1) {
        // 之前沒有接收的話,會call this.passMessageIntentToSdk()
        if (!this.alreadyReceivedMessage(var1.getStringExtra("google.message_id"))) {
            this.passMessageIntentToSdk(var1);
        }
    }
    // 將messaging intent 交給 SDK
    private void passMessageIntentToSdk(Intent var1) {
        String var2 = var1.getStringExtra("message_type");
        if (var2 == null) {
            var2 = "gcm";
        }
        // 我們的message_type 就是 gcm , 所以var3 = 0
        byte var3;
        label37: {
            switch(var2.hashCode()) {
            case -2062414158:
                if (var2.equals("deleted_messages")) {
                    var3 = 1;
                    break label37;
                }
                break;
            case 102161:
                if (var2.equals("gcm")) {
                    var3 = 0;
                    break label37;
                }
                break;
            case 814694033:
                if (var2.equals("send_error")) {
                    var3 = 3;
                    break label37;
                }
                break;
            case 814800675:
                if (var2.equals("send_event")) {
                    var3 = 2;
                    break label37;
                }
            }

            var3 = -1;
        }
        
        // 我們的message_type 就是 gcm , 所以var3 = 0
        switch(var3) {
        case 0:
            // MessagingAnalytics FCM數(shù)據(jù)分析,不用管
            MessagingAnalytics.logNotificationReceived(var1);
            // 分發(fā)消息!!!
            this.dispatchMessage(var1);
            return;
        case 1:
            this.onDeletedMessages();
            return;
        case 2:
            this.onMessageSent(var1.getStringExtra("google.message_id"));
            return;
        case 3:
            var2 = this.getMessageId(var1);
            SendException var6 = new SendException(var1.getStringExtra("error"));
            this.onSendError(var2, var6);
            return;
        default:
            String var4 = "Received message with unknown type: ";
            if (var2.length() != 0) {
                var4 = var4.concat(var2);
            } else {
                String var5 = new String(var4);
                var4 = var5;
            }

            Log.w("FirebaseMessaging", var4);
        }
    }

    // 分發(fā)已經(jīng)收到的message
    private void dispatchMessage(Intent var1) {
        Bundle var2 = var1.getExtras();
        if (var2 == null) {
            var2 = new Bundle();
        }
        // 將收到的intent bundle值進(jìn)行加工
        var2.remove("androidx.content.wakelockid");
        if (NotificationParams.isNotification(var2)) {
            NotificationParams var3 = new NotificationParams(var2);
            ExecutorService var4 = FcmExecutors.newNetworkIOExecutor();
            DisplayNotification var5 = new DisplayNotification(this, var3, var4);
            boolean var7 = false;

            boolean var10;
            try {
                var7 = true;
               // 判斷APP是否在后臺,
               // true,則APP在后臺, 系統(tǒng)自己顯示出來
               // false,則APP在前臺,APP自己處理
                var10 = var5.handleNotification();
                var7 = false;
            } finally {
                if (var7) {
                    var4.shutdown();
                }
            }

            if (var10) {
              // true, 系統(tǒng)需要自己顯示通知
                var4.shutdown();
                return;
            }

            var4.shutdown();
            if (MessagingAnalytics.shouldUploadScionMetrics(var1)) {
                MessagingAnalytics.logNotificationForeground(var1);
            }
        }

        RemoteMessage var9 = new RemoteMessage(var2);
        // APP在前臺, APP自己處理intent
        this.onMessageReceived(var9);
    }
}
package com.google.firebase.messaging;

class DisplayNotification {
    private final Executor networkIoExecutor;
    private final Context context;
    private final NotificationParams params;

    boolean handleNotification() {
        if (this.params.getBoolean("gcm.n.noui")) {
            return true;
        } else if (this.isAppForeground()) {
            // 如果APP在前臺,則返回false
            return false;
        } else {
            // APP在后臺,則系統(tǒng)就開始build notification出來顯示
            //題外話:細(xì)看可以知道, ImageDownload 會限制大小為1M
            ImageDownload var1 = this.startImageDownloadInBackground();
            DisplayNotificationInfo var2 = CommonNotificationBuilder.createNotificationInfo(this.context, this.params);
            this.waitForAndApplyImageDownload(var2.notificationBuilder, var1);
            this.showNotification(var2);
            return true;
        }
    }

    // 判斷是否APP在前臺
    private boolean isAppForeground() {
        if (((KeyguardManager)this.context.getSystemService("keyguard")).inKeyguardRestrictedInputMode()) {
            return false;
        } else {
            if (!PlatformVersion.isAtLeastLollipop()) {
                SystemClock.sleep(10L);
            }
            //獲取當(dāng)前進(jìn)程Pid
            int var1 = Process.myPid();
            //獲取所有的正在運(yùn)行的進(jìn)程信息
            List var3 = ((ActivityManager)this.context.getSystemService("activity")).getRunningAppProcesses();
            if (var3 != null) {
                Iterator var4 = var3.iterator();

                // 遍歷所有進(jìn)程信息
                while(var4.hasNext()) {
                    RunningAppProcessInfo var2 = (RunningAppProcessInfo)var4.next();
                    // 如果正在運(yùn)行的進(jìn)程Pid 等于 當(dāng)前進(jìn)程Pid
                    // 而且進(jìn)程的importance等于100
                    // 則返回true
                    // 否則返回false
                    if (var2.pid == var1) {
                        if (var2.importance == 100) {
                            return true;
                        }
                        return false;
                    }
                }
            }

            return false;
        }
    }
}

所以綜上可知,其實(shí)前臺后臺的處理,其實(shí)是FirebaseMessagingService 收到Intent后做的二次處理,當(dāng)APP在前臺的時候,就會回調(diào)onMessageReceived()方法,否則就會系統(tǒng)自行顯示出來.所以如果有特定需要可以重寫FirebaseMessagingService 或者 直接繼承 EnhancedIntentService 去做一系列操作.

總結(jié)

通過一系列分析,我們了解到了以下幾點(diǎn):

  1. Firebase 是如何跟FCM SDK通信,而FCM SDK又是如何讓通知欄收到通知的,
  2. 明白了前后臺接收信息的機(jī)制,
  3. 為什么國內(nèi)ROM kill APP后收不到message,(國內(nèi)ROM從最近程序中移走APP,會直接kill APP,所以service也被kill了,處理不了IPC過來的intent)

引用

  1. FCM 架構(gòu)概覽
  2. 在Android應(yīng)用中處理消息
  3. Github Firebase iid 源碼
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喇闸,一起剝皮案震驚了整個濱河市站宗,隨后出現(xiàn)的幾起案子实檀,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆虚,死亡現(xiàn)場離奇詭異朗若,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蓝角,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門阱穗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人使鹅,你說我怎么就攤上這事揪阶。” “怎么了患朱?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵鲁僚,是天一觀的道長。 經(jīng)常有香客問我裁厅,道長冰沙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任执虹,我火速辦了婚禮拓挥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袋励。我一直安慰自己侥啤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布茬故。 她就那樣靜靜地躺著锣吼,像睡著了一般饱岸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天斩个,我揣著相機(jī)與錄音,去河邊找鬼讳推。 笑死析砸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垮庐。 我是一名探鬼主播松邪,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哨查!你這毒婦竟也來了逗抑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎邮府,沒想到半個月后荧关,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褂傀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年忍啤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仙辟。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡同波,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叠国,到底是詐尸還是另有隱情未檩,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布粟焊,位于F島的核電站冤狡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吆玖。R本人自食惡果不足惜筒溃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沾乘。 院中可真熱鬧怜奖,春花似錦、人聲如沸翅阵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掷匠。三九已至滥崩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讹语,已是汗流浹背钙皮。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顽决,地道東北人短条。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像才菠,于是被迫代替她去往敵國和親茸时。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容