LocalBroadcastManager本地廣播原理解析

之前有被問到過Android普通廣播和本地廣播的區(qū)別,所以打算分析下本地廣播的實現(xiàn)原理以及簡單結(jié)束兩者的區(qū)別豁鲤,算是對自我的一此源碼學(xué)習(xí)總結(jié)茎杂。

目前官網(wǎng)文檔上是說LocalBroadcastManager被廢棄了兜材,如果想使用的話需要自行依賴或者使用LiveData(后面會簡單介紹下其用法)來代替它理澎。

image

基本使用

首先我們在項目添加本地廣播的依賴

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

界面是Activity包裹兩個Fragment逞力,一個負責(zé)接受廣播刷新界面,另一個負責(zé)發(fā)送廣播糠爬。

布局比較簡單寇荧,貼個大概就曉得了。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.localbroad.LocalBroadActivity">

    <fragment
        android:id="@+id/fragmentOne"
        android:name="com.vico.livedatademo.fragment.localbroad.LocalBroadOneFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        tools:layout="@layout/fragment_local_broad_one" />

    <View
        android:layout_width="match_parent"
        android:layout_height="3dp"
        android:background="@android:color/black" />

    <fragment
        android:id="@+id/fragmentTwo"
        android:name="com.vico.livedatademo.fragment.localbroad.LocalBroadTwoFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        tools:layout="@layout/fragment_local_broad_two" />

</LinearLayout>
image

從上圖可以看到执隧,除本地廣播之外我還增加了普通廣播發(fā)送的功能揩抡,目的也是為了下文的跨應(yīng)用接收做驗證。

來看看代碼的實現(xiàn)部分:

class LocalBroadOneFragment : Fragment() {

    companion object {
        const val LOCAL = "com.vico.livedatademo_local"
        const val EXPORT = "com.vico.livedatademo_export"
    }

    private lateinit var localBroadcast: LocalBroadcast
    private lateinit var exportBroadcast: ExportBroadcast

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_local_broad_one, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        //注冊本地廣播
        localBroadcast = LocalBroadcast()
        //指定action
        val localIntentFilter = IntentFilter(LOCAL)
        LocalBroadcastManager.getInstance(requireContext()).registerReceiver(localBroadcast, localIntentFilter)

        //注冊普通廣播
        exportBroadcast = ExportBroadcast()
        val exportIntentFilter = IntentFilter(EXPORT)
        requireContext().registerReceiver(exportBroadcast, exportIntentFilter)
    }

    override fun onDetach() {
        //別忘了最后要進行注銷的操作
        LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(localBroadcast)
        requireContext().unregisterReceiver(exportBroadcast)
        super.onDetach()
    }

    //自定義廣播镀琉,用來接收本地廣播發(fā)來的數(shù)據(jù)
    inner class LocalBroadcast : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == LOCAL) {
                tv1.text = intent.getStringExtra(LocalBroadTwoFragment.LOCAL_EVENT) ?: ""
            }
        }
    }

    inner class ExportBroadcast : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == EXPORT) {
                tv2.text = intent.getStringExtra(LocalBroadTwoFragment.EXPORT_EVENT) ?: ""
            }
        }

    }
}
class LocalBroadTwoFragment : Fragment() {

    companion object {
        const val LOCAL_EVENT = "local_event"
        const val EXPORT_EVENT = "export_event"
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_local_broad_two, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        //發(fā)送一條包含時間戳內(nèi)容的本地廣播峦嗤,指定action
        btnLocal.setOnClickListener {
            val localIntent = Intent(LocalBroadOneFragment.LOCAL)
            localIntent.putExtra(LOCAL_EVENT, System.currentTimeMillis().toString())
            LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(localIntent)
        }

        //發(fā)送一條包含時間戳內(nèi)容的普通廣播,指定action
        btnNormal.setOnClickListener {
            val exportIntent = Intent(LocalBroadOneFragment.EXPORT)
            exportIntent.putExtra(EXPORT_EVENT, (System.currentTimeMillis() / 100).toString())
            requireContext().sendBroadcast(exportIntent)
        }
    }
}

