ContentProvider啟動流程分析(三)

0x01 扯東扯西的前言&概述


本片博客對應(yīng)時序圖上的step15—26上接第二篇博客:ContentProvider啟動流程分析二!

同時此系列博客同步在博客園發(fā)布:ContentProvider啟動流程分析系列!詳情戳這里即可訪問~

0x02 ContentProvider啟動流程分析


step15: ApplicationThread#bindApplication()

上一步,在ApplicationThreadProxy類的bindApplication()函數(shù)中,通過Binder對象mRemote發(fā)出了一個進程間通信請求!

反查源碼很容易知道,bindApplication()函數(shù),其實是IApplicationThread接口類的接口函數(shù),而ActivityThread類的內(nèi)部類ApplicationThread實現(xiàn)了這一接口,所以自然也就實現(xiàn)了接口函數(shù)bindApplication(),我們來看ApplicationThread類的函數(shù)bindApplication()源碼刪減如下:

public final void bindApplication(String processName, ApplicationInfo appInfo,
            List<ProviderInfo> providers, ComponentName instrumentationName,
            ProfilerInfo profilerInfo, Bundle instrumentationArgs,
            IInstrumentationWatcher instrumentationWatcher,
            IUiAutomationConnection instrumentationUiConnection, int debugMode,
            boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
            Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
            Bundle coreSettings) {
    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providers;
    data.instrumentationName = instrumentationName;
    data.instrumentationArgs = instrumentationArgs;
    data.instrumentationWatcher = instrumentationWatcher;
    data.instrumentationUiAutomationConnection = instrumentationUiConnection;
    data.debugMode = debugMode;
    data.enableOpenGlTrace = enableOpenGlTrace;
    data.restrictedBackupMode = isRestrictedBackupMode;
    data.persistent = persistent;
    data.config = config;
    data.compatInfo = compatInfo;
    data.initProfilerInfo = profilerInfo;
    sendMessage(H.BIND_APPLICATION, data);
}
  • 沒錯!ApplicationThread類的成員函數(shù)bindApplication(),就是類型為BIND_APPLICATION_TRANSACTION的進程間通信請求的真正處理者!處理的邏輯是:先把將要啟動的ContentProvider組件列表,封裝成一個AppBindData對象;
  • 然后再調(diào)用外部類ActivityThread的成員函數(shù)sendMessage(),再次將這個AppBindData對象封裝成一個類型為BIND_APPLICATION的消息,以便可以發(fā)送到新建應(yīng)用程序進程的主線程的消息隊列中!

step16: ActivityThread#sendMessage()

ActivityThread類持有一個mH成員變量,mH實際上是一個Handler對象,通過mH.sendMessage()發(fā)送消息到新建應(yīng)用程序進程的主線程的消息隊列中!所以,這個消息最后是最終是在mH.handleMessage()函數(shù)中處理的。

step17: ActivityThread#handleMessage()

ActivityThread內(nèi)部類H的程艷方法handleMessage()源碼刪減如下:

public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
        ......
    case BIND_APPLICATION:
        AppBindData data = (AppBindData)msg.obj;
        handleBindApplication(data);
        break;
        ......
    }
    if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
  • 在handleMessage()函數(shù)中,我們只考慮BIND_APPLICATION的情況,首先將Message對象msg的成員變量obj強轉(zhuǎn)成個AppBindData對象,它包含了要啟動的ContentProvider列表。
  • 然后,調(diào)用handleBindApplication()函數(shù)把這些ContentProvider組件啟動起來!

接下來,關(guān)注handleBindApplication()函數(shù),是怎樣把這些ContentProvider組件啟動起來的?

step18: ActivityThread#handleBindApplication()

ActivityThread類的成員函數(shù)handleBindApplication()源碼如下:

private void handleBindApplication(AppBindData data) {
    ......
    // don't bring up providers in restricted mode; they may depend on the
    // app's custom Application class
    if (!data.restrictedBackupMode) {
        List<ProviderInfo> providers = data.providers;
        if (providers != null) {
            installContentProviders(app, providers);
            ......
        }
    }
}

參數(shù)data是一個AppBindData對象,data的成員變量providers保存了,需要在當(dāng)前進程中啟動的ContentProvider組件,接下來則會調(diào)用ActivityThread類的成員函數(shù)installContentProviders()來啟動這些組件!

step19: ActivityThread#installContentProviders()

ActivityThread類的成員函數(shù)installContentProviders()源碼如下:

