View的繪制(4)-換膚框架(一)之Support v7庫(kù)解析

主目錄見(jiàn):Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)

一.目標(biāo)

秉承一貫的原則,不以目的為出發(fā)點(diǎn)的源碼解析都是耍流氓。所以我們來(lái)說(shuō)明下今天的目的:
1)復(fù)習(xí)《setContentView源碼分析》的知識(shí)點(diǎn)。
2)為下一篇的換膚框架打一個(gè)基礎(chǔ).
3)當(dāng)然也是為《小紅書的效果》做一個(gè)鋪墊.

二.support v7庫(kù)解析

1.回顧setContentView的一個(gè)知識(shí)點(diǎn)

為了省去大家去查找前面文章的麻煩(當(dāng)然我建議有時(shí)間還是可以去看下《setContentView源碼分析》這一定不會(huì)浪費(fèi)你時(shí)間),我這個(gè)地方重新貼一遍這一段關(guān)鍵代碼,這也是原則鲸伴,重要的知識(shí)點(diǎn)提醒冗疮!提醒矾兜!提醒托嚣!

  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
//此處省略一些代碼巩检,減少眼疲勞
............
            try {
            View view;
//前方高能!高能示启!高能兢哭!
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
..........
        }
    }

這個(gè)地方我們看到下面這句代碼 :

 if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

這句代碼非常關(guān)鍵,也是我們接下來(lái)?yè)Q膚框架要用到的知識(shí)點(diǎn)夫嗓,我們看到這個(gè)地方創(chuàng)建view的時(shí)候會(huì)先用factory來(lái)創(chuàng)建即這邊的mFactory2迟螺,mFactory,這兩個(gè)東西是什么呢舍咖?這兩個(gè)其實(shí)是Factory的子類矩父,LayoutInflater其中有個(gè)方法:

public void setFactory(Factory factory) {
}

這個(gè)方法可以設(shè)置factory,也就是說(shuō)只要我們?cè)O(shè)置進(jìn)來(lái)我們自己的factory排霉,那么系統(tǒng)就會(huì)用我們的factory來(lái)創(chuàng)建view窍株,這也就達(dá)到攔截view創(chuàng)建過(guò)程的作用。Fantastic Resource Code9ツ<欣选!

fatastic

2.AppCompatActivity onCreate

我們知道我們現(xiàn)在創(chuàng)建Activity的時(shí)候會(huì)繼承兼容包里面的AppCompatActivity來(lái)達(dá)到使用高版本控件的能力辙诞。那我們的源碼之旅就從onCreate開(kāi)始辙售。

 protected void onCreate(@Nullable Bundle savedInstanceState) {
 //我們看到接下來(lái)這三句,主要就是得到delegate飞涂,然后執(zhí)行delegate里面的onCreate方法
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        if (delegate.applyDayNight() && mThemeId != 0) {
            // If DayNight has been applied, we need to re-apply the theme for
            // the changes to take effect. On API 23+, we should bypass
            // setTheme(), which will no-op if the theme ID is identical to the
            // current theme ID.
            if (Build.VERSION.SDK_INT >= 23) {
                onApplyThemeResource(getTheme(), mThemeId, false);
            } else {
                setTheme(mThemeId);
            }
        }
        super.onCreate(savedInstanceState);
    }

看到這個(gè)方法旦部,我們看到第一句getDelegate()方法,后面的操作都是在這個(gè)delegate對(duì)象里面较店,那么我們看看這個(gè)delegate到底是哪里的孫猴子=》列車直通花果山:

    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

so easy士八!有沒(méi)有,就是調(diào)用的AppCompatDelegate的create方法梁呈,順其代碼直接進(jìn)入AppCompatDelegate的這個(gè)方法:

   private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

我去婚度。。蝗茁。。童話里都是騙人的寻咒,剛說(shuō)的so easy呢哮翘?這里竟然不同版本返回的還是不一樣,但是這有個(gè)有趣的地方毛秘,待我給大家揭露:

class AppCompatDelegateImplN extends AppCompatDelegateImplV23 {
}
class AppCompatDelegateImplV23 extends AppCompatDelegateImplV14 {
}
class AppCompatDelegateImplV14 extends AppCompatDelegateImplV11 {
}
class AppCompatDelegateImplV11 extends AppCompatDelegateImplV9 {
}
class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
        implements MenuBuilder.Callback, LayoutInflaterFactory {
}

這里看到了沒(méi)有饭寺,各個(gè)的類其實(shí)最終都是從AppCompatDelegateImplV9 出來(lái)的阻课,那為什么要弄出幾個(gè)呢?其實(shí)只是為了兼容后面版本一些夜間模式功能而已艰匙,所以這里返回的對(duì)象我們就當(dāng)做AppCompatDelegateImplV9 限煞。

3.delegate installViewFactory

