Android中手?jǐn)]IOC框架

在剛接觸安卓的第二天 , 自己最熟悉的代碼 , 就是那句findViewById. 記得當(dāng)時(shí)特別舒服的啪啪啪敲完一行有用的代碼 , 心里美滋兒啊 , 心里想著 , 啥時(shí)候?qū)懫渌倪壿嬀拖襁@段啪啪啪就能完成的代碼一樣 , 那多舒服 孝宗。

但是吧 , 人都是有惰性的 , 我說(shuō)的是偷懶的那種惰性:
在面對(duì)一個(gè)比較復(fù)雜的界面的時(shí)候 , 你需要機(jī)械化的吧所有的組件統(tǒng)統(tǒng)findViewById找出來(lái) , 然后再去做相關(guān)操作 ; 有時(shí)候僅僅是為了設(shè)置一個(gè)點(diǎn)擊事件 , 但卻必須要先聲明 , 查找 , 才能繼續(xù)完成接下來(lái)的工作 .于是 , 我想偷個(gè)懶了.....

抽取方法

當(dāng)然了,最先想到的蒿涎,當(dāng)然是想吧這些麻煩的重復(fù)性操作抽出來(lái)舷礼,其實(shí)也就是少寫(xiě)了findViewById的這么幾個(gè)字候齿,本質(zhì)上還是和以前的邏輯一樣:

protected final <T extends View> T $(@IdRes int id) {
        return (T) view.findViewById(id);
}

這樣迄靠,就可以吧代碼簡(jiǎn)化為iv_head = $(R.id.iv_head);
emmm,好像意義不是很大卓缰,仍然是重復(fù)的工作叙赚。

IOC的出現(xiàn)

偶然的機(jī)會(huì),接觸到了ButterKnife的框架僚饭,這個(gè)框架極大地簡(jiǎn)化了組件查找,事件等一系列的操作胧砰,只需要一個(gè)注解就可以輕松搞定那些繁雜的工作鳍鸵。

@Bind(R.id.toolbar)
protected Toolbar toolbar;

這樣,通過(guò)注解就可以完成定義到查找的兩個(gè)工作尉间,恩~舒服偿乖,但是這個(gè)注解的背后又存在著怎么樣的實(shí)現(xiàn)呢?

略微看一下BK的源碼吧

  1. 首先哲嘲,我們點(diǎn)進(jìn)去@Bind這個(gè)注解贪薪,來(lái)到了響應(yīng)的注解接口
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
  /** View ID to which the field will be bound. */
  int[] value();
}

哇什么鬼,還有@Interface,是不是沒(méi)見(jiàn)過(guò)的科技? 不要急 , 慢慢來(lái)看
這里一共有三個(gè)注解

  • Rentation: Reteniton的作用是定義被它所注解的注解保留多久,它的取值是一個(gè)枚舉類(lèi)型眠副,有三種:
    SOURCE 被編譯器忽略
    CLASS 注解將會(huì)被保留在Class文件中画切,但在運(yùn)行時(shí)并不會(huì)被VM保留。這是默認(rèn)行為
    RUNTIME 保留至運(yùn)行時(shí)囱怕。所以我們可以通過(guò)反射去獲取注解信息霍弹。

  • Target 用于設(shè)定注解使用范圍,接收參數(shù)為ElementType的枚舉
    METHOD 可用于方法上
    TYPE 可用于類(lèi)或者接口上
    ANNOTATION_TYPE 可用于注解類(lèi)型上(被@interface修飾的類(lèi)型)
    CONSTRUCTOR 可用于構(gòu)造方法上
    FIELD 可用于域上
    LOCAL_VARIABLE 可用于局部變量上
    PACKAGE 用于記錄java文件的package信息
    PARAMETER 可用于參數(shù)上

  • interface: 用于自定義注解
    自定義注解也就是可以自己寫(xiě)需要的注解


    image.png
  1. 使用@interface關(guān)鍵字定義注解娃弓,注意關(guān)鍵字的位置
  2. 成員以無(wú)參數(shù)無(wú)異常的方式聲明典格,注意區(qū)別一般類(lèi)成員變量的聲明
  3. 可以使用default為成員指定一個(gè)默認(rèn)值,如上所示
  4. 成員類(lèi)型是受限的台丛,合法的類(lèi)型包括原始類(lèi)型以及String耍缴、Class、Annotation、Enumeration (JAVA的基本數(shù)據(jù)類(lèi)型有8種:byte(字節(jié))防嗡、short(短整型)变汪、int(整數(shù)型)、long(長(zhǎng)整型)本鸣、float(單精度浮點(diǎn)數(shù)類(lèi)型)疫衩、double(雙精度浮點(diǎn)數(shù)類(lèi)型)、char(字符類(lèi)型)荣德、boolean(布爾類(lèi)型)
  5. 注解類(lèi)可以沒(méi)有成員闷煤,沒(méi)有成員的注解稱(chēng)為標(biāo)識(shí)注解
  6. 如果注解只有一個(gè)成員,并且把成員取名為value()涮瞻,則在使用時(shí)可以忽略成員名和賦值號(hào)“=” ,例如JDK注解的@SuppviseWarnings 鲤拿;如果成員名不為value,則使用時(shí)需指明成員名和賦值號(hào)"="

打造手?jǐn)]的IOC框架

