EventBus3.0詳解

android EventBus


修改日志
2017-12-1 添加索引部分得細(xì)節(jié)态蒂,添加kotlin的支持方式

寫在前面

1.前言

曾經(jīng),一層又一層的業(yè)務(wù)邏輯讓我不知所措辽社,一個(gè)又一個(gè)的回調(diào)讓你頭暈眼花伟墙,一個(gè)又一個(gè)的參數(shù)讓你混亂不堪。EventBus滴铅,戳葵,一個(gè)耦合度低到令你害怕的框架。

2.什么是EventBus

EventBus是一個(gè)消息總線汉匙,以觀察者模式實(shí)現(xiàn)拱烁,用于簡(jiǎn)化程序的組件、線程通信噩翠,可以輕易切換線程戏自、開辟線程。EventBus3.0跟先前版本的區(qū)別在于加入了annotation @Subscribe伤锚,取代了以前約定命名的方式擅笔。

3.相似產(chǎn)品比較

產(chǎn)品名 開發(fā)者 備注
EventBus greenrobot 用戶最多,簡(jiǎn)潔见芹,方便剂娄,小巧玄呛,文檔簡(jiǎn)潔明了
Guava google 一個(gè)龐大的工具類庫,EventBus只是一個(gè)小功能
otto square fork guava 徘铝,用的人不少
AndroidEventBus 何紅輝 模仿EventBus開發(fā)的

使用EventBus3.0三部曲

1.定義事件

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

2.準(zhǔn)備訂閱者

    // This method will be called when a MessageEvent is posted
    @Subscribe
    public void onMessageEvent(MessageEvent event){
        Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
    }
    
    // This method will be called when a SomeOtherEvent is posted
    @Subscribe
    public void handleSomethingElse(SomeOtherEvent event){
        doSomethingWith(event);
    }
    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }
    
    @Override
    public void onStop() {
       EventBus.getDefault().unregister(this);
        super.onStop();
    }

3.發(fā)送事件

    EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

深入了解

1.ThreadMode線程通信

EventBus可以很簡(jiǎn)單的實(shí)現(xiàn)線程間的切換,包括后臺(tái)線程惕它、UI線程、異步線程

ThreadMode.POSTING

    //默認(rèn)調(diào)用方式郁惜,在調(diào)用post方法的線程執(zhí)行,避免了線程切換甲锡,性能開銷最少    
    // Called in the same thread (default)
    @Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional here
    public void onMessage(MessageEvent event) {
        log(event.message);
    }

ThreadMode.MAIN

    // Called in Android UI's main thread
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessage(MessageEvent event) {
        textField.setText(event.message);
    }

ThreadMode.BACKGROUND

    // 如果調(diào)用post方法的線程不是主線程羽戒,則直接在該線程執(zhí)行
    // 如果是主線程易稠,則切換到后臺(tái)單例線程驶社,多個(gè)方法公用同個(gè)后臺(tái)線程,按順序執(zhí)行硅瞧,避免耗時(shí)操作
    // Called in the background thread
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onMessage(MessageEvent event){
        saveToDisk(event.message);
    }

ThreadMode.ASYNC

    //開辟新獨(dú)立線程,用來執(zhí)行耗時(shí)操作,例如網(wǎng)絡(luò)訪問
    //EventBus內(nèi)部使用了線程池,但是要盡量避免大量長(zhǎng)時(shí)間運(yùn)行的異步線程月腋,限制并發(fā)線程數(shù)量
    //可以通過EventBusBuilder修改瓣赂,默認(rèn)使用Executors.newCachedThreadPool()
    // Called in a separate thread
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onMessage(MessageEvent event){
        backend.send(event.message);
    }

2.配置EventBusBuilder

