Android源码设计模式之笔记(陆续更新)

开篇之单例模式:
相信大家已经很熟悉单例模式了吧,特别是在保证某个类只有一个对象的场景下才会使用到。那有人会问什么时候用到单例模式呢,其实如果一个对象创建需要消耗过多资源时,正是单例模式用到之处。

看看单例模式的UML类图:


单例模式UML图.png
  • 单例类里面有成员变量(当前实例自己)
  • 暴露一个public类型的getInstance方法
  • 当前构造器不公开,private类型

样例代码:

public class SingletonDemo {
    private static SingletonDemo singletonDemo;
    private SingletonDemo(){}
    //懒汉式获取单例实例
    public static SingletonDemo getInstance() {
        if (singletonDemo == null) {
            synchronized (SingletonDemo.class) {
                if (singletonDemo == null) {
                    singletonDemo = new SingletonDemo();
                }
            }
        }
        return singletonDemo;
    }

   ***********************
    //饱汉式
    private static final SingletonDemo singletonDemo = new SingletonDemo();
    private SingletonDemo() {
    }
    public static SingletonDemo getInstance() {
        return SingletonDemo.singletonDemo;
    }
}

看到这个地方,可能大家还是觉得不好使用单例模式。想想你的项目中那些网络操作对话框的管理io文件的读取等等单一实例的地方是不是都可以去处理呢。但是单例模式由于是一直持有实例的,因此对于上下文(context)的地方,需要注意注意内存泄漏的情况了。

Builder模式:
builder设计模式的出现,是为了更好地为一些复杂对象进行分离处理。通俗点也就是类的功能太多了,将一些行为放到Builder类中间接处理。

UML类图:


builder模式UML图.png

样例代码:

public class Factory {
    Builder builder;

    public Factory() {
        builder = new Builder();
    }

    public void createEngine() {
        builder.createEngine();
    }

    public void createGearbox() {
        builder.createGearbox();
    }

    public void createSuspension() {
        builder.createSuspension();
    }

    public static class Builder {
        private Car car;

        public Builder() {
            car = new Car();
        }

        public Builder createEngine() {
            car.engine = "自然吸气";
            return this;
        }

        public Builder createGearbox() {
            car.gearbox = "at变速箱";
            return this;
        }

        public Builder createSuspension() {
            car.suspension = "独立悬架";
            return this;
        }
    }
}

public class Car {
    //发动机
    public String engine;
    //变速箱
    public String gearbox;
    //悬架
    public String suspension;

    //正规写法是提供set方法的,参数都是由外部提供的,为了省事,你懂的
}

上面事例代码,也正好说明了Builder模式的特点,将复杂对象的构成放到我们的内部类中进行处理。

还记得之前我们显示一个对话框的代码吗?

AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("dialog title");
builder.setMessage("I am a dialog.");
builder.setPositiveButton("ok", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        //// TODO: 17/12/26
    }
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        //// TODO: 17/12/26
    }
});
builder.show();

相信这段代码谁都会写了吧,以前总是一头雾水地写完这段代码,然后交给经理乐乐地说搞定了。现在不行啊,对源码得有点分析,下面就去看看源码吧:

首先是生成了一个AlertDialog.Builder对象,那咋们瞧瞧吧:

public class AlertDialog extends AppCompatDialog implements DialogInterface {
    ******省略AlertDialog类中代码******
    public static class Builder {
        private final AlertController.AlertParams P;
        private final int mTheme;
        /**
         * Creates a builder for an alert dialog that uses the default alert
         * dialog theme.
         * <p>
         * The default alert dialog theme is defined by
         * {@link android.R.attr#alertDialogTheme} within the parent
         * {@code context}'s theme.
         *
         * @param context the parent context
         */
        public Builder(@NonNull Context context) {
            this(context, resolveDialogTheme(context, 0));
        }
        /**
         * Creates a builder for an alert dialog that uses an explicit theme
         * resource.
         * <p>
         * The specified theme resource ({@code themeResId}) is applied on top
         * of the parent {@code context}'s theme. It may be specified as a
         * style resource containing a fully-populated theme, such as
         * {@link R.style#Theme_AppCompat_Dialog}, to replace all
         * attributes in the parent {@code context}'s theme including primary
         * and accent colors.
         * <p>
         * To preserve attributes such as primary and accent colors, the
         * {@code themeResId} may instead be specified as an overlay theme such
         * as {@link R.style#ThemeOverlay_AppCompat_Dialog}. This will
         * override only the window attributes necessary to style the alert
         * window as a dialog.
         * <p>
         * Alternatively, the {@code themeResId} may be specified as {@code 0}
         * to use the parent {@code context}'s resolved value for
         * {@link android.R.attr#alertDialogTheme}.
         *
         * @param context the parent context
         * @param themeResId the resource ID of the theme against which to infra
         *                   this dialog, or {@code 0} to use the parent
         *                   {@code context}'s default alert dialog theme
         */
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }
        /**
         * Returns a {@link Context} with the appropriate theme for dialogs area
         * Applications should use this Context for obtaining LayoutInflaters of
         * that will be used in the resulting dialogs, as it will cause views to
         * the correct theme.
         *
         * @return A Context for built Dialogs.
         */
        @NonNull
        public Context getContext() {
            return P.mContext;
        }
        /**
         * Set the title using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set met
         */
        public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }
        /**
         * Set the title displayed in the {@link Dialog}.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTitle(@Nullable CharSequence title) {
            P.mTitle = title;
            return this;
        }
        /**
         * Set the message to display using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMessage(@StringRes int messageId) {
            P.mMessage = P.mContext.getText(messageId);
            return this;
        }

        /**
         * Set the message to display.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMessage(@Nullable CharSequence message) {
            P.mMessage = message;
            return this;
        }
        /**
         * Set a listener to be invoked when the positive button of the dialog is pressed.
         * @param textId The resource id of the text to display in the positive button
         * @param listener The {@link DialogInterface.OnClickListener} to use.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
            P.mPositiveButtonText = P.mContext.getText(textId);
            P.mPositiveButtonListener = listener;
            return this;
        }

        /**
         * Set a listener to be invoked when the positive button of the dialog is pressed.
         * @param text The text to display in the positive button
         * @param listener The {@link DialogInterface.OnClickListener} to use.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
            P.mPositiveButtonText = text;
            P.mPositiveButtonListener = listener;
            return this;
        }

        /**
         * Set a listener to be invoked when the negative button of the dialog is pressed.
         * @param textId The resource id of the text to display in the negative button
         * @param listener The {@link DialogInterface.OnClickListener} to use.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) {
            P.mNegativeButtonText = P.mContext.getText(textId);
            P.mNegativeButtonListener = listener;
            return this;
        }

        /**
         * Set a listener to be invoked when the negative button of the dialog is pressed.
         * @param text The text to display in the negative button
         * @param listener The {@link DialogInterface.OnClickListener} to use.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
            P.mNegativeButtonText = text;
            P.mNegativeButtonListener = listener;
            return this;
        }
        /**
         * Creates an {@link AlertDialog} with the arguments supplied to this
         * builder and immediately displays the dialog.
         * <p>
         * Calling this method is functionally identical to:
         * <pre>
         *     AlertDialog dialog = builder.create();
         *     dialog.show();
         * </pre>
         */
        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
        /**
         * Creates an {@link AlertDialog} with the arguments supplied to this
         * builder.
         * <p>
         * Calling this method does not display the dialog. If no additional
         * processing is needed, {@link #show()} may be called instead to both
         * create and display the dialog.
         */
        public AlertDialog create() {
            // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
            // so we always have to re-set the theme
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }
    }
}

这里就是我们刚才显示对话框的时候,调的几个方法。可以看到Builder内部类中的setTitlesetMessagesetPositiveButton
setNegativeButton方法都是给AlertController.AlertParams P变量赋值。看来AlertParams又是AlertController内部类了。接着就是调了show方法,show方法里面紧接着调了create方法。在create方法里面生成了一个AlertDialog,然后调用了
AlertController.AlertParamsapply方法,去看看做了些啥吧:

public void apply(AlertController dialog) {
    if (mCustomTitleView != null) {
        dialog.setCustomTitle(mCustomTitleView);
    } else {
        if (mTitle != null) {
            dialog.setTitle(mTitle);
        }
        if (mIcon != null) {
            dialog.setIcon(mIcon);
        }
        if (mIconId != 0) {
            dialog.setIcon(mIconId);
        }
        if (mIconAttrId != 0) {
            dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
        }
    }
    if (mMessage != null) {
        dialog.setMessage(mMessage);
    }
    if (mPositiveButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                mPositiveButtonListener, null);
    }
    if (mNegativeButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                mNegativeButtonListener, null);
    }
    if (mNeutralButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                mNeutralButtonListener, null);
    }
    // For a list, the client can either supply an array of items or an
    // adapter or a cursor
    if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
        createListView(dialog);
    }
    if (mView != null) {
        if (mViewSpacingSpecified) {
            dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                    mViewSpacingBottom);
        } else {
            dialog.setView(mView);
        }
    } else if (mViewLayoutResId != 0) {
        dialog.setView(mViewLayoutResId);
    }
    /*
    dialog.setCancelable(mCancelable);
    dialog.setOnCancelListener(mOnCancelListener);
    if (mOnKeyListener != null) {
        dialog.setOnKeyListener(mOnKeyListener);
    }
    */
}