private void installContentProviders(
    Context context, List<ProviderInfo> providers) {
    final ArrayList<IActivityManager.ContentProviderHolder> results =
        new ArrayList<IActivityManager.ContentProviderHolder>();

    for (ProviderInfo cpi : providers) {
        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi, false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }

    try {
        ActivityManagerNative.getDefault().publishContentProviders(
            getApplicationThread(), results);
    } catch (RemoteException ex) {
    }
}
  • for-each循環(huán)得到保存在providers中的每一個組件ProviderInfo對象cpi,然后調(diào)用installProvider()函數(shù)啟動每個cpi對應(yīng)的ContnetProvider組件;
  • 這些組件啟動之后,就可以獲得其對應(yīng)的一個IContentProvider接口;然后,把這個接口封裝成一個ContentProviderHolder對象;
  • 最后調(diào)用AMS代理對象(也即,ActivityManagerProxy)的成員函數(shù)publishContentProviders(),將這些ContentProviderHolder對象傳遞給AMS;AMS正是通過這些ContentProviderHolder對象獲取它所描述的ContentProvider組件的一個訪問接口;

接下來,先分析ActivityThread類成員函數(shù)installProvider()在當(dāng)前應(yīng)用程序進程中啟動一個ContentProvider組件的過程;然后,分析AMS代理對象ActivityManagerProxy成員函數(shù)publishContentProviders()將啟動完成的組件發(fā)布到AMS的過程!

step20: ActivityThread#installProvider()

ActivityThread類成員函數(shù)installProvider()源碼如下:

/**
 * Installs the provider.
 *
 * Providers that are local to the process or that come from the system server
 * may be installed permanently which is indicated by setting noReleaseNeeded to true.
 * Other remote providers are reference counted.  The initial reference count
 * for all reference counted providers is one.  Providers that are not reference
 * counted do not have a reference count (at all).
 *
 * This method detects when a provider has already been installed.  When this happens,
 * it increments the reference count of the existing provider (if appropriate)
 * and returns the existing provider.  This can happen due to concurrent
 * attempts to acquire the same provider.
 */
private IActivityManager.ContentProviderHolder installProvider(Context context,
        IActivityManager.ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    // holder為空,表示參數(shù)info所描述的ContentProvider組件需要在當(dāng)前進程中啟動
    if (holder == null || holder.provider == null) {
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        if (context.getPackageName().equals(ai.packageName)) {
            c = context;
        }
        ......
        final java.lang.ClassLoader cl = c.getClassLoader();
        localProvider = (ContentProvider)cl.
                        loadClass(info.name).newInstance();
        provider = localProvider.getIContentProvider();
        if (provider == null) {
            return null;
        }
        // XXX Need to create the correct context for this provider.
        localProvider.attachInfo(c, info);
    } else {
        provider = holder.provider;
    }

    IActivityManager.ContentProviderHolder retHolder;
    synchronized (mProviderMap) {
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) {
            ComponentName cname = new ComponentName(info.packageName, info.name);
            ProviderClientRecord pr = mLocalProvidersByName.get(cname);
            if (pr != null) {
                provider = pr.mProvider;
            } else {
                holder = new IActivityManager.ContentProviderHolder(info);
                holder.provider = provider;
                holder.noReleaseNeeded = true;
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                mLocalProviders.put(jBinder, pr);
                mLocalProvidersByName.put(cname, pr);
            }
            retHolder = pr.mHolder;
        } else {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                ......
            } else {
                ProviderClientRecord client = installProviderAuthoritiesLocked(
                                                  provider, localProvider, holder);
                if (noReleaseNeeded) {
                    prc = new ProviderRefCount(holder, client, 1000, 1000);
                } else {
                    prc = stable ? new ProviderRefCount(holder, client, 1, 0)
                          : new ProviderRefCount(holder, client, 0, 1);
                }
                mProviderRefCountMap.put(jBinder, prc);
            }
            retHolder = prc.holder;
        }
    }
    return retHolder;
}
  • 對傳進來的參數(shù)holder判空,如果為null,表示要在當(dāng)前應(yīng)用程序進程中,將參數(shù)info所描述的ContentProvider組件啟動起來;另一個參數(shù)context描述了將要啟動的ContentProvider組件運行的上下文環(huán)境;
  • localProvider表示一個ContentProvider組件對象,通過localProvider.getIContentProvider(),也即調(diào)用localProvider的成員函數(shù)getIContentProvider()來獲得該組件對應(yīng)的一個IContentProvider接口provider;這個IContentProvider最終需要發(fā)布到AMS,AMS會將它返回給那些需要訪問它所描述的ContentProvider組件的調(diào)用方應(yīng)用程序進程;
  • 接下來通過localProvider.attachInfo()來進一步初始化前面所創(chuàng)建的ContentProvider組件;
  • 接下來對全局變量mProviderMap加同步鎖,然后對localProvider判空,正常情況下此時的localProvider應(yīng)該是非空的,所以,自然要把它保存到mLocalProviders和mLocalProvidersByName中;
  • 但是如果localProvider仍然為空,接下來會出現(xiàn)一個ProviderRefCount對象prc,用來表示和記錄ContentProvider組件的引用計數(shù),如果prc為空,則把這個localProvider封裝成一個ProviderClientRecord對象,并保存在prc變量中;
  • 最后,返回這個ContentProviderHolder對象!