EventBus提供了很多配置煌集,一般的情況下我們可以不用配置.但是,如果你有一些其他要求,比如控制日志在開發(fā)的時(shí)候輸出,發(fā)布的時(shí)候不輸出,在開發(fā)的時(shí)候錯(cuò)誤崩潰卷拘,而發(fā)布的時(shí)候不崩潰...等情況。
EventBus提供了一個(gè)默認(rèn)的實(shí)現(xiàn)乍赫,但不是單例揪惦。

    EventBus eventBus = new EventBus();
    //下面這一條的效果是完全一樣的
    EventBus eventBus = EventBus.builder().build();
    //修改默認(rèn)實(shí)現(xiàn)的配置器腋,記住纫塌,必須在第一次EventBus.getDefault()之前配置措左,且只能設(shè)置一次。建議在application.onCreate()調(diào)用
    EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();

3.StickyEvent

StickyEvent在內(nèi)存中保存最新的消息凉逛,取消原有消息,執(zhí)行最新消息诬辈,只有在注冊(cè)后才會(huì)執(zhí)行口渔,如果沒有注冊(cè),消息會(huì)一直保留來內(nèi)存中

    //在注冊(cè)之前發(fā)送消息
    EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
    //限制枪向,新界面啟動(dòng)了
   @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }
    //在onStart調(diào)用register后,執(zhí)行消息
    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent event) {
        // UI updates must run on MainThread
        textField.setText(event.message);
    }

    @Override
    public void onStop() {
        EventBus.getDefault().unregister(this);
        super.onStop();
    }

你也可以手動(dòng)管理StickyEvent

    MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
    // Better check that an event was actually posted before
    if(stickyEvent != null) {
        // "Consume" the sticky event
        EventBus.getDefault().removeStickyEvent(stickyEvent);
        //or
         EventBus.getDefault().removeAllStickyEvents();
        // Now do something with it
    }

在這里负蠕,或許你會(huì)有個(gè)疑問遮糖,
StickyEvent=true的訂閱者能否接收post的事件?
StickyEvent=false的訂閱者能否接收postSticky的事件?
查看源碼發(fā)現(xiàn)

   /**
     * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
     * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
     */
    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        ...省略部分代碼
        if (subscriberMethod.sticky) {
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }
        boolean checkAdd(Method method, Class<?> eventType) {
            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
            // Usually a subscriber doesn't have methods listening to the same event type.
            Object existing = anyMethodByEventType.put(eventType, method);
            if (existing == null) {
                return true;
            } else {
                if (existing instanceof Method) {
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                        // Paranoia check
                        throw new IllegalStateException();
                    }
                    // Put any non-Method object to "consume" the existing Method
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }

發(fā)現(xiàn)殿较,post方法沒有過濾StickyEvent斜脂,而postSticky是調(diào)用post方法的,所以,無論post還是postSticky蔬胯,StickyEvent是true或false,都會(huì)執(zhí)行

4.priority事件優(yōu)先級(jí)

    //priority越大舞竿,級(jí)別越高
    @Subscribe(priority = 1);
    public void onEvent(MessageEvent event) {
    …
    }
//優(yōu)先級(jí)實(shí)現(xiàn)方式,遍歷當(dāng)前列表执桌,把當(dāng)前
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

5.中止事件傳遞

    // 中止事件傳遞伴逸,后續(xù)事件不在調(diào)用,注意错蝴,只能在傳遞事件的時(shí)候調(diào)用
    @Subscribe
    public void onEvent(MessageEvent event){
        …
        EventBus.getDefault().cancelEventDelivery(event) ;
    }

6.index索引加速

EventBus使用了annotation,默認(rèn)在編譯時(shí)生成代碼,生成索引玛界,
添加index后會(huì)在編譯時(shí)運(yùn)行,自動(dòng)生成相應(yīng)代碼笨枯。
ps:由于apt的限制严嗜,匿名內(nèi)部類中的annotation不會(huì)被識(shí)別,會(huì)自動(dòng)降級(jí)在運(yùn)行時(shí)反射压彭,此時(shí)汗盘,效率會(huì)降低
EventBus官網(wǎng)地址

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
    arguments {
        eventBusIndex "com.example.myapp.MyEventBusIndex"
    }
}

