应用最广的模式-单例模式(结合Android源码)

谈起设计模式估计大家都不会陌生,一个项目中至少会用到其中的一种模式,今天要说的主角就是单列,我了大致总结了它的几种用法同时也结合了Android的源码进行单列的分析;

好了正题开始了,其实个人总结了下自我学习的方法,在学习任何一个新的事物的时候,不能盲目的去干,而应适当的采取一定的技巧性东西,OK;

我大致分了三大步:

1:要知道这个东西是个什么玩意,这个东西有啥用,一般用在啥地方;
2:这个东西该怎么用了,我平时有没有遇到类似的用法;
3:熟悉了用法之后,总结下为什么别人那样去写,这样写的优缺点是什么,我能不能仿写下或者能不能改写下别人的代码,进行深度的总结下,然后用于到实践中,记住,看完了,千万不要就丢掉了,东西太多了,也许今天记住了,明天就会忘记,所以最好写几个案列实践下;实践是检验真理的位移标准,不是吗;

1: 单列模式的定义以及应用场景

1.1 定义:

确保这个类在内存中只会存在一个对象,而且自行实例化并向整个系统提供这个实例;

1.2 场景:

一般创建一个对象需要消耗过多的资源,如:访问I0和数据库等资源或者有很多个地方都用到了这个实例;

2:单列模式的几种基本写法:

2.1 最常见的写法:(懒汉式和饿汉式)
2.1.1 饿汉式
public class Singleton {

    private static final Singleton INSTANCE=new Singleton();

    private Singleton(){
    }

    public static Singleton getInstance(){
        return INSTANCE;
    }
}
2.1.1 懒汉式
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

上边的两种是最常见的,顾名思义懒汉式和饿汉式,一个是拿时间换空间,一个是拿空间换时间,懒汉式只有我需要他的时候才去加载它,懒加载机制,饿汉式不管需不需要我先加载了再说,先在内存中开辟一块空间,占用一块地方,等用到了直接就拿来用.这两种是最基本的单列模式,

2.1.2.1懒汉式:

懒汉式缺点:效率低,第一次加载需要实例化,反应稍慢。每次调用getInstance方法都会进行同步,消耗不必要的资源。

2.1.1.1饿汉式:

缺点:不需要的时候就加载了,造成资源浪费.

2.2 双重检查单列(DCL实现单列)
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }

    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这种写法估计是我们在开发中最常用的,这次代码的亮点是是在getInstance()方法中进行了双重的判断,第一层判断的主要避免了不必要的同步,第二层判断是为了在null的情况下再去创建实例;举个简单的列子:假如现在有多个线程同时触发这个方法: 线程A执行到nstance = new Singleton(),它大致的做了三件事:
(1):给Singleton实例分配内存,将函数压栈,并且申明变量类型;
(2):初始化构造函数以及里面的字段,在堆内存开辟空间;
(3):将instance对象指向分配的内存空间;

演示图.png

这种写法也并不是保证完全100%的可靠,由于java编译器允许执行无序,并且jdk1.5之前的jvm(java内存模型)中的Cache,寄存器到主内存的回写顺序规定,第二个和第三个执行是无法保证按顺序执行的,也就是说有可能1-2-3也有可能是1-3-2; 这时假如有A和B两条线程,A线程执行到3的步骤,但是未执行2,这时候B线程来了抢了权限,直接取走instance这时候就有可能报错,同时我也放了一张内存模型,帮助大家更好的去理解,如图;

内存模型.png
简单总结就是说jdk1.5之前会造成两个问题

1:线程间共享变量不可见性;
2:无序性(执行顺序无法保证);
当然这个bug已经修复了,SUN官方调整了JVM,具体了Volatile关键字,因此在jdk1.5之前只需要写成这样既可, private Volatitle static Singleton instance; 这样就可以保证每次都是从主内存中取,当然这样写或多或少的回影响性能,但是为了安全起见,这点性能牺牲还是值得;

双重检查单列(DCL)

优点:资源利用率高,第一次执行方法是单列对象才会被实例化;
缺点:第一次加载时会稍慢,jdk1.5之之前有可能会加载会失败;

Android常用的框架:Eventbus(DCL双重检查)
  static volatile EventBus defaultInstance;
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
2.3 静态内部内实现单列
public class Singleton {

    private static Singleton instance;

    private Singleton() {

    }

    public static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

这种方式不仅确保了线程的安全性,也能够保证对象的唯一性,同时也是延迟加载,很多技术大牛也是这样推荐书写;

2.4 枚举实现单列
public enum SingletonEnum {