接下來,繼續(xù)分析ContentProvider組件的成員函數(shù)getIContentProvider()和成員函數(shù)attachInfo()的實現(xiàn)細節(jié)!

step21: ContentProvider#getIContnetProvider()

ContentProvider類成員函數(shù)getIContentProvider()源碼如下:

public abstract class ContentProvider implements ComponentCallbacks2 {
    ......
    private Transport mTransport = new Transport();
    ......
    class Transport extends ContentProviderNative {......}
    ......
    public IContentProvider getIContentProvider() {
        return mTransport;
    }
    ......
}
  • 每一個ContentProvider組件都有一個內(nèi)部類Transport,其本質(zhì)是一個Binder本地對象;并且持有一個類型為Transport的全局變量mTransPort來表示一個Binder本地對象。
  • 通過將這個Binder本地對象傳遞給AMS,然后AMS會將引用了這個Binder本地對象的一個Binder代理對象返回給需要訪問該ContentProvider組件的其他應(yīng)用程序進程;這樣,其他應(yīng)用程序進程就可以通過這個Binder代理對象來間接的訪問一個ContentProvider組件中的數(shù)據(jù)了!
  • ContentProvider類成員函數(shù)getIContentProvider()的實現(xiàn)很簡單,只是簡單地將全局變量mTransPort描述的一個IContentProvider接口返回給調(diào)用者。

step22: ContentProvider#attachInfo()

ContentProvider類的成員函數(shù)attachInfo(),作用是初始化前面所創(chuàng)建的一個ContentProvider組件!attachInfo()源碼如下:

public abstract class ContentProvider implements ComponentCallbacks2 {
    ......
    /**
     * After being instantiated, this is called to tell the content provider
     * about itself.
     *
     * @param context The context this provider is running in
     * @param info Registered information about this content provider
     */
    public void attachInfo(Context context, ProviderInfo info) {
        attachInfo(context, info, false);
    }

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        ......
        /*
         * Only allow it to be set once, so after the content service gives
         * this to us clients can't change it.
         */
        if (mContext == null) {
            mContext = context;
            if (context != null) {
                mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                ......
            }
            ContentProvider.this.onCreate();
        }
    }
    ......
}
  • 依然是兩個重載函數(shù),接收兩個參數(shù)的attachInfo()函數(shù)內(nèi)部調(diào)用了接收三個參數(shù)的attachInfo()函數(shù),我們直接關(guān)注接收三個參數(shù)的attachInfo()函數(shù);
  • setReadPermission(info.readPermission),setWritePermission(info.writePermission)和setPathPermissions(info.pathPermissions)這三個函數(shù)的作用是,設(shè)置ContentProvider組件的讀寫權(quán)限和訪問權(quán)限;
  • 最后ContentProvider.this.onCreate()函數(shù),實際調(diào)用的是COntentProvider子類的onCreate()函數(shù),以便在子類的onCreate()函數(shù)中,執(zhí)行一些業(yè)務(wù)相關(guān)的初始化操作!
  • 在我們最開始的場景中,ContentProvider組件指的就是BxxApp應(yīng)用程序進程中SubContentProvider組件,所以上面調(diào)用的時加上就是SubContentProvider類的onCreate()函數(shù),而我們的業(yè)務(wù)邏輯相關(guān)的一些初始化工作,也正是放在SubContentProvider類的onCreate()函數(shù)中執(zhí)行的!

step23: SubContentProvider#onCreate()