由于kotlin的使用annotation處理方式不同尸执,需要使用kapt

apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied

dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

kapt {
    arguments {
        arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
    }
}

ps:如果是多個(gè)模塊都需要加索引,則每個(gè)模塊都要加上上面三個(gè)apt,包引入可以不需要重復(fù)

這樣脆丁,編譯后就會(huì)自動(dòng)生成代碼


/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(com.zhiyecn.demoeventbus.ScrollingActivity.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("eventBus", com.zhiyecn.demoeventbus.StickEvent.class),
            new SubscriberMethodInfo("eventBusStick", com.zhiyecn.demoeventbus.StickEvent.class, ThreadMode.BACKGROUND,
                    0, true),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

在編譯時(shí)通過反射生成event方法存在map中,在發(fā)送事件的時(shí)候,調(diào)用getSubscriberInfo獲取該map。

private SubscriberInfo getSubscriberInfo(FindState findState) {
    ...省略
    if (subscriberInfoIndexes != null) {
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
    }
    return null;
}

使用方式

//給自定義eventbus添加索引
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
//給默認(rèn)eventbus添加索引
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
//多個(gè)庫需要添加索引
EventBus eventBus = EventBus.builder()
    .addIndex(new MyEventBusAppIndex())
    .addIndex(new MyEventBusLibIndex()).build();

7.NoSubscriberEvent

如果沒找到訂閱者事件,可以通過EventBusBuilder設(shè)置是否默認(rèn)發(fā)送NoSubscriberEvent,默認(rèn)是打開的


/**
 * This Event is posted by EventBus when no subscriber is found for a posted event.
 * 
 * @author Markus
 */
public final class NoSubscriberEvent {
    /** The {@link EventBus} instance to with the original event was posted to. */
    public final EventBus eventBus;

    /** The original event that could not be delivered to any subscriber. */
    public final Object originalEvent;

    public NoSubscriberEvent(EventBus eventBus, Object originalEvent) {
        this.eventBus = eventBus;
        this.originalEvent = originalEvent;
    }

}
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        ....
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                Log.d(TAG, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

8.混淆

-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}

9.利弊

好處

簡(jiǎn)單,方便籍嘹,小巧辱士,文檔清晰颂碘,性能消耗少塔拳,可定制行強(qiáng)靠抑,耦合度低

壞處

耦合度太低

這絕對(duì)不是個(gè)笑話荠列,,EventBus的耦合太低了诉瓦,如果不加以控制管理,你會(huì)不知道猴贰,你發(fā)的消息到跑哪里去了瑟捣。也不知道你的這條消息迈套,會(huì)在哪里發(fā)出。如果你沒有很好的方法解決這個(gè)問題贵白,建議不好用太多禁荒。

使用建議

1勃痴、EventBus管理

EventBus運(yùn)行創(chuàng)建多個(gè),那么污它,明確事件的生命周期,根據(jù)不同生命周期使用不同的EventBus歇攻?

/**
 * 方法1
 * 用annotation配合使用工廠
 * EventBusFactory.getBus(EventBusFactory.START);
 * EventBusFactory.getBus();
 */
public class EventBusFactory {
    private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2);

    @IntDef({CREATE, START})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BusType {
    }

    public static final int CREATE = 0;
    public static final int START = 1;

    static {
        mBusSparseArray.put(CREATE, EventBus.builder().build());
        mBusSparseArray.put(START, EventBus.getDefault());
    }

    public static EventBus getBus() {
        return getBus(START);
    }

    public static EventBus getBus(@BusType int type) {
        return mBusSparseArray.get(type);
    }

}
/**
 * 方法2
 * 用枚舉工廠
 * EventBusFactory.START.getBus();
 */
public enum EventBusFactory {
    CREATE(0),
    START(1);

    private int mType;

    EventBusFactory(int type) {
        mType = type;
    }

    public EventBus getBus() {
        return mBusSparseArray.get(mType);
    }