來看下運行效果:

image

em........這樣好像也沒什么區(qū)別嘛屋摔,沒事我們在新啟一個應(yīng)用來看看能否接收當(dāng)前應(yīng)用發(fā)出的廣播烁设。

新建的Phone Module代碼跟上面接收廣播的類似

OtherActivity:

class OtherActivity : AppCompatActivity() {

    companion object {
        const val LOCAL = "com.vico.livedatademo_local"
        const val EXPORT = "com.vico.livedatademo_export"
    }

    private lateinit var localBroadcast: LocalBroadcast
    private lateinit var exportBroadcast: ExportBroadcast

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_other)
        localBroadcast = LocalBroadcast()
        val localIntentFilter = IntentFilter(LOCAL)
        LocalBroadcastManager.getInstance(this).registerReceiver(localBroadcast, localIntentFilter)

        exportBroadcast = ExportBroadcast()
        val exportIntentFilter = IntentFilter(EXPORT)
        registerReceiver(exportBroadcast, exportIntentFilter)
    }

    override fun onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(localBroadcast)
        unregisterReceiver(exportBroadcast)
        super.onDestroy()
    }

    inner class LocalBroadcast : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == LOCAL) {
                tv1.text = intent.getStringExtra("local_event") ?: ""
            }
        }
    }

    inner class ExportBroadcast : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == EXPORT) {
                tv2.text = intent.getStringExtra("export_event") ?: ""
            }
        }

    }
}

activity_other.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".OtherActivity">

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="來自本地廣播的值" />

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="來自普通廣播的值" />

</LinearLayout>

運行一下,看下這個新建的應(yīng)用能否接收到另一個應(yīng)用發(fā)送出來的廣播钓试。

image

確實本地廣播無法做到跨應(yīng)用發(fā)送装黑,那么其內(nèi)部的實現(xiàn)原理是什么呢?下面我們一起來看看

LocalBroadcastManager原理

通過對LocalBroadcastManager的使用我們可以得知弓熏,LocalBroadcastManger采用了單例設(shè)計模式恋谭,將其構(gòu)造函數(shù)私有化⊥炀希看下構(gòu)造函數(shù)里做了些上什么:

    private final Context mAppContext;
    
    static final int MSG_EXEC_PENDING_BROADCASTS = 1;

    private final Handler mHandler;
    
    private static final Object mLock = new Object();
    private static LocalBroadcastManager mInstance;
    

    @NonNull
    public static LocalBroadcastManager getInstance(@NonNull Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext());
            }
            return mInstance;
        }
    }

    private LocalBroadcastManager(Context context) {
        mAppContext = context;
        //初始化一個在主線程運行的Handler
        mHandler = new Handler(context.getMainLooper()) {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_EXEC_PENDING_BROADCASTS:
                        //執(zhí)行待處理的廣播
                        executePendingBroadcasts();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    }

嗯疚颊,在構(gòu)造函數(shù)里初始化了一個在主線程運行的Handler,在接收到消息后就會執(zhí)行待處理的廣播。

好的滞谢,接下去看看注冊registerReceiver函數(shù)里做了什么:

    //維護不同BroadcastReceiver的ReceiverRecord集合的HashMap
    private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers
            = new HashMap<>();

    //維護不同Action的ReceiverRecord集合的HashMap
    private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();
    
    private static final class ReceiverRecord {
        final IntentFilter filter;
        final BroadcastReceiver receiver;

        boolean broadcasting;
        //是否被注銷
        boolean dead;

        ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
            filter = _filter;
            receiver = _receiver;
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder(128);
            builder.append("Receiver{");
            builder.append(receiver);
            builder.append(" filter=");
            builder.append(filter);
            if (dead) {
                builder.append(" DEAD");
            }
            builder.append("}");
            return builder.toString();
        }
    }
    
    
    /**
     * 注冊一個接收匹配給定的IntentFilter任何本地廣播
     *
     * @param receiver 處理廣播的BroadCastReceiver
     * @param filter 
     */
    public void registerReceiver(@NonNull BroadcastReceiver receiver,
            @NonNull IntentFilter filter) {
        synchronized (mReceivers) {
            //將filter和receiver包裝成ReceiverRecord對象
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            //查找mReceivers這個HashMap里是否有對應(yīng)的key串稀,有則在value里添加一條記錄除抛,無則put
            ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
            if (filters == null) {
                filters = new ArrayList<>(1);
                mReceivers.put(receiver, filters);
            }
            filters.add(entry);
            //mActions同樣也是這個道理
            for (int i=0; i<filter.countActions(); i++) {
                String action = filter.getAction(i);
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    mActions.put(action, entries);
                }
                entries.add(entry);
            }
        }
    }