什么署咽,才剛剛不到5分鐘就可以開(kāi)始手?jǐn)]了么近顷,別慌, 剛宁否!
首先 窒升, 我們模擬三種需求:

  1. 使用注解設(shè)置布局,代替setContentView
  2. 使用ViewInject代替findViewById
  3. 使用@OnClick和@OnLongClick代替點(diǎn)擊與長(zhǎng)按

IOC的本質(zhì)就是控制反轉(zhuǎn) , 也就是將A要做的事情, 委托給B來(lái)做 , 所以我們需要一個(gè)第三方的容器類(lèi)來(lái)完成這些工作
首先做好準(zhǔn)備工作, 將BaseActivity搭建好 , 在里面調(diào)用容器的初始化 , 這樣讓每一個(gè)Activity去繼承改基類(lèi)就可以完成初始化的操作

public abstract class BaseActivity extends AppCompatActivity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //將當(dāng)前對(duì)象注入到第三方的容器
        InjectUtils.inject(this);
        increate(savedInstanceState);
    }

    public abstract void increate(Bundle savedInstanceState);
}

然后新建InjectUtils類(lèi) , 編寫(xiě)靜態(tài)的Inject方法 , 這里傳入上下文對(duì)象.

public static void inject(Context context) {
        //先注入視圖,再注入控件
        injectLayout(context);
        injectView(context);
        injectEvents(context);
    }

定義好三個(gè)方法之后 , 我們的基礎(chǔ)框架就基本完成了 , 接下來(lái)實(shí)現(xiàn)一下具體的注解實(shí)現(xiàn)細(xì)節(jié)

任務(wù)1. 使用注解設(shè)置布局

@ConvertView(R.layout.activity_second)

首先 , 既然是自定義的注解 , 當(dāng)然要先有一個(gè)類(lèi)似于剛剛上面的自定義的@inteface , 然后默認(rèn)的方法參數(shù)為int類(lèi)型 , 代碼如下:

//運(yùn)行是也存在,用于注解
@Retention(RetentionPolicy.RUNTIME)
//用在類(lèi)上的注解, 寫(xiě)在類(lèi)的上方
@Target(ElementType.TYPE)
public @interface ConvertView {
    int value();
}

定義好了注解之后 , 就要完成第三方容器中的方法了 , 這里我們使用反射的方法去找到注解, 然后反射到響應(yīng)的方法 , 并調(diào)用方法本身 , 就完成了容器的作用 , 也就是上面準(zhǔn)備的injectLayout方法