    INSTANCE;
    public void doSomething() {

    }
}

优点:相对于其他单列来说枚举写法最简单,并且任何情况下都是单列的,JDK1.5之后才有的;

2.5 使用容器单列
public class SingletonManager {

    private static Map<String, Object> objMap = new HashMap<>();

    private SingletonManager() {

    }

    public static void putObject(String key, Object instance){
        if(!objMap.containsKey(key)){
            objMap.put(key, instance);
        }
    }

    public static Object getObject(String key){
        return objMap.get(key);
    }
}

在程序开始的时候将单列类型注入到一个容器之中,也就是单列ManagerClass,在使用的时候再根据key值获取对应的实例,这种方式可以使我们很方便的管理很多单列对象,也对用户隐藏了具体实现类,降低了耦合度;但是为了避免造成内存泄漏,所以我们一般在生命周期销毁的时候也要去销毁它;

Android源码分析:

说了这么多,也写了这么多,摩拳擦掌,让我们深吸一口气,准备好,老司机发车了,上车了,一起来看看Android源码中是如何实现单列的,今天的的重点就是LayoutInflater这个类;

LayoutInflater的单列模式实现:

基本用法:LayoutInflater mInflater = LayoutInflater.from(this);

上边的写法估计没有人会陌生,获取LayoutInflater 的实例,我们一起看看具体的源码实现:

  /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
    //调用Context getSystemService()方法获取;
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
Context 类

public abstract Object getSystemService(@ServiceName @NonNull String name);

我们知道Context是一个抽象类,那么到底是那个类具体实现的了,我们C+H(window)一下,看看他到底有哪些子类,看下图;


Paste_Image.png

我擦,是不是搞事情,这么多类怎么找,一个类一类的去翻吗?既然不能从这个地方下手,那就只能改走其他的道路,那我就从入口函数开始,也就是我们熟悉的main函数 在Android中ActivityThread类中,看主要的方法和类;

 ### ActivityThread thread = new ActivityThread();  
主要看thread的attach(false)方法:
public static void main(String[] args) {
    省略.......
       //初始化thread        
        ActivityThread thread = new ActivityThread(); 
        thread.attach(false);
      省略.......
    }
  private void attach(boolean system) {
     //是不是系统应用:传递的false
       if (!system) {
       省略......
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
          省略......
        }

在main方法中初始化ActivityThread的实例,并且调用了attach方法 传入false,证明不是系统应用,紧接着获取了IActivityManager 实例,其实也就是ActivityManagerService的对象,他们的关系图如下;


@62[RUXWSUUF5}IJ86]_GL8.png

接着调用attachApplication(mAppThread);绑定当前的ApplicationThread;接着往下走,看看attachApplication(mAppThread)方法,还是一样的抓住核心,只看重点;

 @Override
    public final void attachApplication(IApplicationThread thread) {
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid);
            Binder.restoreCallingIdentity(origId);
        }
    }

这个方法逻辑就很简单了锁定当前的thread和pid 接着继续往下走;

ActivityManagerService

private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
省略.......
            thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());
    //省略........
        // See if the top visible activity is waiting to run in this process...
        if (normalMode) {
            try {
                if (mStackSupervisor.attachApplicationLocked(app)) {
                    didSomething = true;
                }
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
            }
        }
//下边主要是介绍了些receiver和broadcast 这些都不是重点主要看和app有关的,  所以就省略掉了;

这个方法代码很长但是逻辑并不是很复杂,有两个重要的方法, thread.bindApplication()和attachApplicationLocked(app);bindApplication见名之意,将thread绑定到ActivityManagerService中,那我们来看看attachApplicationLocked(app)这个方法,

ActivityStackSupervisor

boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
        final String processName = app.processName;
        boolean didSomething = false;
        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                final ActivityStack stack = stacks.get(stackNdx);
                if (!isFrontStack(stack)) {
                    continue;
                }
             //返回当前栈顶的activity
                ActivityRecord hr = stack.topRunningActivityLocked(null);
                if (hr != null) {
                    if (hr.app == null && app.uid == hr.info.applicationInfo.uid
                            && processName.equals(hr.processName)) {
                        try {
       //真正的开启activity;原来找了半天就是这个家伙;
                            if (realStartActivityLocked(hr, app, true, true)) {
                                didSomething = true;
                            }
                        } catch (RemoteException e) {
                            Slog.w(TAG, "Exception in new application when starting activity "
                                  + hr.intent.getComponent().flattenToShortString(), e);
                            throw e;
                        }
                    }
                }
            }
        }
      
    }

一看这个名字就知道肯定和Activity的任务栈有关的,当前内部持有一个ActivityStack,相当于ActivityStack的辅助管理类;realStartActivityLocked(hr, app, true, true)而这个方法是真正的去开启activity的