public class SubContentProvider extends ContentProvider {
    ......
    @Override
    public boolean onCreate() {
        ContentResolver resolver = getContext().getContentResolver();
        DatabaseHelper helper = new DtatabaseHelper(......);
        ......
        return true;
    }
    ......
}
  • 需要注意,由于一個ContentProvider組件再啟動過程中需要執(zhí)行onCreate()函數(shù),因此,我們應(yīng)該避免在onCeate()方法中執(zhí)行耗時操作,例如和IO相關(guān)的操作,否則可能造成這個ContentProvider組件啟動超時!

這一步執(zhí)行完成后,就會返回到前面的step20,然后再次返回到前面的step19,即ActivityThread類成員函數(shù)installContentProviders()中,接下來就會調(diào)用AMS代理對象的成員函數(shù)publishContentProviders(),也即ActivityManagerProxy的成員函數(shù)publishContentProviders(),將前面所有啟動的ContentProvider組件的一個IContentProvider訪問接口發(fā)布到ActivityManagerService中~

step24: ActivityManagerProxy#publishContentProviders()

ActivityManagerProxy類成員函數(shù)publishContentProviders(),會將所有啟動的ContentProvider組件的一個IContentProvider訪問接口發(fā)布到ActivityManagerService中,源碼如下:

public void publishContentProviders(IApplicationThread caller,
        List<ContentProviderHolder> providers) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    data.writeStrongBinder(caller != null ? caller.asBinder() : null);
    data.writeTypedList(providers);
    mRemote.transact(PUBLISH_CONTENT_PROVIDERS_TRANSACTION, data, reply, 0);
    reply.readException();
    data.recycle();
    reply.recycle();
}
  • 先把接收的參數(shù)保存到Parcel對象data中;
  • 接著再通過ActivityManagerProxy類內(nèi)部的一個Binder代理對象mRemote向ActivityMnagerService發(fā)送一個類型為PUBLISH_CONTENT_PROVIDERS_TRANSACTION進程間通信請求;

step25: ActivityManagerService#publishContentProviders()

以上10步都是在新建的應(yīng)用程序進程中執(zhí)行的!step25是在ActivityManagerService中執(zhí)行的,AMS類成員函數(shù)publishContentProviders(),用來處理類型為PUBLISH_CONTENT_PROVIDERS_TRANSACTION進程間通信請求,其源碼如下:

AMS#publishContentProviders()
  • 參數(shù)caller是一個類型為ApplicationThread的Binder代理對象,它引用了運行在新建應(yīng)用程序進程中的一個ApplicationThread對象,getRecordForAppLocked(caller)方法通過caller來獲取一個用來描述新建應(yīng)用程序進程的ProcessRecord對象r;
  • 新建應(yīng)用程序進程在啟動時,會將需要在它里面運行的ContentProvider組件啟動起來!從前面的step13可知,在AMS中,這些ContentProvider組件使用一個ContentProviderRecord對象來描述,它們保存在用來描述新建應(yīng)用程序進程的一個ProcessRecord對象r的一個成員變量pubProviders中;
  • 第10到14行,第一個for循環(huán),取出providers中的每一個ContentProvider組件,并且拿到ContentProvider組件對應(yīng)的ContentProviderRecord對象dst;
  • 第15到22行,第二個for循環(huán),通過兩種方式,把ContentProviderRecord對象dst保存到全局變量mProviderMap;
  • 參數(shù)providers包含了要發(fā)布到AMS中的ContentProvider組件,每一個ContentProvider組件都使用一個ContentProviderHolder對象來描述,它里面包含了要發(fā)布的ContentProvider組件的一個IContentProvider接口,如圖第34行到40行所示!

從前面的step8可知,一個應(yīng)用程序進程請求ActivityManagerService返回一個ContentProvider組件的代理對象時,如果這個ContentProvider組件還未啟動起來,那么AMS就會先創(chuàng)建一個新的應(yīng)用程序進程來啟動該ContentProvider組件,然后再在一個while循環(huán)中等待該ContentProvider組件啟動完成,并且將他的一個代理對象發(fā)布到AMS中。

現(xiàn)在既然這個ContentProvider已經(jīng)啟動完成,并且將它的一個代理對象,即一個類型為Transport的Binder代理對象發(fā)布到AMS,因此,前面正在等待的一個AMS線程就可以停止等待,并且將這個類型為Transport的Binder代理對象封裝成一個ContentProvider對象返回給請求它的應(yīng)用程序進程。

