DroidPlugin手札——home鍵強(qiáng)殺處理

DroidPlugin手札——home鍵強(qiáng)殺處理

DroidPlugin是360開源的插件化框架瓷马,github地址為:https://github.com/DroidPluginTeam/DroidPlugin
因公司業(yè)務(wù)及項(xiàng)目歷史原因跨晴,來公司的這段時(shí)間一直在使用DroidPlugin進(jìn)行業(yè)務(wù)開發(fā)欧聘,期間遇到的一些問題在此進(jìn)行總結(jié)記錄。

一端盆、背景

為了方便訪客知道本章在解決什么問題怀骤,這里先把需求背景說明清楚。

  1. 公司業(yè)務(wù)需求焕妙,需要在產(chǎn)品App中以插件的方式安裝游戲apk蒋伦,之前的Android開發(fā)團(tuán)隊(duì)選用了360的DroidPlugin來實(shí)現(xiàn)這個(gè)需求。
  2. 需求方(金主)要求當(dāng)用戶在按下home鍵后焚鹊,我們的app不得駐留進(jìn)程痕届,也就是說,這個(gè)使用了DroidPlugin開發(fā)的產(chǎn)品app末患,需要在接收到home事件時(shí)研叫,將與該app相關(guān)的所有進(jìn)程全部殺死。

這里的所有進(jìn)程指的是產(chǎn)品app本身的【宿主進(jìn)程】璧针,與作為插件安裝的游戲【插件進(jìn)程】嚷炉。

二、home事件與進(jìn)程自殺處理

1探橱、怎么監(jiān)聽home事件

在我們每次點(diǎn)擊Home按鍵時(shí)系統(tǒng)會(huì)發(fā)出action為Intent.ACTION_CLOSE_SYSTEM_DIALOGS的廣播申屹,用于關(guān)閉系統(tǒng)Dialog,此廣播可以來監(jiān)聽Home按鍵走搁,這種方式是我目前用過的最好的独柑。

/**
 * @創(chuàng)建者 LQR
 * @時(shí)間 2019/1/7
 * @描述 home鍵監(jiān)聽
 */
public class HomeEventWatcher extends BroadcastReceiver {

    private Context mContext;

    private HomeEventWatcher(Context context) {
        mContext = context;
    }

    private static HomeEventWatcher INSTATNCE;

    public static final HomeEventWatcher get(Context context) {
        if (INSTATNCE == null) {
            synchronized (HomeEventWatcher.class) {
                if (INSTATNCE == null) {
                    INSTATNCE = new HomeEventWatcher(context.getApplicationContext());
                }
            }
        }
        return INSTATNCE;
    }

    /**
     * 注冊(cè)事件監(jiān)聽(在onCreate()中執(zhí)行)
     */
    public HomeEventWatcher register() {
        if (mHomeClickListener != null && mContext != null) {
            IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            mContext.registerReceiver(this, filter);
        }
        return this;
    }

    /**
     * 反注冊(cè)事件監(jiān)聽(在onDestroy()中執(zhí)行)
     */
    public void unRegister() {
        mContext.unregisterReceiver(this);
    }

    /*------------------ 點(diǎn)擊事件監(jiān)聽 begin ------------------*/
    private static final class Home {
        private static final String SYSTEM_DIALOG_REASON_KEY      = "reason";
        private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
    }

    private OnHomeClickListener mHomeClickListener;

    public HomeEventWatcher setHomeClickListener(OnHomeClickListener homeClickListener) {
        mHomeClickListener = homeClickListener;
        return this;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        // Log.i("MyAPP", "intentAction =" + intentAction);

        // 按下home鍵事件
        if (TextUtils.equals(intentAction, Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
            String reason = intent.getStringExtra(Home.SYSTEM_DIALOG_REASON_KEY);
            // Log.i("MyAPP", "reason =" + reason);
            if (TextUtils.equals(Home.SYSTEM_DIALOG_REASON_HOME_KEY, reason)) {
                if (mHomeClickListener != null) {
                    mHomeClickListener.onHomeClick();
                }
            }
        }
        // 其他按鍵事件
        // ...
    }

    /*------------------ 點(diǎn)擊事件監(jiān)聽 end ------------------*/
    public interface OnHomeClickListener {
        void onHomeClick();
    }
}

2、強(qiáng)殺進(jìn)程

以下方法二選一:

android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);

