Dagger2入門有這篇就夠了(入門篇)

網上的Dagger2各種多,但看完了,只能讓原本懵逼的更懵逼,我只能說大神的世界我不懂,那么Dagger2真那么難嗎?給我耐心,我給你答案?。。?/h5>

1.定義:沒耐心了解的,也可以下一步。

Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier versioncreated by Square and now maintained by Google. Dagger aims to address many of the development and performance issues that have plagued reflection-based solutions. More details can be found in this talk(slides) by +Gregory Kick.

什么意思???高屋建瓴,一針見血,力透紙背,入木三分,高端大氣上檔次,

  • Dagger是為Android和Java平臺提供的一個完全靜態的,在編譯時進行依賴注入的框架,Dagger解決了基于反射帶來的開發和性能上的問題(因為Dagger并沒有用反射來做依賴注入)就是說它幫我們自動生成注入需要的代碼,跟純手寫沒區別,只是寫這些機械的代碼的過程Dagger2 代勞了。

2. 添加依賴

  • Project級別bulid.gradle 添加classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8',如下
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

  • Module 級別 build.gradle 頂上位置加apply plugin: 'android-apt',dependencies 里加
    provided 'javax.annotation:javax.annotation-api:1.2'
    compile 'com.google.dagger:dagger:2.5'
    apt 'com.google.dagger:dagger-compiler:2.5'
    完成代碼如下:

    apply plugin: 'com.android.application'
    