這一步執(zhí)行完畢,就會使得AMS從前面的step8返回step5,即返回到MainActivity組件所運行的應(yīng)用程序進程中,即AxxApp應(yīng)用程序進程!然后,繼續(xù)執(zhí)行ActivityThread類成員函數(shù)installProvider(),用來保存前面從ActivityManagerService獲得的一個ContentProvider組件的一個IContentProvider訪問接口。

step26: ActivityThread#installProvider()

ActivityThread類成員函數(shù)installProvider()源碼如下:

/**
 * Installs the provider.
 *
 * Providers that are local to the process or that come from the system server
 * may be installed permanently which is indicated by setting noReleaseNeeded to true.
 * Other remote providers are reference counted.  The initial reference count
 * for all reference counted providers is one.  Providers that are not reference
 * counted do not have a reference count (at all).
 *
 * This method detects when a provider has already been installed.  When this happens,
 * it increments the reference count of the existing provider (if appropriate)
 * and returns the existing provider.  This can happen due to concurrent
 * attempts to acquire the same provider.
 */
private IActivityManager.ContentProviderHolder installProvider(Context context,
        IActivityManager.ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        if (context.getPackageName().equals(ai.packageName)) {
            c = context;
        } else if (mInitialApplication != null &&
                   mInitialApplication.getPackageName().equals(ai.packageName)) {
            c = mInitialApplication;
        } else {
            try {
                c = context.createPackageContext(ai.packageName,
                                                 Context.CONTEXT_INCLUDE_CODE);
            } catch (PackageManager.NameNotFoundException e) {
                // Ignore
            }
        }
        if (c == null) {
            return null;
        }
        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
            localProvider = (ContentProvider)cl.
                            loadClass(info.name).newInstance();
            provider = localProvider.getIContentProvider();
            if (provider == null) {
                return null;
            }
            // XXX Need to create the correct context for this provider.
            localProvider.attachInfo(c, info);
        } catch (java.lang.Exception e) {......}
    } else {
        provider = holder.provider;
    }

    IActivityManager.ContentProviderHolder retHolder;

    synchronized (mProviderMap) {
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) {
            ComponentName cname = new ComponentName(info.packageName, info.name);
            ProviderClientRecord pr = mLocalProvidersByName.get(cname);
            if (pr != null) {
                provider = pr.mProvider;
            } else {
                holder = new IActivityManager.ContentProviderHolder(info);
                holder.provider = provider;
                holder.noReleaseNeeded = true;
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                mLocalProviders.put(jBinder, pr);
                mLocalProvidersByName.put(cname, pr);
            }
            retHolder = pr.mHolder;
        } else {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                // We need to transfer our new reference to the existing
                // ref count, releasing the old one...  but only if
                // release is needed (that is, it is not running in the
                // system process).
                if (!noReleaseNeeded) {
                    incProviderRefLocked(prc, stable);
                    try {
                        ActivityManagerNative.getDefault().removeContentProvider(
                            holder.connection, stable);
                    } catch (RemoteException e) {
                        //do nothing content provider object is dead any way
                    }
                }
            } else {
                ProviderClientRecord client = installProviderAuthoritiesLocked(
                                                provider, localProvider, holder);
                if (noReleaseNeeded) {
                    prc = new ProviderRefCount(holder, client, 1000, 1000);
                } else {
                    prc = stable
                          ? new ProviderRefCount(holder, client, 1, 0)
                          : new ProviderRefCount(holder, client, 0, 1);
                }
                mProviderRefCountMap.put(jBinder, prc);
            }
            retHolder = prc.holder;
        }
    }
    return retHolder;
}
  • 這步與前面的step20,都是在ActivityThread類成員函數(shù)installProvider()。不過,前面step20是在啟動ContentProvider組件的應(yīng)用程序進程中執(zhí)行的;而這步是在AxxApp應(yīng)用程序的MainActivity中執(zhí)行的!另一個區(qū)別是:這步得到參數(shù)provider不等于null,它用來描述一個在其他應(yīng)用程序進程中啟動的ContentProvider組件的一個IContentProvider訪問接口。

  • 這步執(zhí)行完成后,就返回到前面的step1,這時在AxxApp應(yīng)用的MainActivity中,就獲得了與URI值對應(yīng)的SubContentProvider組件的一個IContentProvider訪問接口,最后就可以通過這個接口訪問另一個應(yīng)用程序的數(shù)據(jù)內(nèi)容了!

0x03 參考文獻與簡單的結(jié)語


至此,ContentProvider啟動流程分析到此結(jié)束!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,003評論 2 374

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