注意私植,最好在確保app進(jìn)程處于后臺(tái)進(jìn)程時(shí)再執(zhí)行忌栅,因?yàn)椴糠衷O(shè)備會(huì)自動(dòng)重啟那些被強(qiáng)殺的前臺(tái)進(jìn)程∏冢或者索绪,想辦法關(guān)閉所有的Activity,然后直接執(zhí)行強(qiáng)殺贫悄,至于如何關(guān)閉所有Activity瑞驱,下面會(huì)提供一種簡單粗暴的方法。

3窄坦、adb指令

這里提供2個(gè)adb指令唤反,方便查看進(jìn)程狀況凳寺、強(qiáng)制結(jié)束進(jìn)程。

adb shell " procrank | grep com.xxx.yyy "   // 查看進(jìn)程狀況(若進(jìn)程不存在彤侍,則終端不顯示任何信息)
adb shell am force-stop com.xxx.yyy        // 強(qiáng)制結(jié)束進(jìn)程

注意:
1)com.xxx.yyy不是包名肠缨,而是applicationId,通常情況下盏阶,包名與applicationId一致晒奕。
2)使用DroidPlugin運(yùn)行的插件,會(huì)多出來一個(gè)插件進(jìn)程名斟,進(jìn)程名一般為 宿主進(jìn)程名+PluginP07脑慧。

三、DroidPlugin強(qiáng)殺躺坑

下面正式進(jìn)入本章核心內(nèi)容砰盐,情景前提:產(chǎn)品app在接收到home事件時(shí)闷袒,會(huì)執(zhí)行進(jìn)程自殺邏輯,殺死與當(dāng)前app相關(guān)的所有進(jìn)程岩梳。

1霜运、殺不死的宿主進(jìn)程

1)現(xiàn)象

啟動(dòng)產(chǎn)品app,然后直接按home鍵蒋腮,使用AndroidStudio觀察進(jìn)程并查看日志輸出淘捡,看到控制臺(tái)輸出了強(qiáng)殺日志,而app進(jìn)程在殺死后重啟了池摧。

2)分析

通過日志可以確定強(qiáng)殺代碼有被執(zhí)行到焦除,并且進(jìn)程也被殺死過,這個(gè)進(jìn)程重啟不是項(xiàng)目代碼觸發(fā)的作彤,應(yīng)該是DroidPlugin設(shè)置了類似北炱牵活機(jī)制的東西,導(dǎo)致Android系統(tǒng)拉起被強(qiáng)殺的產(chǎn)品app竭讳。通過查閱DroidPlugin源碼创葡,可以知道DroidPlugin會(huì)啟動(dòng)一個(gè)Service,用來管理插件(安裝绢慢、卸載等)灿渴,這個(gè)Service使用了start和bind方式啟動(dòng),并且設(shè)置前臺(tái)進(jìn)程币扔撸活骚露,代碼如下:

// =================== com.morgoo.droidplugin.pm.PluginManager ===================
public void connectToService() {
    if (mPluginManager == null) {
        try {
            Intent intent = new Intent(mHostContext, PluginManagerService.class);
            intent.setPackage(mHostContext.getPackageName());
            mHostContext.startService(intent);

            String auth = mHostContext.getPackageName() + ".plugin.servicemanager";
            Uri uri = Uri.parse("content://" + auth);
            Bundle args = new Bundle();
            args.putString(PluginServiceProvider.URI_VALUE, "content://" + auth);
            Bundle res = ContentProviderCompat.call(mHostContext, uri,
                    PluginServiceProvider.Method_GetManager,
                    null, args);
            if (res != null) {
                IBinder clientBinder = BundleCompat.getBinder(res, PluginServiceProvider.Arg_Binder);
                onServiceConnected(intent.getComponent(), clientBinder);
            } else {
                mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
            }
        } catch (Exception e) {
            Log.e(TAG, "connectToService", e);
        }

    }
}