apply plugin: 'android-apt'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "com.zhang.testing.daggertest"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.0'
testCompile 'junit:junit:4.12'
//dagger2
provided 'javax.annotation:javax.annotation-api:1.2'
compile 'com.google.dagger:dagger:2.5'
apt 'com.google.dagger:dagger-compiler:2.5'
}

 到這,我們就能在項目中使用Dagger2了
 #### 3. 最簡單的dagger2使用
 這里是基于最簡單的MVP模式的dagger2最簡單使用。我們先看代碼,這樣效率高,容易理解。不信,follow me !!!
 
 1. 新建一個類Prensenter(它的實例就是商品),,這個是要注入到MainActivity(MainActivity就是采購商)中的,不懂沒事,接著往下看,**這里注意@Inject這個注解(相當于訂單,Dagger2老司機就認這個)**
 ```
 public class Presenter {
 private MainActivity mMainActivity;

 @Inject
 public Presenter(MainActivity mainActivity) {
     this.mMainActivity = mainActivity;
 }

 //下載數據的方法,這里只是模擬
 public void loadData() {
     System.out.println("下載中");
 }
}
  1. 再新建個類MainActivityModule(這里比喻為一個倉庫,用@Module表示)Module中有用@Provides注解的方法,就相當于倉庫中的貨架,可以對外提供商品。下面代碼中的方法provideMainActivity()方法的返回是MainActivity,那么它提供的商品就是MainActivity的實例
/**
 * Created by zyg on 2016/11/8.
 */
@(個人博客)Module
public class MainActivityModule {
    private MainActivity mMainActivity;

    public MainActivityModule(MainActivity activity) {

        this.mMainActivity = activity;
    }
    @Provides
    public MainActivity provideMainActivity(){
        return mMainActivity;
    }

}
  1. 再 新建接口 MainComponent,這個接口被@Component注解,表示它是個Component(供貨商,交易商),在注解的后面的括號里,指明了modules= MainActivityModule.class,將Component 和Module 關聯起來(就是說MainActivityModule這個倉庫,是MainComponent這個供貨商的倉庫)
/**
 * Created by zyg on 2016/11/8.
 */
@Component(modules = MainActivityModule.class)
public interface MainComponent {
    void inject(MainActivity mainActivity);
}
  1. 再看MainActivity方法,我們的目的就是將 Prensenter的實例注入到MainActivity中, 這里我們申明了Presenter mPresenter,并且在它的頭上也有個@Inject標注(告訴老司機Dagger2這是我缺的貨,快去給我從供貨商那給我拉回來),然后rebuild工程,注入所需要的代碼就會自動生成。然后我們再調用下面這些方法,完成最后的注入

DaggerMainComponent.builder()
.mainActivityModule(new MainActivityModule(this))
.build()
.inject(this);**

package com.zhang.testing.simplemvpdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.zhang.testing.simplemvpdemo.di.component.DaggerMainComponent;
import com.zhang.testing.simplemvpdemo.di.module.MainActivityModule;
import com.zhang.testing.simplemvpdemo.presenter.Presenter;

import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "daggerTest";
    @Inject
    Presenter mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.builder()
                .mainActivityModule(new MainActivityModule(this))
                .build()
                .inject(this);

    }

    //更新UI的操作
    public void refeshUi() {
        Log.d(TAG, "更新ui");

    }
}

分析總結:到這里一個最簡單的dagger2注入過程就完成了,我們把Presenter 的實例注入到MainActivity中,這時候我們就能調用Prensent 中的方法了。因為是Mvp模式,所以Prensenter類也需要拿到了MainActivity的實例,(至于怎么拿到的,少年莫急,一步一步慢慢來?。┱{用Prensent中的方法完成下載后,就可以調用MainActivity中的方法完成UI的更新。

  • 下面我們分析下Dagger2老司機怎么把MainActivity這個采購商從供貨商哪里采購到商品的
    • 在MainActivity 中的@Inject注解告訴Dagger2這個貨車老司機需要采購什么了,我缺什么貨了,我缺Presenter類的實例(呵呵,Dagger2這個老司機只認識@Inject注解的訂單)
    • 然后Dagger2就會去和MainActivity相關的供貨商Component去要,Component就會去和它關聯的Module倉庫中去找,但是打開倉庫也是有條件的(管理比較嚴格),就是得有MainActivity的身份證(就是MainAcitivty的實例,,你理解成錢也好,身份證也罷,反正就是個憑證),Component在貨架(@Provides注解的方法)中找了半天也沒有,只發現提供MainAcivity的實例的貨架。
    • 于是Component 只能去找Presenter本人碰碰運氣,運氣還不錯。Presenter中有@Inject注解的構造方法,這里可以提供。但是Presenter說你得給我提供一個 MainActivity的身份證(還是 MainActivity的實例),我才能給你new 一個Prensenter實例,巧了,剛在Module倉庫的不是有提供 MainActivity的實例的貨架?把MainActivity實例給了Presenter,Presenter就new了個實例給了Component,同時Prensenter也拿到了 Activity的實例。
    • Component屁顛屁顛的跑回來和Dagger2老司機說,拿到Presenter實例了,但是你得提供MainActivity的身份證(MainActivity的實例)咱們簽訂合同,你就把Presenter的實例拉走,調用inject方法簽訂合同,Dagger2老司機就把貨給MainActivity拉回來了?。ɡ纤緳C就是牛!?。。?/li>
    • 說到這里,整個注入過程就完成了。具體怎么注入,一會我們從生成的源代碼分析,這里先嘗試著理解。為了生動易于理解,以上的分析不代表實際執行過程,但你這么理解是沒有問題的。說到這里代碼中各個注解的意思,應該已經理解了,不過這里還是提一下
      • @Module:表明這個類是個Module類(Module倉庫),會跟Component關聯,這個類中可能有些提供方法可以向外提供一些類的實例
      • @Provides:表明該方法是個提供方法,這個方法就是在Module中向外提供實例的方法(貨架,提供的實例就是商品)
      • @Component:表明這個接口是個Component(供貨商),是連接Module(倉庫) 和被注入類(商品),目標類(采購商)的橋梁,需要注入到哪個類,在inject方法中,目標類的實例作為參數傳進來,這里注意下,我們這里的目標類是MainActivity,所以參數就是MainActivity的實例,但是我們不能用MainAcvity的父類接收,那樣會報錯的(MainActivity父親的來都不行)。

4. Dagger2避免了反射對性能造成的影像,通過自動生成代碼完成,注入。現在我們從它生成代碼的角度去看看它到底是怎么完成注入的,自動生成的代碼在\DaggerTest\app\build\generated\source\apt\debug\下面。下面看代碼吧!!

  • 我們以MainActivity中的調用順序來看,代碼的走向 **DaggerMainComponent.builder() **我們看這句代碼做了些什么事
 public static Builder builder() {
    return new Builder();
  }

這是個靜態方法,new 了一個 Builder的實例返回,Builder為其內部類,構造方法中,什么也沒有做。接著看MainActivity中下一步調用什么。

  • 然后就是調用Bulider類的mainActivityModule(new MainActivityModule(this))方法,并且傳入一個MainActivityModule的實例作為參數。而new一個MainActivityModule的實例需要傳入MainActivity的實例,下面看這個方法做了什么.
public static final class Builder {
    private MainActivityModule mainActivityModule;

private Builder() {}

public MainComponent build() {
  if (mainActivityModule == null) {
    throw new IllegalStateException(
        MainActivityModule.class.getCanonicalName() + " must be set");
  }
  return new DaggerMainComponent(this);
}

public Builder mainActivityModule(MainActivityModule mainActivityModule) {
  this.mainActivityModule = Preconditions.checkNotNull(mainActivityModule);
  return this;
}
}

這個方法,把傳進來的MainActivityModule的實例被Builder這個類裝進自己兜里了是不是?然后又返回了自己。

  • 拿到返回的builder實例,MainActivity中繼續調用Builder的build()方法,這個方法首先判斷了下全局變量mainActivityModule,是否為空,為空就拋出異常。這個Builder,還真是貪婪,非要確認MainActiivtyModule的實例是不是已經裝兜里了,然后就new個了DaggerMainComponent的實例返回,并把自己作為參數傳了進去,注意:這時候的Buidler可不簡單,是個小土豪,兜里裝著MainActivityModule的實例,而MainAcvitivityModule的實例又有揣著 MainActivity的實例。

  • 然后我們看下 DaggerMainComponent的new方法里都做了些什么,

public final class DaggerMainComponent implements MainComponent {
    private Provider<MainActivity> provideMainActivityProvider;

  private Provider<Presenter> presenterProvider;

  private MembersInjector<MainActivity> mainActivityMembersInjector;

  private DaggerMainComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.provideMainActivityProvider =
        MainActivityModule_ProvideMainActivityFactory.create(builder.mainActivityModule);

    this.presenterProvider = Presenter_Factory.create(provideMainActivityProvider);

    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(presenterProvider);
  }

  @Override
  public void inject(MainActivity mainActivity) {
    mainActivityMembersInjector.injectMembers(mainActivity);
  }

  public static final class Builder {
    private MainActivityModule mainActivityModule;

    private Builder() {}

    public MainComponent build() {
      if (mainActivityModule == null) {
        throw new IllegalStateException(
            MainActivityModule.class.getCanonicalName() + " must be set");
      }
      return new DaggerMainComponent(this);
    }

    public Builder mainActivityModule(MainActivityModule mainActivityModule) {
      this.mainActivityModule = Preconditions.checkNotNull(mainActivityModule);
      return this;
    }
  }
}       
  • 在DaggerMainComponent 的new方法里,它調用了initialize(builder),方法,并把Bulider實例傳進去了,接著看initialize做了什么,
  • 第一步它調用** MainActivityModule_ProvideMainActivityFactory.create(builder.mainActivityModule);**,這個類名很長,我們 就叫MF(Module工廠類),調用了MF 的creat方法,并把builder中的mainActivityModule傳了進去作為參數(這下Bulider的財富被轉移了,哈哈)MF的creat方法,new了一個MF返回,然后把mainActivityModule裝自己兜里了,都貪財??!然后DaggerMainComponent 用一個類接收了MF的實例給接收了,至于什么類,我們不用去關心,總之肯定是父類超類什么能接受的。下面是MF類的代碼
 public final class MainActivityModule_ProvideMainActivityFactory implements Factory<MainActivity> {
  private final MainActivityModule module;

  public MainActivityModule_ProvideMainActivityFactory(MainActivityModule module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public MainActivity get() {
    return Preconditions.checkNotNull(
        module.provideMainActivity(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<MainActivity> create(MainActivityModule module) {
    return new MainActivityModule_ProvideMainActivityFactory(module);
  }
}
  • 第二步Presenter_Factory.create(provideMainActivityProvider);方法,Presenter_Factory.我們把這個類簡稱為PF(Presenter工廠類),調用PF的creat方法,并把MF的實例傳進去。這時候的MF也是個富豪哦。 PF的creat方法,也沒什么新意,依然是new一個PF實例返回,然后把MF實例裝包包里,大魚吃小魚啊!DaggerMainComponent,又把PF實例收了。下面是PF的代碼
public final class Presenter_Factory implements Factory<Presenter> {
  private final Provider<MainActivity> mainActivityProvider;

  public Presenter_Factory(Provider<MainActivity> mainActivityProvider) {
    assert mainActivityProvider != null;
    this.mainActivityProvider = mainActivityProvider;
  }

  @Override
  public Presenter get() {
    return new Presenter(mainActivityProvider.get());
  }

  public static Factory<Presenter> create(Provider<MainActivity> mainActivityProvider) {
    return new Presenter_Factory(mainActivityProvider);
  }
}
  • 第三步 this.mainActivityMembersInjector = MainActivity_MembersInjector.create(presenterProvider); MainActivity_MembersInjector這個類我們簡稱為MI吧!這里依然是老套路,MI的creat方法,依然是返回MI的實例,然后把PF的實例裝自己的兜里。DaggerMainComponent 依然是把MI的實例接收了。到目前為MI很富裕,它里面有PF的實例,而PF里有MF的實例,MF里有MainActivityModule的實例,而MainActivityModule中又有MainActivity的實例,大魚吃小魚下雨吃蝦米的關系啊。下面是MI的代碼
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final Provider<Presenter> mPresenterProvider;

  public MainActivity_MembersInjector(Provider<Presenter> mPresenterProvider) {
    assert mPresenterProvider != null;
    this.mPresenterProvider = mPresenterProvider;
  }

  public static MembersInjector<MainActivity> create(Provider<Presenter> mPresenterProvider) {
    return new MainActivity_MembersInjector(mPresenterProvider);
  }

  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.mPresenter = mPresenterProvider.get();
  }

  public static void injectMPresenter(
      MainActivity instance, Provider<Presenter> mPresenterProvider) {
    instance.mPresenter = mPresenterProvider.get();
  }
} 
  • 這時候我們已經拿到了DaggerMainComponent 的實例,再看下MainActivity中,又調用了DaggerMainComponent 的inject,方法,傳入參數MainActivity的實例。這個方法又調用MI的injectMembers方法并把MainActivity的實例作為參數傳進去了
  • 在看Mi的injectMembers方法,這是個抽象方法,具體實現在MI里,那么我們到Mi里看唄。
  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.mPresenter = mPresenterProvider.get();
  }

MI的injectMembers,方法把 MainActvity中的mPresenter 取出來了,mPresenter 是誰??,我們需要注入到MainActivity中的Presenter類的引用,只要它被實例化了,咱們工作基本就完成了,那么再看看等號的右邊,mPresenterProvider.get(),mPresenterProvider何許人也?,他就是MI creat方法的時候傳進來的PF實例,被MI包裝了下,搞的差點不認識。(嘿嘿,整容了)。調用它的get方法,依然是個抽象方法,實現就在PF里,(僅僅是換個行頭,所以該PF做的事還是PF做啊)

  • 在看PF的get方法都干了些什么,
@Override
  public Presenter get() {
    return new Presenter(mainActivityProvider.get());
  }

這里PF new了一個Presenter的實例并返回了,到這里,等號的左邊(不要糾結于概念,你非要說在這里它不叫等號,那么我也只能無語)是Presenter的引用,右邊是實例,mPresenter實例化完成,即注入完成。這里我們也看到,new Presenter的時候需要傳入MainActivity實例作為參數,這里通過mainActivityProvider的get方法獲取,mainActivityProvider是誰?不用多說了吧!就是經過整容的MF,可要認的出來才行。

  • get方法,依然是抽象方法,具體實現在MF里。我們在看看MF的get方法
  @Override
  public MainActivity get() {
    return Preconditions.checkNotNull(
        module.provideMainActivity(), "Cannot return null from a non-@Nullable @Provides method");
  }

調用了module的provideMainActivity方法,這 module是誰,不用再說了吧,就是MainActivityMoule的實例,它的provideMainActivity,方法返回什么,還是我們自己寫的呢。就是返回MainActivity的實例。通過一系列的get方法,拿到MainActivity的實例,然后傳到Presenter的new方法,這樣Presenter就拿到了MainActivity的實例。到這里,所有的注入才算是真正完成。我們的最佳MVP也算是正在完成。

如果到這里你還不明白,不怕我們還有一招,看圖
image

這里我就不做過多解釋了,根據上面文字描述繪制的圖譜,從new MainActivityModule開始,MainActivity的實例被包裝到 Module里,然后整個Module 的實例又被包裝到 Builder里,然后把Modle取出,再次包裝到 MF里(Modle的工廠類)然后再 在包裝到PF里,最后又把PF包裝到MI里,然后再調用一系列的get方法取出this,完成注入。

總結:好累啊,Dagger2最簡單的使用和流程,從老司機寓言故事法,到源代碼分析法,到流程圖譜,用了三種方法來說明Dagger2的代碼調用流程,旨在用簡單的辦法,把原理說清楚。那么你明白了嗎?自己去敲代碼驗證一下吧,源碼請看這里,simpleMvpDemo的那個Module哦!這里只是最簡單的使用,下一篇進階篇。

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

推薦閱讀更多精彩內容

  • 寫在前面:我目前就職于阿里巴巴-菜鳥,團隊目前缺人,招聘java和客戶端開發,招聘對象為:社招和19屆畢業的校招生...
    littleKang閱讀 115,418評論 93 745
  • 一、首先你要知道什么是依賴? 想要理解Dagger2,首先你要理解一個概念,就是什么是依賴,懂的同學可以省過此段。...
    為夢想戰斗閱讀 456評論 0 0
  • 現在Dagger2在項目里用的越來越多了,最近花了些時間學習了一下Dagger2,這篇文章主要幫助理解Dagger...
    axiaochao閱讀 616評論 0 0
  • ****(說在最前:閱讀本篇之前,希望大家對Dagger2已經有了一個初步的了解。從而幫助感覺似是而非的同學進一步...
    我是昵稱閱讀 926評論 3 6
  • 親愛的誠誠 首先要恭喜你幼兒園順利畢業。今天是你在大班學習的最后一天,你們幼兒園老師別出心裁地策劃設計一場畢業儀...
    楚人不服周閱讀 1,332評論 2 3