我們到這里已經(jīng)找到我們delegate(AppCompatDelegateImplV9 )了,所以我們直接進(jìn)入AppCompatDelegateImplV9 的installViewFactory()方法:

   @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
            if (!(LayoutInflaterCompat.getFactory(layoutInflater)
                    instanceof AppCompatDelegateImplV9)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

其實(shí)這句代碼也很簡(jiǎn)單有沒(méi)有员凝,就是得到layoutInflater對(duì)象晰骑,然后判斷factory存不存在,不存在則將factory設(shè)置成AppCompatDelegateImplV9绊序。這個(gè)地方是不是和開(kāi)頭我們說(shuō)的想吻合了硕舆。我們通過(guò)設(shè)置factory來(lái)攔截創(chuàng)建view的過(guò)程。

4.delegate onCreateView

既然已經(jīng)將我們的factory設(shè)置進(jìn)去了骤公,那我們知道創(chuàng)建view的時(shí)候會(huì)調(diào)用onCreateView抚官,這也是我們開(kāi)篇說(shuō)過(guò)的,所以我們這里進(jìn)入AppCompatDelegateImplV9這個(gè)方法看有什么不同:

   @Override
    public final View onCreateView(View parent, String name,
            Context context, AttributeSet attrs) {
        // First let the Activity's Factory try and inflate the view
        final View view = callActivityOnCreateView(parent, name, context, attrs);
        if (view != null) {
            return view;
        }

        // If the Factory didn't handle it, let our createView() method try
        return createView(parent, name, context, attrs);
    }

其實(shí)從英文注釋也可以看出阶捆,首先會(huì)調(diào)用Activity的onCreateView進(jìn)行嘗試創(chuàng)建View凌节,如果沒(méi)有創(chuàng)建成功則調(diào)用我們的createView(),我們知道,如果用到高級(jí)控件的話洒试,有些屬性是低版本沒(méi)有的所以如果用到低版本沒(méi)有的屬性的話那么肯定會(huì)創(chuàng)建失敗即這里會(huì)調(diào)用到我們的createView()方法倍奢。

    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        final boolean isPre21 = Build.VERSION.SDK_INT < 21;

        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }

        // We only want the View to inherit its context if we're running pre-v21
        final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

這個(gè)方法其實(shí)沒(méi)有什么內(nèi)容就是創(chuàng)建一個(gè)mAppCompatViewInflater 對(duì)象,然后調(diào)用他的createView()方法垒棋,所以我們直接跳到這個(gè)方法:

 public final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;
//關(guān)鍵代碼看這里卒煞,這下面的控件就是我們要兼容的控件,也是support V7要攔截的view
        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            case "CheckBox":
                view = new AppCompatCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = new AppCompatRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = new AppCompatRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = new AppCompatSeekBar(context, attrs);
                break;
        }

//當(dāng)然如果不是上面的控件則會(huì)嘗試用其他方法創(chuàng)建view
        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check it's android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

這個(gè)方法很長(zhǎng)叼架,但是其實(shí)很簡(jiǎn)單畔裕,就是根據(jù)name(這個(gè)name就是我們控件的名字),如果發(fā)現(xiàn)name對(duì)應(yīng)的控件則new出對(duì)應(yīng)的控件乖订。我們這個(gè)地方挑一個(gè)控件來(lái)看扮饶,那就挑AppCompatTextView吧。

5.AppCompatTextView

到這里我們的講解就快完成了哈乍构,大家堅(jiān)持一下甜无,馬上就見(jiàn)到曙光了。

堅(jiān)持.png

我們直接打開(kāi)AppCompatTextView類哥遮,然后看到構(gòu)造方法岂丘,我們都知道,自定義有好幾個(gè)構(gòu)造函數(shù)昔善,但是一個(gè)參數(shù)的構(gòu)造函數(shù)會(huì)調(diào)用兩個(gè)參數(shù)的構(gòu)造函數(shù)元潘,兩個(gè)參數(shù)的構(gòu)造函數(shù)會(huì)調(diào)用三個(gè)參數(shù)的構(gòu)造函數(shù)畔乙,所以我們看三個(gè)參數(shù)的構(gòu)造函數(shù)君仆。

    public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(TintContextWrapper.wrap(context), attrs, defStyleAttr);

        mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
        mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);

        mTextHelper = AppCompatTextHelper.create(this);
        mTextHelper.loadFromAttributes(attrs, defStyleAttr);
        mTextHelper.applyCompoundDrawablesTints();
    }

這個(gè)方法其實(shí)也很簡(jiǎn)單,我們可以看到主要有AppCompatBackgroundHelper和AppCompatTextHelper,那這兩個(gè)是干什么的呢返咱?我們先來(lái)看AppCompatBackgroundHelper:

5.1.AppCompatBackgroundHelper loadFromAttributes