public static void injectLayout(Context context) {

        int layoutId = 0;
        Class clazz = context.getClass();

        //從注解出拿到注解中的值
        ConvertView view = (ConvertView) clazz.getAnnotation(ConvertView.class);

        if (null != view) {
            //從接口的value函數(shù)中獲取id值
            try {
                layoutId = view.value();
                //利用反射獲取需要的方法
                Method method = clazz.getMethod("setContentView", int.class);

                //拿到setContentView后調(diào)用函數(shù)
                method.invoke(context, layoutId);

            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

至此 , 我們完成了第一個(gè)需求 , 以后不再有setContentView, 只需要注解就可以了:

@ConvertView(R.layout.activity_second)
public class SecondActivity extends BaseActivity {
    @Override
    public void increate(Bundle savedInstanceState) {
       
    }
}

任務(wù)2. 使用ViewInject代替findViewById

有了布局的經(jīng)驗(yàn)之后 , 我們可以輕車(chē)熟路的按照流程來(lái)實(shí)現(xiàn)這個(gè)方法
首先是自定義的注解

//運(yùn)行時(shí)也存在
@Retention(RetentionPolicy.RUNTIME)
//用在域上的注解
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

接下來(lái)在容器中實(shí)現(xiàn)委托的操作

public static void injectView(Context context) {

        Class<? extends Context> aClass = context.getClass();
        //獲取到上下文中所有成員變量
        Field[] declaredFields = aClass.getDeclaredFields();

        for (Field f : declaredFields) {
            //獲取有注解的控件,注意注解之后沒(méi)有加分號(hào),意味著注解和類(lèi)型聲明是同一行語(yǔ)句 , 這里利用注解獲取控件的本質(zhì)是通過(guò)反射到成員變量
            ViewInject annotation = f.getAnnotation(ViewInject.class);
            //如果有注解 , 則獲取注解的值
            if (null != annotation) {
                int value = annotation.value();

                try {
                    //調(diào)用了Activity的findViewById方法,context中沒(méi)有該方法,需要反射獲取
                    Method findViewById = aClass.getMethod("findViewById", int.class);

                    View view = (View) findViewById.invoke(context, value);
                    //允許反射私有變量
                    f.setAccessible(true);
                    //為反射到的變量賦值
                    f.set(context, view);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }

好像比設(shè)置布局要復(fù)雜一點(diǎn)?其實(shí)是差不多的 , 只不過(guò)這里在拿到方法之后, 有設(shè)置了返回值并實(shí)例化(改變字段) , 懂了第一個(gè)之后 , 也不是很難理解吧 .

任務(wù)3. 使用@OnClick和@OnLongClick代替點(diǎn)擊與長(zhǎng)按

這個(gè)相比于前兩個(gè)就要復(fù)雜一些了 ,

  • setContentView()只需要調(diào)用方法傳入?yún)?shù)即可
  • findViewById(), 需要傳入?yún)?shù)并拿到返回值即可
  • setOnclickListener()需要傳入一個(gè)接口并實(shí)現(xiàn)其方法 .
    納尼?要傳入一個(gè)方法慕匠,也就是View.OnclickListener的實(shí)現(xiàn)方法饱须。我們知道這個(gè)方法是在View中的,這里難道還要傳入一個(gè)View的參數(shù)么台谊?沒(méi)必要這么麻煩的蓉媳。當(dāng)我們需要訪問(wèn)某個(gè)對(duì)象但存在困難時(shí),可以通過(guò)一個(gè)代理對(duì)象去間接的訪問(wèn)锅铅,所以就需要繞個(gè)小彎子: 使用代理模式酪呻。

在定義注解之前我們先想一下 , 如果要用注解編寫(xiě)點(diǎn)擊事件的話(huà) , 我們會(huì)省略掉一下幾個(gè)步驟:

  1. 點(diǎn)擊事件的方法 被省略
  2. 點(diǎn)擊事件的參數(shù): 被省略
  3. 匿名內(nèi)部類(lèi)的回調(diào)方法的方法回調(diào) 被省略
    所以我們?cè)谧⒔庵行枰齻€(gè)參數(shù) 。而且后期我們會(huì)添加各種各樣的監(jiān)聽(tīng)事件盐须,所以在點(diǎn)擊事件上做一個(gè)封裝玩荠,用來(lái)管理所有的事件
@Retention(RetentionPolicy.RUNTIME)
//用于注解上的注解
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {

    //設(shè)置監(jiān)聽(tīng)方法
    String listenerSetter();

    //事件類(lèi)型
    Class listenerType();

    //事件回調(diào)
    String callBackMethod();
}

可以理解為注解的基類(lèi)。我們的點(diǎn)擊事件也好 , 長(zhǎng)按事件也好 , 都基于該基類(lèi)擴(kuò)展 , 所以該基類(lèi)會(huì)作為一個(gè)參數(shù)出現(xiàn)在另一個(gè)注解中 , 所以我們的@Target必須為ANNOTATION_TYPE , 也就是注解中的注解. 并且贼邓,將參數(shù)設(shè)置為String而不是Method姨蟋,也是為了反射與擴(kuò)展的方便。

首先我們編寫(xiě)OnClick注解立帖,使用基類(lèi)來(lái)傳遞參數(shù):

@Retention(RetentionPolicy.RUNTIME)
//用于方法上的注解
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",
      listenerType = View.OnClickListener.class,
      callBackMethod = "onClick")