final boolean realStartActivityLocked(ActivityRecord r,
          ProcessRecord app, boolean andResume, boolean checkConfig)
          throws RemoteException {
//省略..主要是检查一些配置信息和设置相关的参数等等........
//参数设置完毕终于准备启动activity了,发车了;
          app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                  System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                  new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
                  task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                  newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);

     //省略...........
}

重点的东西来了既然这个方法是用来开启activity的我猜想他肯定和Context有关,既然和Context有关那么也就能找到Context的子类带这个目标我们出发了,我已饥渴难耐了;

ActivityThread

 @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }

这个方法准备了要启动的activity的一些信息,重要的一点他利用Handler发送了一个消息, sendMessage(H.LAUNCH_ACTIVITY, r);我们来找找这个接收消息的地方;

 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
               //最终调用
                    handleLaunchActivity(r, null);
           
}省略..............
}

我们接着具体看看 handleLaunchActivity(r, null);这个方法到底做了什么东西;

  private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略.......
Activity a = performLaunchActivity(r, customIntent); .
//返回Activity对象;我们经常用到Context的时候就传入this,我猜想Activity的创建和Context应该是少不了的关联,没办法只能接着找;
}
省略........ 

performLaunchActivity 代码太多我本来想只是截取一部分,可是看了好久感觉还是贴出来一大部分吧,毕竟都是比较重要的,

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    /省略......
      //终于找到了activity的创建了;你用类加载器采用反射机制;
        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);
            }
        } 
...........
//初始化Application 
        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
//获取当前activity的Context 终于还是给我找到了...
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
           .........
                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);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
                r.stopped = true;
                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                if (!r.activity.mFinished) {
                    if (r.isPersistable()) {
                        if (r.state != null || r.persistentState != null) {
                            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                        }
                    } else if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
...........
       

撑住 撑住 就到了....

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
       //省略........
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;
//省略....还好代码不多;感谢老铁 这个不就是我要找到的他的实现类吗.....赶紧看看,对了之前的方法可别忘了,

ContextImpl类:

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

一步一步点下去

/**
     * Gets a system service from a given context. 注释写的多清楚,哈哈
     */
 public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
终于没了吧

看看真面目

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

原来就是HashMap存贮,也就是我上边写的最后一种单列方式容器存贮,其实写到这里还并没有写完了,既然我们是直接获取的也并没有自己进行注入,那么你想过没有那么到底系统是啥时候给我们注入的了,带这个问题,我们在翻翻源码,瞧瞧,别怕,有我在嘿嘿.....继续接着撸起;

SystemServiceRegistry类:

我们看看这个HashMap到底是啥时候注入的只关心这个map集合就好了,搜索一下;

/**
     * Statically registers a system service with the context.
     * This method must be called during static initialization only.
    注释还是注释写的真的太清楚了,虽然我英语没过好多级,这些还是一看就明白的
     */
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
   .........
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

原来在这地方调用,那到底是啥时候调用的这个registerService还是需要搜索一下,这就比之前简单多了,

static {
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});

        registerService(Context.CAPTIONING_SERVICE, CaptioningManager.class,
                new CachedServiceFetcher<CaptioningManager>() {
            @Override
            public CaptioningManager createService(ContextImpl ctx) {
                return new CaptioningManager(ctx);
            }});

   ......省略
      重点在这,这不是就是我们获取的东西吗;
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
..省略..........

总结:

原来我们app已启动的时候下边已经多了大量的工作,在第一次加载类的时候就会注册各种ServiceFetcher,将这些以键值对的形式存进去,需要用到的时候在通过key去取值,到此现在这个流程基本上明白了,那我就用一个流程图来再一次的回头整理下图,只是贴出了一些重要的方法以便回顾之前看的;


启动流程图.png

其实分析了这么多的源码,说到底就是一个核心原理就是构造私有,并且通过静态方法获取一个实例,在这个过程中必须保证线程的安全性;如果觉得写的不错的给个赞哦,写的有问题的地方希望大家能给你纠正,谢谢!

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

推荐阅读更多精彩内容

  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,207评论 4 34
  • 1 场景问题# 1.1 读取配置文件的内容## 考虑这样一个应用,读取配置文件的内容。 很多应用项目,都有与应用相...
    七寸知架构阅读 6,570评论 12 67
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,523评论 18 399
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,199评论 25 707
  • 名叫汤的贤君和名叫棘的贤人也有过类似的问答:上下四方有极限吗?棘答道:无极之外,又是无极。在不毛之地的北方有一大池...
    菁苹之末阅读 728评论 0 0