// =================== com.morgoo.droidplugin.PluginManagerService ===================
@Override
public void onCreate() {
    super.onCreate();
    keepAlive();
    getPluginPackageManager(this);
}
private void keepAlive() {
    try {
        Notification notification = new Notification();
        notification.flags |= Notification.FLAG_NO_CLEAR;
        notification.flags |= Notification.FLAG_ONGOING_EVENT;
        startForeground(0, notification); // 設(shè)置為前臺(tái)服務(wù)避免kill,Android4.3及以上需要設(shè)置id為0時(shí)通知欄才不顯示該通知缚窿;
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

3)方案

應(yīng)該大致可以確定棘幸,宿主進(jìn)程殺不死的原因,就是這個(gè)PluginManagerService導(dǎo)致的倦零,處理方式有2種误续。

  • 宿主自殺前先關(guān)閉PluginManagerService
/**
 * 停止插件服務(wù)
 */
private void stopPluginServer() {
    Intent intent = new Intent();
    intent.setClass(PluginManager.getInstance().getHostContext(), PluginManagerService.class);
    CONTEXT.getApplicationContext().stopService(intent);
}
  • 取消PluginManagerService倍趾罚活,并且不使用start方式啟動(dòng)蹋嵌。因?yàn)閎ind方式啟動(dòng)的Service畜份,其生命周期與app一致,按home鍵時(shí)會(huì)觸發(fā)強(qiáng)殺進(jìn)程欣尼,不需要手動(dòng)關(guān)閉。
// =================== com.morgoo.droidplugin.pm.PluginManager ===================
public void connectToService() {
    if (mPluginManager == null) {
        try {
            Intent intent = new Intent(mHostContext, PluginManagerService.class);
            intent.setPackage(mHostContext.getPackageName());
            // mHostContext.startService(intent);
            ...
            mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
        } catch (Exception e) {
            Log.e(TAG, "connectToService", e);
        }

    }
}

// =================== com.morgoo.droidplugin.PluginManagerService ===================
@Override
public void onCreate() {
    super.onCreate();
    // keepAlive();
    getPluginPackageManager(this);
}

2停蕉、啟動(dòng)App直接進(jìn)入強(qiáng)殺前運(yùn)行的插件

1)現(xiàn)象

游戲運(yùn)行中愕鼓,按下home鍵強(qiáng)殺app,點(diǎn)擊App icon再次啟動(dòng)App慧起,直接進(jìn)入剛剛的游戲菇晃。

2)分析

在插件游戲運(yùn)行過程中,打開終端或cmd蚓挤,使用adb查看當(dāng)前棧信息:

adb shell dumpsys activity activities top  

可以看到磺送,游戲進(jìn)程(插件進(jìn)程)與產(chǎn)品app進(jìn)程(宿主進(jìn)程)共用一個(gè)Activity棧,由此可以推測灿意,因?yàn)樗拗鰽pp在被強(qiáng)殺的時(shí)候估灿,系統(tǒng)保存了宿主進(jìn)程的Activity棧信息,所以缤剧,在產(chǎn)品app下次啟動(dòng)時(shí)馅袁,系統(tǒng)會(huì)恢復(fù)棧記錄。

3)方案

根據(jù)前面的推測荒辕,針對(duì)目前的問題汗销,方案無非就2個(gè),要么讓宿主進(jìn)程在被強(qiáng)殺時(shí)不要被系統(tǒng)保存棧記錄抵窒,要么讓宿主進(jìn)程與插件進(jìn)程不要共用一個(gè)棧弛针。要注意,方案一才是關(guān)鍵李皇,但這個(gè)與第3個(gè)坑有關(guān)聯(lián)削茁,所以,這里就只說下方案二吧掉房。很簡單付材,修改產(chǎn)品app(宿主)入口Activity的啟動(dòng)模式即可,如把 launchMode 修改為 singleInstance圃阳,這樣的話厌衔,下次通過icon啟動(dòng)產(chǎn)品app時(shí),系統(tǒng)會(huì)單獨(dú)使用一個(gè)棧來存放這個(gè)入口Activity捍岳,從而避免與插件共用一個(gè)棧的問題富寿。修改完成后睬隶,啟動(dòng)產(chǎn)品app,再啟動(dòng)游戲插件页徐,這時(shí)苏潜,通過adb命令查看當(dāng)前棧信息:

adb shell dumpsys activity activities top  