public @interface OnClick {

    int[] value() default -1;
}

現(xiàn)在明白基類(lèi)的作用了吧眼溶,其實(shí)就是限制了參數(shù)的傳遞規(guī)范(這個(gè)規(guī)范使用枚舉會(huì)更有可讀性,這里就不浪了晓勇,大家自己來(lái)吧)堂飞。


敲重點(diǎn)灌旧,在委托容器中完成注入操作之前 , 我們先要編寫(xiě)代理 , 在代理中調(diào)用方法:

public class ListenerInvactionHandler implements InvocationHandler {
    //被代理的真實(shí)對(duì)象的應(yīng)用
    private Context context;
    //保存方法名與方法體, 用來(lái)判斷是否需要被代理
    private Map<String, Method> methodMap;

    public ListenerInvactionHandler(Context context, Map<String, Method> methodMap) {
        this.context = context;
        this.methodMap = methodMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //首先獲取到方法名
        String name = method.getName();
        //根據(jù)方法名找方法,看是否需要代理
        Method metf = methodMap.get(name);

        if (metf != null) {
            //需要代理,使用代理調(diào)用
            return metf.invoke(context, args);
        } else {
            return method.invoke(proxy, args);
        }
    }
}

還算好理解吧 , 上面這個(gè)方法中, 集合時(shí)用來(lái)保存類(lèi)中所有的方法, 然后根據(jù)方法名來(lái)查看該方法是否需要被代理 , 如果需要的話(huà)使用代理來(lái)調(diào)用 , 否則使用本身來(lái)調(diào)用.
接下來(lái)是容器中的操作先貼上代碼,在代碼中吧重點(diǎn)都注釋了.

@SuppressLint("NewApi")
    private static void injectEvents(Context context) {
        Class<? extends Context> aClass = context.getClass();
        //拿到類(lèi)中所有的方法
        Method[] declaredMethods = aClass.getDeclaredMethods();

        //遍歷所有的方法并查找?guī)ё⒔獾姆椒?        for (Method m : declaredMethods) {

            Annotation[] annotations = m.getAnnotations();
            for (Annotation a : annotations) {
                //獲取注解  annoType:OnClick
                Class<? extends Annotation> annoType = a.annotationType();
                //獲取注解的值,onClick注解上面的EventBase
                EventBase base = annoType.getAnnotation(EventBase.class);
                if (null == base) {
                    continue; //跳出本輪循環(huán)
                }

                /**
                 *拿到帶注解的方法, 開(kāi)始獲取事件三要素 , 通過(guò)反射注入進(jìn)去拿到真正的方法
                 */
                //1. 返回setOnclickListener字符串
                String listenerSetter = base.listenerSetter();
                //2. 返回View.OnClickListener字節(jié)碼
                Class<?> listenerType = base.listenerType();
                //3. 返回onClick字符串
                String callMethod = base.callBackMethod();

                //保存方法名與方法的應(yīng)映射, 在接下來(lái)的操作中方便使用
                Map<String, Method> methodMap = new HashMap<>();
                methodMap.put(callMethod, m);

                try {
                    //拿到注解中的value方法
                    Method value = annoType.getDeclaredMethod("value");
                    //對(duì)應(yīng)value方法的返回值,這里通過(guò)反射是為了通用性,如果指定具體的類(lèi)可以直接獲取,但是擴(kuò)展性很低
                    int[] ids = (int[]) value.invoke(a);

                    //注入事件
                    for (int viewId : ids) {
                        Method findv = aClass.getMethod("findViewById", int.class);
                        //通過(guò)反射拿到View
                        View view = (View) findv.invoke(context, viewId);
                        if (null == view) continue;

                        /**
                         * @Param listenerSetter: setOnClickListener的字符串,可以反射出方法
                         * @Param listenerType: 參數(shù)類(lèi)型,為View.OnClickListener.class
                         */
                        Method setOnclick = view.getClass().getMethod(listenerSetter, listenerType);

                        ListenerInvactionHandler handler = new ListenerInvactionHandler(context, methodMap);
                        //設(shè)置返回對(duì)象的類(lèi)型: proxyy是實(shí)現(xiàn)了OnclickListener接口,也就是listenerType接口的代理對(duì)象
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
                                new Class[]{listenerType},
                                handler);

                        //利用代理設(shè)置監(jiān)聽(tīng)
                        setOnclick.invoke(view,proxy);
                    }

                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }

    }