可以看出registerReceiver函數(shù)是對兩個HashMap的新增操作狮杨,那么unregisterReceiver同理是對兩個HashMap的刪除操作:

    public void unregisterReceiver(@NonNull BroadcastReceiver receiver) {
        synchronized (mReceivers) {
            final ArrayList<ReceiverRecord> filters = mReceivers.remove(receiver);
            if (filters == null) {
                return;
            }
            for (int i=filters.size()-1; i>=0; i--) {
                final ReceiverRecord filter = filters.get(i);
                //將ReceiverRecord標記為已注銷
                filter.dead = true;
                for (int j=0; j<filter.filter.countActions(); j++) {
                    final String action = filter.filter.getAction(j);
                    final ArrayList<ReceiverRecord> receivers = mActions.get(action);
                    if (receivers != null) {
                        for (int k=receivers.size()-1; k>=0; k--) {
                            final ReceiverRecord rec = receivers.get(k);
                            if (rec.receiver == receiver) {
                                //將ReceiverRecord標記為已注銷
                                rec.dead = true;
                                receivers.remove(k);
                            }
                        }
                        if (receivers.size() <= 0) {
                            mActions.remove(action);
                        }
                    }
                }
            }
        }
    }

好的,看完注冊和注銷到忽,我們看看發(fā)送廣播sendBroadcast(Intent intent)是怎么實現(xiàn)的:

    private static final String TAG = "LocalBroadcastManager";
    private static final boolean DEBUG = false;
    
    private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
    
    private static final class BroadcastRecord {
        final Intent intent;
        final ArrayList<ReceiverRecord> receivers;

        BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
            intent = _intent;
            receivers = _receivers;
        }
    }
    
    public boolean sendBroadcast(@NonNull Intent intent) {
        synchronized (mReceivers) {
            final String action = intent.getAction();
            final String type = intent.resolveTypeIfNeeded(
                    mAppContext.getContentResolver());
            final Uri data = intent.getData();
            final String scheme = intent.getScheme();
            final Set<String> categories = intent.getCategories();

            final boolean debug = DEBUG ||
                    ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
            if (debug) Log.v(
                    TAG, "Resolving type " + type + " scheme " + scheme
                    + " of intent " + intent);
            
            //查詢是否有action匹配的記錄橄教,無則結(jié)束
            ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
            if (entries != null) {
                if (debug) Log.v(TAG, "Action list: " + entries);

                //初始化一個要發(fā)送的記錄
                ArrayList<ReceiverRecord> receivers = null;
                for (int i=0; i<entries.size(); i++) {
                    ReceiverRecord receiver = entries.get(i);
                    if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);

                    //跳過正在執(zhí)行的記錄
                    if (receiver.broadcasting) {
                        if (debug) {
                            Log.v(TAG, "  Filter's target already added");
                        }
                        continue;
                    }

                    //看IntentFilter是否匹配,不匹配就結(jié)束
                    int match = receiver.filter.match(action, type, scheme, data,
                            categories, "LocalBroadcastManager");
                    if (match >= 0) {
                        if (debug) Log.v(TAG, "  Filter matched!  match=0x" +
                                Integer.toHexString(match));
                        if (receivers == null) {
                            receivers = new ArrayList<ReceiverRecord>();
                        }
                        //往要發(fā)送的集合里添加
                        receivers.add(receiver);
                        receiver.broadcasting = true;
                    } else {
                        if (debug) {
                            String reason;
                            switch (match) {
                                case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
                                case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
                                case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
                                case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
                                default: reason = "unknown reason"; break;
                            }
                            Log.v(TAG, "  Filter did not match: " + reason);
                        }
                    }
                }

                if (receivers != null) {
                    for (int i=0; i<receivers.size(); i++) {
                        receivers.get(i).broadcasting = false;
                    }
                    //往待執(zhí)行廣播的集合填充
                    mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                    //Handler發(fā)送一條消息喘漏,最終在handleMessage里會執(zhí)行處理廣播的操作executePendingBroadcasts
                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }
            }
        }
        return false;
    }