    private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2);

    static {
        mBusSparseArray.put(CREATE.mType, EventBus.builder().build());
        mBusSparseArray.put(START.mType, EventBus.getDefault());
    }
}

2、以事件為對(duì)象

將數(shù)據(jù)封裝到一個(gè)事件類。所有事件放到一個(gè)包下。如果事件太多础废,同個(gè)模塊的事件可以考慮使用靜態(tài)內(nèi)部類帘瞭,或者再分包。

public class Event  {  
    public static class UserListEvent {  
        public List<User> users ;  
    }
    public static class ItemListEvent {  
        public List<Item> items;  
    }    
}  

注意媒殉,不是相同類型就一定要作為一個(gè)事件封裝柄错,具體需要考慮業(yè)務(wù)情景跟代碼情況,比如事件行為不同疫萤、事件生命周期不同扯饶,如果有必要尾序,寫封裝成兩個(gè)Event可能是更好的選擇。

public class Event  { 
    public static class UserListUpdateEventOnCreate {  
        public List<User> users;  
    } 
    public static class UserListUpdateEventOnStart {  
        public List<User> users ;  
    }
    public static class UserListRemoveEventOnStart {  
        public List<User> users;  
    } 
}  

參考文獻(xiàn)

  1. EventBus官網(wǎng)地址
  2. EventBus github地址
  3. EventBus 3.0的用法詳解

說了這么多廢話,揭保,下面進(jìn)入正題

福利
福利
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子励负,更是在濱河造成了極大的恐慌,老刑警劉巖略吨,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異秽之,居然都是意外死亡跨细,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乐尊,“玉大人,你說我怎么就攤上這事对人∫黾福” “怎么了蛇捌?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵回溺,是天一觀的道長(zhǎng)遗遵。 經(jīng)常有香客問我车要,道長(zhǎng)类垫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谴仙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掀虎。我一直安慰自己驰怎,他們只是感情好县忌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般走芋。 火紅的嫁衣襯著肌膚如雪翁逞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音必怜,去河邊找鬼梳庆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛更米,可吹牛的內(nèi)容都是我干的迟几。 我是一名探鬼主播瘤旨,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼七婴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼打厘!你這毒婦竟也來了户盯?” 一聲冷哼從身側(cè)響起吗伤,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎族奢,沒想到半個(gè)月后越走,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廊敌,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薪缆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了减拭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旺罢,到底是詐尸還是另有隱情正卧,我是刑警寧澤穗酥,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布骏啰,位于F島的核電站,受9級(jí)特大地震影響壁熄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莹桅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓮孙,春花似錦栗涂、人聲如沸斤程。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽望薄。三九已至痕支,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間花嘶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喘沿,地道東北人蚜印。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像忆绰,于是被迫代替她去往敵國(guó)和親错敢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稚茅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理欺税,服務(wù)發(fā)現(xiàn)晚凿,斷路器晃虫,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 項(xiàng)目到了一定階段會(huì)出現(xiàn)一種甜蜜的負(fù)擔(dān):業(yè)務(wù)的不斷發(fā)展與人員的流動(dòng)性越來越大扛吞,代碼維護(hù)與測(cè)試回歸流程越來越繁瑣滥比。這個(gè)...
    fdacc6a1e764閱讀 3,181評(píng)論 0 6
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評(píng)論 25 707
  • 地球在無聲地旋轉(zhuǎn)酒奶,日照逐漸變長(zhǎng)惋嚎,氣溫只上升了幾度另伍,就帶來了天翻地覆的變化,歐洲大陸淹沒在綠色的海洋中摆尝,森林的黃金季...
    安言片語閱讀 1,056評(píng)論 0 50
  • 你是否也經(jīng)常遇到一類人,很難看到他們有笑容綻放在臉上,遇到任何事都憂心忡忡,思來憂去苹支,愛抱怨多過愛生活债蜜。 自己不快...
    柯錦川閱讀 3,287評(píng)論 22 154