可以看到產(chǎn)品app與游戲插件不在一個(gè)棧內(nèi),這時(shí)变勇,按home鍵恤左,再啟動(dòng)就不會(huì)再進(jìn)入游戲界面了。但是搀绣,方案二并不是正確的解決辦法飞袋,方案一才是,因?yàn)檫M(jìn)程強(qiáng)殺前的棧信息還是會(huì)被保留下來的链患,如果項(xiàng)目采用的是Activity + Fragment架構(gòu)巧鸭,這時(shí),效果會(huì)很"神奇"麻捻,這絕對(duì)不是產(chǎn)品希望看到的纲仍。那要怎樣才能讓進(jìn)程在被強(qiáng)殺時(shí)不要被系統(tǒng)保存棧記錄呢?請(qǐng)繼續(xù)往下看贸毕。

3郑叠、啟動(dòng)插件B時(shí)直接啟動(dòng)插件A

1)現(xiàn)象

進(jìn)入產(chǎn)品app,啟動(dòng)游戲A明棍,按home鍵锻拘,再進(jìn)入產(chǎn)品app,啟動(dòng)游戲B击蹲,這時(shí)署拟,直接啟動(dòng)了游戲A。

2)分析

這就是前面問題2說到的歌豺,狀態(tài)保存問題推穷,插件進(jìn)程在按下home時(shí)被強(qiáng)殺,這時(shí)类咧,系統(tǒng)認(rèn)為該游戲插件是意外退出馒铃,會(huì)保存當(dāng)前游戲的狀態(tài),以便下次啟動(dòng)時(shí)恢復(fù)痕惋。要知道区宇,DroidPlugin使用組件預(yù)先占坑的方式,預(yù)先在宿主清單文件中聲明好多個(gè)Activity值戳、Service等议谷,并且會(huì)對(duì)組件進(jìn)行復(fù)用,所以堕虹,當(dāng)下次啟動(dòng)另一個(gè)游戲時(shí)卧晓,剛好復(fù)用了前一個(gè)游戲使用過的組件(Activity)芬首,于是在恢復(fù)狀態(tài)的時(shí)候,就把前一個(gè)游戲恢復(fù)回來了逼裆。

以上分析個(gè)人猜測郁稍,不知說法是否正確,如有問題請(qǐng)不吝賜教~

3)方案

游戲(插件)退出時(shí)胜宇,銷毀游戲所有的Activity耀怜,銷毀當(dāng)前進(jìn)程所有Activity的方法如下:

/**
 * 關(guān)閉當(dāng)前App所有Activity
 */
public void finishAllActivities(Application application) {
    List<Activity> activities = getActivitiesByApplication(application);
    if (activities != null && activities.size() > 0) {
        for (int i = activities.size() - 1; i >= 0; i--) {
            Activity activity = activities.get(i);
            activity.finish();
            Log.e("lqr", "finish activity : " + activity);
        }
    }
}

/**
 * 獲取當(dāng)前App中所有Activity
 */