最后看看executePendingBroadcasts

    void executePendingBroadcasts() {
        while (true) {
            final BroadcastRecord[] brs;
            synchronized (mReceivers) {
                final int N = mPendingBroadcasts.size();
                if (N <= 0) {
                    return;
                }
                brs = new BroadcastRecord[N];
                mPendingBroadcasts.toArray(brs);
                mPendingBroadcasts.clear();
            }
            for (int i=0; i<brs.length; i++) {
                final BroadcastRecord br = brs[i];
                final int nbr = br.receivers.size();
                for (int j=0; j<nbr; j++) {
                    final ReceiverRecord rec = br.receivers.get(j);
                    if (!rec.dead) {
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    }

將mPendingBroadcasts里元素進行復(fù)制并清空护蝶,復(fù)制后的數(shù)組遍歷執(zhí)行receiver。本地廣播的流程到這里就結(jié)束了翩迈,下面講講它和普通的廣播有什么區(qū)別持灰。

和普通廣播的區(qū)別在哪?

兩個不同的應(yīng)用在Android系統(tǒng)中相當(dāng)于兩個不同的進程负饲。當(dāng)前應(yīng)用發(fā)出的普通廣播能夠被其他應(yīng)用所接收到堤魁,那么這勢必是一個進程間通信的過程(IPC)喂链。

廣播的注冊分為靜態(tài)和動態(tài)兩種注冊方式,其中靜態(tài)注冊的廣播在應(yīng)用安裝時由系統(tǒng)自動完成注冊妥泉,具體點是PMS(PackageMangerService)完成的椭微。這里就只簡單分析下廣播的動態(tài)注冊過程。動態(tài)注冊的過程從ContextWrapperregisterReceiver方法開始盲链。ContextWrapper并沒有做什么實際工作蝇率,將過程交給了ContextImpl來完成。

    @Override
    public Intent registerReceiver(
        BroadcastReceiver receiver, IntentFilter filter) {
        return mBase.registerReceiver(receiver, filter);
    }

ContextImplregisterReceiver方法調(diào)用了自己的registerReceiverInterval方法刽沾,實現(xiàn)如下:

    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context, int flags) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
        try {
            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

系統(tǒng)首先從mPackageInfo獲取IIntentReceiver對象本慕,然后再采用跨進程的方式向AMS發(fā)送廣播注冊的請求。IIntentReceiver是一個Binder接口侧漓,它的具體實現(xiàn)是LoadedApk.ReceiverDispatcher.InnerRecevier,ReceiverDispatcher的內(nèi)部同時保存了BroadcastReceiverInnerReceiver间狂,這樣當(dāng)接收到廣播時,ReceiverDispatcher可以很方便地調(diào)用BroadcastReceiveronReceive方法火架。

相比而言鉴象,使用LocalBroadcastManager來收發(fā)本地廣播效率更高(無需進行進程間通信),并且無需考慮其他應(yīng)用在收發(fā)廣播時帶來的任何安全問題何鸡。

使用LiveData替換纺弊?

開頭說到LocalBroadcastManager已經(jīng)被廢棄,需要引入依賴支持骡男∠危可以使用LiveData來代替實現(xiàn)相關(guān)功能。這里就簡單舉例一下:

image

界面如上圖所示隔盛,F(xiàn)ragment1犹菱、Fragment2以及包裹它們的Activity都共同持有一個ViewModel。點擊Fragment2中的按鈕吮炕,F(xiàn)ragment1中的TextView和中間的分割線會發(fā)生變化腊脱。

創(chuàng)建一個需要用到的ViewModel:

class LiveDataViewModel : ViewModel() {

    /**
     * 只暴露不可變的LiveData給外部。
     */
    private val _value = MutableLiveData<String>()
    val value: LiveData<String> get() = _value

    fun setValue() {
        _value.value = System.currentTimeMillis().toString()
    }
}

在Fragment2中更改value的值:

class LiveDataTwoFragment : Fragment() {

    private lateinit var viewModel: LiveDataViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_live_data_two, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(LiveDataViewModel::class.java)

        btnLiveData.setOnClickListener {
            viewModel.setValue()
        }
    }
}

在Fragment1和Activity中觀察value的變化刷新UI:

class LiveDataActivity : AppCompatActivity() {

    private lateinit var viewModel: LiveDataViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_live_data)

        viewModel = ViewModelProvider(this).get(LiveDataViewModel::class.java)

        viewModel.value.observe(this, Observer {
            val random = Random()
            val r = random.nextInt(255)
            val g = random.nextInt(255)
            val b = random.nextInt(255)
            viewLine.setBackgroundColor(Color.rgb(r, g, b))
        })
    }
}
class LiveDataOneFragment : Fragment() {

    private lateinit var viewModel: LiveDataViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_live_data_one, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(LiveDataViewModel::class.java)

        viewModel.value.observe(requireActivity(), Observer { value ->
            tvLiveData.text = value
        })
    }
}

