應用最廣的模式-單例模式(結合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閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • 單例模式(SingletonPattern)一般被認為是最簡單、最易理解的設計模式,也因為它的簡潔易懂,是項目中最...
    成熱了閱讀 4,279評論 4 34
  • 1 場景問題# 1.1 讀取配置文件的內容## 考慮這樣一個應用,讀取配置文件的內容。 很多應用項目,都有與應用相...
    七寸知架構閱讀 6,848評論 12 68
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,710評論 18 399
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,615評論 25 708
  • 名叫湯的賢君和名叫棘的賢人也有過類似的問答:上下四方有極限嗎?棘答道:無極之外,又是無極。在不毛之地的北方有一大池...
    菁蘋之末閱讀 763評論 0 0