public List<Activity> getActivitiesByApplication(Application application) {
    List<Activity> list = new ArrayList<>();
    try {
        Class<Application> applicationClass = Application.class;
        Field mLoadedApkField = applicationClass.getDeclaredField("mLoadedApk");
        mLoadedApkField.setAccessible(true);
        Object mLoadedApk = mLoadedApkField.get(application);
        Class<?> mLoadedApkClass = mLoadedApk.getClass();
        Field mActivityThreadField = mLoadedApkClass.getDeclaredField("mActivityThread");
        mActivityThreadField.setAccessible(true);
        Object mActivityThread = mActivityThreadField.get(mLoadedApk);
        Class<?> mActivityThreadClass = mActivityThread.getClass();
        Field mActivitiesField = mActivityThreadClass.getDeclaredField("mActivities");
        mActivitiesField.setAccessible(true);
        Object mActivities = mActivitiesField.get(mActivityThread);
        // 注意這里一定寫成Map,低版本這里用的是HashMap桐愉,高版本用的是ArrayMap
        if (mActivities instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<Object, Object> arrayMap = (Map<Object, Object>) mActivities;
            for (Map.Entry<Object, Object> entry : arrayMap.entrySet()) {
                Object value = entry.getValue();
                Class<?> activityClientRecordClass = value.getClass();
                Field activityField = activityClientRecordClass.getDeclaredField("activity");
                activityField.setAccessible(true);
                Object o = activityField.get(value);
                list.add((Activity) o);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        list = null;
    }
    return list;
}

注意:這個(gè)關(guān)閉所有Activity的方法可以用來解決問題2最后遺留的問題财破。

要注意,DroidPlugin會(huì)為每個(gè)插件單獨(dú)創(chuàng)建進(jìn)程仅财,也就是說,如果你項(xiàng)目中使用了DroidPlugin碗淌,就會(huì)涉及到多進(jìn)程盏求,在啟動(dòng)插件時(shí),宿主的Application內(nèi)的邏輯會(huì)執(zhí)行多次(宿主亿眠、插件進(jìn)程一創(chuàng)建就會(huì)執(zhí)行)碎罚,所以,建議在項(xiàng)目的自定義Application中對(duì)進(jìn)程進(jìn)行區(qū)分纳像,根據(jù)不同進(jìn)程分別處理(如:第三方面SDK只需要在產(chǎn)品app宿主進(jìn)程中初始化)荆烈,判斷當(dāng)前進(jìn)程是否為插件進(jìn)程的方法如下:

/**
 * 判斷當(dāng)前進(jìn)程是否為插件進(jìn)程
 *
 * @param context   上下文
 * @param hostAppId 宿主appid
 * @return
 */
public boolean adjustPluginProcess(Context context, String hostAppId) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
    if (runningAppProcesses != null && runningAppProcesses.size() > 0) {
        for (ActivityManager.RunningAppProcessInfo info : runningAppProcesses) {
            // Step 1. 找到當(dāng)前進(jìn)程
            if (info.pid == Process.myPid()) {
                // Log.e("lqr", "info.processName = " + info.processName);
                // Step 2. 判斷當(dāng)前進(jìn)程是否為插件進(jìn)程(依據(jù))
                return !info.processName.equals(hostAppId);
            }
        }
    }
    return false;
}

Q:為什么要傳入宿主的appid?
A:這里說的appid指的就是applicationId竟趾。因?yàn)閍ppid不等同于包名憔购,我們常說的一個(gè)設(shè)備上不能安裝相同包名的app這種說法是不嚴(yán)謹(jǐn)?shù)模瑧?yīng)該是不能安裝相同appid的app岔帽,此外玫鸟,一個(gè)項(xiàng)目在多渠道的情況下,是可以通過gradle來指定修改appid的犀勒,如果你的項(xiàng)目中有使用過多渠道打包屎飘,相信應(yīng)該能夠明白,綜上贾费,包名不能作為判斷宿主進(jìn)程的依據(jù)钦购,所以只能使用appid來判斷。
Q:為什么不以進(jìn)程名是否帶有 "PluginP" 字樣來判斷是否為插件進(jìn)程褂萧?
A:親測這種方式不準(zhǔn)確押桃,在有些設(shè)備上,插件進(jìn)程的進(jìn)程名是這樣的規(guī)則导犹,但有些設(shè)備不是怨规,直接是插件原本的applicationId陌宿。

通過上面的代碼,根據(jù)項(xiàng)目的具體情況波丰,分別處理宿主進(jìn)程與插件進(jìn)程吧壳坪,建議2個(gè)進(jìn)程在監(jiān)聽到home事件時(shí),都關(guān)閉所有Activity掰烟,這樣系統(tǒng)就不會(huì)保存棧狀態(tài)了(一定要先關(guān)閉插件的爽蝴,再關(guān)閉宿主的!H移铩)蝎亚。

4、部分4.x設(shè)備安裝插件失敗-500

公司是做盒子應(yīng)用開發(fā)的先馆,在部分4.x的盒子上確實(shí)出現(xiàn)了使用DroidPlugin無法正常安裝插件的情況发框,但舊版的DroidPlugin就不會(huì),我比對(duì)了2個(gè)版本的DroidPlugin煤墙,最終定位到在com.morgoo.droidplugin.pm包下的PluginManager梅惯,其中有這么一個(gè)方法:

新版的DroidPlugin適配了高版本的Android系統(tǒng)(如:Android8.0)

// =================== 舊版DroidPlugin ===================
public void connectToService() {
    if (mPluginManager == null) {
        try {
            Intent intent = new Intent(mHostContext, PluginManagerService.class);
            intent.setPackage(mHostContext.getPackageName());
            mHostContext.startService(intent);
            mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
        } catch (Exception e) {
            Log.e(TAG, "connectToService", e);
        }
    }
}