看到了没,这里才是将上面builder中的赋值又传给了AlertController类,这里我们就看下AlertControllersetTitle方法:

public void setTitle(CharSequence title) {
    mTitle = title;
    if (mTitleView != null) {
        mTitleView.setText(title);
    }
}

这里就把builder中传过来的title给了AlertController中的mTitleView。那咱们看看mTitleView是什么时候生成的吧:

private void setupTitle(ViewGroup topPanel) {
    if (mCustomTitleView != null) {
       //省略代码
    } else {
        mIconView = (ImageView) mWindow.findViewById(android.R.id.icon);
        final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
        if (hasTextTitle && mShowTitle) {
            // Display the title if a title is supplied, else hide it.
            mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
            mTitleView.setText(mTitle);
            //省略代码
    }
}

可以看到实际上mTitleViewwindow对象的R.id.alertTitle布局了。这里可以自己去研究该window下是怎么生成的,这里就把create的过程屡了一遍了,最后就剩下了show。可以看到Buildershow方法最后调用了AlertDialogshow方法。

这里可以画张流程图更清晰:

dialog生成的流程图.png

关于builder模式就先说这么多了,总结下来就是将复杂对象的生成放到单独的一个类进行处理,对主类进行分离。

一直看好的工厂模式:

说到工厂模式其实大家可能没怎么留意,而且自己在写代码的时候,也很少知道自己写的是不是工厂模式了。工厂模式显著的特点是具体产品类专门由一个叫工厂类专门去构造,也就是new的过程。这样的模式好处是调用者无需关心具体构造产品的过程,而且对于一些复杂对象的构造,也起到了透明的效果。
UML类图:

工厂模式UML图.png

事例代码也很简单:

//抽象的产品
public abstract class Product {
    public abstract void function();
}
//具体的产品
public class ProductA extends Product {
    @Override
    public void function() {
        //// TODO: 17/12/27  
    }
}
//具体的产品
public class ProductB extends Product {
    @Override
    public void function() {
        //// TODO: 17/12/27  
    }
}
//抽象工厂
public abstract class Factory {
    abstract Product createProductA();

    abstract Product createProductB();
}
//具体的工厂类
public class FactoryProduct extends Factory {
    @Override
    Product createProductA() {
        return new ProductA();
    }

    @Override
    Product createProductB() {
        return new ProductB();
    }
}

所以从上面结构看,标准的工厂模式是产品有各种各样的,而工厂就只有一个

下面通过另外一种方式去看下工厂类的写法(反射来获取具体产品):

//反射方式的抽象工厂类
public abstract class ReflexFactory {
    protected abstract <T extends Product> T createProduct(Class<T> cl);
}
//具体的反射工厂类
public class ReflexFactoryProduct extends ReflexFactory {
    @Override
    protected <T extends Product> T createProduct(Class<T> cl) {
        Product p = null;
        try {
            p = (Product) Class.forName(cl.getName()).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) p;
    }
}

这里和普通的工厂类相比,少了创建不同product的方法,通过传入不同的class类型来获得不同的product。但是貌似只能调用到无参的product,这里尴尬了,要是构造器要传入属性就麻烦了,不就相当于new Object()了。

这里除了上面两种工厂类之外,还有种静态工厂类的形式:

public class StaticFactory {
    enum ProductType {
        ProductA, ProductB;
    }

    public static Product createProduct(ProductType type) {
        switch (type) {
            case ProductA:
                return new ProductA();
            case ProductB:
                return new ProductB();
        }
        return null;
    }
}

静态工厂类就一个类啊,通过分支创建不同的Product。缺点就是如果很多种product的话,那这个类就庞大了,而且这里如果每种product需要传参的话,那静态方法的参数也是不定的。好处就是一个类搞定了啊。

好了,说了几种工厂模式后,去看下android源码中有没有应用了:

AudioManager audioManager=context.getSystemService(Context.AUDIO_SERVICE)

相信大家都获取过****Manager了吧,那咱们去看看这句代码跟工厂模式有关系没。
是不是在activity中直接有getSystemService呢,那咱们去看下吧:

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

直接看最后一行调用了父类的getSystemService方法:

Activity父类图.png

那咱们去ContextThemeWrapper中去找呗:

    @Override
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

看最后一行就行了,这里获取getBaseContext后,然后调了getSystemService。咱们看下getBseContext是什么鬼。又要去ContextThemeWrapper的父类去找getBaseContext了:

ContextThemeWrapper父类图.png

好吧,父类是ContextWrapper,那咱们看下getBaseContext是什么了:

    /**
     * @return the base context as set by the constructor or setBaseContext
     */
    public Context getBaseContext() {
        return mBase;
    }

这里是ContextWrapper中的mBase变量了:

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     * 
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

赋值就这两个地方了。那咱们就知道Activity中的context实际上就是ContextWrapper中的mBase变量了。这就要追溯到Activity创建的地方了,才能揭穿mBase的真面目了。这里需要知道点Android的应用启动流程了,咱们就直接看ActivityThread类了:

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //省略代码
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
            if (localLOGV) Slog.v(
                    TAG, r + ": app=" + app
                    + ", appName=" + app.getPackageName()
                    + ", pkg=" + r.packageInfo.getPackageName()
                    + ", comp=" + r.intent.getComponent().toShortString()
                    + ", dir=" + r.packageInfo.getAppDir());

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);
        //省略代码

        return activity;
    }