邏輯是復(fù)雜了一點(diǎn) , 這里只是有一個(gè)三重的for循環(huán) , 慢慢拆解下來(lái) , 其實(shí)也不算很難 , 重要的注釋都寫(xiě)在方法中了 , 去嘗試一下吧 .


但是 , 懂的老鐵們會(huì)說(shuō) , 你這個(gè)是運(yùn)行時(shí)注解绰筛,在使用的時(shí)候枢泰,大量的反射會(huì)影響性能,是的铝噩,這個(gè)確實(shí)是一個(gè)很大的缺點(diǎn)衡蚂,所以我們可以參考ButterKnife的做法,打造自己的編譯時(shí)注解框架骏庸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毛甲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子具被,更是在濱河造成了極大的恐慌玻募,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件一姿,死亡現(xiàn)場(chǎng)離奇詭異七咧,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)叮叹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)艾栋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蛉顽,你說(shuō)我怎么就攤上這事蝗砾。” “怎么了蜂林?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拇泣。 經(jīng)常有香客問(wèn)我噪叙,道長(zhǎng),這世上最難降的妖魔是什么霉翔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任睁蕾,我火速辦了婚禮,結(jié)果婚禮上债朵,老公的妹妹穿的比我還像新娘子眶。我一直安慰自己,他們只是感情好序芦,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布臭杰。 她就那樣靜靜地躺著,像睡著了一般谚中。 火紅的嫁衣襯著肌膚如雪渴杆。 梳的紋絲不亂的頭發(fā)上寥枝,一...
    開(kāi)封第一講書(shū)人閱讀 52,713評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音磁奖,去河邊找鬼囊拜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛比搭,可吹牛的內(nèi)容都是我干的冠跷。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼身诺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜜托!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起戚长,我...
    開(kāi)封第一講書(shū)人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盗冷,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后同廉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體仪糖,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年迫肖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锅劝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蟆湖,死狀恐怖故爵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隅津,我是刑警寧澤诬垂,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站伦仍,受9級(jí)特大地震影響结窘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜充蓝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一隧枫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谓苟,春花似錦官脓、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仑撞,卻和暖如春湾趾,著一層夾襖步出監(jiān)牢的瞬間芭商,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工搀缠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铛楣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓艺普,卻偏偏與公主長(zhǎng)得像簸州,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歧譬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,328評(píng)論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,867評(píng)論 6 342
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理岸浑,服務(wù)發(fā)現(xiàn),斷路器瑰步,智...
    卡卡羅2017閱讀 134,716評(píng)論 18 139
  • 轉(zhuǎn)眼間矢洲,做大時(shí)代已經(jīng)7個(gè)月了,從VIP起步做到現(xiàn)在的總代級(jí)別缩焦,一路順暢读虏,沒(méi)有阻礙!很慶幸加入了大時(shí)代袁滥,因一...
    林夕冉閱讀 297評(píng)論 0 0
  • 《一首詩(shī)的斟酌》 坐在桌前 遲遲寫(xiě)不出一個(gè)字 疲勞的心盖桥,身 磨滅了我的靈感 也許是樹(shù)不夠高 永遠(yuǎn)觸碰不到天 詩(shī)不夠...
    金書(shū)js閱讀 210評(píng)論 0 0