最終的效果如下:

image

END

文章到這里就已經(jīng)結(jié)束了龙亲,這一次就LocalBroadcastManager的實現(xiàn)原理和與普通廣播的區(qū)別做了介紹陕凹,以及相關(guān)的其他方案。文中的LiveData+ViewModel的實現(xiàn)只是其中一種方式而已鳄炉,還有其他諸多優(yōu)秀的方案可以選擇杜耙,比如:EventBus,RxBus,LiveEventBus等等。
由于個人水平的原因拂盯,可能無法更深層次的去介紹佑女,希望能多多包涵。我也同廣大的開發(fā)者一樣,一步步地在慢慢成長团驱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簸呈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子店茶,更是在濱河造成了極大的恐慌蜕便,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贩幻,死亡現(xiàn)場離奇詭異轿腺,居然都是意外死亡,警方通過查閱死者的電腦和手機丛楚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門族壳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人趣些,你說我怎么就攤上這事仿荆。” “怎么了坏平?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵拢操,是天一觀的道長。 經(jīng)常有香客問我舶替,道長令境,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任顾瞪,我火速辦了婚禮舔庶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陈醒。我一直安慰自己惕橙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布钉跷。 她就那樣靜靜地躺著弥鹦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尘应。 梳的紋絲不亂的頭發(fā)上惶凝,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天吼虎,我揣著相機與錄音犬钢,去河邊找鬼。 笑死思灰,一個胖子當(dāng)著我的面吹牛玷犹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洒疚,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼歹颓,長吁一口氣:“原來是場噩夢啊……” “哼坯屿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巍扛,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤领跛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撤奸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吠昭,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年胧瓜,在試婚紗的時候發(fā)現(xiàn)自己被綠了矢棚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡府喳,死狀恐怖跨琳,靈堂內(nèi)的尸體忽然破棺而出宠漩,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布粗仓,位于F島的核電站,受9級特大地震影響溉愁,放射性物質(zhì)發(fā)生泄漏攻锰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一熟吏、第九天 我趴在偏房一處隱蔽的房頂上張望距糖。 院中可真熱鬧,春花似錦牵寺、人聲如沸悍引。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趣斤。三九已至,卻和暖如春黎休,著一層夾襖步出監(jiān)牢的瞬間浓领,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工势腮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留联贩,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓捎拯,卻偏偏與公主長得像泪幌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344