看到最后面调用了activityattach方法,并且把创建的appContext传进了attach方法,那咱们看下创建这个context是什么了:

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
    //省略代码
    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.token, displayId, r.overrideConfig);
    appContext.setOuterContext(activity);
    Context baseContext = appContext;
    //省略代码
    return baseContext;
}

看到这的时候,基本就知道上面说的mBase其实就是ContextImpl了。Activityattach的方法也正是把传进来的ContextImpl给了ContextWrapper中的mBase

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window) {
    //调用了父类ContextWrapper的attach方法
    attachBaseContext(context);
    //省略代码
}

到这里ContextWrapper中的mBase其实是一个ContextImpl了,下面就去看下ContextImpgetSystemService方法了:

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

好吧,又是走了一层了,去看下SystemServiceRegistry中的getSystemService方法吧:

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

这里就很清晰了,首先通过nameSYSTEM_SERVICE_FETCHERS中获取一个ServiceFetcher对象,然后调用了getService方法:

private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new HashMap<String, ServiceFetcher<?>>();

好吧,这里是个HashMap,那咱们看下什么时候put进去的ServiceFetcher

private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

这里是在registerService的时候将serviceFetcherput进去的:

static {
    registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
            new CachedServiceFetcher<AccessibilityManager>() {
        @Override
        public AccessibilityManager createService(ContextImpl ctx) {
            return AccessibilityManager.getInstance(cox);
        }});
    registerService(Context.CAPTIONING_SERVICE, CaptioningManager.class,
            new CachedServiceFetcher<CaptioningManager>() {
        @Override
        public CaptioningManager createService(ContextImpl ctx) {
            return new CaptioningManager(cox);
        }});
    registerService(Context.ACCOUNT_SERVICE, AccountManager.class,
            new CachedServiceFetcher<AccountManager>() {
        @Override
        public AccountManager createService(ContextImpl ctx) {
            IBinder b = ServiceManager.getService(Context.ACCOUNT_SERVICE);
            IAccountManager service = IAccountManager.Stub.asInterface(b);
            return new AccountManager(ctx, service);
        }});
        //registerService太多了,我这里就罗列上面几个了
}

看到这的时候,才看到有工厂模式的影子啊,好多小伙伴都要哭了。源码真的是藏得深啊。这里简单说下在静态的时候,通过service的name构造出不同的ServiceFetcher,并存储在SYSTEM_SERVICE_FETCHERS中。然后在getSystemService过程中通过传进来的name获取不同的ServiceFetcher,最后调用getService方法获取相应的Manager了。不难看出这里的ServiceFetcher就是产品抽象类,SystemServiceRegistry就是一个生产***Manager的静态工厂类了。

下面画张图理解下ActivitygetSystemService

getSystemService流程图.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,604评论 18 399
  • 设计模式基本原则 开放-封闭原则(OCP),是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。开-闭原...
    西山薄凉阅读 3,777评论 3 14
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,922评论 1 15
  • 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于...
    JxMY阅读 934评论 1 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,642评论 18 139