我們看到這個(gè)方法里面做了啥:

    void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
        TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
                R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
        try {
            if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
                mBackgroundResId = a.getResourceId(
                        R.styleable.ViewBackgroundHelper_android_background, -1);
                ColorStateList tint = mDrawableManager
                        .getTintList(mView.getContext(), mBackgroundResId);
                if (tint != null) {
                    setInternalBackgroundTint(tint);
                }
            }
            if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTint)) {
                ViewCompat.setBackgroundTintList(mView,
                        a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint));
            }
            if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) {
                ViewCompat.setBackgroundTintMode(mView,
                        DrawableUtils.parseTintMode(
                                a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1),
                                null));
            }
        } finally {
            a.recycle();
        }
    }

看到這個(gè)是不是很熟悉钥庇,其實(shí)就是得到背景,背景著色器等等屬性咖摹,然后進(jìn)行設(shè)置進(jìn)TextView评姨。

5.1.AppCompatTextHelper loadFromAttributes

這是方法其實(shí)也是獲取對(duì)應(yīng)屬性的值,然后分別進(jìn)行設(shè)置萤晴,由于這個(gè)地方屬性太多了吐句,代碼也會(huì)比較多所以我就貼出部分代碼,因?yàn)檫@不是重點(diǎn)店读,主要給大家一個(gè)思路嗦枢,可以通過(guò)這種方式來(lái)自定義屬性:

  void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
        final Context context = mView.getContext();
        final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();

        // First read the TextAppearance style id
        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
                R.styleable.AppCompatTextHelper, defStyleAttr, 0);
        final int ap = a.getResourceId(R.styleable.AppCompatTextHelper_android_textAppearance, -1);
        // Now read the compound drawable and grab any tints
        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) {
            mDrawableLeftTint = createTintInfo(context, drawableManager,
                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableLeft, 0));
        }
        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableTop)) {
            mDrawableTopTint = createTintInfo(context, drawableManager,
                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableTop, 0));
        }
        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableRight)) {
            mDrawableRightTint = createTintInfo(context, drawableManager,
                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableRight, 0));
        }
        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableBottom)) {
            mDrawableBottomTint = createTintInfo(context, drawableManager,
                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableBottom, 0));
        }
        a.recycle();
//底下省略部分代碼,跟上面這段類似
...........
    }

所以我們知道其實(shí)AppCompatTextView這個(gè)控件也好屯断,還是其他控件也好文虏,這個(gè)地方就是獲取我們自定義的屬性然后進(jìn)行設(shè)置。這樣我們的TextView就有了一些高級(jí)屬性殖演,達(dá)到了自定義的作用.到這里我們的源碼分析就已經(jīng)完成了氧秘,希望大家有enjoy這段旅程。

enjoy-more

總結(jié):我們的support v7庫(kù)就是設(shè)置LayoutInflater的Factory然后攔截onCreateView方法來(lái)創(chuàng)建View趴久,原理特別簡(jiǎn)單丸相,但是這是一個(gè)很有用的技能。彼棍。已添。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市滥酥,隨后出現(xiàn)的幾起案子更舞,更是在濱河造成了極大的恐慌,老刑警劉巖坎吻,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缆蝉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瘦真,警方通過(guò)查閱死者的電腦和手機(jī)刊头,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诸尽,“玉大人原杂,你說(shuō)我怎么就攤上這事∧” “怎么了穿肄?”我有些...
    開(kāi)封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵年局,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我咸产,道長(zhǎng)矢否,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任脑溢,我火速辦了婚禮僵朗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屑彻。我一直安慰自己验庙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布社牲。 她就那樣靜靜地躺著壶谒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪膳沽。 梳的紋絲不亂的頭發(fā)上汗菜,一...
    開(kāi)封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音挑社,去河邊找鬼陨界。 笑死,一個(gè)胖子當(dāng)著我的面吹牛痛阻,可吹牛的內(nèi)容都是我干的菌瘪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼阱当,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼俏扩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起弊添,我...
    開(kāi)封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤录淡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后油坝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體嫉戚,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年澈圈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彬檀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞬女,死狀恐怖窍帝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诽偷,我是刑警寧澤坤学,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布疯坤,位于F島的核電站,受9級(jí)特大地震影響拥峦,放射性物質(zhì)發(fā)生泄漏贴膘。R本人自食惡果不足惜卖子,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一略号、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洋闽,春花似錦玄柠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至刊懈,卻和暖如春这弧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虚汛。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工匾浪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卷哩。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓蛋辈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親将谊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冷溶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)尊浓,斷路器逞频,智...
    卡卡羅2017閱讀 134,702評(píng)論 18 139
  • 主目錄見(jiàn):Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)?終于迎來(lái)我們的換膚框架最終章了,前面我們也學(xué)了suppor...
    ZJ_Rocky閱讀 1,929評(píng)論 1 8
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,163評(píng)論 30 470
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,298評(píng)論 25 707
  • 作為一枚愛(ài)好唱k的麥霸栋齿,體驗(yàn)一下聲樂(lè)課給自己提升一下成了我有閑卻沒(méi)什么錢階段的一個(gè)愿望虏劲。于是比較了價(jià)格和地點(diǎn)后,我...
    聶筱筱倩閱讀 963評(píng)論 0 0