// =================== 新版DroidPlugin ===================
public void connectToService() {
    if (mPluginManager == null) {
        try {
            Intent intent = new Intent(mHostContext, PluginManagerService.class);
            intent.setPackage(mHostContext.getPackageName());
            mHostContext.startService(intent);

            String auth = mHostContext.getPackageName() + ".plugin.servicemanager";
            Uri uri = Uri.parse("content://" + auth);
            Bundle args = new Bundle();
            args.putString(PluginServiceProvider.URI_VALUE, "content://" + auth);
            Bundle res = ContentProviderCompat.call(mHostContext, uri,
                    PluginServiceProvider.Method_GetManager,
                    null, args);
            if (res != null) {
                IBinder clientBinder = BundleCompat.getBinder(res, PluginServiceProvider.Arg_Binder);
                onServiceConnected(intent.getComponent(), clientBinder);
            } else {
                mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
            }
        } catch (Exception e) {
            Log.e(TAG, "connectToService", e);
        }

    }
}

正是因?yàn)檫@部分多出來的代碼,導(dǎo)致新版的DroidPlugin無法在個(gè)別4.x設(shè)備上正常安裝插件仿野,所以铣减,我們可以對(duì)源碼進(jìn)行修改,區(qū)分4.x以下及高版本的代碼邏輯即可脚作,如:

public void connectToService() {
    if (mPluginManager == null) {
        try {
            Intent intent = new Intent(mHostContext, PluginManagerService.class);
            intent.setPackage(mHostContext.getPackageName());
            // mHostContext.startService(intent);

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
            } else {
                String auth = mHostContext.getPackageName() + ".plugin.servicemanager";
                Uri uri = Uri.parse("content://" + auth);
                Bundle args = new Bundle();
                args.putString(PluginServiceProvider.URI_VALUE, "content://" + auth);
                Bundle res = ContentProviderCompat.call(mHostContext, uri,
                        PluginServiceProvider.Method_GetManager,
                        null, args);
                if (res != null) {
                    IBinder clientBinder = BundleCompat.getBinder(res, PluginServiceProvider.Arg_Binder);
                    onServiceConnected(intent.getComponent(), clientBinder);
                } else {
                    mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
                }
            }

        } catch (Exception e) {
            Log.e(TAG, "connectToService", e);
        }

    }
}

四葫哗、最后

以上,就是本人在實(shí)際開發(fā)中球涛,使用DroidPlugin的項(xiàng)目在強(qiáng)殺時(shí)的踩坑記錄分享劣针,如果有什么更好的解決方案,希望可以一起交流亿扁,如文章中說明有問題歡迎指出交流酿秸,不喜勿噴。

歡迎關(guān)注微信公眾號(hào):全棧行動(dòng)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末魏烫,一起剝皮案震驚了整個(gè)濱河市辣苏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哄褒,老刑警劉巖稀蟋,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異呐赡,居然都是意外死亡退客,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萌狂,“玉大人档玻,你說我怎么就攤上這事∶2兀” “怎么了误趴?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長务傲。 經(jīng)常有香客問我凉当,道長,這世上最難降的妖魔是什么售葡? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任看杭,我火速辦了婚禮,結(jié)果婚禮上挟伙,老公的妹妹穿的比我還像新娘楼雹。我一直安慰自己,他們只是感情好尖阔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布贮缅。 她就那樣靜靜地躺著,像睡著了一般诺祸。 火紅的嫁衣襯著肌膚如雪携悯。 梳的紋絲不亂的頭發(fā)上祭芦,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天筷笨,我揣著相機(jī)與錄音,去河邊找鬼龟劲。 笑死胃夏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昌跌。 我是一名探鬼主播仰禀,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼蚕愤!你這毒婦竟也來了答恶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤萍诱,失蹤者是張志新(化名)和其女友劉穎悬嗓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裕坊,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡包竹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片周瞎。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苗缩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出声诸,到底是詐尸還是另有隱情酱讶,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布双絮,位于F島的核電站浴麻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏囤攀。R本人自食惡果不足惜软免,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焚挠。 院中可真熱鬧膏萧,春花似錦、人聲如沸蝌衔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽噩斟。三九已至曹锨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剃允,已是汗流浹背沛简。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斥废,地道東北人椒楣。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像牡肉,于是被迫代替她去往敵國和親捧灰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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